Skip to content

Commit 4635f76

Browse files
committed
RELEASE: v4.1.1
- FEAT: JavaScriptBuilder will now minify it's output by default. - FEAT: Add the ability to delay the execution of client-side evidence properties.
2 parents 1ebc500 + 16cf7ea commit 4635f76

File tree

6 files changed

+436
-76
lines changed

6 files changed

+436
-76
lines changed

JavaScriptResource.mustache

Lines changed: 135 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ fiftyoneDegreesManager = function() {
2020
// by 1.
2121
var callbackCounter = 0;
2222

23+
// Array of JavaScript properties that have started evaluation.
24+
var jsPropertiesStarted = [];
25+
2326
// startsWith polyfill.
2427
var startsWith = function(source, searchValue){
2528
return source.lastIndexOf(searchValue, 0) === 0;
@@ -63,8 +66,11 @@ fiftyoneDegreesManager = function() {
6366
// Fetch a value safely from the json object. If a key somewhere down the
6467
// '.' separated hierarchy of keys is not present then 'undefined' is
6568
// returned rather than letting an exception occur.
66-
var getFromJson = function(key) {
69+
var getFromJson = function(key, allowObjects, allowBooleans) {
6770
var result = undefined;
71+
if(typeof allowObjects === 'undefined') { allowObjects = false; }
72+
if(typeof allowBooleans === 'undefined') { allowBooleans = false; }
73+
6874
if (typeof(key) === 'string') {
6975
var functions = json;
7076
var segments = key.split('.');
@@ -74,6 +80,10 @@ fiftyoneDegreesManager = function() {
7480
}
7581
if (typeof(functions) === "string") {
7682
result = functions;
83+
} else if (allowBooleans && typeof(functions) === "boolean") {
84+
result = functions;
85+
} else if (allowObjects && typeof functions === 'object' && functions !== null) {
86+
result = functions;
7787
}
7888
}
7989
return result;
@@ -95,56 +105,66 @@ fiftyoneDegreesManager = function() {
95105

96106
// Executes any Javascript contained in the json data. Sets the processedJs
97107
// flag to true when there is no further Javascript to be processed.
98-
var processJSproperties = function(resolve, reject) {
108+
var processJsProperties = function(resolve, reject, jsProperties, ignoreDelayFlag) {
99109
var executeCallback = true;
110+
var started = 0;
100111
101-
if (json.javascriptProperties !== undefined &&
102-
json.javascriptProperties.length > 0) {
112+
if (jsProperties !== undefined &&
113+
jsProperties.length > 0) {
103114
104115
// Execute each of the Javascript property code snippets using the
105116
// index of the value to access the value to avoid problems with
106117
// JavaScript returning erroneous values.
107118
for(var index = 0;
108-
index < json.javascriptProperties.length;
119+
index < jsProperties.length;
109120
index++) {
110121
111-
var name = json.javascriptProperties[index];
112-
113-
// Create new function bound to this instance and execute it.
114-
// This is needed to ensure the scope of the function is
115-
// associated with this instance if any members are altered or
116-
// added. Avoids global scoped variables.
117-
var body = getFromJson(name);
118-
119-
if (body !== undefined) {
120-
var func = undefined;
121-
var searchString = '// 51D replace this comment with callback function.';
122-
123-
if(body.indexOf(searchString) !== -1){
124-
callbackCounter++;
125-
body = body.replace(/\/\/ 51D replace this comment with callback function./g, 'callbackFunc(resolveFunc, rejectFunc);');
126-
func = new Function('callbackFunc', 'resolveFunc', 'rejectFunc',
127-
"try {\n" +
128-
body + "\n" +
129-
"} catch (err) {\n" +
130-
"console.log(err);" +
131-
"}"
132-
);
133-
func(completedCallback, resolve, reject);
134-
executeCallback = false;
135-
} else {
136-
func = new Function(
137-
"try {\n" +
138-
body + "\n" +
139-
"} catch (err) {\n" +
140-
"console.log(err);" +
141-
"}"
142-
);
143-
func();
122+
var name = jsProperties[index];
123+
124+
if(jsPropertiesStarted.includes(name) === false) {
125+
// Create new function bound to this instance and execute it.
126+
// This is needed to ensure the scope of the function is
127+
// associated with this instance if any members are altered or
128+
// added. Avoids global scoped variables.
129+
var body = getFromJson(name);
130+
var delay = getFromJson(name + 'delayexecution', false, true);
131+
132+
if ((ignoreDelayFlag || (delay === undefined || delay === false)) &&
133+
body !== undefined) {
134+
var func = undefined;
135+
var searchString = '// 51D replace this comment with callback function.';
136+
completed = false;
137+
jsPropertiesStarted.push(name);
138+
started++;
139+
140+
if(body.indexOf(searchString) !== -1){
141+
callbackCounter++;
142+
body = body.replace(/\/\/ 51D replace this comment with callback function./g, 'callbackFunc(resolveFunc, rejectFunc);');
143+
func = new Function('callbackFunc', 'resolveFunc', 'rejectFunc',
144+
"try {\n" +
145+
body + "\n" +
146+
"} catch (err) {\n" +
147+
"console.log(err);" +
148+
"}"
149+
);
150+
func(completedCallback, resolve, reject);
151+
executeCallback = false;
152+
} else {
153+
func = new Function(
154+
"try {\n" +
155+
body + "\n" +
156+
"} catch (err) {\n" +
157+
"console.log(err);" +
158+
"}"
159+
);
160+
func();
161+
}
144162
}
145163
}
146164
}
147-
} else {
165+
}
166+
167+
if(started === 0) {
148168
executeCallback = false;
149169
completed = true;
150170
}
@@ -198,7 +218,7 @@ fiftyoneDegreesManager = function() {
198218
199219
// Process the JavaScript properties.
200220
var process = function(resolve, reject){
201-
processJSproperties(resolve, reject);
221+
processJsProperties(resolve, reject, json.javascriptProperties, false);
202222
}
203223
204224
var fireChangeFuncs = function(json) {
@@ -308,6 +328,59 @@ fiftyoneDegreesManager = function() {
308328
});
309329
}
310330

331+
{{#_hasDelayedProperties}}
332+
// Get the JS property(s) that, when evaluated, will populate
333+
// evidence that can be used to determine the value of the
334+
// supplied property.
335+
// The supplied name can either be a complete property name or a top level
336+
// aspect name.
337+
// Where the aspect name is given, ALL evidence properties under that
338+
// key will be returned.
339+
// Example property names are 'location.country' or 'devices.profiles.hardwarename'
340+
// Example aspect names are 'location' or 'devices'
341+
var getEvidenceProperties = function (name) {
342+
var evidenceProperties = getFromJson(name + 'evidenceproperties');
343+
if(typeof evidenceProperties === "undefined") {
344+
var item = getFromJson(name, true);
345+
evidenceProperties = getEvidencePropertiesFromObject(item);
346+
}
347+
return evidenceProperties;
348+
}
349+
350+
// Get all values in any 'evidenceproperty' fields on this object
351+
// or sub-objects.
352+
var getEvidencePropertiesFromObject = function (dataObject) {
353+
evidenceProperties = [];
354+
355+
for (var prop in dataObject) {
356+
if (dataObject.hasOwnProperty(prop)) {
357+
var value = dataObject[prop];
358+
// Property name ends with 'evidenceproperties' so is
359+
// what we're looking for.
360+
// Add the values to the array if we don't already have it.
361+
if (value !== null && Array.isArray(value) && prop.endsWith('evidenceproperties')) {
362+
value.forEach(function(item, index) {
363+
if(evidenceProperties.includes(item) === false) {
364+
evidenceProperties.push(item);
365+
}
366+
});
367+
}
368+
// Item is an object so recursively call this method
369+
// and add any resulting evidence properties to the list.
370+
else if(typeof value === 'object' && value !== null) {
371+
getEvidencePropertiesFromObject(value).forEach(function(item, index) {
372+
if(evidenceProperties.includes(item) === false) {
373+
evidenceProperties.push(item);
374+
}
375+
});
376+
}
377+
}
378+
}
379+
380+
return evidenceProperties;
381+
}
382+
{{/_hasDelayedProperties}}
383+
311384
{{#_supportsPromises}}
312385
this.promise = new Promise(function(resolve, reject) {
313386
process(resolve,reject);
@@ -318,7 +391,28 @@ fiftyoneDegreesManager = function() {
318391
changeFuncs.push(resolve);
319392
}
320393

321-
this.complete = function(resolve) {
394+
this.complete = function(resolve, properties) {
395+
{{#_hasDelayedProperties}}
396+
// If properties is set then check if we need to kick off
397+
// processing of anything.
398+
if(typeof properties !== "undefined") {
399+
// If properties is a string then split on comma to produce
400+
// an array of one or more key names.
401+
if(typeof properties === "string") {
402+
properties = properties.split(',');
403+
}
404+
if(Array.isArray(properties)) {
405+
properties.forEach(function(key, i) {
406+
// We pass an empty function rather than 'resolve' because we
407+
// don't want to call resolve when a single evidence function
408+
// evaluates but after all of them have completed.
409+
// This is handled by the 'if(complete)' code below.
410+
processJsProperties(function(json) {}, catchError, getEvidenceProperties(key), true);
411+
});
412+
}
413+
}
414+
415+
{{/_hasDelayedProperties}}
322416
if(completed){
323417
resolve(json);
324418
}else{
@@ -337,7 +431,6 @@ fiftyoneDegreesManager = function() {
337431
this.promise.then(function(value) {
338432
// JSON has been updated so replace the current instance.
339433
update.call(parent, value);
340-
resolve(parent);
341434
completed = true;
342435
}).catch(catchError);
343436
{{/_supportsPromises}}

JavascriptBuilder.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
namespace fiftyone\pipeline\core;
2525

26+
use NodejsPhpFallback\Uglify;
27+
2628
/**
2729
* The JavaScriptBuilder aggregates JavaScript properties
2830
* from FlowElements in the Pipeline. This JavaScript also (when needed)
@@ -47,6 +49,7 @@ public function __construct($settings = array())
4749
"_enableCookies" => isset($settings["enableCookies"]) ? $settings["enableCookies"] : true
4850

4951
];
52+
$this->minify = isset($settings["minify"]) ? $settings["minify"] : true;
5053
}
5154

5255
public $dataKey = "javascriptbuilder";
@@ -159,8 +162,18 @@ public function processInternal($flowData)
159162
} else {
160163
$vars["_supportsPromises"] = false;
161164
}
162-
165+
166+
// Check if any delayedproperties exist in the json
167+
168+
$vars["_hasDelayedProperties"] = strpos($vars["_jsonObject"], "delayexecution") !== false;
169+
163170
$output = $m->render(file_get_contents(__DIR__ . "/JavaScriptResource.mustache"), $vars);
171+
172+
if($this->minify) {
173+
// Minify the output
174+
$uglify = new Uglify(array($output));
175+
$output = $uglify->getMinifiedJs();
176+
}
164177

165178
$data = new ElementDataDictionary($this, ["javascript" => $output]);
166179

0 commit comments

Comments
 (0)