How to define knockout data model for given situation? - javascript

My data model is as given below.
Module
Fields (ObservableArray)
Actions(ObservableArray)
Fields (?)
name (dyanmic from field list)
type (dynamic from field list)
selected (entered by user in UI)
Module is main object. Fields and Actions are observable arrays. Field lists under each action needs to be have updated field list and also will have an additional property which is captured from UI.
How the Fields under action model should be populated? Fields list under each action will have unique value for selected field.
Do I need to subscribe to fields ObservableArray and manipulate the Fields list under each action manually or is there any other better way doing this?

This is how I handle this situation
http://plnkr.co/edit/sWVqrFHdzWUXob42xS7Z?p=preview
Javascript
var childObject = function(data){
var self = this;
//No special mapping needed here, the mapping plugin does it for us
ko.mapping.fromJS(data, {}, self);
this.Select = function(){
self.selected(!self.selected());
};
};
var parentObject = function(data){
var self = this;
//Map the object to myself, using the mapping object we made earlier
ko.mapping.fromJS(data, {}, self);
//Remap the actions column to observable's
this.Actions = ko.observableArray(_.map(self.Actions(), function(item){
return new childObject(item);
}));
};
var myViewModel = function(){
var self = this;
this.RootObject = ko.observable();
var objectData = {
"Fields": [1, 2, 3, 4],
"Actions": [
{
"name": "David",
"type": "string",
"selected": false
},
{
"name": "Nish",
"type": "string",
"selected": true
}]
};
this.Init = function(){
//Pass the object data to the parent object.
self.RootObject(new parentObject(objectData))
};
};
$(function(){
myApp = new myViewModel();
myApp.Init();
ko.applyBindings(myApp);
})
Html
<div data-bind="with: RootObject">
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Selected</th>
</tr>
</thead>
<tbody data-bind="foreach: Actions">
<tr data-bind="click: Select">
<td data-bind="text: name"></td>
<td data-bind="text: type"></td>
<td data-bind="text: selected"></td>
</tr>
</tbody>
</table>
</div>

Related

AngularJS In ng-repeat splice the last object of an array and it removes the properties of a form tag

In my AngularJS app, I use ng-repeat to display rows of records. When the user removes the last record in the array, it removes the custom properties on the form tag. This is breaking all of the custom validations set to those properties. If the user removes all other records, the properties are still intact. Any help is much appreciated. Please see the plunker for code.
http://plnkr.co/edit/8s5brh7Hj9cu0gdpNpxt?p=preview
<body ng-controller="MainCtrl as vm">
<form name="vm.cptForm" role="form" ng-submit="vm.submit()" novalidate="">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>DATE OF SERVICE</th>
<th>REMOVE</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in vm.items">
<td>{{item.id}}</td>
<td type = "text"
name="name"
class = "form-control ng-class: 'error': vm.showErrors && !vm.cptForm.name.$valid}"
ng-model="item.name">{{item.name}}</td>
<td type = "text"
name="dos"
class = "form-control ng-class: 'error': vm.showErrors && !vm.cptForm.dos.$valid}"
ng-model="item.dos">{{item.dos}}</td>
<td>
<button class="btn btn-xs btn-danger" type="button" ng-click="vm.remove(item)">Delete</button>
</td>
</tr>
</tbody>
</table>
<div class="row">
<span class="error" ng-show="vm.showErrors && vm.cptForm.dos.$error.termedMember">
</form>
</body>
and here's the js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
var vm = this;
vm.cptForm = {};
vm.items = [];
vm.items = [
{
"id":0,
"name": "Jane",
"dos":"05/05/2017"
},
{
"id":1,
"name": "Janet",
"dos":"05/05/2017"
},
{
"id":2,
"name": "John",
"dos":"05/05/2017"
},
{
"id":3,
"name": "Johnathan",
"dos":"05/05/2017"
},
{
"id":4,
"name": "Joanne",
"dos":"05/05/2017"
}
];
vm.remove = function(item){
console.log(item);
console.log(vm.cptForm); //before splice vm.cptForm contains dos and name properties
var index = vm.items.indexOf(item);
debugger;
vm.items.splice(index,1);
// console.log(vm.items);
//console.log(vm.cptForm); //after splice vm.cptForm no longer contains dos and name properties
vm.validate(vm.items);
};
vm.validate = function(items){
angular.forEach(items,function(item){
if(item.dos < getDate()){ //compared to today for code simplicity
vm.cptForm.dos.$setValidity("termedMember", false);
}
});
};
});
Edit
I tried creating a copy of the form prior to splicing it and then assigning it back to the original form, but that did not work. How can I retain the form's properties for each item when using ng-repeat?

Model not accesseble outside namespace

Im trying to bind my knockout array to a table, but cant reach the model outside the javascript function.
Here is my javascript code
(function (conf, $, undefined) {
var model = { menuRows : [], orderRows : [], menuDetails : null };
conf.getMenuRows = function () {
$.get("/orderpackage/row", function (data) {
model.orderRows = data;
});
};
conf.getMenuRows();
ko.applyBindings(model);
}(window.conf = window.conf || {}, jQuery));
And this is the HTML
<table class="table table-hover table-bordered">
<thead>
<tr>
<th>Beskrivning</th>
</tr>
</thead>
<tbody data-bind="foreach: model.orderRows">
<tr>
<td data-bind="text: description"></td>
</tr>
</tbody>
</table>
model.orderRows is not found.
Cant understand what im doing wrong here.
your models are not using the observable array function. You will need something like the following:
function Model() {
var self = this();
self.menuRows = ko.observableArray();
self.orderRows = ko.observableArray();
self.getMenuRows = function() {
$.get("/orderpackage/row", function (data) {
self.orderRows = ko.observableArray(data)
});
....
}
Then you can call
(function (conf, $, undefined) {
var model = Model();
model.getMenuRows();
ko.applyBindings(model);
}(window.conf = window.conf || {}, jQuery));
Then you should be able to bind like you are doing in your HTML.
More tutorials can be found here: http://learn.knockoutjs.com/
If you then want to bind to items in each of the array elements, the description for example, you will need to create an additional model definition for the row and parse the data returned from your api to the model type.
I found a good solution
(function (conf, $, undefined) {
var model = {
menuRows: ko.observableArray([]),
order: ko.observableArray([]),
menuDetails: ko.observable()
};
conf.getMenuRows = function () {
$.ajax({
url: "/orderpackage/row",
cache: false,
type: "GET",
datatype: "json",
contenttype: "application/json;utf8"
}).done(function (data) {
model.order(data.model);
});
};
conf.getMenuRows();
ko.applyBindings(model);
}(window.conf = window.conf || {}, jQuery));

AngularJS select with ng-options not updating referenced object property in the parent scope

My select is populating with the contents of the model, but when I select an option, the model does not update.
I'm using ng-options, not ng-repeat and my ng-model is an object on the parent scope, not a primitive, so I think I've avoided the "child-scope" issues I've seen on similar posts. I've recreated the problem on jsfiddle:
http://jsfiddle.net/bobweil/wfdjrej5/
When the user clicks on a row in the table, a small form shows up below that row, permitting a new status value to be selected for that row for posting to the backend service.
Here's my javascript:
angular.module('myApp', [])
.controller('TaskCtrl', function HomeController($scope, $filter) {
$scope.statusMasters = [{
"Id": 1,
"DisplayOrder": 100,
"Text": "Review"
}, {
"Id": 2,
"DisplayOrder": 200,
"Text": "New"
}, {
"Id": 3,
"DisplayOrder": 300,
"Text": "Working"
}, {
"Id": 4,
"DisplayOrder": 400,
"Text": "Complete"
}]
$scope.tasks = [{
"taskId": 1000,
"Descr": "My first task",
"statusId": 1
}, {
"taskId": 2000,
"Descr": "My second task",
"statusId": 1
}, {
"taskId": 3000,
"Descr": "My third task",
"statusId": 1
}];
$scope.selectedTask = null;
$scope.newTaskStatus = {};
$scope.opGroup = "A";
$scope.selectTask = function (thisTask) {
$scope.selectedTask = thisTask;
$scope.newTaskStatus = {};
$scope.newTaskStatus.taskId = thisTask.taskId;
$scope.newTaskStatus.statusId = thisTask.statusId;
};
$scope.isSelected = function (thisTask) {
if (thisTask.hasOwnProperty('taskId')) {
return $scope.selectedTask.taskId === thisTask.taskId;
} else return false;
};
});
And here's my html:
<div ng-controller="TaskCtrl">
<table class="table table-bordered">
<thead>
<tr>
<th>Task #</th>
<th>Description</th>
<th>StatusId</th>
<th>Status Text</th>
</tr>
</thead>
<tbody ng-repeat="item in tasks" ng-click="selectTask(item)" ng-switch on="isSelected(item)">
<tr>
<td>{{item.taskId }}</td>
<td>{{item.Descr}}</td>
<td>{{item.statusId}}</td>
<td>{{statusMasters[item.statusId - 1].Text}}</td>
</tr>
<tr ng-switch-when="true">
<td colspan="10">
<div>Debug: contents of new task status object: <pre>{{newTaskStatus | json}}</pre>
</div>
<label>Select a new status for task {{newTaskStatus.taskId}}:</label>
<select ng-model="newTaskStatus.taskId" ng-show="(opGroup == 'A')" class="form-control" ng-options="rec.Id as rec.Text for rec in statusMasters | orderBy : 'DisplayOrder'"></select>
<select ng-model="newTaskStatus.taskId" ng-show="(opGroup == 'B')" class="form-control" ng-options="rec.Id as rec.Text for rec in statusMasters | orderBy : 'DisplayOrder'"></select>
</td>
</tr>
</tbody>
</table>
Part of the issue is you are trying to set a click event on the tbody, you need to set the ng-click on the row (tr).
Secondly, unless it is needed for another reason, I wouldn't duplicate the values from the "selectedTask" into a "newTaskStatus" when you are planning on changing the status and sending back that value, it can all be done with one object on the scope.
Third, you could clean up your .js a little by changing the 'ng-switch on' to do the check if it is selected. It replaces an entire function with a comparison.
I would do something like this.
<tbody ng-repeat="item in tasks" ng-switch on="selectedTask.taskId == item.taskId">
<tr ng-click="selectTask(item)">
<td>{{item.taskId }}</td>
<td>{{item.Descr}}</td>
<td>{{item.statusId}}</td>
<td>{{statusMasters[item.statusId - 1].Text}}</td>
</tr>
<tr ng-switch-when="true">
<td colspan="10">
<div>Debug: contents of new task status object: <pre>{{selectedTask | json}}</pre>
</div>
<label>Select a new status for task {{selectedTask.taskId}}:</label>
<select ng-model="selectedTask.statusId" ng-show="(opGroup == 'A')" class="form-control" ng-options="rec.Id as rec.Text for rec in statusMasters | orderBy : 'DisplayOrder'"></select>
<select ng-model="selectedTask.statusId" ng-show="(opGroup == 'B')" class="form-control" ng-options="rec.Id as rec.Text for rec in statusMasters | orderBy : 'DisplayOrder'"></select>
</td>
</tr>
</tbody>
With the .js I would remove the unnecessary items:
$scope.selectedTask = null;
$scope.opGroup = "A";
$scope.selectTask = function (thisTask) {
$scope.selectedTask = thisTask;
};
$scope.isSelected = function (thisTask) {
if (thisTask.hasOwnProperty('taskId')) {
return $scope.selectedTask.taskId === thisTask.taskId;
} else return false;
};
I forked your jsfiddle here to demonstrate what I mean. Good Luck!

EmberJS: ArrayController -> ArrayController -> ObjectController error

I am having a problem adding an array controller as an item controller of another array controller.
Error I am getting is:
Error while loading route: TypeError {} ember.min.js:15
Uncaught TypeError: Object # has no method 'addArrayObserver'
JSFiddle: http://jsfiddle.net/t5Uyr/3/
Here is my HTML:
<script type="text/x-handlebars">
<table>
<thead>
<tr>
<th>id</th>
<th>items</th>
</tr>
</thead>
<tbody>
{{#each}}
<tr>
<td>{{id}}</td>
<td>
<ul>
{{#each items}}
<li>{{formattedName}}</li>
{{/each}}
</ul>
</td>
</tr>
{{/each}}
</tbody>
</table>
</script>
As you can see, inside the template I iterate over a collection of data with each loop, inside the each loop I want to iterate over a subcollection of the data.
Here is my JS code:
window.App = Ember.Application.create({});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
var data = [
{
id: "111",
items: [
{
name: "foo"
},
{
name: "bar"
}
]
},
{
id: "222",
items: [
{
name: "hello"
},
{
name: "world"
}
]
}
];
return data;
}
});
App.ApplicationController = Ember.ArrayController.extend({
itemController: "row"
});
App.RowController = Ember.ArrayController.extend({
itemController: "item"
});
App.ItemController = Ember.ObjectController.extend({
formattedName: function () {
return "My name is " + this.get("name");
}.property("name")
});
App.RowController should be an objectController your items (rows) are objects with an array in one of their properties and not arrays themselves...
You can assing the controller in the inner each directly and remove itemController from the App.RowController.
JavaScript
App.RowController = Ember.ObjectController.extend()
Handlebars
{{each items itemController='item'}}
JsFiddle http://jsfiddle.net/mUJAa/3/

KNockoutJS with jQuery datatables, bound rows are not updated properly

Here's JS code:
function ProductViewModel() {
// Init.
var self = this;
self.products = ko.observableArray();
self.singleProduct = ko.observable();
var mappedProducts;
// Initialize table here.
$.getJSON("/admin/test", function(allData) {
mappedProducts = $.map(allData, function(item) { return new Product(item);});
self.products(mappedProducts);
self.oTable = $('#products-table').dataTable( {
"aoColumns": [
{ "bSortable": false, "mDataProp": null, sDefaultContent: '' },
{"mData": "name"},
{"mData": "dealer"},
{"mData": "cost"},
{"mData": "price"},
{ "bSortable": false, sDefaultContent: '' }
],
});
});
// Here i'm using the basic switch pattern, as from KO tutorials.
// This is intended for showing a single product form.
self.edit = function(product) {
self.singleProduct(product);
}
// This is intended to hide form and show list back.
self.list = function() {
self.singleProduct(null);
}
// This is the form save handler, actually does nothing
// but switch the view back on list.
self.doEdit = function(product) {
self.list();
}
}
// My model.
function Product(item) {
this.name = ko.observable(item.name);
this.dealer = ko.observable(item.dealer);
this.cost = ko.observable(item.cost);
this.price = ko.observable(item.price);
this.picture = ko.observable();
}
Here's my markup:
<table id="products-table" class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>Pic</th>
<th>Name</th>
<th>Dealer</th>
<th>Cost</th>
<th>Price</th>
<th>Actions</th>
</tr>
</thead>
<tbody data-bind="foreach: $parent.products">
<tr>
<td><span data-bind='ifnot: picture'>-</span></td>
<td><a data-bind="text: name"></a></td>
<td><span data-bind='text: dealer'></span></td>
<td><span data-bind='text: cost'></span></td>
<td><span data-bind='text: price'></span></td>
<td>
<button data-bind='click: $root.edit'><i class='icon-pencil'></i>
</button>
</td>
</tr>
</tbody>
</table>
When i do click on the edit button, triggering the $root.edit handler, a form is shown because of a
<div data-bind='with: singleProduct'>
binding i have made. Inside this binding i have a form, with a
<input data-bind="value: name" type="text" id="" placeholder="Name"
> class="col-xs-10 col-sm-5">
field.
Problem: When i edit the value in the input field, the relative row in the datatable is not updated. I have tried a basic table without the datatables plugin and it does work, meaning that if i change value, the row in the table is properly updated.
What's wrong here?
== EDIT ==
I found out that moving the bind point to the table TD fixed the problem, though still i can't figure out why.
<tr>
<td data-bind="text: name"></td>
<!-- More columns... -->
</tr>
The above code is working properly now. Why ?
== EDIT2 ==
Now that i fixed first issue, comes the second. I implemented my "save new" method like so
self.doAdd = function(product) {
$.ajax("/product/", {
data: ko.toJSON({ product: product }),
type: "post", contentType: "application/json",
success: function(result) { alert('ehh'); }
}).then(function(){
self.products.push(product); // <--- Look at this!
self.list();
});
}
The self.products.push(product); in the success handler is properly updating my products observable. Then, a new row is automatically added to my table, and this is the good news.
Bad news is that datatables controls, such search field or clickable sorting arrows, disappear as soon as i push the new product in the array. Why so!?
Did you ever resolve this?
I've been having similar issues for ages.
In the end my fix was to map all my entities using ko.mapping.fromJS(entity) - this then hooked up all the required dependencies and ensured that any changes flowed through my model.
http://jsfiddle.net/zachpainter77/4tLabu56/
ko.bindingHandlers.DataTablesForEach = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var nodes = Array.prototype.slice.call(element.childNodes, 0);
ko.utils.arrayForEach(nodes, function (node) {
if (node && node.nodeType !== 1) {
node.parentNode.removeChild(node);
}
});
return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor()),
key = "DataTablesForEach_Initialized";
var newValue = function () {
return {
data: value.data || value,
beforeRenderAll: function (el, index, data) {
if (ko.utils.domData.get(element, key)) {
$(element).closest('table').DataTable().destroy();
}
},
afterRenderAll: function (el, index, data) {
$(element).closest('table').DataTable(value.options);
}
};
};
ko.bindingHandlers.foreach.update(element, newValue, allBindingsAccessor, viewModel, bindingContext);
//if we have not previously marked this as initialized and there is currently items in the array, then cache on the element that it has been initialized
if (!ko.utils.domData.get(element, key) && (value.data || value.length)) {
ko.utils.domData.set(element, key, true);
}
return { controlsDescendantBindings: true };
}
};
https://rawgit.com/zachpainter77/zach-knockout.js/master/zach-knockout.debug.js

Categories

Resources