Knockout.js - Adding properties to data sent serverside - javascript

I've got some data sent via a server, formatted like this:
[
{"Username":"user1#domain.com", "id":1},
{"Username":"user2#domain.com", "id":2},
{"Username":"user3#domain.com", "id":3}
]
I bind it to a table, but I'd like the ability to add a class to the table row when the checkbox is checked (to indicate it's been selected). Here's what will eventually work, and I know the problem is that Selected is not a property currently in my data.
<table>
<tbody data-bind="foreach: Items">
<tr data-bind="css:{selected: Selected}">
<td>
<input type='checkbox' data-bind="attr:{name: id}, checked: Selected" />
</td>
<td data-bind="text: Username"> </td>
</tr>
</tbody>
</table>​
Since the concept of Selected is something purely for the UI, it seems a little dumb to have the server send that over the wire for each item in my data.
What I want to happen is basically this: http://jsfiddle.net/xSSMX/ but without having to add the observable Selected property on each item.
How can I add a property to each existing item in my data to achieve this?

You could just use map to add the field to the array that you get from the server like this...
data = data.map(function(item) {
item.Selected = ko.observable(false);
return item;
});
Which will add Selected on to each item. Although if I'm not mistaken map isn't supported in all browsers so you'd have to add support which you could do with a function similar to the one found here... http://www.tutorialspoint.com/javascript/array_map.htm. Or you could achieve the same effect using jQuery's $.each.

When the server sends over the data, you can create a userModel(id, userName). This model by default will have selected on false. I included a jsFiddle to make this a bit more clear.
http://jsfiddle.net/xSSMX/1/

Related

How can I preselect a generated radio button?

My Website is meant to have several carts per user and they are listed and updated via AngularJS like this:
<tr ng-repeat="cart in carts|orderBy:orderByField:reverseSort">
<td>
<input type="radio" ng-model="form['cart_id']"
ng-value="cart.cart.cart_id" />
</td>
<td>{{cart.cart.alias}}</td>
<td>{{cart.cart.description}}</td>
<td>{{cart.cart.created}}</td>
</tr>
When I add another cart in $scope.carts it gets updated pretty well, but I am not able to let the new cart be preselected when it is shown.
I tried using adding ng-checked=true which did not work, adding an ID and setting .attr('checked','checked') in JQuery, but the element is not known at that moment.
I am using Angular 1.2.22, JQuery 1.9.0 and Bootstrap 2.3.2. So I am well aware it's pretty outdated, but as I am new here, I am not able to change it. Yet.
Radio buttons are pre-selected by setting their model to the defined value. To select the last item in the carts array:
var last = $scope.carts.length-1;
$scope.form.cart_id = $scope.carts[last].cart_id;
For more information, see
AngularJS <input type=radio> Directive API Reference
Use value prop to make radio preselected, like: value=<value of ng-model variable which you want to get preselected>
Refer to this: https://docs.angularjs.org/api/ng/input/input%5Bradio%5D

JQuery validation on not existing checkbox

I have a JQuery DataTable that has checkboxes to select items in it. I added j
JQuery validation such that you can't submit the form without having at least selected one checkbox. Here is what my validation looks like:
jQuery.validator.addMethod("noApplicationSelected", function(value, element, params) { return $('.cbDisplay').length != 0; },'{0}');
$('[name="cbDisplay"]').rules('add', { noApplicationSelected: ['No checkbox selected'] });
Everything works fine but my problem is that if I for example add a filter in my DataTable search such that no entries are present (empty DataTable), and I try to submit the form, it submits even though I haven't selected any checkboxes.
The reason for that I think is because the validation can't bind the rules on the checkboxes because they do not exist.
How can I fix this problem?
Edit 1:
Would adding some validation that checks for whether the DataTable is empty and a checkbox is selected fix my problem? But the issue I see with that is what do I bind this validation to? I guess I could bind it to the select all/deselect all checkbox but that doesn't make sense
Edit 2:
Demo of the probelm: https://jsfiddle.net/mu79L32w/14/
If you try to find the inputs/checkboxes inside the displayed document, you only get the rendered elements of the datatables-element. This means it is possible that you will not find any checkbox-element in the document if the filter of datatables is used. If a row is not displayed in the datatables-element it is removed from the rendered table and as a result all elements inside that row are also not available for a wrapping form-element.
But you can iterate over the data/rows of the DataTable-Object with javascript.
Here is an example on how to test if any checkbox is checked when the form submits by using jquery-validates submitHandler.
$(document).ready(function() {
let table = $('#example').DataTable({
"columns": [
{ "data": "column_a" },
{ "data": "column_b" }
]
});
function isAnythingChecked() {
let foundChecked = false;
let inputs = table.rows().nodes().to$().find('input');
for (let input of inputs) {
if (input.checked) foundChecked = true;
}
return foundChecked;
}
$("#myForm").validate({
submitHandler: function(form) {
if (isAnythingChecked()) {
alert('Yeah! A checkbox is selected');
//form.submit();
} else {
alert('Oh no! No checkbox selected');
}
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-validation#1.19.0/dist/jquery.validate.min.js"></script>
<link href="https://cdn.datatables.net/v/dt/dt-1.10.18/datatables.min.css" rel="stylesheet"/>
<script src="https://cdn.datatables.net/v/dt/dt-1.10.18/datatables.min.js"></script>
<form id="myForm">
<table id="example" class="display" style="width:100%">
<thead>
<tr>
<th>Column A</th>
<th>Column B</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox"> Checkbox 1</td>
<td>Test</td>
</tr>
<tr>
<td><input type="checkbox"> Checkbox 2</td>
<td>Lorem</td>
</tr>
<tr>
<td><input type="checkbox"> Checkbox 3</td>
<td>ipsum</td>
</tr>
</tbody>
<table>
<button id="btn" type="submit">Submit</button>
</form>
As you suspected, you cannot bind validation to any input that does not yet exist.
I don't see enough code for a demo, so some generics below:
You do not need to write a custom method for making checkboxes required using this plugin. If the checkboxes are in a group sharing the same name, then required will make at least one from the group mandatory. See: jsfiddle.net/q357zcwy/1/
The whole point of the .rules() method is for dynamically adding removing rules after the plugin has already been initialized on the form. So if you dynamically add an input, then call .rules() on that input after it's been created. See: jsfiddle.net/q357zcwy/2/
If you only want to add required, then the easiest way to handle this is by putting the required="required" attribute on the input and my item #2 above is negated. You do not have to worry about whether it exists or when to call .rules(). Simply create the input that includes this attribute and the plugin will automatically pick it up. See: jsfiddle.net/q357zcwy/3/
<input type="checkbox" name="foo" required="required" ....
Hidden or invisible fields are always ignored by default. If your plugin is hiding any of the fields you are trying to validate, then you'll need to over-ride this behavior using the ignore option. Set ignore to []("nothing") so that nothing is ignored and everything gets validated.
ignore: []
EDIT: More notes after seeing your jsFiddle:
The .validate() method gets called once to initialize the plugin on the form. Typically on page load. However, you have it within the function that is called by your click handler, which means validation is not initialized until the button is clicked and it's repeatedly called unnecessarily every time the button is clicked.
The DataTables plugin is indeed removing the rows entirely when you search/filter the table. Although the validation plugin can handle invisible or hidden elements (see item #4 above), there is no way anyone can reasonably expect it to handle elements that no longer exist in the DOM. I find it strange that DataTables can not merely hide these rows or render them invisible rather than removing entirely.
The solution by Jan is interesting as it iterates through the DataTables object looking for checked items. However, there does not seem to be a way to properly integrate this function into the operation of the jQuery Validate plugin. While the validate plugin is normally tied to each input and automatically shows/hides messages based on rules, this "workaround" is clumsily shoved into the submitHandler, which is normally only invoked after the form is valid. It also cannot leverage the default validation messages, highlight, unhighlight, success, invalidHandler, or any of the other built-in functions.
There might be a way to leverage showErrors to show & hide the default messages programmatically based upon the iteration through the DataTables object. However, this workaround would get overly complex. You would also need to gather the name(s) of the checked object from the DataTable object in order to know which messages to toggle. Then if none of the rows exist when you want to validate, you'd have to find another way to trigger validation. Since nearly all of the default functionality of the jQuery Validation plugin would be over-written anyway, it might actually make more sense to just write your own form validation from scratch. No off-the-shelf validation plugin is going to be able to handle input elements that don't exist.

jQuery next select element on row

I have 2 select boxes in my html called type and brand. When the type is changed i use the onchange event to create a ajax request to the server and retrieve the brands which produce the products in the type field. That is all working fine but i need to load the retrieved data in the brand select and i don't know how to do that because i have multiple rows containing type and brand select boxes. How can i find the next select element on the same row as the changed type?
My code
HTML:
<tr>
<td>
<select id="type" onchange="on_part_type_changed(this)" name "type[]"> --[options]-- </select>
</td>
<td>
<select id="brand" name="brand[]"></select>
</td>
</tr>
Javascript:
function on_part_type_changed(selectObject) {
// ajax call here (works)
// now need to update the brand select that is on the same row as selectObject (jQuery)
}
Save the brand object before you do the ajax call. It'll be ready for you once the ajax response comes back.
function on_part_type_changed(selectObject) {
//clumsy way of finding the brand object nextdoor
var brandObj=selectObject.parent().next().children('.brand');
// ajax call here
someKindOfAjax(response) {
//put response into brand object we got earlier
brandObj.html(response);
}
}
As others have said, if there can be multiple selects that are "brand", it should be a class instead of an ID.
Try this
function on_part_type_changed(selectObject) {
$(selectObject).next('.brand').html('...');
}

Set observable/computed value from a foreach of inputs knockoutjs

It's my first time working with KnockoutJS, and I have a little bit of problem.
I'm trying to get multiple values from inputs then set the addition of the values to a observable or make a computed that get the result of the addition. But I can't figure out how, because the table it's generated from a database and I don't know the quantity of rows until I run the app. Any help will be appreciated. Thanks.
Here is a capture of the site
I need in the field "INVERSION TOTAL DEL MES" the adition of the values from the inputs from "IMPORTE TOTAL"
Here's the part of the html with the bindings
<tbody data-bind="foreach: RendicionesPager.pagedRows">
<tr><td class="text-right">$ <input style="width:90%;text-align:right" data-bind="textInput:ImporteTotal" /></td>
</tr>
</tbody>
<tr>
<td colspan="7" class="text-right" >INVERSION TOTAL DEL MES</td>
<td class="text-right"><span data-bind="text:'$ ' + InversionDelMes()"></span></td>
</tr>
Do something like:
InversionDelMes = ko.computed(function(){
return RendicionesPager.pagedRows.reduce(function(p,n){ return p.ImporteTotal + n.ImporteTotal ;});
});
You will need to adjust all contexts, add () if ImporteTotal is an observable etc.etc but you get the idea :)
Edit: to explain better, create a ko computed, that will be updated when RendicionesPager.pagedRows is changed, and each time it will aggregate the ImporteTotal field of all contained elements and return it.
Also have in mind that this will work when you add/remove items from your observable array RendicionesPager.pagedRows (if it is obsArray that is). If you want to recalculate your values as a total each time a user changes an input value then you would have to subscribe to each observable field and run a similar logic,for example
ImporteTotal.subscribe(function(){
myTotal = RendicionesPager.pagedRows.reduce(function(p,n){
return p.ImporteTotal + n.ImporteTotal ;
});
});
so that when any of the observables changes its value, it will fun this function which will in turn update the total value

How can I modify an array model in an Angular end-to-end test?

I've got an angular app which has a table defined in the controllers like so:
$scope.rowHeaders = ["Revenues","Costs","Profit"];
$scope.data = [[100,230,300,400,500,600,700,800,900,1000,1100,1200], [30,40,50,60,70,80,90,100,110,120,130,140], [70,160,250,340,430,540,630,720,810,900,990,1100]];
The rowHeaders apply to each row in the data object. This is connected to the template as follows:
<tbody>
<tr ng-repeat="row in rowHeaders track by $index">
<td>{{row}}</td>
<td ng-repeat="cellData in data[$index] track by $index">
<input id="data{{$parent.$index}}_{{$index}}" type="text" class="clsinputboxes" ng-model="data[$parent.$index][$index]" name="data{{$parent.$index}}_{{$index}}" ng-blur="updateColor($parent.$index, $index, data[$parent.$index][$index])" style="background-color: {{color[$parent.$index][$index]}}">
</td>
</tr>
</tbody>
This produces a simple table with input boxes. When the input in a box changes, the background color of the box will also change with code that is inside updateColor(), specified in the controller.
All of this works fine. I want to be able to write a dead-simple end-to-end test, that will change the contents of one of the boxes in the table, and then check that the updateColor() function was triggered properly (perhaps by checking that the background color has indeed changed). This is proving to be incredibly hard to do.
I had thought something as simple as the following should have worked:
input("data[1][1]").enter(20);
But this actually does not work, and I get the following error:
http://localhost:8000/test/e2e/scenarios.js:17:4
Selector [ng\:model="data[1][1]"] did not match any elements.
Whereas, the code works fine when not in test mode, and the model is bound properly in the input tag using ng-model="data[$parent.$index][$index]".
For and end-to-end test, how do I go about entering some data into any box in the table that is linked up to an array model?
I also tried the following (although I'd much rather work with input().enter()):
element("#data1_1").query(function(el, done) {
el.click();
el.val(20);
done();
});
This changes the box contents but does not trigger updateColor(). I also tried putting e1.blur() - that does not work either.
I was able to finally get this to work in the elegant end-to-end scenario runner of Angular.
First, I gave an id to the table tag - "inputTable".
Then, the following in the scenario runner did the trick:
using("table#inputTable tbody tr:eq(1) td:eq(2)").input("data[$parent.$index][$index]").enter(20);
A shoutout to Ari Lerner who helped me figure this out.

Categories