Skip to content

Instantly share code, notes, and snippets.

@LarryBarker
Created March 18, 2024 03:29
Show Gist options
  • Save LarryBarker/50e95d1740a929185d90f8f906eecf81 to your computer and use it in GitHub Desktop.
Save LarryBarker/50e95d1740a929185d90f8f906eecf81 to your computer and use it in GitHub Desktop.
Input Trigger API - This API allows you to change an elements' visibility or status (enabled/disabled) based on another elements' status(es)
/*
* Input Trigger API
* This API allows to change elements' visibility or status (enabled/disabled) basing on other elements' statuses.
* Example: enable a button if any checkbox inside another element is checked.
* - Checked Condition:
<input type="checkbox" id="triggerChk1" />
<button class="btn disabled"
data-trigger-action="enable"
data-trigger="#triggerChk1"
data-trigger-condition="checked">
Check the checkbox
</button>
*
* - Value Condition:
* <p>
<input
type="text"
id="triggerTxt1"
value=""
onkeyup="$(this).trigger('change')"
placeholder="Enter 'foo' or 'bar' here"
class="form-control" />
</p>
<div
class="callout callout-success"
data-trigger-action="show"
data-trigger="#triggerTxt1"
data-trigger-condition="value[foo][bar]">
<div class="content">
Passphrase is valid!
</div>
</div>
*
* Supported data attributes:
* - data-trigger-action, values: show, hide, enable, disable, empty
* - data-trigger: a CSS selector for elements that trigger the action (checkboxes)
* - data-trigger-condition, values:
* - checked: determines the condition the elements specified in the data-trigger should satisfy in order the condition to be considered as "true".
* - unchecked: inverse condition of "checked".
* - value[somevalue]: determines if the value of data-trigger equals the specified value (somevalue) the condition is considered "true".
* - data-trigger-closest-parent: optional, specifies a CSS selector for a closest common parent for the source and destination input elements.
*
* Example code:
* <input type="button" class="btn disabled"
data-trigger-action="enable"
data-trigger="#cblist input[type=checkbox]"
data-trigger-condition="checked" ... >
*
* Multiple actions are supported:
* data-trigger-action="hide|empty"
*
* Multiple value conditions are supported:
* data-trigger-condition="value[foo][bar]"
*
* Supported events:
* - fn.triggerOn.update - triggers the update. Trigger this event on the element the plugin is bound to, forcing it to check the condition and update itself. This is useful when the page ontent is updated via AJAX.
* - fn.triggerOn.afterUpdate - triggered after the element is updated
*
* JavaScript API:
* $('#mybutton').triggerOn({
triggerCondition: 'checked',
trigger: '#cblist input[type=checkbox]',
triggerAction: 'enable'
})
*
*/
(function() { "use strict";
var oldOnload = onload ? onload : function () {};
onload = function () {
oldOnload();
var TriggerOn = function (element, options) {
var $el = this.$el = $(element);
this.options = options || {};
if (this.options.triggerCondition === false)
throw new Error('Trigger condition is not specified.')
if (this.options.trigger === false)
throw new Error('Trigger selector is not specified.')
if (this.options.triggerAction === false)
throw new Error('Trigger action is not specified.')
this.triggerCondition = this.options.triggerCondition
if (this.options.triggerCondition.indexOf('value') == 0) {
var match = this.options.triggerCondition.match(/[^[\]]+(?=])/g)
this.triggerCondition = 'value'
this.triggerConditionValue = (match) ? match : [""]
}
this.triggerParent = undefined
if (this.options.triggerClosestParent !== undefined) {
var closestParentElements = this.options.triggerClosestParent.split(',')
for (var i = 0; i < closestParentElements.length; i++) {
var $triggerElement = $el.closest(closestParentElements[i])
if ($triggerElement.length) {
this.triggerParent = $triggerElement
break
}
}
}
if (
this.triggerCondition == 'checked' ||
this.triggerCondition == 'unchecked' ||
this.triggerCondition == 'value'
) {
$(document).on('change', this.options.trigger, $.proxy(this.onConditionChanged, this))
}
var self = this
$el.on('fn.triggerOn.update', function(e){
e.stopPropagation()
self.onConditionChanged()
})
// Make any triggered fields optional to begin with
// If they are required, the trigger action will re-apply the attribute
self.normalizeInputs()
self.onConditionChanged()
}
TriggerOn.prototype.normalizeInputs = function() {
this.$el
.find('input, select')
.prop('required', false)
}
TriggerOn.prototype.onConditionChanged = function() {
if (this.triggerCondition == 'checked') {
this.updateTarget(!!$(this.options.trigger + ':checked', this.triggerParent).length)
}
else if (this.triggerCondition == 'unchecked') {
this.updateTarget(!$(this.options.trigger + ':checked', this.triggerParent).length)
}
else if (this.triggerCondition == 'value') {
var trigger, triggered = false
trigger = $(this.options.trigger, this.triggerParent)
.not('input[type=checkbox], input[type=radio], input[type=button], input[type=submit]')
if (!trigger.length) {
trigger = $(this.options.trigger, this.triggerParent)
.not(':not(input[type=checkbox]:checked, input[type=radio]:checked)')
}
var self = this
trigger.each(function() {
var triggerValue = $(this).val();
$.each($.isArray(triggerValue) ? triggerValue : [triggerValue], function(key, val) {
triggered = $.inArray(val, self.triggerConditionValue) != -1
return !triggered
})
return !triggered
})
this.updateTarget(triggered)
}
}
TriggerOn.prototype.updateTarget = function(status) {
var self = this,
actions = this.options.triggerAction.split('|')
$.each(actions, function(index, action) {
self.updateTargetAction(action, status)
})
$(window).trigger('resize')
this.$el.trigger('fn.triggerOn.afterUpdate', status)
}
TriggerOn.prototype.updateTargetAction = function(action, status) {
if (action == 'show') {
this.$el
.toggleClass('hide', !status)
.trigger('hide.fn.triggerapi', [!status])
}
else if (action == 'hide') {
this.$el
.toggleClass('hide', status)
.trigger('hide.fn.triggerapi', [status])
}
else if (action == 'enable') {
this.$el
.prop('disabled', !status)
.toggleClass('control-disabled', !status)
.trigger('disable.fn.triggerapi', [!status])
}
else if (action == 'disable') {
this.$el
.prop('disabled', status)
.toggleClass('control-disabled', status)
.trigger('disable.oc.triggerapi', [status])
}
else if (action == 'empty' && status) {
this.$el
.not('input[type=checkbox], input[type=radio], input[type=button], input[type=submit]')
.val('')
this.$el
.not(':not(input[type=checkbox], input[type=radio])')
.prop('checked', false)
this.$el
.trigger('empty.fn.triggerapi')
.trigger('change')
}
if (action == 'require') {
this.$el
.find('input, select')
.prop('required', status)
}
if (action == 'show' || action == 'hide') {
this.fixButtonClasses()
}
}
TriggerOn.prototype.fixButtonClasses = function() {
var group = this.$el.closest('.btn-group')
if (group.length > 0 && this.$el.is(':last-child'))
this.$el.prev().toggleClass('last', this.$el.hasClass('hide'))
}
TriggerOn.DEFAULTS = {
triggerAction: false,
triggerCondition: false,
triggerClosestParent: undefined,
trigger: false
}
// TRIGGERON PLUGIN DEFINITION
// ============================
var old = $.fn.triggerOn
$.fn.triggerOn = function (option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('fn.triggerOn')
var options = $.extend({}, TriggerOn.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('fn.triggerOn', (data = new TriggerOn(this, options)))
})
}
$.fn.triggerOn.Constructor = TriggerOn
// TRIGGERON NO CONFLICT
// =================
$.fn.triggerOn.noConflict = function () {
$.fn.triggerOn = old
return this
}
// TRIGGERON DATA-API
// ===============
$('[data-trigger]').triggerOn()
}
}(window.jQuery));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment