With emberjs (1.0.0rc1) and ember-data (very recent build #36d3f1b), I am trying to setup a basic crud example. I can't figure out how to retrieve a submitted model from a view and then update/save it. Here is what my code looks like:
App = Ember.Application.create();
App.Router.map(function() {
this.resource('posts', function() {
this.route('create');
this.route('edit', {
path: '/:post_id'
});
});
});
App.PostsIndexRoute = Ember.Route.extend({
model: function() {
return App.Post.find();
}
});
App.PostsCreateView = Ember.View.extend({
submit: function () {
console.log(this.get('model')); // undefined
}
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
body: DS.attr('string')
});
App.Post.FIXTURES = [{
id: 2,
title: 'a',
body: 'aa'
}, {
id: 5,
title: 'b',
body: 'bb'
}];
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.FixtureAdapter.create({
simulateRemoteResponse: false
})
});
and the create template:
<script type="text/x-handlebars" data-template-name="posts/create">
{{#view App.PostsCreateView tagName="form" classNames="form-horizontal"}}
<h3>Create</h3>
<div class="control-group">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input type="text" id="title" placeholder="Title" />
{{view Ember.TextField valueBinding="title"}}
</div>
</div>
<div class="control-group">
<label class="control-label" for="body">Body</label>
<div class="controls">
<input type="password" id="body" placeholder="Body" />
</div>
</div>
<div class="control-group">
<div class="controls">
<button class="btn">Create</button>
</div>
</div>
<div>{{#linkTo 'posts'}}Back{{/linkTo}}</div>
{{/view}}
</script>
How can I access the value of the form (serialized to the model) from the submit hook? Secondly, how do I then persist this via the FixtureAdapter?
The first part of your question is tricky to answer because it's actually pretty simple, but in order for it to be simple you'll need to change the way you think about model CRUD. Your "submit" function is not needed. When you instantiate a view) it should have an instance of your model bound to it. (If you're creating a new one it will be a new, empty instance.) When you make changes to that model in the view, they are made instantly; no need for submit. (After all, what would you submit to?)
I'm not sure this actually answers your question, but maybe it puts you on a track to answering it.
I can be a lot more definite about your second question, persisting a value via the FixturesAdapter: you can't. The FixturesAdapter is just that, an adapter for loading fixtures (essentially read-only data) into the store. Changes made to models from the FixturesAdapter will only last until the app is reloaded. To persist data you will need to transition from the FixturesAdapter to a different adapter (probably the RestAdapter).
this article deals with building a small example app including a creation form (it's originally in portuguese, but fortunately, google translate doesn't garble the text too much in this case).
the code can be found here (the important part is way down the page) and a live example here.
Related
I need some enlightenment.
Right now, I'm learning vue.js, and I want to know how to take all data value from input form?? I want take all data from there & save into my database. But, I still don't know, how to get all these data.. thx
<div id="example-1">
<form>
<input v-model="info.name">
<input v-model="info.nickname">
<input v-model="info.gender">
<input type="submit" name="" value="Submit" v-on:click="submitData">
</form>
</div>
<script>
var example1 = new Vue({
el: '#example-1',
data: {
info: {
name: '',
nickname: '',
gender: ''
}
}
},
methods: {
submitData: function() {
console.log(this.info);
// this.$http.post('/api/something', JSON.stringify(this.info));
}
})
</script>
I bet you need to prevent the default action. When you submit a form, the page automatically reloads. You won't ever see a response from the server in the console because it will clear when the page refreshes after the submission action.
Go here, and ctrl + f 'Event Modifiers'. You'll want to use .prevent in this case, then write your own code to handle the submission / response.
How can you re-use a template? Consider the following javascript object:
{
MyName: '',
Address: {
Street: ''
},
MyEmployer: {
CompanyName: '',
Address: {
Street: ''
}
}
}
And a template file at /templates/myTemplate.html:
<div ng-app="someapp" ng-controller="somecontroller">
MyName: <input type="text" ng-model="MyName" />
<div ng-include="'/templates/address.html'"></div>
My Company: <input type="text" ng-model="MyEmployer.CompanyName" />
<div ng-include="'/templates/address.html'"></div>
</div>
Here is how I would imagine the address template file at /templates/address.html would look like:
<div>
Street: <input type="text" ng-model="Street" />
</div>
As you can see i'm trying to re-use the address template here. So how do you pass the proper objects to this template?
Answer
I managed to solve the problem myself and wanted to post it here for the next person who might be looking for the same treat.
My Solution
In my OP I had a model object with two addresses, one for the person's home and another for his/her employer. So taken the same object model and same address template still at /templates/address.html, we can add two additional controllers:
app.controller('personAddressController', ['$scope', function($scope) {
$scope.Street = function(newValue) { return arguments.length ? model.Address.Street = newValue : model.Address.Street; }
}]);
app.controller('companyAddressController', ['$scope', function($scope) {
$scope.Street = function(newValue) { return arguments.length ? model.MyEmployer.Address.Street = newValue : model.MyEmployer.Address.Street; }
}]);
And ng-include usage for our address template:
<div ng-include="'/templates/address.html'" ng-controller="personAddressController"></div>
<div ng-include="'/templates/address.html'" ng-controller="companyAddressController"></div>
Also we need to modify our address template:
<div>
Street: <input type="text" ng-model="Street" ng-model-options="{ getterSetter: true }" />
</div>
<!--or add a form if you have more fields like i did-->
<form ng-model-options="{ getterSetter: true }">
<div>
Street: <input type="text" ng-model="Street" />
</div>
</form>
More information on ng-model-options and setter/getter (bottom of the page): https://docs.angularjs.org/api/ng/directive/ngModel
Though watch out when adding a form since enter key will do a submission by default, but you can easily control that if you wanted to.
Lastly, you can use directives to do all of this as someone has suggested already, but you would end-up using angular directives as controllers and according to angular they aren't meant for that, more info here: AngularJS - Directives vs Controllers
ng-include share the same controller and the same data.
So a solution probably will be to think of something else. Maybe a template directive. You can pass a template and also different values to it.
// Directive
app.directive('addressTemplate', function() {
return {
templateUrl: '/templates/address.html',
scope: {
street: '='
}
};
});
// My template
<address-template street="MyEmployer.Address.Street"></address-template>
// Template
<div>
Street: <input type="text" ng-model="street" />
</div>
After struggling during months with that problem, I now use that way to avoid duplication of template:
In the controller:
$scope.myelement = {...};
$scope.myelements = [{ ...}, { ... }]
In the template myelement.html :
{{ myelement.name }} {{ myelement.anything }}
In a template:
<div ng-include="'myelement.html'"></div> <!-- $scope.myelement is used -->
<div ng-repeat="myelement in myelements track by $index"></div> <!-- $scope.myelements[$index] used myelements.length times -->
If you wanna specify another variable you can do:
<div ng-include="'myelement.html'" ng-init="myelement = SomeScopeVariable;"></div>
But in that case the ng-init is only called when the ng-include is compiled. And you won't be able to change the 'myelement' inside it, you will only be able to modify its attributes. I don't recommand to use ng-init, except in case you just want to modify / visualize a specific element in your controller. If you want to remove it or create another one, don't use ng-init.
I have a form within the new-phone route which populates a phone model. This model has a belongsTo relationship with the client.
App.Router.map(function() {
this.resource("client", { path: "/client/:id" }, function(){
this.resource("phone", function(){
this.route("new-phone")
})
})
})
App.Client = DS.Model.extend({
name: DS.attr(),
phone: DS.belongsTo("Phone", { async: true })
});
App.Phone = DS.Model.extend({
name: DS.attr(),
model: DS.attr(),
year: DS.attr()
});
When I complete the form, the response from the server comes back correctly with the newly created record.
I'm getting data from JSON driven API.
So I'm posting to:
POST: /api/client/1/phone
I have set the transition to go back to the phone.index page (after the save is complete) which in turn (should) fire the model hook (GET request: /api/client/1/phone) and get the new data for the (phones.index) page. But for some reason, I get a 'data is null' error from Ember. It doesn't even seem to make the request before this error appears.
If I use the HTTP requester outside of the Ember app, the data appears.
App.ClientRoute = Ember.Route.extend({
model: function(){
return this.store.find("client", 1)
}
});
App.PhoneIndexRoute = Ember.Route.extend({
model: function(){
return this.modelFor("client").get("phone").then(function(data){
//Reload to manually get new data
return data.reload();
});
}
})
This is the version of Ember I'm using:
DEBUG: Ember : 1.8.1
DEBUG: Ember Data : 1.0.0-beta.11
DEBUG: Handlebars : 1.3.0
DEBUG: jQuery : 1.10.2
I dont think you need a new route to show a form for person's phone. Instead make a property to toggle when user clicks for phone form
Let's say your template looks like this
/user/1.hbs
{{model.user_name}} {{model.first_name}}
{{model.phone.number}}
{{#if showPhoneForm}}
<div class="form">
{{input value=model.phone.number placeholder="phone nr"}}
</div>
<div class="button" {{action "saveUser"}}> Save </button> // Save form data, hide user form, updaet user phone data
{{else}}
<div class="button" {{action "makePhoneForm"}}> Add phone </button> // Create form for user
{{/if}}
controllers/user/
showPhoneForm: false,
actions: {
makePhoneForm: function() {
this.set('showPhoneForm', true);
},
saveUser: function() {
this.get('model.phone').then(function(phoneRecord) {
phoneRecord.save().then(function(){
this.set('showPhoneForm', false);
}.bind(this):
}
}
I'm having some major issues trying to do something that should be simple, which means that I'm obviously missing something simple.
I'm pulling articles via an API, and instead of having someone click the link to an article, I want a modal to popup with the content of the article on click, so that there wouldn't be any need for page changing or having to load the api multiple times. Naturally, the easiest way I thought of solving this was to use an action to set a property from false to true, and use that property on a bind-attr class to show the modal. However, no matter what I seem to do, I can't ever get the property value initially set or changed in the action, and logging the variable to check and see what it returns results in an error saying the variable is not defined. I would really like to see what the problem is here so I can also use this solution on my list/grid class toggling functions, because right now I resorted to using jQuery for that due to having similar problems.
Below is the code used for my articles listing and the action handling. The action does fire, which I confirmed with an alert(), but no luck on the property. Thanks for the help!
HTML
<script type="text/x-handlebars" data-template-name="articles">
<button {{action 'listStyle'}}>List</button>
<button {{action 'gridStyle'}}>Grid</button>
<section id="articles-category" class="grid">
<div class="row">
{{#each}}
<div class="col-xs-6 col-md-3 article-wrapper" {{action "openArticle"}}>
<div class="article">
<img src="http://placehold.it/300x270">
<div class="content">
<h3>{{title}}</h3>
</div>
</div>
</div>
<div {{bind-attr class=":article-modal isArticleActive:enabled" target="controller"}}>
<div class="article-close">X</div>
<div class="article-back">Back to Articles</div>
<div class="article-wrapper">
<div class="container">
<h2 class="article">{{title}}</h2>
{{{unescape html_body}}}
</div>
</div>
</div>
{{/each}}
</div>
</section>
</script>
app.js
// MAIN APP CONFIGURATION
App = Ember.Application.create();
DS.RESTAdapter.reopen({
host: 'http://mihair.herokuapp.com',
namespace: 'api'
});
Ember.Handlebars.helper('unescape', function(value) {
newValue = value.replace(/(?:\r\n|\r|\n)/g, '<br />');
return new Ember.Handlebars.SafeString(value);
});
// ROUTER
App.Router.map(function(){
this.resource('articles', {path: ':id'});
this.resource('article', {path: 'articles/:id_or_slug'});
});
App.ArticlesRoute = Ember.Route.extend({
model: function() {
return this.store.find('articles');
}
});
App.ArticleRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('articles', params.id_or_slug)
}
});
// MODELS
App.Response = DS.Model.extend({
success: DS.attr('boolean', {defaultValue: false}),
message: DS.attr('string')
});
App.Articles = DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
html_body: DS.attr('string')
});
App.Article = DS.Model.extend({
id: DS.attr('number'),
title: DS.attr('string'),
description: DS.attr('string'),
html_body: DS.attr('string')
});
// CONTROLLERS
App.ArticlesController = Ember.ArrayController.extend({
isArticleActive: false,
actions: {
listStyle: function() {
$('#articles-category').removeClass('grid').addClass('list');
},
gridStyle: function(){
$('#articles-category').removeClass('list').addClass('grid');
},
openArticle: function() {
this.set('isArticleActive', true);
}
}
});
isArticleActive lives on the ArticlesController (which is the collection), but when you use {{#each}} you change the context from the controller to an individual article. You'll want to work with an itemController in order to create properties for each item individual model instead of for the collection as a whole.
Ember.js Controller Does not work
I have the following view:
Main.Views.Login = EventQ.View.extend({
events: {
"submit form": "login"
},
template: "login",
login: function(e) {
var me = this;
$.ajax({
url: "/api/users/login",
type: 'POST',
dataType: "json",
data: $(e.currentTarget).serializeArray(),
success: function(data, status){
EventQ.app.router.navigate('dashboard', true);
},
error: function(xhr, status, e) {
var result = $.parseJSON(xhr.responseText);
me.render_with_errors(result.errors);
}
});
return false;
},
render: function(done) {
var me = this;
// Fetch the template, render it to the View element and call done.
EventQ.fetchTemplate(me.template, function(tmpl) {
me.el.innerHTML = tmpl(me.model.toJSON());
done(me.el);
});
},
render_with_errors: function(errors) {
var me = this;
// Fetch the template, render it to the View element and call done.
EventQ.fetchTemplate(this.template, function(tmpl) {
me.el.innerHTML = tmpl(errors);
});
}
});
and a simple template like this:
<form>
<input name="username" />
<input name="password" />
<button type="submit" />
</form>
what I'm looking to do is be able to re-render the template if errors are returned but keep the input's populated. An error template would like like:
<form>
<input name="username" />
<label class="error">required</label>
<input name="password" />
<button type="submit" />
</form>
Is there a way to bind the view to a model or something that I can check? Right now the render_with_errors works except for the fact that I lose all the data filled out on the form.
It's common for people to get in the mode where they only way they think that the only way they can change the page is a full re-render of a template. But rendering templates are only 1 solution to updating the page. You are still free to use traditional methods from within your backbone view. So another possible solution is for you to simply adjust the dom from your view.
So make your template be the following:
<form>
<input name="username" />
<label class="error" style="display:none">required</label>
<input name="password" />
<button type="submit" />
</form>
And then make the following change in your login function:
error: function(xhr, status, e) {
var result = $.parseJSON(xhr.responseText);
me.showLoginError();
}
showLoginError: function() {
this.$('.error').show();
}
And of course you can always add more to that, message customizations, etc.
It's just important to remember that full template renders aren't the only way for your backbone code to react to changes in the application state, and it's ok to manipulate the DOM in a method other than render.
Supposed you have a Model like this:
Main.Models.LoginModel = EventQ.Model.extend({
/* ... */
defaults: {
'username': "",
'password': ""
},
/* ... */
When your Ajax-Request successes, you can navigate to the next page.
If it fails, you can set your model to use undefined to indicate a missing value:
// assumed you did not enter your password
this.me.model.set( { 'username': textBoxValueSoFar, 'password': undefined });
Then can built up an template like this (it will be the same as on first page load):
<form>
<input name="username" value="{{username}}" />
{{#unless username}}
<label class="error">required</label>
{{/unless}}
<input name="password" value="{{password}}" />
{{#unless password}}
<label class="error">required</label>
{{/unless}}
</form>
{{unless}} checks if, the value is not false, undefined, null or []. So on first page load, it is an empty string and no error message is provided.
See http://handlebarsjs.com/ the part with "unless" for more details.
So what you do is: you use an empty string to indicate that no wrong value has been entered so far. You use undefined to check if a wrong value (in fact, nothing) has been entered.
In your template you can check for this an act appropriately.