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.
Related
I have added a treeview in my vue component and on the selecting node it should update an array and then show the checkbox as selected. Currently, I am stuck in a situation where I am selecting elements from the tree (which is a folder containing an item) and then going to another folder to select different items. When I am opening the folder I previously selected, it will remove all the selected elements.
Here is my code:
<v-treeview
:items="patchItemsTreeList"
v-model="selectedPatchItemUUIDs"
#input="onChangeOfPatchSelection"
:name="'patchItemsTree'"
:open="openParentNode"
:item-text="'text'"
:key="fold"
:open-all="openParentNode.length === 0"
:selection-type="selectionType"
selectable
open-on-click
dense
#input=onChangeOfPatchSelection
This method is called twice interrupting my logic and deleting my selected items from the array.
Try switching from #input to either #update:active or #update:open. In your case I believe that you need #update:active
<v-treeview
:items="patchItemsTreeList"
v-model="selectedPatchItemUUIDs"
#update:active="onChangeOfPatchSelection"
:name="'patchItemsTree'"
:open="openParentNode"
:item-text="'text'"
:key="fold"
:open-all="openParentNode.length === 0"
:selection-type="selectionType"
selectable
open-on-click
dense
/>
What this will do is instead of emitting the array of selected items, it will emit the open items instead.
I have an Angular 7 application in which I'm trying to handle a text input in ngAfterViewChecked().
The text input is a node in a mat-tree. It's visibility depends on an ngIf condition. If that condition is not met, I display a span instead. Essentially, if the user double clicks on a node in the tree (a span element), it becomes a text input so that the user can edit the text:
<mat-tree [dataSource]="nestedDataSource" [treeControl]="nestedTreeControl">
<mat-tree-node *matTreeNodeDef="let node">
<li>
<span *ngIf="!node.isInput" (dblClick)="nodeDoubleClicked(node)">{{ node.name }}</span>
<input *ngIf="node.isInput" #nodeNameInput type="text" [(ngModel)]="node.name" (blur)="doneEditting(node)" (keypress)="keyPressed($event, node)" />
</li>
</mat-tree-node>
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
<button mat-icon-button matTreeNodeToggle>
<mat-icon>
{{ nestedTreeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
</mat-icon>
</button>
<span *ngIf="!node.isInput" (dblClick)="nodeDoubleClicked(node)">{{ node.name }}</span>
<input *ngIf="node.isInput" #nodeNameInput type="text" [(ngModel)]="node.name" (blur)="doneEditting(node)" (keypress)="keyPressed($event, node)" />
<ul [class.collapsed]="!nestedTreeControl.isExpanded(node)">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
</mat-nested-tree-node>
</mat-tree>
When the user double clicks on a node, I not only want it to turn into an input text, I want it to gain focus and select the text inside. In order to do this, I have to get the native element and call .focus() and .select() on it. In order to get the native element, I have to use ViewChildren (where the input is tagged with #nodeNameInput as you can see in the code snippet above). And finally, I need to hook into ngAfterViewChecked() in order to be sure that the QueryList of the ViewChildren is ready.
Here is the code for the component:
#ViewChildren('nodeNameInput') nodeNameInputs: QueryList<ElementRef>;
...
ngAfterViewChecked() {
if (this.nodeNameInputs && this.nodeNameInputs.length) {
this.nodeNameInputs.first.nativeElement.focus();
this.nodeNameInputs.first.nativeElement.select();
}
}
I've ensured that there is only ever one node being edited at a time, so it's safe to use first rather than search through nodeNameInputs to find the one to put in focus and select the text.
This seems to work, but there is a problem. It seems like for every key stroke, ngAfterViewChecked() is also called. What this means is that as the user is editing the text for the node, it gets re-selected for every key stroke. This results in the text the user enters being overwritten on every key stroke.
I have a workaround to this problem:
ngAfterViewChecked() {
if (this.nodeNameInputs && this.nodeNameInputs.length) {
this.nodeNameInputs.first.nativeElement.focus();
if (!this.keyStroked) {
this.nodeNameInputs.first.nativeElement.select();
}
}
}
...where keyStroked is set in the keyPressed handler and set to false in the blur handler.
But I'm wondering if there is another hook that can reliably be used to focus the input and select its text while not responding to key strokes. I chose ngAfterViewChecked because a test showed that it was the only hook in which nodeNameInputs was consistently ready every time (i.e. this.nodeNameInputs.length was always 1). But maybe I missed certain hooks.
My workaround seems like a hack. How would you solve this problem?
Create a focus directive and place that on the input you want focused, you wont have to worry about life cycle events.
import { Directive, ElementRef } from '#angular/core';
#Directive({
selector: '[focus]'
})
export class FocusDirective {
constructor(elm: ElementRef) {
elm.nativeElement.focus();
}
}
and use it
<input focus>
https://stackblitz.com/edit/angular-qnjw1s?file=src%2Fapp%2Fapp.component.html
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>
I'm trying to make a React version of this.
You can see what I have so far on this Codepen.
The issue here is that when you type into a text field (under the "Suspected Player" column) and then click the adjacent name (under the "Role" column) to remove that row, the text persists inside the input, and is then adjacent to a different name.
The expected behavior is that you'd click the name to remove the row and both the elements (the name and text input) would disappear.
All the items in the table are stored in an array called checklist in the state of the RoleList class.
The component for each list item in the table is this:
class RoleCheckpoint extends React.Component {
constructor() {
super();
this.state = {
text: ""
};
}
deleteThis() {
this.props.removeRole(this.props.role.id);
}
onChange(e) {
this.setState({
text: e.target.value
});
}
render() {
console.log(this.props);
return (
<tr>
<td className="vertAlign remove noselect" onClick={this.deleteThis.bind(this)}>
{this.props.role.el}
</td>
<td>
<input type="text" className="form-control" spellCheck="false" onChange={this.onChange.bind(this)} value={this.state.text} />
</td>
</tr>
);
}
}
When you click on the name it invokes the components deleteThis function, which in turn invokes RoleList's removeRole function (that goes through the array of list items, and removes the one with the matching ID from the components state), which is being passed as a prop to RoleCheckpoint.
I've tried using this.forceUpdate() in the deleteThis function, and as you can see I've tried binding the input field's value to the components state (called text), yet still the input doesn't update when removing the component from the DOM.
Any help is appreciated, if you need more clarification I'll be happy to provide it.
A "key" is a special string attribute you need to include when creating lists of elements.
<RoleCheckpoint role={role} key={role.id} removeRole={this.removeRole.bind(this)} />
The behaviour is typical when you have a list of components and you have not set the key of the objects correctly. It need to be something that identifies the object.
Probably you can use the name field:
<RoleCheckpoint key={role.name} .../>
See https://facebook.github.io/react/docs/lists-and-keys.html
very new to react. you can say I have not yet started to think like React.
here is the problem:
<div>
<DropDown> </DropDown>
<Panel> </Panel>
</div>
In the dropdown, I select a value. Store it in state, as something as , currentLocation.
Then I go to Panel, hit a button, and I want to open a modal. When i open a modal, I need to pass the currentLocation to that model.
I can pass in arbitrary value to modal, but I cannot figure out a way to get the currently selected item from DropDown.
How do I get the value of the currently selected item to the Panel?
Am I even making sense?
When you call the setState in the dropdown that will force an update of the page.
Then if you call this.state in your component you should have the value you need there.
You should go over the basic tutorials to grasp the react basics.
But it goes like this:
getInitialState: function() {
return {
myVar: ''
//set your variables here
//getInitialState will get called before the component gets mounted
};
},
myCustomFunction: function(newVariable) {
this.setState({
myVar: newVariable
});
},
render: function() {
return (
<div>
<input
onChange={this.myCustomFunction}
/>
<MyCustomComponent myProp={this.state.myVar}/>
//everytime the state changes MyCustomComponent will have that new value as a prop
</div>
);
}
There is a lot of ambiguity in your question but I'll try for the simplest case.
You have a Panel component and a Dropdown component.
You want to the Panel to have access to a value that was set when the Dropdown was used.
Solution: When the Dropdown is actuated, it creates an Action that Stores the selected value.
When the modal button in the Panel is actuated, it creates an Action that requires the DropDownStore. Then it decides what to do based on that value.
The pattern I am loosely describing is known Facebook's Flux architecture which is basically just a more specific application architecture for React applications similar to pub/sub or an event bus.