Accessing child array by index and updating view when index increments - javascript

I have been working on a sample Knockout.js application where there is a list of offices and each office is associated with 1 or more sets of opening hours.
When I click on select in the table the opening hours are shown underneath for the first set of opening hours.
I want to be able to cycle through the array by clicking on the button to show the opening hours for Rota A, then B, then C and back to Rota A again.
With my current solution, if I press on the >> button currently the index is incremented but the opening hours table is not updated until I re-press the select button.
The jsfiddle is here https://jsfiddle.net/58p57dv3/
How, do I make the opening hours in the table update automatically when I press the >>> button
HTML
<div id="container">
<div id="officeInfo">
<table>
<thead>
<tr>
<th>Office ID</th>
<th>Office Name</th>
</tr>
</thead>
<tbody data-bind="foreach: offices">
<tr>
<td data-bind="text: OfficeID"></td>
<td data-bind="text: OfficeName"></td>
<td>Select</td>
</tr>
</tbody>
</table>
</div>
<div id="openingHours" data-bind="with: selectedOffice">
<input type="button" value=">>>" data-bind="click: $parent.nextOpeningHourType">
<table>
<thead>
<tr>
<th>Day</th>
<th>Opening Time</th>
<th>Closing Time</th>
</tr>
</thead>
<tbody data-bind="with: OpeningHours()[$parent.index]">
<tr><td>Monday</td><td data-bind="text: MondayStartTime"></td><td data-bind="text: MondayEndTime"></tr>
<tr><td>Tueday</td><td data-bind="text: TuesdayStartTime"></td><td data-bind="text: TuesdayEndTime"></tr>
<tr><td>Wednesday</td><td data-bind="text: WednesdayStartTime"></td><td data-bind="text: WednesdayEndTime"></tr>
<tr><td>Thursday</td><td data-bind="text: ThursdayStartTime"></td><td data-bind="text: ThursdayEndTime"></tr>
<tr><td>Friday</td><td data-bind="text: FridayStartTime"></td><td data-bind="text: FridayEndTime"></tr>
<tr><td>Saturday</td><td data-bind="text: SaturdayStartTime"></td><td data-bind="text: SaturdayEndTime"></tr>
<tr><td>Sunday</td><td data-bind="text: SundayStartTime"></td><td data-bind="text: SundayEndTime"></tr>
</tbody>
</table>
</div>
</div>
JS
var OfficeViewModel = function() {
var self = this;
self.offices = officeList;
self.selectedOffice = ko.observable();
self.index = ko.observable();
self.index = 0;
self.nextOpeningHourType = function() {
if (self.index < 2) {
self.index++;
} else {
self.index = 0;
}
}
self.selectOffice = function (office) {
self.selectedOffice(office);
}
}

I guess it will work fine if you just make index an observable
self.index = ko.observable(0);
self.nextOpeningHourType = function() {
if (self.index() < 2) {
self.index(self.index()+1);
} else {
self.index(0);
}
}
self.selectOffice = function (office) {
self.index(0);
self.selectedOffice(office);
}
and change your markup as
<tbody data-bind="with: OpeningHours()[$parent.index()]">
JsFiddle: https://jsfiddle.net/newuserjs/58p57dv3/2/

Related

Knockout.js button only visible on maximum index of observableArray

I have a table where I want the last row to to display a button, but only on the last row. The code below works on showing and hiding the button, but it appears on every row.
How would I go about only displaying it on the last row?
Viewmodel:
var loadCustomFileName = function () {
for (i = 0; i < self.GetCanSeam().length; i++) {
var obj = {
appendFileName: parseFileName(i),
displayFileName: parseDisplayName(i)
if (i == self.GetCanSeam().length - 1) {
self.isMax(true);
}
};
self.GetCustomFile.push(obj);
}
};
View:
<div class="csFormField" data-bind="visible: GetCustomFile().length > 0">
<table style="width: 100%;">
<thead>
<tr>
<th>File Name Template</th>
<th>File Name Append</th>
</tr>
</thead>
<tbody data-bind='foreach: GetCustomFile()'>
<tr>
<td><p class="cs-label"><label data-bind="text: displayFileName" /></p></td>
<td><p><input class="cs-input" data-bind="textInput: appendFileName" />
<button class="addFormat" data-bind="visible: isMax">+</button></p></td>
</tr>
</tbody>
</table>
</div>
I think your isMax is initialized on parent context. Maybe try this
var obj = {
appendFileName: parseFileName(i),
displayFileName: parseDisplayName(i)
};
obj.isMax = ko.observable(i==(self.GetCanSeam().length - 1));
self.GetCustomFile.push(obj);
You can compare the index of your forloop. If it gets to the last element then visible the button.
Example :https://jsfiddle.net/kyr6w2x3/12/
self.ArrayLength = ko.observable()
self.ArrayLength(self.GetCustomFile().length);
HTML :
<button class="addFormat" data-bind="visible: $index() == $parent.ArrayLength() -1">+</button></p></td>

How to increase quantity of array item with Knockout

I've got two tables. Left side containing all items, right side showing the ordered items. (See picture)
What I want to do:
If an item from the left table is already added on the right table, instead of adding it again, just increase the quantity by one instead. If that's not the case, just add it..
Code:
HTML
<form id="OrderedItemsWithoutBatch" class="orderedDataWithoutBatch">
<table class="orderFormArticlesTable" style="width: 47%;float: right; font-size: 9pt;">
<thead>
<tr>
<th>SKU</th>
<th>Product</th>
<th style="width: 15%">Qty</th>
<th></th>
</tr>
</thead>
<tbody id="orderedItemsVM" data-bind="foreach: orderedItems">
<tr class="clickable" data-bind="css: { alternate: $index()%2 }">
<td data-bind="text: MateriaalSku"> </td>
<td data-bind="text: MateriaalDescription"> </td>
#*<td class="onelineData"><input style="width:2em" type="text" /> [pieces] </td>*#
<td><input class="orderedQty" style="max-width: 15%" data-bind="value: materiaalAantal"/>[pieces]</td>
<td data-bind="click: function() { $parent.orderedItems.remove($data); }">Remove</td>
</tr>
</tbody>
</table>
JS
$(document).ready(function () {
var dataTableForm = $("form.dataWithoutBatch");
var orderedItemsTable = $("form.orderedDataWithoutBatch");
var dataReloading = false;
var currentPage = 0;
//viewModel
var viewModel = {
orderedItems: ko.observableArray(),
items: ko.observableArray(),
sortColumn: ko.observable(),
total: ko.observable()
}
var request = $.post(url, postData);
request.fail(function () {
alert('Failed!');
});
request.always(function () {
listItemsParameterInputs.prop('disabled', false);
$(document.body).removeClass("loading");
dataReloading = false;
if (callback) {
callback();
}
});
request.done(function (data) {
viewModel.total(data.Total);
viewModel.items.removeAll();
for (var index = 0; index < data.Items.length; index++) {
var item = data.Items[index];
if (item.hasOwnProperty("qty")) {
item.qty = ko.observable(item.qty);
}
else {
item.qty = ko.observable("");
}
item.addItem = function () {
//Check if item is already in the table on the right
if(viewModel.orderedItems.indexOf(this) < 0){
viewModel.orderedItems.push(this);
}
else{
// Increase quantity by one.
}
}
viewModel.items.push(item);
}
Instead of using two arrays and removing elements from the second, why don't you use the same array, just add qty variable and show lines only if they satisfy qty()>0
That way, you could just increment the value of qty by one in the first table with this.qty(this.qty()+1); and set the value to 0 in the second when clicking on remove.
Code for your second table should look something like this:
<tbody id="orderedItemsVM" data-bind="foreach: items">
<!-- ko if: qty()>0 -->
<tr class="clickable" >
<td data-bind="text: MateriaalSku"> </td>
<td data-bind="text: MateriaalDescription"> </td>
<td>
<input style="width:2em" type="text" data-bind="value:qty"/>
[pieces]
</td>
<td><a data-bind="click: function() { qty(""); }">Remove</a></td>
</tr>
<!-- /ko -->
</tbody>
more on the if binding at http://knockoutjs.com/documentation/if-binding.html
Cant you just increment the quantity of your observable as follows:
...
else {
// Increase quantity by one.
this.qty(this.qty() += 1)
}

Delete Table Row from nested <ul> list item

I have a html table that has an <ul> in it. The table is bound by a knockout view model.
When I click on the TEST text I would like remove the <tr> that the clicked TEST text is in, from the table. I have it now so when I click on REMOVE it removes the <tr> but I need it so when I click on the TEST it removes the <tr>. Help is greatly appreciated!
FIDDLE with it, and/or see code snippet below.
VIEW MODEL
var rowModel = function(id, list) {
this.uid = ko.observable(id);
this.myList = ko.observableArray(list);
};
function myViewModel() {
var self = this;
self.myArr = ko.observableArray([
new rowModel(1, ["str1", "str2", "str3"]),
new rowModel(2, ["str1", "str2"]),
new rowModel(3, ["str1"])
]);
//this.myList = ko.observableArray();
self.removeItem = function(rowItem) {
self.myArr.remove(rowItem);
}
}
ko.applyBindings(new myViewModel());
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table class="table table-striped">
<thead>
<tr>
<td>Col 1</td>
<td>Col 2</td>
</tr>
</thead>
<tbody data-bind="foreach: myArr">
<tr>
<td data-bind="text: uid"></td>
<td>
<ul data-bind="foreach: myList">
<li><span data-bind="text: $data"></span> TEST
</li>
</ul>
</td>
<td>REMOVE
</td>
</tr>
</tbody>
</table>
The problem is that the context of the data for the TEST link is a myList item, not a myArr item. Your removeItem() function is expecting a myList item. You need to pass the data from the (in this case) parent context. You can use $parentContext.$data or simply $parent.
<ul data-bind="foreach: myList">
<li>
<span data-bind="text: $data"></span>
TEST
</li>
</ul>

Change cell color via ng-click

I have change color of my cell when I click in my button but when I click second time my color cell is not keep.
I wish that when I click a second time on another button my first cell keeps color
First click:
Second click:
HTML:
<table class="table table-striped table-bordered" cellspacing="0" width="100%">
<thead>
<tr class="warning">
<th>Key</th>
<th>Valeur version {{application.version}}</th>
<th></th>
<th>Valeur version {{applicationcible.version}}</th>
</tr>
</thead>
<tbody ng-repeat="group in groups">
<tr>
<td class="danger" colspan="4" ng-click="hideGroup = !hideGroup">
<a href="" ng-click="group.$hideRows = !group.$hideRows">
<span class="glyphicon" ng-class="{ 'glyphicon-chevron-right': group.$hideRows, 'glyphicon-chevron-down': !group.$hideRows }"></span>
<strong>{{group.name}}</strong>
</a>
</td>
</tr>
<tr ng-repeat-start="member in group.members" ng-hide="hideGroup">
<td rowspan="2">
{{ member.name }}
</td>
<td rowspan="2" ng-class="{selected: $index==selectedRowLeft}">{{ member.valueRef }}</td>
<td class="cube" >
<div ng-if="group.id != 1">
<button type="button" ng-click="moveLeft($index, group)" ><span class="glyphicon glyphicon-chevron-left"></span></button>
</div>
</td>
<td rowspan="2" ng-class="{selected: $index==selectedRowRight}">{{ member.valueCible }}</td>
</tr>
<tr ng-repeat-end ng-hide="hideGroup" >
<td class="cube" >
<div ng-if="group.id != 2">
<button type="button" ng-click="moveRight($index, group)"><span class="glyphicon glyphicon-chevron-right"></span></button>
</div>
</td>
</tr>
</tbody>
</table>
CSS:
.selected { background-color: #ffff05; }
JS:
scope.moveLeft = function (index, group) {
move(scope.properties, group.id, index, 'left');
};
scope.moveRight = function (index, group) {
move(scope.propertiescible, group.id, index, 'right');
};
var move = function (properties, groupId, origin, destination) {
unregister();
var value;
if (destination === 'right') {
scope.selectedRowRight = origin;
} else {
scope.selectedRowLeft = origin;
}
...
You might need to create an array to keep the cells that are selected thus background color is changed.
Also you will need to change the ng-click function to check the array, and implement the following logic "if there is a record for the clicked row as selected change it's bg-color to yellow"
You will need to do something like this
<td ng-repeat="group in groups" ng-class="{'selected': check == group.title}" ng-click="select(group)">{{group.title}}</li>
in css make sure you have this selected class defined
.selected {
background: 'red';
}
in controller
$scope.check = '';
$scope.select = function (group) {
$scope.check = group.title
};
See this plunker http://plnkr.co/edit/pLbLMWsJ7A6Zr1Z9uAmz?p=preview

setting viewmodel property to be an item from a list

ok so i have my view model
function viewModel(calendarData) {
var self = this;
self.Calendars = ko.mapping.fromJS(calendarData);
self.ViewedCalendar = {};//what can/should i set this to be on initial creation?
self.DisplayCalendar = function (calendar) {
self.ViewedCalendar = calendar;
};
};
I then have my html:
<div data-bind="visible: Calendars().length > 0">
<h2>You have <span data-bind="text: Calendars().length"></span> calendars</h2>
<table class="table">
<thead>
<tr>
<th>Calendars</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: Calendars">
<tr>
<td data-bind="text: Name"></td>
<td><span data-bind="text: Events().length"></span> events</td>
<td><a href='#' data-bind='click: $root.DisplayCalendar'>View</a></td>
</tr>
</tbody>
</table>
</div>
<div data-bind="visible: ViewedCalendar() !== null">
Your are viewing <span data-bind="text: ViewedCalendar.Name"></span><br />
</div>
What I am trying to achieve is when the user clicks View for a given calendar, that via DisplayCalendar() I set the property ViewedCalendar to be set to the given calendar.
This then shows my div that contains the label stating what calendar is being viewed.
This is all rough code at the minute just to get the basic functionality in place but I'm new to knockout so could use some help.
I'm getting TypeError: ViewedCalendar is not a function or ViewedCalendar is undefined.
Any help would be much appreciated.
The ViewedCalendar property needs to be an observable for knockout to reach to changes in it's value. You defined it like this:
self.ViewedCalendar = {};
Which is an (empty) object literal and not a function (as the error message correctly stated). What you need is:
self.ViewedCalendar = ko.observable(); // empty () give you an empty observable - no calendar selected yet
And then you can update it in your click handler with:
self.ViewedCalendar(calendar);
Here's a working full example:
function viewModel(calendarData) {
var self = this;
self.Calendars = ko.mapping.fromJS(calendarData);
self.ViewedCalendar = ko.observable();
self.DisplayCalendar = function (calendar) {
self.ViewedCalendar(calendar);
};
};
ko.applyBindings(new viewModel([{Name:'some calendar', Events: []}, {Name:'another calendar', Events: []}]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<div data-bind="visible: Calendars().length > 0">
<h2>You have <span data-bind="text: Calendars().length"></span> calendars</h2>
<table class="table">
<thead>
<tr>
<th>Calendars</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: Calendars">
<tr>
<td data-bind="text: Name"></td>
<td><span data-bind="text: Events().length"></span> events</td>
<td><a href='#' data-bind='click: $root.DisplayCalendar'>View</a></td>
</tr>
</tbody>
</table>
</div>
<div data-bind="with: ViewedCalendar">
Your are viewing <span style="font-weight: bold" data-bind="text: Name"></span><br />
</div>

Categories