has_mailbox 1.5.5

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.
Files changed (30) hide show
  1. data/Gemfile +4 -0
  2. data/README.rdoc +95 -0
  3. data/Rakefile +2 -0
  4. data/app/controllers/mailboxes_controller.rb +3 -0
  5. data/has_mailbox.gemspec +24 -0
  6. data/lib/generators/has_mailbox/install/install_generator.rb +33 -0
  7. data/lib/generators/has_mailbox/install/templates/README +26 -0
  8. data/lib/generators/has_mailbox/install/templates/jquery.tokeninput.js +718 -0
  9. data/lib/generators/has_mailbox/install/templates/mailboxes.css +147 -0
  10. data/lib/generators/has_mailbox/install/templates/mailboxes.js +28 -0
  11. data/lib/generators/has_mailbox/install/templates/token-input-facebook.css +122 -0
  12. data/lib/generators/has_mailbox/install/templates/views/_head.html.erb +19 -0
  13. data/lib/generators/has_mailbox/install/templates/views/_messages.html.erb +39 -0
  14. data/lib/generators/has_mailbox/install/templates/views/_tabs_panel.html.erb +11 -0
  15. data/lib/generators/has_mailbox/install/templates/views/index.html.erb +10 -0
  16. data/lib/generators/has_mailbox/install/templates/views/index.js.erb +1 -0
  17. data/lib/generators/has_mailbox/install/templates/views/new.html.erb +28 -0
  18. data/lib/generators/has_mailbox/install/templates/views/show.html.erb +35 -0
  19. data/lib/generators/has_mailbox/migration/migration_generator.rb +22 -0
  20. data/lib/generators/has_mailbox/migration/templates/create_message_copies_table.rb +17 -0
  21. data/lib/generators/has_mailbox/migration/templates/create_messages_table.rb +19 -0
  22. data/lib/has_mailbox.rb +14 -0
  23. data/lib/has_mailbox/controllers/method_helpers.rb +112 -0
  24. data/lib/has_mailbox/has_mailbox.rb +95 -0
  25. data/lib/has_mailbox/mailboxes/engine.rb +6 -0
  26. data/lib/has_mailbox/mailboxes/routing.rb +22 -0
  27. data/lib/has_mailbox/models/message.rb +52 -0
  28. data/lib/has_mailbox/models/message_copies.rb +30 -0
  29. data/lib/has_mailbox/version.rb +3 -0
  30. metadata +96 -0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in has_mailbox.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,95 @@
1
+ = has_mailbox
2
+
3
+ If you like to add messaging functionality between the users in your rails app, use this gem!
4
+ this gem also provide standard mailbox/messagebox interface allowing each users have their own inbox, outbox and trash.
5
+
6
+ This rails engine compatible with Rails 3.x only.
7
+
8
+ == Setup
9
+
10
+ add the gem to your Gemfile.
11
+
12
+ gem "has_mailbox"
13
+
14
+ run bundle command and generate the migration files, this will generate messages table and messagecopies table,
15
+ after that don't forget to run the migration.
16
+
17
+ rails g has_mailbox:migration
18
+ rake db:migrate
19
+
20
+ Then add has_mailbox method in your user model, for example :
21
+
22
+ class User < ActiveRecord::Base
23
+ has_mailbox
24
+ ...
25
+ end
26
+
27
+ Until this step you are able to send messages between the users across your application.
28
+ Try to create two user object and send messages to one user with another,
29
+ and list all messages from the entire mailbox.
30
+
31
+ @user1 = User.first # => create first user
32
+ @user2 = User.last # => return the second user
33
+
34
+ @user1.send_message("Hi Subject","Hi Body !!!",@user2) # => send message to @user2
35
+ @user1.send_message?("Hi Subject","Hi Body !!!",@user2) # => send message with true/false return
36
+ @user1.send_message("Hi Subject","Hi Body !!!",@user2,@user3) # => send message with with multiple recipients
37
+
38
+ @user1.inbox # => return all incoming messages for @user1
39
+ @user1.outbox # => return outgoing messages for @user1
40
+ @user1.trash # => return all messages that has been deleted
41
+
42
+ If you like to delete the message from user object, and empty the box try use this method.
43
+
44
+ @message = @user1.inbox.find(1) # => get message from inbox with id 1
45
+ @message.delete # => delete message from inbox will be moved to trash box.
46
+
47
+ @user1.trash # => return deleted messages
48
+
49
+ @message = @user.trash.find(1) # => get the current deleted message
50
+
51
+ @message.undelete # => will return the message to inbox
52
+ or
53
+ @message.delete # => will delete message permanently
54
+
55
+ @user.empty_mailbox(:inbox => true) # => will delete all messages from inbox to trash.
56
+ @user.empty_mailbox(:outbox => true) # => will delete all outgoing messages permanently.
57
+ @user.empty_mailbox(:inbox => true) # => will delete all messages from trash permanently.
58
+
59
+ Each user are able to mark their message as read/unread. Example :
60
+
61
+ @user1.inbox.find(1).mark_as_read # update attribute message opened to true
62
+ @user1.inbox.find(1).mark_as_unread # to false
63
+
64
+ == Install Mailbox Views
65
+ This gem has been build using Devise authentication, but if you like using another authentication plugin,
66
+ such as authlogic / restful-authentication or any other authentication plugins, it still suits to your app.
67
+
68
+ rails g has_mailbox:install
69
+
70
+ this generator will install to your application the views and stylesheet file in your public directory,
71
+ notice the required argument "user_attribute_name", you have to fill this with desired attribute name in your user model
72
+ you wish to display in your generated views. e.g. : email, username, first_name, etc.
73
+ the default will be set to "email", cause Devise default user login using "email" attribute.
74
+
75
+ here's the tricky part. you have to define the route for mailboxes in order to get controller will work with your views.
76
+ Also you have to specifies the current user object from your authentication plugin and also the attribute name you wish to display.
77
+
78
+ with Devise authentication to get the default user object that is currently sign in is "current_user"
79
+ and the display attribute using "email".
80
+ So in your config/routes.rb, please add mailboxes_for method. Example :
81
+
82
+ mailboxes_for :users # => with this argument user_object_name
83
+ # => will set to "current_user" and
84
+ # => user_attribute_name will set to "email"
85
+
86
+ or if you using different plugins/gems, you can set with your own user_object_name and user_attribute_name
87
+
88
+ mailboxes_for :users, :user_object_name => "current_user_sign_in", :user_attribute_name => "username"
89
+
90
+
91
+ == Your issues are needed
92
+
93
+ This is gem are still under development mode, so if you are having some problems with has_mailbox, please submit an issues here.
94
+
95
+ http://github.com/fajrif/has_mailbox/issues
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,3 @@
1
+ class MailboxesController < ApplicationController
2
+ include HasMailbox::Controllers::MethodHelpers
3
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "has_mailbox/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "has_mailbox"
7
+ s.version = HasMailbox::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Fajri Fachriansyah"]
10
+ s.email = ["fajrif@hotmail.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Add ability for each user to have a Mailbox}
13
+ s.description = %q{This gem allowing each user to send and receive private messages}
14
+
15
+ s.rubyforge_project = "has_mailbox"
16
+
17
+ s.add_runtime_dependency "jquery-rails"
18
+ s.add_runtime_dependency "will_paginate"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end
@@ -0,0 +1,33 @@
1
+ module HasMailbox
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.join(File.dirname(__FILE__), 'templates')
5
+
6
+ desc "Create views, javascripts and stylesheets files for mailboxes controller."
7
+
8
+ def copy_stylesheet
9
+ copy_file 'mailboxes.css', 'public/stylesheets/mailboxes.css'
10
+ copy_file 'token-input-facebook.css', 'public/stylesheets/token-input-facebook.css'
11
+ end
12
+
13
+ def copy_javascript
14
+ copy_file 'mailboxes.js', 'public/javascripts/mailboxes.js'
15
+ copy_file 'jquery.tokeninput.js', 'public/javascripts/jquery.tokeninput.js'
16
+ end
17
+
18
+ def copy_views
19
+ copy_file "views/_head.html.erb", "app/views/mailboxes/_head.html.erb"
20
+ copy_file "views/_messages.html.erb", "app/views/mailboxes/_messages.html.erb"
21
+ copy_file "views/_tabs_panel.html.erb", "app/views/mailboxes/_tabs_panel.html.erb"
22
+ copy_file "views/index.html.erb", "app/views/mailboxes/index.html.erb"
23
+ copy_file "views/index.js.erb", "app/views/mailboxes/index.js.erb"
24
+ copy_file "views/new.html.erb", "app/views/mailboxes/new.html.erb"
25
+ copy_file "views/show.html.erb", "app/views/mailboxes/show.html.erb"
26
+ end
27
+
28
+ def show_readme
29
+ readme "README"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+
2
+ *****************************************************************************************
3
+
4
+ Some setup you must do manually if you haven't yet:
5
+
6
+ 1. Ensure the has_mailbox method has been defined your user model.
7
+
8
+ class User < ActiveRecord::Base
9
+ has_mailbox
10
+ ...
11
+ end
12
+
13
+ 2. Ensure you have defined mailboxes route to your config/routes.rb.
14
+ For example:
15
+
16
+ mailboxes_for :users
17
+ or
18
+ mailboxes_for :users, :user_object_name => "current_user", :user_display_attribute => "email"
19
+
20
+ 3. Ensure you have flash messages in app/views/layouts/application.html.erb.
21
+ For example:
22
+
23
+ <p class="notice"><%= notice %></p>
24
+ <p class="alert"><%= alert %></p>
25
+
26
+ *****************************************************************************************
@@ -0,0 +1,718 @@
1
+ /*
2
+ * jQuery Plugin: Tokenizing Autocomplete Text Entry
3
+ * Version 1.4.2
4
+ *
5
+ * Copyright (c) 2009 James Smith (http://loopj.com)
6
+ * Licensed jointly under the GPL and MIT licenses,
7
+ * choose which one suits your project best!
8
+ *
9
+ */
10
+
11
+ (function ($) {
12
+ // Default settings
13
+ var DEFAULT_SETTINGS = {
14
+ hintText: "Type in a search term",
15
+ noResultsText: "No results",
16
+ searchingText: "Searching...",
17
+ deleteText: "&times;",
18
+ searchDelay: 300,
19
+ minChars: 1,
20
+ tokenLimit: null,
21
+ jsonContainer: null,
22
+ method: "GET",
23
+ contentType: "json",
24
+ queryParam: "q",
25
+ tokenDelimiter: ",",
26
+ preventDuplicates: false,
27
+ prePopulate: null,
28
+ animateDropdown: true,
29
+ onResult: null,
30
+ onAdd: null,
31
+ onDelete: null
32
+ };
33
+
34
+ // Default classes to use when theming
35
+ var DEFAULT_CLASSES = {
36
+ tokenList: "token-input-list",
37
+ token: "token-input-token",
38
+ tokenDelete: "token-input-delete-token",
39
+ selectedToken: "token-input-selected-token",
40
+ highlightedToken: "token-input-highlighted-token",
41
+ dropdown: "token-input-dropdown",
42
+ dropdownItem: "token-input-dropdown-item",
43
+ dropdownItem2: "token-input-dropdown-item2",
44
+ selectedDropdownItem: "token-input-selected-dropdown-item",
45
+ inputToken: "token-input-input-token"
46
+ };
47
+
48
+ // Input box position "enum"
49
+ var POSITION = {
50
+ BEFORE: 0,
51
+ AFTER: 1,
52
+ END: 2
53
+ };
54
+
55
+ // Keys "enum"
56
+ var KEY = {
57
+ BACKSPACE: 8,
58
+ TAB: 9,
59
+ ENTER: 13,
60
+ ESCAPE: 27,
61
+ SPACE: 32,
62
+ PAGE_UP: 33,
63
+ PAGE_DOWN: 34,
64
+ END: 35,
65
+ HOME: 36,
66
+ LEFT: 37,
67
+ UP: 38,
68
+ RIGHT: 39,
69
+ DOWN: 40,
70
+ NUMPAD_ENTER: 108,
71
+ COMMA: 188
72
+ };
73
+
74
+
75
+ // Expose the .tokenInput function to jQuery as a plugin
76
+ $.fn.tokenInput = function (url_or_data, options) {
77
+ var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
78
+
79
+ return this.each(function () {
80
+ new $.TokenList(this, url_or_data, settings);
81
+ });
82
+ };
83
+
84
+
85
+ // TokenList class for each input
86
+ $.TokenList = function (input, url_or_data, settings) {
87
+ //
88
+ // Initialization
89
+ //
90
+
91
+ // Configure the data source
92
+ if($.type(url_or_data) === "string") {
93
+ // Set the url to query against
94
+ settings.url = url_or_data;
95
+
96
+ // Make a smart guess about cross-domain if it wasn't explicitly specified
97
+ if(settings.crossDomain === undefined) {
98
+ if(settings.url.indexOf("://") === -1) {
99
+ settings.crossDomain = false;
100
+ } else {
101
+ settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]);
102
+ }
103
+ }
104
+ } else if($.type(url_or_data) === "array") {
105
+ // Set the local data to search through
106
+ settings.local_data = url_or_data;
107
+ }
108
+
109
+ // Build class names
110
+ if(settings.classes) {
111
+ // Use custom class names
112
+ settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
113
+ } else if(settings.theme) {
114
+ // Use theme-suffixed default class names
115
+ settings.classes = {};
116
+ $.each(DEFAULT_CLASSES, function(key, value) {
117
+ settings.classes[key] = value + "-" + settings.theme;
118
+ });
119
+ } else {
120
+ settings.classes = DEFAULT_CLASSES;
121
+ }
122
+
123
+
124
+ // Save the tokens
125
+ var saved_tokens = [];
126
+
127
+ // Keep track of the number of tokens in the list
128
+ var token_count = 0;
129
+
130
+ // Basic cache to save on db hits
131
+ var cache = new $.TokenList.Cache();
132
+
133
+ // Keep track of the timeout, old vals
134
+ var timeout;
135
+ var input_val;
136
+
137
+ // Create a new text input an attach keyup events
138
+ var input_box = $("<input type=\"text\" autocomplete=\"off\">")
139
+ .css({
140
+ outline: "none"
141
+ })
142
+ .focus(function () {
143
+ if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
144
+ show_dropdown_hint();
145
+ }
146
+ })
147
+ .blur(function () {
148
+ hide_dropdown();
149
+ })
150
+ .bind("keyup keydown blur update", resize_input)
151
+ .keydown(function (event) {
152
+ var previous_token;
153
+ var next_token;
154
+
155
+ switch(event.keyCode) {
156
+ case KEY.LEFT:
157
+ case KEY.RIGHT:
158
+ case KEY.UP:
159
+ case KEY.DOWN:
160
+ if(!$(this).val()) {
161
+ previous_token = input_token.prev();
162
+ next_token = input_token.next();
163
+
164
+ if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
165
+ // Check if there is a previous/next token and it is selected
166
+ if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
167
+ deselect_token($(selected_token), POSITION.BEFORE);
168
+ } else {
169
+ deselect_token($(selected_token), POSITION.AFTER);
170
+ }
171
+ } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
172
+ // We are moving left, select the previous token if it exists
173
+ select_token($(previous_token.get(0)));
174
+ } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
175
+ // We are moving right, select the next token if it exists
176
+ select_token($(next_token.get(0)));
177
+ }
178
+ } else {
179
+ var dropdown_item = null;
180
+
181
+ if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
182
+ dropdown_item = $(selected_dropdown_item).next();
183
+ } else {
184
+ dropdown_item = $(selected_dropdown_item).prev();
185
+ }
186
+
187
+ if(dropdown_item.length) {
188
+ select_dropdown_item(dropdown_item);
189
+ }
190
+ return false;
191
+ }
192
+ break;
193
+
194
+ case KEY.BACKSPACE:
195
+ previous_token = input_token.prev();
196
+
197
+ if(!$(this).val().length) {
198
+ if(selected_token) {
199
+ delete_token($(selected_token));
200
+ } else if(previous_token.length) {
201
+ select_token($(previous_token.get(0)));
202
+ }
203
+
204
+ return false;
205
+ } else if($(this).val().length === 1) {
206
+ hide_dropdown();
207
+ } else {
208
+ // set a timeout just long enough to let this function finish.
209
+ setTimeout(function(){do_search();}, 5);
210
+ }
211
+ break;
212
+
213
+ case KEY.TAB:
214
+ case KEY.ENTER:
215
+ case KEY.NUMPAD_ENTER:
216
+ case KEY.COMMA:
217
+ if(selected_dropdown_item) {
218
+ add_token($(selected_dropdown_item));
219
+ return false;
220
+ }
221
+ break;
222
+
223
+ case KEY.ESCAPE:
224
+ hide_dropdown();
225
+ return true;
226
+
227
+ default:
228
+ if(String.fromCharCode(event.which)) {
229
+ // set a timeout just long enough to let this function finish.
230
+ setTimeout(function(){do_search();}, 5);
231
+ }
232
+ break;
233
+ }
234
+ });
235
+
236
+ // Keep a reference to the original input box
237
+ var hidden_input = $(input)
238
+ .hide()
239
+ .val("")
240
+ .focus(function () {
241
+ input_box.focus();
242
+ })
243
+ .blur(function () {
244
+ input_box.blur();
245
+ });
246
+
247
+ // Keep a reference to the selected token and dropdown item
248
+ var selected_token = null;
249
+ var selected_dropdown_item = null;
250
+
251
+ // The list to store the token items in
252
+ var token_list = $("<ul />")
253
+ .addClass(settings.classes.tokenList)
254
+ .click(function (event) {
255
+ var li = $(event.target).closest("li");
256
+ if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
257
+ toggle_select_token(li);
258
+ } else {
259
+ // Deselect selected token
260
+ if(selected_token) {
261
+ deselect_token($(selected_token), POSITION.END);
262
+ }
263
+
264
+ // Focus input box
265
+ input_box.focus();
266
+ }
267
+ })
268
+ .mouseover(function (event) {
269
+ var li = $(event.target).closest("li");
270
+ if(li && selected_token !== this) {
271
+ li.addClass(settings.classes.highlightedToken);
272
+ }
273
+ })
274
+ .mouseout(function (event) {
275
+ var li = $(event.target).closest("li");
276
+ if(li && selected_token !== this) {
277
+ li.removeClass(settings.classes.highlightedToken);
278
+ }
279
+ })
280
+ .insertBefore(hidden_input);
281
+
282
+ // The token holding the input box
283
+ var input_token = $("<li />")
284
+ .addClass(settings.classes.inputToken)
285
+ .appendTo(token_list)
286
+ .append(input_box);
287
+
288
+ // The list to store the dropdown items in
289
+ var dropdown = $("<div>")
290
+ .addClass(settings.classes.dropdown)
291
+ .appendTo("body")
292
+ .hide();
293
+
294
+ // Magic element to help us resize the text input
295
+ var input_resizer = $("<tester/>")
296
+ .insertAfter(input_box)
297
+ .css({
298
+ position: "absolute",
299
+ top: -9999,
300
+ left: -9999,
301
+ width: "auto",
302
+ fontSize: input_box.css("fontSize"),
303
+ fontFamily: input_box.css("fontFamily"),
304
+ fontWeight: input_box.css("fontWeight"),
305
+ letterSpacing: input_box.css("letterSpacing"),
306
+ whiteSpace: "nowrap"
307
+ });
308
+
309
+ // Pre-populate list if items exist
310
+ hidden_input.val("");
311
+ li_data = settings.prePopulate || hidden_input.data("pre");
312
+ if(li_data && li_data.length) {
313
+ $.each(li_data, function (index, value) {
314
+ insert_token(value.id, value.name);
315
+ });
316
+ }
317
+
318
+
319
+
320
+ //
321
+ // Private functions
322
+ //
323
+
324
+ function resize_input() {
325
+ if(input_val === (input_val = input_box.val())) {return;}
326
+
327
+ // Enter new content into resizer and resize input accordingly
328
+ var escaped = input_val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
329
+ input_resizer.html(escaped);
330
+ input_box.width(input_resizer.width() + 30);
331
+ }
332
+
333
+ function is_printable_character(keycode) {
334
+ return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
335
+ (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
336
+ (keycode >= 186 && keycode <= 192) || // ; = , - . / ^
337
+ (keycode >= 219 && keycode <= 222)); // ( \ ) '
338
+ }
339
+
340
+ // Inner function to a token to the list
341
+ function insert_token(id, value) {
342
+ var this_token = $("<li><p>"+ value +"</p> </li>")
343
+ .addClass(settings.classes.token)
344
+ .insertBefore(input_token);
345
+
346
+ // The 'delete token' button
347
+ $("<span>" + settings.deleteText + "</span>")
348
+ .addClass(settings.classes.tokenDelete)
349
+ .appendTo(this_token)
350
+ .click(function () {
351
+ delete_token($(this).parent());
352
+ return false;
353
+ });
354
+
355
+ // Store data on the token
356
+ var token_data = {"id": id, "name": value};
357
+ $.data(this_token.get(0), "tokeninput", token_data);
358
+
359
+ // Save this token for duplicate checking
360
+ saved_tokens.push(token_data);
361
+
362
+ // Update the hidden input
363
+ var token_ids = $.map(saved_tokens, function (el) {
364
+ return el.id;
365
+ });
366
+ hidden_input.val(token_ids.join(settings.tokenDelimiter));
367
+
368
+ token_count += 1;
369
+
370
+ return this_token;
371
+ }
372
+
373
+ // Add a token to the token list based on user input
374
+ function add_token (item) {
375
+ var li_data = $.data(item.get(0), "tokeninput");
376
+ var callback = settings.onAdd;
377
+
378
+ // See if the token already exists and select it if we don't want duplicates
379
+ if(token_count > 0 && settings.preventDuplicates) {
380
+ var found_existing_token = null;
381
+ token_list.children().each(function () {
382
+ var existing_token = $(this);
383
+ var existing_data = $.data(existing_token.get(0), "tokeninput");
384
+ if(existing_data && existing_data.id === li_data.id) {
385
+ found_existing_token = existing_token;
386
+ return false;
387
+ }
388
+ });
389
+
390
+ if(found_existing_token) {
391
+ select_token(found_existing_token);
392
+ input_token.insertAfter(found_existing_token);
393
+ input_box.focus();
394
+ return;
395
+ }
396
+ }
397
+
398
+ // Insert the new tokens
399
+ insert_token(li_data.id, li_data.name);
400
+
401
+ // Check the token limit
402
+ if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
403
+ input_box.hide();
404
+ hide_dropdown();
405
+ return;
406
+ } else {
407
+ input_box.focus();
408
+ }
409
+
410
+ // Clear input box
411
+ input_box.val("");
412
+
413
+ // Don't show the help dropdown, they've got the idea
414
+ hide_dropdown();
415
+
416
+ // Execute the onAdd callback if defined
417
+ if($.isFunction(callback)) {
418
+ callback(li_data);
419
+ }
420
+ }
421
+
422
+ // Select a token in the token list
423
+ function select_token (token) {
424
+ token.addClass(settings.classes.selectedToken);
425
+ selected_token = token.get(0);
426
+
427
+ // Hide input box
428
+ input_box.val("");
429
+
430
+ // Hide dropdown if it is visible (eg if we clicked to select token)
431
+ hide_dropdown();
432
+ }
433
+
434
+ // Deselect a token in the token list
435
+ function deselect_token (token, position) {
436
+ token.removeClass(settings.classes.selectedToken);
437
+ selected_token = null;
438
+
439
+ if(position === POSITION.BEFORE) {
440
+ input_token.insertBefore(token);
441
+ } else if(position === POSITION.AFTER) {
442
+ input_token.insertAfter(token);
443
+ } else {
444
+ input_token.appendTo(token_list);
445
+ }
446
+
447
+ // Show the input box and give it focus again
448
+ input_box.focus();
449
+ }
450
+
451
+ // Toggle selection of a token in the token list
452
+ function toggle_select_token(token) {
453
+ var previous_selected_token = selected_token;
454
+
455
+ if(selected_token) {
456
+ deselect_token($(selected_token), POSITION.END);
457
+ }
458
+
459
+ if(previous_selected_token === token.get(0)) {
460
+ deselect_token(token, POSITION.END);
461
+ } else {
462
+ select_token(token);
463
+ }
464
+ }
465
+
466
+ // Delete a token from the token list
467
+ function delete_token (token) {
468
+ // Remove the id from the saved list
469
+ var token_data = $.data(token.get(0), "tokeninput");
470
+ var callback = settings.onDelete;
471
+
472
+ // Delete the token
473
+ token.remove();
474
+ selected_token = null;
475
+
476
+ // Show the input box and give it focus again
477
+ input_box.focus();
478
+
479
+ // Remove this token from the saved list
480
+ saved_tokens = $.grep(saved_tokens, function (val) {
481
+ return (val.id !== token_data.id);
482
+ });
483
+
484
+ // Update the hidden input
485
+ var token_ids = $.map(saved_tokens, function (el) {
486
+ return el.id;
487
+ });
488
+ hidden_input.val(token_ids.join(settings.tokenDelimiter));
489
+
490
+ token_count -= 1;
491
+
492
+ if(settings.tokenLimit !== null) {
493
+ input_box
494
+ .show()
495
+ .val("")
496
+ .focus();
497
+ }
498
+
499
+ // Execute the onDelete callback if defined
500
+ if($.isFunction(callback)) {
501
+ callback(token_data);
502
+ }
503
+ }
504
+
505
+ // Hide and clear the results dropdown
506
+ function hide_dropdown () {
507
+ dropdown.hide().empty();
508
+ selected_dropdown_item = null;
509
+ }
510
+
511
+ function show_dropdown() {
512
+ dropdown
513
+ .css({
514
+ position: "absolute",
515
+ top: $(token_list).offset().top + $(token_list).outerHeight(),
516
+ left: $(token_list).offset().left,
517
+ zindex: 999
518
+ })
519
+ .show();
520
+ }
521
+
522
+ function show_dropdown_searching () {
523
+ if(settings.searchingText) {
524
+ dropdown.html("<p>"+settings.searchingText+"</p>");
525
+ show_dropdown();
526
+ }
527
+ }
528
+
529
+ function show_dropdown_hint () {
530
+ if(settings.hintText) {
531
+ dropdown.html("<p>"+settings.hintText+"</p>");
532
+ show_dropdown();
533
+ }
534
+ }
535
+
536
+ // Highlight the query part of the search term
537
+ function highlight_term(value, term) {
538
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
539
+ }
540
+
541
+ // Populate the results dropdown with some results
542
+ function populate_dropdown (query, results) {
543
+ if(results && results.length) {
544
+ dropdown.empty();
545
+ var dropdown_ul = $("<ul>")
546
+ .appendTo(dropdown)
547
+ .mouseover(function (event) {
548
+ select_dropdown_item($(event.target).closest("li"));
549
+ })
550
+ .mousedown(function (event) {
551
+ add_token($(event.target).closest("li"));
552
+ return false;
553
+ })
554
+ .hide();
555
+
556
+ $.each(results, function(index, value) {
557
+ var this_li = $("<li>" + highlight_term(value.name, query) + "</li>")
558
+ .appendTo(dropdown_ul);
559
+
560
+ if(index % 2) {
561
+ this_li.addClass(settings.classes.dropdownItem);
562
+ } else {
563
+ this_li.addClass(settings.classes.dropdownItem2);
564
+ }
565
+
566
+ if(index === 0) {
567
+ select_dropdown_item(this_li);
568
+ }
569
+
570
+ $.data(this_li.get(0), "tokeninput", {"id": value.id, "name": value.name});
571
+ });
572
+
573
+ show_dropdown();
574
+
575
+ if(settings.animateDropdown) {
576
+ dropdown_ul.slideDown("fast");
577
+ } else {
578
+ dropdown_ul.show();
579
+ }
580
+ } else {
581
+ if(settings.noResultsText) {
582
+ dropdown.html("<p>"+settings.noResultsText+"</p>");
583
+ show_dropdown();
584
+ }
585
+ }
586
+ }
587
+
588
+ // Highlight an item in the results dropdown
589
+ function select_dropdown_item (item) {
590
+ if(item) {
591
+ if(selected_dropdown_item) {
592
+ deselect_dropdown_item($(selected_dropdown_item));
593
+ }
594
+
595
+ item.addClass(settings.classes.selectedDropdownItem);
596
+ selected_dropdown_item = item.get(0);
597
+ }
598
+ }
599
+
600
+ // Remove highlighting from an item in the results dropdown
601
+ function deselect_dropdown_item (item) {
602
+ item.removeClass(settings.classes.selectedDropdownItem);
603
+ selected_dropdown_item = null;
604
+ }
605
+
606
+ // Do a search and show the "searching" dropdown if the input is longer
607
+ // than settings.minChars
608
+ function do_search() {
609
+ var query = input_box.val().toLowerCase();
610
+
611
+ if(query && query.length) {
612
+ if(selected_token) {
613
+ deselect_token($(selected_token), POSITION.AFTER);
614
+ }
615
+
616
+ if(query.length >= settings.minChars) {
617
+ show_dropdown_searching();
618
+ clearTimeout(timeout);
619
+
620
+ timeout = setTimeout(function(){
621
+ run_search(query);
622
+ }, settings.searchDelay);
623
+ } else {
624
+ hide_dropdown();
625
+ }
626
+ }
627
+ }
628
+
629
+ // Do the actual search
630
+ function run_search(query) {
631
+ var cached_results = cache.get(query);
632
+ if(cached_results) {
633
+ populate_dropdown(query, cached_results);
634
+ } else {
635
+ // Are we doing an ajax search or local data search?
636
+ if(settings.url) {
637
+ // Extract exisiting get params
638
+ var ajax_params = {};
639
+ ajax_params.data = {};
640
+ if(settings.url.indexOf("?") > -1) {
641
+ var parts = settings.url.split("?");
642
+ ajax_params.url = parts[0];
643
+
644
+ var param_array = parts[1].split("&");
645
+ $.each(param_array, function (index, value) {
646
+ var kv = value.split("=");
647
+ ajax_params.data[kv[0]] = kv[1];
648
+ });
649
+ } else {
650
+ ajax_params.url = settings.url;
651
+ }
652
+
653
+ // Prepare the request
654
+ ajax_params.data[settings.queryParam] = query;
655
+ ajax_params.type = settings.method;
656
+ ajax_params.dataType = settings.contentType;
657
+ if(settings.crossDomain) {
658
+ ajax_params.dataType = "jsonp";
659
+ }
660
+
661
+ // Attach the success callback
662
+ ajax_params.success = function(results) {
663
+ if($.isFunction(settings.onResult)) {
664
+ results = settings.onResult.call(this, results);
665
+ }
666
+ cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
667
+
668
+ // only populate the dropdown if the results are associated with the active search query
669
+ if(input_box.val().toLowerCase() === query) {
670
+ populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
671
+ }
672
+ };
673
+
674
+ // Make the request
675
+ $.ajax(ajax_params);
676
+ } else if(settings.local_data) {
677
+ // Do the search through local data
678
+ var results = $.grep(settings.local_data, function (row) {
679
+ return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1;
680
+ });
681
+
682
+ populate_dropdown(query, results);
683
+ }
684
+ }
685
+ }
686
+ };
687
+
688
+ // Really basic cache for the results
689
+ $.TokenList.Cache = function (options) {
690
+ var settings = $.extend({
691
+ max_size: 500
692
+ }, options);
693
+
694
+ var data = {};
695
+ var size = 0;
696
+
697
+ var flush = function () {
698
+ data = {};
699
+ size = 0;
700
+ };
701
+
702
+ this.add = function (query, results) {
703
+ if(size > settings.max_size) {
704
+ flush();
705
+ }
706
+
707
+ if(!data[query]) {
708
+ size += 1;
709
+ }
710
+
711
+ data[query] = results;
712
+ };
713
+
714
+ this.get = function (query) {
715
+ return data[query];
716
+ };
717
+ };
718
+ }(jQuery));