I've got a VueJS application which filters items based on a number of checkbox items like a category filter for a shop.
When a user clicks a checkbox, we fire off an API request and a list of updated items is returned. The URL is also updated with a query string representing the checkbox that they have selected.
If a user navigates to a query stringed URL we want to have the checkboxes relating to the filters in the query string checked. That way if there is a page refresh, all the same checkboxes are checked.
We've done this so far using an if(window.location.search) and then parsing that query string, adding the parsed query string into an object. Passing that object down into the child component as a prop then setting the model the checkboxes are bound to to the query string object on update.
This works and is fine. The issue is theres stuttering and flashing of the checkboxes. You click the checkbox, it initially unchecks after selecting, the when the API response comes back, it select. Not very good for UX. I'm assuming this is because we're modifying the model the checkboxes are bound to while also trying to update it on checkbox click.
So I'm wondering if there's a better way of doing this please and if someone else has tackled a similar issue.
I've attached some code below, but as its spread across multiple components its quite hard to display here.
Thanks
<template>
<ul>
<li v-for="(filter, index) in filters" v-bind:key="index">
<input type="checkbox" name="filters" v-model="checked" v-on:change="changeItems">{{filter.filterName}}
</li>
{{checked}}
</ul>
</template>
<script>
export default {
data() {
return {
checked: []
}
},
props: [
'filters',
'checkedFilters' //passed object of filters in query string
],
updated: function() {
this.checked = this.checkedFilters
},
methods: {
changeItems: function (){
this.$emit('change-items', this.checked)
}
}
}
</script>
Related
I have an array of questions, with each question having a question field and an options field which is essentially an array of string options. The app is being build in Angular.
{
question: "Is this the question?",
options: ["yes", "no", "maybe", "not sure"]
}
The questions are received from a service dynamically, and may change over time. The user must have the option to select more than one options, hence I am using a checkbox. Specifically, PrimeNG's checkbox.
<p-checkbox #checkbox class="checkbox-option" [label]="opt.val" [value]="opt.val" value="opt.val">
</p-checkbox>
The question being, which approach should be used in order to receive the checked options of one question at a time. The checkbox would have a submit button below, after which I'll need to check the checked options.
PS. I am new, if anyone needs any other relevant imformation or piece of code, just notify me. Thanks in advance.
Firstly, use ngModel in your elements, then you can just pull the selected values easily.
Here's an example with a loop
<p-checkbox *ngFor="let opt of boxes" class="checkbox-option"
[label]="opt.val"
[value]="opt.val"
[(ngModel)]="selectedBoxes" >
</p-checkbox>
<button (click)="getSelected()">Get answers</button>
in component.ts
export class SomeComponent {
selectedBoxes: string[] = [];
getSelected() {
console.log(this.selectedBoxes)
}
Playing aroud with vue with a todo list.
At this stage, I am able to show the list within and with an edit button, the text will be an input so I can edit the todo items. But for example if I have 10 items, and I clicked edit for item 2 and item 4 and item 5, then all these 3 items will change into input.
What I am thinking is if I click on item 4, item 2 will change back to text, so only one item will show input if edit clicked.
I have my template code as below
<td class="col-8">
<!-- if editable is false, show the checkbox and todo item -->
<div class="custom-control custom-checkbox" v-if="!editable">
{{ todo.item }}
</div>
<!-- if editable is true, turn item into an input so user can enter new value -->
<div v-else>
<input ref="editItem" type="text" class="form-control" :value="todo.item" #input="onInput">
</div>
</td>
<td class="col-2">
<button
class="btn btn-sm"
:class="editable ? 'btn-outline-success' : 'btn-outline-info'"
#click.prevent="editable ? onSave() : onEdit()">{{ editable ? 'save' : 'edit' }}
</button>
</td>
As I open up the vue inspect in chrome, I can see all items have their different data value.
How can I change others siblings' value?
Thanks in advance for any sugguestins and advices.
I wouldn't recommend you to change the value of another sibling, since you cannot be sure of the side-effects it can cause, obviously when you look at a todo list, it would feel like there are no side-effects but in general practice, a node should change elements that are below it in the parentage, neither above nor the siblings.
If you want to change elements above, its always drive via events. You let the parent know that something needs to be changed rather than changing it yourself.
This is what you can do here:
Rather using editable as a data property, use it as a prop to the TodoListItem component.
props: {
editable: {
type: Boolean,
default: false
},
}
When the Save/Edit button is clicked in the child component, rather than changing the value there itself, use events like so.
/** You should keep a deepCopy of the props if you are changing the state
of that object and then send the updated local data prop to the parent
as well for updation rather than changing the prop object.
NEVER CHANGE THE PROP OBJECT DIRECTLY. -> this.todoItem =
this.deepCopy(this.todo);
Do this in before mount hook of the child component.
**/
this.$emit('todo-item-action', action, todoItem);
Once this event is emitted, in the parent TodoList component, catch this event and change the state of the items like so:
<todo-list-item
v-for="todo in todos"
:key="todo['id']"
:todo="todo"
:editable="todo['editable']"
#todo-item-action="handleItemAction"
>
or if you don't want to mutate the todo object. here you can make sure editableItem is null if none is selected or only the active item ID is referred so that the prop will change and automatically other list item will become un-editable.
<todo-list-item
v-for="todo in todos"
:key="todo['id']"
:todo="todo"
:editable="editableItem === todo['id']"
#todo-item-action="handleItemAction"
>
The more you drive a component via events and props, the more re-usable it becomes. Always have an orchestrator component which manipulates these stateless components by handling events propogated by them and sending state via props.
This is the approach I have always followed and it always allow me to refactor cleanly and quickly.
Regards.
I´m working with a list of element and filtering the list using pipes, the filter is multi-selection to filter using more than one value, i save the filter in the localstorage to have persistent in the filter after close the window or reload the page, in the mat-checkbox component i use i function in the [checked]="existInArray(color.id, filterColor)" directive to check the checkbox if the value is already in the filter array to check the checkbox, but i have a problem, if the checkbox is checked using the function, the next time i click the checkbox to uncheck it, the checkbox doesnt change the state of checked true to false, only when i clicked a second time change to checked false
Template
<mat-checkbox *ngFor="let color of filterService.getFilter(filterType.FILTER_COLOR).items.ToArray() | filterQuery:filterOptions.color"
[checked]="existInArray(color.id, filterColor)" class="filter-checkbox" [value]="color.id" [hidden]="color.id === '999999'"
(click)="filterBy(filterType.FILTER_COLOR, color.id, filterColor)">
<div class="assigned">
<div class="assigned-avatar text-center" [ngStyle]="{ 'background-color': color?.color?.bgColor }"></div>
<p class="assigned-name">{{ color.name }}</p>
</div>
</mat-checkbox>
Filter.ts
public existInArray(element, array: Array<string>): boolean {
return array.indexOf(element) > -1;
}
public filterBy(filterType: FilterTypeEnum, element: any, array: Array<string>) {
this.toggleInArray(element, array);
this.updateFilterObservable(filterType);
}
Example, as you can see in the image, the checkbox is already checked by default because i use the existInArray function to check if the value of the check already exist in the filter array, if i try uncheck it, the first time doesn´t work, but when i click a second time the checkbox is unchecked, any idea what could be....?
I think the reason is because the [checked] is trigger before i toggle the element from the array, but no idea so far idea how to solved.... any idea.
This is a timing issue of your [checked]="existInArray(color.id)" being out of sync with your click event.... [checked]="existInArray(color.id)" happens before your click. Subscribe to the change event instead.
(change)="toggleInArray(color.id)"
Top_Select: any[] = []; // selection clear when selected... in your ts file
//on the check_click event firing add this make sure you get the index (index_chkbx) in the event
this.Top_Select[index_chkbx] = true;
in you html
This is my first foray into using VueJS so any pointers or better ways to tackle the problem are much appreciated. Here is where I am at http://codepen.io/anon/pen/ezBwJw
I'm building a pricing plan table where users can browse 4 different payment plans. The table is interactive, users have access to radio buttons which toggle between viewing prices in GBP & USD, as well as viewing the cost if they pay per year or per month. All of this is working, but the issue I now have is that I want to pass some data to a 'summary' section which will be presented to the user before they choose to sign up. The one piece of data I am struggling to pass to the summary table is the price.
When a user selects a plan I want the price that is currently showing in that plan to show in the 'Total to pay now' field. In jQuery I'd do something like this (simplified version of Codepen)...
<div>
<h1>Basic</h1>
<div class="price">7</div>
Select this plan
</div>
<h2 class="total"></h2>
$('.select-plan').on('click', function() {
var planName = $(this).parent().find('.price').text();
$('.total').text(planName);
});
I'm currently using v-if to show the different prices for the respective plans, so I'm lost as to how I would get the item that is currently in view and pass that to the summary field.
JQuery Way
One option is to create watchers that call an updatePrice method whenever a variable that effects the current price changes. For example:
watch: {
'activePlan': 'updatePrice',
'currency': 'updatePrice',
'frequency': 'updatePrice'
},
... and then in methods:
updatePrice: function() {
this.price = $('.price.' + this.activePlan).text();
}
Here is a fork of your CodePen with that change. Notice that I've added the plan name as a class so that the JQuery selector can find the correct element.
Component Way (do this!)
Personally, I think it you'd be better off taking a totally different approach. I would make a custom component for plans. This will let you encapsulates all the functionality you require in a reusable and manageable way.
For example, you could make a component like this
Vue.component('plan-component', {
template: '#plan-component',
props: ['frequency', 'name', 'priceYearly', 'priceMonthly'],
computed: {
'price': function() {
// Move the logic for determining the price into the component
if (this.frequency === 'year') {
return this.priceYearly;
} else {
return this.priceMonthly;
}
}
},
methods: {
makeActivePlan() {
// We dispatch an event setting this to become the active plan
// A parent component or the Vue instance would need to listen
// to this event and act accordingly when it occurs
this.$dispatch('set-active-plan', this);
}
}
});
Components are related to an HTML template. So in your HTML you would need a template tag with id plan-component.
<template id="plan-component">
<h1>{{ name }}</h1>
<div>
<span>{{ price }}</span>
</div>
<a class="select-plan" v-on:click="makeActivePlan($event)" href="#">Select this plan</a>
</template>
Thus each plan gets its own component which handles the data related to that plan. And instead of repeating the same HTML for each plan in a table, you can just use your new custom <plan-component>, and bind the appropriate values to each plan (these are the props).
I've implemented this more fully as a JSFiddle here. I got rid of USB vs GBP currency because I wanted to keep things simple. I hope this gives you some idea about how to tackle your problem!
My HTML:
<div class="check" ng-repeat="point in dbs">
<input
name="db"
id="{{point.id}}"
ng-model="point.select"
ng-click="update($index, dbs)"
ng-checked="false"
type="checkbox"
ng-required="point.select" />
</div>
Whilst my update() function looks like:
$scope.update = function(position, dbs) {
angular.forEach(dbs, function(point, index) {
if (position != index)
point.select = false;
});
}
This works as with regards to tracking what the selected checkbox is, and sending into another controller that expects the value, all is working good.
However, when I go back from the resulting page, back to this search form again, somehow the checkbox I selected before, is preselected, and I don't want anything to appear, rather just have everything blank.
Would it be as easy as simply stating:
$scope.point.select = null;
as I can't seem to find a good solution for this, so that the checkbox is always blank / not pre selected when you arrive on this form.
Let me see if I get what you are doing. It looks like you are trying to make your list of checkboxes mutually exclusive. I might look at using a radio button list (a set of radio buttons with the same name attribute, HTML interprets this as a mutually exclusive list). If you create a variable which will hold the value of the selected option and pass that value around, you probably can achieve the same result with less code. Take a look here: https://jsbin.com/sunusihuse/edit?html,js,output
As for clearing the list of controls when you revisit the page, what I have described will do that too because the value of the variable which will hold the selected value is initialized to an empty string. Hope this helps.