I am attempting to create a custom binding for a knockout jquery datatable. (I was working on an existing SO question someone had posted) everything seems to be working well except when adding a new row. the datatable also adds the rows that were already there. my gues is that the .draw() function on the datatable is not firing. here is the fiddle.
http://jsfiddle.net/LkqTU/35814/
if you fill in the form and click add you will notice the original row is duplicated.
here is my binding.
ko.bindingHandlers.dataTable = {
init: function(element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor(),
rows = ko.toJS(value);
allBindings = ko.utils.unwrapObservable(allBindingsAccessor()),
$element = $(element);
var table = $element.DataTable( {
data: rows,
columns: [
{ data: "id" },
{ data: "firstName" },
{ data: "lastName" },
{ data: "phone" },
{
data: "ssn",
"render": function ( data, type, full, meta ) {
return '<a href="/Medlem/'+data+'">' + data + '<a>';
}
}
]
} );
alert('added');
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$element.dataTable().fnDestroy();
});
value.subscribe(function(newValue) {
rows = ko.toJS(newValue)
console.log(rows);
$element.find("tbody tr").remove();
table.rows().remove().draw();
$.each(rows, function( index, row ) {
table.row.add(row).draw()
});
}, null);
}
}
I just noticed if I add set timeout to the part where it adds the rows. then everything works. very odd.
setTimeout(function(){ $.each(rows, function( index, row ) {
table.row.add(row).draw()
});
}, 0);
here is the updated fiddle.
http://jsfiddle.net/LkqTU/35820/
It has specifically to do with the timing of the draw() call. This is because you have a Knockout binding inside your table, so you have both jQuery and Knockout trying to manage this DOM, and they're stepping on each other.
The answer: You don't need to have the table body at all; DataTables will put that in for you.
I've reorganized your code a bit to use the update portion of the bindingHandler for data updates.
ko.bindingHandlers.dataTable = {
init(element, valueAccessor, allBindingsAccessor) {
const $element = $(element);
$element.DataTable({
columns: [{
data: "id"
}, {
data: "firstName"
}, {
data: "lastName"
}, {
data: "phone"
}, {
data: "ssn",
"render": function(data, type, full, meta) {
return '<a href="/Medlem/' + data + '">' + data + '<a>';
}
}]
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$element.dataTable().fnDestroy();
});
},
update(element, valueAccessor, allBindingsAccessor) {
const rows = ko.toJS(valueAccessor());
const table = $(element).DataTable();
table.clear().rows.add(rows).draw();
}
}
function employee(id, firstName, lastName, phone, ssn) {
var self = this;
this.id = ko.observable(id);
this.firstName = ko.observable(firstName);
this.lastName = ko.observable(lastName);
this.phone = ko.observable(phone);
this.ssn = ko.observable(ssn);
}
function model() {
var self = this;
this.employees = ko.observableArray([
new employee('1', 'Joe', 'Smith', '333-657-4366', '111-11-1111')
]);
this.id = ko.observable('');
this.firstName = ko.observable('');
this.lastName = ko.observable('');
this.phone = ko.observable('');
this.ssn = ko.observable('');
this.add = function() {
self.employees.push(new employee(
this.id(), this.firstName(), this.lastName(), this.phone(), this.ssn()
));
}
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
});
<link href="//cdn.datatables.net/1.10.13/css/jquery.dataTables.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdn.datatables.net/1.10.13/js/jquery.dataTables.min.js"></script>
<table data-bind="dataTable: employees">
<thead>
<tr>
<th>Id</th>
<th>First</th>
<th>Last</th>
<th>Phone</th>
<th>ssn</th>
</tr>
</thead>
</table>
<p style="padding-top: 20px;">
Id:
<input data-bind="textInput: id" />
</p>
<p>
First:
<input data-bind="textInput: firstName" />
</p>
<p>
Last:
<input data-bind="textInput: lastName" />
</p>
<p>
phone:
<input data-bind="textInput: phone" />
</p>
<p>
ssn:
<input data-bind="textInput: ssn" />
</p>
<p>
<input type="button" value="add employee" data-bind="click: add" />
</p>
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>
Using Knockout and Semantic UI.
I'm trying to figure out how to get the values selected for my multi select dropdown. The first dropdown works with just single values, but the multi select one dosent. I have an observable array inside another collection:
<tbody id="tbodyelement" data-bind="foreach: groupUserCollection">
<tr>
<td>
<div class="ui selection dropdown fluid">
<input type="hidden" name="groupDD" data-bind="value: group.name">
<i class="dropdown icon"></i>
<div class="default text">Select Group</div>
<div class="menu" data-bind="foreach: $parent.groupCollection">
<div class="item" data-bind="text: $data.name(), attr: {'data-value': $data.id()}"></div>
</div>
</div>
</td>
<td>
<div class="ui multiple selection dropdown long-width" id="multi-select">
<div data-bind="foreach: user">
<input type="hidden" name="userDD" data-bind="value: firstLastName">
</div>
<div class="default text">Select User</div>
<div class="menu" data-bind="foreach: $parent.userCollection">
<div class="item" data-bind="text: $data.firstLastName(), attr: {'data-value': $data.id()}"></div>
</div>
<i class="dropdown icon"></i>
</div>
</td>
</tr>
</tbody>
I have one model groupuser that has a group model in it and a collection of roles.
var groupUser = function (data) {
var self = this;
self.group = ko.mapping.fromJS(data.group),
self.user = ko.observableArray([]),
self.id = ko.observable(data.id),
self.group.subscribe = function () {
showButtons();
},
self.user.subscribe = function () {
// self.user.push(data.user);
showButtons();
}
};
var group = function (data) {
var self = this;
self.id = ko.observable(data.id),
self.name = ko.observable(data.name),
self.project = ko.observable(data.project),
self.projectId = ko.observable(data.projectId),
self.role = ko.observable(data.role),
self.roleId = ko.observable(data.roleId)
};
var user = function (data) {
var self = this;
self.id = ko.observable(data.id),
self.accountId = ko.observable(data.accountId),
self.email = ko.observable(data.email),
self.firstName = ko.observable(data.firstName),
self.lastName = ko.observable(data.lastName),
self.firstLastName = ko.pureComputed({
read: function()
{
return self.firstName() + " " + self.lastName();
}
,
write: function(value)
{
var lastSpacePos = value.lastIndexOf(" ");
if (lastSpacePos > 0) {
self.firstName(value.substring(0, lastSpacePos));
self.lastName(value.substring(lastSpacePos + 1));
}
console.log("firstname: " + self.firstName());
}
}),
};
groupViewModel = {
groupUserCollection: ko.observableArray(),
userCollection: ko.observableArray(),
groupCollection: ko.observableArray()
}
I add the data using this function:
$(data).each(function (index, element) {
var newGroup = new group({
id: element.group.id,
name: element.group.name,
project: element.group.project,
projectId: element.group.projectId,
role: element.group.role,
roleId: element.group.roleId
});
newGroup.id.subscribe(
function () {
newGroupUser.showButtons();
}
);
newGroup.name.subscribe(
function () {
newGroupUser.showButtons();
}
);
var newGroupUser = new groupUser({
group: newGroup,
id: element.id,
});
ko.utils.arrayForEach(element.user, function (data) {
var newUser = new user({
id: data.id,
accountId: data.accountId,
email: data.email,
firstName: data.firstName,
lastName: data.lastName,
});
newUser.id.subscribe(
function () {
newGroupUser.showButtons();
}
);
newUser.firstName.subscribe(
function () {
newGroupUser.showButtons();
}
);
newUser.lastName.subscribe(
function () {
newGroupUser.showButtons();
}
);
newGroupUser.user.push(newUser);
});
groupViewModel.groupUserCollection.push(newGroupUser);
});
I ended up adding in a custom bind to the data-bind on the hidden input and it worked. But now my subscription dosent work when I add values or remove them.
Code that worked:
ko.bindingHandlers.customMultiBind = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.utils.arrayForEach(bindingContext.$data.user(), function (data) {
if (element.value === "")
{
element.value = ko.utils.unwrapObservable(data.id)
}
else {
element.value = element.value + "," + ko.utils.unwrapObservable(data.id)
}
});
}
};
I'm new to knockout. I already have this fiddle in which it will:
Populate the left box as the lists of items
Once you select an item in the left box, it will show the detailed item in the right box
It seems that I have it running correctly. But I want to add more to it.
Upon load, select the first foo with its displayed details
Once I click the + Add foo link, it will add new foo and have it highlighted then the editor in the right box will appear then when I click save it will add it to the collection of foo.
Is this possbile?
Snippet below
var data = [
{ id: 1, name: "foo1", is_active: true },
{ id: 2, name: "foo2", is_active: true },
{ id: 3, name: "foo3", is_active: true },
{ id: 4, name: "foo4", is_active: false },
];
var FooBar = function (selected) {
this.id = ko.observable("");
this.name = ko.observable("");
this.is_active = ko.observable(true);
this.status_text = ko.computed(function () {
return this.is_active() === true ? "Active" : "Inactive";
}, this);
this.is_selected = ko.computed(function () {
return selected() === this;
}, this);
};
var vm = (function () {
var foo_bars = ko.observableArray([]),
selected_foo = ko.observable(),
load = function () {
for (var i = 0; i < data.length; i++) {
foo_bars.push(new FooBar(selected_foo)
.name(data[i].name)
.is_active(data[i].is_active)
.id(data[i].id));
}
},
select_foo = function (item) {
selected_foo(item);
},
add_foo = function () {
// I have tried this but ain't working at all. Please help
// foo_bars.push(new FooBar(selected_foo));
};
return {
foo_bars: foo_bars,
load: load,
selected_foo: selected_foo,
select_foo: select_foo,
add_foo: add_foo
};
}());
vm.load();
ko.applyBindings(vm);
.box {
float: left;
width: 250px;
height: 250px;
border: 1px solid #ccc;
}
.selected {
background-color: #ffa !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="box">
+ Add foo
<table>
<thead>
<tr>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody data-bind="foreach: foo_bars">
<tr data-bind="click: $root.select_foo, css: { selected: is_selected }">
<td></td>
<td data-bind="text: $data.status_text"></td>
</tr>
</tbody>
</table>
</div>
<div class="box" data-bind="with: selected_foo">
Id: <span data-bind="text: id"></span><br />
Name: <input type="text" data-bind="value: name" /><br />
Status: <input type="checkbox" data-bind="checked: is_active"> <span data-bind="text: status_text"></span><br />
<button>Save</button>
</div>
demo: http://jsfiddle.net/wguhqn86/4/
load = function () {
for(var i = 0; i < data.length; i++){
foo_bars.push(new FooBar(selected_foo)
.name(data[i].name)
.is_active(data[i].is_active)
.id(data[i].id)
);
}
selected_foo(foo_bars()[0]); // default select first foo
},
add_foo = function() {
var count = foo_bars().length + 1;
var newItem = new FooBar(selected_foo).name('')
.is_active(true)
.id(count)
foo_bars.push(newItem);
selected_foo(newItem);
};
The new object you are adding didn't have the name and id assigned, that is probably why it was not showing up. In addition to that, once the new object is added you will need to select it before it can show up as selected in the left panel. below is my code definition for your add method
add_foo = function() {
var new_foo=new FooBar(selected_foo)
.name("foo"+(foo_bars().length+1));
select_foo(new_foo);
foo_bars.push(new_foo);
};
and the complete working fiddle can be found here http://jsfiddle.net/euq48em4/1/
I am developing a table editor with Ember.js. I created a view called FocusedTextField that focuses the text field when it is rendered. I want to implement a tabbing ability such that hitting tab will focus the next cell in a row. The current code will change the currently selected cell to be uneditable and change the next cell to be a text field, but will not focus on the next field's value. It seems that the code not working is an effect of timing. What's a better way of approaching this problem?
Here's my JSBin: http://emberjs.jsbin.com/tey/12/edit
jQuery 1.10.2
Handlebars 1.2.1
Ember 1.1.2
HTML:
<script type="text/x-handlebars" data-template-name="row">
<tr>
{{#collection cellCollection}}
{{view view.content.view}}
{{/collection}}
</tr>
</script>
<script type="text/x-handlebars" data-template-name="cell">
<td {{action "click"}}>
{{#if editMode}}
{{view textField valueBinding=value}}
{{else}}
{{value}}
{{/if}}
</td>
</script>
<script type="text/x-handlebars" data-template-name="table">
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Points</th>
</tr>
{{#collection rowCollection}}
{{view view.content.view}}
{{/collection}}
</table>
</script>
<div id="main"></div>
JavaScript:
App = Ember.Application.create();
var TemplatedViewController = Ember.Object.extend({
templateFunction: null,
viewArgs: null,
viewBaseClass: Ember.View,
view: function () {
var controller = this;
var viewArgs = this.get('viewArgs') || {};
var args = {
template: controller.get('templateFunction'),
controller: controller
};
args = $.extend(viewArgs, args);
return this.get('viewBaseClass').extend(args);
}.property('templateFunction', 'viewArgs'),
appendView: function (selector) {
this.get('view').create().appendTo(selector);
},
appendViewToBody: function () {
this.get('view').create().append();
},
appendPropertyViewToBody: function (property) {
this.get(property).create().append();
}
});
var FocusedTextField = Ember.TextField.extend({
focusTheTextField: function() {
this.$().focus();
}.on('didInsertElement')
});
var Cell = TemplatedViewController.extend({
row: null,
editMode: false,
value: null,
textField: function () {
var cell = this;
return FocusedTextField.extend({
keyDown: function (event) {
// Hitting the enter key disables edit mode
if (event.keyCode === 13) {
cell.set('editMode', false);
// Hitting the tab key selects the next cell
} else if (event.keyCode === 9) {
cell.set('editMode', false);
var nextCell = cell.getNextCell();
nextCell.set('editMode', true);
}
}
});
}.property(),
flipEditMode: function () {
if (this.get('editMode')) {
this.set('editMode', false);
} else {
this.set('editMode', true);
}
},
click: function () {
console.log('cell clicked, value: '+this.get('value'));
this.flipEditMode();
},
getNextCell: function () {
return this.get('row').getNextCell(this);
},
view: function () {
var controller = this;
return this.get('viewBaseClass').extend({
controller: controller,
templateName: 'cell'
});
}.property()
});
var Row = TemplatedViewController.extend({
headers: ['firstName', 'lastName', 'points'],
firstName: null,
lastName: null,
points: null,
cells: null,
cellCollection: function () {
return Ember.CollectionView.extend({
content: this.get('cells')
});
}.property('cells'),
init: function () {
this._super();
var row = this;
var cells = [];
this.get('headers').forEach(function (item, index, enumerable) {
var header = item;
var value = row.get(header);
var cell = Cell.create({
row: row,
value: value
});
cells.pushObject(cell);
});
this.set('cells', cells);
},
getNextCell: function (cell) {
if (this.get('cells').contains(cell)) {
var lastIndex = this.get('cells').length - 1;
var cellIndex = this.get('cells').indexOf(cell);
if (cellIndex < lastIndex) {
var nextIndex = cellIndex + 1;
return this.get('cells')[nextIndex];
}
}
},
view: function () {
var controller = this;
return this.get('viewBaseClass').extend({
controller: controller,
templateName: 'row'
});
}.property()
});
var rows = [];
var DATA = [
{first_name: 'Jill', last_name: 'Smith', points: 50},
{first_name: 'Eve', last_name: 'Jackson', points: 94},
{first_name: 'John', last_name: 'Doe', points: 80},
{first_name: 'Adam', last_name: 'Johnson', points: 67}
];
DATA.forEach(function (item, index, enumerable) {
var row = Row.create({
firstName: item.first_name,
lastName: item.last_name,
points: item.points
});
rows.pushObject(row);
});
var Table = TemplatedViewController.extend({
view: function () {
var controller = this;
return this.get('viewBaseClass').extend({
controller: controller,
templateName: 'table'
});
}.property(),
rows: null,
rowCollection: function () {
return Ember.CollectionView.extend({
content: this.get('rows')
});
}.property('rows')
});
var table = Table.create({rows: rows});
$(function () {
table.appendView('#main');
});
Wrap the call to focus() in Ember.run.next like so:
var FocusedTextField = Ember.TextField.extend({
focusTheTextField: function() {
var self = this;
Ember.run.next( function() { self.$().focus(); });
}.on('didInsertElement')
});
For a description of Ember.run.next, see: http://emberjs.com/api/classes/Ember.run.html#method_next
A good description of the Ember run loop: What is Ember RunLoop and how does it work?
I am trying to add the Rich Text Editor to my Survey system using CKeditor and knockout. I have my ViewModel, which has an observerable array of quesitons. I want to make the Name in each question use the ckeditor. I have look at the post Knockout.js: array parameter in custom binding. And have immplemented that but my OnBlur is not working. The ValueAccessor() is not returning an observable object. So I get an error that string is not a function() on this line of code..
var observable = valueAccessor();
observable($(element).val());
Here is my Html, I am just using a static Id for now on question, and was going to change that after I got this to work for just one question in the array.
<tbody data-bind="foreach: questionModel">
<tr>
<td>
<button data-bind='click: $root.addQuestion' class="btn btn-success" title="Add Question"><i class="icon-plus-sign fontColorWhite"></i></button>
<button data-bind='click: $root.removeQuestion' class="btn btn-danger" title="Remove Question"><i class="icon-minus-sign fontColorWhite"></i></button>
</td>
<td><textarea id="question123" class="RichText" data-bind="richText: Name"></textarea></td>
<td><input type="checkbox" data-bind="checked: AllowComment" /></td>
<td><button data-bind="click: $root.addAnswer" class="btn btn-success" title="Add Answer"><i class="icon-plus-sign fontColorWhite"></i></button></td>
<td>
<div data-bind="foreach: possibleAnswerModel">
<input style="width: 278px" style="margin-bottom: 5px;" data-bind='value: Name' />
<button data-bind='click: $root.removeAnswer' class="btn btn-danger" title="Remove Answer"><i class="icon-minus-sign fontColorWhite"></i></button>
</div>
</td>
</tr>
<tr>
</tbody>
Below is my ViewModel as well as my custom binding....
ko.bindingHandlers.richText = {
init: function (element, valueAccessor, allBindingsAccessor, ViewModel) {
var txtBoxID = $(element).attr("id");
console.log("TextBoxId: " + txtBoxID);
var options = allBindingsAccessor().richTextOptions || {};
options.toolbar_Full = [
['Bold', 'Italic'],
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent'],
['Link', 'Unlink']
];
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
if (CKEDITOR.instances[txtBoxID]) {
CKEDITOR.remove(CKEDITOR.instances[txtBoxID]);
};
});
$(element).ckeditor(options);
//wire up the blur event to ensure our observable is properly updated
CKEDITOR.instances[txtBoxID].focusManager.blur = function () {
console.log("blur");
console.log("Value: " + valueAccessor());
console.log("Value: " + $(element).val());
var observable = valueAccessor();
observable($(element).val());
};
},
update: function (element, valueAccessor, allBindingsAccessor, ViewModel) {
var value = valueAccessor();
console.log("Value Accessor: " + value);
var valueUnwrapped = ko.utils.unwrapObservable(value);
//var val = ko.utils.unwrapObservable(valueAccessor());
console.log("Value: " + valueUnwrapped);
$(element).val(valueUnwrapped);
}
};
function ViewModel(survey) {
// Data
var self = this;
self.StartDate = ko.observable(survey.StartDate).extend({ required: { message: 'Start Date is required' } });
self.EndDate = ko.observable(survey.EndDate).extend({ required: { message: 'End Date is required' } });
self.Name = ko.observable(survey.Name).extend({ required: { message: 'Name is required' } });
self.ButtonLock = ko.observable(true);
self.questionModel = ko.observableArray(ko.utils.arrayMap(survey.questionModel, function(question) {
return { Id: question.QuestionId, Name: ko.observable(question.Name), Sort: question.Sort, IsActive: question.IsActive, AllowComment: question.AllowComment, possibleAnswerModel: ko.observableArray(question.possibleAnswerModel) };
}));
// Operations
self.addQuestion = function () {
self.questionModel.push({
Id: "0",
Name: "",
AllowComment: true,
Sort: self.questionModel().length + 1,
possibleAnswerModel: ko.observableArray(),
IsActive:true
});
};
self.addAnswer = function (question) {
question.possibleAnswerModel.push({
Id: "0",
Name: "",
Sort: question.possibleAnswerModel().length + 1,
IsActive:true
});
};
self.GetBallotById = function (id) {
for (var c = 0; c < self.BallotProjectStandardList().length; c++) {
if (self.BallotProjectStandardList()[c].BallotId === id) {
return self.BallotProjectStandardList()[c];
}
}
return null;
};
self.removeQuestion = function(question) { self.questionModel.remove(question); };
self.removeAnswer = function(possibleAnswer) { $.each(self.questionModel(), function() { this.possibleAnswerModel.remove(possibleAnswer) }) };
self.save = function() {
if (self.errors().length == 0) {
self.ButtonLock(true);
$.ajax("#Url.Content("~/Survey/Create/")", {
data: ko.toJSON(self),
type: "post",
contentType: 'application/json',
dataType: 'json',
success: function(data) { self.successHandler(data, data.success); },
error: function() {
self.ButtonLock(true);
self.errorHandler();
}
});
} else {
self.errors.showAllMessages();
}
};
}
ViewModel.prototype = new ErrorHandlingViewModel();
var mainViewModel = new ViewModel(#Html.Raw(jsonData));
mainViewModel.errors = ko.validation.group(mainViewModel);
ko.applyBindings(mainViewModel);
I figured what I was doing wrong. When I define the observableArray() I was defining the object as ko.observable, however, when I add a question to the array, I was initializing it as a string. So I change that to match and it worked like a champ. Here is the change push.
self.questionModel = ko.observableArray(ko.utils.arrayMap(survey.questionModel, function(question) {
return { Id: question.QuestionId, Name: ko.observable(question.Name), Sort: question.Sort, IsActive: question.IsActive, AllowComment: question.AllowComment, possibleAnswerModel: ko.observableArray(question.possibleAnswerModel) };
}));
// Operations
self.addQuestion = function () {
self.questionModel.push({
Id: "0",
Name: ko.observable(),
AllowComment: true,
Sort: self.questionModel().length + 1,
possibleAnswerModel: ko.observableArray(),
IsActive:true
});
};