How can we initialize and manipulate a check box value? I've looked at quite a number of examples, but haven't been able to get any to work.
I'm trying to present a N x M table where the rows represent tasks, and the columns students. The idea is that checking one of the check-boxes in the table assigns a task to a student.
There is a typescript hash map which contains the value of all the checkboxes;
assigned : { [key:string]:boolean; } = {};
the hash key is:
var key = a.TaskId + '_' + a.StudentId;
The table is generated with a nested ngFor:
<tr *ngFor="let t of tasks">
<td>Task Name for task... {{t.taskId}} </td>
<td *ngFor="let s of students">
<input type="checkbox" name=#{{t.TaskId}}_{{s.StudentId}} change="onAssignmentChange(t,s)" [checked]="cbValue(t, s)">
</td>
</tr>
the cbValue(t, s) is below:
cbValue(taskItem, studentItem) {
var key = taskItem.TaskId + '_' +studentItem.StudentId;
return this.assigned[key];
}
This doesn't work, all the checkboxes in the table come up unchecked, no matter what the values in the hash.
I've also tried:
<input type="checkbox" change="onAssignmentChange(t,s)" [checked]="cbValue(t, s)">
<input type="checkbox" change="onAssignmentChange(t,s)" [(ngModel)]={{t.TaskId}}+'_'+{{s.StudentId}} >
<input type="checkbox" change="onAssignmentChange(t,s)" [(ngModel)]="assigned[t.TaskId"+'_'+"s.StudentId"]>
none of which works.
I seem to be quite in the dark here; onAssignmentChange doesn't get triggered either, there are no Errors in console.
Also,
... name=#{{t.TaskId}}_{{s.StudentId}} ...
is this supposed to be a local target or something?
thanks in advance
This is fairly trivial as we're just going to bind straight to the assigned object, since we're using JavaScript's bracket property accessor we also get a free dynamic instantiation of the property through the template (dirty, maybe, but powerful). Additionally, wherever you're processing this later assume that a missing value is false:
template
<tr *ngFor="let t of tasks">
<td>Task Name for task... {{t.taskName}} </td>
<td *ngFor="let s of students">
<input type="checkbox" name="{{t.taskId}}_{{s.studentId}}" [(ngModel)]="assigned[t.taskId + '_' + s.studentId]">
</td>
</tr>
Here's a plunker to demonstrate: http://plnkr.co/edit/9RorhJnv42cCJanWb80L?p=preview
Related
I am developing a Point of Sale cart module to sharpen my javascript skills. I am currently trying to calculate the total of a product, by multiplying it's price and quantity needed. I am loading the products through a json file, so far so good. But I have a problem in this javaScript function. The quantity is being loaded through the HTML as shown below.
function AddToCart(food){
const row = document.createElement('tr');
row.innerHTML = `
<tr data-price='100' data-quantity='5' id='${food.FoodID}'>
<td>
${food.FoodName}
</td>
<td id = "PriceFood">
${food.FoodPrice}
</td>
<td>
**<input class='' id="Quantity" type="number" min="1" max="9" step="1" value="1">**
</td>
<td id="ProductTotal">
**${getTotal(food.FoodPrice, document.getElementById('#Quantity'))}**
</td>
<td>
X
</td>
</tr>
`
;
cart_content.appendChild(row);
}
function getTotal(price, qty){
console.log(price, qty)
return Number(price) * qty;
}
By running the result above in the console. console.log(price, qty) returns this :-
100 null
The price is being parsed but it returns a null for the quantity.
I can't really be able to identify the problem why it returns a null.
Some help would be appreciated..thanks :)
.getElementById() does just that, gets an element by its id. Because of this you don't prepend the id with a # as you are doing here:
document.getElementById('#Quantity')
Remove the # to correctly get the quantity element.
Then, to get the value of that element, you'll need to use qty.value, not just qty.
getElementById, assumes you are supplying the ID of the DOM node, therefore you don't need to precede it with #.
With that, you would be returning just the DOM node to the function - you need to return the value of the input, like so:
**${getTotal(food.FoodPrice, document.getElementById('Quantity').value)}**
I have an table created using ng-repeat and there hundreds of rows, up to 600 or 700. Each row includes a checkbox and I have a "Check All" box at the top to check all the boxes in one go. However I'm running into browser performance issues, IE11 (the clients preferred choice) in particular becomes unresponsive. After several minutes all the checkboxes appear checked but you still can't scroll or do anything so it is effectively useless.
I have created a controller array and when the checkAll box is clicked it loops through the model (the one used in ng-repeat) and adds a value to the array. I presume it's this looping through the array that is causing the slow-down but I'm not sure. Pagination has been ruled out, they want all the rows on one page.
<table>
<tbody>
<tr>
<th>Table Header</th>
<th><input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments>
<td>{{ payment.somePaymentValue }}</td>
<td>
<input type="checkbox" class="paymentsApprovalCheckbox"
ng-checked="vm.approvedPayments.indexOf(payment.payId) > - 1"
ng-value="payment.payId" ng-model="payment.approved"
ng-click="vm.handleCheckBoxClick(payment.payId)" />
</td>
</tr>
</tbody>
</table>
Here is the angular function that checks/unchecks all
vm.tickOrUntickAllCheckBoxes = function(){
if (vm.allChecked == false) {
vm.approvedPayments = [];
} else {
vm.payments.forEach(function(payment){
vm.approvedPayments.push(payment.payId);
});
}
};
Swapping out the angular vm.tickOrUntickAllCheckBoxes() function for a plain old javascript option makes the checkAll box work almost instantaneously in IE11 however I lose access to the checked payment.payId values. I wonder is there away for angular to get them? Here is the plain javascript checkAll() function:
<script>
function checkAll(x) {
var checkBoxes = document.getElementsByClassName('paymentsApprovalCheckbox');
for (var i = 0; i < checkBoxes.length ; i++) {
checkBoxes[i].checked = (x.checked == true);
}
}
</script>
Then I update the checkAll checkbox like this:
<input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" onclick="checkAll(this)" />
If you check one checkbox individually then the ng-model="payment.approved" in the repeating checkboxes is updated but this does not happen if they are checked with the checkAll function. Is it possible for angular to detect the boxes checked with checkAll()? I guess this is just putting off the same old inevitable slow-down to a slightly later point in the process.
Anyone have any ideas or work-arounds? Thanks!
I would use the ng-model to the best of its abilities. In your controller:
$onInit() {
// If you need this from a REST call to populate, you'll have to
// remember to do that here;
this.model = {
all: true,
items: {}
};
}
In your loop:
<tr>
<th>Table Header</th>
<th>
<input type="checkbox"
id="checkAllCheckBox"
ng-model="vm.model.all"
ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments track by $index">
<td ng-bind="payment.somePaymentValue"></td>
<td>
<input type="checkbox"
class="paymentsApprovalCheckbox"
ng-change="vm.approvedPayments($index)"
ng-model="vm.model.items[$index]" />
</td>
</tr>
Then in your controller:
tickOrUntickAllCheckBoxes() {
const boxes = this.model.items.length;
this.model.all = !this.model.all;
// Several ways to do this, forEach, map, etc.,
this.model.items.forEach((item) => { item.checked = !this.model.all });
}
And for setting it individually:
approvedPayments(idx) {
// Sets all the item's boxes checked, or unchecked;
this.model.items[idx].checked = !this.model.items[idx].checked;
// Possible call to extended model to get payment info;
handleCheckBoxClick(idx);
}
You should be able to put all the payment information into the one approvedPayments() method rather than have two separate methods (move logic out of template and into the controller or a service). I.e., your model could look like:
this.model.items = [
// One 'option' with id, payment etc;
{
id: 73,
paymentId: 73,
somePaymentValue: 210.73,
currencyType: 'GBP',
checked: false
},
{
// Another 'option' etc...
}
]
One issue to note is the incompatibility of ngChecked with ngModel, had to look it up (which is why I haven't used ng-checked in the above).
Thank to everyone for the suggestions. The solution I came up with was to push some of the work back to the server side. Instead of just loading the payments model (in which each payment record contains a lot of info) i am now loading two additional models when the page loads, one of which is a set of key/value pairs where the keys are payId and the values are all false and another one with the same keys and all values are true. Example:
{
"1": false,
"2": false
}
These are used for the checkAll/Uncheck all - just set the vm.approvedIDs variable to the true or false one. Then, the vm.approvedIDs variable is used as the model in the ng-repeat checkbox.
I have to do a bit of extra work on the server side when the user sends the approvedIDs back to the server to get only the key/id of the 'true' entries. Here are the relevant angular controller functions:
$onInit() {
// call http to get 'data' from server
vm.payments = data.payments;
vm.paymentIDsFalse = vm.approvedIDs = data.paymentIDsFalse;
vm.paymentIDsTrue = data.paymentIDsTrue;
};
// tick/untick all boxes
vm.tickOrUntickAllCheckBoxes = function(){
if (vm.allChecked == false) {
vm.approvedPayments = vm.paymentIDsFalse;
} else {
vm.approvedPayments = vm.paymentIDsTrue;
}
};
// tick/untick one box
vm.handleCheckBoxClick = function(payId, currentValue){
vm.approvedPayments[payId] = currentValue;
};
vm.submitApprovedIds = function(){
// post vm.approvedPayments to server
};
HTML:
<table>
<tbody>
<tr>
<th>Table Header</th>
<th><input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments>
<td>{{ payment.somePaymentValue }}</td>
<td>
<input type="checkbox" class="paymentsApprovalCheckbox"
ng-value="payment.payId"
ng-model="vm.approvedPayments[payment.payId]"
ng-click="vm.handleCheckBoxClick(payment.payId, vm.approvedPayments[payment.payId])" />
</td>
</tr>
</tbody>
</table>
It looks to me as if there must be a better way than creating these additional models but it is working pretty smoothly for now and I can move on to the next thing!
This is how I populate the Table and attach checkbox to controller
<tr ng-repeat="key in queryResults.userPropNames">
<td><input type="checkbox"
data-ng-checked="selectedKeys.indexOf(key) != -1"
data-ng-click="toggleSelect(key)">
</td>
<td>{{key}}</td>
<td ng-repeat="user in queryResults.users">
{{user.properties[key]}}
</td>
</tr>
This is how my HTML for button looks
<div>
<span ng-if="!validKeys" class="button-terminal primary save-user-keys"
data-disabled="false">Save Keys</span>
<span ng-if="validKeys" class="button-terminal primary save-user-keys"
data-ng-click="saveUserKeys()">Save Keys</span>
</div>
and my Controller looks like
$scope.toggleSelect = function (attribute) {
if ($scope.selectedKeys.indexOf(attribute) === -1) {
$scope.selectedKeys.push(attribute);
} else {
$scope.selectedKeys.splice($scope.selectedKeys.indexOf(attribute), 1);
}
};
$scope.saveUserKeys = function() {
$scope.customAttributes.mappingForUser = $scope.selectedKeys;
$scope.saveMappings();
};
$scope.validKeys = !!$scope.selectedKeys;
But my button is always enabled, even if I de-select all the checkboxes
What is wrong with this code?
Thank you
$scope.selectedKeys is an Array, even when no keys are selected. However empty Arrays are truthy (!![] // => true).
One fix would be to check the length of selectedKeys instead:
$scope.validKeys = $scope.selectedKeys && $scope.selectedKeys.length;
Alternatively, if assigning validKeys was just an attempt to get the view to render correctly, on the view you could just update the ngIf to ng-if="selectedKeys.lengh"
If you print validKeys (i.e. {{validKeys}}, do you see it changing between true/false? Also, if I understand this correctly, you should be testing for the length of validKeys - if higher than 0, enable the button, otherwise disable it.
I am using a foreach in Knockout to view data, and for each record I want to check if the Date field is the same as the Date field of the previous record, if so not show it, otherwise show it.
Ideally I want a layout something like this :
12/12/2014
----------
Record 1 - Field 1 Field 2 Field 3
Record 2 - Field 1 Field 2 Field 3
14/12/2014
----------
Record 3 - Field 1 Field 2 Field 3
Here is my current foreach loop, what syntax would I use to be able to compare a field value to the previous field value in the array? I have googled around but not found any examples of variables being used within Knockout foreach loops.
<tbody data-bind="foreach: fixture.fixtures">
<tr>
<td data-bind="text: FixtureDate"></td>
<td data-bind="text: FixtureId"></td>
<td><img data-bind="attr:{src: HomeBadge}" /></td>
<td data-bind="text: HomeName"></td>
<td data-bind="text: HomeScore"></td>
<td style="width:15px;"></td>
<td><img data-bind="attr:{src: AwayBadge}" /></td>
<td data-bind="text: AwayName"></td>
<td data-bind="text: AwayScore"></td>
</tr>
</tbody>
Make a Computed Observable to do the filtering, and bind your tbody to that. Any conditionals should be controlled by your model, not the view. Something like:
var deduped = ko.pureComputed(function () {
var result = [];
var originalValues = fixture.fixtures();
for (var i=1; i<originalValues.length; ++i) {
if (originalValues[i].date != originalValues[i-1].date) {
result.push(orignalValues[i]);
}
}
return result;
});
Actually, it looks like you want to have a couple of nested foreach loops: the outer one for dates, and the inner one for the record/field data. Write your HTML as if your data were convenient to present, and then use Computed Observables to make it that way.
guys,
once again I come begging for help. I can't get my head around this - all I need is update the name attribute's indexes of texts in my form. I want to show to the user one line in the table and, as he/she clicks on the plus sign, another line is added.
For my form to work on the server side, I need do reindex the name parameter. That's how I'm trying to do it:
function reindex () {
$("table").find("tr.faixa").each(function(index, value) {
value.find("input.faixa_min").attr("name", "data[" + index + "][BonusRecarga][faixa_min]"); // <- here's where I get the error
value.find("input.faixa_max").attr("name", "data[" + index + "][BonusRecarga][faixa_max]");
value.find("input.valor_bonus").attr("name", "data[" + index + "][BonusRecarga][valor_bonus]");
value.find("input.perc_bonus").attr("name", "data[" + index + "][BonusRecarga][perc_bonus]");
});
}
And here's a fragment of the form:
<tr class="faixa">
<td>
<input name="data[0][BonusRecarga][faixa_min]" class="faixa_min" type="text" required="required"/>
</td>
<td>
<input name="data[0][BonusRecarga][faixa_max]" class="faixa_max" type="text" required="required"/>
</td>
<td>
<input name="data[0][BonusRecarga][valor_bonus]" class="valor_bonus" type="text" required="required"/>
</td>
<td>
<input name="data[0][BonusRecarga][perc_bonus]" class="perc_bonus input-small" type="text" required="required"/>
</td>
<td>
<span class="help-inline"><i class="icon-plus" style="cursor: pointer"></i></span>
<span class="help-inline"><i class="icon-minus" style="cursor: pointer"></i></span>
</td>
</tr>
I have also tried using a common for(var i = 0; i < $(".faixa").lenght; i++), but I always get the "undefined is not a function" in the first value.find(...). It looks like "value" doesn't have the find() method, but when I log it, it shows me a regular "....
Do you know why the find() method is not working on the "value" variable?
Thanks in advance.
value is a DOM Element object while .find() is a jQuery method. You need to create a jquery object :
var $value = $(value);
//Then use $value.find()
Try using $(this).find('input...')... . it works, and is better inside each.
A much faster result for jquery would be to use the EQ function and append.
https://api.jquery.com/eq/
if EQ is not what your looking for leave a comment and ill look further into your issue.