jstree.types.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. /**
  2. * ### Types plugin
  3. *
  4. * Makes it possible to add predefined types for groups of nodes, which make it possible to easily control nesting rules and icon for each group.
  5. */
  6. /*globals jQuery, define, exports, require */
  7. (function (factory) {
  8. "use strict";
  9. if (typeof define === 'function' && define.amd) {
  10. define('jstree.types', ['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. if($.jstree.plugins.types) { return; }
  21. /**
  22. * An object storing all types as key value pairs, where the key is the type name and the value is an object that could contain following keys (all optional).
  23. *
  24. * * `max_children` the maximum number of immediate children this node type can have. Do not specify or set to `-1` for unlimited.
  25. * * `max_depth` the maximum number of nesting this node type can have. A value of `1` would mean that the node can have children, but no grandchildren. Do not specify or set to `-1` for unlimited.
  26. * * `valid_children` an array of node type strings, that nodes of this type can have as children. Do not specify or set to `-1` for no limits.
  27. * * `icon` a string - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class. Omit to use the default icon from your theme.
  28. * * `li_attr` an object of values which will be used to add HTML attributes on the resulting LI DOM node (merged with the node's own data)
  29. * * `a_attr` an object of values which will be used to add HTML attributes on the resulting A DOM node (merged with the node's own data)
  30. *
  31. * There are two predefined types:
  32. *
  33. * * `#` represents the root of the tree, for example `max_children` would control the maximum number of root nodes.
  34. * * `default` represents the default node - any settings here will be applied to all nodes that do not have a type specified.
  35. *
  36. * @name $.jstree.defaults.types
  37. * @plugin types
  38. */
  39. $.jstree.defaults.types = {
  40. 'default' : {}
  41. };
  42. $.jstree.defaults.types[$.jstree.root] = {};
  43. $.jstree.plugins.types = function (options, parent) {
  44. this.init = function (el, options) {
  45. var i, j;
  46. if(options && options.types && options.types['default']) {
  47. for(i in options.types) {
  48. if(i !== "default" && i !== $.jstree.root && options.types.hasOwnProperty(i)) {
  49. for(j in options.types['default']) {
  50. if(options.types['default'].hasOwnProperty(j) && options.types[i][j] === undefined) {
  51. options.types[i][j] = options.types['default'][j];
  52. }
  53. }
  54. }
  55. }
  56. }
  57. parent.init.call(this, el, options);
  58. this._model.data[$.jstree.root].type = $.jstree.root;
  59. };
  60. this.refresh = function (skip_loading, forget_state) {
  61. parent.refresh.call(this, skip_loading, forget_state);
  62. this._model.data[$.jstree.root].type = $.jstree.root;
  63. };
  64. this.bind = function () {
  65. this.element
  66. .on('model.jstree', $.proxy(function (e, data) {
  67. var m = this._model.data,
  68. dpc = data.nodes,
  69. t = this.settings.types,
  70. i, j, c = 'default', k;
  71. for(i = 0, j = dpc.length; i < j; i++) {
  72. c = 'default';
  73. if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) {
  74. c = m[dpc[i]].original.type;
  75. }
  76. if(m[dpc[i]].data && m[dpc[i]].data.jstree && m[dpc[i]].data.jstree.type && t[m[dpc[i]].data.jstree.type]) {
  77. c = m[dpc[i]].data.jstree.type;
  78. }
  79. m[dpc[i]].type = c;
  80. if(m[dpc[i]].icon === true && t[c].icon !== undefined) {
  81. m[dpc[i]].icon = t[c].icon;
  82. }
  83. if(t[c].li_attr !== undefined && typeof t[c].li_attr === 'object') {
  84. for (k in t[c].li_attr) {
  85. if (t[c].li_attr.hasOwnProperty(k)) {
  86. if (k === 'id') {
  87. continue;
  88. }
  89. else if (m[dpc[i]].li_attr[k] === undefined) {
  90. m[dpc[i]].li_attr[k] = t[c].li_attr[k];
  91. }
  92. else if (k === 'class') {
  93. m[dpc[i]].li_attr['class'] = t[c].li_attr['class'] + ' ' + m[dpc[i]].li_attr['class'];
  94. }
  95. }
  96. }
  97. }
  98. if(t[c].a_attr !== undefined && typeof t[c].a_attr === 'object') {
  99. for (k in t[c].a_attr) {
  100. if (t[c].a_attr.hasOwnProperty(k)) {
  101. if (k === 'id') {
  102. continue;
  103. }
  104. else if (m[dpc[i]].a_attr[k] === undefined) {
  105. m[dpc[i]].a_attr[k] = t[c].a_attr[k];
  106. }
  107. else if (k === 'href' && m[dpc[i]].a_attr[k] === '#') {
  108. m[dpc[i]].a_attr['href'] = t[c].a_attr['href'];
  109. }
  110. else if (k === 'class') {
  111. m[dpc[i]].a_attr['class'] = t[c].a_attr['class'] + ' ' + m[dpc[i]].a_attr['class'];
  112. }
  113. }
  114. }
  115. }
  116. }
  117. m[$.jstree.root].type = $.jstree.root;
  118. }, this));
  119. parent.bind.call(this);
  120. };
  121. this.get_json = function (obj, options, flat) {
  122. var i, j,
  123. m = this._model.data,
  124. opt = options ? $.extend(true, {}, options, {no_id:false}) : {},
  125. tmp = parent.get_json.call(this, obj, opt, flat);
  126. if(tmp === false) { return false; }
  127. if($.isArray(tmp)) {
  128. for(i = 0, j = tmp.length; i < j; i++) {
  129. tmp[i].type = tmp[i].id && m[tmp[i].id] && m[tmp[i].id].type ? m[tmp[i].id].type : "default";
  130. if(options && options.no_id) {
  131. delete tmp[i].id;
  132. if(tmp[i].li_attr && tmp[i].li_attr.id) {
  133. delete tmp[i].li_attr.id;
  134. }
  135. if(tmp[i].a_attr && tmp[i].a_attr.id) {
  136. delete tmp[i].a_attr.id;
  137. }
  138. }
  139. }
  140. }
  141. else {
  142. tmp.type = tmp.id && m[tmp.id] && m[tmp.id].type ? m[tmp.id].type : "default";
  143. if(options && options.no_id) {
  144. tmp = this._delete_ids(tmp);
  145. }
  146. }
  147. return tmp;
  148. };
  149. this._delete_ids = function (tmp) {
  150. if($.isArray(tmp)) {
  151. for(var i = 0, j = tmp.length; i < j; i++) {
  152. tmp[i] = this._delete_ids(tmp[i]);
  153. }
  154. return tmp;
  155. }
  156. delete tmp.id;
  157. if(tmp.li_attr && tmp.li_attr.id) {
  158. delete tmp.li_attr.id;
  159. }
  160. if(tmp.a_attr && tmp.a_attr.id) {
  161. delete tmp.a_attr.id;
  162. }
  163. if(tmp.children && $.isArray(tmp.children)) {
  164. tmp.children = this._delete_ids(tmp.children);
  165. }
  166. return tmp;
  167. };
  168. this.check = function (chk, obj, par, pos, more) {
  169. if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
  170. obj = obj && obj.id ? obj : this.get_node(obj);
  171. par = par && par.id ? par : this.get_node(par);
  172. var m = obj && obj.id ? (more && more.origin ? more.origin : $.jstree.reference(obj.id)) : null, tmp, d, i, j;
  173. m = m && m._model && m._model.data ? m._model.data : null;
  174. switch(chk) {
  175. case "create_node":
  176. case "move_node":
  177. case "copy_node":
  178. if(chk !== 'move_node' || $.inArray(obj.id, par.children) === -1) {
  179. tmp = this.get_rules(par);
  180. if(tmp.max_children !== undefined && tmp.max_children !== -1 && tmp.max_children === par.children.length) {
  181. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_01', 'reason' : 'max_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  182. return false;
  183. }
  184. if(tmp.valid_children !== undefined && tmp.valid_children !== -1 && $.inArray((obj.type || 'default'), tmp.valid_children) === -1) {
  185. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_02', 'reason' : 'valid_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  186. return false;
  187. }
  188. if(m && obj.children_d && obj.parents) {
  189. d = 0;
  190. for(i = 0, j = obj.children_d.length; i < j; i++) {
  191. d = Math.max(d, m[obj.children_d[i]].parents.length);
  192. }
  193. d = d - obj.parents.length + 1;
  194. }
  195. if(d <= 0 || d === undefined) { d = 1; }
  196. do {
  197. if(tmp.max_depth !== undefined && tmp.max_depth !== -1 && tmp.max_depth < d) {
  198. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_03', 'reason' : 'max_depth prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  199. return false;
  200. }
  201. par = this.get_node(par.parent);
  202. tmp = this.get_rules(par);
  203. d++;
  204. } while(par);
  205. }
  206. break;
  207. }
  208. return true;
  209. };
  210. /**
  211. * used to retrieve the type settings object for a node
  212. * @name get_rules(obj)
  213. * @param {mixed} obj the node to find the rules for
  214. * @return {Object}
  215. * @plugin types
  216. */
  217. this.get_rules = function (obj) {
  218. obj = this.get_node(obj);
  219. if(!obj) { return false; }
  220. var tmp = this.get_type(obj, true);
  221. if(tmp.max_depth === undefined) { tmp.max_depth = -1; }
  222. if(tmp.max_children === undefined) { tmp.max_children = -1; }
  223. if(tmp.valid_children === undefined) { tmp.valid_children = -1; }
  224. return tmp;
  225. };
  226. /**
  227. * used to retrieve the type string or settings object for a node
  228. * @name get_type(obj [, rules])
  229. * @param {mixed} obj the node to find the rules for
  230. * @param {Boolean} rules if set to `true` instead of a string the settings object will be returned
  231. * @return {String|Object}
  232. * @plugin types
  233. */
  234. this.get_type = function (obj, rules) {
  235. obj = this.get_node(obj);
  236. return (!obj) ? false : ( rules ? $.extend({ 'type' : obj.type }, this.settings.types[obj.type]) : obj.type);
  237. };
  238. /**
  239. * used to change a node's type
  240. * @name set_type(obj, type)
  241. * @param {mixed} obj the node to change
  242. * @param {String} type the new type
  243. * @plugin types
  244. */
  245. this.set_type = function (obj, type) {
  246. var m = this._model.data, t, t1, t2, old_type, old_icon, k, d, a;
  247. if($.isArray(obj)) {
  248. obj = obj.slice();
  249. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  250. this.set_type(obj[t1], type);
  251. }
  252. return true;
  253. }
  254. t = this.settings.types;
  255. obj = this.get_node(obj);
  256. if(!t[type] || !obj) { return false; }
  257. d = this.get_node(obj, true);
  258. if (d && d.length) {
  259. a = d.children('.jstree-anchor');
  260. }
  261. old_type = obj.type;
  262. old_icon = this.get_icon(obj);
  263. obj.type = type;
  264. if(old_icon === true || !t[old_type] || (t[old_type].icon !== undefined && old_icon === t[old_type].icon)) {
  265. this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true);
  266. }
  267. // remove old type props
  268. if(t[old_type] && t[old_type].li_attr !== undefined && typeof t[old_type].li_attr === 'object') {
  269. for (k in t[old_type].li_attr) {
  270. if (t[old_type].li_attr.hasOwnProperty(k)) {
  271. if (k === 'id') {
  272. continue;
  273. }
  274. else if (k === 'class') {
  275. m[obj.id].li_attr['class'] = (m[obj.id].li_attr['class'] || '').replace(t[old_type].li_attr[k], '');
  276. if (d) { d.removeClass(t[old_type].li_attr[k]); }
  277. }
  278. else if (m[obj.id].li_attr[k] === t[old_type].li_attr[k]) {
  279. m[obj.id].li_attr[k] = null;
  280. if (d) { d.removeAttr(k); }
  281. }
  282. }
  283. }
  284. }
  285. if(t[old_type] && t[old_type].a_attr !== undefined && typeof t[old_type].a_attr === 'object') {
  286. for (k in t[old_type].a_attr) {
  287. if (t[old_type].a_attr.hasOwnProperty(k)) {
  288. if (k === 'id') {
  289. continue;
  290. }
  291. else if (k === 'class') {
  292. m[obj.id].a_attr['class'] = (m[obj.id].a_attr['class'] || '').replace(t[old_type].a_attr[k], '');
  293. if (a) { a.removeClass(t[old_type].a_attr[k]); }
  294. }
  295. else if (m[obj.id].a_attr[k] === t[old_type].a_attr[k]) {
  296. if (k === 'href') {
  297. m[obj.id].a_attr[k] = '#';
  298. if (a) { a.attr('href', '#'); }
  299. }
  300. else {
  301. delete m[obj.id].a_attr[k];
  302. if (a) { a.removeAttr(k); }
  303. }
  304. }
  305. }
  306. }
  307. }
  308. // add new props
  309. if(t[type].li_attr !== undefined && typeof t[type].li_attr === 'object') {
  310. for (k in t[type].li_attr) {
  311. if (t[type].li_attr.hasOwnProperty(k)) {
  312. if (k === 'id') {
  313. continue;
  314. }
  315. else if (m[obj.id].li_attr[k] === undefined) {
  316. m[obj.id].li_attr[k] = t[type].li_attr[k];
  317. if (d) {
  318. if (k === 'class') {
  319. d.addClass(t[type].li_attr[k]);
  320. }
  321. else {
  322. d.attr(k, t[type].li_attr[k]);
  323. }
  324. }
  325. }
  326. else if (k === 'class') {
  327. m[obj.id].li_attr['class'] = t[type].li_attr[k] + ' ' + m[obj.id].li_attr['class'];
  328. if (d) { d.addClass(t[type].li_attr[k]); }
  329. }
  330. }
  331. }
  332. }
  333. if(t[type].a_attr !== undefined && typeof t[type].a_attr === 'object') {
  334. for (k in t[type].a_attr) {
  335. if (t[type].a_attr.hasOwnProperty(k)) {
  336. if (k === 'id') {
  337. continue;
  338. }
  339. else if (m[obj.id].a_attr[k] === undefined) {
  340. m[obj.id].a_attr[k] = t[type].a_attr[k];
  341. if (a) {
  342. if (k === 'class') {
  343. a.addClass(t[type].a_attr[k]);
  344. }
  345. else {
  346. a.attr(k, t[type].a_attr[k]);
  347. }
  348. }
  349. }
  350. else if (k === 'href' && m[obj.id].a_attr[k] === '#') {
  351. m[obj.id].a_attr['href'] = t[type].a_attr['href'];
  352. if (a) { a.attr('href', t[type].a_attr['href']); }
  353. }
  354. else if (k === 'class') {
  355. m[obj.id].a_attr['class'] = t[type].a_attr['class'] + ' ' + m[obj.id].a_attr['class'];
  356. if (a) { a.addClass(t[type].a_attr[k]); }
  357. }
  358. }
  359. }
  360. }
  361. return true;
  362. };
  363. };
  364. // include the types plugin by default
  365. // $.jstree.defaults.plugins.push("types");
  366. }));