mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-22 09:53:35 +02:00
Use own-written TriState checkbox library, which makes form submissions a lot easier.
This commit is contained in:
parent
8e6bc25d2e
commit
ef389dcc15
3 changed files with 225 additions and 243 deletions
207
assets/js/lib/TristateCheckbox.js
Normal file
207
assets/js/lib/TristateCheckbox.js
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
const DEFAULT_OPTIONS = {
|
||||||
|
true: "true",
|
||||||
|
false: "false",
|
||||||
|
null: "indeterminate",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple tristate checkbox
|
||||||
|
*/
|
||||||
|
export default class TristateCheckbox {
|
||||||
|
|
||||||
|
static instances = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {null|boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_state = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The element representing the checkbox.
|
||||||
|
* @type {HTMLInputElement}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_element = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hidden input element representing the value of the checkbox
|
||||||
|
* @type {HTMLInputElement}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_hiddenInput = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The values of the checkbox.
|
||||||
|
* @type {{null: string, true: string, false: string}}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_options = DEFAULT_OPTIONS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the instance of the TristateCheckbox for the given element if already existing, otherwise a new one is created.
|
||||||
|
* @param element
|
||||||
|
* @param options
|
||||||
|
* @return {any}
|
||||||
|
*/
|
||||||
|
static getInstance(element, options = {})
|
||||||
|
{
|
||||||
|
if(!TristateCheckbox.instances.has(element)) {
|
||||||
|
TristateCheckbox.instances.set(element, new TristateCheckbox(element, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
return TristateCheckbox.instances.get(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} element
|
||||||
|
*/
|
||||||
|
constructor(element, options = {})
|
||||||
|
{
|
||||||
|
if(!element instanceof HTMLInputElement || !(element.tagName === 'INPUT' && element.type === 'checkbox')) {
|
||||||
|
throw new Error("The given element is not an input checkbox");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Apply options
|
||||||
|
this._options = Object.assign(this._options, options);
|
||||||
|
|
||||||
|
this._element = element;
|
||||||
|
|
||||||
|
//Set the state of our element to the value of the passed input value
|
||||||
|
this._parseInitialState();
|
||||||
|
|
||||||
|
//Create a hidden input field to store the value of the checkbox, because this will be always be submitted in the form
|
||||||
|
this._hiddenInput = document.createElement('input');
|
||||||
|
this._hiddenInput.type = 'hidden';
|
||||||
|
this._hiddenInput.name = this._element.name;
|
||||||
|
this._hiddenInput.value = this._element.value;
|
||||||
|
|
||||||
|
//Insert the hidden input field after the checkbox and remove the checkbox from form submission (by removing the name property)
|
||||||
|
element.after(this._hiddenInput);
|
||||||
|
this._element.removeAttribute('name');
|
||||||
|
|
||||||
|
//Do a refresh to set the correct styling of the checkbox
|
||||||
|
this._refresh();
|
||||||
|
|
||||||
|
this._element.addEventListener('click', this.click.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the attributes of the checkbox and set the correct state.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_parseInitialState()
|
||||||
|
{
|
||||||
|
if(this._element.hasAttribute('value')) {
|
||||||
|
this._state = this._stringToState(this._element.getAttribute('value'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this._element.checked) {
|
||||||
|
this._state = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this._element.indeterminate) {
|
||||||
|
this._state = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._state = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_refresh()
|
||||||
|
{
|
||||||
|
this._element.indeterminate = this._state === null;
|
||||||
|
this._element.checked = this._state === true;
|
||||||
|
//Set the value field of the checkbox and the hidden input to correct value
|
||||||
|
this._element.value = this._stateToString(this._state);
|
||||||
|
this._hiddenInput.value = this._stateToString(this._state);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current state of the checkbox. True if checked, false if unchecked, null if indeterminate.
|
||||||
|
* @return {boolean|null}
|
||||||
|
*/
|
||||||
|
get state() {
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state of the checkbox. True if checked, false if unchecked, null if indeterminate.
|
||||||
|
* @param state
|
||||||
|
*/
|
||||||
|
set state(state) {
|
||||||
|
this._state = state;
|
||||||
|
this._refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current state of the checkbox as string, according to the options.
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
get stateString() {
|
||||||
|
return this._stateToString(this._state);
|
||||||
|
}
|
||||||
|
|
||||||
|
set stateString(string) {
|
||||||
|
this.state = this._stringToState(string);
|
||||||
|
this._refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean|null} state
|
||||||
|
* @return string
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_stateToString(state)
|
||||||
|
{
|
||||||
|
if (this.state === null) {
|
||||||
|
return this._options.null;
|
||||||
|
} else if (this.state === true) {
|
||||||
|
return this._options.true;
|
||||||
|
} else if (this.state === false) {
|
||||||
|
return this._options.false;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Invalid state " + state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string to a state according to the options.
|
||||||
|
* @param string
|
||||||
|
* @param throwError
|
||||||
|
* @return {null|boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_stringToState(string, throwError = true)
|
||||||
|
{
|
||||||
|
if (string === this._options.true) {
|
||||||
|
return true;
|
||||||
|
} else if (string === this._options.false) {
|
||||||
|
return false;
|
||||||
|
} else if (string === this._options.null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(throwError) {
|
||||||
|
throw new Error("Invalid state string " + string);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
click()
|
||||||
|
{
|
||||||
|
switch (this._state) {
|
||||||
|
case true: this._state = false; break;
|
||||||
|
case false: this._state = null; break;
|
||||||
|
default: this._state = true; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,213 +0,0 @@
|
||||||
/*jslint devel: true, bitwise: true, regexp: true, browser: true, confusion: true, unparam: true, eqeq: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
|
|
||||||
/*globals jQuery */
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Tristate v1.2.1
|
|
||||||
*
|
|
||||||
* Copyright (c) 2013-2017 Martijn W. van der Lee
|
|
||||||
* Licensed under the MIT.
|
|
||||||
*/
|
|
||||||
/* Based on work by:
|
|
||||||
* Chris Coyier (http://css-tricks.com/indeterminate-checkboxes/)
|
|
||||||
*
|
|
||||||
* Tristate checkbox with support features
|
|
||||||
* pseudo selectors
|
|
||||||
* val() overwrite
|
|
||||||
*/
|
|
||||||
|
|
||||||
;(function($, undefined) {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var pluginName = 'tristate',
|
|
||||||
defaults = {
|
|
||||||
'change': undefined,
|
|
||||||
'checked': undefined,
|
|
||||||
'indeterminate': undefined,
|
|
||||||
'init': undefined,
|
|
||||||
'reverse': false,
|
|
||||||
'state': undefined,
|
|
||||||
'unchecked': undefined,
|
|
||||||
'value': undefined // one-way only!
|
|
||||||
},
|
|
||||||
valFunction = $.fn.val;
|
|
||||||
|
|
||||||
function Plugin(element, options) {
|
|
||||||
if($(element).is(':checkbox')) {
|
|
||||||
this.element = $(element);
|
|
||||||
this.settings = $.extend( {}, defaults, options );
|
|
||||||
this._create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$.extend(Plugin.prototype, {
|
|
||||||
_create: function() {
|
|
||||||
var that = this,
|
|
||||||
state;
|
|
||||||
|
|
||||||
// Fix for #1
|
|
||||||
if (window.navigator.userAgent.indexOf('Trident') >= 0) {
|
|
||||||
this.element.click(function(e) {
|
|
||||||
that._change.call(that, e);
|
|
||||||
that.element.closest('form').change();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.element.change(function(e) {
|
|
||||||
that._change.call(that, e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.settings.checked = this.element.attr('checkedvalue') || this.settings.checked;
|
|
||||||
this.settings.unchecked = this.element.attr('uncheckedvalue') || this.settings.unchecked;
|
|
||||||
this.settings.indeterminate = this.element.attr('indeterminatevalue') || this.settings.indeterminate;
|
|
||||||
|
|
||||||
// Initially, set state based on option state or attributes
|
|
||||||
if (typeof this.settings.state === 'undefined') {
|
|
||||||
this.settings.state = typeof this.element.attr('indeterminate') !== 'undefined'? null : this.element.is(':checked');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If value specified, overwrite with value
|
|
||||||
if (typeof this.settings.value !== 'undefined') {
|
|
||||||
state = this._parseValue(this.settings.value);
|
|
||||||
if (typeof state !== 'undefined') {
|
|
||||||
this.settings.state = state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._refresh(this.settings.init);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
_change: function(e) {
|
|
||||||
if (e.isTrigger || !e.hasOwnProperty('which')) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.settings.state) {
|
|
||||||
case true: this.settings.state = (this.settings.reverse ? false : null); break;
|
|
||||||
case false: this.settings.state = (this.settings.reverse ? null : true); break;
|
|
||||||
default: this.settings.state = (this.settings.reverse ? true : false); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._refresh(this.settings.change);
|
|
||||||
},
|
|
||||||
|
|
||||||
_refresh: function(callback) {
|
|
||||||
var value = this.value();
|
|
||||||
|
|
||||||
this.element.data("vanderlee." + pluginName, value);
|
|
||||||
|
|
||||||
this.element[this.settings.state === null ? 'attr' : 'removeAttr']('indeterminate', 'indeterminate');
|
|
||||||
this.element.prop('indeterminate', this.settings.state === null);
|
|
||||||
this.element.get(0).indeterminate = this.settings.state === null;
|
|
||||||
|
|
||||||
this.element[this.settings.state === true ? 'attr' : 'removeAttr']('checked', true);
|
|
||||||
this.element.prop('checked', this.settings.state === true);
|
|
||||||
|
|
||||||
if ($.isFunction(callback)) {
|
|
||||||
callback.call(this.element, this.settings.state, this.value());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
state: function(value) {
|
|
||||||
if (typeof value === 'undefined') {
|
|
||||||
return this.settings.state;
|
|
||||||
} else if (value === true || value === false || value === null) {
|
|
||||||
this.settings.state = value;
|
|
||||||
|
|
||||||
this._refresh(this.settings.change);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
_parseValue: function(value) {
|
|
||||||
if (value === this.settings.checked) {
|
|
||||||
return true;
|
|
||||||
} else if (value === this.settings.unchecked) {
|
|
||||||
return false;
|
|
||||||
} else if (value === this.settings.indeterminate) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
value: function(value) {
|
|
||||||
if (typeof value === 'undefined') {
|
|
||||||
var value;
|
|
||||||
switch (this.settings.state) {
|
|
||||||
case true:
|
|
||||||
value = this.settings.checked;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case false:
|
|
||||||
value = this.settings.unchecked;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case null:
|
|
||||||
value = this.settings.indeterminate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return typeof value === 'undefined'? this.element.attr('value') : value;
|
|
||||||
} else {
|
|
||||||
var state = this._parseValue(value);
|
|
||||||
if (typeof state !== 'undefined') {
|
|
||||||
this.settings.state = state;
|
|
||||||
this._refresh(this.settings.change);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$.fn[pluginName] = function (options, value) {
|
|
||||||
var result = this;
|
|
||||||
|
|
||||||
this.each(function() {
|
|
||||||
if (!$.data(this, "plugin.vanderlee." + pluginName)) {
|
|
||||||
$.data(this, "plugin.vanderlee." + pluginName, new Plugin(this, options));
|
|
||||||
} else if (typeof options === 'string') {
|
|
||||||
if (typeof value === 'undefined') {
|
|
||||||
result = $(this).data("plugin.vanderlee." + pluginName)[options]();
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
$(this).data("plugin.vanderlee." + pluginName)[options](value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Overwrite fn.val
|
|
||||||
$.fn.val = function(value) {
|
|
||||||
var data = this.data("vanderlee." + pluginName);
|
|
||||||
if (typeof data === 'undefined') {
|
|
||||||
if (typeof value === 'undefined') {
|
|
||||||
return valFunction.call(this);
|
|
||||||
} else {
|
|
||||||
return valFunction.call(this, value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (typeof value === 'undefined') {
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
this.data("vanderlee." + pluginName, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// :indeterminate pseudo selector
|
|
||||||
$.expr.filters.indeterminate = function(element) {
|
|
||||||
var $element = $(element);
|
|
||||||
return typeof $element.data("vanderlee." + pluginName) !== 'undefined' && $element.prop('indeterminate');
|
|
||||||
};
|
|
||||||
|
|
||||||
// :determinate pseudo selector
|
|
||||||
$.expr.filters.determinate = function(element) {
|
|
||||||
return !($.expr.filters.indeterminate(element));
|
|
||||||
};
|
|
||||||
|
|
||||||
// :tristate selector
|
|
||||||
$.expr.filters.tristate = function(element) {
|
|
||||||
return typeof $(element).data("vanderlee." + pluginName) !== 'undefined';
|
|
||||||
};
|
|
||||||
})(jQuery);
|
|
|
@ -1,49 +1,37 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import "./lib/jquery.tristate"
|
import TristateCheckbox from "./lib/TristateCheckbox";
|
||||||
|
|
||||||
class TristateHelper {
|
class TristateHelper {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.registerTriStateCheckboxes();
|
this.registerTriStateCheckboxes();
|
||||||
this.registerSubmitHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
registerSubmitHandler() {
|
|
||||||
document.addEventListener("turbo:submit-start", (e) => {
|
|
||||||
var form = e.detail.formSubmission.formElement;
|
|
||||||
var formData = e.detail.formSubmission.formData;
|
|
||||||
|
|
||||||
var $tristate_checkboxes = $('.tristate:checkbox', form);
|
|
||||||
|
|
||||||
//Iterate over each tristate checkbox in the form and set formData to the correct value
|
|
||||||
$tristate_checkboxes.each(function() {
|
|
||||||
var $checkbox = $(this);
|
|
||||||
var state = $checkbox.tristate('state');
|
|
||||||
|
|
||||||
formData.set($checkbox.attr('name'), state);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerTriStateCheckboxes() {
|
registerTriStateCheckboxes() {
|
||||||
//Initialize tristate checkboxes and if needed the multicheckbox functionality
|
//Initialize tristate checkboxes and if needed the multicheckbox functionality
|
||||||
|
|
||||||
const listener = () => {
|
const listener = () => {
|
||||||
$(".tristate").tristate( {
|
|
||||||
checked: "true",
|
const tristates = document.querySelectorAll("input.tristate");
|
||||||
unchecked: "false",
|
|
||||||
indeterminate: "indeterminate",
|
tristates.forEach(tristate => {
|
||||||
|
TristateCheckbox.getInstance(tristate);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.permission_multicheckbox:checkbox').change(function() {
|
|
||||||
//Find the other checkboxes in this row, and change their value
|
|
||||||
var $row = $(this).parents('tr');
|
|
||||||
|
|
||||||
//@ts-ignore
|
//Register multi checkboxes in permission tables
|
||||||
var new_state = $(this).tristate('state');
|
const multicheckboxes = document.querySelectorAll("input.permission_multicheckbox");
|
||||||
|
multicheckboxes.forEach(multicheckbox => {
|
||||||
|
multicheckbox.addEventListener("change", (event) => {
|
||||||
|
const newValue = TristateCheckbox.getInstance(event.target).state;
|
||||||
|
const row = event.target.closest("tr");
|
||||||
|
|
||||||
//@ts-ignore
|
//Find all tristate checkboxes in the same row and set their state to the new value
|
||||||
$('.tristate:checkbox', $row).tristate('state', new_state);
|
const tristateCheckboxes = row.querySelectorAll("input.tristate");
|
||||||
|
tristateCheckboxes.forEach(tristateCheckbox => {
|
||||||
|
TristateCheckbox.getInstance(tristateCheckbox).state = newValue;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue