flexite 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (170) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +12 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  6. data/app/assets/fonts/glyphicons-halflings-regular.svg +288 -0
  7. data/app/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  8. data/app/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  9. data/app/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
  10. data/app/assets/images/flexite/glyphicons-halflings-white.png +0 -0
  11. data/app/assets/images/flexite/glyphicons-halflings.png +0 -0
  12. data/app/assets/javascripts/flexite/application.js +18 -0
  13. data/app/assets/javascripts/flexite/bootstrap.min.js +7 -0
  14. data/app/assets/javascripts/flexite/common.js.erb +2 -0
  15. data/app/assets/javascripts/flexite/jquery.min.js +2 -0
  16. data/app/assets/javascripts/flexite/tree.js.erb +83 -0
  17. data/app/assets/javascripts/flexite/treeview.js +1335 -0
  18. data/app/assets/stylesheets/flexite/application.css +29 -0
  19. data/app/assets/stylesheets/flexite/bootstrap-treeview.min.css +1 -0
  20. data/app/assets/stylesheets/flexite/bootstrap.min.css +5 -0
  21. data/app/assets/stylesheets/flexite/configs.css +8 -0
  22. data/app/assets/stylesheets/flexite/entries.css +4 -0
  23. data/app/assets/stylesheets/flexite/tree.css +21 -0
  24. data/app/assets/stylesheets/scaffold.css +56 -0
  25. data/app/controllers/flexite/application_controller.rb +25 -0
  26. data/app/controllers/flexite/configs_controller.rb +61 -0
  27. data/app/controllers/flexite/entries_controller.rb +82 -0
  28. data/app/factories/flexite/base_factory.rb +7 -0
  29. data/app/factories/flexite/service_factory.rb +19 -0
  30. data/app/forms/flexite/base_form.rb +24 -0
  31. data/app/forms/flexite/config/form.rb +12 -0
  32. data/app/forms/flexite/entry/array_form.rb +11 -0
  33. data/app/forms/flexite/entry/form.rb +16 -0
  34. data/app/helpers/flexite/application_helper.rb +15 -0
  35. data/app/helpers/flexite/configs_helper.rb +4 -0
  36. data/app/helpers/flexite/entries_helper.rb +46 -0
  37. data/app/models/flexite/arr_entry.rb +35 -0
  38. data/app/models/flexite/bool_entry.rb +19 -0
  39. data/app/models/flexite/config.rb +64 -0
  40. data/app/models/flexite/entry.rb +30 -0
  41. data/app/models/flexite/int_entry.rb +13 -0
  42. data/app/models/flexite/str_entry.rb +2 -0
  43. data/app/models/flexite/sym_entry.rb +17 -0
  44. data/app/services/flexite/action_service/result.rb +44 -0
  45. data/app/services/flexite/action_service.rb +30 -0
  46. data/app/services/flexite/config/create_service.rb +25 -0
  47. data/app/services/flexite/config/update_service.rb +28 -0
  48. data/app/services/flexite/data/hash.rb +17 -0
  49. data/app/services/flexite/data/migrators/yaml.rb +62 -0
  50. data/app/services/flexite/data/new.rb +87 -0
  51. data/app/services/flexite/entry/array_create_service.rb +69 -0
  52. data/app/services/flexite/entry/array_update_service.rb +69 -0
  53. data/app/services/flexite/entry/create_service.rb +31 -0
  54. data/app/services/flexite/entry/destroy_array_entry_service.rb +25 -0
  55. data/app/services/flexite/entry/destroy_service.rb +22 -0
  56. data/app/services/flexite/entry/update_service.rb +26 -0
  57. data/app/simple_form/array_input.rb +13 -0
  58. data/app/views/flexite/application/index.html.haml +14 -0
  59. data/app/views/flexite/configs/_form.html.haml +7 -0
  60. data/app/views/flexite/configs/create.js.haml +3 -0
  61. data/app/views/flexite/configs/edit.js.haml +1 -0
  62. data/app/views/flexite/configs/index.json.erb +10 -0
  63. data/app/views/flexite/configs/new.js.haml +1 -0
  64. data/app/views/flexite/configs/update.js.haml +2 -0
  65. data/app/views/flexite/entries/_form.html.haml +11 -0
  66. data/app/views/flexite/entries/_new_array_entry_form.html.haml +7 -0
  67. data/app/views/flexite/entries/_popup.html.haml +18 -0
  68. data/app/views/flexite/entries/_types_dropdown.html.haml +8 -0
  69. data/app/views/flexite/entries/create.js.haml +4 -0
  70. data/app/views/flexite/entries/destroy.js.haml +4 -0
  71. data/app/views/flexite/entries/destroy_array_entry.js.haml +6 -0
  72. data/app/views/flexite/entries/edit.js.haml +1 -0
  73. data/app/views/flexite/entries/new.js.haml +1 -0
  74. data/app/views/flexite/entries/new_array_entry.js.haml +1 -0
  75. data/app/views/flexite/entries/select_type.js.haml +1 -0
  76. data/app/views/flexite/entries/types/_arr_entry.html.haml +19 -0
  77. data/app/views/flexite/entries/types/_bool_entry.html.haml +4 -0
  78. data/app/views/flexite/entries/types/_int_entry.html.haml +4 -0
  79. data/app/views/flexite/entries/types/_str_entry.html.haml +4 -0
  80. data/app/views/flexite/entries/types/_sym_entry.html.haml +1 -0
  81. data/app/views/flexite/entries/update.js.haml +1 -0
  82. data/app/views/flexite/shared/_messages.html.haml +5 -0
  83. data/app/views/flexite/shared/_show_flash.js.haml +5 -0
  84. data/app/views/layouts/flexite/application.html.haml +11 -0
  85. data/config/initializers/simple_form.rb +143 -0
  86. data/config/initializers/simple_form_bootstrap.rb +43 -0
  87. data/config/locales/simple_form.en.yml +26 -0
  88. data/config/routes.rb +17 -0
  89. data/db/migrate/20180503102555_create_flexite_configs.rb +14 -0
  90. data/db/migrate/20180503103109_create_flexite_entries.rb +14 -0
  91. data/lib/flexite/cached_node.rb +19 -0
  92. data/lib/flexite/configuration.rb +24 -0
  93. data/lib/flexite/engine.rb +18 -0
  94. data/lib/flexite/flexy.rb +13 -0
  95. data/lib/flexite/nodes_hash.rb +5 -0
  96. data/lib/flexite/version.rb +3 -0
  97. data/lib/flexite.rb +44 -0
  98. data/lib/tasks/flexite_tasks.rake +20 -0
  99. data/test/dummy/README.rdoc +261 -0
  100. data/test/dummy/Rakefile +7 -0
  101. data/test/dummy/app/assets/javascripts/application.js +15 -0
  102. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  103. data/test/dummy/app/controllers/application_controller.rb +3 -0
  104. data/test/dummy/app/helpers/application_helper.rb +2 -0
  105. data/test/dummy/config/application.rb +59 -0
  106. data/test/dummy/config/application.yml +599 -0
  107. data/test/dummy/config/boot.rb +10 -0
  108. data/test/dummy/config/database.yml +25 -0
  109. data/test/dummy/config/environment.rb +5 -0
  110. data/test/dummy/config/environments/development.rb +40 -0
  111. data/test/dummy/config/environments/production.rb +67 -0
  112. data/test/dummy/config/environments/test.rb +37 -0
  113. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  114. data/test/dummy/config/initializers/flexite.rb +5 -0
  115. data/test/dummy/config/initializers/inflections.rb +15 -0
  116. data/test/dummy/config/initializers/mime_types.rb +5 -0
  117. data/test/dummy/config/initializers/secret_token.rb +7 -0
  118. data/test/dummy/config/initializers/session_store.rb +8 -0
  119. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  120. data/test/dummy/config/locales/en.yml +5 -0
  121. data/test/dummy/config/routes.rb +3 -0
  122. data/test/dummy/config.ru +4 -0
  123. data/test/dummy/db/development.sqlite3 +0 -0
  124. data/test/dummy/db/schema.rb +39 -0
  125. data/test/dummy/db/seeds.rb +3 -0
  126. data/test/dummy/db/test.sqlite3 +0 -0
  127. data/test/dummy/log/development.log +86588 -0
  128. data/test/dummy/public/404.html +26 -0
  129. data/test/dummy/public/422.html +26 -0
  130. data/test/dummy/public/500.html +25 -0
  131. data/test/dummy/public/favicon.ico +0 -0
  132. data/test/dummy/script/rails +6 -0
  133. data/test/dummy/tmp/cache/assets/C67/060/sprockets%2Ffaf176441f0544dd2b51901280044b40 +0 -0
  134. data/test/dummy/tmp/cache/assets/C9D/530/sprockets%2Fdcd49c063327c12052812f41c20a8e74 +0 -0
  135. data/test/dummy/tmp/cache/assets/CA3/270/sprockets%2F502b740063f8ec15e7e12811da71c772 +0 -0
  136. data/test/dummy/tmp/cache/assets/CDC/060/sprockets%2F8ff1d307d1b36810549d0829722b7aea +0 -0
  137. data/test/dummy/tmp/cache/assets/CDE/120/sprockets%2F5fd8b3fa3724451579552aed373410ce +0 -0
  138. data/test/dummy/tmp/cache/assets/CF8/980/sprockets%2F4e5077b95460dc34d7c9d7d9880e7b47 +0 -0
  139. data/test/dummy/tmp/cache/assets/D00/4C0/sprockets%2F603c6d7b825c2f4a6422b3d4af4e6203 +0 -0
  140. data/test/dummy/tmp/cache/assets/D00/C40/sprockets%2F999847c008fb4ce26fff5607c39d8358 +0 -0
  141. data/test/dummy/tmp/cache/assets/D09/A30/sprockets%2F2a71e20a2f3544acb51956504cd8d8e9 +0 -0
  142. data/test/dummy/tmp/cache/assets/D0D/E60/sprockets%2F90ea9eaa4671ec2d76703bae31972634 +0 -0
  143. data/test/dummy/tmp/cache/assets/D2A/160/sprockets%2Fbe17d4d0be4b381500e2824bbe273d70 +0 -0
  144. data/test/dummy/tmp/cache/assets/D2E/700/sprockets%2Fb21f85e0940bbcb3a8914c9cb0b07218 +0 -0
  145. data/test/dummy/tmp/cache/assets/D39/CD0/sprockets%2F0e8f6981475e49ea9fe14698fa57e4e9 +0 -0
  146. data/test/dummy/tmp/cache/assets/D40/590/sprockets%2F181dd5673b58f1ec4c89e50028bfad60 +0 -0
  147. data/test/dummy/tmp/cache/assets/D46/870/sprockets%2Fd880bdf72c5d0009b88fda8521439dc8 +0 -0
  148. data/test/dummy/tmp/cache/assets/D48/5F0/sprockets%2Fe6a83afb2d92f4692ffb391b5285a518 +0 -0
  149. data/test/dummy/tmp/cache/assets/D4B/A20/sprockets%2Febde89014596e655c35df9c4a01ee636 +0 -0
  150. data/test/dummy/tmp/cache/assets/D54/A50/sprockets%2Ff49839d906f69fd92904ebac07a3a38e +0 -0
  151. data/test/dummy/tmp/cache/assets/D69/B50/sprockets%2F4186e2787004e19ceb0c4afed46cf04b +0 -0
  152. data/test/dummy/tmp/cache/assets/D6D/C20/sprockets%2F112e96fea93f5f1e31d2077f1cdaf283 +0 -0
  153. data/test/dummy/tmp/cache/assets/D7A/8F0/sprockets%2F8eefc5f9824d950317a5c4a2e68b3c4e +0 -0
  154. data/test/dummy/tmp/cache/assets/DB3/360/sprockets%2F24b1f98ad736884b91d6ebeb2fc68ed8 +0 -0
  155. data/test/dummy/tmp/cache/assets/DD2/E90/sprockets%2Ff01f48c96ba588d15b20dd57ed3ccb7f +0 -0
  156. data/test/dummy/tmp/cache/assets/DEE/0C0/sprockets%2Feedcd74856542eedb5ea3ab1b1502ca4 +0 -0
  157. data/test/dummy/tmp/cache/assets/DF1/210/sprockets%2Ff4fe7dba1445ba1b668aaa1bd34ca984 +0 -0
  158. data/test/dummy/tmp/cache/assets/E44/790/sprockets%2Fefb8d8ea9e97da9d675a8bfa1d5de343 +0 -0
  159. data/test/fixtures/flexite/configs.yml +7 -0
  160. data/test/fixtures/flexite/entries.yml +13 -0
  161. data/test/flexite_test.rb +7 -0
  162. data/test/functional/flexite/configs_controller_test.rb +51 -0
  163. data/test/functional/flexite/entries_controller_test.rb +51 -0
  164. data/test/integration/navigation_test.rb +10 -0
  165. data/test/test_helper.rb +15 -0
  166. data/test/unit/flexite/config_test.rb +9 -0
  167. data/test/unit/flexite/entry_test.rb +9 -0
  168. data/test/unit/helpers/flexite/configs_helper_test.rb +6 -0
  169. data/test/unit/helpers/flexite/entries_helper_test.rb +6 -0
  170. metadata +366 -0
@@ -0,0 +1,1335 @@
1
+ /* =========================================================
2
+ * bootstrap-treeview.js v1.2.0
3
+ * =========================================================
4
+ * Copyright 2013 Jonathan Miles
5
+ * Project URL : http://www.jondmiles.com/bootstrap-treeview
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ * ========================================================= */
19
+
20
+ ;(function ($, window, document, undefined) {
21
+
22
+ /*global jQuery, console*/
23
+
24
+ 'use strict';
25
+
26
+ var pluginName = 'treeview';
27
+
28
+ var _default = {};
29
+
30
+ _default.settings = {
31
+
32
+ injectStyle: true,
33
+
34
+ levels: 2,
35
+
36
+ expandIcon: 'glyphicon glyphicon-plus',
37
+ collapseIcon: 'glyphicon glyphicon-minus',
38
+ emptyIcon: 'glyphicon',
39
+ nodeIcon: '',
40
+ selectedIcon: '',
41
+ checkedIcon: 'glyphicon glyphicon-check',
42
+ uncheckedIcon: 'glyphicon glyphicon-unchecked',
43
+
44
+ color: undefined, // '#000000',
45
+ backColor: undefined, // '#FFFFFF',
46
+ borderColor: undefined, // '#dddddd',
47
+ onhoverColor: '#F5F5F5',
48
+ selectedColor: '#FFFFFF',
49
+ selectedBackColor: '#428bca',
50
+ searchResultColor: '#D9534F',
51
+ searchResultBackColor: undefined, //'#FFFFFF',
52
+
53
+ enableLinks: false,
54
+ highlightSelected: true,
55
+ highlightSearchResults: true,
56
+ showBorder: true,
57
+ showIcon: true,
58
+ showCheckbox: false,
59
+ showTags: false,
60
+ multiSelect: false,
61
+
62
+ // Event handlers
63
+ onNodeChecked: undefined,
64
+ onNodeCollapsed: undefined,
65
+ onNodeDisabled: undefined,
66
+ onNodeEnabled: undefined,
67
+ onNodeExpanded: undefined,
68
+ onNodeSelected: undefined,
69
+ onNodeUnchecked: undefined,
70
+ onNodeUnselected: undefined,
71
+ onSearchComplete: undefined,
72
+ onSearchCleared: undefined
73
+ };
74
+
75
+ _default.options = {
76
+ silent: false,
77
+ ignoreChildren: false
78
+ };
79
+
80
+ _default.searchOptions = {
81
+ ignoreCase: true,
82
+ exactMatch: false,
83
+ revealResults: true
84
+ };
85
+
86
+ var Tree = function (element, options) {
87
+
88
+ this.$element = $(element);
89
+ this.elementId = element.id;
90
+ this.styleId = this.elementId + '-style';
91
+
92
+ this.init(options);
93
+
94
+ return {
95
+
96
+ // Options (public access)
97
+ options: this.options,
98
+
99
+ // Initialize / destroy methods
100
+ init: $.proxy(this.init, this),
101
+ remove: $.proxy(this.remove, this),
102
+
103
+ // Get methods
104
+ addNodes: $.proxy(this.addNodes, this),
105
+ editNode: $.proxy(this.editNode, this),
106
+ addToParent: $.proxy(this.addToParent, this),
107
+ removeNodes: $.proxy(this.removeNodes, this),
108
+ removeNode: $.proxy(this.removeNode, this),
109
+ getNode: $.proxy(this.getNode, this),
110
+ getParent: $.proxy(this.getParent, this),
111
+ getSiblings: $.proxy(this.getSiblings, this),
112
+ getSelected: $.proxy(this.getSelected, this),
113
+ getUnselected: $.proxy(this.getUnselected, this),
114
+ getExpanded: $.proxy(this.getExpanded, this),
115
+ getCollapsed: $.proxy(this.getCollapsed, this),
116
+ getChecked: $.proxy(this.getChecked, this),
117
+ getUnchecked: $.proxy(this.getUnchecked, this),
118
+ getDisabled: $.proxy(this.getDisabled, this),
119
+ getEnabled: $.proxy(this.getEnabled, this),
120
+
121
+ // Select methods
122
+ selectNode: $.proxy(this.selectNode, this),
123
+ unselectNode: $.proxy(this.unselectNode, this),
124
+ toggleNodeSelected: $.proxy(this.toggleNodeSelected, this),
125
+
126
+ // Expand / collapse methods
127
+ collapseAll: $.proxy(this.collapseAll, this),
128
+ collapseNode: $.proxy(this.collapseNode, this),
129
+ expandAll: $.proxy(this.expandAll, this),
130
+ expandNode: $.proxy(this.expandNode, this),
131
+ toggleNodeExpanded: $.proxy(this.toggleNodeExpanded, this),
132
+ revealNode: $.proxy(this.revealNode, this),
133
+
134
+ // Expand / collapse methods
135
+ checkAll: $.proxy(this.checkAll, this),
136
+ checkNode: $.proxy(this.checkNode, this),
137
+ uncheckAll: $.proxy(this.uncheckAll, this),
138
+ uncheckNode: $.proxy(this.uncheckNode, this),
139
+ toggleNodeChecked: $.proxy(this.toggleNodeChecked, this),
140
+
141
+ // Disable / enable methods
142
+ disableAll: $.proxy(this.disableAll, this),
143
+ disableNode: $.proxy(this.disableNode, this),
144
+ enableAll: $.proxy(this.enableAll, this),
145
+ enableNode: $.proxy(this.enableNode, this),
146
+ toggleNodeDisabled: $.proxy(this.toggleNodeDisabled, this),
147
+
148
+ // Search methods
149
+ search: $.proxy(this.search, this),
150
+ clearSearch: $.proxy(this.clearSearch, this)
151
+ };
152
+ };
153
+
154
+ Tree.prototype.init = function (options) {
155
+
156
+ this.tree = [];
157
+ this.nodes = [];
158
+
159
+ if (options.data) {
160
+ if (typeof options.data === 'string') {
161
+ options.data = $.parseJSON(options.data);
162
+ }
163
+ this.tree = $.extend(true, [], options.data);
164
+ delete options.data;
165
+ }
166
+ this.options = $.extend({}, _default.settings, options);
167
+
168
+ this.destroy();
169
+ this.subscribeEvents();
170
+ this.setInitialStates({ nodes: this.tree }, 0);
171
+ this.render();
172
+ };
173
+
174
+ Tree.prototype.addNodes = function (nodes, parentId) {
175
+ var parent = this.getNode(parentId);
176
+
177
+ $.each(nodes, function(index, node) {
178
+ parent.nodes.push(node);
179
+ });
180
+
181
+ this.nodes = [];
182
+ this.destroy();
183
+ this.subscribeEvents();
184
+ this.setInitialStates({ nodes: this.tree }, 0);
185
+ this.render();
186
+ };
187
+
188
+ Tree.prototype.removeNodes = function(parentId) {
189
+ var parent = this.getNode(parentId);
190
+
191
+ parent.nodes = [];
192
+ this.nodes = [];
193
+ this.destroy();
194
+ this.subscribeEvents();
195
+ this.setInitialStates({ nodes: this.tree }, 0);
196
+ this.render();
197
+ };
198
+
199
+ Tree.prototype.removeNode = function(node) {
200
+ if (typeof node.parentId !== 'undefined') {
201
+ var parent = this.getNode(node.parentId);
202
+ var pIndex = parent.nodes.indexOf(node);
203
+ parent.nodes.splice(pIndex, 1);
204
+
205
+ if (!parent.nodes.length) {
206
+ parent.nodes = null;
207
+ parent.state.expanded = false;
208
+ }
209
+ } else {
210
+ var tIndex = this.tree.indexOf(node);
211
+ this.tree.splice(tIndex, 1);
212
+ }
213
+
214
+ this.nodes = [];
215
+ this.destroy();
216
+ this.subscribeEvents();
217
+ this.setInitialStates({ nodes: this.tree }, 0);
218
+ this.render();
219
+ };
220
+
221
+ Tree.prototype.remove = function () {
222
+ this.destroy();
223
+ $.removeData(this, pluginName);
224
+ $('#' + this.styleId).remove();
225
+ };
226
+
227
+ Tree.prototype.destroy = function () {
228
+
229
+ if (!this.initialized) return;
230
+
231
+ this.$wrapper.remove();
232
+ this.$wrapper = null;
233
+
234
+ // Switch off events
235
+ this.unsubscribeEvents();
236
+
237
+ // Reset this.initialized flag
238
+ this.initialized = false;
239
+ };
240
+
241
+ Tree.prototype.unsubscribeEvents = function () {
242
+
243
+ this.$element.off('click');
244
+ this.$element.off('nodeChecked');
245
+ this.$element.off('nodeCollapsed');
246
+ this.$element.off('nodeDisabled');
247
+ this.$element.off('nodeEnabled');
248
+ this.$element.off('nodeExpanded');
249
+ this.$element.off('nodeSelected');
250
+ this.$element.off('nodeUnchecked');
251
+ this.$element.off('nodeUnselected');
252
+ this.$element.off('searchComplete');
253
+ this.$element.off('searchCleared');
254
+ };
255
+
256
+ Tree.prototype.subscribeEvents = function () {
257
+
258
+ this.unsubscribeEvents();
259
+
260
+ this.$element.on('click', $.proxy(this.clickHandler, this));
261
+
262
+ if (typeof (this.options.onNodeChecked) === 'function') {
263
+ this.$element.on('nodeChecked', this.options.onNodeChecked);
264
+ }
265
+
266
+ if (typeof (this.options.onNodeCollapsed) === 'function') {
267
+ this.$element.on('nodeCollapsed', this.options.onNodeCollapsed);
268
+ }
269
+
270
+ if (typeof (this.options.onNodeDisabled) === 'function') {
271
+ this.$element.on('nodeDisabled', this.options.onNodeDisabled);
272
+ }
273
+
274
+ if (typeof (this.options.onNodeEnabled) === 'function') {
275
+ this.$element.on('nodeEnabled', this.options.onNodeEnabled);
276
+ }
277
+
278
+ if (typeof (this.options.onNodeExpanded) === 'function') {
279
+ this.$element.on('nodeExpanded', this.options.onNodeExpanded);
280
+ }
281
+
282
+ if (typeof (this.options.onNodeSelected) === 'function') {
283
+ this.$element.on('nodeSelected', this.options.onNodeSelected);
284
+ }
285
+
286
+ if (typeof (this.options.onNodeUnchecked) === 'function') {
287
+ this.$element.on('nodeUnchecked', this.options.onNodeUnchecked);
288
+ }
289
+
290
+ if (typeof (this.options.onNodeUnselected) === 'function') {
291
+ this.$element.on('nodeUnselected', this.options.onNodeUnselected);
292
+ }
293
+
294
+ if (typeof (this.options.onSearchComplete) === 'function') {
295
+ this.$element.on('searchComplete', this.options.onSearchComplete);
296
+ }
297
+
298
+ if (typeof (this.options.onSearchCleared) === 'function') {
299
+ this.$element.on('searchCleared', this.options.onSearchCleared);
300
+ }
301
+ };
302
+
303
+ /*
304
+ Recurse the tree structure and ensure all nodes have
305
+ valid initial states. User defined states will be preserved.
306
+ For performance we also take this opportunity to
307
+ index nodes in a flattened structure
308
+ */
309
+ Tree.prototype.setInitialStates = function (node, level) {
310
+
311
+ if (!node.nodes) return;
312
+ level += 1;
313
+
314
+ var parent = node;
315
+ var _this = this;
316
+ $.each(node.nodes, function checkStates(index, node) {
317
+
318
+ // nodeId : unique, incremental identifier
319
+ node.nodeId = _this.nodes.length;
320
+
321
+ // parentId : transversing up the tree
322
+ node.parentId = parent.nodeId;
323
+
324
+ // if not provided set selectable default value
325
+ if (!node.hasOwnProperty('selectable')) {
326
+ node.selectable = true;
327
+ }
328
+
329
+ // where provided we should preserve states
330
+ node.state = node.state || {};
331
+
332
+ // set checked state; unless set always false
333
+ if (!node.state.hasOwnProperty('checked')) {
334
+ node.state.checked = false;
335
+ }
336
+
337
+ // set enabled state; unless set always false
338
+ if (!node.state.hasOwnProperty('disabled')) {
339
+ node.state.disabled = false;
340
+ }
341
+
342
+ // set expanded state; if not provided based on levels
343
+ if (!node.state.hasOwnProperty('expanded')) {
344
+ if (!node.state.disabled &&
345
+ (level < _this.options.levels) &&
346
+ (node.nodes && node.nodes.length > 0)) {
347
+ node.state.expanded = true;
348
+ }
349
+ else {
350
+ node.state.expanded = false;
351
+ }
352
+ }
353
+
354
+ // set selected state; unless set always false
355
+ if (!node.state.hasOwnProperty('selected')) {
356
+ node.state.selected = false;
357
+ }
358
+
359
+ // index nodes in a flattened structure for use later
360
+ _this.nodes.push(node);
361
+
362
+ // recurse child nodes and transverse the tree
363
+ if (node.nodes) {
364
+ _this.setInitialStates(node, level);
365
+ }
366
+ });
367
+ };
368
+
369
+ Tree.prototype.clickHandler = function (event) {
370
+
371
+ if (!this.options.enableLinks) event.preventDefault();
372
+
373
+ var target = $(event.target);
374
+ var node = this.findNode(target);
375
+ if (!node || node.state.disabled) return;
376
+
377
+ var classList = target.attr('class') ? target.attr('class').split(' ') : [];
378
+ if ((classList.indexOf('expand-icon') !== -1)) {
379
+
380
+ this.toggleExpandedState(node, _default.options);
381
+ this.render();
382
+ }
383
+ else if ((classList.indexOf('check-icon') !== -1)) {
384
+
385
+ this.toggleCheckedState(node, _default.options);
386
+ this.render();
387
+ }
388
+ else {
389
+
390
+ if (node.selectable) {
391
+ this.toggleSelectedState(node, _default.options);
392
+ } else {
393
+ this.toggleExpandedState(node, _default.options);
394
+ }
395
+
396
+ this.render();
397
+ }
398
+ };
399
+
400
+ // Looks up the DOM for the closest parent list item to retrieve the
401
+ // data attribute nodeid, which is used to lookup the node in the flattened structure.
402
+ Tree.prototype.findNode = function (target) {
403
+
404
+ var nodeId = target.closest('li.list-group-item').attr('data-nodeid');
405
+ var node = this.nodes[nodeId];
406
+
407
+ if (!node) {
408
+ console.log('Error: node does not exist');
409
+ }
410
+ return node;
411
+ };
412
+
413
+ Tree.prototype.toggleExpandedState = function (node, options) {
414
+ if (!node) return;
415
+ this.setExpandedState(node, !node.state.expanded, options);
416
+ };
417
+
418
+ Tree.prototype.setExpandedState = function (node, state, options) {
419
+
420
+ if (state === node.state.expanded) return;
421
+
422
+ if (state && node.nodes) {
423
+
424
+ // Expand a node
425
+ node.state.expanded = true;
426
+ if (!options.silent) {
427
+ this.$element.trigger('nodeExpanded', $.extend(true, {}, node));
428
+ }
429
+ }
430
+ else if (!state) {
431
+
432
+ // Collapse a node
433
+ node.state.expanded = false;
434
+ if (!options.silent) {
435
+ this.$element.trigger('nodeCollapsed', $.extend(true, {}, node));
436
+ }
437
+
438
+ // Collapse child nodes
439
+ if (node.nodes && !options.ignoreChildren) {
440
+ $.each(node.nodes, $.proxy(function (index, node) {
441
+ this.setExpandedState(node, false, options);
442
+ }, this));
443
+ }
444
+ }
445
+ };
446
+
447
+ Tree.prototype.toggleSelectedState = function (node, options) {
448
+ if (!node) return;
449
+ this.setSelectedState(node, !node.state.selected, options);
450
+ };
451
+
452
+ Tree.prototype.setSelectedState = function (node, state, options) {
453
+
454
+ if (state === node.state.selected) return;
455
+
456
+ if (state) {
457
+
458
+ // If multiSelect false, unselect previously selected
459
+ if (!this.options.multiSelect) {
460
+ $.each(this.findNodes('true', 'g', 'state.selected'), $.proxy(function (index, node) {
461
+ this.setSelectedState(node, false, options);
462
+ }, this));
463
+ }
464
+
465
+ // Continue selecting node
466
+ node.state.selected = true;
467
+ if (!options.silent) {
468
+ this.$element.trigger('nodeSelected', $.extend(true, {}, node));
469
+ }
470
+ }
471
+ else {
472
+
473
+ // Unselect node
474
+ node.state.selected = false;
475
+ if (!options.silent) {
476
+ this.$element.trigger('nodeUnselected', $.extend(true, {}, node));
477
+ }
478
+ }
479
+ };
480
+
481
+ Tree.prototype.toggleCheckedState = function (node, options) {
482
+ if (!node) return;
483
+ this.setCheckedState(node, !node.state.checked, options);
484
+ };
485
+
486
+ Tree.prototype.setCheckedState = function (node, state, options) {
487
+
488
+ if (state === node.state.checked) return;
489
+
490
+ if (state) {
491
+
492
+ // Check node
493
+ node.state.checked = true;
494
+
495
+ if (!options.silent) {
496
+ this.$element.trigger('nodeChecked', $.extend(true, {}, node));
497
+ }
498
+ }
499
+ else {
500
+
501
+ // Uncheck node
502
+ node.state.checked = false;
503
+ if (!options.silent) {
504
+ this.$element.trigger('nodeUnchecked', $.extend(true, {}, node));
505
+ }
506
+ }
507
+ };
508
+
509
+ Tree.prototype.setDisabledState = function (node, state, options) {
510
+
511
+ if (state === node.state.disabled) return;
512
+
513
+ if (state) {
514
+
515
+ // Disable node
516
+ node.state.disabled = true;
517
+
518
+ // Disable all other states
519
+ this.setExpandedState(node, false, options);
520
+ this.setSelectedState(node, false, options);
521
+ this.setCheckedState(node, false, options);
522
+
523
+ if (!options.silent) {
524
+ this.$element.trigger('nodeDisabled', $.extend(true, {}, node));
525
+ }
526
+ }
527
+ else {
528
+
529
+ // Enabled node
530
+ node.state.disabled = false;
531
+ if (!options.silent) {
532
+ this.$element.trigger('nodeEnabled', $.extend(true, {}, node));
533
+ }
534
+ }
535
+ };
536
+
537
+ Tree.prototype.render = function () {
538
+
539
+ if (!this.initialized) {
540
+
541
+ // Setup first time only components
542
+ this.$element.addClass(pluginName);
543
+ this.$wrapper = $(this.template.list);
544
+
545
+ this.injectStyle();
546
+
547
+ this.initialized = true;
548
+ }
549
+
550
+ this.$element.empty().append(this.$wrapper.empty());
551
+
552
+ // Build tree
553
+ this.buildTree(this.tree, 0);
554
+ };
555
+
556
+ // Starting from the root node, and recursing down the
557
+ // structure we build the tree one node at a time
558
+ Tree.prototype.buildTree = function (nodes, level) {
559
+
560
+ if (!nodes) return;
561
+ level += 1;
562
+
563
+ var _this = this;
564
+ $.each(nodes, function addNodes(id, node) {
565
+
566
+ var treeItem = $(_this.template.item)
567
+ .addClass('node-' + _this.elementId)
568
+ .addClass(node.state.checked ? 'node-checked' : '')
569
+ .addClass(node.state.disabled ? 'node-disabled': '')
570
+ .addClass(node.state.selected ? 'node-selected' : '')
571
+ .addClass(node.searchResult ? 'search-result' : '')
572
+ .attr('data-nodeid', node.nodeId)
573
+ .attr('style', _this.buildStyleOverride(node));
574
+
575
+ // Add indent/spacer to mimic tree structure
576
+ for (var i = 0; i < (level - 1); i++) {
577
+ treeItem.append(_this.template.indent);
578
+ }
579
+
580
+ // Add expand, collapse or empty spacer icons
581
+ var classList = [];
582
+ if (node.nodes) {
583
+ classList.push('expand-icon');
584
+ if (node.state.expanded) {
585
+ classList.push(_this.options.collapseIcon);
586
+ }
587
+ else {
588
+ classList.push(_this.options.expandIcon);
589
+ }
590
+ }
591
+ else {
592
+ classList.push(_this.options.emptyIcon);
593
+ }
594
+
595
+ treeItem
596
+ .append($(_this.template.icon)
597
+ .addClass(classList.join(' '))
598
+ );
599
+
600
+
601
+ // Add node icon
602
+ if (_this.options.showIcon) {
603
+
604
+ var classList = ['node-icon'];
605
+
606
+ classList.push(node.icon || _this.options.nodeIcon);
607
+ if (node.state.selected) {
608
+ classList.pop();
609
+ classList.push(node.selectedIcon || _this.options.selectedIcon ||
610
+ node.icon || _this.options.nodeIcon);
611
+ }
612
+
613
+ treeItem
614
+ .append($(_this.template.icon)
615
+ .addClass(classList.join(' '))
616
+ );
617
+ }
618
+
619
+ // Add check / unchecked icon
620
+ if (_this.options.showCheckbox) {
621
+
622
+ var classList = ['check-icon'];
623
+ if (node.state.checked) {
624
+ classList.push(_this.options.checkedIcon);
625
+ }
626
+ else {
627
+ classList.push(_this.options.uncheckedIcon);
628
+ }
629
+
630
+ treeItem
631
+ .append($(_this.template.icon)
632
+ .addClass(classList.join(' '))
633
+ );
634
+ }
635
+
636
+ // Add text
637
+ if (_this.options.enableLinks) {
638
+ // Add hyperlink
639
+ treeItem
640
+ .append($(_this.template.link)
641
+ .attr('href', node.href)
642
+ .append(node.text)
643
+ );
644
+ }
645
+ else {
646
+ // otherwise just text
647
+ treeItem
648
+ .append(node.text);
649
+ }
650
+
651
+ // Add tags as badges
652
+ if (_this.options.showTags && node.tags) {
653
+ $.each(node.tags, function addTag(id, tag) {
654
+ treeItem
655
+ .append($(_this.template.badge)
656
+ .append(tag)
657
+ );
658
+ });
659
+ }
660
+
661
+ // Add item to the tree
662
+ _this.$wrapper.append(treeItem);
663
+
664
+ // Recursively add child ndoes
665
+ if (node.nodes && node.state.expanded && !node.state.disabled) {
666
+ return _this.buildTree(node.nodes, level);
667
+ }
668
+ });
669
+ };
670
+
671
+ // Define any node level style override for
672
+ // 1. selectedNode
673
+ // 2. node|data assigned color overrides
674
+ Tree.prototype.buildStyleOverride = function (node) {
675
+
676
+ if (node.state.disabled) return '';
677
+
678
+ var color = node.color;
679
+ var backColor = node.backColor;
680
+
681
+ if (this.options.highlightSelected && node.state.selected) {
682
+ if (this.options.selectedColor) {
683
+ color = this.options.selectedColor;
684
+ }
685
+ if (this.options.selectedBackColor) {
686
+ backColor = this.options.selectedBackColor;
687
+ }
688
+ }
689
+
690
+ if (this.options.highlightSearchResults && node.searchResult && !node.state.disabled) {
691
+ if (this.options.searchResultColor) {
692
+ color = this.options.searchResultColor;
693
+ }
694
+ if (this.options.searchResultBackColor) {
695
+ backColor = this.options.searchResultBackColor;
696
+ }
697
+ }
698
+
699
+ return 'color:' + color +
700
+ ';background-color:' + backColor + ';';
701
+ };
702
+
703
+ // Add inline style into head
704
+ Tree.prototype.injectStyle = function () {
705
+
706
+ if (this.options.injectStyle && !document.getElementById(this.styleId)) {
707
+ $('<style type="text/css" id="' + this.styleId + '"> ' + this.buildStyle() + ' </style>').appendTo('head');
708
+ }
709
+ };
710
+
711
+ // Construct trees style based on user options
712
+ Tree.prototype.buildStyle = function () {
713
+
714
+ var style = '.node-' + this.elementId + '{';
715
+
716
+ if (this.options.color) {
717
+ style += 'color:' + this.options.color + ';';
718
+ }
719
+
720
+ if (this.options.backColor) {
721
+ style += 'background-color:' + this.options.backColor + ';';
722
+ }
723
+
724
+ if (!this.options.showBorder) {
725
+ style += 'border:none;';
726
+ }
727
+ else if (this.options.borderColor) {
728
+ style += 'border:1px solid ' + this.options.borderColor + ';';
729
+ }
730
+ style += '}';
731
+
732
+ if (this.options.onhoverColor) {
733
+ style += '.node-' + this.elementId + ':not(.node-disabled):hover{' +
734
+ 'background-color:' + this.options.onhoverColor + ';' +
735
+ '}';
736
+ }
737
+
738
+ return this.css + style;
739
+ };
740
+
741
+ Tree.prototype.template = {
742
+ list: '<ul class="list-group"></ul>',
743
+ item: '<li class="list-group-item"></li>',
744
+ indent: '<span class="indent"></span>',
745
+ icon: '<span class="icon"></span>',
746
+ link: '<a href="#" style="color:inherit;"></a>',
747
+ badge: '<span class="badge"></span>'
748
+ };
749
+
750
+ Tree.prototype.css = '.treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}'
751
+
752
+
753
+ /**
754
+ Returns a single node object that matches the given node id.
755
+ @param {Number} nodeId - A node's unique identifier
756
+ @return {Object} node - Matching node
757
+ */
758
+ Tree.prototype.getNode = function (nodeId) {
759
+ return this.nodes[nodeId];
760
+ };
761
+
762
+ Tree.prototype.addToParent = function(node, parentId) {
763
+ var parent = this.nodes.find(function (node, index) {
764
+ return node.id == parentId;
765
+ });
766
+
767
+ if (parent && parent.state.expanded) {
768
+ parent.nodes.push(node);
769
+ } else if(parent && !parent.state.expanded && !parent.nodes) {
770
+ parent.nodes = [];
771
+ } else if (!parentId) {
772
+ this.tree.push(node);
773
+ }
774
+
775
+ this.nodes = [];
776
+ this.destroy();
777
+ this.subscribeEvents();
778
+ this.setInitialStates({ nodes: this.tree }, 0);
779
+ this.render();
780
+ };
781
+
782
+ Tree.prototype.editNode = function(editedNode) {
783
+ var node = this.nodes.find(function (node, index) {
784
+ return node.id == editedNode.id;
785
+ });
786
+
787
+ delete editedNode.nodes;
788
+ Object.assign(node, editedNode);
789
+ this.nodes = [];
790
+ this.destroy();
791
+ this.subscribeEvents();
792
+ this.setInitialStates({ nodes: this.tree }, 0);
793
+ this.render();
794
+ };
795
+
796
+ /**
797
+ Returns the parent node of a given node, if valid otherwise returns undefined.
798
+ @param {Object|Number} identifier - A valid node or node id
799
+ @returns {Object} node - The parent node
800
+ */
801
+ Tree.prototype.getParent = function (identifier) {
802
+ var node = this.identifyNode(identifier);
803
+ return this.nodes[node.parentId];
804
+ };
805
+
806
+ /**
807
+ Returns an array of sibling nodes for a given node, if valid otherwise returns undefined.
808
+ @param {Object|Number} identifier - A valid node or node id
809
+ @returns {Array} nodes - Sibling nodes
810
+ */
811
+ Tree.prototype.getSiblings = function (identifier) {
812
+ var node = this.identifyNode(identifier);
813
+ var parent = this.getParent(node);
814
+ var nodes = parent ? parent.nodes : this.tree;
815
+ return nodes.filter(function (obj) {
816
+ return obj.nodeId !== node.nodeId;
817
+ });
818
+ };
819
+
820
+ /**
821
+ Returns an array of selected nodes.
822
+ @returns {Array} nodes - Selected nodes
823
+ */
824
+ Tree.prototype.getSelected = function () {
825
+ return this.findNodes('true', 'g', 'state.selected');
826
+ };
827
+
828
+ /**
829
+ Returns an array of unselected nodes.
830
+ @returns {Array} nodes - Unselected nodes
831
+ */
832
+ Tree.prototype.getUnselected = function () {
833
+ return this.findNodes('false', 'g', 'state.selected');
834
+ };
835
+
836
+ /**
837
+ Returns an array of expanded nodes.
838
+ @returns {Array} nodes - Expanded nodes
839
+ */
840
+ Tree.prototype.getExpanded = function () {
841
+ return this.findNodes('true', 'g', 'state.expanded');
842
+ };
843
+
844
+ /**
845
+ Returns an array of collapsed nodes.
846
+ @returns {Array} nodes - Collapsed nodes
847
+ */
848
+ Tree.prototype.getCollapsed = function () {
849
+ return this.findNodes('false', 'g', 'state.expanded');
850
+ };
851
+
852
+ /**
853
+ Returns an array of checked nodes.
854
+ @returns {Array} nodes - Checked nodes
855
+ */
856
+ Tree.prototype.getChecked = function () {
857
+ return this.findNodes('true', 'g', 'state.checked');
858
+ };
859
+
860
+ /**
861
+ Returns an array of unchecked nodes.
862
+ @returns {Array} nodes - Unchecked nodes
863
+ */
864
+ Tree.prototype.getUnchecked = function () {
865
+ return this.findNodes('false', 'g', 'state.checked');
866
+ };
867
+
868
+ /**
869
+ Returns an array of disabled nodes.
870
+ @returns {Array} nodes - Disabled nodes
871
+ */
872
+ Tree.prototype.getDisabled = function () {
873
+ return this.findNodes('true', 'g', 'state.disabled');
874
+ };
875
+
876
+ /**
877
+ Returns an array of enabled nodes.
878
+ @returns {Array} nodes - Enabled nodes
879
+ */
880
+ Tree.prototype.getEnabled = function () {
881
+ return this.findNodes('false', 'g', 'state.disabled');
882
+ };
883
+
884
+
885
+ /**
886
+ Set a node state to selected
887
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
888
+ @param {optional Object} options
889
+ */
890
+ Tree.prototype.selectNode = function (identifiers, options) {
891
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
892
+ this.setSelectedState(node, true, options);
893
+ }, this));
894
+
895
+ this.render();
896
+ };
897
+
898
+ /**
899
+ Set a node state to unselected
900
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
901
+ @param {optional Object} options
902
+ */
903
+ Tree.prototype.unselectNode = function (identifiers, options) {
904
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
905
+ this.setSelectedState(node, false, options);
906
+ }, this));
907
+
908
+ this.render();
909
+ };
910
+
911
+ /**
912
+ Toggles a node selected state; selecting if unselected, unselecting if selected.
913
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
914
+ @param {optional Object} options
915
+ */
916
+ Tree.prototype.toggleNodeSelected = function (identifiers, options) {
917
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
918
+ this.toggleSelectedState(node, options);
919
+ }, this));
920
+
921
+ this.render();
922
+ };
923
+
924
+
925
+ /**
926
+ Collapse all tree nodes
927
+ @param {optional Object} options
928
+ */
929
+ Tree.prototype.collapseAll = function (options) {
930
+ var identifiers = this.findNodes('true', 'g', 'state.expanded');
931
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
932
+ this.setExpandedState(node, false, options);
933
+ }, this));
934
+
935
+ this.render();
936
+ };
937
+
938
+ /**
939
+ Collapse a given tree node
940
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
941
+ @param {optional Object} options
942
+ */
943
+ Tree.prototype.collapseNode = function (identifiers, options) {
944
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
945
+ this.setExpandedState(node, false, options);
946
+ }, this));
947
+
948
+ this.render();
949
+ };
950
+
951
+ /**
952
+ Expand all tree nodes
953
+ @param {optional Object} options
954
+ */
955
+ Tree.prototype.expandAll = function (options) {
956
+ options = $.extend({}, _default.options, options);
957
+
958
+ if (options && options.levels) {
959
+ this.expandLevels(this.tree, options.levels, options);
960
+ }
961
+ else {
962
+ var identifiers = this.findNodes('false', 'g', 'state.expanded');
963
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
964
+ this.setExpandedState(node, true, options);
965
+ }, this));
966
+ }
967
+
968
+ this.render();
969
+ };
970
+
971
+ /**
972
+ Expand a given tree node
973
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
974
+ @param {optional Object} options
975
+ */
976
+ Tree.prototype.expandNode = function (identifiers, options) {
977
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
978
+ this.setExpandedState(node, true, options);
979
+ if (node.nodes && (options && options.levels)) {
980
+ this.expandLevels(node.nodes, options.levels-1, options);
981
+ }
982
+ }, this));
983
+
984
+ this.render();
985
+ };
986
+
987
+ Tree.prototype.expandLevels = function (nodes, level, options) {
988
+ options = $.extend({}, _default.options, options);
989
+
990
+ $.each(nodes, $.proxy(function (index, node) {
991
+ this.setExpandedState(node, (level > 0) ? true : false, options);
992
+ if (node.nodes) {
993
+ this.expandLevels(node.nodes, level-1, options);
994
+ }
995
+ }, this));
996
+ };
997
+
998
+ /**
999
+ Reveals a given tree node, expanding the tree from node to root.
1000
+ @param {Object|Number|Array} identifiers - A valid node, node id or array of node identifiers
1001
+ @param {optional Object} options
1002
+ */
1003
+ Tree.prototype.revealNode = function (identifiers, options) {
1004
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1005
+ var parentNode = this.getParent(node);
1006
+ while (parentNode) {
1007
+ this.setExpandedState(parentNode, true, options);
1008
+ parentNode = this.getParent(parentNode);
1009
+ };
1010
+ }, this));
1011
+
1012
+ this.render();
1013
+ };
1014
+
1015
+ /**
1016
+ Toggles a nodes expanded state; collapsing if expanded, expanding if collapsed.
1017
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1018
+ @param {optional Object} options
1019
+ */
1020
+ Tree.prototype.toggleNodeExpanded = function (identifiers, options) {
1021
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1022
+ this.toggleExpandedState(node, options);
1023
+ }, this));
1024
+
1025
+ this.render();
1026
+ };
1027
+
1028
+
1029
+ /**
1030
+ Check all tree nodes
1031
+ @param {optional Object} options
1032
+ */
1033
+ Tree.prototype.checkAll = function (options) {
1034
+ var identifiers = this.findNodes('false', 'g', 'state.checked');
1035
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1036
+ this.setCheckedState(node, true, options);
1037
+ }, this));
1038
+
1039
+ this.render();
1040
+ };
1041
+
1042
+ /**
1043
+ Check a given tree node
1044
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1045
+ @param {optional Object} options
1046
+ */
1047
+ Tree.prototype.checkNode = function (identifiers, options) {
1048
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1049
+ this.setCheckedState(node, true, options);
1050
+ }, this));
1051
+
1052
+ this.render();
1053
+ };
1054
+
1055
+ /**
1056
+ Uncheck all tree nodes
1057
+ @param {optional Object} options
1058
+ */
1059
+ Tree.prototype.uncheckAll = function (options) {
1060
+ var identifiers = this.findNodes('true', 'g', 'state.checked');
1061
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1062
+ this.setCheckedState(node, false, options);
1063
+ }, this));
1064
+
1065
+ this.render();
1066
+ };
1067
+
1068
+ /**
1069
+ Uncheck a given tree node
1070
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1071
+ @param {optional Object} options
1072
+ */
1073
+ Tree.prototype.uncheckNode = function (identifiers, options) {
1074
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1075
+ this.setCheckedState(node, false, options);
1076
+ }, this));
1077
+
1078
+ this.render();
1079
+ };
1080
+
1081
+ /**
1082
+ Toggles a nodes checked state; checking if unchecked, unchecking if checked.
1083
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1084
+ @param {optional Object} options
1085
+ */
1086
+ Tree.prototype.toggleNodeChecked = function (identifiers, options) {
1087
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1088
+ this.toggleCheckedState(node, options);
1089
+ }, this));
1090
+
1091
+ this.render();
1092
+ };
1093
+
1094
+
1095
+ /**
1096
+ Disable all tree nodes
1097
+ @param {optional Object} options
1098
+ */
1099
+ Tree.prototype.disableAll = function (options) {
1100
+ var identifiers = this.findNodes('false', 'g', 'state.disabled');
1101
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1102
+ this.setDisabledState(node, true, options);
1103
+ }, this));
1104
+
1105
+ this.render();
1106
+ };
1107
+
1108
+ /**
1109
+ Disable a given tree node
1110
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1111
+ @param {optional Object} options
1112
+ */
1113
+ Tree.prototype.disableNode = function (identifiers, options) {
1114
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1115
+ this.setDisabledState(node, true, options);
1116
+ }, this));
1117
+
1118
+ this.render();
1119
+ };
1120
+
1121
+ /**
1122
+ Enable all tree nodes
1123
+ @param {optional Object} options
1124
+ */
1125
+ Tree.prototype.enableAll = function (options) {
1126
+ var identifiers = this.findNodes('true', 'g', 'state.disabled');
1127
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1128
+ this.setDisabledState(node, false, options);
1129
+ }, this));
1130
+
1131
+ this.render();
1132
+ };
1133
+
1134
+ /**
1135
+ Enable a given tree node
1136
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1137
+ @param {optional Object} options
1138
+ */
1139
+ Tree.prototype.enableNode = function (identifiers, options) {
1140
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1141
+ this.setDisabledState(node, false, options);
1142
+ }, this));
1143
+
1144
+ this.render();
1145
+ };
1146
+
1147
+ /**
1148
+ Toggles a nodes disabled state; disabling is enabled, enabling if disabled.
1149
+ @param {Object|Number} identifiers - A valid node, node id or array of node identifiers
1150
+ @param {optional Object} options
1151
+ */
1152
+ Tree.prototype.toggleNodeDisabled = function (identifiers, options) {
1153
+ this.forEachIdentifier(identifiers, options, $.proxy(function (node, options) {
1154
+ this.setDisabledState(node, !node.state.disabled, options);
1155
+ }, this));
1156
+
1157
+ this.render();
1158
+ };
1159
+
1160
+
1161
+ /**
1162
+ Common code for processing multiple identifiers
1163
+ */
1164
+ Tree.prototype.forEachIdentifier = function (identifiers, options, callback) {
1165
+
1166
+ options = $.extend({}, _default.options, options);
1167
+
1168
+ if (!(identifiers instanceof Array)) {
1169
+ identifiers = [identifiers];
1170
+ }
1171
+
1172
+ $.each(identifiers, $.proxy(function (index, identifier) {
1173
+ callback(this.identifyNode(identifier), options);
1174
+ }, this));
1175
+ };
1176
+
1177
+ /*
1178
+ Identifies a node from either a node id or object
1179
+ */
1180
+ Tree.prototype.identifyNode = function (identifier) {
1181
+ return ((typeof identifier) === 'number') ?
1182
+ this.nodes[identifier] :
1183
+ identifier;
1184
+ };
1185
+
1186
+ /**
1187
+ Searches the tree for nodes (text) that match given criteria
1188
+ @param {String} pattern - A given string to match against
1189
+ @param {optional Object} options - Search criteria options
1190
+ @return {Array} nodes - Matching nodes
1191
+ */
1192
+ Tree.prototype.search = function (pattern, options) {
1193
+ options = $.extend({}, _default.searchOptions, options);
1194
+
1195
+ this.clearSearch({ render: false });
1196
+
1197
+ var results = [];
1198
+ if (pattern && pattern.length > 0) {
1199
+
1200
+ if (options.exactMatch) {
1201
+ pattern = '^' + pattern + '$';
1202
+ }
1203
+
1204
+ var modifier = 'g';
1205
+ if (options.ignoreCase) {
1206
+ modifier += 'i';
1207
+ }
1208
+
1209
+ results = this.findNodes(pattern, modifier);
1210
+
1211
+ // Add searchResult property to all matching nodes
1212
+ // This will be used to apply custom styles
1213
+ // and when identifying result to be cleared
1214
+ $.each(results, function (index, node) {
1215
+ node.searchResult = true;
1216
+ })
1217
+ }
1218
+
1219
+ // If revealResults, then render is triggered from revealNode
1220
+ // otherwise we just call render.
1221
+ if (options.revealResults) {
1222
+ this.revealNode(results);
1223
+ }
1224
+ else {
1225
+ this.render();
1226
+ }
1227
+
1228
+ this.$element.trigger('searchComplete', $.extend(true, {}, results));
1229
+
1230
+ return results;
1231
+ };
1232
+
1233
+ /**
1234
+ Clears previous search results
1235
+ */
1236
+ Tree.prototype.clearSearch = function (options) {
1237
+
1238
+ options = $.extend({}, { render: true }, options);
1239
+
1240
+ var results = $.each(this.findNodes('true', 'g', 'searchResult'), function (index, node) {
1241
+ node.searchResult = false;
1242
+ });
1243
+
1244
+ if (options.render) {
1245
+ this.render();
1246
+ }
1247
+
1248
+ this.$element.trigger('searchCleared', $.extend(true, {}, results));
1249
+ };
1250
+
1251
+ /**
1252
+ Find nodes that match a given criteria
1253
+ @param {String} pattern - A given string to match against
1254
+ @param {optional String} modifier - Valid RegEx modifiers
1255
+ @param {optional String} attribute - Attribute to compare pattern against
1256
+ @return {Array} nodes - Nodes that match your criteria
1257
+ */
1258
+ Tree.prototype.findNodes = function (pattern, modifier, attribute) {
1259
+
1260
+ modifier = modifier || 'g';
1261
+ attribute = attribute || 'text';
1262
+
1263
+ var _this = this;
1264
+ return $.grep(this.nodes, function (node) {
1265
+ var val = _this.getNodeValue(node, attribute);
1266
+ if (typeof val === 'string') {
1267
+ return val.match(new RegExp(pattern, modifier));
1268
+ }
1269
+ });
1270
+ };
1271
+
1272
+ /**
1273
+ Recursive find for retrieving nested attributes values
1274
+ All values are return as strings, unless invalid
1275
+ @param {Object} obj - Typically a node, could be any object
1276
+ @param {String} attr - Identifies an object property using dot notation
1277
+ @return {String} value - Matching attributes string representation
1278
+ */
1279
+ Tree.prototype.getNodeValue = function (obj, attr) {
1280
+ var index = attr.indexOf('.');
1281
+ if (index > 0) {
1282
+ var _obj = obj[attr.substring(0, index)];
1283
+ var _attr = attr.substring(index + 1, attr.length);
1284
+ return this.getNodeValue(_obj, _attr);
1285
+ }
1286
+ else {
1287
+ if (obj.hasOwnProperty(attr)) {
1288
+ return obj[attr].toString();
1289
+ }
1290
+ else {
1291
+ return undefined;
1292
+ }
1293
+ }
1294
+ };
1295
+
1296
+ var logError = function (message) {
1297
+ if (window.console) {
1298
+ window.console.error(message);
1299
+ }
1300
+ };
1301
+
1302
+ // Prevent against multiple instantiations,
1303
+ // handle updates and method calls
1304
+ $.fn[pluginName] = function (options, args) {
1305
+
1306
+ var result;
1307
+
1308
+ this.each(function () {
1309
+ var _this = $.data(this, pluginName);
1310
+ if (typeof options === 'string') {
1311
+ if (!_this) {
1312
+ logError('Not initialized, can not call method : ' + options);
1313
+ }
1314
+ else if (!$.isFunction(_this[options]) || options.charAt(0) === '_') {
1315
+ logError('No such method : ' + options);
1316
+ }
1317
+ else {
1318
+ if (!(args instanceof Array)) {
1319
+ args = [ args ];
1320
+ }
1321
+ result = _this[options].apply(_this, args);
1322
+ }
1323
+ }
1324
+ else if (typeof options === 'boolean') {
1325
+ result = _this;
1326
+ }
1327
+ else {
1328
+ $.data(this, pluginName, new Tree(this, $.extend(true, {}, options)));
1329
+ }
1330
+ });
1331
+
1332
+ return result || this;
1333
+ };
1334
+
1335
+ })(jQuery, window, document);