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.
Related
What would be the simplest way to implement knockout bindings for a customer queue timer? I am not that good with knockout and my search found only questions about countdown timers. I don't where exactly should I start with this. Custom binding? Manual refresh? Every advice is appreciated.
I have an observable array which contains my customer models, one of the properties of the customer model is EnqueueTime which contains the time, when the customer entered the queue. I want to display the time customer has spent in the queue (time span between the EnqueueTime and current time).
So far, I am just showing the value of EnqueueTime. This is just a snippet from my testing code I currently use, later it will be getting some reasonable form and data:
this.Customers = ko.observableArray();
var vm = {};
vm.CustomerNote = "Test";
vm.EnqueueTime = Date.now();
vm.Priority = true;
this.Customers.push(vm);
And the view:
<div id="customerQueue" class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th>Note</th>
<th>Time in queue</th>
</tr>
</thead>
<tbody data-bind="foreach: Customers">
<tr data-bind="css: {danger: Priority}">
<td>
<span data-bind="visible: Priority" class="glyphicon glyphicon-warning-sign"></span>
</td>
<td data-bind="text: CustomerNote"></td>
<td data-bind="text: EnqueueTime"></td>
</tr>
</tbody>
</table>
</div>
If you only have a few objects active at a time, a good quick solution is to use a computed observable. However since nothing else changes, you'll need an external observable that can be updated independently to refresh the displayed values.
Here's a quick example:
var vm = { models: ko.observableArray() };
var trigger = ko.observable(null).extend({ notify: 'always'});
class Model {
constructor(time) {
this.EnqueueTime = time;
this.Elapsed = ko.computed(() => {
trigger(); // read observable to be able to update when it updates
var date = new Date(null);
date.setTime(new Date() - this.EnqueueTime);
return date.toISOString().substr(11, 8);
});
}
}
vm.models.push(new Model(new Date()));
vm.models.push(new Model(new Date(2018, 1, 1)));
setInterval(() => trigger(null), 1000);
ko.applyBindings(vm);
And fiddle:
https://jsfiddle.net/AlexPaven/vnk92gt7/
Let me know if I can help further.
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
The documentation is not quite clear on this, or I may not be understanding how to implement this in the HTML, but how do you handle a template in tempojs that has an empty list/array of items coming from JSON output? Is there a template directive that can be used to display something when the data list is empty (i.e. like the else empty in normal conditional code)?
Here's an example:
Javascript:
$(function() {
/*var data = [
{id:'1',name:'Test One',coordinates:'12.0012,-122.92'}
];*/
var data = [];
Tempo.prepare('userLocs').render(data);
});
HTML:
...
<tbody id="userLocs">
<tr data-template>
<td>{{name}}</td>
<td>{{coordinates}}</td>
<td>Delete</td>
</tr>
<tr data-template-fallback>
<td colspan="3">Javascript is not available.</td>
</tr>
</tbody>
...
I am using handlebars to compile and get the html from the JS object. Is it possible to transform (html) back to JS object (using handlebars or anyother library)? To be more precise; Using handlebars, I have the following template:
<tr>
<td>{{qty}}</td>
<td>{{rate}}</td>
<td>{{gstPerc}}</td>
<td>{{discountPerc}}</td>
<td>{{amount}}</td>
</tr>
and following JS Object:
{
qty : 12,
rate : 1000,
gstPerc : 10,
discountPerc : 2,
amount: 1500
}
after compilation using handlebars, it gets transform to simple html i.e following, for example.
<tr>
<td>12</td>
<td>1000</td>
<td>10</td>
<td>2</td>
<td>1500</td>
</tr>
Now what I was wondering is, Is it possible (using handlebars), to transform the given HTML back to the object?
give give data-name as qty ,rate etc
var obj = getElementsByTagName('td');
$data = {};
for(var i=0;i<obj.length;i++)
{
$data[obj[i].dataset.name] = obj[i].innerHtml;
}
you can do reverse process you want to populate table with object data
You can use grid control like jqgrid for easy integration
For arbitrary templates and values, this is not possible. Consider the following template:
<td>{{cell1}}</td><td>{{cell2}}</td>
and the following result:
<td></td><td></td><td></td>
Which of cell1, cell2 is empty, and which contains </td><td>?
If you know the HTML inserted is valid and you know the template in advance, this is easy. For your specific template:
var table = document.createElement("table")
table.innerHTML = input
var tds = table.rows[0].cells
return {qty: tds[0].innerHTML, rate: tds[1].innerHTML ...}
If you know the values inserted should be numbers, you can convert them as such:
return {qty: +tds[0].innerHTML, rate: +tds[1].innerHTML ...}
Maybe you could use jQuery and end up with something like this
In your template: add classes to the <td> elements:
<table>
<tr id='someId'>
<td class='qty'>12</td>
<td class='rate'>1000</td>
<td class='gstPerc'>10</td>
<td class='discountPerc'>2</td>
<td class='amount'>1500</td>
</tr>
</table>
Create your object again:
var myObj = {};
$("#someId td").each(function() {
var td = $(this);
myObj[td.attr("class")] = td.text();
});
alert(JSON.stringify(myObj));
I have an object that contains a mixture of numbers and text for values. I'd like to apply the numbers filter to the object's value when it's a number (obviously). But when it isn't a number, I'd be okay with it just spitting out the string. As is, applying | number to the value formats the numbers, but leaves the string values empty (afterall, they aren't numbers).
I'm guessing it'll have to be a custom filter (which I have yet had a need to make). Is there a way to do it solely within the HTML when doing the ng-repeat?
<table>
<tr ng-repeat="(metric, metricData) in data">
<td>{{metric}}</td>
<td>{{metricData | number}}</td>
</tr>
</table>
$scope.data = { name:"this is the name",
score:48
outcome:"as expected",
attendance:820,
total:212.34
};
Here is the requested alternate version of the answer from #callmekatootie using ng-if (v1.1.5):
<table>
<tr ng-repeat="(metric, metricData) in data">
<td>{{metric}}</td>
<td ng-if="isNumber(metricData)">{{metricData | number}}</td>
<td ng-if="!isNumber(metricData)">{{metricData}}</td>
</tr>
</table>
This has the advantage of only running the filter on the elements which are numeric. This is probably of little benefit in this case but may be useful in other more complex filter situations. To answer your other question about the built-in angular.isNumber, #callmekatootie does use that in the scope function isNumber, which is only a wrapper for using the built-in in the view.
Here is a fiddle
You could try it this way - In your controller, you can have a function which identifies if the provided value is a string or a number:
$scope.isNumber = function (value) {
return angular.isNumber(value);
};
Next, in your view you could have the following:
<table>
<tr ng-repeat="(metric, metricData) in data">
<td>{{metric}}</td>
<td ng-show="isNumber(metricData)">{{metricData | number}}</td>
<td ng-hide="isNumber(metricData)">{{metricData}}</td>
</tr>
</table>
Thus, when the metricData is a number, it is filtered and when it is a string, it is output as it is.
I know this is old, but I think the best solution is to move the logic to a filter.
app.filter("metricDataFilter", function($filter) {
return function(value) {
if(angular.isNumber(value)) {
return $filter("number", value);
}
return value;
}
}
That way the HTML is more concise, and angular won't have to redraw dom elements
<table>
<tr ng-repeat="(metric, metricData) in data">
<td>{{metric}}</td>
<td>{{metricData | metricDataFilter}}</td>
</tr>
</table>