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
Predefined theme or not
  • Loading branch information
stakovicz authored and alexander-schranz committed Jul 18, 2022
commit 66defabee2b376faadecfed81520fc701b4c210e
46 changes: 42 additions & 4 deletions src/FormCollection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ yarn encore dev
Also make sure you have at least version 2.0 of [@symfony/stimulus-bridge](https://github.com/symfony/stimulus-bridge)
in your `package.json` file.

## Use predefined theme

You need to select the right theme from the one you are using :
```yaml
# config/packages/twig.yaml
Expand All @@ -33,6 +35,42 @@ You have 2 different themes :

[Check the Symfony doc](https://symfony.com/doc/4.4/form/form_themes.html) for the different ways to set themes in Symfony.

## Use manual theming

> Consider your `BlogFormType` form set up and with a comments field that is a `CollectionType`, you can
render it in your template:

```twig
{% macro commentFormRow(commentForm) %}
<div
class="col-4"
data-symfony--ux-form-collection--collection-target="entry"
>
{{ form_errors(commentForm) }}
{{ form_row(commentForm.content) }}
{{ form_row(commentForm.otherField) }}

<button type="button" data-action="symfony--ux-form-collection--collection#delete">
Remove
</button>
</div>
{% endmacro %}

<div
class="row"
data-controller="symfony--ux-form-collection--collection"
data-prototype="{{ _self.commentFormRow(form.comments.vars.prototype)|e }}"
>
{% for commentForm in form.comments %}
{{ _self.commentFormRow(commentForm) }}
{% endfor %}

<button type="button" data-action="symfony--ux-form-collection--collection#add">
Add Another
</button>
</div>
```

## Usage

The most common usage of Form Collection is to use it as a replacement of
Expand All @@ -51,15 +89,15 @@ class BlogFormType extends AbstractType
->add('comments', UXCollectionType::class, [
// ...
'button_add' => [
// Default text for the add button
// Default text for the add button (used by predefined theme)
'text' => 'Add',
// Add HTML classes to the add button
// Add HTML classes to the add button (used by predefined theme)
'class' => 'btn btn-outline-primary'
],
'button_delete' => [
// Default text for the delete button
// Default text for the delete button (used by predefined theme)
'text' => 'Remove',
// Add HTML classes to the add button
// Add HTML classes to the add button (used by predefined theme)
'class' => 'btn btn-outline-secondary'
],
])
Expand Down
54 changes: 31 additions & 23 deletions src/FormCollection/Resources/assets/dist/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ var _default = /*#__PURE__*/function (_Controller) {
key: "connect",
value: function connect() {
this.controllerName = this.context.scope.identifier;
this.index = this.entryTargets.length - 1;

if (!this.prototypeNameValue) {
this.prototypeNameValue = '__name__';
}

this._dispatchEvent('form-collection:pre-connect', {
allowAdd: this.allowAddValue,
Expand All @@ -68,16 +73,15 @@ var _default = /*#__PURE__*/function (_Controller) {
// Add button Add
var buttonAdd = this._textToNode(this.buttonAddValue);

this.containerTarget.prepend(buttonAdd);
this.element.prepend(buttonAdd);
} // Add buttons Delete


if (true === this.allowDeleteValue) {
for (var i = 0; i < this.entryTargets.length; i++) {
this.index = i;
var entry = this.entryTargets[i];

this._addDeleteButton(entry, this.index);
this._addDeleteButton(entry, i);
}
}

Expand All @@ -91,7 +95,12 @@ var _default = /*#__PURE__*/function (_Controller) {
value: function add(event) {
this.index++; // Compute the new entry

var newEntry = this.containerTarget.dataset.prototype;
var newEntry = this.element.dataset.prototype;

if (!newEntry) {
newEntry = this.prototypeValue;
}

var regExp = new RegExp(this.prototypeNameValue + 'label__', 'g');
newEntry = newEntry.replace(regExp, this.index);
regExp = new RegExp(this.prototypeNameValue, 'g');
Expand All @@ -103,7 +112,7 @@ var _default = /*#__PURE__*/function (_Controller) {
element: newEntry
});

this.containerTarget.append(newEntry); // Retrieve the entry from targets to make sure that this is the one
this.element.append(newEntry); // Retrieve the entry from targets to make sure that this is the one

var entry = this.entryTargets[this.entryTargets.length - 1];
entry = this._addDeleteButton(entry, this.index);
Expand All @@ -116,25 +125,19 @@ var _default = /*#__PURE__*/function (_Controller) {
}, {
key: "delete",
value: function _delete(event) {
var theIndexEntryToDelete = event.target.dataset.indexEntry; // Search the entry to delete from the data-index-entry attribute

for (var i = 0; i < this.entryTargets.length; i++) {
var entry = this.entryTargets[i];
var entry = event.target.closest('[data-' + this.controllerName + '-target="entry"]');

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

entry.remove();
entry.remove();

this._dispatchEvent('form-collection:delete', {
index: entry.dataset.indexEntry,
element: entry
});
}
}
this._dispatchEvent('form-collection:delete', {
index: entry.dataset.indexEntry,
element: entry
});
}
/**
* Add the delete button to the entry
Expand All @@ -152,6 +155,10 @@ var _default = /*#__PURE__*/function (_Controller) {

var buttonDelete = this._textToNode(this.buttonDeleteValue);

if (!buttonDelete) {
return entry;
}

buttonDelete.dataset.indexEntry = index;

if ('TR' === entry.nodeName) {
Expand Down Expand Up @@ -195,12 +202,13 @@ var _default = /*#__PURE__*/function (_Controller) {

exports["default"] = _default;

_defineProperty(_default, "targets", ['container', 'entry']);
_defineProperty(_default, "targets", ['entry']);

_defineProperty(_default, "values", {
allowAdd: Boolean,
allowDelete: Boolean,
buttonAdd: String,
buttonDelete: String,
prototypeName: String
prototypeName: String,
prototype: String
});
59 changes: 30 additions & 29 deletions src/FormCollection/Resources/assets/src/controller.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
'use strict';

import { Controller } from 'stimulus';
import {Controller} from 'stimulus';

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

Expand All @@ -13,7 +12,8 @@ export default class extends Controller {
allowDelete: Boolean,
buttonAdd: String,
buttonDelete: String,
prototypeName: String
prototypeName: String,
prototype: String
};

/**
Expand All @@ -30,7 +30,11 @@ export default class extends Controller {

connect() {
this.controllerName = this.context.scope.identifier;
this.index = this.entryTargets.length - 1;

if (!this.prototypeNameValue) {
this.prototypeNameValue = '__name__';
}

this._dispatchEvent('form-collection:pre-connect', {
allowAdd: this.allowAddValue,
Expand All @@ -40,15 +44,14 @@ export default class extends Controller {
if (true === this.allowAddValue) {
// Add button Add
let buttonAdd = this._textToNode(this.buttonAddValue);
this.containerTarget.prepend(buttonAdd);
this.element.prepend(buttonAdd);
}

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

Expand All @@ -63,9 +66,12 @@ export default class extends Controller {
this.index++;

// Compute the new entry
let newEntry = this.containerTarget.dataset.prototype;

let regExp = new RegExp(this.prototypeNameValue+'label__', 'g');
let newEntry = this.element.dataset.prototype;
if (!newEntry) {
newEntry = this.prototypeValue;
}

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

regExp = new RegExp(this.prototypeNameValue, 'g');
Expand All @@ -78,7 +84,7 @@ export default class extends Controller {
element: newEntry
});

this.containerTarget.append(newEntry);
this.element.append(newEntry);

// Retrieve the entry from targets to make sure that this is the one
let entry = this.entryTargets[this.entryTargets.length - 1];
Expand All @@ -91,27 +97,19 @@ export default class extends Controller {
}

delete(event) {
let entry = event.target.closest('[data-' + this.controllerName + '-target="entry"]');

let theIndexEntryToDelete = event.target.dataset.indexEntry;

// Search the entry to delete from the data-index-entry attribute
for (let i = 0; i < this.entryTargets.length; i++) {
let entry = this.entryTargets[i];
if (theIndexEntryToDelete === entry.dataset.indexEntry) {

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

entry.remove();
entry.remove();

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

/**
Expand All @@ -127,9 +125,12 @@ export default class extends Controller {
entry.dataset.indexEntry = index;

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

if('TR' === entry.nodeName) {
if ('TR' === entry.nodeName) {
entry.lastElementChild.append(buttonDelete);
} else {
entry.append(buttonDelete);
Expand Down
28 changes: 14 additions & 14 deletions src/FormCollection/Resources/views/form_theme_div.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,31 @@
{# attr for the data target on the entry of the collection #}
{%- set attrDataTarget = {('data-' ~ controllerName ~ '-target'): 'entry' } -%}

{% if prototype is defined and not prototype.rendered %}
{%- set prototype_attr = prototype.vars.attr|merge(attrDataTarget) -%}
{%- set attr = attr|merge({'data-prototype': form_row(prototype, {'row_attr': prototype_attr}) }) -%}
{% endif %}
{%- set attr = attr|merge({('data-' ~ controllerName ~ '-target'): 'container' }) -%}

<div data-controller="{{ dataController }}"
data-{{ controllerName }}-allow-add-value="{{ allow_add|json_encode }}"
data-{{ controllerName }}-allow-delete-value="{{ allow_delete|json_encode }}"
data-{{ controllerName }}-button-add-value="{{ block('button_add')|e }}"
data-{{ controllerName }}-button-delete-value="{{ block('button_delete')|e }}"
data-{{ controllerName }}-prototype-name-value="{{ prototype_name }}"
{{ block('widget_container_attributes') }}
>
{% if prototype is defined and not prototype.rendered %}
{%- set prototype_attr = prototype.vars.attr|merge(attrDataTarget) -%}
{%- set attr = attr|merge({'data-prototype': form_row(prototype, {'row_attr': prototype_attr}) }) -%}
{% endif %}
{%- set attr = attr|merge({('data-' ~ controllerName ~ '-target'): 'container' }) -%}
{%- if form is rootform -%}
{{ form_errors(form) }}
{%- endif -%}

<div {{ block('widget_container_attributes') }}>
{%- if form is rootform -%}
{{ form_errors(form) }}
{%- endif -%}
{% for child in form|filter(child => not child.rendered) %}

{% for child in form|filter(child => not child.rendered) %}
{%- set child_attr = child.vars.attr|merge(attrDataTarget) -%}
{{- form_row(child, {'row_attr': child_attr}) -}}

{%- set child_attr = child.vars.attr|merge(attrDataTarget) -%}
{{- form_row(child, {'row_attr': child_attr}) -}}
{% endfor %}

{% endfor %}
</div>
{{- form_rest(form) -}}
</div>
{%- endblock %}
14 changes: 7 additions & 7 deletions src/FormCollection/Resources/views/form_theme_table.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@
{# attr for the data target on the entry of the collection #}
{%- set attrDataTarget = {('data-' ~ controllerName ~ '-target'): 'entry' } -%}

{% if prototype is defined and not prototype.rendered %}
{%- set prototype_attr = prototype.vars.attr|merge(attrDataTarget) -%}
{%- set attr = attr|merge({'data-prototype': form_row(prototype, {'row_attr': prototype_attr}) }) -%}
{% endif %}

<div data-controller="{{ dataController }}"
data-{{ controllerName }}-allow-add-value="{{ allow_add|json_encode }}"
data-{{ controllerName }}-allow-delete-value="{{ allow_delete|json_encode }}"
data-{{ controllerName }}-button-add-value="{{ block('button_add')|e }}"
data-{{ controllerName }}-button-delete-value="{{ block('button_delete')|e }}"
data-{{ controllerName }}-prototype-name-value="{{ prototype_name }}"
{{ block('widget_container_attributes') }}
>
{% if prototype is defined and not prototype.rendered %}
{%- set prototype_attr = prototype.vars.attr|merge(attrDataTarget) -%}
{%- set attr = attr|merge({'data-prototype': form_row(prototype, {'row_attr': prototype_attr}) }) -%}
{% endif %}
{%- set attr = attr|merge({('data-' ~ controllerName ~ '-target'): 'container' }) -%}

<table {{ block('widget_container_attributes') }}>
<table>
{%- if form is rootform -%}
{{ form_errors(form) }}
{%- endif -%}
Expand Down