diff --git a/assets/datagrid.ts b/assets/datagrid.ts index de36fcaa..393d787a 100644 --- a/assets/datagrid.ts +++ b/assets/datagrid.ts @@ -78,8 +78,7 @@ export class Datagrid extends EventTarget { this.ajax.addEventListener("success", ({detail: {payload}}) => { // todo: maybe move? if (payload._datagrid_name && payload._datagrid_name === this.name) { - this.el.querySelector("[data-datagrid-reset-filter-by-column]") - ?.classList.add("hidden"); + this.el.querySelectorAll("[data-datagrid-reset-filter-by-column]").forEach( el => el.classList.add("hidden")) if (payload.non_empty_filters && payload.non_empty_filters.length >= 1) { const resets = Array.from(this.el.querySelectorAll( @@ -90,9 +89,8 @@ export class Datagrid extends EventTarget { "data-datagrid-reset-filter-by-column" ) - /// tf? for (const columnName of payload.non_empty_filters) { - resets.find(getColumnName)?.classList.remove("hidden"); + resets.find(el => getColumnName(el) === columnName)?.classList.remove("hidden"); } const href = this.el.querySelector(".reset-filter") diff --git a/assets/integrations/happy.ts b/assets/integrations/happy.ts index fb09196b..02d5ac70 100644 --- a/assets/integrations/happy.ts +++ b/assets/integrations/happy.ts @@ -6,6 +6,8 @@ import { happyStyles } from "../css/happy.css"; export class Happy { private colors: string[] = ["primary", "success", "info", "warning", "danger", "white", "gray"]; + private sheet :null|CSSStyleSheet = null; + private templates = { radio: '
', checkbox: @@ -15,8 +17,9 @@ export class Happy { }; init() { - if (!document.querySelector('[data-happy-stylesheet]')) { - document.head.append(``) + + if(!this.sheet) { + this.firstInit() } this.removeBySelector(".happy-radio"); this.removeBySelector(".happy-checkbox"); @@ -25,6 +28,24 @@ export class Happy { this.initCheckbox(); } + firstInit(){ + const styleElement = new CSSStyleSheet(); + styleElement.replaceSync(happyStyles); + document.adoptedStyleSheets.push(styleElement); + this.sheet = styleElement; + + /** + * The first init call, adds handlers for fancy looking checkboxes. The callback neds only be added once. + */ + document.addEventListener("click", this.checkCheckboxStateOnClick.bind(this)); + document.addEventListener("change", this.checkCheckboxStateOnChange.bind(this)); + + /** + * Set aciton functionality for native change of radios + */ + document.addEventListener("change", this.radioOnChange.bind(this)); + } + /** * @deprecated */ @@ -90,15 +111,11 @@ export class Happy { * Init state */ this.checkRadioState(input); - - /** - * Set aciton functionality for native change - */ - document.addEventListener("change", this.radioOnChange.bind(this)); }); } initCheckbox() { + document.querySelectorAll("input[type=checkbox].happy").forEach(input => { /** * Paste happy component into html @@ -123,12 +140,6 @@ export class Happy { * Init state */ this.checkCheckboxState(input); - - /** - * Set action functionality for click || native change - */ - document.addEventListener("click", this.checkCheckboxStateOnClick.bind(this)); - document.addEventListener("change", this.checkCheckboxStateOnChange.bind(this)); }); } @@ -144,7 +155,6 @@ export class Happy { : target instanceof SVGGraphicsElement ? target.closest("svg")?.parentNode : target; - if (!(happyInput instanceof HTMLElement) || !happyInput.classList.contains("happy-checkbox")) { return; } @@ -155,14 +165,17 @@ export class Happy { const value = happyInput.getAttribute("data-value"); const input = document.querySelector( - `.happy-checkbox[data-name="${name}"]` + (!!value ? `[value="${value}"]` : "") + `input[type=checkbox].happy[name="${name}"]` + (!!value ? `[value="${value}"]` : "") ); if (!(input instanceof HTMLInputElement)) return; const checked = happyInput.classList.contains("active"); - - input.checked = !checked; checked ? happyInput.classList.remove("active") : happyInput.classList.add("active"); + if(input.checked !== !checked){ + const evt = new Event('change',{bubbles: false, cancelable: true,composed: false}); + input.checked = !checked; + input.dispatchEvent(evt); + } } checkCheckboxStateOnChange({target}: Event) { diff --git a/assets/integrations/tom-select.ts b/assets/integrations/tom-select.ts index 6e3594bb..6cd51521 100644 --- a/assets/integrations/tom-select.ts +++ b/assets/integrations/tom-select.ts @@ -14,9 +14,17 @@ export class TomSelect implements Selectpicker { const Select = this.select ?? window()?.TomSelect ?? null; if (Select) { - elements.forEach(element => new Select( + + elements.forEach(element => { + if(element.hasAttribute('data-Tom-Initialised')){ + return; + } + element.setAttribute('data-Tom-Initialised','true'); + + new Select( element as TomInput, typeof this.opts === "function" ? this.opts(element) : this.opts) + } ); } } diff --git a/assets/plugins/features/autosubmit.ts b/assets/plugins/features/autosubmit.ts index 96aa4310..85788153 100644 --- a/assets/plugins/features/autosubmit.ts +++ b/assets/plugins/features/autosubmit.ts @@ -30,10 +30,10 @@ export class AutosubmitPlugin implements DatagridPlugin { if (!inputEl) { inputEl = pageSelectEl.parentElement?.querySelector("button[type=submit]"); } - console.log({ inputEl }); + //console.log({ inputEl }); if (!(inputEl instanceof HTMLElement)) return; const form = inputEl.closest('form'); - console.log({ form }); + //console.log({ form }); form && datagrid.ajax.submitForm(form); }); }); diff --git a/assets/plugins/features/checkboxes.ts b/assets/plugins/features/checkboxes.ts index 5003fa17..16b2556a 100644 --- a/assets/plugins/features/checkboxes.ts +++ b/assets/plugins/features/checkboxes.ts @@ -1,19 +1,49 @@ -import { DatagridPlugin } from "../../types"; -import { Datagrid } from "../.."; +import {DatagridPlugin} from "../../types"; +import {Datagrid} from "../.."; export const CheckboxAttribute = "data-check"; export class CheckboxPlugin implements DatagridPlugin { + + private wasInit: Array = []; + + loadCheckboxCount(datagrid: Datagrid) { + const counter = document.querySelector(".datagrid-selected-rows-count"); + const total = Array.from(datagrid.el.querySelectorAll(`input[data-check='${datagrid.name}']`)).filter(c => !c.hasAttribute("data-check-all")); + const checked = total.filter(e => (e.checked)); + if (counter) { + counter.innerText = `${checked.length}/${total.length}`; + } + document.querySelectorAll( + ".row-group-actions *[type='submit']" + ).forEach(button => { + button.disabled = checked.length === 0; + }); + const select = datagrid.el.querySelector("select[name='group_action[group_action]']"); + if (select) { + select.disabled = checked.length === 0; + } + } + onDatagridInit(datagrid: Datagrid): boolean { - let lastCheckbox = null; + if (!this.wasInit.includes(datagrid)) { + datagrid.ajax.addEventListener('complete', () => { + this.onDatagridInit(datagrid) + }); + this.wasInit.push(datagrid); + this.loadCheckboxCount(datagrid); + } + this.loadCheckboxCount(datagrid); - datagrid.el.addEventListener("click", e => { - if (!(e.target instanceof HTMLElement)) return; + let lastCheckbox: null | HTMLElement = null; + datagrid.el.addEventListener("click", e => { + if (!(e.target instanceof HTMLElement)) { + return; + } if (e.target.classList.contains("col-checkbox")) { - lastCheckbox = e.target; if (e.shiftKey && lastCheckbox) { - const currentCheckboxRow = lastCheckbox.closest("tr"); + const currentCheckboxRow = e.target.closest("tr"); if (!currentCheckboxRow) return; const lastCheckboxRow = lastCheckbox.closest("tr"); @@ -23,68 +53,74 @@ export class CheckboxPlugin implements DatagridPlugin { if (!lastCheckboxTbody) return; const checkboxesRows = Array.from(lastCheckboxTbody.querySelectorAll("tr")); - const [start, end] = [lastCheckboxRow.rowIndex, currentCheckboxRow.rowIndex].sort(); + const headerRows = Array.from(lastCheckboxTbody.closest('table')?.querySelectorAll("thead tr") ?? []).length; + + const [start, end] = [lastCheckboxRow.rowIndex -headerRows, currentCheckboxRow.rowIndex -headerRows].sort(); const rows = checkboxesRows.slice(start, end + 1); rows.forEach(row => { const input = row.querySelector('.col-checkbox input[type="checkbox"]'); if (input) { - input.checked = true; + if (!input.checked) { + input.checked = true; + input.dispatchEvent(new Event('change', {bubbles: true})) + } + } }); } + lastCheckbox = e.target; } }); + let checkboxes = datagrid.el.querySelectorAll(`input[data-check='${datagrid.name}']`); - const select = datagrid.el.querySelector("select[name='group_action[group_action]']"); - const actionButtons = document.querySelectorAll( - ".row-group-actions *[type='submit']" - ); - const counter = document.querySelector(".datagrid-selected-rows-count"); // Handling a checkbox click + select all checkbox + let notUserInvoked = false; checkboxes.forEach(checkEl => { checkEl.addEventListener("change", () => { // Select all const isSelectAll = checkEl.hasAttribute("data-check-all"); if (isSelectAll) { - if (datagrid.name !== checkEl.getAttribute("data-check-all")) return; - - checkboxes.forEach(checkbox => (checkbox.checked = checkEl.checked)); - - // todo: refactor not to repeat this code twice - actionButtons.forEach(button => (button.disabled = !checkEl.checked)); - - if (select) { - select.disabled = !checkEl.checked; - } - - if (counter) { - const total = Array.from(checkboxes).filter(c => !c.hasAttribute("data-check-all")).length; - counter.innerText = `${checkEl.checked ? total : 0}/${total}`; + if (notUserInvoked) { + return; } + if (datagrid.name !== checkEl.getAttribute("data-check-all")) return; + const targetCheck = checkEl.checked;//this is vital as it gets swithced around due to not all being checked just yet. + checkboxes.forEach(checkbox => { + if (checkbox !== checkEl && checkbox.checked !== targetCheck && !checkbox.hasAttribute("data-check-all")) { + checkbox.checked = targetCheck; + //this will end up calling this callback a lot. But it needs to eb done as otherwise the happy checkboxes fail horribly. + //Bubbles is needed as the happy callback catches on document + notUserInvoked = true;//prevent nesting + checkbox.dispatchEvent(new Event('change', {bubbles: true})); + notUserInvoked = false; + } + }); return; - } else { - const selectAll = datagrid.el.querySelector(`input[data-check='${datagrid.name}'][data-check-all]`); - if (selectAll) { - selectAll.checked = Array.from(checkboxes).filter(c => !c.hasAttribute("data-check-all")).every(c => c.checked); - } } - const checkedBoxes = Array.from(checkboxes).filter(checkbox => checkbox.checked && !checkEl.hasAttribute("data-check-all")); - const hasChecked = checkedBoxes.length >= 1; - - actionButtons.forEach(button => (button.disabled = !hasChecked)); - - if (select) { - select.disabled = !hasChecked; - } - - if (counter) { - counter.innerText = `${checkedBoxes.length}/${checkboxes.length}`; + const selectAll = datagrid.el.querySelectorAll(`input[data-check='${datagrid.name}'][data-check-all]`); + if (selectAll.length > 0) { + const allChecked = Array.from(checkboxes).filter(c => !c.hasAttribute("data-check-all")).every(c => c.checked); + if (allChecked != selectAll.checked) { + selectAll.forEach(el => { + if(el.hasAttribute("data-override-check-all")){ + return; + } + notUserInvoked = true; + el.checked = allChecked; + el.dispatchEvent(new Event('change', {bubbles: true})); + notUserInvoked = false; + }) + } } }); + + checkEl.addEventListener("change", () => { + this.loadCheckboxCount(datagrid); + }) }); return true; diff --git a/assets/plugins/features/confirm.ts b/assets/plugins/features/confirm.ts index a3436d73..d0e505ed 100644 --- a/assets/plugins/features/confirm.ts +++ b/assets/plugins/features/confirm.ts @@ -11,13 +11,20 @@ export class ConfirmPlugin implements DatagridPlugin { confirmEl.addEventListener("click", e => this.confirmEventHandler.bind(datagrid)(e.target as HTMLElement, e)) ); + datagrid.ajax.addEventListener("success", e => + datagrid.el + .querySelectorAll(`[${ConfirmAttribute}]:not(.ajax)`) + .forEach(confirmEl => + confirmEl.addEventListener("click", e => this.confirmEventHandler.bind(datagrid)(e.target as HTMLElement, e)) + ) + ); datagrid.ajax.addEventListener("interact", e => this.confirmEventHandler.bind(datagrid)(e.detail.element, e)); return true; } confirmEventHandler(this: Datagrid, el: HTMLElement, e: Event) { - const message = el.closest('a').getAttribute(ConfirmAttribute)!; + const message = el.closest('a')?.getAttribute(ConfirmAttribute)!; if (!message) return; if (!window.confirm.bind(window)(message)) { diff --git a/assets/plugins/integrations/happy.ts b/assets/plugins/integrations/happy.ts index e7596a8c..fe62cda7 100644 --- a/assets/plugins/integrations/happy.ts +++ b/assets/plugins/integrations/happy.ts @@ -1,13 +1,13 @@ -import { Datagrid } from "../.."; -import { DatagridPlugin } from "../../types"; -import { window } from "../../utils"; -import type { Happy } from "../../integrations"; +import {Datagrid} from "../.."; +import {DatagridPlugin} from "../../types"; +import {window} from "../../utils"; +import type {Happy} from "../../integrations"; export class HappyPlugin implements DatagridPlugin { constructor(private happy?: Happy) { } - onDatagridInit(datagrid: Datagrid): boolean { + doInit(datagrid: Datagrid): boolean { const happy = this.happy ?? window().happy ?? null; if (happy) { @@ -16,4 +16,11 @@ export class HappyPlugin implements DatagridPlugin { return true; } + + onDatagridInit(datagrid: Datagrid): boolean { + datagrid.ajax.addEventListener('complete', (event) => { + this.doInit(datagrid) + }); + return this.doInit(datagrid); + } } diff --git a/assets/plugins/integrations/selectpicker.ts b/assets/plugins/integrations/selectpicker.ts index a95e2af6..8f7a3d7c 100644 --- a/assets/plugins/integrations/selectpicker.ts +++ b/assets/plugins/integrations/selectpicker.ts @@ -5,13 +5,21 @@ export class SelectpickerPlugin implements DatagridPlugin { constructor(private selectpicker: Selectpicker) { } - onDatagridInit(datagrid: Datagrid): boolean { + doInit(datagrid:Datagrid){ const elements = datagrid.el.querySelectorAll(".selectpicker"); - if (elements.length >= 1) { - this.selectpicker.initSelectpickers(Array.from(elements), datagrid); + const filtered = Array.from(elements).filter(el => el instanceof HTMLInputElement || el instanceof HTMLSelectElement); + if(filtered.length >= 1){ + this.selectpicker.initSelectpickers(filtered, datagrid); + } } + } + onDatagridInit(datagrid: Datagrid): boolean { + datagrid.ajax.addEventListener('complete', (event) => { + this.doInit(datagrid); + }); + this.doInit(datagrid); return true; } }