typus 3.0.10 → 3.0.11.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/.gitignore +0 -1
  2. data/Gemfile +9 -5
  3. data/README.md +17 -53
  4. data/app/assets/vendor/typus/javascripts/application.js +2 -0
  5. data/app/assets/vendor/typus/javascripts/jquery.application.js +29 -0
  6. data/app/assets/vendor/typus/javascripts/jquery.rails.autocomplete.js +32 -0
  7. data/app/assets/vendor/typus/javascripts/jquery.rails.js +158 -0
  8. data/app/assets/vendor/typus/javascripts/jquery.searchField.js +91 -0
  9. data/app/assets/vendor/typus/stylesheets/application.css +11 -0
  10. data/app/assets/vendor/typus/stylesheets/reset.css +48 -0
  11. data/app/assets/vendor/typus/stylesheets/screen.css +361 -0
  12. data/app/assets/vendor/typus/vendor/jquery-tokeninput/css/token-input-facebook.css +122 -0
  13. data/app/assets/vendor/typus/vendor/jquery-tokeninput/css/token-input-mac.css +204 -0
  14. data/app/assets/vendor/typus/vendor/jquery-tokeninput/css/token-input.css +120 -0
  15. data/app/assets/vendor/typus/vendor/jquery-tokeninput/js/jquery.tokeninput.js +736 -0
  16. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  17. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  18. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png +0 -0
  19. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  20. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  21. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  22. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  23. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  24. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  25. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-icons_222222_256x240.png +0 -0
  26. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-icons_228ef1_256x240.png +0 -0
  27. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-icons_ef8c08_256x240.png +0 -0
  28. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-icons_ffd27a_256x240.png +0 -0
  29. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/images/ui-icons_ffffff_256x240.png +0 -0
  30. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/css/ui-lightness/jquery-ui-1.8.12.custom.css +578 -0
  31. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/index.html +367 -0
  32. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/js/jquery-1.5.1.min.js +16 -0
  33. data/app/assets/vendor/typus/vendor/jquery-ui-1.8.12.custom/js/jquery-ui-1.8.12.custom.min.js +783 -0
  34. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/blank.gif +0 -0
  35. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_close.png +0 -0
  36. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_loading.png +0 -0
  37. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_nav_left.png +0 -0
  38. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_nav_right.png +0 -0
  39. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_shadow_e.png +0 -0
  40. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_shadow_n.png +0 -0
  41. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_shadow_ne.png +0 -0
  42. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_shadow_nw.png +0 -0
  43. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_shadow_s.png +0 -0
  44. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_shadow_se.png +0 -0
  45. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_shadow_sw.png +0 -0
  46. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_shadow_w.png +0 -0
  47. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_title_left.png +0 -0
  48. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_title_main.png +0 -0
  49. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_title_over.png +0 -0
  50. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancy_title_right.png +0 -0
  51. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancybox-x.png +0 -0
  52. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancybox-y.png +0 -0
  53. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/fancybox.png +0 -0
  54. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/jquery.easing-1.3.pack.js +72 -0
  55. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/jquery.fancybox-1.3.4.css +359 -0
  56. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/jquery.fancybox-1.3.4.js +1156 -0
  57. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/jquery.fancybox-1.3.4.pack.js +46 -0
  58. data/app/assets/vendor/typus/vendor/jquery.fancybox-1.3.4/fancybox/jquery.mousewheel-3.0.4.pack.js +14 -0
  59. data/app/controllers/admin/account_controller.rb +2 -2
  60. data/app/controllers/admin/base_controller.rb +4 -0
  61. data/app/controllers/admin/resources_controller.rb +8 -83
  62. data/app/controllers/admin/session_controller.rb +10 -2
  63. data/app/helpers/admin/file_preview_helper.rb +15 -1
  64. data/app/helpers/admin/filters_helper.rb +4 -4
  65. data/app/helpers/admin/relationships_helper.rb +11 -6
  66. data/app/helpers/admin/resources_helper.rb +4 -1
  67. data/app/helpers/admin/table_helper.rb +6 -3
  68. data/app/views/admin/dashboard/_sidebar.html.erb +1 -3
  69. data/app/views/admin/helpers/table/_table.html.erb +0 -8
  70. data/app/views/admin/resources/index.html.erb +3 -1
  71. data/app/views/admin/shared/_head.html.erb +30 -0
  72. data/app/views/admin/templates/_belongs_to_with_autocomplete.html.erb +23 -4
  73. data/app/views/admin/templates/_relate_form.html.erb +1 -1
  74. data/app/views/admin/templates/_selector.html.erb +5 -1
  75. data/app/views/layouts/admin/base.html.erb +1 -30
  76. data/app/views/layouts/admin/headless.html.erb +1 -30
  77. data/app/views/layouts/admin/session.html.erb +1 -19
  78. data/doc/mongo_db.md +10 -0
  79. data/lib/generators/typus/typus_generator.rb +0 -5
  80. data/lib/support/active_record.rb +8 -0
  81. data/lib/typus.rb +2 -1
  82. data/lib/typus/controller/actions.rb +7 -0
  83. data/lib/typus/controller/acts_as.rb +22 -0
  84. data/lib/typus/controller/associations.rb +66 -0
  85. data/lib/typus/controller/autocomplete.rb +1 -1
  86. data/lib/typus/controller/format.rb +1 -1
  87. data/lib/typus/engine.rb +7 -2
  88. data/lib/typus/orm/active_record/class_methods.rb +1 -1
  89. data/lib/typus/version.rb +1 -1
  90. data/test/app/controllers/admin/assets_controller_test.rb +8 -5
  91. data/test/app/controllers/admin/pages_controller_test.rb +2 -2
  92. data/test/app/controllers/admin/posts_controller_test.rb +27 -2
  93. data/test/app/controllers/admin/views_controller_test.rb +36 -0
  94. data/test/app/helpers/admin/file_preview_helper_test.rb +9 -1
  95. data/test/app/helpers/admin/filters_helper_test.rb +5 -0
  96. data/test/app/helpers/admin/resources_helper_test.rb +2 -2
  97. data/test/app/helpers/admin/table_helper_test.rb +26 -0
  98. data/test/factories.rb +11 -0
  99. data/test/fixtures/rails_app/app/controllers/admin/hits_controller.rb +0 -3
  100. data/test/fixtures/rails_app/app/controllers/admin/views_controller.rb +6 -0
  101. data/test/fixtures/rails_app/app/models/asset.rb +16 -4
  102. data/test/fixtures/rails_app/app/models/site.rb +16 -0
  103. data/test/fixtures/rails_app/app/models/view.rb +2 -0
  104. data/test/fixtures/rails_app/config/typus/crud_extended.yml +2 -2
  105. data/test/fixtures/rails_app/db/schema.rb +15 -0
  106. data/test/lib/support/active_record_test.rb +24 -0
  107. data/test/lib/typus/orm/active_record/class_methods_test.rb +4 -4
  108. data/test/lib/typus_test.rb +1 -1
  109. metadata +68 -190
  110. data/.gemtest +0 -0
  111. data/doc/hudson_setup.md +0 -19
  112. data/lib/generators/typus/assets_generator.rb +0 -19
  113. data/test/fixtures/rails_app/app/views/layouts/admin/headless.html.erb +0 -38
@@ -0,0 +1,204 @@
1
+ /* Example tokeninput style #2: Mac Style */
2
+ fieldset.token-input-mac {
3
+ position: relative;
4
+ padding: 0;
5
+ margin: 5px 0;
6
+ background: #fff;
7
+ width: 400px;
8
+ border: 1px solid #A4BDEC;
9
+ border-radius: 10px;
10
+ -moz-border-radius: 10px;
11
+ -webkit-border-radius: 10px;
12
+ }
13
+
14
+ fieldset.token-input-mac.token-input-dropdown-mac {
15
+ border-radius: 10px 10px 0 0;
16
+ -moz-border-radius: 10px 10px 0 0;
17
+ -webkit-border-radius: 10px 10px 0 0;
18
+ box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25);
19
+ -moz-box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25);
20
+ -webkit-box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25);
21
+ }
22
+
23
+ ul.token-input-list-mac {
24
+ overflow: hidden;
25
+ height: auto !important;
26
+ height: 1%;
27
+ cursor: text;
28
+ font-size: 12px;
29
+ font-family: Verdana;
30
+ min-height: 1px;
31
+ z-index: 999;
32
+ margin: 0;
33
+ padding: 3px;
34
+ background: transparent;
35
+ }
36
+
37
+ ul.token-input-list-mac.error {
38
+ border: 1px solid #C52020;
39
+ }
40
+
41
+ ul.token-input-list-mac li {
42
+ list-style-type: none;
43
+ }
44
+
45
+ li.token-input-token-mac p {
46
+ display: inline;
47
+ padding: 0;
48
+ margin: 0;
49
+ }
50
+
51
+ li.token-input-token-mac span {
52
+ color: #a6b3cf;
53
+ margin-left: 5px;
54
+ font-weight: bold;
55
+ cursor: pointer;
56
+ }
57
+
58
+ /* TOKENS */
59
+
60
+ li.token-input-token-mac {
61
+ font-family: "Lucida Grande", Arial, serif;
62
+ font-size: 9pt;
63
+ line-height: 12pt;
64
+ overflow: hidden;
65
+ height: 16px;
66
+ margin: 3px;
67
+ padding: 0 10px;
68
+ background: none;
69
+ background-color: #dee7f8;
70
+ color: #000;
71
+ cursor: default;
72
+ border: 1px solid #a4bdec;
73
+ border-radius: 15px;
74
+ -moz-border-radius: 15px;
75
+ -webkit-border-radius: 15px;
76
+ float: left;
77
+ }
78
+
79
+ li.token-input-highlighted-token-mac {
80
+ background-color: #bbcef1;
81
+ border: 1px solid #598bec;
82
+ color: #000;
83
+ }
84
+
85
+ li.token-input-selected-token-mac {
86
+ background-color: #598bec;
87
+ border: 1px solid transparent;
88
+ color: #fff;
89
+ }
90
+
91
+ li.token-input-highlighted-token-mac span.token-input-delete-token-mac {
92
+ color: #000;
93
+ }
94
+
95
+ li.token-input-selected-token-mac span.token-input-delete-token-mac {
96
+ color: #fff;
97
+ }
98
+
99
+ li.token-input-input-token-mac {
100
+ border: none;
101
+ background: transparent;
102
+ float: left;
103
+ padding: 0;
104
+ margin: 0;
105
+ }
106
+
107
+ li.token-input-input-token-mac input {
108
+ border: 0;
109
+ width: 100px;
110
+ padding: 3px;
111
+ background-color: transparent;
112
+ margin: 0;
113
+ }
114
+
115
+ div.token-input-dropdown-mac {
116
+ position: absolute;
117
+ border: 1px solid #A4BDEC;
118
+ border-top: none;
119
+ left: -1px;
120
+ right: -1px;
121
+ background-color: #fff;
122
+ overflow: hidden;
123
+ cursor: default;
124
+ font-size: 10pt;
125
+ font-family: "Lucida Grande", Arial, serif;
126
+ padding: 5px;
127
+ border-radius: 0 0 10px 10px;
128
+ -moz-border-radius: 0 0 10px 10px;
129
+ -webkit-border-radius: 0 0 10px 10px;
130
+ box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25);
131
+ -moz-box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25);
132
+ -webkit-box-shadow: 0 5px 20px 0 rgba(0,0,0,0.25);
133
+ clip:rect(0px, 1000px, 1000px, -10px);
134
+ }
135
+
136
+ div.token-input-dropdown-mac p {
137
+ font-size: 8pt;
138
+ margin: 0;
139
+ padding: 0 5px;
140
+ font-style: italic;
141
+ color: #aaa;
142
+ }
143
+
144
+ div.token-input-dropdown-mac h3.token-input-dropdown-category-mac {
145
+ font-family: "Lucida Grande", Arial, serif;
146
+ font-size: 10pt;
147
+ font-weight: bold;
148
+ border: none;
149
+ padding: 0 5px;
150
+ margin: 0;
151
+ }
152
+
153
+ div.token-input-dropdown-mac ul {
154
+ margin: 0;
155
+ padding: 0;
156
+ }
157
+
158
+ div.token-input-dropdown-mac ul li {
159
+ list-style-type: none;
160
+ cursor: pointer;
161
+ background: none;
162
+ background-color: #fff;
163
+ margin: 0;
164
+ padding: 0 0 0 25px;
165
+ }
166
+
167
+ div.token-input-dropdown-mac ul li.token-input-dropdown-item-mac {
168
+ background-color: #fff;
169
+ }
170
+
171
+ div.token-input-dropdown-mac ul li.token-input-dropdown-item-mac.odd {
172
+ background-color: #ECF4F9;
173
+ border-radius: 15px;
174
+ -moz-border-radius: 15px;
175
+ -webkit-border-radius: 15px;
176
+ }
177
+
178
+ div.token-input-dropdown-mac ul li.token-input-dropdown-item-mac span.token-input-dropdown-item-description-mac {
179
+ float: right;
180
+ font-size: 8pt;
181
+ font-style: italic;
182
+ padding: 0 10px 0 0;
183
+ color: #999;
184
+ }
185
+
186
+ div.token-input-dropdown-mac ul li strong {
187
+ font-weight: bold;
188
+ text-decoration: underline;
189
+ font-style: none;
190
+ }
191
+
192
+ div.token-input-dropdown-mac ul li.token-input-selected-dropdown-item-mac,
193
+ div.token-input-dropdown-mac ul li.token-input-selected-dropdown-item-mac.odd {
194
+ background-color: #598bec;
195
+ color: #fff;
196
+ border-radius: 15px;
197
+ -moz-border-radius: 15px;
198
+ -webkit-border-radius: 15px;
199
+ }
200
+
201
+ div.token-input-dropdown-mac ul li.token-input-selected-dropdown-item-mac span.token-input-dropdown-item-description-mac,
202
+ div.token-input-dropdown-mac ul li.token-input-selected-dropdown-item-mac.odd span.token-input-dropdown-item-description-mac {
203
+ color: #fff;
204
+ }
@@ -0,0 +1,120 @@
1
+ /* Example tokeninput style #1: Token vertical list*/
2
+ ul.token-input-list {
3
+ overflow: hidden;
4
+ height: auto !important;
5
+ height: 1%;
6
+ width: 695px;
7
+ border: 1px solid #999;
8
+ cursor: text;
9
+ font-size: 12px;
10
+ font-family: Verdana;
11
+ z-index: 999;
12
+ margin: 0!important;
13
+ padding: 0!important;
14
+ background-color: #fff;
15
+ list-style-type: none;
16
+ clear: left;
17
+ }
18
+
19
+ ul.token-input-list li {
20
+ margin: 0!important;
21
+ padding: 0px 0px 0 5px!important;
22
+ list-style-type: none;
23
+ }
24
+
25
+ ul.token-input-list li input {
26
+ border: 0;
27
+ width: 350px;
28
+ padding: 3px 8px;
29
+ background-color: white;
30
+ -webkit-appearance: caret;
31
+ }
32
+
33
+ li.token-input-token {
34
+ overflow: hidden;
35
+ height: auto !important;
36
+ height: 1%;
37
+ margin: 5px!important;
38
+ padding: 100px 5px!important;
39
+ /* background-color: #F5F5F5; */
40
+ color: #000;
41
+ font-weight: bold;
42
+ cursor: default;
43
+ display: block;
44
+ }
45
+
46
+ li.token-input-token p {
47
+ float: left;
48
+ padding: 0;
49
+ margin: 5px 1px!important;
50
+ font-family: "Lucida Grande", Sans-serif;
51
+ font-size: 20px;
52
+ font-weight: normal;
53
+ color: #000;
54
+ }
55
+
56
+ li.token-input-token span {
57
+ float: right;
58
+ color: #777;
59
+ cursor: pointer;
60
+ font-size: 20px;
61
+ margin: 5px!important;
62
+ }
63
+
64
+ li.token-input-selected-token {
65
+ /* background-color: #08844e; */
66
+ color: #fff;
67
+ }
68
+
69
+ li.token-input-selected-token span {
70
+ /* color: #bbb; */
71
+ }
72
+
73
+ div.token-input-dropdown {
74
+ position: absolute;
75
+ width: 695px;
76
+ background-color: #fff;
77
+ overflow: hidden;
78
+ border-left: 1px solid #ccc;
79
+ border-right: 1px solid #ccc;
80
+ border-bottom: 1px solid #ccc;
81
+ cursor: default;
82
+ z-index: 1;
83
+ font-size: 16px!important;
84
+ }
85
+
86
+ div.token-input-dropdown p {
87
+ margin: 0;
88
+ padding: 5px;
89
+ font-weight: bold;
90
+ color: #777;
91
+ }
92
+
93
+ div.token-input-dropdown ul {
94
+ margin: 0;
95
+ padding: 0;
96
+ }
97
+
98
+ div.token-input-dropdown ul li {
99
+ background-color: #fff;
100
+ padding: 3px;
101
+ list-style-type: none;
102
+ }
103
+
104
+ div.token-input-dropdown ul li.token-input-dropdown-item {
105
+ background-color: #FAFAFA;
106
+ }
107
+
108
+ div.token-input-dropdown ul li.token-input-dropdown-item2 {
109
+ background-color: #FFF;
110
+ }
111
+
112
+ div.token-input-dropdown ul li em {
113
+ font-weight: bold;
114
+ font-style: normal;
115
+ }
116
+
117
+ div.token-input-dropdown ul li.token-input-selected-dropdown-item {
118
+ /* background-color: #d0efa0; */
119
+ }
120
+
@@ -0,0 +1,736 @@
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
+ processPrePopulate: false,
29
+ animateDropdown: true,
30
+ onResult: null,
31
+ onAdd: null,
32
+ onDelete: null
33
+ };
34
+
35
+ // Default classes to use when theming
36
+ var DEFAULT_CLASSES = {
37
+ tokenList: "token-input-list",
38
+ token: "token-input-token",
39
+ tokenDelete: "token-input-delete-token",
40
+ selectedToken: "token-input-selected-token",
41
+ highlightedToken: "token-input-highlighted-token",
42
+ dropdown: "token-input-dropdown",
43
+ dropdownItem: "token-input-dropdown-item",
44
+ dropdownItem2: "token-input-dropdown-item2",
45
+ selectedDropdownItem: "token-input-selected-dropdown-item",
46
+ inputToken: "token-input-input-token"
47
+ };
48
+
49
+ // Input box position "enum"
50
+ var POSITION = {
51
+ BEFORE: 0,
52
+ AFTER: 1,
53
+ END: 2
54
+ };
55
+
56
+ // Keys "enum"
57
+ var KEY = {
58
+ BACKSPACE: 8,
59
+ TAB: 9,
60
+ ENTER: 13,
61
+ ESCAPE: 27,
62
+ SPACE: 32,
63
+ PAGE_UP: 33,
64
+ PAGE_DOWN: 34,
65
+ END: 35,
66
+ HOME: 36,
67
+ LEFT: 37,
68
+ UP: 38,
69
+ RIGHT: 39,
70
+ DOWN: 40,
71
+ NUMPAD_ENTER: 108,
72
+ COMMA: 188
73
+ };
74
+
75
+
76
+ // Expose the .tokenInput function to jQuery as a plugin
77
+ $.fn.tokenInput = function (url_or_data, options) {
78
+ var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
79
+
80
+ return this.each(function () {
81
+ new $.TokenList(this, url_or_data, settings);
82
+ });
83
+ };
84
+
85
+
86
+ // TokenList class for each input
87
+ $.TokenList = function (input, url_or_data, settings) {
88
+ //
89
+ // Initialization
90
+ //
91
+
92
+ // Configure the data source
93
+ if(typeof(url_or_data) === "string") {
94
+ // Set the url to query against
95
+ settings.url = url_or_data;
96
+
97
+ // Make a smart guess about cross-domain if it wasn't explicitly specified
98
+ if(settings.crossDomain === undefined) {
99
+ if(settings.url.indexOf("://") === -1) {
100
+ settings.crossDomain = false;
101
+ } else {
102
+ settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]);
103
+ }
104
+ }
105
+ } else if(typeof(url_or_data) === "object") {
106
+ // Set the local data to search through
107
+ settings.local_data = url_or_data;
108
+ }
109
+
110
+ // Build class names
111
+ if(settings.classes) {
112
+ // Use custom class names
113
+ settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
114
+ } else if(settings.theme) {
115
+ // Use theme-suffixed default class names
116
+ settings.classes = {};
117
+ $.each(DEFAULT_CLASSES, function(key, value) {
118
+ settings.classes[key] = value + "-" + settings.theme;
119
+ });
120
+ } else {
121
+ settings.classes = DEFAULT_CLASSES;
122
+ }
123
+
124
+
125
+ // Save the tokens
126
+ var saved_tokens = [];
127
+
128
+ // Keep track of the number of tokens in the list
129
+ var token_count = 0;
130
+
131
+ // Basic cache to save on db hits
132
+ var cache = new $.TokenList.Cache();
133
+
134
+ // Keep track of the timeout, old vals
135
+ var timeout;
136
+ var input_val;
137
+
138
+ // Create a new text input an attach keyup events
139
+ var input_box = $("<input type=\"text\" autocomplete=\"off\">")
140
+ .css({
141
+ outline: "none"
142
+ })
143
+ .focus(function () {
144
+ if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
145
+ show_dropdown_hint();
146
+ }
147
+ })
148
+ .blur(function () {
149
+ hide_dropdown();
150
+ })
151
+ .bind("keyup keydown blur update", resize_input)
152
+ .keydown(function (event) {
153
+ var previous_token;
154
+ var next_token;
155
+
156
+ switch(event.keyCode) {
157
+ case KEY.LEFT:
158
+ case KEY.RIGHT:
159
+ case KEY.UP:
160
+ case KEY.DOWN:
161
+ if(!$(this).val()) {
162
+ previous_token = input_token.prev();
163
+ next_token = input_token.next();
164
+
165
+ if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
166
+ // Check if there is a previous/next token and it is selected
167
+ if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
168
+ deselect_token($(selected_token), POSITION.BEFORE);
169
+ } else {
170
+ deselect_token($(selected_token), POSITION.AFTER);
171
+ }
172
+ } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
173
+ // We are moving left, select the previous token if it exists
174
+ select_token($(previous_token.get(0)));
175
+ } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
176
+ // We are moving right, select the next token if it exists
177
+ select_token($(next_token.get(0)));
178
+ }
179
+ } else {
180
+ var dropdown_item = null;
181
+
182
+ if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
183
+ dropdown_item = $(selected_dropdown_item).next();
184
+ } else {
185
+ dropdown_item = $(selected_dropdown_item).prev();
186
+ }
187
+
188
+ if(dropdown_item.length) {
189
+ select_dropdown_item(dropdown_item);
190
+ }
191
+ return false;
192
+ }
193
+ break;
194
+
195
+ case KEY.BACKSPACE:
196
+ previous_token = input_token.prev();
197
+
198
+ if(!$(this).val().length) {
199
+ if(selected_token) {
200
+ delete_token($(selected_token));
201
+ } else if(previous_token.length) {
202
+ select_token($(previous_token.get(0)));
203
+ }
204
+
205
+ return false;
206
+ } else if($(this).val().length === 1) {
207
+ hide_dropdown();
208
+ } else {
209
+ // set a timeout just long enough to let this function finish.
210
+ setTimeout(function(){do_search();}, 5);
211
+ }
212
+ break;
213
+
214
+ case KEY.TAB:
215
+ case KEY.ENTER:
216
+ case KEY.NUMPAD_ENTER:
217
+ case KEY.COMMA:
218
+ if(selected_dropdown_item) {
219
+ add_token($(selected_dropdown_item));
220
+ return false;
221
+ }
222
+ break;
223
+
224
+ case KEY.ESCAPE:
225
+ hide_dropdown();
226
+ return true;
227
+
228
+ default:
229
+ if(String.fromCharCode(event.which)) {
230
+ // set a timeout just long enough to let this function finish.
231
+ setTimeout(function(){do_search();}, 5);
232
+ }
233
+ break;
234
+ }
235
+ });
236
+
237
+ // Keep a reference to the original input box
238
+ var hidden_input = $(input)
239
+ .hide()
240
+ .val("")
241
+ .focus(function () {
242
+ input_box.focus();
243
+ })
244
+ .blur(function () {
245
+ input_box.blur();
246
+ });
247
+
248
+ // Keep a reference to the selected token and dropdown item
249
+ var selected_token = null;
250
+ var selected_token_index = 0;
251
+ var selected_dropdown_item = null;
252
+
253
+ // The list to store the token items in
254
+ var token_list = $("<ul />")
255
+ .addClass(settings.classes.tokenList)
256
+ .click(function (event) {
257
+ var li = $(event.target).closest("li");
258
+ if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
259
+ toggle_select_token(li);
260
+ } else {
261
+ // Deselect selected token
262
+ if(selected_token) {
263
+ deselect_token($(selected_token), POSITION.END);
264
+ }
265
+
266
+ // Focus input box
267
+ input_box.focus();
268
+ }
269
+ })
270
+ .mouseover(function (event) {
271
+ var li = $(event.target).closest("li");
272
+ if(li && selected_token !== this) {
273
+ li.addClass(settings.classes.highlightedToken);
274
+ }
275
+ })
276
+ .mouseout(function (event) {
277
+ var li = $(event.target).closest("li");
278
+ if(li && selected_token !== this) {
279
+ li.removeClass(settings.classes.highlightedToken);
280
+ }
281
+ })
282
+ .insertBefore(hidden_input);
283
+
284
+ // The token holding the input box
285
+ var input_token = $("<li />")
286
+ .addClass(settings.classes.inputToken)
287
+ .appendTo(token_list)
288
+ .append(input_box);
289
+
290
+ // The list to store the dropdown items in
291
+ var dropdown = $("<div>")
292
+ .addClass(settings.classes.dropdown)
293
+ .appendTo("body")
294
+ .hide();
295
+
296
+ // Magic element to help us resize the text input
297
+ var input_resizer = $("<tester/>")
298
+ .insertAfter(input_box)
299
+ .css({
300
+ position: "absolute",
301
+ top: -9999,
302
+ left: -9999,
303
+ width: "auto",
304
+ fontSize: input_box.css("fontSize"),
305
+ fontFamily: input_box.css("fontFamily"),
306
+ fontWeight: input_box.css("fontWeight"),
307
+ letterSpacing: input_box.css("letterSpacing"),
308
+ whiteSpace: "nowrap"
309
+ });
310
+
311
+ // Pre-populate list if items exist
312
+ hidden_input.val("");
313
+ var li_data = settings.prePopulate || hidden_input.data("pre");
314
+ if(settings.processPrePopulate && $.isFunction(settings.onResult)) {
315
+ li_data = settings.onResult.call(hidden_input, li_data);
316
+ }
317
+ if(li_data && li_data.length) {
318
+ $.each(li_data, function (index, value) {
319
+ insert_token(value.id, value.name);
320
+ });
321
+ }
322
+
323
+ //
324
+ // Private functions
325
+ //
326
+
327
+ function resize_input() {
328
+ if(input_val === (input_val = input_box.val())) {return;}
329
+
330
+ // Enter new content into resizer and resize input accordingly
331
+ var escaped = input_val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
332
+ input_resizer.html(escaped);
333
+ input_box.width(input_resizer.width() + 30);
334
+ }
335
+
336
+ function is_printable_character(keycode) {
337
+ return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
338
+ (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
339
+ (keycode >= 186 && keycode <= 192) || // ; = , - . / ^
340
+ (keycode >= 219 && keycode <= 222)); // ( \ ) '
341
+ }
342
+
343
+ // Inner function to a token to the list
344
+ function insert_token(id, value) {
345
+ var this_token = $("<li><p>"+ value +"</p></li>")
346
+ .addClass(settings.classes.token)
347
+ .insertBefore(input_token);
348
+
349
+ // The 'delete token' button
350
+ $("<span>" + settings.deleteText + "</span>")
351
+ .addClass(settings.classes.tokenDelete)
352
+ .appendTo(this_token)
353
+ .click(function () {
354
+ delete_token($(this).parent());
355
+ return false;
356
+ });
357
+
358
+ // Store data on the token
359
+ var token_data = {"id": id, "name": value};
360
+ $.data(this_token.get(0), "tokeninput", token_data);
361
+
362
+ // Save this token for duplicate checking
363
+ saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
364
+ selected_token_index++;
365
+
366
+ // Update the hidden input
367
+ var token_ids = $.map(saved_tokens, function (el) {
368
+ return el.id;
369
+ });
370
+ hidden_input.val(token_ids.join(settings.tokenDelimiter));
371
+
372
+ token_count += 1;
373
+
374
+ // Check the token limit
375
+ if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
376
+ input_box.hide();
377
+ hide_dropdown();
378
+ }
379
+
380
+ return this_token;
381
+ }
382
+
383
+ // Add a token to the token list based on user input
384
+ function add_token (item) {
385
+ var li_data = $.data(item.get(0), "tokeninput");
386
+ var callback = settings.onAdd;
387
+
388
+ // See if the token already exists and select it if we don't want duplicates
389
+ if(token_count > 0 && settings.preventDuplicates) {
390
+ var found_existing_token = null;
391
+ token_list.children().each(function () {
392
+ var existing_token = $(this);
393
+ var existing_data = $.data(existing_token.get(0), "tokeninput");
394
+ if(existing_data && existing_data.id === li_data.id) {
395
+ found_existing_token = existing_token;
396
+ return false;
397
+ }
398
+ });
399
+
400
+ if(found_existing_token) {
401
+ select_token(found_existing_token);
402
+ input_token.insertAfter(found_existing_token);
403
+ input_box.focus();
404
+ return;
405
+ }
406
+ }
407
+
408
+ // Insert the new tokens
409
+ insert_token(li_data.id, li_data.name);
410
+
411
+ // Check the token limit
412
+ if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
413
+ input_box.hide();
414
+ hide_dropdown();
415
+ } else {
416
+ input_box.focus();
417
+
418
+ // Clear input box
419
+ input_box.val("");
420
+
421
+ // Don't show the help dropdown, they've got the idea
422
+ hide_dropdown();
423
+ }
424
+
425
+ // Execute the onAdd callback if defined
426
+ if($.isFunction(callback)) {
427
+ callback.call(hidden_input,li_data);
428
+ }
429
+ }
430
+
431
+ // Select a token in the token list
432
+ function select_token (token) {
433
+ token.addClass(settings.classes.selectedToken);
434
+ selected_token = token.get(0);
435
+
436
+ // Hide input box
437
+ input_box.val("");
438
+
439
+ // Hide dropdown if it is visible (eg if we clicked to select token)
440
+ hide_dropdown();
441
+ }
442
+
443
+ // Deselect a token in the token list
444
+ function deselect_token (token, position) {
445
+ token.removeClass(settings.classes.selectedToken);
446
+ selected_token = null;
447
+
448
+ if(position === POSITION.BEFORE) {
449
+ input_token.insertBefore(token);
450
+ selected_token_index--;
451
+ } else if(position === POSITION.AFTER) {
452
+ input_token.insertAfter(token);
453
+ selected_token_index++;
454
+ } else {
455
+ input_token.appendTo(token_list);
456
+ selected_token_index = token_count;
457
+ }
458
+
459
+ // Show the input box and give it focus again
460
+ input_box.focus();
461
+ }
462
+
463
+ // Toggle selection of a token in the token list
464
+ function toggle_select_token(token) {
465
+ var previous_selected_token = selected_token;
466
+
467
+ if(selected_token) {
468
+ deselect_token($(selected_token), POSITION.END);
469
+ }
470
+
471
+ if(previous_selected_token === token.get(0)) {
472
+ deselect_token(token, POSITION.END);
473
+ } else {
474
+ select_token(token);
475
+ }
476
+ }
477
+
478
+ // Delete a token from the token list
479
+ function delete_token (token) {
480
+ // Remove the id from the saved list
481
+ var token_data = $.data(token.get(0), "tokeninput");
482
+ var callback = settings.onDelete;
483
+
484
+ var index = token.prevAll().length;
485
+ if(index > selected_token_index) index--;
486
+
487
+ // Delete the token
488
+ token.remove();
489
+ selected_token = null;
490
+
491
+ // Show the input box and give it focus again
492
+ input_box.focus();
493
+
494
+ // Remove this token from the saved list
495
+ saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
496
+ if(index < selected_token_index) selected_token_index--;
497
+
498
+ // Update the hidden input
499
+ var token_ids = $.map(saved_tokens, function (el) {
500
+ return el.id;
501
+ });
502
+ hidden_input.val(token_ids.join(settings.tokenDelimiter));
503
+
504
+ token_count -= 1;
505
+
506
+ if(settings.tokenLimit !== null) {
507
+ input_box
508
+ .show()
509
+ .val("")
510
+ .focus();
511
+ }
512
+
513
+ // Execute the onDelete callback if defined
514
+ if($.isFunction(callback)) {
515
+ callback.call(hidden_input,token_data);
516
+ }
517
+ }
518
+
519
+ // Hide and clear the results dropdown
520
+ function hide_dropdown () {
521
+ dropdown.hide().empty();
522
+ selected_dropdown_item = null;
523
+ }
524
+
525
+ function show_dropdown() {
526
+ dropdown
527
+ .css({
528
+ position: "absolute",
529
+ top: $(token_list).offset().top + $(token_list).outerHeight(),
530
+ left: $(token_list).offset().left,
531
+ zindex: 999
532
+ })
533
+ .show();
534
+ }
535
+
536
+ function show_dropdown_searching () {
537
+ if(settings.searchingText) {
538
+ dropdown.html("<p>"+settings.searchingText+"</p>");
539
+ show_dropdown();
540
+ }
541
+ }
542
+
543
+ function show_dropdown_hint () {
544
+ if(settings.hintText) {
545
+ dropdown.html("<p>"+settings.hintText+"</p>");
546
+ show_dropdown();
547
+ }
548
+ }
549
+
550
+ // Highlight the query part of the search term
551
+ function highlight_term(value, term) {
552
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
553
+ }
554
+
555
+ // Populate the results dropdown with some results
556
+ function populate_dropdown (query, results) {
557
+ if(results && results.length) {
558
+ dropdown.empty();
559
+ var dropdown_ul = $("<ul>")
560
+ .appendTo(dropdown)
561
+ .mouseover(function (event) {
562
+ select_dropdown_item($(event.target).closest("li"));
563
+ })
564
+ .mousedown(function (event) {
565
+ add_token($(event.target).closest("li"));
566
+ return false;
567
+ })
568
+ .hide();
569
+
570
+ $.each(results, function(index, value) {
571
+ var this_li = $("<li>" + highlight_term(value.name, query) + "</li>")
572
+ .appendTo(dropdown_ul);
573
+
574
+ if(index % 2) {
575
+ this_li.addClass(settings.classes.dropdownItem);
576
+ } else {
577
+ this_li.addClass(settings.classes.dropdownItem2);
578
+ }
579
+
580
+ if(index === 0) {
581
+ select_dropdown_item(this_li);
582
+ }
583
+
584
+ $.data(this_li.get(0), "tokeninput", {"id": value.id, "name": value.name});
585
+ });
586
+
587
+ show_dropdown();
588
+
589
+ if(settings.animateDropdown) {
590
+ dropdown_ul.slideDown("fast");
591
+ } else {
592
+ dropdown_ul.show();
593
+ }
594
+ } else {
595
+ if(settings.noResultsText) {
596
+ dropdown.html("<p>"+settings.noResultsText+"</p>");
597
+ show_dropdown();
598
+ }
599
+ }
600
+ }
601
+
602
+ // Highlight an item in the results dropdown
603
+ function select_dropdown_item (item) {
604
+ if(item) {
605
+ if(selected_dropdown_item) {
606
+ deselect_dropdown_item($(selected_dropdown_item));
607
+ }
608
+
609
+ item.addClass(settings.classes.selectedDropdownItem);
610
+ selected_dropdown_item = item.get(0);
611
+ }
612
+ }
613
+
614
+ // Remove highlighting from an item in the results dropdown
615
+ function deselect_dropdown_item (item) {
616
+ item.removeClass(settings.classes.selectedDropdownItem);
617
+ selected_dropdown_item = null;
618
+ }
619
+
620
+ // Do a search and show the "searching" dropdown if the input is longer
621
+ // than settings.minChars
622
+ function do_search() {
623
+ var query = input_box.val().toLowerCase();
624
+
625
+ if(query && query.length) {
626
+ if(selected_token) {
627
+ deselect_token($(selected_token), POSITION.AFTER);
628
+ }
629
+
630
+ if(query.length >= settings.minChars) {
631
+ show_dropdown_searching();
632
+ clearTimeout(timeout);
633
+
634
+ timeout = setTimeout(function(){
635
+ run_search(query);
636
+ }, settings.searchDelay);
637
+ } else {
638
+ hide_dropdown();
639
+ }
640
+ }
641
+ }
642
+
643
+ // Do the actual search
644
+ function run_search(query) {
645
+ var cached_results = cache.get(query);
646
+ if(cached_results) {
647
+ populate_dropdown(query, cached_results);
648
+ } else {
649
+ // Are we doing an ajax search or local data search?
650
+ if(settings.url) {
651
+ // Extract exisiting get params
652
+ var ajax_params = {};
653
+ ajax_params.data = {};
654
+ if(settings.url.indexOf("?") > -1) {
655
+ var parts = settings.url.split("?");
656
+ ajax_params.url = parts[0];
657
+
658
+ var param_array = parts[1].split("&");
659
+ $.each(param_array, function (index, value) {
660
+ var kv = value.split("=");
661
+ ajax_params.data[kv[0]] = kv[1];
662
+ });
663
+ } else {
664
+ ajax_params.url = settings.url;
665
+ }
666
+
667
+ // Prepare the request
668
+ ajax_params.data[settings.queryParam] = query;
669
+ ajax_params.type = settings.method;
670
+ ajax_params.dataType = settings.contentType;
671
+ if(settings.crossDomain) {
672
+ ajax_params.dataType = "jsonp";
673
+ }
674
+
675
+ // Attach the success callback
676
+ ajax_params.success = function(results) {
677
+ if($.isFunction(settings.onResult)) {
678
+ results = settings.onResult.call(hidden_input, results);
679
+ }
680
+ cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
681
+
682
+ // only populate the dropdown if the results are associated with the active search query
683
+ if(input_box.val().toLowerCase() === query) {
684
+ populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
685
+ }
686
+ };
687
+
688
+ // Make the request
689
+ $.ajax(ajax_params);
690
+ } else if(settings.local_data) {
691
+ // Do the search through local data
692
+ var results = $.grep(settings.local_data, function (row) {
693
+ return row.name.toLowerCase().indexOf(query.toLowerCase()) > -1;
694
+ });
695
+
696
+ if($.isFunction(settings.onResult)) {
697
+ results = settings.onResult.call(hidden_input, results);
698
+ }
699
+ cache.add(query, results);
700
+ populate_dropdown(query, results);
701
+ }
702
+ }
703
+ }
704
+ };
705
+
706
+ // Really basic cache for the results
707
+ $.TokenList.Cache = function (options) {
708
+ var settings = $.extend({
709
+ max_size: 500
710
+ }, options);
711
+
712
+ var data = {};
713
+ var size = 0;
714
+
715
+ var flush = function () {
716
+ data = {};
717
+ size = 0;
718
+ };
719
+
720
+ this.add = function (query, results) {
721
+ if(size > settings.max_size) {
722
+ flush();
723
+ }
724
+
725
+ if(!data[query]) {
726
+ size += 1;
727
+ }
728
+
729
+ data[query] = results;
730
+ };
731
+
732
+ this.get = function (query) {
733
+ return data[query];
734
+ };
735
+ };
736
+ }(jQuery));