I have the following data structure:
A :
{
"B": [
{
"C":["Hello", "World"]
}
]
}
A has an attribute B which is an array. Every element of B has an attribute C which is again an array.
<template name="render">
{{#each B}}
{{#each C}}
<div class="clickme">{{this}}</div>
{{/each}}
{{/each}}
</template>
So now I have the event handler where I want to access the element of B in which 'this' (== element of C) was rendered in. But it seems impossible. How do I do it?
Template.render.events({
"click .clickme" : function (event, template) {
//template.data == A
//Template.parentData(0) == A
//Template.parentData(1) == A
console.log("what was my parent B?");
}
})
You can go multiple levels upwards by using ../
In your case, suppose you want index of parent element, then:
{{#../index}}
To access another key of your parent element (not applicable in your case): {{../key}} References:
Access properties of the parent with a Handlebars 'each' loop
Handlebars.js: How to access parent index in nested each?
Edit: To access the parent element from "click .clickme" event handler, I do not know of a direct way to access from event handler, but one possible way is to create a onclick event in html, and call a function, to which you can pass the parent element (or its index) as parameter. In the function, set is as a session variable, which can then be reffered to in the "click .clickme" event handler.
Template:
<template name="render">
{{#each B}}
{{#each C}}
<div class="clickme" onclick="onclickfunction('{{#../index}}')">{{this}}</div>
{{/each}}
{{/each}}
</template>
In client side javascript, define a function:
onclickfunction = function(parentindex) {
Session.set('indexofparent',parentindex);
}
In "click .clickme" event handler, get the session variable:
Template.render.events({
"click .clickme" : function (event, template) {
//template.data == A
//Template.parentData(0) == A
//Template.parentData(1) == A
var parentelement = Session.get('indexofparent');
console.log(parentelement);//Shows index of B.
}
})
P.S. Index of B is a string, use pareseInt(parentelement) to parse to integer.
Related
Is there any way of detecting when the content of a component changes?
For example:
HTML:
<component>
<div>{{name}}</div>
</component>
JS:
component = Ractive.extend({
on...: function () {
// invoked when the inner HTML changes
}
});
I use {{yield}} so the content is rendered in the context of the parent.
For now I'll have to pass name to the component just for the purpose of observing changes (even though I don't need the value in the context of the component). (Or I'll add a function that I can call).
<component changes="{{name}}">
<div>{{name}}</div>
</component>
Any ideas?
It's possible, but a bit hacky.
http://plnkr.co/edit/YoTZpyTTyCyXijEteGkg?p=preview
<comp>
{{name}} hi {{thing}}
</comp>
comp = Ractive.extend({
template: '<div>{{yield}}</div>',
oncomplete: function() {
var self = this;
self.partials.content.forEach(function(partial) {
if (partial.r) {
self.parent.observe(partial.r, function(newValue) {
console.log(partial.r + ' changed to ', newValue)
}, {init: false});
}
});
}
})
Yield/Content are really just special partials, so this will loop through the items in that partial and set up an observer for each keypath.
This demo only works with simple expressions like {{foo}}. If you have more complicated things inside the partial you'll have to inspect the rendered partial to figure out what keypath you want to observe the parent on.
I have parent template included with child template. I need to use parents ReactiveVar from child template. I can use Session method but for my requirement Session method doesn't works. How do I access ReactiveVar value from parent templates?
HTML:
<template name="ParentTemplate">
{{> ChildTemplate}}
</template>
<template name="ChildTemplate">
//Some HTML content
</template>
JS
Template.ParentTemplate.onCreated(function () {
this.myvalue = new ReactiveVar(5); //I tried this.data.myvalue but doesnt works
});
Template.ChildTemplate.helpers({
'myhelper' : function(){
return Template.parentData(1).myvalue.get();
}
});
Here's an example were the child is a direct descendant of the parent:
<template name="parent">
{{> child}}
</template>
<template name="child">
<p>{{parentValue}}</p>
</template>
In this case we can access the parent's instance variable like this:
Template.child.helpers({
parentValue: function() {
var parentView = Blaze.currentView.parentView.parentView;
var parentInstance = parentView.templateInstance();
// replace parentVariable with the name of the instance variable
return parentInstance.parentVariable.get();
}
});
If the two templates have a more complex relationship in the DOM, you can use something like this:
// replace .parent-class will the selector for your parent template
el = $('.parent-class')[0]
var parentInstance = Blaze.getView(el).templateInstance();
// replace parentVariable with the name of the instance variable
return templateInstance.parentVariable.get();
Another possible solution could be to pass the data to the child explicitly.
// js
if (Meteor.isClient) {
Template.parent.onCreated(function () {
this.reactiveV = new ReactiveVar(42);
});
Template.parent.helpers({
getReactiveVar: function() {
return Template.instance().reactiveV;
},
});
Template.parent.events({
'click button': function(e, tmp) {
tmp.reactiveV.set(tmp.reactiveV.get() + 2);
},
});
}
and in the template file:
<template name="parent">
<p>this is the parent!</p>
<button> var++ </button>
{{> child parentData=getReactiveVar}}
</template>
<template name="child">
<h3>
child template
</h3>
{{parentData.get}}
</template>
as you press the button you will see the child template update. If you needed to, you could also assign the parent data in some other way in the Template.child.onCreated function.
This might provide loser coupling between the two templates.
I'm trying to get the higher height of some the "big-card" in my DOM to put them all at the same height.
{{#each skills}}
<div class="big-card">
<div class="card-grid add-option-part">
<div class="card-text">
<p>{{this}}</p>
</div>
</div>
<div class="option-part">
<div class="half-option-part white-line-part"><img class="seemore-button" src="/img/expand.png"/></div>
<div class="half-option-part">{{> StarsRating}}</div>
</div>
</div>
{{/each}}
The function to take get their heights is :
function boxContentNormal(){
var elementHeights = [];
$('.big-card').map(function() {
var currentItem = $(this).find('.card-text');
var currentItemHeight = currentItem.height();
var currentItemPaddingTop = parseInt(currentItem.css('padding-top').replace("px", ""));
var currentItemPaddingBottom = parseInt(currentItem.css('padding-bottom').replace("px", ""));
elementHeights.push(currentItemHeight + currentItemPaddingBottom + currentItemPaddingTop);
});
var maxHeight = 0;
$.each(elementHeights, function(i, element){
maxHeight = (element > maxHeight) ? element : maxHeight;
});
console.log("Max height : "+maxHeight);
}
It's called by that :
Template.MyTemplate.onRendered(function(){
boxContentNormal();
$(window).resize(function(){
boxContentNormal();
});
});
This function is used when a new route is called and the template will be displayed at the same time.
It works like that:
I click on a link that goes to a new route
Once arrived to the route, the template will be displayed
When the template is rendered, the function is called for the first time
After that, if the window resizes the function will be called again
The problem is at the third step, when the function is called it doesn't get the height of the cards. Then all the heights are equal to 0. And when I resize the window, it works fine.
So I think the function is called too early and the "cards" don't exist yet. Do you know how I can "wait" for them or another solution ?
Thanks :)
I suppose your skills helper is returning a cursor from a client side collection synced with the server via the Pub/Sub mechanism.
You can use the template controller pattern along with template subscriptions to make sure your template is initially rendered after the published data made its way to the client.
HTML
<template name="skillsController">
{{#if Template.subscriptionsReady}}
{{> skillsList items=skills}}
{{/if}}
</template>
<template name="skillsList">
{{#each items}}
{{! skill item}}
{{/each}}
</template>
JS
Template.skillsController.onCreated(function(){
this.subscribe("skills");
});
Template.skillsController.helpers({
skills: function(){
return Skills.find();
}
});
Template.skillsList.onRendered(function(){
console.log(this.$(".big-card").length == this.data.items.count());
});
Using this pattern, the skillsList template onRendered life cycle event is executed after the data is already there so the {{#each}} block helper will correctly render its initial list of skill items.
If you don't wait for the subscription to be ready, the initial template rendering will run using an {{#each}} fed with an empty cursor. Once the data arrives, the {{#each}} will rerun and correctly render the items, but the onRendered hook won't.
I ran into this issue yesterday and was wondering if anyone had experienced anything similar and/or had an explanation for why this is happening.
Essentially, I have an ngRepeat block where I had been using track by $index (this was necessary for other reasons outside the scope of this issue). Each item in the list fired a method on click that would apply a class to itself (some CSS for exit effect) and then update it's status to be removed from the list.
Adding the class involved using a selector to target the item by an id associated with the argument to the ngClick method - each item would pass its own id. The ngRepeat collection is generated by a method that filters out any collection members with a particular property, which would also be added in the ngClick method.
The issue is that the class is being applied to two elements - the ngClick'ed element as well as the next element in the collection. Only the ngClick'ed element has the property added and is thus removed from the ngRepeat.
Additionally, console.loging the selection shows some interesting results. Notice the selector versus the 0th element in the result set:
This is a simplified example of the controller logic:
$scope.list = [
{ name : "Joe", id : 1},
{ name : "Clark", id : 2},
{ name : "Matt", id : 3},
{ name : "Jimmy", id : 4},
{ name : "Bob", id : 5}
];
$scope.getItems = function() {
return _.reject($scope.list, 'clicked');
};
$scope.selectItem = function(id) {
angular.element('#item-' + id).addClass('selected');
_.each($scope.list, function(item) { if(item.id === id) { item.clicked = true; } });
};
And this is the ngRepeat in the view:
<div ng-repeat="item in getItems() track by $index">
<h5 ng-bind="item.name" id="item-{{item.id}}" ng-click="selectItem(item.id)"></h5>
</div>
Fiddle here: http://jsfiddle.net/h8bLm8pL/3/
To resolve the issue, I tracked by the id property of each collection member instead of the Angular internal $index, like item in getItems() track by item.id. Still, I'm unclear how this could be happening.
I think it is happening because you are creating the id of the element in the loop but before that when a click event is processed it is targeting the previous element. Try this simplified solution.
<div ng-repeat="item in getItems()">
<h5 ng-bind="item.name" ng-click="item.clicked=true" ng-class="{selected: item.clicked}"></h5>
</div>
Demo http://plnkr.co/edit/VD9lXBtr4TyeviWau8l7?p=preview
I'm using a script that creates an HTML structure like this by adding objects with JS and jQuery using createElement and appendChild:
<div id="divLayer">
<div id="divBox">
<!-- stuff -->
<div id="btnSave" class="button small">Save</div>
</div>
</div>
Elements positions are respectively absolute, absolute and relative and displays are block, block and inline-block.
I have defined an array that contains objects attributes like this:
var objects = {
divLayer: {
tag: 'DIV',
id: 'divLayer',
parent: 'body'
},
divBox: {
tag: 'DIV',
id: 'divBox',
parent: '#divLayer'
},
btnSave: {
tag: 'DIV',
HTML: 'Save',
id: 'btnSave',
parent: '#divBox',
events: {
click: function () {
alert ( 'Clicked' );
}
}
}
}
This is the part where I create objects and add listeners to them:
for ( var config in objects ) {
var object = document.createElement ( config.tag );
...Setup other attributes...
for ( var event in config.events ) object.addEventListener ( event, config.events [ event ] );
$ ( config.parent ).get ( 0 ).appendChild ( object );
}
Graphically everything's correct. I can see divLayer on the background, divBox over it and btnSave, inside the box and every attribute is correctly compiled but, when I click the button nothing happens.
If I put the button directly on divLayer it works.
Where am I doing wrong?
Thx in advance!
Aside from using an Object where an Array would be safer, you're trying to access a property on config, but config is just a string representing the property name.
You need to use config to get the value.
for (var config in objects) {
var obj = objects[config]; // get the object
var object = document.createElement(obj.tag);
object.id = obj.id; // set the ID
for (var event in obj.events)
object.addEventListener(event, obj.events[event]);
$(obj.parent).get(0).appendChild(object);
}
Also, you're leaving out some code, so I can't tell if the other properties are being set correctly. I added the ID, and now your code works.
DEMO: http://jsfiddle.net/yBN6V/
Try binding using jQuery itself, delegating the event to a static parent:
for (var event in config.events)
$(document).on(event, object, config.events[event]);
I've bound the event to document in that example, you should really use the closest non-dynamic element which is available on page load.