Following the advice given on one of my other questions (Get a list of all objects from loaded model), I added this code on my application to retrieve all properties from all objects:
function onGeometryLoadedEvent(viewer) {
var dbIds = Object.keys(
viewer.model.getData().instanceTree.nodeAccess.dbIdToIndex
);
this.viewer.model.getBulkProperties(
dbIds,
{
propFilter: false,
ignoreHidden: true
},
(objects) => console.log(objects),
() => console.log('error')
)
}
It correctly shows all the objects. The problem is it takes A LOT of time to complete (+1 minute), even for a model with just 10,000 objects.
Is this normal?
I really need the list of objects with their categories, I have to sort them after getting them to present a list of all the available categories and properties to the user.
I know I can use the Model Derivatives API, but I'd like to avoid it if possible.
Note that when you list all elements on the model it will include those that are not visible, like categories. If you need only elements on the model, then you need the leaf of the model. This article described it and the source code is below:
function getAllLeafComponents(viewer, callback) {
var cbCount = 0; // count pending callbacks
var components = []; // store the results
var tree; // the instance tree
function getLeafComponentsRec(parent) {
cbCount++;
if (tree.getChildCount(parent) != 0) {
tree.enumNodeChildren(parent, function (children) {
getLeafComponentsRec(children);
}, false);
} else {
components.push(parent);
}
if (--cbCount == 0) callback(components);
}
viewer.getObjectTree(function (objectTree) {
tree = objectTree;
var allLeafComponents = getLeafComponentsRec(tree.getRootId());
});
}
Now with the .getBulkProperties you can specify which property you want, so something like Category, right? So specify that, as shown at this article and used with the above .getAllLeafComponent function:
getAllLeafComponents(viewer, function (dbIds) {
viewer.model.getBulkProperties(dbIds, ['Category'],
function(elements){
// ToDo here
// this will only include leaf with Category property
})
})
And I would expect this to be faster than your original code, as I'm filtering elements and properties.
Related
I am storing some settings in the database with keys and JSON data but when I get these settings from a Laravel API, it returns an array which becomes a hectic work in reassigning data to the input fields. I want to know if there is an easier way of doing it.
So far I have tried iterating and using the switch statement to identify keys and reassign them. But the problem is I can't access the VueJS data variable in the loop.
Here is a look at the database table:
Database Table
Here are the objects I am using in Vue:
helpful_notification: {
email: false,
sms: false,
push: false,
},
updates_newsletter: {
email: false,
sms: false,
push: false,
},
Here is my Code to Iterate over results:
axios.get('/api/notificationsettings')
.then(response => {
var data = response.data;
let list = [];
console.log(data)
$.each(data, function(i, j){
switch(j.key){
case 'transactional':
var settings = JSON.parse(j.settings)
var x = {
transactional : settings
}
list.push(x)
break;
case 'task_reminder':
var settings = JSON.parse(j.settings)
x = {
task_reminder : settings
}
list.push(x)
break;
}
});
this.transactional = list;
// this.task_reminder= list.task_reminder;
console.log(list);
})
.catch(error => {
});
In JavaScript, functions have their own scope, save for a few exceptions. Which means that, inside your anonymous function (i.e:
$.each(data, function(i, j){
// this here is the function scope, not the outside scope
})
...), this is not the outside scope, it's the function's scope
There are two ways to make the outside scope available inside your function:
a) place it inside a variable
const _this = this;
$.each(data, function(i, j){
// this is function scope,
// _this is outside scope (i.e: _this.list.task_reminder)
})
b) use an arrow function
$.each(data, (i, j) => {
// this is the outside scope
// the arrow function doesn't have a scope.
})
The above is a simplification aimed at helping you access the outside scope inside your function. But this can differ based on the context it is used in. You can read more about this here.
I have a unique situation here which I am having trouble solving in an elegant fashion.
A user passes up an array of signals which they want to export data for. This array can be 1 -> Any_Number so first I go fetch the table names (each signal stores data in a separate table) based on the signals passed and store those in an object.
The next step is to iterate over that object (which contains the table names I need to query), execute the query per table and store the results in an object which will be passed to next chain in the Promise. I haven't seen any examples online of good ways to handle this but I know it's a fairly unique scenario.
My code prior to attempting to add support for arrays of signals was simply the following:
exports.getRawDataForExport = function(data) {
return new Promise(function(resolve, reject) {
var getTableName = function() {
return knex('monitored_parameter')
.where('device_id', data.device_id)
.andWhere('internal_name', data.param)
.first()
.then(function(row) {
if(row) {
var resp = {"table" : 'monitored_parameter_data_' + row.id, "param" : row.display_name};
return resp;
}
});
}
var getData = function(runningResult) {
return knexHistory(runningResult.table)
.select('data_value as value', 'unit', 'created')
.then(function(rows) {
runningResult.data = rows;
return runningResult;
});
}
var createFile = function(runningResult) {
var fields = ['value', 'unit', 'created'],
csvFileName = filePathExport + runningResult.param + '_export.csv',
zipFileName = filePathExport + runningResult.param + '_export.gz';
var csv = json2csv({data : runningResult.data, fields : fields, doubleQuotes : ''});
fs.writeFileSync(csvFileName, csv);
// create streams for gZipping
var input = fs.createReadStream(csvFileName);
var output = fs.createWriteStream(zipFileName);
// gZip
input.pipe(gzip).pipe(output);
return zipFileName;
}
getTableName()
.then(getData)
.then(createFile)
.then(function(zipFile) {
resolve(zipFile);
});
});
}
Obviously that works fine for a single table and I have gotten the getTableName() and createFile() methods updated to handle arrays of data so this question only pertains to the getData() method.
Cheers!
This kind of problem is far from unique and, approached the right way, is very simply solved.
Don't rewrite any of the three internal functions.
Just purge the explicit promise construction antipattern from .getRawDataForExport() such that it returns a naturally occurring promise and propagates asynchronous errors to the caller.
return getTableName()
.then(getData)
.then(createFile);
Now, .getRawDataForExport() is the basic building-block for your multiple "gets".
Then, a design choice; parallel versus sequential operations. Both are very well documented.
Parallel:
exports.getMultiple = function(arrayOfSignals) {
return Promise.all(arrayOfSignals.map(getRawDataForExport));
};
Sequential:
exports.getMultiple = function(arrayOfSignals) {
return arrayOfSignals.reduce(function(promise, signal) {
return promise.then(function() {
return getRawDataForExport(signal);
});
}, Promise.resolve());
};
In the first instance, for best potential performance, try parallel.
If the server chokes, or is likely ever to choke, on parallel operations, choose sequential.
I developped an Angular2 service to retrieve a list a categories from a backend server and count how many 'links' exist per category.
Once I have the number of links for each category, I add a property to the Json object to 'store' the value.
Here is the code:
nbLinks = '';
...
getCategories() {
return this.category.find({where: {clientId: this.userApi.getCurrentId()}}).map((data) => {
this.categoriesList = data;
for (var i = 0; i < this.categoriesList.length; i++) {
var obj = this.categoriesList[i].id;
this.category.countLinks(obj).subscribe((linksCount) => {
this.nbLinks = linksCount;
}, err => {
console.log(err);
});
}
return data;
},
err => {
console.log(err);
}
);
I am getting the categories in a json object with the correct 'where' clause.
I am looping on the Json to 'count' the number of link in this category.
My problem is that outside the for loop (getting out) the variable i is bigger than my Json length so the app is crashing.
My second problem is that I do not have the visiblity of this.nbLinks outside the for ... loop.
Thanks an Regards
I'm not sure I understand your code, but two things stand out:
1) It looks like you're mixing synchronous and asynchronous code. It cannot work.
Sync code: the for loop. Async code: the observable.
Instead, could you refactor your code to ONLY work with observables and chain all the operations? You can wrap any piece of data in an observable with Observable.from() or Observable.of().
For instance:
getCategories() {
const categories = this.category.find({where: {clientId: this.userApi.getCurrentId()}});
return Observable.from(categories)
.map(category => countLinksInCategory(category));
}
If countLinksInCategory() is an async operation, then have that function return an Observable, and use .mergeMap() instead of .map() in the code above.
2) Try avoiding setting an outside variable from within your observable
// This part is not ideal
Obs.subscribe(linksCount => {
this.nbLinks = linksCount;
});
I would suggest renaming getCategories() to getNumLinks() to reflect the role of the function. The only job of the Observable inside this function is to produce a value. Then, the consumer of the Observable can use that value (i.e. assign it, display it...).
In terms of code:
getNumLinks(): Observable<number> {
// Here, count the number of links - See example code above.
// Eventually, return an observable wrapping the final value.
}
Then, elsewhere in your code:
// This is where you assign the value returned by the Observable.
// Note that we are OUTSIDE the Observable now.
getNumLinks().subscribe(numLinks => this.nbLinks = numLinks);
i have problem rendering my view...the view return always the last in the json object: This is the code:
Router.js:
var list = new clientCollection();
var cards = new cardsView({model:list})
list.fetch({success: function (collection, response, options) {
cards.render();
}
});
Cards.js view:
....
tagName: 'section',
className: 'list',
template: Handlebars.compile(cardsTemplate),
render: function () {
var list = this.model.toJSON(),
self = this,
wrapperHtml = $("#board"),
fragment = document.createDocumentFragment();
$(list).each(function (index, item) {
$(self.el).html(self.template({card: item}));
$.each(item.cards, function (i, c) {
var card = new cardView({model : c});
$(self.el).find('.list-cards').append(card.render().el);
});
fragment.appendChild(self.el);
});
wrapperHtml.append(fragment.cloneNode(true));
},
...
This is my json data:
[
{"id":"9","name_client":"XXXXXXX","cards":[]},
{"id":"8","name_client":"XXXXXXX","cards":[{"id":"8","title":"xxxxx.it","description":"some desc","due_date":"2016-01-23","sort":"0"}]}
]
Can u help me to render the view?
It's hard to know for sure without seeing how the view(s) are attached to the DOM, but your problem appears to be this line ...
$(self.el).html(self.template({card: item}));
That is essentially rendering each element in the collection as the full contents of this view, then replacing it on each iteration. Try instead appending the contents of each template to the view's element.
Also, since you tagged this with backbone.js and collections, note that the easier, more Backbone-y way to iterate through a collection would be:
this.model.each(function(item) {
// 'item' is now an instance of the Backbone.Model type
// contained within the collection. Also, note the use
// of 'this' within the iterator function, as well as
// this.$el within a View is automatically the same as
// $(self.el)
this.$el.append(this.template({ card: item });
// ... and so on ...
// By providing 'this' as the second argument to 'each(...)',
// the context of the iterator function is set for you.
}, this);
There's a lot packed in there, so ...
Backbone.Collection Underscore Methods
Backbone.View this.$el
Take the following TypeScript/Angular 2 code sample:
query(): Rx.Observable<any> {
return Observable.create((o) => {
var refinedPosts = new Array<RefinedPost>();
var observable = this.server.get('http://localhost/rawData.json').toRx().concatMap(
result =>
result.json().posts
)
.map((post: any) => {
// Assume I want to convert the raw JSON data into a nice class object with
// methods, etc.
var refinedPost = new RefinedPost();
refinedPost.Message = post.Message.toLowerCase();
refinedPosts.push(refinedPost);
})
.subscribeOnCompleted(() => {
o.onNext(refinedPosts);
})
});
}
Written out, the database is returning JSON. I want to iterate over the raw JSON and create a custom object, eventually returning to subscribers an Array<RefinedPost>.
The code works and the final subscribers get what they need, but I can't help but feel like I didn't do it the "Reactive Way". I cheated and used an external accumulator to gather up the elements in the Array, which seems to defeat the purpose of using streams.
So, the question is, is there a better, more concise, reactive way to write this code?
Answering my own question.
query(): Rx.Observable<any> {
return this.server.get('http://localhost/rawData.json').toRx().concatMap(
result =>
result.json().posts
)
.map((post: any) => {
var refinedPost = new RefinedPost();
refinedPost.Message = post.Message.toLowerCase();
return refinedPost;
}).toArray();
}
This removes the internal accumulator and the wrapped Observable. toArray() took the sequence of items and brought them together into an array.