I tried to detect which template includes another, in order to have different css classes for specific template inclusions. I already asked this question here.
The suggested solution is like this:
app.html:
<body>
{{> parentTemplate parentContext}}
</body>
<template name="parentTemplate">
{{> childTemplate specialContext}}
{{> childTemplate}}
</template>
<template name="childTemplate">
<div class="{{isSpecialClass}}">
<p>parent name: {{name}}</p>
</div>
</template>
app.js
if (Meteor.isClient) {
Template.body.helpers({
// add some context to the parent do demo how it can be modified
parentContext: {name: 'dave'}
});
Template.parentTemplate.helpers({
specialContext: function () {
// make a copy of the parent data context
var data = _.clone(Template.instance().data || {});
// modify the context to indicate the child is special
data.isSpecial = true;
return data;
}
});
Template.childTemplate.helpers({
isSpecialClass: function () {
// grab the context for this child (note it can be undefined)
var data = Template.instance().data;
if (data && data.isSpecial)
// add the 'awesome' class if this child is special
return 'awesome';
}
});
}
Now the problem is that my childTemplate has the context of parentTemplate. I checked the data of parentTemplate and it has the field isSpecial, it just has the wrong context. Any idea why this happens? For instance, if I use {{title}} in my childTemplate I will get the title of the parent context object, but I want the context of the childTemplate.
I misunderstood the original question. My answer was overly complex because I thought the parent context had to be preserved. It's actually a bit easier if you just need to modify the child context. Here's a working example:
app.html
<body>
{{> parentTemplate}}
</body>
<template name="parentTemplate">
{{#each children}}
{{> childTemplate}}
{{/each}}
</template>
<template name="childTemplate">
<div class="{{isSpecialClass}}">
<p>name: {{name}}</p>
</div>
</template>
app.js
if (Meteor.isClient) {
Children = new Mongo.Collection(null);
Meteor.startup(function () {
Children.insert({name: 'joe'});
Children.insert({name: 'bob'});
Children.insert({name: 'sam'});
});
Template.parentTemplate.helpers({
children: function () {
// find all of the children and modify the context as needed
return Children.find().map(function(child, index) {
// modify the child context based on some aspect of the child or index
if ((index == 0) || (child.name == 'bob'))
child.isSpecial = true;
return child;
});
}
});
Template.childTemplate.helpers({
isSpecialClass: function () {
// add the 'awesome' class if this child is special
if (this.isSpecial)
return 'awesome';
}
});
}
In this version, the parent finds all of the children and modifies each by adding isSpecial to the child context only if the child is either first in the list or if the child has the name 'bob'. Now, the child only needs to check this.isSpecial in its class helper. Please let me know if you have any questions.
Related
A plugin I use creates dynamic html and I want to add a dynamic background-color using a hex passed via props.
This is the html in my component
<template>
<div class="pagination" slot="pagination"></div>
</template>
Generates dynamic HTML of this
<div class="pagination" slot="pagination">
<span class="swiper-pagination-bullet"></span>
<span class="swiper-pagination-bullet"></span>
</div>
The components receives props
props: ['primaryBgColor']
I can obviously see the color in the template if I write
<template>
<div>
{{ this.primaryBgColor }}
</div>
</template>
However when I write a style within the component like
<style>
.swiper-pagination-bullet {
background-color: {{ this.primaryBgColor }}
}
</style>
Webpack returns an error saying I have a CSS syntax error. Any ideas?
suresh's answer may not work as it listens to the Vue mounted event, but by the time Vue component mounted, the plugin element may not yet be been injected.
if the plugin provides a similar event, you can register the handler there. if not, you can listen to the outer dom element using MutationObserver instead.
<template>
<div id="outer">
</div>
</template>
<script>
const observer = new MutationObserver(mutations => {
// suresh's function here
});
observer.observe(document.getElementById('outer'), { childList: true });
</script>
In your template, you can directly inject style
<template>
<div :style="this.primaryBgColor">
{{ this.primaryBgColor }}
</div>
</template>
primaryBgColor should contain object like {'background':"#cccc"}
For more option, vuejs had superb documentation. you can refer https://v2.vuejs.org/v2/guide/class-and-style.html#Object-Syntax-1
We can query the element and apply style like as follows
mounted: function () {
var elems = document.querySelectorAll('.swiper-pagination-bullet ')
var index = 0
var length = elems.length
for (; index < length; index++) {
elems[index].style.backgroundColor = this.primaryBgColor
}
},
I am pretty new to polymer, I wanted to perform below scenario...
I have a bind variable {{readonly}} through which I send data from parent Dom-HTML to Child Dom-HTML, in Polymer
The code snippet is roughly like this..
calling child DOM from parent,( I already initialized readonly in parent with "true")
<wms-griddata order-obj='{{item}}' readonly='{{readonly}}'></wms-griddata>
In child Element
<dom-module id="wms-griddata">
.....
<template is="dom-if" if="{{_isreadonly()}}">
<callsome1></callsome1>
</template>
<template is="dom-if" if="{{!_isreadonly()}}">
<callsome2></callsome2>
</template>
<script>
Polymer({
is : 'wms-griddata',
properties: {
readonly: {
type: String
}
.......
.......
_isreadonly: function(){
if(this.readonly == 'true')
return true;
else
return false;
}
I find that in Child Element first the created is fired, then the HTML is painted, then all the properties of the local DOM is getting value from parent.
But I want that the dom-if is validated only after I get the value from the parent {{readonly}}, so that I can call the correct sub-child-dom [callsome1, or callsome2]
Can anyone please provide me an idea/trick of how can I achieve the requisite?
Thanks in advance
Instead to call this.readOnly I think that you should pass that variable as parameter to _isReadOnly function as follows:
_isreadonly: function(readOnly){
return readOnly == 'true';
}
So, your html will look:
<template is="dom-if" if="{{_isreadonly(readOnly)}}">
<callsome1></callsome1>
</template>
<template is="dom-if" if="{{!_isreadonly(readonly)}}">
<callsome2></callsome2>
</template>
In this case, everytime readonly change, _isreadonly function will be invoked and the DOM will be updated.
I an trying a simple test about observeNodes Polymer facility. Essentially my code defines an observer for child node changes on the component.
<dom-module id="wc-A">
<template>
<div>Added Nodes : <span id="added"></span></div>
<div>Removed Nodes : <span id="removed"></span></div>
</template>
<script>
Polymer ({
is: 'wc-A',
ready: function () {
Polymer
.dom (this)
.observeNodes (function (nodes) {
console.log (nodes)
this.$.added.textContent = nodes.addedNodes.length;
this.$.removed.textContent = nodes.removedNodes.length;
});
}
});
</script>
</dom-module>
This example works properly on creation time (from my test span#added contains 5 and span#removed contains 0), but when I programmatically add/remove elements on the light DOM, the observation mechanism does not respond (span's do not change). This is my test:
<div>
<button id="btnAdd">New</button>
<button id="btnRemove">Remove</button>
</div>
<wc-A> <!-- (1) Fires observer -->
<div class="data">1</div>
<div class="data">2</div>
</wc-A>
<template id=template>
<div class="data">3</div>
</template>
<script>
HTMLImports.whenReady (function () {
document
.querySelector ('#btnAdd')
.addEventListener ('click', function (e) {
var template = document.querySelector ('#template').content;
var div = template.querySelector ('div');
var wcA = document.querySelector ('wc-A')
wcA.appendChild (div.cloneNode (true)); // (2) Does not fire observer
});
document
.querySelector ('#btnRemove')
.addEventListener ('click', function (e) {
var wcA = document.querySelector ('wc-A')
var child = wcA.querySelector ('.data');
if (child)
wcA.removeChild ( // (3) Does not fire observer
child
);
});
});
</script>
The complete code can be checked http://plnkr.co/edit/DHiH40T3pBLx9Nu6Tv3W?p=preview
What is my error? Thanks in advance.
You need to use Polymer.dom(this).appendChild instead of this.appendChild to make it work with Polymer 1.0 according to this:
https://github.com/Polymer/polymer/issues/3102
I have the following template code
<template name="home">
<div class="mainBox">
<ul class="itemList">
{{#each this}}
{{> listItem}}
{{/each}}
</ul>
</div>
</template>
<template name="listItem">
<li class="item">
{{username}}
</li>
</template>
And I'd like to execute a code once ALL of the "listItem" are rendered. There are about 100 of them. I tried the following
Template.home.rendered = function() {
// is this called once all of its 'subviews' are rendered?
};
But it doesn't wait until all views are loaded.
What's the best way of knowing when all sub-view templates are loaded?
This is how I proceed :
client/views/home/home.html
<template name="home">
{{#if itemsReady}}
{{> itemsList}}
{{/if}}
</template>
<template name="itemsList">
<ul>
{{#each items}}
{{> item}}
{{/each}}
</ul>
</template>
<template name="item">
<li>{{value}}</li>
</template>
client/views/home/home.js
Template.home.helpers({
itemsReady:function(){
return Meteor.subscribe("items").ready();
}
});
Template.itemsList.helpers({
items:function(){
return Items.find();
}
});
Template.itemsList.rendered=function(){
// will output 100, once
console.log(this.$("li").length);
};
lib/collections/items.js
Items=new Mongo.Collection("items");
server/collections/items.js
insertItems=function(){
var range=_.range(100);
_.each(range,function(index){
Items.insert({value:"Item "+index});
});
};
Meteor.publish("items",function(){
return Items.find();
});
server/startup.js
Meteor.startup(function(){
Items.remove({});
if(Items.find().count()===0){
insertItems();
}
});
We specify that we want to render our list of items only when the publication is ready, so by that time data is available and the correct number of li elements will get displayed in the list rendered callback.
Now the same using iron:router waitOn feature :
client/views/home/controller.js
HomeController=RouteController.extend({
template:"home",
waitOn:function(){
return Meteor.subscribe("items");
}
});
client/lib/router.js
Router.configure({
loadingTemplate:"loading"
});
Router.onBeforeAction("loading");
Router.map(function(){
this.route("home",{
path:"/",
controller:"HomeController"
});
});
client/views/loading/loading.html
<template name="loading">
<p>LOADING...</p>
</template>
Using iron:router is probably better because it solves a common pattern elegantly : we don't need the itemsReady helper anymore, the home template will get rendered only when the WaitList returned by waitOn will be globally ready.
One must not forget to add both a loading template and setup the default "loading" hook otherwise it won't work.
I had this same problem with needing to wait on all my subtemplates to load before calling a slick JavaScript carousel plugin (or any cool JavaScript plugin like charts or graphs that need your whole data set loaded in the DOM before calling it).
I solved it by simply comparing the rank of the subtemplate to the overall count that should be returned for whatever query I was doing. Once the rank is equal to the count, you can call your plugin from the subtemplate.rendered helper because all the subtemplates have been inserted into the DOM. So in your example:
Template.listItem.rendered = function() {
if(this.data.rank === ListItems.find({/* whatever query */}).count()) {
console.log("Last item has been inserted into DOM!");
// Call your plugin
$("#carousel").owlCarousel({
// plugin options, etc.
});
}
}
Then you just need your helper for listItems to return a rank, which is easy enough:
Template.home.helpers({
listItems: function() {
return ListItems.find({/* whatever query */}).map(function(listItem, index) {
listItem.rank = index + 1; // Starts at 1 versus 0, just a preference
});
}
}
the method rendered works of this way
This callback is called once when an instance of Template.myTemplate is rendered into DOM nodes and put into the document for the first time.
so, when is rendered you doesn't have variable reactive in this case.
// this would sufficient
Template.listItem.helpers = function() {
username:function(){
return ...
}
};
I'd suggest something like:
var unrendered = [];
Template.listItem.created = function () {
var newId = Random.id();
this._id = newId;
unrendered.push(newId);
};
Template.listItem.rendered = function () {
unrendered = _.without(unrendered, this._id);
if (!unrendered.length) {
// WHATEVER NEEDS DOING WHEN THEY'VE ALL RENDERED
}
};
CAVEAT
This works on the assumption that essentially all template instances will be created before they first ones have been rendered, otherwise your code will run before it should. I think this should be the case, but you'll have to try it out as I don't really have time to run a 100+ sub-template test. If it's not the case, then I can't see how you can achieve this behavior without knowing in advance exactly how many sub-templates will be created.
If you do know how many there will be then the code above can be simplified to a counter that decrements every time rendered runs, and it's easy.
unrendered = [number of listitems];
Template.listItem.rendered = function () {
unrendered--;
if (!unrendered) {
// WHATEVER NEEDS DOING WHEN THEY'VE ALL RENDERED
}
};
Also, you may need to meteor add random, but I think this package is now included in core.
Apparently there are various ways to handle your situation. You could easily use template subscriptions.
Template.myView.onCreated(function() {
var self = this;
self.autorun(function(){
self.mySub = self.subscribe('mySubscription');
});
if(self.mySub.ready()) {
// my sweet fancy code...
}
});
<template name="myTemplate">
<ul>
{{#if Template.subscriptionsReady}}
{{#each items}}
<li>{{item}}</li>
{{/each}}
{{else}}
<div class="loading">Loading...</div>
{{/if}}
</ul>
</template>
It's list sorted by likes.
Meteor template and reactivity system automatically rerender html template whenever data defined by cursor(barvy.find({}, {sort:{likes: -1}})) changes. So now on screen list is always sorted by 'likes'. If first item has 50 likes, second item 50 likes and i add one like to second item, then it moves to first position on the screen, because cursor returns it like first item.
My question is: how i can show arrow up on items that moves up in ordered list and arrow down on items that moves down? Because creating of DOM element is handled by Meteor, i don't know how to get info about what elements changes their positions.
Template.poradi.barvy = function () {
return barvy.find({}, {sort:{likes: -1}});
};
Html template:
<body>
{{> poradi}}
</body>
<template name="poradi">
<h2>Poradi</h2>
<ul>
{{#each barvy}}
<li>{{barva}}, {{likes}} <input type="button" id="button_{{barva}}" value="like" /></li>
{{/each}}
</ul>
</template>
I would like to try something to store the old position with an unique id and compare it against the new position. You could write a extra function by extending your template e.g. (not tested code, but logic should work):
Template.HelloWorld.getArrow = function(uniqueId, currentPosition) {
if(typeof array[uniqueId] == 'undefined') { // If there is no old data
array[uniqueId] = currentPosition;
return "same.png";
}
oldPosition = array[uniqueId];
if(oldPosition < currentPosition) {
arrow = "up.png";
}
else if(oldPosition > currentPosition) {
arrow = "down.png";
}
else {
arrow = "same.png";
}
array[uniqueId] = currentPosition;
return arrow;
};
And thats how to call it in your template "HelloWorld":
<img src="{{getArrow "itemId" "positionNumber"}}">
Every time the data in your collection is changing, the template is redrawn and so the function getArrow would be recalled for every item.
If I understand the question correctly, you want to know how each click corresponds to a rendered item. There are a couple of ways to do this, but the easiest is just to render each list item in a separate template. Here is a complete working example:
likes.html
<body>
<h1>Items to like</h1>
{{> itemsList}}
</body>
<template name="itemsList">
<ul>
{{#each items}}
{{> item}}
{{/each}}
</ul>
</template>
<template name="item">
<li>
{{text}} ({{likes}})
<button class='up'>Up</button>
<button class='down'>Down</button>
</li>
</template>
likes.js
if (Meteor.isClient) {
Items = new Meteor.Collection(null);
Meteor.startup(function () {
Items.insert({text: 'apples', likes: 10});
Items.insert({text: 'grapes', likes: 8});
Items.insert({text: 'pears', likes: 6});
Items.insert({text: 'oranges', likes: 4});
});
Template.itemsList.items = function () {
return Items.find({}, {sort: {likes: -1}});
};
Template.item.events({
'click .up' : function () {
Items.update(this._id, {$inc: {likes: 1}});
return false;
},
'click .down' : function () {
Items.update(this._id, {$inc: {likes: -1}});
return false;
}
});
}
I've used a collection local to the client for easy initialization, and so it will reset every time you refresh the page. The key insight is that if you render each item in its own template, the click events refer only to that item, so you have access to this._id.