1// Copyright (c) 2021-2025 GrayBits LLC 2 3import * as InitializerCallbacks from 'gray_matter/modules/initializable/initializer_callbacks' 4import * as Builder from 'gray_matter/utility/builder' 5import * as EventHelpers from 'gray_matter/utility/event_helpers' 6import * as Notification from 'gray_matter/utility/notification' 7import * as ItemForm from 'gray_matter/forms/item_form' 8import CopyFrom from 'gray_matter/modules/copy_from' 9import PopInInterface from 'gray_matter/utility/pop_in_interface' 10import cellPreview from 'gray_matter/modules/cell_preview' 11import axios from 'axios' 12 13export default class CellGroup{ 14 constructor(e){ 15 this.element = e; 16 this.element.cell_group = this; 17 this.parent_type = e.dataset.type; 18 this.attribute = e.dataset.attribute; 19 this.child_index = e.querySelectorAll('.cell').length; 20 this.cells_list = e.querySelector('ul'); 21 this.initAddButtons(e.querySelector('.cell_group_top')); 22 this.form = e.closest('form.item_form'); 23 24 let go_to_bottom = e.querySelector('.go_to_bottom'); 25 if (go_to_bottom){ 26 e.querySelector('.go_to_bottom').addEventListener('click', (e) => this.goToBottom(e)); 27 } 28 let copy_from = e.querySelector('.copy_from'); 29 if (copy_from){ 30 e.querySelector('.copy_from').addEventListener('click', (e) => this.copyFrom(e)); 31 } 32 if (e.classList.contains('single_cell_type')){ 33 e.querySelector('.cell_group_top label').addEventListener('click', (e) => this.addCell(e)); 34 e.querySelector('.cell_group_top .add').addEventListener('click', (e) => this.addCell(e)); 35 e.querySelector('.single_cell_type_bottom .add').addEventListener('click', (e) => this.addCell(e, true)); 36 } 37 this.cells_list.querySelectorAll('li.cell').forEach((cell) => this.initializeCell(cell)); 38 this.setBlankClass(); 39 } 40 41 initAddButtons(e){ 42 e.querySelectorAll('.cell_group_add_buttons .add').forEach((button) => { 43 button.addEventListener('click', (e) => this.addCell(e)); 44 }); 45 } 46 47 openAddButtons(e){ 48 e.target.closest('.cell_group_add_buttons').classList.toggle('closed'); 49 } 50 51 deleteCell(e){ 52 let button = e.target.closest('button'); 53 let cell = e.target.closest('.cell'); 54 button.disabled = true; 55 this.addDestroyFields(cell); 56 cell.addEventListener('transitionend', (e) => this.removeAfterTransition(e)); 57 cell.classList.add('poppedout'); 58 cell.offsetHeight; 59 } 60 61 addDestroyFields(cell){ 62 let cell_id = cell.querySelector('input[type="hidden"].cell_id'); 63 let cell_destroy = cell.querySelector('input[type="hidden"].cell_destroy'); 64 if (cell_id.value * 1 > 0){ 65 cell_destroy.value = 'true'; 66 cell_destroy.classList.add('to_clear'); 67 cell_id.classList.add('to_clear'); 68 this.form.appendChild(cell_destroy); 69 this.form.appendChild(cell_id); 70 } 71 } 72 73 removeAfterTransition(e){ 74 if (e.propertyName == 'opacity'){ 75 e.target.remove(); 76 this.setBlankClass(); 77 } 78 } 79 80 async addCell(e, add_to_bottom){ 81 let cell = e.target.closest('.cell'); 82 let type = e.target.closest('button'); 83 let params = { attribute: this.attribute, child_index: this.child_index }; 84 85 if (this.element.dataset.belongsToAttribute){ 86 params['belongs_to_attribute'] = this.element.dataset.belongsToAttribute; 87 } 88 89 if (type){ 90 type = type.dataset.type || 'nested'; 91 } else { 92 type = e.target.closest('.cell_group_top').querySelector('.button.add').dataset.type || 'nested'; 93 } 94 this.child_index += 1; 95 try{ 96 const response = await axios.get( 97 `/${this.form.dataset.routesPrefix}/association/cells/${this.parent_type}/${type}`, 98 { params: params }); 99 let new_cell = Builder.createFromHTML(response.data.cell); 100 if (add_to_bottom){ 101 this.cells_list.append(new_cell); 102 } else if (cell){ 103 Builder.insertAfter(cell, new_cell); 104 } else { 105 this.cells_list.prepend(new_cell); 106 } 107 this.cells_list.dispatchEvent(new Event('inserted')); 108 this.initializeCell(new_cell); 109 this.setBlankClass(); 110 return new_cell; 111 } catch (error){ 112 Notification.showResponseError(error); 113 } 114 } 115 116 initializeCell(cell){ 117 if (!cell.initialized){ 118 cell.offsetHeight; 119 InitializerCallbacks.applyAll(cell); 120 this.initAddButtons(cell); 121 cell.querySelector('.cell_bar_single .delete, .cell_bar .delete').addEventListener('click', (e) => this.deleteCell(e)); 122 cell.classList.remove('prepop'); 123 let open_button = cell.querySelector('.cell_group_add_buttons .open.button'); 124 if (open_button){ 125 open_button.addEventListener('click', (e) => this.openAddButtons(e)); 126 } 127 cell.initialized = true; 128 } 129 } 130 131 setBlankClass(){ 132 this.element.classList.toggle('with_cells', this.cells_list.childElementCount > 0); 133 } 134 135 goToBottom(){ 136 this.cells_list.lastElementChild.scrollIntoView(); 137 } 138 139 async copyFrom(){ 140 let belongs_to_attribute = ''; 141 if (this.element.dataset.belongsToAttribute){ 142 belongs_to_attribute = `/${this.element.dataset.belongsToAttribute}`; 143 } 144 this.copy_from = new PopInInterface( 145 this.element, 146 { 147 url: `${ItemForm.basePath(this.form)}/copy_from/${this.attribute}${belongs_to_attribute}`, 148 setup: (pop_in_interface) => { 149 CopyFrom.init(pop_in_interface.element, (e) => this.copyHandler(e), this.child_index); 150 } 151 } 152 ); 153 } 154 155 copyHandler(response){ 156 this.cells_list.querySelectorAll('.cell').forEach((cell) => this.addDestroyFields(cell)); 157 this.cells_list.innerHTML = '';
158 response.data.cells.forEach((cell) => { 159 let new_cell = Builder.createFromHTML(cell); 160 this.cells_list.append(new_cell); 161 this.initializeCell(new_cell); 162 }); 163 this.cells_list.dispatchEvent(new Event('inserted')); 164 this.setBlankClass(); 165 this.copy_from.cancel(); 166 } 167 168 static goToTop(){ 169 this.closest('.cell_group').querySelector('.cell_group_top').scrollIntoView(); 170 } 171 172 static toggleCell(e){ 173 if (e.target.checked) { 174 CellGroup.expandCell(e.target.closest('.cell')); 175 } else { 176 CellGroup.collapseCell(e.target.closest('.cell')); 177 } 178 } 179 180 static toggleCellGroup(e){ 181 let group = this.closest('.cell_group').cell_group; 182 if (e.target.checked) { 183 [].slice.call(group.cells_list.children).forEach(CellGroup.expandCell); 184 } else { 185 [].slice.call(group.cells_list.children).forEach(CellGroup.collapseCell); 186 } 187 } 188 189 static expandCell(cell){ 190 if (cell.classList.contains('collapsed')){ 191 cell.classList.remove('collapsed'); 192 cell.querySelector('.cell_toggle').toggle.check(); 193 let cell_preview = cell.querySelector('.cell_preview'); 194 cell_preview.innerHTML = cell.default_preview; 195 } 196 } 197 198 static collapseCell(cell){ 199 if (!cell.classList.contains('collapsed')){ 200 cell.classList.add('collapsed'); 201 cell.querySelector('.cell_toggle').toggle.uncheck(); 202 let cell_preview = cell.querySelector('.cell_preview'); 203 cell.default_preview = cell_preview.innerHTML; 204 cell_preview.innerHTML = `<span class="preview_type_icon">${cell_preview.textContent.substring(0, 1)}</span>`; 205 cell_preview.innerHTML += cellPreview(cell); 206 } 207 } 208 209 static initInstance(e){ 210 if (!e.initilized){ 211 new this(e); 212 } 213 } 214 215 static init(){ 216 let form = document.querySelector('form.item_form'); 217 if (!form) return; 218 219 document.querySelectorAll('.cell_group').forEach((e) => CellGroup.initInstance(e)); 220 EventHelpers.register(form, { 221 change: { 222 cell_toggle: CellGroup.toggleCell, 223 cell_group_toggle: CellGroup.toggleCellGroup 224 }, 225 click: { 226 go_to_top: CellGroup.goToTop 227 } 228 }); 229 } 230};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.