Version Description
- Urgent update
Download this release
Release Info
Developer | Unyson |
Plugin | Unyson |
Version | 2.7.6 |
Comparing to | |
See all releases |
Code changes from version 2.7.5 to 2.7.6
- framework/manifest.php +1 -1
- framework/static/js/fw-reactive-options-registry.js +413 -0
- framework/static/js/fw-reactive-options-simple-options.js +109 -0
- framework/static/js/fw-reactive-options-undefined-option.js +287 -0
- framework/static/js/fw-reactive-options.js +274 -0
- readme.txt +4 -1
- unyson.php +1 -1
framework/manifest.php
CHANGED
@@ -4,4 +4,4 @@ $manifest = array();
|
|
4 |
|
5 |
$manifest['name'] = __('Unyson', 'fw');
|
6 |
|
7 |
-
$manifest['version'] = '2.7.
|
4 |
|
5 |
$manifest['name'] = __('Unyson', 'fw');
|
6 |
|
7 |
+
$manifest['version'] = '2.7.6';
|
framework/static/js/fw-reactive-options-registry.js
ADDED
@@ -0,0 +1,413 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Basic options registry
|
3 |
+
*/
|
4 |
+
fw.options = (function ($, currentFwOptions) {
|
5 |
+
/**
|
6 |
+
* An object of hints
|
7 |
+
*/
|
8 |
+
var allOptionTypes = {};
|
9 |
+
|
10 |
+
currentFwOptions.get = get;
|
11 |
+
currentFwOptions.getAll = getAll;
|
12 |
+
currentFwOptions.register = register;
|
13 |
+
currentFwOptions.getOptionDescriptor = getOptionDescriptor;
|
14 |
+
currentFwOptions.startListeningToEvents = startListeningToEvents;
|
15 |
+
currentFwOptions.getContextOptions = getContextOptions;
|
16 |
+
currentFwOptions.findOptionInContextForPath = findOptionInContextForPath;
|
17 |
+
currentFwOptions.findOptionInSameContextFor = findOptionInSameContextFor;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* fw.options.getValueForEl(element)
|
21 |
+
* .then(function (values, optionDescriptor) {
|
22 |
+
* // current values for option type
|
23 |
+
* console.log(values)
|
24 |
+
* })
|
25 |
+
* .fail(function () {
|
26 |
+
* // value extraction failed for some reason
|
27 |
+
* });
|
28 |
+
*/
|
29 |
+
currentFwOptions.getValueForEl = getValueForEl;
|
30 |
+
currentFwOptions.getContextValue = getContextValue;
|
31 |
+
|
32 |
+
return currentFwOptions;
|
33 |
+
|
34 |
+
/**
|
35 |
+
* get hint object for a specific type
|
36 |
+
*/
|
37 |
+
function get (type) {
|
38 |
+
return allOptionTypes[type] || allOptionTypes['fw-undefined'];
|
39 |
+
}
|
40 |
+
|
41 |
+
function getAll () {
|
42 |
+
return allOptionTypes;
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Returns:
|
47 |
+
* el
|
48 |
+
* ID
|
49 |
+
* type
|
50 |
+
* isRootOption
|
51 |
+
* context
|
52 |
+
* nonOptionContext
|
53 |
+
*/
|
54 |
+
function getOptionDescriptor (el) {
|
55 |
+
var data = {};
|
56 |
+
|
57 |
+
if (! el) return null;
|
58 |
+
|
59 |
+
data.context = detectDOMContext(el);
|
60 |
+
|
61 |
+
data.el = findOptionDescriptorEl(el);
|
62 |
+
|
63 |
+
data.rootContext = findNonOptionContext(data.el);
|
64 |
+
data.id = $(data.el).attr('data-fw-option-id');
|
65 |
+
data.type = $(data.el).attr('data-fw-option-type');
|
66 |
+
data.isRootOption = isRootOption(data.el, findNonOptionContext(data.el));
|
67 |
+
data.hasNestedOptions = hasNestedOptions(data.el);
|
68 |
+
|
69 |
+
data.pathToTheTopContext = data.isRootOption
|
70 |
+
? []
|
71 |
+
: findPathToTheTopContext(data.el, findNonOptionContext(data.el));
|
72 |
+
|
73 |
+
return data;
|
74 |
+
}
|
75 |
+
|
76 |
+
function findOptionInSameContextFor (el, path) {
|
77 |
+
var rootContext = getOptionDescriptor(el).rootContext;
|
78 |
+
|
79 |
+
return findOptionInContextForPath(
|
80 |
+
rootContext, path
|
81 |
+
);
|
82 |
+
}
|
83 |
+
|
84 |
+
/**
|
85 |
+
* This receives a context (option as context works too)
|
86 |
+
* and returns the option descriptor which respects the path
|
87 |
+
*
|
88 |
+
* - form
|
89 |
+
* - .fw-backend-options-virtual-context
|
90 |
+
* - .fw-backend-option-descriptor
|
91 |
+
*
|
92 |
+
* path:
|
93 |
+
* id/other_id/another_one
|
94 |
+
*/
|
95 |
+
function findOptionInContextForPath (context, path) {
|
96 |
+
var pathToTheTop = path.split('/');
|
97 |
+
|
98 |
+
return pathToTheTop.reduce(function (currentContext, path, index) {
|
99 |
+
if (! currentContext) return false;
|
100 |
+
|
101 |
+
var elOrDescriptorForPath = _.compose(
|
102 |
+
index === pathToTheTop.length - 1
|
103 |
+
? getOptionDescriptor
|
104 |
+
: _.identity,
|
105 |
+
|
106 |
+
_.partial(
|
107 |
+
maybeFindFirstLevelOptionInContext,
|
108 |
+
currentContext
|
109 |
+
)
|
110 |
+
|
111 |
+
);
|
112 |
+
|
113 |
+
return elOrDescriptorForPath(path);
|
114 |
+
|
115 |
+
}, context);
|
116 |
+
|
117 |
+
function maybeFindFirstLevelOptionInContext (context, firstLevelId) {
|
118 |
+
return (getContextOptions(context).filter(
|
119 |
+
function (optionDescriptor) {
|
120 |
+
return optionDescriptor.id === firstLevelId;
|
121 |
+
}
|
122 |
+
)[0] || {}).el;
|
123 |
+
}
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* This receives a context (option as context works too)
|
128 |
+
* and returns the first level of options underneath it.
|
129 |
+
*
|
130 |
+
* - form
|
131 |
+
* - .fw-backend-options-virtual-context
|
132 |
+
* - .fw-backend-option-descriptor
|
133 |
+
*/
|
134 |
+
function getContextOptions (el) {
|
135 |
+
el = (el instanceof jQuery) ? el[0] : el;
|
136 |
+
|
137 |
+
if (! (
|
138 |
+
el.tagName === 'FORM'
|
139 |
+
||
|
140 |
+
el.classList.contains('fw-backend-options-virtual-context')
|
141 |
+
||
|
142 |
+
el.classList.contains('fw-backend-option-descriptor')
|
143 |
+
)) {
|
144 |
+
throw "You passed an incorrect context element."
|
145 |
+
}
|
146 |
+
|
147 |
+
return $(el)
|
148 |
+
.find('.fw-backend-option-descriptor')
|
149 |
+
.not(
|
150 |
+
$(el).find('.fw-backend-options-virtual-context .fw-backend-option-descriptor')
|
151 |
+
)
|
152 |
+
.toArray()
|
153 |
+
.map(getOptionDescriptor)
|
154 |
+
.filter(function (descriptor) {
|
155 |
+
return isRootOption(descriptor.el, el)
|
156 |
+
})
|
157 |
+
}
|
158 |
+
|
159 |
+
function getContextValue (el) {
|
160 |
+
var optionDescriptors = getContextOptions(el);
|
161 |
+
|
162 |
+
var promise = $.Deferred();
|
163 |
+
|
164 |
+
if (jQuery.when.all===undefined) {
|
165 |
+
jQuery.when.all = function(deferreds) {
|
166 |
+
var deferred = new jQuery.Deferred();
|
167 |
+
$.when.apply(jQuery, deferreds).then(
|
168 |
+
function() {
|
169 |
+
deferred.resolve(Array.prototype.slice.call(arguments));
|
170 |
+
},
|
171 |
+
function() {
|
172 |
+
deferred.fail(Array.prototype.slice.call(arguments));
|
173 |
+
});
|
174 |
+
|
175 |
+
return deferred;
|
176 |
+
}
|
177 |
+
}
|
178 |
+
|
179 |
+
$.when.all(
|
180 |
+
optionDescriptors.map(getValueForOptionDescriptor)
|
181 |
+
)
|
182 |
+
.then(function (valuesAsArray) {
|
183 |
+
var values = {};
|
184 |
+
|
185 |
+
optionDescriptors.map(function (optionDescriptor, index) {
|
186 |
+
values[optionDescriptor.id] = valuesAsArray[index].value;
|
187 |
+
});
|
188 |
+
|
189 |
+
promise.resolve({
|
190 |
+
valueAsArray: valuesAsArray,
|
191 |
+
optionDescriptors: optionDescriptors,
|
192 |
+
value: values
|
193 |
+
});
|
194 |
+
})
|
195 |
+
.fail(function () {
|
196 |
+
// TODO: pass a reason
|
197 |
+
promise.reject();
|
198 |
+
});
|
199 |
+
|
200 |
+
return promise;
|
201 |
+
}
|
202 |
+
|
203 |
+
function getValueForOptionDescriptor (optionDescriptor) {
|
204 |
+
var maybePromise = get(optionDescriptor.type).getValue(optionDescriptor)
|
205 |
+
|
206 |
+
var promise = maybePromise;
|
207 |
+
|
208 |
+
/**
|
209 |
+
* A promise has a then method usually
|
210 |
+
*/
|
211 |
+
if (! promise.then) {
|
212 |
+
promise = $.Deferred();
|
213 |
+
promise.resolve(maybePromise);
|
214 |
+
}
|
215 |
+
|
216 |
+
return promise;
|
217 |
+
}
|
218 |
+
|
219 |
+
function getValueForEl (el) {
|
220 |
+
return getValueForOptionDescriptor(getOptionDescriptor(el));
|
221 |
+
}
|
222 |
+
|
223 |
+
/**
|
224 |
+
* You are not registering here a full fledge class definition for an
|
225 |
+
* option type just like we have on backend. It is more of a hint on how
|
226 |
+
* to treat the option type on frontend. Everything should be working
|
227 |
+
* almost fine even if you don't provide any hints.
|
228 |
+
*
|
229 |
+
* interface:
|
230 |
+
*
|
231 |
+
* startListeningForChanges
|
232 |
+
* getValue
|
233 |
+
*/
|
234 |
+
function register (type, hintObject) {
|
235 |
+
// TODO: maybe start triggering events on option type register
|
236 |
+
|
237 |
+
if (allOptionTypes[type]) {
|
238 |
+
throw "Can't re-register an option type again";
|
239 |
+
}
|
240 |
+
|
241 |
+
allOptionTypes[type] = jQuery.extend(
|
242 |
+
{}, defaultHintObject(),
|
243 |
+
hintObject || {}
|
244 |
+
);
|
245 |
+
}
|
246 |
+
|
247 |
+
/**
|
248 |
+
* This will be automatically called at each fw:options:init event.
|
249 |
+
* This will make each option type start listening to events
|
250 |
+
*/
|
251 |
+
function startListeningToEvents (el) {
|
252 |
+
// TODO: compute path up untill non-option context
|
253 |
+
el = (el instanceof jQuery) ? el[0] : el;
|
254 |
+
|
255 |
+
[].map.call(
|
256 |
+
el.querySelectorAll('.fw-backend-option-descriptor[data-fw-option-type]'),
|
257 |
+
function (el) {
|
258 |
+
startListeningToEventsForSingle(getOptionDescriptor(el));
|
259 |
+
}
|
260 |
+
);
|
261 |
+
}
|
262 |
+
|
263 |
+
function startListeningToEventsForSingle (optionDescriptor) {
|
264 |
+
get(optionDescriptor.type).startListeningForChanges(optionDescriptor)
|
265 |
+
}
|
266 |
+
|
267 |
+
/**
|
268 |
+
* We rely on the fact that by default, when we try to register some option
|
269 |
+
* type -- the undefined and default one will be already registered.
|
270 |
+
*/
|
271 |
+
function defaultHintObject () {
|
272 |
+
return get('fw-undefined') || {};
|
273 |
+
}
|
274 |
+
|
275 |
+
function detectDOMContext (el) {
|
276 |
+
el = findOptionDescriptorEl(el);
|
277 |
+
|
278 |
+
var nonOptionContext = findNonOptionContext(el);
|
279 |
+
|
280 |
+
return isRootOption(el, nonOptionContext)
|
281 |
+
? nonOptionContext
|
282 |
+
: findOptionDescriptorEl(el.parentElement);
|
283 |
+
}
|
284 |
+
|
285 |
+
function findOptionDescriptorEl (el) {
|
286 |
+
el = (el instanceof jQuery) ? el[0] : el;
|
287 |
+
|
288 |
+
if (! el) return false;
|
289 |
+
|
290 |
+
if (el.classList.contains('fw-backend-option-descriptor')) {
|
291 |
+
return el;
|
292 |
+
} else {
|
293 |
+
var closestOption = $(el).closest(
|
294 |
+
'.fw-backend-option-descriptor'
|
295 |
+
);
|
296 |
+
|
297 |
+
if (closestOption.length === 0) {
|
298 |
+
throw "There is no option descriptor for that element."
|
299 |
+
}
|
300 |
+
|
301 |
+
return closestOption[0];
|
302 |
+
}
|
303 |
+
}
|
304 |
+
|
305 |
+
function isRootOption(el, nonOptionContext) {
|
306 |
+
var parent;
|
307 |
+
|
308 |
+
// traverse parents
|
309 |
+
while (el) {
|
310 |
+
parent = el.parentElement;
|
311 |
+
|
312 |
+
if (parent === nonOptionContext) {
|
313 |
+
return true;
|
314 |
+
}
|
315 |
+
|
316 |
+
if (parent && elementMatches(parent, '.fw-backend-option-descriptor')) {
|
317 |
+
return false;
|
318 |
+
}
|
319 |
+
|
320 |
+
el = parent;
|
321 |
+
}
|
322 |
+
}
|
323 |
+
|
324 |
+
function findPathToTheTopContext (el, nonOptionContext) {
|
325 |
+
var parent;
|
326 |
+
|
327 |
+
var result = [];
|
328 |
+
|
329 |
+
// traverse parents
|
330 |
+
while (el) {
|
331 |
+
parent = el.parentElement;
|
332 |
+
|
333 |
+
if (parent === nonOptionContext) {
|
334 |
+
return result;
|
335 |
+
}
|
336 |
+
|
337 |
+
if (parent && elementMatches(parent, '.fw-backend-option-descriptor')) {
|
338 |
+
// result.push(parent.getAttribute('data-fw-option-type'));
|
339 |
+
result.push(parent);
|
340 |
+
}
|
341 |
+
|
342 |
+
el = parent;
|
343 |
+
}
|
344 |
+
|
345 |
+
return result.reverse();
|
346 |
+
}
|
347 |
+
|
348 |
+
/**
|
349 |
+
* A non-option context has two possible values:
|
350 |
+
*
|
351 |
+
* - a form tag which encloses a list of root options
|
352 |
+
* - a virtual context is an el with `.fw-backend-options-virtual-context`
|
353 |
+
*/
|
354 |
+
function findNonOptionContext (el) {
|
355 |
+
var parent;
|
356 |
+
|
357 |
+
// traverse parents
|
358 |
+
while (el) {
|
359 |
+
parent = el.parentElement;
|
360 |
+
|
361 |
+
if (parent && elementMatches(parent, '.fw-backend-options-virtual-context, form')) {
|
362 |
+
return parent;
|
363 |
+
}
|
364 |
+
|
365 |
+
el = parent;
|
366 |
+
}
|
367 |
+
|
368 |
+
return null;
|
369 |
+
}
|
370 |
+
|
371 |
+
function hasNestedOptions (el) {
|
372 |
+
// exclude nested options within a virtual context
|
373 |
+
|
374 |
+
var optionDescriptor = findOptionDescriptorEl(el);
|
375 |
+
|
376 |
+
var hasVirtualContext = optionDescriptor.querySelector(
|
377 |
+
'.fw-backend-options-virtual-context'
|
378 |
+
);
|
379 |
+
|
380 |
+
if (! hasVirtualContext) {
|
381 |
+
return !! optionDescriptor.querySelector(
|
382 |
+
'.fw-backend-option-descriptor'
|
383 |
+
);
|
384 |
+
}
|
385 |
+
|
386 |
+
// check if we have options which are not in the virtual context
|
387 |
+
return optionDescriptor.querySelectorAll(
|
388 |
+
'.fw-backend-option-descriptor'
|
389 |
+
).length > optionDescriptor.querySelectorAll(
|
390 |
+
'.fw-backend-options-virtual-context .fw-backend-option-descriptor'
|
391 |
+
).length;
|
392 |
+
}
|
393 |
+
|
394 |
+
function elementMatches (element, selector) {
|
395 |
+
var matchesFn;
|
396 |
+
|
397 |
+
// find vendor prefix
|
398 |
+
[
|
399 |
+
'matches','webkitMatchesSelector','mozMatchesSelector',
|
400 |
+
'msMatchesSelector','oMatchesSelector'
|
401 |
+
].some(function(fn) {
|
402 |
+
if (typeof document.body[fn] === 'function') {
|
403 |
+
matchesFn = fn;
|
404 |
+
return true;
|
405 |
+
}
|
406 |
+
|
407 |
+
return false;
|
408 |
+
})
|
409 |
+
|
410 |
+
return element[matchesFn](selector);
|
411 |
+
}
|
412 |
+
})(jQuery, (fw.options || {}));
|
413 |
+
|
framework/static/js/fw-reactive-options-simple-options.js
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function($) {
|
2 |
+
var simpleInputs = [
|
3 |
+
'text',
|
4 |
+
'short-text',
|
5 |
+
'hidden',
|
6 |
+
'password',
|
7 |
+
'textarea',
|
8 |
+
'html',
|
9 |
+
'html-fixed',
|
10 |
+
'html-full',
|
11 |
+
'select',
|
12 |
+
'short-select',
|
13 |
+
'gmap-key',
|
14 |
+
'slider',
|
15 |
+
'short-slider',
|
16 |
+
];
|
17 |
+
|
18 |
+
simpleInputs.map(function(optionType) {
|
19 |
+
fw.options.register(optionType, {
|
20 |
+
getValue: getValueForSimpleInput,
|
21 |
+
});
|
22 |
+
});
|
23 |
+
|
24 |
+
function getValueForSimpleInput(optionDescriptor) {
|
25 |
+
return {
|
26 |
+
value: optionDescriptor.el.querySelector('input, textarea, select')
|
27 |
+
.value,
|
28 |
+
optionDescriptor: optionDescriptor,
|
29 |
+
};
|
30 |
+
}
|
31 |
+
|
32 |
+
fw.options.register('unique', {
|
33 |
+
getValue: function(optionDescriptor) {
|
34 |
+
var actualValue = optionDescriptor.el.querySelector(
|
35 |
+
'input, textarea, select'
|
36 |
+
).value;
|
37 |
+
|
38 |
+
return {
|
39 |
+
value: !!actualValue.trim() ? actualValue : fw.randomMD5(),
|
40 |
+
optionDescriptor: optionDescriptor,
|
41 |
+
};
|
42 |
+
},
|
43 |
+
});
|
44 |
+
|
45 |
+
fw.options.register('checkbox', {
|
46 |
+
getValue: function(optionDescriptor) {
|
47 |
+
return {
|
48 |
+
value: optionDescriptor.el.querySelector(
|
49 |
+
'input.fw-option-type-checkbox'
|
50 |
+
).checked,
|
51 |
+
optionDescriptor: optionDescriptor,
|
52 |
+
};
|
53 |
+
},
|
54 |
+
});
|
55 |
+
|
56 |
+
fw.options.register('checkboxes', {
|
57 |
+
getValue: function(optionDescriptor) {
|
58 |
+
var checkboxes = $(optionDescriptor.el)
|
59 |
+
.find('[type="checkbox"]')
|
60 |
+
.slice(1);
|
61 |
+
|
62 |
+
var value = {};
|
63 |
+
|
64 |
+
checkboxes.toArray().map(function(el) {
|
65 |
+
value[$(el).attr('data-fw-checkbox-id')] = el.checked;
|
66 |
+
});
|
67 |
+
|
68 |
+
return {
|
69 |
+
value: value,
|
70 |
+
optionDescriptor: optionDescriptor,
|
71 |
+
};
|
72 |
+
},
|
73 |
+
});
|
74 |
+
|
75 |
+
fw.options.register('radio', {
|
76 |
+
getValue: function(optionDescriptor) {
|
77 |
+
return {
|
78 |
+
value: $(optionDescriptor.el).find('input:checked').val(),
|
79 |
+
optionDescriptor: optionDescriptor,
|
80 |
+
};
|
81 |
+
},
|
82 |
+
});
|
83 |
+
|
84 |
+
fw.options.register('select-multiple', {
|
85 |
+
getValue: function(optionDescriptor) {
|
86 |
+
return {
|
87 |
+
value: $(optionDescriptor.el.querySelector('select')).val(),
|
88 |
+
optionDescriptor: optionDescriptor,
|
89 |
+
};
|
90 |
+
},
|
91 |
+
});
|
92 |
+
|
93 |
+
fw.options.register('multi', {
|
94 |
+
getValue: function(optionDescriptor) {
|
95 |
+
var promise = $.Deferred();
|
96 |
+
|
97 |
+
fw.options
|
98 |
+
.getContextValue(optionDescriptor.el)
|
99 |
+
.then(function(result) {
|
100 |
+
promise.resolve({
|
101 |
+
value: result.value,
|
102 |
+
optionDescriptor: optionDescriptor,
|
103 |
+
});
|
104 |
+
});
|
105 |
+
|
106 |
+
return promise;
|
107 |
+
},
|
108 |
+
});
|
109 |
+
})(jQuery);
|
framework/static/js/fw-reactive-options-undefined-option.js
ADDED
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function ($) {
|
2 |
+
|
3 |
+
fw.options.register('fw-undefined', {
|
4 |
+
startListeningForChanges: defaultStartListeningForChanges,
|
5 |
+
getValue: defaultGetValue
|
6 |
+
});
|
7 |
+
|
8 |
+
function defaultGetValue (optionDescriptor) {
|
9 |
+
var resultPromise = $.Deferred();
|
10 |
+
|
11 |
+
/**
|
12 |
+
* If we get a really undefined option type.
|
13 |
+
*/
|
14 |
+
if (optionDescriptor.type === 'fw-undefined') {
|
15 |
+
resultPromise.resolve({
|
16 |
+
value: '',
|
17 |
+
optionDescriptor: optionDescriptor
|
18 |
+
})
|
19 |
+
|
20 |
+
return resultPromise;
|
21 |
+
}
|
22 |
+
|
23 |
+
// 1. find all inputs and ignore virtual contexts
|
24 |
+
// this really should include nested options and properly serialize
|
25 |
+
// them together
|
26 |
+
//
|
27 |
+
// we should serialize those inputs into an object, based on their
|
28 |
+
// name attribute
|
29 |
+
var formInstance = new FormSerializer($, optionDescriptor.el);
|
30 |
+
|
31 |
+
var inputValues = formInstance.addPairs(
|
32 |
+
findInputsFromAContextAndIgnoreVirtualScopes(
|
33 |
+
optionDescriptor.el
|
34 |
+
).serializeArray()
|
35 |
+
).serialize();
|
36 |
+
|
37 |
+
// 2. remove name_prefixes from those inputs
|
38 |
+
// optionsDescriptor.id === 'laptop'
|
39 |
+
// name="fw_options[nesting][laptop]"
|
40 |
+
//
|
41 |
+
// This step should get
|
42 |
+
// inputValues['fw_options']['nesting']['laptop']
|
43 |
+
|
44 |
+
inputValues = inputValues[
|
45 |
+
Object.keys(inputValues)[0]
|
46 |
+
];
|
47 |
+
|
48 |
+
if (optionDescriptor.pathToTheTopContext.length > 0) {
|
49 |
+
var IDs = optionDescriptor.pathToTheTopContext.map(
|
50 |
+
fw.options.getOptionDescriptor
|
51 |
+
);
|
52 |
+
|
53 |
+
IDs.map(function (localDescriptor) {
|
54 |
+
inputValues = inputValues[localDescriptor.id];
|
55 |
+
});
|
56 |
+
}
|
57 |
+
|
58 |
+
var options = {};
|
59 |
+
|
60 |
+
options[optionDescriptor.id] = JSON.parse(jQuery(optionDescriptor.el).attr(
|
61 |
+
'data-fw-for-js'
|
62 |
+
)).option;
|
63 |
+
|
64 |
+
// 3. construct an AJAX request with correct options and input values
|
65 |
+
$.ajax({
|
66 |
+
type: 'POST',
|
67 |
+
dataType: "json",
|
68 |
+
url: ajaxurl,
|
69 |
+
data: {
|
70 |
+
action: 'fw_backend_options_get_values',
|
71 |
+
name_prefix: 'fw_options',
|
72 |
+
options: [
|
73 |
+
options
|
74 |
+
],
|
75 |
+
fw_options: inputValues
|
76 |
+
}
|
77 |
+
})
|
78 |
+
.then(function (response, status, request) {
|
79 |
+
if (response.success && request.status === 200) {
|
80 |
+
resultPromise.resolve(
|
81 |
+
{
|
82 |
+
value: response.data.values[optionDescriptor.id],
|
83 |
+
optionDescriptor: optionDescriptor
|
84 |
+
}
|
85 |
+
);
|
86 |
+
} else {
|
87 |
+
resultPromise.reject();
|
88 |
+
}
|
89 |
+
})
|
90 |
+
.fail(function () {
|
91 |
+
// TODO: pass a reason
|
92 |
+
resultPromise.reject();
|
93 |
+
});
|
94 |
+
|
95 |
+
return resultPromise;
|
96 |
+
}
|
97 |
+
|
98 |
+
// By default, for unknown option types do listening only once
|
99 |
+
function defaultStartListeningForChanges (optionDescriptor) {
|
100 |
+
if (optionDescriptor.el.classList.contains('fw-listening-started')) {
|
101 |
+
return;
|
102 |
+
}
|
103 |
+
|
104 |
+
optionDescriptor.el.classList.add('fw-listening-started');
|
105 |
+
|
106 |
+
listenToChangesForCurrentOptionAndPreserveScoping(
|
107 |
+
optionDescriptor.el,
|
108 |
+
_.throttle(function (e) {
|
109 |
+
fw.options.trigger.changeForEl(e.target);
|
110 |
+
}, 300)
|
111 |
+
);
|
112 |
+
|
113 |
+
if (optionDescriptor.hasNestedOptions) {
|
114 |
+
fw.options.on.changeByContext(optionDescriptor.el, function (nestedDescriptor) {
|
115 |
+
fw.options.trigger.changeForEl(optionDescriptor.el);
|
116 |
+
});
|
117 |
+
}
|
118 |
+
}
|
119 |
+
|
120 |
+
/**
|
121 |
+
* TODO
|
122 |
+
* rewrite that with:
|
123 |
+
*
|
124 |
+
* - Array.filter
|
125 |
+
* - Array.includes
|
126 |
+
* - addEventListener
|
127 |
+
* - querySelectorAll
|
128 |
+
*/
|
129 |
+
function listenToChangesForCurrentOptionAndPreserveScoping (el, callback) {
|
130 |
+
jQuery(el).find(
|
131 |
+
'input, select, textarea'
|
132 |
+
).not(
|
133 |
+
jQuery(el).find(
|
134 |
+
'.fw-backend-option-descriptor input'
|
135 |
+
).add(
|
136 |
+
jQuery(el).find(
|
137 |
+
'.fw-backend-option-descriptor select'
|
138 |
+
)
|
139 |
+
).add(
|
140 |
+
jQuery(el).find(
|
141 |
+
'.fw-backend-option-descriptor textarea'
|
142 |
+
)
|
143 |
+
).add(
|
144 |
+
jQuery(el).find(
|
145 |
+
'.fw-backend-options-virtual-context input'
|
146 |
+
)
|
147 |
+
).add(
|
148 |
+
jQuery(el).find(
|
149 |
+
'.fw-backend-options-virtual-context select'
|
150 |
+
)
|
151 |
+
).add(
|
152 |
+
jQuery(el).find(
|
153 |
+
'.fw-backend-options-virtual-context textarea'
|
154 |
+
)
|
155 |
+
)
|
156 |
+
).on('change', callback);
|
157 |
+
}
|
158 |
+
|
159 |
+
function findInputsFromAContextAndIgnoreVirtualScopes (el) {
|
160 |
+
return jQuery(el).find(
|
161 |
+
'input, select, textarea'
|
162 |
+
).not(
|
163 |
+
jQuery(el).find(
|
164 |
+
'.fw-backend-options-virtual-context input'
|
165 |
+
).add(
|
166 |
+
jQuery(el).find(
|
167 |
+
'.fw-backend-options-virtual-context select'
|
168 |
+
)
|
169 |
+
).add(
|
170 |
+
jQuery(el).find(
|
171 |
+
'.fw-backend-options-virtual-context textarea'
|
172 |
+
)
|
173 |
+
)
|
174 |
+
).not(
|
175 |
+
jQuery(el).find(
|
176 |
+
'.fw-filter-from-serialization input'
|
177 |
+
).add(
|
178 |
+
jQuery(el).find(
|
179 |
+
'.fw-filter-from-serialization select'
|
180 |
+
)
|
181 |
+
).add(
|
182 |
+
jQuery(el).find(
|
183 |
+
'.fw-filter-from-serialization textarea'
|
184 |
+
)
|
185 |
+
)
|
186 |
+
);
|
187 |
+
}
|
188 |
+
|
189 |
+
/**
|
190 |
+
* USAGE:
|
191 |
+
*
|
192 |
+
* var formInstance = new FormSerializer(jQuery, document.body);
|
193 |
+
*
|
194 |
+
* formInstance.addPairs(jQuery('input').serializeArray());
|
195 |
+
* formInstance.serialize();
|
196 |
+
*/
|
197 |
+
function FormSerializer(helper, $form) {
|
198 |
+
var patterns = {
|
199 |
+
push: /^$/,
|
200 |
+
fixed: /^\d+$/,
|
201 |
+
validate: /^[a-z][a-z0-9_-]*(?:\[(?:\d*|[a-z0-9_-]+)\])*$/i,
|
202 |
+
key: /[a-z0-9_-]+|(?=\[\])/gi,
|
203 |
+
named: /^[a-z0-9_-]+$/i
|
204 |
+
};
|
205 |
+
|
206 |
+
// private variables
|
207 |
+
var data = {},
|
208 |
+
pushes = {};
|
209 |
+
|
210 |
+
// private API
|
211 |
+
function build(base, key, value) {
|
212 |
+
base[key] = value;
|
213 |
+
return base;
|
214 |
+
}
|
215 |
+
|
216 |
+
function makeObject(root, value) {
|
217 |
+
var keys = root.match(patterns.key), k;
|
218 |
+
|
219 |
+
// nest, nest, ..., nest
|
220 |
+
while ((k = keys.pop()) !== undefined) {
|
221 |
+
// foo[]
|
222 |
+
if (patterns.push.test(k)) {
|
223 |
+
var idx = incrementPush(root.replace(/\[\]$/, ''));
|
224 |
+
value = build([], idx, value);
|
225 |
+
}
|
226 |
+
|
227 |
+
// foo[n]
|
228 |
+
else if (patterns.fixed.test(k)) {
|
229 |
+
value = build([], k, value);
|
230 |
+
}
|
231 |
+
|
232 |
+
// foo; foo[bar]
|
233 |
+
else if (patterns.named.test(k)) {
|
234 |
+
value = build({}, k, value);
|
235 |
+
}
|
236 |
+
}
|
237 |
+
|
238 |
+
return value;
|
239 |
+
}
|
240 |
+
|
241 |
+
function incrementPush(key) {
|
242 |
+
if (pushes[key] === undefined) {
|
243 |
+
pushes[key] = 0;
|
244 |
+
}
|
245 |
+
|
246 |
+
return pushes[key]++;
|
247 |
+
}
|
248 |
+
|
249 |
+
function encode(pair) {
|
250 |
+
switch ($('[name="' + pair.name + '"]', $form).attr("type")) {
|
251 |
+
case "checkbox":
|
252 |
+
return pair.value === "on" ? true : pair.value;
|
253 |
+
default:
|
254 |
+
return pair.value;
|
255 |
+
}
|
256 |
+
}
|
257 |
+
|
258 |
+
function addPair(pair) {
|
259 |
+
if (! patterns.validate.test(pair.name)) return this;
|
260 |
+
|
261 |
+
var obj = makeObject(pair.name, encode(pair));
|
262 |
+
data = helper.extend(true, data, obj);
|
263 |
+
|
264 |
+
return this;
|
265 |
+
}
|
266 |
+
|
267 |
+
function addPairs(pairs) {
|
268 |
+
if (!helper.isArray(pairs)) {
|
269 |
+
throw new Error("formSerializer.addPairs expects an Array");
|
270 |
+
}
|
271 |
+
for (var i=0, len=pairs.length; i<len; i++) {
|
272 |
+
this.addPair(pairs[i]);
|
273 |
+
}
|
274 |
+
return this;
|
275 |
+
}
|
276 |
+
|
277 |
+
function serialize() {
|
278 |
+
return data;
|
279 |
+
}
|
280 |
+
|
281 |
+
// public API
|
282 |
+
this.addPair = addPair;
|
283 |
+
this.addPairs = addPairs;
|
284 |
+
this.serialize = serialize;
|
285 |
+
};
|
286 |
+
|
287 |
+
})(jQuery);
|
framework/static/js/fw-reactive-options.js
ADDED
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Basic mechanism that allows option types to notify the rest of the world
|
3 |
+
* about the fact that they was changed by the user.
|
4 |
+
*
|
5 |
+
* Each option type is responsible for triggering such events and they should
|
6 |
+
* at least try to supply a reasonable current value for it. Additional meta
|
7 |
+
* data about the particular option type is infered automatically and can be
|
8 |
+
* overrided by the option which triggers the event.
|
9 |
+
*
|
10 |
+
* In theory (and also in practice), options can and should trigger events
|
11 |
+
* which are different than the `change`, because complicated option types
|
12 |
+
* has many lifecycle events which everyone should be aware about and be able
|
13 |
+
* to hook into. A lot of options already trigger such events but they do that
|
14 |
+
* in an inconsistent manner which leads to poorly named and poorly namespaced
|
15 |
+
* events.
|
16 |
+
*
|
17 |
+
* TODO: document this better
|
18 |
+
*/
|
19 |
+
fw.options = (function($, currentFwOptions) {
|
20 |
+
currentFwOptions.on = on;
|
21 |
+
currentFwOptions.off = off;
|
22 |
+
currentFwOptions.trigger = trigger;
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Allows:
|
26 |
+
* fw.options.trigger(...)
|
27 |
+
* fw.options.trigger.change(...)
|
28 |
+
* fw.options.trigger.forEl(...)
|
29 |
+
* fw.options.trigger.changeForEl(...)
|
30 |
+
* fw.options.trigger.scopedByType(...)
|
31 |
+
*/
|
32 |
+
currentFwOptions.trigger.change = triggerChange;
|
33 |
+
currentFwOptions.trigger.forEl = triggerForEl;
|
34 |
+
currentFwOptions.trigger.changeForEl = triggerChangeForEl;
|
35 |
+
currentFwOptions.trigger.scopedByType = triggerScopedByType;
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Allows:
|
39 |
+
* fw.options.on(...)
|
40 |
+
* fw.options.on.one(...)
|
41 |
+
* fw.options.on.change(...)
|
42 |
+
* fw.options.on.changeByContext(...)
|
43 |
+
*/
|
44 |
+
currentFwOptions.on.one = one;
|
45 |
+
currentFwOptions.on.change = onChange;
|
46 |
+
currentFwOptions.on.changeByContext = onChangeByContext;
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Allows:
|
50 |
+
* fw.options.off(...)
|
51 |
+
* fw.options.off.change(...)
|
52 |
+
*/
|
53 |
+
currentFwOptions.off.change = offChange;
|
54 |
+
|
55 |
+
/**
|
56 |
+
* A small little service for fetching HTML for option types from server.
|
57 |
+
* It will perform caching for results inside a key-value store.
|
58 |
+
*
|
59 |
+
* Allows:
|
60 |
+
*
|
61 |
+
* fw.options.fetchHtml(options, values)
|
62 |
+
* fw.options.fetchHtml.getCacheEntryFor(options, values)
|
63 |
+
* fw.options.fetchHtml.emptyCache();
|
64 |
+
*
|
65 |
+
* // TODO: provide a way to empty cache for a specific set of options???
|
66 |
+
*/
|
67 |
+
var htmlCache = {};
|
68 |
+
|
69 |
+
fw.options.fetchHtml = fetchHtml;
|
70 |
+
fw.options.fetchHtml.getCacheEntryFor = fetchHtmlGetCacheEntryFor;
|
71 |
+
fw.options.fetchHtml.emptyCache = fetchHtmlEmptyCache;
|
72 |
+
|
73 |
+
/**
|
74 |
+
* A helper for getting actual values for a set of options and values.
|
75 |
+
* Much better than fw.getValuesFromServer() because it doesn't require
|
76 |
+
* you to encode values as form data params. You just pass a valid JSON
|
77 |
+
* object and it just works.
|
78 |
+
*
|
79 |
+
* fw.options
|
80 |
+
* .getActualValues({a: {type: 'text', value: 'Initial'})
|
81 |
+
* .then(function (values) {
|
82 |
+
* // {
|
83 |
+
* // a: 'Initial'
|
84 |
+
* // }
|
85 |
+
* console.log(values);
|
86 |
+
* });
|
87 |
+
*
|
88 |
+
* fw.options
|
89 |
+
* .getActualValues({a: {type: 'text', value: 'Initial'}, {a: 'Changed'})
|
90 |
+
* .then(function (values) {
|
91 |
+
* // {
|
92 |
+
* // a: 'Changed'
|
93 |
+
* // }
|
94 |
+
* console.log(values);
|
95 |
+
* });
|
96 |
+
*/
|
97 |
+
fw.options.getActualValues = getActualValues;
|
98 |
+
|
99 |
+
return currentFwOptions;
|
100 |
+
|
101 |
+
function onChange(listener) {
|
102 |
+
on('change', listener);
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Please note that you won't be able to off that listener easily because
|
107 |
+
* it rewrites the listener which gets passed to fwEvents.
|
108 |
+
*
|
109 |
+
* If you want to be able to off the listener you should attach it with
|
110 |
+
* onChange and filter based on context by yourself.
|
111 |
+
*/
|
112 |
+
function onChangeByContext(context, listener) {
|
113 |
+
onChange(function(data) {
|
114 |
+
if (data.context === findOptionDescriptorEl(context)) {
|
115 |
+
listener(data);
|
116 |
+
}
|
117 |
+
});
|
118 |
+
}
|
119 |
+
|
120 |
+
function on(eventName, listener) {
|
121 |
+
fwEvents.on('fw:options:' + eventName, listener);
|
122 |
+
}
|
123 |
+
|
124 |
+
function one(eventName, listener) {
|
125 |
+
fwEvents.one('fw:options:' + eventName, listener);
|
126 |
+
}
|
127 |
+
|
128 |
+
function off(eventName, listener) {
|
129 |
+
fwEvents.off('fw:options:' + eventName, listener);
|
130 |
+
}
|
131 |
+
|
132 |
+
function offChange(listener) {
|
133 |
+
off('change', listener);
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* data:
|
138 |
+
* optionId
|
139 |
+
* type
|
140 |
+
* value
|
141 |
+
* context
|
142 |
+
* el
|
143 |
+
*/
|
144 |
+
function trigger(eventName, data) {
|
145 |
+
fwEvents.trigger('fw:options:' + eventName, data);
|
146 |
+
}
|
147 |
+
|
148 |
+
function triggerForEl(eventName, el, data) {
|
149 |
+
trigger(eventName, getActualData(el, data));
|
150 |
+
}
|
151 |
+
|
152 |
+
function triggerChange(data) {
|
153 |
+
trigger('change', data);
|
154 |
+
}
|
155 |
+
|
156 |
+
function triggerChangeForEl(el, data) {
|
157 |
+
triggerChange(getActualData(el, data));
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Trigger a scoped event for a specific option type, has the form:
|
162 |
+
* fw:options:{type}:{eventName}
|
163 |
+
*/
|
164 |
+
function triggerScopedByType(eventName, el, data) {
|
165 |
+
data = getActualData(el, data);
|
166 |
+
|
167 |
+
trigger(data.type + ':' + eventName, data);
|
168 |
+
}
|
169 |
+
|
170 |
+
function getActualData(el, data) {
|
171 |
+
return $.extend({}, currentFwOptions.getOptionDescriptor(el), data);
|
172 |
+
}
|
173 |
+
|
174 |
+
function findOptionDescriptorEl(el) {
|
175 |
+
el = el instanceof jQuery ? el[0] : el;
|
176 |
+
|
177 |
+
return el.classList.contains('fw-backend-option-descriptor')
|
178 |
+
? el
|
179 |
+
: $(el).closest('.fw-backend-option-descriptor')[0];
|
180 |
+
}
|
181 |
+
|
182 |
+
function fetchHtml(options, values, settings) {
|
183 |
+
var promise = $.Deferred();
|
184 |
+
|
185 |
+
if (!settings) settings = {};
|
186 |
+
|
187 |
+
settings = _.extend({ name_prefix: 'fw_edit_options_modal' }, settings);
|
188 |
+
|
189 |
+
var cacheId = fetchHtmlGetCacheId(options, values);
|
190 |
+
|
191 |
+
if (typeof htmlCache[cacheId] !== 'undefined') {
|
192 |
+
promise.resolve(htmlCache[cacheId]);
|
193 |
+
return promise;
|
194 |
+
}
|
195 |
+
|
196 |
+
$.ajax({
|
197 |
+
url: ajaxurl,
|
198 |
+
type: 'POST',
|
199 |
+
data: {
|
200 |
+
action: 'fw_backend_options_render',
|
201 |
+
options: JSON.stringify(options),
|
202 |
+
values: JSON.stringify(
|
203 |
+
typeof values == 'undefined' ? {} : values
|
204 |
+
),
|
205 |
+
data: {
|
206 |
+
name_prefix: settings.name_prefix,
|
207 |
+
id_prefix: settings.name_prefix.replace(/_/g, '-') + '-',
|
208 |
+
},
|
209 |
+
},
|
210 |
+
dataType: 'json',
|
211 |
+
success: function(response, status, xhr) {
|
212 |
+
if (!response.success) {
|
213 |
+
promise.reject('Error: ' + response.data.message);
|
214 |
+
return;
|
215 |
+
}
|
216 |
+
|
217 |
+
htmlCache[cacheId] = response.data.html;
|
218 |
+
|
219 |
+
promise.resolve(response.data.html, response, status, xhr);
|
220 |
+
},
|
221 |
+
error: function(xhr, status, error) {
|
222 |
+
promise.reject(status + ': ' + String(error));
|
223 |
+
},
|
224 |
+
});
|
225 |
+
|
226 |
+
return promise;
|
227 |
+
}
|
228 |
+
|
229 |
+
function fetchHtmlGetCacheEntryFor(options, values) {
|
230 |
+
return htmlCache[fetchHtmlGetCacheId(options, values)];
|
231 |
+
}
|
232 |
+
|
233 |
+
function fetchHtmlEmptyCache() {
|
234 |
+
htmlCache = {};
|
235 |
+
}
|
236 |
+
|
237 |
+
function fetchHtmlGetCacheId(options, values) {
|
238 |
+
return fw.md5(
|
239 |
+
JSON.stringify(options) +
|
240 |
+
'~' +
|
241 |
+
JSON.stringify(typeof values == 'undefined' ? {} : values)
|
242 |
+
);
|
243 |
+
}
|
244 |
+
|
245 |
+
function getActualValues (options, values) {
|
246 |
+
var promise = $.Deferred();
|
247 |
+
|
248 |
+
$.ajax({
|
249 |
+
url: ajaxurl,
|
250 |
+
type: 'POST',
|
251 |
+
data: {
|
252 |
+
action: 'fw_backend_options_get_values_json',
|
253 |
+
options: JSON.stringify(options),
|
254 |
+
values: JSON.stringify(
|
255 |
+
typeof values == 'undefined' ? {} : values
|
256 |
+
)
|
257 |
+
},
|
258 |
+
dataType: 'json',
|
259 |
+
success: function(response, status, xhr) {
|
260 |
+
if (!response.success) {
|
261 |
+
promise.reject('Error: ' + response.data.message);
|
262 |
+
return;
|
263 |
+
}
|
264 |
+
|
265 |
+
promise.resolve(response.data.values, response, status, xhr);
|
266 |
+
},
|
267 |
+
error: function(xhr, status, error) {
|
268 |
+
promise.reject(status + ': ' + String(error));
|
269 |
+
},
|
270 |
+
});
|
271 |
+
|
272 |
+
return promise;
|
273 |
+
}
|
274 |
+
})(jQuery, fw.options || {});
|
readme.txt
CHANGED
@@ -3,7 +3,7 @@ Contributors: unyson
|
|
3 |
Tags: page builder, shortcodes, backup, seo, breadcrumbs, portfolio, framework
|
4 |
Requires at least: 4.4
|
5 |
Tested up to: 4.8
|
6 |
-
Stable tag: 2.7.
|
7 |
License: GPLv2 or later
|
8 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
9 |
|
@@ -85,6 +85,9 @@ Yes; Unyson will work with any theme.
|
|
85 |
|
86 |
== Changelog ==
|
87 |
|
|
|
|
|
|
|
88 |
= 2.7.5 =
|
89 |
* Urgent update
|
90 |
|
3 |
Tags: page builder, shortcodes, backup, seo, breadcrumbs, portfolio, framework
|
4 |
Requires at least: 4.4
|
5 |
Tested up to: 4.8
|
6 |
+
Stable tag: 2.7.6
|
7 |
License: GPLv2 or later
|
8 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
9 |
|
85 |
|
86 |
== Changelog ==
|
87 |
|
88 |
+
= 2.7.6 =
|
89 |
+
* Urgent update
|
90 |
+
|
91 |
= 2.7.5 =
|
92 |
* Urgent update
|
93 |
|
unyson.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
* Plugin Name: Unyson
|
4 |
* Plugin URI: http://unyson.io/
|
5 |
* Description: A free drag & drop framework that comes with a bunch of built in extensions that will help you develop premium themes fast & easy.
|
6 |
-
* Version: 2.7.
|
7 |
* Author: ThemeFuse
|
8 |
* Author URI: http://themefuse.com
|
9 |
* License: GPL2+
|
3 |
* Plugin Name: Unyson
|
4 |
* Plugin URI: http://unyson.io/
|
5 |
* Description: A free drag & drop framework that comes with a bunch of built in extensions that will help you develop premium themes fast & easy.
|
6 |
+
* Version: 2.7.6
|
7 |
* Author: ThemeFuse
|
8 |
* Author URI: http://themefuse.com
|
9 |
* License: GPL2+
|