Skip to content

Instantly share code, notes, and snippets.

@maricn
Created March 27, 2014 13:46
Show Gist options
  • Save maricn/9807908 to your computer and use it in GitHub Desktop.
Save maricn/9807908 to your computer and use it in GitHub Desktop.
JavaScript Feedback form (similar to github.com/niklasvh/feedback.js) with back button to get one step back. [feedback.js is used as an example in index.html as if it is in 'js' folder with jquery and feedback.css in 'css' folder with bootstrap]
/*!
* Bootstrap v2.0.4
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/
.feedback-review label {
font-size: 14px;
}
.feedback-body .feedback-error {
color: #b94a48;
border-color: #b94a48;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.feedback-body .feedback-error:focus {
border-color: #953b39;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
}
.feedback-body p a {
color: #0088cc;
text-decoration: none;
}
.feedback-body p a:hover {
color: #005580;
text-decoration: underline;
}
.feedback-modal h3 {
line-height: 27px;
font-size: 18px;
margin: 0;
font-weight: bold;
color: #333;
text-rendering: optimizelegibility;
}
.feedback-modal {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
line-height: 18px;
color: #333;
position: fixed;
top: 50%;
left: 50%;
z-index: 2050;
max-height: 500px;
overflow: auto;
width: 560px;
margin: -250px 0 0 -280px;
background-color: #ffffff;
border: 1px solid #999;
border: 1px solid rgba(0, 0, 0, 0.3);
*border: 1px solid #999;
/* IE6-7 */
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
-webkit-background-clip: padding-box;
-moz-background-clip: padding-box;
background-clip: padding-box;
-webkit-transition: all 2s ease 0s;
-moz-transition: all 2s ease 0s;
-ms-transition: all 2s ease 0s;
-o-transition: all 2s ease 0s;
}
.feedback-modal .feedback-close {
float: right;
font-size: 20px;
font-weight: bold;
line-height: 18px;
color: #000000;
text-shadow: 0 1px 0 #ffffff;
opacity: 0.2;
filter: alpha(opacity=20);
}
.feedback-modal .feedback-close:hover {
color: #000000;
text-decoration: none;
opacity: 0.4;
filter: alpha(opacity=40);
cursor: pointer;
}
.feedback-btn {
display: inline-block;
padding: 4px 10px 4px;
margin-bottom: 0;
font-size: 13px;
line-height: 18px;
color: #333333;
text-align: center;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
vertical-align: middle;
background-color: #f5f5f5;
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
background-image: linear-gradient(top, #ffffff, #e6e6e6);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
border-color: #e6e6e6 #e6e6e6 #bfbfbf;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
border: 1px solid #ccc;
border-bottom-color: #bbb;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
cursor: pointer;
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
*margin-left: .3em;
}
.feedback-btn:hover,
.feedback-btn:active,
.feedback-btn.active,
.feedback-btn.disabled,
.feedback-btn[disabled] {
background-color: #e6e6e6;
}
.feedback-btn:active, .feedback-btn.active {
background-color: #cccccc \9;
}
.feedback-btn:first-child {
*margin-left: 0;
}
.feedback-btn:hover {
color: #333333;
text-decoration: none;
background-color: #e6e6e6;
background-position: 0 -15px;
-webkit-transition: background-position 0.1s linear;
-moz-transition: background-position 0.1s linear;
-ms-transition: background-position 0.1s linear;
-o-transition: background-position 0.1s linear;
transition: background-position 0.1s linear;
}
.feedback-btn:focus {
outline: thin dotted #333;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
.feedback-btn.active, .feedback-btn:active {
background-image: none;
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
background-color: #e6e6e6;
background-color: #d9d9d9 \9;
outline: 0;
}
.feedback-btn.disabled, .feedback-btn[disabled] {
cursor: default;
background-image: none;
background-color: #e6e6e6;
opacity: 0.65;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.feedback-btn-small {
padding: 5px 9px;
font-size: 11px;
line-height: 16px;
}
.feedback-btn-small [class^="icon-"] {
margin-top: -1px;
}
.feedback-btn-inverse,
.feedback-btn-inverse:hover {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
color: #ffffff;
}
.feedback-btn-inverse {
background-color: #393939;
background-image: -moz-linear-gradient(top, #454545, #262626);
background-image: -ms-linear-gradient(top, #454545, #262626);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#454545), to(#262626));
background-image: -webkit-linear-gradient(top, #454545, #262626);
background-image: -o-linear-gradient(top, #454545, #262626);
background-image: linear-gradient(top, #454545, #262626);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#454545', endColorstr='#262626', GradientType=0);
border-color: #262626 #262626 #000000;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
}
.feedback-btn-inverse:hover,
.feedback-btn-inverse:active,
.feedback-btn-inverse.active,
.feedback-btn-inverse.disabled,
.feedback-btn-inverse[disabled] {
background-color: #262626;
}
.feedback-btn-inverse:active, .feedback-btn-inverse.active {
background-color: #0c0c0c \9;
}
button.feedback-btn, input[type="submit"].feedback-btn {
*padding-top: 2px;
*padding-bottom: 2px;
}
button.feedback-btn::-moz-focus-inner, input[type="submit"].feedback-btn::-moz-focus-inner {
padding: 0;
border: 0;
}
button.feedback-btn.large, input[type="submit"].feedback-btn.large {
*padding-top: 7px;
*padding-bottom: 7px;
}
button.feedback-btn.small, input[type="submit"].feedback-btn.small {
*padding-top: 3px;
*padding-bottom: 3px;
}
.feedback-btn .caret {
margin-top: 7px;
margin-left: 0;
}
.feedback-btn:hover .caret, .open.btn-group .caret {
opacity: 1;
filter: alpha(opacity=100);
}
.feedback-btn-inverse .caret {
border-top-color: #ffffff;
opacity: 0.75;
filter: alpha(opacity=75);
}
.feedback-btn-small .caret {
margin-top: 4px;
}
.feedback-bottom-right {
position:fixed;
bottom:5px;
right:5px;
}
.feedback-header {
padding: 9px 15px;
border-bottom: 1px solid #eee;
}
.feedback-header .feedback-close {
margin-top: 2px;
}
.feedback-body {
padding: 15px;
}
.feedback-body .modal-form {
margin-bottom: 0;
}
.feedback-body textarea {
margin: 0;
font-size: 100%;
vertical-align: middle;
overflow: auto;
vertical-align: top;
font-size: 13px;
font-weight: normal;
line-height: 18px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
display: inline-block;
width: 520px;
height: 100px;
padding: 4px;
margin-bottom: 9px;
font-size: 13px;
line-height: 18px;
color: #555555;
border: 1px solid #ccc;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
-ms-transition: border linear 0.2s, box-shadow linear 0.2s;
-o-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
}
.feedback-body textarea:focus {
border-color: rgba(82, 168, 236, 0.8);
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
outline: 0;
outline: thin dotted \9;
/* IE6-9 */
}
.feedback-footer {
padding: 5px 14px 15px 15px;
line-height:20px;
height:20px;
margin-bottom: 0;
background-color: #f5f5f5;
border-top: 1px solid #ddd;
-webkit-border-radius: 0 0 6px 6px;
-moz-border-radius: 0 0 6px 6px;
border-radius: 0 0 6px 6px;
-webkit-box-shadow: inset 0 1px 0 #ffffff;
-moz-box-shadow: inset 0 1px 0 #ffffff;
box-shadow: inset 0 1px 0 #ffffff;
*zoom: 1;
}
.feedback-footer:before, .feedback-footer:after {
display: table;
content: "";
}
.feedback-footer:after {
clear: both;
}
.feedback-footer .feedback-btn {
float: right;
margin-left: 5px;
margin-bottom: 0;
}
.feedback-footer .feedback-btn-prev {
float: left;
margin-left: 0;
margin-bottom: 0;
}
.feedback-glass {
position:fixed;
z-index:2000;
top:0;
left:0;
width: 100%;
height:100%;
background: #777;
opacity: 0.5;
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
filter: alpha(opacity=50);
}
.feedback-canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
z-index:1999;
}
#feedback-blackout-element {
background: #000;
opacity: 0.8;
position:absolute;
}
.feedback-highlight-element, .feedback-highlighted {
position:absolute;
-webkit-border-radius: 4px;
border-radius: 4px;
z-index: 2000;
-moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
-webkit-box-shadow: 0px 0px 8px 0px #000;
box-shadow: 0px 0px 8px 0px #000;
}
.feedback-highlight-element {
pointer-events:none;
}
#feedback-highlight-close {
background: #ccc;
border:1px solid black;
text-align:center;
line-height:20px;
font-size:20px;
font-weight:bold;
width:20px;
height:20px;
position:absolute;
cursor:pointer;
margin-left:-12px;
margin-top:-12px;
z-index:2001;
-webkit-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 0px 0px 4px 0px #615e5e;
box-shadow: 0px 0px 4px 0px #615e5e;
background-color: #c5c5c5;
background-image: -webkit-gradient(linear, left top, left bottom, from(#c5c5c5), to(#999999));
background-image: -webkit-linear-gradient(top, #c5c5c5, #999999);
background-image: -moz-linear-gradient(top, #c5c5c5, #999999);
background-image: -o-linear-gradient(top, #c5c5c5, #999999);
background-image: linear-gradient(to bottom, #c5c5c5, #999999);
}
.feedback-blackedout {
background: #000;
position:absolute;
z-index: 2000;
}
#feedback-highlight-container {
position:absolute;
top:0;
left:0;
pointer-events:none;
z-index:2010;
}
.feedback-animate-toside {
top: 100%;
left:100%;
width: 300px;
margin-left:-320px;
margin-top: -200px;
}
.feedback-animate-review {
-webkit-transition: all 1s ease 0s;
-moz-transition: all 1s ease 0s;
-ms-transition: all 1s ease 0s;
-o-transition: all 1s ease 0s;
top: 50%;
left:50%;
width: 600px;
margin-left:-300px;
margin-top: -200px;
}
.feedback-canvas-complete {
-webkit-filter:blur(5px);
}
.feedback-body canvas {
-webkit-border-radius: 4px;
border-radius: 4px;
border:1px solid #636363;
-webkit-box-shadow: 0px 0px 12px 0px #615e5e;
box-shadow: 0px 0px 12px 0px #615e5e;
position:absolute;
}
.feedback-browser {
margin-left:330px;
}
.feedback-loader {
text-align: center;
}
.feedback-loader span {
display: inline-block;
vertical-align: middle;
width: 10px;
height: 10px;
margin: 50px auto;
background: black;
border-radius: 50px;
-webkit-animation: feedback-loader 0.6s infinite alternate;
-moz-animation: feedback-loader 0.6s infinite alternate;
}
.feedback-loader span:nth-of-type(2) {
-webkit-animation-delay: 0.2s;
-moz-animation-delay: 0.2s;
}
.feedback-loader span:nth-of-type(3) {
-webkit-animation-delay: 0.5s;
-moz-animation-delay: 0.5s;
}
@-webkit-keyframes feedback-loader {
0% {
width: 10px;
height: 10px;
opacity: 0.9;
-webkit-transform: translateY(0);
}
100% {
width: 24px;
height: 24px;
opacity: 0.1;
-webkit-transform: translateY(-21px);
}
}
@-moz-keyframes feedback-loader {
0% {
width: 10px;
height: 10px;
opacity: 0.9;
-moz-transform: translateY(0);
}
100% {
width: 24px;
height: 24px;
opacity: 0.1;
-moz-transform: translateY(-21px);
}
}
/*
feedback.js <http://experiments.hertzen.com/jsfeedback/>
Copyright (c) 2012 Niklas von Hertzen. All rights reserved.
http://www.twitter.com/niklasvh
Released under MIT License
*/
(function( window, document, undefined ) {
if ( window.Feedback !== undefined ) {
return;
}
// log proxy function
var log = function( msg ) {
window.console.log( msg );
},
// function to remove elements, input as arrays
removeElements = function( remove ) {
for (var i = 0, len = remove.length; i < len; i++ ) {
var item = Array.prototype.pop.call( remove );
if ( item !== undefined ) {
if (item.parentNode !== null ) { // check that the item was actually added to DOM
item.parentNode.removeChild( item );
}
}
}
},
loader = function() {
var div = document.createElement("div"), i = 3;
div.className = "feedback-loader";
while (i--) { div.appendChild( document.createElement( "span" )); }
return div;
},
getBounds = function( el ) {
return el.getBoundingClientRect();
},
emptyElements = function( el ) {
var item;
while( (( item = el.firstChild ) !== null ? el.removeChild( item ) : false) ) {}
},
element = function( name, text ) {
var el = document.createElement( name );
el.appendChild( document.createTextNode( text ) );
return el;
},
// script onload function to provide support for IE as well
scriptLoader = function( script, func ){
if (script.onload === undefined) {
// IE lack of support for script onload
if( script.onreadystatechange !== undefined ) {
var intervalFunc = function() {
if (script.readyState !== "loaded" && script.readyState !== "complete") {
window.setTimeout( intervalFunc, 250 );
} else {
// it is loaded
func();
}
};
window.setTimeout( intervalFunc, 250 );
} else {
log("ERROR: We can't track when script is loaded");
}
} else {
return func;
}
},
prevButton,
nextButton,
H2C_IGNORE = "data-html2canvas-ignore",
currentPage,
modalBody = document.createElement("div"),
myOptions;
window.Feedback = function( options ) {
options = options || {};
// default properties
options.label = options.label || "Send Feedback";
options.header = options.header || "Send Feedback";
options.url = options.url || "/";
options.adapter = options.adapter || new window.Feedback.XHR( options.url );
options.prevLabel = options.prevLabel || "Return";
options.nextLabel = options.nextLabel || "Continue";
options.reviewLabel = options.reviewLabel || "Review";
options.sendLabel = options.sendLabel || "Send";
options.closeLabel = options.closeLabel || "Close";
options.reviewFormHeader = options.reviewFormHeader || "Issue";
options.reviewScreenshotHeader = options.reviewScreenshotHeader || "Screenshot";
options.messageSuccess = options.messageSuccess || "Your feedback was sent successfully.";
options.messageError = options.messageError || "There was an error sending your feedback to the server.";
options.user = options.user || { chid: '', accountId: '', email: ''};
myOptions = options;
if (options.pages === undefined ) {
options.pages = [
new window.Feedback.Form(),
new window.Feedback.Screenshot( options ),
new window.Feedback.Review()
];
}
var button,
modal,
currentPage,
glass = document.createElement("div"),
fromPrevious = false,
returnMethods = {
// open send feedback modal window
open: function() {
var len = options.pages.length;
currentPage = 0;
for (; currentPage < len; currentPage++) {
// create DOM for each page in the wizard
if ( !(options.pages[ currentPage ] instanceof window.Feedback.Review) ) {
options.pages[ currentPage ].render();
}
}
var a = element("a", "×"),
modalHeader = document.createElement("div"),
// modal container
modalFooter = document.createElement("div");
modal = document.createElement("div");
document.body.appendChild( glass );
// modal close button
a.className = "feedback-close";
a.onclick = returnMethods.close;
a.href = "#";
button.disabled = true;
// build header element
modalHeader.appendChild( a );
modalHeader.appendChild( element("h3", options.header ) );
modalHeader.className = "feedback-header";
modalBody.className = "feedback-body";
emptyElements( modalBody );
currentPage = 0;
modalBody.appendChild( options.pages[ currentPage++ ].dom );
// Next button
nextButton = element( "button", options.nextLabel );
nextButton.className = "feedback-btn";
nextButton.onclick = function() {
if (currentPage > 0 ) {
if ( options.pages[ currentPage - 1 ].end( modal ) === false ) {
// page failed validation, cancel onclick
return;
}
if (currentPage == 1) {
modalFooter.appendChild( prevButton );
}
}
emptyElements( modalBody );
if ( currentPage === len ) {
returnMethods.send( options.adapter );
} else {
options.pages[ currentPage ].start( modal, modalHeader, modalFooter, nextButton );
if ( options.pages[ currentPage ] instanceof window.Feedback.Review ) {
// create DOM for review page, based on collected data
options.pages[ currentPage ].render( options.pages );
}
// add page DOM to modal
modalBody.appendChild( options.pages[ currentPage++ ].dom );
// if last page, change button label to send
if ( currentPage === len ) {
nextButton.firstChild.nodeValue = options.sendLabel;
}
// if next page is review page, change button label
if ( options.pages[ currentPage ] instanceof window.Feedback.Review ) {
nextButton.firstChild.nodeValue = options.reviewLabel;
}
}
};
prevButton = element( "button", options.prevLabel );
prevButton.className = "feedback-btn feedback-btn-prev";
prevButton.onclick = function() {
window.Feedback.fromPrevious = true;
emptyElements( modalBody );
if ( currentPage-- === 2) {
prevButton.parentNode.removeChild( prevButton );
}
options.pages [currentPage].end( modal );
//currentPage--;
options.pages[ currentPage - 1 ].start( modal, modalHeader, modalFooter, nextButton );
if ( options.pages[ currentPage ] instanceof window.Feedback.Review ) {
// create DOM for review page, based on collected data
options.pages[ currentPage ].render( options.pages );
}
if ( options.pages[ currentPage ] instanceof window.Feedback.Screenshot ) {
this.h2cDone = true;
}
currentPage--;
// add page DOM to modal
modalBody.appendChild( options.pages[ currentPage ].dom );
// if pre-last page page, change button label to next
nextButton.firstChild.nodeValue = options.nextLabel;
// if next page is review page, change button label
if ( options.pages[ currentPage ] instanceof window.Feedback.Review ) {
nextButton.firstChild.nodeValue = options.reviewLabel;
}
currentPage++;
};
modalFooter.className = "feedback-footer";
modalFooter.appendChild( nextButton );
//modalFooter.appendChild( prevButton );
modal.className = "feedback-modal";
modal.setAttribute(H2C_IGNORE, true); // don't render in html2canvas
modal.appendChild( modalHeader );
modal.appendChild( modalBody );
modal.appendChild( modalFooter );
document.body.appendChild( modal );
},
// close modal window
close: function() {
button.disabled = false;
// remove feedback elements
removeElements( [ modal, glass ] );
// call end event for current page
if (currentPage > 0 ) {
options.pages[ currentPage - 1 ].end( modal );
}
// call close events for all pages
for (var i = 0, len = options.pages.length; i < len; i++) {
options.pages[ i ].close();
}
return false;
},
// send data
send: function( adapter ) {
// make sure send adapter is of right prototype
if ( !(adapter instanceof window.Feedback.Send) ) {
throw new Error( "Adapter is not an instance of Feedback.Send" );
}
// fetch data from all pages
for (var i = 0, len = options.pages.length, data = [], p = 0, tmp; i < len; i++) {
if ( (tmp = options.pages[ i ].data()) !== false ) {
data[ p++ ] = tmp;
}
}
nextButton.disabled = true;
emptyElements( modalBody );
modalBody.appendChild( loader() );
// send data to adapter for processing
adapter.send( data, function( success ) {
emptyElements( modalBody );
nextButton.disabled = false;
prevButton.parentNode.removeChild( prevButton );
nextButton.firstChild.nodeValue = options.closeLabel;
nextButton.onclick = function() {
returnMethods.close();
return false;
};
if ( success === true ) {
modalBody.appendChild( document.createTextNode( options.messageSuccess ) );
} else {
modalBody.appendChild( document.createTextNode( options.messageError ) );
}
} );
}
};
glass.className = "feedback-glass";
glass.style.pointerEvents = "none";
glass.setAttribute(H2C_IGNORE, true);
options = options || {};
button = element( "button", options.label );
button.className = "feedback-btn feedback-bottom-right";
button.setAttribute(H2C_IGNORE, true);
button.onclick = returnMethods.open;
if ( options.appendTo !== null ) {
((options.appendTo !== undefined) ? options.appendTo : document.body).appendChild( button );
}
return returnMethods;
};
window.Feedback.Page = function() {};
window.Feedback.Page.prototype = {
render: function( dom ) {
this.dom = dom;
},
start: function() {},
close: function() {},
data: function() {
// don't collect data from page by default
return false;
},
review: function() {
return null;
},
end: function() { return true; }
};
window.Feedback.Send = function() {};
window.Feedback.Send.prototype = {
send: function() {}
};
window.Feedback.fromPrevious = false;
window.Feedback.Form = function( elements ) {
this.elements = elements || [{
type: "textarea",
name: myOptions.reviewFormHeader || "Issue",
label: null,
placeholder: myOptions.placeholder || "Please describe the issue you are experiencing",
required: true
}];
this.dom = document.createElement("div");
};
window.Feedback.Form.prototype = new window.Feedback.Page();
window.Feedback.Form.prototype.render = function() {
var i = 0, len = this.elements.length, item;
emptyElements( this.dom );
for (; i < len; i++) {
item = this.elements[ i ];
switch( item.type ) {
case "textarea":
if (item.label != null) this.dom.appendChild( element("label", item.label + ":" + (( item.required === true ) ? " *" : "")) );
item.element = document.createElement('textarea');
item.element.placeholder = item.placeholder;
this.dom.appendChild( item.element );
break;
}
}
return this;
};
window.Feedback.Form.prototype.end = function() {
// form validation
var i = 0, len = this.elements.length, item;
for (; i < len; i++) {
item = this.elements[ i ];
// check that all required fields are entered
if ( item.required === true && item.element.value.length === 0) {
item.element.className = "feedback-error";
return false;
} else {
item.element.className = "";
}
}
return true;
};
window.Feedback.Form.prototype.data = function() {
if ( this._data !== undefined ) {
// return cached value
return this._data;
}
var i = 0, len = this.elements.length, item, data = {};
for (; i < len; i++) {
item = this.elements[ i ];
data[ item.name ] = item.element.value;
}
// cache and return data
return ( this._data = data );
};
window.Feedback.Form.prototype.review = function( dom ) {
var i = 0, item, len = this.elements.length;
for (; i < len; i++) {
item = this.elements[ i ];
if (item.element.value.length > 0) {
dom.appendChild( element("label", item.name + ":") );
dom.appendChild( document.createTextNode( item.element.value ) );
dom.appendChild( document.createElement( "hr" ) );
}
}
return dom;
};
window.Feedback.Review = function() {
this.dom = document.createElement("div");
this.dom.className = "feedback-review";
};
window.Feedback.Review.prototype = new window.Feedback.Page();
window.Feedback.Review.prototype.render = function( pages ) {
var i = 0, len = pages.length, item;
emptyElements( this.dom );
for (; i < len; i++) {
// get preview DOM items
pages[ i ].review( this.dom );
}
return this;
};
window.Feedback.Screenshot = function( options ) {
this.options = options || {};
this.options.blackoutClass = this.options.blackoutClass || 'feedback-blackedout';
this.options.highlightClass = this.options.highlightClass || 'feedback-highlighted';
this.h2cDone = false;
};
window.Feedback.Screenshot.prototype = new window.Feedback.Page();
window.Feedback.Screenshot.prototype.pauseInterval = 200;
window.Feedback.Screenshot.prototype.end = function( modal ){
modal.className = modal.className.replace(/feedback\-animate\-toside/, "");
// remove event listeners
document.body.removeEventListener("mousemove", this.mouseMoveEvent, false);
document.body.removeEventListener("click", this.mouseClickEvent, false);
removeElements( [this.h2cCanvas] );
this.h2cDone = false;
};
window.Feedback.Screenshot.prototype.close = function(){
removeElements( [ this.blackoutBox, this.highlightContainer, this.highlightBox, this.highlightClose ] );
//removeElements( document.getElementsByClassName('feedback-highlight-container') );
removeElements( document.getElementsByClassName('feedback-highlight-close') );
removeElements( document.getElementsByClassName('feedback-highlight-element') );
removeElements( document.getElementsByClassName('feedback-blackout-element') );
removeElements( document.getElementsByClassName( this.options.blackoutClass ) );
removeElements( document.getElementsByClassName( this.options.highlightClass ) );
};
window.Feedback.Screenshot.prototype.start = function( modal, modalHeader, modalFooter, nextButton ) {
if ( this.h2cDone ) {
emptyElements( this.dom );
nextButton.disabled = false;
window.Feedback.fromPrevious = false;
var $this = this,
feedbackHighlightElement = "feedback-highlight-element",
dataExclude = "data-exclude";
var action = true;
// delegate mouse move event for body
this.mouseMoveEvent = function( e ) {
// set close button
if ( e.target !== previousElement && (e.target.className.indexOf( $this.options.blackoutClass ) !== -1 || e.target.className.indexOf( $this.options.highlightClass ) !== -1)) {
var left = (parseInt(e.target.style.left, 10) + parseInt(e.target.style.width, 10));
left = Math.max( left, 10 );
left = Math.min( left, window.innerWidth - 15 );
var top = (parseInt(e.target.style.top, 10));
top = Math.max( top, 10 );
highlightClose.style.left = left + "px";
highlightClose.style.top = top + "px";
removeElement = e.target;
clearBox();
previousElement = undefined;
return;
}
// don't do anything if we are highlighting a close button or body tag
if (e.target.nodeName === "BODY" || e.target === highlightClose || e.target === modal || e.target === nextButton || e.target === prevButton || e.target.parentNode === modal || e.target.parentNode === modalHeader) {
// we are not gonna blackout the whole page or the close item
clearBox();
previousElement = e.target;
return;
}
hideClose();
if (e.target !== previousElement ) {
previousElement = e.target;
window.clearTimeout( timer );
timer = window.setTimeout(function(){
var bounds = getBounds( previousElement ),
item;
if ( action === false ) {
item = blackoutBox;
} else {
item = highlightBox;
item.width = bounds.width;
item.height = bounds.height;
ctx.drawImage($this.h2cCanvas, window.pageXOffset + bounds.left, window.pageYOffset + bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height );
}
// we are only targetting IE>=9, so window.pageYOffset works fine
item.setAttribute(dataExclude, false);
item.style.left = window.pageXOffset + bounds.left + "px";
item.style.top = window.pageYOffset + bounds.top + "px";
item.style.width = bounds.width + "px";
item.style.height = bounds.height + "px";
}, 100);
}
};
// delegate event for body click
this.mouseClickEvent = function( e ){
e.preventDefault();
if ( action === false) {
if ( blackoutBox.getAttribute(dataExclude) === "false") {
var blackout = document.createElement("div");
blackout.className = $this.options.blackoutClass;
blackout.style.left = blackoutBox.style.left;
blackout.style.top = blackoutBox.style.top;
blackout.style.width = blackoutBox.style.width;
blackout.style.height = blackoutBox.style.height;
document.body.appendChild( blackout );
previousElement = undefined;
}
} else {
if ( highlightBox.getAttribute(dataExclude) === "false") {
highlightBox.className += " " + $this.options.highlightClass;
highlightBox.className = highlightBox.className.replace(/feedback\-highlight\-element/g,"");
$this.highlightBox = highlightBox = document.createElement('canvas');
ctx = highlightBox.getContext("2d");
highlightBox.className += " " + feedbackHighlightElement;
document.body.appendChild( highlightBox );
clearBox();
previousElement = undefined;
}
}
};
this.highlightClose = element("div", "×");
this.blackoutBox = document.createElement('div');
this.highlightBox = document.createElement( "canvas" );
this.highlightContainer = document.createElement('div');
var timer,
highlightClose = this.highlightClose,
highlightBox = this.highlightBox,
blackoutBox = this.blackoutBox,
highlightContainer = this.highlightContainer,
removeElement,
ctx = highlightBox.getContext("2d"),
buttonClickFunction = function( e ) {
e.preventDefault();
if (blackoutButton.className.indexOf("active") === -1) {
blackoutButton.className += " active";
highlightButton.className = highlightButton.className.replace(/active/g,"");
} else {
highlightButton.className += " active";
blackoutButton.className = blackoutButton.className.replace(/active/g,"");
}
action = !action;
},
clearBox = function() {
clearBoxEl(blackoutBox);
clearBoxEl(highlightBox);
window.clearTimeout( timer );
},
clearBoxEl = function( el ) {
el.style.left = "-5px";
el.style.top = "-5px";
el.style.width = "0px";
el.style.height = "0px";
el.setAttribute(dataExclude, true);
},
hideClose = function() {
highlightClose.style.left = "-50px";
highlightClose.style.top = "-50px";
},
blackoutButton = element("a", "Blackout"),
highlightButton = element("a", "Highlight"),
previousElement;
modal.className += ' feedback-animate-toside';
highlightClose.id = "feedback-highlight-close";
highlightClose.addEventListener("click", function(){
removeElement.parentNode.removeChild( removeElement );
hideClose();
}, false);
document.body.appendChild( highlightClose );
this.h2cCanvas.className = 'feedback-canvas';
document.body.appendChild( this.h2cCanvas);
var buttonItem = [ highlightButton, blackoutButton ];
this.dom.appendChild( element("p", "Highlight or blackout important information") );
// add highlight and blackout buttons
for (var i = 0; i < 2; i++ ) {
buttonItem[ i ].className = 'feedback-btn feedback-btn-small ' + (i === 0 ? 'active' : 'feedback-btn-inverse');
buttonItem[ i ].href = "#";
buttonItem[ i ].onclick = buttonClickFunction;
this.dom.appendChild( buttonItem[ i ] );
this.dom.appendChild( document.createTextNode(" ") );
}
highlightContainer.id = "feedback-highlight-container";
highlightContainer.style.width = this.h2cCanvas.width + "px";
highlightContainer.style.height = this.h2cCanvas.height + "px";
this.highlightBox.className += " " + feedbackHighlightElement;
this.blackoutBox.id = "feedback-blackout-element";
document.body.appendChild( this.highlightBox );
highlightContainer.appendChild( this.blackoutBox );
document.body.appendChild( highlightContainer );
// bind mouse delegate events
document.body.addEventListener("mousemove", this.mouseMoveEvent, false);
document.body.addEventListener("click", this.mouseClickEvent, false);
} else {
// still loading html2canvas
var args = arguments,
$this = this;
if ( nextButton.disabled !== true) {
this.dom.appendChild( loader() );
}
nextButton.disabled = true;
window.setTimeout(function(){
if (window.Feedback.fromPrevious) {
$this.h2cDone = true;
$this.pauseInterval = 200;
} else {
$this.pauseInterval += 100;
}
$this.start.apply( $this, args );
}, this.pauseInterval);
}
};
window.Feedback.Screenshot.prototype.render = function() {
this.dom = document.createElement("div");
// execute the html2canvas script
var script,
$this = this,
options = this.options,
runH2c = function(){
try {
options.onrendered = options.onrendered || function( canvas ) {
$this.h2cCanvas = canvas;
$this.h2cDone = true;
};
window.html2canvas([ document.body ], options);
} catch( e ) {
$this.h2cDone = true;
log("Error in html2canvas: " + e.message);
}
};
if ( window.html2canvas === undefined && script === undefined ) {
// let's load html2canvas library while user is writing message
script = document.createElement("script");
script.src = options.h2cPath || "libs/html2canvas.js";
script.onerror = function() {
log("Failed to load html2canvas library, check that the path is correctly defined");
};
script.onload = (scriptLoader)(script, function() {
if (window.html2canvas === undefined) {
log("Loaded html2canvas, but library not found");
return;
}
window.html2canvas.logging = window.Feedback.debug;
runH2c();
});
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(script, s);
} else {
// html2canvas already loaded, just run it then
runH2c();
}
return this;
};
window.Feedback.Screenshot.prototype.data = function() {
if ( this._data !== undefined ) {
return this._data;
}
if ( this.h2cCanvas !== undefined ) {
var ctx = this.h2cCanvas.getContext("2d"),
canvasCopy,
copyCtx,
radius = 5;
ctx.fillStyle = "#000";
// draw blackouts
Array.prototype.slice.call( document.getElementsByClassName('feedback-blackedout'), 0).forEach( function( item ) {
var bounds = getBounds( item );
ctx.fillRect( bounds.left, bounds.top, bounds.width, bounds.height );
});
// draw highlights
var items = Array.prototype.slice.call( document.getElementsByClassName('feedback-highlighted'), 0);
if (items.length > 0 ) {
// copy canvas
canvasCopy = document.createElement( "canvas" );
copyCtx = canvasCopy.getContext('2d');
canvasCopy.width = this.h2cCanvas.width;
canvasCopy.height = this.h2cCanvas.height;
copyCtx.drawImage( this.h2cCanvas, 0, 0 );
ctx.fillStyle = "#777";
ctx.globalAlpha = 0.5;
ctx.fillRect( 0, 0, this.h2cCanvas.width, this.h2cCanvas.height );
ctx.beginPath();
items.forEach( function( item ) {
var x = parseInt(item.style.left, 10),
y = parseInt(item.style.top, 10),
width = parseInt(item.style.width, 10),
height = parseInt(item.style.height, 10);
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
});
ctx.closePath();
ctx.clip();
ctx.globalAlpha = 1;
ctx.drawImage(canvasCopy, 0,0);
}
// to avoid security error break for tainted canvas
try {
// cache and return data
return ( this._data = this.h2cCanvas.toDataURL() );
} catch( e ) {}
}
};
window.Feedback.Screenshot.prototype.review = function( dom ) {
var data = this.data();
if ( data !== undefined ) {
var img = new Image();
img.src = data;
img.style.width = "300px";
dom.appendChild( element("label", myOptions.reviewScreenshotHeader + " :") );
dom.appendChild( img );
}
};
window.Feedback.XHR = function( url ) {
this.xhr = new XMLHttpRequest();
this.url = url;
};
window.Feedback.XHR.prototype = new window.Feedback.Send();
window.Feedback.XHR.prototype.send = function( data, callback ) {
var xhr = this.xhr;
xhr.onreadystatechange = function() {
if( xhr.readyState == 4 ){
callback( (xhr.status === 200) );
}
};
xhr.open( "POST", this.url, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send( "data=" + encodeURIComponent( window.JSON.stringify( data ) ) );
};
})( window, document );
<!DOCTYPE html>
<!-- saved from url=(0054)http://twitter.github.com/bootstrap/examples/hero.html -->
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>Bootstrap, from Twitter</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<!-- Le styles -->
<link rel="stylesheet" href="css/bootstrap.css" type="text/css" media="screen" />
<link rel="stylesheet" href="css/feedback.css" type="text/css" media="screen" />
<style type="text/css">
body {
padding-top: 60px;
padding-bottom: 40px;
}
</style>
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="#_">Feedback.js Example</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li class="active"><a href="#_">Home</a></li>
<li><a href="#_">About</a></li>
<li><a href="#_">Contact</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<div class="hero-unit">
<h1>Feedback.js</h1>
<p>This page serves as an example for how feedback.js is used. Click on the Feedback button on the bottom right of the screen to try it out</p>
<p><a class="btn btn-primary btn-large">Learn more »</a></p>
</div>
<!-- Example row of columns -->
<div class="row">
<div class="span4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
<p><a class="btn" href="#_">View details »</a></p>
</div>
<div class="span4">
<h2>Heading</h2>
<p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
<p><a class="btn" href="#_">View details »</a></p>
</div>
<div class="span4">
<h2>Heading</h2>
<p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
<p><a class="btn" href="#_">View details »</a></p>
</div>
</div>
<hr>
<footer>
<p>Feedback.js - tell us more!</p>
</footer>
</div> <!-- /container -->
<!-- Le javascript -->
<script src="js/jquery.js"></script>
<script src="js/feedback.js"></script>
<script type="text/javascript">
$(document).ready(function() {
Feedback({
label: 'Feedback',
header: 'Send us a feedback',
prevLabel: 'Back',
nextLabel: 'Next',
reviewLabel: 'Next',
sendLabel: '',
placeholder: 'Give us your feedback or ask for help...',
closeLabel: '',
reviewFormHeader: 'Your feedback',
reviewScreenshotHeader: 'Your screenshot',
h2cPath: 'js/html2canvas.js'
});
});
</script>
</body></html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment