Checking if item is last in each helper - javascript

I have a set of items and I want to make a row element around every 4 items, I found this helper here
https://funkjedi.com/technology/412-every-nth-item-in-handlebars/
Handlebars.registerHelper('grouped_each', function(every, context, options) {
var out = "", subcontext = [], i;
if (context && context.length > 0) {
for (i = 0; i < context.length; i++) {
if (i > 0 && i % every === 0) {
out += options.fn(subcontext);
subcontext = [];
}
subcontext.push(context[i]);
}
out += options.fn(subcontext);
}
return out;
});
Which accompanied with the each helper I can do this
{{#grouped_each 4 data}}
<div class="message-row">
{{#each this}}
<div class="message">
[Content here...]
</div>
{{/each}}
</div>
{{/grouped_each}}
Which works well, every 4 items I get a new row element, but the last row will always be one element short, because in the last elements place I want to play a generic "Read more" type of item.
Ideally I would just like something like this
{{#grouped_each 4 data}}
<div class="message-row">
{{#each this}}
<div class="message">
[Content here...]
</div>
{{#if $last}}
<div class="message">
[Content for last item here...]
</div>
{{/if}}
{{/each}}
</div>
{{/grouped_each}}
Where the helper can just make that isLast variable true when it's on the final item, and thus render the final read more item after the final item has been rendered. I'm having trouble understanding how I can pass a variable to the helper that tells me that.
I've seen this work before but I can't seem to tweak the snippets I've seen to work for my scenario.

I think what you want to do, if indeed you want to do it this way, is to append a "last: true" key/value to your subcontext that is output by your helper, instead of trying to pass something into the helper, so that the template gets access to it. This may require re-shaping the data structure of your subcontext a bit. But then you would be able to check
{{#if this.last}}
<somestuff></somestuff>
{{/if}}
Here's a quick shot at how you might do that using your own code from above:
Handlebars.registerHelper('grouped_each', function(every, context, options) {
var out = "", subcontext = {}, i;
if (context && context.length > 0) {
for (i = 0; i < context.length; i++) {
if (i > 0 && i % every === 0) {
out += options.fn(subcontext);
subcontext.list = [];
}
if (i === context.length - 1){
subcontext.last = true;
}
subcontext.list.push(context[i]);
}
out += options.fn(subcontext);
}
return out;
});
then in your template do:
{{#grouped_each 4 data}}
<div class="message-row">
{{#each this.list}}
<div class="message">
[Content here...]
</div>
{{#if this.last}}
<div class="message">
[Content for last item here...]
</div>
{{/if}}
{{/each}}
</div>
{{/grouped_each}}

Related

Opening a div in a {{/if}} and closing it later with meteor/blaze

I'm trying to make something that is not possible in HTML/Blaze only.
By that I mean that I'm trying to open a div in a {{#if}} without closing it in this specific if. Impossible to do it the following way :
{{#each getData}}
{{#if equals this.level first}}
<div><p>content</p>
{{/if}}
{{#if equals this.level last}}
</div>
{{/if}}}
{{/each}}
So I found something that could solve this problem. Using triple curly braces and generate the HTML part in JS.
{{#each getData}}
{{{getContent this}}}
{{/each}}
getContent() return my HTML. It looks like this :
getContent(obj) {
if (obj.level == first)
return "<div><p>content</p>";
if (obj.level == last)
return "<p>content></div>";
else
return "<p>content</p>";
}
It works, it render the HTML, but there is one problem. The div I'm opening seems to close itself.
This is what's rendered :
<div><p>Content</p></div>
<p>content</p>
<p>content</p>
instead of :
<div><p>content</p>
<p>content</p>
<p>content</p>
</div
I'm really tired, I apologize if I'm not clear enough or if the solution is obvious.
EDIT: What I have now, and it's working fine, is the following :
HTML
{{#with getDataFromButton}}
{{formatData this}}
{{/with}}
JS
formatData(data) {
var res = "";
for (var i = 0; i < data.length; i++) {
if (data[i]["level"] == 0) {
if (res.length > 0)
res += "</div>";
res += "<div class=\"whiteBox\"><p>" + data[i]["value"] + "</p>";
}
else if ('last' in data[i]) {
res += "<p>" + data[i]["value"] + "</p></div>";
}
else {
res += "<p>" + data[i]["value"] + "</p>";
}
}
return res;
},
Thanks to all of you for the explanations ! And happy new year (it's not too late).
This is done because of browser try to fix HTML structure on any change. If you using Blaze, then HTML is rendered on the client side and in any evaluation of your helper code is injected to DOM. Then browser gets this HTML and tries to fix it.
Best solution is use simple pattern
<div>
{{#each getData}}
<p>content</p>
{{/each}}
</div>
If you want to apply the logic that you presented you have to prepare full correct HTML element in JS. You can to it as follow.
In template type: {{ myFullCorrectNodeElement }}
In JS type:
helpers: {
myFullCorrectNodeElement() {
let html = "";
const data = Template.instance().getData;
for(let i=0; i<data.length; i++) {
// your logic
if(i===0) {
html += `<div><p>content</p>`
// there you can access to variables by syntax ${}
// insde of ``, you can read about template strings from ES6
}
// ... and rest of your logic
}
return html;
}
}

meteor: product grid with pictures in FS collection

I want to make a grid of a 'partner' collection
Each partner consist of a partnerGridForm template
<template name="partnerGridForm">
<div class="col-sm-4">
<div class="panel panel-default">
{{_id}}
{{#each profileImages}}
{{>partnerGridImage}}
{{/each}}
<h3 class="panel-title">{{profile.partner_company}}</h3>
<hr>
{{profile.partner_address_line_1}}<br>
{{#if profile.partner_address_line2}}
{{profile.partner_address_line2}}<br>
{{/if}}
{{profile.partner_zipcode}} {{profile.partner_city}}<br>
{{profile.partner_state}} - {{profile.partner_country}}<br>
{{profile.partner_phone_nr}}<br>
{{profile.partner_mobile_nr}}<br>
<hr>
<b>
{{profile.partner_category}}<br>
{{profile.partner_sub_category}}</b>
<hr>
{{{profile.partner_promo_text}}}
</div><!-- end panel -->
</div><!-- end feature -->
</template>
All these partnerGridForm templates are displayed on a page trough a loop trough the partner collection (which is in fact the user collection)
<template name="partnerGrid">
<div class="container container-top">
{{#each partners}}
{{>partnerGridForm}}
{{/each}}
</div> <!-- end container -->
</template>
The pictures for each partner are stored separately in a ProductImages.fs file
I want each partnerGridForm to display the images in the partnerGridForm but I can't make it work.
Trough the Reactive character of meteor all my gridforms are constantly updated with the last picture I get.
Currently i have the following helpers on the partnerGrid template
// Template Helpers
Template.partnerGrid.helpers({
partners: function () {
return Meteor.users.find();
},
});
// On rendering get lists
Template.partnerGrid.onRendered(function (event,template) {
if (typeof partnerSub !== 'undefined') {
if ( partnerSub != null ) {
partnerSub.stop();
};
};
partnerSub = Meteor.subscribe('partnerList');
});
and the following helpers on the partnerGridForm template
Template.partnerGridForm.helpers({
profileImages: function() {
return PartnerImages.find();
},
});
Template.partnerGridForm.onRendered(function(event,template){
currentPartner = Template.currentData() // Get current data in template
currentPartnerId = currentPartner._id;
if (typeof ImageSub !== 'undefined') {
if ( ImageSub != null ) {
ImageSub.stop();
};
};
ImageSub = Meteor.subscribe('partnerImages', currentPartnerId);
});
The problem is that the helpers on the partnerGridForm update each form that is rendered so each form wil always get the last image?
Can anyone help?

How to attach function to second element of ng-repeat

I just started learning angular
and I have a question related to ng-repeater
I have an ng-repeater like:
<div ng-if="!promo.promotion.useHTML"
class="{{promo.promotion.monetateId}}"
ng-repeat="promo in promotions track by promo.promotion.slotId">
<div ng-if="!promo.useHTML">
<img ng-src="{{promo.promotion.imageURL}}"
ng-click="promoOnClick(promo.promotion.promotionURL)"/>
</div>
I want to have a different ng-click function on second element of repeater.
How would I get this?
You can try using $index variable which contains the repeater array offset index. You can use it to build conditional statements in your repeater.
<div ng-if="!promo.promotion.useHTML" class="{{promo.promotion.monetateId}}" ng-repeat="promo in promotions track by promo.promotion.slotId">
<div ng-if="!promo.useHTML && $index !== 2">
<img ng-src="{{promo.promotion.imageURL}}" ng-click="promoOnClick(promo.promotion.promotionURL)"/>
</div>
<div ng-if="!promo.useHTML && $index == 2">
<img ng-src="{{promo.promotion.imageURL}}" ng-click="promoOnClickAnotherFunction(promo.promotion.promotionURL)"/>
</div>
</div>
Or as suggested by comment below
<div ng-if="!promo.promotion.useHTML" class="{{promo.promotion.monetateId}}" ng-repeat="promo in promotions track by promo.promotion.slotId">
<div ng-if="!promo.useHTML>
<img ng-src="{{promo.promotion.imageURL}}" ng-click="($index==1) ? function1() : function2()"/>
</div>
</div>
or as a third option, you can pass the index into the callback function as a second parameter and move your logic into your controller.
and in your callback, check the index and ... based on the index call your
function1, or function 2. Its a good idea to move logic away from your templates/views.
even better option would be creating/setting your promote() function function in your controller. E.g when you load your promotions array/objects, loop over them and set their promote function based on ... whatever your requirements are.
var i;
var yourPromoteFunctionForSecondElement = function( promo_object ) {
/*do your promo specific stuff here*/
}
for (i = 0; i < promotions.length; i++) {
var promotion = promotions[i];
if ( i == 1 ) {promotion.promote = yourPromoteFunctionForSecondElement(promotion);}
else if () {promotion.promote = someOtherPromoteFunction(promotion);}
else {/*etc*/}
}

Backbone Changing Class Name during {{each items}}

I want to change the classname during an each loop;
so that it will look like this;
<div class="active">
// do somthing
</div>
<div class="static">
//do
</div>
my code looks like this
{{#each pages}}
<div class="active">
//do
</div>
{{/each}}
there is no identifier of the class. So the 1st one will be active, the rest of the items will be static.
You could add your own helper if you want to do this inside the template. Something like this:
Handlebars.registerHelper('each_with_class', function(ary, first, rest, options) {
if(!ary || ary.length == 0)
return options.inverse(this);
var result = [ ];
var context = null;
var cls = { cls: first };
for(var i = 0; i < ary.length; ++i) {
context = _({}).extend(ary[i], cls);
result.push(options.fn(context));
cls.cls = rest;
}
return result.join('');
});
Then your template could say things like this:
{{#each_with_class pages "active" "static"}}
<div class="{{cls}}">
Same stuff you're doing now.
</div>
{{/each_with_class}}
If you don't mind ary[i] getting modified along the way then you can assign directly to ary[i].cls instead of using _.extend to make a copy.
Demo: http://jsfiddle.net/ambiguous/ZMSQh/1/
as far as I know there are no indices provided by {{#each}} that could be checked..
a workaround would be to store this classes in your pages items and then use something like
{{#each pages}}
<div class="{{this.classname}}">
//do
</div>
{{/each}}

handlebars - is it possible to access parent context in a partial?

I've got a handlebar template that loads a partial for a sub-element.
I would need to access a variable from the parent context in the calling template, from within the partial. .. doesn't seem to resolve to anything inside the partial.
Simplified code goes like this:
the template
{{#each items}}
{{> item-template}}
{{/each}}
the partial
value is {{value}}
(obviously the real code is more complicated but it's the same principle, within the partial .. appears to be undefined.)
To show it's undefined, I've used a very simple helper whatis like this:
Handlebars.registerHelper('whatis', function(param) {
console.log(param);
});
and updated the above code to this:
updated template
{{#each items}}
{{whatis ..}} <-- Console shows the correct parent context
{{> item-template}}
{{/each}}
updated partial
{{whatis ..}} <-- Console shows "undefined"
value is {{value}}
Is there a way to go around that issue? Am I missing something?
EDIT: There's an open issue relating to this question on handlebars' github project
Just in case anyone stumbles across this question. This functionality exists now in Handlebars.
Do this:
{{#each items}}
{{! Will pass the current item in items to your partial }}
{{> item-template this}}
{{/each}}
Working fiddle (inspired by handlebars pull request #385 by AndrewHenderson)
http://jsfiddle.net/QV9em/4/
Handlebars.registerHelper('include', function(options) {
var context = {},
mergeContext = function(obj) {
for(var k in obj)context[k]=obj[k];
};
mergeContext(this);
mergeContext(options.hash);
return options.fn(context);
});
Here's how you'd setup the parent template:
{{#each items}}
{{#include parent=..}}
{{> item-template}}
{{/include}}
{{/each}}
And the partial:
value is {{parent}}
As of 2.0.0 partials now supports passing in values.
{{#each items}}
{{> item-template some_parent_var=../some_parent_var}}
{{/each}}
Took me awhile to find this, hope it's useful for someone else too!
The easiest way to pass the parent context to the partial is to do the loop inside the partial. This way the parent context is passed by default and when you do the loop inside the partial the {{../variable}} convention can access the parent context.
example fiddle here.
The Data
{
color: "#000"
items: [
{ title: "title one" },
{ title: "title two" },
]
}
The Template
<div class="mainTemplate">
Parent Color: {{color}}
{{> partial}}
</div>
The Partial
<div>
{{#each items}}
<div style="color:{{../color}}">
{{title}}
</div>
{{/each}}
</div>
You can use some of the proposed solutions on the comments from the link to github:
https://github.com/wycats/handlebars.js/issues/182#issuecomment-4206666
https://github.com/wycats/handlebars.js/issues/182#issuecomment-4445747
They create helpers to pass the info to the partial.
I created an each Helper function that includes the parent key/values within the subcontext under the key parentContext.
http://jsfiddle.net/AndrewHenderson/kQZpu/1/
Note: Underscore is a dependency.
Handlebars.registerHelper('eachIncludeParent', function ( context, options ) {
var fn = options.fn,
inverse = options.inverse,
ret = "",
_context = [];
$.each(context, function (index, object) {
var _object = $.extend({}, object);
_context.push(_object);
});
if ( _context && _context.length > 0 ) {
for ( var i = 0, j = _context.length; i < j; i++ ) {
_context[i]["parentContext"] = options.hash.parent;
ret = ret + fn(_context[i]);
}
} else {
ret = inverse(this);
}
return ret;
});
To be used as follows:
{{#eachIncludeParent context parent=this}}
{{> yourPartial}}
{{/eachIncludeParent}}
Access parent context values in your partial using {{parentContext.value}}
I needed dynamic form attributes for something like this...
{{#each model.questions}}
<h3>{{text}}</h3>
{{#each answers}}
{{formbuilder ../type id ../id text}}
{{/each}}
{{/each}}
and a helper like so...
Handlebars.registerHelper('formbuilder', function(type, id, qnum, text, options)
{
var q_type = options.contexts[0][type],
a_id = options.contexts[1].id,
q_number = options.contexts[0][qnum],
a_text = options.contexts[1].text;
return new Handlebars.SafeString(
'<input type=' + q_type + ' id=' + a_id + ' name=' + q_number + '>' + a_text + '</input><br/>'
);
});
Which produces...
<input type="checkbox" id="1" name="surveyQ0">First question</input>
My model is a big blob of arrays and objects mixed together. What's noteworthy is that using '../' like so '../type', passes in the parent model as the context, and without it, such as with 'id', it passes in the current model as the context.
To get specifically the parent of the partial (where you may be several partials deep) then follow the other answers like SeanWM.
If you know that the parent is the main template then you can use #root which resolves to the top-most context no matter how deep you are.
e.g. {{#root.rootObject.rootProperty}}
It is a pity that ../../.. does not go up past a partial.

Categories

Resources