Backbone: View has lost contact to element - javascript

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'
},

Related

Save form data with handlebarsjs and marionettejs

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();

RadioButtonList in Ember, Binding a model property to the selected value

I've reading and trying some codes but nothing has been working like I want i to... this is the code I have right now.
index.html:
<div class="form-group">
<label for="accommodation">3.2. ACCOMMODATION (*)</label> <br />
<div>
{{view Ember.RadioButton name="accommodation" valueBinding='accommodation' selectionBinding="isSelected" value="Not Required"}}
Not Required
</div> <br />
<div>
{{view Ember.RadioButton name="accommodation" valueBinding='accommodation' selectionBinding="isSelected" value="Required"}}
Required
</div>
</div>
View radiobutton.js:
Ember.RadioButton = Ember.View.extend({
tagName: "input",
type: "radio",
attributeBindings: ["name", "type", "value", "checked:checked:"],
click: function () {
this.set("selection", this.$().val())
},
checked: function () {
return this.get("value") == this.get("selection");
if (this.get("selection") == "Required") {
$('#panelAccommodation').style.display = "block";
}
if (this.get("selection") == "Not Required") {
$('#panelAccommodation').style.display = "none";
}
}.property()
What I want from these RadioButtonLists is to be able to retrieve the value of the selected item so I can send it as a POST to a api and to use the selected value to hide/show a div dependidng on the selected value. Any ideas on how to do this?
EDIT:
I'm trying andrusieczko's code and so far I have this:
radio.js :
App.RadioView = Ember.View.extend({
tagName: 'input',
type: 'radio',
attributeBindings: ['type', 'htmlChecked:checked', 'value', 'name'],
htmlChecked: function () {
return this.get('value') === this.get('checked');
}.property('value', 'checked'),
change: function () {
this.set('checked', this.get('value'));
},
_updateElementValue: function () {
Ember.run.next(this, function () {
this.$().prop('checked', this.get('htmlChecked'));
});
}.observes('htmlChecked')
});
Ember.Handlebars.helper('radio-button', App.RadioView);
controller:
App.EventsNewController = Ember.ObjectController.extend({
acRequired: 'Required',
acNotRequired: 'Not Required',
accommodation: null,
Index.html :
<div class="form-group">
<label for="accommodation">3.2. ACCOMMODATION (*)</label> <br />
<div>
{{radio-button value=acRequired checked=accommodation}}
Not Required
</div>
<div>
{{radio-button value=acNotRequired checked=accommodation}}
Required
</div>
</div>
still not sending the selected value on the POST request.
EDIT 2:
I've written some code that in all others aspects is behaving like I want it to but it's still not binding the selected value to the property I want it to.
View radio.js:
Ember.Radio = Ember.View.extend({
tagName: "input",
type: "radio",
attributeBindings: ["name", "type", "value", "checked:checked:"],
click: function () {
this.set("selection", this.$().val())
if(this.get("selection") === "acRequired") {
$('#panelAccommodation').css("display", "block");
this.set("selection", "Required");
selectionBinding: "App.event.accommodation"
}
if (this.get("selection") === "acNotRequired") {
$('#panelAccommodation').css("display", "none");
this.set("selection", "Not Required");
selectionBinding: "App.event.accommodation"
}
if (this.get("selection") === "flRequired") {
$('#panelFlight').css("display", "block");
this.set("selection", "Required");
selectionBinding: "App.event.flight"
}
if (this.get("selection") === "flNotRequired") {
$('#panelFlight').css("display", "none");
this.set("selection", "Not Required");
selectionBinding: "App.event.flight"
}
},
checked: function () {
return this.get("value") == this.get("selection");
}.property()
});
Index.html :
<!-- Accommodation - RadioButtonList -->
<div class="form-group">
<label for="accommodation">3.2. ACCOMMODATION (*)</label> <br />
<div>
{{view Ember.Radio name="accommodation" selectionBinding='accommodation' value="acNotRequired"}}
Not Required
</div>
<div>
{{view Ember.Radio name="accommodation" selectionBinding='accommodation' value="acRequired"}}
Required
</div>
</div>
controller new.js :
App.EventsNewController = Ember.ObjectController.extend({
accommodation: "Not Required",
flight: "Not Required",
actions: {
save: function () {
var self = this;
this.get('model').set('status', 'Created');
this.get('model').save().then(function () {
self.transitionToRoute('events.table');
});
}
}
});
PS: I'm using Ember version 1.6.1
If you're not using either Ember App Kit or ember-cli, then all you have to do is:
App.RadioView = Ember.View.extend({
tagName: 'input',
type: 'radio',
attributeBindings: ['type', 'htmlChecked:checked', 'value', 'name'],
htmlChecked: function() {
return this.get('value') === this.get('checked');
}.property('value', 'checked'),
change: function() {
this.set('checked', this.get('value'));
},
_updateElementValue: function() {
Ember.run.next(this, function() {
this.$().prop('checked', this.get('htmlChecked'));
});
}.observes('htmlChecked')
});
Ember.Handlebars.helper('radio-button', App.RadioView);
Then, in your Controller:
App.IndexController = Ember.Controller.extend({
country1: 'USA',
country2: 'Singapore',
country3: 'Poland',
country: null,
});
and in your template:
{{radio-button value=country1 checked=country}}
{{radio-button value=country2 checked=country}}
{{radio-button value=country3 checked=country}}
and you can listen to the properties you passed to the helper and based on that trigger other actions.
And it can be easily used along with {{each}} :)
Does it help?
The working example (Ember 1.6.1, ember-data 1.0.0-beta.9):
https://github.com/andrusieczko/radiobutton-example.git
Disclaimer: I'm not the author of the code, I took it from somewhere some time ago.
If you can handle not using HTML <input> elements, try using this component that I wrote.
Note that I'm using Ember 1.10 and Ember-CLI.
my-template.js (controller)
import Ember from 'ember';
export default Ember.Controller.extend({
options: [Ember.Object.create({
name: "Required",
value: "1",
}), Ember.Object.create({
name: "Not Required",
value: "0",
})],
actions: {
optionChanged: function(option) {
// option.value will either be 1 or 0 in this case.
}
}
});
my-template.hbs
{{#mutex-button-set key="value" default=options.firstObject stateChanged="optionChanged" buttons=options as |button|}}
<div>{{button.name}}</div>
{{/mutex-button-set}}
mutex-button-set.hbs
{{#each button in buttons}}
<div {{bind-attr class=":mutex-button button.selected:selected"}} {{action "setSelected" button}}>
{{yield button}}
</div>
{{/each}}
mutex-button-set.js
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['mutex-button-set'],
actions: {
setSelected: function(obj) {
var key = this.get("key");
this.get("buttons").forEach(function(button) {
Ember.set(button, "selected", obj && (button.get(key) === obj.get(key)));
});
if (obj) {
this.sendAction('stateChanged', obj);
}
}
},
didInsertElement: function() {
this.send("setSelected", this.get("default"));
}
});
HTML Generated
<div id="ember554" class="ember-view mutex-button-set">
<div data-ember-action="559" class="mutex-button selected">
<div>Required</div>
</div>
<div data-ember-action="563" class="mutex-button ">
<div>Not Required</div>
</div>
</div>

Submit form event doesn't fire

I develop the first backbone RESTfull application. I have the following view:
var app = app || {};
app.AssessorsCollectionView = Backbone.View.extend({
el: '#assessors',
tagName: 'table',
events:{
"submit form": "searchAssessors"
},
initialize: function() {
this.collection = new app.AssessorsCollection();
this.collection.fetch({reset: true, data: $.param({ online: false}) });
this.render();
this.listenTo( this.collection, 'reset', this.render );
},
render: function() { /// },
renderRegion: function( item ) { /// },
searchAssessors: function( e ) {
e.preventDefault();
this.collection.remove();
console.log("EVENT");
}
});
This is my HTML Form:
<form id="form-search-assessors" class="form-horizontal" role="form">
.........
<input id="search-assessors-button" class="btn btn-lg btn-success btn-block" type="submit" value="Search" />
<!--<button id="search-assessors-button" class="btn btn-lg btn-success btn-block" type="submit">Search</button>-->
</form>
I put breakpoint to searchAssessors, but it does not work. Also, I don't see any messages in console.
Try changing the event to "submit #form-search-assessors": "searchAssessors".
Did you initialize the view ?, Since is just a fragment from your code I removed a couple of things and comments, this should work.
Thanks
HTML
<div id="assessors">
<form id="form-search-assessors" class="form-horizontal" role="form">
<input id="search-assessors-button" class="btn btn-lg btn-success btn-block" type="submit" value="Search" />
</form>
<div>
JS
AssessorsCollectionView = Backbone.View.extend({
el: '#assessors',
tagName: 'div',
events:{
'submit form': 'searchAssessors'
},
initialize: function() {
this.render();
},
render: function() { },
searchAssessors: function( e ) {
e.preventDefault();
console.log('EVENT');
}
});
view = new AssessorsCollectionView();
The solution of my problem:
$( "form#form-search-assessors" ).submit($.proxy(function(e) {
e.preventDefault();
console.log(this);
this.collection.fetch({reset: true, data: $.param({ online: true}) });
this.render();
}, this));
The sourse is: https://stackoverflow.com/a/9813760/1756750

Backbone.js submit event not firing

I'm at a loss as to what's going on here. I think it's because I'm loading the form later on in the view, but I'm honestly not sure. Here's my view.
define([
'jquery',
'underscore',
'backbone',
'serializeForm',
'backboneForms',
'text!templates/services/ServicesTemplate.html',
'models/ServiceModel',
'forms/NewServiceForm',
'text!templates/forms/ServiceFormTemplate.html',
'collections/RegionsCollection',
'collections/UsersCollection'
], function($, _, Backbone, serializeForm, backboneForms, ServicesTemplate, ServiceModel,
NewServiceForm, ServiceFormTemplate, RegionsCollection, UsersCollection){
// The form
var form;
// What's gonna be clicked
var clicked;
// Get the user's credentials
if($.cookie('UserInfo'))
var userCreds = JSON.parse($.cookie('UserInfo'));
var ServicesView = Backbone.View.extend({
el: '.body',
render: function() {
// Load everything
var servicesTemplate = _.template(ServicesTemplate);
this.$el.html(servicesTemplate);
},
events: {
'click .btn': 'loadForm',
'submit': 'addService'
},
loadForm: function(ev) {
// Save what was clicked
clicked = $(ev.target).html();
// Save the scope
var that = this;
// Create the regions collection
var regionsCollection = new RegionsCollection();
var serviceModel = new ServiceModel();
var serviceFormTemplate = _.template(ServiceFormTemplate);
// Create the form
form = new NewServiceForm({
template: serviceFormTemplate,
model: serviceModel
}).render();
$("#form").html(form.el);
$('.body').on('submit', 'form', function() {
alert( "submit firing" );
});
},
addService: function(ev) {
var errors = form.commit();
if(!errors) {
var newService = $(ev.currentTarget).serializeForm();
newService.cluster = clicked;
console.log(newService);
} else {
$.each(errors, function(key, value) {
$("[name='" + key + "']").closest(".control-group").addClass("error");
$("[name='" + key + "']").closest(".control-group").find(".text-error").html("<small class='control-group error'>" + value.message + "</small>");
});
}
return false;
}
});
return ServicesView;
});
I've tried to bind the event to just the form that gets generated and, as you can see above, I've tried just catching any submit event. Any help on this is greatly appreciated.
EDIT: Here's what my index page looks like
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Backbone Auth</title>
<?php include('includes/head_includes.php'); ?>
</head>
<body>
<div class="header"></div>
<div class="container body">
</div>
<?php include('includes/js_files.php'); ?>
</body>
</html>
And here's roughly what my form looks like. The .control-groups all get populated with fields using the backbone-forms extension.
<form id="addservice" accept-charset="UTF-8">
<div id="error" class="alert alert-error" style="display:none;"></div>
<fieldset>
<div class="control-group">
<div class="controls">
<label>Region</label>
<div class="text-error"></div>
<div data-editors="region"></div>
</div>
</div>
<div class="control-group">
<div class="controls">
<label>Stage</label>
<div class="text-error"></div>
<div data-editors="stage"></div>
</div>
</div>
<div class="control-group">
<div class="controls">
<label>Description</label>
<div class="text-error"></div>
<div data-editors="description"></div>
</div>
</div>
<div class="control-group">
<div class="controls">
<label>Primary Contact</label>
<div class="text-error"></div>
<div data-editors="contact"></div>
</div>
</div>
<input id="AddService" class="btn btn-primary" type="submit" name="submit" value="Add Service" />
</fieldset>
</form>
EDIT2: Here is my NewServiceForm
define([
'jquery',
'underscore',
'backbone',
'backboneForms'
], function($, _, Backbone, backboneForms){
var NewServiceForm = Backbone.Form.extend({
schema: {
region: {
type: 'Select',
title: 'Disaster Region',
options: [],
validators: [
'required'
]
},
stage: {
type: 'Select',
title: 'Stage',
options: [
{val: "", label: "Select One"},
{val: "Assessment", label: "Assessment"},
{val: "Planned", label: "Planned"},
{val: "Commenced", label: "Commenced"}
],
validators: [
'required'
]
},
description: {
type: 'TextArea',
title: 'Description',
editorAttrs: {
maxlength: 140
},
validators: [
'required'
]
},
contact: {
type: 'Select',
title: 'Primary Contact',
options: [],
validators: [
'required'
]
}
}
});
return NewServiceForm;
});
WOOP!! Finally figured this out! I went ahead and moved the rendering of the form into the render function of the view. So now render looks like this:
render: function() {
var serviceModel = new ServiceModel();
var serviceFormTemplate = _.template(ServiceFormTemplate);
// Create the form
form = new NewServiceForm({
template: serviceFormTemplate,
model: serviceModel
}).render();
// Load everything
var servicesTemplate = _.template(ServicesTemplate);
this.$el.html(servicesTemplate);
}
Everything else stays the same. And now it works!

Adding ID and Class on template dynamically using backbone views

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.

Categories