So, this is what my model looks like (represented by fixture data):
var posts = [{
id: 'b026324c6904b2a9',
title: "My new front door",
author: { name: "Matthieu" },
date: new Date('2013-10-28T12:19:30.789'),
status: 'new',
hidden_message: "hidden1"
}, {
id: '26ab0db90d72e28a',
title: "Best pizza in town",
author: { name: "Harry" },
date: new Date('2013-10-28T12:19:30.789'),
status: '',
hidden_message: "hidden2"
}, {
id: '6d7fce9fee471194',
title: "Skateboard dreamland",
author: { name: "Matthieu" },
date: new Date('2013-10-28T12:19:30.789'),
status: 'solved',
hidden_message: "hidden3"
}, {
id: '48a24b70a0b37653',
title: "my house looks like a pumpkin",
author: { name: "Harry" },
date: new Date('2013-10-28T12:19:30.789'),
status: '',
hidden_message: "hidden4"
}];
My route:
App.IndexRoute = Ember.Route.extend({
model: function() {
return posts;
}
});
And, I'd like to be able to display a certain piece of HTML in the template if the corresponding post is new, a different one if it's solved, and nothing if the status is blank. It seems to me as if the best way to do this is using an {{#if}} helper, but that doesn't do equality comparisons, it can only take a boolean variable. So, I'd like to do something like this:
App.IndexController = Ember.ArrayController.extend({
isNew: function(val) {
if(this.get('currentitem.status') === 'new') {
return true;
}
return false;
}.property('isNew')
});
But I can't find out how to select the item being currently accessed by {{#each}} in the template. Is this even possible, and if yes, how do I do it (or something similar)?
Thanks all!
The correct way to do this is to create an itemcontroller that helps you by providing a controller per item in your collection.
App.IndexController = Ember.ArrayController.extend({
itemController: "PostItem",
});
App.PostItemController = Ember.ObjectController.extend({
isNew: function() {
if(this.get('status') === 'new') {
return true;
}
return false;
}.property('status')
});
Then in your handlebar template you can just call {{isNew}} in the {{#each}}-context.
I've put up a working fiddle that you can test it out in.
http://jsfiddle.net/LordDaimos/v8NR3/1/
Best way would probably be to wrap each post in an object that has the isNew method, like this:
var postObject = Ember.Object.extend({
isNew: function() {
if(this.get('status') === 'new') {
return true;
}
return false;
}.property('status')
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return posts.map(function(post){
return postObject.create(post);
});
}
});
this way you could query on each post.
Related
I am using aldeed:autoform in order to render a form and run its result through a Meteor.method(). My form looks as follows:
SelectPlanTemplates = new SimpleSchema({
templates: {
type: [String],
autoform: {
options: function() {
return PlanTemplates.find().map(function(doc) {
return { label: doc.title, value: doc._id };
});
},
noselect: true
}
},
userId: {
type: String,
allowedValues: function() {
return Meteor.users.find().map(function(doc) {
return doc._id;
});
},
autoform: {
omit: true
}
}
});
On my template, I just do the following.
+ionContent
+quickForm(schema="SelectPlanTemplates" id="SelectPlanTemplatesForm" type="method" meteormethod="createPlanFromTemplates")
My url is constructed like /plan/from_templates/{:userId}. I tried creating a hook to add the user id before submitting it.
AutoForm.hooks({
SelectPlanTemplatesForm: {
before: {
method: function(doc) {
doc.userId = Router.current().params.userId;
return doc;
}
}
}
});
However, it never seems to get to this hook.
How would I take a route parameter and pass it with my form to a meteor method with auto form?
I think I figured out a little bit of a weird way of do it.
In the router:
this.route('selectPlans', {
waitOn: function() {
return Meteor.subscribe('plan_templates');
},
path: '/select/plan_templates/:_id',
template: 'selectTemplates',
data: function() {
return new selectPlanTemplates({ userId: this.params._id });
}
});
Then I added doc=this to my template
I'm initializing nested collection like te following:
var post = {
id: 123,
title: 'Sterling Archer',
comments: [
{text: 'Comment text', tags: ['tag1', 'tag2', 'tag3']},
{text: 'Comment test', tags: ['tag2', 'tag5']}
]
};
var PostModel = Backbone.Model.extend({
parse: function (response) {
if (response.comments) {
response.comments = new Backbone.Collection(response.comments);
}
return response;
}
});
var post = new PostModel(post, {parse: true});
How should I remove nested 'comments' collection when removing model?
post.destroy();
You can override destroy method of your PostModel instead of sync (which will not be called in case of a new model without id attribute):
destroy: function(options) {
this.get('comments').each(function(mdl) {
mdl.destroy();
});
Backbone.Model.prototype.destroy.call(this, options)
}
This something can be use to delete comments.
sync : function(method,model,options){
if(method=='delete'){
this.comments.destroy();
}
Backbone.sync(method,model,options);
}
I’m doing a very basic application with Ember and Ember Data.
For some reason I always have the same problem. My application renders and displays the data correctly, but if I remove and search, it doesn't update the view.
I’ve already asked this here—the link has more code examples—but with not much luck. Here is how I’m trying to do it:
App = Ember.Application.create({
LOG_TRANSITIONS: true, LOG_VIEW_LOOKUPS: true
});
App.ApplicationAdapter = DS.FixtureAdapter.extend();
App.Sample = DS.Model.extend({ name: DS.attr('string') });
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('sample');
}
});
App.IndexController = Ember.ArrayController.extend({
actions: {
remove: function(sample) {
sample.destroyRecord();
}
}
});
App.Sample.FIXTURES = [
{ id: 1, name: 'Learn Ember.js'},
{ id: 2, name: 'Record 2' },
{ id: 3, name: 'Test Delete' }
];
App.ApplicationRoute = Ember.Route.extend({
actions: {
showModal: function(name, content) {
this.controllerFor(name).set('content', content);
this.render(name, {
into: 'application',
outlet: 'modal'
});
},
removeModal: function() {
this.disconnectOutlet({
outlet: 'modal',
parentView: 'application'
});
}
}
});
App.MyModalComponent = Ember.Component.extend({
actions: {
ok: function() {
this.$('.modal').modal('hide');
this.sendAction('ok');
}
},
show: function() {
this.$('.modal').modal().on('hidden.bs.modal', function() {
this.sendAction('close');
}.bind(this));
}.on('didInsertElement')
});
From your code I have tried to come up with a reasonable solution for your problem
Before I get into the solution I think the controller should be IndexController rather than sampleDeleteModalController because ember expects controller to have same name as the route.
App.SampleDeleteModalController = Ember.ObjectController.extend({
actions: {
remove: function() {
// Two ways
this.get('model').destroyRecord();
this.transitionToRoute('index');
}
}
});
transitionToRoute from the same route will not refresh a view.This will work only when you want to redirect to another route.
Solution to refresh view
option 1 : you can capture the same action inside index route after removing the record you can do this.refesh() which refreshes the model.
option 2 : You have to explicitly update the binded model inside the controller.
actions: {
remove: function() {
// Two ways
var localCopy = this.get('model');
localCopy.destroyRecord();
this.set('model',localCopy);
}
}
option 3 : After you set your model your model and then do this.rerender().Which is almost equivalent to window.reload()
sorry if the question is a little abstract from the title. I will try to explain it here and supply a Gist with relevant code.
I have a JSON api which I grab through AngularJS. This is basically a project which has several tasks. I want to go through the tasks in my $scope.projects variable (in my projects controller) and get all the 'progress' values in each task. Then I want calculate the average of them to give the overall progress for the project itself and assign it to a $scope variable to be used in my template.
I cant seem to get access to the tasks array no matter what I try and I have no idea why. So i thought asking here for some advice might be a good option. Any help would be greatly appreciated.
Gist: https://gist.github.com/Tasemu/8002741
JS
App.controller('ProjectCtrl', ['$scope', 'Project', 'Task', '$routeParams', '$location', function($scope, Project, Task, $routeParams, $location) {
$scope.updateProjects = function() {
if (!$routeParams.id) {
Project.query(function(data) {
$scope.projects = data;
});
} else {
$scope.projects = Project.get({id: $routeParams.id})
}
};
$scope.deleteProject = function(project) {
Project.delete({id: project.id}, function() {
$scope.updateProjects({id: project.id});
$location.path('/');
});
};
$scope.deleteTask = function(task) {
Task.delete({id: task.id}, function() {
$scope.updateProjects({id: task.id});
});
};
$scope.updateProject = function(formData) {
$scope.projects.name = formData.name;
$scope.projects.description = formData.description;
$scope.projects.client = formData.client;
$scope.projects.due = formData.due;
$scope.projects.$update({id: formData.id}, function() {
$location.path('/');
});
};
$scope.saveProject = function(project) {
Project.save({}, project, function() {
$location.path('/');
});
};
$scope.updateProjects();
$scope.progs = [];
for (var i = 0; i > $scope.projects.tasks.length; i++) {
progs.push($scope.projects.tasks.array[i].progress);
};
}]);
JSON
{
id: 1,
name: "Project 1",
description: "this project",
client: "monty",
due: "2013-12-15",
tasks: [
{
id: 2,
name: "Task 2",
progress: 22,
project_id: 1,
created_at: "2013-12-17T03:08:53.849Z",
updated_at: "2013-12-17T05:06:31.602Z"
},
{
id: 1,
name: "Task 1",
progress: 75,
project_id: 1,
created_at: "2013-12-17T03:08:53.845Z",
updated_at: "2013-12-17T05:25:50.405Z"
}
],
created_at: "2013-12-17T03:08:53.719Z",
updated_at: "2013-12-17T06:57:52.699Z"
}
JS
App.factory('Project', function($resource) {
return $resource(
'/api/v1/projects/:id.json',
{id: '#id'},
{
update: {
method: 'PUT',
params: { id: '#id' },
isArray: false
}
}
);
});
If you need any more information please don't hesitate to ask!
You have to delay the progress calculation (line 46 to 48 in project_controller.js) until your data is fully loaded. Extract the calculation into a new method and call this new method in the callback on line 6 and in a new callback you have to create at line 9.
You are trying to access project.tasks before it is ready. I suggest you wrap the calculation in a function and call it in the success callback when retrieving/updating projects.
$scope.updateProjects = function() {
if (!$routeParams.id) {
Project.query(function(data) {
$scope.projects = data;
$scope.calculateTasks();
});
} else {
$scope.projects = Project.get({id: $routeParams.id})
}
};
...
$scope.calculateTasks = function () {
$scope.progs = [];
for (var i = 0; i > $scope.projects.tasks.length; i++) {
$scope.progs.push($scope.projects.tasks.array[i].progress);
};
}
$scope.updateProjects();
I'm struggling to get a simple master-detail scenario working with Backbone. Here's the jsfiddle and code is below.
Problem 1: this navigation doesn't work at all if I switch "pushstate" to true. What I really want is to have no hashes/pound signs in my urls.
Problem 2: my users might rock up on a url like /accommodation/287, not always on the home page. How would you deal with that using the router?
Thanks a lot for any help!
var AccommodationItem = Backbone.Model.extend({
defaults: {
html: "",
loaded: false
},
urlRoot: "/Home/Accommodation/"
});
var AccommodationItemView = Backbone.View.extend({
tagName: "li",
template: _.template("<a href='#accommodation/<%= id %>'><%= description %></a>"),
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var AccommodationList = Backbone.Collection.extend({
model: AccommodationItem
});
var DetailView = Backbone.View.extend({
initialize: function () { },
render: function () {
this.$el.html(this.model.get("html"));
},
setModel: function (model) {
this.model = model;
var $this = this;
if (!this.model.get("loaded")) {
/*
this.model.fetch({ success: function () {
$this.model.set("loaded", true);
$this.render();
}
});*/
$this.model.set("html", "<h2>Full item " + this.model.get("id") + "</h2>");
$this.model.set("loaded", true);
$this.render();
} else {
$this.render();
}
}
});
var AccommodationListView = Backbone.View.extend({
tagName: "ul",
initialize: function () {
this.collection.on("reset", this.render, this);
},
render: function () {
this.addAll();
},
addOne: function (item) {
var itemView = new AccommodationItemView({ model: item });
this.$el.append(itemView.render().el);
},
addAll: function () {
this.collection.forEach(this.addOne, this);
}
});
var App = new (Backbone.Router.extend({
routes: {
"": "index",
"accommodation/:id": "show"
},
initialize: function () {
this.detailView = new DetailView({ model: new AccommodationItem({ id: 1 }) });
$("#detail").append(this.detailView.el);
this.accommodationList = new AccommodationList();
this.accommodationListView = new AccommodationListView({ collection: this.accommodationList });
$("#app").append(this.accommodationListView.el);
},
start: function () {
Backbone.history.start({ pushState: false });
},
index: function () {
this.fetchCollections();
},
show: function (id) {
var model = this.accommodationList.get(id);
this.detailView.setModel(model);
},
fetchCollections: function () {
var items = [{ id: 1, description: "item one" }, { id: 2, description: "item two" }, { id: 3, description: "item three" }];
this.accommodationList.reset(items);
}
}));
$(function () {
App.start();
});
EDIT: In a comment below I mentioned the Codeschool backbone.js tutorial. Just want to say that I have now finished BOTH parts of the course and it DOES cover exactly the AppView pattern described in the accepted answer. It's an excellent course and I thoroughly recommend it.
you have a few of the concepts mixed up.
There is too much to explain here, so I've (very roughly) put together a patch of your code that works as you intend. I would advise that you put it side-by-side with your own and see what I have done differently.
http://jsfiddle.net/wtxK8/2
A couple of things, you should not init Backbone.history from within a router. your 'init' should look something more like this
$(function () {
window.app = new App();
window.appView = new AppView({el:document});
Backbone.history.start({ pushState: true });
});
This is setting a 'wrapper' view than encompasses the entire page. Also, you have far too much logic in your router. Try to only use the router for routes. After my quick re factor, your router only contains this:
var App = Backbone.Router.extend({
routes: {
"": "index",
"accommodation/:id": "show"
},
show: function (id) {
var model = window.appView.accommodationList.get(id);
window.appView.detailView.setModel(model);
}
});
The AppView (that I have written for you now does all of that initialize work.
var AppView = Backbone.View.extend({
initialize : function(){
this.detailView = new DetailView({ model: new AccommodationItem({ id: 1 }) });
$("#detail").append(this.detailView.el);
this.accommodationList = new AccommodationList();
this.accommodationListView = new AccommodationListView({ collection: this.accommodationList });
$("#app").append(this.accommodationListView.el);
this.fetchCollections();
},
fetchCollections: function () {
var items = [
{ id: 1, description: "item one" },
{ id: 2, description: "item two" },
{ id: 3, description: "item three" }
];
this.accommodationList.reset(items);
}
});
Even after my re factor, it's still far from optimal, but I have provided it all to help you on your journey of learning :)
I would then recommend you follow some of the on-line tutorials step-by-step so that you can set up the structure of your app in a better way.
Good Luck, and be sure to check out http://jsfiddle.net/wtxK8/2 to see it working.
EDIT: I have not address your second question. there is enough to be worked on with question 1 to keep you busy. If I have more time later, I will help further.