jquery-atwho-rails 0.1.7 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,3 +1,8 @@
1
+ ### Notice
2
+ updated to `v0.2.0`.
3
+ The stable and old one would live in branch `stable-v0.1.x` branch taged `v0.1.7`
4
+ More details in [At.js](https://github.com/ichord/At.js) project.
5
+
1
6
  ### Usage
2
7
  ---
3
8
  bind your textarea
@@ -8,7 +13,7 @@ $('textarea').atWho("@",{'data':data});
8
13
  ```
9
14
 
10
15
  that's it, check it out!
11
- more details in [At.js](https://github.com/ichord/At.js) on github.
16
+ more details in [At.js Home Page](http://ichord.github.com/At.js/)
12
17
 
13
18
  ### Installtion
14
19
  ---
data/Rakefile CHANGED
@@ -1,27 +1,23 @@
1
1
  require "bundler/gem_tasks"
2
- #require "uglifier"
3
2
 
4
3
  desc "sync from At.js"
5
4
  task :sync do
5
+ puts " * syncing..."
6
6
  at_dir = "tmp/At.js"
7
- FileUtils.mkdir_p("tmp") if not Dir.exist? "tmp"
8
- if not Dir.exist? at_dir
7
+ FileUtils.mkdir_p("tmp") unless Dir.exist? "tmp"
8
+ unless Dir.exist? at_dir
9
9
  system "git clone git://github.com/ichord/At.js.git #{at_dir}"
10
10
  else
11
- Dir.chdir(at_dir) do
12
- puts %x{git pull}
13
- end
11
+ Dir.chdir(at_dir) { puts %x{git pull -X theirs} }
14
12
  end
15
13
  end
16
14
 
17
- desc "complie and compress"
15
+ desc "copy assets"
18
16
  task :fresh => :sync do
19
- at_dir = "tmp/At.js"
20
- the_js = "lib/assets/javascripts/jquery.atwho.js"
21
- sh "coffee -j tmp/atwho.js -c #{at_dir}/coffee"
22
- #File.open("atwho.js","w") { |f|
23
- # f.write Uglifier.complie(File.read(the_js))
24
- #}
25
- FileUtils.copy_file "tmp/atwho.js", the_js
26
- FileUtils.copy_file "#{at_dir}/css/jquery.atwho.css", "lib/assets/stylesheets/jquery.atwho.css"
17
+ puts "", " * Copying..."
18
+ source_dir = "tmp/At.js/dist"
19
+ dist_dir = "lib/assets"
20
+ FileUtils.copy "#{source_dir}/js/jquery.atwho.js", "#{dist_dir}/javascripts/"
21
+ FileUtils.copy "#{source_dir}/css/jquery.atwho.css", "#{dist_dir}/stylesheets/"
22
+ puts `ls -R #{dist_dir}`
27
23
  end
data/changelog.md CHANGED
@@ -1 +1,2 @@
1
+ * 2013.1.24 updated to v0.2.0.
1
2
  * 2012.7.1 remove the mirror(copy) element of the textarea(inputor) which will disturb css style. increase timeout of displaying the result list view.
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
9
9
  s.email = ["chord.luo@gmail.com"]
10
10
  s.homepage = "http://ichord.github.com/jquery-atwho-rails"
11
11
  s.summary = %q{jquery plugin: @mentions}
12
- s.description = %q{This is a jQuery plugin
13
- that implement Twitter/Weibo like @ mentions.}
12
+ s.description = %q{This is a jQuery plugin
13
+ that implement Github-like mentions.}
14
14
 
15
15
  s.rubyforge_project = "jquery-atwho-rails"
16
16
 
@@ -1,41 +1,34 @@
1
- // Generated by CoffeeScript 1.3.3
2
1
 
3
2
  /*
4
- Implement Twitter/Weibo @ mentions
5
-
6
- Copyright (c) 2012 chord.luo@gmail.com
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining
9
- a copy of this software and associated documentation files (the
10
- "Software"), to deal in the Software without restriction, including
11
- without limitation the rights to use, copy, modify, merge, publish,
12
- distribute, sublicense, and/or sell copies of the Software, and to
13
- permit persons to whom the Software is furnished to do so, subject to
14
- the following conditions:
15
-
16
- The above copyright notice and this permission notice shall be
17
- included in all copies or substantial portions of the Software.
18
-
19
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3
+ Implement Github like autocomplete mentions
4
+ http://ichord.github.com/At.js
5
+
6
+ Copyright (c) 2013 chord.luo@gmail.com
7
+ Licensed under the MIT license.
26
8
  */
27
9
 
28
10
 
29
11
  (function() {
30
12
 
31
- (function($) {
32
- var At, AtView, Mirror, log, _DEFAULT_TPL, _evalTpl, _highlighter, _isNil, _objectify, _sorter, _unique;
33
- Mirror = {
34
- $mirror: null,
35
- css: ["overflowY", "height", "width", "paddingTop", "paddingLeft", "paddingRight", "paddingBottom", "marginTop", "marginLeft", "marginRight", "marginBottom", 'fontFamily', 'borderStyle', 'borderWidth', 'wordWrap', 'fontSize', 'lineHeight', 'overflowX'],
36
- init: function($origin) {
37
- var $mirror, css;
38
- $mirror = $('<div></div>');
13
+ (function(factory) {
14
+ if (typeof define === 'function' && define.amd) {
15
+ return define(['jquery'], factory);
16
+ } else {
17
+ return factory(window.jQuery);
18
+ }
19
+ })(function($) {
20
+ var Controller, DEFAULT_CALLBACKS, DEFAULT_TPL, KEY_CODE, Mirror, View;
21
+ Mirror = (function() {
22
+
23
+ Mirror.prototype.css_attr = ["overflowY", "height", "width", "paddingTop", "paddingLeft", "paddingRight", "paddingBottom", "marginTop", "marginLeft", "marginRight", "marginBottom", 'fontFamily', 'borderStyle', 'borderWidth', 'wordWrap', 'fontSize', 'lineHeight', 'overflowX'];
24
+
25
+ function Mirror($inputor) {
26
+ this.$inputor = $inputor;
27
+ }
28
+
29
+ Mirror.prototype.copy_inputor_css = function() {
30
+ var css,
31
+ _this = this;
39
32
  css = {
40
33
  position: 'absolute',
41
34
  left: -9999,
@@ -43,19 +36,21 @@
43
36
  zIndex: -20000,
44
37
  'white-space': 'pre-wrap'
45
38
  };
46
- $.each(this.css, function(i, p) {
47
- return css[p] = $origin.css(p);
39
+ $.each(this.css_attr, function(i, p) {
40
+ return css[p] = _this.$inputor.css(p);
48
41
  });
49
- $mirror.css(css);
50
- this.$mirror = $mirror;
51
- $origin.after($mirror);
52
- return this;
53
- },
54
- setContent: function(html) {
42
+ return css;
43
+ };
44
+
45
+ Mirror.prototype.create = function(html) {
46
+ this.$mirror = $('<div></div>');
47
+ this.$mirror.css(this.copy_inputor_css());
55
48
  this.$mirror.html(html);
49
+ this.$inputor.after(this.$mirror);
56
50
  return this;
57
- },
58
- getFlagRect: function() {
51
+ };
52
+
53
+ Mirror.prototype.get_flag_rect = function() {
59
54
  var $flag, pos, rect;
60
55
  $flag = this.$mirror.find("span#flag");
61
56
  pos = $flag.position();
@@ -66,83 +61,174 @@
66
61
  };
67
62
  this.$mirror.remove();
68
63
  return rect;
69
- }
70
- };
71
- At = function(inputor) {
72
- var $inputor,
73
- _this = this;
74
- $inputor = this.$inputor = $(inputor);
75
- this.options = {};
76
- this.query = {
77
- text: "",
78
- start: 0,
79
- stop: 0
80
64
  };
81
- this._cache = {};
82
- this.pos = 0;
83
- this.flags = {};
84
- this.theflag = null;
85
- this.search_word = {};
86
- this.view = AtView;
87
- this.mirror = Mirror;
88
- $inputor.on("keyup.inputor", function(e) {
89
- var lookup, stop;
90
- stop = e.keyCode === 40 || e.keyCode === 38;
91
- lookup = !(stop && _this.view.isShowing());
92
- if (lookup) {
93
- return _this.lookup();
94
- }
95
- }).on("mouseup.inputor", function(e) {
96
- return _this.lookup();
97
- });
98
- this.init();
99
- log("At.new", $inputor[0]);
100
- return this;
65
+
66
+ return Mirror;
67
+
68
+ })();
69
+ KEY_CODE = {
70
+ DOWN: 40,
71
+ UP: 38,
72
+ ESC: 27,
73
+ TAB: 9,
74
+ ENTER: 13
101
75
  };
102
- At.prototype = {
103
- constructor: At,
104
- init: function() {
105
- var _this = this;
106
- this.$inputor.on('keyup.inputor', function(e) {
107
- return _this.onkeyup(e);
108
- }).on('keydown.inputor', function(e) {
109
- return _this.onkeydown(e);
110
- }).on('scroll.inputor', function(e) {
111
- return _this.view.hide();
112
- }).on('blur.inputor', function(e) {
113
- return _this.view.hide(1000);
76
+ DEFAULT_CALLBACKS = {
77
+ data_refactor: function(data) {
78
+ return $.map(data, function(item, k) {
79
+ if (!$.isPlainObject(item)) {
80
+ item = {
81
+ name: item
82
+ };
83
+ }
84
+ return item;
114
85
  });
115
- return log("At.init", this.$inputor[0]);
116
86
  },
117
- reg: function(flag, options) {
118
- var opt, _base, _default;
119
- opt = {};
120
- if ($.isFunction(options)) {
121
- opt['callback'] = options;
122
- } else {
123
- opt = options;
87
+ matcher: function(flag, subtext) {
88
+ var match, matched, regexp;
89
+ regexp = new RegExp(flag + '([A-Za-z0-9_\+\-]*)$|' + flag + '([^\\x00-\\xff]*)$', 'gi');
90
+ match = regexp.exec(subtext);
91
+ matched = null;
92
+ if (match) {
93
+ matched = match[2] ? match[2] : match[1];
124
94
  }
125
- _default = (_base = this.options)[flag] || (_base[flag] = $.fn.atWho["default"]);
126
- this.options[flag] = $.extend({}, _default, opt);
127
- return log("At.reg", this.$inputor[0], flag, options);
95
+ return matched;
96
+ },
97
+ filter: function(query, data, search_key) {
98
+ var _this = this;
99
+ return $.map(data, function(item, i) {
100
+ var name;
101
+ name = $.isPlainObject(item) ? item[search_key] : item;
102
+ if (name.toLowerCase().indexOf(query) >= 0) {
103
+ return item;
104
+ }
105
+ });
106
+ },
107
+ remote_filter: function(params, url, render_view) {
108
+ return $.ajax(url, {
109
+ data: params,
110
+ success: function(data) {
111
+ return render_view(data);
112
+ }
113
+ });
128
114
  },
129
- dataValue: function() {
130
- var match, search_word;
131
- search_word = this.search_word[this.theflag];
132
- if (search_word) {
133
- return search_word;
115
+ sorter: function(query, items, search_key) {
116
+ var item, results, text, _i, _len;
117
+ if (!query) {
118
+ items;
119
+
134
120
  }
135
- match = /data-value=["']?\$\{(\w+)\}/g.exec(this.getOpt('tpl'));
136
- return this.search_word[this.theflag] = !_isNil(match) ? match[1] : null;
121
+ results = [];
122
+ for (_i = 0, _len = items.length; _i < _len; _i++) {
123
+ item = items[_i];
124
+ text = item[search_key];
125
+ item.order = text.toLowerCase().indexOf(query);
126
+ results.push(item);
127
+ }
128
+ return results.sort(function(a, b) {
129
+ return a.order - b.order;
130
+ });
137
131
  },
138
- getOpt: function(key) {
132
+ tpl_eval: function(tpl, map) {
133
+ var el;
139
134
  try {
140
- return this.options[this.theflag][key];
135
+ return el = tpl.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
136
+ return map[key];
137
+ });
141
138
  } catch (error) {
142
- return null;
139
+ return "";
143
140
  }
144
141
  },
145
- rect: function() {
142
+ highlighter: function(li, query) {
143
+ if (!query) {
144
+ return li;
145
+ }
146
+ return li.replace(new RegExp(">\\s*(\\w*)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig'), function(str, $1, $2, $3) {
147
+ return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
148
+ });
149
+ },
150
+ selector: function($li) {
151
+ if ($li.length > 0) {
152
+ return this.replace_str($li.data("value") || "");
153
+ }
154
+ }
155
+ };
156
+ Controller = (function() {
157
+
158
+ function Controller(inputor) {
159
+ this.settings = {};
160
+ this.common_settings = {};
161
+ this.pos = 0;
162
+ this.flags = null;
163
+ this.current_flag = null;
164
+ this.query = null;
165
+ this.$inputor = $(inputor);
166
+ this.mirror = new Mirror(this.$inputor);
167
+ this.common_settings = $.extend({}, $.fn.atwho["default"]);
168
+ this.view = new View(this, this.$el);
169
+ this.listen();
170
+ }
171
+
172
+ Controller.prototype.listen = function() {
173
+ var _this = this;
174
+ return this.$inputor.on('keyup.atwho', function(e) {
175
+ return _this.on_keyup(e);
176
+ }).on('keydown.atwho', function(e) {
177
+ return _this.on_keydown(e);
178
+ }).on('scroll.atwho', function(e) {
179
+ return _this.view.hide();
180
+ }).on('blur.atwho', function(e) {
181
+ return _this.view.hide(_this.get_opt("display_timeout"));
182
+ });
183
+ };
184
+
185
+ Controller.prototype.reg = function(flag, settings) {
186
+ var current_settings, data;
187
+ current_settings = {};
188
+ current_settings = $.isPlainObject(flag) ? this.common_settings = $.extend({}, this.common_settings, flag) : !this.settings[flag] ? this.settings[flag] = $.extend({}, settings) : this.settings[flag] = $.extend({}, this.settings[flag], settings);
189
+ data = current_settings["data"];
190
+ if (typeof data === "string") {
191
+ current_settings["data"] = data;
192
+ } else if (data) {
193
+ current_settings["data"] = this.callbacks("data_refactor").call(this, data);
194
+ }
195
+ return this;
196
+ };
197
+
198
+ Controller.prototype.trigger = function(name, data) {
199
+ data || (data = []);
200
+ data.push(this);
201
+ return this.$inputor.trigger("" + name + ".atwho", data);
202
+ };
203
+
204
+ Controller.prototype.data = function() {
205
+ return this.get_opt("data");
206
+ };
207
+
208
+ Controller.prototype.callbacks = function(func_name) {
209
+ var func;
210
+ if (!(func = this.get_opt("callbacks", {})[func_name])) {
211
+ func = this.common_settings["callbacks"][func_name];
212
+ }
213
+ return func;
214
+ };
215
+
216
+ Controller.prototype.get_opt = function(key, default_value) {
217
+ var value;
218
+ try {
219
+ if (this.current_flag) {
220
+ value = this.settings[this.current_flag][key];
221
+ }
222
+ if (value === void 0) {
223
+ value = this.common_settings[key];
224
+ }
225
+ return value = value === void 0 ? default_value : value;
226
+ } catch (e) {
227
+ return value = default_value === void 0 ? null : default_value;
228
+ }
229
+ };
230
+
231
+ Controller.prototype.rect = function() {
146
232
  var $inputor, Sel, at_rect, bottom, format, html, offset, start_range, x, y;
147
233
  $inputor = this.$inputor;
148
234
  if (document.selection) {
@@ -165,16 +251,16 @@
165
251
 
166
252
  start_range = $inputor.val().slice(0, this.pos - 1);
167
253
  html = "<span>" + format(start_range) + "</span>";
168
- html += "<span id='flag'>@</span>";
254
+ html += "<span id='flag'>?</span>";
169
255
  /*
170
- 将inputor的 offset(相对于document)
171
- 和@在inputor里的position相加
172
- 就得到了@相对于document的offset.
173
- 当然,还要加上行高和滚动条的偏移量.
256
+ 将inputor的 offset(相对于document)
257
+ 和@在inputor里的position相加
258
+ 就得到了@相对于document的offset.
259
+ 当然,还要加上行高和滚动条的偏移量.
174
260
  */
175
261
 
176
262
  offset = $inputor.offset();
177
- at_rect = this.mirror.init($inputor).setContent(html).getFlagRect();
263
+ at_rect = this.mirror.create(html).get_flag_rect();
178
264
  x = offset.left + at_rect.left - $inputor.scrollLeft();
179
265
  y = offset.top - $inputor.scrollTop();
180
266
  bottom = y + at_rect.bottom;
@@ -184,263 +270,244 @@
184
270
  left: x,
185
271
  bottom: bottom + 2
186
272
  };
187
- },
188
- cache: function(value) {
189
- var key, _base;
190
- key = this.query.text;
191
- if (!this.getOpt("cache") || !key) {
192
- return null;
193
- }
194
- return (_base = this._cache)[key] || (_base[key] = value);
195
- },
196
- getKeyname: function() {
197
- var $inputor, caret_pos, end, key, matched, start, subtext, text,
273
+ };
274
+
275
+ Controller.prototype.catch_query = function() {
276
+ var caret_pos, content, end, query, start, subtext,
198
277
  _this = this;
199
- $inputor = this.$inputor;
200
- text = $inputor.val();
201
- caret_pos = $inputor.caretPos();
278
+ content = this.$inputor.val();
279
+ caret_pos = this.$inputor.caretPos();
202
280
  /* 向在插入符前的的文本进行正则匹配
203
281
  * 考虑会有多个 @ 的存在, 匹配离插入符最近的一个
204
282
  */
205
283
 
206
- subtext = text.slice(0, caret_pos);
207
- matched = null;
208
- $.each(this.options, function(flag) {
209
- var match, regexp;
210
- regexp = new RegExp(flag + '([A-Za-z0-9_\+\-]*)$|' + flag + '([^\\x00-\\xff]*)$', 'gi');
211
- match = regexp.exec(subtext);
212
- if (!_isNil(match)) {
213
- matched = match[2] ? match[2] : match[1];
214
- _this.theflag = flag;
284
+ subtext = content.slice(0, caret_pos);
285
+ query = null;
286
+ $.each(this.settings, function(flag, settings) {
287
+ query = _this.callbacks("matcher").call(_this, flag, subtext);
288
+ if (query != null) {
289
+ _this.current_flag = flag;
215
290
  return false;
216
291
  }
217
292
  });
218
- if (typeof matched === 'string' && matched.length <= 20) {
219
- start = caret_pos - matched.length;
220
- end = start + matched.length;
293
+ if (typeof query === "string" && query.length <= 20) {
294
+ start = caret_pos - query.length;
295
+ end = start + query.length;
221
296
  this.pos = start;
222
- key = {
223
- 'text': matched.toLowerCase(),
224
- 'start': start,
225
- 'end': end
297
+ query = {
298
+ 'text': query.toLowerCase(),
299
+ 'head_pos': start,
300
+ 'end_pos': end
226
301
  };
302
+ this.trigger("matched", [this.current_flag, query.text]);
227
303
  } else {
228
304
  this.view.hide();
229
305
  }
230
- log("At.getKeyname", key);
231
- return this.query = key;
232
- },
233
- replaceStr: function(str) {
234
- var $inputor, flag_len, key, source, start_str, text;
306
+ return this.query = query;
307
+ };
308
+
309
+ Controller.prototype.replace_str = function(str) {
310
+ var $inputor, flag_len, source, start_str, text;
235
311
  $inputor = this.$inputor;
236
- key = this.query;
237
312
  source = $inputor.val();
238
- flag_len = this.getOpt("display_flag") ? 0 : this.theflag.length;
239
- start_str = source.slice(0, key.start - flag_len);
240
- text = start_str + str + source.slice(key.end);
313
+ flag_len = this.get_opt("display_flag") ? 0 : this.current_flag.length;
314
+ start_str = source.slice(0, (this.query['head_pos'] || 0) - flag_len);
315
+ text = "" + start_str + str + " " + (source.slice(this.query['end_pos'] || 0));
241
316
  $inputor.val(text);
242
- $inputor.caretPos(start_str.length + str.length);
243
- $inputor.change();
244
- return log("At.replaceStr", text);
245
- },
246
- onkeyup: function(e) {
247
- var view;
248
- view = this.view;
249
- if (!view.isShowing()) {
250
- return;
251
- }
317
+ $inputor.caretPos(start_str.length + str.length + 1);
318
+ return $inputor.change();
319
+ };
320
+
321
+ Controller.prototype.on_keyup = function(e) {
252
322
  switch (e.keyCode) {
253
- case 27:
323
+ case KEY_CODE.ESC:
254
324
  e.preventDefault();
255
- view.hide();
325
+ this.view.hide();
256
326
  break;
257
- default:
327
+ case KEY_CODE.DOWN:
328
+ case KEY_CODE.UP:
258
329
  $.noop();
330
+ break;
331
+ default:
332
+ this.look_up();
259
333
  }
260
334
  return e.stopPropagation();
261
- },
262
- onkeydown: function(e) {
263
- var view;
264
- view = this.view;
265
- if (!view.isShowing()) {
335
+ };
336
+
337
+ Controller.prototype.on_keydown = function(e) {
338
+ if (!this.view.visible()) {
266
339
  return;
267
340
  }
268
341
  switch (e.keyCode) {
269
- case 27:
342
+ case KEY_CODE.ESC:
270
343
  e.preventDefault();
271
- view.hide();
344
+ this.view.hide();
272
345
  break;
273
- case 38:
346
+ case KEY_CODE.UP:
274
347
  e.preventDefault();
275
- view.prev();
348
+ this.view.prev();
276
349
  break;
277
- case 40:
350
+ case KEY_CODE.DOWN:
278
351
  e.preventDefault();
279
- view.next();
352
+ this.view.next();
280
353
  break;
281
- case 9:
282
- case 13:
283
- if (!view.isShowing()) {
354
+ case KEY_CODE.TAB:
355
+ case KEY_CODE.ENTER:
356
+ if (!this.view.visible()) {
284
357
  return;
285
358
  }
286
359
  e.preventDefault();
287
- view.choose();
360
+ this.view.choose();
288
361
  break;
289
362
  default:
290
363
  $.noop();
291
364
  }
292
365
  return e.stopPropagation();
293
- },
294
- renderView: function(datas) {
295
- log("At.renderView", this, datas);
296
- datas = datas.splice(0, this.getOpt('limit'));
297
- datas = _unique(datas, this.dataValue());
298
- datas = _objectify(datas);
299
- datas = _sorter.call(this, datas);
300
- return this.view.render(this, datas);
301
- },
302
- lookup: function() {
303
- var callback, datas, key;
304
- key = this.getKeyname();
305
- if (!key) {
366
+ };
367
+
368
+ Controller.prototype.render_view = function(data) {
369
+ var search_key;
370
+ search_key = this.get_opt("search_key");
371
+ data = this.callbacks("sorter").call(this, this.query.text, data, search_key);
372
+ data = data.splice(0, this.get_opt('limit'));
373
+ return this.view.render(data);
374
+ };
375
+
376
+ Controller.prototype.look_up = function() {
377
+ var data, origin_data, params, query, search_key;
378
+ query = this.catch_query();
379
+ if (!query) {
306
380
  return false;
307
381
  }
308
- log("At.lookup.key", key);
309
- if (!_isNil(datas = this.cache())) {
310
- this.renderView(datas);
311
- } else if (!_isNil(datas = this.lookupWithData(key))) {
312
- this.renderView(datas);
313
- } else if ($.isFunction(callback = this.getOpt('callback'))) {
314
- callback(key.text, $.proxy(this.renderView, this));
382
+ origin_data = this.get_opt("data");
383
+ search_key = this.get_opt("search_key");
384
+ if (typeof origin_data === "string") {
385
+ params = {
386
+ q: query.text,
387
+ limit: this.get_opt("limit")
388
+ };
389
+ this.callbacks('remote_filter').call(this, params, origin_data, $.proxy(this.render_view, this));
390
+ } else if ((data = this.callbacks('filter').call(this, query.text, origin_data, search_key))) {
391
+ this.render_view(data);
315
392
  } else {
316
393
  this.view.hide();
317
394
  }
318
395
  return $.noop();
319
- },
320
- lookupWithData: function(key) {
321
- var data, items,
322
- _this = this;
323
- data = this.getOpt("data");
324
- if ($.isArray(data) && data.length !== 0) {
325
- items = $.map(data, function(item, i) {
326
- var match, name, regexp;
327
- try {
328
- name = $.isPlainObject(item) ? item[_this.dataValue()] : item;
329
- regexp = new RegExp(key.text.replace("+", "\\+"), 'i');
330
- match = name.match(regexp);
331
- } catch (e) {
332
- return null;
333
- }
334
- if (match) {
335
- return item;
336
- } else {
337
- return null;
338
- }
339
- });
340
- }
341
- return items;
396
+ };
397
+
398
+ return Controller;
399
+
400
+ })();
401
+ View = (function() {
402
+
403
+ function View(controller) {
404
+ this.controller = controller;
405
+ this.id = this.controller.get_opt("view_id", "at-view");
406
+ this.timeout_id = null;
407
+ this.$el = $("#" + this.id);
408
+ this.create_view();
342
409
  }
343
- };
344
- AtView = {
345
- timeout_id: null,
346
- id: '#at-view',
347
- holder: null,
348
- _jqo: null,
349
- jqo: function() {
350
- var jqo;
351
- jqo = this._jqo;
352
- return jqo = _isNil(jqo) ? (this._jqo = $(this.id)) : jqo;
353
- },
354
- init: function() {
410
+
411
+ View.prototype.create_view = function() {
355
412
  var $menu, tpl,
356
413
  _this = this;
357
- if (!_isNil(this.jqo())) {
414
+ if (this.exist()) {
358
415
  return;
359
416
  }
360
- tpl = "<div id='" + this.id.slice(1) + "' class='at-view'><ul id='" + this.id.slice(1) + "-ul'></ul></div>";
417
+ tpl = "<div id='" + this.id + "' class='at-view'><ul id='" + this.id + "-ul'></ul></div>";
361
418
  $("body").append(tpl);
362
- $menu = this.jqo().find('ul');
419
+ this.$el = $("#" + this.id);
420
+ $menu = this.$el.find('ul');
363
421
  return $menu.on('mouseenter.view', 'li', function(e) {
364
422
  $menu.find('.cur').removeClass('cur');
365
423
  return $(e.currentTarget).addClass('cur');
366
424
  }).on('click', function(e) {
367
425
  e.stopPropagation();
368
426
  e.preventDefault();
369
- return _this.choose();
427
+ return _this.$el.data("_view").choose();
370
428
  });
371
- },
372
- isShowing: function() {
373
- return this.jqo().is(":visible");
374
- },
375
- choose: function() {
376
- var $li, str;
377
- $li = this.jqo().find(".cur");
378
- str = _isNil($li) ? this.holder.query.text + " " : $li.attr(this.holder.getOpt("choose")) + " ";
379
- this.holder.replaceStr(str);
429
+ };
430
+
431
+ View.prototype.exist = function() {
432
+ return $("#" + this.id).length > 0;
433
+ };
434
+
435
+ View.prototype.visible = function() {
436
+ return this.$el.is(":visible");
437
+ };
438
+
439
+ View.prototype.choose = function() {
440
+ var $li;
441
+ $li = this.$el.find(".cur");
442
+ this.controller.callbacks("selector").call(this.controller, $li);
443
+ this.controller.trigger("choose", [$li]);
380
444
  return this.hide();
381
- },
382
- rePosition: function() {
383
- var rect;
384
- rect = this.holder.rect();
385
- if (rect.bottom + this.jqo().height() - $(window).scrollTop() > $(window).height()) {
386
- rect.bottom = rect.top - this.jqo().height();
445
+ };
446
+
447
+ View.prototype.reposition = function() {
448
+ var offset, rect;
449
+ rect = this.controller.rect();
450
+ if (rect.bottom + this.$el.height() - $(window).scrollTop() > $(window).height()) {
451
+ rect.bottom = rect.top - this.$el.height();
387
452
  }
388
- log("AtView.rePosition", {
389
- left: rect.left,
390
- top: rect.bottom
391
- });
392
- return this.jqo().offset({
453
+ offset = {
393
454
  left: rect.left,
394
455
  top: rect.bottom
395
- });
396
- },
397
- next: function() {
456
+ };
457
+ this.$el.offset(offset);
458
+ return this.controller.trigger("reposition", [offset]);
459
+ };
460
+
461
+ View.prototype.next = function() {
398
462
  var cur, next;
399
- cur = this.jqo().find('.cur').removeClass('cur');
463
+ cur = this.$el.find('.cur').removeClass('cur');
400
464
  next = cur.next();
401
465
  if (!next.length) {
402
- next = $(this.jqo().find('li')[0]);
466
+ next = $(this.$el.find('li')[0]);
403
467
  }
404
468
  return next.addClass('cur');
405
- },
406
- prev: function() {
469
+ };
470
+
471
+ View.prototype.prev = function() {
407
472
  var cur, prev;
408
- cur = this.jqo().find('.cur').removeClass('cur');
473
+ cur = this.$el.find('.cur').removeClass('cur');
409
474
  prev = cur.prev();
410
475
  if (!prev.length) {
411
- prev = this.jqo().find('li').last();
476
+ prev = this.$el.find('li').last();
412
477
  }
413
478
  return prev.addClass('cur');
414
- },
415
- show: function() {
416
- if (!this.isShowing()) {
417
- this.jqo().show();
479
+ };
480
+
481
+ View.prototype.show = function() {
482
+ if (!this.visible()) {
483
+ this.$el.show();
418
484
  }
419
- return this.rePosition();
420
- },
421
- hide: function(time) {
485
+ return this.reposition();
486
+ };
487
+
488
+ View.prototype.hide = function(time) {
422
489
  var callback,
423
490
  _this = this;
424
491
  if (isNaN(time)) {
425
- if (this.isShowing()) {
426
- return this.jqo().hide();
492
+ if (this.visible()) {
493
+ return this.$el.hide();
427
494
  }
428
495
  } else {
429
496
  callback = function() {
430
497
  return _this.hide();
431
498
  };
432
499
  clearTimeout(this.timeout_id);
433
- return this.timeout_id = setTimeout(callback, 300);
434
- }
435
- },
436
- clear: function(clear_all) {
437
- if (clear_all === true) {
438
- this._cache = {};
500
+ return this.timeout_id = setTimeout(callback, time);
439
501
  }
440
- return this.jqo().find('ul').empty();
441
- },
442
- render: function(holder, list) {
443
- var $ul, tpl;
502
+ };
503
+
504
+ View.prototype.clear = function() {
505
+ return this.$el.find('ul').empty();
506
+ };
507
+
508
+ View.prototype.render = function(list) {
509
+ var $ul, tpl,
510
+ _this = this;
444
511
  if (!$.isArray(list)) {
445
512
  return false;
446
513
  }
@@ -448,169 +515,98 @@
448
515
  this.hide();
449
516
  return true;
450
517
  }
451
- this.holder = holder;
452
- holder.cache(list);
453
518
  this.clear();
454
- $ul = this.jqo().find('ul');
455
- tpl = holder.getOpt('tpl');
519
+ this.$el.data("_view", this);
520
+ $ul = this.$el.find('ul');
521
+ tpl = this.controller.get_opt('tpl', DEFAULT_TPL);
456
522
  $.each(list, function(i, item) {
457
- var li;
458
- tpl || (tpl = _DEFAULT_TPL);
459
- li = _evalTpl(tpl, item);
460
- log("AtView.render", li);
461
- return $ul.append(_highlighter(li, holder.query.text));
523
+ var $li, li;
524
+ li = _this.controller.callbacks("tpl_eval").call(_this.controller, tpl, item);
525
+ $li = $(_this.controller.callbacks("highlighter").call(_this.controller, li, _this.controller.query.text));
526
+ $li.data("info", item);
527
+ return $ul.append($li);
462
528
  });
463
529
  this.show();
464
530
  return $ul.find("li:eq(0)").addClass("cur");
465
- }
466
- };
467
- _objectify = function(list) {
468
- return $.map(list, function(item, k) {
469
- if (!$.isPlainObject(item)) {
470
- item = {
471
- id: k,
472
- name: item
473
- };
474
- }
475
- return item;
476
- });
477
- };
478
- _evalTpl = function(tpl, map) {
479
- var el;
480
- try {
481
- return el = tpl.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
482
- return map[key];
483
- });
484
- } catch (error) {
485
- return "";
486
- }
487
- };
488
- _highlighter = function(li, query) {
489
- if (_isNil(query)) {
490
- return li;
491
- }
492
- return li.replace(new RegExp(">\\s*(\\w*)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig'), function(str, $1, $2, $3) {
493
- return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
494
- });
495
- };
496
- _sorter = function(items) {
497
- var data_value, item, query, results, text, _i, _len;
498
- data_value = this.dataValue();
499
- query = this.query.text;
500
- results = [];
501
- for (_i = 0, _len = items.length; _i < _len; _i++) {
502
- item = items[_i];
503
- text = item[data_value];
504
- if (text.toLowerCase().indexOf(query) === -1) {
505
- continue;
506
- }
507
- item.order = text.toLowerCase().indexOf(query);
508
- results.push(item);
509
- }
510
- results.sort(function(a, b) {
511
- return a.order - b.order;
512
- });
513
- return results;
514
- };
515
- /*
516
- maybe we can use $._unique.
517
- But i don't know it will delete li element frequently or not.
518
- I think we should not change DOM element frequently.
519
- more, It seems batter not to call evalTpl function too much times.
520
- */
521
-
522
- _unique = function(list, query) {
523
- var record;
524
- record = [];
525
- return $.map(list, function(v, id) {
526
- var value;
527
- value = $.isPlainObject(v) ? v[query] : v;
528
- if ($.inArray(value, record) < 0) {
529
- record.push(value);
530
- return v;
531
- }
532
- });
533
- };
534
- _isNil = function(target) {
535
- return !target || ($.isPlainObject(target) && $.isEmptyObject(target)) || ($.isArray(target) && target.length === 0) || (target instanceof $ && target.length === 0) || target === void 0;
536
- };
537
- _DEFAULT_TPL = "<li id='${id}' data-value='${name}'>${name}</li>";
538
- log = function() {};
539
- $.fn.atWho = function(flag, options) {
540
- AtView.init();
531
+ };
532
+
533
+ return View;
534
+
535
+ })();
536
+ DEFAULT_TPL = "<li data-value='${name}'>${name}</li>";
537
+ $.fn.atwho = function(flag, options) {
541
538
  return this.filter('textarea, input').each(function() {
542
539
  var $this, data;
543
540
  $this = $(this);
544
- data = $this.data("AtWho");
541
+ data = $this.data("atwho");
545
542
  if (!data) {
546
- $this.data('AtWho', (data = new At(this)));
543
+ $this.data('atwho', (data = new Controller(this)));
547
544
  }
548
545
  return data.reg(flag, options);
549
546
  });
550
547
  };
551
- return $.fn.atWho["default"] = {
552
- data: [],
553
- choose: "data-value",
554
- callback: null,
555
- cache: true,
548
+ $.fn.atwho.Controller = Controller;
549
+ $.fn.atwho.View = View;
550
+ $.fn.atwho.Mirror = Mirror;
551
+ return $.fn.atwho["default"] = {
552
+ data: null,
553
+ search_key: "name",
554
+ callbacks: DEFAULT_CALLBACKS,
556
555
  limit: 5,
557
556
  display_flag: true,
558
- tpl: _DEFAULT_TPL
557
+ display_timeout: 300,
558
+ tpl: DEFAULT_TPL
559
559
  };
560
- })(window.jQuery);
561
-
562
- /*
563
- Implement Twitter/Weibo @ mentions
564
-
565
- Copyright (c) 2012 chord.luo@gmail.com
566
-
567
- Permission is hereby granted, free of charge, to any person obtaining
568
- a copy of this software and associated documentation files (the
569
- "Software"), to deal in the Software without restriction, including
570
- without limitation the rights to use, copy, modify, merge, publish,
571
- distribute, sublicense, and/or sell copies of the Software, and to
572
- permit persons to whom the Software is furnished to do so, subject to
573
- the following conditions:
574
-
575
- The above copyright notice and this permission notice shall be
576
- included in all copies or substantial portions of the Software.
577
-
578
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
579
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
580
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
581
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
582
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
583
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
584
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
585
- */
586
-
587
-
588
- /*
589
- 本插件操作 textarea 或者 input 内的插入符
590
- 只实现了获得插入符在文本框中的位置,我设置
591
- 插入符的位置.
592
- */
593
-
594
-
595
- (function($) {
560
+ });
561
+
562
+ }).call(this);
563
+
564
+
565
+ /*
566
+ Implement Github like autocomplete mentions
567
+ http://ichord.github.com/At.js
568
+
569
+ Copyright (c) 2013 chord.luo@gmail.com
570
+ Licensed under the MIT license.
571
+ */
572
+
573
+
574
+ /*
575
+ 本插件操作 textarea 或者 input 内的插入符
576
+ 只实现了获得插入符在文本框中的位置,我设置
577
+ 插入符的位置.
578
+ */
579
+
580
+
581
+ (function() {
582
+
583
+ (function(factory) {
584
+ if (typeof exports === 'object') {
585
+ return factory(require('jquery'));
586
+ } else if (typeof define === 'function' && define.amd) {
587
+ return define(['jquery']);
588
+ } else {
589
+ return factory(window.jQuery);
590
+ }
591
+ })(function($) {
596
592
  var getCaretPos, setCaretPos;
597
593
  getCaretPos = function(inputor) {
598
594
  var end, endRange, len, normalizedValue, pos, range, start, textInputRange;
599
595
  if (document.selection) {
600
596
  /*
601
- #assume we select "HATE" in the inputor such as textarea -> { }.
602
- * start end-point.
603
- * /
604
- * < I really [HATE] IE > between the brackets is the selection range.
605
- * \
606
- * end end-point.
597
+ #assume we select "HATE" in the inputor such as textarea -> { }.
598
+ * start end-point.
599
+ * /
600
+ * < I really [HATE] IE > between the brackets is the selection range.
601
+ * \
602
+ * end end-point.
607
603
  */
608
604
 
609
605
  range = document.selection.createRange();
610
606
  pos = 0;
611
607
  if (range && range.parentElement() === inputor) {
612
608
  normalizedValue = inputor.value.replace(/\r\n/g, "\n");
613
- /* SOMETIME !!!
609
+ /* SOMETIME !!!
614
610
  "/r/n" is counted as two char.
615
611
  one line is two, two will be four. balalala.
616
612
  so we have to using the normalized one's length.;
@@ -618,8 +614,8 @@
618
614
 
619
615
  len = normalizedValue.length;
620
616
  /*
621
- <[ I really HATE IE ]>:
622
- the whole content in the inputor will be the textInputRange.
617
+ <[ I really HATE IE ]>:
618
+ the whole content in the inputor will be the textInputRange.
623
619
  */
624
620
 
625
621
  textInputRange = inputor.createTextRange();
@@ -639,26 +635,26 @@
639
635
 
640
636
  endRange.collapse(false);
641
637
  /*
642
- ___VS____
643
- / \
644
- < I really [[HATE] IE []]>
645
- \_endRange end-point.
646
-
647
- " > -1" mean the start end-point will be the same or right to the end end-point
648
- * simplelly, all in the end.
638
+ ___VS____
639
+ / \
640
+ < I really [[HATE] IE []]>
641
+ \_endRange end-point.
642
+
643
+ " > -1" mean the start end-point will be the same or right to the end end-point
644
+ * simplelly, all in the end.
649
645
  */
650
646
 
651
647
  if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
652
648
  start = end = len;
653
649
  } else {
654
650
  /*
655
- I really |HATE] IE ]>
656
- <-|
657
- I really[ [HATE] IE ]>
658
- <-[
659
- I reall[y [HATE] IE ]>
660
-
661
- will return how many unit have moved.
651
+ I really |HATE] IE ]>
652
+ <-|
653
+ I really[ [HATE] IE ]>
654
+ <-[
655
+ I reall[y [HATE] IE ]>
656
+
657
+ will return how many unit have moved.
662
658
  */
663
659
 
664
660
  start = -textInputRange.moveStart("character", -len);
@@ -690,6 +686,6 @@
690
686
  return getCaretPos(inputor);
691
687
  }
692
688
  };
693
- })(window.jQuery);
689
+ });
694
690
 
695
691
  }).call(this);
@@ -1,47 +1 @@
1
- #at-view {
2
- position:absolute;
3
- top: 0;
4
- left: 0;
5
- display: none;
6
- margin-top: 18px;
7
- background: white;
8
- border: 1px solid #DDD;
9
- border-radius: 3px;
10
- box-shadow: 0 0 5px rgba(0,0,0,0.1);
11
- min-width: 120px;
12
- }
13
-
14
- #at-view .cur {
15
- background: #3366FF;
16
- color: white;
17
- }
18
- #at-view .cur small {
19
- color: white;
20
- }
21
- #at-view strong {
22
- color: #3366FF;
23
- }
24
- #at-view .cur strong {
25
- color: white;
26
- font:bold;
27
- }
28
- #at-view ul {
29
- /* width: 100px; */
30
- list-style:none;
31
- padding:0;
32
- margin:auto;
33
- }
34
- #at-view ul li {
35
- display: block;
36
- padding: 5px 10px;
37
- border-bottom: 1px solid #DDD;
38
- cursor: pointer;
39
- /* border-top: 1px solid #C8C8C8; */
40
- }
41
- #at-view small {
42
- font-size: smaller;
43
- color: #777;
44
- font-weight: normal;
45
- }
46
-
47
-
1
+ #at-view{position:absolute;top:0;left:0;display:none;margin-top:18px;background:#fff;border:1px solid #DDD;border-radius:3px;box-shadow:0 0 5px rgba(0,0,0,.1);min-width:120px;z-index:10}#at-view .cur{background:#36F;color:#fff}#at-view .cur small{color:#fff}#at-view strong{color:#36F}#at-view .cur strong{color:#fff;font:bold}#at-view ul{list-style:none;padding:0;margin:auto}#at-view ul li{display:block;padding:5px 10px;border-bottom:1px solid #DDD;cursor:pointer}#at-view small{font-size:smaller;color:#777;font-weight:400}
@@ -1,7 +1,7 @@
1
1
  module Jquery
2
2
  module Atwho
3
3
  module Rails
4
- VERSION = "0.1.7"
4
+ VERSION = "0.2.1"
5
5
  end
6
6
  end
7
7
  end
@@ -1,4 +1,5 @@
1
1
  require "jquery-atwho-rails/version"
2
+ require "ActiveSupport"
2
3
 
3
4
  module Jquery
4
5
  module Atwho
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jquery-atwho-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-06 00:00:00.000000000 Z
12
+ date: 2013-01-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -43,8 +43,7 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
- description: ! "This is a jQuery plugin \n that implement Twitter/Weibo like @
47
- mentions."
46
+ description: ! "This is a jQuery plugin\n that implement Github-like mentions."
48
47
  email:
49
48
  - chord.luo@gmail.com
50
49
  executables: []
@@ -76,12 +75,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
75
  - - ! '>='
77
76
  - !ruby/object:Gem::Version
78
77
  version: '0'
78
+ segments:
79
+ - 0
80
+ hash: -3411578706402898122
79
81
  required_rubygems_version: !ruby/object:Gem::Requirement
80
82
  none: false
81
83
  requirements:
82
84
  - - ! '>='
83
85
  - !ruby/object:Gem::Version
84
86
  version: '0'
87
+ segments:
88
+ - 0
89
+ hash: -3411578706402898122
85
90
  requirements: []
86
91
  rubyforge_project: jquery-atwho-rails
87
92
  rubygems_version: 1.8.24
@@ -91,4 +96,3 @@ summary: ! 'jquery plugin: @mentions'
91
96
  test_files:
92
97
  - spec/generators/install_generator_spec.rb
93
98
  - spec/spec_helper.rb
94
- has_rdoc: