I have some troubles wihth gridster & meteor. At first, I loaded the whole widgets into my template and then recalculate the grid with a method below.
I have a template named dashboard, in this template I do a loop through my widgets and call a second template called widgetTmpl that contains all the formatted html
<template name="dashboard">
<div id="dashboardBody">
<button id="configMode" class="btn btn-primary"> <i class="fa fa-pencil"></i>Configuration </button>
<div class="gridster">
<ul id="widgetItemList" class="widget_item">
{{#each activeWidgets}}
{{> widgetTmpl}}
{{/each}}
</ul>
</div>
</div>
</template>
I execute this code with the callback onRendered
Template.dashboard.onRendered(function(){
var gridsterUl = jQuery(".gridster ul");
gridsterUl.gridster({
widget_margins: [5, 5],
widget_base_dimensions: [25, 25],
resize : {
enabled : true
},
draggable: {
start: overlay_fix_start,
stop: overlay_fix_stop
},
serialize_params : function($w, wgd){
return {
id : $w.prop('id'),
col : wgd.col,
row : wgd.row,
size_x : wgd.size_x,
size_y : wgd.size_y
};
}
});
});
This works fine, but when I add or reload a widget, I have to refresh the page due to the onRedered callback.
I heard about a gridster.add_widget methods, that does the perfect job but I don't know how to implement it to my code.
Where should I use the gridster.add_widget ?
There is methods like Blaze.insert and Blaze.renderWithData but I have no idea how to use it
I've never tried using Gridster, however I have integrated d3 into quite a few of my meteor apps. When I want the d3 context to reactivity change without a page refresh, I use a Tracker.autorun in my onRendered callback with a reactive datasource. For example my simplified code would look like this:
Template.d3Plot.onRendered( function () {
Tracker.autorun(function() {
// Function That Draws and ReDraws due to Tracker and Reactive Data
drawPlot(Plots.find());
});
});
drawPlot = function (plotItems) {
.....
}
Where Plots is my mongo collection so that whenever a new item is inserted/updated in the collection, the drawPlot function re-fires.
Related
I'm trying to understand options for using a stateful / non-reactive DOM component in a Meteor template, in a way that allows the component to retain its state as Meteor updates the DOM.
One specific example involves Leaflet.js: I have an application that includes a Leaftlet map, and I want the user to be able to switch between a display of the map, and some other content. The map is interactive --- the user can pan and zoom in the map --- and I'd like the current zoom/pan state of the map to be retained if/when the user switches away from the map to other content, and then back to the map.
My first attempt at doing this is to put the map in one template, and the other content in another template, and use conditional logic in the containing template to determine which template is rendered:
HTML:
<body>
<div>
<input type="submit" id="mapbutton" value="Display Map">
<input type="submit" id="otherbutton" value="Display Other Stuff">
</div>
{{#if showmap}}
{{> map}}
{{else}}
{{> otherstuff}}
{{/if}}
</body>
<template name="map">
<div id="map"></div>
</template>
<template name="otherstuff">
<p>Here is some other stuff</p>
</template>
JS:
Template.map.rendered = function() {
var map = L.map('map', {
doubleClickZoom: false
}).setView([38.0, -98.0], 5);
L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', {
maxZoom: 18,
id: 'examples.map-i875mjb7'
}).addTo(map);
};
Session.setDefault("showmap", true);
Template.body.helpers({
"showmap" : function() {
return Session.get("showmap");
}
});
Template.body.events({
"click input#mapbutton": function() {
Session.set("showmap", true);
},
"click input#otherbutton": function() {
Session.set("showmap", false);
}
});
The problem with this approach is that every time the user switches to the map display, Meteor re-renders the map template, creating a new Leaflet map (and associated DOM component), which is initialized from scratch. This means that whatever pan and/or zoom settings the user had previously made in the map are lost. It also involves a short delay in the display while the Leaflet map is constructed. I'd like the Leaflet map to get created one time only, the first time it is displayed, and then saved somewhere off-screen when the user swiches to other content, so that it can be immediately swapped back in later, without incurring the construction delay, and retaining its previous pan/zoom state.
I know that one way to accomplish this would be to design my HTML templates to keep the map div in the DOM when switching displays,
and to use CSS to hide it when necessary. Something like the following:
HTML:
<body>
<div>
<input type="submit" id="mapbutton" value="Map">
<input type="submit" id="otherbutton" value="Other Stuff">
</div>
<div id="map" class="{{#if showmap}}visible{{else}}hidden{{/if}}"></div>
{{#if showother}}
{{> otherstuff}}
{{/if}}
</body>
<template name="otherstuff">
<p>Here is some other stuff</p>
</template>
JS:
Template.body.rendered = function() {
var map = L.map('map', {
doubleClickZoom: false
}).setView([38.0, -98.0], 5);
L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', {
maxZoom: 18,
id: 'examples.map-i875mjb7'
}).addTo(map);
};
Session.setDefault("showmap", true);
Template.body.helpers({
"showmap" : function() {
return Session.get("showmap");
},
"showother" : function() {
return !Session.get("showmap");
}
});
Template.body.events({
"click input#mapbutton": function() {
Session.set("showmap", true);
},
"click input#otherbutton": function() {
Session.set("showmap", false);
}
});
CSS:
#map.visible {
display: block;
}
#map.hidden {
display: none;
}
This works fine for this simple example, but in reality my application (and the associated templates and resulting DOM) is much more complex.
What I REALLY want is to be able to move the map component around arbitrarily in the DOM. For example, depending on the context, the
map might appear inside a table, or full-screen, or not at all, and I'd like to retain the map's internal state between all of these contexts. Using a Meteor template for the map with conditional logic that determines where it is included seems like a natural way to structure this kind of thing, but that returns to the above problem that every time the map template is rendered, the map is rebuilt from
scratch and reset to its initial state.
Is there a way to tell Meteor to "cache" its rendering of a particular template, and to hang on to the associated DOM element, so that subsequent times when that template is used in the rendering of other content, the previously constructed DOM element is used? I realize this goes against the grain of the reactive approach, but this is a situation where I'm trying to use a complex non-reactive component, and it seems like support for such things could be useful in many contexts.
This issue isn't specific to Leaftlet.js, by the way. I have other non-reactive, stateful components that I would like to use in my Meteor application, and I'd love to find a graceful way to solve this problem for all of them.
Does anyone know if there is a way to do this, or have ideas for a better approach?
Thanks!
I don't think you can keep a rendered item ready for hiding/displaying without any re-rendering, except if you use CSS.
Blaze (the component taking care of rendering templates) can't do that (yet). Have a look at this topic where they basically say the same, but it comes from a meteor dev: https://github.com/meteor/meteor/issues/4351
Either you rely on CSS, either you keep the values you need in for example a reactive dictionary and use them when you render your map template.
Thanks #Billybobbonnet. Your comment to keep the values you need and re-use them when rendering the template gave me the idea to try this:
HTML:
<body>
<div>
<input type="submit" id="mapbutton" value="Map">
<input type="submit" id="otherbutton" value="Other Stuff">
</div>
{{#if showmap}}
{{> map}}
{{else}}
{{> otherstuff}}
{{/if}}
</body>
<template name="map">
<div id="mapcontainer">
<div id="map"></div>
</div>
</template>
<template name="otherstuff">
<p>Here is some other stuff</p>
</template>
JS:
var $mapdiv = undefined;
Template.map.rendered = function() {
if ($mapdiv === undefined) {
// if this is the first time the map has been rendered, create it
var map = L.map('map', {
doubleClickZoom: false
}).setView([38.0, -98.0], 5);
L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', {
maxZoom: 18,
id: 'examples.map-i875mjb7'
}).addTo(map);
// and hang on to the map's div element for re-use later
$mapdiv = $("#map");
} else {
// map has already been created, so just empty out the container
// and re-insert it
$("#mapcontainer").empty();
$("#mapcontainer").append($mapdiv);
}
};
Session.setDefault("showmap", true);
Template.body.helpers({
"showmap" : function() {
return Session.get("showmap");
}
});
Template.body.events({
"click input#mapbutton": function() {
Session.set("showmap", true);
},
"click input#otherbutton": function() {
Session.set("showmap", false);
}
});
This seems to be working well. It feels a little kludgy, but I like the fact that it lets me put the map in a template which I can use anywhere, just like any other template, and yet the map is only created once.
Is it possible for a parent Meteor template to access a subtemplate directly? Ideally, I'd like to use templates a widgets with an API. I was hoping for something like this:
mypage.html
<template name="myPage">
<div class="widgetContainer"></div>
<button>submit</button>
</template>
mypage.js
Template.myPage.rendered = function(){
this.myWidgetInstance = UI.render(Template.myWidget)
UI.insert(this.myWidgetInstance, $('.widgetContainer')[0]);
}
Template.myPage.events({
'click button': function(e, template){
// I don't want this logic to live inside of mywidget.js.
// I also don't want this template to know about the <input> field directly.
var val = template.data.myWidgetInstance.getMyValue();
}
});
mywidget.html
<template name="myWidget">
<input></input>
</template>
mywidget.js
Template.myWidget.getValue = function(){
return this.$('input').val();
}
The above doesn't work because myWidgetInstance.getMyValue() doesn't exist. There doesn't appear to be a way for external code to access template helper functions on an instance.
Is anyone using Meteor templates in the way I'm trying to use them above? Or is this better suited for a separate jQuery widget? If so, it'd be a shame because I still want my widget template to benefit from the features Meteor provides.
It is possible to access subtemplate helper function.
Your example will work once you apply few fixes:
fix 1 : getValue() instead of getMyValue()
Template.myPage.events({
'click button': function(e, template){
// I don't want this logic to live inside of mywidget.js.
// I also don't want this template to know about the <input> field directly.
var val = template.myWidgetInstance.getValue();
console.log(val);
}
});
fix 2 : $('input').val(); instead this.$('input').val();
Template.myWidget.getValue = function(){
return $('input').val();
}
fix 3 : <input> should have no close tag.
<template name="myWidget">
<input type="text" value="sample value">
</template>
I want to know how to bind/set template-passed-parameter-value to click event of an item in the template in Meteor.
I'm using Meteor 0.7.0.1 with Blaze UI package. My main idea is to build a re-usable custom components in Meteor with Blaze template engine.
I have the following component which is working fine at the moment but I want this to be more customizable and remove some dependencies.
This is my component template named postLinks
<template name="postLinks">
<div id="link-popover-wrapper" >
<ul class="link-popover">
{{#each linkOptions}}
<li><a tabindex="-1" class="link-action" id="link-{{value}}" href="#">{{label}}</a>
</li>
{{/each}}
</ul>
</div>
</template>
This postLinks component is used in the myPostItem helper.
Template.myPostItem.events({
'click .post-item-link-picker': function (evt, tmpl) {
var tmpPostId = this._id;
var tempData = {linkOptions:[{label:'Favorite', value : 'favorite'},{label:'Wish list', value : 'wishlist'},{label:'Later', value : 'later'}, {label:"Read", value:"read"}]};
var linkContent = Template.postLinks(tempData);
$(".item-link-picker").popover({
content: linkContent, html: true, placement: 'bottom', trigger: "manual",
template: "UI_POPOVER_TEMPLATE"});
$("#item-link-picker-"+tmpPostId).popover('show');
},
'click .link-action': function (evt, tmpl) {
//.... some code here to update link selection in db
}
});
Above code is working fine and I want to improve it to have following
Pass item click event externally to be bind to link-action like
After above two changes it will look like :
Template.myPostItem.events({
'click .post-item-link-picker': function (evt, tmpl) {
var tmpPostId = this._id;
var tempData = { itemClick:function(){}, linkOptions:[{label:'Favorite', value : 'favorite'},...]};
var linkContent = Template.postLinks(tempData);
$(".item-link-picker").popover({
content: linkContent, html: true, placement: 'bottom', trigger: "manual",
template: "UI_POPOVER_TEMPLATE"});
$("#item-link-picker-"+tmpPostId).popover('show');
}
});
I lack knowledge how/where to bind that passed event handling function to link-action elements in template or helper. I really appreciate if anybody could help to find a way to do that.
You go the other way around and use jQuery event triggering system, so
Template.myPostItem.events({
'click .link-action': function (evt, tmpl) {
$(evn.target).trigger('post-link-action', this /* extra data */);
},
});
This event can be easily catched in any parent template:
<template name="someOtherTamplate">
{{> myPostItem}}
</template>
Template.someOtherTemplate.events({
'post-link-action': function (evt, tmpl, extra) {
// the user of your component can define their custom behavior here
},
});
Please note that the event extra parameter will only be supported in the next Meteor release. Currently (0.8.0) it is included in the devel branch.
How do I access one 'sibling' variable in a meteor template helper, when I am in the context of another? I want to determine whether the user that is logged in and viewing the page is the same user that posted the ride offering, so that I can hide or show the "bid" button accordingly.
For example, here is my template (html) file:
<!-- client/views/ride_offers.html -->
<template name="RideOfferPage">
<p>UserIsOwner:{{UserIsOwner}}</p>
{{#with CurrentRideOffer}}
{{> RideOffer}}
{{/with}}
</template>
<template name="RideOffer">
<div class="post">
<div class="post-content">
<p>Details, Author: {{author}}, From: {{origin}}, To: {{destination}}, between {{earliest}} and {{latest}} for {{nseats}} person(s). Asking ${{price}}.
<button type="button" class="btn btn-primary" >Bid</button><p>
<p>UserIsOwner:{{UserIsOwner}}</p>
</div>
</div>
</template>
And here is my JavaScript file:
Template.RideOfferPage.helpers({
CurrentRideOffer: function() {
return RideOffers.findOne(Session.get('CurrentOfferId'));
},
UserIsOwner: function() {
return RideOffers.find({_id: Session.get('CurrentOfferId'), userId: Meteor.userId()}).count() > 0;
}
});
In the "RideOffer" template, I am able access the variables author, origin, ..., price. But I am unable to access the boolean UserIsOwner. I am, however, able to access the boolean UserIsOwner in the "RideOfferPage" template.
Does anyone know how I can access the boolean UserIsOwner in the "RideOffer" template?
Cheers,
Put the userIsOwner function outside the helper as an anonymous function and then call it from both templates.
Template.RideOfferPage.helpers({
CurrentRideOffer: function() {
return RideOffers.findOne(Session.get('CurrentOfferId'));
},
UserIsOwner: checkUserIsOwner()
});
Template.RideOffer.helpers({
UserIsOwner: checkUserIsOwner()
});
checkUserIsOwner= function() {
return RideOffers.find({_id: Session.get('CurrentOfferId'), userId: Meteor.userId()}).count() > 0;
}
There are several ways to do what you're asking.
In your particular example you are not asking about siblings, but about parents, since the RideOfferPage template renders the RideOffer template. You can access variables in the parent data context (but not helpers) like so:
<template name="RideOffer">
<div class="post">
<div class="post-content">
<!--
other stuff
-->
<p>UserIsOwner:{{../UserIsOwner}}</p>
</div>
</div>
</template>
In other cases, you may have a template being rendered as a sibling of this one. In that case, you can't actually know what the sibling is until the template is actually on the page; however, you can find it in the rendered callback:
Template.foo.rendered = function() {
var current = this.firstNode;
var next = $(currentItem).next(); // or .prev()
if (next.length) {
nextData = Spark.getDataContext(next[0]);
}
// Do something with nextData
};
Finally, you can get the parent context of any rendered DOM element by repeatedly iterating through its parents. This isn't super efficient but I've used it in places where there is extensive drag and drop with DOMFragments moving around on the page:
Template.bar.events = {
"click .something": function(e) {
var target = e.target;
var context = Spark.getDataContext(target);
var parentContext = context;
while (parentContext === context) {
parentContext = Spark.getDataContext(target = target.parentNode);
}
// Do something with parentContext
}
};
I'm curious to know if there is a better way to do the last thing, which may potentially have to iterate through many DOM elements. In any case, you may want to check out my meteor-autocomplete package for this and other cool tricks.
If I have an {{# each}} binding in Meteor, and I want to update a property on only one instance of the template inside the #each. How would I do that? I've tried setting a value on the "template" object inside the events map, but that doesn't seem to be reactive. I've also tried binding to a Session property, but that will cause every instance to update instead of just the one I want...
for example:
{{#each dates}}
{{> dateTemplate}}
{{/each}}
<template name="dateTemplate">
{{date}}
<span style="color: red;">{{errorMsg}}</span> <--- how do i update errorMsg?
</template>
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = 'not valid'; <--- this doesn't do anything
}
});
EDIT TO ADDRESS ANSWER BELOW:
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid';} <--- this also doesn't do anything
}
});
You don't have to use handlebars for this, because its not something that needs reactivity to pass the message through, reactive variables work best with db data, or data that would be updated by another client over the air.
You could use JQuery (included by default) to update it, it can also get a bit fancier:
<template name="dateTemplate">
{{date}}
<span style="color: red;display: none" class="errorMessage"></span>
</template>
Template.dateTemplate.events({
'click': function(event, template) {
$(template.find('.errorMessage')).html('Your Error Message').slideDown();
}
});
Ive edited it so the error is hidden by default, and slides down with an animation
I'm experimenting handling this by passing a different reactive object to each instance of the template. Then the template can bind to the reactive object (which is unique per instance) and we don't have any extra boilerplate.
It ends up looking like this:
Initial render:
Template.firstTemplateWithPoll(ContextProvider.getContext())
Template.secondTemplateWithPoll(ContextProvider.getContext())
// (I actually pass getContext an identifier so I always get the same context for the same template)
JS:
Template.poll.events = {
'click .yes' : function() {
this.reactive.set('selection', 'yes');
},
'click .no' : function() {
this.reactive.set('selection', 'no');
}
};
Template.poll.selection = function(arg) {
return this.reactive.get('selection');
}
Template:
<template name="poll">
<blockquote>
<p>
Your selection on this poll is {{selection}}
</p>
</blockquote>
<button class='yes'>YES</button>
<button class='no'>NO</button>
</template>
template.errorMsg should be a function that returns your error.
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid'; };
}
});