Collection doesn't work in template - javascript

I send a model to a template. The model has a collection. In the template I echo some variables and functions:
console.log(comments);
console.log(_.size(comments));
console.log(comments instanceof App.Collections.Comments);
console.log(_.pluck(comments, 'created'));
_.each(comments, function(com) {
console.log(com);
});
The first three work, but the last two underscore functions don't. Pluck gives 3x undefined and each doesn't iterate.
Object { length=3, models=[3], _byId={...}, more...}
3
true
[undefined, undefined, undefined]
How do I get the underscore functions to work?

Backbone collections have some Underscore methods mixed in so you can use the Underscore methods directly on the collection instance:
console.log(comments.pluck('created'));
comments.each(function(com) { console.log(com) });
Demo: http://jsfiddle.net/ambiguous/3jRNX/
This one:
console.log(_.size(comments));
works fine for you because _.size looks like this:
_.size = function(obj) {
return _.toArray(obj).length;
};
and _.toArray calls the collection's toArray:
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
which unwraps the collection's data to give you the correct length. The above is taken from the 1.3.1 source, the current Github master version's _.size has a different implementation so your _.size call is likely to break during an upgrade.

You'll want to call pluck directly on the collection, as the Collection class supports it:
http://documentcloud.github.com/backbone/#Collection-pluck
So instead of:
_.pluck(comments, 'created')
You chould call:
comments.pluck('created');

Related

array like object using array methods [duplicate]

I was looking at some snippets of code, and I found multiple elements calling a function over a node list with a forEach applied to an empty array.
For example I have something like:
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
but I can't understand how it works. Can anyone explain me the behaviour of the empty array in front of the forEach and how the call works?
[] is an array.
This array isn't used at all.
It's being put on the page, because using an array gives you access to array prototypes, like .forEach.
This is just faster than typing Array.prototype.forEach.call(...);
Next, forEach is a function which takes a function as an input...
[1,2,3].forEach(function (num) { console.log(num); });
...and for each element in this (where this is array-like, in that it has a length and you can access its parts like this[1]) it will pass three things:
the element in the array
the index of the element (third element would pass 2)
a reference to the array
Lastly, .call is a prototype which functions have (it's a function which gets called on other functions).
.call will take its first argument and replace this inside of the regular function with whatever you passed call, as the first argument (undefined or null will use window in everyday JS, or will be whatever you passed, if in "strict-mode"). The rest of the arguments will be passed to the original function.
[1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
console.log(i + ": " + item);
});
// 0: "a"
// 1: "b"
// 2: "c"
Therefore, you're creating a quick way to call the forEach function, and you're changing this from the empty array to a list of all <a> tags, and for each <a> in-order, you are calling the function provided.
EDIT
Logical Conclusion / Cleanup
Below, there's a link to an article suggesting that we scrap attempts at functional programming, and stick to manual, inline looping, every time, because this solution is hack-ish and unsightly.
I'd say that while .forEach is less helpful than its counterparts, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), it still serves purposes when all you really want to do is modify the outside world (not the array), n-times, while having access to either arr[i] or i.
So how do we deal with the disparity, as Motto is clearly a talented and knowledgeable guy, and I would like to imagine that I know what I'm doing/where I'm going (now and then... ...other times it's head-first learning)?
The answer is actually quite simple, and something Uncle Bob and Sir Crockford would both facepalm, due to the oversight:
clean it up.
function toArray (arrLike) { // or asArray(), or array(), or *whatever*
return [].slice.call(arrLike);
}
var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);
Now, if you're questioning whether you need to do this, yourself, the answer may well be no...
This exact thing is done by... ...every(?) library with higher-order features these days.
If you're using lodash or underscore or even jQuery, they're all going to have a way of taking a set of elements, and performing an action n-times.
If you aren't using such a thing, then by all means, write your own.
lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
lib.extend = function (subject) {
var others = lib.array(arguments, 1);
return others.reduce(appendKeys, subject);
};
Update for ES6(ES2015) and Beyond
Not only is a slice( )/array( )/etc helper method going to make life easier for people who want to use lists just like they use arrays (as they should), but for the people who have the luxury of operating in ES6+ browsers of the relatively-near future, or of "transpiling" in Babel today, you have language features built in, which make this type of thing unnecessary.
function countArgs (...allArgs) {
return allArgs.length;
}
function logArgs (...allArgs) {
return allArgs.forEach(arg => console.log(arg));
}
function extend (subject, ...others) { /* return ... */ }
var nodeArray = [ ...nodeList1, ...nodeList2 ];
Super-clean, and very useful.
Look up the Rest and Spread operators; try them out at the BabelJS site; if your tech stack is in order, use them in production with Babel and a build step.
There's no good reason not to be able to use the transform from non-array into array... ...just don't make a mess of your code doing nothing but pasting that same ugly line, everywhere.
The querySelectorAll method returns a NodeList, which is similar to an array, but it's not quite an array. Therefore, it doesn't have a forEach method (which array objects inherit via Array.prototype).
Since a NodeList is similar to an array, array methods will actually work on it, so by using [].forEach.call you are invoking the Array.prototype.forEach method in the context of the NodeList, as if you had been able to simply do yourNodeList.forEach(/*...*/).
Note that the empty array literal is just a shortcut to the expanded version, which you will probably see quite often too:
Array.prototype.forEach.call(/*...*/);
The other answers have explained this code very well, so I'll just add a suggestion.
This is a good example of code that should be refactored for simplicity and clarity. Instead of using [].forEach.call() or Array.prototype.forEach.call() every time you do this, make a simple function out of it:
function forEach( list, callback ) {
Array.prototype.forEach.call( list, callback );
}
Now you can call this function instead of the more complicated and obscure code:
forEach( document.querySelectorAll('a'), function( el ) {
// whatever with the current node
});
It can be better written using
Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {
});
What is does is document.querySelectorAll('a') returns an object similar to an array, but it does not inherit from the Array type.
So we calls the forEach method from the Array.prototype object with the context as the value returned by document.querySelectorAll('a')
[].forEach.call( document.querySelectorAll('a'), function(el) {
// whatever with the current node
});
It is basically the same as:
var arr = document.querySelectorAll('a');
arr.forEach(function(el) {
// whatever with the current node
});
Want to update on this old question:
The reason to use [].foreach.call() to loop through elements in the modern browsers is mostly over. We can use document.querySelectorAll("a").foreach() directly.
NodeList objects are collections of nodes, usually returned by
properties such as Node.childNodes and methods such as
document.querySelectorAll().
Although NodeList is not an Array, it is possible to iterate over it
with forEach(). It can also be converted to a real Array using
Array.from().
However, some older browsers have not implemented NodeList.forEach()
nor Array.from(). This can be circumvented by using
Array.prototype.forEach() — see this document's Example.
Lots of good info on this page (see answer+answer+comment), but I recently had the same question as the OP, and it took some digging to get the whole picture. So, here's a short version:
The goal is to use Array methods on an array-like NodeList that doesn't have those methods itself.
An older pattern co-opted Array's methods via Function.call(), and used an array literal ([]) rather than than Array.prototype because it was shorter to type:
[].forEach.call(document.querySelectorAll('a'), a => {})
A newer pattern (post ECMAScript 2015) is to use Array.from():
Array.from(document.querySelectorAll('a')).forEach(a => {})
An empty array has a property forEach in its prototype which is a Function object. (The empty array is just an easy way to obtain a reference to the forEach function that all Array objects have.) Function objects, in turn, have a call property which is also a function. When you invoke a Function's call function, it runs the function with the given arguments. The first argument becomes this in the called function.
You can find documentation for the call function here. Documentation for forEach is here.
Just add one line:
NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;
And voila!
document.querySelectorAll('a').forEach(function(el) {
// whatever with the current node
});
Enjoy :—)
Warning: NodeList is a global class. Don't use this recomendation if you writing public library. However it's very convenient way for increasing self-efficacy when you work on website or node.js app.
Just a quick and dirty solution I always end up using. I wouldn't touch prototypes, just as good practice. Of course, there are a lot of ways to make this better, but you get the idea.
const forEach = (array, callback) => {
if (!array || !array.length || !callback) return
for (var i = 0; i < array.length; i++) {
callback(array[i], i);
}
}
forEach(document.querySelectorAll('.a-class'), (item, index) => {
console.log(`Item: ${item}, index: ${index}`);
});
[] always returns a new array, it is equivalent to new Array() but is guaranteed to return an array because Array could be overwritten by the user whereas [] can not. So this is a safe way to get the prototype of Array, then as described, call is used to execute the function on the arraylike nodelist (this).
Calls a function with a given this value and arguments provided
individually. mdn
Norguard explained WHAT [].forEach.call() does and James Allardice WHY we do it: because querySelectorAll returns a NodeList that doesn't have a forEach method...
Unless you have modern browser like Chrome 51+, Firefox 50+, Opera 38, Safari 10.
If not you can add a Polyfill:
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = function (callback, thisArg) {
thisArg = thisArg || window;
for (var i = 0; i < this.length; i++) {
callback.call(thisArg, this[i], i, this);
}
};
}
let's say you have : const myList= document.querySelectorAll("p");
This will return an list/array of all in your HTML.
Now Array.prototype.forEach.call(myList, myCallback)
is equivalent to [].forEach.call(myList, myCallback)
where 'myCallback' is a callback function.
You are basically running the callback function on each element of myList.
Hope this helped you!
I don't know if there is any restriction, but it works.
I turned the nodeList into an iterator object using the spread operator and mapped it:
let _btns = document.querySelectorAll('.btn');
[..._btns].map(function(elem, i) {
elem.addEventListener('click', function (e) {
console.log(elem.textContent);
})
})
.btn {
padding: 5px;
color:#fff;
background-color: darkred;
text-align:center;
color: white;
}
<button class="btn">button 1</button>
<button class="btn">button 2</button>

Use underscore.js "pluck" on a knockout observable array

I have an observable array of objects and I want to pluck out the values using underscore.js
For example:
ko.observableArray([{
id: ko.observable(1),
name: ko.observable("name1")
},
{
id: ko.observable(2),
name: ko.observable("name2")
},
...])
And I just want to pluck the values inside of the object rather than the whole observable.
Can I do this with just one command?
I tried:
_.pluck(myArray(), "id()") and _.pluck(myArray(), "id"())
But these return an array of undefineds and "id is not a function" respectively.
Thanks!
Short answer
Use _.invoke instead of _.pluck
See this sample fiddle.
Long answer
_.pluck(list, propertyName) works as documented:
A convenient version of what is perhaps the most common use-case for map: extracting a list of property values.
Or, as better exlained on lodash docs: _.pluck(collection, path)
Gets the property value of path from all elements in collection.
So, if you do this:
_.pluck(myArray(), "id")
what you get is an array with all the id's. And all of these id's are observables, as in the objects of the original array
But you can use _.invoke(list, methodName, *arguments), which, as documented:
Calls the method named by methodName on each value in the list. Any extra arguments passed to invoke will be forwarded on to the method invocation.
or, on lodash version _.invoke(collection, path, [args])
Invokes the method at path on each element in collection, returning an array of the results of each invoked method. Any additional arguments are provided to each invoked method. If methodName is a function it is invoked for, and this bound to, each element in collection.
In this way, you execute each observable, and get its value as expected:
_.invoke(myArray(), "id")
Mind the viewmodels full of observables!
The first comment to this question has made me include this notice:
The best solution is using ko.toJS to convert all the observables in a view model into a regular JavaScript object, with regular properties. Once you do it, underscore, or any other library, will work as expected.
The _.invoke solution only works for a single level of observables, as this case. If there were several level of nested observables, it will completely fail, because it invokes a function at the end of the path, not at each step of the path, for example, _.invoke wouldn't work for this case:
var advices = [{
person: ko.observable({
name = ko.observable('John')
}),
advice: ko.observable('Beware of the observables!')
}];
In this case, you could only use _.invoke on the first level, like this:
var sentences = _.invoke(advices,'advice');
But this wouldn't work:
var names = _.invoke(advices,'person.name');
In this call, only name would be invoked, but person not, so this would fail, because person is an observable, thus it doesn't have a name property.
NOTE: lodash is another library similar, and mostly compatible with underscore, but better in some aspects
I was able to solve this by using the "map" function:
_.map(myArray(), function(item) {return item.id()});
But I was hoping to use pluck since it's the exact use-case for this type of scenario.
Because name is a function, how about pluck your original array into an array of functions, then using ko.toJS to convert it into string array?
var myArray = ko.observableArray([{
id: ko.observable(1),
name: ko.observable("name1")
},
{
id: ko.observable(2),
name: ko.observable("name2")
}]);
var names = _.pluck(myArray(), 'name');
console.log(ko.toJS(names)); // Output: ["name1", "name2"]
Unwrap it first
_.pluck(ko.toJS(myArray), 'id')
_(ko.toJS(myArray)).pluck('id)

Serialising into JSON a Backbone collection which contains attributes

I would like to know how exactly a serialisation into JSON of a Backbone collection which contains attributes and models could be performed.
So per example if I have a collection like this:
var myCollection = Backbone.Collection.extend({
initialize: function (attr, options) {
this.property = options.property;
}
});
When trying to stringify with the JSON.stringify(myCollection) function, the stringify will call the object toJSON method defined in the Backbone Collection Object. Backbone collection defines this method as follows:
toJSON : function() {
return this.map(function(model){ return model.toJSON(); });
}
This means that only the models included in the collection will be included in the result JSON object but not the attributes of the collection I previously defined.
Do you know who this could be achieved?
Thanks.
Edit: Maybe not clear enough in my original question, I know I could override the toJSON method my questions is how exactly do it to achieve what I need in a general matter (not only for this specific attribute but imagine I have another collections which contains other properties, and all of them inherit from a baseCollection object. More over I would like to know how to restore that JSON object back to the backbone original state (not sure if by creating a new collection passing the JSON object in the parameters of the constructor would do the trick with this attributes)
You could override the "toJSON" method in your MyCollection class and return the string that you want.
You could override the toJSON method like #trunal-bhanse said, but if your goal is to sync that data with your server then you should probably find a better way of representing that data. Backbone Collections by default return an array when you call toJSON on an instance of one. That array then gets converted into a string using JSON.stringify before being sent to the server. However, the JSON.stringify method will not include any properties you've set on the array:
var test = ["foo", "bar", "baz"];
test.Hello = "World!";
console.log(test); // ["foo", "bar", "baz", Hello: "World!"]
JSON.stringify(test); // '["foo", "bar", "baz"]'

How does jQuery invoke methods of objects when an Array of jQuery objects is returned?

After passing a string "selector" into the jQuery function:
$('#onenode')
An array of jQuery object's is returned.
One of the methods of these object's is "html", which is why:
$('#onenode').html('hello!');
Works.
However...
This:
$('.somenodes')
Returns an array of jQuery objects, each of the objects in this array has the method "html".
So how does:
$('.somenodes').html('hello');
Work? The "html" method must be a method of the Array Object that is returned.
I assume therefore that the "html" method of the Array Object looks similar to this:
html: function(value) {
for(var i=0; i<this.length; i+=1) {
this[i].html(value);
}
}
These are all assumptions, I'm pretty much guessing.
I am attempting to create my own small library that uses "selectors", but I am struggling with this part. I know that this is probably incorrect -- could someone explain how jQuery really does it?
jQuery is basically a big prototype with a bunch of methods that return this, where this is the instance of jQuery you're working with.
I am attempting to create my own small library that uses "selectors"
Here's a very reduced example of a jQuery-like pattern that works in modern browsers:
(function(win, doc) {
var __slice = [].slice;
function jQuery(selector) {
this.el = this._init(selector);
this.length = this.el.length;
}
function $(selector) {
return new jQuery(selector);
}
jQuery.prototype = {
_init: function(selector) {
return __slice.call(doc.querySelectorAll(selector));
},
each: function(fn) {
return this.el.some(fn), this;
},
map: function(fn) {
return this.el.map(fn), this;
}
};
win.$ = $;
}(window, document));
You can use that to build your jQuery like library. It works exactly like jQuery:
$('p').each(function() {
console.log(this);
});
A currying function, each and map is all you need to get started. Those are the methods the jQuery uses pretty much everywhere to manipulate DOM elements. this.el is the array of elements while this is the jQuery instance. Just don't forget that you need to return this in every method that will be chained.
$('.somenodes') does not return an array, its a jquery object only, which have some functions of native array..
I also had similar doubt some time back, check this answer on my question..https://stackoverflow.com/a/11158649/1114536
jQuery objects currently support 3 array methods:
var methods = 'pop push reverse shift sort splice unshift concat join slice toString indexOf lastIndexOf filter forEach every map some reduce reduceRight'.split(' ')
var implemented = $.grep(methods, function(m) {
return $.prototype[m] == Array.prototype[m];
});
console.log(implemented); // => ["push", "sort", "splice"]
They also have slice, but it's not the same slice as arrays have:
$.prototype.slice === Array.prototype.slice // => false

Filtering Backbone.js collection

I'm working with several backbone collections and sometimes I need to access parts of them based on some criteria.
METHOD 1
As already stated in this question, using filter() on the collection itself returns an array of models and not another collection. This can work in simple cases, but it has the effect of losing collection's method concatenation as a plain array of models won't have all methods defined in the collection.
METHOD 2
The answer to that question suggested creating a new collection passing the array of models to the constructor. This works but has the side effect of calling the collection's constructor every time, so any event binding that might be defined there gets stacked on every time you filter the collection.
So what's the correct way to create a sub-collection based on some filter criteria?
Should I use method 1 and create more filtering methods instead on relying on method chaining?
Should I go with method 2 and avoid binding events in the collection's constructor?
Personally I would create more filtering methods on the collection, because it has the additional benefit of encapsulating logic inside the collection.
You could also try to reuse the existing collection. I was toying around with the idea, and arrived at something like this:
var Collection = Backbone.Collection.extend({
//Takes in n arrays. The first item of each array is the method you want
//to call and the rest are the arguments to that method.
//Sets the collection.models property to the value of each successive filter
//and returns the result of the last. Revers the collection.models to its original value.
chainFilters: function(/*args..*/) {
var models = this.models;
try {
filters = _.toArray(arguments);
_.each(filters, function(filter) {
this.models = filter[0].apply(this, _.rest(filter));
}, this);
} catch(err) {
this.models = models;
throw err;
}
var filtered = this.models;
this.models = models;
return filtered;
}
});
Usage:
var results = collection.chainFilters(
[ collection.filter, function(model) { return model.get('name') === 'foo'; } ],
[ collection.someMethod, 'someargument' ],
[ collection.someOtherMethod ]
);
Here's a working sample. It's a bit peculiar, I know.
It depends on the use case. If you want those models to update a view then you probably want a new collection as otherwise you don't get the nice reactive template updates. If you simply wanted the models to iterate through or manipulate data without worrying about the data updating then use the array + underscore.js.
Try it with the arrays and if you find yourself writing a lot of boiler plate code with features already in a collection but not in underscore.js, just start using a collection.

Categories