canvasinput-rails 1.2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1475 @@
1
+ /*!
2
+ * CanvasInput v1.2.7
3
+ * http://goldfirestudios.com/blog/108/CanvasInput-HTML5-Canvas-Text-Input
4
+ *
5
+ * (c) 2013-2017, James Simpson of GoldFire Studios
6
+ * goldfirestudios.com
7
+ *
8
+ * MIT License
9
+ */
10
+
11
+ (function() {
12
+ // create a buffer that stores all inputs so that tabbing
13
+ // between them is made possible.
14
+ var inputs = [];
15
+
16
+ // initialize the Canvas Input
17
+ var CanvasInput = window.CanvasInput = function(o) {
18
+ var self = this;
19
+
20
+ o = o ? o : {};
21
+
22
+ // setup the defaults
23
+ self._canvas = o.canvas || null;
24
+ self._ctx = self._canvas ? self._canvas.getContext('2d') : null;
25
+ self._x = o.x || 0;
26
+ self._y = o.y || 0;
27
+ self._extraX = o.extraX || 0;
28
+ self._extraY = o.extraY || 0;
29
+ self._fontSize = o.fontSize || 14;
30
+ self._fontFamily = o.fontFamily || 'Arial';
31
+ self._fontColor = o.fontColor || '#000';
32
+ self._placeHolderColor = o.placeHolderColor || '#bfbebd';
33
+ self._fontWeight = o.fontWeight || 'normal';
34
+ self._fontStyle = o.fontStyle || 'normal';
35
+ self._fontShadowColor = o.fontShadowColor || '';
36
+ self._fontShadowBlur = o.fontShadowBlur || 0;
37
+ self._fontShadowOffsetX = o.fontShadowOffsetX || 0;
38
+ self._fontShadowOffsetY = o.fontShadowOffsetY || 0;
39
+ self._readonly = o.readonly || false;
40
+ self._maxlength = o.maxlength || null;
41
+ self._width = o.width || 150;
42
+ self._height = o.height || self._fontSize;
43
+ self._padding = o.padding >= 0 ? o.padding : 5;
44
+ self._borderWidth = o.borderWidth >= 0 ? o.borderWidth : 1;
45
+ self._borderColor = o.borderColor || '#959595';
46
+ self._borderRadius = o.borderRadius >= 0 ? o.borderRadius : 3;
47
+ self._backgroundImage = o.backgroundImage || '';
48
+ self._boxShadow = o.boxShadow || '1px 1px 0px rgba(255, 255, 255, 1)';
49
+ self._innerShadow = o.innerShadow || '0px 0px 4px rgba(0, 0, 0, 0.4)';
50
+ self._selectionColor = o.selectionColor || 'rgba(179, 212, 253, 0.8)';
51
+ self._placeHolder = o.placeHolder || '';
52
+ self._value = (o.value || self._placeHolder) + '';
53
+ self._onsubmit = o.onsubmit || function() {};
54
+ self._onkeydown = o.onkeydown || function() {};
55
+ self._onkeyup = o.onkeyup || function() {};
56
+ self._onfocus = o.onfocus || function() {};
57
+ self._onblur = o.onblur || function() {};
58
+ self._cursor = false;
59
+ self._cursorPos = 0;
60
+ self._hasFocus = false;
61
+ self._selection = [0, 0];
62
+ self._wasOver = false;
63
+
64
+ // parse box shadow
65
+ self.boxShadow(self._boxShadow, true);
66
+
67
+ // calculate the full width and height with padding, borders and shadows
68
+ self._calcWH();
69
+
70
+ // setup the off-DOM canvas
71
+ self._renderCanvas = document.createElement('canvas');
72
+ self._renderCanvas.setAttribute('width', self.outerW);
73
+ self._renderCanvas.setAttribute('height', self.outerH);
74
+ self._renderCtx = self._renderCanvas.getContext('2d');
75
+
76
+ // setup another off-DOM canvas for inner-shadows
77
+ self._shadowCanvas = document.createElement('canvas');
78
+ self._shadowCanvas.setAttribute('width', self._width + self._padding * 2);
79
+ self._shadowCanvas.setAttribute('height', self._height + self._padding * 2);
80
+ self._shadowCtx = self._shadowCanvas.getContext('2d');
81
+
82
+ // setup the background color
83
+ if (typeof o.backgroundGradient !== 'undefined') {
84
+ self._backgroundColor = self._renderCtx.createLinearGradient(
85
+ 0,
86
+ 0,
87
+ 0,
88
+ self.outerH
89
+ );
90
+ self._backgroundColor.addColorStop(0, o.backgroundGradient[0]);
91
+ self._backgroundColor.addColorStop(1, o.backgroundGradient[1]);
92
+ } else {
93
+ self._backgroundColor = o.backgroundColor || '#fff';
94
+ }
95
+
96
+ // setup main canvas events
97
+ if (self._canvas) {
98
+ self._canvas.addEventListener('mousemove', function(e) {
99
+ e = e || window.event;
100
+ self.mousemove(e, self);
101
+ }, false);
102
+
103
+ self._canvas.addEventListener('mousedown', function(e) {
104
+ e = e || window.event;
105
+ self.mousedown(e, self);
106
+ }, false);
107
+
108
+ self._canvas.addEventListener('mouseup', function(e) {
109
+ e = e || window.event;
110
+ self.mouseup(e, self);
111
+ }, false);
112
+ }
113
+
114
+ // setup a global mouseup to blur the input outside of the canvas
115
+ var autoBlur = function(e) {
116
+ e = e || window.event;
117
+
118
+ if (self._hasFocus && !self._mouseDown) {
119
+ self.blur();
120
+ }
121
+ };
122
+ window.addEventListener('mouseup', autoBlur, true);
123
+ window.addEventListener('touchend', autoBlur, true);
124
+
125
+ // create the hidden input element
126
+ self._hiddenInput = document.createElement('input');
127
+ self._hiddenInput.type = 'text';
128
+ self._hiddenInput.style.position = 'absolute';
129
+ self._hiddenInput.style.opacity = 0;
130
+ self._hiddenInput.style.pointerEvents = 'none';
131
+ self._hiddenInput.style.zIndex = 0;
132
+ // hide native blue text cursor on iOS
133
+ self._hiddenInput.style.transform = 'scale(0)';
134
+
135
+ self._updateHiddenInput();
136
+ if (self._maxlength) {
137
+ self._hiddenInput.maxLength = self._maxlength;
138
+ }
139
+ document.body.appendChild(self._hiddenInput);
140
+ self._hiddenInput.value = self._value;
141
+
142
+ // setup the keydown listener
143
+ self._hiddenInput.addEventListener('keydown', function(e) {
144
+ e = e || window.event;
145
+
146
+ if (self._hasFocus) {
147
+ // hack to fix touch event bug in iOS Safari
148
+ window.focus();
149
+ self._hiddenInput.focus();
150
+
151
+ // continue with the keydown event
152
+ self.keydown(e, self);
153
+ }
154
+ });
155
+
156
+ // setup the keyup listener
157
+ self._hiddenInput.addEventListener('keyup', function(e) {
158
+ e = e || window.event;
159
+
160
+ // update the canvas input state information from the hidden input
161
+ self._value = self._hiddenInput.value;
162
+ self._cursorPos = self._hiddenInput.selectionStart;
163
+ // update selection to hidden input's selection in case user did keyboard-based selection
164
+ self._selection = [self._hiddenInput.selectionStart, self._hiddenInput.selectionEnd];
165
+ self.render();
166
+
167
+ if (self._hasFocus) {
168
+ self._onkeyup(e, self);
169
+ }
170
+ });
171
+
172
+ // add this to the buffer
173
+ inputs.push(self);
174
+ self._inputsIndex = inputs.length - 1;
175
+
176
+ // draw the text box
177
+ self.render();
178
+ };
179
+
180
+ // setup the prototype
181
+ CanvasInput.prototype = {
182
+ /**
183
+ * Get/set the main canvas.
184
+ * @param {Object} data Canvas reference.
185
+ * @return {Mixed} CanvasInput or current canvas.
186
+ */
187
+ canvas: function(data) {
188
+ var self = this;
189
+
190
+ if (typeof data !== 'undefined') {
191
+ self._canvas = data;
192
+ self._ctx = self._canvas.getContext('2d');
193
+
194
+ return self.render();
195
+ } else {
196
+ return self._canvas;
197
+ }
198
+ },
199
+
200
+ /**
201
+ * Get/set the x-position.
202
+ * @param {Number} data The pixel position along the x-coordinate.
203
+ * @return {Mixed} CanvasInput or current x-value.
204
+ */
205
+ x: function(data) {
206
+ var self = this;
207
+
208
+ if (typeof data !== 'undefined') {
209
+ self._x = data;
210
+ self._updateHiddenInput();
211
+
212
+ return self.render();
213
+ } else {
214
+ return self._x;
215
+ }
216
+ },
217
+
218
+ /**
219
+ * Get/set the y-position.
220
+ * @param {Number} data The pixel position along the y-coordinate.
221
+ * @return {Mixed} CanvasInput or current y-value.
222
+ */
223
+ y: function(data) {
224
+ var self = this;
225
+
226
+ if (typeof data !== 'undefined') {
227
+ self._y = data;
228
+ self._updateHiddenInput();
229
+
230
+ return self.render();
231
+ } else {
232
+ return self._y;
233
+ }
234
+ },
235
+
236
+ /**
237
+ * Get/set the extra x-position (generally used when no canvas is specified).
238
+ * @param {Number} data The pixel position along the x-coordinate.
239
+ * @return {Mixed} CanvasInput or current x-value.
240
+ */
241
+ extraX: function(data) {
242
+ var self = this;
243
+
244
+ if (typeof data !== 'undefined') {
245
+ self._extraX = data;
246
+ self._updateHiddenInput();
247
+
248
+ return self.render();
249
+ } else {
250
+ return self._extraX;
251
+ }
252
+ },
253
+
254
+ /**
255
+ * Get/set the extra y-position (generally used when no canvas is specified).
256
+ * @param {Number} data The pixel position along the y-coordinate.
257
+ * @return {Mixed} CanvasInput or current y-value.
258
+ */
259
+ extraY: function(data) {
260
+ var self = this;
261
+
262
+ if (typeof data !== 'undefined') {
263
+ self._extraY = data;
264
+ self._updateHiddenInput();
265
+
266
+ return self.render();
267
+ } else {
268
+ return self._extraY;
269
+ }
270
+ },
271
+
272
+ /**
273
+ * Get/set the font size.
274
+ * @param {Number} data Font size.
275
+ * @return {Mixed} CanvasInput or current font size.
276
+ */
277
+ fontSize: function(data) {
278
+ var self = this;
279
+
280
+ if (typeof data !== 'undefined') {
281
+ self._fontSize = data;
282
+
283
+ return self.render();
284
+ } else {
285
+ return self._fontSize;
286
+ }
287
+ },
288
+
289
+ /**
290
+ * Get/set the font family.
291
+ * @param {String} data Font family.
292
+ * @return {Mixed} CanvasInput or current font family.
293
+ */
294
+ fontFamily: function(data) {
295
+ var self = this;
296
+
297
+ if (typeof data !== 'undefined') {
298
+ self._fontFamily = data;
299
+
300
+ return self.render();
301
+ } else {
302
+ return self._fontFamily;
303
+ }
304
+ },
305
+
306
+ /**
307
+ * Get/set the font color.
308
+ * @param {String} data Font color.
309
+ * @return {Mixed} CanvasInput or current font color.
310
+ */
311
+ fontColor: function(data) {
312
+ var self = this;
313
+
314
+ if (typeof data !== 'undefined') {
315
+ self._fontColor = data;
316
+
317
+ return self.render();
318
+ } else {
319
+ return self._fontColor;
320
+ }
321
+ },
322
+
323
+ /**
324
+ * Get/set the place holder font color.
325
+ * @param {String} data Font color.
326
+ * @return {Mixed} CanvasInput or current place holder font color.
327
+ */
328
+ placeHolderColor: function(data) {
329
+ var self = this;
330
+
331
+ if (typeof data !== 'undefined') {
332
+ self._placeHolderColor = data;
333
+
334
+ return self.render();
335
+ } else {
336
+ return self._placeHolderColor;
337
+ }
338
+ },
339
+
340
+ /**
341
+ * Get/set the font weight.
342
+ * @param {String} data Font weight.
343
+ * @return {Mixed} CanvasInput or current font weight.
344
+ */
345
+ fontWeight: function(data) {
346
+ var self = this;
347
+
348
+ if (typeof data !== 'undefined') {
349
+ self._fontWeight = data;
350
+
351
+ return self.render();
352
+ } else {
353
+ return self._fontWeight;
354
+ }
355
+ },
356
+
357
+ /**
358
+ * Get/set the font style.
359
+ * @param {String} data Font style.
360
+ * @return {Mixed} CanvasInput or current font style.
361
+ */
362
+ fontStyle: function(data) {
363
+ var self = this;
364
+
365
+ if (typeof data !== 'undefined') {
366
+ self._fontStyle = data;
367
+
368
+ return self.render();
369
+ } else {
370
+ return self._fontStyle;
371
+ }
372
+ },
373
+
374
+ /**
375
+ * Get/set the font shadow color.
376
+ * @param {String} data Font shadow color.
377
+ * @return {Mixed} CanvasInput or current font shadow color.
378
+ */
379
+ fontShadowColor: function(data) {
380
+ var self = this;
381
+
382
+ if (typeof data !== 'undefined') {
383
+ self._fontShadowColor = data;
384
+
385
+ return self.render();
386
+ } else {
387
+ return self._fontShadowColor;
388
+ }
389
+ },
390
+
391
+ /**
392
+ * Get/set the font shadow blur.
393
+ * @param {String} data Font shadow blur.
394
+ * @return {Mixed} CanvasInput or current font shadow blur.
395
+ */
396
+ fontShadowBlur: function(data) {
397
+ var self = this;
398
+
399
+ if (typeof data !== 'undefined') {
400
+ self._fontShadowBlur = data;
401
+
402
+ return self.render();
403
+ } else {
404
+ return self._fontShadowBlur;
405
+ }
406
+ },
407
+
408
+ /**
409
+ * Get/set the font shadow x-offset.
410
+ * @param {String} data Font shadow x-offset.
411
+ * @return {Mixed} CanvasInput or current font shadow x-offset.
412
+ */
413
+ fontShadowOffsetX: function(data) {
414
+ var self = this;
415
+
416
+ if (typeof data !== 'undefined') {
417
+ self._fontShadowOffsetX = data;
418
+
419
+ return self.render();
420
+ } else {
421
+ return self._fontShadowOffsetX;
422
+ }
423
+ },
424
+
425
+ /**
426
+ * Get/set the font shadow y-offset.
427
+ * @param {String} data Font shadow y-offset.
428
+ * @return {Mixed} CanvasInput or current font shadow y-offset.
429
+ */
430
+ fontShadowOffsetY: function(data) {
431
+ var self = this;
432
+
433
+ if (typeof data !== 'undefined') {
434
+ self._fontShadowOffsetY = data;
435
+
436
+ return self.render();
437
+ } else {
438
+ return self._fontShadowOffsetY;
439
+ }
440
+ },
441
+
442
+ /**
443
+ * Get/set the width of the text box.
444
+ * @param {Number} data Width in pixels.
445
+ * @return {Mixed} CanvasInput or current width.
446
+ */
447
+ width: function(data) {
448
+ var self = this;
449
+
450
+ if (typeof data !== 'undefined') {
451
+ self._width = data;
452
+ self._calcWH();
453
+ self._updateCanvasWH();
454
+ self._updateHiddenInput();
455
+
456
+ return self.render();
457
+ } else {
458
+ return self._width;
459
+ }
460
+ },
461
+
462
+ /**
463
+ * Get/set the height of the text box.
464
+ * @param {Number} data Height in pixels.
465
+ * @return {Mixed} CanvasInput or current height.
466
+ */
467
+ height: function(data) {
468
+ var self = this;
469
+
470
+ if (typeof data !== 'undefined') {
471
+ self._height = data;
472
+ self._calcWH();
473
+ self._updateCanvasWH();
474
+ self._updateHiddenInput();
475
+
476
+ return self.render();
477
+ } else {
478
+ return self._height;
479
+ }
480
+ },
481
+
482
+ /**
483
+ * Get/set the padding of the text box.
484
+ * @param {Number} data Padding in pixels.
485
+ * @return {Mixed} CanvasInput or current padding.
486
+ */
487
+ padding: function(data) {
488
+ var self = this;
489
+
490
+ if (typeof data !== 'undefined') {
491
+ self._padding = data;
492
+ self._calcWH();
493
+ self._updateCanvasWH();
494
+
495
+ return self.render();
496
+ } else {
497
+ return self._padding;
498
+ }
499
+ },
500
+
501
+ /**
502
+ * Get/set the border width.
503
+ * @param {Number} data Border width.
504
+ * @return {Mixed} CanvasInput or current border width.
505
+ */
506
+ borderWidth: function(data) {
507
+ var self = this;
508
+
509
+ if (typeof data !== 'undefined') {
510
+ self._borderWidth = data;
511
+ self._calcWH();
512
+ self._updateCanvasWH();
513
+
514
+ return self.render();
515
+ } else {
516
+ return self._borderWidth;
517
+ }
518
+ },
519
+
520
+ /**
521
+ * Get/set the border color.
522
+ * @param {String} data Border color.
523
+ * @return {Mixed} CanvasInput or current border color.
524
+ */
525
+ borderColor: function(data) {
526
+ var self = this;
527
+
528
+ if (typeof data !== 'undefined') {
529
+ self._borderColor = data;
530
+
531
+ return self.render();
532
+ } else {
533
+ return self._borderColor;
534
+ }
535
+ },
536
+
537
+ /**
538
+ * Get/set the border radius.
539
+ * @param {Number} data Border radius.
540
+ * @return {Mixed} CanvasInput or current border radius.
541
+ */
542
+ borderRadius: function(data) {
543
+ var self = this;
544
+
545
+ if (typeof data !== 'undefined') {
546
+ self._borderRadius = data;
547
+
548
+ return self.render();
549
+ } else {
550
+ return self._borderRadius;
551
+ }
552
+ },
553
+
554
+ /**
555
+ * Get/set the background color.
556
+ * @param {Number} data Background color.
557
+ * @return {Mixed} CanvasInput or current background color.
558
+ */
559
+ backgroundColor: function(data) {
560
+ var self = this;
561
+
562
+ if (typeof data !== 'undefined') {
563
+ self._backgroundColor = data;
564
+
565
+ return self.render();
566
+ } else {
567
+ return self._backgroundColor;
568
+ }
569
+ },
570
+
571
+ /**
572
+ * Get/set the background gradient.
573
+ * @param {Number} data Background gradient.
574
+ * @return {Mixed} CanvasInput or current background gradient.
575
+ */
576
+ backgroundGradient: function(data) {
577
+ var self = this;
578
+
579
+ if (typeof data !== 'undefined') {
580
+ self._backgroundColor = self._renderCtx.createLinearGradient(
581
+ 0,
582
+ 0,
583
+ 0,
584
+ self.outerH
585
+ );
586
+ self._backgroundColor.addColorStop(0, data[0]);
587
+ self._backgroundColor.addColorStop(1, data[1]);
588
+
589
+ return self.render();
590
+ } else {
591
+ return self._backgroundColor;
592
+ }
593
+ },
594
+
595
+ /**
596
+ * Get/set the box shadow.
597
+ * @param {String} data Box shadow in CSS format (1px 1px 1px rgba(0, 0, 0.5)).
598
+ * @param {Boolean} doReturn (optional) True to prevent a premature render.
599
+ * @return {Mixed} CanvasInput or current box shadow.
600
+ */
601
+ boxShadow: function(data, doReturn) {
602
+ var self = this;
603
+
604
+ if (typeof data !== 'undefined') {
605
+ // parse box shadow
606
+ var boxShadow = data.split('px ');
607
+ self._boxShadow = {
608
+ x: self._boxShadow === 'none' ? 0 : parseInt(boxShadow[0], 10),
609
+ y: self._boxShadow === 'none' ? 0 : parseInt(boxShadow[1], 10),
610
+ blur: self._boxShadow === 'none' ? 0 : parseInt(boxShadow[2], 10),
611
+ color: self._boxShadow === 'none' ? '' : boxShadow[3]
612
+ };
613
+
614
+ // take into account the shadow and its direction
615
+ if (self._boxShadow.x < 0) {
616
+ self.shadowL = Math.abs(self._boxShadow.x) + self._boxShadow.blur;
617
+ self.shadowR = self._boxShadow.blur + self._boxShadow.x;
618
+ } else {
619
+ self.shadowL = Math.abs(self._boxShadow.blur - self._boxShadow.x);
620
+ self.shadowR = self._boxShadow.blur + self._boxShadow.x;
621
+ }
622
+ if (self._boxShadow.y < 0) {
623
+ self.shadowT = Math.abs(self._boxShadow.y) + self._boxShadow.blur;
624
+ self.shadowB = self._boxShadow.blur + self._boxShadow.y;
625
+ } else {
626
+ self.shadowT = Math.abs(self._boxShadow.blur - self._boxShadow.y);
627
+ self.shadowB = self._boxShadow.blur + self._boxShadow.y;
628
+ }
629
+
630
+ self.shadowW = self.shadowL + self.shadowR;
631
+ self.shadowH = self.shadowT + self.shadowB;
632
+
633
+ self._calcWH();
634
+
635
+ if (!doReturn) {
636
+ self._updateCanvasWH();
637
+
638
+ return self.render();
639
+ }
640
+ } else {
641
+ return self._boxShadow;
642
+ }
643
+ },
644
+
645
+ /**
646
+ * Get/set the inner shadow.
647
+ * @param {String} data In the format of a CSS box shadow (1px 1px 1px rgba(0, 0, 0.5)).
648
+ * @return {Mixed} CanvasInput or current inner shadow.
649
+ */
650
+ innerShadow: function(data) {
651
+ var self = this;
652
+
653
+ if (typeof data !== 'undefined') {
654
+ self._innerShadow = data;
655
+
656
+ return self.render();
657
+ } else {
658
+ return self._innerShadow;
659
+ }
660
+ },
661
+
662
+ /**
663
+ * Get/set the text selection color.
664
+ * @param {String} data Color.
665
+ * @return {Mixed} CanvasInput or current selection color.
666
+ */
667
+ selectionColor: function(data) {
668
+ var self = this;
669
+
670
+ if (typeof data !== 'undefined') {
671
+ self._selectionColor = data;
672
+
673
+ return self.render();
674
+ } else {
675
+ return self._selectionColor;
676
+ }
677
+ },
678
+
679
+ /**
680
+ * Get/set the place holder text.
681
+ * @param {String} data Place holder text.
682
+ * @return {Mixed} CanvasInput or current place holder text.
683
+ */
684
+ placeHolder: function(data) {
685
+ var self = this;
686
+
687
+ if (typeof data !== 'undefined') {
688
+ self._placeHolder = data;
689
+
690
+ return self.render();
691
+ } else {
692
+ return self._placeHolder;
693
+ }
694
+ },
695
+
696
+ /**
697
+ * Get/set the current text box value.
698
+ * @param {String} data Text value.
699
+ * @return {Mixed} CanvasInput or current text value.
700
+ */
701
+ value: function(data) {
702
+ var self = this;
703
+
704
+ if (typeof data !== 'undefined') {
705
+ self._value = data + '';
706
+ self._hiddenInput.value = data + '';
707
+
708
+ // update the cursor position
709
+ self._cursorPos = self._clipText().length;
710
+
711
+ self.render();
712
+
713
+ return self;
714
+ } else {
715
+ return (self._value === self._placeHolder) ? '' : self._value;
716
+ }
717
+ },
718
+
719
+ /**
720
+ * Set or fire the onsubmit event.
721
+ * @param {Function} fn Custom callback.
722
+ */
723
+ onsubmit: function(fn) {
724
+ var self = this;
725
+
726
+ if (typeof fn !== 'undefined') {
727
+ self._onsubmit = fn;
728
+
729
+ return self;
730
+ } else {
731
+ self._onsubmit();
732
+ }
733
+ },
734
+
735
+ /**
736
+ * Set or fire the onkeydown event.
737
+ * @param {Function} fn Custom callback.
738
+ */
739
+ onkeydown: function(fn) {
740
+ var self = this;
741
+
742
+ if (typeof fn !== 'undefined') {
743
+ self._onkeydown = fn;
744
+
745
+ return self;
746
+ } else {
747
+ self._onkeydown();
748
+ }
749
+ },
750
+
751
+ /**
752
+ * Set or fire the onkeyup event.
753
+ * @param {Function} fn Custom callback.
754
+ */
755
+ onkeyup: function(fn) {
756
+ var self = this;
757
+
758
+ if (typeof fn !== 'undefined') {
759
+ self._onkeyup = fn;
760
+
761
+ return self;
762
+ } else {
763
+ self._onkeyup();
764
+ }
765
+ },
766
+
767
+ /**
768
+ * Place focus on the CanvasInput box, placing the cursor
769
+ * either at the end of the text or where the user clicked.
770
+ * @param {Number} pos (optional) The position to place the cursor.
771
+ * @return {CanvasInput}
772
+ */
773
+ focus: function(pos) {
774
+ var self = this;
775
+
776
+ // only fire the focus event when going from unfocussed
777
+ if (!self._hasFocus) {
778
+ self._onfocus(self);
779
+
780
+ // remove focus from all other inputs
781
+ for (var i=0; i<inputs.length; i++) {
782
+ if (inputs[i]._hasFocus) {
783
+ inputs[i].blur();
784
+ }
785
+ }
786
+ }
787
+
788
+ // remove selection
789
+ if (!self._selectionUpdated) {
790
+ self._selection = [0, 0];
791
+ } else {
792
+ delete self._selectionUpdated;
793
+ }
794
+
795
+ // if this is readonly, don't allow it to get focus
796
+ self._hasFocus = true;
797
+ if (self._readonly) {
798
+ self._hiddenInput.readOnly = true;
799
+ } else {
800
+ self._hiddenInput.readOnly = false;
801
+
802
+ // update the cursor position
803
+ self._cursorPos = (typeof pos === 'number') ? pos : self._clipText().length;
804
+
805
+ // clear the place holder
806
+ if (self._placeHolder === self._value) {
807
+ self._value = '';
808
+ self._hiddenInput.value = '';
809
+ }
810
+
811
+ self._cursor = true;
812
+
813
+ // setup cursor interval
814
+ if (self._cursorInterval) {
815
+ clearInterval(self._cursorInterval);
816
+ }
817
+ self._cursorInterval = setInterval(function() {
818
+ self._cursor = !self._cursor;
819
+ self.render();
820
+ }, 500);
821
+ }
822
+
823
+ // move the real focus to the hidden input
824
+ var hasSelection = (self._selection[0] > 0 || self._selection[1] > 0);
825
+ self._hiddenInput.focus();
826
+ self._hiddenInput.selectionStart = hasSelection ? self._selection[0] : self._cursorPos;
827
+ self._hiddenInput.selectionEnd = hasSelection ? self._selection[1] : self._cursorPos;
828
+
829
+ return self.render();
830
+ },
831
+
832
+ /**
833
+ * Removes focus from the CanvasInput box.
834
+ * @param {Object} _this Reference to this.
835
+ * @return {CanvasInput}
836
+ */
837
+ blur: function(_this) {
838
+ var self = _this || this;
839
+
840
+ self._onblur(self);
841
+
842
+ if (self._cursorInterval) {
843
+ clearInterval(self._cursorInterval);
844
+ }
845
+ self._hasFocus = false;
846
+ self._cursor = false;
847
+ self._selection = [0, 0];
848
+ self._hiddenInput.blur();
849
+
850
+ // fill the place holder
851
+ if (self._value === '') {
852
+ self._value = self._placeHolder;
853
+ }
854
+
855
+ return self.render();
856
+ },
857
+
858
+ /**
859
+ * Fired with the keydown event to draw the typed characters.
860
+ * @param {Event} e The keydown event.
861
+ * @param {CanvasInput} self
862
+ * @return {CanvasInput}
863
+ */
864
+ keydown: function(e, self) {
865
+ var keyCode = e.which,
866
+ isShift = e.shiftKey,
867
+ key = null,
868
+ startText, endText;
869
+
870
+ // make sure the correct text field is being updated
871
+ if (self._readonly || !self._hasFocus) {
872
+ return;
873
+ }
874
+
875
+ // fire custom user event
876
+ self._onkeydown(e, self);
877
+
878
+ // add support for Ctrl/Cmd+A selection
879
+ if (keyCode === 65 && (e.ctrlKey || e.metaKey)) {
880
+ self.selectText();
881
+ e.preventDefault();
882
+ return self.render();
883
+ }
884
+
885
+ // block keys that shouldn't be processed
886
+ if (keyCode === 17 || e.metaKey || e.ctrlKey) {
887
+ return self;
888
+ }
889
+
890
+ if (keyCode === 13) { // enter key
891
+ e.preventDefault();
892
+ self._onsubmit(e, self);
893
+ } else if (keyCode === 9) { // tab key
894
+ e.preventDefault();
895
+ if (inputs.length > 1) {
896
+ var next = (inputs[self._inputsIndex + 1]) ? self._inputsIndex + 1 : 0;
897
+ self.blur();
898
+ setTimeout(function() {
899
+ inputs[next].focus();
900
+ }, 10);
901
+ }
902
+ }
903
+
904
+ // update the canvas input state information from the hidden input
905
+ self._value = self._hiddenInput.value;
906
+ self._cursorPos = self._hiddenInput.selectionStart;
907
+ self._selection = [0, 0];
908
+
909
+ return self.render();
910
+ },
911
+
912
+ /**
913
+ * Fired with the click event on the canvas, and puts focus on/off
914
+ * based on where the user clicks.
915
+ * @param {Event} e The click event.
916
+ * @param {CanvasInput} self
917
+ * @return {CanvasInput}
918
+ */
919
+ click: function(e, self) {
920
+ var mouse = self._mousePos(e),
921
+ x = mouse.x,
922
+ y = mouse.y;
923
+
924
+ if (self._endSelection) {
925
+ delete self._endSelection;
926
+ delete self._selectionUpdated;
927
+ return;
928
+ }
929
+
930
+ if (self._canvas && self._overInput(x, y) || !self._canvas) {
931
+ if (self._mouseDown) {
932
+ self._mouseDown = false;
933
+ self.click(e, self);
934
+ return self.focus(self._clickPos(x, y));
935
+ }
936
+ } else {
937
+ return self.blur();
938
+ }
939
+ },
940
+
941
+ /**
942
+ * Fired with the mousemove event to update the default cursor.
943
+ * @param {Event} e The mousemove event.
944
+ * @param {CanvasInput} self
945
+ * @return {CanvasInput}
946
+ */
947
+ mousemove: function(e, self) {
948
+ var mouse = self._mousePos(e),
949
+ x = mouse.x,
950
+ y = mouse.y,
951
+ isOver = self._overInput(x, y);
952
+
953
+ if (isOver && self._canvas) {
954
+ self._canvas.style.cursor = 'text';
955
+ self._wasOver = true;
956
+ } else if (self._wasOver && self._canvas) {
957
+ self._canvas.style.cursor = 'default';
958
+ self._wasOver = false;
959
+ }
960
+
961
+ if (self._hasFocus && self._selectionStart >= 0) {
962
+ var curPos = self._clickPos(x, y),
963
+ start = Math.min(self._selectionStart, curPos),
964
+ end = Math.max(self._selectionStart, curPos);
965
+
966
+ if (!isOver) {
967
+ self._selectionUpdated = true;
968
+ self._endSelection = true;
969
+ delete self._selectionStart;
970
+ self.render();
971
+ return;
972
+ }
973
+
974
+ if (self._selection[0] !== start || self._selection[1] !== end) {
975
+ self._selection = [start, end];
976
+ self.render();
977
+ }
978
+ }
979
+ },
980
+
981
+ /**
982
+ * Fired with the mousedown event to start a selection drag.
983
+ * @param {Event} e The mousedown event.
984
+ * @param {CanvasInput} self
985
+ */
986
+ mousedown: function(e, self) {
987
+ var mouse = self._mousePos(e),
988
+ x = mouse.x,
989
+ y = mouse.y,
990
+ isOver = self._overInput(x, y);
991
+
992
+ // setup the 'click' event
993
+ self._mouseDown = isOver;
994
+
995
+ // start the selection drag if inside the input
996
+ if (self._hasFocus && isOver) {
997
+ self._selectionStart = self._clickPos(x, y);
998
+ }
999
+ },
1000
+
1001
+ /**
1002
+ * Fired with the mouseup event to end a selection drag.
1003
+ * @param {Event} e The mouseup event.
1004
+ * @param {CanvasInput} self
1005
+ */
1006
+ mouseup: function(e, self) {
1007
+ var mouse = self._mousePos(e),
1008
+ x = mouse.x,
1009
+ y = mouse.y;
1010
+
1011
+ // update selection if a drag has happened
1012
+ var isSelection = self._clickPos(x, y) !== self._selectionStart;
1013
+ if (self._hasFocus && self._selectionStart >= 0 && self._overInput(x, y) && isSelection) {
1014
+ self._selectionUpdated = true;
1015
+ delete self._selectionStart;
1016
+ self.render();
1017
+ } else {
1018
+ delete self._selectionStart;
1019
+ }
1020
+
1021
+ self.click(e, self);
1022
+ },
1023
+
1024
+ /**
1025
+ * Select a range of text in the input.
1026
+ * @param {Array} range (optional) Leave blank to select all. Format: [start, end]
1027
+ * @return {CanvasInput}
1028
+ */
1029
+ selectText: function(range) {
1030
+ var self = this,
1031
+ range = range || [0, self._value.length];
1032
+
1033
+ // select the range of text specified (or all if none specified)
1034
+ setTimeout(function() {
1035
+ self._selection = [range[0], range[1]];
1036
+ self._hiddenInput.selectionStart = range[0];
1037
+ self._hiddenInput.selectionEnd = range[1];
1038
+ self.render();
1039
+ }, 1);
1040
+
1041
+ return self;
1042
+ },
1043
+
1044
+ /**
1045
+ * Helper method to get the off-DOM canvas.
1046
+ * @return {Object} Reference to the canvas.
1047
+ */
1048
+ renderCanvas: function() {
1049
+ return this._renderCanvas;
1050
+ },
1051
+
1052
+ /**
1053
+ * Clears and redraws the CanvasInput on an off-DOM canvas,
1054
+ * and if a main canvas is provided, draws it all onto that.
1055
+ * @return {CanvasInput}
1056
+ */
1057
+ render: function() {
1058
+ var self = this,
1059
+ ctx = self._renderCtx,
1060
+ w = self.outerW,
1061
+ h = self.outerH,
1062
+ br = self._borderRadius,
1063
+ bw = self._borderWidth,
1064
+ sw = self.shadowW,
1065
+ sh = self.shadowH;
1066
+
1067
+ if (!ctx) {
1068
+ return;
1069
+ }
1070
+
1071
+ // clear the canvas
1072
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
1073
+
1074
+ // setup the box shadow
1075
+ ctx.shadowOffsetX = self._boxShadow.x;
1076
+ ctx.shadowOffsetY = self._boxShadow.y;
1077
+ ctx.shadowBlur = self._boxShadow.blur;
1078
+ ctx.shadowColor = self._boxShadow.color;
1079
+
1080
+ // draw the border
1081
+ if (self._borderWidth > 0) {
1082
+ ctx.fillStyle = self._borderColor;
1083
+ self._roundedRect(ctx, self.shadowL, self.shadowT, w - sw, h - sh, br);
1084
+ ctx.fill();
1085
+
1086
+ ctx.shadowOffsetX = 0;
1087
+ ctx.shadowOffsetY = 0;
1088
+ ctx.shadowBlur = 0;
1089
+ }
1090
+
1091
+ // draw the text box background
1092
+ self._drawTextBox(function() {
1093
+ // make sure all shadows are reset
1094
+ ctx.shadowOffsetX = 0;
1095
+ ctx.shadowOffsetY = 0;
1096
+ ctx.shadowBlur = 0;
1097
+
1098
+ // clip the text so that it fits within the box
1099
+ var text = self._clipText();
1100
+
1101
+ // draw the selection
1102
+ var paddingBorder = self._padding + self._borderWidth + self.shadowT;
1103
+ if (self._selection[1] > 0) {
1104
+ var selectOffset = self._textWidth(text.substring(0, self._selection[0])),
1105
+ selectWidth = self._textWidth(text.substring(self._selection[0], self._selection[1]));
1106
+
1107
+ ctx.fillStyle = self._selectionColor;
1108
+ ctx.fillRect(paddingBorder + selectOffset, paddingBorder, selectWidth, self._height);
1109
+ }
1110
+
1111
+ // draw the cursor
1112
+ if (self._cursor) {
1113
+ var cursorOffset = self._textWidth(text.substring(0, self._cursorPos));
1114
+ ctx.fillStyle = self._fontColor;
1115
+ ctx.fillRect(paddingBorder + cursorOffset, paddingBorder, 1, self._height);
1116
+ }
1117
+
1118
+ // draw the text
1119
+ var textX = self._padding + self._borderWidth + self.shadowL,
1120
+ textY = Math.round(paddingBorder + self._height / 2);
1121
+
1122
+ // only remove the placeholder text if they have typed something
1123
+ text = (text === '' && self._placeHolder) ? self._placeHolder : text;
1124
+
1125
+ ctx.fillStyle = (self._value !== '' && self._value !== self._placeHolder) ? self._fontColor : self._placeHolderColor;
1126
+ ctx.font = self._fontStyle + ' ' + self._fontWeight + ' ' + self._fontSize + 'px ' + self._fontFamily;
1127
+ ctx.shadowColor = self._fontShadowColor;
1128
+ ctx.shadowBlur = self._fontShadowBlur;
1129
+ ctx.shadowOffsetX = self._fontShadowOffsetX;
1130
+ ctx.shadowOffsetY = self._fontShadowOffsetY;
1131
+ ctx.textAlign = 'left';
1132
+ ctx.textBaseline = 'middle';
1133
+ ctx.fillText(text, textX, textY);
1134
+
1135
+ // parse inner shadow
1136
+ var innerShadow = self._innerShadow.split('px '),
1137
+ isOffsetX = self._innerShadow === 'none' ? 0 : parseInt(innerShadow[0], 10),
1138
+ isOffsetY = self._innerShadow === 'none' ? 0 : parseInt(innerShadow[1], 10),
1139
+ isBlur = self._innerShadow === 'none' ? 0 : parseInt(innerShadow[2], 10),
1140
+ isColor = self._innerShadow === 'none' ? '' : innerShadow[3];
1141
+
1142
+ // draw the inner-shadow (damn you canvas, this should be easier than this...)
1143
+ if (isBlur > 0) {
1144
+ var shadowCtx = self._shadowCtx,
1145
+ scw = shadowCtx.canvas.width,
1146
+ sch = shadowCtx.canvas.height;
1147
+
1148
+ shadowCtx.clearRect(0, 0, scw, sch);
1149
+ shadowCtx.shadowBlur = isBlur;
1150
+ shadowCtx.shadowColor = isColor;
1151
+
1152
+ // top shadow
1153
+ shadowCtx.shadowOffsetX = 0;
1154
+ shadowCtx.shadowOffsetY = isOffsetY;
1155
+ shadowCtx.fillRect(-1 * w, -100, 3 * w, 100);
1156
+
1157
+ // right shadow
1158
+ shadowCtx.shadowOffsetX = isOffsetX;
1159
+ shadowCtx.shadowOffsetY = 0;
1160
+ shadowCtx.fillRect(scw, -1 * h, 100, 3 * h);
1161
+
1162
+ // bottom shadow
1163
+ shadowCtx.shadowOffsetX = 0;
1164
+ shadowCtx.shadowOffsetY = isOffsetY;
1165
+ shadowCtx.fillRect(-1 * w, sch, 3 * w, 100);
1166
+
1167
+ // left shadow
1168
+ shadowCtx.shadowOffsetX = isOffsetX;
1169
+ shadowCtx.shadowOffsetY = 0;
1170
+ shadowCtx.fillRect(-100, -1 * h, 100, 3 * h);
1171
+
1172
+ // create a clipping mask on the main canvas
1173
+ self._roundedRect(ctx, bw + self.shadowL, bw + self.shadowT, w - bw * 2 - sw, h - bw * 2 - sh, br);
1174
+ ctx.clip();
1175
+
1176
+ // draw the inner-shadow from the off-DOM canvas
1177
+ ctx.drawImage(self._shadowCanvas, 0, 0, scw, sch, bw + self.shadowL, bw + self.shadowT, scw, sch);
1178
+ }
1179
+
1180
+ // draw to the visible canvas
1181
+ if (self._ctx) {
1182
+ self._ctx.clearRect(self._x, self._y, ctx.canvas.width, ctx.canvas.height);
1183
+ self._ctx.drawImage(self._renderCanvas, self._x, self._y);
1184
+ }
1185
+
1186
+ return self;
1187
+
1188
+ });
1189
+ },
1190
+
1191
+ /**
1192
+ * Destroy this input and stop rendering it.
1193
+ */
1194
+ destroy: function() {
1195
+ var self = this;
1196
+
1197
+ // pull from the inputs array
1198
+ var index = inputs.indexOf(self);
1199
+ if (index != -1) {
1200
+ inputs.splice(index, 1);
1201
+ }
1202
+
1203
+ // remove focus
1204
+ if (self._hasFocus) {
1205
+ self.blur();
1206
+ }
1207
+
1208
+ // remove the hidden input box
1209
+ document.body.removeChild(self._hiddenInput);
1210
+
1211
+ // remove off-DOM canvas
1212
+ self._renderCanvas = null;
1213
+ self._shadowCanvas = null;
1214
+ self._renderCtx = null;
1215
+ },
1216
+
1217
+ /**
1218
+ * Draw the text box area with either an image or background color.
1219
+ * @param {Function} fn Callback.
1220
+ */
1221
+ _drawTextBox: function(fn) {
1222
+ var self = this,
1223
+ ctx = self._renderCtx,
1224
+ w = self.outerW,
1225
+ h = self.outerH,
1226
+ br = self._borderRadius,
1227
+ bw = self._borderWidth,
1228
+ sw = self.shadowW,
1229
+ sh = self.shadowH;
1230
+
1231
+ // only draw the background shape if no image is being used
1232
+ if (self._backgroundImage === '') {
1233
+ ctx.fillStyle = self._backgroundColor;
1234
+ self._roundedRect(ctx, bw + self.shadowL, bw + self.shadowT, w - bw * 2 - sw, h - bw * 2 - sh, br);
1235
+ ctx.fill();
1236
+
1237
+ fn();
1238
+ } else {
1239
+ var img = new Image();
1240
+ img.src = self._backgroundImage;
1241
+ img.onload = function() {
1242
+ ctx.drawImage(img, 0, 0, img.width, img.height, bw + self.shadowL, bw + self.shadowT, w, h);
1243
+
1244
+ fn();
1245
+ };
1246
+ }
1247
+ },
1248
+
1249
+ /**
1250
+ * Deletes selected text in selection range and repositions cursor.
1251
+ * @return {Boolean} true if text removed.
1252
+ */
1253
+ _clearSelection: function() {
1254
+ var self = this;
1255
+
1256
+ if (self._selection[1] > 0) {
1257
+ // clear the selected contents
1258
+ var start = self._selection[0],
1259
+ end = self._selection[1];
1260
+
1261
+ self._value = self._value.substr(0, start) + self._value.substr(end);
1262
+ self._cursorPos = start;
1263
+ self._cursorPos = (self._cursorPos < 0) ? 0 : self._cursorPos;
1264
+ self._selection = [0, 0];
1265
+
1266
+ return true;
1267
+ }
1268
+
1269
+ return false;
1270
+ },
1271
+
1272
+ /**
1273
+ * Clip the text string to only return what fits in the visible text box.
1274
+ * @param {String} value The text to clip.
1275
+ * @return {String} The clipped text.
1276
+ */
1277
+ _clipText: function(value) {
1278
+ var self = this;
1279
+ value = (typeof value === 'undefined') ? self._value : value;
1280
+
1281
+ var textWidth = self._textWidth(value),
1282
+ fillPer = textWidth / (self._width - self._padding),
1283
+ text = fillPer > 1 ? value.substr(-1 * Math.floor(value.length / fillPer)) : value;
1284
+
1285
+ return text + '';
1286
+ },
1287
+
1288
+ /**
1289
+ * Gets the pixel with of passed text.
1290
+ * @param {String} text The text to measure.
1291
+ * @return {Number} The measured width.
1292
+ */
1293
+ _textWidth: function(text) {
1294
+ var self = this,
1295
+ ctx = self._renderCtx;
1296
+
1297
+ ctx.font = self._fontStyle + ' ' + self._fontWeight + ' ' + self._fontSize + 'px ' + self._fontFamily;
1298
+ ctx.textAlign = 'left';
1299
+
1300
+ return ctx.measureText(text).width;
1301
+ },
1302
+
1303
+ /**
1304
+ * Recalculate the outer with and height of the text box.
1305
+ */
1306
+ _calcWH: function() {
1307
+ var self = this;
1308
+
1309
+ // calculate the full width and height with padding, borders and shadows
1310
+ self.outerW = self._width + self._padding * 2 + self._borderWidth * 2 + self.shadowW;
1311
+ self.outerH = self._height + self._padding * 2 + self._borderWidth * 2 + self.shadowH;
1312
+ },
1313
+
1314
+ /**
1315
+ * Update the width and height of the off-DOM canvas when attributes are changed.
1316
+ */
1317
+ _updateCanvasWH: function() {
1318
+ var self = this,
1319
+ oldW = self._renderCanvas.width,
1320
+ oldH = self._renderCanvas.height;
1321
+
1322
+ // update off-DOM canvas
1323
+ self._renderCanvas.setAttribute('width', self.outerW);
1324
+ self._renderCanvas.setAttribute('height', self.outerH);
1325
+ self._shadowCanvas.setAttribute('width', self._width + self._padding * 2);
1326
+ self._shadowCanvas.setAttribute('height', self._height + self._padding * 2);
1327
+
1328
+ // clear the main canvas
1329
+ if (self._ctx) {
1330
+ self._ctx.clearRect(self._x, self._y, oldW, oldH);
1331
+ }
1332
+ },
1333
+
1334
+ /**
1335
+ * Update the size and position of the hidden input (better UX on mobile).
1336
+ */
1337
+ _updateHiddenInput: function() {
1338
+ var self = this;
1339
+
1340
+ self._hiddenInput.style.left = (self._x + self._extraX + (self._canvas ? self._canvas.offsetLeft : 0)) + 'px';
1341
+ self._hiddenInput.style.top = (self._y + self._extraY + (self._canvas ? self._canvas.offsetTop : 0)) + 'px';
1342
+ self._hiddenInput.style.width = (self._width + self._padding * 2) + 'px';
1343
+ self._hiddenInput.style.height = (self._height + self._padding * 2) + 'px';
1344
+ },
1345
+
1346
+ /**
1347
+ * Creates the path for a rectangle with rounded corners.
1348
+ * Must call ctx.fill() after calling this to draw the rectangle.
1349
+ * @param {Object} ctx Canvas context.
1350
+ * @param {Number} x x-coordinate to draw from.
1351
+ * @param {Number} y y-coordinate to draw from.
1352
+ * @param {Number} w Width of rectangle.
1353
+ * @param {Number} h Height of rectangle.
1354
+ * @param {Number} r Border radius.
1355
+ */
1356
+ _roundedRect: function(ctx, x, y, w, h, r) {
1357
+ if (w < 2 * r) r = w / 2;
1358
+ if (h < 2 * r) r = h / 2;
1359
+
1360
+ ctx.beginPath();
1361
+
1362
+ ctx.moveTo(x + r, y);
1363
+ ctx.lineTo(x + w - r, y);
1364
+ ctx.quadraticCurveTo(x + w, y, x + w, y + r);
1365
+ ctx.lineTo(x + w, y + h - r);
1366
+ ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
1367
+ ctx.lineTo(x + r, y + h);
1368
+ ctx.quadraticCurveTo(x, y + h, x, y + h - r);
1369
+ ctx.lineTo(x, y + r);
1370
+ ctx.quadraticCurveTo(x, y, x + r, y);
1371
+
1372
+ ctx.closePath();
1373
+ },
1374
+
1375
+ /**
1376
+ * Checks if a coordinate point is over the input box.
1377
+ * @param {Number} x x-coordinate position.
1378
+ * @param {Number} y y-coordinate position.
1379
+ * @return {Boolean} True if it is over the input box.
1380
+ */
1381
+ _overInput: function(x, y) {
1382
+ var self = this,
1383
+ xLeft = x >= self._x + self._extraX,
1384
+ xRight = x <= self._x + self._extraX + self._width + self._padding * 2,
1385
+ yTop = y >= self._y + self._extraY,
1386
+ yBottom = y <= self._y + self._extraY + self._height + self._padding * 2;
1387
+
1388
+ return xLeft && xRight && yTop && yBottom;
1389
+ },
1390
+
1391
+ /**
1392
+ * Use the mouse's x & y coordinates to determine
1393
+ * the position clicked in the text.
1394
+ * @param {Number} x X-coordinate.
1395
+ * @param {Number} y Y-coordinate.
1396
+ * @return {Number} Cursor position.
1397
+ */
1398
+ _clickPos: function(x, y) {
1399
+ var self = this,
1400
+ value = self._value;
1401
+
1402
+ // don't count placeholder text in this
1403
+ if (self._value === self._placeHolder) {
1404
+ value = '';
1405
+ }
1406
+
1407
+ // determine where the click was made along the string
1408
+ var text = self._clipText(value),
1409
+ totalW = 0,
1410
+ pos = text.length;
1411
+
1412
+ if (x - (self._x + self._extraX) < self._textWidth(text)) {
1413
+ // loop through each character to identify the position
1414
+ for (var i=0; i<text.length; i++) {
1415
+ totalW += self._textWidth(text[i]);
1416
+ if (totalW >= x - (self._x + self._extraX)) {
1417
+ pos = i;
1418
+ break;
1419
+ }
1420
+ }
1421
+ }
1422
+
1423
+ return pos;
1424
+ },
1425
+
1426
+ /**
1427
+ * Calculate the mouse position based on the event callback and the elements on the page.
1428
+ * @param {Event} e
1429
+ * @return {Object} x & y values
1430
+ */
1431
+ _mousePos: function(e) {
1432
+ var elm = e.target,
1433
+ x = e.pageX,
1434
+ y = e.pageY;
1435
+
1436
+ // support touch events in page location calculation
1437
+ if (e.touches && e.touches.length) {
1438
+ elm = e.touches[0].target;
1439
+ x = e.touches[0].pageX;
1440
+ y = e.touches[0].pageY;
1441
+ } else if (e.changedTouches && e.changedTouches.length) {
1442
+ elm = e.changedTouches[0].target;
1443
+ x = e.changedTouches[0].pageX;
1444
+ y = e.changedTouches[0].pageY;
1445
+ }
1446
+
1447
+ var style = document.defaultView.getComputedStyle(elm, undefined),
1448
+ paddingLeft = parseInt(style['paddingLeft'], 10) || 0,
1449
+ paddingTop = parseInt(style['paddingLeft'], 10) || 0,
1450
+ borderLeft = parseInt(style['borderLeftWidth'], 10) || 0,
1451
+ borderTop = parseInt(style['borderLeftWidth'], 10) || 0,
1452
+ htmlTop = document.body.parentNode.offsetTop || 0,
1453
+ htmlLeft = document.body.parentNode.offsetLeft || 0,
1454
+ offsetX = 0,
1455
+ offsetY = 0;
1456
+
1457
+ // calculate the total offset
1458
+ if (typeof elm.offsetParent !== 'undefined') {
1459
+ do {
1460
+ offsetX += elm.offsetLeft;
1461
+ offsetY += elm.offsetTop;
1462
+ } while ((elm = elm.offsetParent));
1463
+ }
1464
+
1465
+ // take into account borders and padding
1466
+ offsetX += paddingLeft + borderLeft + htmlLeft;
1467
+ offsetY += paddingTop + borderTop + htmlTop;
1468
+
1469
+ return {
1470
+ x: x - offsetX,
1471
+ y: y - offsetY
1472
+ };
1473
+ }
1474
+ };
1475
+ })();