templates/js/library/symfony_form_data_builder/symfony_form_data_builder.js.twig line 1

Open in your IDE?
  1. /*
  2.  * SymfonyFormDataBuilder
  3.  * Utility to create and update FormData for Symfony-style form field names.
  4.  *
  5.  * Usage:
  6.  *   const builder = new SymfonyFormDataBuilder('reviewer_settings');
  7.  *   builder.setTextArrayField('candidateDeclineCommentsAsArray', ['a','b']);
  8.  *   const fd = builder.getFormData();
  9.  */
  10. class SymfonyFormDataBuilder {
  11.     /**
  12.      * @param {string} formPrefix - optional form prefix used by Symfony form (e.g. "reviewer_settings").
  13.      */
  14.     constructor(formPrefix = '') {
  15.         this.formPrefix = formPrefix || '';
  16.         this.formData = null; // lazy-created FormData
  17.     }
  18.     // internal: build full field name with prefix
  19.     _prefixedName(name) {
  20.         if (!this.formPrefix) return name;
  21.         return `${this.formPrefix}[${name}]`;
  22.     }
  23.     // Create new FormData (replaces existing)
  24.     create() {
  25.         this.formData = new FormData();
  26.         return this.formData;
  27.     }
  28.     // Ensure FormData exists
  29.     ensure() {
  30.         if (!this.formData) this.create();
  31.         return this.formData;
  32.     }
  33.     // Completely clear current FormData
  34.     clear() {
  35.         this.formData = new FormData();
  36.         return this;
  37.     }
  38.     // Get current FormData (may be null)
  39.     getFormData() {
  40.         return this.formData;
  41.     }
  42.     // Set scalar field (overwrites existing key)
  43.     setField(name, value) {
  44.         this.ensure();
  45.         this.formData.set(this._prefixedName(name), value);
  46.         return this;
  47.     }
  48.     // Append scalar field (allows duplicate keys)
  49.     appendField(name, value) {
  50.         this.ensure();
  51.         this.formData.append(this._prefixedName(name), value);
  52.         return this;
  53.     }
  54.     // Remove all keys that belong to array field `name`, e.g. form[name][0], form[name][1]...
  55.     _removeArrayKeys(name) {
  56.         if (!this.formData) return;
  57.         // Remove any key that equals baseName or starts with baseName[  (handles different renderings)
  58.         const baseName = this._prefixedName(name); // e.g. reviewer_settings[candidateDeclineCommentsAsArray]
  59.         const prefix = baseName + '[';
  60.         const newFd = new FormData();
  61.         for (let pair of this.formData.entries()) {
  62.             const key = pair[0];
  63.             const value = pair[1];
  64.             if (key === baseName || key.indexOf(prefix) === 0) {
  65.                 // skip array keys for this name
  66.                 continue;
  67.             }
  68.             newFd.append(key, value);
  69.         }
  70.         this.formData = newFd;
  71.     }
  72.     removeField(name) {
  73.         if (!this.formData) return this;
  74.         const fullName = this._prefixedName(name);
  75.         const newFd = new FormData();
  76.         for (let pair of this.formData.entries()) {
  77.             const key = pair[0];
  78.             const value = pair[1];
  79.             if (key === fullName) {
  80.                 // skip this key
  81.                 continue;
  82.             }
  83.             newFd.append(key, value);
  84.         }
  85.         this.formData = newFd;
  86.         return this;
  87.     }
  88.     /**
  89.      * Установить поле-массив текстовых строк для Symfony-формы.
  90.      * Будет создана (если необходимо) FormData, существующие ключи этого массива удалены и
  91.      * добавлены новые элементы в форме `prefix[name][]`.
  92.      *
  93.      * @param {string} name - имя поля, например 'candidateDeclineCommentsAsArray'
  94.      * @param {string[]} textArray - массив строк
  95.      * @returns {SymfonyFormDataBuilder}
  96.      */
  97.     setTextArrayField(name, textArray) {
  98.         if (!Array.isArray(textArray)) {
  99.             throw new Error('textArray must be an Array of strings');
  100.         }
  101.         this.ensure();
  102.         // remove old keys related to this array
  103.         this._removeArrayKeys(name);
  104.         const baseName = this._prefixedName(name); // e.g. reviewer_settings[candidateDeclineCommentsAsArray]
  105.         // Symfony обычно ожидает индексы 0..N-1. Добавляем поля именно в таком формате.
  106.         // Используем формат с пустыми скобками (e.g. name[]), который PHP/Symfony распарсят как массив.
  107.         const arrayKey = `${baseName}[]`;
  108.         for (let i = 0; i < textArray.length; i++) {
  109.             const value = textArray[i] == null ? '' : String(textArray[i]);
  110.             this.formData.append(arrayKey, value);
  111.         }
  112.         // Для безопасности: если массив пустой, добавим пустой ключ без индекса — Symfony может корректно обработать.
  113.         // if (textArray.length === 0) {
  114.         //     // ensure there's at least an empty value so Symfony treats it as an empty array
  115.         //     const arrayKeyEmpty = `${baseName}[]`;
  116.         //     this.formData.append(arrayKeyEmpty, '');
  117.         // }
  118.         // NOTE: removed DOM synchronization. This builder now only manipulates FormData,
  119.         // and does not add/remove hidden inputs in the HTML form. The caller may
  120.         // submit FormData via fetch/XHR or use native form rendering to handle inputs.
  121.         return this;
  122.     }
  123.     // Debug helper: convert FormData to plain object (arrays for duplicate keys)
  124.     toObject() {
  125.         if (!this.formData) return {};
  126.         const out = {};
  127.         for (let pair of this.formData.entries()) {
  128.             const key = pair[0];
  129.             const value = pair[1];
  130.             if (out.hasOwnProperty(key)) {
  131.                 if (!Array.isArray(out[key])) out[key] = [out[key]];
  132.                 out[key].push(value);
  133.             } else {
  134.                 out[key] = value;
  135.             }
  136.         }
  137.         return out;
  138.     }
  139. }