PageSourceSearch

https://poweredbyh4c.org/Assets/mentions/js/jquery.mentionsInput.js

js poweredbyh4c.org collected 2026-10-08 19:55:14 UTC 29,028 bytes, 588 lines download raw bytes

1/*
2 * Mentions Input
3 * Version 1.0.2
4 * Written by: Kenneth Auchenberg (Podio)
5 *
6 * Using underscore.js
7 *
8 * License: MIT License - http://www.opensource.org/licenses/mit-license.php
9 */
10
11(function ($, _, undefined) {
12
13    // Settings
14    var KEY = { BACKSPACE: 8, TAB: 9, RETURN: 13, ESC: 27, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, COMMA: 188, SPACE: 32, HOME: 36, END: 35 }; // Keys "enum"
15
16    //Default settings
17    var defaultSettings = {
18        triggerChar: '@', //Char that respond to event
19        onDataRequest: $.noop, //Function where we can search the data
20        minChars: 2, //Minimum chars to fire the event
21        allowRepeat: false, //Allow repeat mentions
22        showAvatars: true, //Show the avatars
23        elastic: false, //Grow the textarea automatically
24        defaultValue: '',
25        onCaret: false,
26        classes: {
27            autoCompleteItemActive: "active" //Classes to apply in each item
28        },
29        templates: {
30            wrapper: _.template('<div class="mentions-input-box"></div>'),
31            autocompleteList: _.template('<div class="mentions-autocomplete-list"></div>'),
32            autocompleteListItem: _.template('<li data-ref-id="<%= id %>" data-ref-type="<%= type %>" data-display="<%= display %>"><%= content %></li>'),
33            autocompleteListItemAvatar: _.template('<img src="<%= avatar %>" />'),
34            autocompleteListItemIcon: _.template('<div class="icon <%= icon %>"></div>'),
35            mentionsOverlay: _.template('<div class="mentions"><div></div></div>'),
36            mentionItemSyntax: _.template('@[<%= value %>](<%= type %>:<%= id %>)'),
37            mentionItemHighlight: _.template('<strong><span><%= value %></span></strong>')
38        }
39    };
40
41    //Class util
42    var utils = {
43        //Encodes the character with _.escape function (undersocre)
44        htmlEncode: function (str) {
45            return _.escape(str);
46        },
47        //Encodes the character to be used with RegExp
48        regexpEncode: function (str) {
49            return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
50        },
51        highlightTerm: function (value, term) {
52            if (!term && !term.length) {
53                return value;
54            }
55            return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
56        },
57        //Sets the caret in a valid position
58        setCaratPosition: function (domNode, caretPos) {
59            if (domNode.createTextRange) {
60                var range = domNode.createTextRange();
61                range.move('character', caretPos);
62                range.select();
63            } else {
64                if (domNode.selectionStart) {
65                    domNode.focus();
66                    domNode.setSelectionRange(caretPos, caretPos);
67                } else {
68                    domNode.focus();
69                }
70            }
71        },
72        //Deletes the white spaces
73        rtrim: function (string) {
74            return string.replace(/\s+$/, "");
75        }
76    };
77
78    //Main class of MentionsInput plugin
79    var MentionsInput = function (settings) {
80
81        var domInput,
82            elmInputBox,
83            elmInputWrapper,
84            elmAutocompleteList,
85            elmWrapperBox,
86            elmMentionsOverlay,
87            elmActiveAutoCompleteItem,
88            mentionsCollection = [],
89            autocompleteItemCollection = {},
90            inputBuffer = [],
91            currentDataQuery = '';
92
93        //Mix the default setting with the users settings
94        settings = $.extend(true, {}, defaultSettings, settings);
95
96        //Initializes the text area target
97        function initTextarea() {
98            elmInputBox = $(domInput); //Get the text area target
99
100            //If the text area is already configured, return
101            if (elmInputBox.attr('data-mentions-input') === 'true') {
102                return;
103            }
104
105            elmInputWrapper = elmInputBox.parent(); //Get the DOM element parent
106            elmWrapperBox = $(settings.templates.wrapper());
107            elmInputBox.wrapAll(elmWrapperBox);
107 //Wrap all the text area into the div elmWrapperBox
108            elmWrapperBox = elmInputWrapper.find('> div.mentions-input-box'); //Obtains the div elmWrapperBox that now contains the text area
109
110            elmInputBox.attr('data-mentions-input', 'true'); //Sets the attribute data-mentions-input to true -> Defines if the text area is already configured
111            elmInputBox.bind('keydown', onInputBoxKeyDown); //Bind the keydown event to the text area
112            elmInputBox.bind('keypress', onInputBoxKeyPress); //Bind the keypress event to the text area
113            elmInputBox.bind('click', onInputBoxClick); //Bind the click event to the text area
114            elmInputBox.bind('blur', onInputBoxBlur); //Bind the blur event to the text area
115
116            if (navigator.userAgent.indexOf("MSIE 8") > -1) {
117                elmInputBox.bind('propertychange', onInputBoxInput); //IE8 won't fire the input event, so let's bind to the propertychange
118            } else {
119                elmInputBox.bind('input', onInputBoxInput); //Bind the input event to the text area
120            }
121
122            // Elastic textareas, grow automatically
123            if (settings.elastic) {
124                elmInputBox.elastic();
125            }
126        }
127
128        //Initializes the autocomplete list, append to elmWrapperBox and delegate the mousedown event to li elements
129        function initAutocomplete() {
130            elmAutocompleteList = $(settings.templates.autocompleteList()); //Get the HTML code for the list
131            elmAutocompleteList.appendTo(elmWrapperBox); //Append to elmWrapperBox element
132            elmAutocompleteList.delegate('li', 'mousedown', onAutoCompleteItemClick); //Delegate the event
133        }
134
135        //Initializes the mentions' overlay
136        function initMentionsOverlay() {
137            elmMentionsOverlay = $(settings.templates.mentionsOverlay()); //Get the HTML code of the mentions' overlay
138            elmMentionsOverlay.prependTo(elmWrapperBox); //Insert into elmWrapperBox the mentions overlay
139        }
140
141        //Updates the values of the main variables
142        function updateValues() {
143            var syntaxMessage = getInputBoxValue(); //Get the actual value of the text area
144
145            _.each(mentionsCollection, function (mention) {
146                var textSyntax = settings.templates.mentionItemSyntax(mention);
147                syntaxMessage = syntaxMessage.replace(new RegExp(utils.regexpEncode(mention.value), 'g'), textSyntax);
148            });
149
150            var mentionText = utils.htmlEncode(syntaxMessage); //Encode the syntaxMessage
151
152            _.each(mentionsCollection, function (mention) {
153                var formattedMention = _.extend({}, mention, { value: utils.htmlEncode(mention.value) });
154                var textSyntax = settings.templates.mentionItemSyntax(formattedMention);
155                var textHighlight = settings.templates.mentionItemHighlight(formattedMention);
156
157                mentionText = mentionText.replace(new RegExp(utils.regexpEncode(textSyntax), 'g'), textHighlight);
158            });
159
160            mentionText = mentionText.replace(/\n/g, '<br />'); //Replace the escape character for <br />
161            mentionText = mentionText.replace(/ {2}/g, '&nbsp; '); //Replace the 2 preceding token to &nbsp;
162
163            elmInputBox.data('messageText', syntaxMessage); //Save the messageText to elmInputBox
164            elmInputBox.trigger('updated');
165            elmMentionsOverlay.find('div').html(mentionText); //Insert into a div of the elmMentionsOverlay the mention text
166        }
167
168        //Cleans the buffer
169        function resetBuffer() {
170            inputBuffer = [];
171        }
172
173        //Updates the mentions collection
174        function updateMentionsCollection() {
175            var inputText = getInputBoxValue(); //Get the actual value of text area
176
177            //Returns the values that doesn't match the condition
178            mentionsCollection = _.reject(mentionsCollection, function (mention, index) {
179                return !mention.value || inputText.indexOf(mention.value) == -1;
180            });
181            mentionsCollection = _.compact(mentionsCollection); //Delete all the falsy values of the array and return the new array
182        }
183
184        //Adds mention to mentions collections
185        function addMention(mention) {
186
187            var currentMessage = getInputBoxValue(),
188                caretStart = elmInputBox[0].selectionStart,
189                shortestDistance = false,
190                bestLastIndex = false;
191
192            // Using a regex to figure out positions
193            var regex = new RegExp("\\" + settings.triggerChar + currentDataQuery, "gi"),
194                regexMatch;
195
196            while (regexMatch = regex.exec(currentMessage)) {
197                if (shortestDistance === false || Math.abs(regex.lastIndex - caretStart) < shortestDistance) {
198                    shortestDistance = Math.abs(regex.lastIndex - caretStart);
199                    bestLastIndex = regex.lastIndex;
200                }
201            }
202
203            var startCaretPosition = bestLastIndex - currentDataQuery.length /*- 1*/; //Set the start caret position (right before the @)
204            var currentCaretPosition = bestLastIndex; //Set the current caret position (right after the end of the "mention")
205
206
207            var start = currentMessage.substr(0, startCaretPosition);
208            var end = currentMessage.substr(currentCaretPosition, currentMessage.length);
209            var startEndIndex = (start + mention.value).length + 1;
210
211            // See if there's the same mention in the list
212            if (!_.find(mentionsCollection, function (object) { return object.id == mention.id; })) {
213                mentionsCollection.push(mention);//Add the mention to mentionsColletions
214            }
215
216            // Cleaning before inserting the value, otherwise auto-complete would be triggered with "old" inputbuffer
217            resetBuffer();
218            currentDataQuery = '';
219            hideAutoComplete();
220
221            // Mentions and syntax message
222            var updatedMessageText = start + mention.value + ' ' + end;
223            elmInputBox.val(updatedMessageText); //Set the value to the txt area
224            elmInputBox.trigger('mention');
225            updateValues();
226
227            // Set correct focus and selection
228            elmInputBox.focus();
229            utils.setCaratPosition(elmInputBox[0], startEndIndex);
230        }
231
232        //Gets the actual value of the text area without white spaces from the beginning and end of the value
233        function getInputBoxValue() {
234            return $.trim(elmInputBox.val());
235        }
236
237        // This is taken straight from live (as of Sep 2012) GitHub code. The
238        // technique is known around the web. Just google it. Github's is quite
239        // succint though. NOTE: relies on selectionEnd, which as far as IE is concerned,
240        // it'll only work on 9+. Good news is nothing will happen if the browser
241        // doesn't support it.
242        function textareaSelectionPosition($el) {
243            var a, b, c, d, e, f, g, h, i, j, k;
244            if (!(i = $el[0])) return;
245            if (!$(i).is("textarea")) return;
246            if (i.selectionEnd == null) return;
247            g = {
248                position: "absolute",
249                overflow: "auto",
250                whiteSpace: "pre-wrap",
251                wordWrap: "break-word",
252                boxSizing: "content-box",
253                top: 0,
254                left: -9999
255            }, h = ["boxSizing", "fontFamily", "fontSize", "fontStyle", "fontVariant", "fontWeight", "height", "letterSpacing", "lineHeight", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", "textDecoration", "textIndent", "textTransform", "width", "word-spacing"];
256            for (j = 0, k = h.length; j < k; j++) e = h[j], g[e] = $(i).css(e);
257            return c = document.createElement("div"), $(c).css(g), $(i).after(c), b = document.createTextNode(i.value.substring(0, i.selectionEnd)), a = document.createTextNode(i.value.substring(i.selectionEnd)), d = document.createElement("span"), d.innerHTML = "&nbsp;", c.appendChild(b), c.appendChild(d), c.appendChild(a), c.scrollTop = i.scrollTop, f = $(d).position(), $(c).remove(), f
258        }
259
260        //same as above function but return offset instead of position
261        function textareaSelectionOffset($el) {
262            var a, b, c, d, e, f, g, h, i, j, k;
263            if (!(i = $el[0])) return;
264            if (!$(i).is("textarea")) return;
265            if (i.selectionEnd == null) return;
266            g = {
267                position: "absolute",
268                overflow: "auto",
269                whiteSpace: "pre-wrap",
270                wordWrap: "break-word",
271                boxSizing: "content-box",
272                top: 0,
273                left: -9999
274            }, h = ["boxSizing", "fontFamily", "fontSize", "fontStyle", "fontVariant", "fontWeight", "height", "letterSpacing", "lineHeight", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", "textDecoration", "textIndent", "textTransform", "width", "word-spacing"];
275            for (j = 0, k = h.length; j < k; j++) e = h[j], g[e] = $(i).css(e);
276            return c = document.createElement("div"), $(c).css(g), $(i).after(c), b = document.createTextNode(i.value.substring(0, i.selectionEnd)), a = document.createTextNode(i.value.substring(i.selectionEnd)), d = document.createElement("span"), d.innerHTML = "&nbsp;", c.appendChild(b), c.appendChild(d), c.appendChild(a), c.scrollTop = i.scrollTop, f = $(d).offset(), $(c).remove(), f
277        }
278
279        //Scrolls back to the input after autocomplete if the window has scrolled past the input
280        function scrollToInput() {
281            var elmDistanceFromTop = $(elmInputBox).offset().top; //input offset
282            var bodyDistanceFromTop = $('body').offset().top; //body offset
283            var distanceScrolled = $(window).scrollTop(); //distance scrolled
284
285            if (distanceScrolled > elmDistanceFromTop) {
286                //subtracts body distance to handle fixed headers
287                $(window).scrollTop(elmDistanceFromTop - bodyDistanceFromTop);
288            }
289        }
290
291        //Takes the click event when the user select a item of the dropdown
292        function onAutoCompleteItemClick(e) {
293            var elmTarget = $(this); //Get the item selected
294            var mention = autocompleteItemCollection[elmTarget.attr('data-uid')]; //Obtains the mention
295            var inputId = $(elmInputBox).attr("id");
296            //if (inputId == "SearchModel_Title") {
297            //    // call update get search
298            //    getFilteredPosts(mention.name, null);
299            //    $('#SearchModel_Title').val('');
300            //}
301            addMention(mention);
302            scrollToInput();
303            return false;
304        }
305
306        //Takes the click event on text area
307        function onInputBoxClick(e) {
308            resetBuffer();
309        }
310
311        //Takes the blur event on text area
312        function onInputBoxBlur(e) {
313            hideAutoComplete();
314        }
315
316        //Takes the input event when users write or delete something
317        function onInputBoxInput(e) {
318            updateValues();
319            updateMentionsCollection();
320
321            var triggerCharIndex = _.lastIndexOf(inputBuffer, settings.triggerChar); //Returns the last match of the triggerChar in the inputBuffer
322            if (triggerCharIndex > -1) { //If the triggerChar is present in the inputBuffer array
323                currentDataQuery = inputBuffer.slice(triggerCharIndex + 1).join(''); //Gets the currentDataQuery
324                currentDataQuery = utils.rtrim(currentDataQuery); //Deletes the whitespaces
325                _.defer(_.bind(doSearch, this, currentDataQuery)); //Invoking the function doSearch ( Bind the function to this)
326            }
327        }
328
329        //Takes the keypress event
330        function onInputBoxKeyPress(e) {
331            if (e.keyCode !== KEY.BACKSPACE) { //If the key pressed is not the backspace
332                var typedValue = String.fromCharCode(e.which || e.keyCode); //Takes the string that represent this CharCode
333                inputBuffer.push(typedValue); //Push the value pressed into inputBuffer
334            }
335        }
336
337        //Takes the keydown event
338        function onInputBoxKeyDown(e) {
339
340            // This also matches HOME/END on OSX which is CMD+LEFT, CMD+RIGHT
341            if (e.keyCode === KEY.LEFT || e.keyCode === KEY.RIGHT || e.keyCode === KEY.HOME || e.keyCode === KEY.END) {
342                // Defer execution to ensure carat pos has changed after HOME/END keys then call the resetBuffer function
343                _.defer(resetBuffer);
344
345                // IE9 doesn't fire the oninput event when backspace or delete is pressed. This causes the highlighting
346                // to stay on the screen whenever backspace is pressed after a highlighed word. This is simply a hack
347                // to force updateValues() to fire when backspace/delete is pressed in IE9.
348                if (navigator.userAgent.indexOf("MSIE 9") > -1) {
349                    _.defer(updateValues); //Call the updateValues function
350                }
351
352                return;
353            }
354
355            //If the key pressed was the backspace
356            if (e.keyCode === KEY.BACKSPACE) {
357                inputBuffer = inputBuffer.slice(0, -1 + inputBuffer.length); // Can't use splice, not available in IE
358                return;
359            }
360
361            //If the elmAutocompleteList is hidden
362            if (!elmAutocompleteList.is(':visible')) {
363                return true;
364            }
365
366            switch (e.keyCode) {
367                case KEY.UP: //If the key pressed was UP or DOWN
368                case KEY.DOWN:
369                    var elmCurrentAutoCompleteItem = null;
370                    if (e.keyCode === KEY.DOWN) { //If the key pressed was DOWN
371                        if (elmActiveAutoCompleteItem && elmActiveAutoCompleteItem.length) { //If elmActiveAutoCompleteItem exits
372                            elmCurrentAutoCompleteItem = elmActiveAutoCompleteItem.next(); //Gets the next li element in the list
373                        } else {
374                            elmCurrentAutoCompleteItem = elmAutocompleteList.find('li').first(); //Gets the first li element found
375                        }
376                    } else {
377                        elmCurrentAutoCompleteItem = $(elmActiveAutoCompleteItem).prev(); //The key pressed was UP and gets the previous li element
378                    }
379                    if (elmCurrentAutoCompleteItem.length) {
380                        selectAutoCompleteItem(elmCurrentAutoCompleteItem);
381                    }
382                    return false;
383                case KEY.RETURN: //If the key pressed was RETURN or TAB
384                case KEY.TAB:
385                    if (elmActiveAutoCompleteItem && elmActiveAutoCompleteItem.length) { //If the elmActiveAutoCompleteItem exists
386                        elmActiveAutoCompleteItem.trigger('mousedown'); //Calls the mousedown event
387                        return false;
388                    }
389                    break;
390            }
391
392            return true;
393        }
394
395        //Hides the autoomplete
396        function hideAutoComplete() {
397            elmActiveAutoCompleteItem = null;
398            elmAutocompleteList.empty().hide();
399        }
400
401        //Selects the item in the autocomplete list
402        function selectAutoCompleteItem(elmItem) {
403            elmItem.addClass(settings.classes.autoCompleteItemActive); //Add the class active to item
404            elmItem.siblings().removeClass(settings.classes.autoCompleteItemActive); //Gets all li elements in autocomplete list and remove the class active
405
406            elmActiveAutoCompleteItem = elmItem; //Sets the item to elmActiveAutoCompleteItem
407        }
408
409        //Populates dropdown
410        function populateDropdown(query, results) {
411            elmAutocompleteList.show(); //Shows the autocomplete list
412
413            if (!settings.allowRepeat) {
414                // Filter items that has already been mentioned
415                var mentionValues = _.pluck(mentionsCollection, 'value');
416                results = _.reject(results, function (item) {
417                    return _.include(mentionValues, item.name);
418                });
419            }
420
421            if (!results.length) { //If there are not elements hide the autocomplete list
422                hideAutoComplete();
423                return;
424            }
425
426            elmAutocompleteList.empty(); //Remove all li elements in autocomplete list
427            var elmDropDownList = $("<ul>").appendTo(elmAutocompleteList).hide(); //Inserts a ul element to autocomplete div and hide it
428
429            _.each(results, function (item, index) {
430                var itemUid = _.uniqueId('mention_'); //Gets the item with unique id
431
432                autocompleteItemCollection[itemUid] = _.extend({}, item, { value: item.name }); //Inserts the new item to autocompleteItemCollection
433
434                var elmListItem = $(settings.templates.autocompleteListItem({
435                    'id': utils.htmlEncode(item.id),
436                    'display': utils.htmlEncode(item.name),
437                    'type': utils.htmlEncode(item.type),
438                    'content': utils.highlightTerm(utils.htmlEncode((item.display ? item.display : item.name)), query)
439                })).attr('data-uid', itemUid); //Inserts the new item to list
440
441                //If the index is 0
442                if (index === 0) {
443                    selectAutoCompleteItem(elmListItem);
444                }
445
446                //If show avatars is true
447                if (settings.showAvatars) {
448                    var elmIcon;
449
450                    //If the item has an avatar
451                    if (item.avatar) {
452                        elmIcon = $(settings.templates.autocompleteListItemAvatar({ avatar: item.avatar }));
453                    } else { //If not then we set an default icon
454                        elmIcon = $(settings.templates.autocompleteListItemIcon({ icon: item.icon }));
455                    }
456                    elmIcon.prependTo(elmListItem); //Inserts the elmIcon to elmListItem
457                }
458                elmListItem = elmListItem.appendTo(elmDropDownList); //Insets the elmListItem to elmDropDownList
459            });
460
461            elmAutocompleteList.show(); //Shows the elmAutocompleteList div
462            if (settings.onCaret) {
463                positionAutocomplete(elmAutocompleteList, elmInputBox);
464            }
465            elmDropDownList.show(); //Shows the elmDropDownList
466        }
467
468        //Search into data list passed as parameter
469        function doSearch(query) {
470            //If the query is not null, undefined, empty and has the minimum chars
471            if (query && query.length && query.length >= settings.minChars) {
472                //Call the onDataRequest function and then call the populateDropDrown
473                settings.onDataRequest.call(this, 'search', query, function (responseData) {
474                    populateDropdown(query, responseData);
475                });
476            } else { //If the query is null, undefined, empty or has not the minimun chars
477                hideAutoComplete(); //Hide the autocompletelist
478            }
479        }
480
481        function positionAutocomplete(elmAutocompleteList, elmInputBox) {
482            var elmAutocompleteListPosition = elmAutocompleteList.css('position');
483            if (elmAutocompleteListPosition == 'absolute') {
484                var position = textareaSelectionPosition(elmInputBox),
485                    lineHeight = parseInt(elmInputBox.css('line-height'), 10) || 18;
486                elmAutocompleteList.css('width', '15em'); // Sort of a guess
487                elmAutocompleteList.css('left', position.left);
488                elmAutocompleteList.css('top', lineHeight + position.top);
489
490                //check if the right position of auto complete is larger than the right position of the input
491                //if yes, reset the left of auto complete list to make it fit the input
492                var elmInputBoxRight = elmInputBox.offset().left + elmInputBox.width(),
493                    elmAutocompleteListRight = elmAutocompleteList.offset().left + elmAutocompleteList.width();
494                if (elmInputBoxRight <= elmAutocompleteListRight) {
495                    elmAutocompleteList.css('left', Math.abs(elmAutocompleteList.position().left - (elmAutocompleteListRight - elmInputBoxRight)));
496                }
497            }
498            else if (elmAutocompleteListPosition == 'fixed') {
499                var offset = textareaSelectionOffset(elmInputBox),
500                    lineHeight = parseInt(elmInputBox.css('line-height'), 10) || 18;
501                elmAutocompleteList.css('width', '15em'); // Sort of a guess
502                elmAutocompleteList.css('left', offset.left + 10000);
503                elmAutocompleteList.css('top', lineHeight + offset.top);
504            }
505        }
506
507        //Resets the text area
508        function resetInput(currentVal) {
509            mentionsCollection = [];
510            var mentionText = utils.htmlEncode(currentVal);
511            var regex = new RegExp("(" + settings.triggerChar + ")\\[(.*?)\\]\\((.*?):(.*?)\\)", "gi");
512            var match, newMentionText = mentionText;
513            while ((match = regex.exec(mentionText)) != null) {
514                newMentionText = newMentionText.replace(match[0], match[1] + match[2]);
515                mentionsCollection.push({ 'id': match[4], 'type': match[3], 'value': match[2], 'trigger': match[1] });
516            }
517            elmInputBox.val(newMentionText);
518            updateValues();
519        }
520        // Public methods
521        return {
522            //Initializes the mentionsInput component on a specific element.
523            init: function (domTarget) {
524
525                domInput = domTarget;
526
527                initTextarea();
528                initAutocomplete();
529                initMentionsOverlay();
530                resetInput(settings.defaultValue);
531
532                //If the autocomplete list has prefill mentions
533                if (settings.prefillMention) {
534                    addMention(settings.prefillMention);
535                }
536            },
537
538            //An async method which accepts a callback function and returns a value of the input field (including markup) as a first parameter of this function. This is the value you want to send to your server.
539            val: function (callback) {
540                if (!_.isFunction(callback)) {
541                    return;
542                }
543                callback.call(this, mentionsCollection.length ? elmInputBox.data('messageText') : getInputBoxValue());
544            },
545
546            //Resets the text area value and clears all mentions
547            reset: function () {
548                resetInput();
549            },
550
551            //Reinit with the text area value if it was changed programmatically
552            reinit: function () {
553                resetInput(false);
554            },
555
556            //An async method which accepts a callback function and returns a collection of mentions as hash objects as a first parameter.
557            getMentions: function (callback) {
558                if (!_.isFunction(callback)) {
559                    return;
560                }
561                callback.call(this, mentionsCollection);
562            }
563        };
564    };
565
566    //Main function to include into jQuery and initialize the plugin
567    $.fn.mentionsInput = function (method, settings) {
568
569        var outerArguments = arguments; //Gets the arguments
570        //If method is not a function
571        if (typeof method === 'object' || !method) {
572            settings = method;
573        }
574
575        return this.each(function () {
576            var instance = $.data(this, 'mentionsInput') || $.data(this, 'mentionsInput', new MentionsInput(settings));
577
578            if (_.isFunction(instance[method])) {
579                return instance[method].apply(this, Array.prototype.slice.call(outerArguments, 1));
580            } else if (typeof method === 'object' || !method) {
581                return instance.init.call(this, this);
582            } else {
583                $.error('Method ' + method + ' does not exist');
584            }
585        });
586    };
587
588})(jQuery, _);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.