I'm beginning to use Handlebars and the architectural pattern Model View ViewModel and I wrote this code :
var data = {
currentPlayer: this._model.currentPlayer,
line: [
{
row:
[
{
caseNumber:1,
caseValue: this._model.getCaseState(0,0)
},
{
caseNumber:2,
caseValue: this._model.getCaseState(0,1)
},
{
caseNumber:3,
caseValue: this._model.getCaseState(0,2)
}
]
},
{
row:[
{
caseNumber:4,
caseValue:this._model.getCaseState(1,0)
},
{
caseNumber:5,
caseValue:this._model.getCaseState(1,1)
},
{
caseNumber:6,
caseValue:this._model.getCaseState(1,2)
}
]
},
{
row:[
{
caseNumber:7,
caseValue:this._model.getCaseState(2,0)
},
{
caseNumber:8,
caseValue:this._model.getCaseState(2,1)
},
{
caseNumber:9,
caseValue: this._model.getCaseState(2,2)
}
]
}
]
};
var htmlContent = this._template(data);
this._element.html(htmlContent);
With the following template :
<div>
<h3>It is to player {{currentPlayer}}</h3>
<table>
{{#each line}}
<tr>
{{#row}}
<td data="{{caseNumber}}" class="case{{caseValue}}">{{caseValue}}</td>
{{/row}}
</tr>
{{/each}}
</table>
</div>
This code works fine but I'm asking if I cannot reduce it. So I tried to use a for loop in the var data but I realized that I can't do this.
My other choice was to use an if in the template like this :
{{#each line}}
<tr>
{{#row}}
{{#if caseValue}}
<td data="{{caseNumber}}" class="case{{caseValue}}">O</td>
{{else}}
<td data="{{caseNumber}}" class="case{{caseValue}}">X</td>
{{/if}}
{{/row}}
</tr>
{{/each}}
by testing the value of the var caseValue. However, as caseValue takes the value of 1 or 0 or undefined, if the case isn't checked all the cells are filled with a "X".
So, I can't find a compact solution with the aim of :
At the beginning, all the TD tags are empty.
Depending on the value of getCaseState which returns 0 or 1 fill
the cell with an "X" or an "O".
EDIT : I manage the different values of getCaseState with this code :
Handlebars.registerHelper('displayTd', function(data) {
var result;
if(data.caseValue === undefined) {
result = '<td data="' + data.caseNumber + '"></td>';
return new Handlebars.SafeString(result);
} else if(data.caseValue === 1) {
result = '<td data="' + data.caseNumber + '" class="case' + data.caseValue + '">X</td>';
return new Handlebars.SafeString(result);
} else {
result = '<td data="' + data.caseNumber + '" class="case' + data.caseValue + '">O</td>';
return new Handlebars.SafeString(result);
}
});
The first step I would take to reduce the code was the one you alluded to about using loops to construct your data. The data in your line object follows a simple pattern, so we can construct with the following code:
var numRows = 3;
var numColumns = 3;
var line = [];
for (var rowIndex = 0; rowIndex < numRows; rowIndex++) {
line[rowIndex] = { row: [] };
for (var columnIndex = 0; columnIndex < numColumns; columnIndex++) {
line[rowIndex].row[columnIndex] = {
caseNumber: ((rowIndex * numColumns) + columnIndex + 1),
caseValue: getCaseState(rowIndex, columnIndex)
};
}
}
*Note that you will have to call getCaseState on your existing model object.
Our data object then becomes:
var data = {
currentPlayer: this._model.currentPlayer,
line: line
};
As for the conditional within your template, I would recommend creating your own Handlebars helper. Fortunately, Handlebars has an isEmpty utility method that returns true for:
Array with length 0
falsy values other than 0
This means that we can use this utility method to check if our caseValue is undefined:
Handlebars.registerHelper('getCharacter', function (caseValue) {
return Handlebars.Utils.isEmpty(caseValue) ? '' : (caseValue === 0 ? 'X' : 'O');
});
We then use our new helper in our template in the following way:
{{#each row}}
<td data="{{caseNumber}}" class="case{{caseValue}}">{{getCharacter caseValue}}</td>
{{/each}}
Related
Good evening. I am new to JavaScript and I need help with my mini-project and I have only one issue here and it is in the this.Add = function ().
It works properly when I enter a duplicate value from my list therefore it displays an alert that no dupes are allowed. But... when I enter a unique value, it only adds up the last element present (Wash the dishes) from myTasks list. instead of the one I recently entered and the list goes on adding the same ones. Did I just misplace something?
This is my final activity yet and I want to finish it to move to the next function. Thank you in advance.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tasks CRUD</title>
<style>
#tasks{
display: none;
}
</style>
</head>
<body>
<center>
<form action="javascript:void(0);" method="POST" onsubmit="app.Add()">
<input type="text" id="add-task" placeholder="Add another card">
<input type="submit" value="Add">
</form>
<div id="tasks" role="aria-hidden">
<form action="javascript:void(0);" method="POST" id="saveEdit">
<input type="text" id="edit-task">
<input type="submit" value="Edit" /> <a onclick="CloseInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Name</th>
</tr>
<tbody id="myTasks">
</tbody>
</table>
</center>
<script>
var app = new function() {
this.el = document.getElementById('myTasks');
this.myTasks = ['Clean the bathroom', 'Wash the dishes'];
this.Count = function(data) {
var el = document.getElementById('counter');
var name = 'task';
if (data) {
if (data > 1) {
name = 'Things To DO';
}
el.innerHTML = data + ' ' + name ;
} else {
el.innerHTML = 'No ' + name;
}
};
this.FetchAll = function() {
var data = '';
if (this.myTasks.length > 0) {
for (i = 0; i < this.myTasks.length; i++) {
data += '<tr>';
data += '<td>' + this.myTasks[i] + '</td>';
data += '<td><button onclick="app.Edit(' + i + ')">Edit</button></td>';
data += '<td><button onclick="app.Delete(' + i + ')">Delete</button></td>';
data += '</tr>';
}
}
this.Count(this.myTasks.length);
return this.el.innerHTML = data;
};
this.Add = function () {
el = document.getElementById('add-task');
// Get the value
var task = el.value;
if (task ) {
for(task of this.myTasks)
{
var ctr = 0;
if(document.getElementById("add-task").value == task){
ctr = 1;
break;
}
}
if(ctr == 1)
{
window.alert("Duplicates not allowed.");
}else{
// Add the new value
this.myTasks.push(task.trim());
// Reset input value
el.value = '';
// Dislay the new list
this.FetchAll();
}
}
};
this.Edit = function (item) {
var el = document.getElementById('edit-task');
// Display value in the field
el.value = this.myTasks[item];
// Display fields
document.getElementById('tasks').style.display = 'block';
self = this;
document.getElementById('saveEdit').onsubmit = function() {
// Get value
var task = el.value;
if (task) {
// Edit value
self.myTasks.splice(item, 1, task.trim());
// Display the new list
self.FetchAll();
// Hide fields
CloseInput();
}
}
};
this.Delete = function (item) {
// Delete the current row
this.myTasks.splice(item, 1);
// Display the new list
this.FetchAll();
};
}
app.FetchAll();
function CloseInput() {
document.getElementById('tasks').style.display = 'none';
}
</script>
</body>
</html>
In your for loop:
for (task of this.myTask) {
}
You are not declaring a new task variable, but instead assigning to the outer task variable, hence the repeated addition of tasks already in your list.
You can declare a new variable in the for scope like so:
for (const task of this.myTask) {
}
Your HTML as it is.
And your Javascript goes like below. You have a bug while checking if the task already exists in the array. As you're comparing string value either use simple for loop with triple equals or do as i have attached below.
var app = new function() {
this.el = document.getElementById('myTasks');
this.myTasks = ['Clean the bathroom', 'Wash the dishes'];
this.Count = function(data) {
var el = document.getElementById('counter');
var name = 'task';
if (data) {
if (data > 1) {
name = 'Things To DO';
}
el.innerHTML = data + ' ' + name ;
} else {
el.innerHTML = 'No ' + name;
}
};
this.FetchAll = function() {
var data = '';
if (this.myTasks.length > 0) {
for (i = 0; i < this.myTasks.length; i++) {
data += '<tr>';
data += '<td>' + this.myTasks[i] + '</td>';
data += '<td><button onclick="app.Edit(' + i + ')">Edit</button></td>';
data += '<td><button onclick="app.Delete(' + i + ')">Delete</button></td>';
data += '</tr>';
}
}
this.Count(this.myTasks.length);
console.log(this.myTasks.length);
return this.el.innerHTML = data;
};
this.Add = function () {
el = document.getElementById('add-task');
// Get the value
var task = el.value;
console.log(task);
if (task ){
var arrayContainsTask = (this.myTasks.indexOf(task) > -1);
if(arrayContainsTask == true){
window.alert("Duplicates not allowed.");
}else{
// Add the new value
this.myTasks.push(task);
// Reset input value
el.value = '';
}
// Dislay the new list
this.FetchAll();
}
}
}
I want to create a nested table with data from JSON file. here I use an array of object to simulate the back end:
var response = [
{
"mezzo":"fiat punto",
"tipologia":"auto",
"id":"1"
},
{
"mezzo":"fiat punto",
"tipologia":"auto",
"id":"2"
},
{
"mezzo":"fiat punto",
"tipologia":"auto",
"id":"3"
},
{
"mezzo":"alfa giulia",
"tipologia":"auto",
"id":"1"
},
{
"mezzo":"alfa giulia",
"tipologia":"auto",
"id":"2"
},
{
"mezzo":"fiat punto",
"tipologia":"auto",
"id":"4"
},
{
"mezzo":"alfa giulia",
"tipologia":"auto",
"id":"3"
}
];
I want to group my result table using the "mezzo" field, so I try sorting the array:
response.sort(function(a,b){
var mezzoA = a.mezzo.toLowerCase();
var mezzoB = b.mezzo.toLowerCase();
if (mezzoA > mezzoB) {
return -1;
}
if (mezzoA < mezzoB) {
return 1;
}
return 0;
});
now the jQuery code: I try to create elements and append to the DOM using .each() loop. the commented code works with hardcoded demo, but by now I have to group the rows matching the "mezzo" field in a single expandable row:
$(function () {
var parentFrag = document.createDocumentFragment();
$.each(response, function(i, item) {
var parentTr = document.createElement('tr');
var parentTd = '<td>></td><td>' + item.mezzo + '</td><td>' + item.tipologia + '</td><td>' + item.id +'</td>';
parentTr.innerHTML = parentTd;
parentFrag.appendChild(parentTr);
})
var parentTable = $('.parent');
parentTable[0].appendChild(parentFrag);
// Expand-Collapse details table
// $('table table').hide();
// $('td > a').on('click', function (e) {
// e.preventDefault();
// $(this).closest('tr').next().find('table:first').toggle();
// });
});
This is my html file:
<table border="1">
<thead>
<tr>
<td></td>
<td>Mezzo</td>
<td>Tipologia</td>
<td>ID</td>
</tr>
</thead>
<tbody class="parent">
</tbody>
</table>
I have a table grid, which is editable using Javascript, when I click on that particular field. I can change the value and update the old value.
How can I get old value and also the new value for logging purposes?
<s:iterator var="posting" value="PostLookup">
<tr class="update-rel">
<td id="${post.Id}"><input type="checkbox" /></td>
<td class="alignleft">${post.coin}</td>
<td class="alignleft" >${post.rate}</td>
<td class="alignleft">${posting.trainCode}</td>
<td class="alignleft"><fmt:formatDate pattern="HH:mm" value="${posting.postTime}" /></td>
<td class="alignleft">${posting.payMode}</td>
<td class="alignleft">${posting.payModeSub}</td>
</tr>
</s:iterator>
JavaScript Making rows editable
function activateTableEditing() {
$(document).ready(function() {
var rowIndex;
var columnIndex;
// get dynamic values from the server through a json call to be displayed inside the drop down box
var jsonObject = getJSONObject('ajax/fetchpayMode.action', 'Pay Modes');
var payModes = jsonObject.payModes;
$('#tablePostLookup tbody td').on('click', function(e) {
rowIndex = $(this).parent().index('#tablePostLookup tbody tr');
columnIndex = $(this).index('#tablePostLookup tbody tr:eq(' + rowIndex + ') td');
//console.log('Row ' + rowIndex + ' and column ' + columnIndex + ' is clicked ...');
if (columnIndex == 2 || columnIndex == 3 || columnIndex == 4) {
editInputBox(e, $(this).parent(), rowIndex, columnIndex);
}
if (columnIndex == 5) {
editCombobox(e, $(this).parent(), rowIndex, columnIndex, payMode);
}
if (columnIndex == 6) {
var payModes = $(this).parent().children('td:eq(5)').text();
jsonObject = getJSONObject('ajax/fetchpayModeSub.action?payModeSub=' + payMode, 'paymode sub ');
var paymentModeSub = jsonObject.paymentModeSub;
editCombobox(e, $(this).parent(), rowIndex, columnIndex, paymentModeSub);
}
});
});
}
Getting current values - When update button is clicked.
function savePostLookup(e, action) {
var countRowSelected = 0;
$('#tablePostLookup tbody tr').each(function() {
var result = $(this).children('td').eq(0).children('input[type=checkbox]').eq(0).is(':checked');
if (result) {
++countRowSelected;
e.preventDefault();
var id = $(this).find('td:eq(0)').eq(0).attr('id');
var coin = $(this).find('td:eq(1)').text();
var rate = $(this).find('td:eq(2)').text();
var trainCode = $(this).find('td:eq(3)').text();
var postTime = $(this).find('td:eq(4)').text();
var payMode = $(this).find('td:eq(5)').text();
var payModeSub = $(this).find('td:eq(6)').text();
createJSONObject(id, coin, rate, trainCode, postTime, payMode, payModeSub);
}
});
This is what you could potentially do. Create an object with the following key values pair.
{
"td1": [
{
"oldVlaue": "old",
"newValue": "new"
}
],
"td2": [
{
"oldVlaue": "old1",
"newValue": "new1"
}
]
}
So each time you click on some a table value, you can get the value from the event.target and you can also get which table element is clicked. Just update the value in your JSON object with the values you get.
As I said, this could potenitally be a way to keep track of the old or new values. Feel free to try something different or better.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I am using ng-repeat to show the items in ui in angular js and i need to move the
Items up and down in the ng-repeat table from external button.
i am able to move the items up and down but there is one more
condition in which i need to move group of items example:
id reportname comments
1 report1 na
2 report2 test
2 report4 test
3 report3 test
3 report3 na
4 report4 test
I need to move the enitre group of 3,3 above 2,2 or move entire 2,2 below 3,3.
Can someone please let me know how can we do that?
Here is a complete working solution, using Lodash for a bunch of utility methods.
EDIT: Rewrote and refactored to cater for cases where not all items with same id are grouped together. This now only moves adjacent items with the same id, not all items with the same id in the array.
angular.module('stackoverflow', []).controller('MainCtrl', function($scope) {
$scope.data = [{
id: 1,
reportname: 'report1',
comments: 'na'
}, {
id: 2,
reportname: 'report2',
comments: 'test'
}, {
id: 2,
reportname: 'report4',
comments: 'test'
}, {
id: 3,
reportname: 'report3',
comments: 'test'
}, {
id: 3,
reportname: 'report3',
comments: 'na'
}, {
id: 4,
reportname: 'report4',
comments: 'test'
}];
$scope.moveGroup = function(item, dir) {
dir = normalizeDir(dir);
firstIndexOfGroup
var firstIndexOfGroup = findFirstIndexWithSameId(item);
var endIndexOfGroup = findEndIndexOfGroup(firstIndexOfGroup, item);
var itemsInGroup = $scope.data.slice(firstIndexOfGroup, endIndexOfGroup + 1);
var idToSwapWith = ($scope.data[firstIndexOfGroup - 1] || {}).id;
// if moving down, swap with first entry past group end
if (dir === 'down') {
idToSwapWith = ($scope.data[endIndexOfGroup + 1] || {}).id;
}
if (idToSwapWith > 0) {
// remove from current position
$scope.data.splice(firstIndexOfGroup, _.size(itemsInGroup));
// insert group of items with same id at correct index
var firstItemWithPrevIdIndex = _.findIndex($scope.data, 'id', idToSwapWith);
if (dir === 'down') {
firstItemWithPrevIdIndex = _.findLastIndex($scope.data, 'id', idToSwapWith) + 1;
}
var spliceArgs = [firstItemWithPrevIdIndex, 0].concat(itemsInGroup);
$scope.data.splice.apply($scope.data, spliceArgs);
}
};
$scope.moveItem = function(item, dir) {
var index = $scope.data.indexOf(item);
if (normalizeDir(dir) === 'up') {
$scope.data.splice(index - 1, 2, item, $scope.data[index - 1]);
} else {
$scope.data.splice(index, 2, $scope.data[index + 1], item);
}
}
function normalizeDir(dir) {
switch ((dir || '').toString().toLowerCase()) {
case 'up':
case 'u':
return 'up';
}
return 'down';
}
function findFirstIndexWithSameId(item) {
var index = $scope.data.indexOf(item);
for (var i = index - 1; i >= 0; i--) {
if ($scope.data[i].id !== item.id) {
break;
} else {
index = i;
}
}
return index;
}
function findEndIndexOfGroup(startIndexOfGroup, item) {
var index = startIndexOfGroup;
for (var i = startIndexOfGroup + 1, len = _.size($scope.data); i < len; i++) {
if ($scope.data[i].id === item.id) {
index = i;
} else {
break;
}
}
return index;
}
});
.move-link-btn {
font-size: smaller;
color: lightblue;
padding: 2px 5px;
}
.move-link-btn:hover {
text-decoration: underline;
cursor: pointer;
}
td,
th {
outline: 1px solid #cfcfcf;
padding: 2px 5px;
font-family: sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.8.0/lodash.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="stackoverflow" ng-controller="MainCtrl">
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Comments</th>
<th>Move Commands</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data">
<td>{{item.id}}</td>
<td>{{item.reportname}}</td>
<td>{{item.comments}}</td>
<td>
<a class="move-link-btn" ng-click="moveItem(item, 'up')">Item Up</a>
<a class="move-link-btn" ng-click="moveItem(item, 'down')">Item Down</a>
<a class="move-link-btn" ng-click="moveGroup(item, 'up')">Group Up</a>
<a class="move-link-btn" ng-click="moveGroup(item, 'down')">Group Down</a>
</td>
</tr>
</tbody>
</table>
</div>
I create small demo for up and down element in ng-repeat http://plnkr.co/edit/mBoIY5ZCCQA4sRSVHuQB?p=preview
Please see this and review it.
In above demo add
<input type="button" name="changeOrder" ng-click="changeReportOrder()" value="Change Order">
<div ng-repeat="report in reports | orderBy:predicate:reverce">{{report.id}} {{report.reportname}} {{report.comments}}</div>
in body tag and
$scope.reports = [
{id:1,reportname:'report1',comments:'na'},
{id:2,reportname:'report2',comments:'test'},
{id:2,reportname:'report4',comments:'test'},
{id:3,reportname:'report3',comments:'test'},
{id:3,reportname:'report3',comments:'na'},
{id:4,reportname:'report4',comments:'test'}
];
$scope.predicate = '-id';
$scope.changeReportOrder = function(){
if($scope.predicate == 'id')
$scope.predicate = '-id';
else
$scope.predicate = 'id';
}
In controller. and order change ascending/descending of reports base on id when click on Order Change button.
The HTML
int i = 1;
foreach (var item in Model.MyDataset)
{
<td class="tdBorder">
#Html.DisplayFor(x => item.ID, new { id = "VisibleID" + #i })
#Html.HiddenFor(x => item.ID, new { id = "HiddenID" + #i })
</td>
i += 1;
}
The jQuery
for (i = 1; i <= rowCount; i++) {
var myID_Visible = $.trim($("#VisibleID" + i).val());
var myID_Hidden = $.trim($("#HiddenID" + i).val());
}
I'm trying to learn some MVC and jQuery.
Would some one explain to me why calling
var myID_Visible = $.trim($("#VisibleID" + i).val()); returns an empty string but
var myID_Hidden = $.trim($("#HiddenID" + i).val()); returns the value of my item.ID?
The only difference is that the first jQuery line refers to a #Html.DisplayFor (returns empty string) while the second jQuery line refers to a #Html.HiddenFor (returns actual value).
Why can't i get a value from the #Html.DisplayFor?
Because #Html.DisplayFor() does not render a control and you cannot use .val(). Instead use
myID_Visible = $.trim($("#VisibleID" + i).text())
although this will depend on the html that #Html.DisplayFor() is rendering (are you using a display template?). You need to check the html generated.
By default DisplayFor will just render the text value of the property. You would need to do something like
int i = 1;
#foreach (var item in Model.MyDataset)
{
<td class="tdBorder">
<span id="#i">
#Html.DisplayFor(x => item.ID)
</span>
#Html.HiddenFor(x => item.ID, new { id = "HiddenID" + #i })
</td>
i += 1;
}
and in the script
myID_Visible = $.trim($('#' + i).text());
Some input has .html() rather than .val() try:
var myID_Visible = $.trim($("#VisibleID" + i).html());
EDIT
Another thing, remove the # before the i, you are already inside a C# code
#Html.DisplayFor(x => item.ID, new { id = "VisibleID" + i })
The reason is explained in this and that.
For your case however, I prefer to make it like this:
HTML
int i = 1;
foreach (var item in Model.MyDataset)
{
<td class="tdBorder">
<p id="VisibleID#(i)">#Html.DisplayFor(x => item.ID)</p>
<p id="HiddenID#(i)">#Html.HiddenFor(x => item.ID)</p>
</td>
i += 1;
}
So in the script we can call it:
for (i = 1; i <= rowCount; i++) {
var myID_Visible = $("#VisibleID" + i).text();
var myID_Hidden = $("#HiddenID" + i).text();
}
Hopefully this can help you, cheers!
#Html.DisplayFor(x => item.ID, new { id = "VisibleID" + #i })
rendred html looks like:
<span id="VisibleID1">item id value here</span>
whereas
#Html.HiddenFor(x => item.ID, new { id = "HiddenID" + #i })
rendered html looks like
<input type="hidden" id="HiddenID1" value="item id value here">
So in order to have display for value you should you use, $("#VisibleID1).html() as noted by #Shadi