Editable Pivot/Partially transposed grid for capturing - angularjs

I have been tasked to develop a capturing web app for business. The aim of the app is to capture multiple (x24) KPI values for each day of the week.
Business wants to be able to capture these values in a pivot/transposed table.
Columns:
1. KPI ID
2. KPI Code
3. KPI Description
4. - 10. Days of the selected week.
5. for each KPI-Date combination there is a decimal value.
All KPI's are linked to a Country and Plant which is determined by the User Data of the logged in User Identity.
So far I have based the pivot on:
https://social.technet.microsoft.com/wiki/contents/articles/32770.dynamic-pivot-grid-using-mvc-angularjs-and-web-api.aspx
This helped A LOT!
(My current solution uses MVC 5, EF 5 Database First, Angularjs.)
At the moment my problem is with editing the values in the grid. Based on the above mentioned solution, the values are calculated using ng-bind-html sending parameters (KPI and Date column). I have set the contenteditable= true, which allows me to physically capture values....
BUT, it seems I can't actually do anything with the new/updated values since it is not bound to a model.
I hope all of this makes sense; My Question is, given the above mentioned scenario:
how can I use the changed value to be sent back to SQL via Stored Proc?
I have a feeling this approach is overkill for what I want to accomplish. Can anyone suggest a better approach?
My current thoughts are to maybe use
jquery to catch the change event on the cell and use the ID's of the row/column of the KPI and Date combinations.
So far I have been unsuccessful. The "change" event doesn't even give me an alert when triggered.
a pre-pivoted result from SQL using a stored procedure. BUT... the dates are dynamic and will change with every week selected. I am unable to specify a model for changing columns.
Any advise would be greatly appreciated.
Here is my angularjs controller function:
$scope.GetCaptureLinesPivot = function () {
var response = Dropdownfactory.GetCaptureLinesPivot($scope.UserID, $scope.CountryID, $scope.PlantID, $scope.WeekDate)
.then(function (success) {
$scope.lines = success.data;
angular.forEach($scope.lines, function (obj) {
obj["showEdit"] = false;
$scope.items = [];
$scope.ColDays = [];
$scope.values = [];
var uniqueUser = {},
uniqueKPICode = {},
uniqueKPIDesc = {},
uniqueDate = {},
uniqueKPIValue = {},
i;
//compile list of unique dates. should be 7
for (i = 0; i < $scope.lines.length; i += 1)
{
uniqueDate[$scope.lines[i].Date] = $scope.lines[i];
}
for (i in uniqueDate)
{
$scope.ColDays.push(uniqueDate[i]);
}
var UniqueItem = {}, i
for (i = 0; i < $scope.lines.length; i += 1) {
UniqueItem[$scope.lines[i].KPIID] = $scope.lines[i];
}
for (i in UniqueItem) {
var ItmDetails = {
UserName: UniqueItem[i].UserName,
KPIID: UniqueItem[i].KPIID,
KPICode: UniqueItem[i].Code,
KPIDesc: UniqueItem[i].Description,
KPIValue: UniqueItem[i].Value
};
$scope.items.push(ItmDetails);
}
Returned result from getting the data:
0:Object
Code:"XXXX"
Country:"Botswana"
CountryID:1
CurrentUserID:6
CurrentUserName:"DevPlant"
Date:"2017-03-20T00:00:00"
Description:"PD Km Travelled"
KPIID:38
Plant:"Francistown"
PlantID:186
UserID:6
UserName:"DevPlant"
Value:150
1:Object
Code:"XXXX"
Country:"Botswana"
CountryID:1
CurrentUserID:6
CurrentUserName:"DevPlant"
Date:"2017-03-21T00:00:00"
Description:"PD Km Travelled"
KPIID:38
Plant:"Francistown"
PlantID:186
UserID:6
UserName:"DevPlant"
Value:160
The function in the controller that return the Value to the cshtml:
$scope.showWeekItemDetails = function (colUser, colKPI, colDay) {
$scope.getLineVaue = 0;
$scope.DayCount = 0;
//alert(colUser + " " + colKPI + " " + colDay);
for (i = 0; i < $scope.lines.length; i++) {
if (colUser == $scope.lines[i].UserName) {
if (colKPI == $scope.lines[i].KPIID) {
if (colDay == $scope.lines[i].Date) {
$scope.getLineVaue = parseFloat($scope.lines[i].Value);
}
}
}
}
return $sce.trustAsHtml("<b>" + $scope.getLineVaue.toString() + "</b>");
}
My current cshtml:
<td width="20"></td>
<td width="1" align="left" valign="bottom" ng-show="false"><b>KPIID</b></td>
<td width="1" align="left" valign="bottom" ng-show="false"><b>CID</b></td>
<td width="1" align="left" valign="bottom" ng-show="false"><b>PID</b></td>
<td width="180" align="left" valign="bottom"><b>Description</b></td>
<td width="30" align="left"><b></b></td>
<td align="center" valign="bottom" data-ng-repeat="Cols in ColDays | orderBy: 'Date':false">
<div>
<table class="table table-condensed" font-size 50%; >
<tr>
<td align="center" width="80"><b>{{Cols.Date | date: 'EEE'}}</b></td>
</tr>
<tr>
<td align="center" width="80"><b>{{Cols.Date | date: 'dd MMM yy'}}</b></td>
</tr>
</table>
</div>
</td>
</tr>
<tbody data-ng-repeat="itm in items">
<tr>
<td width="20" class="lineNo" >{{$index+1}}</td>
<td width="1" class="kpiID" ng-show="false" align="left">{{itm.KPIID}}</td>
<td width="1" align="left" class="kpiDesc" ng-show="false">{{itm.CountryID}} </td>
<td width="1" align="left" class="kpiDesc" ng-show="false">{{itm.PlantID}}</td>
<td width="180" align="left">
<span class="kpiDesc">{{itm.KPIDesc}}</span>
</td>
<td width="30" align="left">
<span></span>
</td>
<td align="center" data-ng-repeat="Col in ColDays| orderBy:'Date':false" >
<table class="table table-bordered batch-edit">
<tr>
<td width="40" align="center">
<span ng-bind-html="showWeekItemDetails(itm.UserName,itm.KPIID,ColsNew.Date)" contenteditable="true"></span>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>

Related

Retaining changed data on table pagination - AngularJS

I have a requirement to submit changes made in my angularjs table. This table is populated asynchronously and so the pagination is dynamic.
Whenever I make a change in a page(eg: page 1) and move on to any other page(eg: page 2), the data changed in the page 1 is getting reset on accessing the again. The changed data is not maintained as such.
Worked on to see that, there could be few ways to avoid this.
Restricting the use of st-safe-src in table definition. (But this
option was possible only to synchronous table loading. The table is
not getting populated if I remove the safe source)
Use a variable to store/retrieve state of dynamic form controls like
this example, which had an ajax call and populated the entire input
tags based on ajax validations, which too didn't work since the
default value of my select option is be based on the DB input.
Is there a possible way to do this in angularjs?
My table:
<table id="recordTable" st-table="display_record_returns" st-safe-src="recordReturns" ng-show="recordReturns"
class="table table-bordered table-striped shadow p-3 mb-5 bg-white rounded" ng-controller="BotRulesController">
<thead class="thead-dark">
<tr>
<th style="white-space: nowrap">CASE NO</th>
<th st-sort="code">MATCH CODE</th>
<th st-sort="decision">RULE</th>
<th st-sort="email">EMAIL</th>
</tr>
</thead>
<tbody>
<tr id="row.code" valign="middle" st-select-row="row"
st-select-mode="multiple"
ng-repeat="row in display_record_returns track by row.code"
ng-attr-id="{{::row.code}}">
<td>{{$index + 1}}</td>
<td align="left" ng-bind="row.code"></td>
<td>
<select id="ruleChangeSelect_{{row.code}}" ng-change="ruleChanged(row.code, row.decision, selectionModel[row.code])" class="custom-select"
style="margin-left: 0px"
ng-model="selectionModel[row.code]"
ng-options="choice.name for choice in possibleOptions track by choice.id">
<option value="" hidden="hidden" readonly="" ng-hide="true"></option>
</select>
</td>
<td ng-bind="row.email"></td>
</tr>
</tbody>
<tfoot>
<tr style="font-size:0.8rem !important;">
<td colspan="22" class="text-center">
<div st-pagination="" st-items-by-page="itemsByPage" st-displayed-pages="5"></div>
</td>
</tr>
</tfoot>
</table>
My js:
$scope.getRules = function () {
$http({
'url': '/getRules',
'method': 'POST',
'headers': {
'Content-Type': 'application/json'
}
}).then(function (response) {
$scope.recordReturns = response.data;
$scope.ruleOptions = [{
decision: "A"
}, {
decision: "B"
}, {
decision: "C"
}];
$scope.possibleOptions = getUniqueValues($scope.ruleOptions, 'decision')
.map(function (id) {
return {
id: id,
name: id
}
});
$scope.selectionModel = {};
angular.forEach($scope.recordReturns, function (input) {
$scope.selectionModel[input.code] = $scope.possibleOptions.filter(function (opt) {
return opt.id === input.decision;
})[0];
});
function getUniqueValues(array, prop) {
return [...new Set(array.map(item => item[prop]))];
}
$window.scrollTo(0, 0);
})
};

AngularJS - ng-repeat not working with a map-entry named length and value 0

I would like to show Events with Subevents I got from an API as JSON
[
{
"class":"de.ff.prg.EventItem",
"id":27667,
"additional_info":null,
"comments":null,
"event":{"class":"Event","id":27657},
"length":0,
"runningorder":0,
"screening":{"class":"Screening","id":27529},
"title_eng":"'71",
"title_ger":"'71",
"venue":{"class":"Venue","id":1}},
{"class":"de.ff.prg.EventItem",
"id":27676,
"additional_info":null,
"comments":null,
"event":{"class":"Event","id":27657},
"length":5,
"runningorder":0,
"screening":null,
"title_eng":"NEW",
"title_ger":"NEW",
"venue":{"class":"Venue","id":8}
}
]
In order to display the fields of the items in rows and not in columns, I have nested two tables with ng-repeat so that I get a table of tables.
<!--Items-->
<table>
<thead>
<td colspan="6" style="background-color: #b9da73">
<button class="btnAdd" ng-click="addAndEditEventItem()">Add Item</button>
</td>
</thead>
<tbody>
<tr ng-repeat="item in eventItems">
<h1>{{eventItems.length}}</h1>
<th>
<table>
<thead>
<td colspan="2" style="background-color: #c0da86">{{item.runningorder}}</td>
<td colspan="4" style="background-color: #c0da86">
<button class="btnAdd" ng-click="deleteEventItem(item.id)">Delete Item</button>
</td>
</thead>
<tbody>
{{item}}
<tr ng-repeat="(key,value) in item">
<th colspan="2" style="background-color: #ceeca1">{{key}}</th>
<th colspan="4" style="font-weight: normal;">{{value}} </th>
</tr>
</tbody>
</table>
</th>
</tr>
</tbody>
</table>
Up until now this was no problem, but somewhere along the way I have lost the possibility to display the body of the first sub-table (all other rows render fine). I have inserted {{item}} before the body tag and it shows the missing data, so it's there all right.
Any ideas? Or do you need to see the other code to tell? I have no clue...
Here is a Fiddle
Interesting case.
Empirically, I found that this is because of
"length":0
Just looked into angular source code and found that it transforms object's properties in repeat to an array, and then takes it's length property to iterate through it.
...ngRepeatDirective...
if (isArrayLike(collection)) {
collectionKeys = collection;
trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
} else {
trackByIdFn = trackByIdExpFn || trackByIdObjFn;
// if object, extract keys, sort them and use to determine order of iteration over obj props
collectionKeys = [];
for (key in collection) {
if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
collectionKeys.push(key);
}
}
collectionKeys.sort();
}
arrayLength = collectionKeys.length; <<<--- HERE
// locate existing items
length = nextBlockOrder.length = collectionKeys.length; <<<--- AND HERE
So nothing really happens.
Seems like a bug actually. I've posted an issue.
Just try then iterating through your array and change name of this property. Like:
for(var i = 0; i < $scope.eventItems.length; i++){
$scope.eventItems[i].itemLength = $scope.eventItems[i].length;
delete $scope.eventItems[i].length;
}

knockout js showing related objects(selected id) on click

I'm new to Knockout js and need some advice. What I am trying to do (the correct way) is have orders listed in a grid and a "production" button that when it is click, will show only the production objects that have matching id's to the order id. I'm trying to wrap my head around Knockouts binding, but I think I am over thinking things.
right now I have 2 objects Order and Production with are observable arrays filled with observables. Order has value of orderId and Production have value of prodId that I am checking for a match. I'm now wondering if I should not make this on object with mutli-dimensional array. Would it be easier to show selected data that way?
here is an example of the initial arrays
var initProduction = [
new Production({
proId:"183175",
pType:"Art TIme",
startTime:"11:20",
stopTime:"11:50",
totalTime:"",
by :"MJ"
})
var initData = [
new Order({
date:"06-09-2014",
orderId:"183175",
name:"Columbus Africentric",
dateRec:"05-23-2014",
rushDate:"",
totalQty:55,
parts:"1",
auto:"No",
type:"Local",
})
]
so should I combine into a multidimensional array? And if so, how would I do that? And how would I create a click event to show related data in another table showing only the production info.
I hope this makes sense and someone can help me. I apologize for my ignorance.
here is a stripped down version of my html bindings
<table>
<tbody data-bind="foreach:filteredOrders">
<tr>
<td>
<label class="read" data-bind="text:orderId, visible:true" />
</td>
<!-- controls -->
<td class="tools">
<button class="button toolButton" data-bind="click: $root.showSummary">Show Production</button>
</td>
</tr>
</tbody>
</table>
<h3>Production Summary</h3>
<table class="ko-grid" id="menu" >
<tbody data-bind="foreach:filteredProds">
<tr>
<td>
<div>
<label class="read" data-bind="text:proId, visible:true" />
</div>
</td>
</tr>
</tbody>
</table>
I would just have an orders array and then link the production object to the order.
var model = {
orders: [
{
date:"06-09-2014",
orderId:"183175",
name:"Columbus Africentric",
dateRec:"05-23-2014",
rushDate:"",
totalQty:55,
parts:"1",
auto:"No",
type:"Local",
production: {
proId:"183175",
pType:"Art TIme",
startTime:"11:20",
stopTime:"11:50",
totalTime:"",
by :"MJ"
}
},
{
date:"06-09-2014",
orderId:"183176",
name:"Angle Africentric",
dateRec:"05-23-2014",
rushDate:"",
totalQty:55,
parts:"1",
auto:"No",
type:"Local"
}
]
};
In the above json the second order doesn't have a production object.
Then in the viewModel I would use a computed which will return the orders depending on if all orders or only production orders should be shown. I've created a toggle here which is linked to the button.
var ViewModel = function (model) {
var self = this;
self.orders = $.map(model.orders, function (order) { return new Order (order); });
self.toggleProductionMode = function (order) {
order.showProductionOrder(!order.showProductionOrder());
};
};
var Order = function (order) {
var self = this;
ko.utils.extend(self, order);
self.showProductionOrder = ko.observable(false);
};
View:
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody data-bind="foreach: orders">
<tr>
<td data-bind="text: orderId"></td>
<td data-bind="text: name"></td>
<td data-bind="if: production"><button data-bind="click: $root.toggleProductionMode">Toggle Production Orders</button>
</td>
</tr>
<tr data-bind="visible: showProductionOrder, with: production">
<td colspan="3">
<table>
<tr>
<th>proId</th>
<th>pType</th>
</tr>
<tr>
<td data-bind="text:proId"></td>
<td data-bind="text:pType"></td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
Demo here: http://jsfiddle.net/X3LR6/2/

Get the Dynamic table data from gui in selenium webDriver

I am working on a web based Application that I am testing with Selenium. On one page the content is dynamically loaded in table. I want to get the Table data, i am geting a "org.openqa.selenium.NullPointerElementException" in this line.
WebElement table = log.driver.findElement(By.xpath(tableXpath));
I tried the following complete code.
public int selectfromtable(String tableXpath, String CompareValue, int columnnumber) throws Exception {
WebElement table = log.driver.findElement(By.xpath(tableXpath));
List<WebElement> rows = table.findElements(By.tagName("tr"));
int flag = 0;
for (WebElement row : rows) {
List<WebElement> cells = row.findElements(By.tagName("td"));
if (!cells.isEmpty() && cells.get(columnnumber).getText().equals(CompareValue)) {
flag = 1;
Thread.sleep(1000);
break;
} else {
Thread.sleep(2000);
flag = 0;
}
}
return flag;
}
I am calling the above method like
String tableXpath = ".//*[#id='event_list']/form/div[1]/table/tbody/tr/td/div/table";
selectfromtable(tableXpath, eventType, 3);
my html page is like
<table width="100%">
<tbody style="overflow: auto; background-color: #FFFFFF">
<tr class="trOdd">
<td width="2%" align="center">
<td width="20%" align="center"> Account </td>
<td width="20%" align="center"> Enter Collection </td>
<td width="20%" align="center">
<td width="20%" align="center"> 10 </td>
<td width="20%" align="center"> 1 </td>
</tr>
</tbody>
<tbody style="overflow: auto; background-color: #FFFFFF">
<tr class="trEven">
<td width="2%" align="center">
<td width="20%" align="center"> Account </td>
<td width="20%" align="center"> Resolved From Collection </td>
<td width="20%" align="center">
<td width="20%" align="center"> 10 </td>
<td width="20%" align="center"> 1 </td>
</tr>
</tbody>
</table>
My solution for similar case - I wanted to get a row number from table, which given column contains specific text.
// Method searches in given column of a table and return number of row where
// exactly first value is spotted (title row counts as 0 row and is
// skipped). If no value is found then 0 is returned. Given column number
// starts with 1.
public Integer getTableRowNumberWithValue(String tableId, String value,
Integer columnNumber) {
WebElement table = getDriver().findElement(By.id(tableId));
List<WebElement> rows = table.findElements(By.tagName("tr"));
int j = 0;
int i = 0;
for (WebElement row : rows) {
// Skip title row which counts as 0 row.
if (i > 0) {
if (row.findElements(By.tagName("td")).get(columnNumber - 1)
.getText().equals(value)) {
j = i;
break;
}
}
i++;
}
return j;
}
I had the similar issue. The solution I found was use the method and xpath together.
public void theSearch(String value) throws Exception {
String xpathExpression = "//*[starts-with(#id,'searchResultsTable:')]";
List<WebElement> elementTable= state.getDriver().findElements(By.xpath(xpathExpression));
for (WebElement listofElement : elementTable) {
String theElement= listofElement.getText();
if (theElement.contains(value)) {
Assert.assertEquals(value, theElement);
// System.out.println("The Expected Value " + value + " Equals the actual " + theElement);;
}
}
}

Disable and Enable Checkboxes Javascript

I have a series of Checkboxes:
<tr id="tr5" onmouseover="changeBackgroundColor(this.id)" onmouseout="changeBackgroundColor2(this.id)">
<td class="td5"><input name="benefit" value="Bonuses" id="benefit5" type="checkbox" onchange='addition();'</td>
<td class="td5"><label for="benefit5"> <b>Bonuses</b></label></td>
<tr id="tr6" onmouseover="changeBackgroundColor(this.id)" onmouseout="changeBackgroundColor2(this.id)">
<td class="td6"><input name="benefit" value="Final salary pension" id="benefit6" type="checkbox" onchange='addition();'</td>
<td class="td6"><label for="benefit6"> <b>Final salary pension</b></label></td>
Once a user has selected 3 checkboxes, is it possible to disable the rest in one hit (there are 30 checkboxes - I could do it individually but that seems a pain)? Is so, how would one go about doing that? Also, if the user then un-selected one of the check boxes, is it possible to enable them again?
EDIT: If possible - could someone point me in the right direction, code wise please?
Thanks in advance,
H.
DEMO
var chk=0;
function checkCheckboxes() {
var checkboxes = document.getElementsByName("benefit");
for (var i=0;i<checkboxes.length;i++) {
chk += checkboxes[i].checked?1:0; // count in case we reload
checkboxes[i].onclick=function() { // set up event handler for each
chk+=this.checked?1:-1; // add or subtract one
if (chk > 3) {
console.log(chk,"too many")
this.checked=false;
chk--; // we counted too many
}
}
}
}
function changeBackgroundColor(row,on) {
var id = row.id; // if you need that
row.style.backgroundColor=(on)?"red":"white";
}
window.onload=function() {
var trs = document.getElementById("table1").rows;
for (var i=0;i<trs.length;i++) {
trs[i].onmouseover=function() {
changeBackgroundColor(this,1);
}
trs[i].onmouseout=function() {
changeBackgroundColor(this,0);
}
}
checkCheckboxes();
}
using
<table id="table1">
<tr id="tr1">
<td class="td1"><input name="benefit" value="Bonuses" id="benefit1" type="checkbox"</td>
<td class="td1"><label for="benefit1"> <b>Bonuses</b></label></td>
</tr>
<tr id="tr2">
<td class="td2"><input name="benefit" value="Bonuses" id="benefit2" type="checkbox"</td>
<td class="td2"><label for="benefit2"> <b>Bonuses</b></label></td>
</tr>
<tr id="tr3">
<td class="td3"><input name="benefit" value="Bonuses" id="benefit3" type="checkbox"</td>
<td class="td3"><label for="benefit3"> <b>Bonuses</b></label></td>
</tr>
<tr id="tr4">
<td class="td4"><input name="benefit" value="Bonuses" id="benefit4" type="checkbox"</td>
<td class="td4"><label for="benefit4"> <b>Bonuses</b></label></td>
</tr>
</table>

Resources