Emberjs adding objects to ArrayController, server query is immutable - ember.js

I'm trying to add objects to my Emberjs Arraycontroller. I have a "create" action that fires when a button is pushed. This works fine but I can't seem to add the element with the this.pushObject function to the ArrayController. I get this error message:
Uncaught Error: The result of a server query (on App.Software) is immutable.
I guess this is because I'm using the RESTAdapter to load data and it does not like that I'm adding elements manually?
Here is my controller and the create action.
App.SoftwareIndexController = Ember.ArrayController.extend({
sortProperties: ['revision'],
create:function(){
var revision = $('#software_revision').val();
var doc = $('#software_document').val();
var software = App.Software.createRecord({
product_id: 1,
revision: revision,
doc: doc
});
this.pushObject(software);
}
});
Here is the route
App.SoftwareIndexRoute = Ember.Route.extend({
setupController:function(controller){
var product_id = 1;
controller.set('content', App.Software.find({product_id:1}));
}
});
Here is the model and store
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.RESTAdapter'
});
DS.RESTAdapter.configure("plurals", {
software: "software"
});
App.Software = DS.Model.extend({
revision: DS.attr('string'),
doc: DS.attr('string'),
verified: DS.attr('boolean')
});
And here is the template view with the create form and list of software
<script type="text/x-handlebars" data-template-name="software/index">
<p>
<fieldset>
<legend>Create a new software revision</legend>
<label for="software_revision">Revision</label>
<input id="software_revision" name="software_revision" type="text" placeholder="">
<label for="software_document">Document ID</label>
<input id="software_document" name="software_document" type="text" placeholder="">
<button class="btn btn-success" {{action create}}>Create</button>
</fieldset>
</p>
{{#if length}}
<table class="table">
<thead>
<tr>
<th>Revision</th>
<th>Created</th>
</tr>
</thead>
<tbody>
{{#each controller}}
<tr>
<td>{{revision}}</td>
<td>{{createdAt}}</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<div class="alert alert-info">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong>No software revisions found!</strong> start by creating a new revision above.
</div>
{{/if}}
</script>
Does anybody know the proper way to add new object to a ArrayController store? Thank you!
This works by the way if I change the route so it doesn't use the RESTAdapter
App.SoftwareIndexRoute = Ember.Route.extend({
setupController:function(controller){
var product_id = 1;
controller.set('content', []); // not using the RESTAdapter to load data
}
});

I had this same problem. It looks like the content returned from App.Software.find() is an immutable array. I was able to get around this by getting the content of the immutable array which returned a mutable array.
For example:
In ember 1.0.0 + ember data 1.0.0 beta 3:
this.store.find('software').then(function (softwares) {
controller.set('content', softwares.get('content'));
});
And this is only a guess but for the version you were working with at the time of your post:
controller.set('content', App.Software.find().get('content'));

Related

emberjs instead of rendering component this was rendered <!---->

i am new to emberjs and i am trying to iterate through arr and render a table with the content of elements of the array the each loop renders <!----> instead of rows in the table
what am i doing wrong?
here is my code:
app.js
arr=[{id:1,name:foo,completed:'yes'},{id:2,name:'bar', completed:'no'}]
App= Ember.Application.create();
App.Router.map(function(){
})
App.IndexRoute=Ember.Route.extend({
model: function(){
return arr;
}
})
App.SingleTaskComponent = Ember.Component.extend({
templateName: "components/single-task",
tagName: ""
});
App.TasksCollectionComponent = Ember.Component.extend({
templateName: "components/tasks-collection",
tagName: "tbody",
actions: {
newTask: function(){
console.log("here");
}
}
});
and here is the hbs code:
<script type="text/x-handlebars" id="components/single-task">
<tr {{bind-attr id=id}}>
<td>
<input type="text" name="task" {{bind-attr value=name}} >
</td>
<td>
<input type="checkbox" name="completed">
</td>
</tr>
</script>
<script type="text/x-handlebars" id="components/tasks-collection">
<tr>
<td><button>+</button></td>
<td>Tasks</td>
</tr>
{{#each model as |task|}}
{{single-task id=task.id name=task.name completed=task.completed}}
{{/each}}
</script>
<script type="text/x-handlebars" id="index">
<table>
{{tasks-collection}}
</table>
</script>
Your coding looks like you are not using ember-cli. please use it. In this link you can go through enormous projects which are using ember, you can learn from them how are they using it. You can even play with ember-twiddle for easy learning. I will encourage you to follow ember guides tutorial.
Regarding your question,
You need to pass the required data to components,
{{tasks-collection model=model}}
In single-task component, you can avoid bind-attr instead you can use it like
<tr id={{id}}>
Refer deprecation guide bind-attr for more details

Ember didInsertElement on new elements

I have a topic list that receive a bootstrap popover event on page load.
But when I add new topics to this list without reload page I can't bind popover event in this new topic.
My code is organized in this way:
Forum route:
export default Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
run: this.store.find('run', this.modelFor('dialogo.admin.manager').get('id')),
topics: this.store.find('topic', { run: this.modelFor('dialogo.admin.manager').get('id') })
});
},
setupController: function (controller, model) {
controller.set('runID', this.modelFor('dialogo.admin.manager').get('id'));
controller.set('model', model.topics);
controller.set('currentRun', model.run);
}
});
Forum Controller:
export default Ember.Controller.extend({
needs: ['application'],
activeUser: Ember.computed.alias("controllers.application.model"),
actions: {
createTopic: function() {
var
store = this.get('store'),
user = this.get('activeUser'),
run = this.get('currentRun'),
topic;
topic = store.createRecord('topic', {
content: '',
run: run,
moderator: user
});
this.set('newTopic', topic);
},
saveNewTopic: function() {
var newTopic = this.get('newTopic');
var topicList = this.get('model');
newTopic.save().then(function(topic){
topicList.pushObject(topic);
Ember.$('#newTopic').modal('hide');
});
},
deleteTopic: function (topic) {
topic.destroyRecord();
}
}
});
Forum View:
export default Ember.View.extend({
didInsertElement: function() {
this.$('[data-toggle="popover"]').popover({
html : true,
content: function() {
var delID = Ember.$(this).attr('data-del');
return Ember.$('#alertDel' + delID).html();
}
});
}
});
Forum Template:
<tbody>
{{#each topic in model}}
<tr>
<th scope="row">{{topic.id}}</th>
<td>{{topic.content}}</td>
<td>{{topic.posts.length}}</td>
<td>0</td>
<td>
<button class="btn btn-default" type="button" data-toggle="popover" data-placement="top" data-trigger="focus" data-del="{{topic.id}}" title="ATENÇÃO: Deletar tópico">
DEL
</button> |
<span>EDIT</span> |
<span>PEND</span> |
<span>MOD</span>
<div id="alertDel{{topic.id}}" style="display: none;">
Você deseja deletar o tópico #ID {{topic.id}}?
Essa ação não poderá ser desfeita.
<br><br>
<button class="btn btn-danger" {{action 'deleteTopic' topic}}>Sim</button> |
<button class="btn btn-default">Não</button>
</div>
</td>
</tr>
{{else}}
<tr>
<td rowspan="5">Nenhum tópico encontrado...</td>
</tr>
{{/each}}
</tbody>
So, when I execute saveNewTopic and add this new topic to topicList, I lost my popover event.
Anyone have a clue about how can I bind my popover event on new topic instances?
You can refer to ember cookbook on how to handle bootstrap pop over, which the best way would be creating a component or rendering from router with an action.
http://guides.emberjs.com/v1.10.0/cookbook/user_interface_and_interaction/using_modal_dialogs/
App.ApplicationRoute = Ember.Route.extend({
actions: {
showPopUp: function(name, content){
//the controller for will help you let you access the content from your controller (if using a component)
this.controllerFor(name).set('content', content);
this.render(name, {
into: 'application',
outlet: 'popup'
});
},
removePop: function(){
this.disconnectOutlet({
outlet: 'popup',
parentView: 'application'
});
}
}
}
});
In your main application.hbs you need to add the pop up outlet
<setion id="main-content">
{{ outlet }}
</section>
<section id="footer-scroll">
{{partial "partials/footer"}}
</section>
{{outlet 'popup'}}
You would trigger the show pop up event like this
<a href="#" {{action 'showPopUp' 'select-country-pop' this}} class="cl-7 footer-text-desk ft-w-600">
In your forum template you can use it the same way
<button class="btn btn-default" type="button" data-del="{{topic.id}}" title="ATENÇÃO: Deletar tópico" {{action 'showPopUp' 'popuptemplate' this}}>
DEL
</button>
You need to create the pop up template, for your sanity it would be easier if you create a component
"this", just means it would inherit the data from your model

How should I handle an errors from server in Ember.js?

I have a small app, written using ember.js, that talks to REST API using RESTAdapter and displaying elements in list using ArrayController. When I'm performing a POST request to server and the responds with code 201 (Created) and a model with id the new models is displayed in list. But if server returns code 409 (Conflict) with the payload that looks like this
{
'errors': ['Error description']
}
I expect that model will not be added to list, but it isn't so. The model is still added to list, but without ID.
I found out that this model can be removed in multiple ways, but I don't know which of them is right.
So the questions are:
What is the right way to handle errors in this case (when you are using ArrayController and RESTAdapter)?
Is it possible to prevent model showing up in list if server returns 4xx code?
Here is code that I use now:
App.Usergroup = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
});
App.UsergroupsListController = Ember.ArrayController.extend({
needs: ['usergroups'],
allUsergroups: Ember.computed.alias('controllers.usergroups'),
itemController: 'usergroup'
});
App.UsergroupController = Ember.ObjectController.extend({
actions: {
removeGroup: function() {
var group = this.get('model');
group.deleteRecord();
group.save();
}
}
});
App.UsergroupsController = Ember.ArrayController.extend({
actions: {
addUsergroup: function() {
var model = this.get('model');
var group = this.store.createRecord('usergroup', {
name: model.name,
description: model.description
});
group.save().then(function() {
},
function(error){
group.rollback();
});
this.transitionTo('usergroups.index');
}
}
});
And here is the template excerpt:
<script type="text/x-handlebars" id="usergroups">
<div class="header">
<h1>User groups list</h1>
</div>
<div class="content">
<form class="pure-form">
<fieldset>
{{input valueBinding="model.name" placeholder="Name"}}
{{input valueBinding="model.description" placeholder="Description"}}
<button type="submit" {{action 'addUsergroup'}} class="pure-button pure-button-primary">Add</button>
</fieldset>
</form>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" id="usergroups-list">
{{#if model}}
<table class="pure-table pure-table-striped">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{{#each}}
<tr>
<td>{{id}}</td>
<td>{{name}}</td>
<td>{{description}}</td>
<td><button class="pure-button" {{action "removeGroup"}}><i class="fa fa-trash"></i> Remove</button></td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<p>No user groups.</p>
{{/if}}
</script>
Ember provides an error route and a loading route
Read more about it here - Ember error route
So when the Usergroup model throws an error, Ember will look for UsergroupErrorRoute or
usergroup/error template.
What you can do is you can have
<script type="text/x-handlebars" data-template-name="usergroup/error">
{{message}}
<script>
where {{message}} is transferred from the route
So, as I found out, errors should be handled in Route.
Currently my route looks like this:
App.UsergroupsRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.find('usergroup');
},
redirectToLogin: function(transition) {
var loginController = this.controllerFor('login');
loginController.set('attemptedTransition', transition);
this.transitionTo('login');
},
actions: {
error: function(reason, transition) {
if (reason.status == 401) {
this.redirectToLogin(transition);
}
else {
alert('Something went wrong');
}
}
}
});
It works just fine and does what it should. But now I cannot get what is a reason for placing errors handling in Route?

template rendering is not working properly in ember js

I am very new to Ember js. when i route one template to another it working properly.but the problem is when i was render same template it won't work.
My code is follows:
index.html:Manager template in index.html
<script type="text/x-handlebars" data-template-name="manager">
{{#each model.manager}}
<div class="pure-g-r content-ribbon">
<div class="pure-u-1-3">
<div class="img-viewer-profile">
<img {{bindAttr src="image"}}>
</div>
</div>
<div class="pure-u-2-3">
<div class="l-box">
<h4 class="content-subhead">{{name}}</h4>
<table class="pure-table">
<tbody>
<tr class="pure-table-odd">
<td>Id</td>
<td><b>ATL-{{id}}</b></td>
</tr>
<tr class="pure-table-odd">
<td>Team</td>
<td><b>{{team}}</b></td>
</tr>
<tr>
<td>Division</td>
<td><b>{{division}}</b></br>
</tr>
<tr class="pure-table-odd">
<td>Manager</td>
<td>
<b>
<a href="#" {{action "managerinfo" manager_id}}>
{{manager_name}}
</a>
</b>
</td>
</tr>
{{/each}}
<tr>
<td>Reportees</td>
<td>
{{#each model.results}}
<br><b>
<a href="#" {{action "profileinfo" id}}>
{{reportees}}
</a>
</b></br>
{{/each}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{{outlet}}
</script>
app.js:
=========
App.ManagerController = Ember.ObjectController.extend({
manager_id: '',
id: '',
actions:{
managerinfo: function(manager_id) {
// the manager id of selected manager field
var manager_id = manager_id;
alert("profile");
alert(manager_id);
managerdata = $.ajax({
dataType: "json",
url: "/manager?manager_id=" + manager_id,
async: false}).responseJSON
managerdata.manger_id = manager_id;
console.log(managerdata);
this.transitionToRoute('manager', managerdata);
}
)}
Now my problem is when render the new manager data to manager it wont be displayed.
After refresh only it shows the data.
what mistake i was done in this code , i dont understand.so please provide me solution.
i added my complete code here with proper tags order.
As per your comment, the way you are doing is not suggested. You can pass manager_id in the route url. I will give you very basic skeleton which you can make further changes. Do remember, if you want to set model data from ajax, use model hook in the route
First Router
App.Router.map(function() {
this.resource("managers", function(){
this.route("all");
this.route("manager", { path: "/:manager_id" });
});
});
So here with #/manager/all displays all managers list
#/manager/:manager_id displays manager with id manager_id
Next ManagerAll route
App.ManagersAllRoute = Ember.Route.extend({
model: function() {
//do ajax and return managers data which sets as model for this route
}
});
sameway
App.ManagersManagerRoute = Ember.Route.extend({
model: function(params) {
//do ajax and return managers data which sets as model for this route
//here params will have the manager_id passed through url
//as it is ajax, create a promise
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
dataType: "json",
url: "/manager?manager_id=" + params.manager_id,
async: false}).then(function(data){
resolve(data);
})
});
}
});
Next is template for managers
{{#each model.manager}}
{{#link-to 'managers.manager' manager_id}}{{{manager_name}}{{/link-to}}
{{/each}}
Now a template for manager
{{manager_id}}
{{manager_name}} blah blah blah
You didn't close the each tag properly.
<script type="text/x-handlebars" data-template-name="manager">
{{#each model.manager}}
<a href="#" {{action "managerinfo" manager_id}}>
{{manager_name}}
</a>
{{/each}}
</script>
Also you haven't closed the ManagerController correctly in the given above. Double check that.
App.ManagerController = Ember.ObjectController.extend({
manager_id: '',
id: '',
actions:{
managerinfo: function(manager_id) {
// the manager id of selected manager field
var self=this;
var manager_id = manager_id;
alert("profile");
alert(manager_id);
managerdata = $.ajax({
dataType: "json",
url: "/manager?manager_id=" + self.manager_id,
async: false}).responseJSON.then(function(managerdata) {
managerdata.manger_id = self.manager_id;
console.log(managerdata);
this.transitionToRoute('manager', managerdata);
})
}
)}

EmberJS: How to update model attributes

I've got a list of messages that are provided by a Rails backend. What I need is when the "toggle_visibility" action button is pressed, it would toggle the "publicly_viewable" property. This means, making a corresponding REST call (to effect the database) and changing the state of the corresponding cached message. Here is where I'm at so far.
Here's what I've got so far, that manages to end up on the debug console:
# app.js
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.extend({
url: 'http://localhost:3000'
})
});
App.Message = DS.Model.extend({
body: DS.attr('string'),
mobile_number: DS.attr('string'),
publicly_viewable: DS.attr('boolean'),
created_at: DS.attr('date')
});
App.Router.map(function() {
this.resource('messages');
});
App.MessagesRoute = Ember.Route.extend({
model: function() { return App.Message.find() }
});
App.MessagesController = Ember.ArrayController.extend({
toggle_visibility: function(){
debugger;
}
});
# index.html
{{#each model}}
<button class="close" {{action toggle_visibility this}}><i class="icon-eye-close"></i></button>
<p class="message_body lead">{{body}}</p>
<small class="source_number">from {{mobile_number}}, received {{date created_at}}</small>
{{/each}}
I've been spending the past few hours reading through the Ember Guides and while I've gotten an idea on what the different classes there are, I still can't visualize clearly how to go about it. Particularly, I'm not sure if this should be a route concern or a controller, and I know that if ever it was a controller responsibility, I know that it should be on an ObjectController but I've been having trouble making it work.
You can use ArrayController#itemController and define a controller for the individual record in your ModelArray. Then you have to specify in the Array Controller the Object Controller responsible for a single object, which you have to reference as well in Handlebars. You can do something like this:
JS:
App.MessageController = Ember.ObjectController.extend({
visibilityClass: function() {
var visibility = this.get('model.publiclyViewable');
return 'toggle-visibility mdi-action-visibility%#'.fmt(
visibility ? '':'-off'
);
}.property('model.publiclyViewable'),
actions: {
toggleVisibility: function() {
var model = this.get('model');
model.toggleProperty('publiclyViewable');
model.save();
}
}
});
Handlebars:
<script type="text/x-handlebars" data-template-name="messages">
<!--
At this point the {{each}} helper will know how to lookup for
the controller simply by it's name
-->
{{#each model itemController="message"}}
<div class="panel panel-primary">
<div class="panel-heading">
<div class="pull-left">
<h3 class="panel-title">{{title}}</h3>
</div>
<div class="pull-right">
<a {{action 'toggleVisibility'}}>
<i class={{visibilityClass}} style="color: #FFF"></i>
</a>
</div>
</div>
<div class="panel-body">
{{body}}
</div>
<div class="panel-footer">
<i class="mdi-communication-quick-contacts-dialer"></i> {{mobileNumber}}
<i class="mdi-notification-event-note"></i> {{createdAt}}
</div>
</div>
{{/each}}
</script>
(see fiddle)
Note: Updated to Ember 1.11.x-beta and changed the code a little bit