has_messages_generators 0.0.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/MIT-LICENSE +20 -0
- data/README.mdown +43 -0
- data/lib/generators/base.rb +179 -0
- data/lib/generators/has_messages/install/USAGE +10 -0
- data/lib/generators/has_messages/install/install_generator.rb +126 -0
- data/lib/generators/has_messages/install/templates/README +26 -0
- data/lib/generators/has_messages/install/templates/assets/javascripts/jquery.tokeninput.js +718 -0
- data/lib/generators/has_messages/install/templates/assets/javascripts/messages.js +19 -0
- data/lib/generators/has_messages/install/templates/assets/stylesheets/messages.css +107 -0
- data/lib/generators/has_messages/install/templates/assets/stylesheets/token-input-facebook.css +122 -0
- data/lib/generators/has_messages/install/templates/controllers/messages_controller.rb +111 -0
- data/lib/generators/has_messages/install/templates/helpers/messages_helper.rb +2 -0
- data/lib/generators/has_messages/install/templates/lib/has_messages.rb +67 -0
- data/lib/generators/has_messages/install/templates/models/create_messages.rb +24 -0
- data/lib/generators/has_messages/install/templates/models/message.rb +67 -0
- data/lib/generators/has_messages/install/templates/views/_head.html.erb +20 -0
- data/lib/generators/has_messages/install/templates/views/_messages.html.erb +56 -0
- data/lib/generators/has_messages/install/templates/views/_tabs_panel.html.erb +11 -0
- data/lib/generators/has_messages/install/templates/views/index.html.erb +10 -0
- data/lib/generators/has_messages/install/templates/views/index.js.erb +1 -0
- data/lib/generators/has_messages/install/templates/views/new.html.erb +26 -0
- data/lib/generators/has_messages/install/templates/views/show.html.erb +35 -0
- metadata +78 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2011 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.mdown
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
## Gem Dependency
|
2
|
+
- `devise` (works with other authentication gems, but needs some modifications)
|
3
|
+
- `jquery-rails`
|
4
|
+
- `kaminari`
|
5
|
+
- `ancestry`
|
6
|
+
|
7
|
+
# Installation Instructions
|
8
|
+
|
9
|
+
This generator will required you to have Authentication gems such as Devise, to use this
|
10
|
+
you must include `devise` and `has_messages` in your Gemfile.
|
11
|
+
|
12
|
+
gem "devise"
|
13
|
+
gem "has_messages"
|
14
|
+
|
15
|
+
After that setup the `devise` properly.
|
16
|
+
|
17
|
+
rails g devise:install
|
18
|
+
rails g devise user # => user model name example.
|
19
|
+
|
20
|
+
After you configure `devise` properly now install `has_messages` with the given USER MODEL NAME
|
21
|
+
|
22
|
+
rails g has_messages:install user
|
23
|
+
|
24
|
+
this will generate all the necessary files and code into your Rails app.
|
25
|
+
|
26
|
+
At the end of the installation just follow the instructions to modify your layout file.
|
27
|
+
|
28
|
+
If you wish to add some links for `devise` and `has_messages` path, just add this code into your layout file.
|
29
|
+
|
30
|
+
<% if user_signed_in? %>
|
31
|
+
<%= link_to "inbox(#{current_user.inbox(:opened => false).count})", messages_path(:inbox), :id => "inbox-link" %> |
|
32
|
+
Signed in as <%= current_user.email %> Not You?
|
33
|
+
<%= link_to 'Sign out', destroy_user_session_path, :method => :delete %>
|
34
|
+
<% else %>
|
35
|
+
<%= link_to 'Sign up', new_user_registration_path %> or <%= link_to 'Sign in', new_user_session_path %>
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
Run the migration and start the app!
|
39
|
+
|
40
|
+
## Found a bug?
|
41
|
+
|
42
|
+
This is stil under development mode, if you are having some problem with `has_messages`,
|
43
|
+
feel free to submit an issue here. http://github.com/fajrif/has_messages/issues
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require 'bundler'
|
3
|
+
require 'bundler/dsl'
|
4
|
+
|
5
|
+
module HasMessages
|
6
|
+
module Generators
|
7
|
+
class Base < Rails::Generators::Base #:nodoc:
|
8
|
+
|
9
|
+
def self.source_root
|
10
|
+
@_has_messages_source_root = File.expand_path(File.join(File.dirname(__FILE__), 'has_messages', generator_name, 'templates'))
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.banner
|
14
|
+
"rails generate has_messages:#{generator_name} #{self.arguments.map{ |a| a.usage }.join(' ')} [options]"
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def root_path(path)
|
20
|
+
File.expand_path(File.join(File.dirname(__FILE__), 'has_messages', path))
|
21
|
+
end
|
22
|
+
|
23
|
+
def destination_path(path)
|
24
|
+
File.join(destination_root, path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def file_exists?(path)
|
28
|
+
File.exist? destination_path(path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def folder_exists?(path)
|
32
|
+
File.directory? path
|
33
|
+
end
|
34
|
+
|
35
|
+
def class_exists?(class_name)
|
36
|
+
klass = Rails.application.class.parent_name.constantize.const_get(class_name)
|
37
|
+
return klass.is_a?(Class)
|
38
|
+
rescue NameError
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
|
42
|
+
def extract(filepath,destinationpath,foldername)
|
43
|
+
begin
|
44
|
+
print_notes("Extracting #{filepath}")
|
45
|
+
system("tar -C '#{destination_path(destinationpath)}' -xzf '#{root_path(filepath)}' #{foldername}/")
|
46
|
+
rescue Exception => e
|
47
|
+
raise e
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def asking(messages,&block)
|
52
|
+
opt = ask("=> #{messages} [yes]")
|
53
|
+
if opt == "yes" || opt.blank?
|
54
|
+
yield
|
55
|
+
end
|
56
|
+
rescue Exception => e
|
57
|
+
raise e
|
58
|
+
end
|
59
|
+
|
60
|
+
def print_notes(message,notes = "notes",color = :yellow)
|
61
|
+
unless message.blank?
|
62
|
+
puts '', '='*80
|
63
|
+
say_status "#{notes}", "#{message}", color
|
64
|
+
puts '='*80, ''; sleep 0.5
|
65
|
+
else
|
66
|
+
puts "\n"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def print_usage
|
71
|
+
self.class.help(Thor::Base.shell.new)
|
72
|
+
exit
|
73
|
+
end
|
74
|
+
|
75
|
+
def install_local_gem(name,version = nil)
|
76
|
+
::Bundler.with_clean_env do
|
77
|
+
if version
|
78
|
+
`gem install #{name} -v=#{version}`
|
79
|
+
else
|
80
|
+
`gem install #{name}`
|
81
|
+
end
|
82
|
+
end
|
83
|
+
$? == 0 ? true : false
|
84
|
+
rescue Exception => e
|
85
|
+
raise e
|
86
|
+
end
|
87
|
+
|
88
|
+
def check_local_gem?(name,version = nil)
|
89
|
+
::Bundler.with_clean_env do
|
90
|
+
if version
|
91
|
+
`gem list #{name} -i -v=#{version}`
|
92
|
+
else
|
93
|
+
`gem list #{name} -i`
|
94
|
+
end
|
95
|
+
end
|
96
|
+
$? == 0 ? true : false
|
97
|
+
rescue Exception => e
|
98
|
+
raise e
|
99
|
+
end
|
100
|
+
|
101
|
+
def refresh_bundle
|
102
|
+
::Bundler.with_clean_env do
|
103
|
+
`bundle`
|
104
|
+
end
|
105
|
+
rescue Exception => e
|
106
|
+
raise e
|
107
|
+
end
|
108
|
+
|
109
|
+
def set_application_config(&block)
|
110
|
+
inject_into_class "config/application.rb", "Application" do
|
111
|
+
yield
|
112
|
+
end
|
113
|
+
rescue Exception => e
|
114
|
+
raise e
|
115
|
+
end
|
116
|
+
|
117
|
+
def must_load_lib_directory
|
118
|
+
set_application_config do
|
119
|
+
' config.autoload_paths += %W(#{config.root}/lib)' + "\n"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def gemfile_included?(name)
|
124
|
+
::Bundler.with_clean_env do
|
125
|
+
`bundle show #{name}`
|
126
|
+
end
|
127
|
+
$?.exitstatus == 0 ? true : false
|
128
|
+
rescue Exception => e
|
129
|
+
raise e
|
130
|
+
end
|
131
|
+
|
132
|
+
def check_required_gems?(*names)
|
133
|
+
names.each do |name|
|
134
|
+
return false unless gemfile_included? name
|
135
|
+
end
|
136
|
+
true
|
137
|
+
rescue Exception => e
|
138
|
+
raise e
|
139
|
+
end
|
140
|
+
|
141
|
+
def rails_3_1?
|
142
|
+
Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR >= 1
|
143
|
+
end
|
144
|
+
|
145
|
+
def copy_asset(source, *args, &block)
|
146
|
+
if rails_3_1?
|
147
|
+
if args.first =~ /^public/
|
148
|
+
args.first.gsub!(/^public/,"app/assets")
|
149
|
+
end
|
150
|
+
if args.first.include?("javascripts/application.js") or args.first.include?("stylesheets/application.css")
|
151
|
+
last_line = IO.readlines(args.first).last
|
152
|
+
content = IO.read(File.expand_path(find_in_source_paths(source.to_s)))
|
153
|
+
content.gsub!(/images/,"assets")
|
154
|
+
inject_into_file args.first, :after => last_line do
|
155
|
+
content
|
156
|
+
end
|
157
|
+
return
|
158
|
+
end
|
159
|
+
end
|
160
|
+
copy_file(source, *args, &block)
|
161
|
+
end
|
162
|
+
|
163
|
+
def remove_asset(path, config={})
|
164
|
+
if rails_3_1?
|
165
|
+
if path =~ /^public/
|
166
|
+
path.gsub!(/^public/,"app/assets")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
remove_asset(path, config)
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# => set callback on when calling +gem+
|
177
|
+
set_trace_func proc { |event, file, line, id, binding, classname|
|
178
|
+
::Bundler.with_clean_env { `bundle` } if classname == Rails::Generators::Actions && id == :gem && event == 'return'
|
179
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Description:
|
2
|
+
The has_messages generator creates a basic messaging system between user in your app.
|
3
|
+
|
4
|
+
To install all the files into your app:
|
5
|
+
|
6
|
+
rails g has_messages:install user
|
7
|
+
|
8
|
+
or if you want to completely remove the files from your directory
|
9
|
+
|
10
|
+
rails g has_messages:install user --destroy
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'generators/base'
|
2
|
+
require 'rails/generators/active_record'
|
3
|
+
require 'rails/generators/migration'
|
4
|
+
require 'rails/generators/generated_attribute'
|
5
|
+
|
6
|
+
module HasMessages
|
7
|
+
module Generators
|
8
|
+
class InstallGenerator < Base
|
9
|
+
include Rails::Generators::Migration
|
10
|
+
|
11
|
+
argument :arg, :type => :string, :required => true, :banner => 'USER MODEL NAME'
|
12
|
+
class_option :destroy, :desc => 'Destroy all `has_messages` files', :type => :boolean, :default => false
|
13
|
+
|
14
|
+
def generate_has_messages
|
15
|
+
@model_path = "app/models/#{arg}.rb"
|
16
|
+
@class_name = arg.classify
|
17
|
+
if file_exists?(@model_path)
|
18
|
+
if class_exists?(@class_name)
|
19
|
+
if options.destroy?
|
20
|
+
destroy_has_messages
|
21
|
+
else
|
22
|
+
install_required_gem
|
23
|
+
must_load_lib_directory unless rails_3_1?
|
24
|
+
copy_migrations
|
25
|
+
copy_models_and_inject_code_into_user_model
|
26
|
+
copy_controller_and_helper
|
27
|
+
copy_views
|
28
|
+
copy_assets
|
29
|
+
add_routes
|
30
|
+
readme "README"
|
31
|
+
end
|
32
|
+
else
|
33
|
+
raise "#{@class_name} class are not exists!"
|
34
|
+
end
|
35
|
+
else
|
36
|
+
raise "#{@model_path} are not exists in your current directory!"
|
37
|
+
end
|
38
|
+
rescue Exception => e
|
39
|
+
print_notes(e.message,"error",:red)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def install_required_gem
|
45
|
+
gem "jquery-rails"
|
46
|
+
generate("jquery:install") unless rails_3_1?
|
47
|
+
gem "kaminari"
|
48
|
+
gem "ancestry"
|
49
|
+
end
|
50
|
+
|
51
|
+
def copy_migrations
|
52
|
+
migration_template "models/create_messages.rb", "db/migrate/create_messages.rb"
|
53
|
+
end
|
54
|
+
|
55
|
+
def copy_models_and_inject_code_into_user_model
|
56
|
+
template "models/message.rb", "app/models/message.rb"
|
57
|
+
copy_file "lib/has_messages.rb", "lib/has_messages.rb"
|
58
|
+
|
59
|
+
inject_into_class @model_path, @class_name do
|
60
|
+
"\n has_many :messages" +
|
61
|
+
"\n include HasMessages\n"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def copy_controller_and_helper
|
66
|
+
template 'controllers/messages_controller.rb', 'app/controllers/messages_controller.rb'
|
67
|
+
copy_file 'helpers/messages_helper.rb', 'app/helpers/messages_helper.rb'
|
68
|
+
end
|
69
|
+
|
70
|
+
def copy_views
|
71
|
+
copy_file "views/_head.html.erb", "app/views/messages/_head.html.erb"
|
72
|
+
copy_file "views/_messages.html.erb", "app/views/messages/_messages.html.erb"
|
73
|
+
copy_file "views/_tabs_panel.html.erb", "app/views/messages/_tabs_panel.html.erb"
|
74
|
+
copy_file "views/index.html.erb", "app/views/messages/index.html.erb"
|
75
|
+
copy_file "views/index.js.erb", "app/views/messages/index.js.erb"
|
76
|
+
copy_file "views/new.html.erb", "app/views/messages/new.html.erb"
|
77
|
+
copy_file "views/show.html.erb", "app/views/messages/show.html.erb"
|
78
|
+
end
|
79
|
+
|
80
|
+
def copy_assets
|
81
|
+
copy_asset 'assets/stylesheets/messages.css', 'public/stylesheets/messages.css'
|
82
|
+
copy_asset 'assets/stylesheets/token-input-facebook.css', 'public/stylesheets/token-input-facebook.css'
|
83
|
+
copy_asset 'assets/javascripts/messages.js', 'public/javascripts/messages.js'
|
84
|
+
copy_asset 'assets/javascripts/jquery.tokeninput.js', 'public/javascripts/jquery.tokeninput.js'
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_routes
|
88
|
+
inject_into_file "config/routes.rb", :after => "Application.routes.draw do" do
|
89
|
+
"\n\n\t resources :messages, :only => [:new, :create] do" +
|
90
|
+
"\n\t collection do" +
|
91
|
+
"\n\t get 'token' => 'messages#token', :as => 'token'" +
|
92
|
+
"\n\t post 'empty/:messagebox' => 'messages#empty', :as => 'empty'" +
|
93
|
+
"\n\t put 'update' => 'messages#update'" +
|
94
|
+
"\n\t get ':messagebox/show/:id' => 'messages#show', :as => 'show', :constraints => { :messagebox => /inbox|outbox|trash/ }" +
|
95
|
+
"\n\t get '(/:messagebox)' => 'messages#index', :as => 'box', :constraints => { :messagebox => /inbox|outbox|trash/ }" +
|
96
|
+
"\n\t end" +
|
97
|
+
"\n\t end\n"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def destroy_has_messages
|
102
|
+
asking "Are you sure want to destroy the `has_messages` files?" do
|
103
|
+
remove_file "app/models/message.rb"
|
104
|
+
run('rm db/migrate/*_create_messages.rb')
|
105
|
+
remove_file "app/controllers/messages_controller.rb"
|
106
|
+
remove_file "app/helpers/messages_helper.rb"
|
107
|
+
gsub_file @model_path, /has_many :messages/, ''
|
108
|
+
gsub_file @model_path, /include HasMessages/, ''
|
109
|
+
remove_file "lib/has_messages.rb"
|
110
|
+
remove_dir "app/views/messages"
|
111
|
+
remove_asset 'public/stylesheets/messages.css'
|
112
|
+
remove_asset 'public/stylesheets/token-input-facebook.css'
|
113
|
+
remove_asset 'public/javascripts/messages.js'
|
114
|
+
remove_asset 'public/javascripts/jquery.tokeninput.js'
|
115
|
+
gsub_file 'config/routes.rb', /resources :messages.*:constraints => { :messagebox => \/inbox|outbox|trash\/ }(\s*end){2}/m, ''
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# FIXME: Should be proxied to ActiveRecord::Generators::Base
|
120
|
+
# Implement the required interface for Rails::Generators::Migration.
|
121
|
+
def self.next_migration_number(dirname) #:nodoc:
|
122
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
========================================================================================================================
|
3
|
+
|
4
|
+
Some setup you must do manually if you haven't yet:
|
5
|
+
|
6
|
+
1. Ensure you have set authentication filter in app/controllers/messages_controller.rb.
|
7
|
+
For example if using Devise:
|
8
|
+
|
9
|
+
before_filter :authenticate_user!
|
10
|
+
|
11
|
+
2. Ensure you have content placeholder :head in app/views/layouts/application.html.erb.
|
12
|
+
For example:
|
13
|
+
|
14
|
+
<%= yield(:head) %>
|
15
|
+
|
16
|
+
3. Ensure you have flash messages in app/views/layouts/application.html.erb.
|
17
|
+
For example:
|
18
|
+
|
19
|
+
<p class="notice"><%= notice %></p>
|
20
|
+
<p class="alert"><%= alert %></p>
|
21
|
+
|
22
|
+
4. If you like to put some link to your app/views/layouts/application.html.erb.
|
23
|
+
|
24
|
+
<%= link_to "inbox(#{current_user.inbox(:opened => false).count})", messages_path(:inbox), :id => "inbox-link" %>
|
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: "×",
|
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, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
|
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));
|