Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bugs with selectors #1164

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 30 additions & 17 deletions assets/integrations/happy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<div class="happy-radio"><b></b></div>',
checkbox:
Expand All @@ -15,8 +17,9 @@ export class Happy {
};

init() {
if (!document.querySelector('[data-happy-stylesheet]')) {
document.head.append(`<style data-happy-stylesheet>${happyStyles}</style>`)

if(!this.sheet) {
this.firstInit()
}
this.removeBySelector(".happy-radio");
this.removeBySelector(".happy-checkbox");
Expand All @@ -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
*/
Expand Down Expand Up @@ -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<HTMLInputElement>("input[type=checkbox].happy").forEach(input => {
/**
* Paste happy component into html
Expand All @@ -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));
});
}

Expand All @@ -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;
}
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions assets/plugins/features/autosubmit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Expand Down
119 changes: 75 additions & 44 deletions assets/plugins/features/checkboxes.ts
Original file line number Diff line number Diff line change
@@ -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<Datagrid> = [];

loadCheckboxCount(datagrid: Datagrid) {
const counter = document.querySelector<HTMLElement>(".datagrid-selected-rows-count");
const total = Array.from(datagrid.el.querySelectorAll<HTMLInputElement>(`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<HTMLInputElement | HTMLButtonElement>(
".row-group-actions *[type='submit']"
).forEach(button => {
button.disabled = checked.length === 0;
});
const select = datagrid.el.querySelector<HTMLSelectElement>("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");
Expand All @@ -23,68 +53,69 @@ export class CheckboxPlugin implements DatagridPlugin {
if (!lastCheckboxTbody) return;

const checkboxesRows = Array.from(lastCheckboxTbody.querySelectorAll<HTMLElement>("tr"));
const [start, end] = [lastCheckboxRow.rowIndex, currentCheckboxRow.rowIndex].sort();
const headerRows = Array.from(lastCheckboxTbody.closest('table')?.querySelectorAll<HTMLElement>("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<HTMLInputElement>('.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<HTMLInputElement>(`input[data-check='${datagrid.name}']`);
const select = datagrid.el.querySelector<HTMLSelectElement>("select[name='group_action[group_action]']");
const actionButtons = document.querySelectorAll<HTMLInputElement | HTMLButtonElement>(
".row-group-actions *[type='submit']"
);
const counter = document.querySelector<HTMLElement>(".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.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<HTMLInputElement>(`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.querySelector<HTMLInputElement>(`input[data-check='${datagrid.name}'][data-check-all]`);
if (selectAll) {
const allChecked = Array.from(checkboxes).filter(c => !c.hasAttribute("data-check-all")).every(c => c.checked);
if (allChecked != selectAll.checked) {
notUserInvoked = true;
selectAll.checked = allChecked;
selectAll.dispatchEvent(new Event('change', {bubbles: true}));
notUserInvoked = false;
}
}
});

checkEl.addEventListener("change", () => {
this.loadCheckboxCount(datagrid);
})
});

return true;
Expand Down
2 changes: 1 addition & 1 deletion assets/plugins/features/confirm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class ConfirmPlugin implements DatagridPlugin {
}

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)) {
Expand Down
17 changes: 12 additions & 5 deletions assets/plugins/integrations/happy.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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);
}
}