From 3d762f592f6cdc325e04739c9db0605370c1869e Mon Sep 17 00:00:00 2001 From: lucianmoldovan Date: Thu, 17 Jul 2014 12:50:47 +0300 Subject: [PATCH 1/5] Update feedback.js If you try to make another screen shot after you already made one, the script will always capture the first screen shot you made. I consider this a bug. Please update feedback.js to no use the cached screen shot. Thank you. --- feedback.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/feedback.js b/feedback.js index 37ba9ef..dd08db5 100644 --- a/feedback.js +++ b/feedback.js @@ -373,11 +373,6 @@ window.Feedback.Form.prototype.end = function() { 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++) { @@ -764,10 +759,6 @@ window.Feedback.Screenshot.prototype.render = function() { window.Feedback.Screenshot.prototype.data = function() { - if ( this._data !== undefined ) { - return this._data; - } - if ( this.h2cCanvas !== undefined ) { var ctx = this.h2cCanvas.getContext("2d"), From 70febaf3514c80e9c06cd4c70446a2fe9f97564d Mon Sep 17 00:00:00 2001 From: lucianmoldovan Date: Wed, 8 Oct 2014 09:41:04 +0300 Subject: [PATCH 2/5] Black boxes positioned incorrectly after capture Black boxes were positioned incorrectly after screen capture on a page with scroll, this commit fixes it. --- feedback.js | 1366 ++++++++++++++++++++++++++------------------------- 1 file changed, 691 insertions(+), 675 deletions(-) diff --git a/feedback.js b/feedback.js index dd08db5..66b3c3e 100644 --- a/feedback.js +++ b/feedback.js @@ -5,863 +5,879 @@ 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 ); - } - } +(function (window, document, undefined) { + if (window.Feedback !== undefined) { + return; } -}, -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 ); + + // 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 { - // it is loaded - func(); + log("ERROR: We can't track when script is loaded"); } - }; - window.setTimeout( intervalFunc, 250 ); + } else { + return func; + } - } else { - log("ERROR: We can't track when script is loaded"); - } + }, + nextButton, + H2C_IGNORE = "data-html2canvas-ignore", + currentPage, + modalBody = document.createElement("div"); - } else { - return func; - } + window.Feedback = function (options) { + + options = options || {}; -}, -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 = [ + // 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.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 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"); + var a = element("a", "×"), + modalHeader = document.createElement("div"), + // modal container + modalFooter = document.createElement("div"); - modal = document.createElement("div"); - document.body.appendChild( glass ); + modal = document.createElement("div"); + document.body.appendChild(glass); - // modal close button - a.className = "feedback-close"; - a.onclick = returnMethods.close; - a.href = "#"; + // modal close button + a.className = "feedback-close"; + a.onclick = returnMethods.close; + a.href = "#"; - button.disabled = true; + button.disabled = true; - // build header element - modalHeader.appendChild( a ); - modalHeader.appendChild( element("h3", options.header ) ); - modalHeader.className = "feedback-header"; + // build header element + modalHeader.appendChild(a); + modalHeader.appendChild(element("h3", options.header)); + modalHeader.className = "feedback-header"; - modalBody.className = "feedback-body"; + modalBody.className = "feedback-body"; - emptyElements( modalBody ); - currentPage = 0; - modalBody.appendChild( options.pages[ currentPage++ ].dom ); + emptyElements(modalBody); + currentPage = 0; + modalBody.appendChild(options.pages[currentPage++].dom); - // Next button - nextButton = element( "button", options.nextLabel ); + // 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 ); + nextButton.className = "feedback-btn"; + nextButton.onclick = function () { - if ( currentPage === len ) { - returnMethods.send( options.adapter ); - } else { + 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); - 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 ); + 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); } - - // 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; + // call close events for all pages + for (var i = 0, len = options.pages.length; i < len; i++) { + options.pages[i].close(); } - - // if next page is review page, change button label - if ( options.pages[ currentPage ] instanceof window.Feedback.Review ) { - nextButton.firstChild.nodeValue = options.reviewLabel; + + 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; - modalFooter.className = "feedback-footer"; - modalFooter.appendChild( nextButton ); + emptyElements(modalBody); + modalBody.appendChild(loader()); + // send data to adapter for processing + adapter.send(data, function (success) { - modal.className = "feedback-modal"; - modal.setAttribute(H2C_IGNORE, true); // don't render in html2canvas + emptyElements(modalBody); + nextButton.disabled = false; + nextButton.firstChild.nodeValue = options.closeLabel; - modal.appendChild( modalHeader ); - modal.appendChild( modalBody ); - modal.appendChild( modalFooter ); + nextButton.onclick = function () { + returnMethods.close(); + return false; + }; - document.body.appendChild( modal ); - }, + if (success === true) { + modalBody.appendChild(document.createTextNode(options.messageSuccess)); + } else { + modalBody.appendChild(document.createTextNode(options.messageError)); + } + }); - // close modal window - close: function() { + } + }; - button.disabled = false; + glass.className = "feedback-glass"; + glass.style.pointerEvents = "none"; + glass.setAttribute(H2C_IGNORE, true); - // remove feedback elements - removeElements( [ modal, glass ] ); + options = options || {}; - // 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(); - } + button = element("button", options.label); + button.className = "feedback-btn feedback-bottom-right"; - return false; + button.setAttribute(H2C_IGNORE, true); - }, - - // 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; - } - } + button.onclick = returnMethods.open; - 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 ) ); - } - - } ); - + 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; + } - glass.className = "feedback-glass"; - glass.style.pointerEvents = "none"; - glass.setAttribute(H2C_IGNORE, true); + }; + window.Feedback.Send = function () {}; + window.Feedback.Send.prototype = { - options = options || {}; + send: function () {} - button = element( "button", options.label ); - button.className = "feedback-btn feedback-bottom-right"; + }; - button.setAttribute(H2C_IGNORE, true); + window.Feedback.Form = function (elements) { - 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.elements = elements || [{ + type: "textarea", + name: "Issue", + label: "Please describe the issue you are experiencing", + required: false }]; - this.dom = document.createElement("div"); + this.dom = document.createElement("div"); -}; + }; -window.Feedback.Form.prototype = new window.Feedback.Page(); + window.Feedback.Form.prototype = new window.Feedback.Page(); -window.Feedback.Form.prototype.render = function() { + 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 ]; + var i = 0, + len = this.elements.length, + item; + emptyElements(this.dom); + for (; i < len; i++) { + item = this.elements[i]; - switch( item.type ) { + switch (item.type) { case "textarea": - this.dom.appendChild( element("label", item.label + ":" + (( item.required === true ) ? " *" : "")) ); - this.dom.appendChild( ( item.element = document.createElement("textarea")) ); + this.dom.appendChild(element("label", item.label + ":" + ((item.required === true) ? " *" : ""))); + this.dom.appendChild((item.element = document.createElement("textarea"))); break; + } } - } - return this; + 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 = ""; + 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() { - - 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 true; + + }; + + window.Feedback.Form.prototype.data = function () { + + var i = 0, + len = this.elements.length, + item, data = {}; + + for (; i < len; i++) { + item = this.elements[i]; + data[item.name] = item.element.value; } - - } - - return dom; - -}; -window.Feedback.Review = function() { - this.dom = document.createElement("div"); - this.dom.className = "feedback-review"; + // cache and return data + return (this._data = data); + }; -}; -window.Feedback.Review.prototype = new window.Feedback.Page(); + window.Feedback.Form.prototype.review = function (dom) { -window.Feedback.Review.prototype.render = function( pages ) { + var i = 0, + item, len = this.elements.length; - var i = 0, len = pages.length, item; - emptyElements( this.dom ); - - for (; i < len; i++) { - - // get preview DOM items - pages[ i ].review( this.dom ); + 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 this; + } -}; + return dom; + }; + window.Feedback.Review = function () { + this.dom = document.createElement("div"); + this.dom.className = "feedback-review"; + }; -window.Feedback.Screenshot = function( options ) { - this.options = options || {}; + window.Feedback.Review.prototype = new window.Feedback.Page(); - this.options.blackoutClass = this.options.blackoutClass || 'feedback-blackedout'; - this.options.highlightClass = this.options.highlightClass || 'feedback-highlighted'; + window.Feedback.Review.prototype.render = function (pages) { - this.h2cDone = false; -}; + var i = 0, + len = pages.length, + item; + emptyElements(this.dom); -window.Feedback.Screenshot.prototype = new window.Feedback.Page(); + for (; i < len; i++) { -window.Feedback.Screenshot.prototype.end = function( modal ){ - modal.className = modal.className.replace(/feedback\-animate\-toside/, ""); + // get preview DOM items + pages[i].review(this.dom); - // remove event listeners - document.body.removeEventListener("mousemove", this.mouseMoveEvent, false); - document.body.removeEventListener("click", this.mouseClickEvent, false); + } - removeElements( [this.h2cCanvas] ); + return this; - 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 = function (options) { + this.options = options || {}; -window.Feedback.Screenshot.prototype.start = function( modal, modalHeader, modalFooter, nextButton ) { + this.options.blackoutClass = this.options.blackoutClass || 'feedback-blackedout'; + this.options.highlightClass = this.options.highlightClass || 'feedback-highlighted'; - if ( this.h2cDone ) { - emptyElements( this.dom ); - nextButton.disabled = false; - - var $this = this, - feedbackHighlightElement = "feedback-highlight-element", - dataExclude = "data-exclude"; + this.h2cDone = false; + }; - var action = true; + window.Feedback.Screenshot.prototype = new window.Feedback.Page(); - // delegate mouse move event for body - this.mouseMoveEvent = function( e ) { + window.Feedback.Screenshot.prototype.end = function (modal) { + modal.className = modal.className.replace(/feedback\-animate\-toside/, ""); - // set close button - if ( e.target !== previousElement && (e.target.className.indexOf( $this.options.blackoutClass ) !== -1 || e.target.className.indexOf( $this.options.highlightClass ) !== -1)) { + // remove event listeners + document.body.removeEventListener("mousemove", this.mouseMoveEvent, false); + document.body.removeEventListener("click", this.mouseClickEvent, false); - var left = (parseInt(e.target.style.left, 10) + parseInt(e.target.style.width, 10)); - left = Math.max( left, 10 ); + removeElements([this.h2cCanvas]); - left = Math.min( left, window.innerWidth - 15 ); + this.h2cDone = false; - 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; - } + window.Feedback.Screenshot.prototype.close = function () { + removeElements([this.blackoutBox, this.highlightContainer, this.highlightBox, this.highlightClose]); - // 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; - } + removeElements(document.getElementsByClassName(this.options.blackoutClass)); + removeElements(document.getElementsByClassName(this.options.highlightClass)); - hideClose(); + }; - if (e.target !== previousElement ) { - previousElement = e.target; + window.Feedback.Screenshot.prototype.start = function (modal, modalHeader, modalFooter, nextButton) { - window.clearTimeout( timer ); + if (this.h2cDone) { + emptyElements(this.dom); + nextButton.disabled = false; - timer = window.setTimeout(function(){ - var bounds = getBounds( previousElement ), - item; + var $this = this, + feedbackHighlightElement = "feedback-highlight-element", + dataExclude = "data-exclude"; - 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 ); - } + var action = true; - // 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 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; + } - // delegate event for body click - this.mouseClickEvent = function( e ){ + hideClose(); - e.preventDefault(); + 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); - 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; + // 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,""); - } + 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); + action = !action; + }, + clearBox = function () { - 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"; + clearBoxEl(blackoutBox); + clearBoxEl(highlightBox); - }, - blackoutButton = element("a", "Blackout"), - highlightButton = element("a", "Highlight"), - previousElement; + 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'; + modal.className += ' feedback-animate-toside'; - highlightClose.id = "feedback-highlight-close"; + highlightClose.id = "feedback-highlight-close"; - highlightClose.addEventListener("click", function(){ - removeElement.parentNode.removeChild( removeElement ); - hideClose(); - }, false); - document.body.appendChild( highlightClose ); + highlightClose.addEventListener("click", function () { + removeElement.parentNode.removeChild(removeElement); + hideClose(); + }, false); + document.body.appendChild(highlightClose); - this.h2cCanvas.className = 'feedback-canvas'; - document.body.appendChild( this.h2cCanvas); + this.h2cCanvas.className = 'feedback-canvas'; + document.body.appendChild(this.h2cCanvas); - var buttonItem = [ highlightButton, blackoutButton ]; - this.dom.appendChild( element("p", "Highlight or blackout important information") ); + var buttonItem = [highlightButton, blackoutButton]; - // 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'); + this.dom.appendChild(element("p", "Highlight or blackout important information")); - buttonItem[ i ].href = "#"; - buttonItem[ i ].onclick = buttonClickFunction; + // 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'); - this.dom.appendChild( buttonItem[ i ] ); + buttonItem[i].href = "#"; + buttonItem[i].onclick = buttonClickFunction; - this.dom.appendChild( document.createTextNode(" ") ); + 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 ); + highlightContainer.id = "feedback-highlight-container"; + highlightContainer.style.width = this.h2cCanvas.width + "px"; + highlightContainer.style.height = this.h2cCanvas.height + "px"; - document.body.appendChild( highlightContainer ); + this.highlightBox.className += " " + feedbackHighlightElement; + this.blackoutBox.id = "feedback-blackout-element"; + document.body.appendChild(this.highlightBox); + highlightContainer.appendChild(this.blackoutBox); - // bind mouse delegate events - document.body.addEventListener("mousemove", this.mouseMoveEvent, false); - document.body.addEventListener("click", this.mouseClickEvent, false); + document.body.appendChild(highlightContainer); - } else { - // still loading html2canvas - var args = arguments, - $this = this; + // bind mouse delegate events + document.body.addEventListener("mousemove", this.mouseMoveEvent, false); + document.body.addEventListener("click", this.mouseClickEvent, false); - if ( nextButton.disabled !== true) { - this.dom.appendChild( loader() ); + } 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); } - nextButton.disabled = true; + }; + + window.Feedback.Screenshot.prototype.render = function () { - window.setTimeout(function(){ - $this.start.apply( $this, args ); - }, 500); - } + this.dom = document.createElement("div"); -}; + // execute the html2canvas script + var script, + $this = this, + options = this.options, + runH2c = function () { + try { -window.Feedback.Screenshot.prototype.render = function() { + options.onrendered = options.onrendered || function (canvas) { + $this.h2cCanvas = canvas; + $this.h2cDone = true; + }; - this.dom = document.createElement("div"); + window.html2canvas([document.body], options); - // execute the html2canvas script - var script, - $this = this, - options = this.options, - runH2c = function(){ - try { + } catch (e) { - options.onrendered = options.onrendered || function( canvas ) { - $this.h2cCanvas = canvas; - $this.h2cDone = true; + $this.h2cDone = true; + log("Error in html2canvas: " + e.message); + } }; - window.html2canvas([ document.body ], options); + if (window.html2canvas === undefined && script === undefined) { - } catch( e ) { + // let's load html2canvas library while user is writing message - $this.h2cDone = true; - log("Error in html2canvas: " + e.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"); + }; - if ( window.html2canvas === undefined && script === undefined ) { + script.onload = (scriptLoader)(script, function () { - // let's load html2canvas library while user is writing message + if (window.html2canvas === undefined) { + log("Loaded html2canvas, but library not found"); + return; + } - 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"); - }; + window.html2canvas.logging = window.Feedback.debug; + runH2c(); - script.onload = (scriptLoader)(script, function() { - if (window.html2canvas === undefined) { - log("Loaded html2canvas, but library not found"); - return; - } + }); + + var s = document.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(script, s); - window.html2canvas.logging = window.Feedback.debug; + } else { + // html2canvas already loaded, just run it then runH2c(); + } + return this; + }; - }); + window.Feedback.Screenshot.prototype.data = function () { - var s = document.getElementsByTagName('script')[0]; - s.parentNode.insertBefore(script, s); + if (this.h2cCanvas !== undefined) { - } else { - // html2canvas already loaded, just run it then - runH2c(); - } + var ctx = this.h2cCanvas.getContext("2d"), + canvasCopy, + copyCtx, + radius = 5; + ctx.fillStyle = "#000"; - return this; -}; - -window.Feedback.Screenshot.prototype.data = function() { - - 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); - + // draw blackouts + Array.prototype.slice.call(document.getElementsByClassName('feedback-blackedout'), 0).forEach(function (item) { + item.style.position = "fixed"; + var bounds = getBounds(item); + ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height); + item.style.position = "absolute"; }); - ctx.closePath(); - ctx.clip(); - ctx.globalAlpha = 1; + // 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) {} - 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) ); + 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))); + }; - - 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 ); +})(window, document); \ No newline at end of file From bbc21f95da4b8fdadc327fdd6a5b8a91f7933b9c Mon Sep 17 00:00:00 2001 From: lucianmoldovan Date: Wed, 8 Oct 2014 09:50:55 +0300 Subject: [PATCH 3/5] Revert "Black boxes positioned incorrectly after capture" This reverts commit 70febaf3514c80e9c06cd4c70446a2fe9f97564d. --- feedback.js | 1366 +++++++++++++++++++++++++-------------------------- 1 file changed, 675 insertions(+), 691 deletions(-) diff --git a/feedback.js b/feedback.js index 66b3c3e..dd08db5 100644 --- a/feedback.js +++ b/feedback.js @@ -5,879 +5,863 @@ 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")); +(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 ); } - 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); - + } + } +}, +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 { - log("ERROR: We can't track when script is loaded"); + // it is loaded + func(); } + }; - } 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"; + window.setTimeout( intervalFunc, 250 ); - options.messageSuccess = options.messageSuccess || "Your feedback was sent succesfully."; - options.messageError = options.messageError || "There was an error sending your feedback to the server."; + } else { + log("ERROR: We can't track when script is loaded"); + } + } else { + return func; + } - if (options.pages === undefined) { - options.pages = [ +}, +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.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; - } - + } - } + 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"); - modalFooter.className = "feedback-footer"; - modalFooter.appendChild(nextButton); + modal = document.createElement("div"); + document.body.appendChild( glass ); + // modal close button + a.className = "feedback-close"; + a.onclick = returnMethods.close; + a.href = "#"; - modal.className = "feedback-modal"; - modal.setAttribute(H2C_IGNORE, true); // don't render in html2canvas + button.disabled = true; + // build header element + modalHeader.appendChild( a ); + modalHeader.appendChild( element("h3", options.header ) ); + modalHeader.className = "feedback-header"; - modal.appendChild(modalHeader); - modal.appendChild(modalBody); - modal.appendChild(modalFooter); + modalBody.className = "feedback-body"; - document.body.appendChild(modal); - }, + emptyElements( modalBody ); + currentPage = 0; + modalBody.appendChild( options.pages[ currentPage++ ].dom ); - // close modal window - close: function () { + // Next button + nextButton = element( "button", options.nextLabel ); - button.disabled = false; + 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 ); - // remove feedback elements - removeElements([modal, glass]); + if ( currentPage === len ) { + returnMethods.send( options.adapter ); + } else { - // call end event for current page - if (currentPage > 0) { - options.pages[currentPage - 1].end(modal); + 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 ); - // call close events for all pages - for (var i = 0, len = options.pages.length; i < len; i++) { - options.pages[i].close(); + // if last page, change button label to send + if ( currentPage === len ) { + nextButton.firstChild.nodeValue = options.sendLabel; } - - 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"); + + // if next page is review page, change button label + if ( options.pages[ currentPage ] instanceof window.Feedback.Review ) { + nextButton.firstChild.nodeValue = options.reviewLabel; } + - // 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()); + modalFooter.className = "feedback-footer"; + modalFooter.appendChild( nextButton ); - // send data to adapter for processing - adapter.send(data, function (success) { - emptyElements(modalBody); - nextButton.disabled = false; + modal.className = "feedback-modal"; + modal.setAttribute(H2C_IGNORE, true); // don't render in html2canvas - nextButton.firstChild.nodeValue = options.closeLabel; - nextButton.onclick = function () { - returnMethods.close(); - return false; - }; + modal.appendChild( modalHeader ); + modal.appendChild( modalBody ); + modal.appendChild( modalFooter ); - if (success === true) { - modalBody.appendChild(document.createTextNode(options.messageSuccess)); - } else { - modalBody.appendChild(document.createTextNode(options.messageError)); - } + document.body.appendChild( modal ); + }, - }); - } - }; + // close modal window + close: function() { - glass.className = "feedback-glass"; - glass.style.pointerEvents = "none"; - glass.setAttribute(H2C_IGNORE, true); + button.disabled = false; - options = options || {}; + // remove feedback elements + removeElements( [ modal, glass ] ); - button = element("button", options.label); - button.className = "feedback-btn feedback-bottom-right"; + // 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(); + } - button.setAttribute(H2C_IGNORE, true); + return false; - button.onclick = returnMethods.open; + }, + + // 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; + } + } - if (options.appendTo !== null) { - ((options.appendTo !== undefined) ? options.appendTo : document.body).appendChild(button); + 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 ) ); + } + + } ); + } - - 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 = { + glass.className = "feedback-glass"; + glass.style.pointerEvents = "none"; + glass.setAttribute(H2C_IGNORE, true); - send: function () {} + options = options || {}; - }; + button = element( "button", options.label ); + button.className = "feedback-btn feedback-bottom-right"; - window.Feedback.Form = function (elements) { + button.setAttribute(H2C_IGNORE, true); - this.elements = elements || [{ - type: "textarea", - name: "Issue", - label: "Please describe the issue you are experiencing", - required: false + 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"); + this.dom = document.createElement("div"); - }; +}; - window.Feedback.Form.prototype = new window.Feedback.Page(); +window.Feedback.Form.prototype = new window.Feedback.Page(); - window.Feedback.Form.prototype.render = function () { +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]; + var i = 0, len = this.elements.length, item; + emptyElements( this.dom ); + for (; i < len; i++) { + item = this.elements[ i ]; - switch (item.type) { + switch( item.type ) { case "textarea": - this.dom.appendChild(element("label", item.label + ":" + ((item.required === true) ? " *" : ""))); - this.dom.appendChild((item.element = document.createElement("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; - - }; + return this; - window.Feedback.Form.prototype.data = function () { +}; - var i = 0, - len = this.elements.length, - item, data = {}; +window.Feedback.Form.prototype.end = function() { + // form validation + var i = 0, len = this.elements.length, item; + for (; i < len; i++) { + item = this.elements[ i ]; - for (; i < len; i++) { - item = this.elements[i]; - data[item.name] = item.element.value; + // 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 = ""; } - - // 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 true; + +}; + +window.Feedback.Form.prototype.data = function() { + + 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() { - return dom; - - }; - window.Feedback.Review = function () { - - this.dom = document.createElement("div"); - this.dom.className = "feedback-review"; - - }; - - window.Feedback.Review.prototype = new window.Feedback.Page(); + this.dom = document.createElement("div"); + this.dom.className = "feedback-review"; - window.Feedback.Review.prototype.render = function (pages) { +}; - var i = 0, - len = pages.length, - item; - emptyElements(this.dom); +window.Feedback.Review.prototype = new window.Feedback.Page(); - for (; i < len; i++) { +window.Feedback.Review.prototype.render = function( pages ) { - // get preview DOM items - pages[i].review(this.dom); + 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; + return this; - }; +}; - window.Feedback.Screenshot = function (options) { - this.options = options || {}; +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.options.blackoutClass = this.options.blackoutClass || 'feedback-blackedout'; + this.options.highlightClass = this.options.highlightClass || 'feedback-highlighted'; - this.h2cDone = false; - }; + this.h2cDone = false; +}; - window.Feedback.Screenshot.prototype = new window.Feedback.Page(); +window.Feedback.Screenshot.prototype = new window.Feedback.Page(); - window.Feedback.Screenshot.prototype.end = function (modal) { - modal.className = modal.className.replace(/feedback\-animate\-toside/, ""); +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); + // remove event listeners + document.body.removeEventListener("mousemove", this.mouseMoveEvent, false); + document.body.removeEventListener("click", this.mouseClickEvent, false); - removeElements([this.h2cCanvas]); + removeElements( [this.h2cCanvas] ); - this.h2cDone = false; + this.h2cDone = false; - }; +}; - window.Feedback.Screenshot.prototype.close = function () { - removeElements([this.blackoutBox, this.highlightContainer, this.highlightBox, this.highlightClose]); +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)); + 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.Screenshot.prototype.start = function( modal, modalHeader, modalFooter, nextButton ) { - var $this = this, - feedbackHighlightElement = "feedback-highlight-element", - dataExclude = "data-exclude"; + if ( this.h2cDone ) { + emptyElements( this.dom ); + nextButton.disabled = false; + + var $this = this, + feedbackHighlightElement = "feedback-highlight-element", + dataExclude = "data-exclude"; - var action = true; + var action = true; - // delegate mouse move event for body - this.mouseMoveEvent = function (e) { + // 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)) { + // 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); + 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); + left = Math.min( left, window.innerWidth - 15 ); - var top = (parseInt(e.target.style.top, 10)); - top = Math.max(top, 10); + 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; - } + 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; - } + // 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(); + hideClose(); - if (e.target !== previousElement) { - previousElement = e.target; + if (e.target !== previousElement ) { + previousElement = e.target; - window.clearTimeout(timer); + window.clearTimeout( timer ); - timer = window.setTimeout(function () { - var bounds = getBounds(previousElement), - item; + 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); - } + 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); + // 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) { + // delegate event for body click + this.mouseClickEvent = function( e ){ - e.preventDefault(); + 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; + 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") { + 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'); + highlightBox.className += " " + $this.options.highlightClass; + highlightBox.className = highlightBox.className.replace(/feedback\-highlight\-element/g,""); + $this.highlightBox = highlightBox = document.createElement('canvas'); - ctx = highlightBox.getContext("2d"); + ctx = highlightBox.getContext("2d"); - highlightBox.className += " " + feedbackHighlightElement; + highlightBox.className += " " + feedbackHighlightElement; - document.body.appendChild(highlightBox); - clearBox(); - previousElement = undefined; - } + 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"; + 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,""); + } - }, - blackoutButton = element("a", "Blackout"), - highlightButton = element("a", "Highlight"), - previousElement; + 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"; - modal.className += ' feedback-animate-toside'; + }, + blackoutButton = element("a", "Blackout"), + highlightButton = element("a", "Highlight"), + previousElement; - highlightClose.id = "feedback-highlight-close"; + modal.className += ' feedback-animate-toside'; - highlightClose.addEventListener("click", function () { - removeElement.parentNode.removeChild(removeElement); - hideClose(); - }, false); + highlightClose.id = "feedback-highlight-close"; - document.body.appendChild(highlightClose); + highlightClose.addEventListener("click", function(){ + removeElement.parentNode.removeChild( removeElement ); + hideClose(); + }, false); - this.h2cCanvas.className = 'feedback-canvas'; - document.body.appendChild(this.h2cCanvas); + document.body.appendChild( highlightClose ); - var buttonItem = [highlightButton, blackoutButton]; + this.h2cCanvas.className = 'feedback-canvas'; + document.body.appendChild( this.h2cCanvas); - 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'); + var buttonItem = [ highlightButton, blackoutButton ]; - buttonItem[i].href = "#"; - buttonItem[i].onclick = buttonClickFunction; + this.dom.appendChild( element("p", "Highlight or blackout important information") ); - this.dom.appendChild(buttonItem[i]); + // 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'); - this.dom.appendChild(document.createTextNode(" ")); + 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); + highlightContainer.id = "feedback-highlight-container"; + highlightContainer.style.width = this.h2cCanvas.width + "px"; + highlightContainer.style.height = this.h2cCanvas.height + "px"; - // bind mouse delegate events - document.body.addEventListener("mousemove", this.mouseMoveEvent, false); - document.body.addEventListener("click", this.mouseClickEvent, false); + this.highlightBox.className += " " + feedbackHighlightElement; + this.blackoutBox.id = "feedback-blackout-element"; + document.body.appendChild( this.highlightBox ); + highlightContainer.appendChild( this.blackoutBox ); - } else { - // still loading html2canvas - var args = arguments, - $this = this; + document.body.appendChild( highlightContainer ); - if (nextButton.disabled !== true) { - this.dom.appendChild(loader()); - } + // bind mouse delegate events + document.body.addEventListener("mousemove", this.mouseMoveEvent, false); + document.body.addEventListener("click", this.mouseClickEvent, false); - nextButton.disabled = true; + } else { + // still loading html2canvas + var args = arguments, + $this = this; - window.setTimeout(function () { - $this.start.apply($this, args); - }, 500); + if ( nextButton.disabled !== true) { + this.dom.appendChild( loader() ); } - }; - - window.Feedback.Screenshot.prototype.render = function () { + nextButton.disabled = true; - this.dom = document.createElement("div"); + window.setTimeout(function(){ + $this.start.apply( $this, args ); + }, 500); + } - // 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.Feedback.Screenshot.prototype.render = function() { - window.html2canvas([document.body], options); + this.dom = document.createElement("div"); - } catch (e) { + // execute the html2canvas script + var script, + $this = this, + options = this.options, + runH2c = function(){ + try { - $this.h2cDone = true; - log("Error in html2canvas: " + e.message); - } + options.onrendered = options.onrendered || function( canvas ) { + $this.h2cCanvas = canvas; + $this.h2cDone = true; }; - if (window.html2canvas === undefined && script === undefined) { + window.html2canvas([ document.body ], options); - // let's load html2canvas library while user is writing message + } catch( e ) { - 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(); + $this.h2cDone = true; + log("Error in html2canvas: " + e.message); } - - return this; }; - window.Feedback.Screenshot.prototype.data = function () { - - 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) { - item.style.position = "fixed"; - var bounds = getBounds(item); - ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height); - item.style.position = "absolute"; - }); + if ( window.html2canvas === undefined && script === undefined ) { - // draw highlights - var items = Array.prototype.slice.call(document.getElementsByClassName('feedback-highlighted'), 0); + // let's load html2canvas library while user is writing message - 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); + 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"); + }; - ctx.beginPath(); + script.onload = (scriptLoader)(script, function() { - items.forEach(function (item) { + if (window.html2canvas === undefined) { + log("Loaded html2canvas, but library not found"); + return; + } - 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); + window.html2canvas.logging = window.Feedback.debug; + runH2c(); - 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; + var s = document.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(script, s); - ctx.drawImage(canvasCopy, 0, 0); + } else { + // html2canvas already loaded, just run it then + runH2c(); + } - } + return this; +}; + +window.Feedback.Screenshot.prototype.data = function() { + + 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(); - // to avoid security error break for tainted canvas - try { - // cache and return data - return (this._data = this.h2cCanvas.toDataURL()); - } catch (e) {} + 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.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) ); } - - }; - 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); \ No newline at end of file + + 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 ); From 6789f69d5eb4958686504dc7ea1385dbe2e42fd1 Mon Sep 17 00:00:00 2001 From: lucianmoldovan Date: Wed, 8 Oct 2014 09:55:06 +0300 Subject: [PATCH 4/5] Black boxes positioned incorrectly after capture Black boxes were positioned incorrectly after screen capture on a page with scroll, this commit fixes it. --- feedback.js | 1366 ++++++++++++++++++++++++++------------------------- 1 file changed, 691 insertions(+), 675 deletions(-) diff --git a/feedback.js b/feedback.js index dd08db5..66b3c3e 100644 --- a/feedback.js +++ b/feedback.js @@ -5,863 +5,879 @@ 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 ); - } - } +(function (window, document, undefined) { + if (window.Feedback !== undefined) { + return; } -}, -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 ); + + // 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 { - // it is loaded - func(); + log("ERROR: We can't track when script is loaded"); } - }; - window.setTimeout( intervalFunc, 250 ); + } else { + return func; + } - } else { - log("ERROR: We can't track when script is loaded"); - } + }, + nextButton, + H2C_IGNORE = "data-html2canvas-ignore", + currentPage, + modalBody = document.createElement("div"); - } else { - return func; - } + window.Feedback = function (options) { + + options = options || {}; -}, -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 = [ + // 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.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 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"); + var a = element("a", "×"), + modalHeader = document.createElement("div"), + // modal container + modalFooter = document.createElement("div"); - modal = document.createElement("div"); - document.body.appendChild( glass ); + modal = document.createElement("div"); + document.body.appendChild(glass); - // modal close button - a.className = "feedback-close"; - a.onclick = returnMethods.close; - a.href = "#"; + // modal close button + a.className = "feedback-close"; + a.onclick = returnMethods.close; + a.href = "#"; - button.disabled = true; + button.disabled = true; - // build header element - modalHeader.appendChild( a ); - modalHeader.appendChild( element("h3", options.header ) ); - modalHeader.className = "feedback-header"; + // build header element + modalHeader.appendChild(a); + modalHeader.appendChild(element("h3", options.header)); + modalHeader.className = "feedback-header"; - modalBody.className = "feedback-body"; + modalBody.className = "feedback-body"; - emptyElements( modalBody ); - currentPage = 0; - modalBody.appendChild( options.pages[ currentPage++ ].dom ); + emptyElements(modalBody); + currentPage = 0; + modalBody.appendChild(options.pages[currentPage++].dom); - // Next button - nextButton = element( "button", options.nextLabel ); + // 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 ); + nextButton.className = "feedback-btn"; + nextButton.onclick = function () { - if ( currentPage === len ) { - returnMethods.send( options.adapter ); - } else { + 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); - 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 ); + 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); } - - // 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; + // call close events for all pages + for (var i = 0, len = options.pages.length; i < len; i++) { + options.pages[i].close(); } - - // if next page is review page, change button label - if ( options.pages[ currentPage ] instanceof window.Feedback.Review ) { - nextButton.firstChild.nodeValue = options.reviewLabel; + + 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; - modalFooter.className = "feedback-footer"; - modalFooter.appendChild( nextButton ); + emptyElements(modalBody); + modalBody.appendChild(loader()); + // send data to adapter for processing + adapter.send(data, function (success) { - modal.className = "feedback-modal"; - modal.setAttribute(H2C_IGNORE, true); // don't render in html2canvas + emptyElements(modalBody); + nextButton.disabled = false; + nextButton.firstChild.nodeValue = options.closeLabel; - modal.appendChild( modalHeader ); - modal.appendChild( modalBody ); - modal.appendChild( modalFooter ); + nextButton.onclick = function () { + returnMethods.close(); + return false; + }; - document.body.appendChild( modal ); - }, + if (success === true) { + modalBody.appendChild(document.createTextNode(options.messageSuccess)); + } else { + modalBody.appendChild(document.createTextNode(options.messageError)); + } + }); - // close modal window - close: function() { + } + }; - button.disabled = false; + glass.className = "feedback-glass"; + glass.style.pointerEvents = "none"; + glass.setAttribute(H2C_IGNORE, true); - // remove feedback elements - removeElements( [ modal, glass ] ); + options = options || {}; - // 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(); - } + button = element("button", options.label); + button.className = "feedback-btn feedback-bottom-right"; - return false; + button.setAttribute(H2C_IGNORE, true); - }, - - // 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; - } - } + button.onclick = returnMethods.open; - 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 ) ); - } - - } ); - + 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; + } - glass.className = "feedback-glass"; - glass.style.pointerEvents = "none"; - glass.setAttribute(H2C_IGNORE, true); + }; + window.Feedback.Send = function () {}; + window.Feedback.Send.prototype = { - options = options || {}; + send: function () {} - button = element( "button", options.label ); - button.className = "feedback-btn feedback-bottom-right"; + }; - button.setAttribute(H2C_IGNORE, true); + window.Feedback.Form = function (elements) { - 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.elements = elements || [{ + type: "textarea", + name: "Issue", + label: "Please describe the issue you are experiencing", + required: false }]; - this.dom = document.createElement("div"); + this.dom = document.createElement("div"); -}; + }; -window.Feedback.Form.prototype = new window.Feedback.Page(); + window.Feedback.Form.prototype = new window.Feedback.Page(); -window.Feedback.Form.prototype.render = function() { + 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 ]; + var i = 0, + len = this.elements.length, + item; + emptyElements(this.dom); + for (; i < len; i++) { + item = this.elements[i]; - switch( item.type ) { + switch (item.type) { case "textarea": - this.dom.appendChild( element("label", item.label + ":" + (( item.required === true ) ? " *" : "")) ); - this.dom.appendChild( ( item.element = document.createElement("textarea")) ); + this.dom.appendChild(element("label", item.label + ":" + ((item.required === true) ? " *" : ""))); + this.dom.appendChild((item.element = document.createElement("textarea"))); break; + } } - } - return this; + 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 = ""; + 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() { - - 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 true; + + }; + + window.Feedback.Form.prototype.data = function () { + + var i = 0, + len = this.elements.length, + item, data = {}; + + for (; i < len; i++) { + item = this.elements[i]; + data[item.name] = item.element.value; } - - } - - return dom; - -}; -window.Feedback.Review = function() { - this.dom = document.createElement("div"); - this.dom.className = "feedback-review"; + // cache and return data + return (this._data = data); + }; -}; -window.Feedback.Review.prototype = new window.Feedback.Page(); + window.Feedback.Form.prototype.review = function (dom) { -window.Feedback.Review.prototype.render = function( pages ) { + var i = 0, + item, len = this.elements.length; - var i = 0, len = pages.length, item; - emptyElements( this.dom ); - - for (; i < len; i++) { - - // get preview DOM items - pages[ i ].review( this.dom ); + 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 this; + } -}; + return dom; + }; + window.Feedback.Review = function () { + this.dom = document.createElement("div"); + this.dom.className = "feedback-review"; + }; -window.Feedback.Screenshot = function( options ) { - this.options = options || {}; + window.Feedback.Review.prototype = new window.Feedback.Page(); - this.options.blackoutClass = this.options.blackoutClass || 'feedback-blackedout'; - this.options.highlightClass = this.options.highlightClass || 'feedback-highlighted'; + window.Feedback.Review.prototype.render = function (pages) { - this.h2cDone = false; -}; + var i = 0, + len = pages.length, + item; + emptyElements(this.dom); -window.Feedback.Screenshot.prototype = new window.Feedback.Page(); + for (; i < len; i++) { -window.Feedback.Screenshot.prototype.end = function( modal ){ - modal.className = modal.className.replace(/feedback\-animate\-toside/, ""); + // get preview DOM items + pages[i].review(this.dom); - // remove event listeners - document.body.removeEventListener("mousemove", this.mouseMoveEvent, false); - document.body.removeEventListener("click", this.mouseClickEvent, false); + } - removeElements( [this.h2cCanvas] ); + return this; - 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 = function (options) { + this.options = options || {}; -window.Feedback.Screenshot.prototype.start = function( modal, modalHeader, modalFooter, nextButton ) { + this.options.blackoutClass = this.options.blackoutClass || 'feedback-blackedout'; + this.options.highlightClass = this.options.highlightClass || 'feedback-highlighted'; - if ( this.h2cDone ) { - emptyElements( this.dom ); - nextButton.disabled = false; - - var $this = this, - feedbackHighlightElement = "feedback-highlight-element", - dataExclude = "data-exclude"; + this.h2cDone = false; + }; - var action = true; + window.Feedback.Screenshot.prototype = new window.Feedback.Page(); - // delegate mouse move event for body - this.mouseMoveEvent = function( e ) { + window.Feedback.Screenshot.prototype.end = function (modal) { + modal.className = modal.className.replace(/feedback\-animate\-toside/, ""); - // set close button - if ( e.target !== previousElement && (e.target.className.indexOf( $this.options.blackoutClass ) !== -1 || e.target.className.indexOf( $this.options.highlightClass ) !== -1)) { + // remove event listeners + document.body.removeEventListener("mousemove", this.mouseMoveEvent, false); + document.body.removeEventListener("click", this.mouseClickEvent, false); - var left = (parseInt(e.target.style.left, 10) + parseInt(e.target.style.width, 10)); - left = Math.max( left, 10 ); + removeElements([this.h2cCanvas]); - left = Math.min( left, window.innerWidth - 15 ); + this.h2cDone = false; - 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; - } + window.Feedback.Screenshot.prototype.close = function () { + removeElements([this.blackoutBox, this.highlightContainer, this.highlightBox, this.highlightClose]); - // 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; - } + removeElements(document.getElementsByClassName(this.options.blackoutClass)); + removeElements(document.getElementsByClassName(this.options.highlightClass)); - hideClose(); + }; - if (e.target !== previousElement ) { - previousElement = e.target; + window.Feedback.Screenshot.prototype.start = function (modal, modalHeader, modalFooter, nextButton) { - window.clearTimeout( timer ); + if (this.h2cDone) { + emptyElements(this.dom); + nextButton.disabled = false; - timer = window.setTimeout(function(){ - var bounds = getBounds( previousElement ), - item; + var $this = this, + feedbackHighlightElement = "feedback-highlight-element", + dataExclude = "data-exclude"; - 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 ); - } + var action = true; - // 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 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; + } - // delegate event for body click - this.mouseClickEvent = function( e ){ + hideClose(); - e.preventDefault(); + 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); - 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; + // 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,""); - } + 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); + action = !action; + }, + clearBox = function () { - 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"; + clearBoxEl(blackoutBox); + clearBoxEl(highlightBox); - }, - blackoutButton = element("a", "Blackout"), - highlightButton = element("a", "Highlight"), - previousElement; + 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'; + modal.className += ' feedback-animate-toside'; - highlightClose.id = "feedback-highlight-close"; + highlightClose.id = "feedback-highlight-close"; - highlightClose.addEventListener("click", function(){ - removeElement.parentNode.removeChild( removeElement ); - hideClose(); - }, false); - document.body.appendChild( highlightClose ); + highlightClose.addEventListener("click", function () { + removeElement.parentNode.removeChild(removeElement); + hideClose(); + }, false); + document.body.appendChild(highlightClose); - this.h2cCanvas.className = 'feedback-canvas'; - document.body.appendChild( this.h2cCanvas); + this.h2cCanvas.className = 'feedback-canvas'; + document.body.appendChild(this.h2cCanvas); - var buttonItem = [ highlightButton, blackoutButton ]; - this.dom.appendChild( element("p", "Highlight or blackout important information") ); + var buttonItem = [highlightButton, blackoutButton]; - // 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'); + this.dom.appendChild(element("p", "Highlight or blackout important information")); - buttonItem[ i ].href = "#"; - buttonItem[ i ].onclick = buttonClickFunction; + // 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'); - this.dom.appendChild( buttonItem[ i ] ); + buttonItem[i].href = "#"; + buttonItem[i].onclick = buttonClickFunction; - this.dom.appendChild( document.createTextNode(" ") ); + 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 ); + highlightContainer.id = "feedback-highlight-container"; + highlightContainer.style.width = this.h2cCanvas.width + "px"; + highlightContainer.style.height = this.h2cCanvas.height + "px"; - document.body.appendChild( highlightContainer ); + this.highlightBox.className += " " + feedbackHighlightElement; + this.blackoutBox.id = "feedback-blackout-element"; + document.body.appendChild(this.highlightBox); + highlightContainer.appendChild(this.blackoutBox); - // bind mouse delegate events - document.body.addEventListener("mousemove", this.mouseMoveEvent, false); - document.body.addEventListener("click", this.mouseClickEvent, false); + document.body.appendChild(highlightContainer); - } else { - // still loading html2canvas - var args = arguments, - $this = this; + // bind mouse delegate events + document.body.addEventListener("mousemove", this.mouseMoveEvent, false); + document.body.addEventListener("click", this.mouseClickEvent, false); - if ( nextButton.disabled !== true) { - this.dom.appendChild( loader() ); + } 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); } - nextButton.disabled = true; + }; + + window.Feedback.Screenshot.prototype.render = function () { - window.setTimeout(function(){ - $this.start.apply( $this, args ); - }, 500); - } + this.dom = document.createElement("div"); -}; + // execute the html2canvas script + var script, + $this = this, + options = this.options, + runH2c = function () { + try { -window.Feedback.Screenshot.prototype.render = function() { + options.onrendered = options.onrendered || function (canvas) { + $this.h2cCanvas = canvas; + $this.h2cDone = true; + }; - this.dom = document.createElement("div"); + window.html2canvas([document.body], options); - // execute the html2canvas script - var script, - $this = this, - options = this.options, - runH2c = function(){ - try { + } catch (e) { - options.onrendered = options.onrendered || function( canvas ) { - $this.h2cCanvas = canvas; - $this.h2cDone = true; + $this.h2cDone = true; + log("Error in html2canvas: " + e.message); + } }; - window.html2canvas([ document.body ], options); + if (window.html2canvas === undefined && script === undefined) { - } catch( e ) { + // let's load html2canvas library while user is writing message - $this.h2cDone = true; - log("Error in html2canvas: " + e.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"); + }; - if ( window.html2canvas === undefined && script === undefined ) { + script.onload = (scriptLoader)(script, function () { - // let's load html2canvas library while user is writing message + if (window.html2canvas === undefined) { + log("Loaded html2canvas, but library not found"); + return; + } - 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"); - }; + window.html2canvas.logging = window.Feedback.debug; + runH2c(); - script.onload = (scriptLoader)(script, function() { - if (window.html2canvas === undefined) { - log("Loaded html2canvas, but library not found"); - return; - } + }); + + var s = document.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(script, s); - window.html2canvas.logging = window.Feedback.debug; + } else { + // html2canvas already loaded, just run it then runH2c(); + } + return this; + }; - }); + window.Feedback.Screenshot.prototype.data = function () { - var s = document.getElementsByTagName('script')[0]; - s.parentNode.insertBefore(script, s); + if (this.h2cCanvas !== undefined) { - } else { - // html2canvas already loaded, just run it then - runH2c(); - } + var ctx = this.h2cCanvas.getContext("2d"), + canvasCopy, + copyCtx, + radius = 5; + ctx.fillStyle = "#000"; - return this; -}; - -window.Feedback.Screenshot.prototype.data = function() { - - 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); - + // draw blackouts + Array.prototype.slice.call(document.getElementsByClassName('feedback-blackedout'), 0).forEach(function (item) { + item.style.position = "fixed"; + var bounds = getBounds(item); + ctx.fillRect(bounds.left, bounds.top, bounds.width, bounds.height); + item.style.position = "absolute"; }); - ctx.closePath(); - ctx.clip(); - ctx.globalAlpha = 1; + // 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) {} - 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) ); + 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))); + }; - - 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 ); +})(window, document); \ No newline at end of file From 1b3b226f10e8a211cd006e6b05324d8fe2a0e367 Mon Sep 17 00:00:00 2001 From: lucianmoldovan Date: Fri, 10 Oct 2014 16:17:16 +0300 Subject: [PATCH 5/5] Modal content is being captured fix --- feedback.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/feedback.js b/feedback.js index 66b3c3e..70f988a 100644 --- a/feedback.js +++ b/feedback.js @@ -512,7 +512,18 @@ } // 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) { + if (e.target.nodeName === "BODY" || + e.target === highlightClose || + e.target === modal || + e.target === modalHeader || + e.target.parentNode === modalHeader || + e.target === modalBody || + e.target.parentNode === modalBody || + e.target.parentNode.parentNode === modalBody || + e.target === modalFooter || + e.target.parentNode === modalFooter || + e.target === nextButton + ) { // we are not gonna blackout the whole page or the close item clearBox(); previousElement = e.target;