I have a JS object:
var bookmark = {
id: 'id',
description: 'description',
notes: 'notes'
}
I want to bind to the entire object, display notes in a textarea, and subscribe to changes to notes.
Here's what I have so far:
this.bookmark = ko.observable();
this.bookmark.subscribe = function(bookmarkWithNewNotes) {
//use the bookmarkWithNewNotes.id to update the bookmark in the db
}
I'm setting the bookmark like so:
this.bookmark(ko.mapping.fromJS(existingBookmark));
The view looks like this:
<div databind="with: $root.bookmark" >
Notes
<textarea class="userNotes" rows="10" data-bind="value: notes" ></textarea>
</div>
This isn't working. What do I need to do to make this work the way I want it to work?
Thanks!
Here is a example in Fiddle.
You could do something like this:
<div>
Notes
<div data-bind="foreach: bookmarks">
<textarea rows="10" data-bind="value: note"></textarea>
</div>
</div>
and create viewmodel for your bookmark, like so:
function BookmarkViewModel(id, description, note) {
var self = this;
self.id = id;
self.description = ko.observable(description);
self.note = ko.observable(note);
self.note.subscribe(function(val) {
alert("Save note, id: " + self.id + ", description: " + self.description() + ", note: " + self.note());
});
return self;
}
after you get your data, create VM for every item, like so:
function AppViewModel(data) {
var self = this;
self.bookmarks = ko.observableArray();
for (var i = 0; i < data.length; i++) {
self.bookmarks().push(new BookmarkViewModel(data[i].id, data[i].description, data[i].note));
};
return self;
}
You can create a seperate service to get your data, i just mocked this for poc.
$(function() {
var data = [{
id: 1,
description: 'some description',
note: 'some note'
}, {
id: 2,
description: 'some other description',
note: 'some other note'
}];
ko.applyBindings(new AppViewModel(data));
});
Related
For some reason, foreach in Knockout.js doesn't iterate through my observable array.
In my HTML I have this which works perfectly fine with the observable model:
<div class="field-group">
<label class="popup-label" for="email">Email</label>
<span class="email" data-bind="text: masterVM.employeeVM.Email"></span>
</div>
But in the same model, this code doesn't work:
<ul data-bind="foreach: { data: masterVM.employeeVM.Tags, as: 'tag' }">
<li>
<span class="popup-tag" data-bind="text: tag.tagName"><i class="zmdi zmdi-delete"></i></span>
</li>
</ul>
There are two models:
Employee
var observableEmployee = function(id, email, tags) {
var self = this;
self.Id = ko.observable(id);
self.Email = ko.observable(email);
self.Tags = ko.observableArray(ko.utils.arrayMap(tags, function(item) {
return new observableTag(item.Id, item.EmployeeId, item.TagId, item.tagName)
}));
self.errors = ko.validation.group(this, {
deep: true
});
self.isValid = ko.computed(function() {
return self.errors().length > 0 ? false : true;
});
}
and Tag
var observableTag = function(id, employeeId, tagId, tagName) {
var self = this;
self.Id = ko.observable(id);
self.employeeId = ko.observable(employeeId);
self.tagId = ko.observable(tagId);
self.TagName = ko.observable(tagName);
self.errors = ko.validation.group(this, {
live: true
});
self.isValid = ko.computed(function() {
return self.errors().length > 0 ? false : true;
});
}
and handler function:
var employeeHandler = function () {
var self = this;
self.getEmployeeDetails = function (header) {
$.ajax({
url: masterVM.controller.renderEmployeeDetails,
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({ id: header.data("employeeid") }),
success: function (result) {
masterVM.employeeVM = new observableEmployee(
result.model.Id,
result.model.Email,
result.model.Tags
);
ko.applyBindings(masterVM, $("#employee-planning-selected")[0]);
//header.parent().addClass('open');
//header.next().slideDown('normal');
//hideLoader(header);
console.log('get employee details');
$(document).on('click', "div.employee", onNameCardClick);
},
error: function (xhr, ajaxOptions, thrownError) {
alert('Error!');
}
});
}}
In my HTML file
<script>
masterVM = {
controller: {
renderEmployeeDetails: '#(Html.GetActionUrl<EmployeesController>(c => c.RenderEmployeeDetails(0)))'
},
employeeHandler: new employeeHandler(),
employeeVM: new observableEmployee(0, '', '', '', '')
}
ko.applyBindings(masterVM);
</script>
Tried something like this, and still nothing
<!--ko foreach: employeeVM.Tags -->
<span data-bind="text: $data.Tags"></span>
<!-- /ko -->
And no, there are no errors in the console, I have used KnockouJS context debugger which shows me that there are elements in this collection, even when I try to display them as an object it shows me a list of 4 elements.
Knockout version: 2.3.0
1). If you are binding masterVM object in ko.applyBindings(masterVM), you don't need to specify that object again in your data-bindings.
So, it should be
foreach: { data: employeeVM.Tags, as: 'tag' }
And not
foreach: { data: masterVM.employeeVM.Tags, as: 'tag' }
(I'm not sure how the first data-bind="text: masterVM.employeeVM.Email" is working)
2). You don't need to call applyBindings more than once. If you want to update the employee object, you can turn your employeeVM into an observable and keep updating it inside getEmployeeDetails method.
3) Your containerless control flow syntax won't work. (<!--ko foreach: employeeVM.Tags -->). Inside this foreach, $data is the current Tag object in context. So, it should be <span data-bind="text: $data.TagName"></span>
Here's a minimal version of the code. Click on "Run code snippet" to test it. When you click on Update employee button, I'm updating the employeeVM observable and the data gets rendered again. Without calling applyBindings again
var employeeHandler = function() {
var self = this;
self.getEmployeeDetails = function(header) {
var newEmployee = new observableEmployee(0, 'newEmployee#xyz.com', [{
Id: 3,
EmployeeId: 3,
TagId: 3,
tagName: 'Tag Name 3'
}]);
// You need to use employeeVM(newEmployee) instead of employeeVM = newEmployee
// Because employeeVM is an observable.
masterVM.employeeVM(newEmployee);
}
}
var observableEmployee = function(id, email, tags) {
var self = this;
self.Id = ko.observable(id);
self.Email = ko.observable(email);
self.Tags = ko.observableArray(ko.utils.arrayMap(tags, function(item) {
return new observableTag(item.Id, item.EmployeeId, item.TagId, item.tagName)
}));
}
var observableTag = function(id, employeeId, tagId, tagName) {
var self = this;
self.Id = ko.observable(id);
self.employeeId = ko.observable(employeeId);
self.tagId = ko.observable(tagId);
self.TagName = ko.observable(tagName);
}
var masterVM = {
controller: {
renderEmployeeDetails: ''
},
employeeHandler: new employeeHandler(),
// change this to an observable
employeeVM: ko.observable(new observableEmployee(0, 'abc#xyz.com', [{
Id: 1,
EmployeeId: 1,
TagId: 1,
tagName: 'Tag name 1'
}]))
}
ko.applyBindings(masterVM);
document.getElementById("button").addEventListener("click", function(e) {
masterVM.employeeHandler.getEmployeeDetails()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="field-group">
<label class="popup-label" for="email">Email:</label>
<span class="email" data-bind="text: employeeVM().Email"></span>
</div>
<ul data-bind="foreach: { data: employeeVM().Tags, as: 'tag' }">
<li>
<span class="popup-tag" data-bind="text: tag.employeeId"></span> <br>
<span class="popup-tag" data-bind="text: tag.tagId"></span><br>
<span class="popup-tag" data-bind="text: tag.TagName"></span>
</li>
</ul>
<button id="button">Update employee</button>
I'm doing a JS script which will render a tree view with picking abilities.
But something strange is happening and i found out what it was, however this doesn't make me happy and i want to discover why it's happening.
var picker1 = {},
picker2 = {};
(function($) {
$.fn.treePicker = function(params) {
var exists = $(this).length > 0;
if (exists) {
params.controlDiv = $(this);
var picker = new treePicker(params);
return picker;
}
};
var treePicker = function(params) {
this.mapping.id = params.mapping.id; // Change to this.mapping = params.mapping;
};
$.extend(treePicker.prototype, {
mapping: {
id: 'id',
text: 'text',
},
});
var picker1 = $("#render1").treePicker({
mapping: {
id: 'Id1',
text: 'text1'
}
});
var picker2 = $("#render2").treePicker({
mapping: {
id: 'Id2',
text: 'text2'
}
});
$("#result1").val(picker1.mapping.id + "," + picker1.mapping.text);
$("#result2").val(picker2.mapping.id + "," + picker2.mapping.text);
}(jQuery));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" id="result1" />
<input type="text" id="result2" />
<div id="render1"></div>
<div id="render2"></div>
I've two divs where the controls will be rendered.
To this demo I've two inputs where i show the pickers data.
I'm setting the property picker1.mapping.id and picker1.mapping.text (line 24-29) to match id1 and text1.
var picker1 = $("#render1").treePicker({
mapping: {
id: 'Id1',
text: 'text1'
}
});
Then, I'm setting the same properties to picker2 (line 31-36).
My "constructor" is located on
var treePicker = function (params) {
this.mapping.id = params.mapping.id; // Change to this.mapping.id = params.mapping.id
};
And I'm extending the "class" doing this:
$.extend(treePicker.prototype, {
mapping: {
id: 'id',
text: 'text',
},
});
The problem is that while doing this, picker1.mapping.id will not keep his own value and instead will hold picker2 value.
But... if I change
this.mapping.id = params.mapping.id;
to
this.mapping = params.mapping;
It will work.
Any ideas?
I have the backbone code below which shows a list of models on a html page. However the models are created staticly, what modifications do I have to make on the code below to fetch the JSON data and create models based on the data received.
var Person = Backbone.Model.extend({
defaults: {
name: "John Doe",
age: 27,
designation: "worker"
},
initialize : function(){
this.on("invalid",function(model,error){
alert(error);
});
},
validate: function(attrs){
if(attrs.age < 0){
return 'Age must be positive,stupid';
}
if( ! attrs.name ){
return 'Name should not be empty';
}
},
work: function(){
return this.get('name') + ' is a ' + this.get('designation');
}
});
var PersonCollection = Backbone.Collection.extend({
model: Person
});
var peopleView = Backbone.View.extend({
tagName: 'ul',
render: function(){
//filter through all the items in a collections
//for each, create a new PersonView
//append it to root element
this.collection.each(function(person){
//console.log(person);
var personView = new PersonView({model:person});
this.$el.append(personView.render().el);
},this);
$(this.$el).appendTo('body');
}
});
// The view for a Person
var PersonView = Backbone.View.extend({
tagName : 'li',
className : 'person',
id : 'person-id',
template: _.template( $('#personTemplate').html() ),
initialize : function(){
_.bindAll(this,'render');
//console.log(this.model)
this.render();
},
render: function(){
$(this.$el.html(this.template(this.model.toJSON()))).appendTo('body');
return this;
}
});
var modelperson = new Person;
var viewperson = new PersonView({collection:new PersonCollection(),model : modelperson});
var personCollection = new PersonCollection([
{
name: "raghu",
age:24,
designation: "CEO"
},
{
name: "shashank",
age:23,
designation: "CTO"
},
{
name : "junaid",
age : 30,
designation : "UI"
},
{
name: "vishnu",
age: 23,
designation: "content developer"
}
]);
var pw = new peopleView({collection: personCollection});
pw.render();
html
<script type="text/template" id="personTemplate">
<div class="row">
<div class="cell"><%= name %></div>
<div class="cell"><%= age %></div>
<div class="cell"><%= designation %></div>
</div>
</script>
test on jsfiddle http://jsfiddle.net/HVqMs/1/
You mean fetching the collection? here is a simple example
var Group = Backbone.Model.extend( {
urlRoot: Constants.URL_PREFIX+'/api/v1/group',
});
var GroupCollection = Backbone.Collection.extend( {
url: Constants.URL_PREFIX+'/api/v1/group',
model: Group,
parse(response){
this.metaData = new Backbone.Model(response.meta);
return response.objects;
}
});
then to fetch the collection lets call it MyCollection
var p = new GroupCollection();
p.fetch().done( function() {
p.each(function(item){
console.log(item.get('name'));
});
});
In my example the json returned is something like this
{
metaData:{ size:10,limit:10},
objects:[{name:'pep',id:1},{name:'pep2',id:2}]
}
thats why I override the parse.
I have 2 models as
var Info = Backbone.Model.extend({
defaults: {
name: '',
company: ''
},
initialize: function(){
console.log('Object of type Info created');
},
});
var Emp = Backbone.Model.extend({
defaults: {
empId: '',
empGroup: ''
},
initialize: function(){
console.log('Object of type Emp created');
}
});
View is created as
var model = new Info();
model.set({
name: 'John',
company: 'ABC'
});
model.bind('change', function(){
model.save();
});
model.trigger('change');
var ViewClass = Backbone.View.extend({
_modelBinder: undefined,
initialize: function(){
this._modelBinder = new Backbone.ModelBinder();
this.render();
},
render: function(){
var template = _.template($('#App1').html());
this.$el.html(template);
var bindings = {
name: '[name=name]',
empId: '[name=empId]'
};
this._modelBinder.bind(model, this.el, bindings); // this will bind for Info.
}
});
HTML:
<script type="text/template" id="App1">
<div id="wrapper">
Name: <input type="text" name="name" /><br />
EmpId: <input type="text" name="empId" />
</div>
</script>
How can we bind for both Info and Emp Models ?
I dont really know how Backbone.ModelBinder(); work but i suppose that you have to create two binding;
var infoBindings = {
name: '[name=name]',
};
this._modelBinder.bind(infoModel, this.el, infoBindings); // this will bind for Info.
var empBindings = {
empId: '[name=empId]'
};
this._modelBinder.bind(empModel, this.el, empBindings); // this will bind for Emp.
how do I remove remove a model in collection and make the remove event fire . I tried people.remove([{ name: "joe3" }]); but it wont work.
var Person = Backbone.Model.extend({
initialize: function () {
console.log(" person is initialized");
},
defaults: {
name: "underfined",
age:"underfined"
}
});
var People = Backbone.Collection.extend({
initialize: function () {
console.log("people collection is initialized");
this.bind('add', this.onModelAdded, this);
this.bind('remove', this.onModelRemoved, this);
},
model: Person,
onModelAdded: function(model, collection, options) {
console.log("options = ", options);
alert("added");
},
onModelRemoved: function (model, collection, options) {
console.log("options = ", options);
alert("removed");
},
});
//var person = new Person({ name: "joe1" });
var people = new People();
//people.add([{ name: "joe2" }]);
people.add([{ name: "joe1" }]);
people.add([{ name: "joe2" }]);
people.add([{ name: "joe3" }]);
people.add([{ name: "joe4" }]);
people.add([{ name: "joe5" }]);
people.remove([{ name: "joe3" }]);
console.log(people.toJSON());
For anyone else looking for a remove where, you can simply chain it with a collection.where call. like so to remove all items matching the search:
people.remove(people.where({name: "joe3"}));
see Backbone collection.where
By doing:
people.remove([{ name: "joe3" }]);
you don't remove a model, because you pass just an plain object which is not connected to people collection. Instead you could do something like this:
people.remove(people.at(2));
Or:
var model = new Person({name: "joe3"});
people.add(model);
...
people.remove(model);
will work as well.
So you need to reference actual model object from a collection;
http://jsfiddle.net/kD9Xu/
Another way is shorter a little and fires the remove event for a collection as well:
people.at(2).destroy();
// OR
people.where({name: "joe2"})[0].destroy();
Triggers a "destroy" event on the model, which will bubble up through any collections that contain it.. http://backbonejs.org/#Model-destroy
var Person = Backbone.Model.extend({
defaults: {
name: "underfined",
age:"underfined"
}
});
var People = Backbone.Collection.extend({
initialize: function () {
this.bind('remove', this.onModelRemoved, this);
},
model: Person,
onModelRemoved: function (model, collection, options) {
alert("removed");
},
getByName: function(name){
return this.filter(function(val) {
return val.get("name") === name;
})
}
});
var people = new People();
people.add(new Person({name:"joe1"}));
people.add(new Person({name:"joe2"}));
people.remove(people.getByName("joe1"));
console.info(people.toJSON());
In order to remove "[0]" you can use the following code:
people.findWhere({name: "joe2"}).destroy();