123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- /*
- Implement Github like autocomplete mentions
- http://ichord.github.com/At.js
- Copyright (c) 2013 chord.luo@gmail.com
- Licensed under the MIT license.
- */
- (function() {
- var __slice = [].slice;
- (function(factory) {
- if (typeof define === 'function' && define.amd) {
- return define(['jquery'], factory);
- } else {
- return factory(window.jQuery);
- }
- })(function($) {
- var $CONTAINER, Api, App, Controller, DEFAULT_CALLBACKS, DEFAULT_TPL, KEY_CODE, Model, View;
- App = (function() {
- function App(inputor) {
- this.current_flag = null;
- this.controllers = {};
- this.$inputor = $(inputor);
- this.listen();
- }
- App.prototype.controller = function(key) {
- return this.controllers[key || this.current_flag];
- };
- App.prototype.set_context_for = function(key) {
- this.current_flag = key;
- return this;
- };
- App.prototype.reg = function(flag, setting) {
- var controller, _base;
- controller = (_base = this.controllers)[flag] || (_base[flag] = new Controller(this, flag));
- if (setting.alias) {
- this.controllers[setting.alias] = controller;
- }
- controller.init(setting);
- return this;
- };
- App.prototype.listen = function() {
- var _this = this;
- return this.$inputor.on('keyup.atwho', function(e) {
- return _this.on_keyup(e);
- }).on('keydown.atwho', function(e) {
- return _this.on_keydown(e);
- }).on('scroll.atwho', function(e) {
- var _ref;
- return (_ref = _this.controller()) != null ? _ref.view.hide() : void 0;
- }).on('blur.atwho', function(e) {
- var c;
- if (c = _this.controller()) {
- return c.view.hide(c.get_opt("display_timeout"));
- }
- });
- };
- App.prototype.dispatch = function() {
- var _this = this;
- return $.map(this.controllers, function(c) {
- if (c.look_up()) {
- return _this.set_context_for(c.key);
- }
- });
- };
- App.prototype.on_keyup = function(e) {
- var _ref;
- switch (e.keyCode) {
- case KEY_CODE.ESC:
- e.preventDefault();
- if ((_ref = this.controller()) != null) {
- _ref.view.hide();
- }
- break;
- case KEY_CODE.DOWN:
- case KEY_CODE.UP:
- $.noop();
- break;
- default:
- this.dispatch();
- }
- };
- App.prototype.on_keydown = function(e) {
- var view, _ref;
- view = (_ref = this.controller()) != null ? _ref.view : void 0;
- if (!(view && view.visible())) {
- return;
- }
- switch (e.keyCode) {
- case KEY_CODE.ESC:
- e.preventDefault();
- view.hide();
- break;
- case KEY_CODE.UP:
- e.preventDefault();
- view.prev();
- break;
- case KEY_CODE.DOWN:
- e.preventDefault();
- view.next();
- break;
- case KEY_CODE.TAB:
- case KEY_CODE.ENTER:
- if (!view.visible()) {
- return;
- }
- e.preventDefault();
- view.choose();
- break;
- default:
- $.noop();
- }
- };
- return App;
- })();
- Controller = (function() {
- var uuid, _uuid;
- _uuid = 0;
- uuid = function() {
- return _uuid += 1;
- };
- function Controller(app, key) {
- this.app = app;
- this.key = key;
- this.$inputor = this.app.$inputor;
- this.id = this.$inputor[0].id || uuid();
- this.setting = null;
- this.query = null;
- this.pos = 0;
- $CONTAINER.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>"));
- this.model = new Model(this);
- this.view = new View(this);
- }
- Controller.prototype.init = function(setting) {
- this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
- return this.model.reload(this.setting.data);
- };
- Controller.prototype.super_call = function() {
- var args, func_name;
- func_name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
- try {
- return DEFAULT_CALLBACKS[func_name].apply(this, args);
- } catch (error) {
- return $.error("" + error + " Or maybe At.js doesn't have function " + func_name);
- }
- };
- Controller.prototype.trigger = function(name, data) {
- var alias, event_name;
- data.push(this);
- alias = this.get_opt('alias');
- event_name = alias ? "" + name + "-" + alias + ".atwho" : "" + name + ".atwho";
- return this.$inputor.trigger(event_name, data);
- };
- Controller.prototype.callbacks = function(func_name) {
- return this.get_opt("callbacks")[func_name] || DEFAULT_CALLBACKS[func_name];
- };
- Controller.prototype.get_opt = function(key, default_value) {
- try {
- return this.setting[key];
- } catch (e) {
- return null;
- }
- };
- Controller.prototype.catch_query = function() {
- var caret_pos, content, end, query, start, subtext;
- content = this.$inputor.val();
- caret_pos = this.$inputor.caret('pos');
- subtext = content.slice(0, caret_pos);
- query = this.callbacks("matcher").call(this, this.key, subtext);
- if (typeof query === "string" && query.length <= this.get_opt('max_len', 20)) {
- start = caret_pos - query.length;
- end = start + query.length;
- this.pos = start;
- query = {
- 'text': query.toLowerCase(),
- 'head_pos': start,
- 'end_pos': end
- };
- this.trigger("matched", [this.key, query.text]);
- } else {
- this.view.hide();
- }
- return this.query = query;
- };
- Controller.prototype.rect = function() {
- var c, scale_bottom;
- c = this.$inputor.caret('offset', this.pos - 1);
- scale_bottom = document.selection ? 0 : 2;
- return {
- left: c.left,
- top: c.top,
- bottom: c.top + c.height + scale_bottom
- };
- };
- Controller.prototype.insert = function(str) {
- var $inputor, flag_len, source, start_str, text;
- $inputor = this.$inputor;
- str = '' + str;
- source = $inputor.val();
- flag_len = this.get_opt("display_flag") ? 0 : this.key.length;
- start_str = source.slice(0, (this.query['head_pos'] || 0) - flag_len);
- text = "" + start_str + str + " " + (source.slice(this.query['end_pos'] || 0));
- $inputor.val(text);
- $inputor.caret('pos', start_str.length + str.length + 1);
- return $inputor.change();
- };
- Controller.prototype.render_view = function(data) {
- var search_key;
- search_key = this.get_opt("search_key");
- data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), search_key);
- return this.view.render(data.slice(0, this.get_opt('limit')));
- };
- Controller.prototype.look_up = function() {
- var query, _callback;
- if (!(query = this.catch_query())) {
- return;
- }
- _callback = function(data) {
- if (data && data.length > 0) {
- return this.render_view(data);
- } else {
- return this.view.hide();
- }
- };
- this.model.query(query.text, $.proxy(_callback, this));
- return query;
- };
- return Controller;
- })();
- Model = (function() {
- var _storage;
- _storage = {};
- function Model(context) {
- this.context = context;
- this.key = this.context.key;
- }
- Model.prototype.saved = function() {
- return this.fetch() > 0;
- };
- Model.prototype.query = function(query, callback) {
- var data, search_key, _ref;
- data = this.fetch();
- search_key = this.context.get_opt("search_key");
- callback(data = this.context.callbacks('filter').call(this.context, query, data, search_key));
- if (!(data && data.length > 0)) {
- return (_ref = this.context.callbacks('remote_filter')) != null ? _ref.call(this.context, query, callback) : void 0;
- }
- };
- Model.prototype.fetch = function() {
- return _storage[this.key] || [];
- };
- Model.prototype.save = function(data) {
- return _storage[this.key] = this.context.callbacks("before_save").call(this.context, data || []);
- };
- Model.prototype.load = function(data) {
- if (!(this.saved() || !data)) {
- return this._load(data);
- }
- };
- Model.prototype.reload = function(data) {
- return this._load(data);
- };
- Model.prototype._load = function(data) {
- var _this = this;
- if (typeof data === "string") {
- return $.ajax(data, {
- dataType: "json"
- }).done(function(data) {
- return _this.save(data);
- });
- } else {
- return this.save(data);
- }
- };
- return Model;
- })();
- View = (function() {
- function View(context) {
- this.context = context;
- this.key = this.context.key;
- this.id = this.context.get_opt("alias") || ("at-view-" + (this.key.charCodeAt(0)));
- this.$el = $("<div id='" + this.id + "' class='atwho-view'><ul id='" + this.id + "-ul' class='atwho-view-url'></ul></div>");
- this.timeout_id = null;
- this.context.$el.append(this.$el);
- this.bind_event();
- }
- View.prototype.bind_event = function() {
- var $menu,
- _this = this;
- $menu = this.$el.find('ul');
- return $menu.on('mouseenter.view', 'li', function(e) {
- $menu.find('.cur').removeClass('cur');
- return $(e.currentTarget).addClass('cur');
- }).on('click', function(e) {
- _this.choose();
- return e.preventDefault();
- });
- };
- View.prototype.visible = function() {
- return this.$el.is(":visible");
- };
- View.prototype.choose = function() {
- var $li;
- $li = this.$el.find(".cur");
- this.context.insert(this.context.callbacks("before_insert").call(this.context, $li.data("value"), $li));
- this.context.trigger("inserted", [$li]);
- return this.hide();
- };
- View.prototype.reposition = function() {
- var offset, rect;
- rect = this.context.rect();
- if (rect.bottom + this.$el.height() - $(window).scrollTop() > $(window).height()) {
- rect.bottom = rect.top - this.$el.height();
- }
- offset = {
- left: rect.left,
- top: rect.bottom
- };
- this.$el.offset(offset);
- return this.context.trigger("reposition", [offset]);
- };
- View.prototype.next = function() {
- var cur, next;
- cur = this.$el.find('.cur').removeClass('cur');
- next = cur.next();
- if (!next.length) {
- next = this.$el.find('li:first');
- }
- return next.addClass('cur');
- };
- View.prototype.prev = function() {
- var cur, prev;
- cur = this.$el.find('.cur').removeClass('cur');
- prev = cur.prev();
- if (!prev.length) {
- prev = this.$el.find('li:last');
- }
- return prev.addClass('cur');
- };
- View.prototype.show = function() {
- if (!this.visible()) {
- this.$el.show();
- }
- return this.reposition();
- };
- View.prototype.hide = function(time) {
- var callback,
- _this = this;
- if (isNaN(time && this.visible())) {
- return this.$el.hide();
- } else {
- callback = function() {
- return _this.hide();
- };
- clearTimeout(this.timeout_id);
- return this.timeout_id = setTimeout(callback, time);
- }
- };
- View.prototype.render = function(list) {
- var $li, $ul, item, li, tpl, _i, _len;
- if (!$.isArray(list || list.length <= 0)) {
- this.hide();
- return;
- }
- this.$el.find('ul').empty();
- $ul = this.$el.find('ul');
- tpl = this.context.get_opt('tpl', DEFAULT_TPL);
- for (_i = 0, _len = list.length; _i < _len; _i++) {
- item = list[_i];
- li = this.context.callbacks("tpl_eval").call(this.context, tpl, item);
- $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text));
- $li.data("atwho-info", item);
- $ul.append($li);
- }
- this.show();
- return $ul.find("li:first").addClass("cur");
- };
- return View;
- })();
- KEY_CODE = {
- DOWN: 40,
- UP: 38,
- ESC: 27,
- TAB: 9,
- ENTER: 13
- };
- DEFAULT_CALLBACKS = {
- before_save: function(data) {
- var item, _i, _len, _results;
- if (!$.isArray(data)) {
- return data;
- }
- _results = [];
- for (_i = 0, _len = data.length; _i < _len; _i++) {
- item = data[_i];
- if ($.isPlainObject(item)) {
- _results.push(item);
- } else {
- _results.push({
- name: item
- });
- }
- }
- return _results;
- },
- matcher: function(flag, subtext) {
- var match, regexp;
- flag = '(?:^|\\s)' + flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
- regexp = new RegExp(flag + '([A-Za-z0-9_\+\-]*)$|' + flag + '([^\\x00-\\xff]*)$', 'gi');
- match = regexp.exec(subtext);
- if (match) {
- return match[2] || match[1];
- } else {
- return null;
- }
- },
- filter: function(query, data, search_key) {
- var item, _i, _len, _results;
- _results = [];
- for (_i = 0, _len = data.length; _i < _len; _i++) {
- item = data[_i];
- if (~item[search_key].toLowerCase().indexOf(query)) {
- _results.push(item);
- }
- }
- return _results;
- },
- remote_filter: null,
- sorter: function(query, items, search_key) {
- var item, _i, _len, _results;
- if (!query) {
- return items;
- }
- _results = [];
- for (_i = 0, _len = items.length; _i < _len; _i++) {
- item = items[_i];
- item.atwho_order = item[search_key].toLowerCase().indexOf(query);
- if (item.atwho_order > -1) {
- _results.push(item);
- }
- }
- return _results.sort(function(a, b) {
- return a.atwho_order - b.atwho_order;
- });
- },
- tpl_eval: function(tpl, map) {
- try {
- return tpl.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
- return map[key];
- });
- } catch (error) {
- return "";
- }
- },
- highlighter: function(li, query) {
- var regexp;
- if (!query) {
- return li;
- }
- regexp = new RegExp(">\\s*(\\w*)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig');
- return li.replace(regexp, function(str, $1, $2, $3) {
- return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
- });
- },
- before_insert: function(value, $li) {
- return value;
- }
- };
- DEFAULT_TPL = "<li data-value='${name}'>${name}</li>";
- Api = {
- init: function(options) {
- var $this, app;
- app = ($this = $(this)).data("atwho");
- if (!app) {
- $this.data('atwho', (app = new App(this)));
- }
- return app.reg(options.at, options);
- },
- load: function(key, data) {
- var c;
- if (c = this.controller(key)) {
- return c.model.load(data);
- }
- },
- run: function() {
- return this.dispatch();
- }
- };
- $CONTAINER = $("<div id='atwho-container'></div>");
- $.fn.atwho = function(method) {
- var _args;
- _args = arguments;
- $('body').append($CONTAINER);
- return this.filter('textarea, input').each(function() {
- var app;
- if (typeof method === 'object' || !method) {
- return Api.init.apply(this, _args);
- } else if (Api[method]) {
- if (app = $(this).data('atwho')) {
- return Api[method].apply(app, Array.prototype.slice.call(_args, 1));
- }
- } else {
- return $.error("Method " + method + " does not exist on jQuery.caret");
- }
- });
- };
- return $.fn.atwho["default"] = {
- at: void 0,
- alias: void 0,
- data: null,
- tpl: DEFAULT_TPL,
- callbacks: DEFAULT_CALLBACKS,
- search_key: "name",
- limit: 5,
- max_len: 20,
- display_flag: true,
- display_timeout: 300
- };
- });
- }).call(this);
|