Instantly share code, notes, and snippets.
Created
April 4, 2014 12:36
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save cimmanon/9973839 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function addSubformListeners(el, isSubform) { | |
el.addEventListener("click", addListenerIfSubform, false); // bubbling | |
var useFieldsetFallback = false; | |
// Firefox allows disabled form elements to intercept click events, but does not allow them to bubble, | |
// so we have to inject some special elements to sit on top and intercept the click events | |
if (!hasEventsWhenDisabled()) { | |
addOverlays(); | |
} | |
if (typeof document.createElement('fieldset').disabled === 'undefined') { // browser doesn't support disabling fieldsets | |
useFieldsetFallback = true; | |
var inputs = el.querySelectorAll('fieldset[disabled] input, fieldset[disabled] textarea, fieldset[disabled] select'); | |
for (var i = 0, len = inputs.length; i < len; i++) { | |
if (!inputs[i].getAttribute('data-control')) { | |
inputs[i].disabled = true; | |
} | |
} | |
} | |
function addListenerIfSubform(ev) { | |
var subform; | |
// subformOverlay check here for FF | |
if (ev.target.className == 'subformOverlay') { | |
subform = ev.target.nextSibling; | |
} else { | |
subform = findAncestor(ev.target, isSubform); | |
} | |
if (subform) { | |
subformSetup(subform); | |
// call the event | |
if (document.createEvent) { | |
var evt = document.createEvent('Event'); | |
evt.initEvent("click", true, true); | |
subform.dispatchEvent(evt); | |
} else { // old IE model | |
subform.fireEvent('onclick'); | |
} | |
} | |
} | |
function addOverlays() { | |
var fieldsets = document.querySelectorAll('fieldset[disabled]'); | |
var div = document.createElement('div'); | |
div.className = 'subformOverlay'; | |
for (var i = 0, len = fieldsets.length; i < len; i++) { | |
var d = div.cloneNode(true); | |
var f = fieldsets[i]; | |
var p = f.parentNode; | |
p.insertBefore(d, f); | |
d.style.width = window.getComputedStyle(f).getPropertyValue('width') || f.currentStyle.width; | |
d.style.height = window.getComputedStyle(f).getPropertyValue('height') || f.currentStyle.height; | |
} | |
} | |
} | |
function subformSetup(form) { | |
form.addEventListener('click', clickEvents, false); | |
form.addEventListener('change', changeEvents, false); | |
form.addEventListener('change', setAsModified, false); // IE9 doesn't think onChange counts as onInput | |
form.addEventListener('input', setAsModified, false); | |
var useFieldsetFallback = typeof form.disabled === 'undefined'; | |
var legend = form.getElementsByTagName('legend')[0]; // the primary legend for this subform | |
// toggle to display the options | |
var optionsToggle = legend.querySelector('input[type="checkbox"][data-control]'); | |
// subforms if they exist for a toggle form | |
var subforms = form.getElementsByTagName('fieldset'); | |
var fields = form.querySelectorAll('input, textarea, select'); | |
enableForm(); | |
function changeEvents(ev) { | |
switch (ev.target.type) { // toggleform togglers | |
case 'radio': | |
for (var i = 0, len = subforms.length; i < len; i++) { | |
if (subforms[i].attributes['name'].value == ev.target.value) { | |
enableFieldset(subforms[i]); | |
var thisLegend = subforms[i].getElementsByTagName('legend')[0]; | |
legend.firstChild.nodeValue = thisLegend.innerText || thisLegend.textContent; | |
} else { | |
disableFieldset(subforms[i]); | |
form.className = form.className.replace(subforms[i].attributes['name'].value, ev.target.value); | |
} | |
if (useFieldsetFallback) { | |
fieldsetDisableFallback(fields); | |
} | |
} | |
break; | |
case 'checkbox': | |
if (form.getAttribute('disabled')) { | |
openOptions(); | |
} else { | |
ev.target.checked ? openOptions() : closeOptions(); | |
} | |
break; | |
} | |
} | |
function clickEvents(ev) { | |
switch (ev.target.tagName.toLowerCase()) { | |
case 'legend': | |
if (ev.target == legend) { | |
toggleForm(); | |
} | |
break; | |
case 'input': | |
switch (ev.target.type) { | |
case 'button': // cancel/confirmation buttons | |
switch (ev.target.name) { | |
case 'cancel': | |
disableForm(); | |
ev.preventDefault; | |
break; | |
case 'confirm': | |
closeOptions(); | |
ev.preventDefault; | |
break; | |
default: | |
break; | |
} | |
break; | |
default: | |
break; | |
} | |
break; | |
case 'label': | |
if ((ev.target.getAttribute('for') == optionsToggle.id || ev.target.querySelector('input') == optionsToggle) && form.getAttribute('disabled')) { | |
enableForm(); | |
} | |
break; | |
case 'fieldset': | |
if (ev.target == form) { // should only fire when setting up event delegation on the base subform | |
toggleLegend(); | |
enableForm(); | |
} | |
break; | |
default: | |
// do nothing | |
break; | |
} | |
if (ev.stopPropagation) { | |
ev.stopPropagation(); | |
} else if (window.event) { // when invoking an event via dispatchEvent, window.event will be null in IE9 | |
window.event.cancelBubble = true; | |
} | |
} | |
function toggleLegend() { | |
if (legend.getAttribute('data-label')) { | |
var oldLabel = legend.innerText || legend.textContent; | |
legend.firstChild.nodeValue = legend.getAttribute('data-label'); | |
legend.setAttribute('data-label', oldLabel); | |
} | |
} | |
function toggleForm() { | |
toggleLegend(); | |
form.getAttribute('disabled') ? enableForm() : disableForm(); | |
} | |
function fieldsetDisableFallback(fields) { | |
for (var i = 0, len = fields.length; i < len; i++) { | |
var isDisabled = findAncestor(fields[i], function(el) { | |
return el.tagName.toLowerCase() == 'fieldset' && el.getAttribute('disabled'); | |
}); | |
fields[i].disabled = isDisabled ? true : false; // if the closest ancestor fieldset is disabled, disable it. otherwise enable it | |
} | |
} | |
function enableFieldset(fieldset) { | |
if (useFieldsetFallback) { | |
fieldset.removeAttribute('disabled'); | |
} else { | |
fieldset.disabled = false; | |
} | |
} | |
function disableFieldset(fieldset) { | |
if (useFieldsetFallback) { | |
fieldset.setAttribute('disabled', 'disabled'); | |
} else { | |
fieldset.disabled = true; | |
} | |
} | |
function enableForm() { | |
enableFieldset(form); | |
if (useFieldsetFallback) { | |
fieldsetDisableFallback(fields); | |
} | |
if (optionsToggle && optionsToggle.checked || !optionsToggle) { | |
openOptions(); | |
} | |
} | |
function disableForm() { | |
disableFieldset(form); | |
if (useFieldsetFallback) { | |
fieldsetDisableFallback(fields); | |
} | |
// remove event listeners | |
form.removeEventListener('click', clickEvents, false); | |
form.removeEventListener('change', changeEvents, false); | |
form.removeEventListener('change', setAsModified, false); | |
form.removeEventListener('input', setAsModified, false); | |
closeOptions(); | |
} | |
function toggleOptions() { | |
optionsToggle.checked ? closeOptions() : openOptions(); | |
} | |
function closeOptions() { | |
if (optionsToggle && optionsToggle.checked) { | |
optionsToggle.checked = false; | |
} | |
form.className = form.className.replace(/\bopened\b/g, ''); | |
} | |
function openOptions() { | |
if (optionsToggle && !optionsToggle.checked) { | |
optionsToggle.checked = true; | |
} | |
if (!hasClass('opened')(form)) { | |
form.className += (form.className.length == 0 ? '' : ' ') + ' opened'; | |
} | |
} | |
function setAsModified(ev) { | |
// onChange events do not bubble :-( | |
if (!formIsModified(fields, isSubformControl)) { | |
form.className = form.className.replace(/\bmodified\b/g, ''); | |
} else if (!hasClass('modified')(form)) { | |
form.className += (form.className.length == 0 ? '' : ' ') + 'modified'; | |
} | |
} | |
} | |
// ==================================================================================================== | |
// | Helper Functions | |
// ==================================================================================================== | |
function isFieldset(el) { | |
return el.tagName.toLowerCase() == 'fieldset'; | |
} | |
function isTag(tagName) { | |
return function(el) { | |
return el.tagName.toLowerCase() == tagName; | |
} | |
} | |
function hasClass(className) { | |
return function(el) { | |
return el.className ? el.className.split(' ').indexOf(className) !== -1 : false; | |
} | |
} | |
function findAncestor(el, equalityChecker) { | |
if (!el.parentNode || el == document.documentElement) { // if we're at the root of the document, there's nowhere else to go | |
return false; | |
} | |
var p = el.parentNode; | |
if (equalityChecker(p)) { | |
return p; | |
} else { | |
return findAncestor(p, equalityChecker); | |
} | |
} | |
function isSubformControl(field) { | |
return !!field.getAttribute('data-control'); | |
} | |
function formIsModified(fields, isIgnoredField) { | |
for (var i = 0, len = fields.length; i < len; i++) { | |
var field = fields[i]; | |
if (isIgnoredField(field) || field.disabled) { | |
continue; | |
} | |
if (field.tagName.toLowerCase() == 'select') { | |
if (!field.options[field.selectedIndex].defaultSelected) { | |
return true; | |
} | |
} else if ((field.type == 'radio' || field.type == 'checkbox') && typeof field.defaultChecked !== 'undefined') { | |
if (field.defaultChecked != field.checked) { | |
return true; | |
} | |
} else if ( | |
(typeof field.defaultValue === 'undefined' && field.value != '') || | |
(typeof field.defaultValue !== 'undefined' && field.value != field.defaultValue)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function resetFields(fields, isIgnoredField) { | |
for (var i = 0, len = fields.length; i < len; i++) { | |
var field = fields[i]; | |
if ((typeof isIgnoredField === 'function' && isIgnoredField(field)) || field.disabled) { | |
continue; | |
} | |
if (field.tagName.toLowerCase() == 'select') { | |
var options = field.options; | |
for (var j = 0, len = options.length; j < len; j++) { | |
if (options[j].defaultSelected) { | |
field.selectedIndex = j; | |
continue; | |
} | |
} | |
field.selectedIndex = 0; | |
} else if ((field.type == 'radio' || field.type == 'checkbox') && typeof field.defaultChecked !== 'undefined') { | |
field.checked = field.defaultChecked; | |
} else if (typeof field.defaultValue !== 'undefined' && field.value != field.defaultValue) { | |
field.value = field.defaultValue; | |
} | |
} | |
} | |
function hasEventsWhenDisabled() { | |
var input = document.createElement('input'); | |
input.disabled = true; | |
var e = document.createEvent('HTMLEvents'); | |
e.initEvent('click', true, true); | |
try { | |
input.dispatchEvent(e); | |
return true; | |
} catch(err) {} | |
return false; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment