Mercurial > repos > mingchen0919 > aurora_skewer
comparison vakata-jstree-3.3.5/src/jstree.search.js @ 0:0358f6f78adf draft
planemo upload commit 841d8b22bf9f1aaed6bfe8344b60617f45b275b2-dirty
author | mingchen0919 |
---|---|
date | Fri, 14 Dec 2018 00:40:15 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:0358f6f78adf |
---|---|
1 /** | |
2 * ### Search plugin | |
3 * | |
4 * Adds search functionality to jsTree. | |
5 */ | |
6 /*globals jQuery, define, exports, require, document */ | |
7 (function (factory) { | |
8 "use strict"; | |
9 if (typeof define === 'function' && define.amd) { | |
10 define('jstree.search', ['jquery','jstree'], factory); | |
11 } | |
12 else if(typeof exports === 'object') { | |
13 factory(require('jquery'), require('jstree')); | |
14 } | |
15 else { | |
16 factory(jQuery, jQuery.jstree); | |
17 } | |
18 }(function ($, jstree, undefined) { | |
19 "use strict"; | |
20 | |
21 if($.jstree.plugins.search) { return; } | |
22 | |
23 /** | |
24 * stores all defaults for the search plugin | |
25 * @name $.jstree.defaults.search | |
26 * @plugin search | |
27 */ | |
28 $.jstree.defaults.search = { | |
29 /** | |
30 * a jQuery-like AJAX config, which jstree uses if a server should be queried for results. | |
31 * | |
32 * A `str` (which is the search string) parameter will be added with the request, an optional `inside` parameter will be added if the search is limited to a node id. The expected result is a JSON array with nodes that need to be opened so that matching nodes will be revealed. | |
33 * Leave this setting as `false` to not query the server. You can also set this to a function, which will be invoked in the instance's scope and receive 3 parameters - the search string, the callback to call with the array of nodes to load, and the optional node ID to limit the search to | |
34 * @name $.jstree.defaults.search.ajax | |
35 * @plugin search | |
36 */ | |
37 ajax : false, | |
38 /** | |
39 * Indicates if the search should be fuzzy or not (should `chnd3` match `child node 3`). Default is `false`. | |
40 * @name $.jstree.defaults.search.fuzzy | |
41 * @plugin search | |
42 */ | |
43 fuzzy : false, | |
44 /** | |
45 * Indicates if the search should be case sensitive. Default is `false`. | |
46 * @name $.jstree.defaults.search.case_sensitive | |
47 * @plugin search | |
48 */ | |
49 case_sensitive : false, | |
50 /** | |
51 * Indicates if the tree should be filtered (by default) to show only matching nodes (keep in mind this can be a heavy on large trees in old browsers). | |
52 * This setting can be changed at runtime when calling the search method. Default is `false`. | |
53 * @name $.jstree.defaults.search.show_only_matches | |
54 * @plugin search | |
55 */ | |
56 show_only_matches : false, | |
57 /** | |
58 * Indicates if the children of matched element are shown (when show_only_matches is true) | |
59 * This setting can be changed at runtime when calling the search method. Default is `false`. | |
60 * @name $.jstree.defaults.search.show_only_matches_children | |
61 * @plugin search | |
62 */ | |
63 show_only_matches_children : false, | |
64 /** | |
65 * Indicates if all nodes opened to reveal the search result, should be closed when the search is cleared or a new search is performed. Default is `true`. | |
66 * @name $.jstree.defaults.search.close_opened_onclear | |
67 * @plugin search | |
68 */ | |
69 close_opened_onclear : true, | |
70 /** | |
71 * Indicates if only leaf nodes should be included in search results. Default is `false`. | |
72 * @name $.jstree.defaults.search.search_leaves_only | |
73 * @plugin search | |
74 */ | |
75 search_leaves_only : false, | |
76 /** | |
77 * If set to a function it wil be called in the instance's scope with two arguments - search string and node (where node will be every node in the structure, so use with caution). | |
78 * If the function returns a truthy value the node will be considered a match (it might not be displayed if search_only_leaves is set to true and the node is not a leaf). Default is `false`. | |
79 * @name $.jstree.defaults.search.search_callback | |
80 * @plugin search | |
81 */ | |
82 search_callback : false | |
83 }; | |
84 | |
85 $.jstree.plugins.search = function (options, parent) { | |
86 this.bind = function () { | |
87 parent.bind.call(this); | |
88 | |
89 this._data.search.str = ""; | |
90 this._data.search.dom = $(); | |
91 this._data.search.res = []; | |
92 this._data.search.opn = []; | |
93 this._data.search.som = false; | |
94 this._data.search.smc = false; | |
95 this._data.search.hdn = []; | |
96 | |
97 this.element | |
98 .on("search.jstree", $.proxy(function (e, data) { | |
99 if(this._data.search.som && data.res.length) { | |
100 var m = this._model.data, i, j, p = [], k, l; | |
101 for(i = 0, j = data.res.length; i < j; i++) { | |
102 if(m[data.res[i]] && !m[data.res[i]].state.hidden) { | |
103 p.push(data.res[i]); | |
104 p = p.concat(m[data.res[i]].parents); | |
105 if(this._data.search.smc) { | |
106 for (k = 0, l = m[data.res[i]].children_d.length; k < l; k++) { | |
107 if (m[m[data.res[i]].children_d[k]] && !m[m[data.res[i]].children_d[k]].state.hidden) { | |
108 p.push(m[data.res[i]].children_d[k]); | |
109 } | |
110 } | |
111 } | |
112 } | |
113 } | |
114 p = $.vakata.array_remove_item($.vakata.array_unique(p), $.jstree.root); | |
115 this._data.search.hdn = this.hide_all(true); | |
116 this.show_node(p, true); | |
117 this.redraw(true); | |
118 } | |
119 }, this)) | |
120 .on("clear_search.jstree", $.proxy(function (e, data) { | |
121 if(this._data.search.som && data.res.length) { | |
122 this.show_node(this._data.search.hdn, true); | |
123 this.redraw(true); | |
124 } | |
125 }, this)); | |
126 }; | |
127 /** | |
128 * used to search the tree nodes for a given string | |
129 * @name search(str [, skip_async]) | |
130 * @param {String} str the search string | |
131 * @param {Boolean} skip_async if set to true server will not be queried even if configured | |
132 * @param {Boolean} show_only_matches if set to true only matching nodes will be shown (keep in mind this can be very slow on large trees or old browsers) | |
133 * @param {mixed} inside an optional node to whose children to limit the search | |
134 * @param {Boolean} append if set to true the results of this search are appended to the previous search | |
135 * @plugin search | |
136 * @trigger search.jstree | |
137 */ | |
138 this.search = function (str, skip_async, show_only_matches, inside, append, show_only_matches_children) { | |
139 if(str === false || $.trim(str.toString()) === "") { | |
140 return this.clear_search(); | |
141 } | |
142 inside = this.get_node(inside); | |
143 inside = inside && inside.id ? inside.id : null; | |
144 str = str.toString(); | |
145 var s = this.settings.search, | |
146 a = s.ajax ? s.ajax : false, | |
147 m = this._model.data, | |
148 f = null, | |
149 r = [], | |
150 p = [], i, j; | |
151 if(this._data.search.res.length && !append) { | |
152 this.clear_search(); | |
153 } | |
154 if(show_only_matches === undefined) { | |
155 show_only_matches = s.show_only_matches; | |
156 } | |
157 if(show_only_matches_children === undefined) { | |
158 show_only_matches_children = s.show_only_matches_children; | |
159 } | |
160 if(!skip_async && a !== false) { | |
161 if($.isFunction(a)) { | |
162 return a.call(this, str, $.proxy(function (d) { | |
163 if(d && d.d) { d = d.d; } | |
164 this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () { | |
165 this.search(str, true, show_only_matches, inside, append, show_only_matches_children); | |
166 }); | |
167 }, this), inside); | |
168 } | |
169 else { | |
170 a = $.extend({}, a); | |
171 if(!a.data) { a.data = {}; } | |
172 a.data.str = str; | |
173 if(inside) { | |
174 a.data.inside = inside; | |
175 } | |
176 if (this._data.search.lastRequest) { | |
177 this._data.search.lastRequest.abort(); | |
178 } | |
179 this._data.search.lastRequest = $.ajax(a) | |
180 .fail($.proxy(function () { | |
181 this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) }; | |
182 this.settings.core.error.call(this, this._data.core.last_error); | |
183 }, this)) | |
184 .done($.proxy(function (d) { | |
185 if(d && d.d) { d = d.d; } | |
186 this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () { | |
187 this.search(str, true, show_only_matches, inside, append, show_only_matches_children); | |
188 }); | |
189 }, this)); | |
190 return this._data.search.lastRequest; | |
191 } | |
192 } | |
193 if(!append) { | |
194 this._data.search.str = str; | |
195 this._data.search.dom = $(); | |
196 this._data.search.res = []; | |
197 this._data.search.opn = []; | |
198 this._data.search.som = show_only_matches; | |
199 this._data.search.smc = show_only_matches_children; | |
200 } | |
201 | |
202 f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy }); | |
203 $.each(m[inside ? inside : $.jstree.root].children_d, function (ii, i) { | |
204 var v = m[i]; | |
205 if(v.text && !v.state.hidden && (!s.search_leaves_only || (v.state.loaded && v.children.length === 0)) && ( (s.search_callback && s.search_callback.call(this, str, v)) || (!s.search_callback && f.search(v.text).isMatch) ) ) { | |
206 r.push(i); | |
207 p = p.concat(v.parents); | |
208 } | |
209 }); | |
210 if(r.length) { | |
211 p = $.vakata.array_unique(p); | |
212 for(i = 0, j = p.length; i < j; i++) { | |
213 if(p[i] !== $.jstree.root && m[p[i]] && this.open_node(p[i], null, 0) === true) { | |
214 this._data.search.opn.push(p[i]); | |
215 } | |
216 } | |
217 if(!append) { | |
218 this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #'))); | |
219 this._data.search.res = r; | |
220 } | |
221 else { | |
222 this._data.search.dom = this._data.search.dom.add($(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #')))); | |
223 this._data.search.res = $.vakata.array_unique(this._data.search.res.concat(r)); | |
224 } | |
225 this._data.search.dom.children(".jstree-anchor").addClass('jstree-search'); | |
226 } | |
227 /** | |
228 * triggered after search is complete | |
229 * @event | |
230 * @name search.jstree | |
231 * @param {jQuery} nodes a jQuery collection of matching nodes | |
232 * @param {String} str the search string | |
233 * @param {Array} res a collection of objects represeing the matching nodes | |
234 * @plugin search | |
235 */ | |
236 this.trigger('search', { nodes : this._data.search.dom, str : str, res : this._data.search.res, show_only_matches : show_only_matches }); | |
237 }; | |
238 /** | |
239 * used to clear the last search (removes classes and shows all nodes if filtering is on) | |
240 * @name clear_search() | |
241 * @plugin search | |
242 * @trigger clear_search.jstree | |
243 */ | |
244 this.clear_search = function () { | |
245 if(this.settings.search.close_opened_onclear) { | |
246 this.close_node(this._data.search.opn, 0); | |
247 } | |
248 /** | |
249 * triggered after search is complete | |
250 * @event | |
251 * @name clear_search.jstree | |
252 * @param {jQuery} nodes a jQuery collection of matching nodes (the result from the last search) | |
253 * @param {String} str the search string (the last search string) | |
254 * @param {Array} res a collection of objects represeing the matching nodes (the result from the last search) | |
255 * @plugin search | |
256 */ | |
257 this.trigger('clear_search', { 'nodes' : this._data.search.dom, str : this._data.search.str, res : this._data.search.res }); | |
258 if(this._data.search.res.length) { | |
259 this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(this._data.search.res, function (v) { | |
260 return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); | |
261 }).join(', #'))); | |
262 this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search"); | |
263 } | |
264 this._data.search.str = ""; | |
265 this._data.search.res = []; | |
266 this._data.search.opn = []; | |
267 this._data.search.dom = $(); | |
268 }; | |
269 | |
270 this.redraw_node = function(obj, deep, callback, force_render) { | |
271 obj = parent.redraw_node.apply(this, arguments); | |
272 if(obj) { | |
273 if($.inArray(obj.id, this._data.search.res) !== -1) { | |
274 var i, j, tmp = null; | |
275 for(i = 0, j = obj.childNodes.length; i < j; i++) { | |
276 if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) { | |
277 tmp = obj.childNodes[i]; | |
278 break; | |
279 } | |
280 } | |
281 if(tmp) { | |
282 tmp.className += ' jstree-search'; | |
283 } | |
284 } | |
285 } | |
286 return obj; | |
287 }; | |
288 }; | |
289 | |
290 // helpers | |
291 (function ($) { | |
292 // from http://kiro.me/projects/fuse.html | |
293 $.vakata.search = function(pattern, txt, options) { | |
294 options = options || {}; | |
295 options = $.extend({}, $.vakata.search.defaults, options); | |
296 if(options.fuzzy !== false) { | |
297 options.fuzzy = true; | |
298 } | |
299 pattern = options.caseSensitive ? pattern : pattern.toLowerCase(); | |
300 var MATCH_LOCATION = options.location, | |
301 MATCH_DISTANCE = options.distance, | |
302 MATCH_THRESHOLD = options.threshold, | |
303 patternLen = pattern.length, | |
304 matchmask, pattern_alphabet, match_bitapScore, search; | |
305 if(patternLen > 32) { | |
306 options.fuzzy = false; | |
307 } | |
308 if(options.fuzzy) { | |
309 matchmask = 1 << (patternLen - 1); | |
310 pattern_alphabet = (function () { | |
311 var mask = {}, | |
312 i = 0; | |
313 for (i = 0; i < patternLen; i++) { | |
314 mask[pattern.charAt(i)] = 0; | |
315 } | |
316 for (i = 0; i < patternLen; i++) { | |
317 mask[pattern.charAt(i)] |= 1 << (patternLen - i - 1); | |
318 } | |
319 return mask; | |
320 }()); | |
321 match_bitapScore = function (e, x) { | |
322 var accuracy = e / patternLen, | |
323 proximity = Math.abs(MATCH_LOCATION - x); | |
324 if(!MATCH_DISTANCE) { | |
325 return proximity ? 1.0 : accuracy; | |
326 } | |
327 return accuracy + (proximity / MATCH_DISTANCE); | |
328 }; | |
329 } | |
330 search = function (text) { | |
331 text = options.caseSensitive ? text : text.toLowerCase(); | |
332 if(pattern === text || text.indexOf(pattern) !== -1) { | |
333 return { | |
334 isMatch: true, | |
335 score: 0 | |
336 }; | |
337 } | |
338 if(!options.fuzzy) { | |
339 return { | |
340 isMatch: false, | |
341 score: 1 | |
342 }; | |
343 } | |
344 var i, j, | |
345 textLen = text.length, | |
346 scoreThreshold = MATCH_THRESHOLD, | |
347 bestLoc = text.indexOf(pattern, MATCH_LOCATION), | |
348 binMin, binMid, | |
349 binMax = patternLen + textLen, | |
350 lastRd, start, finish, rd, charMatch, | |
351 score = 1, | |
352 locations = []; | |
353 if (bestLoc !== -1) { | |
354 scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold); | |
355 bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen); | |
356 if (bestLoc !== -1) { | |
357 scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold); | |
358 } | |
359 } | |
360 bestLoc = -1; | |
361 for (i = 0; i < patternLen; i++) { | |
362 binMin = 0; | |
363 binMid = binMax; | |
364 while (binMin < binMid) { | |
365 if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) { | |
366 binMin = binMid; | |
367 } else { | |
368 binMax = binMid; | |
369 } | |
370 binMid = Math.floor((binMax - binMin) / 2 + binMin); | |
371 } | |
372 binMax = binMid; | |
373 start = Math.max(1, MATCH_LOCATION - binMid + 1); | |
374 finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen; | |
375 rd = new Array(finish + 2); | |
376 rd[finish + 1] = (1 << i) - 1; | |
377 for (j = finish; j >= start; j--) { | |
378 charMatch = pattern_alphabet[text.charAt(j - 1)]; | |
379 if (i === 0) { | |
380 rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; | |
381 } else { | |
382 rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1]; | |
383 } | |
384 if (rd[j] & matchmask) { | |
385 score = match_bitapScore(i, j - 1); | |
386 if (score <= scoreThreshold) { | |
387 scoreThreshold = score; | |
388 bestLoc = j - 1; | |
389 locations.push(bestLoc); | |
390 if (bestLoc > MATCH_LOCATION) { | |
391 start = Math.max(1, 2 * MATCH_LOCATION - bestLoc); | |
392 } else { | |
393 break; | |
394 } | |
395 } | |
396 } | |
397 } | |
398 if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) { | |
399 break; | |
400 } | |
401 lastRd = rd; | |
402 } | |
403 return { | |
404 isMatch: bestLoc >= 0, | |
405 score: score | |
406 }; | |
407 }; | |
408 return txt === true ? { 'search' : search } : search(txt); | |
409 }; | |
410 $.vakata.search.defaults = { | |
411 location : 0, | |
412 distance : 100, | |
413 threshold : 0.6, | |
414 fuzzy : false, | |
415 caseSensitive : false | |
416 }; | |
417 }($)); | |
418 | |
419 // include the search plugin by default | |
420 // $.jstree.defaults.plugins.push("search"); | |
421 })); |