mirror of
https://github.com/wahyd4/cdnjs.git
synced 2026-08-29 06:36:08 +10:00
Merge pull request #2495 from jayenashar/feedback.js
Downloaded from https://raw.github.com/niklasvh/feedback.js/master/
This commit is contained in:
@@ -0,0 +1,876 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
},
|
||||
nextButton,
|
||||
H2C_IGNORE = "data-html2canvas-ignore",
|
||||
currentPage,
|
||||
modalBody = document.createElement("div");
|
||||
|
||||
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.nextLabel = options.nextLabel || "Continue";
|
||||
options.reviewLabel = options.reviewLabel || "Review";
|
||||
options.sendLabel = options.sendLabel || "Send";
|
||||
options.closeLabel = options.closeLabel || "Close";
|
||||
|
||||
options.messageSuccess = options.messageSuccess || "Your feedback was sent succesfully.";
|
||||
options.messageError = options.messageError || "There was an error sending your feedback to the server.";
|
||||
|
||||
|
||||
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"),
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
modalFooter.className = "feedback-footer";
|
||||
modalFooter.appendChild( nextButton );
|
||||
|
||||
|
||||
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;
|
||||
|
||||
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.Form = function( elements ) {
|
||||
|
||||
this.elements = elements || [{
|
||||
type: "textarea",
|
||||
name: "Issue",
|
||||
label: "Please describe the issue you are experiencing",
|
||||
required: false
|
||||
}];
|
||||
|
||||
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":
|
||||
this.dom.appendChild( element("label", item.label + ":" + (( item.required === true ) ? " *" : "")) );
|
||||
this.dom.appendChild( ( item.element = document.createElement("textarea")) );
|
||||
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.length ) );
|
||||
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.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( 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;
|
||||
|
||||
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.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(){
|
||||
$this.start.apply( $this, args );
|
||||
}, 500);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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( 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 );
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
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(b,d,g){if(b.Feedback===g){var o=function(a){for(var b=0,c=a.length;b<c;b++){var d=Array.prototype.pop.call(a);d!==g&&d.parentNode!==null&&d.parentNode.removeChild(d)}},y=function(){var a=d.createElement("div"),b=3;for(a.className="feedback-loader";b--;)a.appendChild(d.createElement("span"));return a},q=function(a){for(var b;(b=a.firstChild)!==null&&a.removeChild(b););},l=function(a,b){var c=d.createElement(a);c.appendChild(d.createTextNode(b));return c},w=function(a,d){if(a.onload===g)if(a.onreadystatechange!==
|
||||
g){var c=function(){a.readyState!=="loaded"&&a.readyState!=="complete"?b.setTimeout(c,250):d()};b.setTimeout(c,250)}else b.console.log("ERROR: We can't track when script is loaded");else return d},h,f=d.createElement("div");b.Feedback=function(a){a=a||{};a.label=a.label||"Send Feedback";a.header=a.header||"Send Feedback";a.url=a.url||"/";a.adapter=a.adapter||new b.Feedback.XHR(a.url);a.nextLabel=a.nextLabel||"Continue";a.reviewLabel=a.reviewLabel||"Review";a.sendLabel=a.sendLabel||"Send";a.closeLabel=
|
||||
a.closeLabel||"Close";a.messageSuccess=a.messageSuccess||"Your feedback was sent succesfully.";a.messageError=a.messageError||"There was an error sending your feedback to the server.";if(a.pages===g)a.pages=[new b.Feedback.Form,new b.Feedback.Screenshot(a),new b.Feedback.Review];var i,c,e,j=d.createElement("div"),n={open:function(){var r=a.pages.length;for(e=0;e<r;e++)a.pages[e]instanceof b.Feedback.Review||a.pages[e].render();var m=l("a","\u00d7"),k=d.createElement("div"),g=d.createElement("div");
|
||||
c=d.createElement("div");d.body.appendChild(j);m.className="feedback-close";m.onclick=n.close;m.href="#";i.disabled=!0;k.appendChild(m);k.appendChild(l("h3",a.header));k.className="feedback-header";f.className="feedback-body";q(f);e=0;f.appendChild(a.pages[e++].dom);h=l("button",a.nextLabel);h.className="feedback-btn";h.onclick=function(){if(!(e>0&&a.pages[e-1].end(c)===!1))if(q(f),e===r)n.send(a.adapter);else{a.pages[e].start(c,k,g,h);a.pages[e]instanceof b.Feedback.Review&&a.pages[e].render(a.pages);
|
||||
f.appendChild(a.pages[e++].dom);if(e===r)h.firstChild.nodeValue=a.sendLabel;if(a.pages[e]instanceof b.Feedback.Review)h.firstChild.nodeValue=a.reviewLabel}};g.className="feedback-footer";g.appendChild(h);c.className="feedback-modal";c.setAttribute("data-html2canvas-ignore",!0);c.appendChild(k);c.appendChild(f);c.appendChild(g);d.body.appendChild(c)},close:function(){i.disabled=!1;o([c,j]);e>0&&a.pages[e-1].end(c);for(var b=0,d=a.pages.length;b<d;b++)a.pages[b].close();return!1},send:function(c){if(!(c instanceof
|
||||
b.Feedback.Send))throw Error("Adapter is not an instance of Feedback.Send");for(var i=0,e=a.pages.length,g=[],j=0,l;i<e;i++)if((l=a.pages[i].data())!==!1)g[j++]=l;h.disabled=!0;q(f);f.appendChild(y());c.send(g,function(b){q(f);h.disabled=!1;h.firstChild.nodeValue=a.closeLabel;h.onclick=function(){n.close();return!1};b===!0?f.appendChild(d.createTextNode(a.messageSuccess)):f.appendChild(d.createTextNode(a.messageError))})}};j.className="feedback-glass";j.style.pointerEvents="none";j.setAttribute("data-html2canvas-ignore",
|
||||
!0);a=a||{};i=l("button",a.label);i.className="feedback-btn feedback-bottom-right";i.setAttribute("data-html2canvas-ignore",!0);i.onclick=n.open;a.appendTo!==null&&(a.appendTo!==g?a.appendTo:d.body).appendChild(i);return n};b.Feedback.Page=function(){};b.Feedback.Page.prototype={render:function(a){this.dom=a},start:function(){},close:function(){},data:function(){return!1},review:function(){return null},end:function(){return!0}};b.Feedback.Send=function(){};b.Feedback.Send.prototype={send:function(){}};
|
||||
b.Feedback.Form=function(a){this.elements=a||[{type:"textarea",name:"Issue",label:"Please describe the issue you are experiencing",required:!1}];this.dom=d.createElement("div")};b.Feedback.Form.prototype=new b.Feedback.Page;b.Feedback.Form.prototype.render=function(){var a=0,b=this.elements.length,c;for(q(this.dom);a<b;a++)switch(c=this.elements[a],c.type){case "textarea":this.dom.appendChild(l("label",c.label+":"+(c.required===!0?" *":""))),this.dom.appendChild(c.element=d.createElement("textarea"))}return this};
|
||||
b.Feedback.Form.prototype.end=function(){for(var a=0,b=this.elements.length,c;a<b;a++)if(c=this.elements[a],c.required===!0&&c.element.value.length===0)return c.element.className="feedback-error",!1;else c.element.className="";return!0};b.Feedback.Form.prototype.data=function(){if(this._data!==g)return this._data;for(var a=0,b=this.elements.length,c,d={};a<b;a++)c=this.elements[a],d[c.name]=c.element.value;return this._data=d};b.Feedback.Form.prototype.review=function(a){for(var b=0,c,e=this.elements.length;b<
|
||||
e;b++)c=this.elements[b],c.element.value.length>0&&(a.appendChild(l("label",c.name+":")),a.appendChild(d.createTextNode(c.element.value.length)),a.appendChild(d.createElement("hr")));return a};b.Feedback.Review=function(){this.dom=d.createElement("div");this.dom.className="feedback-review"};b.Feedback.Review.prototype=new b.Feedback.Page;b.Feedback.Review.prototype.render=function(a){var b=0,c=a.length;for(q(this.dom);b<c;b++)a[b].review(this.dom);return this};b.Feedback.Screenshot=function(a){this.options=
|
||||
a||{};this.options.blackoutClass=this.options.blackoutClass||"feedback-blackedout";this.options.highlightClass=this.options.highlightClass||"feedback-highlighted";this.h2cDone=!1};b.Feedback.Screenshot.prototype=new b.Feedback.Page;b.Feedback.Screenshot.prototype.end=function(a){a.className=a.className.replace(/feedback\-animate\-toside/,"");d.body.removeEventListener("mousemove",this.mouseMoveEvent,!1);d.body.removeEventListener("click",this.mouseClickEvent,!1);o([this.h2cCanvas]);this.h2cDone=!1};
|
||||
b.Feedback.Screenshot.prototype.close=function(){o([this.blackoutBox,this.highlightContainer,this.highlightBox,this.highlightClose]);o(d.getElementsByClassName(this.options.blackoutClass));o(d.getElementsByClassName(this.options.highlightClass))};b.Feedback.Screenshot.prototype.start=function(a,i,c,e){if(this.h2cDone){q(this.dom);e.disabled=!1;var j=this,n=!0;this.mouseMoveEvent=function(c){if(c.target!==p&&(c.target.className.indexOf(j.options.blackoutClass)!==-1||c.target.className.indexOf(j.options.highlightClass)!==
|
||||
-1)){var d=parseInt(c.target.style.left,10)+parseInt(c.target.style.width,10),d=Math.max(d,10),d=Math.min(d,b.innerWidth-15),f=parseInt(c.target.style.top,10),f=Math.max(f,10);m.style.left=d+"px";m.style.top=f+"px";o=c.target;x();p=g}else if(c.target.nodeName==="BODY"||c.target===m||c.target===a||c.target===e||c.target.parentNode===a||c.target.parentNode===i)x(),p=c.target;else if(A(),c.target!==p)p=c.target,b.clearTimeout(r),r=b.setTimeout(function(){var a=p.getBoundingClientRect(),c;n===!1?c=h:
|
||||
(c=k,c.width=a.width,c.height=a.height,z.drawImage(j.h2cCanvas,b.pageXOffset+a.left,b.pageYOffset+a.top,a.width,a.height,0,0,a.width,a.height));c.setAttribute("data-exclude",!1);c.style.left=b.pageXOffset+a.left+"px";c.style.top=b.pageYOffset+a.top+"px";c.style.width=a.width+"px";c.style.height=a.height+"px"},100)};this.mouseClickEvent=function(a){a.preventDefault();if(n===!1){if(h.getAttribute("data-exclude")==="false")a=d.createElement("div"),a.className=j.options.blackoutClass,a.style.left=h.style.left,
|
||||
a.style.top=h.style.top,a.style.width=h.style.width,a.style.height=h.style.height,d.body.appendChild(a),p=g}else if(k.getAttribute("data-exclude")==="false")k.className+=" "+j.options.highlightClass,k.className=k.className.replace(/feedback\-highlight\-element/g,""),j.highlightBox=k=d.createElement("canvas"),z=k.getContext("2d"),k.className+=" feedback-highlight-element",d.body.appendChild(k),x(),p=g};this.highlightClose=l("div","\u00d7");this.blackoutBox=d.createElement("div");this.highlightBox=
|
||||
d.createElement("canvas");this.highlightContainer=d.createElement("div");var r,m=this.highlightClose,k=this.highlightBox,h=this.blackoutBox,f=this.highlightContainer,o,z=k.getContext("2d"),w=function(a){a.preventDefault();t.className.indexOf("active")===-1?(t.className+=" active",u.className=u.className.replace(/active/g,"")):(u.className+=" active",t.className=t.className.replace(/active/g,""));n=!n},x=function(){B(h);B(k);b.clearTimeout(r)},B=function(a){a.style.left="-5px";a.style.top="-5px";a.style.width=
|
||||
"0px";a.style.height="0px";a.setAttribute("data-exclude",!0)},A=function(){m.style.left="-50px";m.style.top="-50px"},t=l("a","Blackout"),u=l("a","Highlight"),p;a.className+=" feedback-animate-toside";m.id="feedback-highlight-close";m.addEventListener("click",function(){o.parentNode.removeChild(o);A()},!1);d.body.appendChild(m);this.h2cCanvas.className="feedback-canvas";d.body.appendChild(this.h2cCanvas);var v=[u,t];this.dom.appendChild(l("p","Highlight or blackout important information"));for(var s=
|
||||
0;s<2;s++)v[s].className="feedback-btn feedback-btn-small "+(s===0?"active":"feedback-btn-inverse"),v[s].href="#",v[s].onclick=w,this.dom.appendChild(v[s]),this.dom.appendChild(d.createTextNode(" "));f.id="feedback-highlight-container";f.style.width=this.h2cCanvas.width+"px";f.style.height=this.h2cCanvas.height+"px";this.highlightBox.className+=" feedback-highlight-element";this.blackoutBox.id="feedback-blackout-element";d.body.appendChild(this.highlightBox);f.appendChild(this.blackoutBox);d.body.appendChild(f);
|
||||
d.body.addEventListener("mousemove",this.mouseMoveEvent,!1);d.body.addEventListener("click",this.mouseClickEvent,!1)}else{var C=arguments,j=this;e.disabled!==!0&&this.dom.appendChild(y());e.disabled=!0;b.setTimeout(function(){j.start.apply(j,C)},500)}};b.Feedback.Screenshot.prototype.render=function(){this.dom=d.createElement("div");var a,i=this,c=this.options,e=function(){try{c.onrendered=c.onrendered||function(a){i.h2cCanvas=a;i.h2cDone=!0},b.html2canvas([d.body],c)}catch(a){i.h2cDone=!0,b.console.log("Error in html2canvas: "+
|
||||
a.message)}};if(b.html2canvas===g&&a===g){a=d.createElement("script");a.src=c.h2cPath||"libs/html2canvas.js";a.onerror=function(){b.console.log("Failed to load html2canvas library, check that the path is correctly defined")};a.onload=w(a,function(){b.html2canvas===g?b.console.log("Loaded html2canvas, but library not found"):(b.html2canvas.logging=b.Feedback.debug,e())});var f=d.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}else e();return this};b.Feedback.Screenshot.prototype.data=
|
||||
function(){if(this._data!==g)return this._data;if(this.h2cCanvas!==g){var a=this.h2cCanvas.getContext("2d"),b,c;a.fillStyle="#000";Array.prototype.slice.call(d.getElementsByClassName("feedback-blackedout"),0).forEach(function(b){b=b.getBoundingClientRect();a.fillRect(b.left,b.top,b.width,b.height)});var e=Array.prototype.slice.call(d.getElementsByClassName("feedback-highlighted"),0);if(e.length>0)b=d.createElement("canvas"),c=b.getContext("2d"),b.width=this.h2cCanvas.width,b.height=this.h2cCanvas.height,
|
||||
c.drawImage(this.h2cCanvas,0,0),a.fillStyle="#777",a.globalAlpha=0.5,a.fillRect(0,0,this.h2cCanvas.width,this.h2cCanvas.height),a.beginPath(),e.forEach(function(b){var c=parseInt(b.style.left,10),d=parseInt(b.style.top,10),e=parseInt(b.style.width,10),b=parseInt(b.style.height,10);a.moveTo(c+5,d);a.lineTo(c+e-5,d);a.quadraticCurveTo(c+e,d,c+e,d+5);a.lineTo(c+e,d+b-5);a.quadraticCurveTo(c+e,d+b,c+e-5,d+b);a.lineTo(c+5,d+b);a.quadraticCurveTo(c,d+b,c,d+b-5);a.lineTo(c,d+5);a.quadraticCurveTo(c,d,c+
|
||||
5,d)}),a.closePath(),a.clip(),a.globalAlpha=1,a.drawImage(b,0,0);try{return this._data=this.h2cCanvas.toDataURL()}catch(f){}}};b.Feedback.Screenshot.prototype.review=function(a){var b=this.data();if(b!==g){var c=new Image;c.src=b;c.style.width="300px";a.appendChild(c)}};b.Feedback.XHR=function(a){this.xhr=new XMLHttpRequest;this.url=a};b.Feedback.XHR.prototype=new b.Feedback.Send;b.Feedback.XHR.prototype.send=function(a,d){var c=this.xhr;c.onreadystatechange=function(){c.readyState==4&&d(c.status===
|
||||
200)};c.open("POST",this.url,!0);c.setRequestHeader("Content-type","application/x-www-form-urlencoded");c.send("data="+encodeURIComponent(b.JSON.stringify(a)))}}})(window,document);
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "feedback.js",
|
||||
"filename": "feedback.js",
|
||||
"version": "2012.10.17",
|
||||
"description": "Feedback form with screenshot",
|
||||
"homepage": "http://experiments.hertzen.com/jsfeedback/",
|
||||
"keywords": [
|
||||
"canvas",
|
||||
"screenshot",
|
||||
"html5",
|
||||
"feedback"
|
||||
],
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Jayen Ashar",
|
||||
"email": "jayen@suburbchooser.com"
|
||||
}
|
||||
],
|
||||
"repositories": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/niklasvh/feedback.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user