[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 };
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |