AFRAME: Event on completion of dynamic adding of component - aframe

My use-case is as follows:
In a loop, entities are being created and components are being set up. This is via a json-object that is being passed to the function. The question I have is how best to get an event that the whole set of entities and their components are being initialised. The code is something like this
var parent = document.querySelector('#parent');
var ent = document.createElement('a-entity');
parent.appendChild(ent);
for(var i =0; i = components.length; i++) {
var arr = components[i];
var cl = arr[0]; // class name
var attr = arr[2]; // component name
var attrV = arr[3]; // component data
ent.setAttribute('class', cl);
AFRAME.utils.entity.setComponentProperty(ent, attr, attrV);
//ent.setAttribute(attr, attrV); tried with this too
}
console.log('loop completed')
The loop completed gets logged before the completion of the loading of some of the components. I would like to have some sort of a call back to know that all the components have been completed loaded.
There seems to be an event componentinitialized but it sends a return for only 1 component. My real requirement (not reflected in above code) is that an entity can have multiple components added.
To use the above, I may have to set this event for every component and keep track of whether it has been completed or not. Just wondering if there is a more elegant way to do it. Thanks

Entities emit the "loaded" event. It should be easier than listening for each component initialization within the entity.
Try out:
entity.addEventListener("loaded", (e) => {
console.log(e)
})
like i did here.

Related

Cannot injectScript in Custom Variable Template

I'm trying to injectScript via Custom Variable Template (not tag).
Here is simplified code:
const log = require('logToConsole');
const setTimeout = require('callLater');
const setInWindow = require('setInWindow');
const copyFromWindow = require('copyFromWindow');
const copyFromDataLayer = require('copyFromDataLayer');
const injectScript = require('injectScript');
const pixelSend = function(eventType, eventParams, tries) {
// logic
log('success')
};
log('event - ', copyFromDataLayer('event'));
if (copyFromDataLayer('event') === 'gtm.js') {
injectScript('https://vk.com/js/api/openapi.js', // this one should create **"VK"** object in global scope, used to actually send the events
pixelSend(),
data.gtmOnFailure);
}
return true;
Unfortunately openapi.js never gets injected (checking in network tab) and thus VK object never gets created and I cannot use it.
If I just run in console:
var head = document.getElementsByTagName('head')[0]
var js = document.createElement('script');
js.src = 'https://vk.com/js/api/openapi.js';
head.appendChild(js);
It gets injected and VK object becomes available.
What am I doing wrong?
Just in case:
queryPermission('inject_script', 'https://vk.com/js/api/openapi.js') = true
I tried this, and there were just a few minor bugs - line 11 is missing a semicolon, and you did not mention if you allowed access to read the "event" key from the datalayer.
After that was fixed, the script worked as expected.
Obviously it will only work on the page view trigger (since this is the only case when event equals gtm.js. I probably would move the condition from the tag to the trigger).
Instead of "return true" you should end this will a call to data.gtmOnSuccess(), else you might have trouble using this tag in a tag sequence.
If in the template UI you hit the "run code" switch you will actually get information on all error in your code (alas one at a time, since execution stops at the first error). You can also write tests with mock input, for templates that require settings via input fields.

Flutter GetX variable changes but UI does not

I have two RxLists containing custom models:
var openDepartures = RxList<Departure>([]);
var finishedDepartures = RxList<Departure>([]);
I bind a stream to populate these RxLists with values from firebase. Since my Stream(which I am binding to the variables) changes according to the user's choice, I bind the new stream to the same variable, but since that would result in two streams controlling one variable, I "reset" the variable before that:
openDepartures = RxList<Departure>();
finishedDepartures = RxList<Departure>();
....
openDepartures.bindStream(
Database().getOpenDepartures(
userController.userLocation.value,
),
);
finishedDepartures.bindStream(
Database().getFinishedDepartures(
userController.userLocation.value,
),
);
The problem is that the UI is not refreshing, but when I do not "reset" the variables, everything works fine. What's weird as well is that the variable does get populated with the correct data. It is just not shown in the UI.
What I have tried to fix that:
update() method
Get.reloadAll()
call .refresh() on the variables
disposing and initializing the controller again
My question is now, how do I get the screen to refresh, or does anyone have an idea how to bind a new stream without "resetting" the old one and having multiple streams on one variable?
I finally solved my problem. The underlying problem was that I was binding multiple streams to one variable and therefore the variable was updated whenever one of the streams fired. The solution was the following:
Create an RxList of the variable:
final _openDepartureList = RxList<RxList<Departure>>();
Create a getter for the last element of that list:
RxList<Departure> get openDepartures => _openDepartureList.last;
Remove the old items:
if (_openDepartureList.length >= 2) {
_openDepartureList.removeRange(0, _openDepartureList.length - 1);
}
Add an empty RxList to the List
_openDepartureList.add(RxList.empty());
Bind the stream to the last element of the List, hence the one just added
_openDepartureList.last.bindStream():
That was it! I now always have the right stream bound to the variable I want and do not have multiple streams controlling one variable.
use update() like
void() { foo = newData; update(); }
or use ProgressIndicator
like
RxBool loading = false.obs
void Foo() { loading.value = true; your logic... loading.value = false; }
and use controller.loading.value in UI for check progress and show when loading is complete

Is it possible to create multiple draft items on createdatasource?

I am building an application that will have the ability to create agenda items to discuss in a meeting. The agenda item might include one or more attachments to discuss so there is a one to many relation between the AgendaItems and the AgendaDocs models. So far, I have an insert form that looks like this:
The "Select File" button is a drive picker and the code I have inside the onDocumentSelect event is the following:
var docs = result.docs;
var createDataSource = app.datasources.AgendaDocs.modes.create;
for(var i=0; i<docs.length-1; i++){
var uniqueDraft = createDataSource.item;
createDataSource.items.push(uniqueDraft);
}
for(var i=0; i<createDataSource.items.length-1; i++){
var draft = createDataSource.item;
createDataSource.items[i].DocTitle = docs[i].name;
createDataSource.items[i].DocURL = docs[i].url;
createDataSource.items[i].DriveID = docs[i].id;
}
console.log(createDataSource.items);
The code is supposed to fill out the the List widget below the "Select File" button, but as how you see, the three items are the same. The datasource of the List widget is "AgendaDocs.modes.create" and the datasource of the insert form is "AgendaItems.modes.create".
Reading the official documentation from appmaker, makes me think it is possible since the properties of "CreateDataSource" includes "items". I need help from an expert here. Is this possible? Am I using the wrong approach?
First things first, it seems that you are trying to create records from different models and relationship between them in a one call... at this time App Maker is not that smart to digest such a complex meal. Most likely you'll need to break your flow into multiple steps:
Create (persist) Agenda Item
Create AgendaDocs records and relation with AgendaItem
Similar flow is implemented in Travel Approval template app, but it is not exactly the same as yours, since it doesn't create associations in batches.
Going back to the original question. Yep, it is possible to have multiple drafts, but not with the Create Datasource. You are looking for Manual Save Mode. Somewhere in perfect world your code would look similar to this:
// AgendaItems in Manual Save mode
var agendaDs = app.datasources.AgendaItems;
// this line will create item on client and automatically push it
// to ds.items and set ds.item to it.
agendaDs.createItem();
var agendaDraft = agendaDs.item;
// Field values can be populated from UI via bindings...
agendaDraft.Type = 'X';
agendaDraft.Description = 'Y';
// onDocumentSelect Drive Picker's event handler
var docsDs = agendaDs.relations.AgendaDocs;
result.docs.forEach(function(doc) {
// this line will create item on client and automatically push it
// to ds.items and set ds.item to it...however it will throw an exception
// with this message:
// Cannot save a foreign key association for the 'AgendaItem'
// relation because the target record has not been persisted
// to the server. To fix this, call saveChanges()
// on the data source for that record's model: AgendaItem
docsDs.createItem();
var docDraft = docsDs.item;
docDraft.DocTitle = doc.name;
docDraft.DocURL = doc.url;
docDraft.DriveID = doc.id;
});
// submit button click
agendaDraft.saveChanges();

Meteor - Reactive Objects/Classes

TLDR
I like to really focus on keeping business logic away from the view model / controller. I find this sometimes rather hard in Meteor. Maybe I'm missing the point but I am after one of two things really:
1) A really good document explaining at a really low level how reactive values are being used.
2) A package that somehow manages an object so that if any of the setters are altered, they notify all of the get functions that would change as a result.
Unfortunately I've not seen either.
My Example
I have a fair bit ob business logic sitting behind a dialog used to document a consultation. I might have an event that sets a change of state.
I'd like to do something like this in the event:
const cc = new ConsultationEditor();
cc.setChiefComplaint(event.target.value);
console.log(cc.data());
ConsultationDict.set("consEdit", cc.data() );
When the user has updated this value, I'd then like to show a number of fields, based on the change. For this I have a helper with the following:
fields: function(){
console.log("trying to get fields");
const obj = ConsultationDict.get('consEdit');
cc = new ConsultationEditor(obj);
return cc.getFields();
}
But unfortunately this does not work for me.
What is your ConsultationDict?
The way you describe it, you want it to be a ReactiveDict as in the official ReactiveDict package.
https://atmospherejs.com/meteor/reactive-dict
Check this tutorial for examples:
https://themeteorchef.com/snippets/reactive-dict-reactive-vars-and-session-variables/
If you really need more fine tuning in your reactivity, you can also set a dependency tracker tracker = new Tracker.Dependency, and then refer to it wherever you change a variable with tracker.changed() and where the data needs to be notified with tracker.depend() like this:
var favoriteFood = "apples";
var favoriteFoodDep = new Tracker.Dependency;
var getFavoriteFood = function () {
favoriteFoodDep.depend();
return favoriteFood;
};
var setFavoriteFood = function (newValue) {
favoriteFood = newValue;
favoriteFoodDep.changed();
};
getFavoriteFood();
See the full Tracker doc here:
https://github.com/meteor/meteor/wiki/Tracker-Manual
I also found this gist to be useful to build reactive objects:
https://gist.github.com/richsilv/7d66269aab3552449a4c
and for a ViewModel type of behavior, check out
https://viewmodel.meteor.com/
I hope this helps.

google earth plugin does not respond to appendchild and removechild

I am dynamically adding kml files to google earth. For this, I have written javascript functions to add a kml and to remove a kml. These functions work fine for the first time for a kml. But if called again they do not respond. This happens for each kml that I try to add or remove. If I keep the page on browser for some time, then these functions again respond once and again become unresponsive.
function add(id, fileurl)
{
var link = ge.createLink('');
var href= fileurl;
link.setHref(href);
var networkLink = ge.createNetworkLink("'" + id + "'");
networkLink.set(link, true, true);
ge.getFeatures().appendChild(networkLink);
}
function remove(id)
{
for(var i=0; i<ge.getFeatures().getChildNodes().getLength(); i++)
{
if(ge.getFeatures().getChildNodes().item(i).getId() == id || ge.getFeatures().getChildNodes().item(i).getId() == "'" + id + "'")
{
id = ge.getFeatures().getChildNodes().item(i).getId();
ge.getFeatures().removeChild(ge.getElementById(id));
break;
}
}
The issue is that you can't re-add a feature using an ID that you have already used until all references to it have been released. This is usually done by the internal garbage collector - but you can also force it by calling release() on the object you are deleting. This ...
Permanently deletes an object, allowing its ID to be reused.
Attempting to access the object once it is released will result in an
error.
Also when an object is created with the API the object does not have a base address. In this case, the object can be returned by passing only its ID to getElementById(). This can then be used to remove the feature.
e.g.
function remove(id) {
ge.getElementById(id).release();
}
Really though I would look to avoid using IDs altogether and would simply keep a variable that points to the feature, then use that to remove. e.g.
function add(fileurl) {
var link = ge.createLink(''); //no id
link.setHref(fileurl);
var networkLink = ge.createNetworkLink(''); //no id
networkLink.set(link, true, true);
ge.getFeatures().appendChild(networkLink);
return networkLink;
}
var link1 = add("http://yoursite.com/file.kml");
var link2 = add("http://yoursite.com/file2.kml"); // etc...
// then to remove, simply...
link1.release();
link2.release();
OK. So I figured out that if you remove an object from GE, and then try to add another object with the same id, GE complains and won't create the object - unless some time (approx. 30 seconds in my case) has passed. This time actually is required by JavaScript to garbage collect the object.
Setting the object to null doesn't give immediate result but may help Garbage Collector.
Also release() method offered by GE does not help.

Resources