summaryrefslogtreecommitdiffstats
path: root/core/modules/block/js/block.es6.js
blob: 0bde2add24dc5cfecb9ccae627639dd4f441ecfc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/**
 * @file
 * Block behaviors.
 */

(function ($, window, Drupal) {
  /**
   * Provide the summary information for the block settings vertical tabs.
   *
   * @type {Drupal~behavior}
   *
   * @prop {Drupal~behaviorAttach} attach
   *   Attaches the behavior for the block settings summaries.
   */
  Drupal.behaviors.blockSettingsSummary = {
    attach() {
      // The drupalSetSummary method required for this behavior is not available
      // on the Blocks administration page, so we need to make sure this
      // behavior is processed only if drupalSetSummary is defined.
      if (typeof $.fn.drupalSetSummary === 'undefined') {
        return;
      }

      /**
       * Create a summary for checkboxes in the provided context.
       *
       * @param {HTMLDocument|HTMLElement} context
       *   A context where one would find checkboxes to summarize.
       *
       * @return {string}
       *   A string with the summary.
       */
      function checkboxesSummary(context) {
        const vals = [];
        const $checkboxes = $(context).find('input[type="checkbox"]:checked + label');
        const il = $checkboxes.length;
        for (let i = 0; i < il; i++) {
          vals.push($($checkboxes[i]).html());
        }
        if (!vals.length) {
          vals.push(Drupal.t('Not restricted'));
        }
        return vals.join(', ');
      }

      $('[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"]').drupalSetSummary(checkboxesSummary);

      $('[data-drupal-selector="edit-visibility-request-path"]').drupalSetSummary((context) => {
        const $pages = $(context).find('textarea[name="visibility[request_path][pages]"]');
        if (!$pages.val()) {
          return Drupal.t('Not restricted');
        }

        return Drupal.t('Restricted to certain pages');
      });
    },
  };

  /**
   * Move a block in the blocks table between regions via select list.
   *
   * This behavior is dependent on the tableDrag behavior, since it uses the
   * objects initialized in that behavior to update the row.
   *
   * @type {Drupal~behavior}
   *
   * @prop {Drupal~behaviorAttach} attach
   *   Attaches the tableDrag behaviour for blocks in block administration.
   */
  Drupal.behaviors.blockDrag = {
    attach(context, settings) {
      // tableDrag is required and we should be on the blocks admin page.
      if (typeof Drupal.tableDrag === 'undefined' || typeof Drupal.tableDrag.blocks === 'undefined') {
        return;
      }

      /**
       * Function to check empty regions and toggle classes based on this.
       *
       * @param {jQuery} table
       *   The jQuery object representing the table to inspect.
       * @param {jQuery} rowObject
       *   The jQuery object representing the table row.
       */
      function checkEmptyRegions(table, rowObject) {
        table.find('tr.region-message').each(function () {
          const $this = $(this);
          // If the dragged row is in this region, but above the message row,
          // swap it down one space.
          if ($this.prev('tr').get(0) === rowObject.element) {
            // Prevent a recursion problem when using the keyboard to move rows
            // up.
            if ((rowObject.method !== 'keyboard' || rowObject.direction === 'down')) {
              rowObject.swap('after', this);
            }
          }
          // This region has become empty.
          if ($this.next('tr').is(':not(.draggable)') || $this.next('tr').length === 0) {
            $this.removeClass('region-populated').addClass('region-empty');
          }
          // This region has become populated.
          else if ($this.is('.region-empty')) {
            $this.removeClass('region-empty').addClass('region-populated');
          }
        });
      }

      /**
       * Function to update the last placed row with the correct classes.
       *
       * @param {jQuery} table
       *   The jQuery object representing the table to inspect.
       * @param {jQuery} rowObject
       *   The jQuery object representing the table row.
       */
      function updateLastPlaced(table, rowObject) {
        // Remove the color-success class from new block if applicable.
        table.find('.color-success').removeClass('color-success');

        const $rowObject = $(rowObject);
        if (!$rowObject.is('.drag-previous')) {
          table.find('.drag-previous').removeClass('drag-previous');
          $rowObject.addClass('drag-previous');
        }
      }

      /**
       * Update block weights in the given region.
       *
       * @param {jQuery} table
       *   Table with draggable items.
       * @param {string} region
       *   Machine name of region containing blocks to update.
       */
      function updateBlockWeights(table, region) {
        // Calculate minimum weight.
        let weight = -Math.round(table.find('.draggable').length / 2);
        // Update the block weights.
        table.find(`.region-${region}-message`).nextUntil('.region-title')
          .find('select.block-weight').val(() =>
            // Increment the weight before assigning it to prevent using the
            // absolute minimum available weight. This way we always have an
            // unused upper and lower bound, which makes manually setting the
            // weights easier for users who prefer to do it that way.
             ++weight);
      }

      const table = $('#blocks');
      // Get the blocks tableDrag object.
      const tableDrag = Drupal.tableDrag.blocks;
      // Add a handler for when a row is swapped, update empty regions.
      tableDrag.row.prototype.onSwap = function (swappedRow) {
        checkEmptyRegions(table, this);
        updateLastPlaced(table, this);
      };

      // Add a handler so when a row is dropped, update fields dropped into
      // new regions.
      tableDrag.onDrop = function () {
        const dragObject = this;
        const $rowElement = $(dragObject.rowObject.element);
        // Use "region-message" row instead of "region" row because
        // "region-{region_name}-message" is less prone to regexp match errors.
        const regionRow = $rowElement.prevAll('tr.region-message').get(0);
        const regionName = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
        const regionField = $rowElement.find('select.block-region-select');
        // Check whether the newly picked region is available for this block.
        if (regionField.find(`option[value=${regionName}]`).length === 0) {
          // If not, alert the user and keep the block in its old region
          // setting.
          window.alert(Drupal.t('The block cannot be placed in this region.'));
          // Simulate that there was a selected element change, so the row is
          // put back to from where the user tried to drag it.
          regionField.trigger('change');
        }

        // Update region and weight fields if the region has been changed.
        if (!regionField.is(`.block-region-${regionName}`)) {
          const weightField = $rowElement.find('select.block-weight');
          const oldRegionName = weightField[0].className.replace(/([^ ]+[ ]+)*block-weight-([^ ]+)([ ]+[^ ]+)*/, '$2');
          regionField.removeClass(`block-region-${oldRegionName}`).addClass(`block-region-${regionName}`);
          weightField.removeClass(`block-weight-${oldRegionName}`).addClass(`block-weight-${regionName}`);
          regionField.val(regionName);
        }

        updateBlockWeights(table, regionName);
      };

      // Add the behavior to each region select list.
      $(context).find('select.block-region-select').once('block-region-select')
        .on('change', function (event) {
          // Make our new row and select field.
          const row = $(this).closest('tr');
          const select = $(this);
          // Find the correct region and insert the row as the last in the
          // region.
          tableDrag.rowObject = new tableDrag.row(row[0]);
          const regionMessage = table.find(`.region-${select[0].value}-message`);
          const regionItems = regionMessage.nextUntil('.region-message, .region-title');
          if (regionItems.length) {
            regionItems.last().after(row);
          }
          // We found that regionMessage is the last row.
          else {
            regionMessage.after(row);
          }
          updateBlockWeights(table, select[0].value);
          // Modify empty regions with added or removed fields.
          checkEmptyRegions(table, tableDrag.rowObject);
          // Update last placed block indication.
          updateLastPlaced(table, row);
          // Show unsaved changes warning.
          if (!tableDrag.changed) {
            $(Drupal.theme('tableDragChangedWarning')).insertBefore(tableDrag.table).hide().fadeIn('slow');
            tableDrag.changed = true;
          }
          // Remove focus from selectbox.
          select.trigger('blur');
        });
    },
  };
}(jQuery, window, Drupal));