I have a page where a user edits an uploaded photo and applies a tag for individual photos on the model using Ember-Data. However, after saving on the controller, and transitioning to a page with all of the photos listed, the tag appears on all of the items and replaces any that existed before. If I reopen the page the tag has not saved at all.
I'm not quite sure what is causing this issue. Any help would be appreciated.
//The photo model
App.Photo = DS.Model.extend({
title: attr(),
description: attr(),
image: attr(),
width: attr(),
height: attr(),
important_top: attr(),
important_left: attr(),
important_bottom: attr(),
important_right: attr(),
created: attr('date'),
authors: hasMany('author'),
app_data: {
tags: []
},
imageURL: function() {
return document.location.origin + '/media/' + this.get('image');
}.property('image'),
});
// Photo edit route
App.PhotoseditRoute = Ember.Route.extend({
model: function() {
this.store.find('photo');
// Populate model with photos from the lowest upload ID to higest.
return this.store.filter('photo', function(image){
return image.get('id') >= App.Upload.uploadedImages[0]; // Get data from uploader
});
},
activate: function() {
$('#page-title').text('Edit Photos');
},
});
// Photo Edit Controller
App.PhotoseditController = Ember.ObjectController.extend({
parsedTags: function() {
// Get tags from the view's input field
var tagData = this.get('app_data').tags;
// Convert tags to an array
return tagData.join(',');
}.property('app_data'),
// Watch parsedTags and apply array to model when converted
parsedDataChanged: function() {
Ember.run(this, function() {
this.get('app_data').tags = this.get('parsedTags').split(',');
});
}.observes('parsedTags'),
actions: {
save: function() {
var that = this;
that.get('model').save().then(function(success) {
that.transitionToRoute('photos');
});
}
}
});
// Photo edit template
<h2>Edit Photo Meta Data</h2>
<button {{action 'save'}} style="float:right;">Save All</button>
<table>
<thead>
<tr>
<th></th>
<th>Title</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{{#each object in content itemController='photosedit'}}
<tr>
<td><img {{bind-attr src="imageURL"}} width="200" /></td>
<td>{{input title valueBinding="title"}}</td>
<td>{{input description valueBinding="description"}}</td>
<td>{{input parsedTags valueBinding="parsedTags"}}</td>
</tr>
{{else}}
<tr>
<td colspan="6">No photos yet.</td>
</tr>
{{/each}}
</tbody>
</table>
The problem comes from the way you declare app_data. This variable will be shared across all instances of App.Photo, which explains why you see all photos getting the same tags.
You can solve this by initializing the attribute differently:
App.Photo = DS.Model.extend({
init: function() {
this._super();
this.app_data = { tags: [] };
}
});
instead of
App.Photo = DS.Model.extend({
app_data = { tags: [] }
});
See this JsBin for an example highlighting the problem and the solution http://emberjs.jsbin.com/wawoq/3
You need to check that the store gets called with the correct data when you call save() and trace back from there.
Aside from this, parsedTags and parsedDataChanged seem to be referring to each other.
Related
I'm using CollectionFS to allow image uploads. The image uploads need to belong to specific posts. I followed the steps from the documentation - Storing FS.File references in your objects - however, I'm having a hard time displaying the image of the associated post.
The post currently saves with a postImage that references an image._id - this part is working fine. However, I am unsure how to display the actual photo, as it will need to grab the photo from the images collection (the post collection just saves an ID to reference).
post-list.html
<template name="postList">
<tr data-id="{{ _id }}" class="{{ postType }}">
...
<td><textarea name="postContent" value="{{ postContent }}"></textarea> </td>
<td>{{ postImage._id }} </td> // This currently displays the correct image._id, but I would like to display the image,
<td><button class="delete tiny alert">Delete</button></td>
</tr>
</template>
post-list.js
Template.postList.helpers({
posts: function() {
var currentCalendar = this._id;
return Posts.find({calendarId: currentCalendar}, {sort: [["postDate","asc"]] });
}
});
post-form.js - This form creates a new Post and Image. The Image._id is saved to the Post.postImage.
Template.postForm.events({
// handle the form submission
'submit form': function(event) {
// stop the form from submitting
event.preventDefault();
// get the data we need from the form
var file = $('.myFileInput').get(0).files[0];
var fileObj = Images.insert(file);
var currentCalendar = this._id;
var newPost = {
...
calendarId: currentCalendar,
owner: Meteor.userId(),
postImage: fileObj,
};
// create the new poll
Posts.insert(newPost);
}
});
use reywood:publish-composite and dburles:collection-helpers so;
Collections || collections.js
Posts = new Mongo.Collection('posts');
Images = new FS.Collection("files", {
stores: [
// Store gridfs or fs
]
});
Posts.helpers({
images: function() {
return Images.find({ postId: this._id });
}
});
Template || template.html
<template name="postList">
{{# each posts }}
{{ title }}
{{# each images }}
<img src="{{ url }}">
{{/each}}
{{/each}}
</template>
Client || client.js
Template.postList.helpers({
posts: function() {
return Posts.find();
}
});
Template.postList.events({
// handle the form submission
'submit form': function(event, template) {
// stop the form from submitting
event.preventDefault();
// get the data we need from the form
var file = template.find('.myFileInput').files[0];
Posts.insert({
calendarId: this._id,
owner: Meteor.userId()
}, function(err, _id) {
var image = new FS.File(file);
file.postId = _id;
if (!err) {
Images.insert(image);
}
});
}
});
Router || router.js
Router.route('/', {
name: 'Index',
waitOn: function() {
return Meteor.subscribe('posts');
}
});
Server || publications.js
Meteor.publishComposite('posts', function() {
return {
find: function() {
return Posts.find({ });
},
children: [
{
find: function(post) {
return Images.find({ postId: post._id });
}
}
]
}
});
When using CollectionFS, on the client side you need to ensure that your subscriptions are correctly defined. This is the biggest stumbling block i've encountered with my developers in using CFS - understanding and mapping the subscription correctly.
First things first - you need to have a subscription that is going to hit Images collection. I'm not familiar with the latest CFS updates for their internal mapping but the following approach has usually worked for me.
Meteor.publish('post', function(_id) {
var post = Posts.findOne({_id: _id});
if(!post) return this.ready();
return [
Posts.find({_id: _id}),
Images.find({_id: post.postImage})
]
});
For displaying, there is a handy CFSUI package( https://github.com/CollectionFS/Meteor-cfs-ui ) that provides some nice front end handlers.
With the above mapping your subscription, you can then do something like
{{#with FS.GetFile "images" post.postImage}}
<img src="{{this.url store='thumbnails'}}" alt="">
{{/with}}
I'm trying to implement an interface for a logger with EmberJS and has been great but I run into an wall.
Currently I have the following code:
logs.hbs
...
{{input action="search" onEvent="keypress" value=searchText class="search" placeholder="Search"}}
...
<table id="log" class="log">
<thead>
<th>Service</th>
<th>Start Time</th>
<th>End Time</th>
<th>URL</th>
<th>Method</th>
<th>User</th>
<th>Response Code</th>
</thead>
<tbody>
{{render "dashboard/logTableLine" model}}
</tbody>
</table>
...
logTableLine.hbs
{{#each model}}
{{#link-to "dashboard.log" _id tagName="tr"}}
<td>{{ServiceID}}</td>
<td>{{DateString StartTime}}</td>
<td>{{DateString EndTime}}</td>
<td>{{URL}}</td>
<td>{{Method}}</td>
<td>{{AuthName Auth}}</td>
<td>{{StatusCode}}</td>
{{/link-to}}
{{/each}}
and my app.js
App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true
});
App.Router.map(function(){
this.resource("dashboard", { path: "/" }, function(){
this.route("logs", { path: "/logs" });
this.route("log", { path: "/logs/log/:_id" });
});
});
App.DashboardLogsRoute = Ember.Route.extend({
model: function(){
return Ember.$.getJSON("/logs.json");
},
actions: {
search: function(value){
if(value != '')
this.set("content", Ember.$.getJSON("/logs.json?search=" + value));
}
}
});
App.DashboardLogRoute = Ember.Route.extend({
model: function(params){
return Ember.$.getJSON("/logs/log/" + params._id + ".json");
}
});
My problem is binding that model to the view, so that after the search call to the server the view rerenders. I want to search all data, not only what I have in the interface (last 100 records).
So, first question: why the view isn't updating (binded) to the model?
Second: Is there a better way (best pratice) to do this?
Thanks in advance for any help.
Right now you're implementing search without triggering a re-route. So if the user searched and then wanted to go back to the page before they were at before they searched, they would have no way to do that. What you want to do is re-route the user to the same page, but pass in query parameters. Then in the model function you can see what parameters were passed in and fetch the data accordingly. Ember's latest and greatest way of doing this is the query-params feature: http://emberjs.com/guides/routing/query-params/ It's been in the works for a while and is now in beta. I'd give it a go, because it's a really clean, intuitive way of solving this problem. Good luck!
I have a table thats generated by an {{#each}} loop and it displays user's first and last names. I want to add a delete button to delete the record on that row. I am also using EmberFire to connect with Firebase.
What is the best way to associate the data in that row with that delete button?
Heres the wee bit of relevant code I have:
index.html
{{#each}}
<tr>
<td>
{{this.first}}
</td>
<td>{{this.last}}</td>
<td>
<button>X</button>
</td>
</tr>
{{/each}}
router.js
App.IndexRoute = Ember.Route.extend({
model: function() {
return EmberFire.Array.create({
ref: new Firebase(FirebaseRef + 'testObj')
});
},
renderTemplate: function() {
this.render('index');
this.render('users', {
outlet: 'users',
into : 'index'
});
}
});
controller.js
App.IndexController = Ember.ArrayController.extend({
actions: {
register: function() {
this.pushObject({
'first' : this.get('firstName'),
'last' : this.get('lastName')
});
}
}
})
Thanks!
You could add a delete action to your IndexController:
App.IndexController = Ember.ArrayController.extend({
actions: {
register: function() {
this.pushObject({
'first' : this.get('firstName'),
'last' : this.get('lastName')
});
},
delete: function(person) {
this.content.removeObject(person);
}
}
})
Then add the following to your index.html:
{{#each}}
<tr>
<td>
{{this.first}}
</td>
<td>{{this.last}}</td>
<td>
<button {{action "delete" this}}>X</button>
</td>
</tr>
{{/each}}
I'm new to Ember and Handlebars.
I am trying to create a table with a dynamic content.
I add the code into jsfiddle
I have the following code:
<script type="text/x-handlebars">
<table border="1" bordercolor="FFCC00" style="background-color:FFFFCC" width="400" cellpadding="3" cellspacing="3">
{{#each Table.tableController}}
<tbody>
<tr>
<td width ="30%">{{title}}</td>
<td width ="30%">{{artist}}</td>
<td width ="40%">{{genre}}</td>
</tr>
</tbody>
{{/each}}
</table>
A simple table binding a content from my controller.
And here is my controller and application:
Table = Ember.Application.create({});
Table.Cell = Ember.Object.extend({
title: null,
artist: null,
genre: null,
listens: 0
});
Table.tableController = Ember.ArrayProxy.create({
content: [],
init: function() {
var data = Table.Cell.create({
title: 'Ruby',
artist: 'Kaiser Chiefs',
genre: 'Indie Rock',
});
this.pushObject(data);
data = Table.Cell.create({
title: 'Somebody Told Me',
artist: 'Killers',
genre: 'Indie Rock',
});
this.pushObject(data);
},
createSong: function(title, artist, genre) {
var cell = Table.Cell.create({
title: title,
artist: artist,
genre: genre,
});
this.pushObject(cell);
},
});
That's working just fine.
Now I want to add a new song, and I am doing this via Chrome console with the following line:
Table.tableController.createSong('Feiticeira', 'Deftones', 'Alternative');
I can verify that a new Object is created and inserted into my content array.
But there is no changes into my table.
What am I doing wrong?
Why my table is not creating a new content?
I pushed this code into jsfiddle to get help you help me.
Here's the link to the code again.
UPDATED ANALYSIS
You forgot calling this._super(); in the init, which prevented your instance to have its binding management setup. (Fixed revision)
You felt in a typical Ember gotcha: initial values should never be objects, as they would be shared by all class instances... See this article for more informations.
FIX SAMPLE - Still recommended implementation
Updated your JSFiddle # http://jsfiddle.net/MikeAski/AgyAk/12/ with a few improvements (The most notable: make use of a controller rather than directly ArrayProxy. It is more idiomatic and ready to switch to Router infrastructure).
I'm new with emberjs so it may be possible that I totally missed a few things.
I am trying to render the body of a table with content I retrieve from an Ajax request.
I use an ArrayController and a {{#collection}} in my view that I bind to the content of the controller (as the doc suggests).
It works when I set an initial value to content inside my ArrayController but it doesn't automatically update when I call .set('content', [...]).
Note that it only fails when I set the tagName of my view to 'tobdy', it works if for example I use 'pre' (or pretty much anything else).
JSFiddles that show that issue:
http://jsfiddle.net/midu/DjxG4/10/ (with tbody, should update after 2s, but doesn't)
http://jsfiddle.net/midu/DjxG4/11/ (with pre, updates after 2s)
My questions are:
is this a bug or did I not understand how it should be working?
any advice for rendering the body of a table in a template?
This is what my code looks like:
index.html
<table id="the-table">
<thead>
<tr>
<th>status</th>
<th>title</th>
<th>url</th>
<th># messages</th>
<th>judging status</th>
</tr>
</thead>
</table>
<script type="text/x-handlebars" data-template-name="submissions">
{{#collection contentBinding="App.submissionsController"}}
<tr>
<td>{{content.status}}</td>
<td>{{content.title}}</td>
<td>note</td>
<td>{{content.nbMessages}}</td>
<td>{{content.judgingStatus}}</td>
</tr>
{{/collection}}
</script>
index.js
var App = Em.Application.create({
ready: function () {
App.submissionsController.index();
this._super();
}
});
// model
App.Submission = Em.Object.extend({
title: null,
judgingStatus: null,
status: null,
url: null,
nbMessages: 0
});
// controller... not really.
App.submissionsController = Em.ArrayController.create({
content: [ App.Submission.create({title: 'kevin', status: 'a', judgingStatus: 'b', url: 'http://google.com', nbMessages: 1}) ],
index: function () {
// simulates data that arrives to the page after a few seconds (think ajax request)
window.setTimeout(function () {
App.submissionsController.set('content', [
App.Submission.create({title: 'stefano', status: 'c', judgingStatus: 'd', url: 'http://stackoverflow.com', nbMessages: 2})
]);
}, 2000);
}
});
Em.View.create({
templateName: 'submissions',
tagName: 'tbody'
}).appendTo('#the-table');
Two things I did to fix the code which can be seen on jsfiddle.
I would intermittently get uncaught exception: Error: <Ember.View:ember161> - Unable to find template "submissions" because the handlebar script is not evaluated before the app is inserted into the DOM. See here.
Change the view to {{#each}} instead of the deprecated {{#collection}}. I haven't found a definite source on the deprecation – but it's indicated here and here.