Coffeescript/jQuery Pattern: Caching Data Across Events - javascript

I have a list of items that are abstracts, and can be made to expand via Ajax on click. I'v written the following code in Coffeescript:
current_open_row = null
$('li.faq-item').live 'click', (event) ->
$.post("/faqs/update_rows", {old_row_id: current_open_row, new_row_id: $(this).attr('id')}, (data) ->
replace_items data
, 'json')
current_open_row = $(this).attr('id')
This doesn't read like smooth Coffeescript and I find myself thinking, "what could I have done better," but in particular, instantiating the current_open_row variable outside the scope of the click handler feels strange. Not doing this, of course, causes a new instantiation upon entry to the handler, which is always undefined.
Other than refactoring $(this).attr('id') into a variable, is there anything that jumps out as ugly, suboptimal, unreadable, etc., or is this pretty much the way of it?
Thanks!

Well, first off, I think you'll find yourself switching to camelCase eventually... I know that many people strongly prefer the readability_of_underscores, but every library you interact with (including jQuery) uses camelCase. Just something to keep in mind.
Setting that aside, the issue of having to scope variables with = null is a good one. I've tried to persuade Jeremy that there should be a nicer scoping syntax, but he's firmly against it. So I'd suggest moving the variable to an object property. Fortunately, this is jQuery, so there are plenty of places to stick data. How about using the list's .data method? This has an additional advantage: If you want to do this on multiple lists in the future (with one current_open_row in each list), you won't have to change your code one bit. Just add more lists with .faq-item children to the markup.
One more minor point: You give the post call a callback
(data) -> replace_items data
If that's all you're doing, why not just pass replace_items directly? :)
I'd also put each argument to the post function on its own line for readability. Key-value pairs are automatically combined into a single object, even without curly braces. Here's how it all looks:
$('li.faq-item').live 'click', (event) ->
$row = $(this)
$list = $row.parent()
row_id = $row.attr 'id'
$.post(
"/faqs/update_rows",
old_row_id: $list.data('current_open_row'),
new_row_id: row_id,
replace_items,
'json'
)
$list.data 'current_open_row', row_id

Related

Javascript - two objects which appear to be identical but aren't

JS comparative newbie... spent about 3 hours on this so far... Please consider this code if you'd be so kind:
function updateDataFields( mapIDToNewValue )
{
$.each(dataFields, function(key, value)
{
var dataField = $( value );
// var dataField = $(this); // same difference
console.log( dataField );
var iD = dataField.attr('id');
// DOES NOT WORK!!! i.e. going val() on this object does not update the INPUT element on the page!
// dataField.val(mapIDToNewValue[ iD ]);
var thePageElement = $( '#' + iD );
console.log( thePageElement );
console.log( '£ is dataField the same object as thePageElement?' + ( dataField === thePageElement ? 'yes' : 'no, you fool' ));
// DOES WORK:
thePageElement.val( mapIDToNewValue[ iD ] );
});
}
Explanation: dataFields, a file-global variable*, is passed by an outside script which calls this one, using a (fully) global variable which attaches dataFields to itself in piggyback fashion. This data structure, dataFields, consists of all the page elements with the class .dataField. All of these are in fact INPUT HTML elements.
Using the supplied param mapIDToNewValue (from an AJAX call to a dbase), I want to update the respective contents (i.e. text) of these INPUTs. mapIDToNewValue is a map in which the key is the same as the attr( 'id' ) of these dataFields, and the value is the new value which needs to be displayed in the INPUT.
It turns out that thePageElement is not the same object as dataField. When I examine the console output for them both they appear virtually the same... except that dataField, for example, has 0 height, which is enough to chill the soul!
My working hypothesis is that somehow dataFields, when passed from the calling script via this piggyback global variable, somehow turned its contents into "phantom" objects: they have the same id as thePageElement... but are incapable of having any effect on the real page elements themselves!
NB there is no possibility of duplicate ids here, or anything like that.
Any explanation welcome!
* implemented using anonymous function as per here.
later, in response to Patrick Barr's comment:
It's quite involved. Given your use of the word "interesting", I was moved to find out what your rep might be. If you had been a proven JS guru (you may nonetheless be one of course) I'd have been inclined to think that I need to embark on a forensic fault-finding mission.
The context: I'm developing a sort of "MSAccess Forms for MySQL front end" type of a thing, which is sorely lacking out there IMHO. At the moment I'm tackling subforms, and a major aim is to re-use code as much as is humanly possible. I'm rapidly getting out of my depth, not least because of the asynchronicity/concurrency issues that spring up, gorgon-like, at every turn. I'm thinking about how to answer your question in an informative, useful way.
To anyone else: I now realise I have to strip down my project to the bare bones to find out what's going on here... and if still baffled post an SSCCE (as we call it in Java) ... just thought initially that an expert out there might recognise a well-known issue and be able to set me straight.
Having examined things here I now understand what happened (to a degree).
In fact the dataFields Array was obtained from the result of an AJAX call which loaded (or more accurately returned, as the data param in the callback function) an HTML fragment, containing several page elements with class .dataField, which I then gathered by going data.find( '.dataField' ).
But in fact these "page elements", despite having hundreds of properties, just like objects directly "mapping" to page elements, were indeed "phantom" objects: specifically, properties such as clientHeight were 0, seemingly an indication that this is a non-visible object.
It was only when the HTML returned in the AJAX callback was inserted into the document structure (and made visible by setting hidden to false for the encompassing DIV) that this HTML generated real page elements. I then had to select these page elements with class .dataField to get hold of the non-phantom objects (having clientHeight 20 or whatever).
Quite strange, as these initial "phantom" objects did NOT become "real" objects as a consequence of the HTML being added to the document. They remained ... useless (and confusing!).

Ok to call document.querySelector( ) a bunch

If I have multiple instances of the following lines of code through out my js file:
document.querySelector('#IdName').play();
document.querySelector('#IdName').pause();
Is it a good idea to create a function and pass it the IdName(IdName will change in various parts of the code)? I know what it does but I'm really just curious if it's a good practice to call document.querySelector( )a bunch of times in the file or put it in a function where I only call it twice to perform the play and pause actions.
If you constantly need the same element, change the function to take a DOM node, and store the element in a variable instead
function doStuff(elem) {
elem.play();
}
function stopStuff(elem) {
elem.pause();
}
var element = document.querySelector('#IdName');
doStuff( element );
// later
stopStuff( element );
That way you only get the element once, and avoid unneccesary DOM lookups
The best approach is to cache that query in a variable so you don't need to search the DOM each time.
For an ID selector this time saving is likely minimal but for more complex collections can help
var $el = document.querySelector('#IdName');
$el.play();
$el.pause();
It is good practice to write code that is reusable, so in that case a function is better practice. If the function only contains 1 line of code and you call it many times, it is still preferable because then if you ever decide to update that line of code or add more code, it's centralized and you change in one place only.
As far as actual execution is concerned, these are the same:
document.querySelector('#IdName1').play();
document.querySelector('#IdName1').pause();
document.querySelector('#IdName2').play();
document.querySelector('#IdName2').pause();
document.querySelector('#IdName3').play();
document.querySelector('#IdName3').pause();
vs
playpause("#IdName1");
playpause("#IdName2");
playpause("#IdName3");
function playpause(idname){
document.querySelector(idname).play();
document.querySelector(idname).pause();
}
In addition to Steve's answer, also note that if you are using the same one twice in a row:
document.querySelector('#IdName').play();
document.querySelector('#IdName').pause();
then it is a better practice to do:
var thing_with_play_and_pause = document.querySelector('#IdName');
thing_with_play_and_pause.play();
thing_with_play_and_pause.pause();
This reduces the number of queries you have to make. Some IDEs (PyCharm for instance) will complain if you don't because it is less efficient.

How should I use Variables and jQuery Dom navigation?

I was just wondering which is the correct or most efficient way of navigating through the Dom using variables.
For example, can I concatenate selectors
var $container = '.my-container';
$($container).addClass('hidden');
$($container + ' .button').on('click', function(){
//something here
});
or should I use the jQuery traversal functions
var $container = $('.my-container');
$container.addClass('hidden');
$container.children('.button').on('click', function(){
//something here
});
Is there a different approach, is one best, or can you use them at different times?
The $ is usually used only when working with an actual jquery object. You generally shouldn't prefix anything with that unless it's really something from jquery.
Beyond that little bit though, performance-wise, your second bit of code is going to be faster. I made an example jsperf here: http://jsperf.com/test-jquery-select
The reason the second bit of code is faster is because (if I remember correctly) jquery caches the selection, and then any actions performed on that selection are scoped. When you use .find (which is really what you meant in your code, not .children), instead of trying to find elements through the entire document, it only tries to find them within the scope of whatever my-container is.
The time when you wouldn't want to use the second pattern is when you expect the dom to change frequently. Using a previous selection of items, while efficient, is potentially a problem if more buttons are added or removed. Granted, this isn't a problem if you're simply chaining up a few actions on an item, then discarding the selection anyway.
Besides all of that, who really wants to continuously type $(...). It's awkward.

EmberJS - Adding a binding after creation of object

I am trying to bind a property of an object to a property that's bound in an ArrayController. I want all of this to occur after the object has already been created and added to the ArrayController.
Here is a fiddle with a simplified example of what I'm trying to achieve.
I am wondering if I'm having problems with scope - I've already tried to bind to the global path (i.e. 'App.objectTwoController.objectOne.param3') to set the binding to. I've also tried to bind directly to the objectOneController (which is not what I want to do, but tried it just to see if it worked) and that still didn't work.
Any ideas on what I'm doing incorrectly? Thanks in advance for taking the time to look at this post.
So in the example below (I simplified it a little bit, but same principles apply)... The method below ends up looking for "objectOne" on "objectTwo" instead of on the "objectTwoController".
var objectTwoController: Em.Object.create({
objectOneBinding: 'App.objectOne',
objectTwoBinding: 'App.objectTwo',
_onSomething: function() {
var objectTwo = this.get('objectTwo');
objectTwo.bind('param2', Em.Binding.from('objectOne.param3'));
}.observes('something')
});
The problem is that you can't bind between two none relative objects. If you look in the "connect" method in ember you will see that it only takes one reference object (this) in which to observe both paths (this is true for 9.8.1 from your example and the ember-pre-1.0 release).
You have few options (that I can think of at least).
First: You can tell the objects about each other and in turn the relative paths will start working. This will actually give "objectTwo" an object to reference when binding paths.
....
objectTwo.set('objectOne', this.get('objectOne');
....
Second: You could add your own observer/computed property that will just keep the two in sync (but it is a little more verbose). You might be able to pull off something really slick but it maybe difficult. Even go so far as writing your own binding (like Transforms) to allow you to bind two non-related objects as long as you have paths to both.
_param3: function(){
this.setPath('objectTwo.param2', this.getPath('objectOne.param3');
}.observes('objectOne.param3')
You can make these dynamically and not need to pre-define them...
Third: Simply make them global paths; "App.objectOneController.content.param3" should work as your binding "_from" path (but not sure how much this helps you in your real application, because with larger applications I personally don't like everything global).
EDIT: When setting the full paths. Make sure you wait until end of the current cycle before fetching the value because bindings don't always update until everything is flushed. Meaning, your alert message needs to be wrapped in Ember.run.next or you will not see the change.

What jQuery annoyances should I be aware of as a Prototype user?

We're considering switching our site from Prototype to jQuery. Being all-too-familiar with Prototype, I'm well aware of the things about Prototype that I find limiting or annoying.
My question for jQuery users is: After working with jQuery for a while, what do you find frustrating? Are there things about jQuery that make you think about switching (back) to Prototype?
I think the only that gets me is that when I do a selection query for a single element I have to remember that it returns an array of elements even though I know there is only one. Normally, this doesn't make any difference unless you want to interact with the element directly instead of through jQuery methods.
Probably the only real issue I've ever ran into is $(this) scope problems. For example, if you're doing a nested for loop over elements and sub elements using the built in JQuery .each() function, what does $(this) refer to? In that case it refers to the inner-most scope, as it should be, but its not always expected.
The simple solution is to just cache $(this) to a variable before drilling further into a chain:
$("li").each(function() {
// cache this
var list_item = $(this);
// get all child a tags
list_item.find("a").each(function() {
// scope of this now relates to a tags
$(this).hide("slow");
});
});
My two pain points have been the bracket hell, can get very confusing
$('.myDiv').append($('<ul />').append($('<li />').text('content')));
My other common issue has to do with the use of JSON in jQuery, I always miss the last comma,
$('.myDiv').tabs({ option1:true, options2:false(, woops)});
Finally, I've been using jQuery for about 6 months now and I don't think I'll ever go back to prototypes. I absolutely love jQuery, and a lot of the tricks they use have helped me learn a lot. one cool trick that I like is using string literals for method calls, I never really did that too much with prototypes.
$('.myDiv')[(add ? 'add' : 'remove') + 'Class']('redText');
(The only thing I can think of is that this is the element instead of a jQuery object in $("...").each(function)-calls, as $(element) is more often used then just the element. And that extremly minor thing is just about it.
Example of the above (simplified and I know that there are other much better ways to do this, I just couldn't think of a better example now):
// Make all divs that has foo=bar pink.
$("div").each(function(){
if($(this).attr("foo") == "bar"){
$(this).css("background", "pink");
}
});
each is a function that takes a function as parameter, that function is called once for each matching element. In the function passed, this refers to the actual browser DOM-element, but I find that you often will want to use some jQuery function on each element, thus having to use $(this). If this had been set to what $(this) is, you'd get shorter code, and you could still access the DOM element object using this.get(0). Now I see the reason for things being as they are, namely that writing $(this) instead of this, is hardly that cumbersome, and in case you can do what you want to do with the DOM element the way it is is faster than the way it could have been, and the other way wouldn't be faster in the case you want $(this).)
I don't think there are any real gotchas, or even any lingering annoyances. The other answers here seem to confirm this - issues are caused simply by the slightly different API and different JavaScript coding style that jQuery encourages.
I started using Prototype a couple of years ago and found it a revelation. So powerful, so elegant. After a few months I tried out jQuery and discovered what power and elegance really are. I don't remember any annoyances. Now I am back working on a project using Prototype and it feels like a step back (to be fair, we're using Prototype 1.5.1).
If you reversed the question - "What Prototype annoyances should I be aware of as a jQuery user?" - you would get a lot more answers.
Nope. Nada. Nyet.
.each:
jQuery (you need Index, even if you're not using it):
$.each(collection, function(index, item) {
item.hide();
});
Prototype (you're usually using the item, so you can omit the index):
collection.each(function(item) {
item.hide();
});
This is really only an annoyance if you're doing a lot of DOM manipulation. PrototypeJs automatically adds its API to DOM Elements, so this works in prototypejs (jQuery of course doesn't do this):
var el = document.createElement("div");
el.addClassName("hello"); // addClassName is a prototypejs method implemented on the native HTMLElement
Even without running the native element through the $() function.
PS: Should note that this doesn't work in IE.

Categories

Resources