Reputation: 79
I have a recursive function setup to recreate/load a propertyTemplate array.
The first function is given an object like this:
{
staffedLocation: ['schedulingGroup',{property: 'staff',subProperties: ['description']}],
staff: true,
assignedShifts: true,
editedShifts: true,
deletedShifts: true,
unassignedShifts: true,
rangeStart: true,
rangeEnd: true
}
I need to recreate this object into an array of objects so as to properly populate my UI:
[
{checked: null, name: "staffedLocation", properties: [
{checked: null, name: "oid", properties: null, toggle: null, type: "integer"},
{checked: null, name: "_class", properties: null, toggle: null, type: "string"},
{checked: true, name: "schedulingGroups", properties: null, toggle: null, type: "list"},
{checked: null, name: "staff", properties: [
{checked: null, name: "oid", properties: null, toggle: null, type: "integer"},
{checked: null, name: "_class", properties: null, toggle: null, type: "string"},
{checked: true, name: "description", properties: null, toggle: null, type: "string"},
{checked: null, name: "limits", properties: null, toggle: null, type: "list"},
{checked: null, name: "weeklyMaxHours", properties: null, toggle: null, type: "integer"}
], toggle: true, type: "list"},
], toggle: true, type: "staffedLocation"},
{checked: null,
name: "staff", properties: [
{checked: null, name: "oid", properties: null, toggle: null, type: "integer"},
{checked: null, name: "_class", properties: null, toggle: null, type: "string"},
{checked: null, name: "description", properties: null, toggle: null, type: "string"},
{checked: null, name: "limits", properties: null, toggle: null, type: "list"},
{checked: null, name: "weeklyMaxHours", properties: null, toggle: null, type: "integer"}
], toggle: true, type: "staff"},
{checked: null, name: "assignedShifts", properties: null, toggle: null, type: "shiftForEmail"},
{checked: null, name: "editedShifts", properties: null, toggle: null, type: "shiftForEmail"},
{checked: null, name: "deletedShifts", properties: null, toggle: null, type: "shiftForEmail"},
{checked: null, name: "unassignedShifts", properties: null, toggle: null, type: "shiftForEmail"},
{checked: null, name: "rangeStart", properties: null, toggle: null, type: "timestamp"},
{checked: null, name: "rangeEnd", properties: null, toggle: null, type: "timestamp"}
]
Currently when I call the convert function, it walks through and begins creating my array of objects. However, when it hits the convertRecurse, a promise is created to gather the properties for one of the items.
Initially I thought nesting everything within the promise would run the function properly after I'd gathered all of the properties. However, when I go to the page and load the template the first time, the properties do not appear. If I re-run opening the template again, it works.
I'm thought chaining the promises would cause the 2nd promise to wait to move on. However, I don't have the best understanding of how they work.
function convert(template){
$scope.propertyTemplate = [];
for(var k in template){
var obj = {};
obj.checked = null;
obj.name = k;
if(template[k] !== true){
convertRecurse(template[k], obj, k);
} else {
obj.properties = null;
obj.toggle = null;
}
$scope.propertyTemplate.push(obj);
}
}
function convertRecurse(array, obj, parent){
var propArr = [];
var namespace = new namespaceFactory(parent);
namespace.init();
namespace.fetchProperties().then(function(props){
props.forEach(function(prop){
var fetchObj = {};
fetchObj.name = prop.property;
fetchObj.type = prop.type;
fetchObj.checked = null;
fetchObj.properties = null;
fetchObj.toggle = null;
propArr.push(fetchObj);
});
return propArr;
}).then(function(){
var objArr = [];
for(var j = 0; j < array.length; j++){
if(typeof array[j] === 'object'){
objArr.push(array[j]);
array.splice(j, 1);
j--;
}
for(var i = 0;i < propArr.length; i++){
if(array[j] === propArr[i].name){
propArr[i].checked = true;
}
}
}
for(var k = 0; k < objArr.length; k++){
var propObj = {};
propObj.checked = null;
propObj.name = objArr[k].property;
if(objArr[k].subProperties){
convertRecurse(objArr[k].subProperties, propObj, objArr[k].property)
for(var x = 0; x < propArr.length; x++){
if(propArr[x].name === propObj.name){
propArr.push(propObj);
propArr.splice(x, 1);
}
}
} else {
propObj.properties = null;
propObj.toggle = null;
}
}
obj.properties = propArr;
obj.toggle = true;
}).catch(console.log.bind(console));
}
My issue is that when I initially call this function the first time, it doesn't load.
However, if I call it again, everything seems to work perfectly.
Upvotes: 0
Views: 98
Reputation: 19298
The issue you are having concerns asynchronous flow. The code as currently written treats convertRecurse()
as synchronous, whereas it is actually asynchronous.
Code is difficult to follow in places but the tricky bits are synchronous transforms, which I've not attempted to understand.
The fixes are quite extensive, in part because asynchronous operations occur in loops. More specifically, you need either :
As you'll see below, a major aspect of the asynchronous flow is making appropriate returns all through.
Here it is, adopting the sequential approach :
function convert(template) {
$scope.propertyTemplate = [];
// Make array of template's enumerable keys
// See https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
var keys = Object.keys(template);
// Build a .then() chain from `keys`.
return keys.reduce(function(promise, k) {
return promise.then(function() {
var obj = {
name: k,
checked: null,
properties: null,
toggle: null
};
if(template[k] !== true) {
return convertRecurse(template[k], obj, k);
} else {
return obj;
}
}).then(function(obj_) {
$scope.propertyTemplate.push(obj_);
return obj_; // make obj_ available to the caller
});
}, $q.resolve()); // $q is assumed
}
function convertRecurse(array, obj, parent) {
var namespace = ...
return namespace.fetchProperties().then(function(props) {
var propArr = ... // some synchronous transform of (props)
var objArr = ... // some synchronous transform of (array, propArr)
// Build a .then() chain from `objArr`
return objArr.reduce(function(promise, o) {
return promise.then(function() {
if(o.subProperties) {
var propObj = {
name: o.property,
checked: null,
properties: null,
toggle: null
};
return convertRecurse(o.subProperties, propObj, o.property).then(function() {
propArr = ... // some synchronous transform of (propArr, propObj)
});
}
});
}, $q.resolve())
.then(function() {
obj.properties = propArr;
obj.toggle = true;
return obj; // deliver the augmented obj back to the caller.
});
}).catch(function(error) {
console.log(error);
throw error;
});
}
Barring mistakes on my part (not at all impossible), all you need to do is reinsert the code for the three transforms, which are omitted for clarity of the overall flow. Personally, I would write the transforms as functions and keep the code above much as it is.
Upvotes: 1