In my Meteor app, I am trying to load a random image from this API and I get a JSON like:
{
"id":2026
"url": "https:// ... " ,
"large_url":null,
"source_id":609,
"copyright":"CC0",
"site":"unsplash"
}
I do it this way:
if (Meteor.isClient) {
Template.body.helpers({
randomImage: function() {
Meteor.call("unImage", function(error, results) {
Session.set('url', results.data.url);
});
return Session.get('url');
}
});
}
if (Meteor.isServer) {
Meteor.methods({
unImage: function() {
this.unblock();
return Meteor.http.call("GET", "http://www.splashbase.co/api/v1/images/random");
}
});
}
In my html:
<div class="header" style="background-image: url('{{randomImage}}')">
...
</div>
This is working, but it reloads the image every second - more or less. I guess this is happening because the function unImage, which is on server side, loads all along with the server or something like that (not sure); anyway I cannot make it stop. Any ideas on how to solve it? And why is this happening?
This is because session variable inside of your randomImage helper.
And Session variables are reactive in nature, in which it re-runs in a block whenever its value is changed.
In this case, helper code is re-running again and again and hence, Meteor methods gets called again and again
So, move Meteor.call in helper to rendered event as shown below
if (Meteor.isClient) {
Template.body.rendered= function(){
Meteor.call("unImage", function(error, results) {
Session.set('url', results.data.url);
});
}
Template.body.helpers({
randomImage: function() {
return Session.get('url');
}
});
}
Which should call the Meteor method once template is ready and setting url variable and thus reactively helper randomImage gets re-run and gets value of same
Related
I am trying to implement transitions between pages by using iron:router. I defined the animations in the css and now everything I need is to call them with the iron:router. For some reason the following code:
animateContentOut = function() {
$('#content').removeClass("animated fadeIn");
return $('footer').addClass("hide");
}
fadeContentIn = function() {
$('#content').addClass("animated fadeIn");
return $('footer').removeClass("hide");
}
Router.onBeforeAction(animateContentOut);
Router.onAfterAction(fadeContentIn);
returns an exception:
Route dispatch never rendered. Did you forget to call this.next() in
an onBeforeAction?
As specified in the Iron-Router documentation, now both onBeforeAction and onAfterAction callbacks require this.next(). https://github.com/iron-meteor/iron-router
So simply simply add that line to the end of your fadeContentIn and animateContentOut code.
If you have login try like this
Router.onBeforeAction(function () {
if (!Meteor.user()) {
this.render('Login');
} else {
this.next();
}
});
I have a client method that grays out a rendered document in the DOM by adding a class whose opacity is set to 0.4:
'click .detailCheckbox': function(ev){
var detail = $(ev.target).parent();
if(!detail.hasClass('toggle')){
detail.addClass('toggle');
} else {
detail.removeClass('toggle');
}
}
When I reload the page, though, the DOM element is no longer grayed out, because I never updated the document on the server.
Am I going to have to get super creative here, or am I missing a simple way to solve this?
You can use the meteor-persistent-session package.
Session.setPersistent(key, value) //store a persistent session variable (persistent)
For example.
if(!detail.hasClass('toggle')){
detail.addClass('toggle');
Session.setPersistent('opacity',0.4)
} else {
detail.removeClass('toggle');
Session.clear('opacity')
}
I solved it by creating a new field in the model, which is set (or unset) to the class name that has opacity 0.4
Here is the Meteor method insert method that contains the field checkboxStatus
addDetail: function(detailFormData){
if(! Meteor.userId()){
throw new Meteor.Error('not-authorized');
}
detailsCollection.insert({
detail: detailFormData.detail,
parentId: detailFormData.parentId,
checkboxStatus: detailFormData.checkboxStatus
});
}
I call it on the clicking of the checkbox:
'click .detailCheckbox': function(ev){
ev.preventDefault();
Meteor.call('setToggle', this._id);
}
And the setToggle Meteor method checks the toggle status, and then updates the document accordingly
setToggle: function(detailId){
var checked_detail = detailsCollection.findOne({_id: detailId});
if(checked_detail.checkboxStatus != 'toggle'){
detailsCollection.update(detailId, {
$set: {checkboxStatus: 'toggle'}
});
} else {
detailsCollection.update(detailId, {
$set: {checkboxStatus: 'untoggle'}
});
}
}
Which is then called by a template helper method
checkboxStatus: function(){
var checked_detail = detailsCollection.findOne({_id: this._id});
return checked_detail.checkboxStatus;
}
whose value is returned in the class tag of the template item itself
<li id={{_id}} class="detailViewEntry {{checkboxStatus}}">
I'm using meteorjs and the froala-reactive editor.
In my router I return the collection data to the template, which works fine.
But I need the ability to update the contents of editor. What is the best way to update _value?
The template code:
{{> froalaReactive _onbeforeSave=doSave inlineMode=false _value=getText}}
The router.js code:
Router.route('admin/pages/:_id', function () {
this.render('Page', {
data: function () {
Session.set('editorContent', 'editor content here');
return Pages.findOne({_id: this.params._id})
}});
});
Helper function:
Template.Page.helpers({
getText: function () {
var self = this;
return function (e, editor, data) {
return Session.get("editorContent");
};
}
});
I expect that when the session variable editorContent changes the displayed content in the editor updates, but this is not working.
Your helper function should simply return the Session value, instead of a function.
Like this:
getText: function () {
return Session.get('editorContent');
}
Here's a working example that you can clone and play around with.
I'm learning about Session and reactive data sources in Meteor JS. They work great for setting global UI states. However, I can't figure out how to scope them to a specific instance of a template.
Here's what I'm trying to do
I have multiple contenteditable elements on a page. Below each is an "Edit" button. When the user clicks on the Edit button, it should focus on the element and also show "Save" and "Cancel" buttons.
If the user clicks "Cancel", then any changes are eliminated, and the template instance should rerender with the original content.
Here's the code I have so far
// Helper
Template.form.helpers({
editState: function() {
return Session.get("editState");
}
});
// Rendered
Template.form.rendered = function(e){
var $this = $(this.firstNode);
var formField = this.find('.form-field');
if (Session.get("editState")) formField.focus();
};
// Event map
Template.form.events({
'click .edit-btn' : function (e, template) {
e.preventDefault();
Session.set("editState", "is-editing");
},
'click .cancel-btn' : function (e, template) {
e.preventDefault();
Session.set("editState", null);
},
});
// Template
<template name="form">
<div class="{{editState}}">
<p class="form-field" contenteditable>
{{descriptionText}}
</p>
</div>
Edit
Save
Cancel
</template>
// CSS
.edit-btn
.cancel-btn,
.save-btn {
display: inline-block;
}
.cancel-btn,
.save-btn {
display: none;
}
.is-editing .cancel-btn,
.is-editing .save-btn {
display: inline-block;
}
The problem
If I have more than one instance of the Form template, then .form-field gets focused for each one, instead of just the one being edited. How do I make so that only the one being edited gets focused?
You can render a template with data, which is basically just an object passed to it when inserted in to a page.
The data could simply be the key to use in the Session for editState.
eg, render the template with Template.form({editStateKey:'editState-topForm'})
you could make a handlebars helper eg,
Handlebars.registerHelper('formWithOptions',
function(editStateKey){
return Template.form({editStateKey:editStateKey})
});
then insert it in your template with
{{{formWithOptions 'editState-topForm'}}} (note the triple {, })
Next, change references from Session.x('editState') to Session.x(this.editStateKey)/ Session.x(this.data.editStateKey)
Template.form.helpers({
editState: function() {
return Session.get(this.editStateKey);
}
});
// Rendered
Template.form.rendered = function(e){
var $this = $(this.firstNode);
var formField = this.find('.form-field');
if (Session.get(this.data.editStateKey)) formField.focus();
};
// Event map
Template.form.events({
'click .edit-btn' : function (e, template) {
e.preventDefault();
Session.set(this.editStateKey, "is-editing");
},
'click .cancel-btn' : function (e, template) {
e.preventDefault();
Session.set(this.editStateKey, null);
},
});
Note: if you are using iron-router it has additional api's for passing data to templates.
Note2: In meteor 1.0 there is supposed to be better support for writing your own widgets. Which should allow better control over this sort of thing.
As a matter of policy I avoid Session in almost all cases. I feel their global scope leads to bad habits and lack of good discipline regarding separation-of-concerns as your application grows. Also because of their global scope, Session can lead to trouble when rendering multiple instances of a template. For those reasons I feel other approaches are more scalable.
Alternative approaches
1 addClass/removeClass
Instead of setting a state then reacting to it elsewhere, can you perform the needed action directly. Here classes display and hide blocks as needed:
'click .js-edit-action': function(event, t) {
var $this = $(event.currentTarget),
container = $this.parents('.phenom-comment');
// open and focus
container.addClass('editing');
container.find('textarea').focus();
},
'click .js-confirm-delete-action': function(event, t) {
CardComments.remove(this._id);
},
2 ReactiveVar scoped to template instance
if (Meteor.isClient) {
Template.hello.created = function () {
// counter starts at 0
this.counter = new ReactiveVar(0);
};
Template.hello.helpers({
counter: function () {
return Template.instance().counter.get();
}
});
Template.hello.events({
'click button': function (event, template) {
// increment the counter when button is clicked
template.counter.set(template.counter.get() + 1);
}
});
}
http://meteorcapture.com/a-look-at-local-template-state/
3 Iron-Router's state variables
Get
Router.route('/posts/:_id', {name: 'post'});
PostController = RouteController.extend({
action: function () {
// set the reactive state variable "postId" with a value
// of the id from our url
this.state.set('postId', this.params._id);
this.render();
}
});
Set
Template.Post.helpers({
postId: function () {
var controller = Iron.controller();
// reactively return the value of postId
return controller.state.get('postId');
}
});
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#setting-reactive-state-variables
4 Collection data
Another approach is to simply state by updating data in your collection. Sometimes this makes perfect sense.
5 update the data context
Session is often the worse choice in my opinion. Also I don't personally use #3 as I feel like being less tied to iron-router is better incase we ever want to switch to another router package such as "Flow".
I have a case were I need to choose the template of the view based on the initial property value of the controller. Thus I need to access the controller while I am inside the init hook of the view but when i access the controller it returns "null".
MyApp.ApplicationController = Em.Controller.extend({
templateVersion: 'small'
});
MyApp.ApplicationView = Em.View.extend({
init: function(){
this._super();
console.log('Controller is: ',this.get('controller'));
if(this.get('controller').get('templateVersion') == 'small')
{
this.set('templateName', 'application-small');
} else {
this.set('templateName', 'application-bigger');
}
}
});
This is not the real case but an example for the real scenario.
For an example I have setup a jsbin here
I guess a more appropriate way of doing this would be by determine dynamically the templateName, something like the following:
MyApp.ApplicationView = Ember.View.extend({
templateName: function() {
if (this.get("controller.templateVersion") == "small") {
return "application-small";
} else {
return "application-bigger";
}
}.property('controller.templateVersion')
});
Doing it this way you dont need to hook into the init function and thus not having your controller properties available.
Here your updated jsbin.
Update
After your last comment I realized that the delay is the important part to make your use case work, here is an improved version which indeed changes even if the templateVersion is initially not defined and get's setted with some delay, this time we observe the templateName property of the view and invoke a rerender.
MyApp.ApplicationView = Ember.View.extend({
templateName: function() {
if (this.get("controller.templateVersion") == "small") {
return "application-small";
} else {
return "application-bigger";
}
}.property('controller.templateVersion'),
templateChanged: function() {
this.rerender();
}.observes('templateName')
});
And here another jsbin with the new version with a simulated delay of 2 seconds, but it could be whatever value.
Hope it helps.