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/
Related
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 am trying to add a click event listener to the dynamically created li's so that they work independent of each other(currently, all three fire when you click one of them). Every solution I see involves a closure which I thought I was doing but i guess not. Also, the view keeps getting updated with last element in the model array and I can't figure out why. Any help is greatly appreciated.
$(document).ready(function() {
//Data displayed in the view
var model = {
carOnDisplay: null,
listOfCars: [{
numClicks: 0,
carName: "Challenger",
color: 'grey'
},
{
numClicks: 0,
carName: "Charger",
color: 'blue'
},
{
numClicks: 0,
carName: "Mustang",
color: 'orange'
},
]
};
var controller = {
init: function() {
// set the default car to be displayed
model.carOnDisplay = model.listOfCars[0];
carView.init();
carListView.init();
},
getListOfCars: function() {
return model.listOfCars;
},
setCurrentCar: function(car) {
model.carOnDisplay = car;
},
getCurrentCar: function() {
return model.carOnDisplay;
},
incrementClicks: function() {
model.carOnDisplay.numClicks++;
carView.render();
}
};
var carView = {
init: function() {
// cached DOM
this.car = $('#car');
this.name = $('h3.carName');
this.clicks = $('h4.clicks');
// add click listeners to car
this.car.click(function() {
controller.incrementClicks();
});
this.render();
},
render: function() {
var currentCar = controller.getCurrentCar();
this.car.css('background-color', currentCar.color);
this.name.html(currentCar.carName);
this.clicks.html(currentCar.numClicks);
}
};
var carListView = {
init: function() {
// cache DOM elements
this.carList = $('#carList');
this.render();
},
render: function() {
// this.links = $('#carList').getElementsByTagName('li');
var car = controller.getListOfCars(),
currentCar = controller.getCurrentCar();
for (var i = 0; i < car.length; i++) {
var carNow = car[i];
$('#carList').append("<li>" + carNow.carName + "</li>");
$('#carList').on('click', one());
// // controller.setCurrentCar(carNow);
// // carView.render();
};
function one() {
return function(event) {
controller.setCurrentCar(carNow);
carView.render();
}
}
}
} // end of carListView
controller.init();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="row">
<div class="col-3">
<ul id="carList">
</ul>
</div>
<div id="content" class="col-9">
<div id="car" class="col-4">
<h3 class="carName">x</h3>
<h4 class="clicks"></h4>
</div>
</div>
</div>
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?
With the help of Ryan Niemeyer's blog post http://www.knockmeout.net/2012/02/revisiting-dragging-dropping-and.html I have written code that allows dragging and dropping between nested observablearrays. The problem I'm facing is when I remove all items under "Encounter", and want to return an item, it can't find the container or drop area to properly work. It will drop as if it does work, but if you look at the JSON output, you'll see that it does not properly update. The same problem exists when I remove all encounters, and try to move one encounter back... it doesn't find it's "drop zone". I'm hoping it is something trivial and appreciate the help.
I have a demo of it here http://plnkr.co/edit/n7IGItDOYTzCSfDHlwJS?p=preview
Here is the script:
$(function() {
//control visibility, give element focus, and select the contents (in order)
ko.bindingHandlers.visibleAndSelect = {
update: function(element, valueAccessor) {
ko.bindingHandlers.visible.update(element, valueAccessor);
if (valueAccessor()) {
setTimeout(function() {
$(element).find("input").focus().select();
}, 0); //new encounters are not in DOM yet
}
}
};
var Dataset = function(name) {
var self = this;
self.dName = name;
self.type = "Dataset";
};
// Encounter construction
var Encounter = function(name, dss) {
var self = this;
self.name = name;
self.datasets = ko.observableArray([]);
self.type = "Encounter";
$.each(dss, function(i, p) {
self.datasets.push(new Dataset(p.dName));
});
};
// Patient construction
var Patient = function(id, encounters) {
var self = this;
self.id = ko.observable(id);
self.encounters = ko.observableArray([]);
$.each(encounters, function(i, p) {
self.encounters.push(new Encounter(p.name, p.Datasets));
});
};
my.vm = {
selectedItem: ko.observable(),
patients: ko.observableArray([]),
targetPatients: ko.observableArray([]),
isSelected: function(selectedItem) {
return selectedItem === this.selectedItem();
},
isDropSpotMatch: function(arg) {
//if (!my.vm.isSourceEncounterSelected && !my.vm.isTargetEncounterSelected) {
// arg.cancelDrop = true;
//}
//var t = ko.toJSON(arg);
//console.log(arg);
//alert(ko.toJSON(arg.sourceParent()[0]));
console.log(arg.sourceParent()[0]);
console.log(arg.targetParent()[0]);
if (arg.sourceParent()[0].type != arg.targetParent()[0].type){
arg.cancelDrop = true;
}
},
clearItem: function(data, event) {
if (data === self.selectedItem()) {
my.vm.selectedItem(null);
}
if (data.name() === "") {
my.vm.patients.remove(data);
my.vm.targetPatients.remove(data);
}
},
// loading the observable array with sample data
load: function() {
$.each(my.sourceData.data.Patients, function(i, p) {
my.vm.patients.push(new Patient(p.Id, p.Encounters));
});
$.each(my.targetData.data.Patients, function(i, p) {
my.vm.targetPatients.push(new Patient(p.Id, p.Encounters));
});
}
};
ko.bindingHandlers.sortable.beforeMove = my.vm.isDropSpotMatch;
//ko.bindingHandlers.sortable.afterMove = my.vm.isDropSpotMatch;
my.vm.load();
ko.applyBindings(my.vm);
});
Here is my html:
<!DOCTYPE html>
<script data-require="knockout#*" data-semver="3.0.0" src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js"></script>
<script src="knockout-sortable.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="data.js"></script>
<script src="script.js"></script>
</head>
<body>
<div class="page">
<div id="main">
<div class="showroom">
<table>
<tr>
<td>
<span data-bind="sortable: { template: 'taskTmpl', data: patients }"></span>
</td>
<td>
<span data-bind="sortable: { template: 'taskTmpl', data: targetPatients }"> </span>
</td>
</tr>
</table>
<script id="taskTmpl" type="text/html">
<ul data-bind="sortable: encounters">
<div class="container"><div class="item">
<li>
<div class="encounterItem">
<span data-bind="visible: !$root.isSelected($data)">
</span>
<span data-bind="visibleAndSelect: $root.isSelected($data)">
<input data-bind="value: name, event: { blur: $root.clearItem }" />
</span>
</div>
</div>
<ul data-bind="sortable: datasets">
<li>
<div class="datasetItem">
<span data-bind="visible: !$root.isSelected($data)">
</span>
<span data-bind="visibleAndSelect: $root.isSelected($data)">
<input data-bind="value: dName, event: { blur: $root.clearItem }" />
</span>
</div>
</li>
</ul>
</li>
</ul>
</div>
</div></script>
<div>
JSON OUTPUT
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
</div>
</div>
</div>
</div>
</body>
</html>
Here is the css:
.container {
background-color: #BBB;
}
.datasetItem {
background-color: #BBB;
cursor: move;
text-align: left;
width: 100px;
}
.encounterItem {
background-color: #BBB;
cursor: move;
text-align: left;
width: 100px;
}
.encounterItem input {
width: 40px;
}
.datasetItem input {
width: 40px;
}
.ko_container {
width: 255px;
min-height: 50px;
background-color: #ADDA;
}
And finally my namespace and dummy data:
var my = my || { }; //my namespace
my.dataservice = (function (my) {
"use strict";
var getPatients = function () {
return my.sampleData;
};
return {
getPatient: getPatients
};
})(my);
my.sourceData = (function (my) {
"use strict";
var data = { Patients: [
{ "Id": "1stId", "Encounters": [ { "name": "1stEncounter", "Datasets": [ { "dName": "1stDataset"} ] }, { "name": "2ndEncounter", "Datasets": [ { "dName": "2ndDataset"} ] } ] }
]
};
return {
data: data
};
})(my);
my.targetData = (function (my) {
"use strict";
var data = { Patients: [
{ "Id": "T1stId", "Encounters": [ { "name": "21stEncounter", "Datasets": [ { "dName": "21stDataset"} ] }, { "name": "22ndEncounter", "Datasets": [ { "dName": "22ndDataset"} ] } ] }
]
};
return {
data: data
};
})(my);
I know this is silly, but add a minimum height to the list container.
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
});
};