feedbackandscreencap 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.DS_Store
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p125
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in feedbackandscreencap.gemspec
4
+ gemspec
5
+ gem 'rspec'
6
+ gem 'rails'
7
+ gem 'rspec-rails'
8
+ gem 'simplecov'
9
+ gem 'shoulda-matchers'
10
+ gem 'sqlite3'
11
+ gem 'feedbackandscreencap', :path => '../.'
12
+ #gem 'actioncontroller'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Alberta Motor Association
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # FeedbackAndScreencap
2
+
3
+ This gem displays the feedback.js feedback button and processes the http post to create a new feedback record with text and image.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'feedback_and_screencap'
10
+
11
+ And then execute:
12
+
13
+ $ bundle install
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install feedback_and_screencap
18
+
19
+ Once it is installed, you need to do one thing:
20
+
21
+ * your html body tag needs to be modified:
22
+
23
+ <body onload="Feedback.init()">
24
+
25
+
26
+ ## Usage
27
+
28
+ rails g feedbackandscreencap
29
+
30
+ this create a migration and copy over the views, controller, model and assets
31
+
32
+ ##Contributing to feedback.js
33
+ Feedback.js is project by Niklas von Hertzen (https://github.com/niklasvh) and any issuesw with the javascript libraries or css should be directed to him via: https://github.com/niklasvh/feedback.js/
34
+
35
+ ## Contributing to the Rails Gem
36
+ 1. Fork it
37
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
38
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
39
+ 4. Push to the branch (`git push origin my-new-feature`)
40
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,664 @@
1
+ (function( win, doc, undefined ){
2
+
3
+ if ( win.Feedback !== undefined ) {
4
+ return;
5
+ }
6
+
7
+ // log proxy function
8
+ var log = function( msg ) {
9
+ win.console.log( msg );
10
+ },
11
+ // function to remove elements, input as arrays
12
+ removeElements = function( remove ) {
13
+ for (var i = 0, len = remove.length; i < len; i++ ) {
14
+ var item = Array.prototype.pop.call( remove );
15
+ if ( item !== undefined ) {
16
+ if (item.parentNode !== null ) { // check that the item was actually added to DOM
17
+ item.parentNode.removeChild( item );
18
+ }
19
+ }
20
+ }
21
+ },
22
+ getBounds = function( el ) {
23
+ return el.getBoundingClientRect();
24
+ },
25
+ emptyElements = function( el ) {
26
+ var item;
27
+ while( (( item = el.firstChild ) !== null ? el.removeChild( item ) : false) ) {}
28
+ },
29
+ element = function( name, text ) {
30
+ var el = doc.createElement( name );
31
+ el.appendChild( doc.createTextNode( text ) );
32
+ return el;
33
+ },
34
+ // script onload function to provide support for IE as well
35
+ scriptLoader = function( script, func ){
36
+
37
+ if (script.onload === undefined) {
38
+ // IE lack of support for script onload
39
+
40
+ if( script.onreadystatechange !== undefined ) {
41
+
42
+ var intervalFunc = function() {
43
+ if (script.readyState !== "loaded" && script.readyState !== "complete") {
44
+ win.setTimeout( intervalFunc, 250 );
45
+ } else {
46
+ // it is loaded
47
+ func();
48
+ }
49
+
50
+
51
+ };
52
+
53
+ win.setTimeout( intervalFunc, 250 );
54
+
55
+ } else {
56
+ log("ERROR: We can't track when script is loaded");
57
+ }
58
+
59
+ } else {
60
+ return func;
61
+ }
62
+
63
+ },
64
+ h2cQueue,
65
+ h2cCanvas,
66
+ h2cDone = false,
67
+ clickDone = false,
68
+ fcAvailable,
69
+ html2obj,
70
+ canvasHolder,
71
+ blackoutBox,
72
+ highlightBox,
73
+ highlightClose,
74
+ nextButton,
75
+ body,
76
+ CANVAS = "canvas",
77
+ DIV = "div",
78
+ H2C_IGNORE = "data-html2canvas-ignore",
79
+ PX = "px",
80
+ highlightContainer,
81
+ mouseMoveEvent,
82
+ modalBody,
83
+ message,
84
+ mouseClickEvent,
85
+ dataExclude = "data-exclude",
86
+ h2cAvailable;
87
+
88
+ win.Feedback = {
89
+ /**
90
+ * options
91
+ * string h2cPath
92
+ * string fcPath
93
+ * string label - Button label text, default "Send Feedback"
94
+ * string labelClass - Button class(es), default "btn bottom-right"
95
+ * string closeClass - Close class(es), default "close"
96
+ * string closeLabel
97
+ * string modalClass
98
+ * string modalHeader
99
+ * string modalHeaderClass
100
+ * string modalIntro
101
+ * string buttonClass
102
+ * Element appendTo - where to append button, default doc.body
103
+ */
104
+
105
+ debug: true,
106
+ log: log,
107
+
108
+ init: function( options ) {
109
+ var button,
110
+ modal,
111
+ body = doc.body,
112
+ glass = doc.createElement(DIV),
113
+ returnMethods = {
114
+
115
+
116
+
117
+
118
+ // open send feedback modal window
119
+ open: function() {
120
+ var modalHeader = doc.createElement(DIV),
121
+ modalFooter = doc.createElement(DIV),
122
+ script,
123
+ // execute the html2canvas script
124
+ runH2c = function(){
125
+ try {
126
+
127
+ options.onrendered = options.onrendered || function( canvas ) {
128
+ h2cCanvas = canvas;
129
+
130
+ // window.setTimeout(function(){
131
+ h2cDone = true;
132
+ nextFunc();
133
+ // }, 3000);
134
+
135
+
136
+ };
137
+
138
+ html2obj = win.html2canvas([ doc.body ], options);
139
+
140
+
141
+ } catch( e ) {
142
+
143
+ h2cDone = true;
144
+ nextFunc();
145
+ log("Error in html2canvas: " + e.message);
146
+ }
147
+ },
148
+
149
+
150
+ // Close button
151
+ a = element("a", options.closeLabel || "×");
152
+
153
+ modalBody = doc.createElement(DIV);
154
+
155
+ body.appendChild( glass );
156
+
157
+ // user message
158
+ message = doc.createElement("textarea");
159
+
160
+ if ( win.html2canvas === undefined && script === undefined ) {
161
+
162
+ // let's load html2canvas library while user is writing message
163
+
164
+ script = doc.createElement("script");
165
+ script.src = options.h2cPath || "libs/html2canvas.js";
166
+ script.onerror = function() {
167
+ h2cAvailable = false;
168
+ log("Failed to load html2canvas library, check that the path is correctly defined");
169
+ };
170
+
171
+ script.onload = (scriptLoader)(script, function() {
172
+
173
+ if (win.html2canvas === undefined) {
174
+ log("Loaded html2canvas, but library not found");
175
+ h2cAvailable = false;
176
+ return;
177
+ }
178
+
179
+ win.html2canvas.logging = win.Feedback.debug;
180
+ runH2c();
181
+
182
+ h2cAvailable = true;
183
+ });
184
+
185
+
186
+
187
+
188
+ button.parentNode.appendChild( script );
189
+ } else {
190
+ // html2canvas already loaded, just run it then
191
+ runH2c();
192
+ }
193
+
194
+ a.className = options.closeClass || "close";
195
+ a.onclick = returnMethods.close;
196
+ a.href = "#";
197
+
198
+ button.disabled = true;
199
+
200
+
201
+ modalHeader.appendChild( a );
202
+ modalHeader.appendChild( element("h3", options.modalHeader || "Send Feedback" ) );
203
+
204
+ modalHeader.className = "feedback-header";
205
+
206
+
207
+ modalBody.className = "feedback-body";
208
+
209
+ modalBody.appendChild( element("p", options.modalIntro || "Please describe the issue you are experiencing") );
210
+
211
+ modalBody.appendChild( message );
212
+
213
+
214
+ // Next button
215
+ nextButton = element( "button", options.nextLabel || "Continue" );
216
+
217
+ nextButton.className = options.buttonClass || "btn";
218
+ nextButton.onclick = function() {
219
+ clickDone = true;
220
+ nextButton.disabled = true;
221
+
222
+ emptyElements( modalBody ); // clear it of all elements
223
+
224
+ nextFunc();
225
+
226
+ };
227
+
228
+ function nextFunc() {
229
+
230
+ var progressBar,
231
+ action = true; // true highlight, false blackout
232
+
233
+ if ( clickDone ) {
234
+
235
+ nextButton.onclick = function( e ) {
236
+ e.preventDefault();
237
+
238
+ // remove event listeners
239
+ body.removeEventListener("mousemove", mouseMoveEvent, false);
240
+ body.removeEventListener("click", mouseClickEvent, false);
241
+
242
+ returnMethods.review();
243
+
244
+
245
+
246
+ };
247
+ }
248
+
249
+ // canvas ready and user has entered message
250
+ if (h2cDone && clickDone) {
251
+ nextButton.disabled = false;
252
+
253
+ var timer,
254
+ removeElement,
255
+ ctx,
256
+ buttonClickFunction = function( e ) {
257
+ e.preventDefault();
258
+ blackoutButton.classList.toggle("active");
259
+ highlightButton.classList.toggle("active");
260
+ action = !action;
261
+ },
262
+ clearBox = function() {
263
+ blackoutBox.style.left = "-5px";
264
+ blackoutBox.style.top = "-5px";
265
+ blackoutBox.style.width = "0px";
266
+ blackoutBox.style.height = "0px";
267
+ blackoutBox.setAttribute(dataExclude, true);
268
+
269
+
270
+ highlightBox.style.left = "-5px";
271
+ highlightBox.style.top = "-5px";
272
+ highlightBox.style.width = "0px";
273
+ highlightBox.style.height = "0px";
274
+ highlightBox.setAttribute(dataExclude, true);
275
+
276
+ win.clearTimeout( timer );
277
+ },
278
+ hideClose = function() {
279
+ highlightClose.style.left = "-50px";
280
+ highlightClose.style.top = "-50px";
281
+
282
+ },
283
+ blackoutButton = element("a", "Blackout"),
284
+ highlightButton = element("a", "Highlight"),
285
+ previousElement;
286
+
287
+
288
+ // delegate mouse move event for body
289
+ mouseMoveEvent = function(e) {
290
+
291
+ // set close button
292
+ if ( e.target !== previousElement && (e.target.classList.contains("feedback-blackedout") || e.target.classList.contains("feedback-highlighted"))) {
293
+
294
+ var left = (parseInt(e.target.style.left, 10) + parseInt(e.target.style.width, 10));
295
+ left = Math.max( left, 10 );
296
+
297
+ left = Math.min( left, win.innerWidth - 15 );
298
+
299
+ var top = (parseInt(e.target.style.top, 10));
300
+ top = Math.max( top, 10 );
301
+
302
+ highlightClose.style.left = left + PX;
303
+ highlightClose.style.top = top + PX;
304
+ removeElement = e.target;
305
+ clearBox();
306
+ previousElement = undefined;
307
+ return;
308
+ }
309
+
310
+ // don't do anything if we are highlighting a close button or body tag
311
+ if (e.target.nodeName === "BODY" || e.target === highlightClose || e.target === modal || e.target === nextButton || e.target.parentNode === modal || e.target.parentNode === modalHeader) {
312
+ // we are not gonna blackout the whole page or the close item
313
+ clearBox();
314
+ previousElement = e.target;
315
+ return;
316
+ }
317
+
318
+ hideClose();
319
+
320
+ if (e.target !== previousElement ) {
321
+ previousElement = e.target;
322
+
323
+ win.clearTimeout( timer );
324
+
325
+ timer = win.setTimeout(function(){
326
+ var bounds = getBounds( previousElement ),
327
+ item;
328
+
329
+ if ( action === false ) {
330
+ item = blackoutBox;
331
+ } else {
332
+ item = highlightBox;
333
+ item.width = bounds.width;
334
+ item.height = bounds.height;
335
+ ctx.drawImage(h2cCanvas, win.pageXOffset + bounds.left, win.pageYOffset + bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height );
336
+ }
337
+
338
+ // we are only targetting IE>=9, so window.pageYOffset works fine
339
+ item.setAttribute(dataExclude, false);
340
+ item.style.left = win.pageXOffset + bounds.left + PX;
341
+ item.style.top = win.pageYOffset + bounds.top + PX;
342
+ item.style.width = bounds.width + PX;
343
+ item.style.height = bounds.height + PX;
344
+ }, 100);
345
+
346
+
347
+
348
+ }
349
+
350
+
351
+ };
352
+
353
+
354
+ // delegate event for body click
355
+ mouseClickEvent = function( e ){
356
+ if (button.disabled === false) {
357
+ return;
358
+ }
359
+
360
+ e.preventDefault();
361
+
362
+
363
+ if ( action === false) {
364
+ if ( blackoutBox.getAttribute(dataExclude) === "false") {
365
+ var blackout = doc.createElement(DIV);
366
+ blackout.classList.add('feedback-blackedout');
367
+ blackout.style.left = blackoutBox.style.left;
368
+ blackout.style.top = blackoutBox.style.top;
369
+ blackout.style.width = blackoutBox.style.width;
370
+ blackout.style.height = blackoutBox.style.height;
371
+
372
+ body.appendChild( blackout );
373
+ previousElement = undefined;
374
+ }
375
+ } else {
376
+ if ( highlightBox.getAttribute(dataExclude) === "false") {
377
+
378
+ highlightBox.classList.add('feedback-highlighted');
379
+ highlightBox.classList.remove('feedback-highlight-element');
380
+ highlightBox = doc.createElement('canvas');
381
+
382
+ ctx = highlightBox.getContext("2d");
383
+
384
+ highlightBox.classList.add("feedback-highlight-element");
385
+
386
+ body.appendChild( highlightBox );
387
+ clearBox();
388
+ previousElement = undefined;
389
+ }
390
+ }
391
+
392
+
393
+
394
+ };
395
+
396
+ modal.classList.add('feedback-animate-toside');
397
+
398
+
399
+ blackoutBox = doc.createElement('div');
400
+ highlightBox = doc.createElement( CANVAS );
401
+
402
+ ctx = highlightBox.getContext("2d");
403
+
404
+
405
+ highlightClose = element(DIV, "×");
406
+ highlightContainer = doc.createElement('div');
407
+
408
+ highlightClose.id = "feedback-highlight-close";
409
+
410
+
411
+ highlightClose.addEventListener("click", function(){
412
+ removeElement.parentNode.removeChild( removeElement );
413
+ hideClose();
414
+ }, false);
415
+
416
+ body.appendChild( highlightClose );
417
+
418
+ h2cCanvas.classList.add('feedback-canvas');
419
+
420
+
421
+ body.appendChild( h2cCanvas);
422
+
423
+
424
+ var buttonItem = [ highlightButton, blackoutButton ];
425
+
426
+ modalBody.appendChild( element("p", "Highlight or blackout important information") );
427
+
428
+ // add highlight and blackout buttons
429
+ for (var i = 0; i < 2; i++ ) {
430
+ buttonItem[ i ].classList.add('btn');
431
+ buttonItem[ i ].classList.add('btn-small');
432
+ buttonItem[ i ].classList.add( i === 0 ? 'active' : 'btn-inverse');
433
+
434
+ buttonItem[ i ].href = "#";
435
+ buttonItem[ i ].onclick = buttonClickFunction;
436
+
437
+ modalBody.appendChild( buttonItem[ i ] );
438
+
439
+ modalBody.appendChild( doc.createTextNode(" ") );
440
+
441
+ }
442
+
443
+
444
+
445
+ highlightContainer.id = "feedback-highlight-container";
446
+ highlightContainer.style.width = h2cCanvas.width + PX;
447
+ highlightContainer.style.height = h2cCanvas.height + PX;
448
+
449
+ highlightBox.classList.add("feedback-highlight-element");
450
+ blackoutBox.id = "feedback-blackout-element";
451
+ body.appendChild( highlightBox );
452
+ highlightContainer.appendChild( blackoutBox );
453
+
454
+ body.appendChild( highlightContainer );
455
+
456
+ // bind mouse delegate events
457
+ body.addEventListener("mousemove", mouseMoveEvent, false);
458
+ body.addEventListener("click", mouseClickEvent, false);
459
+
460
+ } else if ( clickDone ) {
461
+
462
+ }
463
+ }
464
+
465
+
466
+
467
+ modalFooter.className = "feedback-footer";
468
+ modalFooter.appendChild( nextButton );
469
+
470
+ // modal container
471
+ modal = doc.createElement(DIV);
472
+ modal.className = options.modalClass || "modal";
473
+ modal.setAttribute(H2C_IGNORE, true); // don't render in html2canvas
474
+
475
+
476
+
477
+ modal.appendChild( modalHeader );
478
+ modal.appendChild( modalBody );
479
+ modal.appendChild( modalFooter );
480
+
481
+ button.parentNode.appendChild( modal );
482
+ },
483
+
484
+
485
+
486
+
487
+
488
+
489
+
490
+ // review the gathered data
491
+ review: function() {
492
+ modal.classList.add("feedback-animate-review");
493
+ emptyElements( modalBody );
494
+
495
+ var browserSpecs = doc.createElement(DIV);
496
+
497
+
498
+ if ( h2cCanvas !== undefined ) {
499
+ var ctx = h2cCanvas.getContext("2d"),
500
+ canvasCopy,
501
+ copyCtx,
502
+ radius = 5;
503
+ ctx.fillStyle = "#000";
504
+
505
+ // draw blackouts
506
+ Array.prototype.slice.call(doc.getElementsByClassName('feedback-blackedout'), 0).forEach( function( item ) {
507
+ var bounds = getBounds( item );
508
+ ctx.fillRect( bounds.left, bounds.top, bounds.width, bounds.height );
509
+ body.removeChild( item );
510
+
511
+ });
512
+
513
+
514
+ var items = Array.prototype.slice.call(doc.getElementsByClassName('feedback-highlighted'), 0);
515
+
516
+ if (items.length > 0 ) {
517
+
518
+ // copy canvas
519
+ canvasCopy = doc.createElement( CANVAS );
520
+ copyCtx = canvasCopy.getContext('2d');
521
+ canvasCopy.width = h2cCanvas.width;
522
+ canvasCopy.height = h2cCanvas.height;
523
+
524
+ copyCtx.drawImage(h2cCanvas, 0, 0);
525
+
526
+ ctx.fillStyle = "#777";
527
+ ctx.globalAlpha = 0.5;
528
+ ctx.fillRect( 0, 0, h2cCanvas.width, h2cCanvas.height );
529
+
530
+ ctx.beginPath();
531
+
532
+ items.forEach( function( item ) {
533
+ var bounds = getBounds( item );
534
+
535
+ var x = parseInt(item.style.left, 10),
536
+ y = parseInt(item.style.top, 10),
537
+ width = parseInt(item.style.width, 10),
538
+ height = parseInt(item.style.height, 10);
539
+
540
+ ctx.moveTo(x + radius, y);
541
+ ctx.lineTo(x + width - radius, y);
542
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
543
+ ctx.lineTo(x + width, y + height - radius);
544
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
545
+ ctx.lineTo(x + radius, y + height);
546
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
547
+ ctx.lineTo(x, y + radius);
548
+ ctx.quadraticCurveTo(x, y, x + radius, y);
549
+ body.removeChild( item );
550
+
551
+ });
552
+ ctx.closePath();
553
+ ctx.clip();
554
+
555
+ ctx.globalAlpha = 1;
556
+
557
+ ctx.drawImage(canvasCopy, 0,0);
558
+ removeElements( [ glass ] );
559
+ }
560
+
561
+ canvasCopy = doc.createElement( CANVAS );
562
+ copyCtx = canvasCopy.getContext('2d');
563
+ canvasCopy.width = 300;
564
+ canvasCopy.height = Math.round(h2cCanvas.height * (canvasCopy.width / h2cCanvas.width));
565
+
566
+ copyCtx.drawImage(h2cCanvas, 0, 0, h2cCanvas.width, h2cCanvas.height, 0, 0, canvasCopy.width, canvasCopy.height);
567
+ modalBody.appendChild( canvasCopy );
568
+
569
+ fullSize = doc.createElement( CANVAS );
570
+ copyCtx = fullSize.getContext('2d');
571
+ fullSize.width = h2cCanvas.width;
572
+ fullSize.height = h2cCanvas.height;
573
+
574
+ copyCtx.drawImage(h2cCanvas, 0, 0, h2cCanvas.width, h2cCanvas.height, 0, 0, fullSize.width, fullSize.height);
575
+
576
+
577
+ modalBody.appendChild( canvasCopy );
578
+
579
+ nextButton.firstChild.nodeValue = "Send form";
580
+ nextButton.onclick = function()
581
+ {
582
+ var http = new XMLHttpRequest();
583
+ var url = "feedback_and_screencaps";
584
+ var dataUrl = fullSize.toDataURL();
585
+ var params = "feedback_and_screencap[message]=" + message.value + "&feedback_and_screencap[screencap]=" + encodeURIComponent(dataUrl); //canvasCopy.toDataURL("image/png");
586
+ http.open("POST", url, true);
587
+ //Send the proper header information along with the request
588
+ http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
589
+ http.send(params);
590
+ returnMethods.close();
591
+ };
592
+
593
+ h2cCanvas.classList.add("feedback-canvas-complete");
594
+
595
+ browserSpecs.style.height = canvasCopy.height + 20 + PX;
596
+
597
+ }
598
+
599
+ browserSpecs.classList.add("feedback-browser");
600
+ browserSpecs.appendChild(element("h3", "Browser information"));
601
+
602
+ modalBody.appendChild( browserSpecs );
603
+
604
+ modalBody.appendChild( element("p", options.modalIntro || "Please describe the issue you are experiencing") );
605
+
606
+ modalBody.appendChild( message );
607
+
608
+
609
+ },
610
+
611
+
612
+
613
+
614
+
615
+
616
+
617
+ // close modal window
618
+ close: function() {
619
+
620
+ button.disabled = false;
621
+ h2cDone = false;
622
+ clickDone = false;
623
+
624
+
625
+ // remove feedback elements
626
+
627
+ removeElements( [ modal, glass, blackoutBox, highlightBox, highlightClose, highlightContainer, h2cCanvas ] );
628
+ removeElements( doc.getElementsByClassName('feedback-blackedout') );
629
+ removeElements( doc.getElementsByClassName('feedback-highlighted') );
630
+
631
+ // remove event listeners
632
+ body.removeEventListener("mousemove", mouseMoveEvent, false);
633
+ body.removeEventListener("click", mouseClickEvent, false);
634
+
635
+ h2cCanvas = undefined;
636
+
637
+
638
+ return false;
639
+
640
+ }
641
+ };
642
+
643
+ glass.classList.add("feedback-glass");
644
+ glass.style.pointerEvents = "none";
645
+ glass.setAttribute(H2C_IGNORE, true);
646
+
647
+ options = options || {};
648
+
649
+ button = element("button", options.label || "Send Feedback");
650
+ button.className = options.labelClass || "btn bottom-right";
651
+
652
+ button.setAttribute(H2C_IGNORE, true);
653
+
654
+ button.onclick = returnMethods.open;
655
+
656
+ ((options.appendTo !== undefined) ? options.appendTo : doc.body).appendChild( button );
657
+ }
658
+
659
+ };
660
+
661
+
662
+ })( window, document );
663
+
664
+