www/include/scripts/main-2.js

Code
Comments
Other
Rev Date Author Line
6155 05 Oct 12 nicklas 1 /* $Id $
6155 05 Oct 12 nicklas 2   ------------------------------------------------------------------
6155 05 Oct 12 nicklas 3   Copyright (C) 2012 Nicklas Nordborg
6155 05 Oct 12 nicklas 4
6155 05 Oct 12 nicklas 5   This file is part of BASE - BioArray Software Environment.
6155 05 Oct 12 nicklas 6   Available at http://base.thep.lu.se/
6155 05 Oct 12 nicklas 7
6155 05 Oct 12 nicklas 8   BASE is free software; you can redistribute it and/or
6155 05 Oct 12 nicklas 9   modify it under the terms of the GNU General Public License
6155 05 Oct 12 nicklas 10   as published by the Free Software Foundation; either version 3
6155 05 Oct 12 nicklas 11   of the License, or (at your option) any later version.
6155 05 Oct 12 nicklas 12
6155 05 Oct 12 nicklas 13   BASE is distributed in the hope that it will be useful,
6155 05 Oct 12 nicklas 14   but WITHOUT ANY WARRANTY; without even the implied warranty of
6155 05 Oct 12 nicklas 15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6155 05 Oct 12 nicklas 16   GNU General Public License for more details.
6155 05 Oct 12 nicklas 17
6155 05 Oct 12 nicklas 18   You should have received a copy of the GNU General Public License
6155 05 Oct 12 nicklas 19   along with BASE. If not, see <http://www.gnu.org/licenses/>.
6155 05 Oct 12 nicklas 20   ------------------------------------------------------------------
6155 05 Oct 12 nicklas 21
6155 05 Oct 12 nicklas 22   @author Nicklas
6155 05 Oct 12 nicklas 23   @since 3.3
6155 05 Oct 12 nicklas 24 */
7419 03 Nov 17 nicklas 25 'use strict';
6155 05 Oct 12 nicklas 26
6155 05 Oct 12 nicklas 27 var App = function()
6155 05 Oct 12 nicklas 28 {
6161 10 Oct 12 nicklas 29   var topWin = null;
6155 05 Oct 12 nicklas 30   var app = {};
6540 26 Sep 14 nicklas 31   var internal = {};
6540 26 Sep 14 nicklas 32
6540 26 Sep 14 nicklas 33   var root;
6540 26 Sep 14 nicklas 34   var sessionId;
6540 26 Sep 14 nicklas 35   var storage;
6540 26 Sep 14 nicklas 36   var rememberDialogPositions;
6540 26 Sep 14 nicklas 37
6155 05 Oct 12 nicklas 38   /**
6155 05 Oct 12 nicklas 39     Get the root URL path of this BASE installation. Always ends with a '/' (eg. /base/)
6155 05 Oct 12 nicklas 40   */
6155 05 Oct 12 nicklas 41   app.getRoot = function()
6155 05 Oct 12 nicklas 42   {
6540 26 Sep 14 nicklas 43     return root;
6155 05 Oct 12 nicklas 44   }
6155 05 Oct 12 nicklas 45   
6155 05 Oct 12 nicklas 46   /**
6155 05 Oct 12 nicklas 47     Get the session-id of the current session or an empty string if no session has been started yet
6155 05 Oct 12 nicklas 48     (should only happen on the top frameset of the login page)
6155 05 Oct 12 nicklas 49   */
6155 05 Oct 12 nicklas 50   app.getSessionId = function()
6155 05 Oct 12 nicklas 51   {
6540 26 Sep 14 nicklas 52     return sessionId;
6155 05 Oct 12 nicklas 53   }
6155 05 Oct 12 nicklas 54
6155 05 Oct 12 nicklas 55   /**
6180 22 Oct 12 nicklas 56     Get the id of the currently logged in user. Return null if the user has not 
6180 22 Oct 12 nicklas 57     logged in yet.
6180 22 Oct 12 nicklas 58   */
6180 22 Oct 12 nicklas 59   app.getLoggedInUserId = function()
6180 22 Oct 12 nicklas 60   {
6520 18 Aug 14 nicklas 61     return Data.int(document.body, 'user-id');
6180 22 Oct 12 nicklas 62   }
6180 22 Oct 12 nicklas 63
6180 22 Oct 12 nicklas 64   /**
6180 22 Oct 12 nicklas 65     Get the id of the currently active project. Return null if no project is active.
6180 22 Oct 12 nicklas 66   */
6180 22 Oct 12 nicklas 67   app.getActiveProjectId = function()
6180 22 Oct 12 nicklas 68   {
6520 18 Aug 14 nicklas 69     return Data.int(document.body, 'project-id');
6180 22 Oct 12 nicklas 70   }
6180 22 Oct 12 nicklas 71
6180 22 Oct 12 nicklas 72   /**
6155 05 Oct 12 nicklas 73     Get the scaling factor to use in the gui for popup windows (and other things that may
6155 05 Oct 12 nicklas 74     require scaling to fit).
6155 05 Oct 12 nicklas 75   */
6155 05 Oct 12 nicklas 76   app.getScale = function()
6155 05 Oct 12 nicklas 77   {
6576 22 Oct 14 nicklas 78     return Data.float(document.body, 'gui-scale');
6155 05 Oct 12 nicklas 79   }
6155 05 Oct 12 nicklas 80   
6155 05 Oct 12 nicklas 81   /**
6155 05 Oct 12 nicklas 82     Get the max length of an URL that is allowed in a GET request. URLs that
6155 05 Oct 12 nicklas 83     are longer than this should be rewritten to a POST request instead. A
6155 05 Oct 12 nicklas 84     value of '0' disable the rewriting.
6155 05 Oct 12 nicklas 85   */
6155 05 Oct 12 nicklas 86   app.getMaxUrlLength = function()
6155 05 Oct 12 nicklas 87   {
6520 18 Aug 14 nicklas 88     return Data.int(document.body, 'max-url-length', 0);
6155 05 Oct 12 nicklas 89   }
6155 05 Oct 12 nicklas 90   
6520 18 Aug 14 nicklas 91   /**
6520 18 Aug 14 nicklas 92     Check if popup dialog positions should be remembered or not.
6520 18 Aug 14 nicklas 93    */
6520 18 Aug 14 nicklas 94   app.rememberDialogPositions = function()
6520 18 Aug 14 nicklas 95   {
6540 26 Sep 14 nicklas 96     return rememberDialogPositions;
6520 18 Aug 14 nicklas 97   }
6520 18 Aug 14 nicklas 98   
6168 15 Oct 12 nicklas 99   /*
6168 15 Oct 12 nicklas 100     Check if the URL is longer than the configured max URL length.
6168 15 Oct 12 nicklas 101   */
6168 15 Oct 12 nicklas 102   app.isTooLongUrl = function(url)
6168 15 Oct 12 nicklas 103   {
6168 15 Oct 12 nicklas 104     var max = app.getMaxUrlLength();
6168 15 Oct 12 nicklas 105     return max > 0 && url.length > max;
6168 15 Oct 12 nicklas 106   }
6168 15 Oct 12 nicklas 107   
6168 15 Oct 12 nicklas 108   /*
6168 15 Oct 12 nicklas 109     Safely set the location of a document as if
6168 15 Oct 12 nicklas 110     win.document.location.href = url or win.document.replace(url)
6168 15 Oct 12 nicklas 111     had been used. If the url is longer than specified by max 
6168 15 Oct 12 nicklas 112     length the url will be re-written to a <form> using hidden 
6168 15 Oct 12 nicklas 113     fields to submit the query values.
6168 15 Oct 12 nicklas 114     @param url The url to set
6168 15 Oct 12 nicklas 115     @param win The window to set the location on, if not given,
6168 15 Oct 12 nicklas 116       the current window is assumed
6168 15 Oct 12 nicklas 117     @param replace If given, use location.replace() instead of 
6168 15 Oct 12 nicklas 118       location.href (only if url is not too long)
6168 15 Oct 12 nicklas 119   */
6168 15 Oct 12 nicklas 120   app.safeSetLocation = function(url, win, replace)
6168 15 Oct 12 nicklas 121   {
6168 15 Oct 12 nicklas 122     if (!url) return;
6168 15 Oct 12 nicklas 123     if (!win) win = window;
6168 15 Oct 12 nicklas 124     var postform;
6168 15 Oct 12 nicklas 125     if (app.isTooLongUrl(url))
6168 15 Oct 12 nicklas 126     {
6168 15 Oct 12 nicklas 127       postform = Forms.convertUrlToForm(url, win);
6168 15 Oct 12 nicklas 128     }
6168 15 Oct 12 nicklas 129     if (postform)
6168 15 Oct 12 nicklas 130     {
6168 15 Oct 12 nicklas 131       document.body.appendChild(postform);
6168 15 Oct 12 nicklas 132       postform.submit();
6168 15 Oct 12 nicklas 133       document.body.removeChild(postform);
6168 15 Oct 12 nicklas 134     }
6168 15 Oct 12 nicklas 135     else if (replace)
6168 15 Oct 12 nicklas 136     {
6168 15 Oct 12 nicklas 137       win.document.location.replace(url);
6168 15 Oct 12 nicklas 138     }
6168 15 Oct 12 nicklas 139     else
6168 15 Oct 12 nicklas 140     {
6168 15 Oct 12 nicklas 141       win.document.location.href = url;
6168 15 Oct 12 nicklas 142     }
6168 15 Oct 12 nicklas 143   }
6168 15 Oct 12 nicklas 144   
6156 05 Oct 12 nicklas 145   /**
6161 10 Oct 12 nicklas 146     Get the top window in the BASE application. 
6161 10 Oct 12 nicklas 147   */
6161 10 Oct 12 nicklas 148   app.topWindow = function()
6161 10 Oct 12 nicklas 149   {
6161 10 Oct 12 nicklas 150     if (!topWin)
6161 10 Oct 12 nicklas 151     {
6161 10 Oct 12 nicklas 152       var topWin = window.top;
6161 10 Oct 12 nicklas 153       while (topWin.opener)
6161 10 Oct 12 nicklas 154       {
6161 10 Oct 12 nicklas 155         topWin = topWin.opener.top;
6161 10 Oct 12 nicklas 156       }
6161 10 Oct 12 nicklas 157     }
6161 10 Oct 12 nicklas 158     return topWin;
6161 10 Oct 12 nicklas 159   }
6161 10 Oct 12 nicklas 160
6166 11 Oct 12 nicklas 161   /**
6166 11 Oct 12 nicklas 162     Close the current popup window.
6166 11 Oct 12 nicklas 163   */
7419 03 Nov 17 nicklas 164   app.closeWindow = function(event)
6166 11 Oct 12 nicklas 165   {
6166 11 Oct 12 nicklas 166     window.top.close();
6166 11 Oct 12 nicklas 167   }
6161 10 Oct 12 nicklas 168   
6161 10 Oct 12 nicklas 169   /**
6308 20 Aug 13 nicklas 170     Reload the current window.
6308 20 Aug 13 nicklas 171   */
7540 03 Dec 18 nicklas 172   app.reloadWindow = function(wait)
6308 20 Aug 13 nicklas 173   {
7540 03 Dec 18 nicklas 174     if (wait) 
7540 03 Dec 18 nicklas 175     {
7540 03 Dec 18 nicklas 176       setTimeout(App.reloadWindow, 250);
7540 03 Dec 18 nicklas 177       return;
7540 03 Dec 18 nicklas 178     }
6308 20 Aug 13 nicklas 179     window.location.reload();
6308 20 Aug 13 nicklas 180   }
6308 20 Aug 13 nicklas 181   
6308 20 Aug 13 nicklas 182   /**
6168 15 Oct 12 nicklas 183     Get the screen coordinates, width and height of
6168 15 Oct 12 nicklas 184     the given window. The result is returned as an
6168 15 Oct 12 nicklas 185     object with 'top', 'left', 'width' and 'height'
6168 15 Oct 12 nicklas 186     properties.
6168 15 Oct 12 nicklas 187     @param win The window to get the position for or
6168 15 Oct 12 nicklas 188       null to use the current window
6168 15 Oct 12 nicklas 189   */
6168 15 Oct 12 nicklas 190   app.getWindowPosition = function(win)
6168 15 Oct 12 nicklas 191   {
6168 15 Oct 12 nicklas 192     if (!win) win = window;
6168 15 Oct 12 nicklas 193     var pos = {};
6168 15 Oct 12 nicklas 194     pos.top = win.screenY;
6168 15 Oct 12 nicklas 195     pos.left = win.screenX;
6168 15 Oct 12 nicklas 196     pos.width = win.innerWidth;
6168 15 Oct 12 nicklas 197     pos.height = win.innerHeight;
6168 15 Oct 12 nicklas 198     return pos;
6168 15 Oct 12 nicklas 199   }
6168 15 Oct 12 nicklas 200     
6168 15 Oct 12 nicklas 201   /**
6156 05 Oct 12 nicklas 202     Get a reference to the localStorage implementation.
6156 05 Oct 12 nicklas 203     Return null if no storage is available.
6156 05 Oct 12 nicklas 204   */
6156 05 Oct 12 nicklas 205   app.localStorage = function()
6156 05 Oct 12 nicklas 206   {
6540 26 Sep 14 nicklas 207     return storage;
6540 26 Sep 14 nicklas 208   }
6540 26 Sep 14 nicklas 209   
6540 26 Sep 14 nicklas 210   app.setLocal = function(key, value)
6540 26 Sep 14 nicklas 211   {
6540 26 Sep 14 nicklas 212     if (storage == null) return;
6540 26 Sep 14 nicklas 213     storage.setItem(root+':'+key, value);
6540 26 Sep 14 nicklas 214   }
6540 26 Sep 14 nicklas 215   
6540 26 Sep 14 nicklas 216   app.getLocal = function(key, defaultValue)
6540 26 Sep 14 nicklas 217   {
6540 26 Sep 14 nicklas 218     if (storage == null) return defaultValue || null;
6540 26 Sep 14 nicklas 219     return storage.getItem(root+':'+key) || defaultValue || null;
6540 26 Sep 14 nicklas 220   }
6540 26 Sep 14 nicklas 221   
6540 26 Sep 14 nicklas 222   app.removeLocal = function(key)
6540 26 Sep 14 nicklas 223   {
6540 26 Sep 14 nicklas 224     if (storage == null) return;
6540 26 Sep 14 nicklas 225     storage.removeItem(root+':'+key);
6540 26 Sep 14 nicklas 226   }
6540 26 Sep 14 nicklas 227   
6540 26 Sep 14 nicklas 228   /**
6540 26 Sep 14 nicklas 229     Write a message to the browser debug console.
6540 26 Sep 14 nicklas 230   */
6540 26 Sep 14 nicklas 231   app.debug = function(message)
6540 26 Sep 14 nicklas 232   {
6540 26 Sep 14 nicklas 233     console.log(message);
6540 26 Sep 14 nicklas 234   }
6540 26 Sep 14 nicklas 235   
6576 22 Oct 14 nicklas 236   /**
6576 22 Oct 14 nicklas 237     Log usage of a deprecated method. A call to this method
6576 22 Oct 14 nicklas 238     should be included as the first line in a deprecated method.
6576 22 Oct 14 nicklas 239     @param method The name of the method (eg. Main.openPopup())
6576 22 Oct 14 nicklas 240     @param voidVersion The version number of BASE in which the method will be removed
6576 22 Oct 14 nicklas 241    */
6576 22 Oct 14 nicklas 242   app.deprecatedMethod = function(method, voidVersion)
6576 22 Oct 14 nicklas 243   {
6576 22 Oct 14 nicklas 244     var msg = 'Deprecated method "' + method + '" will be removed in BASE ' + voidVersion;
6576 22 Oct 14 nicklas 245     console.error(msg);
6576 22 Oct 14 nicklas 246   }
6576 22 Oct 14 nicklas 247   
6540 26 Sep 14 nicklas 248   internal.initApp = function()
6540 26 Sep 14 nicklas 249   {
7428 23 Nov 17 nicklas 250     if (document.body == null) return;
7428 23 Nov 17 nicklas 251     
6540 26 Sep 14 nicklas 252     root = Data.get(document.body, 'app-root');
6540 26 Sep 14 nicklas 253     sessionId = Data.get(document.body, 'session-id', '');
6540 26 Sep 14 nicklas 254
6156 05 Oct 12 nicklas 255     try
6156 05 Oct 12 nicklas 256     {
6156 05 Oct 12 nicklas 257       storage = window.localStorage;
6156 05 Oct 12 nicklas 258     }
6156 05 Oct 12 nicklas 259     catch (exception)
6156 05 Oct 12 nicklas 260     {
6156 05 Oct 12 nicklas 261       // Firefox throws a SecurityException if the 'ask every time' option is
6156 05 Oct 12 nicklas 262       // selected for cookies, so we have to catch this situation
6156 05 Oct 12 nicklas 263     }
6540 26 Sep 14 nicklas 264     
6540 26 Sep 14 nicklas 265     rememberDialogPositions = Data.int(document.body, 'remember-dialog-positions', 1) && storage != null;
6156 05 Oct 12 nicklas 266   }
6540 26 Sep 14 nicklas 267   document.addEventListener('DOMContentLoaded', internal.initApp, false);
6399 24 Jan 14 nicklas 268   
6155 05 Oct 12 nicklas 269   return app;
6155 05 Oct 12 nicklas 270 }();
6155 05 Oct 12 nicklas 271
6155 05 Oct 12 nicklas 272
6155 05 Oct 12 nicklas 273 var Doc = function()
6155 05 Oct 12 nicklas 274 {
6160 10 Oct 12 nicklas 275   var elementInitializers = [];
6160 10 Oct 12 nicklas 276   var onLoadFunctions = [];
6160 10 Oct 12 nicklas 277   var finalizers = [];
6157 08 Oct 12 nicklas 278   
6155 05 Oct 12 nicklas 279   var doc = {};
6157 08 Oct 12 nicklas 280   var internal = {};
6155 05 Oct 12 nicklas 281   
6155 05 Oct 12 nicklas 282   /**
6155 05 Oct 12 nicklas 283     Get the page id of the current page if it has been defined.
6155 05 Oct 12 nicklas 284     Eg. the id attribute of the root <html> tag.
6155 05 Oct 12 nicklas 285   */
6155 05 Oct 12 nicklas 286   doc.getPageId = function()
6155 05 Oct 12 nicklas 287   {
6155 05 Oct 12 nicklas 288     return document.documentElement.id;
6155 05 Oct 12 nicklas 289   }
6155 05 Oct 12 nicklas 290   
6155 05 Oct 12 nicklas 291   /**
6155 05 Oct 12 nicklas 292     Get a document element reference. If the parameter is already a document node it
6155 05 Oct 12 nicklas 293     is returned, otherwise try to find the given element by id.
6155 05 Oct 12 nicklas 294     @param elementOrId A document element or the id of an element
6155 05 Oct 12 nicklas 295   */
6155 05 Oct 12 nicklas 296   doc.element = function(elementOrId)
6155 05 Oct 12 nicklas 297   {
6834 08 Apr 15 nicklas 298     var e = elementOrId && (elementOrId.nodeType || elementOrId.window) ? elementOrId : document.getElementById(elementOrId);
6155 05 Oct 12 nicklas 299     return e;
6155 05 Oct 12 nicklas 300   }
6155 05 Oct 12 nicklas 301   
6155 05 Oct 12 nicklas 302   /**
6389 07 Jan 14 nicklas 303     Get a document form reference. If the parameter is already a document node it
6389 07 Jan 14 nicklas 304     is returned, otherwise try to find the given element by id or name.
6389 07 Jan 14 nicklas 305     @param formNameOrId A form element or the id/name of a form element
6389 07 Jan 14 nicklas 306   */
6389 07 Jan 14 nicklas 307   doc.form = function(formNameOrId)
6389 07 Jan 14 nicklas 308   {
6389 07 Jan 14 nicklas 309     var frm = doc.element(formNameOrId);
6389 07 Jan 14 nicklas 310     if (!frm) frm = document.forms[formNameOrId];
7419 03 Nov 17 nicklas 311     return frm || null;
6389 07 Jan 14 nicklas 312   }
6389 07 Jan 14 nicklas 313   
6389 07 Jan 14 nicklas 314   /**
6400 27 Jan 14 nicklas 315     Get the absolute coordinates of the given element.
6400 27 Jan 14 nicklas 316     Returns an object with the following properties:
6400 27 Jan 14 nicklas 317     left, top, width, height, right, bottom
6400 27 Jan 14 nicklas 318   */
6400 27 Jan 14 nicklas 319   doc.getElementPosition = function(element)
6400 27 Jan 14 nicklas 320   {
6400 27 Jan 14 nicklas 321     element = Doc.element(element);
6400 27 Jan 14 nicklas 322
6400 27 Jan 14 nicklas 323     var nextNode = element;
6400 27 Jan 14 nicklas 324     var offsetTrail = element;
6400 27 Jan 14 nicklas 325     var offsetTop = 0;
6400 27 Jan 14 nicklas 326     var offsetLeft = 0;
6400 27 Jan 14 nicklas 327     var offsetWidth = element.offsetWidth;
6400 27 Jan 14 nicklas 328     var offsetHeight = element.offsetHeight;
6400 27 Jan 14 nicklas 329     
6400 27 Jan 14 nicklas 330     while (nextNode)
6400 27 Jan 14 nicklas 331     {
6400 27 Jan 14 nicklas 332       if (nextNode == offsetTrail)
6400 27 Jan 14 nicklas 333       {
6400 27 Jan 14 nicklas 334         if (offsetTrail.offsetTop) offsetTop += offsetTrail.offsetTop;
6400 27 Jan 14 nicklas 335         if (offsetTrail.offsetLeft) offsetLeft += offsetTrail.offsetLeft;
6400 27 Jan 14 nicklas 336         offsetTrail = offsetTrail.offsetParent;
6400 27 Jan 14 nicklas 337       }
6400 27 Jan 14 nicklas 338       if (nextNode != element && nextNode.scrollTop)
6400 27 Jan 14 nicklas 339       {
6400 27 Jan 14 nicklas 340         offsetTop -= nextNode.scrollTop;
6400 27 Jan 14 nicklas 341       }
6400 27 Jan 14 nicklas 342       nextNode = nextNode.parentNode;
6400 27 Jan 14 nicklas 343     }
6400 27 Jan 14 nicklas 344     
6400 27 Jan 14 nicklas 345     var pos = {
6400 27 Jan 14 nicklas 346       left: offsetLeft, 
6400 27 Jan 14 nicklas 347       top: offsetTop, 
6400 27 Jan 14 nicklas 348       width: offsetWidth, 
6400 27 Jan 14 nicklas 349       height: offsetHeight, 
6400 27 Jan 14 nicklas 350       right: offsetLeft+offsetWidth,
6400 27 Jan 14 nicklas 351       bottom: offsetTop+offsetHeight
6400 27 Jan 14 nicklas 352     };
6400 27 Jan 14 nicklas 353     return pos;
6400 27 Jan 14 nicklas 354   }
6400 27 Jan 14 nicklas 355   
6400 27 Jan 14 nicklas 356   /**
6342 01 Nov 13 nicklas 357     Show an element in the document by setting a proper value for
6342 01 Nov 13 nicklas 358     it's display property.
6342 01 Nov 13 nicklas 359     @param element A document element or the id of an element
6342 01 Nov 13 nicklas 360     @param display The value for the display property, if not set
6342 01 Nov 13 nicklas 361       automatically select a value base on the node type
6342 01 Nov 13 nicklas 362   */
6342 01 Nov 13 nicklas 363   doc.show = function(element, display)
6342 01 Nov 13 nicklas 364   {
6342 01 Nov 13 nicklas 365     element = Doc.element(element);
6342 01 Nov 13 nicklas 366     if (element) 
6342 01 Nov 13 nicklas 367     {
6342 01 Nov 13 nicklas 368       if (!display)
6342 01 Nov 13 nicklas 369       {
6342 01 Nov 13 nicklas 370         if (element.nodeName == 'TR')
6342 01 Nov 13 nicklas 371         {
6342 01 Nov 13 nicklas 372           display='table-row';
6342 01 Nov 13 nicklas 373         }
6342 01 Nov 13 nicklas 374         else if (element.nodeName == 'TBODY')
6342 01 Nov 13 nicklas 375         {
6342 01 Nov 13 nicklas 376           display='table-row-group';
6342 01 Nov 13 nicklas 377         }
6342 01 Nov 13 nicklas 378         else if (element.nodeName == 'TD')
6342 01 Nov 13 nicklas 379         {
6342 01 Nov 13 nicklas 380           display = 'table-cell';
6342 01 Nov 13 nicklas 381         }
6342 01 Nov 13 nicklas 382         else 
6342 01 Nov 13 nicklas 383         {
6342 01 Nov 13 nicklas 384           display = 'block';
6342 01 Nov 13 nicklas 385         }
6342 01 Nov 13 nicklas 386       }
6342 01 Nov 13 nicklas 387       element.style.display = display;
6342 01 Nov 13 nicklas 388     }
6342 01 Nov 13 nicklas 389   }
6342 01 Nov 13 nicklas 390
6342 01 Nov 13 nicklas 391   
6342 01 Nov 13 nicklas 392   /**
6342 01 Nov 13 nicklas 393     Hides an element in the document by setting it's display property
6342 01 Nov 13 nicklas 394     to 'none'.
6342 01 Nov 13 nicklas 395     @param element A document element or the id of an element
6342 01 Nov 13 nicklas 396   */
6342 01 Nov 13 nicklas 397   doc.hide = function(element)
6342 01 Nov 13 nicklas 398   {
6342 01 Nov 13 nicklas 399     element = Doc.element(element);
6342 01 Nov 13 nicklas 400     if (element) element.style.display='none';
6342 01 Nov 13 nicklas 401   }
6342 01 Nov 13 nicklas 402   
6342 01 Nov 13 nicklas 403   /**
6342 01 Nov 13 nicklas 404     Show or hide an element in the document.
6342 01 Nov 13 nicklas 405     @param element A document element or the id of an element
6342 01 Nov 13 nicklas 406     @param showIfTrue If not set, automatically hide visible elements and show
6342 01 Nov 13 nicklas 407       hidden elements, otherwise show/hide depending on the setting
6342 01 Nov 13 nicklas 408   */
6342 01 Nov 13 nicklas 409   doc.showHide = function(element, showIfTrue)
6342 01 Nov 13 nicklas 410   {
6342 01 Nov 13 nicklas 411     element = Doc.element(element);
6342 01 Nov 13 nicklas 412     if (element) 
6342 01 Nov 13 nicklas 413     {
6342 01 Nov 13 nicklas 414       if (showIfTrue == undefined) 
6342 01 Nov 13 nicklas 415       {
6342 01 Nov 13 nicklas 416         showIfTrue = element.style.display == 'none';
6342 01 Nov 13 nicklas 417       }
6342 01 Nov 13 nicklas 418       if (showIfTrue)
6342 01 Nov 13 nicklas 419       {
6342 01 Nov 13 nicklas 420         doc.show(element);
6342 01 Nov 13 nicklas 421       }
6342 01 Nov 13 nicklas 422       else
6342 01 Nov 13 nicklas 423       {
6342 01 Nov 13 nicklas 424         doc.hide(element);
6342 01 Nov 13 nicklas 425       }
6342 01 Nov 13 nicklas 426     }
6342 01 Nov 13 nicklas 427   }
6342 01 Nov 13 nicklas 428
6342 01 Nov 13 nicklas 429   /**
6342 01 Nov 13 nicklas 430     Add a class to an element. 
6342 01 Nov 13 nicklas 431     @param element A document element or the id of an element
6342 01 Nov 13 nicklas 432     @param className The class to add
6342 01 Nov 13 nicklas 433   */
6342 01 Nov 13 nicklas 434   doc.addClass = function(element, className)
6342 01 Nov 13 nicklas 435   {
6342 01 Nov 13 nicklas 436     element = Doc.element(element);
6342 01 Nov 13 nicklas 437     if (element)
6342 01 Nov 13 nicklas 438     {
6723 12 Feb 15 nicklas 439       element.classList.add(className);
6342 01 Nov 13 nicklas 440     }
6342 01 Nov 13 nicklas 441   }
6342 01 Nov 13 nicklas 442   
6342 01 Nov 13 nicklas 443   /**
6342 01 Nov 13 nicklas 444     Remove a class from an element. 
6342 01 Nov 13 nicklas 445     @param element A document element or the id of an element
6342 01 Nov 13 nicklas 446     @param className The class to remove
6342 01 Nov 13 nicklas 447   */
6342 01 Nov 13 nicklas 448   doc.removeClass = function(element, className)
6342 01 Nov 13 nicklas 449   {
6342 01 Nov 13 nicklas 450     element = Doc.element(element);
6342 01 Nov 13 nicklas 451     if (element)
6342 01 Nov 13 nicklas 452     {
6723 12 Feb 15 nicklas 453       element.classList.remove(className);
6342 01 Nov 13 nicklas 454     }
6342 01 Nov 13 nicklas 455   }
6342 01 Nov 13 nicklas 456   
6342 01 Nov 13 nicklas 457   /**
6342 01 Nov 13 nicklas 458     Add or remove a class from an element depending on
6342 01 Nov 13 nicklas 459     if it exists or not.
6342 01 Nov 13 nicklas 460     @param element A document element or the id of an element
6342 01 Nov 13 nicklas 461     @param className The class to add/remove
6342 01 Nov 13 nicklas 462     @param addIfTrue If not set, automatically add/remove the class if
6342 01 Nov 13 nicklas 463       depending on if it exists or not, otherwise add/remove depending 
6342 01 Nov 13 nicklas 464       on the setting
6342 01 Nov 13 nicklas 465    */
6342 01 Nov 13 nicklas 466   doc.addOrRemoveClass = function(element, className, addIfTrue)
6342 01 Nov 13 nicklas 467   {
6342 01 Nov 13 nicklas 468     element = Doc.element(element);
6342 01 Nov 13 nicklas 469     if (element)
6342 01 Nov 13 nicklas 470     {
6342 01 Nov 13 nicklas 471       if (addIfTrue == undefined)
6342 01 Nov 13 nicklas 472       {
6723 12 Feb 15 nicklas 473         element.classList.toggle(className);
6342 01 Nov 13 nicklas 474       }
6723 12 Feb 15 nicklas 475       else if (addIfTrue)
6342 01 Nov 13 nicklas 476       {
6723 12 Feb 15 nicklas 477         element.classList.add(className);
6342 01 Nov 13 nicklas 478       }
6342 01 Nov 13 nicklas 479       else
6342 01 Nov 13 nicklas 480       {
6723 12 Feb 15 nicklas 481         element.classList.remove(className);
6342 01 Nov 13 nicklas 482       }
6342 01 Nov 13 nicklas 483     }
6342 01 Nov 13 nicklas 484   }
6342 01 Nov 13 nicklas 485     
6342 01 Nov 13 nicklas 486   /**
6155 05 Oct 12 nicklas 487     Check if the element is disabled (eg. it's className contains the class 'disabled').
6155 05 Oct 12 nicklas 488     @param element A document element or the id of an element
6155 05 Oct 12 nicklas 489   */
6155 05 Oct 12 nicklas 490   doc.isDisabled = function(element)
6155 05 Oct 12 nicklas 491   {
6155 05 Oct 12 nicklas 492     element = Doc.element(element);
6723 12 Feb 15 nicklas 493     return element.classList.contains('disabled');
6155 05 Oct 12 nicklas 494   }
6155 05 Oct 12 nicklas 495   
6157 08 Oct 12 nicklas 496   /**
6160 10 Oct 12 nicklas 497     Add an element initialization callback to the document. Once the document has finished
6160 10 Oct 12 nicklas 498     loading, the document is scanned for all elements marked with the class name
6160 10 Oct 12 nicklas 499     'auto-init'. Then, for each found element, the callbacks are invoked so that
6160 10 Oct 12 nicklas 500     they can perform their actions on it. Typically, the callbacks are used to add
6160 10 Oct 12 nicklas 501     (click) event handlers to elements. 
6160 10 Oct 12 nicklas 502     
6160 10 Oct 12 nicklas 503     Note that all registered callbacks are invoked for all elements. Each callback should
6160 10 Oct 12 nicklas 504     check that the 'auto-init' parameter has the expected value before proceeding.
6160 10 Oct 12 nicklas 505     
6160 10 Oct 12 nicklas 506     @param f The element initialization callback should be a function that accept two parameters.
6160 10 Oct 12 nicklas 507       The first is a reference to the element, and the second the string value
6160 10 Oct 12 nicklas 508       for the 'data-auto-init' attribute on the element (or null if no such attribute exists)
6160 10 Oct 12 nicklas 509   */
6160 10 Oct 12 nicklas 510   doc.addElementInitializer = function(f)
6160 10 Oct 12 nicklas 511   {
6160 10 Oct 12 nicklas 512     if (!f || !f.call) return; // 'f' is not a function
6160 10 Oct 12 nicklas 513     elementInitializers[elementInitializers.length] = f;
6160 10 Oct 12 nicklas 514   }
6160 10 Oct 12 nicklas 515   
6160 10 Oct 12 nicklas 516   /**
7769 07 Feb 20 nicklas 517     Run all 'auto-init' event handlers on child
7769 07 Feb 20 nicklas 518     elements to the given element.
7769 07 Feb 20 nicklas 519   */
7769 07 Feb 20 nicklas 520   doc.autoInitElements = function(element)
7769 07 Feb 20 nicklas 521   {
7769 07 Feb 20 nicklas 522     element = Doc.element(element);
7769 07 Feb 20 nicklas 523     internal.invokeElementInitializers(element);
7769 07 Feb 20 nicklas 524   }
7769 07 Feb 20 nicklas 525   
7769 07 Feb 20 nicklas 526   /**
6157 08 Oct 12 nicklas 527     Register a function that should be called whenever the document DOM is
6160 10 Oct 12 nicklas 528     ready. Eg. when the 'DOMContentLoaded' event is fired. The functions are
6160 10 Oct 12 nicklas 529     called in the order they have been registered, but after all
6160 10 Oct 12 nicklas 530     element initializers and before all finalizers.
6157 08 Oct 12 nicklas 531   */
6157 08 Oct 12 nicklas 532   doc.onLoad = function(f)
6157 08 Oct 12 nicklas 533   {
6157 08 Oct 12 nicklas 534     if (!f || !f.call) return; // 'f' is not a function
6160 10 Oct 12 nicklas 535     onLoadFunctions[onLoadFunctions.length] = f;
6157 08 Oct 12 nicklas 536   }
6157 08 Oct 12 nicklas 537   
6157 08 Oct 12 nicklas 538   /**
6160 10 Oct 12 nicklas 539     Register a function that should be called to finalize the document initialization. 
6160 10 Oct 12 nicklas 540     Eg. when the 'DOMContentLoaded' event is fired. The functions are
6160 10 Oct 12 nicklas 541     called in the order they have been registered, but after all
6160 10 Oct 12 nicklas 542     element initializers and onload functions.
6157 08 Oct 12 nicklas 543   */
6160 10 Oct 12 nicklas 544   doc.addFinalizer = function(f)
6157 08 Oct 12 nicklas 545   {
6157 08 Oct 12 nicklas 546     if (!f || !f.call) return; // 'f' is not a function
6160 10 Oct 12 nicklas 547     finalizers[finalizers.length] = f;
6157 08 Oct 12 nicklas 548   }
6161 10 Oct 12 nicklas 549     
6157 08 Oct 12 nicklas 550   /**
6162 10 Oct 12 nicklas 551     Invoke all element initializers, onload functions and document finalizers
6160 10 Oct 12 nicklas 552     in this order.
6157 08 Oct 12 nicklas 553   */
6160 10 Oct 12 nicklas 554   internal.initDocument = function()
6157 08 Oct 12 nicklas 555   {
7428 23 Nov 17 nicklas 556     if (document.body == null) return;
6162 10 Oct 12 nicklas 557     internal.blockFormSubmission();
7769 07 Feb 20 nicklas 558     internal.invokeElementInitializers(document);
6160 10 Oct 12 nicklas 559     internal.invokeOnLoadFunctions();
6160 10 Oct 12 nicklas 560     internal.invokeFinalizers();
6540 26 Sep 14 nicklas 561     Events.addEventHandler('page-reload', 'click', function() { location.reload(true) });
6192 31 Oct 12 nicklas 562     var alertMsg = Data.get(document.body, 'alert-message');
6192 31 Oct 12 nicklas 563     if (alertMsg) alert(alertMsg);
6160 10 Oct 12 nicklas 564   }
6160 10 Oct 12 nicklas 565   
6160 10 Oct 12 nicklas 566   document.addEventListener('DOMContentLoaded', internal.initDocument, false);
6160 10 Oct 12 nicklas 567   
6160 10 Oct 12 nicklas 568   /**
6162 10 Oct 12 nicklas 569     Initializer function that adds an event handler to all 'form' elements
6162 10 Oct 12 nicklas 570     that block automatic submission (eg. by pressing ENTER) of the form.
6162 10 Oct 12 nicklas 571     Forms should be submitted by calling form.submit().
6162 10 Oct 12 nicklas 572   */
6162 10 Oct 12 nicklas 573   internal.blockFormSubmission = function()
6162 10 Oct 12 nicklas 574   {
6162 10 Oct 12 nicklas 575     var allForms = document.getElementsByTagName('form');
6162 10 Oct 12 nicklas 576     for (var i = 0; i < allForms.length; i++)
6162 10 Oct 12 nicklas 577     {
6162 10 Oct 12 nicklas 578       allForms[i].addEventListener('submit', Events.preventDefault, false);
6162 10 Oct 12 nicklas 579     }
6162 10 Oct 12 nicklas 580   }
6162 10 Oct 12 nicklas 581     
6162 10 Oct 12 nicklas 582   /**
6160 10 Oct 12 nicklas 583     Scan the document for all elements marked with the class name 'auto-init'.
6160 10 Oct 12 nicklas 584     For each found element, call the registered element initialization callbacks.
6160 10 Oct 12 nicklas 585   */
7769 07 Feb 20 nicklas 586   internal.invokeElementInitializers = function(rootElement)
6160 10 Oct 12 nicklas 587   {
7769 07 Feb 20 nicklas 588     if (!rootElement || elementInitializers.length == 0) return;
7769 07 Feb 20 nicklas 589     var autoElements = rootElement.getElementsByClassName('auto-init');
6157 08 Oct 12 nicklas 590     for (var i = 0; i < autoElements.length; i++)
6157 08 Oct 12 nicklas 591     {
6157 08 Oct 12 nicklas 592       var element = autoElements[i];
7769 07 Feb 20 nicklas 593       if (!element.autoInitDone)
6157 08 Oct 12 nicklas 594       {
7769 07 Feb 20 nicklas 595         element.autoInitDone = true;
7769 07 Feb 20 nicklas 596         var autoInit = Data.get(element, 'auto-init');
7769 07 Feb 20 nicklas 597         for (var j = 0; j < elementInitializers.length; j++)
6160 10 Oct 12 nicklas 598         {
7769 07 Feb 20 nicklas 599           try
6540 26 Sep 14 nicklas 600           {
7769 07 Feb 20 nicklas 601             elementInitializers[j].call(element, element, autoInit);
6540 26 Sep 14 nicklas 602           }
7769 07 Feb 20 nicklas 603           catch (err)
7769 07 Feb 20 nicklas 604           {
7769 07 Feb 20 nicklas 605             var msg = err;
7769 07 Feb 20 nicklas 606             if (err.toString)
7769 07 Feb 20 nicklas 607             {
7769 07 Feb 20 nicklas 608               msg = err.toString() + '\n' + err.stack;
7769 07 Feb 20 nicklas 609             }
7769 07 Feb 20 nicklas 610             console.error(msg);
7769 07 Feb 20 nicklas 611           }
6160 10 Oct 12 nicklas 612         }
6157 08 Oct 12 nicklas 613       }
6160 10 Oct 12 nicklas 614     }
6157 08 Oct 12 nicklas 615   }
6160 10 Oct 12 nicklas 616
6160 10 Oct 12 nicklas 617   /**
6160 10 Oct 12 nicklas 618     Call all onload functions in the order they were registered.
6160 10 Oct 12 nicklas 619   */
6160 10 Oct 12 nicklas 620   internal.invokeOnLoadFunctions = function()
6160 10 Oct 12 nicklas 621   {
6160 10 Oct 12 nicklas 622     for (var i = 0; i < onLoadFunctions.length; i++)
6160 10 Oct 12 nicklas 623     {
6160 10 Oct 12 nicklas 624       try
6160 10 Oct 12 nicklas 625       {
6160 10 Oct 12 nicklas 626         onLoadFunctions[i].call();
6160 10 Oct 12 nicklas 627       }
6160 10 Oct 12 nicklas 628       catch (err)
6160 10 Oct 12 nicklas 629       {
6540 26 Sep 14 nicklas 630         var msg = err;
6540 26 Sep 14 nicklas 631         if (err.toString)
6540 26 Sep 14 nicklas 632         {
6540 26 Sep 14 nicklas 633           msg = err.toString() + '\n' + err.stack;
6540 26 Sep 14 nicklas 634         }
6540 26 Sep 14 nicklas 635         console.error(msg);
6160 10 Oct 12 nicklas 636       }
6160 10 Oct 12 nicklas 637     }
6160 10 Oct 12 nicklas 638     onLoadFunctions = null;
6160 10 Oct 12 nicklas 639   }
6157 08 Oct 12 nicklas 640   
6160 10 Oct 12 nicklas 641   /**
6160 10 Oct 12 nicklas 642     Call all document finalizers in the order they were registered.
6160 10 Oct 12 nicklas 643   */
6160 10 Oct 12 nicklas 644   internal.invokeFinalizers = function()
6160 10 Oct 12 nicklas 645   {
6160 10 Oct 12 nicklas 646     for (var i = 0; i < finalizers.length; i++)
6160 10 Oct 12 nicklas 647     {
6160 10 Oct 12 nicklas 648       try
6160 10 Oct 12 nicklas 649       {
6160 10 Oct 12 nicklas 650         finalizers[i].call();
6160 10 Oct 12 nicklas 651       }
6160 10 Oct 12 nicklas 652       catch (err)
6160 10 Oct 12 nicklas 653       {
6540 26 Sep 14 nicklas 654         var msg = err;
6540 26 Sep 14 nicklas 655         if (err.toString)
6540 26 Sep 14 nicklas 656         {
6540 26 Sep 14 nicklas 657           msg = err.toString() + '\n' + err.stack;
6540 26 Sep 14 nicklas 658         }
6540 26 Sep 14 nicklas 659         console.error(msg);
6160 10 Oct 12 nicklas 660       }
6160 10 Oct 12 nicklas 661     }
6160 10 Oct 12 nicklas 662     finalizers = null;
6160 10 Oct 12 nicklas 663   }
6160 10 Oct 12 nicklas 664   
6155 05 Oct 12 nicklas 665   return doc;
6155 05 Oct 12 nicklas 666 }();
6155 05 Oct 12 nicklas 667
6155 05 Oct 12 nicklas 668
6155 05 Oct 12 nicklas 669 var Events = function()
6155 05 Oct 12 nicklas 670 {
6155 05 Oct 12 nicklas 671   var events = {};
6155 05 Oct 12 nicklas 672   
6155 05 Oct 12 nicklas 673   /**
6155 05 Oct 12 nicklas 674     Add event handler to the given element.
6155 05 Oct 12 nicklas 675     @param element A document element or the id of an element
6155 05 Oct 12 nicklas 676     @param eventName The name of the event to handle (eg. 'click')
6155 05 Oct 12 nicklas 677     @param handler A function reference that is called for the event
6220 10 Jan 13 nicklas 678     @param attributes Additional data attributes that should be attached to
6220 10 Jan 13 nicklas 679       the target element (see Data.define)
6155 05 Oct 12 nicklas 680   */
6220 10 Jan 13 nicklas 681   events.addEventHandler = function(element, eventName, handler, attributes)
6155 05 Oct 12 nicklas 682   {
6155 05 Oct 12 nicklas 683     element = Doc.element(element);
6155 05 Oct 12 nicklas 684     if (!element) return;
6220 10 Jan 13 nicklas 685     if (attributes) Data.define(element, attributes);
6155 05 Oct 12 nicklas 686     element.addEventListener(eventName, handler, false);
6155 05 Oct 12 nicklas 687   }
6155 05 Oct 12 nicklas 688   
6155 05 Oct 12 nicklas 689   /**
6155 05 Oct 12 nicklas 690     Add an event handler to the given element that, when the element has the
6155 05 Oct 12 nicklas 691     focus, reacts to the 'ENTER' key and sends a 'click' event to the same 
6155 05 Oct 12 nicklas 692     element.
6155 05 Oct 12 nicklas 693     @param element A document element or the id of an element
6155 05 Oct 12 nicklas 694   */
6155 05 Oct 12 nicklas 695   events.enableClickOnEnter = function(element)
6155 05 Oct 12 nicklas 696   {
6155 05 Oct 12 nicklas 697     element = Doc.element(element);
6155 05 Oct 12 nicklas 698     if (!element) return;
6155 05 Oct 12 nicklas 699     element.addEventListener('keypress', Events.handleFocusedKeypress, false);
6155 05 Oct 12 nicklas 700   }
6155 05 Oct 12 nicklas 701   
6155 05 Oct 12 nicklas 702   /**
6167 12 Oct 12 nicklas 703     Add an event handler to the given element that, when the element
6167 12 Oct 12 nicklas 704     has the focus, reacts to the 'ENTER' key and then call the given
6167 12 Oct 12 nicklas 705     callback function. 
6167 12 Oct 12 nicklas 706     @param element A document element or the id of an element
6167 12 Oct 12 nicklas 707     @param callback A function callback
6167 12 Oct 12 nicklas 708   */
6167 12 Oct 12 nicklas 709   events.doOnEnter = function(element, callback)
6167 12 Oct 12 nicklas 710   {
6167 12 Oct 12 nicklas 711     element = Doc.element(element);
6167 12 Oct 12 nicklas 712     if (!element) return;
6167 12 Oct 12 nicklas 713     if (!callback || !callback.call) return; // 'callback' is not a function
6167 12 Oct 12 nicklas 714     
6167 12 Oct 12 nicklas 715     element.onEnterCallback = callback;
6167 12 Oct 12 nicklas 716     element.addEventListener('keypress', Events.handleFocusedKeypress, false);
6167 12 Oct 12 nicklas 717   }
6167 12 Oct 12 nicklas 718   
6167 12 Oct 12 nicklas 719   /**
6155 05 Oct 12 nicklas 720     Handle keypress events on focused elements:
6167 12 Oct 12 nicklas 721     If the 'ENTER' key is pressed:
6167 12 Oct 12 nicklas 722     If the element has a property 'onEnterCallback' that is callable, call that function
6167 12 Oct 12 nicklas 723     with the event as the parameter, otherwise send a 'click' event to the target element
6155 05 Oct 12 nicklas 724   */
6155 05 Oct 12 nicklas 725   events.handleFocusedKeypress = function(event)
6155 05 Oct 12 nicklas 726   {
6155 05 Oct 12 nicklas 727     if (event.keyCode == 13)
6155 05 Oct 12 nicklas 728     {
6155 05 Oct 12 nicklas 729       // ENTER key pressed
7419 03 Nov 17 nicklas 730       var target = event.currentTarget;
6155 05 Oct 12 nicklas 731       if (!target) return;
6155 05 Oct 12 nicklas 732
6167 12 Oct 12 nicklas 733       if (target.onEnterCallback && target.onEnterCallback.call)
6155 05 Oct 12 nicklas 734       {
6167 12 Oct 12 nicklas 735         target.onEnterCallback.call(target, event);
6167 12 Oct 12 nicklas 736       }
7419 03 Nov 17 nicklas 737       else
6167 12 Oct 12 nicklas 738       {
7419 03 Nov 17 nicklas 739         events.sendClickEvent(target, event);
6155 05 Oct 12 nicklas 740       }
6155 05 Oct 12 nicklas 741     }
6155 05 Oct 12 nicklas 742   }
6155 05 Oct 12 nicklas 743
6155 05 Oct 12 nicklas 744   /**
6415 05 Feb 14 nicklas 745     Checks if the target of the element has been disabled. It first try
6415 05 Feb 14 nicklas 746     to check if the CSS property 'pointer-events' is set to 'none' for the
6415 05 Feb 14 nicklas 747     target element. If this is not supported it checks the target element
6415 05 Feb 14 nicklas 748     contains 'disabled' in it's class name.
6415 05 Feb 14 nicklas 749     If the target element is considered disabled, the event.stopPropagation()
6415 05 Feb 14 nicklas 750     function is called.
6415 05 Feb 14 nicklas 751   */
6415 05 Feb 14 nicklas 752   events.stopIfDisabled = function(event)
6415 05 Feb 14 nicklas 753   {
6415 05 Feb 14 nicklas 754     var pointerEvents = null;
6415 05 Feb 14 nicklas 755     var disabled = false;
6415 05 Feb 14 nicklas 756     try
6415 05 Feb 14 nicklas 757     {
6415 05 Feb 14 nicklas 758       pointerEvents = window.getComputedStyle(event.currentTarget).getPropertyValue('pointer-events');
6415 05 Feb 14 nicklas 759     }
6415 05 Feb 14 nicklas 760     catch (e)
6415 05 Feb 14 nicklas 761     {}
6415 05 Feb 14 nicklas 762     if (pointerEvents == null)
6415 05 Feb 14 nicklas 763     {
6723 12 Feb 15 nicklas 764       disabled = event.currentTarget.classList.contains('disabled');
6415 05 Feb 14 nicklas 765     }
6415 05 Feb 14 nicklas 766     else
6415 05 Feb 14 nicklas 767     {
6415 05 Feb 14 nicklas 768       disabled = pointerEvents == 'none';
6415 05 Feb 14 nicklas 769     }
6415 05 Feb 14 nicklas 770     if (disabled) event.stopImmediatePropagation();
6415 05 Feb 14 nicklas 771   }
6415 05 Feb 14 nicklas 772
6415 05 Feb 14 nicklas 773   
6415 05 Feb 14 nicklas 774   /**
6155 05 Oct 12 nicklas 775     Event handler that can be attached to 'keypress' event for 
6155 05 Oct 12 nicklas 776     form input fields to block all keys except numeric (including minus).
6155 05 Oct 12 nicklas 777   */
6155 05 Oct 12 nicklas 778   events.integerOnly = function(event)
6155 05 Oct 12 nicklas 779   {
6576 22 Oct 14 nicklas 780     var charCode = event.charCode;
6155 05 Oct 12 nicklas 781     // <31 for tab, delete, etc. 48-57=key 0-9, 45= '-'
6155 05 Oct 12 nicklas 782     var allow = charCode <= 31 || (charCode >= 48 && charCode <= 57) || charCode == 45;
6155 05 Oct 12 nicklas 783     if (!allow) event.preventDefault();
6155 05 Oct 12 nicklas 784   }
6155 05 Oct 12 nicklas 785   
6166 11 Oct 12 nicklas 786   /*
6166 11 Oct 12 nicklas 787     Event handler that can be attached to 'keypress' event for 
6166 11 Oct 12 nicklas 788     form input fields to block all keys except numeric (including minus, plus, decimal dot and E).
6166 11 Oct 12 nicklas 789   */
6166 11 Oct 12 nicklas 790   events.numberOnly = function(event)
6166 11 Oct 12 nicklas 791   {
6576 22 Oct 14 nicklas 792     var charCode = event.charCode;
6166 11 Oct 12 nicklas 793     // <31 for tab, delete, etc. 48-57=key 0-9, 45= '-', 46='.', 69/101=E/e
6166 11 Oct 12 nicklas 794     var allow = charCode <= 31 || (charCode >= 48 && charCode <= 57) || charCode == 45 || charCode == 46 || charCode == 69 || charCode == 101;
6166 11 Oct 12 nicklas 795     if (!allow) event.preventDefault();
6166 11 Oct 12 nicklas 796   }
6166 11 Oct 12 nicklas 797
6166 11 Oct 12 nicklas 798   
6162 10 Oct 12 nicklas 799   /**
6162 10 Oct 12 nicklas 800     Event handler that can be attached to any event/element that
6162 10 Oct 12 nicklas 801     simply calls event.preventDefault() to stop the default
6162 10 Oct 12 nicklas 802     action.
6162 10 Oct 12 nicklas 803   */
6162 10 Oct 12 nicklas 804   events.preventDefault = function(event)
6162 10 Oct 12 nicklas 805   {
6162 10 Oct 12 nicklas 806     event.preventDefault();
6162 10 Oct 12 nicklas 807   }
6162 10 Oct 12 nicklas 808   
6171 17 Oct 12 nicklas 809   /**
6171 17 Oct 12 nicklas 810     Send an 'onchange' event to the given element.
6171 17 Oct 12 nicklas 811     @param element A document element or the id of an element
6171 17 Oct 12 nicklas 812   */
6171 17 Oct 12 nicklas 813   events.sendChangeEvent = function(element)
6171 17 Oct 12 nicklas 814   {
6171 17 Oct 12 nicklas 815     element = Doc.element(element);
6171 17 Oct 12 nicklas 816     var changeEvent = element.ownerDocument.createEvent('HTMLEvents');
7001 05 Nov 15 nicklas 817     changeEvent.initEvent('change', true, true);
6171 17 Oct 12 nicklas 818     element.dispatchEvent(changeEvent);
6171 17 Oct 12 nicklas 819   }
6171 17 Oct 12 nicklas 820   
7419 03 Nov 17 nicklas 821   /**
7419 03 Nov 17 nicklas 822     Send a 'click' event to the specifiec element, pretending that the
7419 03 Nov 17 nicklas 823     mouse is located in the center of the element. A srcEvent may be given
7419 03 Nov 17 nicklas 824     to set meta key status (CTRL, ALT, SHIFT).
7419 03 Nov 17 nicklas 825   */
7419 03 Nov 17 nicklas 826   events.sendClickEvent = function(element, srcEvent)
7419 03 Nov 17 nicklas 827   {
7419 03 Nov 17 nicklas 828     element = Doc.element(element);
7419 03 Nov 17 nicklas 829
7419 03 Nov 17 nicklas 830     var pos = Doc.getElementPosition(element);
7419 03 Nov 17 nicklas 831     var wPos = App.getWindowPosition();
7419 03 Nov 17 nicklas 832     var clientX = pos.left+pos.width/2;
7419 03 Nov 17 nicklas 833     var clientY = pos.top+pos.height/2;
7419 03 Nov 17 nicklas 834     
7419 03 Nov 17 nicklas 835     if (!srcEvent) 
7419 03 Nov 17 nicklas 836     {
7419 03 Nov 17 nicklas 837       srcEvent = {
7419 03 Nov 17 nicklas 838         'ctrlKey': false, 
7419 03 Nov 17 nicklas 839         'altKey': false, 
7419 03 Nov 17 nicklas 840         'shiftKey': false, 
7419 03 Nov 17 nicklas 841         'metaKey': false
7419 03 Nov 17 nicklas 842       };
7419 03 Nov 17 nicklas 843     }
7419 03 Nov 17 nicklas 844       
7419 03 Nov 17 nicklas 845     var clickEvent = document.createEvent("MouseEvent");
7419 03 Nov 17 nicklas 846     clickEvent.initMouseEvent("click", true, true, window, 1, wPos.left + clientX,  wPos.top + clientY, clientX, clientY, srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey, 0, null);
7419 03 Nov 17 nicklas 847     element.dispatchEvent(clickEvent);
7419 03 Nov 17 nicklas 848   }
6222 14 Jan 13 nicklas 849   
6222 14 Jan 13 nicklas 850   /**
6222 14 Jan 13 nicklas 851     Send a custom event to the given element.
6222 14 Jan 13 nicklas 852     @param element A document element or the id of an element
6222 14 Jan 13 nicklas 853     @param eventType The name of the custom event
6222 14 Jan 13 nicklas 854     @param details An object with detailed data to send with the event
6222 14 Jan 13 nicklas 855   */
6222 14 Jan 13 nicklas 856   events.sendCustomEvent = function(element, eventType, details)
6222 14 Jan 13 nicklas 857   {
6222 14 Jan 13 nicklas 858     element = Doc.element(element);
6222 14 Jan 13 nicklas 859     var customEvent = element.ownerDocument.createEvent('CustomEvent');
6380 16 Dec 13 nicklas 860     customEvent.initCustomEvent(eventType, true, true, details);
6222 14 Jan 13 nicklas 861     element.dispatchEvent(customEvent);
6222 14 Jan 13 nicklas 862   }
6222 14 Jan 13 nicklas 863   
6155 05 Oct 12 nicklas 864   return events;
6155 05 Oct 12 nicklas 865
6155 05 Oct 12 nicklas 866 }();
6155 05 Oct 12 nicklas 867
6155 05 Oct 12 nicklas 868
6155 05 Oct 12 nicklas 869 /**
6155 05 Oct 12 nicklas 870   Encapsulates data handling functions for handling data-* attributes on elements.
6155 05 Oct 12 nicklas 871 */
6155 05 Oct 12 nicklas 872 var Data = function()
6155 05 Oct 12 nicklas 873 {
6155 05 Oct 12 nicklas 874   var data = {};
6155 05 Oct 12 nicklas 875   
6155 05 Oct 12 nicklas 876   /**
6155 05 Oct 12 nicklas 877     Get the value of a 'data-*' attribute for an element. If the attribute
6155 05 Oct 12 nicklas 878     doesn't exists, the default value is returned.
6155 05 Oct 12 nicklas 879     @param element A document element or the id of an element
6155 05 Oct 12 nicklas 880     @param attribute The attribute name (not including the 'data-' prefix)
6155 05 Oct 12 nicklas 881     @param An optional default value
6155 05 Oct 12 nicklas 882   */
6155 05 Oct 12 nicklas 883   data.get = function(element, attribute, defaultValue)
6155 05 Oct 12 nicklas 884   {
6155 05 Oct 12 nicklas 885     element = Doc.element(element);
6155 05 Oct 12 nicklas 886     var value = element.getAttribute('data-'+attribute) || defaultValue;
6155 05 Oct 12 nicklas 887     return value === undefined ? null : value;
6155 05 Oct 12 nicklas 888   }
6155 05 Oct 12 nicklas 889   
6161 10 Oct 12 nicklas 890   /**
6218 19 Dec 12 nicklas 891     Set the value of a 'data-*' attribute for an element.
6218 19 Dec 12 nicklas 892     Existing values are overwritten.
6218 19 Dec 12 nicklas 893     @param element A document element or the id of an element
6218 19 Dec 12 nicklas 894     @param attribute The attribute name (not including the 'data-' prefix)
6218 19 Dec 12 nicklas 895     @param value The value to set
6218 19 Dec 12 nicklas 896   */
6218 19 Dec 12 nicklas 897   data.set = function(element, attribute, value)
6218 19 Dec 12 nicklas 898   {
6218 19 Dec 12 nicklas 899     element = Doc.element(element);
6218 19 Dec 12 nicklas 900     element.setAttribute('data-'+attribute, value);
6218 19 Dec 12 nicklas 901   }
6218 19 Dec 12 nicklas 902   
6218 19 Dec 12 nicklas 903   /**
6218 19 Dec 12 nicklas 904     Define missing 'data-*' attributes for an element. Attributes
6218 19 Dec 12 nicklas 905     that already have a value are not overwritten. 
6218 19 Dec 12 nicklas 906     @param element A document element or the id of an element
6218 19 Dec 12 nicklas 907     @param attributes A JSON object with key-value pairs of attributes,
6218 19 Dec 12 nicklas 908       the keys should not include the 'data-' prefix
6218 19 Dec 12 nicklas 909   */
6218 19 Dec 12 nicklas 910   data.define = function(element, attributes)
6218 19 Dec 12 nicklas 911   {
6218 19 Dec 12 nicklas 912     element = Doc.element(element);
6218 19 Dec 12 nicklas 913     for (var attr in attributes)
6218 19 Dec 12 nicklas 914     {
6218 19 Dec 12 nicklas 915       var name = 'data-'+attr;
6218 19 Dec 12 nicklas 916       if (!element.hasAttribute(name))
6218 19 Dec 12 nicklas 917       {
6218 19 Dec 12 nicklas 918         element.setAttribute(name, attributes[attr]);
6218 19 Dec 12 nicklas 919       }
6218 19 Dec 12 nicklas 920     }
6218 19 Dec 12 nicklas 921   }
6218 19 Dec 12 nicklas 922   
6218 19 Dec 12 nicklas 923   /**
6161 10 Oct 12 nicklas 924     Get the value of a 'data-*' attribute as an integer. 
6161 10 Oct 12 nicklas 925     If the attribute doesn't exists, the default value is used.
6161 10 Oct 12 nicklas 926   */
6161 10 Oct 12 nicklas 927   data.int = function(element, attribute, defaultValue)
6161 10 Oct 12 nicklas 928   {
6161 10 Oct 12 nicklas 929     return parseInt(data.get(element, attribute, defaultValue), 10);
6161 10 Oct 12 nicklas 930   }
6155 05 Oct 12 nicklas 931   
6155 05 Oct 12 nicklas 932   /**
6161 10 Oct 12 nicklas 933     Get the value of a 'data-*' attribute as a float. 
6161 10 Oct 12 nicklas 934     If the attribute doesn't exists, the default value is used.
6161 10 Oct 12 nicklas 935   */
6161 10 Oct 12 nicklas 936   data.float = function(element, attribute, defaultValue)
6161 10 Oct 12 nicklas 937   {
6161 10 Oct 12 nicklas 938     return parseFloat(data.get(element, attribute, defaultValue));
6161 10 Oct 12 nicklas 939   }
6161 10 Oct 12 nicklas 940   
6161 10 Oct 12 nicklas 941   /**
6155 05 Oct 12 nicklas 942     Get the JSON-formatted value of a 'data-*' attribute for an element. 
6155 05 Oct 12 nicklas 943     If the attribute doesn't exists, the default value is used.
6155 05 Oct 12 nicklas 944   */
6155 05 Oct 12 nicklas 945   data.json = function(element, attribute, defaultValue)
6155 05 Oct 12 nicklas 946   {
6155 05 Oct 12 nicklas 947     var result;
6155 05 Oct 12 nicklas 948     try
6155 05 Oct 12 nicklas 949     {
6155 05 Oct 12 nicklas 950       result = JSON.parse(data.get(element, attribute, defaultValue));
6155 05 Oct 12 nicklas 951     }
6155 05 Oct 12 nicklas 952     catch (ex)
6155 05 Oct 12 nicklas 953     {
6155 05 Oct 12 nicklas 954       result = {};
6155 05 Oct 12 nicklas 955       console.error('Could not parse JSON attribute "' + attribute + '" on element <' + element.tagName.toLowerCase() +' id="'+element.id +'">: ' + ex);
6155 05 Oct 12 nicklas 956     }
6155 05 Oct 12 nicklas 957     return result;
6155 05 Oct 12 nicklas 958   }
6155 05 Oct 12 nicklas 959   
6161 10 Oct 12 nicklas 960   /**
6161 10 Oct 12 nicklas 961     Store a key/value pair in session-scoped storage (eg. the information is lost
6161 10 Oct 12 nicklas 962     when the user closes the browser or navigates away from the BASE site). The
6161 10 Oct 12 nicklas 963     information is kept by an array in the topmost window (see App.topWindow()).
6161 10 Oct 12 nicklas 964     
6161 10 Oct 12 nicklas 965     @param key The key which must be a string
6161 10 Oct 12 nicklas 966     @param value The value, which can be any type of object
6161 10 Oct 12 nicklas 967     @param namespace Optional, if not specified 'location.pathname' is used
6161 10 Oct 12 nicklas 968   */
6161 10 Oct 12 nicklas 969   data.setPageValue = function(key, value, namespace)
6161 10 Oct 12 nicklas 970   {
6161 10 Oct 12 nicklas 971     var win = App.topWindow();
6161 10 Oct 12 nicklas 972     if (!namespace) namespace = location.pathname;
6161 10 Oct 12 nicklas 973     win.pageValues[namespace+'::'+key] = value;
6161 10 Oct 12 nicklas 974   }
6161 10 Oct 12 nicklas 975   
6161 10 Oct 12 nicklas 976   /**
6161 10 Oct 12 nicklas 977     Get value from session-scoped storage (eg. the information is lost
6161 10 Oct 12 nicklas 978     when the user closes the browser or navigates away from the BASE site). The
6161 10 Oct 12 nicklas 979     information is kept by an array in the topmost window (see App.topWindow()).
6161 10 Oct 12 nicklas 980     If no value is found or if there is an error retrieving it, null is
6161 10 Oct 12 nicklas 981     returned.
6161 10 Oct 12 nicklas 982     
6161 10 Oct 12 nicklas 983     @param key The key which must be a string
6161 10 Oct 12 nicklas 984     @param namespace Optional, if not specified 'location.pathname' is used
6161 10 Oct 12 nicklas 985   */
6161 10 Oct 12 nicklas 986   data.getPageValue = function(key, namespace)
6161 10 Oct 12 nicklas 987   {
6161 10 Oct 12 nicklas 988     var win = App.topWindow();
6161 10 Oct 12 nicklas 989     if (!namespace) namespace = location.pathname;
6161 10 Oct 12 nicklas 990     var value = null;
6161 10 Oct 12 nicklas 991     try
6161 10 Oct 12 nicklas 992     {
6520 18 Aug 14 nicklas 993       value = win.pageValues[namespace+'::'+key] || null;
6161 10 Oct 12 nicklas 994     }
6161 10 Oct 12 nicklas 995     catch (e)
6161 10 Oct 12 nicklas 996     {
6161 10 Oct 12 nicklas 997       value = null;
6161 10 Oct 12 nicklas 998     }
6161 10 Oct 12 nicklas 999     return value;
6161 10 Oct 12 nicklas 1000   }
6161 10 Oct 12 nicklas 1001   
6155 05 Oct 12 nicklas 1002   return data;
6155 05 Oct 12 nicklas 1003 }();
6155 05 Oct 12 nicklas 1004
6155 05 Oct 12 nicklas 1005 /**
6155 05 Oct 12 nicklas 1006   Functions that are specific to buttons in the gui (eg. in the toolbar or free-floating).
6157 08 Oct 12 nicklas 1007 */
6155 05 Oct 12 nicklas 1008 var Buttons = function()
6155 05 Oct 12 nicklas 1009 {
6155 05 Oct 12 nicklas 1010   var buttons = {};
6218 19 Dec 12 nicklas 1011   var internal = {};
6415 05 Feb 14 nicklas 1012
6155 05 Oct 12 nicklas 1013   /**
6415 05 Feb 14 nicklas 1014     Add a click event handler to the button.
6155 05 Oct 12 nicklas 1015     If the button is focusable (eg. a tabindex>=0 exists) a
6155 05 Oct 12 nicklas 1016     'keypress' listener that forwards 'ENTER' to the click handler is 
6155 05 Oct 12 nicklas 1017     also added. The btn is either the id or a button <div> reference.
6155 05 Oct 12 nicklas 1018   */
6218 19 Dec 12 nicklas 1019   buttons.addClickHandler = function(btn, handler, attributes)
6155 05 Oct 12 nicklas 1020   {
6155 05 Oct 12 nicklas 1021     var btn = Doc.element(btn);
6167 12 Oct 12 nicklas 1022     if (!btn) return;
6218 19 Dec 12 nicklas 1023     if (attributes) Data.define(btn, attributes);
6415 05 Feb 14 nicklas 1024     // Event handler for checking if the button has been disabled
6415 05 Feb 14 nicklas 1025     btn.addEventListener('click', Events.stopIfDisabled, true);
6155 05 Oct 12 nicklas 1026     btn.addEventListener('click', handler, false);
6155 05 Oct 12 nicklas 1027     if (btn.tabIndex >= 0)
6155 05 Oct 12 nicklas 1028     {
6155 05 Oct 12 nicklas 1029       Events.enableClickOnEnter(btn);
6155 05 Oct 12 nicklas 1030     }
6155 05 Oct 12 nicklas 1031   }
6413 05 Feb 14 nicklas 1032
6218 19 Dec 12 nicklas 1033   /**
6218 19 Dec 12 nicklas 1034     Event handler that open the 'edit item' dialog for the item 
6218 19 Dec 12 nicklas 1035     that is given by the 'data-item-type' and 'data-item-id'
6218 19 Dec 12 nicklas 1036     attributes. Usually attached to an 'Edit' button on a
6218 19 Dec 12 nicklas 1037     single-item page.
6218 19 Dec 12 nicklas 1038   */
6218 19 Dec 12 nicklas 1039   buttons.editItem = function(event)
6218 19 Dec 12 nicklas 1040   {
6218 19 Dec 12 nicklas 1041     var target = event.currentTarget;
6218 19 Dec 12 nicklas 1042     var itemType = Data.get(target, 'item-type');
6218 19 Dec 12 nicklas 1043     var itemId = Data.get(target, 'item-id');
6260 27 Mar 13 nicklas 1044     var extraUrl = Data.get(target, 'extra-url');
6400 27 Jan 14 nicklas 1045     Items.editItem(itemType, itemId, extraUrl);
6218 19 Dec 12 nicklas 1046   }
6218 19 Dec 12 nicklas 1047   
6218 19 Dec 12 nicklas 1048   /**
6220 10 Jan 13 nicklas 1049     Event handler that open the 'new item' dialog for the item 
6220 10 Jan 13 nicklas 1050     that is given by the 'data-item-type' attribute. 
6220 10 Jan 13 nicklas 1051     Usually attached to a 'New' button on a list page.
6220 10 Jan 13 nicklas 1052   */
6220 10 Jan 13 nicklas 1053   buttons.newItem = function(event)
6220 10 Jan 13 nicklas 1054   {
6220 10 Jan 13 nicklas 1055     var target = event.currentTarget;
6220 10 Jan 13 nicklas 1056     var itemType = Data.get(target, 'item-type');
6260 27 Mar 13 nicklas 1057     var extraUrl = Data.get(target, 'extra-url');
6400 27 Jan 14 nicklas 1058     Items.newItem(itemType, extraUrl);
6220 10 Jan 13 nicklas 1059   }
6220 10 Jan 13 nicklas 1060   
6220 10 Jan 13 nicklas 1061   /**
6218 19 Dec 12 nicklas 1062     Event handler that submits a request for the item 
6218 19 Dec 12 nicklas 1063     that is given by the 'data-item-type' and 'data-item-id'
6218 19 Dec 12 nicklas 1064     attributes to be marked for removal. Usually attached to 
6218 19 Dec 12 nicklas 1065     an 'Remove' button on a single-item page.
6218 19 Dec 12 nicklas 1066   */
6218 19 Dec 12 nicklas 1067   buttons.deleteItem = function(event)
6218 19 Dec 12 nicklas 1068   {
6218 19 Dec 12 nicklas 1069     var target = event.currentTarget;
6218 19 Dec 12 nicklas 1070     var itemType = Data.get(target, 'item-type');
6218 19 Dec 12 nicklas 1071     var itemId = Data.int(target, 'item-id');
6261 27 Mar 13 nicklas 1072     var extraUrl = Data.get(target, 'extra-url');
6289 05 Jun 13 nicklas 1073     var confirm = Data.int(target, 'confirm');
6289 05 Jun 13 nicklas 1074     Items.deleteItem(itemType, itemId, confirm, extraUrl);
6218 19 Dec 12 nicklas 1075   }
6218 19 Dec 12 nicklas 1076
6218 19 Dec 12 nicklas 1077   /**
6220 10 Jan 13 nicklas 1078     Event handler that submits a request for all selected items
6220 10 Jan 13 nicklas 1079     in table to be marked for removal. Usually attached to 
6220 10 Jan 13 nicklas 1080     an 'Remove' button on a list page. The table id must be specifed
6220 10 Jan 13 nicklas 1081     in the 'data-table-id' attribute.
6220 10 Jan 13 nicklas 1082   */
6220 10 Jan 13 nicklas 1083   buttons.deleteItems = function(event)
6220 10 Jan 13 nicklas 1084   {
6220 10 Jan 13 nicklas 1085     var target = event.currentTarget;
6220 10 Jan 13 nicklas 1086     var tableId = Data.get(target, 'table-id');
6220 10 Jan 13 nicklas 1087     var regexp = Data.get(target, 'regexp');
6220 10 Jan 13 nicklas 1088     var confirm = Data.int(target, 'confirm');
6220 10 Jan 13 nicklas 1089     Table.deleteItems(tableId, regexp, confirm);
6220 10 Jan 13 nicklas 1090   }
6220 10 Jan 13 nicklas 1091   
6220 10 Jan 13 nicklas 1092   /**
6218 19 Dec 12 nicklas 1093     Event handler that submits a request for the item 
6218 19 Dec 12 nicklas 1094     that is given by the 'data-item-type' and 'data-item-id'
6218 19 Dec 12 nicklas 1095     attributes to be unmarked for removal. Usually attached to 
6218 19 Dec 12 nicklas 1096     an 'Restore' button on a single-item page.
6218 19 Dec 12 nicklas 1097   */
6218 19 Dec 12 nicklas 1098   buttons.restoreItem = function(event)
6218 19 Dec 12 nicklas 1099   {
6218 19 Dec 12 nicklas 1100     var target = event.currentTarget;
6218 19 Dec 12 nicklas 1101     var itemType = Data.get(target, 'item-type');
6218 19 Dec 12 nicklas 1102     var itemId = Data.int(target, 'item-id');
6261 27 Mar 13 nicklas 1103     var extraUrl = Data.get(target, 'extra-url');
6261 27 Mar 13 nicklas 1104     Items.restoreItem(itemType, itemId, extraUrl);
6218 19 Dec 12 nicklas 1105   }
6220 10 Jan 13 nicklas 1106   
6220 10 Jan 13 nicklas 1107   /**
6220 10 Jan 13 nicklas 1108     Event handler that submits a request for all selected items
6220 10 Jan 13 nicklas 1109     in table to be unmarked for removal. Usually attached to 
6220 10 Jan 13 nicklas 1110     an 'Restore' button on a list page. The table id must be specifed
6220 10 Jan 13 nicklas 1111     in the 'data-table-id' attribute.
6220 10 Jan 13 nicklas 1112   */
6220 10 Jan 13 nicklas 1113   buttons.restoreItems = function(event)
6220 10 Jan 13 nicklas 1114   {
6220 10 Jan 13 nicklas 1115     var target = event.currentTarget;
6220 10 Jan 13 nicklas 1116     var tableId = Data.get(target, 'table-id');
6220 10 Jan 13 nicklas 1117     var regexp = Data.get(target, 'regexp');
6220 10 Jan 13 nicklas 1118     var confirm = Data.int(target, 'confirm', 0);
6220 10 Jan 13 nicklas 1119     Table.restoreItems(tableId, regexp, confirm);
6220 10 Jan 13 nicklas 1120   }
6218 19 Dec 12 nicklas 1121
6218 19 Dec 12 nicklas 1122   /**
6218 19 Dec 12 nicklas 1123     Event handler that submits a request for the item 
6218 19 Dec 12 nicklas 1124     that is given by the 'data-item-type' and 'data-item-id'
6218 19 Dec 12 nicklas 1125     attributes to be deleted from the database. Usually attached 
6307 15 Aug 13 nicklas 1126     to an 'Trashcan' icon on a single-item page. Specify a target
6307 15 Aug 13 nicklas 1127     element id in 'data-notify' attribute to send a 'base-notify'
6307 15 Aug 13 nicklas 1128     message after the item has been deleted, otherwise the current
6307 15 Aug 13 nicklas 1129     page is automatically reloaded with the list page instead.
6218 19 Dec 12 nicklas 1130   */
6218 19 Dec 12 nicklas 1131   buttons.deleteItemPermanently = function(event)
6218 19 Dec 12 nicklas 1132   {
6218 19 Dec 12 nicklas 1133     var target = event.currentTarget;
6218 19 Dec 12 nicklas 1134     var itemType = Data.get(target, 'item-type');
6218 19 Dec 12 nicklas 1135     var itemId = Data.int(target, 'item-id');
6261 27 Mar 13 nicklas 1136     var extraUrl = Data.get(target, 'extra-url');
6307 15 Aug 13 nicklas 1137     var notify = Data.get(target, 'notify');
6307 15 Aug 13 nicklas 1138     Items.deleteItemPermanently(itemType, itemId, true, notify, extraUrl);
6218 19 Dec 12 nicklas 1139   }
6218 19 Dec 12 nicklas 1140   
6218 19 Dec 12 nicklas 1141   /**
6221 10 Jan 13 nicklas 1142     Event handler that opens the 'Share item' dialog
6221 10 Jan 13 nicklas 1143     for the item that is given by the 'data-item-type' and 
6222 14 Jan 13 nicklas 1144     'data-item-id' attributes. Usually attached to a 'Shared' 
6222 14 Jan 13 nicklas 1145     icon on a list page or a button on a single-item page.
6221 10 Jan 13 nicklas 1146   */
6221 10 Jan 13 nicklas 1147   buttons.shareItem = function(event)
6221 10 Jan 13 nicklas 1148   {
6221 10 Jan 13 nicklas 1149     var target = event.currentTarget;
6221 10 Jan 13 nicklas 1150     var itemType = Data.get(target, 'item-type');
6221 10 Jan 13 nicklas 1151     var itemId = Data.int(target, 'item-id');
6221 10 Jan 13 nicklas 1152     Dialogs.openShareItem(itemType, itemId);
6221 10 Jan 13 nicklas 1153   }
6221 10 Jan 13 nicklas 1154   
6221 10 Jan 13 nicklas 1155   /**
6222 14 Jan 13 nicklas 1156     Event handler that opens the 'Share items' dialog
6222 14 Jan 13 nicklas 1157     for all selected items in a table. Usually attached to 
6222 14 Jan 13 nicklas 1158     an 'Share' button on a list page. The table id must be specifed
6222 14 Jan 13 nicklas 1159     in the 'data-table-id' attribute.
6222 14 Jan 13 nicklas 1160   */
6222 14 Jan 13 nicklas 1161   buttons.shareItems = function(event)
6222 14 Jan 13 nicklas 1162   {
6222 14 Jan 13 nicklas 1163     var target = event.currentTarget;
6222 14 Jan 13 nicklas 1164     var tableId = Data.get(target, 'table-id');
6305 09 Aug 13 nicklas 1165     var regexp = Data.get(target, 'regexp');
6305 09 Aug 13 nicklas 1166     Table.shareItems(tableId, regexp);
6222 14 Jan 13 nicklas 1167   }
6222 14 Jan 13 nicklas 1168   
6222 14 Jan 13 nicklas 1169   /**
6222 14 Jan 13 nicklas 1170     Event handler that opens the 'Set owner' dialog
6222 14 Jan 13 nicklas 1171     for the item that is given by the 'data-item-type' and 
6222 14 Jan 13 nicklas 1172     'data-item-id' attributes. Usually attached to a button 
6222 14 Jan 13 nicklas 1173     on a single-item page.
6222 14 Jan 13 nicklas 1174   */
6222 14 Jan 13 nicklas 1175   buttons.setOwner = function(event)
6222 14 Jan 13 nicklas 1176   {
6222 14 Jan 13 nicklas 1177     var target = event.currentTarget;
6222 14 Jan 13 nicklas 1178     var itemType = Data.get(target, 'item-type');
6222 14 Jan 13 nicklas 1179     var itemId = Data.int(target, 'item-id');
6222 14 Jan 13 nicklas 1180     Dialogs.openSetOwner(itemType, itemId);
6222 14 Jan 13 nicklas 1181   }
6222 14 Jan 13 nicklas 1182
6222 14 Jan 13 nicklas 1183   /**
6222 14 Jan 13 nicklas 1184     Event handler that opens the 'Set owner of items' dialog
6222 14 Jan 13 nicklas 1185     for all selected items in a table. Usually attached to 
6222 14 Jan 13 nicklas 1186     an 'Set owner' button on a list page. The table id must be specifed
6222 14 Jan 13 nicklas 1187     in the 'data-table-id' attribute.
6222 14 Jan 13 nicklas 1188   */
6222 14 Jan 13 nicklas 1189   buttons.setOwnerOfItems = function(event)
6222 14 Jan 13 nicklas 1190   {
6222 14 Jan 13 nicklas 1191     var target = event.currentTarget;
6222 14 Jan 13 nicklas 1192     var tableId = Data.get(target, 'table-id');
6307 15 Aug 13 nicklas 1193     var regexp = Data.get(target, 'regexp');
6307 15 Aug 13 nicklas 1194     Table.setOwnerOfItems(tableId, regexp);
6222 14 Jan 13 nicklas 1195   }
6222 14 Jan 13 nicklas 1196
6694 26 Jan 15 nicklas 1197   /**
6694 26 Jan 15 nicklas 1198     Event handler that opens the 'Batch inherit anntations' dialog
6694 26 Jan 15 nicklas 1199     for all selected items in a table. Usually attached to 
6694 26 Jan 15 nicklas 1200     an 'Inherit annotations' button on a list page. The table id must be specifed
6694 26 Jan 15 nicklas 1201     in the 'data-table-id' attribute.
6694 26 Jan 15 nicklas 1202   */
6694 26 Jan 15 nicklas 1203   buttons.inheritAnnotations = function(event)
6694 26 Jan 15 nicklas 1204   {
6694 26 Jan 15 nicklas 1205     var target = event.currentTarget;
6694 26 Jan 15 nicklas 1206     var tableId = Data.get(target, 'table-id');
6694 26 Jan 15 nicklas 1207     var regexp = Data.get(target, 'regexp');
6694 26 Jan 15 nicklas 1208     Table.inheritAnnotations(tableId, regexp);
6694 26 Jan 15 nicklas 1209   }
6694 26 Jan 15 nicklas 1210
6222 14 Jan 13 nicklas 1211   
6222 14 Jan 13 nicklas 1212   /**
6218 19 Dec 12 nicklas 1213     Event handler that open the 'using items' (in the trashcan)
6218 19 Dec 12 nicklas 1214     page for the item that is given by the 'data-item-type' and 
6218 19 Dec 12 nicklas 1215     'data-item-id' attributes. Usually attached to an icon on a
6218 19 Dec 12 nicklas 1216     single-item page for items that have been marked for removal
6218 19 Dec 12 nicklas 1217     but can't be deleted.
6218 19 Dec 12 nicklas 1218   */
6218 19 Dec 12 nicklas 1219   buttons.showUsingItems = function(event)
6218 19 Dec 12 nicklas 1220   {
6218 19 Dec 12 nicklas 1221     var target = event.currentTarget;
6218 19 Dec 12 nicklas 1222     var itemType = Data.get(target, 'item-type');
6218 19 Dec 12 nicklas 1223     var itemId = Data.int(target, 'item-id');
6218 19 Dec 12 nicklas 1224     Items.showUsingItems(itemType, itemId);
6218 19 Dec 12 nicklas 1225   }
6218 19 Dec 12 nicklas 1226   
6220 10 Jan 13 nicklas 1227   /**
6220 10 Jan 13 nicklas 1228     Event handler that opens the 'Run plug-in' dialog in
6220 10 Jan 13 nicklas 1229     a single-item context. The current item must be specified 
6220 10 Jan 13 nicklas 1230     by the 'data-item-type' and 'data-item-id' attributes. The
6220 10 Jan 13 nicklas 1231     plug-in type to select should be specified by the
6220 10 Jan 13 nicklas 1232     'data-plugin-type' attribute. Valid values are 'IMPORT'
6315 06 Sep 13 nicklas 1233     'EXPORT', 'ANALYZE' and 'OTHER'. Usually attached to a button
6220 10 Jan 13 nicklas 1234     on a single-item page.
6220 10 Jan 13 nicklas 1235   */
6220 10 Jan 13 nicklas 1236   buttons.runPlugin = function(event)
6220 10 Jan 13 nicklas 1237   {
6220 10 Jan 13 nicklas 1238     var target = event.currentTarget;
6220 10 Jan 13 nicklas 1239     var itemType = Data.get(target, 'item-type');
6220 10 Jan 13 nicklas 1240     var itemId = Data.int(target, 'item-id');
6220 10 Jan 13 nicklas 1241     var pluginType = Data.get(target, 'plugin-type');
6315 06 Sep 13 nicklas 1242     var extraUrl = Data.get(target, 'extra-url');
6315 06 Sep 13 nicklas 1243     var cmd = Data.get(target, 'cmd');
6315 06 Sep 13 nicklas 1244     Items.runPlugin(itemType, itemId, pluginType, extraUrl, cmd);
6220 10 Jan 13 nicklas 1245   }
6220 10 Jan 13 nicklas 1246
6220 10 Jan 13 nicklas 1247   /**
6220 10 Jan 13 nicklas 1248     Event handler that opens the 'Run plug-in' dialog in
6220 10 Jan 13 nicklas 1249     a list context. The list id must be specified by the
6220 10 Jan 13 nicklas 1250     'data-table-id' attribute. The plug-in type to select 
6220 10 Jan 13 nicklas 1251     should be specified by the 'data-plugin-type' attribute. 
6315 06 Sep 13 nicklas 1252     Valid values are 'IMPORT' 'EXPORT', 'ANALYZE' and 'OTHER'. 
6315 06 Sep 13 nicklas 1253     Usually attached to a button on a list page.
6220 10 Jan 13 nicklas 1254   */
6220 10 Jan 13 nicklas 1255   buttons.runListPlugin = function(event)
6220 10 Jan 13 nicklas 1256   {
6220 10 Jan 13 nicklas 1257     var target = event.currentTarget;
6220 10 Jan 13 nicklas 1258     var tableId = Data.get(target, 'table-id');
6220 10 Jan 13 nicklas 1259     var pluginType = Data.get(target, 'plugin-type');
6315 06 Sep 13 nicklas 1260     var cmd = Data.get(target, 'cmd');
6315 06 Sep 13 nicklas 1261     Table.runPlugin(tableId, pluginType, cmd);
6220 10 Jan 13 nicklas 1262   }
6220 10 Jan 13 nicklas 1263   
6220 10 Jan 13 nicklas 1264   /**
6220 10 Jan 13 nicklas 1265     Event handler that opens the 'Configure columns' dialog
6220 10 Jan 13 nicklas 1266     for a list page. The list id must be specified by the
6220 10 Jan 13 nicklas 1267     'data-table-id' attribute. Usually attached to a button
6220 10 Jan 13 nicklas 1268     on a list page.
6220 10 Jan 13 nicklas 1269   */
6220 10 Jan 13 nicklas 1270   buttons.configureColumns = function(event)
6220 10 Jan 13 nicklas 1271   {
6220 10 Jan 13 nicklas 1272     var target = event.currentTarget;
6220 10 Jan 13 nicklas 1273     var tableId = Data.get(target, 'table-id');
6315 06 Sep 13 nicklas 1274     var settings = Data.get(target, 'settings');
6315 06 Sep 13 nicklas 1275     Table.configureColumns(tableId, settings);
6220 10 Jan 13 nicklas 1276   }
6220 10 Jan 13 nicklas 1277   
6222 14 Jan 13 nicklas 1278   /**
6222 14 Jan 13 nicklas 1279     Event handler that return the selected items in a list page
6222 14 Jan 13 nicklas 1280     to the parent window. This can be done in one of two ways:
6222 14 Jan 13 nicklas 1281     Using a callback method defined by the parent window (old method)
6222 14 Jan 13 nicklas 1282     Using an event handler defined by the parent window (new method)
6222 14 Jan 13 nicklas 1283     In both cases, the list id must be specified by the 'data-table-id' 
6222 14 Jan 13 nicklas 1284     attribute and the name of the callback method or event target must be
6222 14 Jan 13 nicklas 1285     stored in the 'callback' form element of the table. The current window
6222 14 Jan 13 nicklas 1286     is automatically closed when all selected items have been returned
6222 14 Jan 13 nicklas 1287     to the parent.
6222 14 Jan 13 nicklas 1288   */
6222 14 Jan 13 nicklas 1289   buttons.returnSelected = function(event)
6222 14 Jan 13 nicklas 1290   {
6222 14 Jan 13 nicklas 1291     var target = event.currentTarget;
6222 14 Jan 13 nicklas 1292     var tableId = Data.get(target, 'table-id');
6222 14 Jan 13 nicklas 1293     var regexp = Data.get(target, 'regexp');
6222 14 Jan 13 nicklas 1294     
6222 14 Jan 13 nicklas 1295     Table.returnSelectedItems(tableId, regexp);
6222 14 Jan 13 nicklas 1296     App.closeWindow();
6222 14 Jan 13 nicklas 1297   }
6222 14 Jan 13 nicklas 1298   
6155 05 Oct 12 nicklas 1299   return buttons;
6155 05 Oct 12 nicklas 1300 }();
6155 05 Oct 12 nicklas 1301
6166 11 Oct 12 nicklas 1302 /**
6166 11 Oct 12 nicklas 1303   Functions that open common popup dialog windows.
6166 11 Oct 12 nicklas 1304 */
6166 11 Oct 12 nicklas 1305 var Dialogs = function()
6157 08 Oct 12 nicklas 1306 {
6166 11 Oct 12 nicklas 1307   var dialogs = {};
6157 08 Oct 12 nicklas 1308   var internal = {};
6155 05 Oct 12 nicklas 1309
6168 15 Oct 12 nicklas 1310   /*
6168 15 Oct 12 nicklas 1311     Opens a popup window with the specified name and size and loads 
6168 15 Oct 12 nicklas 1312     the given url.
6168 15 Oct 12 nicklas 1313   
6168 15 Oct 12 nicklas 1314     @param url The url to load in the window
6168 15 Oct 12 nicklas 1315     @param name The name of the window
6168 15 Oct 12 nicklas 1316     @param width The width in pixels of the window
6168 15 Oct 12 nicklas 1317     @param height The height in pixels of the window
6168 15 Oct 12 nicklas 1318   */
6168 15 Oct 12 nicklas 1319   dialogs.openPopup = function(url, name, width, height)
6168 15 Oct 12 nicklas 1320   {
6540 26 Sep 14 nicklas 1321     // Get last position of the window -- can be null
6540 26 Sep 14 nicklas 1322     var position = internal.getLastDialogPosition(name);
6168 15 Oct 12 nicklas 1323
6540 26 Sep 14 nicklas 1324     if (position == null)
6520 18 Aug 14 nicklas 1325     {
6520 18 Aug 14 nicklas 1326       // If no last position is know, use the specified settings
6520 18 Aug 14 nicklas 1327       position = {};
6576 22 Oct 14 nicklas 1328       // Rescale according to user settings -- but not larger than current screen
6520 18 Aug 14 nicklas 1329       var scale = App.getScale();
6576 22 Oct 14 nicklas 1330       position.width = Math.ceil(Math.min(width * scale, screen.availWidth));
6576 22 Oct 14 nicklas 1331       position.height = Math.ceil(Math.min(height * scale, screen.availHeight));
6520 18 Aug 14 nicklas 1332       
6520 18 Aug 14 nicklas 1333       //  Try to position the popup window in the center of the parent window.
6520 18 Aug 14 nicklas 1334       var pos = App.getWindowPosition(window.top);
6576 22 Oct 14 nicklas 1335       var top = Math.ceil(pos.top+(pos.height-position.height) / 2);
6576 22 Oct 14 nicklas 1336       var left = Math.ceil(pos.left+(pos.width-position.width) / 2);
6520 18 Aug 14 nicklas 1337       position.top = (top < 0 && pos.top >= 0) ? 0 : top;
6520 18 Aug 14 nicklas 1338       position.left = (left < 0 && pos.left >= 0) ? 0 : left;
6520 18 Aug 14 nicklas 1339     }
6168 15 Oct 12 nicklas 1340
6168 15 Oct 12 nicklas 1341     var options = "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes";
6540 26 Sep 14 nicklas 1342     options += ",top="+position.top+",left="+position.left+",width="+position.width+",height="+position.height;
6452 23 Apr 14 nicklas 1343     // Check if another window with the same name already exists
6540 26 Sep 14 nicklas 1344     var oldWin = window.open('', name, options);
6452 23 Apr 14 nicklas 1345     try
6452 23 Apr 14 nicklas 1346     {
6520 18 Aug 14 nicklas 1347       if (oldWin && !oldWin.closed && oldWin.Popup && !oldWin.Popup.isReadOnly())
6452 23 Apr 14 nicklas 1348       {
6452 23 Apr 14 nicklas 1349         var msg = 'This window has already been opened. ';
6452 23 Apr 14 nicklas 1350         msg += 'Opening it again causes unsaved changes to be lost. Continue?';
6452 23 Apr 14 nicklas 1351         if (!confirm(msg))
6452 23 Apr 14 nicklas 1352         {
6452 23 Apr 14 nicklas 1353           oldWin.focus();
6452 23 Apr 14 nicklas 1354           return;
6452 23 Apr 14 nicklas 1355         }
6452 23 Apr 14 nicklas 1356         url += '&warnIfOpen=0';
6452 23 Apr 14 nicklas 1357       }
6452 23 Apr 14 nicklas 1358     }
6452 23 Apr 14 nicklas 1359     catch (err)
6452 23 Apr 14 nicklas 1360     {}
6452 23 Apr 14 nicklas 1361     
6540 26 Sep 14 nicklas 1362     internal.saveRequestedDialogPosition(name, position);
6540 26 Sep 14 nicklas 1363
6168 15 Oct 12 nicklas 1364     var newWin;
6168 15 Oct 12 nicklas 1365     if (App.isTooLongUrl(url))
6168 15 Oct 12 nicklas 1366     {
6168 15 Oct 12 nicklas 1367       newWin = window.open(App.getRoot()+'blank.jsp', name, options);
6168 15 Oct 12 nicklas 1368       App.safeSetLocation(url, newWin, true);
6168 15 Oct 12 nicklas 1369     }
6168 15 Oct 12 nicklas 1370     else
6168 15 Oct 12 nicklas 1371     {
6188 30 Oct 12 nicklas 1372       if (url == '') url = App.getRoot()+'blank.jsp';
6168 15 Oct 12 nicklas 1373       newWin = window.open(url, name, options);
6168 15 Oct 12 nicklas 1374     }
6520 18 Aug 14 nicklas 1375     
6168 15 Oct 12 nicklas 1376     newWin.focus();
6452 23 Apr 14 nicklas 1377     return newWin;
6168 15 Oct 12 nicklas 1378   }
6168 15 Oct 12 nicklas 1379   
6540 26 Sep 14 nicklas 1380   internal.getLastDialogPosition = function(name)
6540 26 Sep 14 nicklas 1381   {
6540 26 Sep 14 nicklas 1382     if (!App.rememberDialogPositions()) return null;
6540 26 Sep 14 nicklas 1383     return JSON.parse(App.getLocal('dialog-position:'+name));
6540 26 Sep 14 nicklas 1384   }
6540 26 Sep 14 nicklas 1385   
6540 26 Sep 14 nicklas 1386   internal.saveRequestedDialogPosition = function(name, position)
6540 26 Sep 14 nicklas 1387   {
6540 26 Sep 14 nicklas 1388     if (!App.rememberDialogPositions()) return;
6540 26 Sep 14 nicklas 1389     App.setLocal('dialog-position-req:'+name, JSON.stringify(position));
6540 26 Sep 14 nicklas 1390   }
6540 26 Sep 14 nicklas 1391   
6540 26 Sep 14 nicklas 1392   dialogs.forgetDialogPositions = function()
6540 26 Sep 14 nicklas 1393   {
6540 26 Sep 14 nicklas 1394     var storage = App.localStorage();
6540 26 Sep 14 nicklas 1395     if (!storage) return;
6540 26 Sep 14 nicklas 1396     
6540 26 Sep 14 nicklas 1397     var prefix = App.getRoot()+':dialog-position:';
6540 26 Sep 14 nicklas 1398     
6540 26 Sep 14 nicklas 1399     for (var i = storage.length-1; i >= 0; i--)
6540 26 Sep 14 nicklas 1400     {
6540 26 Sep 14 nicklas 1401       var key = storage.key(i);
6540 26 Sep 14 nicklas 1402       if (key.indexOf(prefix) == 0) storage.removeItem(key);
6540 26 Sep 14 nicklas 1403     }
6540 26 Sep 14 nicklas 1404   }
6540 26 Sep 14 nicklas 1405   
6157 08 Oct 12 nicklas 1406   /**
6400 27 Jan 14 nicklas 1407     Get a reference to the popup dialog with the given name.
6400 27 Jan 14 nicklas 1408     Return null if no window with the given name exists.
6400 27 Jan 14 nicklas 1409    */
6400 27 Jan 14 nicklas 1410   dialogs.getDialog = function(name)
6400 27 Jan 14 nicklas 1411   {
6452 23 Apr 14 nicklas 1412     var options = 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes';
6452 23 Apr 14 nicklas 1413     options += ',top=0,left=0,width=100,height=100';
6452 23 Apr 14 nicklas 1414     var win = window.open('', name, options);
6452 23 Apr 14 nicklas 1415     if (!win.closed && win.App)
6452 23 Apr 14 nicklas 1416     {
6452 23 Apr 14 nicklas 1417       return win;
6452 23 Apr 14 nicklas 1418     }
6452 23 Apr 14 nicklas 1419     else
6452 23 Apr 14 nicklas 1420     {
6452 23 Apr 14 nicklas 1421       win.close();
6452 23 Apr 14 nicklas 1422       return null;
6452 23 Apr 14 nicklas 1423     }
6400 27 Jan 14 nicklas 1424   }
6400 27 Jan 14 nicklas 1425   
6400 27 Jan 14 nicklas 1426   /**
6157 08 Oct 12 nicklas 1427     Open a popup window showing help information.
6157 08 Oct 12 nicklas 1428     @param helpId The ID of the help information
6157 08 Oct 12 nicklas 1429   */
6166 11 Oct 12 nicklas 1430   dialogs.openHelp = function(helpId)
6157 08 Oct 12 nicklas 1431   {
6168 15 Oct 12 nicklas 1432     var url = App.getRoot()+'common/help/view_help.jsp?ID='+App.getSessionId();
6168 15 Oct 12 nicklas 1433     url += '&external_id='+helpId;
6168 15 Oct 12 nicklas 1434     Dialogs.openPopup(url, 'Help', 640, 550);
6157 08 Oct 12 nicklas 1435   }
6157 08 Oct 12 nicklas 1436   
6157 08 Oct 12 nicklas 1437   /**
6157 08 Oct 12 nicklas 1438     Open a popup window showing help information. The help section to
6157 08 Oct 12 nicklas 1439     display is given by the currently active tab on the given tab control.
6160 10 Oct 12 nicklas 1440     @param tabControl A tab control element or the id
6160 10 Oct 12 nicklas 1441     @param helpId Optional, a default help id if no help is specified by the tab control
6160 10 Oct 12 nicklas 1442   */
6166 11 Oct 12 nicklas 1443   dialogs.openTabControlHelp = function(tabControl, helpId)  
6157 08 Oct 12 nicklas 1444   {
6160 10 Oct 12 nicklas 1445     var useHelpId = TabControl.getActiveHelpId(tabControl);
6157 08 Oct 12 nicklas 1446     if (!useHelpId) useHelpId = helpId;
6166 11 Oct 12 nicklas 1447     dialogs.openHelp(useHelpId);
6157 08 Oct 12 nicklas 1448   }
6157 08 Oct 12 nicklas 1449   
6166 11 Oct 12 nicklas 1450   /*
6166 11 Oct 12 nicklas 1451     Open a popup window with a large textarea field for editing text.
6166 11 Oct 12 nicklas 1452     @param textarea A form input/textarea element or the id of an element
6166 11 Oct 12 nicklas 1453     @param title The title that the popup dialog should have
6166 11 Oct 12 nicklas 1454   */
6171 17 Oct 12 nicklas 1455   dialogs.openZoom = function(textarea, title, useToolbar)
6166 11 Oct 12 nicklas 1456   {
6166 11 Oct 12 nicklas 1457     textarea = Doc.element(textarea);
6166 11 Oct 12 nicklas 1458     if (!title) title = textarea.id;
6166 11 Oct 12 nicklas 1459     var url = App.getRoot()+'common/zoom.jsp?ID='+App.getSessionId();
6166 11 Oct 12 nicklas 1460     url += '&title='+encodeURIComponent(title)+'&textarea='+textarea.id;
6171 17 Oct 12 nicklas 1461     if (useToolbar) url+= '&useToolbar=1';
6168 15 Oct 12 nicklas 1462     Dialogs.openPopup(url, title.replace(/[^\w]/g, ''), 640, 600);
6166 11 Oct 12 nicklas 1463   }
6166 11 Oct 12 nicklas 1464   
6166 11 Oct 12 nicklas 1465   /*
6166 11 Oct 12 nicklas 1466     Open a popup window for selecting a date or datetime value.
6166 11 Oct 12 nicklas 1467     @param textarea A form input/textarea element or the id of an element
6166 11 Oct 12 nicklas 1468     @param title The title that the popup dialog should have
6216 14 Dec 12 nicklas 1469     @param dateFormat The dateformat to use when reading/writing the value, or null to use the logged in user's default format
6166 11 Oct 12 nicklas 1470     @param useTime 'true' to use date+time, 'false' to only use date
6166 11 Oct 12 nicklas 1471   */
6166 11 Oct 12 nicklas 1472   dialogs.openCalendar = function(textarea, title, dateFormat, useTime)
6166 11 Oct 12 nicklas 1473   {
6166 11 Oct 12 nicklas 1474     textarea = Doc.element(textarea);
6166 11 Oct 12 nicklas 1475     if (!title) title = textarea.id;
6166 11 Oct 12 nicklas 1476     var url = App.getRoot()+'common/calendar.jsp?ID='+App.getSessionId();
6216 14 Dec 12 nicklas 1477     url += '&title='+encodeURIComponent(title)+'&textarea='+textarea.id;
6216 14 Dec 12 nicklas 1478     if (dateFormat) url += '&format='+encodeURIComponent(dateFormat);
6170 16 Oct 12 nicklas 1479     if (useTime) url += '&useTime=1';
6168 15 Oct 12 nicklas 1480     Dialogs.openPopup(url, title.replace(/[^\w]/, ''), 450, 300);
6166 11 Oct 12 nicklas 1481   }
6166 11 Oct 12 nicklas 1482   
6157 08 Oct 12 nicklas 1483   /**
6187 29 Oct 12 nicklas 1484     Open a popup window for selecting a color. When a color has
6187 29 Oct 12 nicklas 1485     been selected the hex value is saved to the given textarea
6187 29 Oct 12 nicklas 1486     element and a 'change' event is sent to it before the
6187 29 Oct 12 nicklas 1487     popup is closed.
6187 29 Oct 12 nicklas 1488     
6187 29 Oct 12 nicklas 1489     @param textarea A form input/textarea element or the id of an
6187 29 Oct 12 nicklas 1490       element
6187 29 Oct 12 nicklas 1491     @param title The title that the popup dialog should have
6187 29 Oct 12 nicklas 1492   */
6187 29 Oct 12 nicklas 1493   dialogs.openColorChart = function(textarea, title)
6187 29 Oct 12 nicklas 1494   {
6187 29 Oct 12 nicklas 1495     textarea = Doc.element(textarea);
6187 29 Oct 12 nicklas 1496     if (!title) title = textarea.id;
6187 29 Oct 12 nicklas 1497     
6187 29 Oct 12 nicklas 1498     var url = App.getRoot()+'common/select_color.jsp?ID=' + App.getSessionId();
6187 29 Oct 12 nicklas 1499     url += '&title='+encodeURIComponent(title)+'&textarea='+textarea.id;
6187 29 Oct 12 nicklas 1500     Dialogs.openPopup(url, title.replace(/[^\w]/, ''), 450, 300);
6187 29 Oct 12 nicklas 1501   }
6187 29 Oct 12 nicklas 1502   
6314 02 Sep 13 nicklas 1503   dialogs.openExpressionBuilder = function(textarea, title, formulaType, rawDataType, channels, bioAssaySetId)
6314 02 Sep 13 nicklas 1504   {
6314 02 Sep 13 nicklas 1505     textarea = Doc.element(textarea);
6314 02 Sep 13 nicklas 1506     if (!title) title = textarea.id;
6314 02 Sep 13 nicklas 1507
6314 02 Sep 13 nicklas 1508     var url = App.getRoot()+'common/expression_builder.jsp?ID='+App.getSessionId();
6314 02 Sep 13 nicklas 1509     url += '&title='+encodeURIComponent(title)+'&textarea='+textarea.id;
6314 02 Sep 13 nicklas 1510     url += '&formulatype='+formulaType;
6314 02 Sep 13 nicklas 1511     url += '&rawdatatype='+rawDataType;
6314 02 Sep 13 nicklas 1512     url += '&channels='+channels;
6314 02 Sep 13 nicklas 1513     url += '&restrictions=' + (formulaType == 'COLUMN_RESTRICTION' ? 1 : 0);
6314 02 Sep 13 nicklas 1514     if (bioAssaySetId) url += '&bioassayset_id='+bioAssaySetId;
6314 02 Sep 13 nicklas 1515     Dialogs.openPopup(url, title.replace(/[^\w]/, ''), 750, 500);
6314 02 Sep 13 nicklas 1516   }
6314 02 Sep 13 nicklas 1517   
6187 29 Oct 12 nicklas 1518   /**
6221 10 Jan 13 nicklas 1519     Open the 'Share item' dialog for a single item.
6221 10 Jan 13 nicklas 1520     @param itemType The type of the item
6221 10 Jan 13 nicklas 1521     @param itemId The id of the item
6221 10 Jan 13 nicklas 1522   */
6221 10 Jan 13 nicklas 1523   dialogs.openShareItem = function(itemType, itemId)
6221 10 Jan 13 nicklas 1524   {
6400 27 Jan 14 nicklas 1525     var controller = Items.getController(itemType);
6221 10 Jan 13 nicklas 1526     var url = App.getRoot() + controller.url;
6221 10 Jan 13 nicklas 1527     url += '?ID='+App.getSessionId();
6221 10 Jan 13 nicklas 1528     url += '&cmd=ShareItem';
6221 10 Jan 13 nicklas 1529     url += '&item_id='+itemId;
6221 10 Jan 13 nicklas 1530     dialogs.openPopup(url, 'Share'+itemType, 600, 400);
6221 10 Jan 13 nicklas 1531   }
6222 14 Jan 13 nicklas 1532
6222 14 Jan 13 nicklas 1533   /**
6222 14 Jan 13 nicklas 1534     Open the 'Set owner' dialog for a single item.
6222 14 Jan 13 nicklas 1535     @param itemType The type of the item
6222 14 Jan 13 nicklas 1536     @param itemId The id of the item
6222 14 Jan 13 nicklas 1537   */
6222 14 Jan 13 nicklas 1538   dialogs.openSetOwner = function(itemType, itemId)
6222 14 Jan 13 nicklas 1539   {
6400 27 Jan 14 nicklas 1540     var controller = Items.getController(itemType);
6222 14 Jan 13 nicklas 1541     var url = App.getRoot() + controller.url;
6222 14 Jan 13 nicklas 1542     url += '?ID='+App.getSessionId();
6222 14 Jan 13 nicklas 1543     url += '&cmd=SetOwnerOfItem';
6222 14 Jan 13 nicklas 1544     url += '&item_id='+itemId;
6222 14 Jan 13 nicklas 1545     dialogs.openPopup(url, 'SetOwnerOfItem'+itemType, 450, 300);
6222 14 Jan 13 nicklas 1546   }
6222 14 Jan 13 nicklas 1547
6222 14 Jan 13 nicklas 1548   /**
6222 14 Jan 13 nicklas 1549     Open a dialog for selecting one or more items of the given
6222 14 Jan 13 nicklas 1550     item type. 
6222 14 Jan 13 nicklas 1551     @param itemType The type of the item to select
6222 14 Jan 13 nicklas 1552     @param callback A document element or the id of an element that should receieve
6222 14 Jan 13 nicklas 1553       'base-selected' event for each selected item
6840 09 Apr 15 nicklas 1554     @param mode The selection mode (selectone, selectmultiple, selectfilter)
6222 14 Jan 13 nicklas 1555     @param extraUrl Extra parameters added to the URL that opens the popup
6222 14 Jan 13 nicklas 1556   */
6840 09 Apr 15 nicklas 1557   dialogs.selectItem = function(itemType, callback, mode, extraUrl)
6222 14 Jan 13 nicklas 1558   {
6315 06 Sep 13 nicklas 1559     var width = 1050;
6315 06 Sep 13 nicklas 1560     var height = 700;
6400 27 Jan 14 nicklas 1561     var controller = Items.getController(itemType);
6222 14 Jan 13 nicklas 1562     var url = App.getRoot() + controller.url;
6222 14 Jan 13 nicklas 1563     url += '?ID='+App.getSessionId();
6282 27 May 13 nicklas 1564     if (itemType == 'FILE')
6282 27 May 13 nicklas 1565     {
6880 21 Apr 15 nicklas 1566       url += '&cmd=' + (mode ? 'SelectMultiple' : 'SelectOne');
6282 27 May 13 nicklas 1567     }
6315 06 Sep 13 nicklas 1568     else if (itemType == 'DIRECTORY')
6315 06 Sep 13 nicklas 1569     {
6880 21 Apr 15 nicklas 1570       if (!mode || !mode.indexOf || mode.indexOf('select') == -1)
6880 21 Apr 15 nicklas 1571       {
6880 21 Apr 15 nicklas 1572         mode = mode ? 'selectmultipledirectory' : 'selectonedirectory';
6880 21 Apr 15 nicklas 1573       }
6880 21 Apr 15 nicklas 1574       url += '&mode=' + mode;
6315 06 Sep 13 nicklas 1575       width = 350;
6315 06 Sep 13 nicklas 1576       height = 500;
6315 06 Sep 13 nicklas 1577     }
6282 27 May 13 nicklas 1578     else
6282 27 May 13 nicklas 1579     {
6840 09 Apr 15 nicklas 1580       if (!mode || !mode.indexOf || mode.indexOf('select') == -1)
6840 09 Apr 15 nicklas 1581       {
6840 09 Apr 15 nicklas 1582         mode = mode ? 'selectmultiple' : 'selectone';
6840 09 Apr 15 nicklas 1583       }
6282 27 May 13 nicklas 1584       url += '&cmd=UpdateContext';
6840 09 Apr 15 nicklas 1585       url += '&mode=' + mode;    
6282 27 May 13 nicklas 1586     }
6222 14 Jan 13 nicklas 1587     if (callback) 
6222 14 Jan 13 nicklas 1588     {
6222 14 Jan 13 nicklas 1589       callback = Doc.element(callback);
6222 14 Jan 13 nicklas 1590       if (!callback.id) callback.id = 'n'+(new Date()).getTime();
6222 14 Jan 13 nicklas 1591       url += '&callback='+callback.id;
6222 14 Jan 13 nicklas 1592     }
6222 14 Jan 13 nicklas 1593     if (extraUrl) url += extraUrl;
6222 14 Jan 13 nicklas 1594     
7419 03 Nov 17 nicklas 1595     dialogs.openPopup(url, 'Select'+itemType, width, height);
6222 14 Jan 13 nicklas 1596   }
6221 10 Jan 13 nicklas 1597   
6221 10 Jan 13 nicklas 1598   /**
6157 08 Oct 12 nicklas 1599     Help icon 'click' handler. The help id is found on the target element's
6166 11 Oct 12 nicklas 1600     'data-help-id' attribute or with the help of the active tab control on the
6166 11 Oct 12 nicklas 1601     'data-tabcontrol-id' attribute.
6157 08 Oct 12 nicklas 1602   */
6157 08 Oct 12 nicklas 1603   internal.helpOnClick = function(event)
6157 08 Oct 12 nicklas 1604   {
6157 08 Oct 12 nicklas 1605     var hlp = event.currentTarget;
6157 08 Oct 12 nicklas 1606     var helpId = Data.get(hlp, 'help-id');
6157 08 Oct 12 nicklas 1607     var tabControlId = Data.get(hlp, 'tabcontrol-id');
6157 08 Oct 12 nicklas 1608     
6157 08 Oct 12 nicklas 1609     if (tabControlId)
6157 08 Oct 12 nicklas 1610     {
6166 11 Oct 12 nicklas 1611       dialogs.openTabControlHelp(tabControlId, helpId);
6157 08 Oct 12 nicklas 1612     }
6157 08 Oct 12 nicklas 1613     else if (helpId)
6157 08 Oct 12 nicklas 1614     {
6166 11 Oct 12 nicklas 1615       dialogs.openHelp(helpId);
6157 08 Oct 12 nicklas 1616     }
6157 08 Oct 12 nicklas 1617   }
6157 08 Oct 12 nicklas 1618   
6157 08 Oct 12 nicklas 1619   /**
6166 11 Oct 12 nicklas 1620     Zoom icon 'click' handler. The id of the textarea is found on the target
6166 11 Oct 12 nicklas 1621     element's 'data-textarea-id' attribute. The dialog title can be specified
6166 11 Oct 12 nicklas 1622     using the 'data-title' attribute, otherwise the textarea-id is used.
6166 11 Oct 12 nicklas 1623    */
6166 11 Oct 12 nicklas 1624   internal.zoomOnClick = function(event)
6166 11 Oct 12 nicklas 1625   {
6166 11 Oct 12 nicklas 1626     var zoom = event.currentTarget;
6166 11 Oct 12 nicklas 1627     var textAreaId = Data.get(zoom, 'textarea-id');
6166 11 Oct 12 nicklas 1628     var dialogTitle = Data.get(zoom, 'title', textAreaId);
6193 31 Oct 12 nicklas 1629     var useToolbar = Data.int(zoom, 'use-toolbar', 0);
6171 17 Oct 12 nicklas 1630     dialogs.openZoom(textAreaId, dialogTitle, useToolbar);
6166 11 Oct 12 nicklas 1631   }
6166 11 Oct 12 nicklas 1632   
6166 11 Oct 12 nicklas 1633   /**
6216 14 Dec 12 nicklas 1634     Calendar icon 'click' handler. The id of the textarea is found on the target
6216 14 Dec 12 nicklas 1635     element's 'data-textarea-id' attribute. The dialog title can be specified
6216 14 Dec 12 nicklas 1636     using the 'data-title' attribute, otherwise the textarea-id is used.
6216 14 Dec 12 nicklas 1637     The date format to use must be specified by 'data-date-format' attribute and
6216 14 Dec 12 nicklas 1638     if time is included the 'data-use-time' must be set.
6216 14 Dec 12 nicklas 1639    */
6216 14 Dec 12 nicklas 1640   internal.calendarOnClick = function(event)
6216 14 Dec 12 nicklas 1641   {
6216 14 Dec 12 nicklas 1642     var cal = event.currentTarget;
6216 14 Dec 12 nicklas 1643     var textAreaId = Data.get(cal, 'textarea-id');
6216 14 Dec 12 nicklas 1644     var dialogTitle = Data.get(cal, 'title', textAreaId);
6216 14 Dec 12 nicklas 1645     var dateFormat = Data.get(cal, 'date-format');
6216 14 Dec 12 nicklas 1646     var useTime = Data.get(cal, 'use-time', 0);
6216 14 Dec 12 nicklas 1647     dialogs.openCalendar(textAreaId, dialogTitle, dateFormat, useTime);
6216 14 Dec 12 nicklas 1648   }
6216 14 Dec 12 nicklas 1649   
6216 14 Dec 12 nicklas 1650   /**
6168 15 Oct 12 nicklas 1651     Event handler that sends a 'click' event to the element
6168 15 Oct 12 nicklas 1652     with id 'close' if the 'ESC' key is pressed. It is expected
6168 15 Oct 12 nicklas 1653     that the 'close' element actually closes the (popup) window,
6168 15 Oct 12 nicklas 1654     but it may perform other actions first.
6168 15 Oct 12 nicklas 1655   */
6168 15 Oct 12 nicklas 1656   internal.closeOnEscape = function(event)
6168 15 Oct 12 nicklas 1657   {
6168 15 Oct 12 nicklas 1658     if (event.keyCode == 27)
6168 15 Oct 12 nicklas 1659     {
7419 03 Nov 17 nicklas 1660       Events.sendClickEvent('close', event);
6168 15 Oct 12 nicklas 1661     }
6168 15 Oct 12 nicklas 1662   }
6168 15 Oct 12 nicklas 1663   
6168 15 Oct 12 nicklas 1664   /**
6166 11 Oct 12 nicklas 1665     Initializer that add 'onclick' event handlers to common
6216 14 Dec 12 nicklas 1666     elements: 'help', 'zoom', 'calendar', etc.
6157 08 Oct 12 nicklas 1667   */
6166 11 Oct 12 nicklas 1668   internal.addOnClickHandler = function(element, autoInit)
6157 08 Oct 12 nicklas 1669   {
6166 11 Oct 12 nicklas 1670     if (autoInit == 'help')
6157 08 Oct 12 nicklas 1671     {
6415 05 Feb 14 nicklas 1672       Buttons.addClickHandler(element, internal.helpOnClick);
6157 08 Oct 12 nicklas 1673     }
6166 11 Oct 12 nicklas 1674     else if (autoInit == 'zoom')
6166 11 Oct 12 nicklas 1675     {
6415 05 Feb 14 nicklas 1676       Buttons.addClickHandler(element, internal.zoomOnClick);
6166 11 Oct 12 nicklas 1677     }
6216 14 Dec 12 nicklas 1678     else if (autoInit == 'calendar')
6216 14 Dec 12 nicklas 1679     {
6415 05 Feb 14 nicklas 1680       Buttons.addClickHandler(element, internal.calendarOnClick);
6216 14 Dec 12 nicklas 1681     }
6157 08 Oct 12 nicklas 1682   }
6168 15 Oct 12 nicklas 1683   
6168 15 Oct 12 nicklas 1684   /**
6168 15 Oct 12 nicklas 1685     Initializer that add a keydown event handler to popup windows
6168 15 Oct 12 nicklas 1686     to detect 'ESC' which should close the window.
6168 15 Oct 12 nicklas 1687   */
6168 15 Oct 12 nicklas 1688   internal.initPage = function()
6168 15 Oct 12 nicklas 1689   {
6168 15 Oct 12 nicklas 1690     if (window.opener && Doc.element('close'))
6168 15 Oct 12 nicklas 1691     {
6168 15 Oct 12 nicklas 1692       window.addEventListener('keydown', internal.closeOnEscape, false);
6168 15 Oct 12 nicklas 1693     }
6168 15 Oct 12 nicklas 1694   }
6168 15 Oct 12 nicklas 1695   
6160 10 Oct 12 nicklas 1696   Doc.addElementInitializer(internal.addOnClickHandler);
6168 15 Oct 12 nicklas 1697   Doc.onLoad(internal.initPage);
6157 08 Oct 12 nicklas 1698   
6166 11 Oct 12 nicklas 1699   return dialogs;
6157 08 Oct 12 nicklas 1700 }();
6157 08 Oct 12 nicklas 1701
6180 22 Oct 12 nicklas 1702 var Items = function()
6180 22 Oct 12 nicklas 1703 {
6180 22 Oct 12 nicklas 1704   var items = {};
6180 22 Oct 12 nicklas 1705   var internal = {};
6400 27 Jan 14 nicklas 1706   var controllers = null;
6180 22 Oct 12 nicklas 1707   
6180 22 Oct 12 nicklas 1708   /**
6400 27 Jan 14 nicklas 1709     Get controller information for a given item type.
6400 27 Jan 14 nicklas 1710   */
6400 27 Jan 14 nicklas 1711   items.getController = function(itemType)
6400 27 Jan 14 nicklas 1712   {
6400 27 Jan 14 nicklas 1713     if (controllers == null) internal.initControllers();
6400 27 Jan 14 nicklas 1714     var controller = controllers[itemType];
6400 27 Jan 14 nicklas 1715     if (!controller)
6400 27 Jan 14 nicklas 1716     {
6400 27 Jan 14 nicklas 1717       var msg = 'Unhandled item: ' + itemType + '\n';
6400 27 Jan 14 nicklas 1718       msg += 'Please report this as a bug to the development team';
6400 27 Jan 14 nicklas 1719       alert(msg);
6400 27 Jan 14 nicklas 1720       return null;
6400 27 Jan 14 nicklas 1721     }
6400 27 Jan 14 nicklas 1722     return controller;
6400 27 Jan 14 nicklas 1723   }
6400 27 Jan 14 nicklas 1724   
6400 27 Jan 14 nicklas 1725   /**
6400 27 Jan 14 nicklas 1726     Get an array with all controllers.
6400 27 Jan 14 nicklas 1727   */
6400 27 Jan 14 nicklas 1728   items.getAllControllers = function()
6400 27 Jan 14 nicklas 1729   {
6400 27 Jan 14 nicklas 1730     if (controllers == null) internal.initControllers();
6400 27 Jan 14 nicklas 1731     return controllers;
6400 27 Jan 14 nicklas 1732   }
6400 27 Jan 14 nicklas 1733   
6400 27 Jan 14 nicklas 1734   /**
6400 27 Jan 14 nicklas 1735     Go to the single-item view page for the given item.
6400 27 Jan 14 nicklas 1736     The page may open in a popup or in the main window.
6400 27 Jan 14 nicklas 1737   */
6400 27 Jan 14 nicklas 1738   items.viewItem = function(itemType, itemId, extraUrl)
6400 27 Jan 14 nicklas 1739   {
6400 27 Jan 14 nicklas 1740     var controller = items.getController(itemType);
6400 27 Jan 14 nicklas 1741     if (controller.edit)
6400 27 Jan 14 nicklas 1742     {
6400 27 Jan 14 nicklas 1743       items.editItem(itemType, itemId, extraUrl);
6400 27 Jan 14 nicklas 1744       return;
6400 27 Jan 14 nicklas 1745     }
6400 27 Jan 14 nicklas 1746     var url = App.getRoot() + controller.url;
6400 27 Jan 14 nicklas 1747     url += '?ID='+App.getSessionId();
6400 27 Jan 14 nicklas 1748     url += '&cmd=ViewItem';
6400 27 Jan 14 nicklas 1749     if (itemId) url += '&item_id='+itemId;
6400 27 Jan 14 nicklas 1750     if (extraUrl) url += extraUrl;
6400 27 Jan 14 nicklas 1751     if (controller.popup)
6400 27 Jan 14 nicklas 1752     {
6400 27 Jan 14 nicklas 1753       Dialogs.openPopup(url, 'ViewItem'+itemType, controller.width, controller.height);
6400 27 Jan 14 nicklas 1754     }
6400 27 Jan 14 nicklas 1755     else
6400 27 Jan 14 nicklas 1756     {
6400 27 Jan 14 nicklas 1757       var theWindow = window;
6400 27 Jan 14 nicklas 1758       var i = 0;
6400 27 Jan 14 nicklas 1759       if (!controller.iframe)
6400 27 Jan 14 nicklas 1760       {
6400 27 Jan 14 nicklas 1761         while (theWindow.name != 'main' && i < 10)
6400 27 Jan 14 nicklas 1762         {
6400 27 Jan 14 nicklas 1763           theWindow = theWindow.parent;
6400 27 Jan 14 nicklas 1764           i++;
6400 27 Jan 14 nicklas 1765         }
6400 27 Jan 14 nicklas 1766       }
6400 27 Jan 14 nicklas 1767       theWindow.location.href = url;
6400 27 Jan 14 nicklas 1768     }
6400 27 Jan 14 nicklas 1769   }
6400 27 Jan 14 nicklas 1770   
6400 27 Jan 14 nicklas 1771   /**
6400 27 Jan 14 nicklas 1772     Open a dialog for creating a new item of the given type.
6400 27 Jan 14 nicklas 1773   */
6400 27 Jan 14 nicklas 1774   items.newItem = function(itemType, extraUrl)
6400 27 Jan 14 nicklas 1775   {
6400 27 Jan 14 nicklas 1776     items.editItem(itemType, 0, extraUrl);
6400 27 Jan 14 nicklas 1777   }
6400 27 Jan 14 nicklas 1778   
6400 27 Jan 14 nicklas 1779   /**
6400 27 Jan 14 nicklas 1780     Open a dialog for editing the item of the given
6400 27 Jan 14 nicklas 1781     type and id.
6400 27 Jan 14 nicklas 1782    */
6400 27 Jan 14 nicklas 1783   items.editItem = function(itemType, itemId, extraUrl)
6400 27 Jan 14 nicklas 1784   {
6400 27 Jan 14 nicklas 1785     var controller = items.getController(itemType);
6400 27 Jan 14 nicklas 1786     if (controller.edit == false)
6400 27 Jan 14 nicklas 1787     {
6400 27 Jan 14 nicklas 1788       items.viewItem(itemType, itemId, extraUrl);
6400 27 Jan 14 nicklas 1789       return;
6400 27 Jan 14 nicklas 1790     }
6400 27 Jan 14 nicklas 1791     var url = App.getRoot() + controller.url;
6400 27 Jan 14 nicklas 1792     url += '?ID='+App.getSessionId();
6400 27 Jan 14 nicklas 1793     var cmd = itemId ? 'EditItem' : 'NewItem';
6400 27 Jan 14 nicklas 1794     url += '&cmd='+cmd;
6400 27 Jan 14 nicklas 1795     if (itemId) url += '&item_id='+itemId;
6400 27 Jan 14 nicklas 1796     if (extraUrl) url += extraUrl;
7096 07 Mar 16 nicklas 1797     Dialogs.openPopup(url, 'Edit'+itemType, controller.width, controller.height);
6400 27 Jan 14 nicklas 1798   }
6400 27 Jan 14 nicklas 1799   
6400 27 Jan 14 nicklas 1800   /**
6817 31 Mar 15 nicklas 1801     Go to the list page for the given item type. Replaces the
6817 31 Mar 15 nicklas 1802     current page in the browser history.
6218 19 Dec 12 nicklas 1803   */
6218 19 Dec 12 nicklas 1804   items.list = function(itemType, extraUrl)
6218 19 Dec 12 nicklas 1805   {
6218 19 Dec 12 nicklas 1806     internal.itemCommand('List', itemType, null, extraUrl);
6218 19 Dec 12 nicklas 1807   }
6218 19 Dec 12 nicklas 1808   
6218 19 Dec 12 nicklas 1809   /**
6817 31 Mar 15 nicklas 1810     Go to the list page for the given item type. Remember the
6817 31 Mar 15 nicklas 1811     last page in the browser history.
6817 31 Mar 15 nicklas 1812    */
6817 31 Mar 15 nicklas 1813   items.listWithHistory = function(itemType, extraUrl)
6817 31 Mar 15 nicklas 1814   {
6817 31 Mar 15 nicklas 1815     internal.itemCommand('List', itemType, null, extraUrl, null, true);
6817 31 Mar 15 nicklas 1816   }
6817 31 Mar 15 nicklas 1817   
6817 31 Mar 15 nicklas 1818   /**
6218 19 Dec 12 nicklas 1819     Submit a request to mark the given item for removal.
6218 19 Dec 12 nicklas 1820     The current page is automatically reloaded.
6218 19 Dec 12 nicklas 1821   */
6261 27 Mar 13 nicklas 1822   items.deleteItem = function(itemType, itemId, ask, extraUrl)
6218 19 Dec 12 nicklas 1823   {
6289 05 Jun 13 nicklas 1824     if (ask)
6289 05 Jun 13 nicklas 1825     {
6289 05 Jun 13 nicklas 1826       if (!confirm('Delete this item?')) return;
6289 05 Jun 13 nicklas 1827     }
6218 19 Dec 12 nicklas 1828     internal.itemCommand('DeleteItem', itemType, itemId, extraUrl);
6218 19 Dec 12 nicklas 1829   }
6218 19 Dec 12 nicklas 1830   
6218 19 Dec 12 nicklas 1831   /**
6218 19 Dec 12 nicklas 1832     Submit a request to unmark the given item for removal.
6218 19 Dec 12 nicklas 1833     The current page is automatically reloaded.
6218 19 Dec 12 nicklas 1834   */
6218 19 Dec 12 nicklas 1835   items.restoreItem = function(itemType, itemId, extraUrl)
6218 19 Dec 12 nicklas 1836   {
6218 19 Dec 12 nicklas 1837     internal.itemCommand('RestoreItem', itemType, itemId, extraUrl);
6218 19 Dec 12 nicklas 1838   }
6218 19 Dec 12 nicklas 1839   
6218 19 Dec 12 nicklas 1840   /**
6218 19 Dec 12 nicklas 1841     Submit a request to delete the given item from the database.
6218 19 Dec 12 nicklas 1842     The current page is automatically changed to the list page.
6218 19 Dec 12 nicklas 1843   */
6307 15 Aug 13 nicklas 1844   items.deleteItemPermanently = function(itemType, itemId, ask, notify, extraUrl)
6218 19 Dec 12 nicklas 1845   {
6218 19 Dec 12 nicklas 1846     if (ask)
6218 19 Dec 12 nicklas 1847     {
6218 19 Dec 12 nicklas 1848       if (!confirm('Delete this item permanently?')) return;
6218 19 Dec 12 nicklas 1849     }
6218 19 Dec 12 nicklas 1850     
6307 15 Aug 13 nicklas 1851     if (!notify)
6307 15 Aug 13 nicklas 1852     {
6307 15 Aug 13 nicklas 1853       // Create temporary div to receive redirect
6307 15 Aug 13 nicklas 1854       // notification from popup window
6307 15 Aug 13 nicklas 1855       var notifyDiv = document.createElement('div');
6307 15 Aug 13 nicklas 1856       notifyDiv.id = 'n'+(new Date()).getTime();
6307 15 Aug 13 nicklas 1857       Events.addEventHandler(notifyDiv, 'base-notify', 
6307 15 Aug 13 nicklas 1858         function() 
6307 15 Aug 13 nicklas 1859         {
6307 15 Aug 13 nicklas 1860           Items.list(itemType, extraUrl); 
6307 15 Aug 13 nicklas 1861         }
6307 15 Aug 13 nicklas 1862       );
6307 15 Aug 13 nicklas 1863       document.body.appendChild(notifyDiv);
6307 15 Aug 13 nicklas 1864       notify = notifyDiv.id;
6307 15 Aug 13 nicklas 1865     }
6218 19 Dec 12 nicklas 1866     
6218 19 Dec 12 nicklas 1867     var url = App.getRoot();
6218 19 Dec 12 nicklas 1868     url += 'views/trashcan/index.jsp';
6218 19 Dec 12 nicklas 1869     url += '?ID='+App.getSessionId();
6218 19 Dec 12 nicklas 1870     url += '&cmd=DeleteItem&popup=1';
6218 19 Dec 12 nicklas 1871     url += '&item_type='+itemType+'&item_id=' + itemId;
6307 15 Aug 13 nicklas 1872     url += '&notify='+notify;
6218 19 Dec 12 nicklas 1873     Dialogs.openPopup(url, 'Delete'+itemType, 300, 200);
6218 19 Dec 12 nicklas 1874   }
6218 19 Dec 12 nicklas 1875
6218 19 Dec 12 nicklas 1876   
6218 19 Dec 12 nicklas 1877   /**
6218 19 Dec 12 nicklas 1878     Go to the trashcan page for the given item and list other
6218 19 Dec 12 nicklas 1879     items that are using it.
6218 19 Dec 12 nicklas 1880   */
6218 19 Dec 12 nicklas 1881   items.showUsingItems = function(itemType, itemId)
6218 19 Dec 12 nicklas 1882   {
6218 19 Dec 12 nicklas 1883     var url = App.getRoot();
6218 19 Dec 12 nicklas 1884     url += 'views/trashcan/index.jsp';
6218 19 Dec 12 nicklas 1885     url += '?ID='+App.getSessionId();
6218 19 Dec 12 nicklas 1886     url += '&cmd=ViewUsingItems';
6218 19 Dec 12 nicklas 1887     url += '&item_type='+itemType+'&item_id='+itemId;
6218 19 Dec 12 nicklas 1888     location.href = url;
6218 19 Dec 12 nicklas 1889   }
6218 19 Dec 12 nicklas 1890   
6220 10 Jan 13 nicklas 1891   /**
6220 10 Jan 13 nicklas 1892     Open a popup window for selecting a plug-in that can run in the 
6220 10 Jan 13 nicklas 1893     current item context.
6220 10 Jan 13 nicklas 1894     @param itemType The current item type
6220 10 Jan 13 nicklas 1895     @param itemId The id of the current item
6315 06 Sep 13 nicklas 1896     @param pluginType One of: IMPORT, EXPORT, ANALYZE or OTHER (default)
6220 10 Jan 13 nicklas 1897   */
6315 06 Sep 13 nicklas 1898   items.runPlugin = function(itemType, itemId, pluginType, extraUrl, cmd)
6218 19 Dec 12 nicklas 1899   {
6315 06 Sep 13 nicklas 1900     if (!cmd)
6220 10 Jan 13 nicklas 1901     {
6315 06 Sep 13 nicklas 1902       if (pluginType == 'EXPORT')
6315 06 Sep 13 nicklas 1903       {
6315 06 Sep 13 nicklas 1904         cmd = 'ExportItem';
6315 06 Sep 13 nicklas 1905       }
6315 06 Sep 13 nicklas 1906       else if (pluginType == 'IMPORT')
6315 06 Sep 13 nicklas 1907       {
6315 06 Sep 13 nicklas 1908         cmd = 'ImportItem';
6315 06 Sep 13 nicklas 1909       }
6315 06 Sep 13 nicklas 1910       else if (pluginType == 'ANALYZE')
6315 06 Sep 13 nicklas 1911       {
6315 06 Sep 13 nicklas 1912         cmd = 'RunAnalysisPlugin';
6315 06 Sep 13 nicklas 1913       }
6315 06 Sep 13 nicklas 1914       else
6315 06 Sep 13 nicklas 1915       {
6315 06 Sep 13 nicklas 1916         cmd = 'RunPlugin';
6315 06 Sep 13 nicklas 1917       }
6220 10 Jan 13 nicklas 1918     }
6220 10 Jan 13 nicklas 1919     internal.itemCommand(cmd, itemType, itemId, extraUrl, {width: 750, height: 500});
6220 10 Jan 13 nicklas 1920   }
6220 10 Jan 13 nicklas 1921   
7001 05 Nov 15 nicklas 1922   /**
7001 05 Nov 15 nicklas 1923     Generic callback for handling 'base-selected' event that is sent
7001 05 Nov 15 nicklas 1924     to the selection list when a single item has been selected.
7001 05 Nov 15 nicklas 1925    */
7001 05 Nov 15 nicklas 1926   items.onItemSelected = function(event)
7001 05 Nov 15 nicklas 1927   {
7001 05 Nov 15 nicklas 1928     var list = event.currentTarget;
7001 05 Nov 15 nicklas 1929     if (!list.options) list = Doc.element(list.id+'.list');
7001 05 Nov 15 nicklas 1930     
7001 05 Nov 15 nicklas 1931     // If 'none' is first option, insert at index=1, otherwise at index=0
7001 05 Nov 15 nicklas 1932     var insertIndex = list.length > 0 && list[0].value == '0' ? 1 : 0;
7001 05 Nov 15 nicklas 1933     
7001 05 Nov 15 nicklas 1934     // Create the new option
7001 05 Nov 15 nicklas 1935     var option = new Option(event.detail.name, event.detail.id);
7001 05 Nov 15 nicklas 1936     option.title = option.text;
7001 05 Nov 15 nicklas 1937     
7001 05 Nov 15 nicklas 1938     var changed = true;
7001 05 Nov 15 nicklas 1939     if (list.length <= insertIndex)
7001 05 Nov 15 nicklas 1940     {
7001 05 Nov 15 nicklas 1941       // Add the new option to the end
7001 05 Nov 15 nicklas 1942       list[list.length] = option;
7001 05 Nov 15 nicklas 1943     }
7001 05 Nov 15 nicklas 1944     else if (list[insertIndex].value == 'NaN')
7001 05 Nov 15 nicklas 1945     {
7001 05 Nov 15 nicklas 1946       // Add a new option before the current
7001 05 Nov 15 nicklas 1947       list.add(option, list[insertIndex]);
7001 05 Nov 15 nicklas 1948     }
7001 05 Nov 15 nicklas 1949     else
7001 05 Nov 15 nicklas 1950     {
7001 05 Nov 15 nicklas 1951       // Replace the current option
7001 05 Nov 15 nicklas 1952       changed = list[list.selectedIndex].value != option.value;
7001 05 Nov 15 nicklas 1953       list[insertIndex] = option;
7001 05 Nov 15 nicklas 1954     }
7001 05 Nov 15 nicklas 1955     list.selectedIndex = insertIndex;
7001 05 Nov 15 nicklas 1956     if (changed) Events.sendChangeEvent(list);
7001 05 Nov 15 nicklas 1957   }
7001 05 Nov 15 nicklas 1958   
6817 31 Mar 15 nicklas 1959   internal.itemCommand = function(cmd, itemType, itemId, extraUrl, popup, noReplace)
6220 10 Jan 13 nicklas 1960   {
6400 27 Jan 14 nicklas 1961     var controller = items.getController(itemType);
6218 19 Dec 12 nicklas 1962     var url = App.getRoot() + controller.url;
6218 19 Dec 12 nicklas 1963     url += '?ID='+App.getSessionId();
6218 19 Dec 12 nicklas 1964     url += '&cmd='+cmd;
6218 19 Dec 12 nicklas 1965     if (itemId) url += '&item_id='+itemId;
6218 19 Dec 12 nicklas 1966     if (extraUrl) url += extraUrl;
6400 27 Jan 14 nicklas 1967     if (popup == true) popup = controller;
6220 10 Jan 13 nicklas 1968     if (popup)
6220 10 Jan 13 nicklas 1969     {
6220 10 Jan 13 nicklas 1970       Dialogs.openPopup(url, cmd+itemType, popup.width, popup.height);
6220 10 Jan 13 nicklas 1971     }
6817 31 Mar 15 nicklas 1972     else if (noReplace)
6817 31 Mar 15 nicklas 1973     {
6817 31 Mar 15 nicklas 1974       location.href = url;
6817 31 Mar 15 nicklas 1975     }
6220 10 Jan 13 nicklas 1976     else
6220 10 Jan 13 nicklas 1977     {
6220 10 Jan 13 nicklas 1978       location.replace(url);
6220 10 Jan 13 nicklas 1979     }
6218 19 Dec 12 nicklas 1980   }
6218 19 Dec 12 nicklas 1981   
6218 19 Dec 12 nicklas 1982   /**
6180 22 Oct 12 nicklas 1983     Event handler for clicking on item links. The item type and id should be stored
6180 22 Oct 12 nicklas 1984     as data-item-type and data-item-id attributes on the target element. If data-no-edit
6308 20 Aug 13 nicklas 1985     is set to 1 the edit dialog is disabled. If data-force-edit is set the edit
6400 27 Jan 14 nicklas 1986     dialog is always used. This event handler is automatically attached
6180 22 Oct 12 nicklas 1987     to auto-init elements with data-auto-init='item-link'.
6180 22 Oct 12 nicklas 1988   */
6180 22 Oct 12 nicklas 1989   items.itemOnClick = function(event)
6180 22 Oct 12 nicklas 1990   {
6180 22 Oct 12 nicklas 1991     var element = event.currentTarget;
6180 22 Oct 12 nicklas 1992     var itemType = Data.get(element, 'item-type');
6180 22 Oct 12 nicklas 1993     var itemId = Data.int(element, 'item-id');
6180 22 Oct 12 nicklas 1994     var noEdit = Data.int(element, 'no-edit', 0);
6308 20 Aug 13 nicklas 1995     var forceEdit = Data.int(element, 'force-edit', 0);
6260 27 Mar 13 nicklas 1996     var extraUrl = Data.get(element, 'extra-url');
6180 22 Oct 12 nicklas 1997     var specialKey = event.altKey || event.ctrlKey || event.shiftKey;
6308 20 Aug 13 nicklas 1998     
6308 20 Aug 13 nicklas 1999     var editDialog = forceEdit || (specialKey && !noEdit);
6400 27 Jan 14 nicklas 2000     if (editDialog)
6400 27 Jan 14 nicklas 2001     {
6400 27 Jan 14 nicklas 2002       items.editItem(itemType, itemId, extraUrl);
6400 27 Jan 14 nicklas 2003     }
6400 27 Jan 14 nicklas 2004     else
6400 27 Jan 14 nicklas 2005     {
6400 27 Jan 14 nicklas 2006       items.viewItem(itemType, itemId, extraUrl);
6400 27 Jan 14 nicklas 2007     }
6180 22 Oct 12 nicklas 2008   }
6180 22 Oct 12 nicklas 2009
6180 22 Oct 12 nicklas 2010   /**
6180 22 Oct 12 nicklas 2011     Initialize all item-link elements by adding a click event
6180 22 Oct 12 nicklas 2012     handler to them.
6180 22 Oct 12 nicklas 2013    */
6180 22 Oct 12 nicklas 2014   internal.initializeItemLinks = function(element, autoInit)
6180 22 Oct 12 nicklas 2015   {
6180 22 Oct 12 nicklas 2016     if (autoInit != 'item-link') return;
6180 22 Oct 12 nicklas 2017     Events.addEventHandler(element, 'click', Items.itemOnClick);
6180 22 Oct 12 nicklas 2018   }
6180 22 Oct 12 nicklas 2019   Doc.addElementInitializer(internal.initializeItemLinks);
6180 22 Oct 12 nicklas 2020
6400 27 Jan 14 nicklas 2021
6400 27 Jan 14 nicklas 2022   /**
6400 27 Jan 14 nicklas 2023     Controller JSP page for all items. Each entry contains:
6400 27 Jan 14 nicklas 2024     
6400 27 Jan 14 nicklas 2025     url: The JSP that is the controller for the item type
6400 27 Jan 14 nicklas 2026     width, height: Size of popup for edit dialog
6400 27 Jan 14 nicklas 2027     edit: If true, always open in edit mode, if false, never open in edit mode
6400 27 Jan 14 nicklas 2028     popup: If set, the view page is opened in a popup
6400 27 Jan 14 nicklas 2029     noAnyToAny: If true, it is not possible to link AnyToAny items
6400 27 Jan 14 nicklas 2030    */
6400 27 Jan 14 nicklas 2031   internal.initControllers = function()
6400 27 Jan 14 nicklas 2032   {
6400 27 Jan 14 nicklas 2033     controllers = [];
6400 27 Jan 14 nicklas 2034     controllers['ANNOTATION'] = { url:'common/annotations/index.jsp', width:750, height:500, edit:true, popup:true, noAnyToAny:true };
7616 04 Mar 19 nicklas 2035     controllers['ANNOTATIONTYPE'] = { url:'admin/annotationtypes/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2036     controllers['ANNOTATIONTYPECATEGORY'] = { url:'admin/annotationtypecategories/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2037     controllers['ANYTOANY'] = { url:'common/anytoany/index.jsp', width:600, height:400, popup:true, noAnyToAny:true };
6400 27 Jan 14 nicklas 2038     controllers['ARRAYBATCH'] = { url:'lims/arraybatches/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2039     controllers['ARRAYDESIGN'] = { url:'lims/arraydesigns/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2040     controllers['ARRAYSLIDE'] = { url:'lims/arrayslides/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2041     controllers['BIOASSAY'] = { url:'views/experiments/bioassays/index.jsp', width:600, height:400, noAnyToAny:true };
6400 27 Jan 14 nicklas 2042     controllers['BIOASSAYSET'] = { url:'views/experiments/bioassaysets/index.jsp', width:750, height:500, noAnyToAny:true };
6400 27 Jan 14 nicklas 2043     controllers['BIOMATERIALEVENT'] = { url:'biomaterials/events/index.jsp', width:600, height:400, noAnyToAny:true };
6400 27 Jan 14 nicklas 2044     controllers['BIOMATERIALLIST'] = { url:'biomaterials/lists/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2045     controllers['BIOPLATE'] = { url:'biomaterials/bioplates/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2046     controllers['BIOPLATEEVENT'] = { url:'biomaterials/bioplates/events/index.jsp', width:600, height:400, noAnyToAny:true };
6400 27 Jan 14 nicklas 2047     controllers['BIOPLATEEVENTTYPE'] = { url:'biomaterials/bioplateeventtypes/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2048     controllers['BIOPLATETYPE'] = { url:'biomaterials/bioplatetypes/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2049     controllers['BIOSOURCE'] = { url:'biomaterials/biosources/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2050     controllers['BIOWELL'] = { url:'biomaterials/bioplates/wells/index.jsp', width:450, height:300, edit: true, noAnyToAny:true };
6400 27 Jan 14 nicklas 2051     controllers['CLIENT'] = { url:'admin/clients/index.jsp', width:450, height:300 };
6400 27 Jan 14 nicklas 2052     controllers['CHANGEHISTORY'] = { url:'common/history/index.jsp', width:600, height:400, edit:false, popup:true, noAnyToAny:true };
6400 27 Jan 14 nicklas 2053     controllers['DATAFILETYPE'] = { url:'admin/datafiletypes/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2054     controllers['DERIVEDBIOASSAY'] = { url:'views/derivedbioassays/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2055     controllers['DIRECTORY'] = { url:'filemanager/directories/index.jsp', width:600, height:400, popup:true };
6400 27 Jan 14 nicklas 2056     controllers['EXPERIMENT'] = { url:'views/experiments/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2057     controllers['EXTRACT'] = { url:'biomaterials/extracts/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2058     controllers['EXTRAVALUE'] = { url:'views/experiments/extravalues/index.jsp', width:600, height:400, edit:false };
6400 27 Jan 14 nicklas 2059     controllers['EXTRAVALUETYPE'] = { url:'admin/extravaluetypes/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2060     controllers['FEATURE'] = { url:'lims/arraydesigns/features/index.jsp', width:750, height:500, popup:true, edit:false, noAnyToAny:true };
6576 22 Oct 14 nicklas 2061     controllers['FILE'] = { url:'filemanager/index.jsp', iframe:true, width:750, height:500 };
6497 26 Jun 14 nicklas 2062     controllers['FILESERVER'] = { url:'filemanager/fileservers/index.jsp', width:750, height:500 };
6576 22 Oct 14 nicklas 2063     controllers['FORMULA'] = { url:'views/formulas/index.jsp', width:900, height:600 };
6400 27 Jan 14 nicklas 2064     controllers['GROUP'] = { url:'admin/groups/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2065     controllers['HARDWARE'] = { url:'admin/hardware/index.jsp', width:800, height:500 };
6400 27 Jan 14 nicklas 2066     controllers['HELP'] = { url:'admin/clients/help/index.jsp', width:600, height:400, noAnyToAny:true };
6742 17 Feb 15 nicklas 2067     controllers['ITEMLIST'] = { url:'views/itemlists/index.jsp', width:750, height:500 };
6770 13 Mar 15 nicklas 2068     controllers['SYNCFILTER'] = { url:'views/itemlists/syncfilter/index.jsp', width:750, height:500, popup:true, noAnyToAny:true };
6613 21 Nov 14 nicklas 2069     controllers['ITEMSUBTYPE'] = { url:'admin/itemsubtypes/index.jsp', width:690, height:460 };
6400 27 Jan 14 nicklas 2070     controllers['JOB'] = { url:'views/jobs/index.jsp', width:750, height:500, popup:true, edit:false };
6621 24 Nov 14 nicklas 2071     controllers['JOBAGENT'] = { url:'admin/jobagents/index.jsp', width:750, height:500 };
6991 02 Nov 15 nicklas 2072     controllers['KIT'] = { url:'biomaterials/kits/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2073     controllers['MESSAGE'] = { url:'my_base/messages/index.jsp', width:600, height:400, popup:true, edit:false };
6400 27 Jan 14 nicklas 2074     controllers['MIMETYPE'] = { url:'admin/mimetypes/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2075     controllers['NEWS'] = { url:'admin/news/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2076     controllers['PERMISSIONTEMPLATE'] = { url:'views/permissiontemplates/index.jsp', width:450, height:300 };
6400 27 Jan 14 nicklas 2077     controllers['PHYSICALBIOASSAY'] = { url:'views/physicalbioassays/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2078     controllers['PLATE'] = { url:'lims/plates/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2079     controllers['PLATEEVENT'] = { url:'lims/plates/events/index.jsp', width:600, height:400, noAnyToAny:true };
6400 27 Jan 14 nicklas 2080     controllers['PLATEEVENTTYPE'] = { url:'lims/platetypes/eventtypes/index.jsp', width:450, height:300 };
6400 27 Jan 14 nicklas 2081     controllers['PLATEGEOMETRY'] = { url:'lims/geometries/index.jsp', width:450, height:300 };
6400 27 Jan 14 nicklas 2082     controllers['PLATEMAPPING'] = { url:'lims/platemappings/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2083     controllers['PLATETYPE'] = { url:'lims/platetypes/index.jsp', width:450, height:300 };
6400 27 Jan 14 nicklas 2084     controllers['PLATFORM'] = { url:'admin/platforms/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2085     controllers['PLATFORMVARIANT'] = { url:'admin/platforms/variants/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2086     controllers['PLUGINCONFIGURATION'] = { url:'admin/pluginconfigurations/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2087     controllers['PLUGINDEFINITION'] = { url:'admin/plugindefinitions/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2088     controllers['PLUGINTYPE'] = { url:'admin/plugintypes/index.jsp', width:600, height:400 };
7199 17 Oct 16 nicklas 2089     controllers['PROJECT'] = { url:'my_base/projects/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2090     controllers['PROTOCOL'] = { url:'admin/protocols/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2091     controllers['QUANTITY'] = { url:'admin/quantities/index.jsp', width:450, height:300 };
6400 27 Jan 14 nicklas 2092     controllers['QUOTA'] = { url:'admin/quota/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2093     controllers['QUOTATYPE'] = { url:'admin/quotatypes/index.jsp', width:450, height:300 };
6400 27 Jan 14 nicklas 2094     controllers['RAWBIOASSAY'] = { url:'views/rawbioassays/index.jsp', width:750, height:500 };
6916 22 May 15 nicklas 2095     controllers['ROOTRAWBIOASSAY'] = { url:'views/experiments/rootrawbioassays/index.jsp', width:750, height:500, noAnyToAny: true };
6400 27 Jan 14 nicklas 2096     controllers['RAWDATA'] = { url:'views/rawbioassays/rawdata/index.jsp', width:750, height:500, popup: true, edit:false, noAnyToAny:true };
6400 27 Jan 14 nicklas 2097     controllers['REPORTER'] = { url:'views/reporters/index.jsp', width:600, height:400, noAnyToAny:true };
6400 27 Jan 14 nicklas 2098     controllers['REPORTERCLONETEMPLATE'] = { url:'admin/reporterclonetemplates/index.jsp', width:750, height:500 };
6400 27 Jan 14 nicklas 2099     controllers['REPORTERLIST'] = { url:'views/reporterlists/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2100     controllers['REPORTERSCORE'] = controllers['REPORTER'];
6400 27 Jan 14 nicklas 2101     controllers['REPORTERTYPE'] = { url:'admin/reportertypes/index.jsp', width:450, height:300 };
6400 27 Jan 14 nicklas 2102     controllers['ROLE'] = { url:'admin/roles/index.jsp', width:600, height:400 };
6400 27 Jan 14 nicklas 2103     controllers['SAMPLE'] = { url:'biomaterials/samples/index.jsp', width:750, height:500 };
7952 12 May 21 nicklas 2104     controllers['SESSION'] = { url:'views/sessions/index.jsp', width:450, height:300, edit:false, noAnyToAny:true };
6400 27 Jan 14 nicklas 2105     controllers['SOFTWARE'] = { url:'admin/software/index.jsp', width:800, height:500 };
6400 27 Jan 14 nicklas 2106     controllers['TAG'] = { url:'biomaterials/tags/index.jsp', width:800, height:500 };
6400 27 Jan 14 nicklas 2107     controllers['TRANSFORMATION'] = { url:'views/experiments/transformations/index.jsp', width:450, height:300, noAnyToAny:true };
6400 27 Jan 14 nicklas 2108     controllers['UNIT'] = { url:'admin/quantities/units/index.jsp', width:600, height:400, noAnyToAny:true };
6400 27 Jan 14 nicklas 2109     controllers['USER'] = { url:'admin/users/index.jsp', width:750, height:500 };
7407 05 Oct 17 nicklas 2110     controllers['USERDEVICE'] = { url:'views/devices/index.jsp', width:450, height:300, noAnyToAny:true };
6400 27 Jan 14 nicklas 2111     controllers['WELL'] = { url:'lims/plates/wells/index.jsp', width:750, height:500, noAnyToAny:true };
6400 27 Jan 14 nicklas 2112   }
6400 27 Jan 14 nicklas 2113
6400 27 Jan 14 nicklas 2114   
6180 22 Oct 12 nicklas 2115   return items;
6180 22 Oct 12 nicklas 2116 }();
6180 22 Oct 12 nicklas 2117
6256 25 Mar 13 nicklas 2118 var Files = function()
6256 25 Mar 13 nicklas 2119 {
6256 25 Mar 13 nicklas 2120   var files = {};
6256 25 Mar 13 nicklas 2121   var internal = {};
6180 22 Oct 12 nicklas 2122
6256 25 Mar 13 nicklas 2123   /**
6256 25 Mar 13 nicklas 2124     Open a new browser window for viewing the file contents of
6256 25 Mar 13 nicklas 2125     the file with the given id. If no window name is specified a
6256 25 Mar 13 nicklas 2126     new window is always created.
6256 25 Mar 13 nicklas 2127   */
6256 25 Mar 13 nicklas 2128   files.viewFile = function(fileId, windowName)
6256 25 Mar 13 nicklas 2129   {
6400 27 Jan 14 nicklas 2130     var controller = Items.getController('FILE');
6256 25 Mar 13 nicklas 2131     var url = App.getRoot() + controller.url;
6256 25 Mar 13 nicklas 2132     url += '?ID='+App.getSessionId();
6277 24 May 13 nicklas 2133     url += '&cmd=ViewFile&item_id='+fileId;
6256 25 Mar 13 nicklas 2134     if (!windowName) windowName = '_blank';
6256 25 Mar 13 nicklas 2135     window.open(url, windowName);
6256 25 Mar 13 nicklas 2136   }
6256 25 Mar 13 nicklas 2137   
6256 25 Mar 13 nicklas 2138   /**
6256 25 Mar 13 nicklas 2139     Open a popup window for downloading the file contents of
6256 25 Mar 13 nicklas 2140     the file with the given id. The download should start 
6256 25 Mar 13 nicklas 2141     automatically.
6256 25 Mar 13 nicklas 2142   */
6256 25 Mar 13 nicklas 2143   files.downloadFile = function(fileId)
6256 25 Mar 13 nicklas 2144   {
6400 27 Jan 14 nicklas 2145     var controller = Items.getController('FILE');
6256 25 Mar 13 nicklas 2146     var url = App.getRoot() + controller.url;
6256 25 Mar 13 nicklas 2147     url += '?ID='+App.getSessionId();
6277 24 May 13 nicklas 2148     url += '&cmd=DownloadFile&item_id='+fileId;
6308 20 Aug 13 nicklas 2149     Dialogs.openPopup(url, 'DownloadFile', 450, 300);
6256 25 Mar 13 nicklas 2150   }
6256 25 Mar 13 nicklas 2151   
6256 25 Mar 13 nicklas 2152   /**
6256 25 Mar 13 nicklas 2153     Event handler that call 'viewFile' when clicking
6256 25 Mar 13 nicklas 2154     on the attached target. The file id should be stored
6256 25 Mar 13 nicklas 2155     in the 'data-file-id' attribute.
6256 25 Mar 13 nicklas 2156   */
6256 25 Mar 13 nicklas 2157   files.viewFileOnClick = function(event)
6256 25 Mar 13 nicklas 2158   {
6256 25 Mar 13 nicklas 2159     files.viewFile(Data.get(event.currentTarget, 'file-id'));
6256 25 Mar 13 nicklas 2160   }
6256 25 Mar 13 nicklas 2161   
6256 25 Mar 13 nicklas 2162   /**
6256 25 Mar 13 nicklas 2163     Event handler that call 'downloadFile' when clicking
6256 25 Mar 13 nicklas 2164     on the attached target. The file id should be stored
6256 25 Mar 13 nicklas 2165     in the 'data-file-id' attribute.
6256 25 Mar 13 nicklas 2166   */
6256 25 Mar 13 nicklas 2167   files.downloadFileOnClick = function(event)
6256 25 Mar 13 nicklas 2168   {
6256 25 Mar 13 nicklas 2169     files.downloadFile(Data.get(event.currentTarget, 'file-id'));
6256 25 Mar 13 nicklas 2170   }
6256 25 Mar 13 nicklas 2171   
6256 25 Mar 13 nicklas 2172   internal.initializeFileLinks = function(element, autoInit)
6256 25 Mar 13 nicklas 2173   {
6256 25 Mar 13 nicklas 2174     if (autoInit == 'view-file')
6256 25 Mar 13 nicklas 2175     {
6256 25 Mar 13 nicklas 2176       Events.addEventHandler(element, 'click', Files.viewFileOnClick);
6256 25 Mar 13 nicklas 2177     }
6256 25 Mar 13 nicklas 2178     else if (autoInit == 'download-file')
6256 25 Mar 13 nicklas 2179     {
6256 25 Mar 13 nicklas 2180       Events.addEventHandler(element, 'click', Files.downloadFileOnClick);
6256 25 Mar 13 nicklas 2181     }
6256 25 Mar 13 nicklas 2182   }
6256 25 Mar 13 nicklas 2183   Doc.addElementInitializer(internal.initializeFileLinks);
6256 25 Mar 13 nicklas 2184   
6256 25 Mar 13 nicklas 2185   return files;
6256 25 Mar 13 nicklas 2186 }();
6180 22 Oct 12 nicklas 2187
6168 15 Oct 12 nicklas 2188 var Forms = function()
6168 15 Oct 12 nicklas 2189 {
6168 15 Oct 12 nicklas 2190   var forms = {};
6169 15 Oct 12 nicklas 2191   var internal = {};
6168 15 Oct 12 nicklas 2192   
6168 15 Oct 12 nicklas 2193   /**
6175 19 Oct 12 nicklas 2194     Display a notification that is related to a form element.
6175 19 Oct 12 nicklas 2195     Typically, an error message that explains what need to be
6175 19 Oct 12 nicklas 2196     filled in in an invalid element. 
6175 19 Oct 12 nicklas 2197     
6175 19 Oct 12 nicklas 2198     @param element A document element or the id of an element, if no
6175 19 Oct 12 nicklas 2199       element is found the message is displayed with an alert dialog
6175 19 Oct 12 nicklas 2200       instead
6175 19 Oct 12 nicklas 2201     @param message The message to display
6175 19 Oct 12 nicklas 2202     @param subclass Optional, a subclass that is applied to the notification 'div' container
6176 19 Oct 12 nicklas 2203     @param pointerClass Optional, if not given the function will automatically decide if
6176 19 Oct 12 nicklas 2204       the pointer is above or below the notification. Accept values: pointer-above, pointer-below, pointer-left
6450 22 Apr 14 nicklas 2205     @param pointerAlign 'left' or 'right', if not specified auto-select depending on location on page
6175 19 Oct 12 nicklas 2206   */
6450 22 Apr 14 nicklas 2207   forms.showNotification = function(element, message, subclass, pointerClass, pointerAlign)
6175 19 Oct 12 nicklas 2208   {
6175 19 Oct 12 nicklas 2209     element = Doc.element(element);
6175 19 Oct 12 nicklas 2210     if (!element)
6175 19 Oct 12 nicklas 2211     {
6175 19 Oct 12 nicklas 2212       alert(message);
6175 19 Oct 12 nicklas 2213     }
6175 19 Oct 12 nicklas 2214     else
6175 19 Oct 12 nicklas 2215     {
6175 19 Oct 12 nicklas 2216       // Create the notification div element
6175 19 Oct 12 nicklas 2217       var notifyDiv = document.createElement('div');
6175 19 Oct 12 nicklas 2218       notifyDiv.className = 'notify';
6175 19 Oct 12 nicklas 2219       if (subclass) notifyDiv.className += ' ' + subclass;
6175 19 Oct 12 nicklas 2220
6175 19 Oct 12 nicklas 2221       // Decide how to position the notification depending on the element's
6175 19 Oct 12 nicklas 2222       // position in the window
6400 27 Jan 14 nicklas 2223       var pos = Doc.getElementPosition(element);
6175 19 Oct 12 nicklas 2224       var winPos = App.getWindowPosition();
6176 19 Oct 12 nicklas 2225       if (!pointerClass)
6175 19 Oct 12 nicklas 2226       {
6176 19 Oct 12 nicklas 2227         // Automatic detection if pointer is above or below
6176 19 Oct 12 nicklas 2228         if (pos.top+pos.height+100 > winPos.height)
6176 19 Oct 12 nicklas 2229         {
6176 19 Oct 12 nicklas 2230           // The element is too close to the bottom so we need to
6176 19 Oct 12 nicklas 2231           // position the notification above it
6176 19 Oct 12 nicklas 2232           pointerClass = 'pointer-below';
6176 19 Oct 12 nicklas 2233         }
6176 19 Oct 12 nicklas 2234         else
6176 19 Oct 12 nicklas 2235         {
6176 19 Oct 12 nicklas 2236           // Position the notification below the element
6176 19 Oct 12 nicklas 2237           pointerClass = 'pointer-above';
6176 19 Oct 12 nicklas 2238         }
6176 19 Oct 12 nicklas 2239       }
6176 19 Oct 12 nicklas 2240       
6176 19 Oct 12 nicklas 2241       var pointerPos; // style for the pointer position
6176 19 Oct 12 nicklas 2242       if (pointerClass == 'pointer-below')
6176 19 Oct 12 nicklas 2243       {
6175 19 Oct 12 nicklas 2244         notifyDiv.style.bottom = (winPos.height-pos.top+8)+'px';
6175 19 Oct 12 nicklas 2245         notifyDiv.style.top = 'auto';
6175 19 Oct 12 nicklas 2246       }
6176 19 Oct 12 nicklas 2247       else if (pointerClass == 'pointer-left')
6175 19 Oct 12 nicklas 2248       {
6176 19 Oct 12 nicklas 2249         notifyDiv.style.top = (pos.top-2) + 'px';
6176 19 Oct 12 nicklas 2250         notifyDiv.style.left = (pos.left+pos.width+8)+'px';
6176 19 Oct 12 nicklas 2251         pointerPos = 'top: 0.6em;';
6175 19 Oct 12 nicklas 2252       }
6176 19 Oct 12 nicklas 2253       else // pointer-above
6175 19 Oct 12 nicklas 2254       {
6176 19 Oct 12 nicklas 2255         notifyDiv.style.top = (pos.top+pos.height+8)+'px';
6175 19 Oct 12 nicklas 2256       }
6176 19 Oct 12 nicklas 2257       
6176 19 Oct 12 nicklas 2258       if (!pointerPos)
6175 19 Oct 12 nicklas 2259       {
6450 22 Apr 14 nicklas 2260         if (pos.left > winPos.width / 2 && pointerAlign != 'left')
6176 19 Oct 12 nicklas 2261         {
6176 19 Oct 12 nicklas 2262           // If we are on the right side of the window, 
6176 19 Oct 12 nicklas 2263           // align the notification to the right
6176 19 Oct 12 nicklas 2264           notifyDiv.style.right = (winPos.width-(pos.left+pos.width)) + 'px';
6176 19 Oct 12 nicklas 2265           pointerPos = pos.width < 30 ? 'right: 0.6em; left: auto;' : 'right: 1.2em; left: auto;';
6176 19 Oct 12 nicklas 2266         }
6176 19 Oct 12 nicklas 2267         else
6176 19 Oct 12 nicklas 2268         {
6176 19 Oct 12 nicklas 2269           // otherwise, align it to the left
6176 19 Oct 12 nicklas 2270           notifyDiv.style.left = (pos.left+3)+'px';
6176 19 Oct 12 nicklas 2271           pointerPos = pos.width < 30 ? 'left: 0.6em;' : 'left: 1.2em;';
6176 19 Oct 12 nicklas 2272         }
6175 19 Oct 12 nicklas 2273       }
6175 19 Oct 12 nicklas 2274       notifyDiv.innerHTML = '<div class="'+pointerClass+'" style="'+pointerPos+'"><div></div></div><div class="notify-message">'+message+'</div>';
6175 19 Oct 12 nicklas 2275
6175 19 Oct 12 nicklas 2276       // Display the notification
6175 19 Oct 12 nicklas 2277       document.body.appendChild(notifyDiv);
6382 17 Dec 13 nicklas 2278       if (element.tabIndex < 0) element.tabIndex = 0;
6175 19 Oct 12 nicklas 2279       if (element.focus) element.focus();
6175 19 Oct 12 nicklas 2280       if (element.select) element.select();
6175 19 Oct 12 nicklas 2281
6175 19 Oct 12 nicklas 2282       // Remember the div element and add event handlers that hide it when the element 
6175 19 Oct 12 nicklas 2283       // loses focus or a key is pressed
6175 19 Oct 12 nicklas 2284       element.notifyDiv = notifyDiv;
6175 19 Oct 12 nicklas 2285       notifyDiv.notifyElement = element;
6175 19 Oct 12 nicklas 2286       element.addEventListener('blur', internal.hideNotification, false);
6175 19 Oct 12 nicklas 2287       element.addEventListener('keypress', internal.hideNotification, false);
7813 19 May 20 nicklas 2288       element.addEventListener('click', internal.hideNotification, false);
6175 19 Oct 12 nicklas 2289       notifyDiv.addEventListener('click', internal.hideNotification, false);
6175 19 Oct 12 nicklas 2290     }
6175 19 Oct 12 nicklas 2291   }
6175 19 Oct 12 nicklas 2292   
6175 19 Oct 12 nicklas 2293   /**
6175 19 Oct 12 nicklas 2294     Event handler that hide the notification message displayed with
6175 19 Oct 12 nicklas 2295     Forms.showNotification
6175 19 Oct 12 nicklas 2296   */
6175 19 Oct 12 nicklas 2297   internal.hideNotification = function(event)
6175 19 Oct 12 nicklas 2298   {
6175 19 Oct 12 nicklas 2299     // The target of the event can be either the form element or the notification div
6175 19 Oct 12 nicklas 2300     var target = event.currentTarget; 
6175 19 Oct 12 nicklas 2301     var element;
6175 19 Oct 12 nicklas 2302     var notifyDiv;
6175 19 Oct 12 nicklas 2303     if (target.notifyDiv)
6175 19 Oct 12 nicklas 2304     {
6175 19 Oct 12 nicklas 2305       element = target;
6175 19 Oct 12 nicklas 2306       notifyDiv = element.notifyDiv;
6175 19 Oct 12 nicklas 2307     }
6175 19 Oct 12 nicklas 2308     else if (target.notifyElement)
6175 19 Oct 12 nicklas 2309     {
6175 19 Oct 12 nicklas 2310       element = target.notifyElement;
6175 19 Oct 12 nicklas 2311       notifyDiv = target;
6175 19 Oct 12 nicklas 2312     }
6175 19 Oct 12 nicklas 2313     
6175 19 Oct 12 nicklas 2314     // Remove the notification
6175 19 Oct 12 nicklas 2315     if (notifyDiv)
6175 19 Oct 12 nicklas 2316     {
6175 19 Oct 12 nicklas 2317       document.body.removeChild(notifyDiv);
6175 19 Oct 12 nicklas 2318     }
6175 19 Oct 12 nicklas 2319     // Remove the event handlers from the element
6175 19 Oct 12 nicklas 2320     if (element)
6175 19 Oct 12 nicklas 2321     {
6175 19 Oct 12 nicklas 2322       element.removeEventListener('keypress', internal.hideNotification);
6175 19 Oct 12 nicklas 2323       element.removeEventListener('blur', internal.hideNotification);
7943 04 May 21 nicklas 2324       element.removeEventListener('click', internal.hideNotification);
6175 19 Oct 12 nicklas 2325     }
6175 19 Oct 12 nicklas 2326   }
6175 19 Oct 12 nicklas 2327
6175 19 Oct 12 nicklas 2328   
6175 19 Oct 12 nicklas 2329   /**
6168 15 Oct 12 nicklas 2330     Create a hidden input element in a form.
6168 15 Oct 12 nicklas 2331     @param frm The form object
6168 15 Oct 12 nicklas 2332     @param name The name of the hidden element
6168 15 Oct 12 nicklas 2333     @param value The value of the hidden element
6168 15 Oct 12 nicklas 2334   */
6168 15 Oct 12 nicklas 2335   forms.addHidden = function(frm, name, value)
6168 15 Oct 12 nicklas 2336   {
6389 07 Jan 14 nicklas 2337     frm = Doc.form(frm);
6168 15 Oct 12 nicklas 2338     var hidden = frm.ownerDocument.createElement('input');
6168 15 Oct 12 nicklas 2339     hidden.setAttribute('type', 'hidden');
6168 15 Oct 12 nicklas 2340     hidden.setAttribute('name', name);
6168 15 Oct 12 nicklas 2341     hidden.setAttribute('value', value);
6168 15 Oct 12 nicklas 2342     frm.appendChild(hidden);
6168 15 Oct 12 nicklas 2343   }
6168 15 Oct 12 nicklas 2344   
6389 07 Jan 14 nicklas 2345   // Kept for backwards compatibility
6576 22 Oct 14 nicklas 2346   // @Deprecated
6389 07 Jan 14 nicklas 2347   forms.createHidden = function(frm, name, value, doc)
6389 07 Jan 14 nicklas 2348   {
6576 22 Oct 14 nicklas 2349     App.deprecatedMethod('Forms.createHidden()', '3.5');
6389 07 Jan 14 nicklas 2350     Forms.addHidden(frm, name, value);
6389 07 Jan 14 nicklas 2351   }
6389 07 Jan 14 nicklas 2352
6389 07 Jan 14 nicklas 2353   
6168 15 Oct 12 nicklas 2354   /*
6168 15 Oct 12 nicklas 2355     Convert an URL to a HTML form using POST. This method extract 
6168 15 Oct 12 nicklas 2356     all query parameters from the URL (except ID) and generate a 
6168 15 Oct 12 nicklas 2357     <form> tag with hidden input fields. 
6168 15 Oct 12 nicklas 2358     The method returns an unattached Form object. To post it, 
6168 15 Oct 12 nicklas 2359     do: document.body.appendChild(form); form.submit(); If there
6168 15 Oct 12 nicklas 2360     is no query part in the URL, null is returned.
6168 15 Oct 12 nicklas 2361     @param url The url to convert
6168 15 Oct 12 nicklas 2362     @param targetWin The window the form should post to, if no window is given
6168 15 Oct 12 nicklas 2363       or if it doesn't have a name the form is posted to the current
6168 15 Oct 12 nicklas 2364       window.
6168 15 Oct 12 nicklas 2365   */
6168 15 Oct 12 nicklas 2366   forms.convertUrlToForm = function(url, targetWin)
6168 15 Oct 12 nicklas 2367   {
6168 15 Oct 12 nicklas 2368     // Split the URL at '?' and extract main part and query part
6168 15 Oct 12 nicklas 2369     // If there is no query part return null.
6168 15 Oct 12 nicklas 2370     var i = url.indexOf('?');
6168 15 Oct 12 nicklas 2371     if (i < 0) return null;
6168 15 Oct 12 nicklas 2372     var query = url.substring(i+1);
6168 15 Oct 12 nicklas 2373     if (!query) return null;
6168 15 Oct 12 nicklas 2374     
6168 15 Oct 12 nicklas 2375     url = url.substring(0, i)+'?ID='+App.getSessionId();
6168 15 Oct 12 nicklas 2376   
6168 15 Oct 12 nicklas 2377     // Create new <form> tag
6168 15 Oct 12 nicklas 2378     var frm = document.createElement('form');
6168 15 Oct 12 nicklas 2379     frm.setAttribute('action', url);
6168 15 Oct 12 nicklas 2380     frm.setAttribute('method', 'post');
6168 15 Oct 12 nicklas 2381     if (targetWin && targetWin.name) 
6168 15 Oct 12 nicklas 2382     {
6168 15 Oct 12 nicklas 2383       frm.setAttribute('target', targetWin.name);
6168 15 Oct 12 nicklas 2384     }
6168 15 Oct 12 nicklas 2385     
6168 15 Oct 12 nicklas 2386     // Add <input type="hidden" ...> elements to the form
6168 15 Oct 12 nicklas 2387     var options = query.split('&');
6168 15 Oct 12 nicklas 2388     for (var i = 0; i < options.length; i++)
6168 15 Oct 12 nicklas 2389     {
6168 15 Oct 12 nicklas 2390       var kv = options[i].split('=');
6168 15 Oct 12 nicklas 2391       var name = kv[0];
6168 15 Oct 12 nicklas 2392       var value = decodeURIComponent(kv[1]);
6168 15 Oct 12 nicklas 2393       if (name && name != 'ID')
6168 15 Oct 12 nicklas 2394       {
6168 15 Oct 12 nicklas 2395         forms.addHidden(frm, name, value, document);
6168 15 Oct 12 nicklas 2396       }
6168 15 Oct 12 nicklas 2397     }
6168 15 Oct 12 nicklas 2398     return frm;
6168 15 Oct 12 nicklas 2399   }
6168 15 Oct 12 nicklas 2400   
6169 15 Oct 12 nicklas 2401   /**
6591 11 Nov 14 nicklas 2402     Clone the given form and add all elements in it as
6591 11 Nov 14 nicklas 2403     hidden elements in the cloned form. Note that unselected
6591 11 Nov 14 nicklas 2404     checkboxes and radio buttons are not cloned. Selection lists
6591 11 Nov 14 nicklas 2405     with multiple selections are cloned as multiple hidden fields.
6591 11 Nov 14 nicklas 2406   */
6591 11 Nov 14 nicklas 2407   forms.cloneAsHidden = function(frm)
6591 11 Nov 14 nicklas 2408   {
6591 11 Nov 14 nicklas 2409     // Duplicate in hidden form
6591 11 Nov 14 nicklas 2410     var cloned = document.createElement('form');
6591 11 Nov 14 nicklas 2411     cloned.setAttribute('action', frm.action);
6591 11 Nov 14 nicklas 2412     cloned.setAttribute('method', frm.method);
6591 11 Nov 14 nicklas 2413     if (frm.target)
6591 11 Nov 14 nicklas 2414     {
6591 11 Nov 14 nicklas 2415       cloned.setAttribute('target', frm.target);
6591 11 Nov 14 nicklas 2416     }
6591 11 Nov 14 nicklas 2417     
6591 11 Nov 14 nicklas 2418     for (var i = 0; i < frm.elements.length; i++)
6591 11 Nov 14 nicklas 2419     {
6591 11 Nov 14 nicklas 2420       var e = frm.elements[i];
6591 11 Nov 14 nicklas 2421       var shouldClone = e.type == 'text' ||  e.type == 'textarea' || e.type == 'select-one' || e.type == 'password' || e.type == 'hidden';
6591 11 Nov 14 nicklas 2422       shouldClone |= (e.type == 'radio' || e.type == 'checkbox' ) && e.checked; 
6591 11 Nov 14 nicklas 2423       if (shouldClone)
6591 11 Nov 14 nicklas 2424       {
6591 11 Nov 14 nicklas 2425         Forms.addHidden(cloned, e.name, e.value);
6591 11 Nov 14 nicklas 2426       }
6591 11 Nov 14 nicklas 2427       else if (e.type == 'select-multiple')
6591 11 Nov 14 nicklas 2428       {
6591 11 Nov 14 nicklas 2429         for (var j = 0; j < e.length; j++)
6591 11 Nov 14 nicklas 2430         {
6591 11 Nov 14 nicklas 2431           if (e[j].selected) Forms.addHidden(cloned, e.name, e[j].value);
6591 11 Nov 14 nicklas 2432         }
6591 11 Nov 14 nicklas 2433       }
6591 11 Nov 14 nicklas 2434     }
6591 11 Nov 14 nicklas 2435     return cloned;
6591 11 Nov 14 nicklas 2436   }
6591 11 Nov 14 nicklas 2437   
6591 11 Nov 14 nicklas 2438   /**
6181 22 Oct 12 nicklas 2439     Event handler that check/uncheck all checkboxes in a form. The form
6181 22 Oct 12 nicklas 2440     should be specified in the target element's data-form attribute. The 
6181 22 Oct 12 nicklas 2441     data-regexp attribute can be set to force a specific match on the name 
6181 22 Oct 12 nicklas 2442     attribute of checkboxes, otherwise only numeric names are matched.
6181 22 Oct 12 nicklas 2443   */
6181 22 Oct 12 nicklas 2444   forms.checkUncheckOnClick = function(event)
6181 22 Oct 12 nicklas 2445   {
6181 22 Oct 12 nicklas 2446     var element = event.currentTarget;
6181 22 Oct 12 nicklas 2447     var form = Data.get(element, 'form');
6181 22 Oct 12 nicklas 2448     var nameRegexp = Data.get(element, 'regexp');
6834 08 Apr 15 nicklas 2449     var specialKey = event.altKey || event.ctrlKey || event.shiftKey;
6834 08 Apr 15 nicklas 2450     if (specialKey)
6834 08 Apr 15 nicklas 2451     {
6834 08 Apr 15 nicklas 2452       Forms.checkUncheck(form, regexp);
6834 08 Apr 15 nicklas 2453     }
6834 08 Apr 15 nicklas 2454     else
6834 08 Apr 15 nicklas 2455     {
6834 08 Apr 15 nicklas 2456       Forms.toggleCheckboxes(form, regexp);
6834 08 Apr 15 nicklas 2457     }
6181 22 Oct 12 nicklas 2458   }
6181 22 Oct 12 nicklas 2459   
6181 22 Oct 12 nicklas 2460   /**
6389 07 Jan 14 nicklas 2461     Check or unchecks all enabled checkboxes in a form. If the first checkbox that
6389 07 Jan 14 nicklas 2462     is found is checked they are unchecked, otherwise they are checked.
6389 07 Jan 14 nicklas 2463     The method only considers checkboxes having a name consisting only
6389 07 Jan 14 nicklas 2464     of digits.
6389 07 Jan 14 nicklas 2465     @param frm A form object or the name or id of a form object
6389 07 Jan 14 nicklas 2466     @param nameRegexp A regular expression that must match the name of the checkbox,
6389 07 Jan 14 nicklas 2467       default matches names with digits
6389 07 Jan 14 nicklas 2468   */
6389 07 Jan 14 nicklas 2469   forms.checkUncheck = function(frm, nameRegexp)
6389 07 Jan 14 nicklas 2470   {
6389 07 Jan 14 nicklas 2471     frm = Doc.form(frm);
6389 07 Jan 14 nicklas 2472     var first = null;
6389 07 Jan 14 nicklas 2473     if (!nameRegexp) nameRegexp = /\d+/;
6389 07 Jan 14 nicklas 2474     for (var i=0; i < frm.elements.length; i++)
6389 07 Jan 14 nicklas 2475     {
6389 07 Jan 14 nicklas 2476       var element = frm.elements[i];
6389 07 Jan 14 nicklas 2477       if (element.type == 'checkbox' && !element.disabled && element.name.match(nameRegexp))
6389 07 Jan 14 nicklas 2478       {
6576 22 Oct 14 nicklas 2479         var was = element.checked;
6389 07 Jan 14 nicklas 2480         if (first == null)
6389 07 Jan 14 nicklas 2481         {
6389 07 Jan 14 nicklas 2482           first = element;
6389 07 Jan 14 nicklas 2483           element.checked = !element.checked;
6389 07 Jan 14 nicklas 2484         }
6389 07 Jan 14 nicklas 2485         else
6389 07 Jan 14 nicklas 2486         {
6389 07 Jan 14 nicklas 2487           element.checked = first.checked;
6389 07 Jan 14 nicklas 2488         }
6576 22 Oct 14 nicklas 2489         if (was != element.checked) Events.sendChangeEvent(element);
6389 07 Jan 14 nicklas 2490       }
6389 07 Jan 14 nicklas 2491     }
6389 07 Jan 14 nicklas 2492   }
6389 07 Jan 14 nicklas 2493   
6389 07 Jan 14 nicklas 2494   /**
6834 08 Apr 15 nicklas 2495     Toggle checkboxes in a form. The method only considers checkboxes 
6834 08 Apr 15 nicklas 2496     having a name consisting only of digits (unless a regular expression is given).
6834 08 Apr 15 nicklas 2497     @param frm A form object or the name or id of a form object
6834 08 Apr 15 nicklas 2498     @param nameRegexp A regular expression that must match the name of the checkbox,
6834 08 Apr 15 nicklas 2499       default matches names with digits
6834 08 Apr 15 nicklas 2500   */
6834 08 Apr 15 nicklas 2501   forms.toggleCheckboxes = function(frm, nameRegexp)
6834 08 Apr 15 nicklas 2502   {
6834 08 Apr 15 nicklas 2503     frm = Doc.form(frm);
6834 08 Apr 15 nicklas 2504     if (!nameRegexp) nameRegexp = /\d+/;
6834 08 Apr 15 nicklas 2505     for (var i=0; i < frm.elements.length; i++)
6834 08 Apr 15 nicklas 2506     {
6834 08 Apr 15 nicklas 2507       var element = frm.elements[i];
6834 08 Apr 15 nicklas 2508       if (element.type == 'checkbox' && !element.disabled && element.name.match(nameRegexp))
6834 08 Apr 15 nicklas 2509       {
6834 08 Apr 15 nicklas 2510         element.checked = !element.checked;
6838 08 Apr 15 nicklas 2511         Events.sendChangeEvent(element);
6834 08 Apr 15 nicklas 2512       }
6834 08 Apr 15 nicklas 2513     }
6834 08 Apr 15 nicklas 2514   }
6834 08 Apr 15 nicklas 2515
6834 08 Apr 15 nicklas 2516   /**
6389 07 Jan 14 nicklas 2517     Calculate the number of checked checkboxes in a form. The method only
6389 07 Jan 14 nicklas 2518     considers checkboxes having a name consisting only
6389 07 Jan 14 nicklas 2519     of digits. As a special case, if no checkboxes are found, then
6389 07 Jan 14 nicklas 2520     it checks if a radio button group with the name item_id exists and
6389 07 Jan 14 nicklas 2521     if one of those is checked.
6389 07 Jan 14 nicklas 2522     @param frm A form object or the name or id of a form object
6389 07 Jan 14 nicklas 2523     @param nameRegexp A regular expression that must match the name of the checkbox,
6389 07 Jan 14 nicklas 2524       default matches names with digits
6389 07 Jan 14 nicklas 2525   */
6389 07 Jan 14 nicklas 2526   forms.numChecked = function(frm, nameRegexp)
6389 07 Jan 14 nicklas 2527   {
6389 07 Jan 14 nicklas 2528     frm = Doc.form(frm);
6389 07 Jan 14 nicklas 2529     var checked = 0;
6389 07 Jan 14 nicklas 2530     if (!nameRegexp) nameRegexp = /\d+/;
6389 07 Jan 14 nicklas 2531     for (var i=0; i < frm.elements.length; i++) 
6389 07 Jan 14 nicklas 2532     {
6389 07 Jan 14 nicklas 2533       var element = frm.elements[i];
6389 07 Jan 14 nicklas 2534       if (element.type == 'checkbox' && element.name.match(nameRegexp) && element.checked)
6389 07 Jan 14 nicklas 2535       {
6389 07 Jan 14 nicklas 2536         checked++;
6389 07 Jan 14 nicklas 2537       }
6389 07 Jan 14 nicklas 2538     }
6389 07 Jan 14 nicklas 2539     if (checked == 0 && frm.item_id)
6389 07 Jan 14 nicklas 2540     {
6389 07 Jan 14 nicklas 2541       checked += Forms.getCheckedRadio(frm.item_id) ? 1 : 0;
6389 07 Jan 14 nicklas 2542     }
6389 07 Jan 14 nicklas 2543     return checked;
6389 07 Jan 14 nicklas 2544   }
6389 07 Jan 14 nicklas 2545
6389 07 Jan 14 nicklas 2546   
6389 07 Jan 14 nicklas 2547   /**
6294 13 Jun 13 nicklas 2548     Adds a list option to a selection list. The option will be placed 
6294 13 Jun 13 nicklas 2549     before the option specified by the index value. Use -1 to insert the 
6294 13 Jun 13 nicklas 2550     option at the end.
6294 13 Jun 13 nicklas 2551     @param list The ID or Select object
6294 13 Jun 13 nicklas 2552     @param index The index of the new option
6294 13 Jun 13 nicklas 2553     @param option The Option object
6294 13 Jun 13 nicklas 2554   */
6294 13 Jun 13 nicklas 2555   forms.addListOption = function(list, index, option)
6294 13 Jun 13 nicklas 2556   {
6294 13 Jun 13 nicklas 2557     list = Doc.element(list);
6684 14 Jan 15 nicklas 2558     if (option.hasAttribute('text') && !option.hasAttribute('title'))
6294 13 Jun 13 nicklas 2559     {
6684 14 Jan 15 nicklas 2560       option.title = option.text;
6294 13 Jun 13 nicklas 2561     }
6294 13 Jun 13 nicklas 2562     if (index < 0 || index >= list.length)
6294 13 Jun 13 nicklas 2563     {
6294 13 Jun 13 nicklas 2564       // Add to end of list
6294 13 Jun 13 nicklas 2565       list[list.length] = option;
6294 13 Jun 13 nicklas 2566     }
6294 13 Jun 13 nicklas 2567     else
6294 13 Jun 13 nicklas 2568     {
6294 13 Jun 13 nicklas 2569       list.add(option, list[index]);
6294 13 Jun 13 nicklas 2570     }
6294 13 Jun 13 nicklas 2571   }
6294 13 Jun 13 nicklas 2572   
6294 13 Jun 13 nicklas 2573   /**
6389 07 Jan 14 nicklas 2574     Switch place of two options in a list
6389 07 Jan 14 nicklas 2575     @param list The list object
6389 07 Jan 14 nicklas 2576     @param index1 The index of the first option
6389 07 Jan 14 nicklas 2577     @param index2 The index of the second option
6389 07 Jan 14 nicklas 2578   */
6389 07 Jan 14 nicklas 2579   forms.switchListOptions = function(list, index1, index2)
6389 07 Jan 14 nicklas 2580   {
6389 07 Jan 14 nicklas 2581     list = Doc.element(list);
6389 07 Jan 14 nicklas 2582     var opt1 = list.options[index1];
6389 07 Jan 14 nicklas 2583     var opt2 = list.options[index2];
6389 07 Jan 14 nicklas 2584     list.options[index1] = new Option();
6389 07 Jan 14 nicklas 2585     list.options[index2] = new Option();
6389 07 Jan 14 nicklas 2586     list.options[index1] = opt2;
6389 07 Jan 14 nicklas 2587     list.options[index2] = opt1;
6389 07 Jan 14 nicklas 2588   }
6389 07 Jan 14 nicklas 2589
6389 07 Jan 14 nicklas 2590   
6389 07 Jan 14 nicklas 2591   /**
6389 07 Jan 14 nicklas 2592     Move all selected options in the list up or down. Returns the number
6389 07 Jan 14 nicklas 2593     of options that were selected (and moved).
6389 07 Jan 14 nicklas 2594     @param list The list as an Select object
6398 24 Jan 14 nicklas 2595     @param down A boolean value (true to move down, false to move up)
6389 07 Jan 14 nicklas 2596   */
6398 24 Jan 14 nicklas 2597   forms.moveListOptions = function(list, down)
6389 07 Jan 14 nicklas 2598   {
6389 07 Jan 14 nicklas 2599     list = Doc.element(list);
6389 07 Jan 14 nicklas 2600     var moved = 0;
6389 07 Jan 14 nicklas 2601     if (list.options.length > 0)
6389 07 Jan 14 nicklas 2602     {
6389 07 Jan 14 nicklas 2603       var index = 1;
6389 07 Jan 14 nicklas 2604       var end_condition = list.options.length;
6389 07 Jan 14 nicklas 2605       var delta = -1;
6398 24 Jan 14 nicklas 2606       if (down)
6389 07 Jan 14 nicklas 2607       {
6389 07 Jan 14 nicklas 2608         index = list.options.length - 2;
6389 07 Jan 14 nicklas 2609         end_condition = -1;
6389 07 Jan 14 nicklas 2610         delta = 1;
6389 07 Jan 14 nicklas 2611       }
6389 07 Jan 14 nicklas 2612       while (index != end_condition)
6389 07 Jan 14 nicklas 2613       {
6389 07 Jan 14 nicklas 2614         if (!list.options[index+delta].selected && list.options[index].selected)
6389 07 Jan 14 nicklas 2615         {
6389 07 Jan 14 nicklas 2616           moved++;
6389 07 Jan 14 nicklas 2617           forms.switchListOptions(list, index, index+delta);
6389 07 Jan 14 nicklas 2618         }
6389 07 Jan 14 nicklas 2619         index = index-delta;
6389 07 Jan 14 nicklas 2620       }
6389 07 Jan 14 nicklas 2621     }
6389 07 Jan 14 nicklas 2622     return moved;
6389 07 Jan 14 nicklas 2623   }
6389 07 Jan 14 nicklas 2624
6389 07 Jan 14 nicklas 2625   
6389 07 Jan 14 nicklas 2626   /**
6389 07 Jan 14 nicklas 2627     Select the option in a list with the specified value. If
6389 07 Jan 14 nicklas 2628     the value is not found among the options the selection is left
6389 07 Jan 14 nicklas 2629     unchanged.
6389 07 Jan 14 nicklas 2630     @param list The list object
6389 07 Jan 14 nicklas 2631     @param value The value to look for
6389 07 Jan 14 nicklas 2632   */
6389 07 Jan 14 nicklas 2633   forms.selectListOption = function(list, value)
6389 07 Jan 14 nicklas 2634   {
6389 07 Jan 14 nicklas 2635     list = Doc.element(list);
6389 07 Jan 14 nicklas 2636     for (var i = 0; i < list.length; i++)
6389 07 Jan 14 nicklas 2637     {
6389 07 Jan 14 nicklas 2638       if (list[i].value == value)
6389 07 Jan 14 nicklas 2639       {
6389 07 Jan 14 nicklas 2640         list.selectedIndex = i;
6389 07 Jan 14 nicklas 2641         return;
6389 07 Jan 14 nicklas 2642       }
6389 07 Jan 14 nicklas 2643     }
6389 07 Jan 14 nicklas 2644   }
6389 07 Jan 14 nicklas 2645
6389 07 Jan 14 nicklas 2646   
6389 07 Jan 14 nicklas 2647   /**
6389 07 Jan 14 nicklas 2648     Select all options in a list which has a value among one of the specified 
6389 07 Jan 14 nicklas 2649     values. Values not found are deselected.
6389 07 Jan 14 nicklas 2650     @param list The list object
6389 07 Jan 14 nicklas 2651     @param values An array with the values
6389 07 Jan 14 nicklas 2652     @param checkTextIfNoValues If set and none of the values was found, the text for each option is also checked
6389 07 Jan 14 nicklas 2653     @return The number of selected values in the list
6389 07 Jan 14 nicklas 2654   */
6389 07 Jan 14 nicklas 2655   forms.selectListOptions = function(list, values, checkTextIfNoValues)
6389 07 Jan 14 nicklas 2656   {
6389 07 Jan 14 nicklas 2657     list = Doc.element(list);
6389 07 Jan 14 nicklas 2658     var numSelected = 0;
6389 07 Jan 14 nicklas 2659     for (var i = 0; i < list.length; i++)
6389 07 Jan 14 nicklas 2660     {
6389 07 Jan 14 nicklas 2661       var selected = false;
6389 07 Jan 14 nicklas 2662       for (var j = 0; j < values.length; j++)
6389 07 Jan 14 nicklas 2663       {
6389 07 Jan 14 nicklas 2664         if (list[i].value == values[j]) 
6389 07 Jan 14 nicklas 2665         {
6389 07 Jan 14 nicklas 2666           selected = true;
6389 07 Jan 14 nicklas 2667           j = values.length;
6389 07 Jan 14 nicklas 2668           numSelected++;
6389 07 Jan 14 nicklas 2669         }
6389 07 Jan 14 nicklas 2670       }
6389 07 Jan 14 nicklas 2671       list[i].selected = selected;
6389 07 Jan 14 nicklas 2672     }
6389 07 Jan 14 nicklas 2673     if (numSelected == 0 && checkTextIfNoValues)
6389 07 Jan 14 nicklas 2674     {
6389 07 Jan 14 nicklas 2675       for (var i = 0; i < list.length; i++)
6389 07 Jan 14 nicklas 2676       {
6389 07 Jan 14 nicklas 2677         var selected = false;
6389 07 Jan 14 nicklas 2678         for (var j = 0; j < values.length; j++)
6389 07 Jan 14 nicklas 2679         {
6389 07 Jan 14 nicklas 2680           if (list[i].text == values[j]) 
6389 07 Jan 14 nicklas 2681           {
6389 07 Jan 14 nicklas 2682             selected = true;
6389 07 Jan 14 nicklas 2683             j = values.length;
6389 07 Jan 14 nicklas 2684             numSelected++;
6389 07 Jan 14 nicklas 2685           }
6389 07 Jan 14 nicklas 2686         }
6389 07 Jan 14 nicklas 2687         list[i].selected = selected;
6389 07 Jan 14 nicklas 2688       }      
6389 07 Jan 14 nicklas 2689     }
6389 07 Jan 14 nicklas 2690     return numSelected;
6389 07 Jan 14 nicklas 2691   }
6389 07 Jan 14 nicklas 2692
6389 07 Jan 14 nicklas 2693   
6389 07 Jan 14 nicklas 2694   /**
6294 13 Jun 13 nicklas 2695     Assuming that a list is sorted, get the index of
6294 13 Jun 13 nicklas 2696     the entry point were an option with the given text 
6294 13 Jun 13 nicklas 2697     would be inserted.
6294 13 Jun 13 nicklas 2698     @param list The list to search
6294 13 Jun 13 nicklas 2699     @param text The value to compare against the text
6294 13 Jun 13 nicklas 2700       on the list options
6294 13 Jun 13 nicklas 2701   */
6294 13 Jun 13 nicklas 2702   forms.getInsertIndexOfSortedList = function(list, text)
6294 13 Jun 13 nicklas 2703   {
6294 13 Jun 13 nicklas 2704     list = Doc.element(list);
6294 13 Jun 13 nicklas 2705     for (var i = 0; i < list.length; i++)
6294 13 Jun 13 nicklas 2706     {
6294 13 Jun 13 nicklas 2707       if (list[i].text > text) return i;
6294 13 Jun 13 nicklas 2708     }
6294 13 Jun 13 nicklas 2709     return list.length;
6294 13 Jun 13 nicklas 2710   }
6294 13 Jun 13 nicklas 2711   
6294 13 Jun 13 nicklas 2712   /**
6294 13 Jun 13 nicklas 2713      Move all selected options from list1 to list2. Return
6294 13 Jun 13 nicklas 2714      the number of moved options. If the 'to' list has
6294 13 Jun 13 nicklas 2715      set the option 'data-is-sorted' the options selected
6294 13 Jun 13 nicklas 2716      options are inserted into their sorted positions, otherwise
6305 09 Aug 13 nicklas 2717      at the end of the list. Options with the 'data-no-remove' attribute
6305 09 Aug 13 nicklas 2718      set are not moved.
6294 13 Jun 13 nicklas 2719   */
6294 13 Jun 13 nicklas 2720   forms.moveSelected = function(from, to)
6294 13 Jun 13 nicklas 2721   {
6294 13 Jun 13 nicklas 2722     from = Doc.element(from);
6294 13 Jun 13 nicklas 2723     to = Doc.element(to);
6294 13 Jun 13 nicklas 2724     var sorted = Data.int(to, 'is-sorted');
6294 13 Jun 13 nicklas 2725     
6294 13 Jun 13 nicklas 2726     var moved = 0;
6294 13 Jun 13 nicklas 2727     for (var i=0; i < from.length; i++)
6294 13 Jun 13 nicklas 2728     {
6305 09 Aug 13 nicklas 2729       var option = from[i];
6305 09 Aug 13 nicklas 2730       if (option.selected && !Data.int(option, 'no-remove', 0))
6294 13 Jun 13 nicklas 2731       {
6294 13 Jun 13 nicklas 2732         moved++;
6305 09 Aug 13 nicklas 2733         var movedOption = new Option(option.text, option.value, false, true);
6305 09 Aug 13 nicklas 2734         var insertIndex = sorted ? forms.getInsertIndexOfSortedList(to, movedOption.text) : to.length;
6305 09 Aug 13 nicklas 2735         forms.addListOption(to, insertIndex, movedOption);
6294 13 Jun 13 nicklas 2736         from[i] = null;
6294 13 Jun 13 nicklas 2737         i--;
6294 13 Jun 13 nicklas 2738       }
6294 13 Jun 13 nicklas 2739     }
6294 13 Jun 13 nicklas 2740     return moved;
6294 13 Jun 13 nicklas 2741   }
6294 13 Jun 13 nicklas 2742   
6294 13 Jun 13 nicklas 2743   /**
6294 13 Jun 13 nicklas 2744     Event handler for moving selected options from one list to
6294 13 Jun 13 nicklas 2745     another. The event handler may be attached to any element
6294 13 Jun 13 nicklas 2746     which must specify 'data-from-id' and 'data-to-id' with
6294 13 Jun 13 nicklas 2747     ID values for the two lists. Or, it can be attached to the
6294 13 Jun 13 nicklas 2748     'from' list (typcially as dblclick event), in which case 
6294 13 Jun 13 nicklas 2749     only a 'data-to-id' must be specified.
6294 13 Jun 13 nicklas 2750   */
6294 13 Jun 13 nicklas 2751   forms.moveSelectedOnClick = function(event)
6294 13 Jun 13 nicklas 2752   {
6294 13 Jun 13 nicklas 2753     var from = Data.get(event.currentTarget, 'from-id') || event.currentTarget;
6294 13 Jun 13 nicklas 2754     var to = Data.get(event.currentTarget, 'to-id');
6294 13 Jun 13 nicklas 2755     forms.moveSelected(from, to);
6294 13 Jun 13 nicklas 2756   }
6389 07 Jan 14 nicklas 2757   
6389 07 Jan 14 nicklas 2758   /**
6389 07 Jan 14 nicklas 2759     Get the checked radio button element in a group of radio buttons. Returns
6389 07 Jan 14 nicklas 2760     null if no radio button is selected.
6389 07 Jan 14 nicklas 2761     @param radio The radio buttons group object
6389 07 Jan 14 nicklas 2762   */
6389 07 Jan 14 nicklas 2763   forms.getCheckedRadio = function(radio)
6389 07 Jan 14 nicklas 2764   {
6389 07 Jan 14 nicklas 2765     if (radio.checked)
6389 07 Jan 14 nicklas 2766     {
6389 07 Jan 14 nicklas 2767       // If there is only one radio button
6389 07 Jan 14 nicklas 2768       return radio;
6389 07 Jan 14 nicklas 2769     }
6389 07 Jan 14 nicklas 2770     for (var i = 0;  i < radio.length; i++)
6389 07 Jan 14 nicklas 2771     {
6389 07 Jan 14 nicklas 2772       if (radio[i].checked) return radio[i];
6389 07 Jan 14 nicklas 2773     }
6389 07 Jan 14 nicklas 2774     return null;
6389 07 Jan 14 nicklas 2775   }
6294 13 Jun 13 nicklas 2776
6389 07 Jan 14 nicklas 2777   /**
6389 07 Jan 14 nicklas 2778     Check the radio button with the specified value. If no
6389 07 Jan 14 nicklas 2779     radio button has a matching value all buttons are unchecked. Returns
6389 07 Jan 14 nicklas 2780     the index of the checked radio button. Radio buttons that have
6389 07 Jan 14 nicklas 2781     been disabled are never checked.
6389 07 Jan 14 nicklas 2782     @param radio The radio buttons group object
6389 07 Jan 14 nicklas 2783     @param value The value of the radio button that should be checked
6389 07 Jan 14 nicklas 2784   */
6389 07 Jan 14 nicklas 2785   forms.checkRadio = function(radio, value)
6389 07 Jan 14 nicklas 2786   {
6389 07 Jan 14 nicklas 2787     var returnValue = -1;
6389 07 Jan 14 nicklas 2788     if (radio.length)
6389 07 Jan 14 nicklas 2789     {
6389 07 Jan 14 nicklas 2790       // More than one item in the list
6389 07 Jan 14 nicklas 2791       for (var i = 0; i < radio.length; i++)
6389 07 Jan 14 nicklas 2792       {
6389 07 Jan 14 nicklas 2793         radio[i].checked = (radio[i].value == value) && !radio[i].disabled;
6389 07 Jan 14 nicklas 2794         returnValue = radio[i].checked ? i : returnValue;
6389 07 Jan 14 nicklas 2795       }
6389 07 Jan 14 nicklas 2796     }
6389 07 Jan 14 nicklas 2797     else
6389 07 Jan 14 nicklas 2798     {
6389 07 Jan 14 nicklas 2799       // Only a single item in the list
6389 07 Jan 14 nicklas 2800       radio.checked = radio.value == value && !radio.disabled;
6389 07 Jan 14 nicklas 2801       returnValue = 0;
6389 07 Jan 14 nicklas 2802     }
6389 07 Jan 14 nicklas 2803     return returnValue;
6389 07 Jan 14 nicklas 2804   }
6389 07 Jan 14 nicklas 2805
6389 07 Jan 14 nicklas 2806   /**
6389 07 Jan 14 nicklas 2807     Check all checkboxes having a value matching one of the
6389 07 Jan 14 nicklas 2808     specified values. Buttons that don't matches any value
6389 07 Jan 14 nicklas 2809     are unchecked.
6389 07 Jan 14 nicklas 2810     @param checkBoxes The check box group
6389 07 Jan 14 nicklas 2811     @param value An array of values
6389 07 Jan 14 nicklas 2812     @return The number of checked boxes
6389 07 Jan 14 nicklas 2813   */
6389 07 Jan 14 nicklas 2814   forms.checkCheckBoxes = function(checkBoxes, values)
6389 07 Jan 14 nicklas 2815   {
6389 07 Jan 14 nicklas 2816     var numChecked = 0;
6389 07 Jan 14 nicklas 2817     for (var i = 0; i < checkBoxes.length; i++)
6389 07 Jan 14 nicklas 2818     {
6389 07 Jan 14 nicklas 2819       checkBoxes[i].checked = false;
6389 07 Jan 14 nicklas 2820       for (var j = 0; j < values.length; j++)
6389 07 Jan 14 nicklas 2821       {
6389 07 Jan 14 nicklas 2822         if (checkBoxes[i].value == values[j])
6389 07 Jan 14 nicklas 2823         {
6389 07 Jan 14 nicklas 2824           checkBoxes[i].checked = true;
6389 07 Jan 14 nicklas 2825           numChecked++;
6389 07 Jan 14 nicklas 2826           j = values.length;
6389 07 Jan 14 nicklas 2827         }
6389 07 Jan 14 nicklas 2828       }
6389 07 Jan 14 nicklas 2829     }
6389 07 Jan 14 nicklas 2830     return numChecked;
6389 07 Jan 14 nicklas 2831   }
6389 07 Jan 14 nicklas 2832
6294 13 Jun 13 nicklas 2833   
6294 13 Jun 13 nicklas 2834   /**
6182 23 Oct 12 nicklas 2835     Event handler that submit the form of the element it is attached to
6182 23 Oct 12 nicklas 2836     when the event is triggered. 
6182 23 Oct 12 nicklas 2837   */
7894 08 Dec 20 nicklas 2838   forms.submit = function(eventOrForm)
6182 23 Oct 12 nicklas 2839   {
7894 08 Dec 20 nicklas 2840     var frm = eventOrForm.submit ? eventOrForm : eventOrForm.currentTarget.form;
7894 08 Dec 20 nicklas 2841     if (frm && frm.submit) 
7894 08 Dec 20 nicklas 2842     {
7894 08 Dec 20 nicklas 2843       // If 'data-enable-please-wait' is a number we start a timer that will trigger a "Please wait" notice
7894 08 Dec 20 nicklas 2844       // 'data-please-wait-msg' may contain a custom message
7894 08 Dec 20 nicklas 2845       var pleaseWait = Data.int(frm, 'enable-please-wait');
7894 08 Dec 20 nicklas 2846       if (pleaseWait)
7894 08 Dec 20 nicklas 2847       {
7894 08 Dec 20 nicklas 2848         setTimeout(forms.pleaseWait, Math.max(pleaseWait, 500), frm.name, Data.get(frm, 'please-wait-msg'));
7894 08 Dec 20 nicklas 2849       }
7894 08 Dec 20 nicklas 2850       frm.submit();
7894 08 Dec 20 nicklas 2851     }
6182 23 Oct 12 nicklas 2852   }
6182 23 Oct 12 nicklas 2853   
6182 23 Oct 12 nicklas 2854   /**
7894 08 Dec 20 nicklas 2855     Display a "Please wait" notice and start a second timer for adding a second message (this may take a long time)
7894 08 Dec 20 nicklas 2856   */
7894 08 Dec 20 nicklas 2857   forms.pleaseWait = function(form, msg)
7894 08 Dec 20 nicklas 2858   {
7894 08 Dec 20 nicklas 2859     var div = document.createElement('div');
7894 08 Dec 20 nicklas 2860     div.className = 'please-wait messagecontainer note';
7894 08 Dec 20 nicklas 2861     div.innerHTML = msg || 'Working, please wait...';
7894 08 Dec 20 nicklas 2862     
7894 08 Dec 20 nicklas 2863     var loader = document.createElement('div');
7894 08 Dec 20 nicklas 2864     loader.className = 'loader';
7894 08 Dec 20 nicklas 2865     div.appendChild(loader);
7894 08 Dec 20 nicklas 2866
7894 08 Dec 20 nicklas 2867     var waitMore = document.createElement('div');
7894 08 Dec 20 nicklas 2868     waitMore.className = 'please-wait-more';
7894 08 Dec 20 nicklas 2869     div.appendChild(waitMore);
7894 08 Dec 20 nicklas 2870
7894 08 Dec 20 nicklas 2871     document.body.appendChild(div);
7894 08 Dec 20 nicklas 2872     setTimeout(internal.pleaseWaitSomeMore, 5000, waitMore);
7894 08 Dec 20 nicklas 2873   }
7894 08 Dec 20 nicklas 2874   
7894 08 Dec 20 nicklas 2875   /**
7894 08 Dec 20 nicklas 2876     Adds a "this make take a long time" notice to the "Please wait" message. Dots are appended at
7894 08 Dec 20 nicklas 2877     regular increasing intervals.
7894 08 Dec 20 nicklas 2878   */
7894 08 Dec 20 nicklas 2879   internal.pleaseWaitSomeMore = function(waitMore)
7894 08 Dec 20 nicklas 2880   {
7894 08 Dec 20 nicklas 2881     var count = Math.min(20, Data.int(waitMore, 'counter', 0));
7894 08 Dec 20 nicklas 2882     waitMore.innerHTML = '(this may take a long time' + '.'.repeat(count == 0 ? 0 : 2+count) + ')';
7894 08 Dec 20 nicklas 2883     Doc.addClass(waitMore.parentNode, 'waiting-more');
7894 08 Dec 20 nicklas 2884     if (count < 20)
7894 08 Dec 20 nicklas 2885     {
7894 08 Dec 20 nicklas 2886       Data.set(waitMore, 'counter', count+1);
7894 08 Dec 20 nicklas 2887       setTimeout(internal.pleaseWaitSomeMore, 5000+count*1000, waitMore);
7894 08 Dec 20 nicklas 2888     }
7894 08 Dec 20 nicklas 2889   }
7894 08 Dec 20 nicklas 2890   
7894 08 Dec 20 nicklas 2891   /**
6169 15 Oct 12 nicklas 2892     Initializer that set the focus on the element
6218 19 Dec 12 nicklas 2893     that has data-auto-init="focus" or data-auto-init="focus-select"
6169 15 Oct 12 nicklas 2894   */
6169 15 Oct 12 nicklas 2895   internal.autoFocus = function(element, autoInit)
6169 15 Oct 12 nicklas 2896   {
6218 19 Dec 12 nicklas 2897     if (autoInit == 'focus' || autoInit == 'focus-select')
6169 15 Oct 12 nicklas 2898     {
6218 19 Dec 12 nicklas 2899       // Defer setting the focus until since the element may not yet be visible
6218 19 Dec 12 nicklas 2900       Doc.addFinalizer(function()
6218 19 Dec 12 nicklas 2901       {
7052 11 Jan 16 nicklas 2902         setTimeout(function()
7052 11 Jan 16 nicklas 2903           {
7052 11 Jan 16 nicklas 2904             element.focus();
7052 11 Jan 16 nicklas 2905             if (autoInit == 'focus-select') element.select();
7052 11 Jan 16 nicklas 2906           }, 250);
6218 19 Dec 12 nicklas 2907       });
6169 15 Oct 12 nicklas 2908     }
6169 15 Oct 12 nicklas 2909   }
6169 15 Oct 12 nicklas 2910   Doc.addElementInitializer(internal.autoFocus);
6169 15 Oct 12 nicklas 2911   
6294 13 Jun 13 nicklas 2912   /**
6294 13 Jun 13 nicklas 2913     Initializer that attach event handler to input elements
6294 13 Jun 13 nicklas 2914     that restricts typing to either integer or numbers.
6294 13 Jun 13 nicklas 2915     Note that this initializer is not active by default, but
6294 13 Jun 13 nicklas 2916     has to be added with Doc.addElementInitializer(Forms.numberOnlyInitializer)
6294 13 Jun 13 nicklas 2917     on pages that need it.
6294 13 Jun 13 nicklas 2918   */
6294 13 Jun 13 nicklas 2919   forms.numberOnlyInitializer = function(element, autoInit)
6294 13 Jun 13 nicklas 2920   {
6294 13 Jun 13 nicklas 2921     if (autoInit == 'integer-only')
6294 13 Jun 13 nicklas 2922     {
6294 13 Jun 13 nicklas 2923       Events.addEventHandler(element, 'keypress', Events.integerOnly);
6294 13 Jun 13 nicklas 2924     }
6294 13 Jun 13 nicklas 2925     else if (autoInit == 'number-only')
6294 13 Jun 13 nicklas 2926     {
6294 13 Jun 13 nicklas 2927       Events.addEventHandler(element, 'keypress', Events.numberOnly);
6294 13 Jun 13 nicklas 2928     }
6294 13 Jun 13 nicklas 2929   }
6294 13 Jun 13 nicklas 2930   
6617 24 Nov 14 nicklas 2931   
6617 24 Nov 14 nicklas 2932   /**
6617 24 Nov 14 nicklas 2933     Find all <label> tags and create a reverse link between it and
6617 24 Nov 14 nicklas 2934     the checkbox/radiobutton it controls so that we can disable the
6617 24 Nov 14 nicklas 2935     label when the checkbox/radiobutton is disabled
6617 24 Nov 14 nicklas 2936   */
6694 26 Jan 15 nicklas 2937   forms.linkCheckboxesWithLabels = function(element)
6617 24 Nov 14 nicklas 2938   {
6694 26 Jan 15 nicklas 2939     if (!element) element = document;
6694 26 Jan 15 nicklas 2940     var labels = element.getElementsByTagName('label');
6617 24 Nov 14 nicklas 2941     if (labels.length > 0)
6617 24 Nov 14 nicklas 2942     {
6617 24 Nov 14 nicklas 2943       if (!window.MutationObserver) return;
6617 24 Nov 14 nicklas 2944       // We listen on attribute changes on the checkboxes/radiobuttons
6617 24 Nov 14 nicklas 2945       var observer = new MutationObserver(internal.checkBoxModified);
6617 24 Nov 14 nicklas 2946       var config = { attributes: true };
6617 24 Nov 14 nicklas 2947       
6617 24 Nov 14 nicklas 2948       for (var i = 0; i < labels.length; i++)
6617 24 Nov 14 nicklas 2949       {
6617 24 Nov 14 nicklas 2950         var lbl = labels[i];
6617 24 Nov 14 nicklas 2951         // Check the 'for' attribute first
6617 24 Nov 14 nicklas 2952         var refId = lbl.getAttribute('for');
6617 24 Nov 14 nicklas 2953         var ref;
6617 24 Nov 14 nicklas 2954         if (refId)
6617 24 Nov 14 nicklas 2955         {
6617 24 Nov 14 nicklas 2956           ref = Doc.element(refId);
6617 24 Nov 14 nicklas 2957         }
6617 24 Nov 14 nicklas 2958         else
6617 24 Nov 14 nicklas 2959         {
6617 24 Nov 14 nicklas 2960           // Check child elements for <input>
6617 24 Nov 14 nicklas 2961           var inputs = lbl.getElementsByTagName('input');
6617 24 Nov 14 nicklas 2962           if (inputs.length == 1) ref = inputs[0];
6617 24 Nov 14 nicklas 2963         }
6617 24 Nov 14 nicklas 2964         if (ref)
6617 24 Nov 14 nicklas 2965         {
6617 24 Nov 14 nicklas 2966           // Set initial 'disabled' status on <label>
6617 24 Nov 14 nicklas 2967           Doc.addOrRemoveClass(lbl, 'disabled', ref.disabled);
6617 24 Nov 14 nicklas 2968           // ...and get notified  when checkbox/radiobutton is modified
6617 24 Nov 14 nicklas 2969           ref.labelRef = lbl;
6617 24 Nov 14 nicklas 2970           observer.observe(ref, config);
6617 24 Nov 14 nicklas 2971         }
6617 24 Nov 14 nicklas 2972       }
6617 24 Nov 14 nicklas 2973     }
6617 24 Nov 14 nicklas 2974   }
6617 24 Nov 14 nicklas 2975   
6617 24 Nov 14 nicklas 2976   /**
6617 24 Nov 14 nicklas 2977     Callback when attributes have changed on list of checkboxes/radiobuttons
6617 24 Nov 14 nicklas 2978   */
6617 24 Nov 14 nicklas 2979   internal.checkBoxModified = function(records)
6617 24 Nov 14 nicklas 2980   {
6617 24 Nov 14 nicklas 2981     for (var i = 0; i < records.length; i++)
6617 24 Nov 14 nicklas 2982     {
6617 24 Nov 14 nicklas 2983       var target = records[i].target;
6617 24 Nov 14 nicklas 2984       if (target.labelRef)
6617 24 Nov 14 nicklas 2985       {
6617 24 Nov 14 nicklas 2986         Doc.addOrRemoveClass(target.labelRef, 'disabled', target.disabled);
6617 24 Nov 14 nicklas 2987       }
6617 24 Nov 14 nicklas 2988     }
6617 24 Nov 14 nicklas 2989   }
6617 24 Nov 14 nicklas 2990   
6694 26 Jan 15 nicklas 2991   Doc.addFinalizer(forms.linkCheckboxesWithLabels);
6617 24 Nov 14 nicklas 2992     
6168 15 Oct 12 nicklas 2993   return forms;
6168 15 Oct 12 nicklas 2994 }();
6157 08 Oct 12 nicklas 2995
6389 07 Jan 14 nicklas 2996 var Strings = function()
6389 07 Jan 14 nicklas 2997 {
6389 07 Jan 14 nicklas 2998   var strings = {};
6389 07 Jan 14 nicklas 2999   
6389 07 Jan 14 nicklas 3000   /**
6389 07 Jan 14 nicklas 3001     Trim leading and trailing whitespace from a string.
6389 07 Jan 14 nicklas 3002     @param value The string to trim
6389 07 Jan 14 nicklas 3003     @return The trimmed string
6389 07 Jan 14 nicklas 3004   */
6389 07 Jan 14 nicklas 3005   strings.trim = function(value)
6389 07 Jan 14 nicklas 3006   {
6449 17 Apr 14 nicklas 3007     if (!value) return '';
6389 07 Jan 14 nicklas 3008     value = value.replace(/^\s+/g, '');
6389 07 Jan 14 nicklas 3009     value = value.replace(/\s+$/g, '');
6389 07 Jan 14 nicklas 3010     return value;
6389 07 Jan 14 nicklas 3011   }
6389 07 Jan 14 nicklas 3012   
6389 07 Jan 14 nicklas 3013   /**
6389 07 Jan 14 nicklas 3014     Cut a string if it is longer than a specified length. If cut,
6389 07 Jan 14 nicklas 3015     three dots are appended to the string. The returned string is never
6389 07 Jan 14 nicklas 3016     longer than the specified max length.
6389 07 Jan 14 nicklas 3017   
6389 07 Jan 14 nicklas 3018     @param value The string to cut
6389 07 Jan 14 nicklas 3019     @param maxLength The maximum length
6389 07 Jan 14 nicklas 3020   */
6389 07 Jan 14 nicklas 3021   strings.cut = function(value, maxLength)
6389 07 Jan 14 nicklas 3022   {
6449 17 Apr 14 nicklas 3023     if (!value) return '';
6389 07 Jan 14 nicklas 3024     if (value.length > maxLength) 
6389 07 Jan 14 nicklas 3025     {
6389 07 Jan 14 nicklas 3026       value = value.substr(0, maxLength-3)+'...';
6389 07 Jan 14 nicklas 3027     }
6389 07 Jan 14 nicklas 3028     return value;
6389 07 Jan 14 nicklas 3029   }
6389 07 Jan 14 nicklas 3030   
6389 07 Jan 14 nicklas 3031   /**
6826 02 Apr 15 nicklas 3032     Encode HTML ampersand, start and end brackets to
6389 07 Jan 14 nicklas 3033     make sure value is displayed as-is (not
6389 07 Jan 14 nicklas 3034     as HTML).
6389 07 Jan 14 nicklas 3035   */
6389 07 Jan 14 nicklas 3036   strings.encodeTags = function(value)
6389 07 Jan 14 nicklas 3037   {
6449 17 Apr 14 nicklas 3038     if (!value) return '';
6428 03 Mar 14 nicklas 3039     var encoded = value;
6389 07 Jan 14 nicklas 3040     encoded = encoded.replace(/\&/g, '&amp;');
6389 07 Jan 14 nicklas 3041     encoded = encoded.replace(/\</g, '&lt;');
6389 07 Jan 14 nicklas 3042     encoded = encoded.replace(/\>/g, '&gt;');
6389 07 Jan 14 nicklas 3043     return encoded;
6389 07 Jan 14 nicklas 3044   }
6168 15 Oct 12 nicklas 3045
6389 07 Jan 14 nicklas 3046   /**
6826 02 Apr 15 nicklas 3047     Decode HTML amptersand, start and end brackets.
6826 02 Apr 15 nicklas 3048   */
6826 02 Apr 15 nicklas 3049   strings.decodeTags = function(value)
6826 02 Apr 15 nicklas 3050   {
6826 02 Apr 15 nicklas 3051     if (!value) return '';
6826 02 Apr 15 nicklas 3052     var unencoded = value;
6826 02 Apr 15 nicklas 3053     unencoded = unencoded.replace(/\&lt\;/g, '<');
6826 02 Apr 15 nicklas 3054     unencoded = unencoded.replace(/\&gt\;/g, '>');
6826 02 Apr 15 nicklas 3055     unencoded = unencoded.replace(/\&amp\;/g, '&');
6826 02 Apr 15 nicklas 3056     return unencoded;
6826 02 Apr 15 nicklas 3057   }
6826 02 Apr 15 nicklas 3058   
6826 02 Apr 15 nicklas 3059   /**
6389 07 Jan 14 nicklas 3060     Compare two strings ignoring case.
6389 07 Jan 14 nicklas 3061     returns -1 if a<b, +1 if a>b, 0 if a==b
6389 07 Jan 14 nicklas 3062   */
6389 07 Jan 14 nicklas 3063   strings.compareIgnoreCase = function(a, b)
6389 07 Jan 14 nicklas 3064   {
6389 07 Jan 14 nicklas 3065     var lcA = a.toLowerCase();
6389 07 Jan 14 nicklas 3066     var lcB = b.toLowerCase();
6389 07 Jan 14 nicklas 3067     if (lcA < lcB) return -1;
6389 07 Jan 14 nicklas 3068     if (lcA > lcB) return +1;
6389 07 Jan 14 nicklas 3069     return 0;
6389 07 Jan 14 nicklas 3070   }
6389 07 Jan 14 nicklas 3071
6389 07 Jan 14 nicklas 3072   return strings;
6389 07 Jan 14 nicklas 3073 }();
6389 07 Jan 14 nicklas 3074
6389 07 Jan 14 nicklas 3075
6389 07 Jan 14 nicklas 3076 var Numbers = function()
6389 07 Jan 14 nicklas 3077 {
6389 07 Jan 14 nicklas 3078   var numbers = {};
6389 07 Jan 14 nicklas 3079   var GB = 1073741824;
6389 07 Jan 14 nicklas 3080   var MB = 1048576;
6389 07 Jan 14 nicklas 3081   var kB = 1024;
6389 07 Jan 14 nicklas 3082
6389 07 Jan 14 nicklas 3083   /**
6389 07 Jan 14 nicklas 3084     Checks if the supplied value is an integer.
6389 07 Jan 14 nicklas 3085   */
6389 07 Jan 14 nicklas 3086   numbers.isInteger = function(value)
6389 07 Jan 14 nicklas 3087   {
6389 07 Jan 14 nicklas 3088     return (value.search(/^\-?\d+$/) != -1);
6389 07 Jan 14 nicklas 3089   }
6389 07 Jan 14 nicklas 3090
6389 07 Jan 14 nicklas 3091   /**
6389 07 Jan 14 nicklas 3092     Format a number with the specified number of decimals
6389 07 Jan 14 nicklas 3093   */
6389 07 Jan 14 nicklas 3094   numbers.formatNumber = function(value, numDecimals, unit)
6389 07 Jan 14 nicklas 3095   {
6421 10 Feb 14 nicklas 3096     if (typeof value != 'number') return '';
6389 07 Jan 14 nicklas 3097     var result = '';
6389 07 Jan 14 nicklas 3098     if (numDecimals < 0)
6389 07 Jan 14 nicklas 3099     {
6421 10 Feb 14 nicklas 3100       result = value;
6389 07 Jan 14 nicklas 3101     }
6389 07 Jan 14 nicklas 3102     else
6389 07 Jan 14 nicklas 3103     {
6421 10 Feb 14 nicklas 3104       result = value.toFixed(numDecimals);
6389 07 Jan 14 nicklas 3105     }
6389 07 Jan 14 nicklas 3106     if (unit) result += unit;
6389 07 Jan 14 nicklas 3107     return result;
6389 07 Jan 14 nicklas 3108   }
6389 07 Jan 14 nicklas 3109
6389 07 Jan 14 nicklas 3110   numbers.formatBytes = function(value)
6389 07 Jan 14 nicklas 3111   {
6520 18 Aug 14 nicklas 3112     if (typeof value != 'number') return '';
6413 05 Feb 14 nicklas 3113     if (value > GB)
6389 07 Jan 14 nicklas 3114     {
6389 07 Jan 14 nicklas 3115       return numbers.formatNumber(value / GB, 1, ' GB');
6389 07 Jan 14 nicklas 3116     }
6413 05 Feb 14 nicklas 3117     else if (value > MB)
6389 07 Jan 14 nicklas 3118     {
6389 07 Jan 14 nicklas 3119       return numbers.formatNumber(value / MB, 1, ' MB');
6389 07 Jan 14 nicklas 3120     }
6413 05 Feb 14 nicklas 3121     else if (value > kB)
6389 07 Jan 14 nicklas 3122     {
6389 07 Jan 14 nicklas 3123       return numbers.formatNumber(value / kB, 1, ' kB');
6389 07 Jan 14 nicklas 3124     }
6389 07 Jan 14 nicklas 3125     else
6389 07 Jan 14 nicklas 3126     {
6389 07 Jan 14 nicklas 3127       return value + ' bytes';
6389 07 Jan 14 nicklas 3128     }
6389 07 Jan 14 nicklas 3129   }
6389 07 Jan 14 nicklas 3130
6389 07 Jan 14 nicklas 3131   numbers.formatSeconds = function(seconds)
6389 07 Jan 14 nicklas 3132   {
7988 30 Jun 21 nicklas 3133     if (!seconds && seconds != 0) return '';
6389 07 Jan 14 nicklas 3134     var result = '';
6389 07 Jan 14 nicklas 3135     var mustAppend = false;
6389 07 Jan 14 nicklas 3136     if (seconds >= 86400)
6389 07 Jan 14 nicklas 3137     {
6389 07 Jan 14 nicklas 3138       var days = Math.floor(seconds / 86400);
6389 07 Jan 14 nicklas 3139       seconds = seconds - 86400*days;
6389 07 Jan 14 nicklas 3140       result += days;
6389 07 Jan 14 nicklas 3141       result += days == 1 ? " day " : " days ";
6389 07 Jan 14 nicklas 3142       mustAppend = true;
6389 07 Jan 14 nicklas 3143     }
6389 07 Jan 14 nicklas 3144     if (seconds >= 3600 || mustAppend)
6389 07 Jan 14 nicklas 3145     {
6389 07 Jan 14 nicklas 3146       var hours = Math.floor(seconds / 3600);
6389 07 Jan 14 nicklas 3147       seconds = seconds - 3600*hours;
6389 07 Jan 14 nicklas 3148       result += hours;
6389 07 Jan 14 nicklas 3149       result += hours == 1 ? " hour " : " hours ";
6389 07 Jan 14 nicklas 3150       mustAppend = true;
6389 07 Jan 14 nicklas 3151     }
6389 07 Jan 14 nicklas 3152     if (seconds >= 60 || mustAppend)
6389 07 Jan 14 nicklas 3153     {
6389 07 Jan 14 nicklas 3154       var minutes = Math.floor(seconds / 60);
6389 07 Jan 14 nicklas 3155       seconds = seconds - 60*minutes;
6389 07 Jan 14 nicklas 3156       result += minutes;
6389 07 Jan 14 nicklas 3157       result += minutes == 1 ? " minute " : " minutes ";
6389 07 Jan 14 nicklas 3158       mustAppend = true;
6389 07 Jan 14 nicklas 3159     }
6389 07 Jan 14 nicklas 3160     result += seconds;
6389 07 Jan 14 nicklas 3161     result += seconds == 1 ? " second" : " seconds";
6389 07 Jan 14 nicklas 3162     return result;
6389 07 Jan 14 nicklas 3163   }
6389 07 Jan 14 nicklas 3164
6422 10 Feb 14 nicklas 3165   // Kept for backwards compatibility
6576 22 Oct 14 nicklas 3166   // @Deprecated
6422 10 Feb 14 nicklas 3167   numbers.numberOnly = function(event)
6422 10 Feb 14 nicklas 3168   {
6576 22 Oct 14 nicklas 3169     App.deprecatedMethod('Numbers.numberOnly()', '3.5');
6422 10 Feb 14 nicklas 3170     Events.numberOnly(event);
6422 10 Feb 14 nicklas 3171   }
6422 10 Feb 14 nicklas 3172   
6422 10 Feb 14 nicklas 3173   // Kept for backwards compatibility
6576 22 Oct 14 nicklas 3174   // @Deprecated
6422 10 Feb 14 nicklas 3175   numbers.integerOnly = function(event)
6422 10 Feb 14 nicklas 3176   {
6576 22 Oct 14 nicklas 3177     App.deprecatedMethod('Numbers.integerOnly()', '3.5');
6422 10 Feb 14 nicklas 3178     Events.integerOnly(event);
6422 10 Feb 14 nicklas 3179   }
6422 10 Feb 14 nicklas 3180   
6389 07 Jan 14 nicklas 3181   return numbers;
6389 07 Jan 14 nicklas 3182 }();
6389 07 Jan 14 nicklas 3183
6399 24 Jan 14 nicklas 3184
6399 24 Jan 14 nicklas 3185 var Dates = function()
6399 24 Jan 14 nicklas 3186 {
6399 24 Jan 14 nicklas 3187   //Functions related to handling date values
6399 24 Jan 14 nicklas 3188   //Implementation based on code provided by:
6399 24 Jan 14 nicklas 3189   //===================================================================
6399 24 Jan 14 nicklas 3190   //Author: Matt Kruse <matt@mattkruse.com>
6399 24 Jan 14 nicklas 3191   //WWW: http://www.mattkruse.com/
6399 24 Jan 14 nicklas 3192   //
6399 24 Jan 14 nicklas 3193   //NOTICE: You may use this code for any purpose, commercial or
6399 24 Jan 14 nicklas 3194   //private, without any further permission from the author. You may
6399 24 Jan 14 nicklas 3195   //remove this notice from your final code if you wish, however it is
6399 24 Jan 14 nicklas 3196   //appreciated by the author if at least my web site address is kept.
6399 24 Jan 14 nicklas 3197   //
6399 24 Jan 14 nicklas 3198   //You may *NOT* re-distribute this code in any way except through its
6399 24 Jan 14 nicklas 3199   //use. That means, you can include it in your product, or your web
6399 24 Jan 14 nicklas 3200   //site, or any other form where the code is actually being used. You
6399 24 Jan 14 nicklas 3201   //may not put the plain javascript up on your site for download or
6399 24 Jan 14 nicklas 3202   //include it in your javascript libraries for download. 
6399 24 Jan 14 nicklas 3203   //If you wish to share this code with others, please just point them
6399 24 Jan 14 nicklas 3204   //to the URL instead.
6399 24 Jan 14 nicklas 3205   //Please DO NOT link directly to my .js files from your site. Copy
6399 24 Jan 14 nicklas 3206   //the files to your server and use them there. Thank you.
6399 24 Jan 14 nicklas 3207   //===================================================================
6399 24 Jan 14 nicklas 3208   //HISTORY
6399 24 Jan 14 nicklas 3209   //------------------------------------------------------------------
6399 24 Jan 14 nicklas 3210   //May 17, 2003: Fixed bug in parseDate() for dates <1970
6399 24 Jan 14 nicklas 3211   //March 11, 2003: Added parseDate() function
6399 24 Jan 14 nicklas 3212   //March 11, 2003: Added "NNN" formatting option. Doesn't match up
6399 24 Jan 14 nicklas 3213   //                perfectly with SimpleDateFormat formats, but 
6399 24 Jan 14 nicklas 3214   //                backwards-compatability was required.
6399 24 Jan 14 nicklas 3215
6399 24 Jan 14 nicklas 3216   //------------------------------------------------------------------
6399 24 Jan 14 nicklas 3217   //These functions use the same 'format' strings as the 
6399 24 Jan 14 nicklas 3218   //java.text.SimpleDateFormat class, with minor exceptions.
6399 24 Jan 14 nicklas 3219   //The format string consists of the following abbreviations:
6399 24 Jan 14 nicklas 3220   //
6399 24 Jan 14 nicklas 3221   //Field        | Full Form          | Short Form
6399 24 Jan 14 nicklas 3222   //-------------+--------------------+-----------------------
6399 24 Jan 14 nicklas 3223   //Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
6399 24 Jan 14 nicklas 3224   //Month        | MMMM(name or abbr.)| MM (2 digits), M (1 or 2 digits)
6399 24 Jan 14 nicklas 3225   //             | NNN (abbr.)        |
6399 24 Jan 14 nicklas 3226   //Day of Month | dd (2 digits)      | d (1 or 2 digits)
6399 24 Jan 14 nicklas 3227   //Day of Week  | EE (name)          | E (abbr)
6399 24 Jan 14 nicklas 3228   //Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
6399 24 Jan 14 nicklas 3229   //Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
6399 24 Jan 14 nicklas 3230   //Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
6399 24 Jan 14 nicklas 3231   //Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
6399 24 Jan 14 nicklas 3232   //Minute       | mm (2 digits)      | m (1 or 2 digits)
6399 24 Jan 14 nicklas 3233   //Second       | ss (2 digits)      | s (1 or 2 digits)
6399 24 Jan 14 nicklas 3234   //AM/PM        | a                  |
6399 24 Jan 14 nicklas 3235   //
6399 24 Jan 14 nicklas 3236   //NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
6399 24 Jan 14 nicklas 3237   //Examples:
6399 24 Jan 14 nicklas 3238   //"MMM d, y" matches: January 01, 2000
6399 24 Jan 14 nicklas 3239   //                     Dec 1, 1900
6399 24 Jan 14 nicklas 3240   //                     Nov 20, 00
6399 24 Jan 14 nicklas 3241   //"M/d/yy"   matches: 01/20/00
6399 24 Jan 14 nicklas 3242   //                     9/2/00
6399 24 Jan 14 nicklas 3243   //"MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
6399 24 Jan 14 nicklas 3244   //------------------------------------------------------------------
6399 24 Jan 14 nicklas 3245   
6399 24 Jan 14 nicklas 3246   var MONTH_NAMES = ['January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
6399 24 Jan 14 nicklas 3247   var DAY_NAMES = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
6399 24 Jan 14 nicklas 3248
6399 24 Jan 14 nicklas 3249   
6399 24 Jan 14 nicklas 3250   var dates = {};
7419 03 Nov 17 nicklas 3251   var internal = {};
6399 24 Jan 14 nicklas 3252   
6399 24 Jan 14 nicklas 3253   // convert a date to a string:
6399 24 Jan 14 nicklas 3254   // default format is yyyy-MM-dd
6399 24 Jan 14 nicklas 3255   dates.formatDate = function(date, format)
6399 24 Jan 14 nicklas 3256   {
6399 24 Jan 14 nicklas 3257     if (!format) format = "yyyy-MM-dd";
6399 24 Jan 14 nicklas 3258     var result="";
6399 24 Jan 14 nicklas 3259     var i_format=0;
6399 24 Jan 14 nicklas 3260     var c="";
6399 24 Jan 14 nicklas 3261     var token="";
6399 24 Jan 14 nicklas 3262     var y=date.getYear()+"";
6399 24 Jan 14 nicklas 3263     var M=date.getMonth()+1;
6399 24 Jan 14 nicklas 3264     var d=date.getDate();
6399 24 Jan 14 nicklas 3265     var E=date.getDay();
6399 24 Jan 14 nicklas 3266     var H=date.getHours();
6399 24 Jan 14 nicklas 3267     var m=date.getMinutes();
6399 24 Jan 14 nicklas 3268     var s=date.getSeconds();
6399 24 Jan 14 nicklas 3269     var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
6399 24 Jan 14 nicklas 3270     
6399 24 Jan 14 nicklas 3271     // Convert real date parts into formatted versions
6399 24 Jan 14 nicklas 3272     var value=new Object();
6399 24 Jan 14 nicklas 3273     if (y.length < 4) 
6399 24 Jan 14 nicklas 3274     {
6399 24 Jan 14 nicklas 3275       y=""+(y-0+1900);
6399 24 Jan 14 nicklas 3276     }
6399 24 Jan 14 nicklas 3277     value["y"]=""+y;
6399 24 Jan 14 nicklas 3278     value["yyyy"]=y;
6399 24 Jan 14 nicklas 3279     value["yy"]=y.substring(2,4);
6399 24 Jan 14 nicklas 3280     value["M"]=M;
7419 03 Nov 17 nicklas 3281     value["MM"]=internal.LZ(M);
6399 24 Jan 14 nicklas 3282     value["MMM"]=MONTH_NAMES[M+11];
6399 24 Jan 14 nicklas 3283     value["MMMM"]=MONTH_NAMES[M-1];
6399 24 Jan 14 nicklas 3284     value["NNN"]=MONTH_NAMES[M+11];
6399 24 Jan 14 nicklas 3285     value["d"]=d;
7419 03 Nov 17 nicklas 3286     value["dd"]=internal.LZ(d);
6399 24 Jan 14 nicklas 3287     value["E"]=DAY_NAMES[E+7];
6399 24 Jan 14 nicklas 3288     value["EE"]=DAY_NAMES[E];
6399 24 Jan 14 nicklas 3289     value["H"]=H;
7419 03 Nov 17 nicklas 3290     value["HH"]=internal.LZ(H);
6399 24 Jan 14 nicklas 3291     if (H==0) 
6399 24 Jan 14 nicklas 3292     {
6399 24 Jan 14 nicklas 3293       value["h"]=12;
6399 24 Jan 14 nicklas 3294     }
6399 24 Jan 14 nicklas 3295     else if (H>12)
6399 24 Jan 14 nicklas 3296     {
6399 24 Jan 14 nicklas 3297       value["h"]=H-12;
6399 24 Jan 14 nicklas 3298     }
6399 24 Jan 14 nicklas 3299     else 
6399 24 Jan 14 nicklas 3300     {
6399 24 Jan 14 nicklas 3301       value["h"]=H;
6399 24 Jan 14 nicklas 3302     }
7419 03 Nov 17 nicklas 3303     value["hh"]=internal.LZ(value["h"]);
6399 24 Jan 14 nicklas 3304     if (H>11)
6399 24 Jan 14 nicklas 3305     {
6399 24 Jan 14 nicklas 3306       value["K"]=H-12;
6399 24 Jan 14 nicklas 3307     }
6399 24 Jan 14 nicklas 3308     else 
6399 24 Jan 14 nicklas 3309     {
6399 24 Jan 14 nicklas 3310       value["K"]=H;
6399 24 Jan 14 nicklas 3311     }
6399 24 Jan 14 nicklas 3312     value["k"]=H+1;
7419 03 Nov 17 nicklas 3313     value["KK"]=internal.LZ(value["K"]);
7419 03 Nov 17 nicklas 3314     value["kk"]=internal.LZ(value["k"]);
6399 24 Jan 14 nicklas 3315     if (H > 11) 
6399 24 Jan 14 nicklas 3316     {
6399 24 Jan 14 nicklas 3317       value["a"]="PM"; 
6399 24 Jan 14 nicklas 3318     }
6399 24 Jan 14 nicklas 3319     else 
6399 24 Jan 14 nicklas 3320     {
6399 24 Jan 14 nicklas 3321       value["a"]="AM"; 
6399 24 Jan 14 nicklas 3322     }
6399 24 Jan 14 nicklas 3323     value["m"]=m;
7419 03 Nov 17 nicklas 3324     value["mm"]=internal.LZ(m);
6399 24 Jan 14 nicklas 3325     value["s"]=s;
7419 03 Nov 17 nicklas 3326     value["ss"]=internal.LZ(s);
6399 24 Jan 14 nicklas 3327     while (i_format < format.length) 
6399 24 Jan 14 nicklas 3328     {
6399 24 Jan 14 nicklas 3329       c=format.charAt(i_format);
6399 24 Jan 14 nicklas 3330       token="";
6399 24 Jan 14 nicklas 3331       while ((format.charAt(i_format)==c) && (i_format < format.length)) 
6399 24 Jan 14 nicklas 3332       {
6399 24 Jan 14 nicklas 3333         token += format.charAt(i_format++);
6399 24 Jan 14 nicklas 3334       }
6399 24 Jan 14 nicklas 3335       if (value[token] != null) 
6399 24 Jan 14 nicklas 3336       {
6399 24 Jan 14 nicklas 3337         result=result + value[token]; 
6399 24 Jan 14 nicklas 3338       }
6399 24 Jan 14 nicklas 3339       else 
6399 24 Jan 14 nicklas 3340       {
6399 24 Jan 14 nicklas 3341         result=result + token; 
6399 24 Jan 14 nicklas 3342       }
6399 24 Jan 14 nicklas 3343     }
6399 24 Jan 14 nicklas 3344     return result;
6399 24 Jan 14 nicklas 3345   }
6399 24 Jan 14 nicklas 3346
6399 24 Jan 14 nicklas 3347   /*
6399 24 Jan 14 nicklas 3348     Convert a year value to a string with four digits, ie. pad with zeroes
6399 24 Jan 14 nicklas 3349     as needed
6399 24 Jan 14 nicklas 3350   */
6399 24 Jan 14 nicklas 3351   dates.getFourDigitYear = function(year)
6399 24 Jan 14 nicklas 3352   {
6399 24 Jan 14 nicklas 3353     if (year < 10) 
6399 24 Jan 14 nicklas 3354     {
6399 24 Jan 14 nicklas 3355       year = '000'+year;
6399 24 Jan 14 nicklas 3356     }
6399 24 Jan 14 nicklas 3357     else if (year < 100) 
6399 24 Jan 14 nicklas 3358     {
6399 24 Jan 14 nicklas 3359       year = '00'+year;
6399 24 Jan 14 nicklas 3360     }
6399 24 Jan 14 nicklas 3361     else if (year < 1000) 
6399 24 Jan 14 nicklas 3362     {
6399 24 Jan 14 nicklas 3363       year = '0'+year;
6399 24 Jan 14 nicklas 3364     }
6399 24 Jan 14 nicklas 3365     return year;
6399 24 Jan 14 nicklas 3366   }
6399 24 Jan 14 nicklas 3367   
6399 24 Jan 14 nicklas 3368   /*
6399 24 Jan 14 nicklas 3369     Create a new date object with the specified year, month and day
6399 24 Jan 14 nicklas 3370   */
6399 24 Jan 14 nicklas 3371   dates.newDate = function(year, month, day)
6399 24 Jan 14 nicklas 3372   {
6399 24 Jan 14 nicklas 3373     var date = new Date();
6399 24 Jan 14 nicklas 3374     date.setFullYear(year, month, day);
6399 24 Jan 14 nicklas 3375     return date;
6399 24 Jan 14 nicklas 3376   }
6399 24 Jan 14 nicklas 3377   
6399 24 Jan 14 nicklas 3378   // ------------------------------------------------------------------
6399 24 Jan 14 nicklas 3379   // isDate ( date_string, format_string )
6399 24 Jan 14 nicklas 3380   // Returns true if date string matches format of format string and
6399 24 Jan 14 nicklas 3381   // is a valid date. Else returns false.
6399 24 Jan 14 nicklas 3382   // It is recommended that you trim whitespace around the value before
6399 24 Jan 14 nicklas 3383   // passing it to this function, as whitespace is NOT ignored!
6399 24 Jan 14 nicklas 3384   // ------------------------------------------------------------------
6399 24 Jan 14 nicklas 3385   dates.isDate = function(val, format) 
6399 24 Jan 14 nicklas 3386   {
6399 24 Jan 14 nicklas 3387     return dates.parseString(val, format) != null;
6399 24 Jan 14 nicklas 3388   }
6399 24 Jan 14 nicklas 3389
6399 24 Jan 14 nicklas 3390   // ------------------------------------------------------------------
6399 24 Jan 14 nicklas 3391   // parseString( date_string , format_string )
6399 24 Jan 14 nicklas 3392   //
6399 24 Jan 14 nicklas 3393   // This function takes a date string and a format string. It matches
6399 24 Jan 14 nicklas 3394   // If the date string matches the format string, it returns the 
6399 24 Jan 14 nicklas 3395   // the date object. If it does not match, it returns null.
6399 24 Jan 14 nicklas 3396   // ------------------------------------------------------------------
6399 24 Jan 14 nicklas 3397   dates.parseString = function(val, format)
6399 24 Jan 14 nicklas 3398   {
6399 24 Jan 14 nicklas 3399     if (!format) format = 'yyyy-MM-dd';
6399 24 Jan 14 nicklas 3400     var i_val=0;
6399 24 Jan 14 nicklas 3401     var i_format=0;
6399 24 Jan 14 nicklas 3402     var c="";
6399 24 Jan 14 nicklas 3403     var token="";
6399 24 Jan 14 nicklas 3404     var token2="";
6399 24 Jan 14 nicklas 3405     var x,y;
6399 24 Jan 14 nicklas 3406     var now=new Date();
6399 24 Jan 14 nicklas 3407     var year=now.getYear();
6399 24 Jan 14 nicklas 3408     var month=now.getMonth()+1;
6399 24 Jan 14 nicklas 3409     var date=1;
6399 24 Jan 14 nicklas 3410     var hh=now.getHours();
6399 24 Jan 14 nicklas 3411     var mm=now.getMinutes();
6399 24 Jan 14 nicklas 3412     var ss=now.getSeconds();
6399 24 Jan 14 nicklas 3413     var ampm="";
6399 24 Jan 14 nicklas 3414     
6399 24 Jan 14 nicklas 3415     while (i_format < format.length) 
6399 24 Jan 14 nicklas 3416     {
6399 24 Jan 14 nicklas 3417       // Get next token from format string
6399 24 Jan 14 nicklas 3418       c=format.charAt(i_format);
6399 24 Jan 14 nicklas 3419       token="";
6399 24 Jan 14 nicklas 3420       while ((format.charAt(i_format)==c) && (i_format < format.length)) 
6399 24 Jan 14 nicklas 3421       {
6399 24 Jan 14 nicklas 3422         token += format.charAt(i_format++);
6399 24 Jan 14 nicklas 3423       }
6399 24 Jan 14 nicklas 3424       // Extract contents of value based on format token
6399 24 Jan 14 nicklas 3425       if (token=="yyyy" || token=="yy" || token=="y") 
6399 24 Jan 14 nicklas 3426       {
6399 24 Jan 14 nicklas 3427         if (token=="yyyy") { x=4;y=4; }
6399 24 Jan 14 nicklas 3428         if (token=="yy")   { x=2;y=2; }
6399 24 Jan 14 nicklas 3429         if (token=="y")    { x=2;y=4; }
7419 03 Nov 17 nicklas 3430         year=internal.getInt(val,i_val,x,y);
6399 24 Jan 14 nicklas 3431         if (year==null) { return null; }
6399 24 Jan 14 nicklas 3432         i_val += year.length;
6399 24 Jan 14 nicklas 3433         if (year.length==2) 
6399 24 Jan 14 nicklas 3434         {
6399 24 Jan 14 nicklas 3435           if (year > 70) 
6399 24 Jan 14 nicklas 3436           { 
6399 24 Jan 14 nicklas 3437             year=1900+(year-0); 
6399 24 Jan 14 nicklas 3438           }
6399 24 Jan 14 nicklas 3439           else 
6399 24 Jan 14 nicklas 3440           { 
6399 24 Jan 14 nicklas 3441             year=2000+(year-0); 
6399 24 Jan 14 nicklas 3442           }
6399 24 Jan 14 nicklas 3443         }
6399 24 Jan 14 nicklas 3444       }
6399 24 Jan 14 nicklas 3445       else if (token=='MMMM' || token=="MMM" || token=="NNN")
6399 24 Jan 14 nicklas 3446       {
6399 24 Jan 14 nicklas 3447         month=0;
6399 24 Jan 14 nicklas 3448         for (var i=0; i<MONTH_NAMES.length; i++) 
6399 24 Jan 14 nicklas 3449         {
6399 24 Jan 14 nicklas 3450           var month_name=MONTH_NAMES[i];
6399 24 Jan 14 nicklas 3451           if (val.substring(i_val,i_val+month_name.length).toLowerCase()==month_name.toLowerCase()) 
6399 24 Jan 14 nicklas 3452           {
6399 24 Jan 14 nicklas 3453             month=i+1;
6399 24 Jan 14 nicklas 3454             if (month>12) { month -= 12; }
6399 24 Jan 14 nicklas 3455             i_val += month_name.length;
6399 24 Jan 14 nicklas 3456             break;
6399 24 Jan 14 nicklas 3457           }
6399 24 Jan 14 nicklas 3458         }
6399 24 Jan 14 nicklas 3459         if ((month < 1)||(month>12)) { return null; }
6399 24 Jan 14 nicklas 3460       }
6399 24 Jan 14 nicklas 3461       else if (token=="EE"||token=="E")
6399 24 Jan 14 nicklas 3462       {
6399 24 Jan 14 nicklas 3463         for (var i=0; i<this.DAY_NAMES.length; i++) 
6399 24 Jan 14 nicklas 3464         {
6399 24 Jan 14 nicklas 3465           var day_name=DAY_NAMES[i];
6399 24 Jan 14 nicklas 3466           if (val.substring(i_val,i_val+day_name.length).toLowerCase()==day_name.toLowerCase()) 
6399 24 Jan 14 nicklas 3467           {
6399 24 Jan 14 nicklas 3468             i_val += day_name.length;
6399 24 Jan 14 nicklas 3469             break;
6399 24 Jan 14 nicklas 3470           }
6399 24 Jan 14 nicklas 3471         }
6399 24 Jan 14 nicklas 3472       }
6399 24 Jan 14 nicklas 3473       else if (token=="MM"||token=="M") 
6399 24 Jan 14 nicklas 3474       {
7419 03 Nov 17 nicklas 3475         month=internal.getInt(val,i_val,token.length,2);
6399 24 Jan 14 nicklas 3476         if(month==null||(month<1)||(month>12)){return null;}
6399 24 Jan 14 nicklas 3477         i_val+=month.length;
6399 24 Jan 14 nicklas 3478       }
6399 24 Jan 14 nicklas 3479       else if (token=="dd"||token=="d") 
6399 24 Jan 14 nicklas 3480       {
7419 03 Nov 17 nicklas 3481         date=internal.getInt(val,i_val,token.length,2);
6399 24 Jan 14 nicklas 3482         if(date==null||(date<1)||(date>31)){return null;}
6399 24 Jan 14 nicklas 3483         i_val+=date.length;
6399 24 Jan 14 nicklas 3484       }
6399 24 Jan 14 nicklas 3485       else if (token=="hh"||token=="h") 
6399 24 Jan 14 nicklas 3486       {
7419 03 Nov 17 nicklas 3487         hh=internal.getInt(val,i_val,token.length,2);
6399 24 Jan 14 nicklas 3488         if(hh==null||(hh<1)||(hh>12)){return null;}
6399 24 Jan 14 nicklas 3489         i_val+=hh.length;
6399 24 Jan 14 nicklas 3490       }
6399 24 Jan 14 nicklas 3491       else if (token=="HH"||token=="H") 
6399 24 Jan 14 nicklas 3492       {
7419 03 Nov 17 nicklas 3493         hh=internal.getInt(val,i_val,token.length,2);
6399 24 Jan 14 nicklas 3494         if(hh==null||(hh<0)||(hh>23)){return null;}
6399 24 Jan 14 nicklas 3495         i_val+=hh.length;
6399 24 Jan 14 nicklas 3496       }
6399 24 Jan 14 nicklas 3497       else if (token=="KK"||token=="K") 
6399 24 Jan 14 nicklas 3498       {
7419 03 Nov 17 nicklas 3499         hh=internal.getInt(val,i_val,token.length,2);
6399 24 Jan 14 nicklas 3500         if(hh==null||(hh<0)||(hh>11)){return null;}
6399 24 Jan 14 nicklas 3501         i_val+=hh.length;
6399 24 Jan 14 nicklas 3502       }
6399 24 Jan 14 nicklas 3503       else if (token=="kk"||token=="k") 
6399 24 Jan 14 nicklas 3504       {
7419 03 Nov 17 nicklas 3505         hh=internal.getInt(val,i_val,token.length,2);
6399 24 Jan 14 nicklas 3506         if(hh==null||(hh<1)||(hh>24)){return null;}
6399 24 Jan 14 nicklas 3507         i_val+=hh.length;hh--;
6399 24 Jan 14 nicklas 3508       }
6399 24 Jan 14 nicklas 3509       else if (token=="mm"||token=="m") 
6399 24 Jan 14 nicklas 3510       {
7419 03 Nov 17 nicklas 3511         mm=internal.getInt(val,i_val,token.length,2);
6399 24 Jan 14 nicklas 3512         if(mm==null||(mm<0)||(mm>59)){return null;}
6399 24 Jan 14 nicklas 3513         i_val+=mm.length;
6399 24 Jan 14 nicklas 3514       }
6399 24 Jan 14 nicklas 3515       else if (token=="ss"||token=="s") 
6399 24 Jan 14 nicklas 3516       {
7419 03 Nov 17 nicklas 3517         ss=internal.getInt(val,i_val,token.length,2);
6399 24 Jan 14 nicklas 3518         if(ss==null||(ss<0)||(ss>59)){return null;}
6399 24 Jan 14 nicklas 3519         i_val+=ss.length;
6399 24 Jan 14 nicklas 3520       }
6399 24 Jan 14 nicklas 3521       else if (token=="a") 
6399 24 Jan 14 nicklas 3522       {
6399 24 Jan 14 nicklas 3523         if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";}
6399 24 Jan 14 nicklas 3524         else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";}
6399 24 Jan 14 nicklas 3525         else {return null;}
6399 24 Jan 14 nicklas 3526         i_val+=2;
6399 24 Jan 14 nicklas 3527       }
6399 24 Jan 14 nicklas 3528       else 
6399 24 Jan 14 nicklas 3529       {
6399 24 Jan 14 nicklas 3530         if (val.substring(i_val,i_val+token.length)!=token) {return null;}
6399 24 Jan 14 nicklas 3531         else {i_val+=token.length;}
6399 24 Jan 14 nicklas 3532       }
6399 24 Jan 14 nicklas 3533     }
6399 24 Jan 14 nicklas 3534     
6399 24 Jan 14 nicklas 3535     // If there are any trailing characters left in the value, it doesn't match
6399 24 Jan 14 nicklas 3536     if (i_val != val.length) { return null; }
6399 24 Jan 14 nicklas 3537     
6399 24 Jan 14 nicklas 3538     // Is date valid for month?
6399 24 Jan 14 nicklas 3539     if (month==2) 
6399 24 Jan 14 nicklas 3540     {
6399 24 Jan 14 nicklas 3541       // Check for leap year
6399 24 Jan 14 nicklas 3542       if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) 
6399 24 Jan 14 nicklas 3543       { // leap year
6399 24 Jan 14 nicklas 3544         if (date > 29){ return null; }
6399 24 Jan 14 nicklas 3545       }
6399 24 Jan 14 nicklas 3546       else 
6399 24 Jan 14 nicklas 3547       { 
6399 24 Jan 14 nicklas 3548         if (date > 28) { return null; } 
6399 24 Jan 14 nicklas 3549       }
6399 24 Jan 14 nicklas 3550     }
6399 24 Jan 14 nicklas 3551     if ((month==4)||(month==6)||(month==9)||(month==11)) 
6399 24 Jan 14 nicklas 3552     {
6399 24 Jan 14 nicklas 3553       if (date > 30) { return null; }
6399 24 Jan 14 nicklas 3554     }
6399 24 Jan 14 nicklas 3555     
6399 24 Jan 14 nicklas 3556     // Correct hours value
6399 24 Jan 14 nicklas 3557     if (hh<12 && ampm=="PM") 
6399 24 Jan 14 nicklas 3558     { 
6399 24 Jan 14 nicklas 3559       hh=hh-0+12; 
6399 24 Jan 14 nicklas 3560     }
6399 24 Jan 14 nicklas 3561     else if (hh>11 && ampm=="AM") 
6399 24 Jan 14 nicklas 3562     { 
6399 24 Jan 14 nicklas 3563       hh-=12; 
6399 24 Jan 14 nicklas 3564     }
6399 24 Jan 14 nicklas 3565     return new Date(year,month-1,date,hh,mm,ss);
6399 24 Jan 14 nicklas 3566   }
6399 24 Jan 14 nicklas 3567
7419 03 Nov 17 nicklas 3568   internal.isInteger = function(val) 
6399 24 Jan 14 nicklas 3569   {
6399 24 Jan 14 nicklas 3570     var digits="1234567890";
6399 24 Jan 14 nicklas 3571     for (var i=0; i < val.length; i++) 
6399 24 Jan 14 nicklas 3572     {
6399 24 Jan 14 nicklas 3573       if (digits.indexOf(val.charAt(i))==-1) { return false; }
6399 24 Jan 14 nicklas 3574     }
6399 24 Jan 14 nicklas 3575     return true;
6399 24 Jan 14 nicklas 3576   }
6399 24 Jan 14 nicklas 3577   
7419 03 Nov 17 nicklas 3578   internal.getInt = function(str,i,minlength,maxlength) 
6399 24 Jan 14 nicklas 3579   {
6399 24 Jan 14 nicklas 3580     for (var x=maxlength; x>=minlength; x--)
6399 24 Jan 14 nicklas 3581     {
6399 24 Jan 14 nicklas 3582       var token=str.substring(i,i+x);
6399 24 Jan 14 nicklas 3583       if (token.length < minlength) { return null; }
7419 03 Nov 17 nicklas 3584       if (internal.isInteger(token)) { return token; }
6399 24 Jan 14 nicklas 3585     }
6399 24 Jan 14 nicklas 3586     return null;
6399 24 Jan 14 nicklas 3587   }
6399 24 Jan 14 nicklas 3588   
7419 03 Nov 17 nicklas 3589   internal.LZ = function(x) 
6399 24 Jan 14 nicklas 3590   {
6399 24 Jan 14 nicklas 3591     return(x<0||x>9?"":"0")+x;
6399 24 Jan 14 nicklas 3592   }
6399 24 Jan 14 nicklas 3593
6399 24 Jan 14 nicklas 3594   /*
6399 24 Jan 14 nicklas 3595     Calculate the number of days in the specified month
6399 24 Jan 14 nicklas 3596     Months are numbered from 1 to 12. Returns 0 if the 
6399 24 Jan 14 nicklas 3597     specified month is invalid.
6399 24 Jan 14 nicklas 3598   */
6399 24 Jan 14 nicklas 3599   dates.daysInMonth = function(year, month)
6399 24 Jan 14 nicklas 3600   {
6399 24 Jan 14 nicklas 3601     switch (month)
6399 24 Jan 14 nicklas 3602     {
6399 24 Jan 14 nicklas 3603       // Months with 31 days
6399 24 Jan 14 nicklas 3604       case 1:
6399 24 Jan 14 nicklas 3605       case 3:
6399 24 Jan 14 nicklas 3606       case 5:
6399 24 Jan 14 nicklas 3607       case 7:
6399 24 Jan 14 nicklas 3608       case 8:
6399 24 Jan 14 nicklas 3609       case 10:
6399 24 Jan 14 nicklas 3610       case 12:
6399 24 Jan 14 nicklas 3611         return 31;
6399 24 Jan 14 nicklas 3612         break;
6399 24 Jan 14 nicklas 3613       // Months with 30 days
6399 24 Jan 14 nicklas 3614       case 4:
6399 24 Jan 14 nicklas 3615       case 6:
6399 24 Jan 14 nicklas 3616       case 9:
6399 24 Jan 14 nicklas 3617       case 11:
6399 24 Jan 14 nicklas 3618         return 30;
6399 24 Jan 14 nicklas 3619         break;
6399 24 Jan 14 nicklas 3620       
6399 24 Jan 14 nicklas 3621       // February, taking leap years into account
6399 24 Jan 14 nicklas 3622       case 2:
6399 24 Jan 14 nicklas 3623         if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
6399 24 Jan 14 nicklas 3624         {
6399 24 Jan 14 nicklas 3625           return 29;
6399 24 Jan 14 nicklas 3626         }
6399 24 Jan 14 nicklas 3627         else
6399 24 Jan 14 nicklas 3628         {
6399 24 Jan 14 nicklas 3629           return 28;
6399 24 Jan 14 nicklas 3630         }
6399 24 Jan 14 nicklas 3631         break;
6399 24 Jan 14 nicklas 3632   
6399 24 Jan 14 nicklas 3633       default:
6399 24 Jan 14 nicklas 3634         return 0;
6399 24 Jan 14 nicklas 3635     }
6399 24 Jan 14 nicklas 3636     return 0;
6399 24 Jan 14 nicklas 3637   }
6399 24 Jan 14 nicklas 3638
6399 24 Jan 14 nicklas 3639   /*
6399 24 Jan 14 nicklas 3640     Opens a popup window allowing a user to select a date graphically. The 
6399 24 Jan 14 nicklas 3641     current date is taken from the specified input field of the specified form and 
6399 24 Jan 14 nicklas 3642     is saved back to it when the window is closed.
6399 24 Jan 14 nicklas 3643   
6399 24 Jan 14 nicklas 3644     @param title The title of the window
6399 24 Jan 14 nicklas 3645     @param form The name of the form where the input field is located
6399 24 Jan 14 nicklas 3646     @param input The name of the input field that holds the date value
6399 24 Jan 14 nicklas 3647     @param callback Deprecated, onchange event is sent instead
6399 24 Jan 14 nicklas 3648   */
6576 22 Oct 14 nicklas 3649   // @Deprecated
6399 24 Jan 14 nicklas 3650   dates.selectDate = function(title, form, input, callback, format)
6399 24 Jan 14 nicklas 3651   {
6576 22 Oct 14 nicklas 3652     App.deprecatedMethod('Dates.selectDate()', '3.5');
6399 24 Jan 14 nicklas 3653     var textarea = document.forms[form][input];
6399 24 Jan 14 nicklas 3654     if (!textarea.id) textarea.id = 't'+(new Date()).getTime();
6399 24 Jan 14 nicklas 3655     Dialogs.openCalendar(textarea, title, format, false);
6399 24 Jan 14 nicklas 3656   }
6399 24 Jan 14 nicklas 3657   
6399 24 Jan 14 nicklas 3658   /*
6399 24 Jan 14 nicklas 3659     Opens a popup window allowing a user to select a date and time graphically. The 
6399 24 Jan 14 nicklas 3660     current value is taken from the specified input field of the specified form and 
6399 24 Jan 14 nicklas 3661     is saved back to it when the window is closed.
6399 24 Jan 14 nicklas 3662   
6399 24 Jan 14 nicklas 3663     @param title The title of the window
6399 24 Jan 14 nicklas 3664     @param form The name of the form where the input field is located
6399 24 Jan 14 nicklas 3665     @param input The name of the input field that holds the date value
6399 24 Jan 14 nicklas 3666     @param callback Deprecated, onchange event is sent instead
6399 24 Jan 14 nicklas 3667   */
6576 22 Oct 14 nicklas 3668   // @Deprecated
6399 24 Jan 14 nicklas 3669   dates.selectDateTime = function(title, form, input, callback, format)
6399 24 Jan 14 nicklas 3670   {
6576 22 Oct 14 nicklas 3671     App.deprecatedMethod('Dates.selectDateTime()', '3.5');
6399 24 Jan 14 nicklas 3672     var textarea = document.forms[form][input];
6399 24 Jan 14 nicklas 3673     if (!textarea.id) textarea.id = 't'+(new Date()).getTime();
6399 24 Jan 14 nicklas 3674     Dialogs.openCalendar(textarea, title, format, true);
6399 24 Jan 14 nicklas 3675   }
6399 24 Jan 14 nicklas 3676
6399 24 Jan 14 nicklas 3677   return dates;
6399 24 Jan 14 nicklas 3678 }();
6399 24 Jan 14 nicklas 3679
6399 24 Jan 14 nicklas 3680
6184 26 Oct 12 nicklas 3681 var MultiSelect = function()
6184 26 Oct 12 nicklas 3682 {
6184 26 Oct 12 nicklas 3683   var select = {};
6184 26 Oct 12 nicklas 3684   var internal = {};
6184 26 Oct 12 nicklas 3685   
6184 26 Oct 12 nicklas 3686   // The currently active multi-select element
6184 26 Oct 12 nicklas 3687   var activeSelect = null;
6184 26 Oct 12 nicklas 3688   // The last active multi-select element
6184 26 Oct 12 nicklas 3689   var lastSelect = null;
6184 26 Oct 12 nicklas 3690   // Flag to indicate that automatic hiding of the options list 
6184 26 Oct 12 nicklas 3691   // should be disabled due to the user is interacting with the list
6184 26 Oct 12 nicklas 3692   var disableHide = false;
6184 26 Oct 12 nicklas 3693   // The option that is currently higlighted
6184 26 Oct 12 nicklas 3694   var highlightedOption = null;
6184 26 Oct 12 nicklas 3695   
6184 26 Oct 12 nicklas 3696   /**
6184 26 Oct 12 nicklas 3697     Get the number of options that are checked in the 
6184 26 Oct 12 nicklas 3698     given multi-select element.
6184 26 Oct 12 nicklas 3699     @param element The ID or element object for a multi-select control
6184 26 Oct 12 nicklas 3700   */
6184 26 Oct 12 nicklas 3701   select.getNumChecked = function(element)
6184 26 Oct 12 nicklas 3702   {
6184 26 Oct 12 nicklas 3703     element = Doc.element(element);
6184 26 Oct 12 nicklas 3704     if (!element.options) return 0;
6184 26 Oct 12 nicklas 3705     var numChecked = 0;
6184 26 Oct 12 nicklas 3706     for (var i = 0; i < element.options.length; i++)
6184 26 Oct 12 nicklas 3707     {
6184 26 Oct 12 nicklas 3708       if (element.options[i].checked)
6184 26 Oct 12 nicklas 3709       {
6184 26 Oct 12 nicklas 3710         numChecked++;
6184 26 Oct 12 nicklas 3711       }
6184 26 Oct 12 nicklas 3712     }
6184 26 Oct 12 nicklas 3713     return numChecked;
6184 26 Oct 12 nicklas 3714   }
6184 26 Oct 12 nicklas 3715   
6184 26 Oct 12 nicklas 3716   /**
6184 26 Oct 12 nicklas 3717     Check the given option. The filter display is
6184 26 Oct 12 nicklas 3718     automatically updated.
6184 26 Oct 12 nicklas 3719     @param option The option element to check
6184 26 Oct 12 nicklas 3720   */
6184 26 Oct 12 nicklas 3721   select.checkOption = function(option)
6184 26 Oct 12 nicklas 3722   {
6184 26 Oct 12 nicklas 3723     if (!Data.int(activeSelect, 'multiple'))
6184 26 Oct 12 nicklas 3724     {
6184 26 Oct 12 nicklas 3725       for (var i = 0; i < activeSelect.options.length; i++)
6184 26 Oct 12 nicklas 3726       {
6184 26 Oct 12 nicklas 3727         activeSelect.options[i].checked = false;
6342 01 Nov 13 nicklas 3728         Doc.removeClass(activeSelect.options[i].div, 'checked');
6184 26 Oct 12 nicklas 3729       }
6184 26 Oct 12 nicklas 3730     }
6184 26 Oct 12 nicklas 3731     option.option.checked = true;
6342 01 Nov 13 nicklas 3732     Doc.addClass(option, 'checked');
6184 26 Oct 12 nicklas 3733     select.updateFilter(activeSelect);
6184 26 Oct 12 nicklas 3734   }
6184 26 Oct 12 nicklas 3735   
6184 26 Oct 12 nicklas 3736   /**
6184 26 Oct 12 nicklas 3737     Uncheck the given option. The filter display is
6184 26 Oct 12 nicklas 3738     automatically updated.
6184 26 Oct 12 nicklas 3739     @param option The option element to uncheck
6184 26 Oct 12 nicklas 3740   */
6184 26 Oct 12 nicklas 3741   select.uncheckOption = function(option)
6184 26 Oct 12 nicklas 3742   {
6184 26 Oct 12 nicklas 3743     option.option.checked = false;
6342 01 Nov 13 nicklas 3744     Doc.removeClass(option, 'checked');
6184 26 Oct 12 nicklas 3745     select.updateFilter(activeSelect);
6184 26 Oct 12 nicklas 3746   }
6184 26 Oct 12 nicklas 3747   
6184 26 Oct 12 nicklas 3748   /**
6184 26 Oct 12 nicklas 3749     Toggle the given option. The filter display is
6184 26 Oct 12 nicklas 3750     automatically updated.
6184 26 Oct 12 nicklas 3751     @param option The option element to check
6184 26 Oct 12 nicklas 3752   */
6184 26 Oct 12 nicklas 3753   select.toggleOption = function(option)
6184 26 Oct 12 nicklas 3754   {
6184 26 Oct 12 nicklas 3755     if (!option.option.checked)
6184 26 Oct 12 nicklas 3756     {
6184 26 Oct 12 nicklas 3757       select.checkOption(option);
6184 26 Oct 12 nicklas 3758     }
6184 26 Oct 12 nicklas 3759     else
6184 26 Oct 12 nicklas 3760     {
6184 26 Oct 12 nicklas 3761       select.uncheckOption(option);
6184 26 Oct 12 nicklas 3762     }
6184 26 Oct 12 nicklas 3763   }
6184 26 Oct 12 nicklas 3764
6824 02 Apr 15 nicklas 3765   /**
6824 02 Apr 15 nicklas 3766     Event handler when clicking on the check/uncheck all icon.
6824 02 Apr 15 nicklas 3767   */
6824 02 Apr 15 nicklas 3768   select.checkUncheckAllOnClick = function(event)
6824 02 Apr 15 nicklas 3769   {
6836 08 Apr 15 nicklas 3770     var specialKey = event.altKey || event.ctrlKey || event.shiftKey;
6836 08 Apr 15 nicklas 3771     if (specialKey)
6836 08 Apr 15 nicklas 3772     {
6836 08 Apr 15 nicklas 3773       select.checkUncheckAll();
6836 08 Apr 15 nicklas 3774     }
6836 08 Apr 15 nicklas 3775     else
6836 08 Apr 15 nicklas 3776     {
6836 08 Apr 15 nicklas 3777       select.toggleAll();
6836 08 Apr 15 nicklas 3778     }
6824 02 Apr 15 nicklas 3779   }
6824 02 Apr 15 nicklas 3780
6824 02 Apr 15 nicklas 3781   
6184 26 Oct 12 nicklas 3782   /*
6824 02 Apr 15 nicklas 3783     Check or uncheck all options. If forceCheck is true/false
6824 02 Apr 15 nicklas 3784     this value is used, otherwise the status of the first
6184 26 Oct 12 nicklas 3785     option is reversed and all other options are set to the
6184 26 Oct 12 nicklas 3786     same value.
6184 26 Oct 12 nicklas 3787   */
6824 02 Apr 15 nicklas 3788   select.checkUncheckAll = function(check)
6184 26 Oct 12 nicklas 3789   {
6184 26 Oct 12 nicklas 3790     if (!activeSelect) return;
6184 26 Oct 12 nicklas 3791     
6184 26 Oct 12 nicklas 3792     var options = activeSelect.options;
6824 02 Apr 15 nicklas 3793     if (check != false && check != true) check = !options[0].checked;
6184 26 Oct 12 nicklas 3794     for (var i = 0; i < options.length; i++)
6184 26 Oct 12 nicklas 3795     {
6184 26 Oct 12 nicklas 3796       options[i].checked = check;
6342 01 Nov 13 nicklas 3797       Doc.addOrRemoveClass(options[i].div, 'checked', check);
6184 26 Oct 12 nicklas 3798     }
6184 26 Oct 12 nicklas 3799     select.updateFilter(activeSelect);
6184 26 Oct 12 nicklas 3800   }
6184 26 Oct 12 nicklas 3801
6836 08 Apr 15 nicklas 3802   /*
6836 08 Apr 15 nicklas 3803     Toggle all options.
6836 08 Apr 15 nicklas 3804   */
6836 08 Apr 15 nicklas 3805   select.toggleAll = function()
6836 08 Apr 15 nicklas 3806   {
6836 08 Apr 15 nicklas 3807     if (!activeSelect) return;
6836 08 Apr 15 nicklas 3808     
6836 08 Apr 15 nicklas 3809     var options = activeSelect.options;
6836 08 Apr 15 nicklas 3810     for (var i = 0; i < options.length; i++)
6836 08 Apr 15 nicklas 3811     {
6836 08 Apr 15 nicklas 3812       options[i].checked = !options[i].checked;
6836 08 Apr 15 nicklas 3813       Doc.addOrRemoveClass(options[i].div, 'checked', options[i].checked);
6836 08 Apr 15 nicklas 3814     }
6836 08 Apr 15 nicklas 3815     select.updateFilter(activeSelect);
6836 08 Apr 15 nicklas 3816   }
6836 08 Apr 15 nicklas 3817
6836 08 Apr 15 nicklas 3818   
6184 26 Oct 12 nicklas 3819   /**
6184 26 Oct 12 nicklas 3820     Update the visible and hidden filter values for
6184 26 Oct 12 nicklas 3821     the given multi-select element.
6184 26 Oct 12 nicklas 3822     @param element The ID or element object for a multi-select control
6184 26 Oct 12 nicklas 3823   */
6184 26 Oct 12 nicklas 3824   select.updateFilter = function(element)
6184 26 Oct 12 nicklas 3825   {
6184 26 Oct 12 nicklas 3826     element = Doc.element(element);
6184 26 Oct 12 nicklas 3827     if (!element.options) return;
6184 26 Oct 12 nicklas 3828     
6184 26 Oct 12 nicklas 3829     var keys = [];
6184 26 Oct 12 nicklas 3830     var values = [];
6184 26 Oct 12 nicklas 3831     
6184 26 Oct 12 nicklas 3832     for (var i = 0; i < element.options.length; i++)
6184 26 Oct 12 nicklas 3833     {
6184 26 Oct 12 nicklas 3834       var option = element.options[i];
6184 26 Oct 12 nicklas 3835       if (option.checked)
6184 26 Oct 12 nicklas 3836       {
6184 26 Oct 12 nicklas 3837         keys[keys.length] = option.key;
6754 20 Feb 15 nicklas 3838         var ii = option.value.indexOf('<');
6826 02 Apr 15 nicklas 3839         values[values.length] = Strings.decodeTags(ii < 0 ? option.value : option.value.substring(0, ii).trim());
6184 26 Oct 12 nicklas 3840       }
6184 26 Oct 12 nicklas 3841     }
6184 26 Oct 12 nicklas 3842     if (element.infoDiv)
6184 26 Oct 12 nicklas 3843     {
6184 26 Oct 12 nicklas 3844       element.infoDiv.innerHTML = values.length + ' selected';
6184 26 Oct 12 nicklas 3845     }
6184 26 Oct 12 nicklas 3846     
6184 26 Oct 12 nicklas 3847     var prefix = element.notEqual && values.length > 0 ? '<>' : '';
6184 26 Oct 12 nicklas 3848     element.displayField.value = prefix+values.join('|');
6184 26 Oct 12 nicklas 3849     element.hiddenField.value = prefix+keys.join('|');
6184 26 Oct 12 nicklas 3850     // Special case when an empty option is selected
6184 26 Oct 12 nicklas 3851     if (values.length > 0 && element.hiddenField.value == '')
6184 26 Oct 12 nicklas 3852     {
6184 26 Oct 12 nicklas 3853       element.hiddenField.value = '=';
6184 26 Oct 12 nicklas 3854     }
6184 26 Oct 12 nicklas 3855   }
6184 26 Oct 12 nicklas 3856   
6184 26 Oct 12 nicklas 3857   /**
6184 26 Oct 12 nicklas 3858     Highlight the given option as if the mouse is over it
6184 26 Oct 12 nicklas 3859     or if the keyboard arrows have been used.
6184 26 Oct 12 nicklas 3860   */
6822 01 Apr 15 nicklas 3861   select.highlightOption = function(option, autoScroll)
6184 26 Oct 12 nicklas 3862   {
6184 26 Oct 12 nicklas 3863     if (highlightedOption)
6184 26 Oct 12 nicklas 3864     {
6608 20 Nov 14 nicklas 3865       Doc.removeClass(highlightedOption, 'active');
6184 26 Oct 12 nicklas 3866     }
6184 26 Oct 12 nicklas 3867     highlightedOption = option;
6184 26 Oct 12 nicklas 3868     if (highlightedOption)
6184 26 Oct 12 nicklas 3869     {
6608 20 Nov 14 nicklas 3870       Doc.addClass(highlightedOption, 'active');
6822 01 Apr 15 nicklas 3871       if (autoScroll) select.scrollIntoView(highlightedOption);
6184 26 Oct 12 nicklas 3872     }
6184 26 Oct 12 nicklas 3873   }
6184 26 Oct 12 nicklas 3874   
6184 26 Oct 12 nicklas 3875   /**
6822 01 Apr 15 nicklas 3876     Scroll the list so the given option is visible. 
6822 01 Apr 15 nicklas 3877     'alignFraction' can be set between 0 and 1 and is the fraction
6822 01 Apr 15 nicklas 3878     between top/bottom alignment. If not set, the alignment is
6822 01 Apr 15 nicklas 3879     auto to top or bottom.
6822 01 Apr 15 nicklas 3880   */
6822 01 Apr 15 nicklas 3881   select.scrollIntoView = function(option, alignFraction)
6822 01 Apr 15 nicklas 3882   {
6822 01 Apr 15 nicklas 3883     var parent = option.parentNode;
6822 01 Apr 15 nicklas 3884     var parentTop = parent.scrollTop;
6822 01 Apr 15 nicklas 3885     var optionTop = option.offsetTop-parent.offsetTop;
6822 01 Apr 15 nicklas 3886     var optionBottom = optionTop + option.offsetHeight;
6822 01 Apr 15 nicklas 3887     var parentBottom = parentTop + parent.offsetHeight;
6822 01 Apr 15 nicklas 3888     
6822 01 Apr 15 nicklas 3889     var scrollToCenter = alignFraction != undefined ? optionTop - alignFraction*(parent.offsetHeight-option.offsetHeight) : 0;
6822 01 Apr 15 nicklas 3890     if (optionTop < parentTop)
6822 01 Apr 15 nicklas 3891     {
6822 01 Apr 15 nicklas 3892       parent.scrollTop = scrollToCenter || optionTop;
6822 01 Apr 15 nicklas 3893     }
6822 01 Apr 15 nicklas 3894     else if (optionBottom > parentBottom)
6822 01 Apr 15 nicklas 3895     {
6822 01 Apr 15 nicklas 3896       parent.scrollTop = scrollToCenter || (optionBottom - parent.offsetHeight);
6822 01 Apr 15 nicklas 3897     }
6822 01 Apr 15 nicklas 3898   }
6822 01 Apr 15 nicklas 3899   
6822 01 Apr 15 nicklas 3900   /**
6184 26 Oct 12 nicklas 3901     Set the given multi-select element as the active element.
6184 26 Oct 12 nicklas 3902     If the currently active element is a different element, it
6184 26 Oct 12 nicklas 3903     is automatically hidden.
6184 26 Oct 12 nicklas 3904   */
6184 26 Oct 12 nicklas 3905   select.setActive = function(element)
6184 26 Oct 12 nicklas 3906   {
6184 26 Oct 12 nicklas 3907     element = Doc.element(element);
6184 26 Oct 12 nicklas 3908     if (element == activeSelect) return;
6184 26 Oct 12 nicklas 3909     
6184 26 Oct 12 nicklas 3910     // Get the dropdown div element
6184 26 Oct 12 nicklas 3911     var dropdownDiv = element.dropdownDiv;
6184 26 Oct 12 nicklas 3912     if (!dropdownDiv)
6184 26 Oct 12 nicklas 3913     {
6184 26 Oct 12 nicklas 3914       dropdownDiv = internal.createDropdownDiv(element);
6184 26 Oct 12 nicklas 3915     }
6184 26 Oct 12 nicklas 3916
6823 01 Apr 15 nicklas 3917     // Position the dropdown
6823 01 Apr 15 nicklas 3918     var pos = select.getFixedDropDownPosition(element);
7447 22 Feb 18 nicklas 3919     dropdownDiv.style.left = pos.left;
7447 22 Feb 18 nicklas 3920     dropdownDiv.style.right = pos.right;
7447 22 Feb 18 nicklas 3921     dropdownDiv.style.width = pos.width;
7447 22 Feb 18 nicklas 3922     dropdownDiv.style.minWidth = pos.minWidth;
7447 22 Feb 18 nicklas 3923     dropdownDiv.style.maxWidth = pos.maxWidth;
7447 22 Feb 18 nicklas 3924
6184 26 Oct 12 nicklas 3925     // Show it
6608 20 Nov 14 nicklas 3926     Doc.addClass(element, 'active');
6184 26 Oct 12 nicklas 3927     dropdownDiv.style.display = 'block';
6184 26 Oct 12 nicklas 3928     activeSelect = element;
6184 26 Oct 12 nicklas 3929     element.displayField.focus();
6822 01 Apr 15 nicklas 3930     // Need timeout since positions seems to not be correct until the dropdown has been draw on the screen
6822 01 Apr 15 nicklas 3931     setTimeout(function(){select.scrollToFirstChecked(element)}, 125);
6184 26 Oct 12 nicklas 3932   }
6184 26 Oct 12 nicklas 3933   
6184 26 Oct 12 nicklas 3934   /**
6823 01 Apr 15 nicklas 3935     Calculate left and width for the dropdown 
6823 01 Apr 15 nicklas 3936     given the filter element.
6823 01 Apr 15 nicklas 3937   */
6823 01 Apr 15 nicklas 3938   select.getFixedDropDownPosition = function(element)
6823 01 Apr 15 nicklas 3939   {
6823 01 Apr 15 nicklas 3940     // Position the dropdown div relative the  main element
6823 01 Apr 15 nicklas 3941     var fpos = Doc.getElementPosition(element);
6823 01 Apr 15 nicklas 3942     var dpos = {};
7447 22 Feb 18 nicklas 3943
7447 22 Feb 18 nicklas 3944     // The default is to make the drop-down the same size as the filter
7447 22 Feb 18 nicklas 3945     dpos.left = (fpos.left+1)+'px';
7447 22 Feb 18 nicklas 3946     dpos.width = (fpos.width)+'px';
7447 22 Feb 18 nicklas 3947     dpos.right = 'auto';
7447 22 Feb 18 nicklas 3948     dpos.minWidth = (Math.max(fpos.width, 150))+'px';
7447 22 Feb 18 nicklas 3949     dpos.maxWidth = 'auto';
6823 01 Apr 15 nicklas 3950     
7447 22 Feb 18 nicklas 3951     if (fpos.width < 300)
6823 01 Apr 15 nicklas 3952     {
7447 22 Feb 18 nicklas 3953       // If the width is smaller than 300 we should open up either
7447 22 Feb 18 nicklas 3954       // to the left or to the right, we need to check how close we are to
7447 22 Feb 18 nicklas 3955       // the right side
6823 01 Apr 15 nicklas 3956       try
6823 01 Apr 15 nicklas 3957       {
6823 01 Apr 15 nicklas 3958         // Now we must check if we end up outside the right side of
7955 02 Jun 21 nicklas 3959         // the browser window, find the parent <div class="itemlist"> element
7955 02 Jun 21 nicklas 3960         // or document.body to get the viewport width. We also need the
7955 02 Jun 21 nicklas 3961         // full width of the table which we get from the <tr class="headerrow"> element
7955 02 Jun 21 nicklas 3962         // Sticky headers complicates everything since this causes the <thead> 
7955 02 Jun 21 nicklas 3963         // to have it's own positioning context
6823 01 Apr 15 nicklas 3964         var p = element.parentNode;
7955 02 Jun 21 nicklas 3965         var tr = null;
7955 02 Jun 21 nicklas 3966         var divData = null;
7955 02 Jun 21 nicklas 3967         var stickyHeaders = false;
7955 02 Jun 21 nicklas 3968         while (p != null)
6823 01 Apr 15 nicklas 3969         {
7955 02 Jun 21 nicklas 3970           if (p.tagName == 'TR' && p.classList.contains('headerrow')) tr = p;
7955 02 Jun 21 nicklas 3971           if (p.tagName == 'DIV' && p.classList.contains('data')) divData = p;
7955 02 Jun 21 nicklas 3972           if (p.tagName == 'DIV' && p.classList.contains('sticky-headers')) stickyHeaders = true;
6823 01 Apr 15 nicklas 3973           p = p.parentNode;
6823 01 Apr 15 nicklas 3974         }
7955 02 Jun 21 nicklas 3975         if (divData==null) divData = document.body;
7955 02 Jun 21 nicklas 3976         if (tr==null) tr = divData;
7447 22 Feb 18 nicklas 3977
6823 01 Apr 15 nicklas 3978         // Get position data for the parent
7447 22 Feb 18 nicklas 3979         // +16 should be safe to account for a scrollbar
7955 02 Jun 21 nicklas 3980         var dataPos = Doc.getElementPosition(divData);
7955 02 Jun 21 nicklas 3981         var trPos = Doc.getElementPosition(tr);
7955 02 Jun 21 nicklas 3982         
7955 02 Jun 21 nicklas 3983         var scrollbar = divData.scrollHeight > divData.clientHeight ? 16 : 0;
7955 02 Jun 21 nicklas 3984         var maxRight = dataPos.width + divData.scrollLeft - scrollbar;
7447 22 Feb 18 nicklas 3985         dpos.width = 'auto';
7447 22 Feb 18 nicklas 3986         dpos.maxWidth = '300px';
7447 22 Feb 18 nicklas 3987         if (fpos.left + 300 > maxRight)
6823 01 Apr 15 nicklas 3988         {
7447 22 Feb 18 nicklas 3989           // Align to right
7447 22 Feb 18 nicklas 3990           dpos.left = 'auto';
7955 02 Jun 21 nicklas 3991           if (stickyHeaders)
7955 02 Jun 21 nicklas 3992           {
7955 02 Jun 21 nicklas 3993             dpos.right = (trPos.right-fpos.right)+'px';
7955 02 Jun 21 nicklas 3994           }
7955 02 Jun 21 nicklas 3995           else
7955 02 Jun 21 nicklas 3996           {
7955 02 Jun 21 nicklas 3997             dpos.right = (dataPos.right-fpos.right-scrollbar)+'px';
7955 02 Jun 21 nicklas 3998           }
6823 01 Apr 15 nicklas 3999           if (fpos.right > maxRight)
6823 01 Apr 15 nicklas 4000           {
6823 01 Apr 15 nicklas 4001             // And adjust scroll position if needed
7955 02 Jun 21 nicklas 4002             divData.scrollLeft += fpos.right - maxRight + scrollbar;
6823 01 Apr 15 nicklas 4003           }
6823 01 Apr 15 nicklas 4004         }
6823 01 Apr 15 nicklas 4005       }
6823 01 Apr 15 nicklas 4006       catch (err)
6823 01 Apr 15 nicklas 4007       {
6823 01 Apr 15 nicklas 4008         var msg = err;
6823 01 Apr 15 nicklas 4009         if (err.toString)
6823 01 Apr 15 nicklas 4010         {
6823 01 Apr 15 nicklas 4011           msg = err.toString() + '\n' + err.stack;
6823 01 Apr 15 nicklas 4012         }
6823 01 Apr 15 nicklas 4013         console.error(msg);
6823 01 Apr 15 nicklas 4014       }
6823 01 Apr 15 nicklas 4015     }
6823 01 Apr 15 nicklas 4016     return dpos;
6823 01 Apr 15 nicklas 4017   }
6823 01 Apr 15 nicklas 4018   
6823 01 Apr 15 nicklas 4019   /**
6822 01 Apr 15 nicklas 4020     Scroll the selection list so that the first selected element is
6822 01 Apr 15 nicklas 4021     visible and aligned about 1/4 between top and bottom.
6822 01 Apr 15 nicklas 4022   */
6822 01 Apr 15 nicklas 4023   select.scrollToFirstChecked = function(element)
6822 01 Apr 15 nicklas 4024   {
6822 01 Apr 15 nicklas 4025     for (var i = 0; i < element.options.length; i++)
6822 01 Apr 15 nicklas 4026     {
6822 01 Apr 15 nicklas 4027       var opt = element.options[i];
6822 01 Apr 15 nicklas 4028       if (opt.checked) 
6822 01 Apr 15 nicklas 4029       {
6822 01 Apr 15 nicklas 4030         select.scrollIntoView(opt.div, 0.25);
6822 01 Apr 15 nicklas 4031         return;
6822 01 Apr 15 nicklas 4032       }
6822 01 Apr 15 nicklas 4033     }
6822 01 Apr 15 nicklas 4034   }
6822 01 Apr 15 nicklas 4035   
6822 01 Apr 15 nicklas 4036   /**
6184 26 Oct 12 nicklas 4037     Hide the currently displayed dropdown (if any)
6184 26 Oct 12 nicklas 4038   */
6184 26 Oct 12 nicklas 4039   select.hideDropdown = function()
6184 26 Oct 12 nicklas 4040   {
6184 26 Oct 12 nicklas 4041     if (!activeSelect) return;
6184 26 Oct 12 nicklas 4042     select.highlightOption(null);
6608 20 Nov 14 nicklas 4043     Doc.removeClass(activeSelect, 'active');
6184 26 Oct 12 nicklas 4044     activeSelect.dropdownDiv.style.display = 'none';
6184 26 Oct 12 nicklas 4045     activeSelect = null;
6184 26 Oct 12 nicklas 4046   }
6184 26 Oct 12 nicklas 4047   
6184 26 Oct 12 nicklas 4048   /**
6184 26 Oct 12 nicklas 4049     Submit the form.
6184 26 Oct 12 nicklas 4050   */
6184 26 Oct 12 nicklas 4051   select.submit = function()
6184 26 Oct 12 nicklas 4052   {
6184 26 Oct 12 nicklas 4053     if (!activeSelect) return;
7894 08 Dec 20 nicklas 4054     Forms.submit(activeSelect.displayField.form);
7894 08 Dec 20 nicklas 4055     select.hideDropdown();
6184 26 Oct 12 nicklas 4056   }
6184 26 Oct 12 nicklas 4057   
6184 26 Oct 12 nicklas 4058   /**
6824 02 Apr 15 nicklas 4059     Clear this filter.
6824 02 Apr 15 nicklas 4060   */
6824 02 Apr 15 nicklas 4061   select.clear = function()
6824 02 Apr 15 nicklas 4062   {
6824 02 Apr 15 nicklas 4063     if (!activeSelect) return;
6824 02 Apr 15 nicklas 4064     select.checkUncheckAll(false);
6824 02 Apr 15 nicklas 4065     select.submit();
6824 02 Apr 15 nicklas 4066   }
6824 02 Apr 15 nicklas 4067   
6824 02 Apr 15 nicklas 4068   /**
6184 26 Oct 12 nicklas 4069     Initialize a multi-select enabled field.
6184 26 Oct 12 nicklas 4070    */
6184 26 Oct 12 nicklas 4071   internal.initializeMultiSelect = function(element, autoInit)
6184 26 Oct 12 nicklas 4072   {
6184 26 Oct 12 nicklas 4073     if (autoInit != 'multiselect') return;
6184 26 Oct 12 nicklas 4074     
6184 26 Oct 12 nicklas 4075     // Get the child elements that are part of the multi-select control
6184 26 Oct 12 nicklas 4076     var hidden = element.getElementsByClassName("multiselect-hidden")[0];
6184 26 Oct 12 nicklas 4077     var display = element.getElementsByClassName("multiselect-display")[0];
6184 26 Oct 12 nicklas 4078     var dropdown = element.getElementsByClassName("dropdown")[0];
6184 26 Oct 12 nicklas 4079
6608 20 Nov 14 nicklas 4080     Doc.addClass(element, 'interactable');
6608 20 Nov 14 nicklas 4081     
6184 26 Oct 12 nicklas 4082     // Store cross-references between the main and child elements
6184 26 Oct 12 nicklas 4083     element.hiddenField = hidden;
6184 26 Oct 12 nicklas 4084     element.displayField = display;
6184 26 Oct 12 nicklas 4085     element.dropdown = dropdown;
6184 26 Oct 12 nicklas 4086     hidden.multiSelect = element;
6184 26 Oct 12 nicklas 4087     display.multiSelect = element;
6184 26 Oct 12 nicklas 4088     dropdown.multiSelect = element;
6184 26 Oct 12 nicklas 4089     // Disable auto-complete on the input field
6184 26 Oct 12 nicklas 4090     display.setAttribute('autocomplete', 'off');
6184 26 Oct 12 nicklas 4091     
6184 26 Oct 12 nicklas 4092     // Load the key-value options from the data-options attribute
6184 26 Oct 12 nicklas 4093     element.options = Data.json(element, 'options');
6184 26 Oct 12 nicklas 4094     // 
6184 26 Oct 12 nicklas 4095     element.notEqual = hidden.value.indexOf('<>') == 0;
6184 26 Oct 12 nicklas 4096     // Show the current filter
6184 26 Oct 12 nicklas 4097     select.updateFilter(element);
6184 26 Oct 12 nicklas 4098     
6184 26 Oct 12 nicklas 4099     // Add event handlers to the 'display' input field
6184 26 Oct 12 nicklas 4100     Events.addEventHandler(display, 'focus', internal.onFocus);
6184 26 Oct 12 nicklas 4101     Events.addEventHandler(display, 'blur', internal.onBlur);
6184 26 Oct 12 nicklas 4102     Events.addEventHandler(display, 'keydown', internal.onKeyDown);
6184 26 Oct 12 nicklas 4103     
6184 26 Oct 12 nicklas 4104     // Add event handler to the 'dropdown' icon
6184 26 Oct 12 nicklas 4105     Events.addEventHandler(dropdown, 'click', internal.dropdownOnClick);
6184 26 Oct 12 nicklas 4106     
6184 26 Oct 12 nicklas 4107     // Ensure that the options list stay visible when the 
6184 26 Oct 12 nicklas 4108     // mouse is inside the active multi-select control
6184 26 Oct 12 nicklas 4109     Events.addEventHandler(element, 'mouseenter', internal.setHideFlag);
6184 26 Oct 12 nicklas 4110     Events.addEventHandler(element, 'mouseleave', internal.setHideFlag);
6184 26 Oct 12 nicklas 4111   }
6184 26 Oct 12 nicklas 4112   Doc.addElementInitializer(internal.initializeMultiSelect);
6184 26 Oct 12 nicklas 4113   
6184 26 Oct 12 nicklas 4114   /**
6184 26 Oct 12 nicklas 4115     Create the dropdown div with all options for the given
6184 26 Oct 12 nicklas 4116     multi-select element.
6184 26 Oct 12 nicklas 4117   */
6184 26 Oct 12 nicklas 4118   internal.createDropdownDiv = function(element)
6184 26 Oct 12 nicklas 4119   {
6184 26 Oct 12 nicklas 4120     var imgRoot = App.getRoot()+'images/';
6184 26 Oct 12 nicklas 4121     
6184 26 Oct 12 nicklas 4122     // Create the control panel
6184 26 Oct 12 nicklas 4123     var controlDiv = document.createElement('div');
6608 20 Nov 14 nicklas 4124     controlDiv.className = 'multioptioncontrol bg-filled-100';
6184 26 Oct 12 nicklas 4125
6184 26 Oct 12 nicklas 4126     // The 'submit' icon
6184 26 Oct 12 nicklas 4127     var submitIcon = document.createElement('span');
6184 26 Oct 12 nicklas 4128     submitIcon.className = 'link submit';
6184 26 Oct 12 nicklas 4129     submitIcon.innerHTML = '<img src="' + imgRoot + 'ok.png">';
6824 02 Apr 15 nicklas 4130     submitIcon.title = 'Submit filter and update table';
6184 26 Oct 12 nicklas 4131     controlDiv.appendChild(submitIcon);
6184 26 Oct 12 nicklas 4132     Events.addEventHandler(submitIcon, 'click', select.submit);
6184 26 Oct 12 nicklas 4133
6824 02 Apr 15 nicklas 4134     // The 'clear' icon
6824 02 Apr 15 nicklas 4135     var clearIcon = document.createElement('span');
6824 02 Apr 15 nicklas 4136     clearIcon.className = 'link clear';
6829 02 Apr 15 nicklas 4137     clearIcon.innerHTML = '<img src="' + imgRoot + 'remove.png">';
6824 02 Apr 15 nicklas 4138     clearIcon.title = 'Clear filter and update table';
6824 02 Apr 15 nicklas 4139     controlDiv.appendChild(clearIcon);
6824 02 Apr 15 nicklas 4140     Events.addEventHandler(clearIcon, 'click', select.clear);
6827 02 Apr 15 nicklas 4141
6827 02 Apr 15 nicklas 4142     // The '=/≠' icon for toggling between equals/not-equals comparison
6835 08 Apr 15 nicklas 4143     var neqDiv = document.createElement('img');
6835 08 Apr 15 nicklas 4144     neqDiv.className = 'link equal-or-not-equal';
6835 08 Apr 15 nicklas 4145     neqDiv.src = imgRoot+(element.notEqual ? 'not-equal.png' : 'equal.png');
6827 02 Apr 15 nicklas 4146     neqDiv.title = 'Toggle between equals and not-equals comparison';
6827 02 Apr 15 nicklas 4147     controlDiv.appendChild(neqDiv);
6827 02 Apr 15 nicklas 4148     Events.addEventHandler(neqDiv, 'click', internal.notEqualOnClick);
6827 02 Apr 15 nicklas 4149
6827 02 Apr 15 nicklas 4150     var multiple = Data.int(element, 'multiple');    
6184 26 Oct 12 nicklas 4151     if (multiple)
6184 26 Oct 12 nicklas 4152     {
6184 26 Oct 12 nicklas 4153       // The 'info' section
6184 26 Oct 12 nicklas 4154       var infoDiv = document.createElement('span');
6827 02 Apr 15 nicklas 4155       infoDiv.className = 'info link';
6184 26 Oct 12 nicklas 4156       infoDiv.innerHTML = select.getNumChecked(element)+' selected';
6836 08 Apr 15 nicklas 4157       infoDiv.title = 'Toggle selection (use CTRL, ALT or SHIFT to select/deselect all)';
6184 26 Oct 12 nicklas 4158       controlDiv.appendChild(infoDiv);
6827 02 Apr 15 nicklas 4159       Events.addEventHandler(infoDiv, 'click', select.checkUncheckAllOnClick);
6184 26 Oct 12 nicklas 4160       element.infoDiv = infoDiv;
6184 26 Oct 12 nicklas 4161     }
6184 26 Oct 12 nicklas 4162
6184 26 Oct 12 nicklas 4163     // Create the list with options
6184 26 Oct 12 nicklas 4164     var options = element.options;
6184 26 Oct 12 nicklas 4165     var allOptionsDiv = document.createElement('div');
6184 26 Oct 12 nicklas 4166     allOptionsDiv.className = 'multioptionoptions';
6184 26 Oct 12 nicklas 4167     
6184 26 Oct 12 nicklas 4168     for (var i = 0; i < options.length; i++)
6184 26 Oct 12 nicklas 4169     {
6184 26 Oct 12 nicklas 4170       var optionDiv = document.createElement('div');
6608 20 Nov 14 nicklas 4171       optionDiv.className = 'multioption interactable' + (options[i].checked ? ' checked' : '');
6184 26 Oct 12 nicklas 4172       optionDiv.option = options[i];
6184 26 Oct 12 nicklas 4173       optionDiv.innerHTML = options[i].value;
6184 26 Oct 12 nicklas 4174       options[i].div = optionDiv;
7447 22 Feb 18 nicklas 4175       optionDiv.title = options[i].value;
6184 26 Oct 12 nicklas 4176       allOptionsDiv.appendChild(optionDiv);
6754 20 Feb 15 nicklas 4177       Events.addEventHandler(optionDiv, 'click', internal.optionOnClick);
6754 20 Feb 15 nicklas 4178       Events.addEventHandler(optionDiv, 'mouseover', internal.optionHighlight);
6184 26 Oct 12 nicklas 4179     }
6184 26 Oct 12 nicklas 4180     
6184 26 Oct 12 nicklas 4181     // Main div
6184 26 Oct 12 nicklas 4182     var mainDiv = document.createElement('div');
6184 26 Oct 12 nicklas 4183     mainDiv.className = 'multioptions ' + (multiple ? 'multiple' : 'single');
6184 26 Oct 12 nicklas 4184     mainDiv.appendChild(controlDiv);
6184 26 Oct 12 nicklas 4185     mainDiv.appendChild(allOptionsDiv);
6184 26 Oct 12 nicklas 4186     
6184 26 Oct 12 nicklas 4187     Events.addEventHandler(mainDiv, 'click', internal.ensureFocused);
6184 26 Oct 12 nicklas 4188     
6184 26 Oct 12 nicklas 4189     element.dropdownDiv = mainDiv;
6184 26 Oct 12 nicklas 4190     element.appendChild(mainDiv);
6184 26 Oct 12 nicklas 4191     return mainDiv;
6184 26 Oct 12 nicklas 4192   }
6184 26 Oct 12 nicklas 4193   
6184 26 Oct 12 nicklas 4194   /**
6184 26 Oct 12 nicklas 4195     Event handler for the 'dropdown' icon. This should toggle the options
6184 26 Oct 12 nicklas 4196     list.
6184 26 Oct 12 nicklas 4197   */
6184 26 Oct 12 nicklas 4198   internal.dropdownOnClick = function(event)
6184 26 Oct 12 nicklas 4199   {
6184 26 Oct 12 nicklas 4200     var multiSelect = event.currentTarget.multiSelect;
6184 26 Oct 12 nicklas 4201     if (multiSelect == activeSelect)
6184 26 Oct 12 nicklas 4202     {
6184 26 Oct 12 nicklas 4203       // Hide the options
6184 26 Oct 12 nicklas 4204       select.hideDropdown();
6184 26 Oct 12 nicklas 4205     }
6184 26 Oct 12 nicklas 4206     else
6184 26 Oct 12 nicklas 4207     {
6184 26 Oct 12 nicklas 4208       // Show the new options
6184 26 Oct 12 nicklas 4209       select.setActive(multiSelect);
6184 26 Oct 12 nicklas 4210       disableHide = true;
6184 26 Oct 12 nicklas 4211     }
6184 26 Oct 12 nicklas 4212   }
6184 26 Oct 12 nicklas 4213   
6184 26 Oct 12 nicklas 4214   /**
6184 26 Oct 12 nicklas 4215     Event handler used when a multi-select enabled field receives
6184 26 Oct 12 nicklas 4216     the focus. This should set the current field to the active
6184 26 Oct 12 nicklas 4217     field and display the options dropdown.
6184 26 Oct 12 nicklas 4218   */
6184 26 Oct 12 nicklas 4219   internal.onFocus = function(event)
6184 26 Oct 12 nicklas 4220   {
6184 26 Oct 12 nicklas 4221     select.setActive(event.target.multiSelect);
6828 02 Apr 15 nicklas 4222     disableHide = true;
6184 26 Oct 12 nicklas 4223   }
6184 26 Oct 12 nicklas 4224   
6184 26 Oct 12 nicklas 4225   /*
6184 26 Oct 12 nicklas 4226     Event handler used when a multi-select enabled field
6184 26 Oct 12 nicklas 4227     loses the focus. This should hide the options and set
6184 26 Oct 12 nicklas 4228     the current field to null. We must use the delayed version 
6184 26 Oct 12 nicklas 4229     since the blur may occur due to a user clicking on an option
6184 26 Oct 12 nicklas 4230     in the dropdown in which case the field should regain focus and
6184 26 Oct 12 nicklas 4231     stay displayed.
6184 26 Oct 12 nicklas 4232   */
6184 26 Oct 12 nicklas 4233   internal.onBlur = function(event)
6184 26 Oct 12 nicklas 4234   {
6184 26 Oct 12 nicklas 4235     if (disableHide) return;
6184 26 Oct 12 nicklas 4236     select.hideDropdown();
6184 26 Oct 12 nicklas 4237   }
6184 26 Oct 12 nicklas 4238
6184 26 Oct 12 nicklas 4239   /*
6184 26 Oct 12 nicklas 4240     Called when a key is pressed in a multi-select enabled field.
6184 26 Oct 12 nicklas 4241     This function detects:
6184 26 Oct 12 nicklas 4242     * up/down arrow: moves the selected option
6184 26 Oct 12 nicklas 4243     * space: toggles the selected status of the current option
6184 26 Oct 12 nicklas 4244     * enter: toggles the current option and submits the form
6184 26 Oct 12 nicklas 4245     * tab: move to the next focusable field
6184 26 Oct 12 nicklas 4246   */
6184 26 Oct 12 nicklas 4247   internal.onKeyDown = function(event)
6184 26 Oct 12 nicklas 4248   {
6184 26 Oct 12 nicklas 4249     var keyCode = event.keyCode;
6184 26 Oct 12 nicklas 4250     var preventDefault = true;
6184 26 Oct 12 nicklas 4251     if (keyCode == 38) // up
6184 26 Oct 12 nicklas 4252     {
6184 26 Oct 12 nicklas 4253       if (highlightedOption && highlightedOption.previousSibling)
6184 26 Oct 12 nicklas 4254       {
6184 26 Oct 12 nicklas 4255         // highlight the previous option
6822 01 Apr 15 nicklas 4256         select.highlightOption(highlightedOption.previousSibling, true);
6184 26 Oct 12 nicklas 4257       }
6184 26 Oct 12 nicklas 4258     }
6184 26 Oct 12 nicklas 4259     else if (keyCode == 40) // down
6184 26 Oct 12 nicklas 4260     {
6184 26 Oct 12 nicklas 4261       if (!highlightedOption)
6184 26 Oct 12 nicklas 4262       {
6184 26 Oct 12 nicklas 4263         // highlight the first option
6822 01 Apr 15 nicklas 4264         select.highlightOption(event.target.multiSelect.options[0].div, true);
6184 26 Oct 12 nicklas 4265       }
6184 26 Oct 12 nicklas 4266       else if (highlightedOption.nextSibling)
6184 26 Oct 12 nicklas 4267       {
6184 26 Oct 12 nicklas 4268         // highlight the next option
6822 01 Apr 15 nicklas 4269         select.highlightOption(highlightedOption.nextSibling, true);
6184 26 Oct 12 nicklas 4270       }
6184 26 Oct 12 nicklas 4271     }
6184 26 Oct 12 nicklas 4272     else if (keyCode == 32) // space
6184 26 Oct 12 nicklas 4273     {
6184 26 Oct 12 nicklas 4274       // SPACE key should toggle the highlighted option
6184 26 Oct 12 nicklas 4275       if (highlightedOption) 
6184 26 Oct 12 nicklas 4276       {
6184 26 Oct 12 nicklas 4277         select.toggleOption(highlightedOption);
6184 26 Oct 12 nicklas 4278       }
6184 26 Oct 12 nicklas 4279     }
6184 26 Oct 12 nicklas 4280     else if (keyCode == 13) // enter
6184 26 Oct 12 nicklas 4281     {
6184 26 Oct 12 nicklas 4282       // ENTER key should toggle the highlighted option
6184 26 Oct 12 nicklas 4283       // and submit the form
6184 26 Oct 12 nicklas 4284       if (highlightedOption) 
6184 26 Oct 12 nicklas 4285       {
6184 26 Oct 12 nicklas 4286         select.toggleOption(highlightedOption);
6184 26 Oct 12 nicklas 4287       }
6184 26 Oct 12 nicklas 4288       select.submit();
6184 26 Oct 12 nicklas 4289     }
6184 26 Oct 12 nicklas 4290     else if (keyCode == 9) // tab
6184 26 Oct 12 nicklas 4291     {
6184 26 Oct 12 nicklas 4292       // Enable TAB key to move to next field
6184 26 Oct 12 nicklas 4293       disableHide = false;
6184 26 Oct 12 nicklas 4294       preventDefault = false;
6184 26 Oct 12 nicklas 4295     }
6184 26 Oct 12 nicklas 4296     if (preventDefault)
6184 26 Oct 12 nicklas 4297     {
6184 26 Oct 12 nicklas 4298       event.preventDefault();
6184 26 Oct 12 nicklas 4299     }
6184 26 Oct 12 nicklas 4300   }
6184 26 Oct 12 nicklas 4301   
6184 26 Oct 12 nicklas 4302   internal.notEqualOnClick = function(event)
6184 26 Oct 12 nicklas 4303   {
6184 26 Oct 12 nicklas 4304     if (!activeSelect) return;
6827 02 Apr 15 nicklas 4305     
6835 08 Apr 15 nicklas 4306     var imgRoot = App.getRoot()+'images/';
6827 02 Apr 15 nicklas 4307     var current = activeSelect.notEqual || false;
6827 02 Apr 15 nicklas 4308     activeSelect.notEqual = !current;
6835 08 Apr 15 nicklas 4309     event.currentTarget.src = imgRoot+(activeSelect.notEqual ? 'not-equal.png' : 'equal.png');
6184 26 Oct 12 nicklas 4310     select.updateFilter(activeSelect);
6184 26 Oct 12 nicklas 4311   }
6184 26 Oct 12 nicklas 4312   
6184 26 Oct 12 nicklas 4313   /**
6184 26 Oct 12 nicklas 4314     Event handler that react to click events on individual options
6184 26 Oct 12 nicklas 4315   */
6184 26 Oct 12 nicklas 4316   internal.optionOnClick = function(event)
6184 26 Oct 12 nicklas 4317   {
6754 20 Feb 15 nicklas 4318     var option = event.currentTarget;
6184 26 Oct 12 nicklas 4319     select.toggleOption(option);
6184 26 Oct 12 nicklas 4320   }
6184 26 Oct 12 nicklas 4321   
6184 26 Oct 12 nicklas 4322   /**
6184 26 Oct 12 nicklas 4323     Highlight the target option when the mouse is over the
6184 26 Oct 12 nicklas 4324     option and remove the highlight when the mouse is outside.
6184 26 Oct 12 nicklas 4325   */
6184 26 Oct 12 nicklas 4326   internal.optionHighlight = function(event)
6184 26 Oct 12 nicklas 4327   {
6754 20 Feb 15 nicklas 4328     var option = event.currentTarget;
6184 26 Oct 12 nicklas 4329     select.highlightOption(option);
6184 26 Oct 12 nicklas 4330   }
6184 26 Oct 12 nicklas 4331   
6184 26 Oct 12 nicklas 4332   /**
6184 26 Oct 12 nicklas 4333     Ensure that the active multi-select's display field has the focus.
6184 26 Oct 12 nicklas 4334   */
6184 26 Oct 12 nicklas 4335   internal.ensureFocused = function(event)
6184 26 Oct 12 nicklas 4336   {
6184 26 Oct 12 nicklas 4337     if (!activeSelect) return;
6184 26 Oct 12 nicklas 4338     activeSelect.displayField.focus();
6184 26 Oct 12 nicklas 4339   }
6184 26 Oct 12 nicklas 4340   
6184 26 Oct 12 nicklas 4341   /**
6184 26 Oct 12 nicklas 4342     Disable automatic hiding of the options list when the mouse
6184 26 Oct 12 nicklas 4343     is entering a multi-select control.
6184 26 Oct 12 nicklas 4344   */
6184 26 Oct 12 nicklas 4345   internal.setHideFlag = function(event)
6184 26 Oct 12 nicklas 4346   {
6184 26 Oct 12 nicklas 4347     disableHide = event.type == 'mouseenter' && (event.currentTarget == activeSelect || !activeSelect);
6184 26 Oct 12 nicklas 4348   }
6184 26 Oct 12 nicklas 4349
6831 07 Apr 15 nicklas 4350   internal.initMultiselect = function()
6831 07 Apr 15 nicklas 4351   {
6831 07 Apr 15 nicklas 4352     window.addEventListener('resize', internal.repositionActiveElement);
6831 07 Apr 15 nicklas 4353   }
6184 26 Oct 12 nicklas 4354   
6831 07 Apr 15 nicklas 4355   /**
6831 07 Apr 15 nicklas 4356     Re-position active element when the window has been resized.
6831 07 Apr 15 nicklas 4357   */
6831 07 Apr 15 nicklas 4358   internal.repositionActiveElement = function()
6831 07 Apr 15 nicklas 4359   {
6831 07 Apr 15 nicklas 4360     if (!activeSelect) return;
6831 07 Apr 15 nicklas 4361     var dropdownDiv = activeSelect.dropdownDiv;
6831 07 Apr 15 nicklas 4362     if (!dropdownDiv) return;
6831 07 Apr 15 nicklas 4363
6831 07 Apr 15 nicklas 4364     // Position the dropdown
6831 07 Apr 15 nicklas 4365     var pos = select.getFixedDropDownPosition(activeSelect);
7447 22 Feb 18 nicklas 4366     dropdownDiv.style.left = pos.left;
7447 22 Feb 18 nicklas 4367     dropdownDiv.style.right = pos.right;
7447 22 Feb 18 nicklas 4368     dropdownDiv.style.width = pos.width;
7447 22 Feb 18 nicklas 4369     dropdownDiv.style.minWidth = pos.minWidth;
7447 22 Feb 18 nicklas 4370     dropdownDiv.style.maxWidth = pos.maxWidth;
6831 07 Apr 15 nicklas 4371   }
6831 07 Apr 15 nicklas 4372   
6831 07 Apr 15 nicklas 4373   Doc.onLoad(internal.initMultiselect);
6831 07 Apr 15 nicklas 4374   
6184 26 Oct 12 nicklas 4375   return select;
6184 26 Oct 12 nicklas 4376 }();
6184 26 Oct 12 nicklas 4377
6190 31 Oct 12 nicklas 4378 var SmartEnum = function()
6190 31 Oct 12 nicklas 4379 {
6190 31 Oct 12 nicklas 4380   var smartenum = {};
6190 31 Oct 12 nicklas 4381   var internal = {};
6190 31 Oct 12 nicklas 4382
6190 31 Oct 12 nicklas 4383   // The currently active smart-enum element
6190 31 Oct 12 nicklas 4384   var activeEnum = null;
6190 31 Oct 12 nicklas 4385   // Flag to indicate that automatic hiding of the options list 
6190 31 Oct 12 nicklas 4386   // should be disabled due to the user is interacting with the list
6190 31 Oct 12 nicklas 4387   var disableHide = false;
6190 31 Oct 12 nicklas 4388   // The option that is currently higlighted
6190 31 Oct 12 nicklas 4389   var highlightedOption = null;
6190 31 Oct 12 nicklas 4390
6190 31 Oct 12 nicklas 4391   
6190 31 Oct 12 nicklas 4392   /**
6190 31 Oct 12 nicklas 4393     Set the given smart-enum element as the active element.
6190 31 Oct 12 nicklas 4394     If the currently active element is a different element, it
6190 31 Oct 12 nicklas 4395     is automatically hidden.
6190 31 Oct 12 nicklas 4396   */
6190 31 Oct 12 nicklas 4397   smartenum.setActive = function(element)
6190 31 Oct 12 nicklas 4398   {
6190 31 Oct 12 nicklas 4399     element = Doc.element(element);
6190 31 Oct 12 nicklas 4400     if (element == activeEnum) return;
6190 31 Oct 12 nicklas 4401     
6190 31 Oct 12 nicklas 4402     // Get the dropdown div element
6190 31 Oct 12 nicklas 4403     var dropdownDiv = element.dropdownDiv;
6190 31 Oct 12 nicklas 4404     if (!dropdownDiv)
6190 31 Oct 12 nicklas 4405     {
6190 31 Oct 12 nicklas 4406       dropdownDiv = internal.createDropdownDiv(element);
6190 31 Oct 12 nicklas 4407       // Position the dropdown div relative the  main element
6400 27 Jan 14 nicklas 4408       var pos = Doc.getElementPosition(element);
6608 20 Nov 14 nicklas 4409       dropdownDiv.style.width = ((pos.width < 150 ? 150 : pos.width))+'px';
6190 31 Oct 12 nicklas 4410     }
6190 31 Oct 12 nicklas 4411   
6823 01 Apr 15 nicklas 4412     // Position the dropdown
6823 01 Apr 15 nicklas 4413     var pos = MultiSelect.getFixedDropDownPosition(element);
7447 22 Feb 18 nicklas 4414     dropdownDiv.style.left = pos.left;
7447 22 Feb 18 nicklas 4415     dropdownDiv.style.right = pos.right;
7447 22 Feb 18 nicklas 4416     dropdownDiv.style.width = pos.width;
7447 22 Feb 18 nicklas 4417     dropdownDiv.style.minWidth = pos.minWidth;
7447 22 Feb 18 nicklas 4418     dropdownDiv.style.maxWidth = pos.maxWidth;
6823 01 Apr 15 nicklas 4419     
6190 31 Oct 12 nicklas 4420     // Show it
6608 20 Nov 14 nicklas 4421     Doc.addClass(element, 'active');
6190 31 Oct 12 nicklas 4422     dropdownDiv.style.display = 'block';
6190 31 Oct 12 nicklas 4423     activeEnum = element;
6190 31 Oct 12 nicklas 4424     element.displayField.focus();
6190 31 Oct 12 nicklas 4425   }
6190 31 Oct 12 nicklas 4426
6190 31 Oct 12 nicklas 4427   
6190 31 Oct 12 nicklas 4428   /**
6190 31 Oct 12 nicklas 4429     Highlight the given option as if the mouse is over it
6190 31 Oct 12 nicklas 4430     or if the keyboard arrows have been used.
6190 31 Oct 12 nicklas 4431   */
6190 31 Oct 12 nicklas 4432   smartenum.highlightOption = function(option)
6190 31 Oct 12 nicklas 4433   {
6190 31 Oct 12 nicklas 4434     if (!activeEnum) return;
6190 31 Oct 12 nicklas 4435     if (highlightedOption)
6190 31 Oct 12 nicklas 4436     {
6608 20 Nov 14 nicklas 4437       Doc.removeClass(highlightedOption, 'active');
6190 31 Oct 12 nicklas 4438     }
6190 31 Oct 12 nicklas 4439     highlightedOption = option;
6190 31 Oct 12 nicklas 4440     if (highlightedOption)
6190 31 Oct 12 nicklas 4441     {
6608 20 Nov 14 nicklas 4442       Doc.addClass(highlightedOption, 'active');
6190 31 Oct 12 nicklas 4443       // Scroll the highlighted option into view
6190 31 Oct 12 nicklas 4444       var parent = option.parentNode;
6190 31 Oct 12 nicklas 4445       var parentTop = parent.scrollTop;
6190 31 Oct 12 nicklas 4446       var optionTop = option.offsetTop;
6190 31 Oct 12 nicklas 4447       var optionBottom = optionTop + option.offsetHeight;
6190 31 Oct 12 nicklas 4448       var parentBottom = parentTop + parent.offsetHeight;
6190 31 Oct 12 nicklas 4449       if (optionTop < parentTop)
6190 31 Oct 12 nicklas 4450       {
6190 31 Oct 12 nicklas 4451         parent.scrollTop = optionTop;
6190 31 Oct 12 nicklas 4452       }
6190 31 Oct 12 nicklas 4453       else if (optionBottom > parentBottom)
6190 31 Oct 12 nicklas 4454       {
6190 31 Oct 12 nicklas 4455         parent.scrollTop = optionBottom - parent.offsetHeight + 2;
6190 31 Oct 12 nicklas 4456       }
6190 31 Oct 12 nicklas 4457     }
6190 31 Oct 12 nicklas 4458   }
6190 31 Oct 12 nicklas 4459
6190 31 Oct 12 nicklas 4460   /**
6190 31 Oct 12 nicklas 4461     Higlight the option that is the next visible one below the given
6190 31 Oct 12 nicklas 4462     option. If there is no such option, the currently highlighted option
6190 31 Oct 12 nicklas 4463     is kept.
6190 31 Oct 12 nicklas 4464   */
6190 31 Oct 12 nicklas 4465   smartenum.highlightNext = function(option)
6190 31 Oct 12 nicklas 4466   {
6190 31 Oct 12 nicklas 4467     if (!activeEnum) return;
6190 31 Oct 12 nicklas 4468     option = option != null ? option.nextSibling : activeEnum.dropdownDiv.firstChild;
6190 31 Oct 12 nicklas 4469     while (option && option.style.display == 'none')
6190 31 Oct 12 nicklas 4470     {
6190 31 Oct 12 nicklas 4471       option = option.nextSibling;
6190 31 Oct 12 nicklas 4472     }
6190 31 Oct 12 nicklas 4473     if (option) smartenum.highlightOption(option);
6190 31 Oct 12 nicklas 4474   }
6190 31 Oct 12 nicklas 4475   
6190 31 Oct 12 nicklas 4476   /**
6190 31 Oct 12 nicklas 4477     Higlight the option that is the previous visible one above the given
6190 31 Oct 12 nicklas 4478     option. If there is no such option, the currently highlighted option
6190 31 Oct 12 nicklas 4479     is kept.
6190 31 Oct 12 nicklas 4480   */
6190 31 Oct 12 nicklas 4481   smartenum.highlightPrevious = function(option)
6190 31 Oct 12 nicklas 4482   {
6190 31 Oct 12 nicklas 4483     if (!activeEnum) return;
6190 31 Oct 12 nicklas 4484     option = option != null ? option.previousSibling : activeEnum.dropdownDiv.lastChild;
6190 31 Oct 12 nicklas 4485     while (option && option.style.display == 'none')
6190 31 Oct 12 nicklas 4486     {
6190 31 Oct 12 nicklas 4487       option = option.previousSibling;
6190 31 Oct 12 nicklas 4488     }
6190 31 Oct 12 nicklas 4489     if (option) smartenum.highlightOption(option);
6190 31 Oct 12 nicklas 4490   }
6190 31 Oct 12 nicklas 4491
6190 31 Oct 12 nicklas 4492   /**
6190 31 Oct 12 nicklas 4493     Filter the options in the drop-down list depending on the
6190 31 Oct 12 nicklas 4494     text entered in the display field. Operator prefixes (eg. =, >, ...)
6190 31 Oct 12 nicklas 4495     are ignored.
6190 31 Oct 12 nicklas 4496   */
6190 31 Oct 12 nicklas 4497   smartenum.filterOptions = function()
6190 31 Oct 12 nicklas 4498   {
6190 31 Oct 12 nicklas 4499     if (!activeEnum) return;
6190 31 Oct 12 nicklas 4500     if (activeEnum.displayField.value == activeEnum.displayField.lastValue) 
6190 31 Oct 12 nicklas 4501     {
6190 31 Oct 12 nicklas 4502       // If the value has not change, there is no need to re-filter the options
6190 31 Oct 12 nicklas 4503       return;
6190 31 Oct 12 nicklas 4504     }
6190 31 Oct 12 nicklas 4505     activeEnum.displayField.lastValue = activeEnum.displayField.value;
6190 31 Oct 12 nicklas 4506     // Strip away operator prefix and split at '%'
6190 31 Oct 12 nicklas 4507     var text = activeEnum.displayField.value.replace(/^[=!<>]*/, '').split('%');
6190 31 Oct 12 nicklas 4508     for (var i = 0; i < activeEnum.dropdownDiv.childNodes.length; i++)
6190 31 Oct 12 nicklas 4509     {
6190 31 Oct 12 nicklas 4510       var opt = activeEnum.dropdownDiv.childNodes[i];
6190 31 Oct 12 nicklas 4511       var optText = opt.innerHTML;
6190 31 Oct 12 nicklas 4512       var startIndex = 0;
6190 31 Oct 12 nicklas 4513       var match = true;
6190 31 Oct 12 nicklas 4514       // Check that each text part is matching...
6190 31 Oct 12 nicklas 4515       for (var j = 0; j < text.length && match; j++)
6190 31 Oct 12 nicklas 4516       {
6190 31 Oct 12 nicklas 4517         // ... and each piece must match AFTER the other match
6190 31 Oct 12 nicklas 4518         var index = optText.indexOf(text[j], startIndex);
6190 31 Oct 12 nicklas 4519         if (index == -1) match = false;
6190 31 Oct 12 nicklas 4520         startIndex = index+text[j].length+1;
6190 31 Oct 12 nicklas 4521       }
6190 31 Oct 12 nicklas 4522       opt.style.display = match ? 'block' : 'none';
6190 31 Oct 12 nicklas 4523     }
6190 31 Oct 12 nicklas 4524     
6190 31 Oct 12 nicklas 4525     if (highlightedOption != null && highlightedOption.style.display == 'none')
6190 31 Oct 12 nicklas 4526     {
6190 31 Oct 12 nicklas 4527       smartenum.highlightOption(null);
6190 31 Oct 12 nicklas 4528     }
6190 31 Oct 12 nicklas 4529   }
6190 31 Oct 12 nicklas 4530   
6190 31 Oct 12 nicklas 4531   /**
6190 31 Oct 12 nicklas 4532     Hide the currently displayed dropdown (if any)
6190 31 Oct 12 nicklas 4533   */
6190 31 Oct 12 nicklas 4534   smartenum.hideDropdown = function()
6190 31 Oct 12 nicklas 4535   {
6190 31 Oct 12 nicklas 4536     if (!activeEnum) return;
6608 20 Nov 14 nicklas 4537     Doc.removeClass(activeEnum, 'active');
6190 31 Oct 12 nicklas 4538     smartenum.highlightOption(null);
6190 31 Oct 12 nicklas 4539     activeEnum.dropdownDiv.style.display = 'none';
6190 31 Oct 12 nicklas 4540     activeEnum = null;
6190 31 Oct 12 nicklas 4541   }
6190 31 Oct 12 nicklas 4542
6190 31 Oct 12 nicklas 4543   /**
6190 31 Oct 12 nicklas 4544     Set the given option as the filter text and then submit the form.
6190 31 Oct 12 nicklas 4545   */
6190 31 Oct 12 nicklas 4546   smartenum.submitOption = function(option)
6190 31 Oct 12 nicklas 4547   {
6190 31 Oct 12 nicklas 4548     if (!activeEnum) return;
6190 31 Oct 12 nicklas 4549     var field = activeEnum.displayField;
6190 31 Oct 12 nicklas 4550     var text = field.value;
6190 31 Oct 12 nicklas 4551     if (option)
6190 31 Oct 12 nicklas 4552     {
6190 31 Oct 12 nicklas 4553       field.value = text.replace(/^([=!<>]*).*/, '$1' + option.innerHTML);
6190 31 Oct 12 nicklas 4554     }
7894 08 Dec 20 nicklas 4555     Forms.submit(field.form);
7894 08 Dec 20 nicklas 4556     smartenum.hideDropdown();
6190 31 Oct 12 nicklas 4557   }
6190 31 Oct 12 nicklas 4558   
6190 31 Oct 12 nicklas 4559   /**
6190 31 Oct 12 nicklas 4560     Initialize a smart-enum enabled field.
6190 31 Oct 12 nicklas 4561    */
6190 31 Oct 12 nicklas 4562   internal.initializeSmartEnum = function(element, autoInit)
6190 31 Oct 12 nicklas 4563   {
6190 31 Oct 12 nicklas 4564     if (autoInit != 'smartenum') return;
6190 31 Oct 12 nicklas 4565
6190 31 Oct 12 nicklas 4566     // Get the child elements that are part of the multi-select control
6190 31 Oct 12 nicklas 4567     var display = element.getElementsByClassName("smartenum-display")[0];
6190 31 Oct 12 nicklas 4568     var dropdown = element.getElementsByClassName("dropdown")[0];
6190 31 Oct 12 nicklas 4569   
6608 20 Nov 14 nicklas 4570     Doc.addClass(element, 'interactable');
6608 20 Nov 14 nicklas 4571     
6190 31 Oct 12 nicklas 4572     // Store cross-references between the main and child elements
6190 31 Oct 12 nicklas 4573     element.displayField = display;
6190 31 Oct 12 nicklas 4574     element.dropdown = dropdown;
6190 31 Oct 12 nicklas 4575     display.smartEnum = element;
6190 31 Oct 12 nicklas 4576     dropdown.smartEnum = element;
6190 31 Oct 12 nicklas 4577     // Disable auto-complete on the input field
6190 31 Oct 12 nicklas 4578     display.setAttribute('autocomplete', 'off');
6190 31 Oct 12 nicklas 4579     
6190 31 Oct 12 nicklas 4580     // Load the options array from the data-options attribute
6190 31 Oct 12 nicklas 4581     element.options = Data.json(element, 'options').sort(Strings.compareIgnoreCase);
6190 31 Oct 12 nicklas 4582     
6190 31 Oct 12 nicklas 4583     // Add event handlers to the 'display' input field
6190 31 Oct 12 nicklas 4584     Events.addEventHandler(display, 'focus', internal.onFocus);
6190 31 Oct 12 nicklas 4585     Events.addEventHandler(display, 'blur', internal.onBlur);
6190 31 Oct 12 nicklas 4586     Events.addEventHandler(display, 'keydown', internal.onKeyDown);
6190 31 Oct 12 nicklas 4587     Events.addEventHandler(display, 'keyup', internal.onKeyUp);
6190 31 Oct 12 nicklas 4588     
6190 31 Oct 12 nicklas 4589     // Add event handler to the 'dropdown' icon
6190 31 Oct 12 nicklas 4590     Events.addEventHandler(dropdown, 'click', internal.dropdownOnClick);
6190 31 Oct 12 nicklas 4591     
6190 31 Oct 12 nicklas 4592     // Ensure that the options list stay visible when the 
6190 31 Oct 12 nicklas 4593     // mouse is inside the active smart-enum control
6190 31 Oct 12 nicklas 4594     Events.addEventHandler(element, 'mouseenter', internal.setHideFlag);
6190 31 Oct 12 nicklas 4595     Events.addEventHandler(element, 'mouseleave', internal.setHideFlag);
6190 31 Oct 12 nicklas 4596   }
6190 31 Oct 12 nicklas 4597   Doc.addElementInitializer(internal.initializeSmartEnum);
6190 31 Oct 12 nicklas 4598
6190 31 Oct 12 nicklas 4599   
6190 31 Oct 12 nicklas 4600   /**
6190 31 Oct 12 nicklas 4601     Event handler for the 'dropdown' icon. This should toggle the options
6190 31 Oct 12 nicklas 4602     list.
6190 31 Oct 12 nicklas 4603   */
6190 31 Oct 12 nicklas 4604   internal.dropdownOnClick = function(event)
6190 31 Oct 12 nicklas 4605   {
6190 31 Oct 12 nicklas 4606     var smartEnum = event.currentTarget.smartEnum;
6190 31 Oct 12 nicklas 4607     if (smartEnum == activeEnum)
6190 31 Oct 12 nicklas 4608     {
6190 31 Oct 12 nicklas 4609       // Hide the options
6190 31 Oct 12 nicklas 4610       smartenum.hideDropdown();
6190 31 Oct 12 nicklas 4611     }
6190 31 Oct 12 nicklas 4612     else
6190 31 Oct 12 nicklas 4613     {
6190 31 Oct 12 nicklas 4614       // Show the new options
6190 31 Oct 12 nicklas 4615       smartenum.setActive(smartEnum);
6190 31 Oct 12 nicklas 4616       disableHide = true;
6190 31 Oct 12 nicklas 4617     }
6190 31 Oct 12 nicklas 4618   }
6190 31 Oct 12 nicklas 4619
6190 31 Oct 12 nicklas 4620   /**
6190 31 Oct 12 nicklas 4621     Create the dropdown div with all options for the given
6190 31 Oct 12 nicklas 4622     smart-enum element.
6190 31 Oct 12 nicklas 4623   */
6190 31 Oct 12 nicklas 4624   internal.createDropdownDiv = function(element)
6190 31 Oct 12 nicklas 4625   {
6190 31 Oct 12 nicklas 4626     // Create the list with options
6190 31 Oct 12 nicklas 4627     var options = element.options;
6190 31 Oct 12 nicklas 4628
6190 31 Oct 12 nicklas 4629     // Main div
6190 31 Oct 12 nicklas 4630     var mainDiv = document.createElement('div');
6190 31 Oct 12 nicklas 4631     mainDiv.className = 'smartenum';
6190 31 Oct 12 nicklas 4632
6190 31 Oct 12 nicklas 4633     for (var i = 0; i < options.length; i++)
6190 31 Oct 12 nicklas 4634     {
6190 31 Oct 12 nicklas 4635       var optionDiv = document.createElement('div');
6608 20 Nov 14 nicklas 4636       optionDiv.className = 'smartoption interactable';
6190 31 Oct 12 nicklas 4637       optionDiv.optionIndex = i;
6190 31 Oct 12 nicklas 4638       optionDiv.innerHTML = options[i];
6190 31 Oct 12 nicklas 4639       mainDiv.appendChild(optionDiv);
6190 31 Oct 12 nicklas 4640     }
6190 31 Oct 12 nicklas 4641     
6190 31 Oct 12 nicklas 4642     Events.addEventHandler(mainDiv, 'click', internal.ensureFocused);
6190 31 Oct 12 nicklas 4643     Events.addEventHandler(mainDiv, 'click', internal.optionOnClick);
6190 31 Oct 12 nicklas 4644     Events.addEventHandler(mainDiv, 'mouseover', internal.optionHighlight);
6190 31 Oct 12 nicklas 4645     
6190 31 Oct 12 nicklas 4646     element.dropdownDiv = mainDiv;
6190 31 Oct 12 nicklas 4647     element.appendChild(mainDiv);
6190 31 Oct 12 nicklas 4648     
6190 31 Oct 12 nicklas 4649     return mainDiv;
6190 31 Oct 12 nicklas 4650   }
6190 31 Oct 12 nicklas 4651
6190 31 Oct 12 nicklas 4652   /**
6190 31 Oct 12 nicklas 4653     Event handler used when a smart-enum enabled field receives
6190 31 Oct 12 nicklas 4654     the focus. This should set the current field to the active
6190 31 Oct 12 nicklas 4655     field.
6190 31 Oct 12 nicklas 4656   */
6190 31 Oct 12 nicklas 4657   internal.onFocus = function(event)
6190 31 Oct 12 nicklas 4658   {
6190 31 Oct 12 nicklas 4659     smartenum.setActive(event.target.smartEnum);
6828 02 Apr 15 nicklas 4660     disableHide = true;
6190 31 Oct 12 nicklas 4661   }
6190 31 Oct 12 nicklas 4662   
6190 31 Oct 12 nicklas 4663   /**
6190 31 Oct 12 nicklas 4664     Event handler used when a multi-select enabled field
6190 31 Oct 12 nicklas 4665     loses the focus. This should hide the options and set
6190 31 Oct 12 nicklas 4666     the current field to null.
6190 31 Oct 12 nicklas 4667   */
6190 31 Oct 12 nicklas 4668   internal.onBlur = function(event)
6190 31 Oct 12 nicklas 4669   {
6190 31 Oct 12 nicklas 4670     if (disableHide) return;
6190 31 Oct 12 nicklas 4671     smartenum.hideDropdown();
6190 31 Oct 12 nicklas 4672   }
6190 31 Oct 12 nicklas 4673
6190 31 Oct 12 nicklas 4674   /**
6190 31 Oct 12 nicklas 4675     Called when a key is pressed in a smart-enum enabled field.
6190 31 Oct 12 nicklas 4676     This function detects:
6190 31 Oct 12 nicklas 4677     * up/down arrow: moves the highlighted option
6190 31 Oct 12 nicklas 4678     * enter: submits the form
6190 31 Oct 12 nicklas 4679   */
6190 31 Oct 12 nicklas 4680   internal.onKeyDown = function(event)
6190 31 Oct 12 nicklas 4681   {
6190 31 Oct 12 nicklas 4682     if (!activeEnum) smartenum.setActive(event.target.smartEnum);
6190 31 Oct 12 nicklas 4683     var keyCode = event.keyCode  
6190 31 Oct 12 nicklas 4684     if (keyCode == 38) // up
6190 31 Oct 12 nicklas 4685     {
6190 31 Oct 12 nicklas 4686       smartenum.highlightPrevious(highlightedOption);
6190 31 Oct 12 nicklas 4687       event.preventDefault();
6190 31 Oct 12 nicklas 4688     }
6190 31 Oct 12 nicklas 4689     else if (keyCode == 40) // down
6190 31 Oct 12 nicklas 4690     {
6190 31 Oct 12 nicklas 4691       smartenum.highlightNext(highlightedOption);
6190 31 Oct 12 nicklas 4692       event.preventDefault();
6190 31 Oct 12 nicklas 4693     }
6190 31 Oct 12 nicklas 4694     else if (keyCode == 13) // enter
6190 31 Oct 12 nicklas 4695     {
6190 31 Oct 12 nicklas 4696       // ENTER key should select the highlighted option and submit the form
6190 31 Oct 12 nicklas 4697       smartenum.submitOption(highlightedOption);
6190 31 Oct 12 nicklas 4698     }
6190 31 Oct 12 nicklas 4699     else if (keyCode == 27) // escape
6190 31 Oct 12 nicklas 4700     {
6190 31 Oct 12 nicklas 4701       smartenum.hideDropdown();
6190 31 Oct 12 nicklas 4702     }
6190 31 Oct 12 nicklas 4703
6190 31 Oct 12 nicklas 4704   }
6190 31 Oct 12 nicklas 4705
6190 31 Oct 12 nicklas 4706   
6190 31 Oct 12 nicklas 4707   /**
6190 31 Oct 12 nicklas 4708     Called when a key is released in a smart-enum enabled field. 
6190 31 Oct 12 nicklas 4709     This function detects changes to the text and displays matching
6190 31 Oct 12 nicklas 4710     options.
6190 31 Oct 12 nicklas 4711   */
6190 31 Oct 12 nicklas 4712   internal.onKeyUp = function(event)
6190 31 Oct 12 nicklas 4713   {
6190 31 Oct 12 nicklas 4714     smartenum.filterOptions();
6190 31 Oct 12 nicklas 4715   }
6190 31 Oct 12 nicklas 4716
6190 31 Oct 12 nicklas 4717   
6190 31 Oct 12 nicklas 4718   /**
6190 31 Oct 12 nicklas 4719     Highlight the target option when the mouse is over the
6190 31 Oct 12 nicklas 4720     option and remove the highlight when the mouse is outside.
6190 31 Oct 12 nicklas 4721   */
6190 31 Oct 12 nicklas 4722   internal.optionHighlight = function(event)
6190 31 Oct 12 nicklas 4723   {
6190 31 Oct 12 nicklas 4724     var option = event.target;
6190 31 Oct 12 nicklas 4725     if (option.optionIndex >= 0)
6190 31 Oct 12 nicklas 4726     {
6190 31 Oct 12 nicklas 4727       smartenum.highlightOption(option);
6190 31 Oct 12 nicklas 4728     }
6190 31 Oct 12 nicklas 4729   }
6190 31 Oct 12 nicklas 4730
6190 31 Oct 12 nicklas 4731   /**
6190 31 Oct 12 nicklas 4732     Event handler that react to click events on individual options
6190 31 Oct 12 nicklas 4733   */
6190 31 Oct 12 nicklas 4734   internal.optionOnClick = function(event)
6190 31 Oct 12 nicklas 4735   {
6190 31 Oct 12 nicklas 4736     var option = event.target;
6190 31 Oct 12 nicklas 4737     if (option.optionIndex >= 0)
6190 31 Oct 12 nicklas 4738     {
6190 31 Oct 12 nicklas 4739       smartenum.submitOption(option);
6190 31 Oct 12 nicklas 4740     }
6190 31 Oct 12 nicklas 4741   }
6190 31 Oct 12 nicklas 4742   
6190 31 Oct 12 nicklas 4743   /**
6190 31 Oct 12 nicklas 4744     Disable automatic hiding of the options list when the mouse
6190 31 Oct 12 nicklas 4745     is entering a smart-enum control.
6190 31 Oct 12 nicklas 4746   */
6190 31 Oct 12 nicklas 4747   internal.setHideFlag = function(event)
6190 31 Oct 12 nicklas 4748   {
6190 31 Oct 12 nicklas 4749     disableHide = event.type == 'mouseenter' && (event.currentTarget == activeEnum || !activeEnum);
6190 31 Oct 12 nicklas 4750   }
6190 31 Oct 12 nicklas 4751
6831 07 Apr 15 nicklas 4752   internal.initEnum = function()
6831 07 Apr 15 nicklas 4753   {
6831 07 Apr 15 nicklas 4754     window.addEventListener('resize', internal.repositionActiveElement);
6831 07 Apr 15 nicklas 4755   }
6831 07 Apr 15 nicklas 4756   
6831 07 Apr 15 nicklas 4757   /**
6831 07 Apr 15 nicklas 4758     Re-position active element when the window has been resized.
6831 07 Apr 15 nicklas 4759   */
6831 07 Apr 15 nicklas 4760   internal.repositionActiveElement = function()
6831 07 Apr 15 nicklas 4761   {
6831 07 Apr 15 nicklas 4762     if (!activeEnum) return;
6831 07 Apr 15 nicklas 4763     var dropdownDiv = activeEnum.dropdownDiv;
6831 07 Apr 15 nicklas 4764     if (!dropdownDiv) return;
6831 07 Apr 15 nicklas 4765
6831 07 Apr 15 nicklas 4766     // Position the dropdown
6831 07 Apr 15 nicklas 4767     var pos = MultiSelect.getFixedDropDownPosition(activeEnum);
7447 22 Feb 18 nicklas 4768     dropdownDiv.style.left = pos.left;
7447 22 Feb 18 nicklas 4769     dropdownDiv.style.right = pos.right;
7447 22 Feb 18 nicklas 4770     dropdownDiv.style.width = pos.width;
7447 22 Feb 18 nicklas 4771     dropdownDiv.style.minWidth = pos.minWidth;
7447 22 Feb 18 nicklas 4772     dropdownDiv.style.maxWidth = pos.maxWidth;
6831 07 Apr 15 nicklas 4773   }
6831 07 Apr 15 nicklas 4774   
6831 07 Apr 15 nicklas 4775   Doc.onLoad(internal.initEnum);
6831 07 Apr 15 nicklas 4776
6831 07 Apr 15 nicklas 4777   
6190 31 Oct 12 nicklas 4778   return smartenum;
6190 31 Oct 12 nicklas 4779 }();
6190 31 Oct 12 nicklas 4780
6191 31 Oct 12 nicklas 4781 /**
6191 31 Oct 12 nicklas 4782   Handles div elements with longs texts that the user may want to 
6191 31 Oct 12 nicklas 4783   only display a part of until clicking with the mouse to reveal the
6191 31 Oct 12 nicklas 4784   complete contents. To begin with the div elements are marked with
6191 31 Oct 12 nicklas 4785   the class name 'constrained autoshow'. By itself, this trigger automatic
6191 31 Oct 12 nicklas 4786   showing/hiding on mouse hover. A scan and check is required to see if
6191 31 Oct 12 nicklas 4787   there is an actual text overflow and instead attach showing/hiding
6191 31 Oct 12 nicklas 4788   to mouse click events. 
6191 31 Oct 12 nicklas 4789 */
6191 31 Oct 12 nicklas 4790 var TextOverflow = function()
6191 31 Oct 12 nicklas 4791 {
6191 31 Oct 12 nicklas 4792   var overflow = {};
6191 31 Oct 12 nicklas 4793   var internal = {};
6191 31 Oct 12 nicklas 4794   
6191 31 Oct 12 nicklas 4795   /**
6191 31 Oct 12 nicklas 4796     Get the text overflow method currently in use. There are
6191 31 Oct 12 nicklas 4797     three possible values:
6191 31 Oct 12 nicklas 4798     * display (the default): overflowed text is displayed
6191 31 Oct 12 nicklas 4799     * hover: overflowed text is displayed on mouse hover
6191 31 Oct 12 nicklas 4800     * click: overflowed text is displayed when the mouse is clicked
6191 31 Oct 12 nicklas 4801   */
6191 31 Oct 12 nicklas 4802   overflow.getOverflowMethod = function()
6191 31 Oct 12 nicklas 4803   {
6191 31 Oct 12 nicklas 4804     return Data.get(document.body, 'text-overflow', 'display');
6191 31 Oct 12 nicklas 4805   }
6191 31 Oct 12 nicklas 4806   
6191 31 Oct 12 nicklas 4807   /**
6191 31 Oct 12 nicklas 4808     Check all tags with class name 'constrained' if they 
6191 31 Oct 12 nicklas 4809     have overflowed text. If so, attach a mouse click
6191 31 Oct 12 nicklas 4810     handler to the element that show/hide the overflow text.
6191 31 Oct 12 nicklas 4811   */
6191 31 Oct 12 nicklas 4812   overflow.checkAndFixAll = function()
6191 31 Oct 12 nicklas 4813   {
6191 31 Oct 12 nicklas 4814     var tags = document.getElementsByClassName('constrained');
6191 31 Oct 12 nicklas 4815     for (var i = 0; i < tags.length; i++)
6191 31 Oct 12 nicklas 4816     {
6191 31 Oct 12 nicklas 4817       overflow.checkTag(tags[i]);
6191 31 Oct 12 nicklas 4818     }
6191 31 Oct 12 nicklas 4819     for (var i = 0; i < tags.length; i++)
6191 31 Oct 12 nicklas 4820     {
6191 31 Oct 12 nicklas 4821       overflow.fixTag(tags[i]);
6191 31 Oct 12 nicklas 4822     }
6191 31 Oct 12 nicklas 4823   }
6190 31 Oct 12 nicklas 4824
6191 31 Oct 12 nicklas 4825   /**
6191 31 Oct 12 nicklas 4826     Check the given tag for overflowed text and attach a
6191 31 Oct 12 nicklas 4827     mouse click handler if needed.
6191 31 Oct 12 nicklas 4828   */
6191 31 Oct 12 nicklas 4829   overflow.checkAndFixTag = function(tag)
6191 31 Oct 12 nicklas 4830   {
6191 31 Oct 12 nicklas 4831     overflow.checkTag(tag);
6191 31 Oct 12 nicklas 4832     overflow.fixTag(tag);
6191 31 Oct 12 nicklas 4833   }
6191 31 Oct 12 nicklas 4834   
6191 31 Oct 12 nicklas 4835   /**
6191 31 Oct 12 nicklas 4836     Check if the given tag has overflowed text of not.
6191 31 Oct 12 nicklas 4837     The result is stored as 3 properties on the tag element
6191 31 Oct 12 nicklas 4838     * tag.isWidthOverflowed
6191 31 Oct 12 nicklas 4839     * tag.isHeightOverflowed
6191 31 Oct 12 nicklas 4840     * tag.isOverflowed
6191 31 Oct 12 nicklas 4841   */
6191 31 Oct 12 nicklas 4842   overflow.checkTag = function(tag)
6191 31 Oct 12 nicklas 4843   {
6191 31 Oct 12 nicklas 4844     var isWidthOverflowed = tag.clientWidth < tag.scrollWidth;
6191 31 Oct 12 nicklas 4845     var isHeightOverflowed = tag.clientHeight + 5 < tag.scrollHeight;
6191 31 Oct 12 nicklas 4846     tag.isWidthOverflowed = isWidthOverflowed;
6191 31 Oct 12 nicklas 4847     tag.isHeightOverflowed = isHeightOverflowed;
6191 31 Oct 12 nicklas 4848     tag.isOverflowed = isWidthOverflowed || isHeightOverflowed;
6191 31 Oct 12 nicklas 4849   }
6191 31 Oct 12 nicklas 4850   
6191 31 Oct 12 nicklas 4851   /**
6191 31 Oct 12 nicklas 4852     Fix the (possible) overflowed tag and attach a mouse click
6191 31 Oct 12 nicklas 4853     handler to it if needed.
6191 31 Oct 12 nicklas 4854   */
6191 31 Oct 12 nicklas 4855   overflow.fixTag = function(tag)
6191 31 Oct 12 nicklas 4856   {
6191 31 Oct 12 nicklas 4857     var className = 'constrained';
6191 31 Oct 12 nicklas 4858     if (tag.isOverflowed)
6191 31 Oct 12 nicklas 4859     {
6191 31 Oct 12 nicklas 4860       className += ' overflowed manualshow';
6191 31 Oct 12 nicklas 4861       Events.addEventHandler(tag, 'click', overflow.overflowedOnClick);
6191 31 Oct 12 nicklas 4862       tag.title = 'Click to show/hide the complete text!';
6191 31 Oct 12 nicklas 4863     }
6191 31 Oct 12 nicklas 4864     tag.className = className;
6191 31 Oct 12 nicklas 4865   }
6191 31 Oct 12 nicklas 4866
6191 31 Oct 12 nicklas 4867   /**
6191 31 Oct 12 nicklas 4868     Event handler that toggle the visiblity status of the overflowed
6191 31 Oct 12 nicklas 4869     text.
6191 31 Oct 12 nicklas 4870   */
6191 31 Oct 12 nicklas 4871   overflow.overflowedOnClick = function(event)
6191 31 Oct 12 nicklas 4872   {
6191 31 Oct 12 nicklas 4873     var tag = event.currentTarget;
6191 31 Oct 12 nicklas 4874     if (tag.isWidthOverflowed)
6191 31 Oct 12 nicklas 4875     {
6342 01 Nov 13 nicklas 4876       Doc.addOrRemoveClass(tag, 'constrained');
6191 31 Oct 12 nicklas 4877     }
6191 31 Oct 12 nicklas 4878     else
6191 31 Oct 12 nicklas 4879     {
6342 01 Nov 13 nicklas 4880       Doc.addOrRemoveClass(tag, 'shown');
6191 31 Oct 12 nicklas 4881     }
6191 31 Oct 12 nicklas 4882   }
6191 31 Oct 12 nicklas 4883
6191 31 Oct 12 nicklas 4884   internal.initCheck = function()
6191 31 Oct 12 nicklas 4885   {
6191 31 Oct 12 nicklas 4886     if (overflow.getOverflowMethod() == 'click')
6191 31 Oct 12 nicklas 4887     {
6610 20 Nov 14 nicklas 4888       setTimeout(overflow.checkAndFixAll, 200);
6191 31 Oct 12 nicklas 4889     }
6191 31 Oct 12 nicklas 4890   };
6191 31 Oct 12 nicklas 4891   Doc.addFinalizer(internal.initCheck);
6191 31 Oct 12 nicklas 4892   
6191 31 Oct 12 nicklas 4893   return overflow;
6191 31 Oct 12 nicklas 4894 }();
6191 31 Oct 12 nicklas 4895
6196 02 Nov 12 nicklas 4896 var HideableSection = function()
6196 02 Nov 12 nicklas 4897 {
6196 02 Nov 12 nicklas 4898   var section = {};
6196 02 Nov 12 nicklas 4899   var internal = {};
6196 02 Nov 12 nicklas 4900
6196 02 Nov 12 nicklas 4901   // Show/hide a section 
6196 02 Nov 12 nicklas 4902   section.toggle = function(sectionDiv)
6196 02 Nov 12 nicklas 4903   {
6196 02 Nov 12 nicklas 4904     sectionDiv = Doc.element(sectionDiv);
6196 02 Nov 12 nicklas 4905     var icon = Doc.element(sectionDiv.id+'.icon');
6196 02 Nov 12 nicklas 4906     var content = Doc.element(sectionDiv.id+'.content');
6196 02 Nov 12 nicklas 4907     var showClass = Data.get(sectionDiv, 'showclass');
6196 02 Nov 12 nicklas 4908     var hideClass = Data.get(sectionDiv, 'hideclass');
6196 02 Nov 12 nicklas 4909     
6196 02 Nov 12 nicklas 4910     var visible = content.style.display != 'none';
6196 02 Nov 12 nicklas 4911     if (visible)
6196 02 Nov 12 nicklas 4912     {
6196 02 Nov 12 nicklas 4913       // Hide the section
6196 02 Nov 12 nicklas 4914       content.style.display = 'none';
6196 02 Nov 12 nicklas 4915       icon.src = App.getRoot() + 'images/show_section.png';
6196 02 Nov 12 nicklas 4916       visible = false;
6342 01 Nov 13 nicklas 4917       if (showClass) Doc.removeClass(main, showClass);
6342 01 Nov 13 nicklas 4918       if (hideClass) Doc.addClass(main, hideClass);
6196 02 Nov 12 nicklas 4919     }
6196 02 Nov 12 nicklas 4920     else
6196 02 Nov 12 nicklas 4921     {
6196 02 Nov 12 nicklas 4922       // Show the section
6196 02 Nov 12 nicklas 4923       content.style.display = 'block';
6196 02 Nov 12 nicklas 4924       icon.src = App.getRoot() + 'images/hide_section.png';
6196 02 Nov 12 nicklas 4925       visible = true;
6342 01 Nov 13 nicklas 4926       if (showClass) Doc.addClass(main, showClass);
6342 01 Nov 13 nicklas 4927       if (hideClass) Doc.removeClass(main, hideClass);
6196 02 Nov 12 nicklas 4928     }
6196 02 Nov 12 nicklas 4929     return visible;
6196 02 Nov 12 nicklas 4930   }
6196 02 Nov 12 nicklas 4931   
6196 02 Nov 12 nicklas 4932   /**
6196 02 Nov 12 nicklas 4933     Show/hide a section and send an Ajax request to the server with
6196 02 Nov 12 nicklas 4934     the new status. If the browser doesn't support Ajax, this method is
6196 02 Nov 12 nicklas 4935     identical to toggle()
6196 02 Nov 12 nicklas 4936   */
6196 02 Nov 12 nicklas 4937   section.toggleAndSendWithAjax = function(sectionDiv, itemType, subcontext)
6196 02 Nov 12 nicklas 4938   {
6196 02 Nov 12 nicklas 4939     var visible = section.toggle(sectionDiv);
6199 02 Nov 12 nicklas 4940     var request = Ajax.getXmlHttpRequest();
6196 02 Nov 12 nicklas 4941     if (request != null)
6196 02 Nov 12 nicklas 4942     {
6196 02 Nov 12 nicklas 4943       var url = App.getRoot()+'common/store_show_hide_section.jsp?ID='+App.getSessionId();
6196 02 Nov 12 nicklas 4944       url += '&sectionId='+sectionDiv.id;
6196 02 Nov 12 nicklas 4945       url += '&itemType='+itemType;
6196 02 Nov 12 nicklas 4946       url += '&subcontext='+subcontext;
6196 02 Nov 12 nicklas 4947       url += '&visible='+(visible ? '1' : '0');
6196 02 Nov 12 nicklas 4948       request.open("GET", url, true);
6196 02 Nov 12 nicklas 4949       request.send(null);
6196 02 Nov 12 nicklas 4950     }
6196 02 Nov 12 nicklas 4951   }
6196 02 Nov 12 nicklas 4952
6196 02 Nov 12 nicklas 4953   /**
6196 02 Nov 12 nicklas 4954     Add click event handlers to the sections.
6196 02 Nov 12 nicklas 4955   */
6196 02 Nov 12 nicklas 4956   internal.initHideableSection = function(element, autoInit)
6196 02 Nov 12 nicklas 4957   {
6196 02 Nov 12 nicklas 4958     if (autoInit != 'hideable-section') return;
6196 02 Nov 12 nicklas 4959     Events.addEventHandler(element, 'click', internal.onClick);
6196 02 Nov 12 nicklas 4960   }
6196 02 Nov 12 nicklas 4961   Doc.addElementInitializer(internal.initHideableSection);
6196 02 Nov 12 nicklas 4962   
6196 02 Nov 12 nicklas 4963   /**
6196 02 Nov 12 nicklas 4964     Click event handler for a section. It will toggle
6196 02 Nov 12 nicklas 4965     the visibility of the content part and, if a context has
6196 02 Nov 12 nicklas 4966     been specified, save the current setting.
6196 02 Nov 12 nicklas 4967   */
6196 02 Nov 12 nicklas 4968   internal.onClick = function(event)
6196 02 Nov 12 nicklas 4969   {
6196 02 Nov 12 nicklas 4970     var target = event.currentTarget;
6196 02 Nov 12 nicklas 4971     var sectionDiv = Doc.element(target.id.substring(0, target.id.indexOf('.')));
6196 02 Nov 12 nicklas 4972     var context = Data.get(sectionDiv, 'context');
6196 02 Nov 12 nicklas 4973     var subcontext = Data.get(sectionDiv, 'subcontext', '');
6196 02 Nov 12 nicklas 4974     if (context)
6196 02 Nov 12 nicklas 4975     {
6196 02 Nov 12 nicklas 4976       section.toggleAndSendWithAjax(sectionDiv, context, subcontext);
6196 02 Nov 12 nicklas 4977     }
6196 02 Nov 12 nicklas 4978     else
6196 02 Nov 12 nicklas 4979     {
6196 02 Nov 12 nicklas 4980       section.toggle(sectionDiv);
6196 02 Nov 12 nicklas 4981     }
6196 02 Nov 12 nicklas 4982   }
6196 02 Nov 12 nicklas 4983   
6196 02 Nov 12 nicklas 4984   return section;
6196 02 Nov 12 nicklas 4985 }();
6196 02 Nov 12 nicklas 4986
6199 02 Nov 12 nicklas 4987 var Ajax = function()
6199 02 Nov 12 nicklas 4988 {
6199 02 Nov 12 nicklas 4989   var ajax = {};
6199 02 Nov 12 nicklas 4990   var internal = {};
6199 02 Nov 12 nicklas 4991   
6199 02 Nov 12 nicklas 4992   ajax.getXmlHttpRequest = function()
6199 02 Nov 12 nicklas 4993   {
6199 02 Nov 12 nicklas 4994     return new XMLHttpRequest();
6199 02 Nov 12 nicklas 4995   }
6199 02 Nov 12 nicklas 4996   
6199 02 Nov 12 nicklas 4997   /**
6199 02 Nov 12 nicklas 4998     Register callback functions for a successful of failed request.
6199 02 Nov 12 nicklas 4999     When the request has been completed, the onSuccessHandler or
6199 02 Nov 12 nicklas 5000     onFailHandler will be called with the request as a parameter:
6199 02 Nov 12 nicklas 5001     onSuccessHandler(request);
6199 02 Nov 12 nicklas 5002   */
6199 02 Nov 12 nicklas 5003   ajax.setReadyStateHandler = function(request, onSuccessHandler, onFailHandler)
6199 02 Nov 12 nicklas 5004   {
6199 02 Nov 12 nicklas 5005     request.addEventListener('readystatechange', function()
6199 02 Nov 12 nicklas 5006       {
6199 02 Nov 12 nicklas 5007         internal.onReadyStateChange(request, onSuccessHandler, onFailHandler);
6199 02 Nov 12 nicklas 5008       }
6199 02 Nov 12 nicklas 5009     );
6199 02 Nov 12 nicklas 5010   }
6196 02 Nov 12 nicklas 5011
6199 02 Nov 12 nicklas 5012   internal.onReadyStateChange = function(request, onSuccessHandler, onFailHandler)
6199 02 Nov 12 nicklas 5013   {
6199 02 Nov 12 nicklas 5014     if (request.readyState == 4)
6199 02 Nov 12 nicklas 5015     {
6199 02 Nov 12 nicklas 5016       if (request.status == 200 && onSuccessHandler)
6199 02 Nov 12 nicklas 5017       {
6199 02 Nov 12 nicklas 5018         onSuccessHandler.call(null, request);
6199 02 Nov 12 nicklas 5019       }
6540 26 Sep 14 nicklas 5020       else if (onFailHandler)
6199 02 Nov 12 nicklas 5021       {
6199 02 Nov 12 nicklas 5022         onFailHandler.call(null, request);
6199 02 Nov 12 nicklas 5023       }
6199 02 Nov 12 nicklas 5024     }
6199 02 Nov 12 nicklas 5025   }
6199 02 Nov 12 nicklas 5026   
6199 02 Nov 12 nicklas 5027   return ajax;
6199 02 Nov 12 nicklas 5028 }();
6199 02 Nov 12 nicklas 5029
6168 15 Oct 12 nicklas 5030 // Place for storing temporary objects
6168 15 Oct 12 nicklas 5031 window.pageValues = [];
6155 05 Oct 12 nicklas 5032