Template doesn't show up when wrapping it with {{#with}}? - javascript

I'm following the Discover Meteor book
For some reason, the content in edit_post.html doesn't show up with the {{#with post}}:
<template name="postEdit">
{{#with post}}
<form class="main">
<div class="control-group">
<label class="control-label" for="url">URL</label>
<div class="controls">
<input name="url" type="text" value="{{url}}" placeholder="Your URL"/>
</div>
</div>
.
.
.
</form>
{{/with}}
</template>
the content of post_edit.js:
Template.postEdit.helpers({
post: function() {
return Posts.findOne(Session.get('currentPostId'));
}
});
Template.postEdit.events({
'submit form': function(e) {
e.preventDefault();
var currentPostId = Session.get('currentPostId');
var postProperties = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
}
Posts.update(currentPostId, {$set: postProperties}, function(error) {
if (error) {
// display the error to the user
alert(error.reason);
} else {
Router.go('postPage', {_id: currentPostId});
}
});
},
'click .delete': function(e) {
e.preventDefault();
.
.
.
}
}
});
route.js:
this.route('postEdit', {
path: '/posts/:_id/edit',
data: function() { return Posts.findOne(this.params._id); }
});
The template shows up if I remove the {{#with post}}.
I'm not sure if this is an error in the book, or whether I'm doing something wrong. I'm a Meteor beginner so I have no clue.
Any suggestion to fix this?

The template helper post calls a Session variable that is never set so I think that findOne() returns a falsy value. So {{#with post}} is correctly keeping the template from displaying. Without the {{#with post}} your template is able to display a post from the data function in the router. You are calling findOne() looking for the same data twice but either method will work to get the data you want for the template.
If you want to use {{#with}} you can change your route.js to:
this.route('postEdit', {
path: '/posts/:_id/edit',
before: function() { Session.set( "currentPostId", this.params._id ); }
});

I'm looking at the code from the book and I cannot see the with block you are referring to.
In fact, it should not be there because the data context of the template is already set by the router.
Your template helper (post) is not supposed to be there since it is both unnecessary and in fact there is no already set session variable so your get returns null as expected.
Just delete your helper and the with block and let iron router provide the data context as it already does.

Related

Display single item in Mongo Db using meteor js using template based subscription

I don't know what the problem is, i have this snippet to display single items but it does not work as suppose, What's do i make right?
publication file:
Meteor.publish('SingleSchool', function (myslug) {
check(myslug, String);
if (!this.userId) {
throw new Meteor.Error('Not authorized');
return false;
} else {
return SchoolDb.find({slug: myslug});
}
})
template base subscription:
Template.view.onCreated(function () {
var instance = this;
instance.autorun(function () {
var slug = FlowRouter.getParam('myslug');
return Meteor.subscribe('SingleSchool', slug);
});
});
the route:
FlowRouter.route('/school/:myslug', {
name: 'view',
action: function (params) {
BlazeLayout.render('sidebarschool', {sidebars: 'view'});
}
})
the template file:
<template name="view">
{{#if currentUser}}
{{#if Template.subscriptionsReady }}
{{#if SingleSchool}}
{{#with SingleSchool}}
<p>{{varibablecalled}}</p>
{{/with}}
{{else}}
<p>Loading...</p>
{{/if}}
{{/if}}
</template>
It goes to the slug but no data is displayed for other contents. The slug in the route works fine.
Did you initialize your SingleSchool collection you're trying to read from in the template?
Also, don't forget that this is collection, so you should do SingleSchool.findOne({slug:...}) to obtain needed item.
Btw, your template file looks like messed up: two <template...> tags.

createRecord creates an empty instance when catching errors

I am wondering why i am getting an empty instance each time i try to save a newly created record and it fails. I see like an empty record being added in my posts object only when i display errors.
Also i tried sorting posts which let me see the top recent created posts but the behavior is kinda odd, because as soon as the post is created, it shows at the end and then immediately goes to the top. I am wondering if this is normal or maybe there is a way to wait for the server response and push the record with some sort of fade in effect, etc. Thanks.
index.hbs
<form {{action "savePost" post on="submit"}}>
<div class="form-group {{if errors.error-content "has-error has-feedback"}}">
{{textarea value=post
rows=3
placeholder="What are you thinking?"
id="content"
class="form-control"}}
<small class="help-block text-danger">
{{errors.error-content}}
</small>
</div>
<div class="clearfix">
<div class="pull-right">
<button type="submit"
class="btn btn-sm btn-success">
Create new post
</button>
</div>
</div>
</form>
{{#each sortedPosts as |post|}}
<article class="wrapper">
<p>{{post.content}}</p>
</article>
{{else}}
<article class="wrapper">
<p>NO CURRENT POSTS TO SHOW!</p>
</article>
{{/each}}
post.js
export default DS.Model.extend({
content: DS.attr('string'),
created_at: DS.attr('date')
});
index.js route
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model() {
return this.store.findAll('post');
},
actions: {
savePost(content) {
const newPost = this.store.createRecord('post', {
content: content
});
newPost.save().then(() => {
this.controller.set('post', '');
this.controller.set('errors', []);
}).catch((resp) => {
let errors = {};
resp.errors.forEach((error) => {
errors[`error-${error.field}`] = error.messages[0];
});
this.controller.set('errors', errors);
});
},
willTransition() {
this.controller.get('post').rollbackAttributes();
this.controller.set('errors', []);
}
}
});
index.js controller
export default Ember.Controller.extend({
sortProp: ['created_at:desc'],
sortedPosts: Ember.computed.sort('model', 'sortProp')
});
Well, your this.store.createRecord will always create a new record. So if your .save() fails, you can unload the record, or you don't create your new record there, or save it for the next .save().
The question is a bit why your .save() fails. If its some kind of validation error, and the user fixes them and saves the post again, I would recommend to use the same, earlier created post again, modify it and try to .save() it again.
Another approach is to filter the records you display on the isNew flag.
With that I would bind the textarea directly to a fresh record. A computed property like this could be nice:
newRecord: Ember.computed('_newRecord.isNew', {
get() {
if(!get(this, '_newRecord') || !get(this, '_newRecord.isNew')) {
set(this, '_newRecord', this.store.createRecord('post'));
}
return get(this, '_newRecord');
}
})
Then you can directly bind your texturea's value to newRecord.content and just .save() this in your action.
For all kind of animations, checkout liquid fire.

Meteor: Create a loading widget using ReactiveVar for subscribing to users collection

I had a loading function for my home template which worked nicely for the subscription of the users collection. Unfortunately with this, the loading took up the whole page and I would like to display some general information so long while I wait for the users to load.
I've implemented this in my create account function with my sign up template using ReactiveVar and that worked nicely.
This is how it was done there:
Template.signup.onCreated(function() {
Template.instance().isLoading = new ReactiveVar(false);
});
Template.signup.helpers({
isLoading() {
return Template.instance().isLoading.get();
}
});
Template.signup.events({
'submit #signup-form': function(event, template) {
// Not important stuff here
template.isLoading.set(true);
Accounts.createUser({
// More not important stuff here
}, function(err) {
// Even more not important stuff here
template.isLoading.set(false);
})
}
});
Then in the template I have this:
<template name="signup>
{{#unless isLoading}}
<!-- Stuff -->
{{else}}
<!-- More Stuff -->
{{> loading}}
{{/unless}}
</template>
This works really well but I want to do the same sort of thing for my home page where I subscribe to users. Is there a way to do something similar? I've set up the home template calls and stuff. But I don't know how to do the template events for loading user data.
This is what I have basically:
Template.home.onCreated(function() {
Template.instance().isLoading = new ReactiveVar(false);
});
Template.home.helpers({
user: function() {
return Meteor.users.find({});
},
isLoading() {
return Template.instance().isLoading.get();
}
});
Template.home.events({
//What do I do here????
});
I appreciate any help I can get :)
Edit: Here's my Meteor publish code:
Meteor.publish('users', function() {
return Meteor.users.find({}, {fields: {username: 1, emails: 1, profile: 1}});
});
I think you want to use the Blaze helper Template.subscriptionsReady:
<template name="home">
{{#if Template.subscriptionsReady}}
<!-- Your template code -->
{{else}}
<!-- Your loading code -->
{{/if}}
</template>
Here is a link to relevant documentation: http://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe

Meteor entering route when calling a method

Can someone explain to me why when I have collections code inside router will cause the route to be called when a method is called?
Consider the following code:
home.html
<template name="home">
{{ duplicate }}
<form>
<input type="text" name="test" value="somevalue">
<input type="submit" value="Submit">
</form>
</template>
script.js
Template.home.events({
'submit form': function (e) {
e.preventDefault();
console.log('Enter Meteor call');
Meteor.call('createDoc', { 'test': e.target.test.value });
}
});
route.js
Router.onBeforeAction(function () {
console.log('Enter onBeforeAction');
$('#loading').show();
this.next();
});
Router.route('/', function () {
console.log('Enter action');
var foo = collection.findOne({ test: 'somevalue' }) ? 'true' : 'false';
this.render('home', {
data: {
'duplicate' : foo
}
});
Template.home.rendered = function () {
console.log('Enter rendered');
$('#loading').hide();
};
});
methods.js
collection = new Mongo.Collection('collection');
Meteor.methods({
createDoc: function (data) {
console.log('Enter createDoc');
collection.insert(data);
}
});
The problem is that if I press submit on the form, after the method is called the router will activate, even though e.preventDefault() presents. The console log shows this behaviour clearly:
"Enter Meteor call" script.js:4:3
"Enter createDoc" methods.js:5:3
"Enter onBeforeAction" routes.js:2:2
"Enter action" routes.js:8:2
"Enter onBeforeAction" routes.js:2:2
"Enter action" routes.js:8:2
Furthermore, you can see that the router is called twice and that it never enters Template.home.rendered. This causes the loading div to appear and never leaves. I can confirm that data are being inserted correctly.
If I remove collection.findOne() in routes.js, however, this behaviour will disappear and everything works as expected.
Questions
Why is the route being called only when I have collection.findOne() inside the route?
Why collection.findOne({ test: 'somevalue' }) never returns anything inside the route? (I know how I can get around this by using Session variables and helpers in script.js, but I want to know exactly why)
This is causing a lot of unexpected behaviour in my app. Thank you very much in advance.
As answered by others the problem you have arises from the fact that Meteor will reactively re-run code that runs in a reactive context, if and only if, that code issues a call to a reactive data source.
In your case, the call to findOne is a call to a reactive data source and the context in Router.route('/', function () { // context }); is a reactive context.
There are two important tools that let you control this behavior: one is good design. Be aware of the reactivity and try to design your code around it.
The other is checking Tracker.active and using Tracker.nonreactive to avoid reactivity inside a reactive data context.
This should answer your first question. As to why your findOne query never finds anything: have you published the data from the server to the client? Please check out Publish-Subscribe. You basically need:
// on the server
Meteor.publish('myPublication', function(author) {
return collection.find();
});
// on the client
Meteor.subscribe('myPublication');
The call to collection.findOne() inside the route is listening to any new changes on the database, every time text is saved on the database the query is run.
A possible solution:
Router.js
Router.onBeforeAction(function () {
console.log('Enter onBeforeAction');
$('#loading').show();
this.next();
});
Router.route('/', {
template: 'home',
waitOn: function() {
return Meteor.subscribe('collection');
},
data: function() {
var foo = collection.findOne({ test: 'somevalue' }) ? 'true' : 'false';
return {
'duplicate': foo
};
},
action: function() {
this.render();
}
});
And a publish file on server/publish.js
Meteor.publish('collection', function () {
return collection.find();
});
I hope this can help you solving your problem.
Best.

computed property not working on controller accessed though the needs-property

Somehow the computed property of a controller I'm accessing through the needs property won't work in the template. Other regular string properties work as expected. Below is my code.
To be clear, what I'm trying to achieve is to access properties of the userController and userModel in actually all of my templates/routes (some of which are computed). However, 'user' itself doesn't have a page, so that's why it's not added in the Router.map. It's just a really important class that handles everything user-related and handles access to the user-model.
I hope somebody with a bit more ember experience knows what I'm doing wrong. Or maybe got some advice on how to do this the ember-way? Any help is greatly appreciated!
PS: I tried to be as complete as possible, if I'm forgetting smt let me know, I'll add it.
App.Router.map(function() {
this.resource('signup');
this.resource('login');
this.resource('profile');
this.resource('practice');
this.resource('overview');
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return this.store.find('user');
}
});
App.LoginRoute = Ember.Route.extend({
controllerName: 'application',
model: function () {}
});
App.UserRoute = Ember.Route.extend({
model: function () {
return this.store.find('user');
}
});
//UserController
App.UserController = Ember.ArrayController.extend({
//pagetitle property to test. Working.
pageTitle: 'Usercontroller',
//userArray property to test, but didn't work.
// Makes sense since the Arraycontroller return an array, so you'll have to use #each-helper
userArray: function(){
return this.get('content');
},
//particularUser computed property to test, but also didn't work.
// Neither did looping #each through the userArray property
particularUser : Ember.computed.filterBy('content' , 'username', 'Sunchild')
});
//LoginController
App.LoginController = Ember.ArrayController.extend({
needs: ['user'],
pageTitle: 'test-title loginController'
});
// Login template feeded into an outlet in the application template
<script type="text/x-handlebars" id="login">
<div class="content">
<form class="input-group">
<div class="input-row">
<label>Username</label>
<input type="text" placeholder="Username">
</div>
<div class="input-row">
<label>Email</label>
<input type="email" placeholder="ratchetframework#gmail.com">
</div>
<button class="btn btn-primary btn-block btn-outlined">Login</button>
</form>
<h3> test1:{{controllers.user.pageTitle}}</h3>
<h3> test2:{{controllers.user.userArray}}</h3>
{{#each user in controllers.user.particularUser}}
<div class="card_wrapper">
<p><h3>Username: {{{user.username}}}</h3><p>
<p>email: {{user.email}}</p>
</div>
{{/each}}
</div>
</script>
Without a user route in your router, the model hook in your UserRoute will never run. That means that the model of your UserController will always be empty. So the way you've set things up won't work for your use case. But, you have the same model on your ApplicationRoute, so why not use that controller instead?
App.ApplicationController = Ember.ArrayController.extend({
// Never use the `content` property, always use the `model` property, they are different
userArray: Ember.computed.alias('model'),
particularUser: function() {
// Be sure to only grab the first item, not an array with the first item in it
return this.get('userArray').filterBy('username', 'Sunchild').get('firstObject');
}.property('userArray.#each.username')
});
This is nice because your application route runs before any other route, so this data will always be available. So in your LoginController:
App.LoginController = Ember.ArrayController.extend({
needs: ['application'],
userArray: Ember.comptued.alias('controllers.application.userArray']
});
Hopefully that clears things up a bit.

Categories

Resources