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.
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.
So I have an array of events and each event has teams participating. these two object are related but neither is a property of the other.
What I want to do is loop through every event and display every team participating in it. I try to do this using nested ng-repeats. My problem is the inner ng-repeats only displays once the outer ng-repeat has finished executing. Meaning that what ever team participated in the last event processed will be displayed in every table.
In my controller I have an array of teams and I update that array every time I get a new event, and an array of every
here is my repeating table
<div ng-repeat="event in events" ng-init="getTeams(event.eventId)">
<div class="active title">Teams in {{event.eventName}} #{{event.eventId}}</div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Sport</th>
<th>Motto</th>
<th>W-L-D</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="team in teams">
<td>{{team.teamName}}</td>
<td>{{team.teamSport}}</td>
<td>{{team.teamMotto}}</td>
<td>{{team.win}}- {{team.lose}} - {{team.draw}}</td>
<td>{{team.teamDescription}}</td>
</tr>
</tbody>
</table>
</div>
and here is my controller
(function(){
angular
.module('foo')
.controller('EventController', function ($scope, $location, EventService) {
$scope.events = [];
$scope.targetEvent = {};
$scope.teams = [];
EventService.getEvents()
.then(function(events){
$scope.events = events;
});
$scope.getTeams = function(currEventId){
EventService.getTeams(currEventId)
.then(function(eventTeams) {
$scope.teams = eventTeams;
})
}
})()
I have tried using track by but tracking it by the eventId or teamId does not seem to work. Thank you in advance for your help
Although you have creating teams variable each time. It would be global and single for all events as per your current implementation. I don't think so you should be using ng-init here, rather perform that operation inside controller itself by looping over events.
EventService.getEvents().then(function(events){
$scope.events = events;
//loop over each event.
events.forEach(function(event){
EventService.getTeams(currEventId)
.then(function(eventTeams) {
//placed specific teams on event level
event.teams = eventTeams;
})
})
});
Then you just need to change the inner ng-repeat to below and remove ng-init
<tr ng-repeat="team in event.teams">
I have been trying for a few days now with no luck. I'm building a ASP.NET MVC 5 application. I'm building a reservations application for a restaurant. The idea is to extract a days reservations group it by location with linq to entities and then send it with signalR to the client side. On the client side I want to bind this grouped query with knockout.js and then display it, and that is where everything goes wrong. Sending the grouped reservations to the client side works fine but I can't seem to make the mapping with knockout work. Please help.
Model on Server Side
var Reservations = db.BistroReservations_Reservations
.GroupBy(reservation => reservation.BistroReservations_Location.Description)
.OrderBy(reservation => reservation.Key.ToString()).ToList();
var context = Microsoft.AspNet.SignalR.GlobalHost.ConnectionManager.GetHubContext<ReservationsHub>();
context.Clients.All.TestingGroupedReservations(Reservations);
Model on Client Side
var ReservationsViewModel = function () {
var self = this;
var connection = $.hubConnection();
var hub = connection.createHubProxy('reservationsHub')
var GroupedReservations = ko.mapping.fromJS(reservations);
//Testing -map a collection object to a observalbe and display it underneath the webpage
hub.on('TestingGroupedReservations', function (reservation) {
ko.mapping.fromJS(reservation, GroupedReservations);
});
}
ko.applyBindings(new ReservationsViewModel());
Code on the client view side
<table class="table" data-bind="visible: !loading()">
<thead class=".h1 glyphicon-bold">Reservations of Selected Day</thead>
<tbody data-bind="foreach: GroupedReservations">
<tr>
<td>Shift</td>
<td>
<table data-bind="foreach:$data">
<tbody>
<tr>
<td data-bind="text:BistroReservations_GuestID"></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
Try
this.GroupedReservations = ko.mapping.fromJS(reservations);
Where is the declaration of "reservations" variable in this line:
var GroupedReservations = ko.mapping.fromJS(reservations);
From first blush you are never setting the GroupedReservations property and you are also not returning self. If you do not return self all of your properties are considered private.
In addition anything you want to be publicly accessible needs to be a property on self.
var ReservationsViewModel = function () {
var self = this;
var connection = $.hubConnection();
var hub = connection.createHubProxy('reservationsHub')
**self.GroupedReservations = ko.mapping.fromJS(reservations);**
//Testing -map a collection object to a observalbe and display it underneath the webpage
hub.on('TestingGroupedReservations', function (reservation) {
ko.mapping.fromJS(reservation, GroupedReservations);
});
**return self;**
}
Being quite new to angular, I am searching the best way to achieve a quite simple task.
My aim is to update in a database, through angular $resource service, the order (I have a position attribute) of a Project model.
I have the following template structure :
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Date</th>
<th>Link</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody id="sortable" my-drag-list>
<tr ng-repeat="project in projects" ng-class="{warning: !project.publish}">
<td></td>
<td>{{project.title}}</td>
<td>{{project.date|date: 'MMMM yyyy'}}</td>
<td><a ng-href="{{project.link}}" target="blank">{{project.link}}</a></td>
<td><a ng-href="#/projects/{{project.id}}/"><span class="glyphicon glyphicon-edit"></span></a></td>
<td><a ng-click="deleteProject(project)"><span class="glyphicon glyphicon-trash"></span></a></td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-success" ng-show="data.saveSort" my-update-sort-button>Save new sort</button>
tr in my tbody element are movable. When I click on the button element (which is out of the scope created by the ng-repeat directive), I want to update my database whith new position values, which are determined by the dom position of each tr (upper tr will have a smaller position value).
At first glance, I intends to do a my-update-sort-button directive with the following :
var link = function(scope, el){
el.on('click', function(){
var els = el.parent().find('tr');
for(var i = 0, len = els.length; i<len; i++){
Projects.update({id: els.eq(i).data('projectId')}, {position: i});
}
});
};
But I am not sure about the "quality" of such solution. I do not like the fact of adding data-project-id attribute on my tr element.
Thanks for any ideas or solutions for this case !
You need to keep info about "project" position in it's model. When "project" is moved you save this info to its model. Do it in "my-drag-list".
When click to update button you just send info from model without scanning the DOM:
var link = function(scope, el){
el.on('click', function(){
var model;
for(var i = 0, len = scope.model.projects.length; i<len; i++){
model = scope.model.projects[i];
Projects.update({id: model .id}, {position: model.position});
}
});
};
Even better don't send a lot of requests to server. Send one request with all info together.
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