Commit 46b25408 authored by Robert Kasza's avatar Robert Kasza Committed by Róbert Kasza
Browse files

Issue #3283141 by kaszarobert: Ability to set read-only column and row count

parent c07f715f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
    }
  ],
  "require": {
    "ext-json": "*",
    "league/commonmark": "^2.3",
    "bower-asset/papaparse": "^5.3",
    "bower-asset/handsontable": "^12.0"
+29 −0
Original line number Diff line number Diff line
@@ -45,3 +45,32 @@ field.formatter.settings.table_formatter:
    skip_rendering_markdown:
      type: boolean
      label: 'Skip rendering markdown'

field.widget.settings.table_widget:
  type: mapping
  label: 'Table widget settings'
  mapping:
    size:
      type: integer
      label: 'Size of textfield'
    rows:
      type: integer
      label: 'Minimal rows'
    columns:
      type: integer
      label: 'Minimal columns'
    readonly_rows:
      type: integer
      label: 'Read-only rows'
    readonly_columns:
      type: integer
      label: 'Read-only columns'
    limit_operations:
      type: boolean
      label: 'Limit operations'
    enabled_operations:
      type: sequence
      label: 'Enabled operations'
    placeholder:
      type: text
      label: 'Size'
+109 −39
Original line number Diff line number Diff line
@@ -13,55 +13,51 @@
                  return;
                }

                let textarea = renderedTable.parentElement.querySelector('input[type="hidden"]');
                let renderedTableParent = renderedTable.parentElement;
                let textarea = renderedTableParent.querySelector('input[type="hidden"]');
                let textAreaValue = textarea.value;
                let textAreaHtml = textarea.innerHTML;

                if ((textAreaValue.length > 0) && (textAreaHtml === '')) {
                    let config = renderedTable.getAttribute('data-blizz-config');
                    let rows = 1;
                    let columns = 2;
                    let readOnlyRows = 0;
                    let readOnlyColumns = 0;

                    if (config !== null
                        && (config.length > 0)
                    ) {
                        config = JSON.parse(config);
                        rows = config?.rows ?? 1;
                        columns = config?.columns ?? 2;
                        readOnlyRows = config?.readonly_rows ?? 0;
                        readOnlyColumns = config?.readonly_columns ?? 0;
                    }
                    let data = Papa.parse(textAreaValue).data;
                    let table = new Handsontable(renderedTable, {
                        data: data,
                        cells: function (row, col, prop) {
                            var cellProperties = {};
                            if (config !== null && config[row] !== undefined && config[row][col] !== undefined) {
                                cellProperties.placeholder = config[row][col];
                            }

                            let isRowReadOnly = (readOnlyRows > 0 && (row + 1) <= readOnlyRows);
                            let isColReadOnly = (readOnlyColumns > 0 && (col + 1) <= readOnlyColumns);

                            if (isRowReadOnly || isColReadOnly) {
                              cellProperties.readOnly = true;
                            }

                            return cellProperties;
                        },
                        minRows: rows,
                        minCols: columns,
                        stretchH: 'all',
                        licenseKey: drupalSettings?.handsontable?.license_key ?? "",
                        autoWrapRow: true,
                        contextMenu: {
                          items: {
                            'row_above': {
                              "name": Drupal.t('Insert row above'),
                            },
                            'row_below': {
                              "name": Drupal.t('Insert row below'),
                            },
                            'hsep1': "---------",
                            'col_left': {
                              "name": Drupal.t('Insert column on the left'),
                            },
                            'col_right': {
                              "name": Drupal.t('Insert column on the right'),
                            },
                            'hsep2': '---------',
                            'remove_row': {
                              "name": Drupal.t('Remove row'),
                            },
                            'remove_col': {
                              "name": Drupal.t('Remove column'),
                            },
                            'hsep3': '---------',
                            'undo': {
                              "name": Drupal.t('Undo'),
                            },
                            'redo': {
                              "name": Drupal.t('Redo'),
                            },
                            'hsep4': "---------",
                            'cut': {
                              "name": Drupal.t('Cut'),
                            },
                            'copy': {
                              "name": Drupal.t('Copy'),
                            },
                          }
                        },
                        contextMenu: Drupal.behaviors.handsontablejson.buildContextMenu(config),
                    });
                    table.updateSettings({
                        afterChange: function (e) {
@@ -88,6 +84,80 @@
                renderedTable.classList.add('is-loaded');
            });

        },
        buildContextMenu: function (config) {
          let result = {};
          let limitOperations = config?.limit_operations ?? false;
          let selectedOperations = config?.selected_operations ?? [];

          if (!limitOperations || ("row_above" in selectedOperations)) {
            result.row_above = {
              "name": Drupal.t('Insert row above'),
            };
          }

          if (!limitOperations || ("row_below" in selectedOperations)) {
            result.row_below = {
              "name": Drupal.t('Insert row below'),
            };
            result.hsep1 = "---------";
          }

          if (!limitOperations || ("col_left" in selectedOperations)) {
            result.col_left = {
              "name": Drupal.t('Insert column on the left'),
            };
          }

          if (!limitOperations || ("col_right" in selectedOperations)) {
            result.col_right = {
              "name": Drupal.t('Insert column on the right'),
            };
            result.hsep2 = "---------";
          }

          if (!limitOperations || ("remove_row" in selectedOperations)) {
            result.remove_row = {
              "name": Drupal.t('Remove row'),
            };
          }

          if (!limitOperations || ("remove_col" in selectedOperations)) {
            result.remove_col = {
              "name": Drupal.t('Remove column'),
            };
            result.hsep3 = "---------";
          }

          if (!limitOperations || ("undo" in selectedOperations)) {
            result.undo = {
              "name": Drupal.t('Undo'),
            };
          }

          if (!limitOperations || ("redo" in selectedOperations)) {
            result.redo = {
              "name": Drupal.t('Redo'),
            };
            result.hsep4 = "---------";
          }

          if (!limitOperations || ("cut" in selectedOperations)) {
            result.cut = {
              "name": Drupal.t('Cut'),
            };
          }

          if (!limitOperations || ("copy" in selectedOperations)) {
            result.copy = {
              "name": Drupal.t('Copy'),
            };
          }

          return {
            items: result,
          };

        }
    };

+93 −45
Original line number Diff line number Diff line
@@ -20,19 +20,22 @@
                let textAreaHtml = textarea.innerHTML;

                if ((textAreaValue.length > 0) && (textAreaHtml === '')) {
                    let config = renderedTableParent.querySelector('.table-config');
                    let config = renderedTable.getAttribute('data-blizz-config');
                    let rows = 1;
                    let columns = 2;
                    let readOnlyRows = 0;
                    let readOnlyColumns = 0;

                    if (config !== null
                        && (config.value.length > 0)) {
                        config = JSON.parse(config.value);
                        rows = config.rows !== undefined ? config.rows : 1;
                        columns = config.columns !== undefined ? config.columns : 2;
                        config = config.value;
                        && (config.length > 0)
                    ) {
                        config = JSON.parse(config);
                        rows = config?.rows ?? 1;
                        columns = config?.columns ?? 2;
                        readOnlyRows = config?.readonly_rows ?? 0;
                        readOnlyColumns = config?.readonly_columns ?? 0;
                    }
                    let data = JSON.parse(textAreaValue);
                    let header = data[0];
                    let table = new Handsontable(renderedTable, {
                        data: data,
                        cells: function (row, col, prop) {
@@ -41,6 +44,13 @@
                                cellProperties.placeholder = config[row][col];
                            }

                            let isRowReadOnly = (readOnlyRows > 0 && (row + 1) <= readOnlyRows);
                            let isColReadOnly = (readOnlyColumns > 0 && (col + 1) <= readOnlyColumns);

                            if (isRowReadOnly || isColReadOnly) {
                              cellProperties.readOnly = true;
                            }

                            return cellProperties;
                        },
                        minRows: rows,
@@ -48,44 +58,7 @@
                        stretchH: 'all',
                        licenseKey: drupalSettings?.handsontable?.license_key ?? "",
                        autoWrapRow: true,
                        contextMenu: {
                          items: {
                            'row_above': {
                              "name": Drupal.t('Insert row above'),
                            },
                            'row_below': {
                              "name": Drupal.t('Insert row below'),
                            },
                            'hsep1': "---------",
                            'col_left': {
                              "name": Drupal.t('Insert column on the left'),
                            },
                            'col_right': {
                              "name": Drupal.t('Insert column on the right'),
                            },
                            'hsep2': '---------',
                            'remove_row': {
                              "name": Drupal.t('Remove row'),
                            },
                            'remove_col': {
                              "name": Drupal.t('Remove column'),
                            },
                            'hsep3': '---------',
                            'undo': {
                              "name": Drupal.t('Undo'),
                            },
                            'redo': {
                              "name": Drupal.t('Redo'),
                            },
                            'hsep4': "---------",
                            'cut': {
                              "name": Drupal.t('Cut'),
                            },
                            'copy': {
                              "name": Drupal.t('Copy'),
                            },
                          }
                        },
                        contextMenu: Drupal.behaviors.handsontablejson.buildContextMenu(config),
                    });

                    table.updateSettings({
@@ -111,8 +84,83 @@
                }

                renderedTable.classList.add('is-loaded');

            });

        },
        buildContextMenu: function (config) {
          let result = {};
          let limitOperations = config?.limit_operations ?? false;
          let selectedOperations = config?.selected_operations ?? [];

          if (!limitOperations || ("row_above" in selectedOperations)) {
            result.row_above = {
              "name": Drupal.t('Insert row above'),
            };
          }

          if (!limitOperations || ("row_below" in selectedOperations)) {
            result.row_below = {
              "name": Drupal.t('Insert row below'),
            };
            result.hsep1 = "---------";
          }

          if (!limitOperations || ("col_left" in selectedOperations)) {
            result.col_left = {
              "name": Drupal.t('Insert column on the left'),
            };
          }

          if (!limitOperations || ("col_right" in selectedOperations)) {
            result.col_right = {
              "name": Drupal.t('Insert column on the right'),
            };
            result.hsep2 = "---------";
          }

          if (!limitOperations || ("remove_row" in selectedOperations)) {
            result.remove_row = {
              "name": Drupal.t('Remove row'),
            };
          }

          if (!limitOperations || ("remove_col" in selectedOperations)) {
            result.remove_col = {
              "name": Drupal.t('Remove column'),
            };
            result.hsep3 = "---------";
          }

          if (!limitOperations || ("undo" in selectedOperations)) {
            result.undo = {
              "name": Drupal.t('Undo'),
            };
          }

          if (!limitOperations || ("redo" in selectedOperations)) {
            result.redo = {
              "name": Drupal.t('Redo'),
            };
            result.hsep4 = "---------";
          }

          if (!limitOperations || ("cut" in selectedOperations)) {
            result.cut = {
              "name": Drupal.t('Cut'),
            };
          }

          if (!limitOperations || ("copy" in selectedOperations)) {
            result.copy = {
              "name": Drupal.t('Copy'),
            };
          }

          return {
            items: result,
          };

        }
    };

+129 −1
Original line number Diff line number Diff line
@@ -79,6 +79,12 @@ class TableWidget extends WidgetBase implements ContainerFactoryPluginInterface
  public static function defaultSettings() {
    return [
      'size' => 60,
      'rows' => 1,
      'columns' => 2,
      'readonly_rows' => 0,
      'readonly_columns' => 0,
      'limit_operations' => FALSE,
      'enabled_operations' => [],
      'placeholder' => '',
    ] + parent::defaultSettings();
  }
@@ -97,6 +103,57 @@ class TableWidget extends WidgetBase implements ContainerFactoryPluginInterface
      '#min' => 1,
    ];

    $elements['rows'] = [
      '#type' => 'number',
      '#title' => $this->t('Minimal rows'),
      '#default_value' => $this->getSetting('rows') ?? 1,
      '#min' => 1,
    ];

    $elements['columns'] = [
      '#type' => 'number',
      '#title' => $this->t('Minimal columns'),
      '#default_value' => $this->getSetting('columns') ?? 2,
      '#min' => 1,
    ];

    $elements['readonly_rows'] = [
      '#type' => 'number',
      '#title' => $this->t('Read-only rows'),
      '#default_value' => $this->getSetting('readonly_rows') ?? 0,
      '#min' => 0,
    ];

    $elements['readonly_columns'] = [
      '#type' => 'number',
      '#title' => $this->t('Read-only columns'),
      '#default_value' => $this->getSetting('readonly_columns') ?? 0,
      '#min' => 0,
    ];

    $elements['limit_operations'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Limit operations'),
      '#default_value' => $this->getSetting('limit_operations') ?? FALSE,
      '#attributes' => [
        'data-name' => [
          'limit_operations',
        ],
      ],
    ];

    $elements['enabled_operations'] = [
      '#type' => 'checkboxes',
      '#title' => $this->t('Enabled operations'),
      '#default_value' => $this->getSetting('enabled_operations') ?? [],
      '#options' => $this->getOperationOptions(),
      '#states' => [
        'visible' => [
          ':input[data-name="limit_operations"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $elements['placeholder'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Placeholder'),
@@ -107,6 +164,27 @@ class TableWidget extends WidgetBase implements ContainerFactoryPluginInterface
    return $elements;
  }

  /**
   * Returns operations for checkbox element.
   *
   * @return string[]
   *   Operation ID as key, translatable markup as value.
   */
  protected function getOperationOptions() {
    return [
      'row_above' => $this->t('Insert row above'),
      'row_below' => $this->t('Insert row below'),
      'col_left' => $this->t('Insert column on the left'),
      'col_right' => $this->t('Insert column on the right'),
      'remove_row' => $this->t('Remove row'),
      'remove_col' => $this->t('Remove column'),
      'undo' => $this->t('Undo'),
      'redo' => $this->t('Redo'),
      'cut' => $this->t('Cut'),
      'copy' => $this->t('Copy'),
    ];
  }

  /**
   * {@inheritdoc}
   */
@@ -119,6 +197,32 @@ class TableWidget extends WidgetBase implements ContainerFactoryPluginInterface
      $summary[] = $this->t('Placeholder: @placeholder', ['@placeholder' => $placeholder_setting]);
    }

    $summary[] = $this->t('Minimal rows: @placeholder', ['@placeholder' => $this->getSetting('rows') ?? 1]);
    $summary[] = $this->t('Minimal columns: @placeholder', ['@placeholder' => $this->getSetting('columns') ?? 2]);
    $summary[] = $this->t('Read-only rows: @placeholder', ['@placeholder' => $this->getSetting('readonly_rows') ?? 0]);
    $summary[] = $this->t('Read-only columns: @placeholder', ['@placeholder' => $this->getSetting('readonly_columns') ?? 0]);
    $isLimitOperations = !empty($this->getSetting('limit_operations'));
    if ($isLimitOperations) {
      $allOperations = $this->getOperationOptions();
      $selectedOperations = $this->getSetting('enabled_operations') ?? [];
      $selectedOperations = array_filter($selectedOperations);

      $result = [];

      foreach ($selectedOperations as $selectedOperation) {
        if (isset($allOperations[$selectedOperation])) {
          $result[] = $allOperations[$selectedOperation];
        }
      }

      $resultString = implode(', ', $result);

      $summary[] = $this->t('Operations are limited to: @placeholder', ['@placeholder' => $resultString]);
    }
    else {
      $summary[] = $this->t('All operations are enabled.');
    }

    return $summary;
  }

@@ -149,8 +253,32 @@ class TableWidget extends WidgetBase implements ContainerFactoryPluginInterface
      '#title' => $title,
    ];

    $rows = $this->getSetting('rows') ?? 1;
    $columns = $this->getSetting('columns') ?? 2;

    $readOnlyRows = $this->getSetting('readonly_rows') ?? 0;
    $readOnlyColumns = $this->getSetting('readonly_columns') ?? 0;

    $limitOperations = $this->getSetting('limit_operations') ?? FALSE;
    $selectedOperations = $this->getSetting('enabled_operations') ?? [];
    $selectedOperations = array_filter($selectedOperations);

    $config = [
      'rows' => (int) $rows,
      'columns' => (int) $columns,
      'readonly_rows' => (int) $readOnlyRows,
      'readonly_columns' => (int) $readOnlyColumns,
      'limit_operations' => $limitOperations,
      'selected_operations' => $selectedOperations,
    ];

    $element['table'] = [
      '#markup' => '<div class="rendered-table format-' . $format . '"></div>',
      '#type' => 'inline_template',
      '#template' => '<div class="rendered-table blizz format-{{ format }}" data-blizz-config="{{ config }}"></div>',
      '#context' => [
        'format' => $format,
        'config' => json_encode((object) $config),
      ],
      '#weight' => 15,
    ];