Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14217 anikendra 1
/*
2
 * typeahead.js
3
 * https://github.com/twitter/typeahead.js
4
 * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
5
 */
6
 
7
var Input = (function() {
8
  'use strict';
9
 
10
  var specialKeyCodeMap;
11
 
12
  specialKeyCodeMap = {
13
    9: 'tab',
14
    27: 'esc',
15
    37: 'left',
16
    39: 'right',
17
    13: 'enter',
18
    38: 'up',
19
    40: 'down'
20
  };
21
 
22
  // constructor
23
  // -----------
24
 
25
  function Input(o) {
26
    var that = this, onBlur, onFocus, onKeydown, onInput;
27
 
28
    o = o || {};
29
 
30
    if (!o.input) {
31
      $.error('input is missing');
32
    }
33
 
34
    // bound functions
35
    onBlur = _.bind(this._onBlur, this);
36
    onFocus = _.bind(this._onFocus, this);
37
    onKeydown = _.bind(this._onKeydown, this);
38
    onInput = _.bind(this._onInput, this);
39
 
40
    this.$hint = $(o.hint);
41
    this.$input = $(o.input)
42
    .on('blur.tt', onBlur)
43
    .on('focus.tt', onFocus)
44
    .on('keydown.tt', onKeydown);
45
 
46
    // if no hint, noop all the hint related functions
47
    if (this.$hint.length === 0) {
48
      this.setHint =
49
      this.getHint =
50
      this.clearHint =
51
      this.clearHintIfInvalid = _.noop;
52
    }
53
 
54
    // ie7 and ie8 don't support the input event
55
    // ie9 doesn't fire the input event when characters are removed
56
    // not sure if ie10 is compatible
57
    if (!_.isMsie()) {
58
      this.$input.on('input.tt', onInput);
59
    }
60
 
61
    else {
62
      this.$input.on('keydown.tt keypress.tt cut.tt paste.tt', function($e) {
63
        // if a special key triggered this, ignore it
64
        if (specialKeyCodeMap[$e.which || $e.keyCode]) { return; }
65
 
66
        // give the browser a chance to update the value of the input
67
        // before checking to see if the query changed
68
        _.defer(_.bind(that._onInput, that, $e));
69
      });
70
    }
71
 
72
    // the query defaults to whatever the value of the input is
73
    // on initialization, it'll most likely be an empty string
74
    this.query = this.$input.val();
75
 
76
    // helps with calculating the width of the input's value
77
    this.$overflowHelper = buildOverflowHelper(this.$input);
78
  }
79
 
80
  // static methods
81
  // --------------
82
 
83
  Input.normalizeQuery = function(str) {
84
    // strips leading whitespace and condenses all whitespace
85
    return (str || '').replace(/^\s*/g, '').replace(/\s{2,}/g, ' ');
86
  };
87
 
88
  // instance methods
89
  // ----------------
90
 
91
  _.mixin(Input.prototype, EventEmitter, {
92
 
93
    // ### private
94
 
95
    _onBlur: function onBlur() {
96
      this.resetInputValue();
97
      this.trigger('blurred');
98
    },
99
 
100
    _onFocus: function onFocus() {
101
      this.trigger('focused');
102
    },
103
 
104
    _onKeydown: function onKeydown($e) {
105
      // which is normalized and consistent (but not for ie)
106
      var keyName = specialKeyCodeMap[$e.which || $e.keyCode];
107
 
108
      this._managePreventDefault(keyName, $e);
109
      if (keyName && this._shouldTrigger(keyName, $e)) {
110
        this.trigger(keyName + 'Keyed', $e);
111
      }
112
    },
113
 
114
    _onInput: function onInput() {
115
      this._checkInputValue();
116
    },
117
 
118
    _managePreventDefault: function managePreventDefault(keyName, $e) {
119
      var preventDefault, hintValue, inputValue;
120
 
121
      switch (keyName) {
122
        case 'tab':
123
          hintValue = this.getHint();
124
          inputValue = this.getInputValue();
125
 
126
          preventDefault = hintValue &&
127
            hintValue !== inputValue &&
128
            !withModifier($e);
129
          break;
130
 
131
        case 'up':
132
        case 'down':
133
          preventDefault = !withModifier($e);
134
          break;
135
 
136
        default:
137
          preventDefault = false;
138
      }
139
 
140
      preventDefault && $e.preventDefault();
141
    },
142
 
143
    _shouldTrigger: function shouldTrigger(keyName, $e) {
144
      var trigger;
145
 
146
      switch (keyName) {
147
        case 'tab':
148
          trigger = !withModifier($e);
149
          break;
150
 
151
        default:
152
          trigger = true;
153
      }
154
 
155
      return trigger;
156
    },
157
 
158
    _checkInputValue: function checkInputValue() {
159
      var inputValue, areEquivalent, hasDifferentWhitespace;
160
 
161
      inputValue = this.getInputValue();
162
      areEquivalent = areQueriesEquivalent(inputValue, this.query);
163
      hasDifferentWhitespace = areEquivalent ?
164
        this.query.length !== inputValue.length : false;
165
 
166
      this.query = inputValue;
167
 
168
      if (!areEquivalent) {
169
        this.trigger('queryChanged', this.query);
170
      }
171
 
172
      else if (hasDifferentWhitespace) {
173
        this.trigger('whitespaceChanged', this.query);
174
      }
175
    },
176
 
177
    // ### public
178
 
179
    focus: function focus() {
180
      this.$input.focus();
181
    },
182
 
183
    blur: function blur() {
184
      this.$input.blur();
185
    },
186
 
187
    getQuery: function getQuery() {
188
      return this.query;
189
    },
190
 
191
    setQuery: function setQuery(query) {
192
      this.query = query;
193
    },
194
 
195
    getInputValue: function getInputValue() {
196
      return this.$input.val();
197
    },
198
 
199
    setInputValue: function setInputValue(value, silent) {
200
      this.$input.val(value);
201
 
202
      // silent prevents any additional events from being triggered
203
      silent ? this.clearHint() : this._checkInputValue();
204
    },
205
 
206
    resetInputValue: function resetInputValue() {
207
      this.setInputValue(this.query, true);
208
    },
209
 
210
    getHint: function getHint() {
211
      return this.$hint.val();
212
    },
213
 
214
    setHint: function setHint(value) {
215
      this.$hint.val(value);
216
    },
217
 
218
    clearHint: function clearHint() {
219
      this.setHint('');
220
    },
221
 
222
    clearHintIfInvalid: function clearHintIfInvalid() {
223
      var val, hint, valIsPrefixOfHint, isValid;
224
 
225
      val = this.getInputValue();
226
      hint = this.getHint();
227
      valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0;
228
      isValid = val !== '' && valIsPrefixOfHint && !this.hasOverflow();
229
 
230
      !isValid && this.clearHint();
231
    },
232
 
233
    getLanguageDirection: function getLanguageDirection() {
234
      return (this.$input.css('direction') || 'ltr').toLowerCase();
235
    },
236
 
237
    hasOverflow: function hasOverflow() {
238
      // 2 is arbitrary, just picking a small number to handle edge cases
239
      var constraint = this.$input.width() - 2;
240
 
241
      this.$overflowHelper.text(this.getInputValue());
242
 
243
      return this.$overflowHelper.width() >= constraint;
244
    },
245
 
246
    isCursorAtEnd: function() {
247
      var valueLength, selectionStart, range;
248
 
249
      valueLength = this.$input.val().length;
250
      selectionStart = this.$input[0].selectionStart;
251
 
252
      if (_.isNumber(selectionStart)) {
253
       return selectionStart === valueLength;
254
      }
255
 
256
      else if (document.selection) {
257
        // NOTE: this won't work unless the input has focus, the good news
258
        // is this code should only get called when the input has focus
259
        range = document.selection.createRange();
260
        range.moveStart('character', -valueLength);
261
 
262
        return valueLength === range.text.length;
263
      }
264
 
265
      return true;
266
    },
267
 
268
    destroy: function destroy() {
269
      this.$hint.off('.tt');
270
      this.$input.off('.tt');
271
 
272
      this.$hint = this.$input = this.$overflowHelper = null;
273
    }
274
  });
275
 
276
  return Input;
277
 
278
  // helper functions
279
  // ----------------
280
 
281
  function buildOverflowHelper($input) {
282
    return $('<pre aria-hidden="true"></pre>')
283
    .css({
284
      // position helper off-screen
285
      position: 'absolute',
286
      visibility: 'hidden',
287
      // avoid line breaks and whitespace collapsing
288
      whiteSpace: 'pre',
289
      // use same font css as input to calculate accurate width
290
      fontFamily: $input.css('font-family'),
291
      fontSize: $input.css('font-size'),
292
      fontStyle: $input.css('font-style'),
293
      fontVariant: $input.css('font-variant'),
294
      fontWeight: $input.css('font-weight'),
295
      wordSpacing: $input.css('word-spacing'),
296
      letterSpacing: $input.css('letter-spacing'),
297
      textIndent: $input.css('text-indent'),
298
      textRendering: $input.css('text-rendering'),
299
      textTransform: $input.css('text-transform')
300
    })
301
    .insertAfter($input);
302
  }
303
 
304
  function areQueriesEquivalent(a, b) {
305
    return Input.normalizeQuery(a) === Input.normalizeQuery(b);
306
  }
307
 
308
  function withModifier($e) {
309
    return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey;
310
  }
311
})();