Meteorjs: Not being able to hide UI elements - javascript

I am following the tutorial here, which is about setting session variables in meteorjs.
But I can't get the example to work. Clicking on hide button doesn't do anything.
JS:
Tasks = new Mongo.Collection("tasks");
if (Meteor.isClient) {
// This code only runs on the client
Template.body.helpers({
tasks: function(){
console.log("function called");
if (Session.get("hideCompleted")) {
// If hide completed is checked, filter tasks
console.log("success!");
return Tasks.find({checked: {$ne: true}}, {sort: {createdAt: -1}});
} else {
console.log("failure!");
// Otherwise, return all of the tasks
return Tasks.find({}, {sort: {createdAt: -1}});
}
},
hideCompleted: function () {
return Session.get("hideCompleted"); }
});
Template.body.events({
"submit .new-task": function (event) {
// Prevent default browser form submit
event.preventDefault();
// Get value from form element
var text = event.target.text.value;
// Insert a task into the collection
Tasks.insert({
text: text,
createdAt: new Date() // current time
});
// Clear form
event.target.text.value = "";
}
});
Template.task.events({
"click .toggle-checked": function () {
// Set the checked property to the opposite of its current value
Tasks.update(this._id, {
$set: {checked: ! this.checked}
});
},
"click .delete": function () {
Tasks.remove(this._id);
},
"change .hide-completed input": function (event) {
console.log("Changed!");
Session.set("hideCompleted", event.target.checked);
}
});
}
HTML:
<head>
<title>Todo List</title>
</head>
<body>
<div class="container">
<header>
<h1>Todo List </h1>
<label class="hide-completed">
<input type="checkbox" checked="{{hideCompleted}}" />
Hide Completed Tasks
</label>
<form class="new-task">
<h2> Add a task </h2>
<input type="text" name="text" placeholder="Type to add new tasks" />
</form>
</header>
<ul>
{{#each tasks}}
{{> task}}
{{/each}}
</ul>
</div>
</body>
<template name="task">
<li class="{{#if checked}}checked{{/if}}">
<button class="delete">×</button>
<input type="checkbox" checked="{{checked}}" class="toggle-checked" />
<span class="text">{{text}}</span>
</li>
</template>
At the beginning my console output is as expected:
function called
failure!
But on clicking the hide-completed checkbox, no event is triggered (i.e., console log doesnt change). What have I missed?

I would say that it is probably because you defined your "toggle checked" handler in the Template.task.events , indeed your checkbox is not part of the "task" template.
Put your handler in the body events, and it should be called properly.

Related

Vue JS - Checkbox validation error on submit

In my registration form I have checkbox that confirms whether the user accepted the terms and conditions. The checkbox should validate once I hit the submit button, however since the checkbox is initially unselected, the validation error shows up straight away. Eventually, the error disappears reactively once I tick the checkbox, but for this particular scenario I would like to have the validation error show up only after I hit submit (if I did not check it). I'm not getting any particular console errors, but I'm simply getting stuck on the execution. Would anyone be able to show me how I can achieve this? I'd appreciate any help!
Checkbox.vue - this is the component representing the checkbox.
<template>
<div class="check-wrapper">
<label :for="id" class="check-label">
<input v-model="checkboxValue"
:id="id"
:checked="isCheckboxChecked"
:oninput="checkCheckbox()"
type="checkbox"
name="newsletter"/>
<span v-if="labelText && !isLabelHtmlText">{{ labelText }}</span>
<span v-if="labelText && isLabelHtmlText" class="label-html" v-html="labelText"></span>
<span :class="{'check-mark-error': checkboxError}" class="check-mark"></span>
</label>
<p v-if="checkboxError" class="checkbox-error text-error">{{checkboxError}}</p>
</div>
</template>
<script>
data: () => ({
checkboxValue: false
}),
methods: {
updateValue: function () {
if (this.$props.callback) {
this.$props.callback(this.$props.id, this.$props.checkboxData, this.checkboxValue);
}
},
checkCheckbox: function () {
this.updateValue();
}
}
</script>
Register.vue - this is the parent component where the registration takes place
<template>
<BasicCheckbox :id="'terms-privacy'"
:callback="onTermsClick"
:label-text="'terms and conditions'"
:is-label-html-text="true"
:checkbox-error="termsPrivacyError"
class="terms-privacy"/>
</template>
<script>
methods: {
validateData: function (data) {
if (!this.termsPrivacyError) {
this.sendRegistration(data).then(response => {
if (response) {
console.log('Registration successful');
this.loginUser({email: data.email, password: data.password}).then(response => {
if (response) {
console.log('User logged in!');
this.$router.push({name: ROUTE_NAMES_HOME.HOME});
}
})
}
});
}
},
// Terms and Privacy Checkbox
onTermsClick: function (checkboxId, checkboxData, data) {
this.termsPrivacyError = !data ? termsPrivacyErrorText : '';
},
}
</script>
First of all, this is not an elegant solution but it works, we use a computed value to control if the error should be displayed. We update it in submit method, and cancel it when we click it checkbox for demonstration purpose.
new Vue({
el: "#app",
data: {
termsState: false,
validated: false
},
computed: {
termsError() {
return this.validated && !this.termsState
}
},
methods: {
handleTermsState() {
this.validated = false
},
handleSubmit() {
this.validated = true
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id='app'>
<label for="terms">
Terms and Privacy Policy
<input type="checkbox" id="terms" name="terms" v-model="termsState" #change="handleTermsState">
{{ termsState }}
</label>
<p style="color: red" class="for-error terms-error" v-if="termsError">You have to agree the terms and privacy condition.</p>
<div><button type="submit" #click="handleSubmit">Submit</button></div>
</div>
From your scenario, what I understood, the validation is not happening when the user didn't check privacy and policy. If the user ticks and unticks the checkbox, the validation is working.
If that's the case, what you can do is check the child component "Checkbox.vue" data property "checkboxValue" value, as the default value is already false, and it will be true if the user did the action and tick the checkbox. Just before submitting the form, add the checkboxValue condition check.
Here is the updated Register.vue component file:
<template>
<BasicCheckbox
:id="'terms-privacy'"
:callback="onTermsClick"
:label-text="'terms and conditions'"
:is-label-html-text="true"
ref="BasicCheckbox"
:checkbox-error="termsPrivacyError"
class="terms-privacy"
/>
</template>
<script>
methods: {
validateData: function (data) {
if (!this.termsPrivacyError && this.$refs.BasicCheckbox.checkboxValue) {
this.sendRegistration(data).then(response => {
if (response) {
console.log('Registration successful');
this.loginUser({email: data.email, password: data.password}).then(response => {
if (response) {
console.log('User logged in!');
this.$router.push({name: ROUTE_NAMES_HOME.HOME});
}
})
}
});
}
},
// Terms and Privacy Checkbox
onTermsClick: function (checkboxId, checkboxData, data) {
this.termsPrivacyError = !data ? termsPrivacyErrorText : '';
},
}
</script>
What I modified only:
I added the attribute of ref for the component stage `BasicCheckbox':
ref="BasicCheckbox"
And for the validation, I just only added condition whether the ref component 'BasicCheckbox' has value `true':
if (!this.termsPrivacyError && this.$refs.BasicCheckbox.checkboxValue)

Link MongoDB collections

I am trying to convert the Meteor tutorial app, simple-todo, into a forum. I have a list that populates with Topics with a delete button and a radio button.
What I want to do, is when a Topic's radio button is selected, display all the comments associated with it. I am trying to accomplish this by putting the topicId(originally taskId), used in the tutorial, in a new Collection called Comments. I then query the Comments for that topicId.the comments are mostly copied and pasted topics, so the attributes and functionality of the two is mostly the same.
I, right now, don't know how to get to the topicId from my Template.body.events. If you can help me tie these two DB's together, that would be very helpful.
HTML:
<head>
<title>Forum</title>
</head>
<body>
<div class="login">
{{> loginButtons}}
</div>
<nav class="mainMenu">
<ul>
<li>Home</li>
<li>Forum</li>
<li>About Us</li>
</ul>
</nav>
<div class="container">
<header>
<h1>Forum</h1>
<h2>Post a topic</h2>
{{#if currentUser}}
<form class="new-topic">
<input type="topicalContent" name="topicalContent" placeholder="Type to add new topics" />
</form>
{{/if}}
</header>
<table>
<td class="topicsCol">
<h3>Topics</h3>
<ul class="topicsList">
{{#each topics}}
{{> topic}}
{{/each}}
</ul>
</td>
<td class="commentsCol">
<h3>Comments</h3>
<ul class="commentsList">
{{#each comments}}
{{> comment}}
{{/each}}
</ul>
<input type="commentContent" name="commentContent" placeholder="Type to Comment" />
</td>
</table>
</div>
</body>
<template name="topic">
<li class="{{#if selected}}select{{/if}}">
<button class="delete">×</button>
<span class="topicalContent">{{topicalContent}} -<strong>{{username}}</strong></span>
<input type="radio" name="curTopic" value="{{curTopic}}" />
</li>
</template>
<template name="commentsList">
<li class="comment">
<button class="delete">×</button>
<span class="responseContent">
<strong>
<!-- {{#if username === owner}}
<style="color:red;">OP
{{else}}-->
{{username}}
<!--{{/if}}-->
</strong>: {{responseContent}}</span>
</li>
</template>
JavaScript:
Topics = new Mongo.Collection("topics");
Comments = new Mongo.Collection("comments");
if(Meteor.isServer){
Meteor.publish("topics", function(){
return Topics.find({
$or: [
{ owner: this.userId }
]
});
});
Meteor.publish("comments", function(){
return Comments.find({
$or: [
{ parent: topicId },
{ owner: this.userId }
]
});
});
}
if (Meteor.isClient) {
// This code only runs on the client
Meteor.subscribe("topics");
Meteor.subscribe("comments");
Template.body.helpers({
topics: function() {
return Topics.find({}, {sort: {createdAt: -1}});
},
comments: function () {
return Comments.find({parent: {parent: topicId}}, {sort: {createdAt: -1}});
}
});
Template.body.events({
"submit .new-topic": function (event) {
//Prevent default browser form submit
event.preventDefault();
//Get value from form element
var topicalContent = event.target.topicalContent.value;
if (topicalContent == "") {
throw new Meteor.Error("Empty Input");
}
//Insert a topic into the collection
Meteor.call("addTopic", topicalContent);
//Clear form
event.target.topicalContent.value = "";
},
"submit .commentContent": function (event) {
event.preventDefault();
var commentContent = event.target.commentContent.value;
Meteor.call("addComment", this.topicId);
event.target.commentContent.value= "";
}
});
Template.topic.helpers({
isOwner: function(){
return this.owner === Meteor.userId();
}
});
Template.topic.events({
"click .curTopic": function() {
//Show Comments of selected radio button
Meteor.call("showComments", this._id);
},
"click .delete":function () {
Meteor.call("deleteTopic", this._id);
}
});
Template.comment.helpers({
isOwner: function(){
return this.owner === Meteor.userId();
}
});
Template.comment.events({
"click .delete":function () {
Meteor.call("deleteComment", this._id);
}
});
Accounts.ui.config({
passwordSignupFields: "USERNAME_ONLY"
});
}
Meteor.methods({
addTopic:function(topicalContent){
if (!Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
Topics.insert({
topicalContent,
createdAt: new Date(),
owner: Meteor.userId(),
username: Meteor.user().username
});
},
deleteTopic:function(topicId) {
var topic = Topics.findOne(topicId);
if (topic.owner !== Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
else {
Topics.remove(topicId);
}
},
showComments:function (topicId) {
Comments.find({"parent":topicId});
},
addComment:function(commentContent, topicId){
if (!Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
Comments.insert({
commentContent,
createdAt: new Date(),
owner: Meteor.userId(),
username: Meteor.user().username,
parent: topicId
});
},
deleteComment:function(commentId) {
var comment = Comments.findOne(commentId);
if (comment.owner !== Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
else {
Comments.remove(commentId);
}
}
});
One way (and there are many) is to use a session variable to track the current topic. In your topic event handler:
Template.topic.events({
"click .curTopic": function() {
Session.set('topicId',this._id); // set the Session variable
Meteor.call("showComments", this._id);
});
Now everywhere else you're looking for the current topicId you can just use Session.get('topicId');

MeteorJS Tasks.find() by <select> dropdown?

In my to-do-app I have a static <select> dropdown list, which I like to use to find specific tasks from the database. But how to get it working? I wired up an event and tried to set a variable assortment but it seems the template doesn't get re-rendered with the new values! Please have a look at the fallowing code:
HTML
<head>
<title>eisenhower</title>
</head>
<body>
{{> eisenhower}}
</body>
<template name="eisenhower">
<div class="eisenhower">
<select id="assortment"> <!-- HERE IS THE DROPDOWN LIST -->
<option value="">all tasks</option>
<option value="true">done tasks</option>
<option value="false" selected>undone tasks</option>
</select>
<div>
<h1>{{doTasksCount}} to do</h1>
<ul>
<li><form><input type="input" name="new" section="do" /></form></li>
{{#each doTasks}}
<li><input type="checkbox" checked="{{checked}}" /> {{task}}</li>
{{/each}}
</ul>
</div>
<div>
<h1>{{scheduleTasksCount}} to schedule</h1>
<ul>
<li><form><input type="input" name="new" section="schedule" /></form></li>
{{#each scheduleTasks}}
<li><input type="checkbox" checked="{{checked}}" /> {{task}}</li>
{{/each}}
</ul>
</div>
<div>
<h1>{{delegateTasksCount}} to delegate</h1>
<ul>
<li><form><input type="input" name="new" section="delegate" /></form></li>
{{#each delegateTasks}}
<li><input type="checkbox" checked="{{checked}}" /> {{task}}</li>
{{/each}}
</ul>
</div>
<div>
<h1>{{abandonTasksCount}} to abandon</h1>
<ul>
<li><form><input type="input" name="new" section="abandon" /></form></li>
{{#each abandonTasks}}
<li><input type="checkbox" checked="{{checked}}" /> {{task}}</li>
{{/each}}
</ul>
</div>
</div>
</template>
JS
Tasks = new Mongo.Collection("tasks");
assortment = true; // HERE IS MY VARIABLE FOR CHANGING THE .find() CLAUSE!
if (Meteor.isClient) {
Template.eisenhower.helpers({
//tasks
doTasks: function() {
return Tasks.find({section: "do", done: assortment}, {sort: {createdAt: -1}});
},
scheduleTasks: function() {
return Tasks.find({section: "schedule", done: assortment}, {sort: {createdAt: -1}});
},
delegateTasks: function() {
return Tasks.find({section: "delegate", done: assortment}, {sort: {createdAt: -1}});
},
abandonTasks: function() {
return Tasks.find({section: "abandon", done: assortment}, {sort: {createdAt: -1}});
},
checked: function() {
return Tasks.findOne(this._id).done;
},
//tasks counter
//doTasksCount: function() { return dos.count(); },
//scheduleTasksCount: function() { return schedules.count(); },
//delegateTasksCount: function() { return delegates.count(); },
//abandonTasksCount: function() { return abandons.count(); },
});
Template.eisenhower.events({
"submit form": function(event, template) {
event.preventDefault();
var obj = event.target.new; //input reference
var task = obj.value;
var section = obj.getAttribute("section"); //obj.section doesnt work here
Tasks.insert({task: task, section: section, done: false, createdAt: new Date()}, function(error, id) {
if(!error) {
event.target.new.value = ""; //clear form
}
});
},
"change checkbox": function(event) {
Tasks.update(this._id, {$set: {done: event.target.checked}});
},
"change select": function(event) {
assortment = (event.target.value) ? event.target.value : true; // SET VARIABLE! BUT DATA DOESNT CHANGE THOUGH!?
}
});
}
The key to get this working is to change the assortment variable to a reactive data source. When a reactive data source is reset in Meteor, the functions depending on it are rerun. When a plain vanilla variable is reset, functions are not rerun.
Your two options here are to make assortment a Session key, or to make it a ReactiveVar. You should take a look at the documentation for each and choose the one that fits best for you:
http://docs.meteor.com/#/full/session
http://docs.meteor.com/#/full/reactivevar
There are some noteworthy differences between the two, e.g., ReactiveVar requires a package to be installed and will not rerun when the value is reset to the same value as existed previously. But to demonstrate the pattern with Session, you will want to set it initially:
Session.setDefault('assortment', true);
In your template helpers, get the value:
doTasks: function() {
return Tasks.find({section: "do", done: Session.get('assortment')}, {sort: {createdAt: -1}});
}
And set it in your template event:
"change select": function(event) {
var assortment = (event.target.value) ? event.target.value : true;
Session.set('assortment', assortment);
}

Ember loading template multiple times and error

I've got an issue where it looks like Ember (1.6.0-beta.4) is attempting to load the same view a second time instead of transitioning to a different view.
The app has a login view and then the main page view. The login view loads great. No issues there. The user enters their creds and hits login. When a successful response comes back, the app transitions to the main page (route is updated as it should be).
But rather than rendering the main page, it renders the login page and the main page stacked together. In the console, there's an error: "Uncaught Error: Assertion Failed: Attempted to register a view with an id already in use: userName"
The only view that has an element with an id of 'userName' is the login view, which leads me to believe it is trying to render the login page a second time, (which is not what I want).
Ember inspector doesn't show anything out of place. When I refresh the main page, the error goes away.
Here are the relevant parts of my app (copied and pasted from a series of js files, I tried to keep things organized as best i could)
my templates:
<script type="text/x-handlebars">
{{#if loggedIn}}
<nav>
<img id="navLogo" src="images/ExsellsiorMAnagerLogo.png" />
<!--<div class="pull-right">Hello {{FirstName}}!</div>-->
</nav>
{{outlet}}
{{else}}
{{outlet}}
{{/if}}
</script>
<script type="text/x-handlebars" id="manifests">
<div class="container-fluid">
<div class="row">
{{render 'filter' model}}
<div id="library" class="col-md-3 left-column"><h2>Library</h2></div>
<div id="stage" class="col-md-7 bg-danger"><h2>Stage</h2></div>
</div>
</div>
</script>
<script type="text/x-handlebars" id="login">
<div class="container">
<form id="login-form" role="form" class="form-horizontal" {{action login on="submit"}}>
<div class="form-group">
<img src="images/ExsellsiorMAnagerLogo.png" alt="Exsellsior Manager Logo" />
</div>
<div class="form-group">
<label for="userName" class="control-label hidden">User Name</label>
{{input id="userName" type="text" class="form-control" placeholder="User Name" value=userName }}
</div>
<div class="form-group">
<label for="pwd" class="control-label hidden">Password</label>
{{input id="pwd" type="password" class="form-control" placeholder="Password" value=password}}
</div>
<div class="form-group">
{{#if inProcess}}
<button id="loginBtn" class="btn btn-primary has-spinner spinner-active" type="submit">
Login<span class="spinner"><i class="icon-primary-spinner"></i></span>
</button>
{{else}}
<button id="loginBtn" class="btn btn-primary has-spinner" type="submit">
Login<span class="spinner"><i class="icon-primary-spinner"></i></span>
</button>
{{/if}}
</div>
{{#if invalidLogin}}
<div id="failure-message" class="form-group has-error bg-danger">
<span class="text-danger">Invalid username or password</span>
</div>
{{/if}}
</form>
</div>
</script>
controllers:
app.ApplicationController = Em.Controller.extend({
needs: ['login'],
loggedIn: false,
tokenChanged: function() {
var self = this,
login = self.get('controllers.login');
if (login.get('token')) {
this.set('loggedIn', true)
} else {
this.set('loggedIn', false)
}
},
userInfoChanged: function () {
var self = this,
login = self.get('controllers.login');
if (login.get('userInfo')) {
this.setProperties(login.get('userInfo'));
}
},
setState: function () {
var self = this;
var login = self.get('controllers.login');
login.addObserver('token', self, self.tokenChanged);
login.addObserver('userInfo', self, self.userInfoChanged);
if (login.get('token')) {
this.set('loggedIn', true);
this.setProperties(login.get('userInfo'));
this.transitionToRoute('manifests');
} else {
this.set('loggedIn', false);
this.transitionToRoute('login');
}
}
});
app.LoginController = Em.Controller.extend({
// resets login info so previous info is not stored
reset: function () {
var self = this;
self.setProperties({
userName: "",
password: "",
invalidLogin: false
});
},
// define dependency on application controller
//needs: ['application'],
// initializes with user token, if one exists
token: localStorage.getItem("token"),
userInfo: JSON.parse(localStorage.getItem("userInfo")),
// monitors if token changes and updates local storage if so
tokenChanged: function() {
localStorage.setItem("token", this.get('token'));
}.observes('token'),
userInfoChanged: function () {
localStorage.setItem("userInfo", JSON.stringify(this.get('userInfo')))
}.observes('userInfo'),
actions: {
// action to fire when user attempts to log in
login: function () {
var self = this;
if (self.get('inProcess')) {
return;
}
self.set('inProcess', true);
// function.bind() specifies the context the function will be executed in
// (the 'this' object within the function)
// login returns the promise from an AJAX call
return app.util.login(self.get('userName'), self.get('password'))
.then(loginSuccess.bind(self), loginFailure.bind(self));
}
}
});
app.FilterController = Em.ObjectController.extend({
showing: true,
actions: {
collapse: function () {
this.set('showing', !this.get('showing'));
}
}
});
Routes:
app.Router.map(function () {
// /login
this.resource('login');
// /manifests
this.resource('manifests',function(){
this.resource('filter');
});
});
app.AuthenticatedRoute = Em.Route.extend({
// checks if we have a token - if not we can assume we're
// not logged in before we make an ajax call
beforeModel: function(transition) {
if (!this.controllerFor('login').get('token')) {
this.redirectToLogin(transition);
}
},
// function to handle re-routing to login screen
redirectToLogin: function(transition) {
var loginController = this.controllerFor('login');
loginController.set('attemptedTransition', transition);
this.transitionTo('login');
},
// reusable function for data requests
executeAjax: function(method, url, data) {
var token = this.controllerFor('login').get('token');
return app.util.executeAjax(method, url, token, data);
},
actions: {
error: function(reason, transition) {
if (reason.status === 401) {
this.redirectToLogin(transition);
} else {
// todo: handle this better
alert('Something went wrong');
}
}
}
});
app.LoginRoute = Em.Route.extend({
// ensures user data is cleared when login page loads/reloads
setupController: function(controller, context) {
controller.reset();
}
});
app.ManifestsRoute = app.AuthenticatedRoute.extend({
model: function () {
return this.executeAjax("GET", "states").then(function (result) {
return {
states: result
}
});
}
});
Comments can't be used in handlebars like this,
<!--<div class="pull-right">Hello {{FirstName}}!</div>-->
They should be wrapped in handlebars:
{{!-- foo --}}
Also your outlet should be out of the scope of the if statement:
{{#if loggedIn}}
<nav>
<img id="navLogo" src="images/ExsellsiorMAnagerLogo.png" />
{{!-- <div class="pull-right">Hello {{FirstName}}!</div> --}}
</nav>
{{/if}}
{{outlet}}

Adding child routes in ember.js

I'm working on ember.js tutorial now. I got a problem on "adding child routes" chapter. My todos list is not displayed. The "todos" template outputing just fine but "todos/index" template doesn't work at all. The console does not show any errors. I guess that this is some local problem or some bug. Maybe someone has already met with a similar problem. What could be the reason of this issue? How can i solve it?
Thanks.
HTML:
<html>
<head>
<meta charset="utf-8">
<title>Ember.js • TodoMVC</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<script type="text/x-handlebars" data-template-name="todos/index">
<ul id="todo-list">
{{#each itemController="todo"}}
<li {{bind-attr class="isCompleted:completed isEditing:editing"}}>
{{#if isEditing}}
{{edit-todo class="edit" value=title focus-out="acceptChanges" insert-newline="acceptChanges"}}
{{else}}
{{input type="checkbox" checked=isCompleted class="toggle"}}
<label {{action "editTodo" on="doubleClick"}}>{{title}}</label><button {{action "removeTodo"}} class="destroy"></button>
{{/if}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="todos">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
{{input type="text" id="new-todo" placeholder="What needs to be done?"
value=newTitle action="createTodo"}}
</header>
<section id="main">
{{outlet}}
<input type="checkbox" id="toggle-all">
</section>
<footer id="footer">
<span id="todo-count">
<strong>{{remaining}}</strong> {{inflection}} left</span>
<ul id="filters">
<li>
All
</li>
<li>
Active
</li>
<li>
Completed
</li>
</ul>
<button id="clear-completed">
Clear completed (1)
</button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
</footer>
</script>
<script src="js/libs/jquery-1.10.2.js"></script>
<script src="js/libs/handlebars-1.1.2.js"></script>
<script src="js/libs/ember-1.2.0.js"></script>
<script src="js/libs/ember-data.js"></script>
<script src="js/app.js"></script>
<script src="js/route.js"></script>
<script src="js/models/todo.js"></script>
<script src="js/controllers/todo_controller.js"></script>
<script src="js/controllers/todos_controller.js"></script>
<script src="js/views/edit_todo_view.js"></script>
</body>
</html>
JS:
window.Todos = Ember.Application.create();
Todos.ApplicationAdapter = DS.FixtureAdapter.extend();
Todos.Router.reopen({
rootURL: '/one/'
});
Todos.Router.map(function () {
this.resource('todos', { path: '/' });
});
Todos.TodosRoute = Ember.Route.extend({
model: function () {
return this.store.find('todo');
}
});
Todos.TodosIndexRoute = Ember.Route.extend({
model: function () {
return this.modelFor('todos');
}
});
Todos.Todo = DS.Model.extend({
title:DS.attr('string'),
isCompleted:DS.attr('boolean')
});
Todos.Todo.FIXTURES = [
{
id: 1,
title: 'Learn Ember.js',
isCompleted: true
},
{
id: 2,
title: '...',
isCompleted: false
},
{
id: 3,
title: 'Profit!',
isCompleted: false
}
];
Todos.TodosController = Ember.ArrayController.extend({
actions: {
createTodo: function () {
// Get the todo title set by the "New Todo" text field
var title = this.get('newTitle');
if (!title.trim()) { return; }
// Create the new Todo model
var todo = this.store.createRecord('todo', {
title: title,
isCompleted: false
});
// Clear the "New Todo" text field
this.set('newTitle', '');
// Save the new model
todo.save();
}
},
remaining: function () {
return this.filterBy('isCompleted', false).get('length');
}.property('#each.isCompleted'),
inflection: function () {
var remaining = this.get('remaining');
return remaining === 1 ? 'item' : 'items';
}.property('remaining'),
});
Todos.TodoController = Ember.ObjectController.extend({
actions:{
editTodo: function () {
this.set('isEditing', true);
},
acceptChanges: function () {
this.set('isEditing', false);
if (Ember.isEmpty(this.get('model.title'))) {
this.send('removeTodo');
} else {
this.get('model').save();
}
},
removeTodo: function () {
var todo = this.get('model');
todo.deleteRecord();
todo.save();
},
},
isEditing: false,
isCompleted: function(key, value){
var model = this.get('model');
if (value === undefined) {
// property being used as a getter
return model.get('isCompleted');
} else {
// property being used as a setter
model.set('isCompleted', value);
model.save();
return value;
}
}.property('model.isCompleted')
});
Technically this isn't a bug, the team figured there is no point in having an index route if you can't go any deeper in the router (this is due to the fact that the todos route and template will render, so there is no real need for the index route. That being said, if you really want it, add an anonymous function and you'll get it.
Todos.Router.map(function () {
this.resource('todos', { path: '/' },function () {});
});
https://github.com/emberjs/ember.js/issues/3995
I had this problem too. After much perplexity, the solution was to use a version of index.html that actually had the <script> wrapped <ul>, instead of the one where I was moving that block around to see if it made a difference and ended up having it nowhere.

Categories