I have a Vue instance with a Vuex Store.
Vue components can call a method on the Vue instance to ensure certain objects are present in the Vuex store. Each object is stored as an instance of a class ApiObject. If only the object's ID is known the object will be pulled from the server with a jQuery Ajax request.
The following issue occurs:
I have two ApiObjects with their respective object IDs of "0d3f7f10" and "f6a7d150". The first one is already present in the Vuex store but the second one is only known by it's id, so an Ajax reqeuest is triggered.
The object with the id "f6a7d150" starts the ajax request and hits the correct endpoint (/api/v1/f6a7d150) and then hits the callback function, which is a method of the ApiObject class.
But when the callback is called, I'm all of a sudden in the wrong instance of ApiObject. I would expect that I'm calling the ApiObject "f6a7d150"'s callback method but I'm hitting "0d3f7f10"'s callback method.
That's the API call and callback methods:
refresh () {
console.log('Calling ' + this.id);
jQuery.ajax({
context: this,
url: this.url(),
method: 'GET',
success: this.handleApiResult
})
}
handleApiResult (result) {
console.log('Handling ' + this.id);
this.data = result['data'];
}
The expected console output would be:
Calling f6a7d150
Handling f6a7d150
But it actually is:
Calling f6a7d150
Handling 0d3f7f10
The result variable in handleApiResult holds the correct object with the ID "f6a7d150", so up until the API call we were in the correct instance.
When I dump the Vuex Store there are two objects with ID "0d3f7f10", one with data and the second one (which is the one we just pulled from the API) without data, so only it's ID is known.
The Vue components create objects in Vuex with these methods on my Vue instance. The console log reports, that objects with ID "f6a7d150" and "0d3f7f10" were created, so I have no idea, how there is no object with ID "f6a7d150" in my store.
ensureObject (type, id, data) {
if (!type || (!id && !data)) {
return;
}
if (!id) {
id = data.id;
}
if (this.$store.state[type].filter(o => o.id === id).length < 1) {
console.log("Creating " + id);
this.$store.state[type].push(new CiliatusObject(type, id, data));
}
},
ensureObjects (type, ids, data) {
if (!ids && !data) {
return;
}
if (!ids) {
data.forEach(obj => this.ensureObject(type, null, obj));
}
else {
let that = this;
ids.forEach(function (id) {
that.ensureObject(
type,
id,
data ? data.filter(o => o && o.id === id)[0] : undefined
)
});
}
}
Any suggestions would be greatly appreciated.
Alright I found it after banging my head against a wall for hours.
In a computed property of a Vue component, there was this line:
let obj = this.$store.state.filter(s => s.id = this.id);
Notice the =where you'd expect a ===.....
Related
I am deleting a FRTDB node, I want to access deleted data from that node. the functions looks as follow:
exports.events = functions.database.ref('/events/{eventId}').onWrite(async (change, context) => {
const eventId = context.params.eventId
if (!change.after.exists() && change.before.exists()) {
//data removed
return Promise.all([admin.database().ref(`/events/${eventId}/dayofweek`).once('value')]).then(n => {
const pms = []
const days = n[0]
days.forEach(x => {
pms.push(admin.database().ref(`${change.before.val().active ? 'active' : 'inactive'}/${x.key}/${eventId}`).set(null))
})
return Promise.all(pms)
});
else {
return null;
}
})
The probem I am having is that
admin.database().ref(`/events/${eventId}/dayofweek
do not loop the data because it seems data is no longer there so the forEach is not working. How can I get access to this data and get to loop the deleted data?
Of course you won't be able to read data that was just deleted. The function runs after the delete is complete. If you want to get the data that was just deleted, you're supposed to use change.before as described in the documentation:
The Change object has a before property that lets you inspect what was
saved to Realtime Database before the event. The before property
returns a DataSnapshot where all methods (for example, val() and
exists()) refer to the previous value. You can read the new value
again by either using the original DataSnapshot or reading the after
property. This property on any Change is another DataSnapshot
representing the state of the data after the event happened.
The data that was deleted from the database is actually included in the call to your Cloud Function. You can get if from change.before.
exports.events = functions.database.ref('/events/{eventId}').onWrite(async (change, context) => {
const eventId = context.params.eventId
if (!change.after.exists() && change.before.exists()) {
//data removed
days = change.before.val().dayofweek;
...
})
I have a vue object with all these getters and setters, here is a screenshot from the console.log:
The structure of the actual DATA (the not-vue stuff) looks like this:
{
Internal_key: "TESTKEY_1",
extensiontable_itc: {
description_itc: "EXTENSION_ITC_1_1",
description_itc2: "EXTENSION_ITC_1_2",
},
extensiontable_sysops: {
description_sysops: "EXTENSION_SYSOPS_1"
}
}
The data might look different in other usecases.
There might be more or less key-value pairs on the outer object, and the keys might be named differently as well. Same goes for the nested objects and their contents.
Is there some convenient way to extract this data into a plain JS Object?
If not, how can I best loop the vue object to extract the data "manually"?
The AJAX request shall be performed by an axios request, if this is important as well.
EDIT:
Here is the relevant data in vue:
data() {
return {
editingRecord: {
original: null,
copy: null
}
}
}
During my programflow, both editingRecord.orginal and editingRecord.copy receive data from an inputform. copy sets its data to original if the user clicks the save/send button. Then, I want to take the data from editingRecord.original with both its keys and values and send them to the server via AJAX request.
Its best not to mix jQuery with Vue so here's how you would do it using Axios https://github.com/axios/axios
methods: {
submit() {
const body = this.editingRecord.original
// make changes to body
axios.post( "backend_url", body )
.then(function(resp){
console.log(resp)
});
}
}
Okay, I found the solution.
let vueObject = Object.entries(this.editingRecord.original)
for(const[key, value] of vueObject){
if(typeof value == 'object' && value !== null){
for(const [nestedKey, nestedValue] of Object.entries(value)){
result[nestedKey] = nestedValue
}
}else{
result[key] = value
}
}
console.log("resultObject is", result)
This way you can iterate over all properties including the properties of nested objects and reassign both key and value to a fresh, one-dimensional array.
I am trying to findOne document in my Template.admin.events code. I have a form and onClick I want to verify if the ID of the ObjectID entered is an existing document in my collection and fetch that result to show it on the template.
My event code on the client :
Template.admin.events({
'click #btnAjouterObjet'(event) {
let objetIdInput = $('#object_id').val().toString();
Meteor.subscribe('objetsFindOne', objetIdInput, {
onReady: function () {
let obj = Objets.findOne();
if (obj) {
console.log("found");
console.log(obj);
objetsArr.push(objetIdInput);
}
else {
console.log("not found");
console.log(obj);
}
}
});
}
});
In my Objets api :
Meteor.publish('objetsFindOne', function objetsFindOne(param_id){
return Objets.find({_id : param_id});
})
I have verified and my objetIdInput always change on click when a different Id is entered but the subscribe always returns the first id entered. I also added the onReady because otherwise it returned undefined.
I am new to Meteor and I have also tried to subscribe to all the collection and doing the find on the client but I don't think it is the best idea as my collection has about 22000 documents.
Just to elaborate a little bit on the first answer, as to how to change this pattern:
(1) you should place your Meteor.subscribe() call in your Template.admin.onCreated() function.
(2) the subscription reads from a reactive value, for example, new ReactiveVar().
(3) now, anytime the reactive value changes, the subscription will re-run. So, in your template event, you set the reactive value to the id and let the subscription handle the rest.
Discover Meteor and other resources should be helpful on any details.
You are going about this all wrong. I suggest you take a look at Template-Level Subscriptions
I opted for the use of a method :
Client side:
'click #btnAjouterObjet'(event) {
let objetIdInput = $('#object_id').val().toString();
let result = Meteor.call('findObj', objetIdInput, function (error, result) {
if (error) {
console.log(error.reason);
return;
}
console.log(result);
});
}
On the server side :
Meteor.methods({
findObj: function (param_id) {
console.log(Objets.find({ _id: param_id }).fetch());
return Objets.find({ _id: param_id }).fetch();
},
});
I have a firebase reference, where I pull data down for a specific custom index I created.
requestsRef
.orderByChild('systemgameindex')
.startAt(lastrequest.systemgameindex.toString())
.endAt(lastrequest.systemgameindex.toString() + '~')
.limitToFirst(customElem.dataops.limit + 1)
.on('child_added', function (snapshot) {
var request = snapshot.val() || {};
request.key = snapshot.key();
request.systemColor = customElem.getSystemColor(request.system);
request.description = customElem.truncateText(request.description, 65);
customElem.getUserProfile(request);
customElem.getCommentCount(request.key);
if (request.systemgameindex !== lastrequest.systemgameindex) { customElem.push('requests', request); };
customElem.removeSpinnerRoo();
});
Right before I make the call to firebase, I have a custom spinner I dislay with a function called addSpinnerRoo(), and when data is returned, I make a call to removeSpinnerRoo() to hide the spinner on the DOM.
It works beautifully when there's data to return from firebase, but if the firebase query brings back no results, the callback on child_added never gets fired, so I have a spinner still spinning on the DOM.
Is there a way to handle when there's no data returned within Firebase?
Any insight would be appreciated a lot. Thanks
After reading this from the documentation from here:
The callback function receives a DataSnapshot, which is a snapshot of the data. A snapshot is a picture of the data at a particular database reference at a single point in time. Calling val() on a snapshot returns the JavaScript object representation of the data. If no data exists at the reference's location, the snapshots value will be null.
I was able to do use "val" instead of "child_added" to actually have firebase still fire the callback for the ".on()" method. So my code now looks like this:
var data = snapshot.val();
if (data !== null && data !== undefined) {
var requests = _.map(data, function (val, key) {
val.key = key;
return val;
});
_.each(requests, function (request) {
request.systemColor = customElem.getSystemColor(request.system);
request.description = customElem.truncateText(request.description, 65);
customElem.getUserProfile(request);
customElem.getCommentCount(request.key);
customElem.push('requests', request);
});
}
customElem.removeSpinnerRoo();
And with that, I was able to get what I needed. If this helps anyone, great...
I'm making a fairly complex HTML 5 + Javascript game. The client is going to have to download images and data at different points of the game depending on the area they are at. I'm having a huge problem resolving some issues with the Data Layer portion of the Javascript architecture.
The problems I need to solve with the Data Layer:
Data used in the application that becomes outdated needs to be automatically updated whenever calls are made to the server that retrieve fresh data.
Data retrieved from the server should be stored locally to reduce any overhead that would come from requesting the same data twice.
Any portion of the code that needs access to data should be able to retrieve it easily and in a uniform way regardless of whether the data is available locally already.
What I've tried to do to accomplish this is build a data layer that has two main components:
1. The portion of the layer that gives access to the data (through get* methods)
2. The portion of the layer that stores and synchronizes local data with data from the server.
The workflow is as follows:
When the game needs access to some data it calls get* method in the data layer for that data, passing a callback function.
bs.data.getInventory({ teamId: this.refTeam.PartyId, callback: this.inventories.initialize.bind(this.inventories) });
The get* method determines whether the data is already available locally. If so it either returns the data directly (if no callback was specified) or calls the callback function passing it the data.
If the data is not available, it stores the callback method locally (setupListener) and makes a call to the communication object passing the originally requested information along.
getInventory: function (obj) {
if ((obj.teamId && !this.teamInventory[obj.teamId]) || obj.refresh) {
this.setupListener(this.inventoryNotifier, obj);
bs.com.getInventory({ teamId: obj.teamId });
}
else if (typeof (obj.callback) === "function") {
if (obj.teamId) {
obj.callback(this.team[obj.teamId].InventoryList);
}
}
else {
if (obj.teamId) {
return this.team[obj.teamId].InventoryList;
}
}
}
The communication object then makes an ajax call to the server and waits for the data to return.
When the data is returned a call is made to the data layer again asking it to publish the retrieved data.
getInventory: function (obj) {
if (obj.teamId) {
this.doAjaxCall({ orig: obj, url: "/Item/GetTeamEquipment/" + obj.teamId, event: "inventoryRefreshed" });
}
},
doAjaxCall: function (obj) {
var that = this;
if (!this.inprocess[obj.url + obj.data]) {
this.inprocess[obj.url + obj.data] = true;
$.ajax({
type: obj.type || "GET",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: obj.data,
url: obj.url,
async: true,
success: function (data) {
try {
ig.fire(bs.com, obj.event, { data: data, orig: obj.orig });
}
catch (ex) {
// this enables ajaxComplete to fire
ig.log(ex.message + '\n' + ex.stack);
}
finally {
that.inprocess[obj.url + obj.data] = false;
}
},
error: function () { that.inprocess[obj.url + obj.data] = false; }
});
}
}
The data layer then stores all of the data in a local object and finally calls the original callback function, passing it the requested data.
publishInventory: function (data) {
if (!this.inventory) this.inventory = {};
for (var i = 0; i < data.data.length; i++) {
if (this.inventory[data.data[i].Id]) {
this.preservingUpdate(this.inventory[data.data[i].Id], data.data[i]);
}
else {
this.inventory[data.data[i].Id] = data.data[i];
}
}
// if we pulled this inventory for a team, update the team
// with the inventory
if (data.orig.teamId && this.team[data.orig.teamId]) {
this.teamInventory[data.orig.teamId] = true;
this.team[data.orig.teamId].InventoryList = [];
for (var i = 0; i < data.data.length; i++) {
this.team[data.orig.teamId].InventoryList.push(data.data[i]);
}
}
// set up the data we'll notify with
var notifyData = [];
for (var i = 0; i < data.data.length; i++) {
notifyData.push(this.inventory[data.data[i].Id]);
}
ig.fire(this.inventoryNotifier, "refresh", notifyData, null, true);
}
There are several problems with this that bother me constantly. I'll list them in order of most annoying :).
Anytime I have to add a call that goes through this process it takes too much time to do so. (at least an hour)
The amount of jumping and callback passing gets confusing and seems very prone to errors.
The hierarchical way in which I am storing the data is incredibly difficult to synchronize and manage. More on that next.
Regarding issue #3 above, if I have objects in the data layer that are being stored that have a structure that looks like this:
this.Account = {Battles[{ Teams: [{ TeamId: 392, Characters: [{}] }] }]}
this.Teams[392] = {Characters: [{}]}
Because I want to store Teams in a way where I can pass the TeamId to retrieve the data (e.g. return Teams[392];) but I also want to store the teams in relation to the Battles in which they exist (this.Account.Battles[0].Teams[0]); I have a nightmare of a time keeping each instance of the same team fresh and maintaining the same object identity (so I am not actually storing it twice and so that my data will automatically update wherever it is being used which is objective #1 of the data layer).
It just seems so messy and jumbled.
I really appreciate any help.
Thanks
You should consider using jquery's deferred objects.
Example:
var deferredObject = $.Deferred();
$.ajax({
...
success: function(data){
deferredObject.resolve(data);
}
});
return deferredObject;
Now with the deferredObject returned, you can attach callbacks to it like this:
var inventoryDfd = getInventory();
$.when(inventoryDfd).done(function(){
// code that needs data to continue
}
and you're probably less prone to errors. You can even nest deferred objects, or combine them so that a callback isn't called until multiple server calls are downloaded.
+1 for Backbone -- it does some great heavy lifting for you.
Also look at the Memoizer in Douglas Crockford's book Javascript the Good Parts. It's dense, but awesome. I hacked it up to make the memo data store optional, and added more things like the ability to set a value without having to query first -- e.g. to handle data freshness.