Browse Source

Rewrite as ES2015 class

JC Brand 7 years ago
parent
commit
ad73abbd12
1 changed files with 185 additions and 231 deletions
  1. 185 231
      src/converse-autocomplete.js

+ 185 - 231
src/converse-autocomplete.js

@@ -20,120 +20,147 @@
             const { _converse } = this;
 
             _converse.FILTER_CONTAINS = function (text, input) {
-                return RegExp($.regExpEscape(input.trim()), "i").test(text);
+                return RegExp(helpers.regExpEscape(input.trim()), "i").test(text);
             };
 
             _converse.FILTER_STARTSWITH = function (text, input) {
-                return RegExp("^" + $.regExpEscape(input.trim()), "i").test(text);
+                return RegExp("^" + helpers.regExpEscape(input.trim()), "i").test(text);
             };
 
-            const _ac = function (el, o) {
-                const me = this;
+            const SORT_BYLENGTH = function (a, b) {
+                if (a.length !== b.length) {
+                    return a.length - b.length;
+                }
+                return a < b? -1 : 1;
+            };
+
+            const REPLACE = (text) => (this.input.value = text.value);
+
+            const ITEM = (text, input) => {
+                input = input.trim();
+                const element = document.createElement("li");
+                element.setAttribute("aria-selected", "false");
+
+                const regex = new RegExp("("+input+")", "ig");
+                const parts = input ? text.split(regex) : [text];
+                parts.forEach((txt) => {
+                    if (input && txt.match(regex)) {
+                        const match = document.createElement("mark");
+                        match.textContent = txt;
+                        element.appendChild(match);
+                    } else {
+                        element.appendChild(document.createTextNode(txt));
+                    }
+                });
+                return element;
+            };
+
+
+            class AutoComplete {
+                
+                constructor (el, o) {
+                    this.is_opened = false;
 
-                this.is_opened = false;
+                    if (u.hasClass('.suggestion-box', el)) {
+                        this.container = el;
+                    } else {
+                        this.container = el.querySelector('.suggestion-box');
+                    }
+                    this.input = this.container.querySelector('.suggestion-box__input');
+                    this.input.setAttribute("autocomplete", "off");
+                    this.input.setAttribute("aria-autocomplete", "list");
+
+                    this.ul = this.container.querySelector('.suggestion-box__results');
+                    this.status = this.container.querySelector('.suggestion-box__additions');
+
+                    o = o || {};
+
+                    _.assignIn(this, {
+                        'match_current_word': false, // Match only the current word, otherwise all input is matched
+                        'match_on_tab': false, // Whether matching should only start when tab's pressed
+                        'min_chars': 2,
+                        'max_items': 10,
+                        'auto_evaluate': true,
+                        'auto_first': false,
+                        'data': _.identity,
+                        'filter': _converse.FILTER_CONTAINS,
+                        'sort': o.sort === false ? false : SORT_BYLENGTH,
+                        'item': ITEM,
+                        'replace': REPLACE
+                    }, o);
+
+                    this.index = -1;
 
-                if (u.hasClass('.suggestion-box', el)) {
-                    this.container = el;
-                } else {
-                    this.container = el.querySelector('.suggestion-box');
+                    const input = {
+                        "blur": this.close.bind(this, {'reason': "blur" }),
+                        "keydown": () => this.onKeyDown()
+                    }
+                    if (this.auto_evaluate) {
+                        input["input"] = this.evaluate.bind(this);
+                    }
+
+                    this.bindEvents(input)
+
+                    if (this.input.hasAttribute("list")) {
+                        this.list = "#" + this.input.getAttribute("list");
+                        this.input.removeAttribute("list");
+                    } else {
+                        this.list = this.input.getAttribute("data-list") || o.list || [];
+                    }
                 }
-                this.input = $(this.container.querySelector('.suggestion-box__input'));
-                this.input.setAttribute("autocomplete", "off");
-                this.input.setAttribute("aria-autocomplete", "list");
-
-                this.ul = $(this.container.querySelector('.suggestion-box__results'));
-                this.status = $(this.container.querySelector('.suggestion-box__additions'));
-
-                o = o || {};
-
-                configure(this, {
-                    'match_current_word': false, // Match only the current word, otherwise all input is matched
-                    'match_on_tab': false, // Whether matching should only start when tab's pressed
-                    'min_chars': 2,
-                    'max_items': 10,
-                    'auto_evaluate': true,
-                    'auto_first': false,
-                    'data': _ac.DATA,
-                    'filter': _ac.FILTER_CONTAINS,
-                    'sort': o.sort === false ? false : _ac.SORT_BYLENGTH,
-                    'item': _ac.ITEM,
-                    'replace': _ac.REPLACE
-                }, o);
-
-                this.index = -1;
-
-                const input = {
-                    "blur": this.close.bind(this, { reason: "blur" }),
-                    "keydown": function(evt) {
-                        const c = evt.keyCode;
-
-                        // If the dropdown `ul` is in view, then act on keydown for the following keys:
-                        // Enter / Esc / Up / Down
-                        if(me.opened) {
-                            if (c === _converse.keycodes.ENTER && me.selected) {
-                                evt.preventDefault();
-                                me.select();
-                            } else if (c === _converse.keycodes.ESCAPE) {
-                                me.close({ reason: "esc" });
-                            } else if (c === _converse.keycodes.UP_ARROW || c === _converse.keycodes.DOWN_ARROW) {
-                                evt.preventDefault();
-                                me[c === _converse.keycodes.UP_ARROW ? "previous" : "next"]();
-                            }
+
+                onKeyDown (evt) {
+                    const c = evt.keyCode;
+                    // If the dropdown `ul` is in view, then act on keydown for the following keys:
+                    // Enter / Esc / Up / Down
+                    if (this.opened) {
+                        if (c === _converse.keycodes.ENTER && this.selected) {
+                            evt.preventDefault();
+                            this.select();
+                        } else if (c === _converse.keycodes.ESCAPE) {
+                            this.close({ reason: "esc" });
+                        } else if (c === _converse.keycodes.UP_ARROW || c === _converse.keycodes.DOWN_ARROW) {
+                            evt.preventDefault();
+                            this[c === _converse.keycodes.UP_ARROW ? "previous" : "next"]();
                         }
                     }
                 }
-                if (this.auto_evaluate) {
-                    input["input"] = this.evaluate.bind(this);
-                }
 
-                // Bind events
-                this._events = {
-                    'input': input,
-                    'form': {
-                        "submit": this.close.bind(this, { reason: "submit" })
-                    },
-                    'ul': {
-                        "mousedown": function(evt) {
-                            let li = evt.target;
-                            if (li !== this) {
-                                while (li && !(/li/i).test(li.nodeName)) {
-                                    li = li.parentNode;
-                                }
+                bindEvents (input) {
+                    // Bind events
+                    this._events = {
+                        'input': input,
+                        'form': {
+                            "submit": this.close.bind(this, { reason: "submit" })
+                        },
+                        'ul': {
+                            "mousedown": (evt) => {
+                                let li = evt.target;
+                                if (li !== this) {
+                                    while (li && !(/li/i).test(li.nodeName)) {
+                                        li = li.parentNode;
+                                    }
 
-                                if (li && evt.button === 0) {  // Only select on left click
-                                    evt.preventDefault();
-                                    me.select(li, evt.target);
+                                    if (li && evt.button === 0) {  // Only select on left click
+                                        evt.preventDefault();
+                                        this.select(li, evt.target);
+                                    }
                                 }
                             }
                         }
-                    }
-                };
-
-                $.bind(this.input, this._events.input);
-                $.bind(this.input.form, this._events.form);
-                $.bind(this.ul, this._events.ul);
-
-                if (this.input.hasAttribute("list")) {
-                    this.list = "#" + this.input.getAttribute("list");
-                    this.input.removeAttribute("list");
-                }
-                else {
-                    this.list = this.input.getAttribute("data-list") || o.list || [];
+                    };
+                    helpers.bind(this.input, this._events.input);
+                    helpers.bind(this.input.form, this._events.form);
+                    helpers.bind(this.ul, this._events.ul);
                 }
 
-                _ac.all.push(this);
-            }
-
-            _ac.prototype = {
                 set list (list) {
-                    if (Array.isArray(list)) {
+                    if (Array.isArray(list) || typeof list === "function") {
                         this._list = list;
-                    }
-                    else if (typeof list === "string" && _.includes(list, ",")) {
+                    } else if (typeof list === "string" && _.includes(list, ",")) {
                         this._list = list.split(/\s*,\s*/);
-                    }
-                    else { // Element or CSS selector
-                        list = $(list);
+                    } else { // Element or CSS selector
+                        list = helpers.getElement(list);
                         if (list && list.children) {
                             const items = [];
                             slice.apply(list.children).forEach(function (el) {
@@ -153,27 +180,26 @@
                     if (document.activeElement === this.input) {
                         this.evaluate();
                     }
-                },
+                }
 
-                get selected() {
+                get selected () {
                     return this.index > -1;
-                },
+                }
 
-                get opened() {
+                get opened () {
                     return this.is_opened;
-                },
+                }
 
                 close (o) {
                     if (!this.opened) {
                         return;
                     }
-
                     this.ul.setAttribute("hidden", "");
                     this.is_opened = false;
                     this.index = -1;
 
-                    $.fire(this.input, "suggestion-box-close", o || {});
-                },
+                    helpers.fire(this.input, "suggestion-box-close", o || {});
+                }
 
                 open () {
                     this.ul.removeAttribute("hidden");
@@ -183,13 +209,13 @@
                         this.goto(0);
                     }
 
-                    $.fire(this.input, "suggestion-box-open");
-                },
+                    helpers.fire(this.input, "suggestion-box-open");
+                }
 
                 destroy () {
                     //remove events from the input and its form
-                    $.unbind(this.input, this._events.input);
-                    $.unbind(this.input.form, this._events.form);
+                    helpers.unbind(this.input, this._events.input);
+                    helpers.unbind(this.input.form, this._events.form);
 
                     //move the input out of the suggestion-box container and remove the container and its children
                     const parentNode = this.container.parentNode;
@@ -200,26 +226,18 @@
                     //remove autocomplete and aria-autocomplete attributes
                     this.input.removeAttribute("autocomplete");
                     this.input.removeAttribute("aria-autocomplete");
-
-                    //remove this awesomeplete instance from the global array of instances
-                    var indexOfAutoComplete = _ac.all.indexOf(this);
-
-                    if (indexOfAutoComplete !== -1) {
-                        _ac.all.splice(indexOfAutoComplete, 1);
-                    }
-                },
+                }
 
                 next () {
-                    var count = this.ul.children.length;
+                    const count = this.ul.children.length;
                     this.goto(this.index < count - 1 ? this.index + 1 : (count ? 0 : -1) );
-                },
+                }
 
                 previous () {
-                    var count = this.ul.children.length;
-                    var pos = this.index - 1;
-
+                    const count = this.ul.children.length,
+                          pos = this.index - 1;
                     this.goto(this.selected && pos !== -1 ? pos : count - 1);
-                },
+                }
 
                 // Should not be used, highlights specific item without any checks!
                 goto (i) {
@@ -238,11 +256,11 @@
                         // scroll to highlighted element in case parent's height is fixed
                         this.ul.scrollTop = lis[i].offsetTop - this.ul.clientHeight + lis[i].clientHeight;
 
-                        $.fire(this.input, "suggestion-box-highlight", {
+                        helpers.fire(this.input, "suggestion-box-highlight", {
                             text: this.suggestions[this.index]
                         });
                     }
-                },
+                }
 
                 select (selected, origin) {
                     if (selected) {
@@ -253,7 +271,7 @@
 
                     if (selected) {
                         const suggestion = this.suggestions[this.index],
-                            allowed = $.fire(this.input, "suggestion-box-select", {
+                            allowed = helpers.fire(this.input, "suggestion-box-select", {
                                 'text': suggestion,
                                 'origin': origin || selected
                             });
@@ -265,7 +283,7 @@
                             this.trigger("suggestion-box-selectcomplete", {'text': suggestion});
                         }
                     }
-                },
+                }
 
                 keyPressed (ev) {
                     if (_.includes([
@@ -284,7 +302,7 @@
                     if (this.auto_completing) {
                         this.evaluate();
                     }
-                },
+                }
 
                 evaluate (ev) {
                     let value = this.input.value;
@@ -292,12 +310,14 @@
                         value = u.getCurrentWord(this.input);
                     }
 
-                    if (value.length >= this.min_chars && this._list.length > 0) {
+                    const list = typeof this._list === "function" ? this._list() : this._list;
+
+                    if (value.length >= this.min_chars && list.length > 0) {
                         this.index = -1;
                         // Populate list with options that match
                         this.ul.innerHTML = "";
 
-                        this.suggestions = this._list
+                        this.suggestions = list
                             .map(item => new Suggestion(this.data(item, value)))
                             .filter(item => this.filter(item, value));
 
@@ -317,46 +337,11 @@
                         this.auto_completing = false;
                     }
                 }
-            };
+            }
 
             // Make it an event emitter
-            _.extend(_ac.prototype, Backbone.Events);
+            _.extend(AutoComplete.prototype, Backbone.Events);
 
-            // Static methods/properties
-            _ac.all = [];
-
-            _ac.SORT_BYLENGTH = function (a, b) {
-                if (a.length !== b.length) {
-                    return a.length - b.length;
-                }
-
-                return a < b? -1 : 1;
-            };
-
-            _ac.ITEM = function (text, input) {
-                input = input.trim();
-                var element = document.createElement("li");
-                element.setAttribute("aria-selected", "false");
-
-                var regex = new RegExp("("+input+")", "ig");
-                var parts = input ? text.split(regex) : [text];
-                parts.forEach(function (txt) {
-                    if (input && txt.match(regex)) {
-                        var match = document.createElement("mark");
-                        match.textContent = txt;
-                        element.appendChild(match);
-                    } else {
-                        element.appendChild(document.createTextNode(txt));
-                    }
-                });
-                return element;
-            };
-
-            _ac.REPLACE = function (text) {
-                this.input.value = text.value;
-            };
-
-            _ac.DATA = function (item/*, input*/) { return item; };
 
             // Private functions
 
@@ -377,89 +362,58 @@
                 return "" + this.label;
             };
 
-            function configure (instance, properties, o) {
-                for (var i in properties) {
-                    if (!Object.prototype.hasOwnProperty.call(properties, i)) {
-                        continue;
-                    }
-
-                    const initial = properties[i],
-                          attr_value = instance.input.getAttribute("data-" + i.toLowerCase());
-
-                    if (typeof initial === "number") {
-                        instance[i] = parseInt(attr_value, 10);
-                    } else if (initial === false) { // Boolean options must be false by default anyway
-                        instance[i] = attr_value !== null;
-                    } else if (initial instanceof Function) {
-                        instance[i] = null;
-                    } else {
-                        instance[i] = attr_value;
-                    }
-
-                    if (!instance[i] && instance[i] !== 0) {
-                        instance[i] = (i in o)? o[i] : initial;
-                    }
-                }
-            }
-
             // Helpers
             var slice = Array.prototype.slice;
 
-            function $(expr, con) {
-                return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
-            }
+            const helpers = {
 
-            function $$(expr, con) {
-                return slice.call((con || document).querySelectorAll(expr));
-            }
+                getElement (expr, el) {
+                    return typeof expr === "string"? (el || document).querySelector(expr) : expr || null;
+                },
 
-            $.bind = function(element, o) {
-                if (element) {
-                    for (var event in o) {
-                        if (!Object.prototype.hasOwnProperty.call(o, event)) {
-                            continue;
+                bind (element, o) {
+                    if (element) {
+                        for (var event in o) {
+                            if (!Object.prototype.hasOwnProperty.call(o, event)) {
+                                continue;
+                            }
+                            const callback = o[event];
+                            event.split(/\s+/).forEach(event => element.addEventListener(event, callback));
                         }
-                        const callback = o[event];
-                        event.split(/\s+/).forEach(event => element.addEventListener(event, callback));
                     }
-                }
-            };
+                },
 
-            $.unbind = function(element, o) {
-                if (element) {
-                    for (var event in o) {
-                        if (!Object.prototype.hasOwnProperty.call(o, event)) {
-                            continue;
+                unbind (element, o) {
+                    if (element) {
+                        for (var event in o) {
+                            if (!Object.prototype.hasOwnProperty.call(o, event)) {
+                                continue;
+                            }
+                            const callback = o[event];
+                            event.split(/\s+/).forEach(event => element.removeEventListener(event, callback));
                         }
-                        const callback = o[event];
-                        event.split(/\s+/).forEach(event => element.removeEventListener(event, callback));
                     }
-                }
-            };
-
-            $.fire = function(target, type, properties) {
-                var evt = document.createEvent("HTMLEvents");
+                },
 
-                evt.initEvent(type, true, true );
+                fire (target, type, properties) {
+                    const evt = document.createEvent("HTMLEvents");
+                    evt.initEvent(type, true, true );
 
-                for (var j in properties) {
-                    if (!Object.prototype.hasOwnProperty.call(properties, j)) {
-                        continue;
+                    for (var j in properties) {
+                        if (!Object.prototype.hasOwnProperty.call(properties, j)) {
+                            continue;
+                        }
+                        evt[j] = properties[j];
                     }
-                    evt[j] = properties[j];
-                }
-
-                return target.dispatchEvent(evt);
-            };
-
-            $.regExpEscape = function (s) {
-                return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
-            };
+                    return target.dispatchEvent(evt);
+                },
 
-            _ac.$ = $;
-            _ac.$$ = $$;
+                regExpEscape (s) {
+                    return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
+                }
+            }
 
-            _converse.AutoComplete = _ac;
+            _converse.AutoComplete = AutoComplete;
         }
     });
 }));