Unyson - Version 2.7.6

Version Description

  • Urgent update
Download this release

Release Info

Developer Unyson
Plugin Icon 128x128 Unyson
Version 2.7.6
Comparing to
See all releases

Code changes from version 2.7.5 to 2.7.6

framework/manifest.php CHANGED
@@ -4,4 +4,4 @@ $manifest = array();
4
 
5
  $manifest['name'] = __('Unyson', 'fw');
6
 
7
- $manifest['version'] = '2.7.5';
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.5
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.5
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+