In my form I have a table with checkbox in all of these columns. I have 3 <tr> and each <tr> has its ng-repeate calling the webservice to display the clones (Json data).
When I click on a checkbox I generate a js array which records id using this code :
checkoptions (array, model) {
angular.forEach(array, (value, key) => {
if (array[key].checked) {
model.push(array[key].id)
}
})
And in HTML :
<tr ng-repeat="developer in $ctrl.developers">
<td>{{developer.label}}</td>
<td>
<input type="checkbox" id="{{developer.id}}"
ng-change="$ctrl.checkoptions($ctrl.developers,$ctrl.employees.developers)"
ng-model="developer.checked">
<label for="{{developer.id}}"></label>
</td>
It works, but the problem is that when I uncheck a checkbox it is not removed from the js array
I included an else part to remove from the array:
http://jsfiddle.net/x9m1nqvp/1/
$scope.checkoptions = function (array, model) {
angular.forEach(array, (value, key) => {
if (array[key].checked) {
var index = model.indexOf(array[key].id);
if(index == -1)
model.push(array[key].id)
}
else {
var index = model.indexOf(array[key].id);
if(index >=0)
model.splice(index, 1);
}
})
While Everton's answer gets the job done, it is a bit redundant checking every item in the array, every time a single checkbox changes state. You don't really have to update for every single item in the array.
Here is an example, where only the checkbox that's actually toggled, is added or removed from the employees.developers array (Note: no need for the redundant angular.forEach):
$scope.checkoption = function (developer){
if (developer.checked) {
var index = $scope.employees.developers.indexOf(developer.id);
if(index == -1)
$scope.employees.developers.push(developer.id)
} else {
var index = $scope.employees.developers.indexOf(developer.id);
if(index >=0)
$scope.employees.developers.splice(index, 1);
}
}
and how this is used in the html:
<tr ng-repeat="developer in developers">
<td>{{developer.label}}</td>
<td>
<input type="checkbox" id="{{developer.id}}"
ng-change="checkoption(developer)"
ng-model="developer.checked">
<label for="{{developer.id}}"></label>
</td>
</tr>
Related
I have a MVC web app in which I show a table.
Some of my rows can have a similar id, on which I need to show only one checkbox for all those rows, and individual checkboxes for the rows which don't have a matching id. Something like below:
row1 and row2 have the same id, hence the checkbox is in between them (denoted by red checkbox).
row3, row4 have different ids, hence they need to have their individual checkboxes (denoted by green).
I know I need to play on the rowspan property, but I am unable to visualize how to get on it.
Below is the sample code:
[Route("Search")]
[HttpGet]
public async Task<IActionResult> Search()
{
//Some API call
return View("Search", model);
}
View Code:
<table id="tblsearch">
#if (Model.HasRecords)
{
var counter = 0;
<tbody>
#foreach (var item in Model.SearchResults)
{
<tr>
<td>
<input type="checkbox" id="Dummy_#counter" name="chkSearch" data-id="#item.Id"/>
<label for="Dummy_#counter"></label>
</td>
<td>#item.FullAddress</td>
<td>#item.Price</td>
<td>#item.OfficeName</td>
}
else
{
<tr><td>Data Not Found</td></tr>
}
</table>
I am trying to first hide all the checkboxes, then trying to match the id's in each row, and then if the ids of 2 rows are same, I am trying to increase the rowspan by 2.
js code:
function pageLoad()
{
var rowCount = $('#tblSearch >tbody >tr').length;
for(var i=0;i<rowCount-1;i++)
{
$('#Dummy_' + i).hide();
}
var searchArray= [];
for (var i = 0; i < rowCount - 1; i++) {
searchArray[i]= $('#tblSearch >tbody >tr')[i].attr('data-id');
}
}
Please guide how to proceed.
You should control the layout of the page in this instance from your View, please forgive my syntax as I primarily work in vbhtml these days.
Important things are to order your search results (in case they aren't already)
Remember and update the last processed Id.
<table id="tblsearch">
#if (Model.HasRecords)
{
var counter = 0;
var lastId = -1;
<tbody>
#foreach (var item in Model.SearchResults.OrderBy(x=>x.Id))
{
<tr>
#if(lastId!= item.Id){
<td rowspan="#(Model.SearchResults.Count(x=>x.Id == item.Id) > 0 ? Model.SearchResults.Count(x=>x.Id == item.Id) : 1 )">
<input type="checkbox" id="Dummy_#counter" name="chkSearch" data-id="#item.Id"/>
<label for="Dummy_#counter"></label>
</td>
}
<td>#item.FullAddress</td>
<td>#item.Price</td>
<td>#item.OfficeName</td>
#lastId = item.Id;
//I assume there was code snipped here...
}
else
{
<tr><td>Data Not Found</td></tr>
}
</table>
There is no need for any javascript. You can simply group your items by the Id property and conditionally render the checkbox column with a rowspan attribute if its the first item in the group.
<tbody>
#foreach (var group in Model.SearchResults.GroupBy(x => x.Id))
{
bool isFirstRow = true;
foreach (var item in group)
{
<tr>
#if (isFirstRow)
{
<td rowspan="#group.Count()">
#Html.CheckBox("chkSearch")
</td>
isFirstRow = false;
}
<td>#item.FullAddress</td>
<td>#item.Price</td>
<td>#item.OfficeName</td>
</tr>
}
}
</tbody>
I have a page where a user submits a query using MVC, and the user can select the columns that are shown using checkboxes. After the user selects the query parameters and columns to view, the user is then sent to another page where a roster of individuals are shown. Checkbox data is stored using local storage, and used on the roster page where there are also checkboxes that the user can use to hide or display columns.
I have a working version, but the code looks awful and I think there is a better way to do this with less lines of code.
Here are the checkboxes used on the query page:
<div id="grpChkBox">
<input type="checkbox" class="columnSelect" name="fullName" /> Full Name
<input type="checkbox" class="columnSelect" name="type" /> Type
<input type="checkbox" class="columnSelect" name="ID" /> ID Number
</div>
Here is the script used to select columns and set values in local storage:
<script type ="text/javascript">
//Default is that all columns are selected
$("#grpChkBox input:checkbox").attr("checked", "checked");
localStorage.setItem("fullName", 1);
localStorage.setItem("type", 1);
localStorage.setItem("ID", 1);
$(function () {
if (localStorage.getItem("fullName") !== null) {
$("input[name='fullName']").attr("checked", "checked");
}
});
$("input[name='fullName']").click(function () {
if ($(this).is(":checked")) {localStorage.setItem("fullName", 1);}
else {localStorage.removeItem("fullName");}
});
$(function () {
if (localStorage.getItem("type") !== null) {$("input[name='type']").attr("checked", "checked");}
});
$("input[name='type']").click(function () {
if ($(this).is(":checked")) { localStorage.setItem("type", 1); }
else {localStorage.removeItem("type"); }
});
$(function () {
if (localStorage.getItem("ID")== null) { $("input[name='ID']").attr("checked", "checked"); }
});
$("input[name='ID']").click(function () {
if ($(this).is(":checked")) { localStorage.setItem("ID", 1); }
else { localStorage.removeItem("ID"); }
});
As you can see, I am creating a function for each checkbox and corresponding column, and there should be a way that I can enumerate columns/checkbox to do this with less lines of code. Just not sure how.
This is the HTML for the roster that is generated on the next page:
<table class="MainContent" style="width: 100%;" id="rosterTable">
<tr>
<th class="fullName" title="Full Name">Name</a></th>
<th class="type" title="Type">Type</a></th>
<th class="ID" title="ID Number">ID Number</a></th>
</tr>
<tr>
<td>Name 1</td>
<td>Type 1</td>
<td>ID Number 1</td>
</tr>
<tr>
<td>Name 2</td>
<td>Type 2</td>
<td>ID Number 2</td>
</tr>
</table>
It also has the same checkboxes as the previous page:
<div id="grpChkBox">
<input type="checkbox" class="columnSelect" name="fullName" /> Full Name
<input type="checkbox" class="columnSelect" name="type" /> Type
<input type="checkbox" class="columnSelect" name="ID" /> ID Number
</div>
And here's the script that reads local storage, and hides/displays columns after the roster is generated:
<script type="text/javascript">
// Reads local storage and check or unchecks, hides/displays
$(document).ready(function () {
if (localStorage.getItem("fullName") !== null) {
$("input[name='fullName']").attr("checked", "checked");
}
else {
var index = $("#rosterTable th").filter(".fullName").index();
$("#rosterTable").find('tr :nth-child(' + (index + 1) + ')').hide();
}
if (localStorage.getItem("type") !== null) {
$("input[name='type']").attr("checked", "checked");
}
else {
var index = $("#rosterTable th").filter(".type").index();
$("#rosterTable").find('tr :nth-child(' + (index + 1) + ')').hide();
}
if (localStorage.getItem("ID") !== null) { $("input[name='ID']").attr("checked", "checked"); }
else {
var index = $("#rosterTable th").filter(".ID").index();
$("#rosterTable").find('tr :nth-child(' + (index + 1) + ')').hide();
}
//After roster is generated users can hide display columns
$(function () {
var $chk = $("#grpChkBox input:checkbox");
var $tbl = $("#rosterTable");
var $tblhead = $("#rosterTable th");
//$chk.prop("checked", true);
$chk.click(function () {
var colToHide = $tblhead.filter("." + $(this).attr("name"));
var index = $(colToHide).index();
$tbl.find('tr :nth-child(' + (index + 1) + ')').toggle();
});
});
</script>
Once again, this should be done with less lines of code than using a case for each column and checkbox. I need to deploy this solution to multiple pages with different columns, so I would like to do this with more dynamic code. I'm pretty sure this could be done with less lines of code.
All help is appreciated
Looks like I found a solution myself. Here's what I did:
First, I replaced localStorage with sessionStorage.
I replaced the section where I set each sessionStorage object explicitly with this:
var selected = [];
$('#grpChkBox input[type=checkbox]').each(function () {
if ($(this).attr("checked")) {
sessionStorage.setItem(($(this).attr('name')), 1);
} else { sessionStorage.removeItem(($(this).attr('name'))); }
});
I replaced all the functions for checking the each sessionStorage value and populating each checkboxes with:
for (var i = 0, len = sessionStorage.length; i < len; i++) {
var key = sessionStorage.key(i);
var value = sessionStorage[key];
//document.write(key + " => " + value + "\n");
if (sessionStorage.getItem(key) !== null) {
$("input[name='" + key + "']").attr("checked", "checked");
}
}
I replaced all the click functions for each checkbox with this:
$('#grpChkBox input[type=checkbox]').click(function () {
if ($(this.name).attr("checked")) {sessionStorage.setItem(this.name, 1); }
else {sessionStorage.removeItem(this.name);}
})
On the page where the roster is created, I replaced all the functions for checking the each sessionStorage value and populating each checkboxes with:
for (var i = 0, len = sessionStorage.length; i < len; i++) {
var key = sessionStorage.key(i);
var value = sessionStorage[key];
//document.write(key + " => " + value + "\n");
if (sessionStorage.getItem(key) !== null) {
$("input[name='" + key + "']").attr("checked", "checked");
}
}
Instead of checking checkboxes that are checked, I am using unchecked values since the sessionStorage will have the corresponding keys/values removed. I replaced the many functions for determining which column is hidden with:
var selected = [];
$('#grpChkBox input:checkbox:not(:checked)').each(function() {
var index = $("#rosterTable th").filter("."+($(this).attr('name'))).index();
$("#rosterTable").find('tr :nth-child(' + (index + 1) + ')').hide();
});
That's it. The only thing that would make the over deployment of this simpler is if I could dynamically create checkboxes based on table header names.
I have a html table structure below:
<table>
<tr><td>ABC</td></tr>
<tr>
<td><input class="inp"/></td>
<td><input class="inp"/></td>
</tr>
<tr><td><input class="inp"/></td></tr>
<tr><td><input class="inp"/></td></tr>
</table>
<button id="btn">Click me</button>
Then my purpose is let the focus always go to next the input field in class "inp" by button click. Here is my script:
var focused = null;
$(".inp").focus(function () {
focused = $(this);
}).first().focus();
$("#btn").click(function(e){
if (focused && focused.length) {
//this only works horizontally.
focused.parent().next().find(".inp").focus();
// this only works vertically.
//focused.closest("tr").next().find(".inp").focus();
}
});
I want to focus the second line after the last element in the first row. How do I combine those two statements?
You do this by using the JQuery .index() and .eq() functions on a wrapped set of all the input elements.
This moves the focus back to the first input when it is on the last input and the button is clicked.
$(function() {
var $inputs = $('.inp');
var index = null;
$inputs.focus(function() {
index = $inputs.index($(this));
});
$inputs.first().focus();
$('#btn').click(function(e) {
if (index != null) {
index = (index + 1) % $inputs.length;
$inputs.eq(index).focus();
}
});
});
jsfiddle
This version does not wrap the focus back to the first input.
$('#btn').click(function(e) {
if (index != null) {
if ((index + 1) < $inputs.length) {
$inputs.eq(index + 1).focus();
}
}
});
jsfiddle
Try this :
HTML
<table>
<tr>
<td>ABC</td>
</tr>
<tr>
<td>
<input class="inp" />
</td>
<td>
<input class="inp" />
</td>
</tr>
<tr>
<td>
<input class="inp" />
</td>
</tr>
<tr>
<td>
<input class="inp" />
</td>
</tr>
</table>
<button id="btn">Click me</button>
JS
// initial just focus on first input
$('.inp').first().focus();
// get all input
var all = $('.inp');
// get first input index and add one number for next focus
var index = (all.index(all.first()) + 1);
$('#btn').click(function () {
// if length of input same with current index
// defined into 0, (goes bact to first input)
// if does't wanna go back just remove this line
if (index == all.length) index = 0;
// focus next input
all.eq(index).focus();
index++;
});
DEMO
This code seems little nasty but it will do the job for you :D
$(document).ready(function() {
var focused = null;
$(".inp").focus(function() {
focused = $(this);
}).first().focus();
$("#btn").click(function(e) {
if (focused && focused.length) {
if ($(focused).closest('tr').find('td').length > 1 && $(focused).closest('td').next().length != 0) {
$(focused).closest('td').next().find('.inp').focus();
} else {
$(focused).closest('tr').next().find('.inp').focus();
}
}
});
});
And here is Plunker : http://plnkr.co/edit/Nm3wETpltRo6i7mAXPtj?p=preview
I have a fiddle that is jumping indexes until you reach the last one. If you want you can modify it to also go backwards.
http://jsfiddle.net/gyebua9e/
$('.inp').first().focus();
var index = 0;
var $inputs = $('.inp').length;
$('#btn').click(function() {
index += 1;
$('.inp').eq(index).focus();
});
Sorry this is a little confusing, what are you trying to accomplish with jumping between input fields on click?
Also are you just trying to always go to the next input no matter what the wrapper is? so if I click the second input it goes to the third input, if I click the first it goes to the second input? etc
I'm trying to create React component that contains a long list (~500 items) with checkboxes opposite each item. It should toggle checked state of each item and toggle checked state of all items in the list. I implemented that component, as I see that. But my solution has low performance and some time lag when I toggle checkbox. When I integrated that in page, it work slower than this jsFiddle example.
jsFiddle
What I'm doing wrong? Should I choose another way to work with items data?
var Hello = React.createClass({
getInitialState: function () {
var db = [];
for (var i = 0, l = 100; i < l; i++) {
db.push({
name: Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5),
value: i
});
}
return {
db: db
};
},
checkAll: function (ev) {
var items = this.state.db.slice();
items.forEach(function (v) {
v.checked = ev.target.checked;
});
this.setState({db: items});
},
handleCheck: function (ev) {
debugger;
var id = ev.target.dataset.id;
var items = this.state.db.slice();
var item = items.filter(function (v) {
return v.value == id;
})[0];
item.checked = ev.target.checked;
console.log(items.filter(function (v) {
return v.checked;
}));
this.state({db: items});
},
render: function () {
return <div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
<th>
<input type="checkbox" onChange={this.handleCheck} id="check-all"/>
<label htmlFor="check-all">Check all</label>
</th>
</tr>
</thead>
<tbody> {
this.state.db.map(function (v, i) {
return (
<tr key={i}>
<td>{v.name}</td>
<td>{v.value}</td>
<td>
<input id={'item-'+i} type="checkbox"
data-id={i}
onChange={this.handleCheck}
checked={v.checked}/>
<label htmlFor={'item-'+i}>Check this</label>
</td>
</tr>
);
}.bind(this))
}
</tbody>
</table>
</div>;
}
});
SOLUTION
I have many cells in my table with complex hierarchy. When I toggle checkbox value, all cells rerendered, including ones with unchanged values, that causes huge time lag. I splited up my component to some small components with shouldComponentUpdate callbacks and that works fine. Thanks all for help!
I've made some improvements to your code: https://jsfiddle.net/Lfbodh90/1/
What I've changed:
In the event handler, you can get the item by index, speeding up lookup time a lot:
handleCheck: function (id) {
var items = this.state.db;
var item = items[id]
item.checked = ev.target.checked;
this.setState({db: items});
}
Another thing: when creating the checkbox, you can pass the index parameter directly, using bind:
<input id={'item-'+i} type="checkbox"
onChange={this.handleCheck.bind(this, i)}
checked={v.checked}/>
<label htmlFor={'item-'+i}>Check this</label>
I've also removed the slice calls which are not necessary in the example given.
I've increased the number of items on the JSFiddle above just for testing. Now when you check/uncheck an specific checkbox, it's really fast.
Hope this helped
I have a tr :
<tr ng-hide="func(item.status)" ng-repeat="item in itemArray" >
<td></td>
.
.
</tr>
In the Func() the item.status is compared with the value of a dropdown D which can be changed by the user at any time.
I have a div which i need to show if the current number of visible tr == 0.
I am storing the number of visible tr as
$scope.qVisible = $("#tableid tbody tr:visible").length;
How can i have the qVisible be updated only once all the ng-hide statements have been executed to get the correct result?
I am going to assume that you have a dropdown somewhere that looks something like..
<select ng-model="selectedStatus" ng-options="status for status in statuses"></select>
So your ng-hide can do something like
<tr ng-hide="item.status !== selectedStatus" ng-repeat="item in itemArray" >
<td></td>
.
.
</tr>
In your controller, you need to setup a watch on the selectedStatus..
$scope.qVisible = false;
$scope.$watch("selectedStatus",function(newVal){
//for loop over items.. counting the number of items that match selectedStatus
// if count is 0, set a scope variable
$scope.qVisible = true;
});
In your html..
<div ng-show="qVisible">...</div>
This is the way to do it without touching the DOM.
You can have $watch that listens to the same as data-ng-repeat runs the same function as hide:
function update(newValue) {
var i;
$scope.numberOfShownItems = 0;
for (i = 0; i < newValue.length; i += 1) {
if ($scope.func(newValue[i])) {
$scope.numberOfShownItems += 1;
}
}
$scope.qVisible = !!$scope.numberOfShownItems;
}
$scope.items = [1,2,3,4,5,6];
$scope.func = function (item) {
if (item % 2 === 0) {
return true;
}
return false;
}
$scope.$watch('items', update);
Example:
http://jsfiddle.net/EgZak/