Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
dbba3b8
UX FormCollection
stakovicz May 3, 2021
83ec2ea
Remove hard coded property_name __name__
stakovicz May 4, 2021
d983cf0
PHP CS Fixer
stakovicz May 4, 2021
a35ff32
First jests
stakovicz May 4, 2021
e1d7574
Rename CollectionType > UXCollectionType
stakovicz May 6, 2021
409c8e7
Rename CollectionType > UXCollectionType
stakovicz May 6, 2021
a624f25
DependencyInjection Clean
stakovicz May 6, 2021
f4ba011
Fix .gitattributes
stakovicz May 6, 2021
7741907
Move default values
stakovicz May 7, 2021
66defab
Predefined theme or not
stakovicz May 23, 2021
145b1cb
Update src/FormCollection/README.md
stakovicz May 24, 2021
2b900cc
Update src/FormCollection/README.md
stakovicz May 24, 2021
25c1454
Update src/FormCollection/README.md
stakovicz May 24, 2021
d66250d
Update src/FormCollection/Resources/views/form_theme_div.html.twig
stakovicz May 24, 2021
2db95e0
Update src/FormCollection/README.md
stakovicz May 24, 2021
8f8b5c2
Update src/FormCollection/Resources/views/form_theme_table.html.twig
stakovicz May 24, 2021
4a68faa
Split in 4 options
stakovicz May 24, 2021
a170618
Default startIndex value
stakovicz Jun 6, 2021
13561e7
Update src/FormCollection/Resources/views/form_theme_div.html.twig
stakovicz Jul 21, 2021
fb847c6
Update src/FormCollection/Resources/views/form_theme_div.html.twig
stakovicz Jul 21, 2021
740d19b
Update src/FormCollection/Resources/views/form_theme_table.html.twig
stakovicz Jul 21, 2021
c57d86e
Update src/FormCollection/Resources/views/form_theme_table.html.twig
stakovicz Jul 21, 2021
0bbac2f
Fix coding-style-js
stakovicz Nov 6, 2021
edfe323
Prettier
stakovicz Nov 6, 2021
086c3a3
Rebase and refresh the code
stakovicz May 21, 2022
480dbb5
fix TU
stakovicz May 21, 2022
3223fae
change buttons attr
stakovicz May 21, 2022
911f18f
Move logic from twig files to form types
alexander-schranz Jul 18, 2022
4cfa27d
Fix types and not used imports
alexander-schranz Jul 18, 2022
0976a9e
Allow overriding the data-controller via attr
alexander-schranz Jul 18, 2022
dc18974
Simplify controller integration
alexander-schranz Jul 18, 2022
9ee00dc
Fix handling of nested blocks
alexander-schranz Jul 18, 2022
d294592
Move docs to index.rst
alexander-schranz Jul 18, 2022
4edfefb
Fix add problem
alexander-schranz Jul 19, 2022
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
Prev Previous commit
Next Next commit
Simplify controller integration
  • Loading branch information
alexander-schranz committed Jul 18, 2022
commit dc18974cce25866c2efca0fd9b30eb2e8ae2e9b9
4 changes: 1 addition & 3 deletions src/FormCollection/Form/UXCollectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,9 @@ public function configureOptions(OptionsResolver $resolver): void
};

$entryOptionsNormalizer = function (OptionsResolver $options, $value) {
$value['row_attr']['data-controller-target'] = 'entry';

return [
'row_attr' => [
'data-controller-target' => 'entry',
'data-collection-target' => 'entry',
],
'allow_delete' => $options['allow_delete'],
'delete_options' => $options['delete_options'],
Expand Down
95 changes: 26 additions & 69 deletions src/FormCollection/Resources/assets/dist/controller.js
Original file line number Diff line number Diff line change
@@ -1,105 +1,62 @@
import { Controller } from '@hotwired/stimulus';

class default_1 extends Controller {
class controller extends Controller {
constructor() {
super(...arguments);
this.index = 0;
this.controllerName = 'collection';
}
static get targets() {
return ['entry', 'addButton', 'removeButton'];
}
connect() {
this.controllerName = this.context.scope.identifier;
this.index = this.startIndexValue ? this.startIndexValue : this.entryTargets.length - 1;
if (!this.prototypeNameValue) {
this.prototypeNameValue = '__name__';
}
this._dispatchEvent('form-collection:pre-connect', {
allowAdd: this.allowAddValue,
allowDelete: this.allowDeleteValue,
});
if (this.allowAddValue) {
const buttonAdd = this._textToNode(this.buttonAddValue);
this.element.prepend(buttonAdd);
}
if (this.allowDeleteValue) {
for (let i = 0; i < this.entryTargets.length; i++) {
const entry = this.entryTargets[i];
this._addDeleteButton(entry, i);
}
}
this._dispatchEvent('form-collection:connect', {
allowAdd: this.allowAddValue,
allowDelete: this.allowDeleteValue,
});
this._dispatchEvent('form-collection:pre-connect');
this.index = this.entryTargets.length;
this._dispatchEvent('form-collection:connect');
}
add() {
this.index++;
let newEntry = this.element.dataset.prototype;
if (!newEntry) {
newEntry = this.prototypeValue;
const prototype = this.element.dataset.prototype;
if (!prototype) {
throw new Error('A "data-prototype" attribute was expected on data-controller="' + this.controllerName + '" element.');
}
let regExp = new RegExp(this.prototypeNameValue + 'label__', 'g');
newEntry = newEntry.replace(regExp, this.index);
regExp = new RegExp(this.prototypeNameValue, 'g');
newEntry = newEntry.replace(regExp, this.index);
newEntry = this._textToNode(newEntry);
this._dispatchEvent('form-collection:pre-add', {
prototype: prototype,
index: this.index,
element: newEntry,
});
this.element.append(newEntry);
let entry = this.entryTargets[this.entryTargets.length - 1];
entry = this._addDeleteButton(entry, this.index);
const newEntry = this._textToNode(prototype.replace(/__name__/g, this.index.toString()));
if (this.entryTargets.length > 1) {
this.entryTargets[this.entryTargets.length - 1].after(newEntry);
}
else {
this.element.prepend(newEntry);
}
this._dispatchEvent('form-collection:add', {
prototype: prototype,
index: this.index,
element: entry,
});
this.index++;
}
delete(event) {
const entry = event.target.closest('[data-' + this.controllerName + '-target="entry"]');
const clickTarget = event.target;
const entry = clickTarget.closest('[data-' + this.controllerName + '-target="entry"]');
this._dispatchEvent('form-collection:pre-delete', {
index: entry.dataset.indexEntry,
element: entry,
});
entry.remove();
this._dispatchEvent('form-collection:delete', {
index: entry.dataset.indexEntry,
element: entry,
});
}
_addDeleteButton(entry, index) {
entry.dataset.indexEntry = index.toString();
const buttonDelete = this._textToNode(this.buttonDeleteValue);
if (!buttonDelete) {
return entry;
}
buttonDelete.dataset.indexEntry = index;
if ('TR' === entry.nodeName) {
entry.lastElementChild.append(buttonDelete);
}
else {
entry.append(buttonDelete);
}
return entry;
}
_textToNode(text) {
const template = document.createElement('template');
text = text.trim();
template.innerHTML = text;
return template.content.firstChild;
}
_dispatchEvent(name, payload) {
console.log('TTTT');
this.element.dispatchEvent(new CustomEvent(name, { detail: payload }));
_dispatchEvent(name, payload = {}) {
this.element.dispatchEvent(new CustomEvent(name, { detail: payload, bubbles: true }));
}
}
default_1.targets = ['entry'];
default_1.values = {
allowAdd: Boolean,
allowDelete: Boolean,
buttonAdd: String,
buttonDelete: String,
prototypeName: String,
prototype: String,
startIndex: Number,
};

export { default_1 as default };
export { controller as default };
145 changes: 36 additions & 109 deletions src/FormCollection/Resources/assets/src/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,154 +3,81 @@
import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
static targets = ['entry'];

static values = {
allowAdd: Boolean,
allowDelete: Boolean,
buttonAdd: String,
buttonDelete: String,
prototypeName: String,
prototype: String,
startIndex: Number,
};

allowAddValue: boolean;
allowDeleteValue: boolean;
buttonAddValue: string;
buttonDeleteValue: string;
prototypeNameValue: string;
prototypeValue: string;
startIndexValue: number;

/**
* Number of elements for the index of the collection
*/
index = 0;

controllerName: string;

entryTargets: Array<any> = [];
static get targets() {
return ['entry', 'addButton', 'removeButton'];
}

connect() {
this.controllerName = this.context.scope.identifier;
this.index = this.startIndexValue ? this.startIndexValue : this.entryTargets.length - 1;
declare readonly entryTargets: HTMLElement[];

if (!this.prototypeNameValue) {
this.prototypeNameValue = '__name__';
}
index: Number = 0;
controllerName: string = 'collection';

this._dispatchEvent('form-collection:pre-connect', {
allowAdd: this.allowAddValue,
allowDelete: this.allowDeleteValue,
});
connect() {
this.controllerName = this.context.scope.identifier;

if (this.allowAddValue) {
// Add button Add
const buttonAdd = this._textToNode(this.buttonAddValue);
this.element.prepend(buttonAdd);
}
this._dispatchEvent('form-collection:pre-connect');

// Add buttons Delete
if (this.allowDeleteValue) {
for (let i = 0; i < this.entryTargets.length; i++) {
const entry = this.entryTargets[i];
this._addDeleteButton(entry, i);
}
}
this.index = this.entryTargets.length;

this._dispatchEvent('form-collection:connect', {
allowAdd: this.allowAddValue,
allowDelete: this.allowDeleteValue,
});
this._dispatchEvent('form-collection:connect');
}

add() {
this.index++;
const prototype = this.element.dataset.prototype;

// Compute the new entry
let newEntry = this.element.dataset.prototype;
if (!newEntry) {
newEntry = this.prototypeValue;
if (!prototype) {
throw new Error(
'A "data-prototype" attribute was expected on data-controller="' + this.controllerName + '" element.'
);
}

let regExp = new RegExp(this.prototypeNameValue + 'label__', 'g');
newEntry = newEntry.replace(regExp, this.index);

regExp = new RegExp(this.prototypeNameValue, 'g');
newEntry = newEntry.replace(regExp, this.index);

newEntry = this._textToNode(newEntry);

this._dispatchEvent('form-collection:pre-add', {
prototype: prototype,
index: this.index,
element: newEntry,
});

this.element.append(newEntry);
const newEntry = this._textToNode(prototype.replace(/__name__/g, this.index.toString()));

// Retrieve the entry from targets to make sure that this is the one
let entry = this.entryTargets[this.entryTargets.length - 1];
entry = this._addDeleteButton(entry, this.index);
if (this.entryTargets.length > 1) {
this.entryTargets[this.entryTargets.length - 1].after(newEntry);
} else {
this.element.prepend(newEntry);
}

this._dispatchEvent('form-collection:add', {
prototype: prototype,
index: this.index,
element: entry,
});

this.index++;
}

delete(event) {
const entry = event.target.closest('[data-' + this.controllerName + '-target="entry"]');
delete(event: MouseEvent) {
const clickTarget = event.target as HTMLButtonElement;

const entry = clickTarget.closest('[data-' + this.controllerName + '-target="entry"]') as HTMLElement;

this._dispatchEvent('form-collection:pre-delete', {
index: entry.dataset.indexEntry,
element: entry,
});

entry.remove();

this._dispatchEvent('form-collection:delete', {
index: entry.dataset.indexEntry,
element: entry,
});
}

/**
* Add the delete button to the entry
* @private
*/
_addDeleteButton(entry: HTMLElement, index: number) {
// link the button and the entry by the data-index-entry attribute
entry.dataset.indexEntry = index.toString();

const buttonDelete = this._textToNode(this.buttonDeleteValue);
if (!buttonDelete) {
return entry;
}
buttonDelete.dataset.indexEntry = index;

if ('TR' === entry.nodeName) {
entry.lastElementChild.append(buttonDelete);
} else {
entry.append(buttonDelete);
}

return entry;
}

/**
* Convert text to Element to insert in the DOM
* @private
*/
_textToNode(text: string) {
_textToNode(text: string): HTMLElement {
const template = document.createElement('template');
text = text.trim(); // Never return a text node of whitespace as the result
text = text.trim();

template.innerHTML = text;

return template.content.firstChild;
return template.content.firstChild as HTMLElement;
}

_dispatchEvent(name: string, payload: any) {
this.element.dispatchEvent(new CustomEvent(name, { detail: payload }));
_dispatchEvent(name: string, payload: {} = {}) {
this.element.dispatchEvent(new CustomEvent(name, { detail: payload, bubbles: true }));
}
}