jQuery data() has an empty object, causes retrieval of data to fail - javascript

I've encountered a very strange issue with jQuery: I previously used the data() method to get an object that is stored on the element, like this:
var player = $(el).data("ytPlayer");
However, suddenly, this is returning an undefined result.
Looking at the $(el) object during setup and initialization of the page, I see it in the list of properties:
and at this point, if I call $(el).data("ytPlayer") it works as expected and I get the object.
But later in the lifecycle of the page, when the call to data() fails, I see this in the list of properties for that element:
There's an empty object, and also you can see my desired object right below it, but I can't seem to get to it. If at this point I call $(el).data() with no parameters, I get an empty object as well.
Is there any way to trace where this empty object is coming from, and/or any way to force jQuery to ignore it to get to the object I need?

It turns out the best way (at least for me) to troubleshoot this problem was to remove all scripts until the call to data() worked again, then gradually add them back one at a time until it failed.
this allowed us to isolate the offending script and fix it.

Related

Vue 2 mutating a data property inside a v-for causes infinite loops / problems

Although I could not find in the documentation, it appears that mutating a data array property isn't a good idea, even when it's not being rendered out to the view.
See this fiddle: https://jsfiddle.net/078v5142/3/
I need to decrement conditionalSet. I'm using pop() on every v-for loop to check the condition of an index. I can't use a computed property because i need to pass the index.
I can't just copy the conditionalSet array either because it needs to be tracked when a condition is set and popped.
This is a greatly simplified problem that I'm facing.
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js! I do not like loops, all that much.',
imageSet: [
'a','b','c','d','e','f','g', 'h', 'i', 'j'
],
conditionalSet: [1,2]
},
methods: {
doShow(index){
if(this.someInnerConditionThatsNotRenderedOut()){
if( index === 1 || index === 4 || index === 5 || index === 6){
this.conditionalSet.pop(); // <-- this is the problem, but how to I track?
return true;
}
}
console.log('I should no show index 5 and index 6')
},
someInnerConditionThatsNotRenderedOut(){
return true //comment this out. No error.
return this.conditionalSet.length > 0
}
}
})
The short answer
To cut a long story short, I think you can (and should) use a computed property for displaying the filtered list. I'm not 100% clear what you were trying to achieve in your example, but hopefully this fiddle gives you a pointer: https://jsfiddle.net/dtchqpjd/ If you look at the console when it runs, you'll be able to see how the logic pans out.
The longer answer
The problem is really caused by a combination of three things:
Your doShow function has a side-effect.
That side-effect is related to a data property, which is 'watched' by VueJS.
That data property is an array, which you need to 'get' in order to modify/pop.
VueJS has a reactivity system which watches data and, when it changes, determines the effect this will have on the DOM. This allows it to efficiently update the DOM in response to data changes, without needing to completely re-create the DOM every time something changes.
In this case, VueJS knows the following:
You get the conditionalSet property. You have to do this to call the pop method. But Vue doesn't know what you did with the value returned.
You change the conditionalSet property by calling the pop method.
Because you get and set the property, VueJS assumes that the result returned by the doShow function is stale, and hence re-evaluates it, and then you get an infinite loop. It can't see that you only 'got' it because you wanted to 'pop' it!
When you perform the filtering and modification all within a computed property, you avoid confusing VueJS (and the infinite loop) as it can see that the result is stable and doesn't need to be re-computed. It also happens to be much clearer code since the logic is all in one place. However, your computed property still has a side-effect which, if maybe if I understood more about what you were trying to do, could probably be avoided too.
A small illustration
To illustrate what's happening, here's a slight modification to your fiddle, where I've taken a separate (unknown to Vue) reference to the array. When I use that reference to call 'pop', you don't get the infinite loop: Vue can still see that we've changed the array, but because we didn't 'get' it, it doesn't assume that the output of the function is now stale. Uncomment the call which just 'gets' the array, and the problem comes back: Vue thinks it needs to re-evaluate the function.
Interestingly, Vue doesn't appear to look at the order of the dependencies: it doesn't matter if you 'get' then 'set', or 'set' then 'get'. It's just the fact that you did both which causes the circular dependency - they get added as dependencies and lead to a re-evaluation on the queue.
Commenting out the return true statement avoids the infinite loop simply because it prevents the array from being continually 'popped': once the array has been popped twice, your someInnerConditionThatsNotRenderedOut function returns false, and the potential for an infinite loop is cut short.
You just thought hard almost ;)
Imagine one computed method which filters your imageSet and returns which result you want and your v-for act on it.
Your fiddle with some changes:
https://jsfiddle.net/5dbakawb/

What does template.data._id do in Meteor?

Beginner Meteor/JS question:
When associating objects in Meteor I see small line of code that I'm not understanding. For example, post with associated comments.
var $body = $(e.target).find('[name=body]');
var comment = {
body: $body.val(),
postId: template.data._id
};
So get the content of the comment, put it in the variable "comment", and also create a postId to go into this comment so you know what post the comment belongs to. This postId is being called in with *'template.data._id'*
My questions are:
So you call template, then wouldn't you want to call the template name? Not data? Where is data coming from?
That aside, so you call data...and then ._id, are there other options to 'data'? IE
template.data.(option)
This isn't working for me, haha, *console.log(template.data._id);* is coming back undefined. So it's not grabbing the object ID as advertised. I'm sure I messed something up.
Here's the surrounding code if you need more context:
https://github.com/DiscoverMeteor/Microscope/blob/master/client/views/comments/comment_submit.js
Beginning with the easiest first, under your point 3 it should read:
console.log(template.data._id);
As to points 1 and 2, the key idea to note is that your code is being called inside of Template.commentSubmit.events({}). Inside of this object (the "{}"), you are working with an instance of the template in the document, including whatever data is being passed to that instance. In a different template, you will receive different data and hence template.data will consist of different keys and values. You can check out more in the documentation here, http://docs.meteor.com/#template_inst.
So in answer to your question, the reason you can invoke "data" rather than the template name is that the template name has already been provided by Template.commentSubmit. Note that inside of events({}), "this" will generally be equivalent to "template.data." So
console.log(template.data._id);
will generally be equivalent to
console.log(this._id);
The properties you can access on the data will always vary based on the instance received by the template. For example, if template.data consists of {_id: 1, name: "x", location: "y"}, you can retrieve these values by calling the keys, e.g. "template.data.name" or "template.data._id" etc. In your example, you are correct that you are setting postID to template.data._id.
The data arriving to the template comes from your Meteor.subscriptions. Hope this helps.
The template has a data context. When you use an {{#each}} block you are iterating through data, in this case posts.
So using template.data retrieves the data context for the template. It would refer to a post from where the comment form is. So template.data._id is the equivalent of this post._id (where post is the post you're commenting on).
The thing is I think this was removed from meteor. It was there a couple of versions back. I'm not sure about this but that's what I thought. I would have thought the correct code should be this._id. Where this ends up being the data context of the form (which would again be the post).
Could you check if that gives you undefined if you changed it out?
The .events method of a template takes in an event map:
http://docs.meteor.com/#eventmaps
The callbacks for each of the events (in this case 'submit form') can take two arguments. The first one 'e' is the javascript event object and the second one 'template' is the instance of the template where the event occurred. That template instance has a bunch of utility methods/properties (see http://docs.meteor.com/#template_inst) one of which is .data (see http://docs.meteor.com/#template_data). That .data property returns the data that the template was bound to (in your Microscope example it looks like it's bound to a comment object) and that object (in this case) has an _id property.
Note that the 'template' (little T) is not the same as Template (big T).

IndexedDB Fails when adding objects that contain element references

I'm writing an application for Google Chrome (targeted audience is an internal team) that allows a user to manipulate elements from within an iframe. The user is able to use her mouse to select DOM elements and to perform various actions to them, such as changing colors, fonts, etc.
I'm using a nodeIterator method to select only elements that have IDs or class names. Then for each of those elements, I add some element-specific properties to an object, and push that object to an array. Then, I open an IndexedDB database and add each object in the array to the database.
My problem is this: Everything works fine so long as I don't include a reference to the element in the object.
// Works fine
array.push({
width : currentNode.offsetWidth,
height : currentNode.offsetHeight,
top : currentNode.style.top;
left : currentNode.style.left;
});
// Doesn't work
array.push({
elem : currentNode,
width : currentNode.offsetWidth,
height : currentNode.offsetHeight,
top : currentNode.style.top;
left : currentNode.style.left;
});
Google chrome fails silently (nothing in the console at all) after trying to add the first element to the IndexedDB store.
My question is this: Has anyone else experienced this behavior and is this a browser-specific bug?
I'll distill my code to JSfiddle tomorrow. Thanks in advance.
IndexedDB store structured clone of your object. Basically your data will converted into JSON object, these exclude Element or Node data type.
However fail silently is not an expected behaviour. Accordingly to the structured clone algorithm, it should throw DataCloneError.
Is it necessary to save the DOM element? Can you just save the ID of the DOM element and retrieve the element back by its ID?
The indexeddb is only capable of storing data that doesn't have circular references. There is maybe one thing you can try. Sometime ago I wrote a blog post on how you can serialize and deserialize functions to JSON. Maybe this can help you, but I would advace you not to store complete elements unless there is no other option. This will add a lot of unnecessary data into your database, and it's possible you'll lose information when serializing to JSON.
You should get an exception in chrome (I just tried on Chrome 23) from the put() itself, which means if you have an onerror handler, it won't get called because the exception gets called first:
i.e. if you have
req = db.transaction("foo", "readwrite").objectStore("foo").put({...data with dom nodes })
req.onsuccess = ...
req.onerror = ...
The exception will be thrown by the first line.

JavaScript Pub/Sub property access issue

I am having a strange issue here I hope you all can help with.
Project Details
I am working on a simple pub/sub implementation for a larger application that includes a pubsub.subscribe_once() method. This method enables the creation of one-off subscriptions, meaning that a generic subscription is created, and then once the correct "publish" event fires and the subscription callback is run, the subscription deletes itself.
subscribe_once: function(topic, func) {
var sub = pubsub.subscribe(topic, func),
old_func = sub.func;
// rewrite our subscription's method to remove itself after invocation
sub.func = function() {
// call the original function
old_func.apply(this);
// remove subscription from topic
pubsub.unsubscribe(sub);
};
return sub;
}
Problem
I seem to be having some kind of issue with the memory flow of this process. (In order to best understand the following explanation I suggest you walk through the jsfiddle demo below as I go.) I create a subscribe_once('someevent') subscription, and then fire publish('someevent'). What you would expect to see when the publish method is invoked is that the topics hashtable contains a "someevent" key, which references an array of Subscription objects. And in fact, if you reference topics["someevent"], you see an array with a single Subscription. If, however, you reference topics you see the "someevent" key, but the array is empty!
By commenting out pubsub.unsubscribe(sub); the problem is eliminated, even though this function does not appear to be fired until after we run console.log(topics).
Further, this does not seem to be an issue with the way a given browser "threads" console.log; try console.log(topics, topics[topic], topics, topics[topic]) and you get the same result.
Demo: http://jsfiddle.net/4Ab6c/
Any help would be greatly appreciated! Thanks.
I'm still looking for some documentation to back me up, but I suspect the object display in the console is performing lazy evaluation on your topics object. I added console.log(topics) to the subscribe method after sub is pushed onto the array and I get the same result as your 'but not here' log. When I wrap the final line of your fiddle pubsub.publish('someevent') in setTimeout and I get the Object tree open before the publish callback runs, then it shows the subscription in the array and it stays that way even after the callback runs. If I don't open the object tree in the console before the callback runs then I see the empty array.
I will keep searching for a blog post or something that confirms lazy evaluation is occurring.
Just in case I haven't made it obvious enough, by lazy I mean, the console isn't gathering the details of the object until the tree view is clicked open in the console.
I am working in Chrome.
UPDATE
I have found similar behavior on Firefox as well. Firefox recognizes that there is one object in the array but if you don't drill down into the array before the publish even fires then the drill-down on the array will be empty.
I updated the fiddle from your comment:
http://jsfiddle.net/4Ab6c/2/
Please try this:
Run the fiddle and expand the object tree for the first console.log before the publish event fires, I set it to a five second timeout but you could make it longer or shorter depending on how quickly you can get down to the console and click the inspector open.
You should see the subscribe object in the array as expected.
Clear the console and run the fiddle again. This time do not open the object inspector until after the publish event has fired and all the code is done running.
Now when you open the object inspector of the first console.log you should not see the subscription event in the array.
UPDATE 2
Here is a much simpler fiddle that exhibits the same behavior:
http://jsfiddle.net/4Ab6c/3/
If you expand first before second shows up then you will get foo: bar. If you expand first after second shows up you will get foo: baz.
UPDATE 3
And, voila, another SO question seconds the motion on lazy evaluation.

How do I compare Application.windows[x] to Application.activeWindow?

I'm creating a Firefox extension, in which I want to iterate through the Application.windows array and check if one of its elements is the same as Application.activeWindow.
The mentioned excerpt from my code looks like this:
for (var i in Application.windows) {
if (Application.windows[i]==Application.activeWindow) alert('debug');
// there was some more complex code than alert('debug'),
// but since it didn't work, I decided to try with an alert
}
Unfortunately, the 'debug' alert is never viewed. Thus I decided to try this code (with only one window opened):
// the following code runs in an event listener for window.onload
alert(Application.windows[0]);
alert(Application.activeWindow);
alert(Application.windows[0]==Application.activeWindow);
Firefox displayed 3 alerts: the first one was [object Object], the second one - [xpconnect wrapped fuelIWindow], and the last one (which didn't surprise me) said false. So it seems the objects I'm trying to compare have different types. How can I deal with this? Thanks in advance.
You have two problems.
The first is that XPConnect doesn't support array-valued properties, so when FUEL (or STEEL or SMILE) return an array, they're actually returning an nsIVariant of internal objects! On the other hand, single-valued objects return an XPConnect wrapper which hides the internal object.
The second is that each time you access windows or activeWindow, new internal objects are created, so even two calls to activeWindow return different objects.
The way around this is to avoid FUEL and enumerate the windows directly using the window mediator.

Categories

Resources