Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
simplify button customization
  • Loading branch information
dunglas committed Jul 20, 2022
commit e141e33727189626a3cc63ca90244e6ec7bb593f
41 changes: 23 additions & 18 deletions src/Collection/Resources/assets/dist/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,24 @@ class default_1 extends Controller {
}
createButton(collectionEl, buttonType) {
var _a;
const attributeName = `${ButtonType[buttonType].toLowerCase()}ButtonTemplateId`;
const buttonTemplateID = (_a = collectionEl.dataset[attributeName]) !== null && _a !== void 0 ? _a : this[`${attributeName}Value`];
if (buttonTemplateID && 'content' in document.createElement('template')) {
const buttonTemplate = document.getElementById(buttonTemplateID);
if (!buttonTemplate)
throw new Error(`template with ID "${buttonTemplateID}" not found`);
const fragment = buttonTemplate.content.cloneNode(true);
if (1 !== fragment.children.length)
throw new Error('template with ID "${buttonTemplateID}" must have exactly one child');
return fragment.firstElementChild;
const attributeName = `${ButtonType[buttonType].toLowerCase()}Button`;
const button = (_a = collectionEl.dataset[attributeName]) !== null && _a !== void 0 ? _a : this.element.dataset[attributeName];
console.log(button);
if ('' === button)
return null;
if (undefined === button || !('content' in document.createElement('template'))) {
const button = document.createElement('button');
button.type = 'button';
button.textContent = ButtonType[buttonType];
return button;
}
const button = document.createElement('button');
button.type = 'button';
button.textContent = buttonType === ButtonType.Add ? 'Add' : 'Delete';
return button;
const buttonTemplate = document.getElementById(button);
if (!buttonTemplate)
throw new Error(`template with ID "${buttonTemplate}" not found`);
const fragment = buttonTemplate.content.cloneNode(true);
if (1 !== fragment.children.length)
throw new Error('template with ID "${buttonTemplateID}" must have exactly one child');
return fragment.firstElementChild;
}
addItem(collectionEl) {
const currentIndex = collectionEl.dataset.currentIndex;
Expand All @@ -57,6 +60,8 @@ class default_1 extends Controller {
}
addAddButton(collectionEl) {
const addButton = this.createButton(collectionEl, ButtonType.Add);
if (!addButton)
return;
addButton.onclick = (e) => {
e.preventDefault();
this.addItem(collectionEl);
Expand All @@ -65,6 +70,8 @@ class default_1 extends Controller {
}
addDeleteButton(collectionEl, itemEl) {
const deleteButton = this.createButton(collectionEl, ButtonType.Delete);
if (!deleteButton)
return;
deleteButton.onclick = (e) => {
e.preventDefault();
itemEl.remove();
Expand All @@ -73,10 +80,8 @@ class default_1 extends Controller {
}
}
default_1.values = {
addButtonTemplateId: "",
disableAddButton: false,
deleteButtonTemplateId: "",
disableDeleteButton: false,
addButton: '',
deleteButton: '',
};

export { default_1 as default };
53 changes: 29 additions & 24 deletions src/Collection/Resources/assets/src/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ interface CollectionDataset extends DOMStringMap {
prototype: string;
currentIndex: string;
itemsSelector?: string;
addButtonTemplateId?: string;
disableAddButton?: string;
deleteButtonTemplateId?: string;
disableDeleteButton?: string;
addButton?: string;
deleteButton?: string;
}

enum ButtonType {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a const enum instead, to inline the add and remove string in the emitted JS code instead of emitting an enum object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the code and using const enum isn't possible anymore (IIRC, const enum causes various issues anyway).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another alternative is to use a tagged union 'add' | 'remove', which will then produce more efficient JS code.

Expand All @@ -19,10 +17,8 @@ enum ButtonType {

export default class extends Controller {
static values = {
addButtonTemplateId: '',
disableAddButton: false,
deleteButtonTemplateId: '',
disableDeleteButton: false,
addButton: '',
deleteButton: '',
};

connect() {
Expand All @@ -45,27 +41,32 @@ export default class extends Controller {
return collectionElement.querySelectorAll(collectionElement.dataset.itemsSelector || DEFAULT_ITEMS_SELECTOR);
}

createButton(collectionEl: HTMLElement, buttonType: ButtonType): HTMLElement {
const attributeName = `${ButtonType[buttonType].toLowerCase()}ButtonTemplateId`;
const buttonTemplateID = collectionEl.dataset[attributeName] ?? (this as any)[`${attributeName}Value`];
if (buttonTemplateID && 'content' in document.createElement('template')) {
// Get from template
const buttonTemplate = document.getElementById(buttonTemplateID) as HTMLTemplateElement | null;
if (!buttonTemplate) throw new Error(`template with ID "${buttonTemplateID}" not found`);
createButton(collectionEl: HTMLElement, buttonType: ButtonType): HTMLElement | null {
const attributeName = `${ButtonType[buttonType].toLowerCase()}Button`;
const button = collectionEl.dataset[attributeName] ?? (this.element as HTMLElement).dataset[attributeName];
console.log(button);

const fragment = buttonTemplate.content.cloneNode(true) as DocumentFragment;
if (1 !== fragment.children.length)
throw new Error('template with ID "${buttonTemplateID}" must have exactly one child');
// Button explicitly disabled through data attribute
if ('' === button) return null;

return fragment.firstElementChild as HTMLElement;
// No data attribute provided or <template> not supported: create raw HTML button
if (undefined === button || !('content' in document.createElement('template'))) {
const button = document.createElement('button') as HTMLButtonElement;
button.type = 'button';
button.textContent = ButtonType[buttonType];

return button;
}

// If no template is provided, create a raw HTML button
const button = document.createElement('button') as HTMLButtonElement;
button.type = 'button';
button.textContent = buttonType === ButtonType.Add ? 'Add' : 'Delete';
// Use the template referenced by the data attribute
const buttonTemplate = document.getElementById(button) as HTMLTemplateElement | null;
if (!buttonTemplate) throw new Error(`template with ID "${buttonTemplate}" not found`);

const fragment = buttonTemplate.content.cloneNode(true) as DocumentFragment;
if (1 !== fragment.children.length)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could use fragment.childElementCount

throw new Error('template with ID "${buttonTemplateID}" must have exactly one child');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to use backticks, not single quotes. Otherwise the interpolation won't work.


return button;
return fragment.firstElementChild as HTMLElement;
}

addItem(collectionEl: HTMLElement) {
Expand All @@ -92,6 +93,8 @@ export default class extends Controller {

addAddButton(collectionEl: HTMLElement) {
const addButton = this.createButton(collectionEl, ButtonType.Add);
if (!addButton) return;

addButton.onclick = (e) => {
e.preventDefault();
this.addItem(collectionEl);
Expand All @@ -101,6 +104,8 @@ export default class extends Controller {

addDeleteButton(collectionEl: HTMLElement, itemEl: HTMLElement) {
const deleteButton = this.createButton(collectionEl, ButtonType.Delete);
if (!deleteButton) return;

deleteButton.onclick = (e) => {
e.preventDefault();
itemEl.remove();
Expand Down
26 changes: 22 additions & 4 deletions src/Collection/Tests/app/templates/template_form.html.twig
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
{% extends 'base.html.twig' %}

{% block stylesheets %}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
<style>
fieldset {
padding: 2em;
border: solid;
}
</style>
{% block javascripts %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>{% endblock %}
{% endblock %}

{% form_theme form 'bootstrap_5_layout.html.twig' %}

{% block body %}
<main class="container">
<h1>Templated buttons</h1>

<template id="addButton">
<button type="button">My add button</button>
<button type="button" class="btn btn-secondary">My add button</button>
</template>

<template id="deleteButton">
<button type="button">My delete button</button>
<button type="button" class="btn btn-danger">My delete button</button>
</template>

{{ form_start(form, {'attr': {
'data-controller': 'symfony--ux-collection--collection',
'data-symfony--ux-collection--collection-add-button-template-id-value': 'addButton',
'data-symfony--ux-collection--collection-delete-button-template-id-value': 'deleteButton',
'data-add-button': 'addButton',
'data-delete-button': 'deleteButton',
}}) }}
{{ form(form) }}
{{ form_end(form) }}
</main>
{% endblock %}