I have a list of properties (houses, flats,...) that I render with Vue.
Each property is shown or not according to some buttons that act like filters. Those "filters" are set in my data object:
data: {
properties: myPropertiesList,
rooms: {
1: false,
2: false,
3: false,
4: false,
},
type: {
flat: false,
house: false,
field: false
}
},
I set those option to true or false when user click on the options buttons.
Currently, I set v-show with the current expression:
v-show="rooms[property.Rooms] && type[property.Category]"
<div v-show="rooms[property.Rooms] && type[property.Category]"
class="col-md-3"
v-for="property in properties"
>
<property :property="property">
</div>
... And it works fine. However, I would rather like to do something like this:
v-show="showProperty(property)"
... and write that showProperty() function that return true or false.
Is something like that possible ?
If it is, where do you declare the function ? I tried in the methods object but it doesn't work.
The usage of a filter as proposed by Jeff is the way to go, but I want to answer your immediate question weither this is possible with a function, because it is.
You simply add the function to the components methods object:
methods: {
showProperty (property) {
return this.rooms[property.Rooms] && this.type[property.Category]
}
}
This looks like a call for v-for filtering:
...
data:function(){
properties:MyPropertyList,
rooms: [1,2,3,4],
types: ['flat','house','field']
},
...
<div v-for="property in properties | filterBy Room in rooms | filterBy Category in types"
class="col-md-3"
>
<property :property="property">
</div>
This will only show properties if property.Room is in the rooms array, and property.Category is in the types array.
If you need the rooms and types in objects like you have them now, you can use a computed property to create the array for filtering.
computed:{
roomList:function(){
//go through the rooms object and return an array of the true ones
}
}
If you want to filter with a custom function you can:
<div v-for="property in properties | filterBy showPropertyFilter rooms type">
And before all of this, create the filter:
Vue.filter('showPropertyFilter',function(properties, rooms, type){
//return only the properties that should show
});
Related
This question already has answers here:
Access vue instance/data inside filter method
(3 answers)
Closed 2 years ago.
I'm creating a simple Vuejs div component (to show a specific value) which needs to receive: a lists, a placeholder and a value as props. What I'm trying to do is displaying the value with the data from my database, if the user picks a new value from the lists, it should take that new value and display it. However, if the user never picks a new value and the data from the database is empty, it should display the placeholder.
So I have used filters to achieve this. However, it outputs an error: "Cannot read property 'lists' of undefined", which comes from the filters (I know because it outputs no error if I comment out the filters). When I changed the filter to this:
filters: {
placeholderFilter () {
return this.placeholderText || this.placeholder
}
}
It says:""Cannot read property 'placeholderText' of undefined"". So I was wondering if the filters properties executed before the data and props properties. What is the execution order of them? I have attached some of the relevant code down below. Anyway, If you could come up with a better way to achieve this. I would appreciate it!
Here is my component:
<template>
<div>{{ placeholderText | placeholderFilter }}</div>
<li #click="pickItem(index)" v-for="(list,index) in lists" :key="index">{{ list }}</li>
</template>
<script>
export default {
props: {
lists: {
type: Array,
required: true
},
value: {
type: [String, Number],
default: ''
},
placeholder: {
type: String,
default: ''
}
},
data () {
return {
selected: -1,
placeholderText: this.value || this.placeholder
}
},
methods: {
pickItem (index) {
this.selected = index
}
},
filters: {
placeholderFilter () {
return this.lists[this.selected] || this.placeholderText || this.placeholder
}
}
}
</script>
And this is where I use it:
<my-component
placeholder="Please type something"
value="Data from database"
lists="['option1','option2','option3']"
>
</my-component>
Filters aren't bound to the component instance, so they simply don't have access to it through the this keyword. They are meant to always be passed a parameter and to return a transformed version of that parameter. So in other words, they're just methods. They were removed in Vue 3 entirely probably for that reason.
And yeah, what you're looking for here is a computed!
I get an array of objects and I add to it the attribute clicked=false, so I can later add or remove a class using vue bind and the value of that attribute
Then I turn this array of objects to an object structure, so I can render the items by type, in the rendering of the loop.
I use a click method on each list item to change the clicked attribute, but it never changes.
How can I do this?
The object has this structure
grouped:{
typeA: [
{
clicked: false,
text: "a1",
type: "typeA"
},
{
clicked: false,
text: "a2",
type: "typeA"
}
],
typeB: [
{
clicked: false,
text: "b1",
type: "typeB"
}
]
}
And then to render by type I do
<div v-for="(group, type) in grouped" :key="type">
<b>{{type}}</b>
<div v-for="(item, index) in group" :key="index" #click="eventItemClick(item)" >
{{item.text}} {{item.clicked}}
</div>
</div>
all the eventItemClick method does is
eventItemClick(item){
item.clicked = !item.clicked;
},
I created a simple js fiddle example that demonstrates. Just remember to click the group button to group the array and render the list
Thanks
You're running into the reactivity caveat because you are trying to use data properties that don't exist at the time of rendering.
Use Vue.set when setting those properties:
this.todos.forEach(e => {
this.$set(e, 'clicked', false); // `Vue.set` in a module
});
Is it possible to pass a null prop? I have defined a master component which takes user prop.
<div id="app">
<master :user="{{ $user }}"></master>
</div>
Prop is defined like this:
props : {
user: {
type: Object
}
},
Now inside the controller I am passing user variable if user is authenticated, otherwise I am passing null:
public function index()
{
$user = Auth::check() ? Auth::user() : null;
return view('master', compact('user'));
}
I am getting an issue that The value for a v-bind expression cannot be empty. I couldn't make it even if I remove the colon so that it doesn't bind. I also tried explicitly setting that prop is not required, but none of that worked.
Can this be resolved somehow so that I pass a null/empty object to Vue?
In JS null is actually of type object. But the prop check of Vue does not consider null to be an object, see here.
Anyway, if $user is null, {{ $user }} is converted to nothing so you end up with
<master :user=""></master>
Which is an empty string.
You could either not specify the type, like this:
props : ['user'],
or if you want to, specify the type as string or Object:
props : {
user: {
type: [Object, String]
}
},
or you create an empty object if $user is null:
<master :user="{{ $user ?? '{}' }}"></master>
Although the accepted answer gives a solution, I think the exact requirement can be easily achieved by using PHP Null Coalescing Operator and VUE Multiple Prop Types
For example:
VUE component prop updated to accept both Object and null type
props: {
user: {
type: [Object, null]
}
},
Then in the blade file
<div id="app">
<master :user="{{ Auth::user() ?? 'null' }}"></master>
</div>
You can use null as default value. I usually do this.
props: {
user: {
type: Object,
default: null,
}
},
The downside is, when user is changed from null to object, you can't change it back to null.
What I do when I need the clear the object, I use empty object and make it as default.
props: {
user: {
type: Object,
default: () => {},
}
},
In my view (HTML) I want to show this:
<p>Age: {{ user.age }} years</p>
In my javascript this is what I have:
new Vue({
el: '#userApp',
data: {
user : user,
alergies : user.alergies,
conditions : user.conditions,
drugs : user.drugs,
solicitudes: solicitudes,
},
computed: {
},
filters: {
active: function(elements) {
return elements.filter(function(element){
return ! element.fecha_fin;
});
}
},
methods: {
}
});
How do I modify the age in the computed properties? I tried doing this
computed: {
user.age: 10
},
but it won't allow it.
Note: I'm getting user, solicitudes from the server.
Computed properties are functions that you use as properties.
In your case, I think that you can use age like you're doing with the drugs or conditions. Than the 2 way data bindings take care of that.
data: {
user : user,
alergies : user.alergies,
conditions : user.conditions,
drugs : user.drugs,
age : user.age,
solicitudes: solicitudes,
}
Based on your comment bellow, maybe you can use this:
compiled: {
user[age] = theAgeOfTheUser
}
That method will create a new property on the user object. Then you can modify like any other value.
Javascript now implements Object.observe and Array.observe, however I cannot seem to find a way to merge the functionality.
What I am trying to do is to have an array of objects. When any property of one of the objects changes, I would like to be notified.
Object.observe allows me to be notified when a property of a specific object changes.
Array.observe allows me to be notified when an array level change occurs.
Unless I specifically observe each element of an array, I have no way to know when a specific element property changes.
For example:
var model = [
{
data: 'Buy some Milk',
completed: false
},
{
data: 'Eat some Food',
completed: false
},
{
data: 'Sleep in a Bed',
completed: false
},
{
data: 'Make Love not War',
completed: false
}
];
Array.observe(model, function(changeRecords) {
console.log('Array observe', changeRecords);
});
model[0].data = 'Teach Me to code';
model[1].completed = true;
model.splice(1,1);
model.push({data: "It's a new one!",completed: true});
gives a console output of:
Array observe [Object, Object]
0: Object
addedCount: 0
index: 1
object: Array[4]
removed: Array[1]
type: "splice"
__proto__: Object
1: Object
addedCount: 1
index: 3
object: Array[4]
removed: Array[0]
type: "splice"
__proto__: Object
length: 2
__proto__: Array[0]
These two notifications relate specifically to:
model.splice(1,1);
and
model.push({data: "It's a new one!",completed: true});
but completely ignore
model[0].data = 'Teach Me to code';
and
model[1].completed = true;
Is there a way to observe every change, no matter whether it is an array level or object level change?
I know there are libraries that may be able to do this for me, however I would like to understand how to implement this directly in my own code.
EDIT: OK, So it seems I wasn't missing any magic functionality, and the only true option is to observe the individual elements of the array.
So to expand upon the original question, what is the most efficient method of adapting arrays to handle element observations?
Array observe observes array changes! You need to observe each object inside Array too using Object.Observe!
See this
function ObserveMyArray(myArray){
var arr=myArray||[];
for(var i=0;i<arr.length;i++){
var item=arr[i];
Object.observe(item , function(changes){
changes.forEach(function(change) {
console.log(change.type, change.name, change.oldValue);
});
});
}
Array.observe(arr, function(changeRecords) {
console.log('Array observe', changeRecords);
});
}