Backbone dropdown change doesn't update model in a collection - javascript

I'm new to Backbone and I have this problem while learning, which I was trying to solve it for some time and it get's annoying. I'm rendering one dropdown for each of the attributes in the model. When I change the selected value, I would like to update the model. When I try to catch the change event in OrderView, it does not work. If I do that in the OrderingView, it works, but then I need to search for the current model.
What am I doing wrong here?
The situation: I have this model:
var Order = Backbone.Model.extend({
defaults:{
column: '',
order: ''
}
});
and corresponding collection having Order as model. The Views:
var orderView = Backbone.View.extend({
model: new Order(),
initialize: function(){
this.template = _.template(order_template);
},
render: function(){
var temp = this.template({model:this.model.toJSON(),order_columns:this.order_columns});
this.$el.html(temp);
return this;
},
events: {
'change .order_columns': 'change_column'
},
change_column: function(){
console.log('column changed..');
}
});
var orderingView = Backbone.View.extend({
model: new OrderCollection(),
initialize: function(){
var that = this;
_(this.options.orderings).each(function(or){
that.model.add(new Order({column: or.column, order: or.order}));
});
this.model.on('add', this.render, this);
this.model.on('change', this.changed_item,this);
},
render: function(){
var that = this;
that.$el.html('');
var temp_order = '';
_.each(this.model.toArray(), function(ordering, i){
var obj = new orderView({model: ordering, order_columns: that.options.order_columns}).render().$el.html();
temp_order += obj;
});
var temp = _.template(ordering_template,{element: temp_order});
that.$el.html(temp);
$(document).ready(function(){
$('.add-ordering').click(function(){
var new_order = new Order({column: 'none', order: 'ASCENDING'});
that.model.add(new_order);
});
});
return this;
},
});
and the call is from different file using requirejs:
that.orderView = new orderingView({el:table,
orderings: that.data.models.layers[0].orderings,
order_columns:that.data.models.layers[0].columns});
Also I load the html templates at the beginning using requirejs and known as order_template and ordering_template
templates:
<div class="control-group">
<div class="controls" style="margin-left:30px;">
<select class="order_columns" name="columns" style="float:left;">
<option value='none' <%=model.column==='none'? 'selected': ''%>>None</option>
<% _(order_columns).each(function(col){ %>
<option value="<%=col.alias%>" <%=model.column===col.alias? 'selected': ''%>><%= col.name %></option>
<% }); %>
</select>
<select class="ordering" name="ordering" style="float:left;margin-left:10px;">
<option value='ASCENDING' <%=model.order === 'ASCENDING'? 'selected':''%> >ASC</option>
<option value='DESCENDING' <%=model.order === 'DESCENDING'? 'selected':''%>>DESC</option>
</select>
<span class="ui-icon ui-icon-circle-close ui-order-close <%=model.column%>" style="margin-left:10px;"></span>
</div>
</div>
and ordering_template
<div class="row">
<form class="form-horizontal span12">
<fieldset>
<br/>
<div class="row">
<div class="span9 ordering_div" style="width:520px;">
<%=element%>
</div>
</div>
</fieldset>
</form>
</div>
Any suggestion?

Related

Adding item to knock out view model , is not updating the view

I have a ViewModel which I am binding to view list item.
var MyViewModel = function() {
var self = this;
self.addItems = function(vm) {
vm.inventoryItems.push('New Item');
}
};
var myVM= new MyViewModel();
ko.applyBindings(myVM);
The view model has a property called inventoryItems (which is from a service).
I am bidning that to view using ,
<ul data-bind="foreach:inventoryItems">
<li>
<input class="form-control" type="text" data-bind="value: $data" />
</li>
</ul>
<div class="text-right">
<a data-bind="click: $parent.addItems">+ Add more</a>
</div>
Now, the items that are already in the collection , inventoryItems are getting rendered fine.
When I am adding a new item using, I can see the items being added via console, but the view is not getting updated!
self.addItems = function(vm) {
vm.inventoryItems.push('New Item');
}
The below code snippet will make your inventoryItems observable
var MyViewModel = function () {
var self = this;
self.inventoryItems = ko.observableArray();
self.addItems = function (vm) {
vm.inventories.push('New Item');
self.inventoryItems(vm.inventories);
}
};
var myVM = new MyViewModel();
ko.applyBindings(myVM);

Underscore Template not working with Backbone View

I am not sure why my Underscore Template is not rendering. I would like it to show 3 select drop down menus based on the data returned.
Here's a fiddle to my code. Check the console for the data: http://jsfiddle.net/f7v3g/
If you see the data returned you'll see the following structure
-models
--attributes
---dimensions
----object0
-----name (Will be the text label that appears next to the first drop down menu)
-----refinements (children of refinements should be the option tags)
----object1
-----name (Will be the text label that appears next to the second drop down menu)
-----refinements (children of refinements should be the option tags)
----object2
-----name (Will be the text label that appears next to the third drop down menu)
-----refinements (children of refinements should be the option tags)
Here's the Backbone JavaScript:
(function () {
var DimensionsModel = Backbone.Model.extend({
defaults: {
dimensionName : 'undefined',
refinements : 'undefined'
}
});
var DimensionsCollection = Backbone.Collection.extend({
model: DimensionsModel,
url: 'http://jsonstub.com/calltestdata',
});
var setHeader = function (xhr) {
xhr.setRequestHeader('JsonStub-User-Key', '0bb5822a-58f7-41cc-b8a7-17b4a30cd9d7');
xhr.setRequestHeader('JsonStub-Project-Key', '9e508c89-b7ac-400d-b414-b7d0dd35a42a');
};
var DimensionsView = Backbone.View.extend({
el: '.js-container',
initialize: function (options) {
this.listenTo(this.model,'change', this.render);
this.model.fetch({
beforeSend: setHeader
});
console.log(this.model);
return this;
},
render: function () {
this.$el.html( this.template(this.model, 'dimensions-template') );
},
template: function (models, target) {
var templateSelectors = _.template($('#'+target).html(),{
dimensions: this.model
});
return templateSelectors;
},
});
var myCollection = new DimensionsCollection();
var myView = new DimensionsView({model: myCollection});
}());
Here is my HTML and Underscore template:
<div class="js-container">
<script type="text/template" id="dimensions-template">
<% _.each(dimensions, function(dimension,i){ %>
<%- dimension.get('dimensionName') %> <select id="<%- dimension.get('dimensionName') %>">
<option>Select</option>
<% _.each(dimension.get('refinements'), function(ref,x){ %>
<option data-refineurl='{
"refinementUrl": "<%- ref.refinementurl %>",
"nVal": "<%- ref.nval %>"
}'><%- ref.name %></option>
<% }); %>
</select>
<% }); %>
</script>
</div>
Edit: Spelling and example of data scructure.
I see few mistake:
1) inside DimensionsView initialize, you should add a this.render call
2) inside template: function (models, target), you use this.models. but you pass models as first parameter ?
3) Did you add model to your collection somewhere? now you template will try to loop over them. So it need models to loop in the collection.

Knockout JS Binding Null Binding

Here is a fiddle
I have this html:
<div class="margin:0px; padding:0px; outline:0; border:0;" data-bind="with: notesViewModel">
<table class="table table-striped table-hover" data-bind="with: notes">
<thead><tr><th>Date Logged</th><th>Content</th><th>Logged By</th><th></th></tr>
</thead>
<tbody data-bind="foreach: allNotes">
<tr>
<td data-bind="text: date"></td>
<td data-bind="text: compressedContent"></td>
<td data-bind="text: logged"></td>
<td><img src="/images/detail.png" data-bind="click: $root.goToNote.bind($data, $index())" width="20" alt="Details"/></td>
</tr>
</tbody>
</table>
<div class="noteView" data-bind="with: chosenNote">
<div class="info">
<p><label>Date:</label><span data-bind="text: date"></span></p>
<p><label>Logged:</label><span data-bind="text: logged"></span></p>
</div>
<p class="message" data-bind="html: content"></p>
<button class="btn btn-default" data-bind="click: $root.toNotes">Back to Notes</button>
</div>
<div class="editor-label" style="margin-top:10px">
Notes
</div>
<div class="editor-field">
<textarea id="contact_note" rows="5" class="form-control" data-bind="value: $root.noteContent"></textarea>
<p data-bind="text: $root.characterCounter"></p>
<button class="btn btn-info" data-bind="click: $root.saveNotes">Save</button>
<div data-bind="html: $root.status">
</div>
</div>
</div>
And this JavaScript using knockout:
var notesViewModel = function () {
var self = this;
self.notes = ko.observable(null);
self.chosenNote = ko.observable();
self.allNotes = new Array();
self.user = "user1";
// behaviours
self.goToNote = function (noteIndex) {
self.notes(null);
self.chosenNote(new note(self.allNotes[noteIndex]));
};
self.toNotes = function () {
self.chosenNote(null);
self.notes({ allNotes: $.map(self.allNotes, function (item) { return new note(item); }) });
console.log(self.notes());
}
self.noteContent = ko.observable();
self.saveNotes = function () {
var request = $.ajax({
url: "EnquiryManagement/Contact/SaveNotes",
type: "GET",
dataType: "json",
data: { id: "1322dsa142d2131we2", content: self.noteContent() }
});
request.done(function (result, message) {
var mess = "";
var err = false;
var imgSrc = "";
if (message = "success") {
if (result.success) {
mess = "Successfully Updated";
imgSrc = "/images/tick.png";
self.allNotes.push({ date: new Date().toUTCString(), content: self.noteContent(), logged: self.user });
self.toNotes();
} else {
mess = "Server Error";
imgSrc = "/images/redcross.png";
err = true;
}
} else {
mess = "Ajax Client Error";
imgSrc = "/images/redcross.png";
err = true;
}
self.status(CRTBL.CreateMessageOutput(err, mess, imgSrc));
self.noteContent(null);
setTimeout(function () {
self.status(null);
}, 4000);
});
};
self.status = ko.observable();
self.characterCounter = ko.computed(function () {
return self.noteContent() == undefined ? 0 : self.noteContent().length;
});
};
var note = function (data) {
var self = this;
console.log(data.date);
self.date = CRTBL.FormatIsoDate(data.date);
self.content = data.content;
self.compressedContent = data.content == null ? "" : data.content.length < 25 ? data.content : data.content.substring(0, 25) + " ...";
self.logged = data.logged;
console.log(this);
};
ko.applyBindings(new notesViewModel());
When I first load the page it says:
Uncaught Error: Unable to parse bindings.
Message: ReferenceError: notes is not defined;
Bindings value: with: notes
However, I pass it null, so it shouldn't show anything, because when I do the function goToNote then do goToNotes it sets the notes observable to null
So why can't I start off with this null value?
The problem is where you have:
<div data-bind="with: notesViewModel">
That makes it look for a property "notesViewModel" within your notesViewModel, which does not exist.
If you only have one view model you can just remove that data binding and it will work fine.
If, however, you wish to apply your view model to just that div specifically and not the entire page, give it an ID or some other form of accessor, and add it as the second parameter in applyBindings, as follows:
HTML:
<div id="myDiv">
JS:
ko.applyBindings(new notesViewModel(), document.getElementById('myDiv'));
This is generally only necessary where you have multiple view models in the same page.
Like what bcmcfc has put, however, due to my scenario being a multi-viewModel scenario I don't think his solution is quite the right one.
In order to achieve the correct results, first of all I extrapolated out the self.notes = ko.observable(null); into a viewModel which makes doing the table binding far easier.
Then to fix the binding issues instead of setting an element for the bind to take place, I merely did this:
ko.applyBindings({
mainViewModel: new mainViewModel(),
notesViewModel: new notesViewModel()
});
In my original code I have two viewModels which is why I was getting this error. With this method the key is:
I don't create dependancies!
Instead of tieing the viewModel to a certain dom element which can change quite easily and cause having to go and changes things with ko, plus if I add more viewModels then it can get more complicated. I simply do:
data-bind="with: viewModel"
That way I can bind to any DOM object and I can have has many as I like.
This is the solution that solved my post.
Here is the jsfiddle

Backbone.js event doubts

i have to create events using backbone.js.Below is my js code
var Trainee = Backbone.Model.extend();
var TraineeColl = Backbone.Collection.extend({
model: Trainee,
url: 'name.json'
});
var TraineeView = Backbone.View.extend({
el: "#area",
template: _.template($('#areaTemplate').html()),
render: function() {
this.model.each(function(good){
var areaTemplate = this.template(good.toJSON());
$('body').append(areaTemplate);
},this);
return this;
}
});
var good = new TraineeColl();
var traineeView = new TraineeView({model: good});
good.fetch();
good.bind('reset', function () {
$('#myButtons').click(function() {
traineeView.render();
});
});
<div class = "area"></div>
<div class="button" id="myButtons">
<button class="firstbutton" id="newbutton">
Display
</button>
</div>
<script id="areaTemplate" type="text/template">
<div class="name">
<%= name %>
</div>
<div class="eid">
<%= eid %>
</div>
<div class="subdomain">
<%= subdomain %>
</div>
my o/p on clicking display button is
Display // this is a button//
Sinduja
E808514
HPS
Shalini
E808130
HBS
Priya
E808515
HSG
Now from the view i have to bind a change event to the model..the changes in the model must be triggered on the view to display the output on the click of display button.
This isn´t exactly answering your queston but:
if trainee (I've renamed it to trainees) is a collection you should set it using:
new TraineeView({collection: trainees});
Then in render:
this.collection.models.each(function(trainee)
And you propably wan´t to move the call to fetch outside the view, in the router perhaps:
trainees = new TraineeColl();
view = new TraineeView({collection: trainees});
trainees.fetch();
That way your view only listens to the model.
You also should move the bind part to the views initialize method
this.collection.bind('reset', function () {
this.render();
});
Hope this helps.
var TraineeView = Backbone.View.extend({
el: "#area",
initialize : function(options){ // you will get the passed model in
//options.model
var trainee = new TraineeColl();
trainee.fetch();
trainee.bind('reset change', this.render,this); //change will trigger render
// whenever any model in the trainee collection changes or is modified
}
template: _.template($('#areaTemplate').html()),
render: function() {
this.model.each(function(trainee){
var areaTemplate = this.template(trainee.toJSON());
$('body').append(areaTemplate);
},this);
return this;
}
});
var traineeView = new TraineeView({model: trainee});
});

backbone.js update one view based on events in another?

I have the following HTML code:
<ul id='item-list'></ul>
<button id='add-item'>Add Item</button>
<script type='text/template' id='item-template'>
<li><%= title %></li>
</script>
and the following javascript / backbone js code:
var Item = Backbone.Model.extend({});
var ItemCollection = Backbone.Collection.extend({
model: Item
});
var ItemListView = Backbone.View.extend({
el : $("#item-list"),
initialize(options) {
this.item_collection = new ItemCollection();
},
appendItem: function() {
new_item = new Item({'title' : 'New Item'});
this.item_collection.add(new_item);
item_template = $("#item-template");
item_html = _.template(item_template,new_item.toJSON());
this.el.append(item_html);
}
});
var AddItemView = Backbone.View.extend({
el: $("add-item"),
events: {
"click" : "addItem"
},
addItem: function() {
// ?
}
});
item_list_view = new ListItemView();
add_item_view = new AddItemView();
How can I add a new item to the item list view and collection from an event in the addItemView View? also, should the creation of the model, appending it to the collection, and appending it to the view all take place in the ListItemView.addItem() function, or should I instead have it binded to the add event of the ItemCollection ? I am still having some trouble wrapping my head around the way bindings and the interactions between various views, models, and collections should work.
here's an example of events between 2 views working with a model/collection. Basically, use collectionName.bind('add',yourFunction,this);
<script type="text/template" id="item-template">
<div class="nonedit"><span ><%= name %> (<%= age %>)</span> <a class="delete" href="#">X</a>
<div class="edit"><input /></div>
</script>
<body>
<ul class="1"></ul>
<div class="count">
<div></div>
<input id="name" placeholder="enter a name"/>
<input id="age" placeholder="enter age"/>
</div>
</body>
var Person = Backbone.Model.extend({
defaults:function(){
return {
name:'unknown',
age:0
};
}
});
var People = Backbone.Collection.extend({
model:Person
});
var people = new People;
var Li = Backbone.View.extend({
tag:'li',
class:'name',
template:_.template($('#item-template').html()),
events:{
'click a.delete':'remove'
},
initialize:function(){
this.model.bind('change',this.render,this);
$(this.el).html(this.template(this.model.attributes));
},
render:function(){
$(this.el).html(this.template(this.model.attributes));
},
remove:function(){
this.model.destroy();
$(this.el).fadeOut(300);
setTimeout(function(){$(this.el).remove()},400);
},
modify:function(){
$(this.el).addClass('edit');
}
});
var Ul = Backbone.View.extend({
el:$('ul'),
events:{
},
initialize:function(){
people.bind('add',this.add,this);
},
render:function(){
},
add:function(model){
var li = new Li({model:model});
this.el.append(li.el);
}
});
var ul = new Ul;
var Div = Backbone.View.extend({
el:$('.count'),
nameInput:$('#name'),
ageInput:$('#age'),
events:{
'keypress input#name':'keypress',
'keypress input#age':'keypress',
},
initialize:function(){
people.bind('add',this.add,this);
},
render:function(){
},
add:function(e){
this.el.find('div').html(people.length);
},
keypress:function(event){
if(event.which == 13){
people.add({name:this.nameInput.val(),age:this.ageInput.val()});
}
}
});
var div = new Div;

Categories