flexite 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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);