I have a multiselect field in angular-formly, with the following options:
vm.fields = [
{
key: 'fruits',
type: 'multiCheckbox',
className: 'multi-check',
templateOptions: {
label: 'Fruits:',
options: [
{
"name": "ALL",
"value":"ALL"
},
{
"name": "apple",
"value":"apple"
},
{
"name": "orange",
"value":"orange"
},
{
"name": "pear",
"value":"pear"
},
{
"name": "blueberry",
"value":"blueberry"
},
],
},
},
];
When I select/unselect "ALL", I want all the options to be selected/unselected.
For Example. If ALL is checked, then all the fruits options( apple, orange, pear, blueberry) should be checked as well
If I unselect ALL, then none of the fruit options should be checked.
How do I enable this behavior in angular-formly?
Here is the link to jsbin:
https://jsbin.com/xololi/edit?html,js,output
I wrote a working example here https://jsbin.com/dukewac/6/edit?js,output
The signature of templateOptions.onClick when a function is $modelValue, fieldOptions, scope, event. This occurs when the ngModelAttrsTemplateManipulator is run. I leveraged these variables in my function.
Some of it is a bit hacky but that partially has to do with how angular implements checkboxes and the workarounds that the multiCheckbox type in angular-formly-templates-bootstrap employs.
Gotchas
Nested Keys
This example will not work with nested keys but should if the multiCheckbox type was updated. This is because it is directly accessing the model using array access notation [see the code here] (https://github.com/formly-js/angular-formly-templates-bootstrap/blob/a69d69e5c3f6382ea6a6c028c1d8bf76a9b94db3/src/types/multiCheckbox.js).
Hardcoded "all" option
The code also assumes that the 'ALL' option is the first in the list and that its value is 'ALL'. This could be fixed by adding a templateOption property that references what the index and value the all functionality is for.
Model contains 'ALL'
'ALL' will appear in your model. A possible way to get around this would be to define a $parser for it.
No support for templateOptions.valueProp
templateOptions.valueProp is not taken into account but shouldn;t be too difficult to add.
Do you have any solution how to add defaultValue to multiCheckbox. I can add it and see it in model but on view (html) it is not displayed.
I'we made example on jsbin where I have all types of fields and also multiCheckbox... jsbin example
Thanks.
here is another working solution (actually updated ckniffty's code), which binds ng-click to formly multi checkbox field and then calls function in controller:
....
ngModelElAttrs: {
"ng-click": "update(this.option.value)"
},
controller: function ($scope) {
$scope.update = function (val) {
var all = 'ALL',
// field key
key = $scope.options.key,
// if true - current checkbox is selected, otherwise - unselected
selected = $scope.model[key].indexOf(val) !== -1,
// index of 'ALL' checkbox within model array
indexOfAll = $scope.model[key].indexOf(all);
// 'ALL' checkbox clicked
if (val === all) {
// on select - select all checkboxes, on unselect - unselect all checkboxes
$scope.options.value(selected ? $scope.to.options.map(function (option) {
return option.value;
}) : []);
}
// other then 'ALL' checkbox unselected, while 'ALL' is selected
else if (!selected && indexOfAll !== -1) {
// unselect 'ALL' checkbox
$scope.model[key].splice(indexOfAll, 1);
}
// all checkboxes selected except of 'ALL'
else if (selected && indexOfAll === -1 && $scope.model[key].length === $scope.to.options.length - 1) {
// select 'ALL' checkbox
$scope.model[key].push(all);
}
};
}
.....
plunker: https://plnkr.co/edit/LWRZSS6HuBmrzSG5fsH9
Related
I have an Array of statuses objects. Every status has a name, and a boolean set at false by default.
It represent checkbox in a form with filters, when a checkbox is checked bool is set at true :
const filters.statuses = [
{
name: "pending",
value: false
},
{
name: "done",
value: false
},
];
I am using Angular HTTP Params to pass params at the URL.
filters.statuses.forEach((status) => {
if (status.value) {
this.urlParams = this.urlParams.append('statuses[]', status.name);
}
});
Url params looks like when a status is checked :
&statuses%5B%5D=pending
My problem is when I want to unchecked.
I know HTTP Params is Immutable, so, I'm trying to delete the param when checkbox is unchecked, so set to false :
...else {
this.urlParams = this.urlParams.delete('statuses');
}
But, it not works, URL doesn't change.
And if I re-check to true after that, the URL looks like :
&statuses%5B%5D=pending&statuses%5B%5D=pending
How can I delete params, if the status value is false, and keep others statuses in URL ?
Project on Angular 10.
Thanks for the help.
UPDATE : It works to delete, my param name was not good :
else {
this.urlParams = this.urlParams.delete('statuses[]', status.name);
}
But, my other problem, it's when I check 2 or more checkbox, the append function write on URL : &statuses%5B%5D=pending&statuses%5B%5D=pending&statuses%5B%5D=done
I have prepared an example to try to answer your question (If I understand this right way).
You can change the checkboxes state or the URL to play with it. Also, I added helper buttons, which will navigate you to different cases (by changing the URL).
Here is the example: https://stackblitz.com/edit/angular-router-basic-example-cffkwu?file=app/views/home/home.component.ts
There are some parts. We will talk about HomeComponent.
You have ngFor which displays statuses, I handled state using ngModel (you can choose whatever you want).
You have a subscription to the activatedRoute.queryParams observable, this is how you get params and set up checkboxes (the model of the checkboxes)
You have the ngModelChange handler, this is how you change the route according to the checkboxes state
Let's focus on 2 & 3 items.
The second one. Rendering the correct state according to the route query params. Here is the code:
ngOnInit() {
this.sub = this.activatedRoute.queryParams.subscribe((params) => {
const statusesFromParams = params?.statuses || [];
this.statuses = this.statuses.map((status) => {
if (statusesFromParams.includes(status.name)) {
return {
name: status.name,
active: true,
};
}
return {
name: status.name,
active: false,
};
});
});
}
Here I parse the statuses queryParam and I set up the statuses model. I decide which is active and which is not here.
The third one. You need to update the URL according to the checkboxes state. Here is the code:
// HTML
<ng-container *ngFor="let status of statuses">
{{ status.name}} <input type="checkbox" [(ngModel)]="status.active" (ngModelChange)="onInputChange()" /> <br />
</ng-container>
// TypeScript
onInputChange() {
this.router.navigate(['./'], {
relativeTo: this.activatedRoute,
queryParams: {
statuses: this.statuses
.filter((status) => status.active)
.map((status) => status.name),
},
});
}
Here you have the ngModelChange handler. When any checkbox is checked/unchecked this handler is invoked. In the handler, I use the navigate method of the Router to change the URL. I collect actual checkboxes state and build the query parameters for the navigation event.
So, now you have a binding of the checkboxes state to the URL and vice versa. Hope this helps.
I'm trying to understand/implement two way attribute binding in a Polymer 3 web component. I've got the following code:
import {html, PolymerElement} from '#polymer/polymer/polymer-element.js';
class CustomInputComponent extends PolymerElement {
static get template() {
return html`
<div>
<template is="dom-repeat" items="{{ratings}}">
<input type="radio"
name="group"
id="item_{{item.id}}"
value="{{item.checked}}"
checked$="{{item.checked}}">
</template>
</div>`;
}
static get properties() {
return {
ratings: {
type: Array,
value: [
{ id: 1, checked: true },
{ id: 2, checked: false }
]
}
};
}
}
window.customElements.define('custom-input-component', CustomInputComponent);
As you can see, I have defined a Array property containing a default list of values. This property is a model from which I want to render a radio group. The initial state looks good. But when I click on the unchecked input, the DOM elements don't update correctly.
I'd bet I'm missing something really simple...
The main things are:
You are binding to the checked attribute ($=), however I don't think radio inputs dynamically update their checked attribute. AFAICT, the checked property is what changes when the input gets selected.
Also, native <input type="radio"> inputs will only fire their change and input events when they are selected, not when they are de-selected. Polymer relies on events to trigger property effects like data bindings; this means that an upwards data binding (from the input to your custom element) will only get processed when the checked property on an input changes from false to true. Effectively, once ratings[n].checked becomes true, it will never be made false because Polymer has no way to know that this has occurred.
Incidentally, to perform two-way binding on a native HTML element, you would also need to include an annotation for the event that the radio input fires when it is selected. So if you did want to capture the changes on selection, it'd be something like checked="{{item.checked::change}}".
A couple of options:
Use paper-radio-buttons inside a paper-radio-group instead of native <input>s. These elements behave well for two-way data binding.
Listen for the change when a new item gets checked, and manually update ratings[n].checked to false for the previously selected item.
A couple more things about your code
(I don't think this is anything to do with your current problem, but it will help avoid future problems) when initializing a default value for an object or array property in a Polymer element, remember to use a function so that each element instance gets its own unique array or object. E.g.:
ratings: {
type: Array,
value: function(){
return [
{ id: 1, checked: true },
{ id: 2, checked: false }
];
}
}
Normally I think, you wouldn't want to change the values of your radio inputs. Conventionally, when the <form> containing a radio input group is submitted, the value on the radio input that is currently checked gets included with the form data, and the other radio input values are ignored. Here's an example on W3Schools. So instead of value="{{item.checked}}", something like value="[[item.data]]".
So the whole thing might be something like
class CustomInputComponent extends PolymerElement {
static get properties () {
return {
ratings: {
type: Array,
value: function(){
return [
{ id: 1, checked: true, value: 'pie' },
{ id: 2, checked: false, value: 'fries' },
{ id: 3, checked: false, value: 'los dos' }
];
}
},
selected: {
// type: Number or Object, idk
// Keep track of the selected <input>
}
};
}
static get template() {
return html`
<p>do you want pie or fries?</p>
<div id="mydiv">
<template is="dom-repeat" items="{{ratings}}">
<input
type="radio"
name="testgroup"
id="[[item.id]]"
value="[[item.value]]"
checked="{{item.checked::input}}"
on-change="radioChanged"
>[[item.value]]
</input>
</template>
</div>
`;
}
radioChanged(event){
// update ratings[n].checked for selected input
// set selected input to currently selected input
}
}
Currently I am facing this issue
I have this array and this select html
$ctrl.languages =
[
{
key: 'Play Prompt',
value: 'blank'
},
{
key: 'Dont Play Prompt - Assume English',
value: 'English'
},
{
key: 'Dont Play Prompt - Assume Spanish',
value: 'Spanish'
}
];
<select class="form-control" name="vdn" ng-model="$ctrl.ivr.Language"
ng-options="item.value as item.key for item in $ctrl.languages" ng-required="true">
</select>
I use that dropdown to show the key in the current dropdown for each option, and I save the "value" property of the option selected in a database but when I retrieve the data the option with the "value" ssaved is not selected.
In this case if I save 'blank' value in the database I want the dropdown to have the 'Play Prompt' key selected.
Thanks
This looks pretty straightforward.
I've provided a working plunker here: https://plnkr.co/edit/sCusyXaSJdO0sjgm3csE?p=preview
Let me know if you were looking for something else.
In my code, I've added a timeout to simulate a web service call.
Here is the controller code from the plunker:
app.controller('mainController', function($scope, $timeout) {
$scope.languages = [
{
key: 'Play Prompt',
value: 'blank'
},
{
key: 'Dont Play Prompt - Assume English',
value: 'English'
},
{
key: 'Dont Play Prompt - Assume Spanish',
value: 'Spanish'
}
];
$scope.ivr = {};
// Simulating Web service Call
$scope.retrievingData = true;
$timeout(function() {
$scope.ivr.Language = 'blank';
$scope.retrievingData = false;
}, 2000);
});
You're setting up item.value as the value that gets set to $ctrl.ivr.Language whenever an option is selected from the dropdown. Since the <select>'s ngModel directive is pointing to $ctrl.ivr.Language, setting a value to $ctrl.ivr.Language within the controller will automatically add selected="selectd" to the item in the dropdown that has a value property that has a matching value.
Therefore, if you want "Play Prompt" to be the value that is selected in the dropdown when the database doesn't contain a value for that setting, then you can check whether the value exists in the API response, and if it doesn't, then set $ctrl.ivr.Language = 'blank' to default to the "Play Prompt" option.
I have the following situation:
A list of indicators, each of them having the properties name, description, essential and differential.
$scope.indicators = [
{ name: 'indicator1' , description: 'blah1', essential: true, differential: false },
{ name: 'indicator2' , description: 'blah2', essential: false, differential: true },
{ name: 'indicator3' , description: 'blah3', essential: true, differential: true },
{ name: 'indicator4' , description: 'blah4', essential: false, differential: false }
]
I'd like to be able to filter with a select the following combinations:
"All", "Essential", "Differential", "Essential and Differential", "Neither Essential nor Differential"
I have tried using ng-model in the select associated with the ng-repeat with | filter, but that ruined the pagination.
I couldn't think of way of using the st-search directive since I'm filtering two properties combined.
Does anyone have a suggestion?
Follows the plunker with the sample code: http://plnkr.co/edit/t9kwNUjyJ15CbLFFbnHb
Thanks!!
The search are cumulative, so if you call the controller api search with multiple you will be able to add the predicates.
Just make sure to reset the sortPredicate object whenever the value changes.
this plunker shows how to write a plugin with your requirements (although I don't understand what all would be in this context
.directive('customSearch',function(){
return {
restrict:'E',
require:'^stTable',
templateUrl:'template.html',
scope:true,
link:function(scope, element, attr, ctrl){
var tableState=ctrl.tableState();
scope.$watch('filterValue',function(value){
if(value){
//reset
tableState.search.predicateObject ={};
if(value==='essential'){
ctrl.search('true', 'essential');
} else if(value === 'differential'){
ctrl.search('true', 'differential')
} else if(value === 'both'){
ctrl.search('true','essential');
ctrl.search('true', 'differential');
} else if(value === 'neither'){
ctrl.search('false','essential');
ctrl.search('false', 'differential');
}
}
})
}
};});
This is how I would do it.
In your controller define an Array with the possible options and the filter for each option, like this:
$scope.options = [
{value:'All', filter:{}},
{value:'Essential', filter:{essential:true}},
{value:'Differential', filter:{differential:true}},
{value:'Essential and Differential', filter:{differential:true, essential:true}},
{value:'Neither Essential nor Differential', filter:{differential:false, essential:false}}
];
Then in your html declare your select using this array, like this:
<select ng-model='diffEssFilter' ng-options='option.value for option in options'>
</select>
And then in your ng-repeat use the filter that would be stored in diffEssFilter, like this:
<tr ng-repeat="indicator in indicators | filter:diffEssFilter.filter">
That's it.
Working example
Good day, i have a grid with boolean column:
var grid = Ext.create('Ext.grid.Panel', {
...
columns: [{
dataIndex: 'visibleForUser',
text: 'Visible',
editor: {
xtype: 'checkboxfield',
inputValue: 1 // <-- this option has no effect
}
},
...
Grid's store is remote via JSON proxy. When i save or update row, the resulting JSON
look like:
{... visibleForUser: false, ... }
As you see, ExtJS serializes checkbox value as true or false JSON terms.
I need to customize this and serialize to, say, 1 and 0, any suggestion how to accomplish this ? Thank you.
I've just changed my checkboxes system-wide to always act/respond to 0 and 1:
Ext.onReady(function(){
// Set the values of checkboxes to 1 (true) or 0 (false),
// so they work with json and SQL's BOOL field type
Ext.override(Ext.form.field.Checkbox, {
inputValue: '1',
uncheckedValue: '0'
});
});
But you can just add this configs per checkbox.
Ext JS 4.1.1 has a new serialize config on record fields. When a writer is preparing the record data, the serialize method is called to produce the output value instead of just taking the actual field value. So you could do something like this:
fields: [{
name: "visibleForUser",
type: "boolean",
serialize: function(v){
return v ? 1 : 0;
}
/* other fields */
}]
I try to avoid overriding default component behavior whenever possible. As I mentioned, this only works in 4.1.1 (it was introduced in 4.1.0 but I believe it was broken). So if you're using an earlier version, one of the other answers would suit you better.
You can try to override getValue
Ext.define('Ext.form.field.Checkbox', {
override : 'Ext.form.field.Checkbox',
getValue: function () {
return this.checked ? 1 : 0;
}
});