✨ feat(RepeatLine.js): Ajoute la classe RepeatLine pour répéter des lignes de formulaire
Ajoute la classe RepeatLine pour gérer la répétition dynamique de lignes de formulaire avec suppression et tri.
This commit is contained in:
@@ -3,12 +3,14 @@ import * as Turbo from "@hotwired/turbo"
|
||||
import {AutoSubmit} from './class/AutoSubmit'
|
||||
import {ServerCard} from './class/ServerCard'
|
||||
import {AutoCustomer} from './class/AutoCustomer'
|
||||
import {RepeatLine} from './class/RepeatLine'
|
||||
|
||||
|
||||
function script() {
|
||||
customElements.define('auto-submit',AutoSubmit,{extends:'form'})
|
||||
customElements.define('server-card',ServerCard,{extends:'div'})
|
||||
customElements.define('auto-customer',AutoCustomer,{extends:'button'})
|
||||
customElements.define('repeat-line',RepeatLine,{extends:'div'})
|
||||
|
||||
}
|
||||
|
||||
|
||||
125
assets/class/RepeatLine.js
Normal file
125
assets/class/RepeatLine.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
export class RepeatLine extends HTMLDivElement{
|
||||
connectedCallback(){
|
||||
this.$props = this.getProps(this, { maxRows: 5 });
|
||||
this.$refs = this.getRefs(this);
|
||||
|
||||
this.rowHTML = this.$refs.rows.children[0].outerHTML;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
// Hook up events for the row.
|
||||
setUpRow(row) {
|
||||
const rowRefs = this.getRefs(row);
|
||||
|
||||
rowRefs.removeButton.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
this.removeRow(row);
|
||||
};
|
||||
}
|
||||
|
||||
// Enable or disable addButton as necessary.
|
||||
updateAddButton() {
|
||||
if (this.$refs.rows.children.length >= this.$props.maxRows) {
|
||||
this.$refs.addButton.setAttribute('disabled', '');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$refs.addButton.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
// Update array key values to the row number
|
||||
updateFieldNames() {
|
||||
[...this.$refs.rows.children]
|
||||
.forEach((el, index) => {
|
||||
el.querySelectorAll('[name]')
|
||||
.forEach(el => {
|
||||
const newName = el.getAttribute('name').replace(/\[\d\]/gm, `[${index}]`);
|
||||
|
||||
el.setAttribute('name', newName);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addRow() {
|
||||
if (
|
||||
!this.rowHTML ||
|
||||
this.$refs.rows.children.length >= this.$props.maxRows
|
||||
) return;
|
||||
|
||||
let newRow = this.createFromHTML(this.rowHTML);
|
||||
newRow.removeAttribute('id');
|
||||
this.setUpRow(newRow);
|
||||
|
||||
this.$refs.rows.appendChild(newRow);
|
||||
newRow.querySelector('input,textarea,select').focus();
|
||||
|
||||
this.updateFieldNames();
|
||||
this.updateAddButton();
|
||||
}
|
||||
|
||||
removeRow(row) {
|
||||
if (this.$refs.rows.children.length <= 1) return;
|
||||
|
||||
row.remove();
|
||||
this.$refs.rows.focus();
|
||||
|
||||
this.updateFieldNames();
|
||||
this.updateFieldNames();
|
||||
}
|
||||
init() {
|
||||
this.setUpRow(this.$refs.rows.children[0]);
|
||||
|
||||
this.$refs.addButton.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
this.addRow();
|
||||
}
|
||||
|
||||
this.updateFieldNames();
|
||||
|
||||
|
||||
let repeater__rows = this.querySelector('.form-repeater__rows');
|
||||
new Sortable(repeater__rows,{
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Return an object that contains references to DOM objects.
|
||||
getRefs(el) {
|
||||
let result = {};
|
||||
|
||||
[...el.querySelectorAll('[data-ref]')]
|
||||
.forEach(ref => {
|
||||
result[ref.dataset.ref] = ref;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
setDefaults(obj, defaults) {
|
||||
let results = obj;
|
||||
|
||||
for (const prop in defaults) {
|
||||
if (!obj.hasOwnProperty(prop)) {
|
||||
results[prop] = defaults[prop];
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
getProps(el, defaults={}) {
|
||||
return this.setDefaults(
|
||||
JSON.parse(el.dataset.props ?? '{}'),
|
||||
defaults
|
||||
);
|
||||
}
|
||||
|
||||
createFromHTML(html='') {
|
||||
let element = document.createElement(null);
|
||||
element.innerHTML = html;
|
||||
return element.firstElementChild;
|
||||
}
|
||||
}
|
||||
@@ -18,116 +18,8 @@ export function repeaterComponent($el) {
|
||||
|
||||
let rowHTML = $refs.rows.children[0].outerHTML;
|
||||
|
||||
// Hook up events for the row.
|
||||
function setUpRow(row) {
|
||||
const rowRefs = getRefs(row);
|
||||
|
||||
rowRefs.removeButton.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
removeRow(row);
|
||||
};
|
||||
}
|
||||
|
||||
// Enable or disable addButton as necessary.
|
||||
function updateAddButton() {
|
||||
if ($refs.rows.children.length >= $props.maxRows) {
|
||||
$refs.addButton.setAttribute('disabled', '');
|
||||
return;
|
||||
}
|
||||
|
||||
$refs.addButton.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
// Update array key values to the row number
|
||||
function updateFieldNames() {
|
||||
[...$refs.rows.children]
|
||||
.forEach((el, index) => {
|
||||
el.querySelectorAll('[name]')
|
||||
.forEach(el => {
|
||||
const newName = el.getAttribute('name').replace(/\[\d\]/gm, `[${index}]`);
|
||||
|
||||
el.setAttribute('name', newName);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addRow() {
|
||||
if (
|
||||
!rowHTML ||
|
||||
$refs.rows.children.length >= $props.maxRows
|
||||
) return;
|
||||
|
||||
let newRow = createFromHTML(rowHTML);
|
||||
newRow.removeAttribute('id');
|
||||
setUpRow(newRow);
|
||||
|
||||
$refs.rows.appendChild(newRow);
|
||||
newRow.querySelector('input,textarea,select').focus();
|
||||
|
||||
updateFieldNames();
|
||||
updateAddButton();
|
||||
}
|
||||
|
||||
function removeRow(row) {
|
||||
if ($refs.rows.children.length <= 1) return;
|
||||
|
||||
row.remove();
|
||||
$refs.rows.focus();
|
||||
|
||||
updateFieldNames();
|
||||
updateAddButton();
|
||||
}
|
||||
|
||||
function init() {
|
||||
setUpRow($refs.rows.children[0]);
|
||||
|
||||
$refs.addButton.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
addRow();
|
||||
}
|
||||
|
||||
updateFieldNames();
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
// Return an object that contains references to DOM objects.
|
||||
function getRefs(el) {
|
||||
let result = {};
|
||||
|
||||
[...el.querySelectorAll('[data-ref]')]
|
||||
.forEach(ref => {
|
||||
result[ref.dataset.ref] = ref;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function setDefaults(obj, defaults) {
|
||||
let results = obj;
|
||||
|
||||
for (const prop in defaults) {
|
||||
if (!obj.hasOwnProperty(prop)) {
|
||||
results[prop] = defaults[prop];
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Get initial component data from the `data-props` attribute in JSON format.
|
||||
function getProps(el, defaults={}) {
|
||||
return setDefaults(
|
||||
JSON.parse(el.dataset.props ?? '{}'),
|
||||
defaults
|
||||
);
|
||||
}
|
||||
|
||||
// Create a new element from an HTML string.
|
||||
function createFromHTML(html='') {
|
||||
let element = document.createElement(null);
|
||||
element.innerHTML = html;
|
||||
return element.firstElementChild;
|
||||
}
|
||||
|
||||
@@ -5,5 +5,93 @@
|
||||
<h2 class="text-3xl font-semibold text-gray-800 dark:text-gray-200">Client - {{ customer.raisonSocial }} - Création devis / avis de paiement / facture</h2>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mt-5 bg-gray-800 rounded-lg shadow-lg p-6 space-y-4">
|
||||
<div class="flex space-x-4">
|
||||
<div class="flex-1">
|
||||
<div class="mb-5">
|
||||
<label for="type" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Type</label>
|
||||
<select id="type" name="type" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
|
||||
|
||||
<option value="avis">Avis de paiement</option>
|
||||
<option value="devis">Devis</option>
|
||||
<option value="facture">Facture</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="mb-5">
|
||||
<label for="num" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Numéro</label>
|
||||
<input type="text" name="num" id="num_devis" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required value="AVIS-" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="mb-5">
|
||||
<label for="date" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date</label>
|
||||
<input type="datetime-local" name="date" id="date" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset class="form-section">
|
||||
<legend>
|
||||
<h2>Guests</h2>
|
||||
</legend>
|
||||
|
||||
<div class="form-repeater" data-component="repeater" is="repeat-line">
|
||||
<ol class="form-repeater__rows" data-ref="rows" tabindex="0">
|
||||
<!-- This element will be repeated: -->
|
||||
<li class="form-repeater__row">
|
||||
<fieldset class="form-group form-group--horizontal">
|
||||
<div class="flex space-x-4">
|
||||
<div class="flex-1">
|
||||
<div class="form-field">
|
||||
<div class="mb-1">
|
||||
<label for="titre" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Titre</label>
|
||||
<input type="text" name="titre" id="lines[0]['title]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="price" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Prix TTC</label>
|
||||
<input type="number" step="0.1" name="titre" id="lines[0]['price]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
|
||||
</div>
|
||||
<button
|
||||
class="w-full form-repeater__remove-button bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded"
|
||||
data-ref="removeButton"
|
||||
type="button"
|
||||
>
|
||||
Supprimer la ligne
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="form-field">
|
||||
<div class="mb-5">
|
||||
<label for="description" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Description</label>
|
||||
<textarea rows="7" type="text" name="description" id="lines[0]['description]" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<button
|
||||
class="form-repeater__add-button w-full bg-purple-600 hover:bg-purple-700 text-white px-3 py-1 rounded"
|
||||
data-ref="addButton"
|
||||
type="button"
|
||||
>
|
||||
+ Ajouter une ligne
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<button
|
||||
class="w-full bg-purple-600 hover:bg-purple-700 text-white px-3 py-1 rounded"
|
||||
type="button"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user