I'm trying to save the form data to a server using a Deployd API; but when i click the save button, a new document it's created at the server but it contain no data. I don't understand how it can work; i guess i have to pass the data to the model to be saved? How do i do that? I thought that the view was already linked with the template and model.
I'm using Requirejs, MarionetteJS and Handlebars.
Here is the code.
MODEL:
define(['underscore','backbone'], function(_, Backbone){
var Student= Backbone.Model.extend({
urlRoot: '/students',
defaults: {
nameStudent: '',
lastnameStudent: ''
},
initialize: function(){
console.log('studentModel: initiated');
},
});
return Student;
});
VIEW:
define([
'jquery',
'underscore',
'backbone',
'marionette',
'handlebars',
'js/models/student',
'text!templates/forms/studentFormAdd.html'
], function($, _, Backbone, Marionette, Handlebars, studentModel, studentFormAddTemp){
var studentFormAdd = Backbone.Marionette.ItemView.extend({
model: studentModel,
template: Handlebars.compile(studentFormAddTemp),
events: {
'click #saveBtn': function(){
this.model.save();
console.log(this.model.toJSON());
console.log('saveBtn event: initiated');
}
},
initialize: function(){
console.log('studentFormAdd: initiated');
this.model = new studentModel();
this.model.on('change', this.render);
},
});
My template has the following sintax:
<div>
<form>
Student Name<br>
<input type="text" name="nameStudent" value="{{nameStudent}}" placeholder="Student name"/>
<br>
Student lastname<br>
<input type="text" name="lastnameStudent" value="{{lastnameStudent}}" placeholder="Student lastname"/><br>
</form>
</div>
Backbone doesn't use two way binding, so:
this.model.save();
in your saveBtn event handler is empty. If you want realtime two-way binding, you can use:
https://github.com/theironcook/Backbone.ModelBinder or https://github.com/NYTimes/backbone.stickit
If you wan't to simply consume the form data on submit and save it in a model, you can use https://github.com/marionettejs/backbone.syphon.
var data = Backbone.Syphon.serialize(this);
this.model.set(data);
this.model.save();
Related
I'm using Marionette with Handlebars templates and I can't get my itemView to render inside a CollectionView.
Here is the CollectionView code:
define( [ 'App', 'marionette', 'handlebars', 'models/Model', 'collections/Collection', 'text!templates/welcome.html'],
function(App, Marionette, Handlebars, Model, Collection, template) {
//ItemView provides some default rendering logic
var ItemView = Marionette.ItemView.extend( {
//Template HTML string
template: Handlebars.compile(template),
//model: new Model(),
// View Event Handlers
events: {
},
initialize: function(o) {
console.log('init itemView');
}
});
return Marionette.CollectionView.extend( {
issues: new Collection(),
itemView: ItemView,
onRender: function() {this.issues.fetch()},
initialize: function(o) { console.log('init collectionView')}
});
});
here is the template
<div class="hero-unit">
<h1>Marionette-Require-Boilerplate Lite</h1>
<p>Lightweight Marionette Boilerplate application to get you off the ground fast.</p>
<p class="muted">
You are viewing this application on
</p>
<br/>
<table>
{{#each items}}
<tr><td>{{title}} - {{number}}</td></tr>
{{/each}}
</table>
<a class="btn btn-primary btn-large" href="https:github.com/BoilerplateMVC/">See more Boilerplates</a>
The only thing I get from this code is that the CollectionView does trigger its initialize method and that the collection is fetched from GitHub.
There are multiple reasons this could not be working, depending on the Marionette version you are using:
For the latest Marionette version, you have to use 'childView' instead of 'itemView'.
The items to display are expected in the property 'collection' not 'issues'.
example:
var IssuesView = Marionette.CollectionView.extend({
childView: IssueView,
onRender: function () {
this.collection.fetch();
},
initialize: function (o) {
console.log('init collectionView');
}
});
new IssuesView({'collection': new Backbone.Collection()});
Now, based on the code you provided, I assume your goal is to display the issues inside 'items', if that is correct, I will suggest to use a 'CompositeView' instead, and then you can provide a 'container' and render the 'issues' inside the items. For example:
var IssueView = Marionette.ItemView.extend({
itemTag: 'tr',
//Template HTML string
template: Handlebars.compile($("#item-template").html()),
//model: new Model(),
// View Event Handlers
events: {
},
initialize: function (o) {
console.log('init itemView');
}
});
var IssuesView = Marionette.CompositeView.extend({
childView: IssueView,
childViewContainer: "#issues",
template: Handlebars.compile($("#some-template").html()),
onRender: function () {
//this.issues.fetch();
},
initialize: function (o) {
console.log('init collectionView');
}
});
Where your templates are:
<script id="item-template" type="text/x-handlebars-template">
<td>
{{title}} - {{number}}
</td>
</script>
<script id="some-template" type="text/x-handlebars-template">
<div class = "hero-unit" >
<h1>Marionette - Require - Boilerplate Lite </h1>
<p>Lightweight Marionette Boilerplate ...</p>
<p class = "muted"> You are viewing this application on </p>
<br/>
<table id="issues">
</table>
</script>
Here is jsfiddle with a working version of this:
http://jsfiddle.net/gvazq82/v5yj6hp4/2/
Your problem is that you're not specifying a collection in your CollectionView. You want to instead do
var collectionView = Marionette.CollectionView.extend({
collection: new issues
...
});
I'm fetching some data from my MySQL database with a php file and now I want to display this data in my View by passing it through a Model with the json_encode method. So far I created a Router, a Model, a Collection (is it necessary?) and a View. When i console.log the collection in my View, I can see that the data is actually there but my View shows nothing. When i console.log the Model I get the "undefined" message. So it seems that the Model is not instantiated, but I dont really know how to solve it. I use RequireJS and the HandlebarsJS for HTML templating purpose.
So here is my Router.
define(['backbone',
'views/firstpage',
'views/secondpage',
'views/thirdpage',
'collections/BandCollection']),
function( Backbone,FirstpageView, SecondpageView, ThirdpageView,BandCollection ) {
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'firstpage' : 'firstpage',
'secondpage' : 'secondpage',
'thirdpage' : 'thirdpage'
},
initialize: function () {
this.bandCollection = new BandCollection();
this.bandCollection.fetch({
error: function () {
console.log("error!!");
},
success: function (collection) {
console.log("no error");
}
});
},
thirdpage: function() {
var thirdpageView = new ThirdpageView({ el:'#topContent', collection:this.bandCollection}).render();
},
});
return Router;
}
);
My Model looks like this:
define([
"jquery",
"backbone"
],
function($, Backbone) {
var BandModel = Backbone.Model.extend({
url: "../metal/db/bands.php",
defaults: {
"id": '',
"band": '',
"label": ''
}
});
return BandModel;
});
My Collection:
define([
"backbone",
"models/BandModel"
],
function(Backbone, BandModel) {
var BandCollection = Backbone.Collection.extend({
model: BandModel,
url: "../metal/db/bands.php"
});
return BandCollection;
});
My HTML template:
<div>
<p><%= id %></p>
<p><%= band %></p>
<p><%= label %></p>
</div>
And My View looks like this:
define(['backbone','handlebars', 'text!templates/Thirdpage.html'],
function(Backbone,Handlebars, Template) {
'use strict';
var ThirdpageView = Backbone.View.extend({
template: Handlebars.compile(Template),
initialize: function () {
_.bindAll(this, 'render');
this.render();
},
render: function() {
console.log(this.collection);
this.$el.html(this.template(this.collection.toJSON()));
return this;
}
});
return ThirdpageView;
}
);
As said before, the console.log(this.collection) tells me that the data is available..
{length: 6, models: Array[6], _byId: Object, constructor: function, model: function…}
but console.log(this.model) gives me "undefined" - and the View actually displays the HTML mentioned before and not the data, meaning it actually shows
<div>
<p><%= id %></p>
<p><%= band %></p>
<p><%= label %></p>
</div>
So, can anyone help me out? I'm out of ideas...
Change your render() method in your view like this:
render: function() {
var self = this;
console.log(this.collection);
self.collection.each(function(model){
console.log(this.model); // can view all models here
self.$el.append(self.template({id:model.get('id'),band:model.get('band'),label:model.get('label')}));
});
return this;
}
Change your Template like this:
<div>
<p>{{id}}</p>
<p>{{band}}</p>
<p>{{label}}></p>
</div>
I have some sort of ViewCollection which renders all sub views.
define(
['jquery', 'underscore', 'backbone', 'text!templates/main/layout.html', 'text!templates/main/index.html', 'views/security/login', 'views/security/registration'],
function($, _, Backbone, LayoutTemplate, ContentTemplate, LoginView, RegistrationView) {
var IndexView = Backbone.View.extend({
tagName: "div",
className: "container",
template: LayoutTemplate,
render: function() {
this.$el.html(LayoutTemplate);
this.$('div.content').html(ContentTemplate);
this.$('div.sidebar').append(new LoginView().render().el);
this.$('div.sidebar').append(new RegistrationView().render().el);
return this;
}
});
return IndexView;
});
This works quite good!
Unfortunatly I have noticed that for example the LoginView can't handle events anymore.
define(
['jquery', 'underscore', 'backbone', 'text!templates/security/login.html'],
function($, _, Backbone, LoginTemplate){
var LoginView = Backbone.View.extend({
tagName: "form",
className: "login well",
template: LoginTemplate,
events: {
"submit form.login": "submit"
},
render: function(){
this.$el.html(this.template);
return this;
},
submit: function(e){
e.preventDefault();
var credentials = {
'username': $(e.target[1]).val()
, 'password': $(e.target[2]).val()
};
console.log(JSON.stringify(credentials));
$.ajax({
url: '/session'
, type: 'POST'
, contentType: 'application/json'
, data: JSON.stringify(credentials)
, success: function() {
window.Router.navigate('node', { trigger: true });
}
, error: function(xhr, status, error) {
console.log([xhr, status, error]);
$(e.target[1]).closest('div.control-group').addClass('error');
$(e.target[2]).closest('div.control-group').addClass('error');
}
});
}
});
return LoginView;
});
Instead of sending an ajax call the browser tries to submit the form data url-encoded as GET request that is a sign that the view doesn't listen on any views anymore...
Is there a option how I can rebind the view events to the element?
login.html
<fieldset>
<legend>login</legend>
<div class="control-group">
<label class="control-label" for="_session_username">username:</label>
<div class="controls">
<input type="text" id="_session_username" name="session[username]" placeholder="george78" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="_session_password">password:</label>
<div class="controls">
<input type="password" id="_session_password" name="session[password]" placeholder="••••••" />
</div>
</div>
<button type="submit" class="btn btn-primary">login</button>
</fieldset>
the form tags are defined in the view also the .login class attribute.
I found the solution.
We have a scoping problem. Because I defined the form directly in the view (tagName: "form") the topest object is "form.login". But this we can't address through the selector anymore. We only can select child objects. (See Backbone internals).
Before:
events: {
'keyup form.login input': 'checkInput'
, 'submit form.login': 'submit'
},
After:
events: {
'keyup input': 'checkInput'
, 'submit': 'submit'
},
I want to add a ID and CLASS attribute to my view template.
this is what i tried but failed.
$("#login").html( _.template( LoginTemplate ) );
this.loginmodel = new LoginModel();
this.loginview = new LoginView({
el: $("#loginContainer"),
model: this.loginmodel,
className: "test"
});
<div id="loginContainer">
<div id="loginForm">
<input class="user-input formContainerInput" id="username" placeholder="Username" required="required"/>
<input class="user-input formContainerInput" id="password" type="password" placeholder="Password" required="required"/>
<a class="connection-btn" data-role="button">Connection</a>
<a class="login-btn" data-role="button">Log In</a>
</div>
I want to assign id and class using the views and not on the html itself. How will i do it?
update
attemp #1
loginview: function(){
$("#login").html( _.template( LoginTemplate ) );
this.loginmodel = new LoginModel();
this.loginview = new LoginView({
id: "#loginContainer",
model: this.loginmodel,
attributes:{ id:'Test', class: "myClass otherClass" }
});
},
it even display an error in aptana on the "class" part.
even tried it on the main view since the code above was the parent view.
var LoginView = Backbone.View.extend({
events: {
"click .login-btn": "Login",
"click .connection-btn": 'Connect',
},
initialize: function(){
//some code here
},
attributes: {
id:"test"
}
});
What about using View.attributes.
You can specify something like:
attributes: {
id: "myId",
class: "myClass otherClass"
}
I didn't try but maybe you also can use functions to make it even more dynamic:
attributes: {
id: function(){ return "element-" + this.id; },
class: "myClass otherClass"
}
Beware this only affects to the view.el DOM element.. not any of its children.
Updated
The above solution only works when the View.el is anonymous.
So, the only solution I see can work in your concrete scenario is to manipulate the View.el directly by JavaScript in the initialize() like this:
initialize: function(){
this.$el.attr( "id", "my-id" );
this.$el.attr( "class", "myclass1 myclass2" );
},
Check the jsfiddle for three different scenarios manipulating the View.el attributes.
I found backbone.js a couple of days ago, and i found out its a pretty code tool for javascript development though my javascript skill aren't great.
However after reading the documentation, i decided to code a simple contact app.
I save the contact data on browser localstorage.
This is code
// Source Code for my contacts app
$(function() {
//Contact Model
Contact = Backbone.Model.extend({
//Contact Defaults
defaults : {
first_name : 'First Name',
last_name : 'Last Name',
phone : 'Phone Number'
},
//Constructor(intialize)
//Ensuring each contact has a first_name,last_name,phone
intialize: function(){
if(!this.get("first_name")) {
this.set({"first_name":this.defaults.first_name});
}
if(!this.get("last_name")) {
this.set({"last_name":this.defaults.last_name});
}
if(!this.get("phone")) {
this.set({"phone":this.defaults.phone});
}
}
});
//Contact Collection
//The collection is backed by localstorage
ContactList = Backbone.Collection.extend({
//Model
model : Contact,
//Save all contacts in localstorage under the namespace of "contacts"
localStorage: new Store("contacts")
});
//Create global collection of Contacts
Contacts = new ContactList;
//Contact View
ContactView = Backbone.View.extend({
tagName : "li",
template: _.template($("#item_template").html()),
events : {
"click span.contact-delete": "delete_contact"
},
intialize: function(){
this.bind('change',this.render,this);
this.bind('destroy',this.remove,this);
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setContent();
return this;
},
setContent: function() {
var first_name = this.model.get("first_name");
var last_name = this.model.get("last_name");
var phone = this.model.get("phone");
var name = first_name+" "+last_name;
this.$('.contact-name').html(name);
this.$('.contact-phone').html(phone);
},
remove: function() {
$(this.el).remove();
},
delete_contact: function() {
this.model.destroy();
}
});
//The Application
AppView = Backbone.View.extend({
el: $("#contact-app"),
events : {
"click #new-contact #save-button": "createContact"
},
intialize: function() {
Contacts.bind("add", this.addOne, this);
Contacts.bind("reset", this.addAll, this);
Contacts.fetch();
},
// Add a single contact item to the list by creating a view for it, and
// appending its element to the `<ul>`.
addOne: function(contact) {
var view = new ContactView({model: contact});
this.$("#contact-list").append(view.render().el);
},
// Add all items in the **Contacts** collection at once.
addAll: function() {
Contacts.each(this.addOne);
},
// Generate the attributes for a new Contact item.
newAttributes: function() {
return {
first_name : this.$('#first_name').val(),
last_name : this.$('#last_name').val(),
phone : this.$('#phone').val()
};
},
createContact: function() {
Contacts.create(this.newAttributes());
//Reset Form
this.$('#first_name').val('');
this.$('#last_name').val('');
this.$('#phone').val('');
}
});
// Finally,kick things off by creating the **App**.
var App = new AppView;
});
And this is my html source
<div id="contact-app">
<div class="title">
<h1>Contacts App</h1>
</div>
<div class="content">
<div id="new-contact">
<input name="first_name" placeholder="First Name" type="text" id="first_name"/>
<input name="last_name" placeholder="Last Name" type="text" id="last_name" />
<input name="phone" placeholder="Phone Number" type="text" id="phone" />
<button id="save-button">Create Contact</button>
</div>
<div id="contacts">
<ul id="contact-list">
</ul>
</div>
<div id="contact-stats"></div>
</div>
</div>
<script type="text/template" id="item_template">
<div class="contact">
<div class="contact-name"></div>
<div class="contact-phone"><div>
<span class="contact-delete"></span>
</div>
</script>
The contact data gets saved in the local storage, which i can see via firebug but the view is not updated. Am new to backbone.js.
What is the problem, there are no javascript errors.
Try using "add" instead of 'create' for adding models to the collection (I don't think the 'add' event is being fired by the 'create' method).
Instead of
Contacts.create(this.newAttributes());
Use
Contacts.add(this.newAttributes());
To save the model to local storage you can call the save method
addOne: function(contact) {
var view = new ContactView({model: contact});
contact.save();
this.$("#contact-list").append(view.render().el);
},
EDIT:
Another thing check the spelling of your "intialize" method i think it should be "initialize".
Here's a jsFiddle, I'm not saving it to localStorage in the jsfiddle, but that should work by you.
On the model, the defaults should take care of the default values, the initialize functions are probably not needed; someone correct me on this if i'm wrong.
On your ContactView, you may have to change your render line to this in your initialize method:
this.model.bind('change', _.bind(this.render, this));