I'm trying to detect if a Model Data has not been changed within a Vue Computed data.
I have two sets of Variables that need to be checked,
before Computed:filteredItems should return a new list or current list.
Below are two data i'm checking
text ( the text input )
selectedInput ( currently selected item )
Current Behavior:
I've changed, selectedInput to null, this updates Computed:filteredList to be triggered. which is expected.
The first Condition is to make sure that this update returns current list if text === selectedInput.text, work as expected
However, I need a second condition to detect if text has not been changed.
<input v-model="text" />
<ul>
<li v-for="item in filteredItems" #click="text=item.text"></li>
</ul>
{
data():{
text: 1,
items: [],
tempList: [],
selectedItem: {text: 1}
},
computed: {
filteredItems(){
// when selectedItem.text === current text input, do not run
if (this.selectedItem.text === text) return this.tempList;
// how do i detect if selectedItem.text has not been changed
if (this.selectedItem.text.hasNotChange??) return this.tempList;
}
}
}
Data Flow: 1update the text > 2filter list > 3click on listItem, update (1) text
[input(text): update on type ] >
[li(filteredItem): filter list on type by value (text) and (selectedInput.text) ] >
[li(item)#click: update (1), and also another value(selectedInput.text) input(text) to equal (item.text) ]
This cycle works until I have action somewhere else that updates selectedInput.text
is there something i can do with a setter/getter for the Text model.
Create a variable, changed. Watch selectedItem.text, and set changed to true. In a watcher on text, set changed to false.
I got this to work using a temp variable
data(){
return: {
text: "",
temp: {
text
}
}
}
computed(){
filteredList(){
var temporaryList,originalList,filteredList
if ((this.text === $store.state.selectedText )||
(this.text === this.temp.text ) ) {
return temporaryList || originalList
}
// update
this.temp.text = this.text
return filteredList
}
}
thought it would be a bad practice to update variables within a Computed method.
Related
I am running a loop with each item that has a **button **that has a **class **that is **binded **to a method. i want to display a certain text for this button, depending on the value returned by the aforementioned method
HTML Template
<button v-for="(item, index) in items"
:key="index"
:class="isComplete(item.status)"
> {{ text_to_render_based_on_isComplete_result }}
</button>
Method
methods: {
isComplete(status) {
let className
// there is another set of code logic here to determine the value of className. below code is just simplified for this question
className = logic ? "btn-complete" : "btn-progress"
return className
}
}
what im hoping to achieve is that if the value of the binded class is equal to "btn-completed", the button text that will be displayed is "DONE". "ON-GOING" if the value is "btn-in-progress"
my first attempt was that i tried to access the button for every iteration by using event.target. this only returned undefined
another option is to make another method that will select all of the generated buttons, get the class and change the textContent based on the class.
newMethod() {
const completed = document.getElementsByClassName('btn-done')
const progress= document.getElementsByClassName('btn-progress')
Array.from(completed).forEach( item => {
item.textContent = "DONE"
})
Array.from(progress).forEach( item => {
item.textContent = "PROGRESS"
})
}
but this may open another set of issues such as this new method completing before isComplete()
i have solved this by returning an array from the isComplete method, and accessed the value by using the index.
<template>
<button v-for="(item, index) in items"
:key="index"
:class="isComplete(item.status)[0]"
:v-html="isComplete(item.status)[1]"
>
</button>
</template>
<script>
export default {
methods: {
isComplete(status) {
let className, buttonText
// there is another set of code logic here to determine the value of className. below code is just simplified for this question
if (className == code logic) {
className = "btn-complete"
buttonText = "DONE"
}
else if (className != code logic) {
className = "btn-progress"
buttonText = "ON-GOING"
}
return [ className, buttonText ]
}
}
}
</script>
Having issues getting some checkboxes to work properly. So in my component I have an array of objects set in a state variable tokenPermissions that look like this
tokenPermissions: [
{
groupName: "App",
allSelected: false,
someSelected: false,
summary: "Full access to all project operations",
permissions: [
{
name: "can_create_app",
summary: "Create new projects",
selected: false,
},
{
name: "can_delete_app",
summary: "Delete existing projects",
selected: false,
},
{
name: "can_edit_app",
summary: "Edit an existing project",
selected: false,
},
],
}
],
The goal is to loop through this array and have a parent and children checkboxes like so tokenPermissions[i].allSelected bound to the parent checkbox and for each object in tokenPermissions[i].permissions a corresponding checkbox bound to the selected property like so tokenPermissions[i].permissions[j].selected.
Desired behaviour when the parent checkbox is selected,
If all child checkboxes checked, uncheck all, including the parent
If child checkboxes are unchecked check all including the parent
if only some of the child checkboxes are selected, the parent would show the indeterminate - icon or sign and on click, uncheck all child checkboxes including the parent.
The issue is point 3. The issue is sometimes the parent checkbox is not correctly checked based on the state of the attribute bounded to. For example allSelected can be false but the parent checkbox is checked.
I put a complete working example on github here https://github.com/oaks-view/vuejs-checkbox-issue.
The bit of code with the binding is as follows
<ul
class="list-group"
v-for="(permissionGroup, permissionGroupIndex) in tokenPermissions"
:key="`${permissionGroup.groupName}_${permissionGroupIndex}`"
>
<li class="list-group-item">
<div class="permission-container">
<div>
<input
type="checkbox"
:indeterminate.prop="
!permissionGroup.allSelected && permissionGroup.someSelected
"
v-model="permissionGroup.allSelected"
:id="permissionGroup.groupName"
v-on:change="
permissionGroupCheckboxChanged($event, permissionGroupIndex)
"
/>
<label :for="permissionGroup.groupName" class="cursor-pointer"
>{{ permissionGroup.groupName }} -
<span style="color: red; margin-left: 14px; padding-right: 3px">{{
permissionGroup.allSelected
}}</span></label
>
</div>
<div class="permission-summary">
{{ permissionGroup.summary }}
</div>
</div>
<ul class="list-group">
<li
class="list-group-item list-group-item-no-margin"
v-for="(permission, permissionIndex) in permissionGroup.permissions"
:key="`${permissionGroup.groupName}_${permission.name}_${permissionIndex}`"
>
<div class="permission-container">
<div>
<input
type="checkbox"
:id="permission.name"
v-bind:checked="permission.selected"
v-on:change="
permissionGroupCheckboxChanged(
$event,
permissionGroupIndex,
permissionIndex
)
"
/>
<label :for="permission.name" class="cursor-pointer"
>{{ permission.name
}}<span
style="color: red; margin-left: 3px; padding-right: 3px"
> {{ permission.selected }}</span
></label
>
</div>
<div class="permission-summary">
{{ permission.summary }}
</div>
</div>
</li>
</ul>
</li>
</ul>
And for updating the checkbox
getPermissionGroupSelectionStatus: function (permissionGroup) {
let allSelected = true;
let someSelected = false;
permissionGroup.permissions.forEach((permission) => {
if (permission.selected === false) {
allSelected = false;
}
if (permission.selected === true) {
someSelected = true;
}
});
return { allSelected, someSelected };
},
permissionGroupCheckboxChanged: function (
$event,
permissionGroupIndex,
permissionIndex
) {
const { checked } = $event.target;
// its single permission selected
if (permissionIndex !== undefined) {
this.tokenPermissions[permissionGroupIndex].permissions[
permissionIndex
].selected = checked;
const { allSelected, someSelected } =
this.getPermissionGroupSelectionStatus(
this.tokenPermissions[permissionGroupIndex]
);
this.tokenPermissions[permissionGroupIndex].allSelected = allSelected;
this.tokenPermissions[permissionGroupIndex].someSelected = someSelected;
} else {
// its selectAll check box
const { allSelected, someSelected } =
this.getPermissionGroupSelectionStatus(
this.tokenPermissions[permissionGroupIndex]
);
let checkAll;
// no checkbox / permission is selected then set all
if (!someSelected && !allSelected) {
checkAll = true;
} else {
checkAll = false;
}
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
this.tokenPermissions[permissionGroupIndex].someSelected = checkAll;
for (
let i = 0;
i < this.tokenPermissions[permissionGroupIndex].permissions.length;
i++
) {
this.tokenPermissions[permissionGroupIndex].permissions[i].selected =
checkAll;
}
}
},
It's a rendering problem.
Vue set the allSelected checkbox as checked, then in the same cycle updates it to false; you can read about Vue life cycle here: https://it.vuejs.org/v2/guide/instance.html
A pretty brutal (but simple) way to resolve it (which I don't recommend, but it's useful to understand what's happening) is to delay the update.
Wrap the last part of the method permissionGroupCheckboxChanged with a this.$nextTick:
this.$nextTick(() => {
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
this.tokenPermissions[permissionGroupIndex].someSelected = checkAll;
for (
let i = 0;
i < this.tokenPermissions[permissionGroupIndex].permissions.length;
i++
) {
this.tokenPermissions[permissionGroupIndex].permissions[i].selected =
checkAll;
}
})
This way when you change the values, the engine reacts accordingly.
Still I don't recommend it (I think nextTick is useful to understand the Vue life cycle, but I would recommend against using it whenever is possible).
A less brutal (and simpler) way is to set the allSelected to null instead of false when checkAll is not true permissionGroupCheckboxChanged:
// this
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll ? checkAll : null;
// instead of this
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
this way the prop wins against the model (as the model value becomes null).
But the even better option (imho) would be to use a component of its own inside the v-for loop and have allSelected and someSelected as computed properties instead of values bound to real variables.
Usually you should not store ui status as data when it can be inferred from real data (I may be wrong, as I don't know your application, but in your case I suspect you are interested in the single checkboxes' values, while allSelected/someSelected are merely used for ui).
CodeSandbox
Reference Video
I'm trying to retrieve a value that was dynamically created and passed to a prop.
After clicking "add card" and clicking on one of the created cards, the goal is to pass the value(prop: randomNum) into a variable(num1).
In the Sandbox, I'm able to get the value of the Id which was also created dynamically.
methods: {
//"emits" or Allows value of the id prop in Array to be reached from parent?
select() {
this.$emit("select", this.id);
}
Above Code from nested /component/card.vue
<card
v-for="cards in cardArray"
:key="cards.id"
:id="cards.id"
:randomNum="cards.randomNum"
#select="selectCard"
:selectedCard="selectedCard"
:playable="cards.playable"
:visable="cards.visable"
></card>
<h1>{{num1}}</h1>
<h1> {{ selectedCard }} </h1>
----------
data() {
return {
cardArray: [],
selectedCard: null,
num1: null,
----------
methods: {
selectCard(cards) {
this.selectedCard = cards;
}
Above code from the main /component/hand.vue
From my understanding, in this case, cards evaluates to this.id?
How can I set num1 to equal cards.randomNum(second item in payload)
The same way that selectedCard evaluates to cards(cards.id)
I've tried variations of "item.Array" and using $emit on this.randomNum the same way it was used to $emit this.Id which doesn't work, how can I properly do this?
//in card hand componenet
select() {
this.$emit("select", this.id);
this.$emit("select", this.randomNum);
}
//in card hand componenet
selectNum(cards.randomNum) {
this.num1 = randomNum;
You can either use two separate events or just pass an object with multiple values.
two events:
select() {
this.$emit("selectId", this.id);
this.$emit("selectNum", this.randomNum);
}
or pass object with multiple values
this.$emit("select", {id:this.id, randomNum:this.randomNum});
I have an input element that bind with v-model to a item value, I want to limit the user input to just type numeric value on range between 0 to 10, I was tried this thing before(add #input and check the input value to keep it in range)
my code is like this:
<v-text-field #input="checkItem" v-model="item"></v-text-field>
checkItem(val) {
if(parseInt(val) < 1) {
this.item = 1;
} else if(parseInt(val) >10) {
this.item = 10;
}
}
Problem
after first time we type number out of range the function works great and keep it in range but when we type out of range number again the element didn't update because the new item value is the same as the old item value! to solve this I try to use forceUpdate and the $forceUpdate() not work!!!
for example
if user type anything between range number into input, because it's in range everything is ok;
but if user type 0 or any number outside of range, on the first time item value change to 1 if value was under 1 but if again type any negative value because the item value last time was changed to 1 when we set it to 1 again nothing happening on the view and the element value was not updated.
The main question is how to force vue to update this input field value?
<div><input type="number" v-model="item"></input></div>
</template>
<script>
export default {
name: "ranges",
data() {
return {
item: Number,
error: String
};
},
watch: {
item(newVal, lastVal) {
if (newVal > 10) this.item = 10
if (newVal < 1) this.item = 1
}
}
};
</script>
Here using the watcher you can do that validation
The only way to force the reactivity when the the final result is always the same, is to re-render the component for it to reflect the changes by updating its key.
Reference link here.
I have forked the sample Vue project from mdiaz00147, and modify into
this, and I think it works as the author intended it to be.
Solution Code modified from mdiaz00147 's code snippet
<template>
<div>
<input :key="inputKey" v-model="item" #change="checkItem" />
</div>
</template>
<script>
export default {
name: "ranges",
data() {
return {
item: null,
inputKey: 0,
};
},
methods: {
checkItem() {
if (parseInt(this.item) < 1) {
this.item = 1;
} else if (parseInt(this.item) > 10) {
this.item = 10;
}
this.inputKey += 1;
},
},
};
</script>
I am basically wanting to do individual selected states on divs that I am rendering in a loop. I can only see a way to change the color of all of the rendered divs, but rather I wish to change the color of which ever one was clicked. Below is the code for the loop.
renderSports() {
const {sports} = this.props
return sports.valueSeq().map(sport => this.renderActualSports(sport))
},
renderActualSports(sport) {
const {sportCount} = this.props
return (
<div className="sportSeparator">
{sport} {this.renderCount(sportCount.get(sport))}
</div>
)
},
This will basically just render a list of some sports. I want to change the color of a selected sport on click.
You will need to store the items that were clicked in your component state.
Assuming you would store this highlighted items in this.state.highlighted and that your sport variable is a string or number:
renderActualSports(sport) {
const {sportCount} = this.props
return (
<div
className="sportSeparator"
onClick={this.highlight(sport)}
style={{color: this.state.highlighted.indexOf(sport) > -1 && 'red' : ''}}
>
{sport} {this.renderCount(sportCount.get(sport))}
</div>
)
},
highlight(sport) {
return () => {
this.setState({highlighted: [...this.state.highlighted, sport]});
}
}
So what you are doing is onClick on the div you add that sport to the this.state.highlighted array and when displaying the list. you check if that sport is in the array and if yes you change the color using an inline style