Curious if angularJs has better method to calculate number of checkboxes - javascript

I need to check if any checkbox is checked. so i am doing it like
self.isButtonEnabled = function() {
var selectLineCheckboxs = document.getElementsByClassName('selectLineRadioInput'),
i = 0, checkboxLength = selectLineCheckboxs.length - 1;
for (i = 0; i <= checkboxLength; i++) {
if (selectLineCheckboxs[i].checked) {
self.selectLineChecked = true;
break;
} else {
self.selectLineChecked = false;
}
}
return self.selectLineChecked;
};
in return i get true if any checkbox is checked.
so quite simple,
Now here i am looking if we can do the same with angularJs with any better approach and i do not want to use watch() function in angular.

I can help with your some code to convert it to look like in Angular way.
use angular.element (provided by jQLite to get element) as instead of document.getElementsByClassName
You could use $filter while checking attribute is checked or not
CODE
self.isButtonEnabled = function() {
var selectLineCheckboxs = angular.element('.selectLineRadioInput');
var checkedValues = $filter('filter')(selectLineCheckboxs, { 'checked': true }); //do filtering and contains check value
self.selectLineChecked = checkedValues.length > 0 ? true : false;
return self.selectLineChecked;
};
Note: You should add $filter dependency on your controller before using $filter
Update
I'd suggest you to create your own custom filter that could be usable in multiple purposes, or dynamically check property value is true or not. I know your code is as same as you ask in answer, but I putted some of your code as reusable component, which can dynamically work for any property value to check is true or not.
Filter
.filter('isPropertyTrue', function () {
return function (elements, property) {
var returnArray = [];
angular.forEach(elements, function (val, index) {
if (val[property]) returnArray.push(val)
});
return returnArray;
}
});
Code
$scope.isButtonEnabled = function () {
var selectLineCheckboxs = document.getElementsByClassName('selectLineRadioInput');
var checkedValues = $filter('isPropertyTrue')(selectLineCheckboxs, 'checked');
self.selectLineChecked = checkedValues.length > 0 ? true : false;
return self.selectLineChecked;
};
JSFiddle
Hope this could help you, Thanks.

i have been using it this way with ng-bind variable in scope $scope.Items .and the binded variable can be used to see what all items are checked.
angular.forEach($scope.Items,function(key,value)
{
if(key.Selected)
{
counter++;
}
});
Here is a JSFiddle to illustrate the same

Related

Switching Between Angular Filters On Button Click

I have written a few different custom filters in my angular module to achieve the sorting that I want, e.g.
Angular:
var sortByValue = function() {
return function(input) {
/*...*/
return some_boolean;
}
};
var sortByCount = function() {
return function(input) {
/*...*/
return some_boolean;
}
};
In my HTML (jade), I can call the different filters as such:
.row(ng-repeat='item in array | sortByValue')
OR
.row(ng-repeat='item in array | sortByOrder')
I wish now to add a button that toggles between the two different sorting options.
I tried to do this by defining a new overall filter function that makes use of a $scope variable to keep track of the toggle (see below), but it isn't working.
Angular:
var myController = function($scope) {
// function called on ng-init
$scope.initialize = function() {
$scope.byValue = true;
}
};
var sortFunction = function() {
if($scope.byValue) {
return sortByValue();
} else {
return sortByCount();
}
}
I have also tried passing in the $scope variable to the filter in the HTML itself, e.g. sortFunction:byValue but it also doesn't work.
Can anyone help? Thank you!
Demo plunker # plnkr.co/edit/altzOow7aIQhqKTN93Oe?p=preview?
Can use one custom filter function and an argument
app.filter('mySorter', function(){
return function(items, sortType){
// sort logic
if(!items) return;
return items.sort(function(a, b){
if(sortType=== 'order'){
// order matching
}else{
// value matching
}
});
}
});
HTML
.row(ng-repeat='item in array | mySorter: sortType')
<button ng-click="toggleSort('order')">
Then in controller switch around variable sortType
$scope.sortType = 'order' // or 'value'
$scope.toggleSort = function(type){
$scope.sortType = type;
}

Javascript: move objects from one array to another: Best approach?

I have two arrays, called 'objects' and 'appliedObjects'. I'm trying to come up with an elegant way in Javascript and/or Angular to move objects from one array to another.
Initially I did something like this:
$scope.remove = function () {
angular.forEach($scope.appliedObjects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.objects.push(element);
$scope.appliedObjects.splice(index, 1);
}
});
}
$scope.add= function () {
angular.forEach($scope.objects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.appliedObjects.push(element);
$scope.objects.splice(index, 1);
}
});
}
But then I realized that when the value was removed from the looping array, and it would not add or remove every other item, since it went by index.
Then I tried using a temporary array to hold the list of items to be added or removed, and I started getting strange referential issues.
I'm starting to spin a bit on what the best solution to this problem would be...any help and/or guidance would much appreciated.
function moveElements(source, target, moveCheck) {
for (var i = 0; i < source.length; i++) {
var element = source[i];
if (moveCheck(element)) {
source.splice(i, 1);
target.push(element);
i--;
}
}
}
function selectionMoveCheck(element) {
if (element.selected) {
element.selected = false;
return true;
}
}
$scope.remove = function () {
moveElements($scope.appliedObjects, $scope.objects, selectionMoveCheck);
}
$scope.add = function () {
moveElements($scope.objects, $scope.appliedObjects, selectionMoveCheck);
}
When a construct does too much automatically (like forEach, or even a for-loop, in this case), use a more primitive construct that allows you to say what should happen clearly, without need to work around the construct. Using a while loop, you can express what needs to happen without resorting to backing up or otherwise applying workarounds:
function moveSelected(src, dest) {
var i = 0;
while ( i < src.length ) {
var item = src[i];
if (item.selected) {
src.splice(i,1);
dest.push(item);
}
else i++;
}
}
You are altering the array while iterating on it, you will always miss some elements.
One way of doing it would be to use a third array to store the references of the objects that need to be removed from the array:
// "$scope.add" case
var objectsToRemove = [];
$scope.objects.forEach(function (value) {
if (value.selected) {
value.selected = false;
$scope.appliedObjects.push(value);
objectsToRemove.push(value);
}
});
objectsToRemove.forEach(function (value) {
$scope.objects.splice($scope.objects.indexOf(value), 1);
});
If you wish to move simply whole array you could do:
appliedObjects = objects;
objects = []
Of course it won't work if they were parameters of a function!
Otherwise I cannot see other way than copying in the loop, e.g.
while (objects.length) {
appliedObjects.push(objects[0]);
objects.splice(0,1);
}
or if you like short code :) :
while (objects.length) appliedObjects.push(objects.splice(0,1));
check fiddle http://jsfiddle.net/060ywajm/
Now this maybe is not a fair answer, but if you notice you are doing alot of complicated object/array manipulations, you should really check out lodash or underscore library. then you could solve this with on liner:
//lodash remove function
appliedObjects.push.apply( appliedObjects, _.remove(objects, { 'selected': true}));
//or if you want to insert in the beginning of the list:
appliedObjects.splice(0, 0, _.remove(objects, { 'selected': true}));
This is a first pass at what I think will work for you. I'm in the process of making a test page so that I can test the accuracy of the work and will update the tweaked result, which hopefully there will not be.
EDIT: I ran it and it seems to do what you are wanting if I understand the problem correctly. There were a couple of syntax errors that I edited out.
Here's the plunk with the condensed, cleaned code http://plnkr.co/edit/K7XuMu?p=preview
HTML
<button ng-click="transferArrays(objects, appliedObjects)">Add</button>
<button ng-click="transferArrays(appliedObjects, objects)">Remove</button>
JS
$scope.transferArrays = function (arrayFrom, arrayTo) {
var selectedElements;
selectedElements = [];
angular.forEach(arrayFrom, function(element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function(element) {
arrayTo.push(arrayFrom.splice(
arrayFrom.map(function(x) {
return x.uniqueId;
})
.indexOf(element.uniqueId), 1));
});
};
Old code
$scope.remove = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.appliedObjects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.objects.push($scope.appliedObjects.splice(
$scope.appliedObjects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
$scope.add = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.objects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.appliedObjects.push($scope.objects.splice(
$scope.objects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
You can use this oneliner as many times as many items you need to move from arr1 to arr2 just prepare check func
arr2.push(arr1.splice(arr1.findIndex(arr1El => check(arr1El)),1)[0])
You can use this to concat 2 arrays:
let array3 = [...array1, ...array2];

Underscore reject Function with IndexOf removes all objects from array

I have a small Angular app that I'm writing that makes use of Underscore to look over each object in an array, and remove the object if it does not match the keyword (user input).
$scope.search = function() {
$scope.posts = _.reject($scope.posts, function(p) {
var i = 0;
if ($scope.keywords.indexOf(p.author) < 0 ) {
i++;
}
if ($scope.keywords.indexOf(p.id) < 0 ) {
i++;
}
if(i > 0) {
return true;
}
});
};
As you can see I'm setting a counter, and then adding to the counter if the keyword is found in the index, then at the end checking the counter to return true or false to remove the object from the array. $scope.posts is array of objects with my data and $scope.keywords is the user input. I'm wanting to lookup the input from $scope.posts.author object and $scope.posts.id object.
If I remove one of the if statements the function performs as expected: everything not matching the keyword is removed from the array. However, as soon as I add another if statement to the function (as seen in my example above), ALL objects are removed from the array.
It looks to me as though filter might be a better fit here:
$scope.posts = _.filter($scope.posts, function(p) {
return $scope.keywords.indexOf(p.author) > -1 || $scope.keywords.indexOf(p.id) > -1;
});
Working example: http://jsfiddle.net/4xp3sm10/
Instead of filter or reject it would be even easier to do it the opposite way using _.where
var newArray = _.where($scope.posts, {keyword : $scope.keyword});
There you go, one line.
Edit:
If you are stuck on doing it this way, here's a way you could clean it up a little.
$scope.posts = _.reject($scope.posts, function(p) {
var check = false;
if ($scope.keywords.indexOf(p.author) < 0 ) {
check = true;
}
if ($scope.keywords.indexOf(p.id) < 0 ) {
check = true;
}
if(i > 0) {
return check;
}
});
};
No need to use an integer like that
Since you are rejecting rows you will want to make sure ALL conditions are true. Your code is just checking for either one to be true.
$scope.search = function() {
$scope.posts = _.reject($scope.posts, function(p) {
return (
($scope.keywords.indexOf(p.author) < 0 ) &&
($scope.keywords.indexOf(p.id) < 0 )
);
});
};

Knockout.js: computed observable not updating as expected

Edit: Added code for function populateDropdown and function isSystemCorrect (see bottom)
Edit 2 I have narrowed it down a bit and the problem seems to arise in the arrayFilter function in the computed observable. This returns an empty array, no matter what I try. I have checked that self.testsuites() looks ok right before filtering, but the filtering still fails.
I have a problem with my computed observable, filteredTestsuites.
As you can see from the screendump, the testsuites observable is populated correctly, but the computed observable remains empty. I have also tried choosing another option than "Payment" from the dropdown menu, to see if this will trigger the observable, it did not.
I would think the computed observable would be updated every time self.testsuites() or self.dropdownSelected() was changed, but it doesnt seem to trigger on neither of them.
What am I doing wrong here?
I simply want to make the computed observable filter the testsuites after the chosen dropdown option, every time either of them change.
function ViewModel() {
var self = this;
// The item currently selected from a dropdown menu
self.dropdownSelected = ko.observable("Payment");
// This will contain all testsuites from all dropdown options
self.testsuites = ko.mapping.fromJS('');
// This will contain only testsuites from the chosen dropdown option
self.filteredTestsuites = ko.computed(function () {
return ko.utils.arrayFilter(self.testsuites(), function (testsuite) {
return (isSystemCorrect(testsuite.System(), self.dropdownSelected()));
});
}, self);
// Function for populating the testsuites observableArray
self.cacheTestsuites = function (data) {
self.testsuites(ko.mapping.fromJS(data));
};
self.populateDropdown = function(testsuiteArray) {
for (var i = 0, len = testsuiteArray().length; i < len; ++i) {
var firstNodeInSystem = testsuiteArray()[i].System().split("/")[0];
var allreadyExists = ko.utils.arrayFirst(self.dropdownOptions(), function(option) {
return (option.Name === firstNodeInSystem);
});
if (!allreadyExists) {
self.dropdownOptions.push({ Name: firstNodeInSystem });
}
}
};
}
$(document).ready(function () {
$.getJSON("/api/TestSuites", function (data) {
vm.cacheTestsuites(data);
vm.populateDropdown(vm.testsuites());
ko.applyBindings(vm);
});
}
Function isSystemCorrect:
function isSystemCorrect(system, partialSystem) {
// Check if partialSystem is contained within system. Must be at beginning of system and go
// on to the end or until a "/" character.
return ((system.indexOf(partialSystem) == 0) && (((system[partialSystem.length] == "/")) || (system[partialSystem.length] == null)));
}
As suggested in a comment - rewrite the cacheTestsuites method:
self.testsuites = ko.observableArray();
self.filteredTestsuites = ko.computed(function () {
return ko.utils.arrayFilter(self.testsuites(), function (testsuite) {
return (isSystemCorrect(testsuite.System(), self.dropdownSelected()));
});
});
self.cacheTestsuites = function (data) {
var a = ko.mapping.fromJS(data);
self.testsuites(a());
};
The only thing different here is the unwrapping of the observableArray from the mapping function.

Knockout is slow when unchecking checkboxes on a large (1000) dataset

I am using this code, to check all checkboxes on my view.
var checked = self.includeAllInSoundscript();
var contents = self.filterContents(self.getFilters());
for (var i = 0; i < contents.length; i++) {
contents[i].includeInSoundscript(checked);
}
return true;
The checkbox
<input type="checkbox" data-bind="checked: includeInSoundscript" title="sometitle" />
This is what contents is:
(function (ko) {
ContentViewModel = function (data) {
this.orderId = data.orderId;
this.contentReferenceId = ko.observable(data.contentReferenceId);
this.includeInSoundscript = ko.observable();
});
This is the filter methods:
self.getFilters = function() {
var filterOrders = $.grep(self.orders(), function (order) {
return (order.usedInfilter());
});
var filterLocations = $.grep(self.locations(), function (location) {
return (location.usedInfilter());
});
return { orders: filterOrders, locations: filterLocations };
};
self.filterContents = function (filter) {
var filteredArray = self.contents();
if (filter.orders.length > 0) {
filteredArray = $.grep(filteredArray, function (content) {
return $.grep(filter.orders, function (order) {
return (order.orderId == content.orderId);
}).length > 0;
});
}
if (filter.locations.length > 0) {
filteredArray = $.grep(filteredArray, function (content) {
return $.grep(filter.locations, function (location) {
return $.inArray(location.location, content.orderedFrom().split('/')) != -1;
}).length > 0;
});
}
return filteredArray;
};
Checking all checkboxes is fast, but when i uncheck, it can take up to 20 seconds. Strange thing is when the filetered result is small, it still takes a bit longer, even if the filtered results is about 40, from a total set of 1000.
The checkbox is in a table, bound using data-bind="foreach: contents"
I have now removed some of the "unescessary" observables, for properties that most likely will not change, it then behaves slightly better, but still very slow, especially in firefox. The big question is, why is this behavior only on unchecking checkboxes, and not on filtering, sorting, checking, etc.
Notice: Its only unchecking the checkboxes, basically when "checked" is false, otherwise its fast.
Edit: I am only displaying 50 items at a time, but i am checking / unchecking all the filtered items. This, so that I have controll over what to post to the server.
This is what I use for this scenario. Maybe it will help you.
The checked binding can work with an array of selected items, but only supports storing strings in the array. I use a custom binding that supports storing objects in the array (like selectedOptions does):
ko.bindingHandlers.checkedInArray = {
init: function (element, valueAccessor) {
ko.utils.registerEventHandler(element, "click", function() {
var options = ko.utils.unwrapObservable(valueAccessor()),
array = options.array, // don't unwrap array because we want to update the observable array itself
value = ko.utils.unwrapObservable(options.value),
checked = element.checked;
ko.utils.addOrRemoveItem(array, value, checked);
});
},
update: function (element, valueAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor()),
array = ko.utils.unwrapObservable(options.array),
value = ko.utils.unwrapObservable(options.value);
element.checked = ko.utils.arrayIndexOf(array, value) >= 0;
}
};
The binding for each checkbox then looks like this:
<input type="checkbox" data-bind="checkedInArray: { array: $parent.selectedItems, value: $data }" />
The checkbox for selecting all items uses the normal checked binding and is attached to a writable computed observable:
this.allItemsSelected = ko.computed({
read: function() {
return this.selectedItems().length === this.items().length;
},
write: function(value) {
this.selectedItems(value ? this.items.slice(0) : [] );
},
owner: this
});
Example: http://jsfiddle.net/mbest/L3LeD/
Update: Knockout 3.0.0 introduced the checkedValue binding option that makes the above custom binding unnecessary. You can now bind the checkboxes like this:
<input type="checkbox" data-bind="checked: $parent.selectedItems, checkedValue: $data" />
Example: http://jsfiddle.net/mbest/RLLX6/
What happens to performance if you use jQuery to check/uncheck all the boxes?
$('#tableId').find('input[type=checkbox]').prop('checked', checked);
Alternatively, could you check all the boxes when you display them, rather than doing all of them in one go?
Also, you could try using the knockout.utils methods for filtering the observable arrays, I'd be interested to see if there's any performance difference there.
var filteredArray = ko.utils.arrayFilter(this.items(), function(item) {
return ko.utils.stringStartsWith(item.name().toLowerCase(), filter);
});
There is also a method for looping over an array and processing each element:
ko.utils.arrayForEach(this.items(), function(item) {
var value = parseFloat(item.priceWithTax());
if (!isNaN(value)) {
total += value;
}
});
Again, I have no idea if this will help with performance or not, though I think it's a bit better prettiness-wise!

Categories

Resources