jquery-atwho-rails 0.1.7 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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: