How do I mark data context as changed? - meteor

I have a page that handles some query data from the url, as in localhost/home?foo=bar.
It then sends a JSON to some server somewhere. That much works.
With iron-router, I handle a form submission like so: Router.go('home', {}, {query: queryObjectVar}), which also works.
So when you submit the form with foo="somethingelse" you end up in localhost/home?foo=somethingelse, the page mostly doesn't reload because of meteor magic thus looking really fast, and the JSON is sent with foo=somethingelse, and all is well.
However if you're in localhost/home?foo=var and then you go to the same exact route with Router.go(...), query included, the url doesn't change, and the page does not send the JSON thing. Which in most cases is fine, since technically nothing changed so nothing should need to be done. However, I want it to behave as if the url changed.
How do I do that?
Currently I have the template hooked up to the query like so:
Router.route('home', {
data: function() { return this.params.query }
})

I'm not sure if this is what you want, but you could change the route by making an extra navigation.
Often, after submitting the form, you want the user to know that the form was received or processed somehow.
I would suggest to make an extra route that only handles the processing, something like /home/process. Have the event handler on form submit navigate to the process route. The action for the route sends the json file based on this.params.query.
After the json was sent (I used console.log but better approach would be Meteor.call('here_I_will_sent_json', this.param.query), render a template that let's the user know that the data was sent. And now the trick: have a link or button in the template that navigates to /home or whatever you want the user to direct to.
Templates.
<template name="main">
<div>Sample application</div>
{{> yield}}
</template>
<template name="mainPage">
<div>Main page</div>
Home
</template>
<template name="home">
<div>Home page</div>
{{> form}}
</template>
<template name="form">
<form id="myForm" method="get">
<input type="text" name="foo">
<input type="submit" value="Save">
</form>
</template>
<template name="finishedSending">
<div>finishedSending!</div>
go to main page
</template>
Javascript:
Router.configure({
layoutTemplate: 'main'
});
Router.route('mainPage', {path: '/'});
Router.route('home', {path: '/home'});
// Extra route just for processing the submitted form
Router.route('process', {
path: '/home/process',
data: function() {
return this.params.query
},
action: function() {
// Here you send the json or do whatever you need to process.
// Best to have some Meteor method do the actual processing.
// You could use a callback if processing takes some time.
console.log('Send the json content based on: ', this.params.query);
this.render('finishedSending');
}
});
if (Meteor.isClient) {
Template.main.events({
'submit #myForm': function (e) {
e.preventDefault();
var fieldValue = event.target.foo.value;
Router.go('process', {} , {query: {foo: fieldValue}})
}
});
}

Related

How to deal with the situation that template is rendered but the data is not ready?

In client startup I subscribe to something:
Meteor.publish("Roles", function(){
return Roles.find();
});
Meteor.startup(function() {
if(Meteor.isClient) {
Meteor.subscribe('Roles');
}
});
And roles template:
Template.roles.helper(function() {
allRoles: function() {
return Roles.find().fetch();
}
})
<template name="roles">
<div>
{{#with allRoles}}
<label>{{> role }}</label>
</div>
</template>
The problem is sometime roles template is rendered before the Roles is ready.
How to deal with this situation?
You can do the subscribe on the template and then use the Template.subscriptionReady helper to create a conditional to show a loading panel whilst your subscription is being loaded as follows:
Template.roles.onCreated(function () {
this.subscribe("Roles");
});
Template.roles.helper(function() {
allRoles: function() {
return Roles.find().fetch();
}
})
<template name="roles">
<div>
{{#if Template.subscriptionsReady}}
{{#with allRoles}}
<label>{{> role }}</label>
{{else}}
Loading...
{{/if}}
</div>
</template>
This replaces your other subscription and these subscriptions can be added to each onCreated method for each template to have subscriptions per template.
There are some common ways of dealing with it. You can use a guard or make use of iron router's waitOn function. With a guard you only return data from the helper if you're getting any results:
allRoles: function() {
var roles = Roles.find();
//explicit version
if (roles.count()) return roles
//implicitly works as well here because you're returning null when there are no results
return roles
}
You don't need the fetch() in this case, because #with works with a cursor. If you run into a situation where you need to fetch first because you're returning partial data, check that there are results first and only then return them.
You can also use iron router's waitOn Option if you're using this as part of a route.

Meteor Block Helper that acts like a template

Here's what I want, a custom block helper that can act like a template, monitoring for it's own events etc. The html would look like this:
{{#expandable}}
{{#if expanded}}
Content!!!
<div id="toggle-area"></div>
{{else}}
<div id="toggle-area"></div>
{{/if}}
{{/expandable}}
And here's some javascript I have put together. This would work if I just declared the above as a template, but I want it to apply to whatever input is given to that expandable block helper.
Template.expandableView.created = function() {
this.data._isExpanded = false;
this.data._isExpandedDep = new Deps.Dependency();
}
Template.expandableView.events({
'click .toggle-area': function(e, t) {
t.data._isExpanded = !t.data._isExpanded;
t.data._isExpandedDep.changed();
}
});
Template.expandableView.expanded = function() {
this._isExpandedDep.depend();
return this._isExpanded;
};
I know I can declare block helpers with syntax like this:
Handlebars.registerHelper('expandable', function() {
var contents = options.fn(this);
// boring block helper that unconditionally returns the content
return contents;
});
But that wouldn't have the template behavior.
Thanks in advance! This might not be really possible with the current Meteor implementation.
Update
The implementation given by HubertOG is super cool, but the expanded helper isn't accessible from within the content below:
<template name="expandableView">
{{expanded}} <!-- this works correctly -->
{{content}}
</template>
<!-- in an appropriate 'home' template -->
{{#expandable}}
{{expanded}} <!-- this doesn't work correctly. Expanded is undefined. -->
<button class="toggle-thing">Toggle</button>
{{#if expanded}}
Content is showing!
{{else}}
Nope....
{{/if}}
{{/expandable}}
In the actual block helper, expanded is undefined, since the real thing is a level up in the context. I tried things like {{../expanded}} and {{this.expanded}}, but to no avail.
Strangely, the event handler is correctly wired up.... it fires when I click that button, but the expanded helper is simply never called from within the content, so even console.log() calls are never fired.
Meteor's new Blaze template engine solves this problem quite nicely.
Here's an excerpt from the Blaze docs showing how they can be used.
Definition:
<template name="ifEven">
{{#if isEven value}}
{{> UI.contentBlock}}
{{else}}
{{> UI.elseBlock}}
{{/if}}
</template>
Template.ifEven.isEven = function (value) {
return (value % 2) === 0;
}
Usage:
{{#ifEven value=2}}
2 is even
{{else}}
2 is odd
{{/ifEven}}
You can achieve this by making a helper that returns a template, and passing the helper options as a data to that template.
First, make your helper template:
<template name="helperTemplate">
<div class="this-is-a-helper-box">
<p>I'm a helper!</p>
{{helperContents}}
</div>
</template>
This will work as a typical template, i.e. it can respond to events:
Template.helperTemplate.events({
'click .click-me': function(e, t) {
alert('CLICK!');
},
});
Finally, make a helper that will return this template.
Handlebars.registerHelper('blockHelper', function(options) {
return new Handlebars.SafeString(Template.helperTemplate({
helperContents: new Handlebars.SafeString(options.fn(this)),
}));
});
The helper options are passed as a helperContents param inside the template data. We used that param in the template to display the contents. Notice also that you need to wrap the returned HTML code in Handlebars.SafeString, both in the case of the template helper and its data.
Then you can use it just as intended:
<template name="example">
{{#blockHelper}}
Blah blah blah
<div class="click-me">CLICK</div>
{{/blockHelper}}
</template>

Pass context to yield when using Meteor Iron Router

I am starting to use Iron Router in my Meteor app and its yields for templating.
I've recently run into a problem where I can't start a named yield with a context, as follows:
{{#with context}}
{{yield 'subtemplate'}}
{{/with}}
and get this error Sorry, couldn't find a yield named "subtemplate". Did you define it in one of the rendered templates like this: {{yield "subtemplate"}}?
If I remove the {{#with}} block expression, I am able to render the yield.
Does anyone know of a good way to pass the context to a named yield?
I have posted my problem as an issue on the iron-router github project, but haven't gotten any solution yet.
Would appreciate any help.
EDIT 1/1/2014:
So my code looks like this:
// main template for the route
<div class="form-container">
<div class="form">
{{yield 'section'}}
</div>
</div>
The logic to get the yield section to display
// router.js
ApplyController = RouteController.extend({
before: function() {
var section = this.params.section || 'personal-info';
Session.set('current', section);
},
action: function() {
var section = Session.get('current');
this.render();
this.render(section, {
to: 'section'
});
},
data: function() {
return {
app: Applications.findOne({user: Meteor.userId()})
}
}
});
Example of one of the section template:
<template name="education">
{{#with app}}
<form id="education" name="education" class="fragment" method="post" action="">
<h2>Education</h2>
<div class="form-group">
<label for="college" class="control-label">College/ University</label>
<select class="form-control" id="college" name="college" placeholder="Select a College/ University">
<option value="">Select a College/ University</option>
{{#each colleges}}
<option value="{{slug}}" {{selected slug ../college}}>{{name}}</option>
{{/each}}
</select>
</div>
<!-- other content here -->
</form>
{{/with}}
</template>
Using the {{#with app}} block is how I currently get around this issue, but because I have 10 different section templates, I have to put that in all of them.
You pass a data context in the router using ironrouter. You can't pass it this way because if you pass a route in the router it would override the route's data context.
It might however work with the shark branch of ironRouter which is based off Meteor UI since it uses {{>yield}} instead of {{yield}}.
You can use this though:
Route specific data context
Router.map(function() {
this.route('template', data: function() { return Something.find() });
});
You basically pass the context using the data param. It might be easier to do it this way than to use {{#with context}} because you can use more dynamic data which is different for each route.
You might have tried this, I'm a bit unsure on whether it would go to a named yield's template.
Using an ordinary Template helper for the template
Template.templateInYieldName.helper = function() {
return Something.find();
}
Then you can use something like {{helper.name}} in your named yield.
Global data context with handlebars helper
If you intend to use data for all the routes you can use a Handlebars global helper. i.e
Handlebars.registerHelper('todaysDate', function() {
return (new Date).toString();
});
then just use {{todaysDate}} in any of your templates. You can use your data instead of a date instead.

meteor : setting fields post opening dialog

I'm trying to complete the parties demo
with an "edit party" feature
I understood the create Dialog opens upon setting Session showCreateDialog
{{#if showCreateDialog}}
{{> createDialog}}
{{/if}}
this shows the popin
but I want to set to fields post opening
and I don't see how to act after the opening action ?
You can set manipulate the DOM inside the Template's rendered event. But if you find yourself writing lots of glue code here ($("#someInput").val("someVal")) then watch out because you're likely on the wrong track!
Template.createDialog.rendered = function() {
// you can manipulate the DOM here
}
Remember, you can bind field values to instances, so something like the below will auto-bind your object
<template name="editDialog">
{{#with party}}
<input type="text" id="myPartyName" value="{{name}}" />
...
{{/with}}
</template>
Template.editDialog.party = function() {
return Parties.findOne(Session.get("selectedParty"));
};

returning findOne object to template

Having troubles understanding how to return and use an object from findOne().
my code is this:
Html:
<head>
<title>count</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
{{showcount}}
</template>
Js:
var Database = new Meteor.Collection("counters");
if(Meteor.is_client) {
Template.hello.showcount = function () {
var c = Database.findOne();
return c;
};
}
if (Meteor.is_server) {
Meteor.startup(function () {
if(Database.find().count() === 0)
{
Database.insert({name: "counter", value: 0});
}
});
}
Now I'm wondering if there is any way I can access the data from my object.
changing from {{showcount}} to {{showcount.name}} doesn't seem to work at all.
This same issue got me a few times when I started out with Meteor...
When the Meteor client connects to the server the template is being rendered before the collections have finished being synchronised. i.e. The client collection is empty at the point you are calling findOne.
To see this in action stick a console.log(c) after your findOne call then try reloading the page. You will see two log entries; Once on initial page load and then again when the collection has finished being synchronised.
To fix this all you need to do is update your hello template to handle the fact the collection might not have been synchronised.
{{#if showcount}}
{{showcount.name}}
{{/if}}
I tested your code with the above change and it works.
The proper way to do this is with the #with tag.
<template name="hello">
{{#with showcount}}
{{name}}
{{/with}}
</template>
See the documentation below for more info on the #with tag
https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md

Resources