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.
Related
I'm trying to edit a form. So I'm using Vuex to store my form information, and the form itself is inside a vuetify dialog inside a child component. I also use props to cue vuetify dialog to open while also sending an index too.
I set the edit vue data (tableUsulanTemp) with the value of vuex getters (tableUsulan) like so:
props: {
value: false,
index: null
},
model: {
prop: "value",
event: "editClicked"
},
// set vue data with vuex getters
watch:{
value:function(value){
if(value){
this.tableUsulanTemp = this.tableUsulan[this.$props.index];
}else{
this.tableUsulanTemp = {};
}
}
},
computed: {
//here is the vuex getters
...mapGetters(["tableUsulan"]),
editOverlay: {
get: function() {
console.log(this.value);
return this.value;
},
set: function(value) {
console.log("edit button clicked");
this.$emit("editClicked", value);
}
}
},
data() {
return {
// here is vue data where i put the getters to
tableUsulanTemp:{},
};
},
the markup:
<v-text-field
label="Name"
v-model="tableUsulanTemp.name"
>
then when user discard the changes by clicking the discard button, it will set the dialog to false which trigger watcher (look at above code) to reset my edit form data to empty object.
discard(){
this.editOverlay = false;
}
but the problem is the data is not discarded instead it changed. I set user to change the information in the vue data but it seems it also changing in the vuex. so i guess it linked now? so how can i get it unlinked? how can i get vuex state into a vue data without changing the vuex when the vue data get changed?
So the solution is to parse the object to json and back
this.tableUsulanTemp = JSON.parse(JSON.stringify(this.tableUsulan));
here is the detail
I can't seem to figure out why a very simple integration test is not passing
Given an ember data model setting.js
export default class SettingModel extends Model {
#attr('string') name;
#attr('number') value;
}
I am rendering this model with a component
<MyComponent #setting={{setting}}
#name={{setting.name}}
#value={{setting.value}}
#values={{array 0 1 2}}
#saveSetting={{saveSetting}} />
The component renders all the possible values (#values) and applies an active class on the one which equals the current value
{{#each #values as |value|}}
<div class="btn {{if (eq #value value) "active"}}" {{on "click" (fn #saveSetting #setting value)}}>
{{value}}
</div>
{{/each}}
I wanted to write a simple test where clicking another button updates the active class, but the last assertion always fails. The active class is never updated. In this example, I have used ember-cli mirage
test('updates new saved value', async function(assert) {
this.server.get('/settings/:id', () => ({ settings: { id: 1, name: "settingName", value: 1 }}), 200);
let setting = await this.owner.lookup('service:store').findRecord('setting', 1);
this.set('setting', setting);
this.set('name', setting.get('name'));
this.set('value', setting.get('value'));
this.set('values', [0, 1, 2]);
this.set('saveSettingFn', (setting, newValue) => {
setting.set('value', newValue);
setting.save().then(() => console.log('also saved!'));
console.log('saved!');
});
await render(hbs`<MyComponent #setting={{this.setting}}
#name={{this.name}}
#value={{this.value}}
#values={{this.values}}
#saveSetting={{this.saveSettingFn}} />`);
// middle option active
assert.ok(this.element.querySelectorAll('.btn')[1].getAttribute('class').includes('active'), 'second button active');
// set up save handler
this.server.put('/settings/:id', () => ({ settings: { id: 1, name: "settingName", value: 1 }}), 200);
// click the first button
await click('.btn');
// first option active
assert.ok(this.element.querySelectorAll('.btn')[0].getAttribute('class').includes('active'), 'first button active');
I've created an example project here with a failing test
https://github.com/spicalous/component-test-example/blob/master/tests/integration/components/my-component-test.js#L55
It looks like the value on the model has been saved
I have tried using waitFor test helper
I have tried await settled();
Any help is appreciated! Thank you!
your saveSettingFn function does nothing. So nothing happens.
I have a page where users can submit forms. They can choose to add new forms to the page(form 2, 3, 4, 5 of the same form type).
I handle that through:
handleAddPastMainData() {
const pastMainData = this.state.mainData[0].pastMainData;
this.setState({
mainData: {
...this.state.mainData,
pastMainData: pastMainData.push([])
}
})
}
Then I map through pastMainData to display the added form.
What I am having a challenge with is with the handler for saving the data since the form is nested.
My current state is like this:
this.state = {
mainData: [
{pastMainData: []},
]
};
And my handler is:
handleMainInputChange = (event) => {
const name = event.target.name;
const value = target.type === 'checkbox' ? target.checked : target.value;
this.setState(state => {
state.mainData[0].pastMainData[name] = value;
return state
})
};
A sample form input field in my app is:
<Field
label="Main name"
id="pastMainName"
name="pastMainName"
placeholder="Super Awesome FName"
value={state.MainData[0].pastMainData.pastMainName}
onChange={handleMainInputChange}
/>
I assign a key to each form when I map through pastMainData.
So a user can choose to add more than 5 of the above field input and submit.
When the data is submitted, I want the state to look like this:
this.state = {
mainData: [
{pastMainData: [[field1], [field2], [field3]},
]
};
When the user returns, this gives me the option to check number of fields/forms the user has already completed and render them with the saved data.
However, this obviously is not working.
Any way to improve my code and make it more efficient will be very appreciated.
Thanks
Currently when I run the code, on clicking Add Form, it renders the
form but continues to the next page. The call to continue to the next
page is only on the handleSubmit method.
To stop it from continuing to the next page you probably need to e.preventDefault() on that button.
"since the forms are multiple, how do I get the key and use that
particular key in the handleMainInputChange."
Add the key to the Field, or Form. For example:
<Field
key={key}
label="Main name"
id="pastMainName"
name="pastMainName"
placeholder="Super Awesome FName"
value={state.MainData[0].pastMainData.pastMainName}
onChange={handleMainInputChange}
/>
And you should be able to access it like you would any data-attr, if not, you can pass it as a a param to the onChange function.
also, maybe it would be easier to work with a different data structure for your state obj:
this.state = {
forms: {
form1: {},
form2: {},
form3: {},
}
};
may make setting and accessing easier in the long run.
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
}
}
I have an ExtJS form which contains several items that have the same name. I expect that when the form is loaded with the values from server-side all of those equally named components will get assigned the same relevant value.
Apparently, what happens is that only the first element from the group of equally named gets the value, others are skipped.
Is there an easy way to alter this observed behavior?
UPDATE
Below is the code of the form:
var productionRunAdvancedParametersForm = new Ext.form.FormPanel({
region : 'center',
name : 'productionRunAdvancedParametersCommand',
border : false,
autoScroll : true,
buttonAlign : 'left',
defaults : {
msgTarget : 'side'
},
layoutConfig : {
trackLabels : true
},
labelWidth : 200,
items : [
{
xtype : 'fieldset',
title : 'ASE',
collapsible : true,
autoHeight : true,
items : [ {
xtype : 'hidden',
name : 'genScens'
}, {
xtype : 'checkbox',
name : 'genScens',
fieldLabel : 'GEN_SCENS',
disabled : true
}]
}]
,
listeners : {
beforerender : function(formPanel) {
formPanel.getForm().load({
url : BASE_URL + 'get-data-from-server.json',
method : 'GET',
success : function(form, action) {
var responseData = Ext.util.JSON.decode(action.response.responseText);
if (!responseData.success) {
Screen.errorMessage('Error', responseData.errorMessage);
}
},
failure : function(form, action) {
Ext.Msg.alert("Error", Ext.util.JSON.decode(action.response.responseText).errorMessage);
}
});
}
}
});
The server response is:
{"data":{"genScens":true},"success":true}
What happens is only the hidden component gets value 'true', the disabled checkbox doesn't get checked. If I swap them in the items arrays, then the checkbox is checked but the hidden doesn't get any value.
The behaviour you see is exactly what I'd expect.
Inside a form, using the same field name multiple times -unless you use it for radiobuttons, which is not the case- is an error. Just think about what the form submit function should do in this case: should it send the same key (input name) twice, possibly with different values?
(Obviously, in the case of radiobuttons the answer is simple: sent the input name as key, and the checked radiobutton's value as value).
What Ext does here is, scan the form seaching for the input field matching the name, and then assign the value to the first matching input (since it assumes no duplicate names).
You can work it around simply by:
using two different names in the form (eg. genScens and genScens_chk )
sending the same value under two different keys in the server-side response, e.g.
{"data":{"genScens":true,"genScens_chk":true},"success":true}
Please note: if you cannot alter the server response, still use two different names, just add a callback to the success function, setting the genScens_chk value accordingly, like that:
success : function(form, action) {
var responseData = Ext.util.JSON.decode(action.response.responseText);
if (!responseData.success) {
Screen.errorMessage('Error', responseData.errorMessage);
}
else{
formPanel.getForm().findField("genScens_chk").
setValue(responseData.data.genScens);
}
},