Custom for-loop helper for EmberJS/HandlebarsJS - javascript

A small two hours ago I started: Nested HandlebarsJS #each helpers with EmberJS not working
Shortly after I figured an acceptable temporary solution myself, question is still unaswered. My problems didn't stop there though.
I am now trying to make a custom helper which will loop through an array of objects, but exclude the first index - pretty much: for(i = 1; i < length; i++) {}. I've read on websites you have to get the length of your context and pass it to options - considering your function looks like: forLoop(context, options).
However, context is a string rather than an actual object. When you do a .length, you will get the length of the string, rather than the size of the array. When I pass that to options, nothing happens - not too mention browser freezes.
I then first tried to do a getPath before passing it to options, this returns an empty string.
What am I supposed to do instead, I made the for-loop code before for just HandlebarsJS and that worked, but EmberJS doesn't seem to take it, why?
EDIT: I pretty much also followed: http://handlebarsjs.com/block_helpers.html -> Simple Iterators

I solved this myself after trying for a long time.
The HandlebarsJS method (as described on the site) is no longer valid for EmberJS, it's now as follows:
function forLoop(context, options) {
var object = Ember.getPath(options.contexts[0], context);
var startIndex = options.hash.start || 0;
for(i = startIndex; i < object.length; i++) {
options(object[i]);
}
}
Heck, you could even extend the for-loop to include an index-value!
function forLoop(context, options) {
var object = Ember.getPath(options.contexts[0], context);
var startIndex = options.hash.start || 0;
for(i = startIndex; i < object.length; i++) {
object[i].index = i;
options(object[i]);
}
}
This is a working for-loop with variable start index. You use it in your templates like so:
{{#for anArray start=1}}
<p>Item #{{unbound index}}</p>
{{/for}}

Here is how I did it (and it works !!!)
First,
i had in my model a 'preview' property/function, that just return the arrayController in an array :
objectToLoop = Ember.Object.extend({
...
arrayController: [],
preview: function() {
return this.get('arrayController').toArray();
}.property('arrayController.#each'),
...
});
Then, I add a new Handlebars helper :
Handlebars.registerHelper("for", function forLoop(arrayToLoop, options) {
var data = Ember.Handlebars.get(this, arrayToLoop, options.fn);
if (data.length == 0) {
return 'Chargement...';
}
filtered = data.slice(options.hash.start || 0, options.hash.end || data.length);
var ret = "";
for(var i=0; i< filtered.length; i++) {
ret = ret + options.fn(filtered[i]);
}
return ret;
});
And thanks to all this magic, I can then call it in my view :
<script type="text/x-handlebars">
<ul>
{{#bind objectToLoop.preview}}
{{#for this end=4}}
<li>{{{someProperty}}}</li>
{{/for}}
{{/bind}}
</ul>
</script>
And that's it.
I know it is not optimal, so whoever have an idea on how to improve it, PLEASE, make me know :)

Related

javascript callback - how to use call backs

I need help with this JS callback function. I am trying to figure out how exactly callbacks work in JS.
--quick test code follows:
function debFilter(deb_array, fillCb){
var filt_darr = [];
for (var inx in deb_array) {
filt_darr.push(fillCb(deb_array[inx]));
}
return filt_darr;
}
console.log(debFilter(savedInp, function(x) { if (x%2 == 0) { return x;}} ));
Let's say my savedInp array contains [2,3,4,5,6,7,8,9] something like this. How do I make sure my callback returns only the even elements and not the odd ones? so my filt_darr would be [2,4,6...etc].
With the above test code I am getting [2,undefined,4,undefined,..etc]. I have tried with other similar conditions too with no avail. I just need to know how to tell JS not to 'push/return' something I dont need. Sorry if this is a beginner Q.
Thanks for the help.
Iterate the array and then push evens into a new array:
var a = [1,2,3,4,5];
function getEvens(originalArray){
var evens = [];
for(var i = 0; i < originalArray.length; ++i){
if(originalArray[i] % 2 === 0){
evens.push(originalArray[i]);
}
}
return evens;
}
As you probably noticed, you are collecting every return value into your result array and your callback returns undefined for every odd.
You could change your code to sth like
function debFilter(deb_array, fillCb){
'use strict';
var filt_darr = [],
len = deb_array.length;
for (var i = 0; i < len; i++) {
if (fillCb(deb_array[i])) {
filt_darr.push(deb_array[i]);
}
}
return filt_darr;
}
By the way, ES 5 supports Array.prototype.filter which might be what you are looking for. There is also a polyfill that you can take some inspiration from.

unable to push each element that is named tab into an array after pulling the dom object

In the developer tools it shows that i was able to pull all the elements that i needed to but the issue is i cannot push them to the array, it stays empty. Unfortunately i am not allowed to use jquery. I have done so much research and just unable to find exactly what i need.
/*
Author:Anthony Weed
Date:2/20/2015
Filename: menus.js
*/
window.addEventListener('load',setTabs);
var currentTab = null;
var maxZ = 1;
var i=0;
function setTabs(){
var menuTabs = [];
var allElements = document.getElementById('page').getElementsByTagName('*');
for(var i=0; i < allElements.length; i++){
if (allElements.children == 'LI.tab'){
menuTabs.push(allElements[i]);
}
else {
continue;
}
i++
}
console.log(allElements);
console.log(menuTabs);
}
Try this:
for(var i=0; i < allElements.length; i++){
if (allElements.children == 'LI.tab'){
var newAryElement = allElements[1];
menuTabs.push(newAryElement);
}
i++
}
This might not solve your problem, but I suspect that it will reveal newAryElement to be an array itself, which you will then need to parse.
Your problem is this line:
if (allElements.children == 'LI.tab'){
allElements.children is referencing something that isn't there. allElements is an HTMLCollection, which is kind of like an array, but it's an object.
I'm a little unclear in what you're trying to do, so I can't really offer a way to make this work, but this is certainly the problem. Depending on what you're trying to do, you might want to be referencing something like:
if (allElements.item(i).tagName == somethingHere ) {
Here's the reference for getElementsByTagName().

Node.js loops and JSON building

Respected ppl ....
This is my node.js code ...
https://gist.github.com/SkyKOG/99d47dbe5a2cec97426b
Im trying to parse the data of our exam results ...example ...
http://www.vtualerts.com/results/get_res.php?usn=1MV09IS002&sem=7
Im getting the results ...and i am traversing back for previous seems too ...
All works but traversing back happens in random ... prolly something wrong with the loops ...
json.results = [];
var output = '';
var k = response.query.results.body.div.div[0].table[1].tr.length;
for (var j = 1; j < k; j++) {
for (var i = 0; i <= 5; i++) {
var result_obj = {};
result_obj.subjects = [];
for (key in response.query.results.body.div.div[0].table[1].tr[j].td[i]) {
if (typeof response.query.results.body.div.div[0].table[1].tr[j].td[i].em === "undefined") {
continue;
}
var subject_obj = {};
output += "Subject : " + response.query.results.body.div.div[0].table[1].tr[j].td[i].em + " " + "\n";
var subtext = response.query.results.body.div.div[0].table[1].tr[j].td[i].em + " " + "\n";
subject_obj.subjectname = subtext.replace(/[(].*[)]/, "").trim();
result_obj.subjects.push(subject_obj);
console.log(subject_obj);
break;
}
console.log(result_obj.subjects);
I presume there is something like async concepts which need to implemented correctly to make the reordering of sems in correct order ...
And to get the JSON in this format ...
https://gist.github.com/SkyKOG/3845d6a94cea3b744296
I dont think im pushing the created objects at the right scope ...
Kindly help in this regard .... thank you ...
(I'll answer the ordering part. Suggest making the JSON issue a separate question to fit in with the Q&A format.)
When you make the HTTP request in your code (see the line below) you're bringing a varying delay into the order that responses are executed
new YQL.exec(queryname, function (response) {
You need to track the order of the requests yourself, or use a library to do it for you.
Code it yourself
In order to get around that you need something that keeps track of the original order of the requests. Because of the way closures work you can't just increment a simple counter because it'll be changed in the global scope as your loop progresses. The idiomatic way to solve this is by passing the counter into an immediately executed function (as a value type)
e.g.
var responseData = [];
for ( var i = 0; i < 100; i++ ){
(function(){
...
// http call goes in here somewhere
responseData[i] = data_from_this_response
...
})(i)
}
Use a library
Check out the async.parallel() call in Caolan's excellent library. You pass it an array of functions and it'll return to your callback with an array of the results.
https://github.com/caolan/async/#parallel
You'll need to create a loop that populates the array with curried versions of your function containing the appropriated variables.

Better way to create a jQuery collection

I wrote a plugin like so to grab a subset of a collection:
jQuery.range = function(start, end, includingTheLast) {
var ret = $([]), i = 0;
while (!this.eq(i).is(start) && i < this.length)
i++;
for (; i < this.length && !this.eq(i).is(end); i++) {
ret = ret.add(this[i]); // we can do better than this
}
if (includingTheLast) ret = ret.add(this[i]); // we can do better than this
return this.pushStack(ret, 'range');
}
It's used like this:
$('a').range(':eq(2)', '#stop')...
Looking at ret = ret.add(this[i]) seems to be very slow, is this a smart way to do it? Should I build an array then turn it into a jQuery object? Is this micro-optimizing?
The jQuery constructor also accepts an array of DOM elements and wraps them in a jQuery object. So, if you are opposed to using .add, you could push them all to an array (as dom elements) and then wrap the whole thing at once.
I have not run a perf test to see what would be faster.
reference: http://api.jquery.com/jQuery/

jQuery/Javascript index into collection/map by object property?

I have the following Javascript defining an array of countries and their states...
var countryStateMap = [{"CountryCode":"CA","Name":"Canada","States":[{"StateCode":"S!","CountryCode":"CA","Name":"State 1"},{"StateCode":"S2","CountryCode":"CA","Name":"State 2"}]},"CountryCode":"US","Name":"United States","States":[{"StateCode":"S1","CountryCode":"US","Name":"State 1"}]}];
Based on what country the user selects, I need to refresh a select box's options for states from the selected Country object. I know I can index into the country collection with an int index like so...
countryStateMap[0].States
I need a way to get the Country by CountryCode property though. I know the following doesn't work but what I would like to do is something like this...
countryStateMap[CountryCode='CA'].States
Can this be achieved without completely rebuilding my collection's structure or iterating over the set each time to find the one I want?
UPDATE:
I accepted mVChr's answer because it worked and was the simplest solution even though it required a second map.
The solution we actually ended up going with was just using the country select box's index to index into the collection. This worked because our country dropdown was also being populated from our data structure. Here is how we indexed in...
countryStateMap[$('#country').attr("selectedIndex")]
If you need to do it any other way, use any of the below solutions.
One thing you could do is cache a map so you only have to do the iteration once:
var csmMap = {};
for (var i = 0, cl = countryStateMap.length; i < cl; i++) {
csmMap[countryStateMap[i].CountryCode] = i;
}
Then if countryCode = 'CA' you can find its states like:
countryStateMap[csmMap[countryCode]].States
countryStateMap.get = function(cc) {
if (countryStateMap.get._cache[cc] === void 0) {
for (var i = 0, ii = countryStateMap.length; i < ii; i++) {
if (countryStateMap[i].CountryCode === cc) {
countryStateMap.get._cache[cc] = countryStateMap[i];
break;
}
}
}
return countryStateMap.get._cache[cc];
}
countryStateMap.get._cache = {};
Now you can just call .get("CA") like so
countryStateMap.get("CA").States
If you prefer syntatic sugar you may be interested in underscore which has utility methods to make this kind of code easier to write
countryStateMap.get = _.memoize(function(cc) {
return _.filter(countryStateMap, function(val) {
val.CountryCode = cc;
})[0];
});
_.memoize , _.filter
love your local jQuery:
small little function for you:
getByCountryCode = function(code){var res={};$.each(countryStateMap, function(i,o){if(o.CountryCode==code)res=o;return false;});return res}
so do this then:
getByCountryCode("CA").States
and it returns:
[Object, Object]

Categories

Resources