I am trying to make a matrix matching table using knockout, but I'm stuck regarding the compare between the current data-bind and the parent data-bind.
Here is the jsfiddle.net/wup9rxeu/4/
<script type="text/html" id="cubeheader-template">
<th data-bind="text: $data.Name"></th>
</script>
<script type="text/html" id="body-template">
<!-- ko foreach: $parent.modeldim -->
<td>x</td>
<!-- /ko -->
</script>
What I want to accomplished is that when the table is populated with x and - for each td, based on the modelcubdim data.
I need some pointer on comparing the ID against the parent ID and if it's a match, then X or else -
Thanks
You can expand your model with transformed data to represent every cell in the table.
// just for easy searching items by its ID
data.itemById = function(arr, id){
return ko.utils.arrayFirst(arr, function(item){
return item.ID == id;
});
};
// the property that will hold actual data for *every* table row
// in the format { Name: [Cub Name], Data [array of "x" and "-"] }
data.series = ko.utils.arrayMap(data.modelcub, function(cub){
var cubdim = data.itemById(data.modelcubdim, cub.ID);
return {
Name: cub.Name,
Data: ko.utils.arrayMap(data.modeldim, function(dim){
var item = cubdim && data.itemById(cubdim.CubeDimension, dim.ID);
return item ? "x" : "-";
})
};
});
Then slightly change your markup:
<tbody data-bind="foreach: series">
<tr>
<th data-bind="text: Name"></th>
<!-- ko foreach: Data -->
<td data-bind="text: $data"></td>
<!-- /ko -->
</tr>
</tbody>
And you will get it working like here: http://jsfiddle.net/wup9rxeu/5/
Related
I currently have a form that will let the users to add item to the submission, since I am very new to KnockoutJS I just made this form to accept the one Product for the submission
<script type="text/html" id="page4-template">
<h4>Strain Information : </h4>
<table>
<tr>
<td class="firstCol">Stock number : </td>
<td><span id="SummaryP1_StockNum" data-bind="text: stockNumber"></span></td>
</tr>
<tr>
<td class="firstCol">Product Needed : </td>
<td>
<span id="SummaryP1_pdtNeeded" data-bind="text: pdtNeeded"></span>
<span data-bind="visible: pdtNeeded() == 'Other'">
<span id="SummaryP1_pdtNeededPleaseExplain" data-bind="text: pdtNeededPleaseExplain"></span>
</span>
</td>
</tr>
<tr>
<td class="firstCol">Requested Ship Date : </td>
<td><span id="SummaryP1_RequestedShipDate" data-bind="text: requestedShipDate"></span></td>
</tr>
<tr>
<td class="firstCol">Aditional Information : </td>
<td><span id="SummaryP1_AdditionalInformation" data-bind="text: additionalInformation"></span></td>
</tr>
</table>
<hr>
</script>
If I need to make this form to allow users to add more item to the submission dynamically, what should I be using here, I am little confused as thee are dynamic bootstrapping, Overservable Array and all. Can anyone please suggest what could I do to simple to allow users to dynamically add item.
I would suggest three steps:
The first step would be collect into one object all those observable properties which you bind to the table's elements:
createRowItem = function(data) {
return {
additionalInformation = ko.observable(data.additionalInformation),
pdtNeeded = ko.observable(data.pdtNeeded),
pdtNeededPleaseExplain = ko.obsevable(data.pdtNeededPleaseExplain),
requestedShipDate = ko.observable(data.requestedShipDate),
stockNumber = ko.observable(data.stockNumber),
}
};
You would obtain an instance of a new rowItem...
var newRowItem = createRowItem(data);
The second step is to create an observableArray (documentation) in your existing view-model:
self.rowItems = ko.observableArray([]);
To populate that array with your collection of rowItem instances you could call self.rowItems.push(newRowItem) (documentation) but it's more efficient to obtain a reference to the inner array (i.e., the primitive array which the observableArray is watching), add the new instance to that, then tell the observableArray that its data has been updated. [The reason for this efficiency has to do with the way Knockout works internally, and tracks mutations.]
My suggestion would be to do this inside a public function on your view-model:
self.addRowItem = function(newRowItem) {
var arr = ko.unwrap(self.rowItems); // obtain the underlying array
arr.push(newRowItem); // add the new object to the underlying array
self.rowItems.valueHasMutated(); // tell Knockout that the underlying array has been modified
};
The final step is to wrap your <tr> elements in a foreach binding (documentation):
<script type="text/html" id="page4-template">
<h4>Strain Information : </h4>
<table data-bind="foreach: rowItems">
<tr>
<td class="firstCol">Stock number : </td>
<td><span id="SummaryP1_StockNum" data-bind="text: stockNumber"></span></td>
</tr>
...
</table>
You will indeed want to use an observableArray to store multiple items. Then you loop through this array with the foreach binding and you add a method on your viewmodel to push new items to this array.
Something like this:
vm.row = ko.observableArray();
vm.addRow = function () {
vm.row.push({
stockNumber: ko.observable(1),
pdtNeeded: ko.observable('Other'),
pdtNeededPleaseExplain: ko.observable('Hello'),
requestedShipDate: ko.observable(),
additionalInformation: ko.observable()
})
}
Fiddle: https://jsfiddle.net/thebluenile/2q8tbp5n/
For good measure, I also added an example of how you could remove the rows.
I have a component that renders table and I need to define cell template, that will be rendered in each row.
<my-table params="files: unprocessed">
<cell-template>
<span data-bind="text: name" />
</cell-template>
</my-table>
However, the cell-template renders only in the first row. How do I define and use template as a parameter, that will be rendered used inside binding?
<template id="my-table-template">
<table class="table table-striped" data-bind="visible: files().length > 0">
<tbody data-bind="foreach: files()">
<tr>
<td data-bind="text: id" />
<td>
<!-- ko template: { nodes: $parent.cellTemplateNodes} -->
<!-- /ko -->
</td>
</tr>
</tbody>
</table>
</template>
js:
function getChildNodes(allNodes: Array<any>, tagName: string) {
var lowerTagName = tagName.toLowerCase(),
node = ko.utils.arrayFirst(allNodes, function (node) { return node.tagName && node.tagName.toLowerCase() === lowerTagName; }),
children = (node ? node.childNodes : null);
return children;
}
ko.components.register("my-table", {
template: { element: 'my-table-template' },
viewModel: {
createViewModel: (params, componentInfo) => {
var a = {
files: params.files,
cellTemplateNodes: getChildNodes(componentInfo.templateNodes, 'cell-template')
};
return a;
}
},
});
Knockout doesn't check for this, but it expects the nodes passed to the template binding to be a true array. That's because the first thing it does is move the nodes to a new parent node. So you should copy the nodes to a new array:
return ko.utils.arrayPushAll([], children);
There's also a bug in Knockout 3.4.x when using the same node list multiple times, although it would cause a different effect. See https://github.com/knockout/knockout/issues/2187
Say I have the following knockout view, how can I obtain the outerHtml for the actual generated code with javascript. Whenever I try to select the outerHtml of "table_1" with javascript I end up with the html containing the knockout markup, rather than the actual HTML visible on screen.
<table id="table_1">
<thead>
<tr>
<th>Name</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: $data.Rows -->
<tr>
<td data-bind="text: Name"></td>
<td data-bind="text: Date"></td>
</tr>
<!-- /ko -->
</tbody>
knockout components can provide the bound element.
see http://knockoutjs.com/documentation/component-registration.html
ko.components.register('printable-component', {
viewModel: {
createViewModel: function(params, componentInfo) {
console.log(componentInfo.element);
}
}
});
using this mechanism a 'printable' component can be developed that has access to the innerHTML of the bound element.
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
function AppViewModel() {
this.Rows = [{
Name: 'Joseph',
Date: '2017-02-13'
}, {
Name: 'Mary',
Date: '2017-02-13'
}];
}
// define a printable component
ko.components.register('printable-component', {
viewModel: {
// create using the factory function
// see http://knockoutjs.com/documentation/component-registration.html
createViewModel: function(params, componentInfo) {
// return a new ViewModel for the component
return new function() {
this.rows = params.rows;
// track the componentInfo
this.componentInfo = componentInfo;
// print method function
this.print = function() {
alert(componentInfo.element.innerHTML);
}
}
}
},
// the component template
// note: can be jsx
// note: can be defined in html using internal template nodes
// note: print button can be hidden using CSS or by defining it outisde the printable component and use the params to start the print function
template: '<table><thead><tr><th>Name</th><th>Date</th></tr></thead><tbody><!-- ko foreach: $data.rows --> <tr><td data-bind="text: Name"></td><td data-bind="text: Date"></td></tr><!-- /ko --></tbody></table>print'
});
// Activates knockout.js
ko.applyBindings(new AppViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<printable-component params="rows:$data.Rows"></printable-component>
I have the following Problem
I have this Code to load Json Data from a external Web api
and Show it in my site this works..
but my Problem is
I must FILTER the Data with a Dropdown List
When i select the Value "Show all Data" all my Data must be Show
and when i select the Value "KV" in the Dropdown only the Data
with the Text "KV" in the Object Arbeitsort must Show..
How can i integrate a Filter in my Code to Filter my Data over a Dropdown ?
and the next is how can i when i insert on each Item where in HTML Rendered a Button
to Show Details of this Item SHOWS his Detail Data ?
when i click Details in a Item i must open a Box and in this Box i must Show all Detail Data
of this specific Item ?
$(document).ready(function () {
function StellenangeboteViewModel() {
var self = this;
self.stellenangebote = ko.observableArray([]);
self.Kat = ko.observable('KV');
$.getJSON('http://api.domain.comn/api/Stellenangebot/', function (data) {
ko.mapping.fromJS(data, {}, self.stellenangebote);
});
}
ko.applyBindings(new StellenangeboteViewModel());
});
I'll give this a go, but there's quite a few unknowns here. My suggestions are as follows:
First, create a computed for your results and bind to that instead of self.stellenangebote
self.stellenangeboteFiltered = ko.computed(function () {
// Check the filter value - if no filter return all data
if (self.Kat() == 'show all data') {
return self.stellenangebote();
}
// otherwise we're filtering
return ko.utils.arrayFilter(self.stellenangebote(), function (item) {
// filter the data for values that contain the filter term
return item.Arbeitsort() == self.Kat();
});
});
With regards the detail link, I'm assuming you are doing a foreach over your data in self.stellenangeboteFiltered(), so add a column to hold a link to show more details:
<table style="width:300px">
<thead>
<tr>
<th>Id</th>
<th>Arbeitsort</th>
<th>Details</th>
</tr>
</thead>
<tbody data-bind="foreach: stellenangeboteFiltered">
<tr>
<td><span data-bind="text: Id"> </span></td>
<td><span data-bind="text: Arbeitsort"> </span></td>
<td>Detail</td>
</tr>
</tbody>
</table>
Add a control to show details:
<div data-bind="visible: detailVisible, with: selectedItem">
<span data-bind="text: Position"> </span>
<span data-bind="text: Arbeitsort"> </span>
</div>
In your JS add a function:
// add some observables to track visibility of detail control and selected item
self.detailVisible = ko.observable(false);
self.selectedItem = ko.observable();
// function takes current row
self.showDetail= function(item){
self.detailVisible(true);
self.selectedItem(item);
};
UPDATE
Here's an updated fiddle: JSFiddle Demo
From what I've been able to find online I don't think it's possible to use the foreach data-bind to iterate through the properties of an observable object in knockout at this time.
If someone could help me with a solution to what I'm trying to do I'd be very thankful.
Let's say I have an array of movies objects:
var movies = [{
title: 'My First Movie',
genre: 'comedy',
year: '1984'
},
{
title: 'My Next Movie',
genre: 'horror',
year: '1988'
},
];
And what I would like to do is display this data in a table, but a different table for each genre of movie.
So I attempted something like this:
<div data-bind="foreach: movieGenre">
<table>
<tr>
<td data-bind="year"></td>
<td data-bind="title"></td>
<td data-bind="genre"></td>
</tr>
</table>
</div>
and my data source changed to look like this:
for (var i = 0; i < movies.length; ++i) {
if (typeof moviesGenres[movies.genre] === 'undefined')
moviesGenres[movies.genre] = [];
moviesGenres[movies.genre].push(movie);
}
I've tried about a dozen other solutions, and I'm starting to wonder if it's my lack of knowledge of knockout(I'm pretty green on it still), or it's just not possible the way I'd like it to be.
You can make your array "movies" an KO observable array and the array "movieGenre" a KO computed property. Have a look at this fiddle.
The code in the fiddle is given below for reader convenience;
KO View Model
function MoviesViewModel() {
var self = this;
self.movies = ko.observableArray([
{
title: 'My First Movie',
genre: 'comedy',
year: '1984'
},
{
title: 'My Next Movie',
genre: 'horror',
year: '1988'
},
{
title: 'My Other Movie',
genre: 'horror',
year: '1986'
}
]);
self.movieGenre = ko.computed(function() {
var genres = new Array();
var moviesArray = self.movies();
for (var i = 0; i < moviesArray.length; i++) {
if (genres.indexOf(moviesArray[i].genre) < 0) {
genres.push(moviesArray[i].genre);
}
}
return genres;
});
};
HTML
<div data-bind="foreach: movieGenre()">
<h3 data-bind="text: 'Genere : ' + $data"></h3>
<table border="solid">
<thead>
<tr>
<th>Title</th>
<th>Genre</th>
<th>Year</th>
</tr>
</thead>
<tbody data-bind="foreach: $parent.movies">
<!-- ko if: $data.genre === $parent -->
<tr>
<td data-bind="text: $data.title"></td>
<td data-bind="text: $data.genre"></td>
<td data-bind="text: $data.year"></td>
</tr>
<!-- /ko -->
</tbody>
</table>
</div>
As you can see "movieGenre" is made a computed property. Whenever the observable array "movies" changes, the moveGenre is calculated and cached. However, since this is not declared as a writable computed property, you cannot bind this to your view. Hence, it's value is used in the data binding.
The approach for rendering is simply looping through the calculated "movieGenre", and nest another loop for movies. Before adding a row to the table, for the corresponding table, the movie object is evaluated with the current movieGenre. Here, the container-less control flow syntax is used. We can use the "if" binding, but that would leave an empty table row per each movie object where the genre is otherwise.
The $parent binding context is used to access the parent contexts in the nested loop.
Hope this helps.