active_element 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -0
- data/.strong_versions.yml +2 -0
- data/Gemfile +10 -2
- data/Gemfile.lock +229 -4
- data/Rakefile +1 -0
- data/active_element.gemspec +7 -0
- data/app/assets/config/active_element/manifest.js +2 -0
- data/app/assets/javascripts/active_element/application.js +10 -0
- data/app/assets/javascripts/active_element/confirm.js +67 -0
- data/app/assets/javascripts/active_element/form.js +61 -0
- data/app/assets/javascripts/active_element/json_field.js +316 -0
- data/app/assets/javascripts/active_element/pagination.js +18 -0
- data/app/assets/javascripts/active_element/search_field.js +127 -0
- data/app/assets/javascripts/active_element/secret.js +40 -0
- data/app/assets/javascripts/active_element/setup.js +36 -0
- data/app/assets/javascripts/active_element/theme.js +42 -0
- data/app/assets/stylesheets/active_element/_variables.scss +142 -0
- data/app/assets/stylesheets/active_element/application.scss +77 -0
- data/app/controllers/active_element/application_controller.rb +41 -0
- data/app/controllers/active_element/text_searches_controller.rb +189 -0
- data/app/views/active_element/components/_horizontal_tabs.html.erb +32 -0
- data/app/views/active_element/components/_vertical_tabs.html.erb +38 -0
- data/app/views/active_element/components/button.html.erb +27 -0
- data/app/views/active_element/components/fields/_boolean.html.erb +11 -0
- data/app/views/active_element/components/form/_check_box.html.erb +3 -0
- data/app/views/active_element/components/form/_check_boxes.html.erb +33 -0
- data/app/views/active_element/components/form/_field.html.erb +28 -0
- data/app/views/active_element/components/form/_generic_field.html.erb +3 -0
- data/app/views/active_element/components/form/_json.html.erb +12 -0
- data/app/views/active_element/components/form/_label.html.erb +17 -0
- data/app/views/active_element/components/form/_option_groups_summary.html.erb +17 -0
- data/app/views/active_element/components/form/_select.html.erb +4 -0
- data/app/views/active_element/components/form/_summary.html.erb +40 -0
- data/app/views/active_element/components/form/_templates.html.erb +85 -0
- data/app/views/active_element/components/form/_text_area.html.erb +4 -0
- data/app/views/active_element/components/form/_text_search.html.erb +16 -0
- data/app/views/active_element/components/form.html.erb +78 -0
- data/app/views/active_element/components/json.html.erb +8 -0
- data/app/views/active_element/components/page_description.html.erb +3 -0
- data/app/views/active_element/components/secret/_field.html.erb +1 -0
- data/app/views/active_element/components/secret/_templates.html.erb +11 -0
- data/app/views/active_element/components/table/_collection_row.html.erb +30 -0
- data/app/views/active_element/components/table/_grouped_collection.html.erb +88 -0
- data/app/views/active_element/components/table/_pagination.html.erb +17 -0
- data/app/views/active_element/components/table/_ungrouped_collection.html.erb +49 -0
- data/app/views/active_element/components/table/collection.html.erb +39 -0
- data/app/views/active_element/components/table/item.html.erb +39 -0
- data/app/views/active_element/components/tabs.html.erb +7 -0
- data/app/views/active_element/decorators/_boolean.html.erb +5 -0
- data/app/views/active_element/decorators/_date.html.erb +3 -0
- data/app/views/active_element/decorators/_datetime.html.erb +3 -0
- data/app/views/active_element/decorators/_time.html.erb +3 -0
- data/app/views/active_element/forbidden.html.erb +33 -0
- data/app/views/active_element/main_menu/_item.html.erb +9 -0
- data/app/views/active_element/navbar/_menu.html.erb +30 -0
- data/app/views/active_element/theme/_select.html.erb +1 -0
- data/app/views/active_element/theme/_templates.html.erb +6 -0
- data/app/views/kaminari/_first_page.html.erb +3 -0
- data/app/views/kaminari/_gap.html.erb +3 -0
- data/app/views/kaminari/_last_page.html.erb +3 -0
- data/app/views/kaminari/_next_page.html.erb +3 -0
- data/app/views/kaminari/_page.html.erb +9 -0
- data/app/views/kaminari/_paginator.html.erb +17 -0
- data/app/views/kaminari/_prev_page.html.erb +3 -0
- data/app/views/layouts/active_element.html.erb +65 -0
- data/app/views/layouts/active_element_error.html.erb +40 -0
- data/config/routes.rb +5 -0
- data/lib/active_element/active_menu_link.rb +80 -0
- data/lib/active_element/active_record_text_search_authorization.rb +12 -0
- data/lib/active_element/colorized_string.rb +33 -0
- data/lib/active_element/component.rb +122 -0
- data/lib/active_element/components/button.rb +156 -0
- data/lib/active_element/components/collection_table.rb +118 -0
- data/lib/active_element/components/form.rb +210 -0
- data/lib/active_element/components/item_table.rb +57 -0
- data/lib/active_element/components/json.rb +31 -0
- data/lib/active_element/components/link_helpers.rb +9 -0
- data/lib/active_element/components/page_description.rb +28 -0
- data/lib/active_element/components/secret_fields.rb +15 -0
- data/lib/active_element/components/tab.rb +37 -0
- data/lib/active_element/components/tabs.rb +35 -0
- data/lib/active_element/components/translations.rb +18 -0
- data/lib/active_element/components/util/association_mapping.rb +80 -0
- data/lib/active_element/components/util/decorator.rb +107 -0
- data/lib/active_element/components/util/display_value_mapping.rb +48 -0
- data/lib/active_element/components/util/field_mapping.rb +144 -0
- data/lib/active_element/components/util/form_field_mapping.rb +104 -0
- data/lib/active_element/components/util/form_value_mapping.rb +49 -0
- data/lib/active_element/components/util/i18n.rb +66 -0
- data/lib/active_element/components/util/record_mapping.rb +111 -0
- data/lib/active_element/components/util.rb +43 -0
- data/lib/active_element/components.rb +20 -0
- data/lib/active_element/controller_action.rb +91 -0
- data/lib/active_element/engine.rb +26 -0
- data/lib/active_element/permissions_check.rb +101 -0
- data/lib/active_element/rails_component.rb +40 -0
- data/lib/active_element/route.rb +112 -0
- data/lib/active_element/routes.rb +62 -0
- data/lib/active_element/version.rb +1 -1
- data/lib/active_element.rb +91 -1
- data/lib/tasks/active_element.rake +23 -0
- data/rspec-documentation/dummy +1 -0
- data/rspec-documentation/pages/Components/Forms.md +1 -0
- data/rspec-documentation/pages/Components/Tables.md +47 -0
- data/rspec-documentation/pages/Components/Tabs.md +1 -0
- data/rspec-documentation/pages/Components.md +1 -0
- data/rspec-documentation/pages/Decorators/Inline Decorators.md +1 -0
- data/rspec-documentation/pages/Decorators/View Decorators.md +1 -0
- data/rspec-documentation/pages/Index.md +3 -0
- data/rspec-documentation/pages/Util/I18n.md +1 -0
- data/rspec-documentation/spec_helper.rb +35 -0
- metadata +191 -3
@@ -0,0 +1,316 @@
|
|
1
|
+
ActiveElement.JsonField = (() => {
|
2
|
+
const cloneElement = (id) => ActiveElement.cloneElement('json', id);
|
3
|
+
|
4
|
+
const humanize = ({ string, singular = false }) => {
|
5
|
+
const humanized = string.split('_').map(item => item.charAt(0).toUpperCase() + item.substring(1)).join(' ');
|
6
|
+
|
7
|
+
if (!singular) return humanized;
|
8
|
+
|
9
|
+
return humanized.replace(/s$/, ''); // FIXME: Expose translations from back-end to make this more useful.
|
10
|
+
};
|
11
|
+
|
12
|
+
const createStore = ({ data, store = { data: {}, paths: {} } }) => {
|
13
|
+
const buildState = ({ data, store, path = [] }) => {
|
14
|
+
const getPath = (key) => {
|
15
|
+
return path.concat([key]);
|
16
|
+
};
|
17
|
+
|
18
|
+
if (Array.isArray(data)) {
|
19
|
+
return data.map((value, index) => buildState({ data: value, store, path: getPath(index) }));
|
20
|
+
} else if (data && typeof(data) === 'object') {
|
21
|
+
return Object.fromEntries(
|
22
|
+
Object.entries(data).map(
|
23
|
+
([key, value]) => [key, buildState({ data: value, store, path: getPath(key) })]
|
24
|
+
)
|
25
|
+
);
|
26
|
+
} else {
|
27
|
+
const id = crypto.randomUUID();
|
28
|
+
store.data[id] = data;
|
29
|
+
store.paths[id] = path;
|
30
|
+
return id;
|
31
|
+
}
|
32
|
+
};
|
33
|
+
|
34
|
+
const state = buildState({ data, store });
|
35
|
+
const getValue = (key) => store.data[key];
|
36
|
+
const setValue = (key, value) => store.data[key] = value;
|
37
|
+
|
38
|
+
return { state, store, getValue, setValue };
|
39
|
+
};
|
40
|
+
|
41
|
+
const getState = ({ store }) => {
|
42
|
+
const data = {};
|
43
|
+
const storeData = Object.entries(store.paths).forEach(([id, path]) => {
|
44
|
+
let value = data;
|
45
|
+
path.forEach((key, index) => {
|
46
|
+
if (index === path.length - 1) {
|
47
|
+
value[key] = store.data[id];
|
48
|
+
} else if (typeof(key) === 'string') {
|
49
|
+
value[key] = value[key] || {};
|
50
|
+
value = value[key];
|
51
|
+
} else {
|
52
|
+
value[key] = value[key] || [];
|
53
|
+
value = value[key];
|
54
|
+
}
|
55
|
+
});
|
56
|
+
|
57
|
+
return value;
|
58
|
+
},
|
59
|
+
{});
|
60
|
+
|
61
|
+
return data;
|
62
|
+
};
|
63
|
+
|
64
|
+
const getValueFromElement = ({ element }) => {
|
65
|
+
if (element.type === 'checkbox') return element.checked;
|
66
|
+
|
67
|
+
return element.value;
|
68
|
+
};
|
69
|
+
|
70
|
+
const getData = (element) => {
|
71
|
+
const dataKey = element.dataset.dataKey;
|
72
|
+
|
73
|
+
return ActiveElement.jsonData[dataKey].data;
|
74
|
+
};
|
75
|
+
|
76
|
+
const getSchema = (element) => {
|
77
|
+
const dataKey = element.dataset.dataKey;
|
78
|
+
|
79
|
+
return ActiveElement.jsonData[dataKey].schema;
|
80
|
+
};
|
81
|
+
|
82
|
+
const trackState = ({ element, schema, getValue }) => {
|
83
|
+
element.addEventListener('change', (ev) => {
|
84
|
+
const key = ev.target.id;
|
85
|
+
const previousValue = getValue(key);
|
86
|
+
const newValue = getValueFromElement({ element: ev.target });
|
87
|
+
|
88
|
+
if (previousValue !== newValue) {
|
89
|
+
// setValue(key, newValue);
|
90
|
+
// TODO: Trigger callbacks
|
91
|
+
}
|
92
|
+
console.log(`Previous: ${previousValue}`);
|
93
|
+
console.log(`Updated: ${newValue}`);
|
94
|
+
return true;
|
95
|
+
});
|
96
|
+
};
|
97
|
+
|
98
|
+
const Component = ({ getValue, schema, state, element }) => {
|
99
|
+
const ObjectField = ({ schema, state, floating = true, omitLabel = false }) => {
|
100
|
+
let element;
|
101
|
+
switch (schema.type) {
|
102
|
+
case 'boolean':
|
103
|
+
return BooleanField({ state, omitLabel, schema });
|
104
|
+
case 'string':
|
105
|
+
return StringField({ state, omitLabel, floating, schema });
|
106
|
+
break;
|
107
|
+
case 'object':
|
108
|
+
element = cloneElement('form-group-floating');
|
109
|
+
|
110
|
+
(schema.shape.fields).forEach((field) => {
|
111
|
+
element.append(
|
112
|
+
ObjectField({
|
113
|
+
name: field.name,
|
114
|
+
floating: false,
|
115
|
+
schema: field,
|
116
|
+
state: state ? state[field.name] : null,
|
117
|
+
})
|
118
|
+
);
|
119
|
+
});
|
120
|
+
|
121
|
+
return element;
|
122
|
+
case 'array':
|
123
|
+
element = cloneElement('form-group');
|
124
|
+
const list = ArrayField({ schema, state });
|
125
|
+
element.append(ExpandCollapseButton({ element }));
|
126
|
+
element.append(Label({ title: schema.name }));
|
127
|
+
element.append(list);
|
128
|
+
element.append(AppendButton({ list, schema, state }));
|
129
|
+
return element;
|
130
|
+
}
|
131
|
+
};
|
132
|
+
|
133
|
+
const BooleanField = ({ omitLabel, schema, state }) => {
|
134
|
+
const checkbox = cloneElement('checkbox-field');
|
135
|
+
|
136
|
+
checkbox.id = state;
|
137
|
+
checkbox.checked = getValue(state);
|
138
|
+
|
139
|
+
if (omitLabel) return checkbox;
|
140
|
+
|
141
|
+
const element = cloneElement('form-check');
|
142
|
+
|
143
|
+
element.append(checkbox);
|
144
|
+
element.append(Label({ title: schema.name, template: 'form-check-label' }));
|
145
|
+
|
146
|
+
return element;
|
147
|
+
};
|
148
|
+
|
149
|
+
const ArrayField = ({ schema, state }) => {
|
150
|
+
const element = cloneElement('list-group');
|
151
|
+
|
152
|
+
if (state) {
|
153
|
+
state.forEach((value) => {
|
154
|
+
const listItem = cloneElement('list-item');
|
155
|
+
const objectField = ObjectField({
|
156
|
+
omitLabel: true,
|
157
|
+
schema: { ...schema, ...schema.shape },
|
158
|
+
state: value,
|
159
|
+
});
|
160
|
+
|
161
|
+
if (schema.shape.type == 'object') {
|
162
|
+
const group = cloneElement('form-group');
|
163
|
+
group.append(DeleteButton({ rootElement: listItem, template: 'delete-object-button' }));
|
164
|
+
group.append(objectField);
|
165
|
+
listItem.append(group);
|
166
|
+
} else {
|
167
|
+
listItem.append(objectField);
|
168
|
+
listItem.append(DeleteButton({ rootElement: listItem }));
|
169
|
+
}
|
170
|
+
|
171
|
+
element.append(listItem);
|
172
|
+
});
|
173
|
+
}
|
174
|
+
|
175
|
+
return element;
|
176
|
+
};
|
177
|
+
|
178
|
+
const Label = ({ title, template }) => {
|
179
|
+
const element = cloneElement(template || 'label');
|
180
|
+
|
181
|
+
element.append(humanize({ string: title }));
|
182
|
+
|
183
|
+
return element;
|
184
|
+
}
|
185
|
+
|
186
|
+
const Select = ({ state, schema }) => {
|
187
|
+
const element = cloneElement('select')
|
188
|
+
|
189
|
+
element.id = state;
|
190
|
+
|
191
|
+
schema.shape.options.forEach((option) => {
|
192
|
+
const optionElement = document.createElement('option');
|
193
|
+
optionElement.value = option;
|
194
|
+
optionElement.append(option);
|
195
|
+
optionElement.selected = option === getValue(state);
|
196
|
+
element.append(optionElement);
|
197
|
+
});
|
198
|
+
|
199
|
+
return element;
|
200
|
+
};
|
201
|
+
|
202
|
+
const TextField = ({ template, state, schema }) => {
|
203
|
+
const element = cloneElement(template || 'text-field');
|
204
|
+
|
205
|
+
element.value = getValue(state) || '';
|
206
|
+
element.id = state;
|
207
|
+
element.placeholder = schema.shape?.placeholder || ' ';
|
208
|
+
|
209
|
+
return element;
|
210
|
+
};
|
211
|
+
|
212
|
+
const StringField = ({ omitLabel, floating, schema, state }) => {
|
213
|
+
let element;
|
214
|
+
|
215
|
+
if (schema.shape?.options?.length) {
|
216
|
+
element = Select({ state, schema });
|
217
|
+
} else {
|
218
|
+
element = TextField({ state, schema });
|
219
|
+
}
|
220
|
+
|
221
|
+
if (omitLabel) return element;
|
222
|
+
|
223
|
+
const group = cloneElement('form-group-floating');
|
224
|
+
|
225
|
+
group.append(element);
|
226
|
+
group.append(Label({ title: schema.name }));
|
227
|
+
|
228
|
+
return group;
|
229
|
+
};
|
230
|
+
|
231
|
+
const ExpandCollapseButton = ({ element }) => {
|
232
|
+
const button = cloneElement('expand-collapse-button');
|
233
|
+
|
234
|
+
button.onclick = (ev) => {
|
235
|
+
ev.stopPropagation();
|
236
|
+
element.classList.toggle('collapsed');
|
237
|
+
|
238
|
+
if (element.classList.contains('collapsed')) {
|
239
|
+
button.innerText = 'Show';
|
240
|
+
} else {
|
241
|
+
button.innerText = 'Hide';
|
242
|
+
}
|
243
|
+
|
244
|
+
return false;
|
245
|
+
};
|
246
|
+
|
247
|
+
return button;
|
248
|
+
};
|
249
|
+
|
250
|
+
const DeleteButton = ({ rootElement, template = 'delete-button' }) => {
|
251
|
+
const element = cloneElement(template);
|
252
|
+
|
253
|
+
element.onclick = (ev) => {
|
254
|
+
ev.stopPropagation();
|
255
|
+
rootElement.remove();
|
256
|
+
|
257
|
+
return false;
|
258
|
+
};
|
259
|
+
|
260
|
+
return element;
|
261
|
+
};
|
262
|
+
|
263
|
+
const AppendButton = ({ list, schema, state }) => {
|
264
|
+
const element = cloneElement('append-button');
|
265
|
+
|
266
|
+
const humanName = humanize({ string: schema.name, singular: true });
|
267
|
+
|
268
|
+
element.append(`Add ${humanName}`);
|
269
|
+
element.onclick = (ev) => {
|
270
|
+
ev.stopPropagation();
|
271
|
+
const listItem = cloneElement('list-item');
|
272
|
+
const objectField = ObjectField(
|
273
|
+
{ name: schema.name, omitLabel: true, state, schema: { ...schema, ...schema.shape } }
|
274
|
+
);
|
275
|
+
|
276
|
+
if (schema.shape.type == 'object') {
|
277
|
+
listItem.append(DeleteButton({ rootElement: listItem, template: 'delete-object-button' }));
|
278
|
+
listItem.append(objectField);
|
279
|
+
} else {
|
280
|
+
listItem.append(objectField);
|
281
|
+
listItem.append(DeleteButton({ rootElement: listItem }));
|
282
|
+
}
|
283
|
+
list.append(listItem);
|
284
|
+
|
285
|
+
return false;
|
286
|
+
};
|
287
|
+
|
288
|
+
return element;
|
289
|
+
};
|
290
|
+
|
291
|
+
element.append(ObjectField({ omitLabel: true, schema, state, getValue }));
|
292
|
+
};
|
293
|
+
|
294
|
+
const JsonField = (element) => {
|
295
|
+
const data = getData(element);
|
296
|
+
const schema = getSchema(element);
|
297
|
+
const { state, store, getValue } = createStore({ data });
|
298
|
+
|
299
|
+
console.log(getState({ store }));
|
300
|
+
|
301
|
+
trackState({ element, schema, getValue });
|
302
|
+
const component = Component({ getValue, schema, state, element });
|
303
|
+
|
304
|
+
return component;
|
305
|
+
};
|
306
|
+
|
307
|
+
return JsonField;
|
308
|
+
})();
|
309
|
+
|
310
|
+
(() => {
|
311
|
+
window.addEventListener('DOMContentLoaded', () => {
|
312
|
+
document.querySelectorAll('.json-field').forEach((element) => {
|
313
|
+
ActiveElement.JsonField(element);
|
314
|
+
});
|
315
|
+
});
|
316
|
+
})();
|
@@ -0,0 +1,18 @@
|
|
1
|
+
(() => {
|
2
|
+
window.addEventListener('DOMContentLoaded', () => {
|
3
|
+
const paginationSelect = document.querySelector('#collection-table-page-size-selector');
|
4
|
+
|
5
|
+
if (paginationSelect) {
|
6
|
+
paginationSelect.addEventListener('change', (ev) => {
|
7
|
+
ev.stopPropagation();
|
8
|
+
|
9
|
+
const params = new URLSearchParams(window.location.search);
|
10
|
+
|
11
|
+
params.set('page_size', ev.target.value);
|
12
|
+
window.location.search = params.toString();
|
13
|
+
|
14
|
+
return false;
|
15
|
+
});
|
16
|
+
}
|
17
|
+
});
|
18
|
+
})();
|
@@ -0,0 +1,127 @@
|
|
1
|
+
(() => {
|
2
|
+
let lastRequestId;
|
3
|
+
|
4
|
+
const cloneElement = (id) => ActiveElement.cloneElement('form-search-field', id);
|
5
|
+
|
6
|
+
const tryParseJSON = (json, defaultValue) => {
|
7
|
+
try {
|
8
|
+
return JSON.parse(json);
|
9
|
+
} catch (error) {
|
10
|
+
ActiveElement.log(error);
|
11
|
+
return defaultValue;
|
12
|
+
}
|
13
|
+
};
|
14
|
+
|
15
|
+
const processResponse = ({
|
16
|
+
element, response, hiddenInput, spinner, clearButton, searchResultsContainer, responseErrorContainer
|
17
|
+
}) => {
|
18
|
+
spinner.classList.add('invisible');
|
19
|
+
clearButton.classList.remove('invisible');
|
20
|
+
|
21
|
+
if (response.ok) {
|
22
|
+
response.json().then((json) => {
|
23
|
+
if (json.request_id !== lastRequestId) {
|
24
|
+
return;
|
25
|
+
}
|
26
|
+
|
27
|
+
responseErrorContainer.innerText = '';
|
28
|
+
|
29
|
+
if (!json.results.length) {
|
30
|
+
responseErrorContainer.innerText = `No matching results for ${element.value}`;
|
31
|
+
return;
|
32
|
+
}
|
33
|
+
|
34
|
+
json.results.forEach(({ value, attributes }) => {
|
35
|
+
const resultsItem = cloneElement('results-item');
|
36
|
+
|
37
|
+
resultsItem.innerText = attributes.length === 0 ? value : `${attributes.join(', ')} (${value})`;
|
38
|
+
resultsItem.addEventListener('click', () => {
|
39
|
+
hiddenInput.value = value;
|
40
|
+
element.value = attributes.length == 0 ? value : `${attributes.join(', ')} (${value})`;
|
41
|
+
searchResultsContainer.replaceChildren();
|
42
|
+
searchResultsContainer.classList.add('d-none');
|
43
|
+
});
|
44
|
+
searchResultsContainer.append(resultsItem);
|
45
|
+
});
|
46
|
+
|
47
|
+
searchResultsContainer.classList.remove('d-none');
|
48
|
+
});
|
49
|
+
} else {
|
50
|
+
response.json().then((json) => responseErrorContainer.innerText = json.message)
|
51
|
+
.catch(() => responseErrorContainer.innerText = 'An unepxected error occurred');
|
52
|
+
}
|
53
|
+
};
|
54
|
+
|
55
|
+
window.addEventListener('DOMContentLoaded', () => {
|
56
|
+
document.querySelectorAll('[data-field-type="text-search"]').forEach((element) => {
|
57
|
+
const id = element.id;
|
58
|
+
const hiddenId = `${id}-hidden-value`;
|
59
|
+
const formId = element.dataset.formId;
|
60
|
+
const form = document.querySelector(`#${formId}`);
|
61
|
+
const model = element.dataset.searchModel;
|
62
|
+
const attributes = tryParseJSON(element.dataset.searchAttributes, []);
|
63
|
+
const value = element.dataset.searchValue;
|
64
|
+
const token = ActiveElement.getAntiCsrfToken();
|
65
|
+
const hiddenInput = cloneElement('hidden-input');
|
66
|
+
const responseErrorContainer = cloneElement('response-error');
|
67
|
+
const searchResultsContainer = cloneElement('results');
|
68
|
+
const spinner = cloneElement('spinner');
|
69
|
+
const clearButton = cloneElement('clear-button');
|
70
|
+
document.addEventListener('click', () => {
|
71
|
+
searchResultsContainer.classList.add('d-none');
|
72
|
+
});
|
73
|
+
|
74
|
+
clearButton.addEventListener('click', () => {
|
75
|
+
element.value = '';
|
76
|
+
hiddenInput.value = '';
|
77
|
+
responseErrorContainer.innerText = '';
|
78
|
+
spinner.classList.add('invisible');
|
79
|
+
clearButton.classList.add('invisible');
|
80
|
+
});
|
81
|
+
|
82
|
+
element.addEventListener('keyup', () => {
|
83
|
+
const query = element.value;
|
84
|
+
const requestId = crypto.randomUUID();
|
85
|
+
lastRequestId = requestId;
|
86
|
+
|
87
|
+
clearButton.classList.add('invisible');
|
88
|
+
spinner.classList.remove('invisible');
|
89
|
+
hiddenInput.value = query;
|
90
|
+
searchResultsContainer.classList.add('d-none');
|
91
|
+
|
92
|
+
if (!query || query.length < 3) {
|
93
|
+
spinner.classList.add('invisible');
|
94
|
+
return;
|
95
|
+
}
|
96
|
+
|
97
|
+
searchResultsContainer.replaceChildren();
|
98
|
+
|
99
|
+
fetch(
|
100
|
+
'/_text_search/',
|
101
|
+
{
|
102
|
+
method: 'POST',
|
103
|
+
headers: { 'Content-Type': 'application/json' },
|
104
|
+
body: JSON.stringify({
|
105
|
+
request_id: requestId,
|
106
|
+
model,
|
107
|
+
value,
|
108
|
+
attributes,
|
109
|
+
query,
|
110
|
+
[token.param]: token.value,
|
111
|
+
}),
|
112
|
+
}
|
113
|
+
).then((response) => processResponse(
|
114
|
+
{ element, response, spinner, clearButton, hiddenInput, searchResultsContainer, responseErrorContainer }
|
115
|
+
));
|
116
|
+
});
|
117
|
+
|
118
|
+
hiddenInput.name = element.name;
|
119
|
+
if (element.value) hiddenInput.value = element.value;
|
120
|
+
form.append(hiddenInput);
|
121
|
+
element.parentElement.append(searchResultsContainer);
|
122
|
+
element.parentElement.append(clearButton);
|
123
|
+
element.parentElement.append(spinner);
|
124
|
+
element.parentElement.append(responseErrorContainer);
|
125
|
+
});
|
126
|
+
});
|
127
|
+
})();
|
@@ -0,0 +1,40 @@
|
|
1
|
+
(() => {
|
2
|
+
const cloneElement = (id) => ActiveElement.cloneElement('secret', id);
|
3
|
+
|
4
|
+
window.addEventListener('DOMContentLoaded', () => {
|
5
|
+
document.querySelectorAll('span[data-field-type="secret"]').forEach((element) => {
|
6
|
+
const secret = element.dataset.secret;
|
7
|
+
const showButton = cloneElement('show-button');
|
8
|
+
const hideButton = cloneElement('hide-button');
|
9
|
+
const content = cloneElement('content');
|
10
|
+
const placeholder = secret.replace(/./g, '*');
|
11
|
+
|
12
|
+
hideButton.classList.add('d-none');
|
13
|
+
content.classList.add('font-monospace');
|
14
|
+
content.classList.add('text-secondary');
|
15
|
+
content.innerText = placeholder;
|
16
|
+
|
17
|
+
showButton.addEventListener('click', () => {
|
18
|
+
showButton.classList.add('d-none');
|
19
|
+
hideButton.classList.remove('d-none');
|
20
|
+
content.classList.remove('text-secondary');
|
21
|
+
content.innerText = secret;
|
22
|
+
|
23
|
+
return false;
|
24
|
+
});
|
25
|
+
|
26
|
+
hideButton.addEventListener('click', () => {
|
27
|
+
showButton.classList.remove('d-none');
|
28
|
+
hideButton.classList.add('d-none');
|
29
|
+
content.classList.add('text-secondary');
|
30
|
+
content.innerText = placeholder;
|
31
|
+
|
32
|
+
return false;
|
33
|
+
});
|
34
|
+
|
35
|
+
element.append(content);
|
36
|
+
element.append(showButton);
|
37
|
+
element.append(hideButton);
|
38
|
+
});
|
39
|
+
});
|
40
|
+
})();
|
@@ -0,0 +1,36 @@
|
|
1
|
+
(() => {
|
2
|
+
const generateId = () => {
|
3
|
+
ActiveElement._id += 1;
|
4
|
+
|
5
|
+
return `active-element-element-${ActiveElement._id}`;
|
6
|
+
};
|
7
|
+
|
8
|
+
const getAntiCsrfToken = () => {
|
9
|
+
const param = document.querySelector('meta[name="csrf-param"]').content;
|
10
|
+
const value = document.querySelector('meta[name="csrf-token"]').content;
|
11
|
+
|
12
|
+
return { param, value };
|
13
|
+
};
|
14
|
+
|
15
|
+
const cloneElement = (category, id) => {
|
16
|
+
const element = document.querySelector(`#${category}-templates`)
|
17
|
+
.querySelector(`#${category}-${id}-template`)
|
18
|
+
.cloneNode(true);
|
19
|
+
element.id = ActiveElement.generateId();
|
20
|
+
return element;
|
21
|
+
};
|
22
|
+
|
23
|
+
const ActiveElement = {
|
24
|
+
log: (message) => { console.log(`[ActiveElement] ${message}`); },
|
25
|
+
_id: 0,
|
26
|
+
generateId,
|
27
|
+
getAntiCsrfToken,
|
28
|
+
cloneElement,
|
29
|
+
components: {},
|
30
|
+
jsonData: {},
|
31
|
+
};
|
32
|
+
|
33
|
+
window.ActiveElement = ActiveElement;
|
34
|
+
})();
|
35
|
+
|
36
|
+
ActiveElement.log('Initialized');
|
@@ -0,0 +1,42 @@
|
|
1
|
+
(() => {
|
2
|
+
const cloneElement = (id) => ActiveElement.cloneElement('theme', id);
|
3
|
+
|
4
|
+
window.addEventListener('DOMContentLoaded', () => {
|
5
|
+
const themeSelect = document.querySelector('#theme-select');
|
6
|
+
|
7
|
+
const setTheme = (theme) => {
|
8
|
+
const themeSelectButtons = themeSelect.children;
|
9
|
+
|
10
|
+
Object.entries(themeSelectButtons).forEach(([_, element]) => {
|
11
|
+
if (element.dataset.themeSwitchTo === theme) {
|
12
|
+
element.classList.add('d-none');
|
13
|
+
} else {
|
14
|
+
element.classList.remove('d-none');
|
15
|
+
}
|
16
|
+
});
|
17
|
+
|
18
|
+
localStorage.setItem('active_element-theme', theme);
|
19
|
+
document.querySelector('html').dataset.bsTheme = theme;
|
20
|
+
};
|
21
|
+
|
22
|
+
|
23
|
+
const initTheme = () => {
|
24
|
+
const theme = localStorage.getItem('active_element-theme') || 'light';
|
25
|
+
const themeSelectButtons = document.querySelector('#theme-select-buttons').children;
|
26
|
+
|
27
|
+
Object.entries(themeSelectButtons).forEach(([_, element]) => {
|
28
|
+
themeSelect.append(element);
|
29
|
+
|
30
|
+
element.addEventListener('click', (ev) => {
|
31
|
+
event.stopPropagation();
|
32
|
+
setTheme(element.dataset.themeSwitchTo);
|
33
|
+
return false;
|
34
|
+
});
|
35
|
+
});
|
36
|
+
|
37
|
+
setTheme(theme);
|
38
|
+
};
|
39
|
+
|
40
|
+
initTheme();
|
41
|
+
});
|
42
|
+
})();
|