DragonByte Tech
Well-known member
Recently, I wanted to take advantage of the
I have created the
Proposed Example Usage:
Proposed JS Class:
I have tested the code with
I have not explicitly tested
I have not tested
I have not tested nested disablers. I predict nested disablers will produce unexpected behaviour, unless the
Developers: If you are implementing this code in your own product pending this feature request, please consider prefixing the class with your own vendor prefix, such as dbtech-ecommerce-multi-disabler and DBTecheCommerce.MultiDisabler rather than copying the code exactly as written.
Fillip
disabler
feature in XF2 to require two checkboxes inside the same checkboxrow
to be checked before the submit button would become available. After some trial and error, I concluded that is not currently possible.I have created the
MultiDisabler
JS class to support this functionality.Proposed Example Usage:
HTML:
<xf:checkboxrow rowtype="fullWidth noLabel" data-xf-init="multi-disabler" data-input-controls=".js-confirmBoxes" data-container=".js-submitDisable">
<xf:option name="confirm" class="js-confirmBoxes">
<xf:label>{{ phrase('please_confirm_this') }}</xf:label>
</xf:option>
<xf:option name="also_confirm" class="js-confirmBoxes">
<xf:label>{{ phrase('please_also_confirm_this') }}</xf:label>
</xf:option>
</xf:checkboxrow>
Proposed JS Class:
JavaScript:
// ################################## CONTROL MULTI-DISABLER HANDLER ###########################################
XF.MultiDisabler = XF.Element.newHandler({
options: {
container: '< li | ul, ol, dl',
controls: 'input, select, textarea, button, .js-attachmentUpload',
inputControls: 'input[type=radio], input[type=checkbox]',
hide: false,
optional: false,
invert: false // if true, system will disable on checked
},
$container: null,
init: function()
{
this.$container = XF.findRelativeIf(this.options.container, this.$target);
if (!this.$container.length)
{
if (!this.options.optional)
{
console.error('Could not find the disabler control container');
}
}
var $inputContainer = this.$target,
$form = $inputContainer.closest('form'),
$self = this;
if ($form.length)
{
$form.on('reset', XF.proxy(this, 'formReset'));
}
$inputContainer.find(this.options.inputControls).each(function()
{
var $input = $(this);
if ($input.is(':radio'))
{
var $context = $form,
name = $input.attr('name');
if (!$form.length)
{
$context = $(document.body);
}
// radios only fire events for the element we click normally, so we need to know
// when we move away from the value by firing every radio's handler for every click
$context.on('click', 'input:radio[name="' + name + '"]', XF.proxy($self, 'click'));
}
else
{
$input.click(XF.proxy($self, 'click'));
}
// this ensures that nested disablers are disabled properly
$input.on('control:enabled control:disabled', XF.proxy($self, 'recalculateAfter'));
});
// this ensures that dependent editors are initialised properly as disabled if needed
this.$container.one('editor:init', XF.proxy(this, 'recalculateAfter'));
this.recalculate(true);
},
click: function(e, options)
{
var noSelect = (options && options.triggered);
this.recalculateAfter(false, noSelect);
},
formReset: function(e)
{
this.recalculateAfter(false, true);
},
recalculateAfter: function(init, noSelect)
{
var t = this;
setTimeout(function()
{
t.recalculate(init, noSelect);
}, 0);
},
recalculate: function(init, noSelect)
{
var $container = this.$container,
$inputContainer = this.$target,
$input = $inputContainer.find(this.options.inputControls),
$controls = $container.find(this.options.controls).not($input),
speed = init ? 0 : XF.config.speed.fast,
enable = $input.not(':enabled').length == 0 && (($input.not(':checked').length == 0 && !this.options.invert) || (this.options.invert && $input.not(':checked').length != 0)),
select = function()
{
if (noSelect)
{
return;
}
$container.find('input:not([type=hidden], [type=file]), textarea, select, button').not($input)
.first().autofocus();
};
if (enable)
{
$container
.prop('disabled', false)
.removeClass('is-disabled');
$controls
.prop('disabled', false)
.removeClass('is-disabled')
.each(function(i, ctrl)
{
var $ctrl = $(ctrl);
if ($ctrl.is('select.is-readonly'))
{
// readonly has to be implemented through disabling so we can't undisable this
$ctrl.prop('disabled', true);
}
})
.trigger('control:enabled');
if (this.options.hide)
{
if (init)
{
$container.show();
}
else
{
var cb = function()
{
XF.layoutChange();
select();
};
$container.slideDown(speed, cb);
}
XF.layoutChange();
}
else if (!init)
{
select();
}
}
else
{
if (this.options.hide)
{
if (init)
{
$container.hide();
}
else
{
$container.slideUp(speed, XF.layoutChange);
}
XF.layoutChange();
}
$container
.prop('disabled', true)
.addClass('is-disabled');
$controls
.prop('disabled', true)
.addClass('is-disabled')
.trigger('control:disabled')
.each(function(i, ctrl)
{
var $ctrl = $(ctrl),
disabledVal = $ctrl.data('disabled');
if (disabledVal !== null && typeof(disabledVal) != 'undefined')
{
$ctrl.val(disabledVal);
}
});
}
}
});
XF.Element.register('multi-disabler', 'XF.MultiDisabler');
I have tested the code with
data-invert
both on and off. All four permutations (invert off, 1/2 checkboxes = disabled, etc) works fine in my testing.I have not explicitly tested
data-hide
but I suspect it will work fine, as I did not modify that part.I have not tested
data-controls
as I don't fully understand its purpose and I couldn't find any examples of this functionality being used in XF2.I have not tested nested disablers. I predict nested disablers will produce unexpected behaviour, unless the
data-input-controls
option is used to restrict which input boxes MultiDisabler
triggers on.Developers: If you are implementing this code in your own product pending this feature request, please consider prefixing the class with your own vendor prefix, such as dbtech-ecommerce-multi-disabler and DBTecheCommerce.MultiDisabler rather than copying the code exactly as written.
Fillip
Upvote
3