Browse Source

add a tokenizer so choices can be automatically created and selected while user is typing or pasting into the field. fixes #101 #81 #292

Igor Vaynberg 12 years ago
parent
commit
289226cf6c
1 changed files with 85 additions and 9 deletions
  1. 85 9
      select2.js

+ 85 - 9
select2.js

@@ -416,6 +416,59 @@
         return count;
         return count;
     }
     }
 
 
+    /**
+     * Default tokenizer. This function uses breaks the input on substring match of any string from the
+     * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
+     * two options have to be defined in order for the tokenizer to work.
+     *
+     * @param input text user has typed so far or pasted into the search field
+     * @param selection currently selected choices
+     * @param selectCallback function(choice) callback tho add the choice to selection
+     * @param opts select2's opts
+     * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
+     */
+    function defaultTokenizer(input, selection, selectCallback, opts) {
+        var original = input, // store the original so we can compare and know if we need to tell the search to update its text
+            dupe = false, // check for whether a token we extracted represents a duplicate selected choice
+            token, // token
+            index, // position at which the separator was found
+            i, l, // looping variables
+            separator; // the matched separator
+
+        if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined;
+
+        while (true) {
+            index = -1;
+
+            for (i = 0, l = opts.tokenSeparators.length; i < l; i++) {
+                separator = opts.tokenSeparators[i];
+                index = input.indexOf(separator);
+                if (index >= 0) break;
+            }
+
+            if (index < 0) break; // did not find any token separator in the input string, bail
+
+            token = input.substring(0, index);
+            input = input.substring(index + separator.length);
+
+            if (token.length > 0) {
+                token = opts.createSearchChoice(token, selection);
+                if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
+                    dupe = false;
+                    for (i = 0, l = selection.length; i < l; i++) {
+                        if (equal(opts.id(token), opts.id(selection[i]))) {
+                            dupe = true; break;
+                        }
+                    }
+
+                    if (!dupe) selectCallback(token);
+                }
+            }
+        }
+
+        if (original.localeCompare(input) != 0) return input;
+    }
+
     /**
     /**
      * blurs any Select2 container that has focus when an element outside them was clicked or received focus
      * blurs any Select2 container that has focus when an element outside them was clicked or received focus
      *
      *
@@ -599,9 +652,6 @@
                 this.select = select = opts.element;
                 this.select = select = opts.element;
             }
             }
 
 
-            //Custom tags separator.
-            opts.separator = opts.separator || ",";
-
             if (select) {
             if (select) {
                 // these options are not allowed when attached to a select because they are picked up off the element itself
                 // these options are not allowed when attached to a select because they are picked up off the element itself
                 $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
                 $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () {
@@ -1074,12 +1124,19 @@
             }
             }
         },
         },
 
 
+        /**
+         * Default tokenizer function which does nothing
+         */
+        tokenize: function() {
+
+        },
+
         /**
         /**
          * @param initial whether or not this is the call to this method right after the dropdown has been opened
          * @param initial whether or not this is the call to this method right after the dropdown has been opened
          */
          */
         // abstract
         // abstract
         updateResults: function (initial) {
         updateResults: function (initial) {
-            var search = this.search, results = this.results, opts = this.opts, data, self=this;
+            var search = this.search, results = this.results, opts = this.opts, data, self=this, input;
 
 
             // if the search is currently hidden we do not alter the results
             // if the search is currently hidden we do not alter the results
             if (initial !== true && (this.showSearchInput === false || !this.opened())) {
             if (initial !== true && (this.showSearchInput === false || !this.opened())) {
@@ -1115,6 +1172,12 @@
                 render("<li class='select2-searching'>" + opts.formatSearching() + "</li>");
                 render("<li class='select2-searching'>" + opts.formatSearching() + "</li>");
             }
             }
 
 
+            // give the tokenizer a chance to pre-process the input
+            input = this.tokenize();
+            if (input != undefined && input != null) {
+                search.val(input);
+            }
+
             this.resultsPage = 1;
             this.resultsPage = 1;
             opts.query({
             opts.query({
                     term: search.val(),
                     term: search.val(),
@@ -1889,6 +1952,18 @@
             self.postprocessResults();
             self.postprocessResults();
         },
         },
 
 
+        tokenize: function() {
+            var input = this.search.val();
+            input = this.opts.tokenizer(input, this.data(), this.bind(this.onSelect), this.opts);
+            if (input != null && input != undefined) {
+                this.search.val(input);
+                if (input.length > 0) {
+                    this.open();
+                }
+            }
+
+        },
+
         // multi
         // multi
         onSelect: function (data) {
         onSelect: function (data) {
             this.addSelectedChoice(data);
             this.addSelectedChoice(data);
@@ -1898,10 +1973,9 @@
                 this.close();
                 this.close();
                 this.search.width(10);
                 this.search.width(10);
             } else {
             } else {
-                this.search.width(10);
-                this.resizeSearch();
-
                 if (this.countSelectableResults()>0) {
                 if (this.countSelectableResults()>0) {
+                    this.search.width(10);
+                    this.resizeSearch();
                     this.positionDropdown();
                     this.positionDropdown();
                 } else {
                 } else {
                     // if nothing left to select close
                     // if nothing left to select close
@@ -2032,7 +2106,6 @@
             containerLeft = this.selection.offset().left;
             containerLeft = this.selection.offset().left;
 
 
             searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
             searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding;
-
             if (searchWidth < minimumWidth) {
             if (searchWidth < minimumWidth) {
                 searchWidth = maxWidth - sideBorderPadding;
                 searchWidth = maxWidth - sideBorderPadding;
             }
             }
@@ -2232,7 +2305,10 @@
         id: function (e) { return e.id; },
         id: function (e) { return e.id; },
         matcher: function(term, text) {
         matcher: function(term, text) {
             return text.toUpperCase().indexOf(term.toUpperCase()) >= 0;
             return text.toUpperCase().indexOf(term.toUpperCase()) >= 0;
-        }
+        },
+        separator: ",",
+        tokenSeparators: [],
+        tokenizer: defaultTokenizer
     };
     };
 
 
     // exports
     // exports