active_element 0.0.19 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -4
- data/app/assets/javascripts/active_element/active_element.js +1 -0
- data/app/assets/javascripts/active_element/ae-ujs.js +531 -0
- data/app/assets/javascripts/active_element/secret.js +3 -0
- data/app/controllers/active_element/application_controller.rb +2 -2
- data/app/views/active_element/components/button.html.erb +1 -1
- data/app/views/active_element/components/form.html.erb +16 -6
- data/app/views/active_element/components/table/_collection_row.html.erb +1 -1
- data/app/views/active_element/components/table/collection.html.erb +6 -1
- data/app/views/active_element/default_views/edit.html.erb +2 -2
- data/app/views/active_element/default_views/index.html.erb +13 -2
- data/app/views/active_element/default_views/show.html.erb +2 -2
- data/app/views/layouts/active_element.html.erb +9 -1
- data/config/routes.rb +1 -0
- data/example_app/Gemfile.lock +1 -1
- data/lib/active_element/components/button.rb +18 -3
- data/lib/active_element/components/collection_table.rb +4 -2
- data/lib/active_element/components/form.rb +19 -3
- data/lib/active_element/components/util/default_display_value.rb +4 -0
- data/lib/active_element/components/util/form_field_mapping.rb +5 -2
- data/lib/active_element/components/util/record_mapping.rb +10 -1
- data/lib/active_element/controller_interface.rb +2 -1
- data/lib/active_element/controller_state.rb +1 -1
- data/lib/active_element/default_controller/controller.rb +37 -3
- data/lib/active_element/default_controller/search.rb +15 -1
- data/lib/active_element/field_options.rb +3 -3
- data/lib/active_element/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61176f5d488163d3f19599507ea4c7a2af3652f682df78e825efb199b1b2099d
|
4
|
+
data.tar.gz: 41af730b7f551b759c79cb0a541e1ba6732d4e45182b902987fbe4c92e42df0c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db6f404ef4eda9dff01f1dfb3c6462cc5a46af1b27889be6dd46984404b478143b9c5d3ce939bbb7e96a251038984f4fc6f98eef73066188916803143b6f8357
|
7
|
+
data.tar.gz: 4be7450ece40217768ce7ce6a8fee08c830438039177ebbeb40f228b0d2fc406cd8dd6417dcab9aba2222a28e902b4548ad7cbf7c3036cd71fc25cd88f791c12
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
active_element (0.0.
|
4
|
+
active_element (0.0.20)
|
5
5
|
bootstrap (~> 5.3.0alpha3)
|
6
6
|
kaminari (~> 1.2)
|
7
7
|
paintbrush (~> 0.1.2)
|
@@ -83,7 +83,7 @@ GEM
|
|
83
83
|
autoprefixer-rails (10.4.16.0)
|
84
84
|
execjs (~> 2)
|
85
85
|
bcrypt (3.1.18)
|
86
|
-
bootstrap (5.3.
|
86
|
+
bootstrap (5.3.3)
|
87
87
|
autoprefixer-rails (>= 9.1.0)
|
88
88
|
popper_js (>= 2.11.8, < 3)
|
89
89
|
brakeman (5.4.1)
|
@@ -150,7 +150,7 @@ GEM
|
|
150
150
|
mini_mime (1.1.5)
|
151
151
|
mini_portile2 (2.8.2)
|
152
152
|
minitest (5.18.1)
|
153
|
-
net-imap (0.4.
|
153
|
+
net-imap (0.4.11)
|
154
154
|
date
|
155
155
|
net-protocol
|
156
156
|
net-pop (0.1.2)
|
@@ -159,7 +159,7 @@ GEM
|
|
159
159
|
timeout
|
160
160
|
net-smtp (0.5.0)
|
161
161
|
net-protocol
|
162
|
-
nio4r (2.7.
|
162
|
+
nio4r (2.7.3)
|
163
163
|
nokogiri (1.15.2)
|
164
164
|
mini_portile2 (~> 2.8.2)
|
165
165
|
racc (~> 1.4)
|
@@ -0,0 +1,531 @@
|
|
1
|
+
/*
|
2
|
+
Unobtrusive JavaScript
|
3
|
+
https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts
|
4
|
+
Released under the MIT license
|
5
|
+
*/;
|
6
|
+
|
7
|
+
/* Reduced and customized for ActiveElementUJS */
|
8
|
+
|
9
|
+
(function() {
|
10
|
+
var context = this;
|
11
|
+
|
12
|
+
(function() {
|
13
|
+
(function() {
|
14
|
+
this.ActiveElementUJS = { linkClickSelector: 'a[data-ae-method]' };
|
15
|
+
}).call(this);
|
16
|
+
}).call(context);
|
17
|
+
|
18
|
+
var ActiveElementUJS = context.ActiveElementUJS;
|
19
|
+
|
20
|
+
(function() {
|
21
|
+
(function() {
|
22
|
+
var nonce;
|
23
|
+
|
24
|
+
nonce = null;
|
25
|
+
|
26
|
+
ActiveElementUJS.loadCSPNonce = function() {
|
27
|
+
var ref;
|
28
|
+
return nonce = (ref = document.querySelector("meta[name=csp-nonce]")) != null ? ref.content : void 0;
|
29
|
+
};
|
30
|
+
|
31
|
+
ActiveElementUJS.cspNonce = function() {
|
32
|
+
return nonce != null ? nonce : ActiveElementUJS.loadCSPNonce();
|
33
|
+
};
|
34
|
+
|
35
|
+
}).call(this);
|
36
|
+
(function() {
|
37
|
+
var expando, m;
|
38
|
+
|
39
|
+
m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector;
|
40
|
+
|
41
|
+
ActiveElementUJS.matches = function(element, selector) {
|
42
|
+
if (selector.exclude != null) {
|
43
|
+
return m.call(element, selector.selector) && !m.call(element, selector.exclude);
|
44
|
+
} else {
|
45
|
+
return m.call(element, selector);
|
46
|
+
}
|
47
|
+
};
|
48
|
+
|
49
|
+
expando = '_ujsData';
|
50
|
+
|
51
|
+
ActiveElementUJS.getData = function(element, key) {
|
52
|
+
var ref;
|
53
|
+
return (ref = element[expando]) != null ? ref[key] : void 0;
|
54
|
+
};
|
55
|
+
|
56
|
+
ActiveElementUJS.setData = function(element, key, value) {
|
57
|
+
if (element[expando] == null) {
|
58
|
+
element[expando] = {};
|
59
|
+
}
|
60
|
+
return element[expando][key] = value;
|
61
|
+
};
|
62
|
+
|
63
|
+
ActiveElementUJS.$ = function(selector) {
|
64
|
+
return Array.prototype.slice.call(document.querySelectorAll(selector));
|
65
|
+
};
|
66
|
+
|
67
|
+
}).call(this);
|
68
|
+
(function() {
|
69
|
+
var $, csrfParam, csrfToken;
|
70
|
+
|
71
|
+
$ = ActiveElementUJS.$;
|
72
|
+
|
73
|
+
csrfToken = ActiveElementUJS.csrfToken = function() {
|
74
|
+
var meta;
|
75
|
+
meta = document.querySelector('meta[name=csrf-token]');
|
76
|
+
return meta && meta.content;
|
77
|
+
};
|
78
|
+
|
79
|
+
csrfParam = ActiveElementUJS.csrfParam = function() {
|
80
|
+
var meta;
|
81
|
+
meta = document.querySelector('meta[name=csrf-param]');
|
82
|
+
return meta && meta.content;
|
83
|
+
};
|
84
|
+
|
85
|
+
ActiveElementUJS.CSRFProtection = function(xhr) {
|
86
|
+
var token;
|
87
|
+
token = csrfToken();
|
88
|
+
if (token != null) {
|
89
|
+
return xhr.setRequestHeader('X-CSRF-Token', token);
|
90
|
+
}
|
91
|
+
};
|
92
|
+
|
93
|
+
ActiveElementUJS.refreshCSRFTokens = function() {
|
94
|
+
var param, token;
|
95
|
+
token = csrfToken();
|
96
|
+
param = csrfParam();
|
97
|
+
if ((token != null) && (param != null)) {
|
98
|
+
return $('form input[name="' + param + '"]').forEach(function(input) {
|
99
|
+
return input.value = token;
|
100
|
+
});
|
101
|
+
}
|
102
|
+
};
|
103
|
+
|
104
|
+
}).call(this);
|
105
|
+
(function() {
|
106
|
+
var CustomEvent, fire, matches, preventDefault;
|
107
|
+
|
108
|
+
matches = ActiveElementUJS.matches;
|
109
|
+
|
110
|
+
CustomEvent = window.CustomEvent;
|
111
|
+
|
112
|
+
if (typeof CustomEvent !== 'function') {
|
113
|
+
CustomEvent = function(event, params) {
|
114
|
+
var evt;
|
115
|
+
evt = document.createEvent('CustomEvent');
|
116
|
+
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
|
117
|
+
return evt;
|
118
|
+
};
|
119
|
+
CustomEvent.prototype = window.Event.prototype;
|
120
|
+
preventDefault = CustomEvent.prototype.preventDefault;
|
121
|
+
CustomEvent.prototype.preventDefault = function() {
|
122
|
+
var result;
|
123
|
+
result = preventDefault.call(this);
|
124
|
+
if (this.cancelable && !this.defaultPrevented) {
|
125
|
+
Object.defineProperty(this, 'defaultPrevented', {
|
126
|
+
get: function() {
|
127
|
+
return true;
|
128
|
+
}
|
129
|
+
});
|
130
|
+
}
|
131
|
+
return result;
|
132
|
+
};
|
133
|
+
}
|
134
|
+
|
135
|
+
fire = ActiveElementUJS.fire = function(obj, name, data) {
|
136
|
+
var event;
|
137
|
+
event = new CustomEvent(name, {
|
138
|
+
bubbles: true,
|
139
|
+
cancelable: true,
|
140
|
+
detail: data
|
141
|
+
});
|
142
|
+
obj.dispatchEvent(event);
|
143
|
+
return !event.defaultPrevented;
|
144
|
+
};
|
145
|
+
|
146
|
+
ActiveElementUJS.stopEverything = function(e) {
|
147
|
+
fire(e.target, 'ujs:everythingStopped');
|
148
|
+
e.preventDefault();
|
149
|
+
e.stopPropagation();
|
150
|
+
return e.stopImmediatePropagation();
|
151
|
+
};
|
152
|
+
|
153
|
+
ActiveElementUJS.delegate = function(element, selector, eventType, handler) {
|
154
|
+
return element.addEventListener(eventType, function(e) {
|
155
|
+
var target;
|
156
|
+
target = e.target;
|
157
|
+
while (!(!(target instanceof Element) || matches(target, selector))) {
|
158
|
+
target = target.parentNode;
|
159
|
+
}
|
160
|
+
if (target instanceof Element && handler.call(target, e) === false) {
|
161
|
+
e.preventDefault();
|
162
|
+
return e.stopPropagation();
|
163
|
+
}
|
164
|
+
});
|
165
|
+
};
|
166
|
+
|
167
|
+
}).call(this);
|
168
|
+
(function() {
|
169
|
+
var AcceptHeaders, CSRFProtection, createXHR, cspNonce, fire, prepareOptions, processResponse;
|
170
|
+
|
171
|
+
cspNonce = ActiveElementUJS.cspNonce, CSRFProtection = ActiveElementUJS.CSRFProtection, fire = ActiveElementUJS.fire;
|
172
|
+
|
173
|
+
AcceptHeaders = {
|
174
|
+
'*': '*/*',
|
175
|
+
text: 'text/plain',
|
176
|
+
html: 'text/html',
|
177
|
+
xml: 'application/xml, text/xml',
|
178
|
+
json: 'application/json, text/javascript',
|
179
|
+
script: 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript'
|
180
|
+
};
|
181
|
+
|
182
|
+
ActiveElementUJS.ajax = function(options) {
|
183
|
+
var xhr;
|
184
|
+
options = prepareOptions(options);
|
185
|
+
xhr = createXHR(options, function() {
|
186
|
+
var ref, response;
|
187
|
+
response = processResponse((ref = xhr.response) != null ? ref : xhr.responseText, xhr.getResponseHeader('Content-Type'));
|
188
|
+
if (Math.floor(xhr.status / 100) === 2) {
|
189
|
+
if (typeof options.success === "function") {
|
190
|
+
options.success(response, xhr.statusText, xhr);
|
191
|
+
}
|
192
|
+
} else {
|
193
|
+
if (typeof options.error === "function") {
|
194
|
+
options.error(response, xhr.statusText, xhr);
|
195
|
+
}
|
196
|
+
}
|
197
|
+
return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : void 0;
|
198
|
+
});
|
199
|
+
if ((options.beforeSend != null) && !options.beforeSend(xhr, options)) {
|
200
|
+
return false;
|
201
|
+
}
|
202
|
+
if (xhr.readyState === XMLHttpRequest.OPENED) {
|
203
|
+
return xhr.send(options.data);
|
204
|
+
}
|
205
|
+
};
|
206
|
+
|
207
|
+
prepareOptions = function(options) {
|
208
|
+
options.url = options.url || location.href;
|
209
|
+
options.type = options.type.toUpperCase();
|
210
|
+
if (options.type === 'GET' && options.data) {
|
211
|
+
if (options.url.indexOf('?') < 0) {
|
212
|
+
options.url += '?' + options.data;
|
213
|
+
} else {
|
214
|
+
options.url += '&' + options.data;
|
215
|
+
}
|
216
|
+
}
|
217
|
+
if (AcceptHeaders[options.dataType] == null) {
|
218
|
+
options.dataType = '*';
|
219
|
+
}
|
220
|
+
options.accept = AcceptHeaders[options.dataType];
|
221
|
+
if (options.dataType !== '*') {
|
222
|
+
options.accept += ', */*; q=0.01';
|
223
|
+
}
|
224
|
+
return options;
|
225
|
+
};
|
226
|
+
|
227
|
+
createXHR = function(options, done) {
|
228
|
+
var xhr;
|
229
|
+
xhr = new XMLHttpRequest();
|
230
|
+
xhr.open(options.type, options.url, true);
|
231
|
+
xhr.setRequestHeader('Accept', options.accept);
|
232
|
+
if (typeof options.data === 'string') {
|
233
|
+
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
|
234
|
+
}
|
235
|
+
if (!options.crossDomain) {
|
236
|
+
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
237
|
+
CSRFProtection(xhr);
|
238
|
+
}
|
239
|
+
xhr.withCredentials = !!options.withCredentials;
|
240
|
+
xhr.onreadystatechange = function() {
|
241
|
+
if (xhr.readyState === XMLHttpRequest.DONE) {
|
242
|
+
return done(xhr);
|
243
|
+
}
|
244
|
+
};
|
245
|
+
return xhr;
|
246
|
+
};
|
247
|
+
|
248
|
+
processResponse = function(response, type) {
|
249
|
+
var parser, script;
|
250
|
+
if (typeof response === 'string' && typeof type === 'string') {
|
251
|
+
if (type.match(/\bjson\b/)) {
|
252
|
+
try {
|
253
|
+
response = JSON.parse(response);
|
254
|
+
} catch (error) {}
|
255
|
+
} else if (type.match(/\b(?:java|ecma)script\b/)) {
|
256
|
+
script = document.createElement('script');
|
257
|
+
script.setAttribute('nonce', cspNonce());
|
258
|
+
script.text = response;
|
259
|
+
document.head.appendChild(script).parentNode.removeChild(script);
|
260
|
+
} else if (type.match(/\b(xml|html|svg)\b/)) {
|
261
|
+
parser = new DOMParser();
|
262
|
+
type = type.replace(/;.+/, '');
|
263
|
+
try {
|
264
|
+
response = parser.parseFromString(response, type);
|
265
|
+
} catch (error) {}
|
266
|
+
}
|
267
|
+
}
|
268
|
+
return response;
|
269
|
+
};
|
270
|
+
|
271
|
+
ActiveElementUJS.href = function(element) {
|
272
|
+
return element.href;
|
273
|
+
};
|
274
|
+
|
275
|
+
ActiveElementUJS.isCrossDomain = function(url) {
|
276
|
+
var e, originAnchor, urlAnchor;
|
277
|
+
originAnchor = document.createElement('a');
|
278
|
+
originAnchor.href = location.href;
|
279
|
+
urlAnchor = document.createElement('a');
|
280
|
+
try {
|
281
|
+
urlAnchor.href = url;
|
282
|
+
return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) || (originAnchor.protocol + '//' + originAnchor.host === urlAnchor.protocol + '//' + urlAnchor.host));
|
283
|
+
} catch (error) {
|
284
|
+
e = error;
|
285
|
+
return true;
|
286
|
+
}
|
287
|
+
};
|
288
|
+
|
289
|
+
}).call(this);
|
290
|
+
(function() {
|
291
|
+
var matches, toArray;
|
292
|
+
|
293
|
+
matches = ActiveElementUJS.matches;
|
294
|
+
|
295
|
+
toArray = function(e) {
|
296
|
+
return Array.prototype.slice.call(e);
|
297
|
+
};
|
298
|
+
|
299
|
+
ActiveElementUJS.serializeElement = function(element, additionalParam) {
|
300
|
+
var inputs, params;
|
301
|
+
inputs = [element];
|
302
|
+
if (matches(element, 'form')) {
|
303
|
+
inputs = toArray(element.elements);
|
304
|
+
}
|
305
|
+
params = [];
|
306
|
+
inputs.forEach(function(input) {
|
307
|
+
if (!input.name || input.disabled) {
|
308
|
+
return;
|
309
|
+
}
|
310
|
+
if (matches(input, 'select')) {
|
311
|
+
return toArray(input.options).forEach(function(option) {
|
312
|
+
if (option.selected) {
|
313
|
+
return params.push({
|
314
|
+
name: input.name,
|
315
|
+
value: option.value
|
316
|
+
});
|
317
|
+
}
|
318
|
+
});
|
319
|
+
} else if (input.checked || ['radio', 'checkbox', 'submit'].indexOf(input.type) === -1) {
|
320
|
+
return params.push({
|
321
|
+
name: input.name,
|
322
|
+
value: input.value
|
323
|
+
});
|
324
|
+
}
|
325
|
+
});
|
326
|
+
if (additionalParam) {
|
327
|
+
params.push(additionalParam);
|
328
|
+
}
|
329
|
+
return params.map(function(param) {
|
330
|
+
if (param.name != null) {
|
331
|
+
return (encodeURIComponent(param.name)) + "=" + (encodeURIComponent(param.value));
|
332
|
+
} else {
|
333
|
+
return param;
|
334
|
+
}
|
335
|
+
}).join('&');
|
336
|
+
};
|
337
|
+
|
338
|
+
ActiveElementUJS.formElements = function(form, selector) {
|
339
|
+
if (matches(form, 'form')) {
|
340
|
+
return toArray(form.elements).filter(function(el) {
|
341
|
+
return matches(el, selector);
|
342
|
+
});
|
343
|
+
} else {
|
344
|
+
return toArray(form.querySelectorAll(selector));
|
345
|
+
}
|
346
|
+
};
|
347
|
+
|
348
|
+
}).call(this);
|
349
|
+
(function() {
|
350
|
+
var allowAction, fire, stopEverything;
|
351
|
+
|
352
|
+
fire = ActiveElementUJS.fire, stopEverything = ActiveElementUJS.stopEverything;
|
353
|
+
|
354
|
+
ActiveElementUJS.handleConfirm = function(e) {
|
355
|
+
if (!allowAction(this)) {
|
356
|
+
return stopEverything(e);
|
357
|
+
}
|
358
|
+
};
|
359
|
+
|
360
|
+
allowAction = function(element) {
|
361
|
+
var answer, callback, message;
|
362
|
+
message = element.getAttribute('data-confirm');
|
363
|
+
if (!message) {
|
364
|
+
return true;
|
365
|
+
}
|
366
|
+
answer = false;
|
367
|
+
if (fire(element, 'confirm')) {
|
368
|
+
try {
|
369
|
+
answer = confirm(message);
|
370
|
+
} catch (error) {}
|
371
|
+
callback = fire(element, 'confirm:complete', [answer]);
|
372
|
+
}
|
373
|
+
return answer && callback;
|
374
|
+
};
|
375
|
+
|
376
|
+
}).call(this);
|
377
|
+
|
378
|
+
(function() {
|
379
|
+
var stopEverything;
|
380
|
+
|
381
|
+
stopEverything = ActiveElementUJS.stopEverything;
|
382
|
+
|
383
|
+
ActiveElementUJS.handleMethod = function(e) {
|
384
|
+
var csrfParam, csrfToken, form, formContent, href, link, method;
|
385
|
+
link = this;
|
386
|
+
method = link.getAttribute('data-ae-method');
|
387
|
+
if (!method) {
|
388
|
+
return;
|
389
|
+
}
|
390
|
+
href = ActiveElementUJS.href(link);
|
391
|
+
csrfToken = ActiveElementUJS.csrfToken();
|
392
|
+
csrfParam = ActiveElementUJS.csrfParam();
|
393
|
+
form = document.createElement('form');
|
394
|
+
formContent = "<input name='_method' value='" + method + "' type='hidden' />";
|
395
|
+
if ((csrfParam != null) && (csrfToken != null) && !ActiveElementUJS.isCrossDomain(href)) {
|
396
|
+
formContent += "<input name='" + csrfParam + "' value='" + csrfToken + "' type='hidden' />";
|
397
|
+
}
|
398
|
+
formContent += '<input type="submit" />';
|
399
|
+
form.method = 'post';
|
400
|
+
form.action = href;
|
401
|
+
form.target = link.target;
|
402
|
+
form.innerHTML = formContent;
|
403
|
+
form.style.display = 'none';
|
404
|
+
document.body.appendChild(form);
|
405
|
+
form.querySelector('[type="submit"]').click();
|
406
|
+
return stopEverything(e);
|
407
|
+
};
|
408
|
+
|
409
|
+
}).call(this);
|
410
|
+
(function() {
|
411
|
+
var ajax, fire, getData, isCrossDomain, isRemote, matches, serializeElement, setData, stopEverything,
|
412
|
+
slice = [].slice;
|
413
|
+
|
414
|
+
matches = ActiveElementUJS.matches, getData = ActiveElementUJS.getData, setData = ActiveElementUJS.setData, fire = ActiveElementUJS.fire, stopEverything = ActiveElementUJS.stopEverything, ajax = ActiveElementUJS.ajax, isCrossDomain = ActiveElementUJS.isCrossDomain, serializeElement = ActiveElementUJS.serializeElement;
|
415
|
+
|
416
|
+
isRemote = function(element) {
|
417
|
+
var value;
|
418
|
+
value = element.getAttribute('data-remote');
|
419
|
+
return (value != null) && value !== 'false';
|
420
|
+
};
|
421
|
+
|
422
|
+
ActiveElementUJS.handleRemote = function(e) {
|
423
|
+
var button, data, dataType, element, method, url, withCredentials;
|
424
|
+
element = this;
|
425
|
+
if (!isRemote(element)) {
|
426
|
+
return true;
|
427
|
+
}
|
428
|
+
if (!fire(element, 'ajax:before')) {
|
429
|
+
fire(element, 'ajax:stopped');
|
430
|
+
return false;
|
431
|
+
}
|
432
|
+
withCredentials = element.getAttribute('data-with-credentials');
|
433
|
+
dataType = element.getAttribute('data-type') || 'script';
|
434
|
+
method = element.getAttribute('data-ae-method');
|
435
|
+
url = ActiveElementUJS.href(element);
|
436
|
+
data = element.getAttribute('data-params');
|
437
|
+
ajax({
|
438
|
+
type: method || 'GET',
|
439
|
+
url: url,
|
440
|
+
data: data,
|
441
|
+
dataType: dataType,
|
442
|
+
beforeSend: function(xhr, options) {
|
443
|
+
if (fire(element, 'ajax:beforeSend', [xhr, options])) {
|
444
|
+
return fire(element, 'ajax:send', [xhr]);
|
445
|
+
} else {
|
446
|
+
fire(element, 'ajax:stopped');
|
447
|
+
return false;
|
448
|
+
}
|
449
|
+
},
|
450
|
+
success: function() {
|
451
|
+
var args;
|
452
|
+
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
453
|
+
return fire(element, 'ajax:success', args);
|
454
|
+
},
|
455
|
+
error: function() {
|
456
|
+
var args;
|
457
|
+
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
458
|
+
return fire(element, 'ajax:error', args);
|
459
|
+
},
|
460
|
+
complete: function() {
|
461
|
+
var args;
|
462
|
+
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
463
|
+
return fire(element, 'ajax:complete', args);
|
464
|
+
},
|
465
|
+
crossDomain: isCrossDomain(url),
|
466
|
+
withCredentials: (withCredentials != null) && withCredentials !== 'false'
|
467
|
+
});
|
468
|
+
return stopEverything(e);
|
469
|
+
};
|
470
|
+
|
471
|
+
ActiveElementUJS.formSubmitButtonClick = function(e) {
|
472
|
+
var button, form;
|
473
|
+
button = this;
|
474
|
+
form = button.form;
|
475
|
+
if (!form) {
|
476
|
+
return;
|
477
|
+
}
|
478
|
+
if (button.name) {
|
479
|
+
setData(form, 'ujs:submit-button', {
|
480
|
+
name: button.name,
|
481
|
+
value: button.value
|
482
|
+
});
|
483
|
+
}
|
484
|
+
setData(form, 'ujs:formnovalidate-button', button.formNoValidate);
|
485
|
+
setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction'));
|
486
|
+
return setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod'));
|
487
|
+
};
|
488
|
+
|
489
|
+
ActiveElementUJS.preventInsignificantClick = function(e) {
|
490
|
+
var data, insignificantMetaClick, link, metaClick, method, nonPrimaryMouseClick;
|
491
|
+
link = this;
|
492
|
+
method = (link.getAttribute('data-ae-method') || 'GET').toUpperCase();
|
493
|
+
data = link.getAttribute('data-params');
|
494
|
+
metaClick = e.metaKey || e.ctrlKey;
|
495
|
+
insignificantMetaClick = metaClick && method === 'GET' && !data;
|
496
|
+
nonPrimaryMouseClick = (e.button != null) && e.button !== 0;
|
497
|
+
if (nonPrimaryMouseClick || insignificantMetaClick) {
|
498
|
+
return e.stopImmediatePropagation();
|
499
|
+
}
|
500
|
+
};
|
501
|
+
|
502
|
+
}).call(this);
|
503
|
+
(function() {
|
504
|
+
var $, CSRFProtection, delegate, disableElement, enableElement, fire, formSubmitButtonClick, getData, handleConfirm, handleDisabledElement, handleMethod, handleRemote, loadCSPNonce, preventInsignificantClick, refreshCSRFTokens;
|
505
|
+
|
506
|
+
fire = ActiveElementUJS.fire, delegate = ActiveElementUJS.delegate, getData = ActiveElementUJS.getData, $ = ActiveElementUJS.$, refreshCSRFTokens = ActiveElementUJS.refreshCSRFTokens, CSRFProtection = ActiveElementUJS.CSRFProtection, loadCSPNonce = ActiveElementUJS.loadCSPNonce, enableElement = ActiveElementUJS.enableElement, disableElement = ActiveElementUJS.disableElement, handleDisabledElement = ActiveElementUJS.handleDisabledElement, handleConfirm = ActiveElementUJS.handleConfirm, preventInsignificantClick = ActiveElementUJS.preventInsignificantClick, handleRemote = ActiveElementUJS.handleRemote, formSubmitButtonClick = ActiveElementUJS.formSubmitButtonClick, handleMethod = ActiveElementUJS.handleMethod;
|
507
|
+
|
508
|
+
ActiveElementUJS.start = function() {
|
509
|
+
if (window._active_element_loaded) {
|
510
|
+
throw new Error('active_element-ujs has already been loaded!');
|
511
|
+
}
|
512
|
+
delegate(document, ActiveElementUJS.linkClickSelector, 'click', handleMethod);
|
513
|
+
document.addEventListener('DOMContentLoaded', refreshCSRFTokens);
|
514
|
+
document.addEventListener('DOMContentLoaded', loadCSPNonce);
|
515
|
+
console.log("ActiveElement-ujs loaded.");
|
516
|
+
return window._active_element_loaded = true;
|
517
|
+
};
|
518
|
+
|
519
|
+
if (window.ActiveElementUJS === ActiveElementUJS && fire(document, 'rails:attachBindings')) {
|
520
|
+
ActiveElementUJS.start();
|
521
|
+
}
|
522
|
+
|
523
|
+
}).call(this);
|
524
|
+
}).call(this);
|
525
|
+
|
526
|
+
if (typeof module === "object" && module.exports) {
|
527
|
+
module.exports = ActiveElementUJS;
|
528
|
+
} else if (typeof define === "function" && define.amd) {
|
529
|
+
define(ActiveElementUJS);
|
530
|
+
}
|
531
|
+
}).call(this);
|
@@ -1,6 +1,8 @@
|
|
1
1
|
(() => {
|
2
2
|
const cloneElement = (id) => ActiveElement.cloneElement('secret', id);
|
3
3
|
|
4
|
+
if (window._active_element_secrets_loaded) return;
|
5
|
+
|
4
6
|
window.addEventListener(ActiveElement.reloadEvent, () => {
|
5
7
|
document.querySelectorAll('span[data-field-type="secret"]').forEach((element) => {
|
6
8
|
const secret = element.dataset.secret;
|
@@ -36,5 +38,6 @@
|
|
36
38
|
element.append(showButton);
|
37
39
|
element.append(hideButton);
|
38
40
|
});
|
41
|
+
window._active_element_secrets_loaded = true;
|
39
42
|
});
|
40
43
|
})();
|
@@ -27,8 +27,8 @@ module ActiveElement
|
|
27
27
|
helper_method :active_element
|
28
28
|
helper_method :render_active_element_hook
|
29
29
|
|
30
|
-
def render_active_element_hook(hook)
|
31
|
-
render_to_string partial: hook
|
30
|
+
def render_active_element_hook(hook, locals: {})
|
31
|
+
render_to_string partial: hook, locals: locals
|
32
32
|
rescue ActionView::MissingTemplate
|
33
33
|
nil
|
34
34
|
end
|
@@ -6,7 +6,7 @@
|
|
6
6
|
'bs-trigger': 'hover',
|
7
7
|
'bs-toggle': 'popover',
|
8
8
|
'bs-content': title,
|
9
|
-
} : {}).merge(method&.to_s == 'get' ? {} : { '
|
9
|
+
} : {}).merge(method&.to_s == 'get' ? {} : { 'ae-method' => method.to_s }),
|
10
10
|
class: "btn #{button_class} #{float_class} #{kwargs_class}",
|
11
11
|
**kwargs
|
12
12
|
) do %>
|
@@ -59,13 +59,23 @@
|
|
59
59
|
<% fields.each_slice(columns) do |field_group| %>
|
60
60
|
<div class="row form-fields mb-3">
|
61
61
|
<% field_group.each do |field, type, options| %>
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
<% if type != :hidden_field %>
|
63
|
+
<div class="col-sm-3">
|
64
|
+
<%= render partial: 'active_element/components/form/label',
|
65
|
+
locals: {
|
66
|
+
component: component,
|
67
|
+
id: id,
|
68
|
+
type: type,
|
69
|
+
form: form,
|
70
|
+
field: field,
|
71
|
+
options: options
|
72
|
+
} %>
|
73
|
+
</div>
|
74
|
+
<% end %>
|
66
75
|
|
67
76
|
|
68
|
-
|
77
|
+
|
78
|
+
<% if type != :hidden_field %><div class="col"><% end %>
|
69
79
|
<%= render partial: 'active_element/components/form/field',
|
70
80
|
locals: {
|
71
81
|
id: id,
|
@@ -76,7 +86,7 @@
|
|
76
86
|
component: component,
|
77
87
|
record: record }
|
78
88
|
%>
|
79
|
-
|
89
|
+
<% if type != :hidden_field %></div><% end %>
|
80
90
|
<% end %>
|
81
91
|
</div>
|
82
92
|
<% end %>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<tr class="<%= (index % 2).zero? ? 'even' : 'odd' %> <%= row_class_mapper.call(item) %>">
|
2
2
|
<% fields.each do |field, class_mapper, label, value_mapper| %>
|
3
|
-
<td class="align-
|
3
|
+
<td class="align-top <%= class_mapper.call(item) %>">
|
4
4
|
<% if component.secret_field?(field) %>
|
5
5
|
<%= controller.helpers.render partial: 'active_element/components/secret/field',
|
6
6
|
locals: { secret: value_mapper.call(item), label: label } %>
|
@@ -1,9 +1,9 @@
|
|
1
1
|
<%= active_element.component.page_title record.model_name.to_s.titleize %>
|
2
2
|
|
3
|
-
<%= render_active_element_hook "#{controller_path}/before_edit" %>
|
3
|
+
<%= render_active_element_hook "#{controller_path}/before_edit", locals: { record: record } %>
|
4
4
|
|
5
5
|
<%= active_element.component.form model: [namespace, record].compact,
|
6
6
|
destroy: active_element.state.deletable?,
|
7
7
|
fields: active_element.state.editable_fields %>
|
8
8
|
|
9
|
-
<%= render_active_element_hook "#{controller_path}/after_edit" %>
|
9
|
+
<%= render_active_element_hook "#{controller_path}/after_edit", locals: { record: record } %>
|
@@ -7,7 +7,17 @@
|
|
7
7
|
fields: active_element.state.searchable_fields %>
|
8
8
|
<% end %>
|
9
9
|
|
10
|
-
<%= render_active_element_hook "#{controller_path}/before_index" %>
|
10
|
+
<%= render_active_element_hook "#{controller_path}/before_index", locals: { collection: collection } %>
|
11
|
+
|
12
|
+
<% if nested_for.present? %>
|
13
|
+
<%=
|
14
|
+
active_element.component.page_section_title(
|
15
|
+
nested_for.map do |nested_for_record|
|
16
|
+
ActiveElement::Components::Util::DefaultDisplayValue.new(object: nested_for_record).value
|
17
|
+
end.join(', ')
|
18
|
+
)
|
19
|
+
%>
|
20
|
+
<% end %>
|
11
21
|
|
12
22
|
<% if active_element.state.search_required && search_filters.compact_blank.blank? %>
|
13
23
|
<% if active_element.state.creatable? %>
|
@@ -20,8 +30,9 @@
|
|
20
30
|
show: active_element.state.viewable?,
|
21
31
|
edit: active_element.state.editable?,
|
22
32
|
destroy: active_element.state.deletable?,
|
33
|
+
nested_for: nested_for,
|
23
34
|
collection: collection,
|
24
35
|
fields: active_element.state.listable_fields %>
|
25
36
|
<% end %>
|
26
37
|
|
27
|
-
<%= render_active_element_hook "#{controller_path}/after_index" %>
|
38
|
+
<%= render_active_element_hook "#{controller_path}/after_index", locals: { collection: collection } %>
|
@@ -1,10 +1,10 @@
|
|
1
1
|
<%= active_element.component.page_title record.model_name.to_s.titleize %>
|
2
2
|
|
3
|
-
<%= render_active_element_hook "#{controller_path}/before_show" %>
|
3
|
+
<%= render_active_element_hook "#{controller_path}/before_show", locals: { record: record } %>
|
4
4
|
|
5
5
|
<%= active_element.component.table item: record,
|
6
6
|
edit: active_element.state.editable?,
|
7
7
|
destroy: active_element.state.deletable?,
|
8
8
|
fields: active_element.state.viewable_fields %>
|
9
9
|
|
10
|
-
<%= render_active_element_hook "#{controller_path}/after_show" %>
|
10
|
+
<%= render_active_element_hook "#{controller_path}/after_show", locals: { record: record } %>
|
@@ -19,6 +19,15 @@
|
|
19
19
|
</script>
|
20
20
|
<% end %>
|
21
21
|
|
22
|
+
<% if respond_to?(:javascript_pack_tag) && defined? Webpacker %>
|
23
|
+
<%= begin
|
24
|
+
javascript_pack_tag 'application'
|
25
|
+
rescue Webpacker::Manifest::MissingEntryError
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
%>
|
29
|
+
<% end %>
|
30
|
+
|
22
31
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
23
32
|
<link rel="stylesheet"
|
24
33
|
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
|
@@ -116,6 +125,5 @@
|
|
116
125
|
<%= javascript_include_tag 'active_element/active_element', 'data-turbo-track': 'reload', 'data-turbolinks-track': 'reload' %>
|
117
126
|
<%= javascript_include_tag 'application', 'data-turbo-track': 'reload', 'data-turbolinks-track': 'reload' %>
|
118
127
|
<% end %>
|
119
|
-
|
120
128
|
</body>
|
121
129
|
</html>
|
data/config/routes.rb
CHANGED
data/example_app/Gemfile.lock
CHANGED
@@ -6,7 +6,7 @@ module ActiveElement
|
|
6
6
|
class Button
|
7
7
|
# rubocop:disable Metrics/MethodLength
|
8
8
|
def initialize(controller, record, flag_or_options, confirm: false, type: :primary, method: nil,
|
9
|
-
float: nil, icon: nil, tooltip: false, **kwargs, &block)
|
9
|
+
float: nil, icon: nil, tooltip: false, nested_for: nil, **kwargs, &block)
|
10
10
|
@controller = controller
|
11
11
|
@record = record.is_a?(ActiveRecord::Relation) ? record.klass.new : record
|
12
12
|
@flag_or_options = flag_or_options
|
@@ -20,6 +20,7 @@ module ActiveElement
|
|
20
20
|
@block_given = block_given?
|
21
21
|
@content = block.call if block_given?
|
22
22
|
@tooltip = tooltip
|
23
|
+
@nested_for = nested_for
|
23
24
|
end
|
24
25
|
# rubocop:enable Metrics/MethodLength
|
25
26
|
|
@@ -49,7 +50,7 @@ module ActiveElement
|
|
49
50
|
private
|
50
51
|
|
51
52
|
attr_reader :controller, :record, :flag_or_options, :float, :kwargs, :kwargs_class, :type, :method, :icon,
|
52
|
-
:block_given, :content, :confirm, :tooltip
|
53
|
+
:block_given, :content, :confirm, :tooltip, :nested_for
|
53
54
|
|
54
55
|
def link_method
|
55
56
|
return method if method.present?
|
@@ -116,7 +117,21 @@ module ActiveElement
|
|
116
117
|
def record_path
|
117
118
|
return nil unless record.class.is_a?(ActiveModel::Naming)
|
118
119
|
|
119
|
-
Util::RecordPath.new(record: record, controller: controller, type: type).path
|
120
|
+
Util::RecordPath.new(record: record, controller: controller, type: type).path(**nested_args)
|
121
|
+
end
|
122
|
+
|
123
|
+
def nested_args
|
124
|
+
case type
|
125
|
+
when :new
|
126
|
+
nested_params
|
127
|
+
else
|
128
|
+
{}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def nested_params
|
133
|
+
route = controller.request.routes.recognize_path(controller.request.path)
|
134
|
+
route.reject { |key, _value| %w[controller action].include?(key.to_s) }
|
120
135
|
end
|
121
136
|
end
|
122
137
|
end
|
@@ -15,7 +15,7 @@ module ActiveElement
|
|
15
15
|
# rubocop:disable Metrics/MethodLength
|
16
16
|
def initialize(controller, class_name:, collection:, fields:, params:, model_name: nil, style: nil,
|
17
17
|
show: false, new: false, edit: false, destroy: false, paginate: true, group: nil,
|
18
|
-
group_title: false, row_class: nil, title: nil, **_kwargs)
|
18
|
+
group_title: false, nested_for: nil, row_class: nil, title: nil, **_kwargs)
|
19
19
|
@controller = controller
|
20
20
|
@class_name = class_name
|
21
21
|
@model_name = model_name
|
@@ -32,6 +32,7 @@ module ActiveElement
|
|
32
32
|
@group_title = group_title
|
33
33
|
@row_class = row_class
|
34
34
|
@title = title
|
35
|
+
@nested_for = nested_for
|
35
36
|
verify_paginate_and_group
|
36
37
|
end
|
37
38
|
# rubocop:enable Metrics/MethodLength
|
@@ -54,6 +55,7 @@ module ActiveElement
|
|
54
55
|
destroy: destroy,
|
55
56
|
group: group,
|
56
57
|
group_title: group_title,
|
58
|
+
nested_for: nested_for,
|
57
59
|
display_pagination: display_pagination?,
|
58
60
|
page_sizes: [5, 10, 25, 50, 75, 100, 200],
|
59
61
|
page_size: page_size,
|
@@ -78,7 +80,7 @@ module ActiveElement
|
|
78
80
|
|
79
81
|
attr_reader :class_name, :collection, :fields, :style, :row_class,
|
80
82
|
:new, :show, :edit, :destroy,
|
81
|
-
:paginate, :params, :group, :group_title, :title
|
83
|
+
:paginate, :params, :group, :group_title, :title, :nested_for
|
82
84
|
|
83
85
|
def paginated_collection
|
84
86
|
return collection unless paginate && collection.respond_to?(:page) && !limit?
|
@@ -190,14 +190,30 @@ module ActiveElement
|
|
190
190
|
end
|
191
191
|
|
192
192
|
def base_options_for_select(field, field_options)
|
193
|
-
return normalized_options(field_options.fetch(:options)) if field_options.key?(:options)
|
193
|
+
return normalized_options(field_options.fetch(:options), field_options) if field_options.key?(:options)
|
194
194
|
return default_options_for_select(field, field_options) if record.class.is_a?(ActiveModel::Naming)
|
195
195
|
|
196
196
|
raise ArgumentError, "Must provide select options `[:#{field}, { options: [...] }]` or a record instance."
|
197
197
|
end
|
198
198
|
|
199
|
-
def normalized_options(options)
|
200
|
-
options.map
|
199
|
+
def normalized_options(options, field_options)
|
200
|
+
options.map do |option|
|
201
|
+
next option if option.is_a?(Array)
|
202
|
+
next active_record_option(option, field_options) if option.is_a?(ActiveRecord::Base)
|
203
|
+
[option, option] if option.is_a?(String)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def active_record_option(option, field_options)
|
208
|
+
[active_record_display_value(option, field_options), option.send(option.class.primary_key)]
|
209
|
+
end
|
210
|
+
|
211
|
+
def active_record_display_value(option, field_options)
|
212
|
+
if field_options[:display_value].is_a?(Proc) && record.present?
|
213
|
+
field_options[:display_value].call(option)
|
214
|
+
else
|
215
|
+
Util::DefaultDisplayValue.new(object: option).value
|
216
|
+
end
|
201
217
|
end
|
202
218
|
|
203
219
|
def default_class_name
|
@@ -12,6 +12,10 @@ module ActiveElement
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def value
|
15
|
+
if object.respond_to?(:default_display_attribute)
|
16
|
+
return object.public_send(object.default_display_attribute)
|
17
|
+
end
|
18
|
+
|
15
19
|
DEFAULT_FIELDS.each do |field|
|
16
20
|
return object.public_send(field) if active_record_value?(field)
|
17
21
|
return object[field] if hash_key(field) if hash_value?(field)
|
@@ -59,10 +59,12 @@ module ActiveElement
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def inline_configured_field(field)
|
62
|
-
field_options = FieldOptions.from_state(
|
62
|
+
field_options = FieldOptions.from_state(
|
63
|
+
field, controller.active_element.state, record, controller
|
64
|
+
)
|
63
65
|
return nil if field_options.blank?
|
64
66
|
|
65
|
-
[field, field_options.type, field_options.options]
|
67
|
+
[field, field_options.type, field_options.options.reverse_merge({ value: field_options.value })]
|
66
68
|
end
|
67
69
|
|
68
70
|
def field_with_provided_type_and_provided_options(field)
|
@@ -201,6 +203,7 @@ module ActiveElement
|
|
201
203
|
json: :json_field,
|
202
204
|
jsonb: :json_field,
|
203
205
|
geometry: :text_area,
|
206
|
+
text: :text_area,
|
204
207
|
datetime: :datetime_field,
|
205
208
|
date: :date_field,
|
206
209
|
time: :time_field,
|
@@ -80,13 +80,22 @@ module ActiveElement
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def value_from_config
|
83
|
-
field_options =
|
83
|
+
field_options = field_options_from_state
|
84
84
|
return nil if field_options.blank?
|
85
85
|
return nil unless DATABASE_TYPES.include?(field_options.type.to_sym)
|
86
86
|
|
87
87
|
send("#{field_options.type}_value")
|
88
88
|
end
|
89
89
|
|
90
|
+
def field_options_from_state
|
91
|
+
FieldOptions.from_state(
|
92
|
+
field,
|
93
|
+
component.controller.active_element.state,
|
94
|
+
record,
|
95
|
+
component.controller
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
90
99
|
# Override these methods as required in a class that includes this module:
|
91
100
|
|
92
101
|
def mapped_association_from_record
|
@@ -25,8 +25,9 @@ module ActiveElement
|
|
25
25
|
@authorize
|
26
26
|
end
|
27
27
|
|
28
|
-
def listable_fields(*args, order: nil)
|
28
|
+
def listable_fields(*args, order: nil, scope: nil)
|
29
29
|
state.list_order = order
|
30
|
+
state.list_scope = scope
|
30
31
|
state.listable_fields.concat(args.map(&:to_sym)).uniq!
|
31
32
|
end
|
32
33
|
|
@@ -8,7 +8,7 @@ module ActiveElement
|
|
8
8
|
attr_reader :permissions, :listable_fields, :viewable_fields, :editable_fields, :searchable_fields,
|
9
9
|
:field_options
|
10
10
|
attr_accessor :sign_in_path, :sign_in, :sign_in_method, :sign_out_path, :sign_out_method,
|
11
|
-
:deletable, :authorizor, :authenticator, :list_order, :search_required, :model
|
11
|
+
:deletable, :authorizor, :authenticator, :list_order, :list_scope, :search_required, :model
|
12
12
|
|
13
13
|
def initialize(controller:)
|
14
14
|
@controller = controller
|
@@ -15,7 +15,8 @@ module ActiveElement
|
|
15
15
|
controller.render 'active_element/default_views/index',
|
16
16
|
locals: {
|
17
17
|
collection: ordered(collection),
|
18
|
-
search_filters: default_text_search.search_filters
|
18
|
+
search_filters: default_text_search.search_filters,
|
19
|
+
nested_for: nested_relations
|
19
20
|
}
|
20
21
|
end
|
21
22
|
|
@@ -124,9 +125,12 @@ module ActiveElement
|
|
124
125
|
end
|
125
126
|
|
126
127
|
def collection
|
127
|
-
return model.
|
128
|
+
return model.public_send(list_scope).where(nested_scope) unless default_text_search.text_search?
|
128
129
|
|
129
|
-
model.
|
130
|
+
model.public_send(list_scope)
|
131
|
+
.left_outer_joins(default_text_search.search_relations)
|
132
|
+
.where(nested_scope)
|
133
|
+
.where(*default_text_search.text_search)
|
130
134
|
end
|
131
135
|
|
132
136
|
def render_range_error(error:, action:)
|
@@ -140,6 +144,36 @@ module ActiveElement
|
|
140
144
|
|
141
145
|
I18n.t('active_element.unexpected_error')
|
142
146
|
end
|
147
|
+
|
148
|
+
def list_scope
|
149
|
+
return :all if state.list_scope.blank?
|
150
|
+
return state.list_scope.call(request) if state.list_scope.is_a?(Proc)
|
151
|
+
|
152
|
+
state.list_scope
|
153
|
+
end
|
154
|
+
|
155
|
+
def nested_scope
|
156
|
+
nested_params.presence || noop
|
157
|
+
end
|
158
|
+
|
159
|
+
def noop
|
160
|
+
Arel::Nodes::True.new.eq(Arel::Nodes::True.new)
|
161
|
+
end
|
162
|
+
|
163
|
+
def nested_params
|
164
|
+
route = controller.request.routes.recognize_path(controller.request.path)
|
165
|
+
route.reject { |key, _value| %w[controller action].include?(key.to_s) }
|
166
|
+
end
|
167
|
+
|
168
|
+
def nested_relations
|
169
|
+
return [] if nested_params.blank?
|
170
|
+
|
171
|
+
nested_params.map do |key, value|
|
172
|
+
collection.model.reflections.values.find do |reflection|
|
173
|
+
reflection.foreign_key.to_s == key.to_s
|
174
|
+
end&.klass&.find(value)
|
175
|
+
end.compact
|
176
|
+
end
|
143
177
|
end
|
144
178
|
end
|
145
179
|
end
|
@@ -25,6 +25,7 @@ module ActiveElement
|
|
25
25
|
conditions = search_filters.to_h.map do |key, value|
|
26
26
|
next relation_matches(key, value) if relation?(key)
|
27
27
|
next datetime_between(key, value) if datetime?(key)
|
28
|
+
next join(key, value) if key.to_s.include?('.')
|
28
29
|
next model.arel_table[key].matches("#{value}%") if string_like_column?(key)
|
29
30
|
|
30
31
|
model.arel_table[key].eq(value)
|
@@ -36,7 +37,15 @@ module ActiveElement
|
|
36
37
|
end
|
37
38
|
|
38
39
|
def search_relations
|
39
|
-
search_filters.to_h.keys.map { |key| relation?(key) ? key.to_sym : nil }.compact
|
40
|
+
relation_joins = search_filters.to_h.keys.map { |key| relation?(key) ? key.to_sym : nil }.compact
|
41
|
+
(relation_joins + shorthand_joins).uniq
|
42
|
+
end
|
43
|
+
|
44
|
+
def shorthand_joins
|
45
|
+
search_filters.to_h
|
46
|
+
.keys
|
47
|
+
.select { |key| key.to_s.include?('.') }
|
48
|
+
.map { |key| key.partition('.').first.to_sym }
|
40
49
|
end
|
41
50
|
|
42
51
|
private
|
@@ -66,6 +75,11 @@ module ActiveElement
|
|
66
75
|
end.compact
|
67
76
|
end
|
68
77
|
|
78
|
+
def join(key, value)
|
79
|
+
table, _, column = key.to_s.partition('.')
|
80
|
+
relation(table).klass.arel_table[column].eq(value)
|
81
|
+
end
|
82
|
+
|
69
83
|
def noop
|
70
84
|
Arel::Nodes::True.new.eq(Arel::Nodes::True.new)
|
71
85
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module ActiveElement
|
2
2
|
class FieldOptions
|
3
|
-
attr_accessor :type, :options
|
3
|
+
attr_accessor :type, :options, :value
|
4
4
|
attr_reader :field
|
5
5
|
|
6
|
-
def self.from_state(field, state, record)
|
6
|
+
def self.from_state(field, state, record, controller)
|
7
7
|
block = state.field_options[field]
|
8
8
|
return nil if block.blank?
|
9
9
|
|
10
10
|
field_options = new(field)
|
11
|
-
block.call(field_options, record)
|
11
|
+
block.call(field_options, record, controller)
|
12
12
|
field_options
|
13
13
|
end
|
14
14
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_element
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.21
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Farrell
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bootstrap
|
@@ -114,6 +114,7 @@ files:
|
|
114
114
|
- active_element.gemspec
|
115
115
|
- app/assets/config/active_element/manifest.js
|
116
116
|
- app/assets/javascripts/active_element/active_element.js
|
117
|
+
- app/assets/javascripts/active_element/ae-ujs.js
|
117
118
|
- app/assets/javascripts/active_element/confirm.js
|
118
119
|
- app/assets/javascripts/active_element/form.js
|
119
120
|
- app/assets/javascripts/active_element/highlight.js
|