PageRenderTime 72ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/secured/phpDocumentor/HTML_TreeMenu-1.1.2/TreeMenu.js

http://oregon-caspages.googlecode.com/
JavaScript | 640 lines | 319 code | 91 blank | 230 comment | 119 complexity | caef1ee4a4e4b6203c9b64f18ecee2cf MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, AGPL-3.0
  1. // +-----------------------------------------------------------------------+
  2. // | Copyright (c) 2002, Richard Heyes, Harald Radi |
  3. // | All rights reserved. |
  4. // | |
  5. // | Redistribution and use in source and binary forms, with or without |
  6. // | modification, are permitted provided that the following conditions |
  7. // | are met: |
  8. // | |
  9. // | o Redistributions of source code must retain the above copyright |
  10. // | notice, this list of conditions and the following disclaimer. |
  11. // | o Redistributions in binary form must reproduce the above copyright |
  12. // | notice, this list of conditions and the following disclaimer in the |
  13. // | documentation and/or other materials provided with the distribution.|
  14. // | o The names of the authors may not be used to endorse or promote |
  15. // | products derived from this software without specific prior written |
  16. // | permission. |
  17. // | |
  18. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
  19. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
  20. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  21. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
  22. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  23. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
  24. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  25. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  26. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
  27. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  28. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
  29. // | |
  30. // +-----------------------------------------------------------------------+
  31. // | Author: Richard Heyes <richard@phpguru.org> |
  32. // | Harald Radi <harald.radi@nme.at> |
  33. // +-----------------------------------------------------------------------+
  34. //
  35. // $Id: TreeMenu.js,v 1.1 2005/10/17 18:36:53 jeichorn Exp $
  36. /**
  37. * TreeMenu class
  38. */
  39. function TreeMenu(iconpath, myname, linkTarget, defaultClass, usePersistence, noTopLevelImages)
  40. {
  41. // Properties
  42. this.iconpath = iconpath;
  43. this.myname = myname;
  44. this.linkTarget = linkTarget;
  45. this.defaultClass = defaultClass;
  46. this.usePersistence = usePersistence;
  47. this.noTopLevelImages = noTopLevelImages;
  48. this.n = new Array();
  49. this.nodeRefs = new Array();
  50. this.branches = new Array();
  51. this.branchStatus = new Array();
  52. this.layerRelations = new Array();
  53. this.childParents = new Array();
  54. this.cookieStatuses = new Array();
  55. this.preloadImages();
  56. }
  57. /**
  58. * Adds a node to the tree
  59. */
  60. TreeMenu.prototype.addItem = function (newNode)
  61. {
  62. newIndex = this.n.length;
  63. this.n[newIndex] = newNode;
  64. return this.n[newIndex];
  65. }
  66. /**
  67. * Preload images hack for Mozilla
  68. */
  69. TreeMenu.prototype.preloadImages = function ()
  70. {
  71. var plustop = new Image; plustop.src = this.iconpath + '/plustop.gif';
  72. var plusbottom = new Image; plusbottom.src = this.iconpath + '/plusbottom.gif';
  73. var plus = new Image; plus.src = this.iconpath + '/plus.gif';
  74. var minustop = new Image; minustop.src = this.iconpath + '/minustop.gif';
  75. var minusbottom = new Image; minusbottom.src = this.iconpath + '/minusbottom.gif';
  76. var minus = new Image; minus.src = this.iconpath + '/minus.gif';
  77. var branchtop = new Image; branchtop.src = this.iconpath + '/branchtop.gif';
  78. var branchbottom = new Image; branchbottom.src = this.iconpath + '/branchbottom.gif';
  79. var branch = new Image; branch.src = this.iconpath + '/branch.gif';
  80. var linebottom = new Image; linebottom.src = this.iconpath + '/linebottom.gif';
  81. var line = new Image; line.src = this.iconpath + '/line.gif';
  82. }
  83. /**
  84. * Main function that draws the menu and assigns it
  85. * to the layer (or document.write()s it)
  86. */
  87. TreeMenu.prototype.drawMenu = function ()// OPTIONAL ARGS: nodes = [], level = [], prepend = '', expanded = false, visbility = 'inline', parentLayerID = null
  88. {
  89. /**
  90. * Necessary variables
  91. */
  92. var output = '';
  93. var modifier = '';
  94. var layerID = '';
  95. var parentLayerID = '';
  96. /**
  97. * Parse any optional arguments
  98. */
  99. var nodes = arguments[0] ? arguments[0] : this.n
  100. var level = arguments[1] ? arguments[1] : [];
  101. var prepend = arguments[2] ? arguments[2] : '';
  102. var expanded = arguments[3] ? arguments[3] : false;
  103. var visibility = arguments[4] ? arguments[4] : 'inline';
  104. var parentLayerID = arguments[5] ? arguments[5] : null;
  105. var currentlevel = level.length;
  106. for (var i=0; i<nodes.length; i++) {
  107. level[currentlevel] = i+1;
  108. layerID = this.myname + '_' + 'node_' + this.implode('_', level);
  109. /**
  110. * Store this object in the nodeRefs array
  111. */
  112. this.nodeRefs[layerID] = nodes[i];
  113. /**
  114. * Store the child/parent relationship
  115. */
  116. this.childParents[layerID] = parentLayerID;
  117. /**
  118. * Gif modifier
  119. */
  120. if (i == 0 && parentLayerID == null) {
  121. modifier = nodes.length > 1 ? "top" : 'single';
  122. } else if(i == (nodes.length-1)) {
  123. modifier = "bottom";
  124. } else {
  125. modifier = "";
  126. }
  127. /**
  128. * Single root branch is always expanded
  129. */
  130. if (!this.doesMenu() || (parentLayerID == null && (nodes.length == 1 || this.noTopLevelImages))) {
  131. expanded = true;
  132. } else if (nodes[i].expanded) {
  133. expanded = true;
  134. } else {
  135. expanded = false;
  136. }
  137. /**
  138. * Make sure visibility is correct based on parent status
  139. */
  140. visibility = this.checkParentVisibility(layerID) ? visibility : 'none';
  141. /**
  142. * Setup branch status and build an indexed array
  143. * of branch layer ids
  144. */
  145. if (nodes[i].n.length > 0) {
  146. this.branchStatus[layerID] = expanded;
  147. this.branches[this.branches.length] = layerID;
  148. }
  149. /**
  150. * Setup toggle relationship
  151. */
  152. if (!this.layerRelations[parentLayerID]) {
  153. this.layerRelations[parentLayerID] = new Array();
  154. }
  155. this.layerRelations[parentLayerID][this.layerRelations[parentLayerID].length] = layerID;
  156. /**
  157. * Branch images
  158. */
  159. var gifname = nodes[i].n.length && this.doesMenu() && nodes[i].isDynamic ? (expanded ? 'minus' : 'plus') : 'branch';
  160. var iconimg = nodes[i].icon ? this.stringFormat('<img src="{0}/{1}" width="20" height="20" align="top">', this.iconpath, nodes[i].icon) : '';
  161. /**
  162. * Add event handlers
  163. */
  164. var eventHandlers = "";
  165. for (j in nodes[i].events) {
  166. eventHandlers += this.stringFormat('{0}="{1}" ', j, nodes[i].events[j]);
  167. }
  168. /**
  169. * Build the html to write to the document
  170. * IMPORTANT:
  171. * document.write()ing the string: '<div style="display:...' will screw up nn4.x
  172. */
  173. var layerTag = this.doesMenu() ? this.stringFormat('<div id="{0}" style="display: {1}" class="{2}">', layerID, visibility, (nodes[i].cssClass ? nodes[i].cssClass : this.defaultClass)) : this.stringFormat('<div class="{0}">', '');
  174. var onMDown = this.doesMenu() && nodes[i].n.length && nodes[i].isDynamic ? this.stringFormat('onmousedown="{0}.toggleBranch(\'{1}\', true)" style="cursor: pointer; cursor: hand"', this.myname, layerID) : '';
  175. var imgTag = this.stringFormat('<img src="{0}/{1}{2}.gif" width="20" height="20" align="top" border="0" name="img_{3}" {4}>', this.iconpath, gifname, modifier, layerID, onMDown);
  176. var linkStart = nodes[i].link ? this.stringFormat('<a href="{0}" target="{1}">', nodes[i].link, this.linkTarget) : '';
  177. var linkEnd = nodes[i].link ? '</a>' : '';
  178. output = this.stringFormat('{0}<nobr>{1}{2}{3}{4}<span {5}>{6}</span>{7}</nobr><br></div>',
  179. layerTag,
  180. prepend,
  181. parentLayerID == null && (nodes.length == 1 || this.noTopLevelImages) ? '' : imgTag,
  182. iconimg,
  183. linkStart,
  184. eventHandlers,
  185. nodes[i].title,
  186. linkEnd);
  187. /**
  188. * Write out the HTML. Uses document.write for speed over layers and
  189. * innerHTML. This however means no dynamic adding/removing nodes on
  190. * the client side. This could be conditional I guess if dynamic
  191. * adding/removing is required.
  192. */
  193. document.write(output + "\r\n");
  194. /**
  195. * Traverse sub nodes ?
  196. */
  197. if (nodes[i].n.length) {
  198. /**
  199. * Determine what to prepend. If there is only one root
  200. * node then the prepend to pass to children is nothing.
  201. * Otherwise it depends on where we are in the tree.
  202. */
  203. if (parentLayerID == null && (nodes.length == 1 || this.noTopLevelImages)) {
  204. var newPrepend = '';
  205. } else if (i < (nodes.length - 1)) {
  206. var newPrepend = prepend + this.stringFormat('<img src="{0}/line.gif" width="20" height="20" align="top">', this.iconpath);
  207. } else {
  208. var newPrepend = prepend + this.stringFormat('<img src="{0}/linebottom.gif" width="20" height="20" align="top">', this.iconpath);
  209. }
  210. this.drawMenu(nodes[i].n,
  211. level,
  212. newPrepend,
  213. nodes[i].expanded,
  214. expanded ? 'inline' : 'none',
  215. layerID);
  216. }
  217. }
  218. }
  219. /**
  220. * Toggles a branches visible status. Called from resetBranches()
  221. * and also when a +/- graphic is clicked.
  222. */
  223. TreeMenu.prototype.toggleBranch = function (layerID, updateStatus) // OPTIONAL ARGS: fireEvents = true
  224. {
  225. var currentDisplay = this.getLayer(layerID).style.display;
  226. var newDisplay = (this.branchStatus[layerID] && currentDisplay == 'inline') ? 'none' : 'inline';
  227. var fireEvents = arguments[2] != null ? arguments[2] : true;
  228. for (var i=0; i<this.layerRelations[layerID].length; i++) {
  229. if (this.branchStatus[this.layerRelations[layerID][i]]) {
  230. this.toggleBranch(this.layerRelations[layerID][i], false);
  231. }
  232. this.getLayer(this.layerRelations[layerID][i]).style.display = newDisplay;
  233. }
  234. if (updateStatus) {
  235. this.branchStatus[layerID] = !this.branchStatus[layerID];
  236. /**
  237. * Persistence
  238. */
  239. if (this.doesPersistence() && !arguments[2] && this.usePersistence) {
  240. this.setExpandedStatusForCookie(layerID, this.branchStatus[layerID]);
  241. }
  242. /**
  243. * Fire custom events
  244. */
  245. if (fireEvents) {
  246. nodeObject = this.nodeRefs[layerID];
  247. if (nodeObject.ontoggle != null) {
  248. eval(nodeObject.ontoggle);
  249. }
  250. if (newDisplay == 'none' && nodeObject.oncollapse != null) {
  251. eval(nodeObject.oncollapse);
  252. } else if (newDisplay == 'inline' && nodeObject.onexpand != null){
  253. eval(nodeObject.onexpand);
  254. }
  255. }
  256. // Swap image
  257. this.swapImage(layerID);
  258. }
  259. }
  260. /**
  261. * Swaps the plus/minus branch images
  262. */
  263. TreeMenu.prototype.swapImage = function (layerID)
  264. {
  265. imgSrc = document.images['img_' + layerID].src;
  266. re = /^(.*)(plus|minus)(bottom|top|single)?.gif$/
  267. if (matches = imgSrc.match(re)) {
  268. document.images['img_' + layerID].src = this.stringFormat('{0}{1}{2}{3}',
  269. matches[1],
  270. matches[2] == 'plus' ? 'minus' : 'plus',
  271. matches[3] ? matches[3] : '',
  272. '.gif');
  273. }
  274. }
  275. /**
  276. * Can the browser handle the dynamic menu?
  277. */
  278. TreeMenu.prototype.doesMenu = function ()
  279. {
  280. return (is_ie4up || is_nav6up || is_gecko);
  281. }
  282. /**
  283. * Can the browser handle save the branch status
  284. */
  285. TreeMenu.prototype.doesPersistence = function ()
  286. {
  287. return (is_ie4up || is_gecko || is_nav6up);
  288. }
  289. /**
  290. * Returns the appropriate layer accessor
  291. */
  292. TreeMenu.prototype.getLayer = function (layerID)
  293. {
  294. if (is_ie4) {
  295. return document.all(layerID);
  296. } else if (document.getElementById(layerID)) {
  297. return document.getElementById(layerID);
  298. } else if (document.all(layerID)) {
  299. return document.all(layerID);
  300. }
  301. }
  302. /**
  303. * Save the status of the layer
  304. */
  305. TreeMenu.prototype.setExpandedStatusForCookie = function (layerID, expanded)
  306. {
  307. this.cookieStatuses[layerID] = expanded;
  308. this.saveCookie();
  309. }
  310. /**
  311. * Load the status of the layer
  312. */
  313. TreeMenu.prototype.getExpandedStatusFromCookie = function (layerID)
  314. {
  315. if (this.cookieStatuses[layerID]) {
  316. return this.cookieStatuses[layerID];
  317. }
  318. return false;
  319. }
  320. /**
  321. * Saves the cookie that holds which branches are expanded.
  322. * Only saves the details of the branches which are expanded.
  323. */
  324. TreeMenu.prototype.saveCookie = function ()
  325. {
  326. var cookieString = new Array();
  327. for (var i in this.cookieStatuses) {
  328. if (this.cookieStatuses[i] == true) {
  329. cookieString[cookieString.length] = i;
  330. }
  331. }
  332. document.cookie = 'TreeMenuBranchStatus=' + cookieString.join(':');
  333. }
  334. /**
  335. * Reads cookie parses it for status info and
  336. * stores that info in the class member.
  337. */
  338. TreeMenu.prototype.loadCookie = function ()
  339. {
  340. var cookie = document.cookie.split('; ');
  341. for (var i=0; i < cookie.length; i++) {
  342. var crumb = cookie[i].split('=');
  343. if ('TreeMenuBranchStatus' == crumb[0] && crumb[1]) {
  344. var expandedBranches = crumb[1].split(':');
  345. for (var j=0; j<expandedBranches.length; j++) {
  346. this.cookieStatuses[expandedBranches[j]] = true;
  347. }
  348. }
  349. }
  350. }
  351. /**
  352. * Reset branch status
  353. */
  354. TreeMenu.prototype.resetBranches = function ()
  355. {
  356. if (!this.doesPersistence()) {
  357. return false;
  358. }
  359. this.loadCookie();
  360. for (var i=0; i<this.branches.length; i++) {
  361. var status = this.getExpandedStatusFromCookie(this.branches[i]);
  362. // Only update if it's supposed to be expanded and it's not already
  363. if (status == true && this.branchStatus[this.branches[i]] != true) {
  364. if (this.checkParentVisibility(this.branches[i])) {
  365. this.toggleBranch(this.branches[i], true, false);
  366. } else {
  367. this.branchStatus[this.branches[i]] = true;
  368. this.swapImage(this.branches[i]);
  369. }
  370. }
  371. }
  372. }
  373. /**
  374. * Checks whether a branch should be open
  375. * or not based on its parents' status
  376. */
  377. TreeMenu.prototype.checkParentVisibility = function (layerID)
  378. {
  379. if (this.in_array(this.childParents[layerID], this.branches)
  380. && this.branchStatus[this.childParents[layerID]]
  381. && this.checkParentVisibility(this.childParents[layerID]) ) {
  382. return true;
  383. } else if (this.childParents[layerID] == null) {
  384. return true;
  385. }
  386. return false;
  387. }
  388. /**
  389. * New C# style string formatter
  390. */
  391. TreeMenu.prototype.stringFormat = function (strInput)
  392. {
  393. var idx = 0;
  394. for (var i=1; i<arguments.length; i++) {
  395. while ((idx = strInput.indexOf('{' + (i - 1) + '}', idx)) != -1) {
  396. strInput = strInput.substring(0, idx) + arguments[i] + strInput.substr(idx + 3);
  397. }
  398. }
  399. return strInput;
  400. }
  401. /**
  402. * Also much adored, the PHP implode() function
  403. */
  404. TreeMenu.prototype.implode = function (seperator, input)
  405. {
  406. var output = '';
  407. for (var i=0; i<input.length; i++) {
  408. if (i == 0) {
  409. output += input[i];
  410. } else {
  411. output += seperator + input[i];
  412. }
  413. }
  414. return output;
  415. }
  416. /**
  417. * Aah, all the old favourites are coming out...
  418. */
  419. TreeMenu.prototype.in_array = function (item, arr)
  420. {
  421. for (var i=0; i<arr.length; i++) {
  422. if (arr[i] == item) {
  423. return true;
  424. }
  425. }
  426. return false;
  427. }
  428. /**
  429. * TreeNode Class
  430. */
  431. function TreeNode(title, icon, link, expanded, isDynamic, cssClass)
  432. {
  433. this.title = title;
  434. this.icon = icon;
  435. this.link = link;
  436. this.expanded = expanded;
  437. this.isDynamic = isDynamic;
  438. this.cssClass = cssClass;
  439. this.n = new Array();
  440. this.events = new Array();
  441. this.handlers = null;
  442. this.oncollapse = null;
  443. this.onexpand = null;
  444. this.ontoggle = null;
  445. }
  446. /**
  447. * Adds a node to an already existing node
  448. */
  449. TreeNode.prototype.addItem = function (newNode)
  450. {
  451. newIndex = this.n.length;
  452. this.n[newIndex] = newNode;
  453. return this.n[newIndex];
  454. }
  455. /**
  456. * Sets an event for this particular node
  457. */
  458. TreeNode.prototype.setEvent = function (eventName, eventHandler)
  459. {
  460. switch (eventName.toLowerCase()) {
  461. case 'onexpand':
  462. this.onexpand = eventHandler;
  463. break;
  464. case 'oncollapse':
  465. this.oncollapse = eventHandler;
  466. break;
  467. case 'ontoggle':
  468. this.ontoggle = eventHandler;
  469. break;
  470. default:
  471. this.events[eventName] = eventHandler;
  472. }
  473. }
  474. /**
  475. * That's the end of the tree classes. What follows is
  476. * the browser detection code.
  477. */
  478. //<!--
  479. // Ultimate client-side JavaScript client sniff. Version 3.03
  480. // (C) Netscape Communications 1999-2001. Permission granted to reuse and distribute.
  481. // Revised 17 May 99 to add is_nav5up and is_ie5up (see below).
  482. // Revised 20 Dec 00 to add is_gecko and change is_nav5up to is_nav6up
  483. // also added support for IE5.5 Opera4&5 HotJava3 AOLTV
  484. // Revised 22 Feb 01 to correct Javascript Detection for IE 5.x, Opera 4,
  485. // correct Opera 5 detection
  486. // add support for winME and win2k
  487. // synch with browser-type-oo.js
  488. // Revised 26 Mar 01 to correct Opera detection
  489. // Revised 02 Oct 01 to add IE6 detection
  490. // Everything you always wanted to know about your JavaScript client
  491. // but were afraid to ask. Creates "is_" variables indicating:
  492. // (1) browser vendor:
  493. // is_nav, is_ie, is_opera, is_hotjava, is_webtv, is_TVNavigator, is_AOLTV
  494. // (2) browser version number:
  495. // is_major (integer indicating major version number: 2, 3, 4 ...)
  496. // is_minor (float indicating full version number: 2.02, 3.01, 4.04 ...)
  497. // (3) browser vendor AND major version number
  498. // is_nav2, is_nav3, is_nav4, is_nav4up, is_nav6, is_nav6up, is_gecko, is_ie3,
  499. // is_ie4, is_ie4up, is_ie5, is_ie5up, is_ie5_5, is_ie5_5up, is_ie6, is_ie6up, is_hotjava3, is_hotjava3up,
  500. // is_opera2, is_opera3, is_opera4, is_opera5, is_opera5up
  501. // (4) JavaScript version number:
  502. // is_js (float indicating full JavaScript version number: 1, 1.1, 1.2 ...)
  503. // (5) OS platform and version:
  504. // is_win, is_win16, is_win32, is_win31, is_win95, is_winnt, is_win98, is_winme, is_win2k
  505. // is_os2
  506. // is_mac, is_mac68k, is_macppc
  507. // is_unix
  508. // is_sun, is_sun4, is_sun5, is_suni86
  509. // is_irix, is_irix5, is_irix6
  510. // is_hpux, is_hpux9, is_hpux10
  511. // is_aix, is_aix1, is_aix2, is_aix3, is_aix4
  512. // is_linux, is_sco, is_unixware, is_mpras, is_reliant
  513. // is_dec, is_sinix, is_freebsd, is_bsd
  514. // is_vms
  515. //
  516. // See http://www.it97.de/JavaScript/JS_tutorial/bstat/navobj.html and
  517. // http://www.it97.de/JavaScript/JS_tutorial/bstat/Browseraol.html
  518. // for detailed lists of userAgent strings.
  519. //
  520. // Note: you don't want your Nav4 or IE4 code to "turn off" or
  521. // stop working when new versions of browsers are released, so
  522. // in conditional code forks, use is_ie5up ("IE 5.0 or greater")
  523. // is_opera5up ("Opera 5.0 or greater") instead of is_ie5 or is_opera5
  524. // to check version in code which you want to work on future
  525. // versions.
  526. /**
  527. * Severly curtailed all this as only certain elements
  528. * are required by TreeMenu, specifically:
  529. * o is_ie4up
  530. * o is_nav6up
  531. * o is_gecko
  532. */
  533. // convert all characters to lowercase to simplify testing
  534. var agt=navigator.userAgent.toLowerCase();
  535. // *** BROWSER VERSION ***
  536. // Note: On IE5, these return 4, so use is_ie5up to detect IE5.
  537. var is_major = parseInt(navigator.appVersion);
  538. var is_minor = parseFloat(navigator.appVersion);
  539. // Note: Opera and WebTV spoof Navigator. We do strict client detection.
  540. // If you want to allow spoofing, take out the tests for opera and webtv.
  541. var is_nav = ((agt.indexOf('mozilla')!=-1) && (agt.indexOf('spoofer')==-1)
  542. && (agt.indexOf('compatible') == -1) && (agt.indexOf('opera')==-1)
  543. && (agt.indexOf('webtv')==-1) && (agt.indexOf('hotjava')==-1));
  544. var is_nav6up = (is_nav && (is_major >= 5));
  545. var is_gecko = (agt.indexOf('gecko') != -1);
  546. var is_ie = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1));
  547. var is_ie4 = (is_ie && (is_major == 4) && (agt.indexOf("msie 4")!=-1) );
  548. var is_ie4up = (is_ie && (is_major >= 4));
  549. //--> end hide JavaScript