canvasinput-rails 1.2.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ })();