[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/form/ -> form.js (source)

   1  /**
   2   * This file contains JS functionality required by mforms and is included automatically
   3   * when required.
   4   */
   5  
   6  // Namespace for the form bits and bobs
   7  M.form = M.form || {};
   8  
   9  if (typeof M.form.dependencyManager === 'undefined') {
  10      var dependencyManager = function() {
  11          dependencyManager.superclass.constructor.apply(this, arguments);
  12      };
  13      Y.extend(dependencyManager, Y.Base, {
  14          _locks: null,
  15          _hides: null,
  16          _dirty: null,
  17          _nameCollections: null,
  18          _fileinputs: null,
  19  
  20          initializer: function() {
  21              // Setup initial values for complex properties.
  22              this._locks = {};
  23              this._hides = {};
  24              this._dirty = {};
  25  
  26              // Setup event handlers.
  27              Y.Object.each(this.get('dependencies'), function(value, i) {
  28                  var elements = this.elementsByName(i);
  29                  elements.each(function(node) {
  30                      var nodeName = node.get('nodeName').toUpperCase();
  31                      if (nodeName == 'INPUT') {
  32                          if (node.getAttribute('type').match(/^(button|submit|radio|checkbox)$/)) {
  33                              node.on('click', this.updateEventDependencies, this);
  34                          } else {
  35                              node.on('blur', this.updateEventDependencies, this);
  36                          }
  37                          node.on('change', this.updateEventDependencies, this);
  38                      } else if (nodeName == 'SELECT') {
  39                          node.on('change', this.updateEventDependencies, this);
  40                      } else {
  41                          node.on('click', this.updateEventDependencies, this);
  42                          node.on('blur', this.updateEventDependencies, this);
  43                          node.on('change', this.updateEventDependencies, this);
  44                      }
  45                  }, this);
  46              }, this);
  47  
  48              // Handle the reset button.
  49              this.get('form').get('elements').each(function(input) {
  50                  if (input.getAttribute('type') == 'reset') {
  51                      input.on('click', function() {
  52                          this.get('form').reset();
  53                          this.updateAllDependencies();
  54                      }, this);
  55                  }
  56              }, this);
  57  
  58              this.updateAllDependencies();
  59          },
  60  
  61          /**
  62           * Initializes the mapping from element name to YUI NodeList
  63           */
  64          initElementsByName: function() {
  65              var names = {};
  66  
  67              // Collect element names.
  68              Y.Object.each(this.get('dependencies'), function(conditions, i) {
  69                  names[i] = new Y.NodeList();
  70                  for (var condition in conditions) {
  71                      for (var value in conditions[condition]) {
  72                          for (var ei in conditions[condition][value]) {
  73                              names[conditions[condition][value][ei]] = new Y.NodeList();
  74                          }
  75                      }
  76                  }
  77              });
  78  
  79              // Locate elements for each name.
  80              this.get('form').get('elements').each(function(node) {
  81                  var name = node.getAttribute('name');
  82                  if (({}).hasOwnProperty.call(names, name)) {
  83                      names[name].push(node);
  84                  }
  85              });
  86              this._nameCollections = names;
  87          },
  88  
  89          /**
  90           * Gets all elements in the form by their name and returns
  91           * a YUI NodeList
  92           *
  93           * @param {String} name The form element name.
  94           * @return {Y.NodeList}
  95           */
  96          elementsByName: function(name) {
  97              if (!this._nameCollections) {
  98                  this.initElementsByName();
  99              }
 100              if (!({}).hasOwnProperty.call(this._nameCollections, name)) {
 101                  return new Y.NodeList();
 102              }
 103              return this._nameCollections[name];
 104          },
 105  
 106          /**
 107           * Checks the dependencies the form has an makes any changes to the
 108           * form that are required.
 109           *
 110           * Changes are made by functions title _dependency{Dependencytype}
 111           * and more can easily be introduced by defining further functions.
 112           *
 113           * @param {EventFacade | null} e The event, if any.
 114           * @param {String} dependon The form element name to check dependencies against.
 115           * @return {Boolean}
 116           */
 117          checkDependencies: function(e, dependon) {
 118              var dependencies = this.get('dependencies'),
 119                  tohide = {},
 120                  tolock = {},
 121                  condition, value, lock, hide,
 122                  checkfunction, result, elements;
 123              if (!({}).hasOwnProperty.call(dependencies, dependon)) {
 124                  return true;
 125              }
 126              elements = this.elementsByName(dependon);
 127              for (condition in dependencies[dependon]) {
 128                  for (value in dependencies[dependon][condition]) {
 129                      checkfunction = '_dependency' + condition[0].toUpperCase() + condition.slice(1);
 130                      if (Y.Lang.isFunction(this[checkfunction])) {
 131                          result = this[checkfunction].apply(this, [elements, value, e]);
 132                      } else {
 133                          result = this._dependencyDefault(elements, value, e);
 134                      }
 135                      lock = result.lock || false;
 136                      hide = result.hide || false;
 137                      for (var ei in dependencies[dependon][condition][value]) {
 138                          var eltolock = dependencies[dependon][condition][value][ei];
 139                          if (({}).hasOwnProperty.call(tohide, eltolock)) {
 140                              tohide[eltolock] = tohide[eltolock] || hide;
 141                          } else {
 142                              tohide[eltolock] = hide;
 143                          }
 144  
 145                          if (({}).hasOwnProperty.call(tolock, eltolock)) {
 146                              tolock[eltolock] = tolock[eltolock] || lock;
 147                          } else {
 148                              tolock[eltolock] = lock;
 149                          }
 150                      }
 151                  }
 152              }
 153  
 154              for (var el in tolock) {
 155                  var needsupdate = false;
 156                  if (!({}).hasOwnProperty.call(this._locks, el)) {
 157                      this._locks[el] = {};
 158                  }
 159                  if (({}).hasOwnProperty.call(tolock, el) && tolock[el]) {
 160                      if (!({}).hasOwnProperty.call(this._locks[el], dependon) || this._locks[el][dependon]) {
 161                          this._locks[el][dependon] = true;
 162                          needsupdate = true;
 163                      }
 164                  } else if (({}).hasOwnProperty.call(this._locks[el], dependon) && this._locks[el][dependon]) {
 165                      delete this._locks[el][dependon];
 166                      needsupdate = true;
 167                  }
 168  
 169                  if (!({}).hasOwnProperty.call(this._hides, el)) {
 170                      this._hides[el] = {};
 171                  }
 172                  if (({}).hasOwnProperty.call(tohide, el) && tohide[el]) {
 173                      if (!({}).hasOwnProperty.call(this._hides[el], dependon) || this._hides[el][dependon]) {
 174                          this._hides[el][dependon] = true;
 175                          needsupdate = true;
 176                      }
 177                  } else if (({}).hasOwnProperty.call(this._hides[el], dependon) && this._hides[el][dependon]) {
 178                      delete this._hides[el][dependon];
 179                      needsupdate = true;
 180                  }
 181  
 182                  if (needsupdate) {
 183                      this._dirty[el] = true;
 184                  }
 185              }
 186  
 187              return true;
 188          },
 189          /**
 190           * Update all dependencies in form
 191           */
 192          updateAllDependencies: function() {
 193              Y.Object.each(this.get('dependencies'), function(value, name) {
 194                  this.checkDependencies(null, name);
 195              }, this);
 196  
 197              this.updateForm();
 198          },
 199          /**
 200           * Update dependencies associated with event
 201           *
 202           * @param {Event} e The event.
 203           */
 204          updateEventDependencies: function(e) {
 205              var el = e.target.getAttribute('name');
 206              this.checkDependencies(e, el);
 207              this.updateForm();
 208          },
 209          /**
 210           * Flush pending changes to the form
 211           */
 212          updateForm: function() {
 213              var el;
 214              for (el in this._dirty) {
 215                  if (({}).hasOwnProperty.call(this._locks, el)) {
 216                      this._disableElement(el, !Y.Object.isEmpty(this._locks[el]));
 217                  }
 218                  if (({}).hasOwnProperty.call(this._hides, el)) {
 219                      this._hideElement(el, !Y.Object.isEmpty(this._hides[el]));
 220                  }
 221              }
 222  
 223              this._dirty = {};
 224          },
 225          /**
 226           * Disables or enables all form elements with the given name
 227           *
 228           * @param {String} name The form element name.
 229           * @param {Boolean} disabled True to disable, false to enable.
 230           */
 231          _disableElement: function(name, disabled) {
 232              var els = this.elementsByName(name);
 233              var filepicker = this.isFilePicker(name);
 234              els.each(function(node) {
 235                  if (disabled) {
 236                      node.setAttribute('disabled', 'disabled');
 237                  } else {
 238                      node.removeAttribute('disabled');
 239                  }
 240  
 241                  // Extra code to disable filepicker or filemanager form elements
 242                  if (filepicker) {
 243                      var fitem = node.ancestor('.fitem');
 244                      if (fitem) {
 245                          if (disabled) {
 246                              fitem.addClass('disabled');
 247                          } else {
 248                              fitem.removeClass('disabled');
 249                          }
 250                      }
 251                  }
 252              });
 253          },
 254          /**
 255           * Hides or shows all form elements with the given name.
 256           *
 257           * @param {String} name The form element name.
 258           * @param {Boolean} hidden True to hide, false to show.
 259           */
 260          _hideElement: function(name, hidden) {
 261              var els = this.elementsByName(name);
 262              els.each(function(node) {
 263                  var e = node.ancestor('.fitem');
 264                  if (e) {
 265                      e.setStyles({
 266                          display: (hidden) ? 'none' : ''
 267                      });
 268                  }
 269              });
 270          },
 271          /**
 272           * Is the form element inside a filepicker or filemanager?
 273           *
 274           * @param {String} el The form element name.
 275           * @return {Boolean}
 276           */
 277          isFilePicker: function(el) {
 278              if (!this._fileinputs) {
 279                  var fileinputs = {};
 280                  var els = this.get('form').all('.fitem.fitem_ffilepicker input,.fitem.fitem_ffilemanager input');
 281                  els.each(function(node) {
 282                      fileinputs[node.getAttribute('name')] = true;
 283                  });
 284                  this._fileinputs = fileinputs;
 285              }
 286  
 287              if (({}).hasOwnProperty.call(this._fileinputs, el)) {
 288                  return this._fileinputs[el] || false;
 289              }
 290  
 291              return false;
 292          },
 293          _dependencyNotchecked: function(elements, value) {
 294              var lock = false;
 295              elements.each(function() {
 296                  if (this.getAttribute('type').toLowerCase() == 'hidden' &&
 297                          !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
 298                      // This is the hidden input that is part of an advcheckbox.
 299                      return;
 300                  }
 301                  if (this.getAttribute('type').toLowerCase() == 'radio' && this.get('value') != value) {
 302                      return;
 303                  }
 304                  lock = lock || !Y.Node.getDOMNode(this).checked;
 305              });
 306              return {
 307                  lock: lock,
 308                  hide: false
 309              };
 310          },
 311          _dependencyChecked: function(elements, value) {
 312              var lock = false;
 313              elements.each(function() {
 314                  if (this.getAttribute('type').toLowerCase() == 'hidden' &&
 315                          !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
 316                      // This is the hidden input that is part of an advcheckbox.
 317                      return;
 318                  }
 319                  if (this.getAttribute('type').toLowerCase() == 'radio' && this.get('value') != value) {
 320                      return;
 321                  }
 322                  lock = lock || Y.Node.getDOMNode(this).checked;
 323              });
 324              return {
 325                  lock: lock,
 326                  hide: false
 327              };
 328          },
 329          _dependencyNoitemselected: function(elements, value) {
 330              var lock = false;
 331              elements.each(function() {
 332                  lock = lock || this.get('selectedIndex') == -1;
 333              });
 334              return {
 335                  lock: lock,
 336                  hide: false
 337              };
 338          },
 339          _dependencyEq: function(elements, value) {
 340              var lock = false;
 341              var hiddenVal = false;
 342              var options, v, selected, values;
 343              elements.each(function() {
 344                  if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
 345                      return;
 346                  } else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
 347                          !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
 348                      // This is the hidden input that is part of an advcheckbox.
 349                      hiddenVal = (this.get('value') == value);
 350                      return;
 351                  } else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
 352                      lock = lock || hiddenVal;
 353                      return;
 354                  }
 355                  if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
 356                      // Check for filepicker status.
 357                      var elementname = this.getAttribute('name');
 358                      if (elementname && M.form_filepicker.instances[elementname].fileadded) {
 359                          lock = false;
 360                      } else {
 361                          lock = true;
 362                      }
 363                  } else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
 364                      // Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator
 365                      // when multiple values have to be selected at the same time.
 366                      values = value.split('|');
 367                      selected = [];
 368                      options = this.get('options');
 369                      options.each(function() {
 370                          if (this.get('selected')) {
 371                              selected[selected.length] = this.get('value');
 372                          }
 373                      });
 374                      if (selected.length > 0 && selected.length === values.length) {
 375                          for (var i in selected) {
 376                              v = selected[i];
 377                              if (values.indexOf(v) > -1) {
 378                                  lock = true;
 379                              } else {
 380                                  lock = false;
 381                                  return;
 382                              }
 383                          }
 384                      } else {
 385                          lock = false;
 386                      }
 387                  } else {
 388                      lock = lock || this.get('value') == value;
 389                  }
 390              });
 391              return {
 392                  lock: lock,
 393                  hide: false
 394              };
 395          },
 396          /**
 397           * Lock the given field if the field value is in the given set of values.
 398           *
 399           * @param {Array} elements
 400           * @param {String} values Single value or pipe (|) separated values when multiple
 401           * @returns {{lock: boolean, hide: boolean}}
 402           * @private
 403           */
 404          _dependencyIn: function(elements, values) {
 405              // A pipe (|) is used as a value separator
 406              // when multiple values have to be passed on at the same time.
 407              values = values.split('|');
 408              var lock = false;
 409              var hiddenVal = false;
 410              var options, v, selected, value;
 411              elements.each(function() {
 412                  if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
 413                      return;
 414                  } else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
 415                          !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
 416                      // This is the hidden input that is part of an advcheckbox.
 417                      hiddenVal = (values.indexOf(this.get('value')) > -1);
 418                      return;
 419                  } else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
 420                      lock = lock || hiddenVal;
 421                      return;
 422                  }
 423                  if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
 424                      // Check for filepicker status.
 425                      var elementname = this.getAttribute('name');
 426                      if (elementname && M.form_filepicker.instances[elementname].fileadded) {
 427                          lock = false;
 428                      } else {
 429                          lock = true;
 430                      }
 431                  } else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
 432                      // Multiple selects can have one or more value assigned.
 433                      selected = [];
 434                      options = this.get('options');
 435                      options.each(function() {
 436                          if (this.get('selected')) {
 437                              selected[selected.length] = this.get('value');
 438                          }
 439                      });
 440                      if (selected.length > 0 && selected.length === values.length) {
 441                          for (var i in selected) {
 442                              v = selected[i];
 443                              if (values.indexOf(v) > -1) {
 444                                  lock = true;
 445                              } else {
 446                                  lock = false;
 447                                  return;
 448                              }
 449                          }
 450                      } else {
 451                          lock = false;
 452                      }
 453                  } else {
 454                      value = this.get('value');
 455                      lock = lock || (values.indexOf(value) > -1);
 456                  }
 457              });
 458              return {
 459                  lock: lock,
 460                  hide: false
 461              };
 462          },
 463          _dependencyHide: function(elements, value) {
 464              return {
 465                  lock: false,
 466                  hide: true
 467              };
 468          },
 469          _dependencyDefault: function(elements, value, ev) {
 470              var lock = false,
 471                  hiddenVal = false,
 472                  values
 473                  ;
 474              elements.each(function() {
 475                  var selected;
 476                  if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
 477                      return;
 478                  } else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
 479                          !this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
 480                      // This is the hidden input that is part of an advcheckbox.
 481                      hiddenVal = (this.get('value') != value);
 482                      return;
 483                  } else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
 484                      lock = lock || hiddenVal;
 485                      return;
 486                  }
 487                  // Check for filepicker status.
 488                  if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
 489                      var elementname = this.getAttribute('name');
 490                      if (elementname && M.form_filepicker.instances[elementname].fileadded) {
 491                          lock = true;
 492                      } else {
 493                          lock = false;
 494                      }
 495                  } else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
 496                      // Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator
 497                      // when multiple values have to be selected at the same time.
 498                      values = value.split('|');
 499                      this.get('options').each(function() {
 500                          if (this.get('selected')) {
 501                              selected[selected.length] = this.get('value');
 502                          }
 503                      });
 504                      if (selected.length > 0 && selected.length === values.length) {
 505                          for (var i in selected) {
 506                              if (values.indexOf(selected[i]) > -1) {
 507                                  lock = false;
 508                              } else {
 509                                  lock = true;
 510                                  return;
 511                              }
 512                          }
 513                      } else {
 514                          lock = true;
 515                      }
 516                  } else {
 517                      lock = lock || this.get('value') != value;
 518                  }
 519              });
 520              return {
 521                  lock: lock,
 522                  hide: false
 523              };
 524          }
 525      }, {
 526          NAME: 'mform-dependency-manager',
 527          ATTRS: {
 528              form: {
 529                  setter: function(value) {
 530                      return Y.one('#' + value);
 531                  },
 532                  value: null
 533              },
 534  
 535              dependencies: {
 536                  value: {}
 537              }
 538          }
 539      });
 540  
 541      M.form.dependencyManager = dependencyManager;
 542  }
 543  
 544  /**
 545   * Stores a list of the dependencyManager for each form on the page.
 546   */
 547  M.form.dependencyManagers = {};
 548  
 549  /**
 550   * Initialises a manager for a forms dependencies.
 551   * This should happen once per form.
 552   *
 553   * @param {YUI} Y YUI3 instance
 554   * @param {String} formid ID of the form
 555   * @param {Array} dependencies array
 556   * @return {M.form.dependencyManager}
 557   */
 558  M.form.initFormDependencies = function(Y, formid, dependencies) {
 559  
 560      // If the dependencies isn't an array or object we don't want to
 561      // know about it
 562      if (!Y.Lang.isArray(dependencies) && !Y.Lang.isObject(dependencies)) {
 563          return false;
 564      }
 565  
 566      /**
 567       * Fixes an issue with YUI's processing method of form.elements property
 568       * in Internet Explorer.
 569       *     http://yuilibrary.com/projects/yui3/ticket/2528030
 570       */
 571      Y.Node.ATTRS.elements = {
 572          getter: function() {
 573              return Y.all(new Y.Array(this._node.elements, 0, true));
 574          }
 575      };
 576  
 577      M.form.dependencyManagers[formid] = new M.form.dependencyManager({form: formid, dependencies: dependencies});
 578      return M.form.dependencyManagers[formid];
 579  };
 580  
 581  /**
 582   * Update the state of a form. You need to call this after, for example, changing
 583   * the state of some of the form input elements in your own code, in order that
 584   * things like the disableIf state of elements can be updated.
 585   *
 586   * @param {String} formid ID of the form
 587   */
 588  M.form.updateFormState = function(formid) {
 589      if (formid in M.form.dependencyManagers) {
 590          M.form.dependencyManagers[formid].updateAllDependencies();
 591      }
 592  };


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1