gui_utils.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. /* This file is part of AToMPM - A Tool for Multi-Paradigm Modelling
  2. * Copyright 2011 by the AToMPM team and licensed under the LGPL
  3. * See COPYING.lesser and README.md in the root of this project for full details
  4. */
  5. GUIUtils = function(){
  6. /**
  7. * This is the old getElementById function
  8. */
  9. this.$$ = function( id ){
  10. return document.getElementById( id );
  11. };
  12. /**
  13. * Converts from page centric X coordinates to canvas centric X coordinates
  14. */
  15. this.convertToCanvasX = function (event) {
  16. return event.layerX;
  17. };
  18. /**
  19. * Converts from page centric Y coordinates to canvas centric Y coordinates
  20. */
  21. this.convertToCanvasY = function (event) {
  22. return event.layerY;
  23. };
  24. /**
  25. * Disables the dock bar
  26. */
  27. this.disableDock = function(){
  28. $('#div_dock').attr('class', 'dock disabled_dock');
  29. };
  30. /**
  31. * Enables the dock bar
  32. */
  33. this.enableDock = function(){
  34. $('#div_dock').attr('class', 'dock');
  35. };
  36. /**
  37. * Constructs and returns a checked checkbox
  38. */
  39. this.getCheckbox = function(checked){
  40. var cb = $('<input>');
  41. cb.attr("type", 'checkbox');
  42. cb.prop("checked", checked);
  43. return cb;
  44. };
  45. // TODO: replace the bundled function with an actual object generation. The
  46. // current method is sloppy.
  47. /**
  48. * Constructs and returns an input field given an attribute's type and value,
  49. * bundled in the return value is a function to retrieve the input fields
  50. * content as well as its initial value
  51. *
  52. * For Maps and Lists, onfocus/onblur toggle a default entry to be shown.
  53. */
  54. this.getInputField = function (type,value){
  55. /* recursively expand specialTypes, if any */
  56. function explodeType(t)
  57. {
  58. var exploded = __specialTypes[t] || t;
  59. while( exploded.match(/\$/) )
  60. exploded = exploded.replace(
  61. /(\$.+?)([,\]>])/g,
  62. function(s,p1,p2){return __specialTypes[p1]+p2;});
  63. return exploded;
  64. }
  65. /* return a default value for the given exploded type string */
  66. function defaultEntry(et)
  67. {
  68. if( et == 'string' || et == 'code' )
  69. return "";
  70. else if( et == 'int' )
  71. return 0;
  72. else if( et == 'boolean' )
  73. return true;
  74. else if( et == 'double' )
  75. return 0.0;
  76. else if( et.match(/^ENUM/) )
  77. return et;
  78. else if( (matches=et.match(/^list<(.*)>$/)) )
  79. return [defaultEntry(matches[1])];
  80. else if( (matches=et.match(/^map<\[(.*?)\],\[(.*)\]>$/)) )
  81. {
  82. var m = {},
  83. keys = matches[1].split(','),
  84. types = [],
  85. depth = 0,
  86. index = 0;
  87. for( var i=0; i<matches[2].length; i++ )
  88. {
  89. if( matches[2].charAt(i) == '(' ||
  90. matches[2].charAt(i) == '<' )
  91. depth++;
  92. else if( matches[2].charAt(i) == ')' ||
  93. matches[2].charAt(i) == '>' )
  94. depth--;
  95. if( matches[2].charAt(i) == ',' && depth == 0 )
  96. {
  97. types.push( matches[2].substring(index,i) );
  98. index = i+1;
  99. }
  100. }
  101. types.push( matches[2].substring(index) );
  102. for( var i=0; i<keys.length; i++ )
  103. m[keys[i]] = defaultEntry(types[i]);
  104. return m;
  105. }
  106. else if( (matches=et.match(/^map<(.*),(.*)>$/)) )
  107. {
  108. var m = {};
  109. m[defaultEntry(matches[1])] = defaultEntry(matches[2]);
  110. return m;
  111. }
  112. }
  113. if( type == 'code' )
  114. var input = GUIUtils.getTextInput(utils.jsond(value,'\n')),
  115. getinput = function(_){return _.val();};
  116. else if( type.match(/^map/) ||
  117. type.match(/^list/) )
  118. {
  119. var input = GUIUtils.getTextInput(
  120. utils.jsond(utils.jsons(value,undefined,' '))),
  121. getinput = function(_){
  122. return utils.jsonp(utils.jsone(_.val()));
  123. };
  124. var de = defaultEntry( explodeType(type) ),
  125. isMap = type.match(/^map/),
  126. matches = undefined;
  127. input.focus(
  128. (isMap ?
  129. function()
  130. {
  131. var newVal = utils.mergeDicts([getinput(input),de]);
  132. input.val( utils.jsond(utils.jsons(newVal,undefined,' ')));
  133. } :
  134. function()
  135. {
  136. var newVal = getinput(input).concat(de);
  137. input.val( utils.jsond(utils.jsons(newVal,undefined,' ')));
  138. }));
  139. input.blur(
  140. (isMap ?
  141. function()
  142. {
  143. var val = getinput(input);
  144. utils.splitDict(val,utils.keys(de));
  145. input.val( utils.jsond(utils.jsons(val,undefined,' ')));
  146. } :
  147. function()
  148. {
  149. var val = getinput(input);
  150. if( utils.jsons(utils.tail(val)) == utils.jsons(de[0]) )
  151. val.pop();
  152. input.val( utils.jsond(utils.jsons(val,undefined,' ')));
  153. }));
  154. }
  155. else if( type.match(/^ENUM/) )
  156. {
  157. var vals = type.match(/^ENUM\((.*)\)$/)[1],
  158. input = GUIUtils.getSelector(vals.split(','),false,[value]),
  159. getinput =
  160. function(_){return HttpUtils.getSelectorSelection(_)[0];};
  161. }
  162. else if( type.match(/^boolean$/) )
  163. {
  164. var input = GUIUtils.getCheckbox(value),
  165. getinput =
  166. function(_){return _.prop("checked");};
  167. }
  168. else if( type.match(/^\$/) )
  169. return GUIUtils.getInputField(__specialTypes[type],value);
  170. else if ((matches = type.match("^file<(.*)>"))) {
  171. var input = GUIUtils.getFileInput(value,matches[1],"code_style string_input",1),
  172. getinput = function(_){return _.val();};
  173. }
  174. else
  175. var input = GUIUtils.getTextInput(value,"code_style string_input",1),
  176. getinput = (type == 'string' ?
  177. function(_){return _.val();} :
  178. function(_){return utils.jsonp(_.val());});
  179. input.title = explodeType(type);
  180. return {'input':input, 'getinput':getinput, 'oldVal':getinput(input)};
  181. };
  182. /**
  183. * Constructs and returns a <select> element with the choices list converted into
  184. * a list of <option> elements.
  185. *
  186. * @param choices - the choices for the <select> element
  187. * @param multipleChoice - if true, allows for multiple options to be selected
  188. * @param defaultSelection - sets the default selection for the list
  189. * @param numVisibleOptions - sets the number of visible options
  190. */
  191. this.getSelector = function(choices,multipleChoice,defaultSelection,numVisibleOptions){
  192. var select = $('<select>');
  193. select.attr("class", 'default_style');
  194. select.attr("size", Math.min(choices.length,numVisibleOptions || __MAX_SELECT_OPTIONS));
  195. select.attr("multiple", multipleChoice);
  196. choices.forEach(
  197. function(choice)
  198. {
  199. var option = $('<option>');
  200. option.val( choice );
  201. option.html( choice );
  202. option.attr('id', "choice_" + choice);
  203. select.append(option);
  204. if( defaultSelection != undefined &&
  205. utils.contains(defaultSelection,choice) )
  206. option.prop("selected", true);
  207. });
  208. return select;
  209. };
  210. /* constructs an INPUT given some text and a CSS class */
  211. /**
  212. * Constructs an <input> element
  213. *
  214. * @param text - the default text for the input element
  215. * @param className - the default class name for the input element
  216. * @param width - the default width of the element. If this is omitted
  217. * then the <input> defaults to 400px wide.
  218. */
  219. this.getStringInput = function(text,className,width){
  220. var input = $('<input>');
  221. input.attr("type", 'text');
  222. input.attr("class", className || 'default_style');
  223. input.val( text );
  224. input.css("width", width || '400px');
  225. return input;
  226. };
  227. this.getFileInput = function(code,pattern,className,rows){
  228. var string_input = this.getTextInput(code, className, rows);
  229. var extra_el = $('<button>');
  230. extra_el.attr("width", 16);
  231. extra_el.attr("height", 16);
  232. extra_el.html("...");
  233. extra_el.click(function(event) {
  234. var options = {'extensions':[pattern],
  235. 'multipleChoice':false,
  236. 'manualInput':false,
  237. 'title':'choose a rule model',
  238. 'startDir':'model'},
  239. callback =
  240. function(fnames)
  241. {
  242. string_input.val(fnames[0]);
  243. };
  244. WindowManagement.openDialog(_FILE_BROWSER,options,callback);
  245. event.stopPropagation();
  246. event.preventDefault();
  247. });
  248. string_input.extra_el = extra_el;
  249. return string_input;
  250. };
  251. /**
  252. * Constructs a <textarea> element. In this element, Alt + Right Arrow
  253. * is treated as a tab.
  254. *
  255. * @param code - the default code for this text area
  256. * @param className - the default class name for the <textarea>
  257. * @param rows - the default number of rows for this <textarea>
  258. */
  259. this.getTextInput = function(code,className,rows){
  260. var input = $('<textarea>');
  261. input.attr("cols", 80);
  262. rows = rows || 7;
  263. input.attr("rows", (rows || 7));
  264. input.val(code);
  265. input.attr("class", className || 'code_style');
  266. input.keydown( function(event) {
  267. if( event.keyCode == KEY_RIGHT_ARROW /* This is to simulate a tab press */ ) {
  268. if( currentKeys[ KEY_ALT ] == 1 && currentKeys[ KEY_CTRL ] != 1){
  269. var cursorPos = event.target.selectionStart,
  270. lineStart = input.val().lastIndexOf('\n',cursorPos-1)+1,
  271. tabBy = __TAB_WIDTH - (cursorPos-lineStart)%__TAB_WIDTH,
  272. tab = '';
  273. for(var i=0; i<tabBy; i++) tab += ' ';
  274. input.val(
  275. input.val().substring(0,cursorPos)+tab+
  276. input.val().substring(cursorPos));
  277. input.get(0).setSelectionRange(cursorPos+tabBy,cursorPos+tabBy);
  278. return true;
  279. }
  280. return true;
  281. }
  282. else if (event.keyCode == KEY_ENTER) {
  283. //for single row fields, don't create a new line
  284. if (rows == 1) {
  285. event.preventDefault();
  286. }
  287. }
  288. });
  289. input.keyup(function (event) {
  290. if (event.keyCode == KEY_ENTER) {
  291. //don't send the enter key for multi-line fields
  292. //this closes the window
  293. if (rows > 1) {
  294. event.stopPropagation();
  295. }
  296. }
  297. });
  298. return input;
  299. };
  300. /**
  301. * Constructs a <span> element.
  302. *
  303. * @param text - the default text to be displayed
  304. * @param className - the default class name
  305. */
  306. this.getTextSpan = function(text,className)
  307. {
  308. var span = $('<span>');
  309. span.html( text.replace(/\n/g,'<br/>') );
  310. span.attr("class", className || 'default_style');
  311. return span;
  312. };
  313. /**
  314. * Finds and removes the specified toolbar, if present
  315. *
  316. * @param tb - the toolbar to be removed
  317. */
  318. this.removeToolbar = function(tb){
  319. tb_fixed = tb.replace(/\//g, "\\/");
  320. if( $('#div_toolbar_'+tb_fixed) )
  321. {
  322. //Find the toolbar in the dock bar and remove it
  323. //from the DOM
  324. $("#div_dock").children("div").each( function() {
  325. if( this.id == "div_toolbar_" + tb ){
  326. $(this).remove();
  327. }
  328. });
  329. // Now delete it from the list of loaded toolbars
  330. delete __loadedToolbars[tb];
  331. }
  332. };
  333. /**
  334. * Throw out the current canvas, replace it with a new one.
  335. * 1. Unselect any current selection
  336. * 2. Clear canvas
  337. * 3. Clear icon and edge data
  338. * 4. reset canvas statechart
  339. */
  340. this.resetCanvas = function (){
  341. __select();
  342. __canvas.clear();
  343. __icons = {};
  344. __edges = {};
  345. __canvasBehaviourStatechart.init();
  346. };
  347. /**
  348. * Sets up a model popup window and displays it.
  349. *
  350. * @param elements - DOM elements to be displayed in order
  351. * @param getinput - function that retrieves the desired user-input
  352. * from "elements".
  353. * @param type - Can be __NO_BUTTONS, __ONE_BUTTON,
  354. * or __TWO_BUTTONS and controls the number of displayed buttons
  355. * @param title - the dialog title
  356. * @param callback - called with the result of getinput when the ok
  357. * button is clicked
  358. */
  359. this.setupAndShowDialog = function(elements,getinput,type,title,callback){
  360. // BehaviorManager.handleUserEvent(__EVENT_CANCELED_DIALOG);
  361. var dialog = $('#div_dialog'),
  362. the_id = __dialog_stack.length;
  363. dialog = dialog.clone().attr("id", 'div_dialog_'+the_id);
  364. dim_bg = $('#div_dim_bg'),
  365. div_title = $('<div>');
  366. __dialog_stack.push(dialog);
  367. dialog.appendTo(document.body);
  368. div_title.attr("class", 'dialog_title');
  369. div_title.append(GUIUtils.getTextSpan(title || ''));
  370. dialog.append(div_title);
  371. elements.forEach(
  372. function(e)
  373. {
  374. dialog.append(e);
  375. dialog.append( $('<br>') );
  376. });
  377. dialog.append( $('<br>') );
  378. if( type != __NO_BUTTONS )
  379. {
  380. var ok = $('<button class="okbutton">'); // HUSEYIN-ENTER
  381. ok.click( function(ev) {
  382. if( getinput == undefined )
  383. {
  384. BehaviorManager.handleUserEvent(__EVENT_OKAYED_DIALOG);
  385. if( callback != undefined )
  386. callback();
  387. }
  388. else
  389. {
  390. try{
  391. var input = getinput();
  392. } catch(err) {
  393. console.error('failed to retrieve dialog input :: '+err);
  394. return;
  395. }
  396. input = (utils.isArray(input) ?
  397. input.map(function(i) {return String(i);}) :
  398. input);
  399. BehaviorManager.handleUserEvent(__EVENT_OKAYED_DIALOG);
  400. callback(input);
  401. }
  402. });
  403. ok.attr("id", "dialog_btn");
  404. ok.html('ok');
  405. dialog.append(ok);
  406. }
  407. if( type == __TWO_BUTTONS )
  408. {
  409. var cancel = $('<button>');
  410. cancel.click(function(ev) {
  411. BehaviorManager.handleUserEvent(__EVENT_CANCELED_DIALOG);
  412. });
  413. cancel.html('cancel');
  414. dialog.append(cancel);
  415. }
  416. dialog.keydown(function (event) {
  417. //tab through the fields
  418. if (event.key == "Tab") {
  419. try {
  420. if (title.startsWith("edit")) {
  421. let table_row = event.target.parentElement.parentElement;
  422. let nextEle = table_row.nextElementSibling;
  423. // at end, so select first element
  424. if (nextEle == null) {
  425. nextEle = table_row.parentElement.firstElementChild;
  426. }
  427. //get the actual text field
  428. let nextField = nextEle.children[1].children[0];
  429. nextField.focus();
  430. } else if (title.startsWith("Parameters")) { //try to tab through workflow parameters
  431. let element = event.target;
  432. //get the next element
  433. //skips the <br>s and labels
  434. let nextEle = element.nextElementSibling.nextElementSibling
  435. .nextElementSibling.nextElementSibling;
  436. //cycle back around to the top
  437. if (nextEle.nodeName == "BUTTON") {
  438. nextEle = nextEle.parentElement.children[3];
  439. }
  440. nextEle.focus();
  441. }
  442. } catch (err) { //catch errors if something was unexpected
  443. console.debug("Tab event failed: " + err);
  444. }
  445. }
  446. });
  447. BehaviorManager.setActiveBehaviourStatechart(__SC_DIALOG);
  448. BehaviorManager.handleUserEvent(__EVENT_SHOW_DIALOG);
  449. };
  450. /*
  451. NOTE:: the sortedButtonNames() function sorts icon definition metamodels and
  452. button models s.t. buttons appear in an order that reflects their
  453. model... to be perfectly clean, this should be absorbed into icon
  454. definition and button model compilers, but since button models aren't
  455. compiled, it was simpler to just let this relatively simple logic live
  456. here
  457. */
  458. /**
  459. * Sets up and shows a toolbar according to the given button model or metamodel.
  460. *
  461. * This method:
  462. * 1. Removes any old instance of the toolbar if it currently exists
  463. * 2. Creates a <div> to hold the buttons
  464. * 3. Creates each button and appends it to the <div>
  465. * 4. Add the <div> to the dock
  466. * 5. Map the toolbar to its data
  467. *
  468. * @param tb - the toolbar to setup and show
  469. * @param data - the data to bind the toolbar to
  470. * @param type - the toolbar type, can be __BUTTON_TOOLBAR or __METAMODEL_TOOLBAR
  471. */
  472. this.setupAndShowToolbar = function(tb,data,type)
  473. {
  474. let imgSrc =
  475. function (name) {
  476. return (type == __BUTTON_TOOLBAR ?
  477. tb.substring(0, tb.lastIndexOf('/') + 1) + name + '.icon.png' :
  478. '/Formalisms/default.icon.png');
  479. };
  480. let className =
  481. function () {
  482. return (type == __BUTTON_TOOLBAR ? 'toolbar_bm' : 'toolbar_mm');
  483. };
  484. let buttons =
  485. (type == __BUTTON_TOOLBAR ? data.asm.nodes : data.types);
  486. let sortedButtons =
  487. function () {
  488. return (type == __BUTTON_TOOLBAR ?
  489. /* sort button names according to their position in their
  490. associated buttons model */
  491. utils.sortDict(data.csm.nodes,
  492. function (b1, b2) {
  493. var pos1 = b1['position']['value'],
  494. pos2 = b2['position']['value'];
  495. if ((pos1[1] < pos2[1]) ||
  496. (pos1[1] == pos2[1] && pos1[0] < pos2[0]))
  497. return -1;
  498. return 1;
  499. }) :
  500. utils.sortDict(data.types,
  501. /* sort type names according to their IconIcon's position in
  502. the associated icon definition model */
  503. function (b1, b2) {
  504. var pos1 = undefined,
  505. pos2 = undefined;
  506. b1.some(function (attr) {
  507. if (attr['name'] == 'position')
  508. pos1 = attr['default'];
  509. return pos1;
  510. });
  511. b2.some(function (attr) {
  512. if (attr['name'] == 'position')
  513. pos2 = attr['default'];
  514. return pos2;
  515. });
  516. if ((pos1[1] < pos2[1]) ||
  517. (pos1[1] == pos2[1] && pos1[0] < pos2[0]))
  518. return -1;
  519. return 1;
  520. }));
  521. };
  522. let createButton =
  523. function (name, tooltip, code) {
  524. var div = $('<div>'),
  525. img = $('<img>');
  526. div.addClass('toolbar_button');
  527. div.attr("id", tb + '/' + name);
  528. div.attr("title", tooltip);
  529. div.click(function (ev) {
  530. var res = HttpUtils.safeEval(code);
  531. if (res['$uerr'])
  532. WindowManagement.openDialog(
  533. _ERROR,
  534. 'unexpected error in button code ::\n ' + res['$uerr']);
  535. else if (res['$err'])
  536. WindowManagement.openDialog(
  537. _ERROR,
  538. 'error in button code ::\n ' + res['$err']);
  539. });
  540. var url = HttpUtils.url(imgSrc(name), __NO_WID);
  541. img.attr("src", url);
  542. //handle missing icon
  543. let defaultUrl = HttpUtils.url("/Formalisms/default.icon.png");
  544. let missingMsg = "Warning: The icon \"" + url + "\" is missing! The default icon has been used.";
  545. let onerrorStr = "this.onerror = ''; this.src = '" + defaultUrl + "'; console.log('" + missingMsg + "');";
  546. img.attr('onerror', onerrorStr);
  547. div.append(img);
  548. return div;
  549. };
  550. GUIUtils.removeToolbar(tb);
  551. var tb_div = $('<div>');
  552. tb_div.attr("id", 'div_toolbar_' + tb);
  553. tb_div.attr("class", className() + ' toolbar unselectable');
  554. tb_div.attr("title", tb);
  555. // record whether this toolbar has buttons
  556. let has_buttons = false;
  557. sortedButtons().forEach(
  558. function (b) {
  559. if (type == __METAMODEL_TOOLBAR && b.match(/(.*)Link$/))
  560. return;
  561. has_buttons = true;
  562. var spc1 = $('<span>'),
  563. spc2 = $('<span>');
  564. // spc1.className = spc2.className = 'toolbar_space';
  565. spc1.attr("class", "toolbar_space");
  566. spc2.attr("class", "toolbar_space");
  567. tb_div.append(spc1);
  568. if (type == __BUTTON_TOOLBAR)
  569. tb_div.append(
  570. createButton(
  571. buttons[b]['name']['value'],
  572. buttons[b]['tooltip']['value'],
  573. buttons[b]['code']['value']));
  574. else if ((matches = b.match(/(.*)Icon/)))
  575. tb_div.append(
  576. createButton(
  577. b,
  578. 'create instance(s) of ' + b.match(/(.*)Icon/)[1],
  579. '_setTypeToCreate("' +
  580. tb.substring(0, tb.length - '.metamodel'.length) +
  581. '/' + b + '");'));
  582. tb_div.append(spc2);
  583. });
  584. // print an informative message if no buttons were loaded
  585. if (! has_buttons){
  586. console.log("Warning: Toolbar '" + tb + "' was loaded, but there are no buttons. This may be due to the toolbar only containing associations or abstract classes.");
  587. }
  588. if (tb_div.children().length == 0)
  589. tb_div.append(GUIUtils.getTextSpan(tb, 'toolbar_alt'));
  590. //get the toolbar
  591. let dock = $('#div_dock');
  592. //create an array and add the new toolbar
  593. let items = Array.from(dock[0].childNodes);
  594. items.push(tb_div[0]);
  595. //sort the dock
  596. items.sort(function (a, b) {
  597. //main menu comes first
  598. if (a.id.includes("MainMenu")) {
  599. return -1;
  600. }
  601. if (b.id.includes("MainMenu")) {
  602. return 1;
  603. }
  604. //toolbars come first
  605. if (a.id.includes("Toolbars") && !(b.id.includes("Toolbars"))) {
  606. return -1;
  607. }
  608. if (b.id.includes("Toolbars") && !(a.id.includes("Toolbars"))) {
  609. return 1;
  610. }
  611. //any other kind of buttons come next
  612. if (a.id.includes(".buttons.model") && !(b.id.includes(".buttons.model"))) {
  613. return -1;
  614. }
  615. if (b.id.includes(".buttons.model") && !(a.id.includes(".buttons.model"))) {
  616. return 1;
  617. }
  618. //otherwise, sort by name
  619. return a.id == b.id ? 0 : (a.id > b.id ? 1 : -1);
  620. });
  621. //add the elements back into the dock
  622. for (let i = 0; i < items.length; ++i) {
  623. dock.append(items[i]);
  624. }
  625. __loadedToolbars[tb] = data;
  626. };
  627. return this;
  628. }();