Formatting rows in Quasar Table based on prop item - vue.js

Using q-table I'm trying to format specific rows based on the name it contains, however somehow the table is not being displayed when I add computed class as class binding.
Is there a way to make it work so that multiple conditions for class binding can be used?
Here
https://codepen.io/pokepim/pen/wvGWNEp
You can see that the table is not loading when this computed class binding is used.

Your codes invoked this.props.row.name inside computed property is wrong usage.
The context of computed property is the instance of current component, not q-tr context, so this.props.row doesn't exist.
The correct usage will be pass props.name of v-slot:body="props" into one method, inside that method returns the class name you expected based on different conditions.
For example (The codepen):
The template:
<template v-slot:body="props">
<q-tr :props="props" :class="tableFormat(props.row.name)">
<q-td v-for="col in props.cols" :key="col.name" :props="props">{{ col.value }}</q-td>
</q-tr>
</template>
The scripts:
{
methods: {
tableFormat: function (name) {
return name ? 'text-bold' : ''
}
}
}

Related

how can i access data property in html template using loop in vue js

i'm trying to use data property commentsToShow in my html template to limit the amount of data that displays on my webpage
this is my template
<div v-if="index < products.length" v-for="(commentIndex, index) in computedProduct">
<div class="title pt-4 pb-1">{{products[index].title}}</div>
</div>
if i add commentsToShow in my for loop i get one product but the computed products doesn't work same way the other way round
this my script tag
<script>
export default {
data() {
return {
commentsToShow: 1,
totalComments: 0,
};
},
computed: {
computedProduct() {
let tempRecipes = this.products;
if (this.filterPrice !== "true");
}
};
</script>
if i change computed property to commentsToShow this the error i get in my console
The computed property "commentsToShow" is already defined in data.
please how can i get the value of commentToShow in my template
according to vue's official docs it's not recommended to use v-for and v-if on the same element
try using v-if on a wrapper div or template element
<div v-for="(commentIndex, index) in computedProduct">
<template v-if="index < products.length">
<div class="title pt-4 pb-1">{{products[index].title}}</div>
</template>
</div>
v-if has higher priority so it's executed first and index will not be defined yet.
also you have to return something on your computed property function in order to use it
You can use the slice method on a computed property, like this:
<script>
export default {
data() {
return {
commentsToShow: 1,
allComments: []
};
},
computed: {
listComments() {
return allComments.slice(0, commentsToShow);
}
};
</script>
You can also use pages to show the comments, in this case you can return like this:
return allComments.slice((currentPage - 1) * commentToShow, commentsToShow);
The first argument of slice is the start index, the second is the number of elements to get
The computed property "commentsToShow" is already defined in data.
Equivalently how you cannot have more than one variable with the same name defined in a scope. A computed property cannot have the same name as an existing data property. Essentially, they co-exist in the same namespace, thus they have to be unique.
You have a name clash, and that is what the error is saying.

VueJS - reusing prop (field name) at definition of other bindigns

I don't know how to exactly name this, I have some sort of forms in Vue which consist of different input types or more complex wrapped elements or even custom components. They all have one thing in common though - their fieldName which is used for checking various stuff such as validation, class binding etc.
Example:
<div class="field">
<div class="label">Product name</div>
<input :value="productName" #input="valueChanged" :class="{ changed: isChanged('productName') }" data-field="productName">
</div>
As you can see, productName is repeated 3 times in just a single line. I use it in dataset so valueChanged method (a global mixin) knows that field name has changed, then in the class binding to check if value has changed to style it properly, and next for the value binding itself.
It grows bigger and bigger as I want to add for example another class binding like error: hasErrors('productName')
Is there any way to define the field name once and re-use it in other bindings? It would still require some repetition, but at least changing the field name in the future would be just one change instead of 4-5. Something like this:
<input :fName="productName" :value="fName" #input="valueChanged" :class="{changed: isChanged(fName), error: hasErrors(fName)" :data-field="fName">
I know that wrapping it in some custom component would probably be one way, but that would require a lot of different conditions to render things correctly as I'm using various field types with different structures. And I would need to re-write half of my app.
Here are two potential solutions:
Use v-bind and generate an object with all of the attributes you need
<input v-bind="getAttrs('productName')" #input="valueChanged" />
...
methods: {
getAttrs(fieldName) {
return {
value: this[fieldName],
class: {
changed: this.isChanged(fieldName),
error: this.hasErrors(fieldName)
},
'data-field': fieldName
}
}
}
Store all the fields in a variable and loop through them:
<div
v-for="field in fields"
class="field"
>
<div class="label">
Product name
// Or e.g. {{ field.label }} if you have an array of field objects
</div>
<input
:value="getValue(field)"
:class="{ changed: isChanged(field), error: hasErrors(field) }"
// Still need to use v-bind to generate the data attribute on the fly
v-bind="getDataAttr(field)"
#input="valueChanged"
/>
</div>
...
data() {
return {
fields: [
'fieldName1',
'fieldName2',
'fieldName3'
],
}
},
methods: {
getValue(fieldName) {
return this[fieldName];
},
getDataAttr(fieldName) {
return {
'data-field': fieldName
}
}
}
I believe that having custom component handling input fields is the smartest way. I don't think, there would be that much conditions and even if there is some workaround, you will still need to rewrite half of your app. You may use input type as prop, so you can use component for different field types and if there are much different structures, you can use slots, to add some custom structure.

How to change value of component property in a code in Vue.js?

I have an array of generic child components in my parent component:
<component v-for="item in items" :key="item.id" :is="componentName">
I can get a child via this.$refs, but I can't set a new value for a prop :is like:
this.$refs[id][0].is = 'MyNewComponentName'
How can I set a value of component instance property in a code?
First define your prop structure like
{
...item, // to use your current variables
componentName: 'MyExistingComponentName'
}
Receive the prop and bind it to a data variable, so something like
data: function() {
returns {
items: this.propItem
}
}
Make the required adjustment in your tag
<component v-for="item in items" :key="item.id" :is="item.componentName">
Now you got 2 options, you can either change the item.componentName by referencing this.items in a method, finding the index and changing it or you could get the parent to change the value of the prop using a custom event using $.event(event-name, 'MyNewComponent`). Both methods are fine, it really depends on your requirements.
Refer to https://v2.vuejs.org/v2/guide/components-custom-events.html
You could also read stackoverflow questions on mutating prop values.

How to encapsulate / wrap a VueJS component?

Hi everybody, please pardon my english :-)
I have a Vue component that can take dynamic slots (the names of the slots will depend on a props).
I use it on several places and some of the slots are always present.
To avoid redundancy, I'm looking for a way to create a component that "wrap" the final component to allow me to define only the additionals slots.
If there is an "obvious" way to achieve it, I may have missed it :-)
Code example
Without a "wrap component"
<b-table
show-empty
small
hover
[...some others and always present props...]
:items="aDataVarThatWillChangeBasedOnTheContext"
[...some others and uniq props...]
>
<template slot="same-1">
A slot that will always be present with the same content (for example, a checkbox in the first column)
</template>
<template slot="same-2">
A slot that will always be present with the same content (for example, some action buttons in the last column)
</template>
[...some others and always present slots...]
<template slot="not-the-same">
A slot that is only used in this context (for example, a duration based on a row timestamp and a timestamp picked by the user)
</template>
[...some others and uniq slots...]
</b-table>
With a "wrap component"
<my-b-table
:items="aDataVarThatWillChangeBasedOnTheContext"
>
<template slot="not-the-same">
A slot that is only used in this context (for example, a duration based on a row timestamp and a timestamp picked by the user)
</template>
</my-b-table>
Note: The dynamic slot name is not predictible.
If I suddenly need a "foo" column, I should be able to pass a "foo" slot (and a "HEAD_foo" slot, in my case)
Some researches
I read here that:
They’re (the functionnal components) also very useful as wrapper components. For example, when you need to:
Programmatically choose one of several other components to delegate to
Manipulate children, props, or data before passing them on to a child component
And "Manipulate children, props, or data before passing them on to a child component" seems to be exactly what I need.
I looked on render function but a lot of things seems to be not implemented, like the v-model, and I have difficulties to figure out how to pass dynamic slots...
Thank you in advance for your(s) answer(s) !
up: At the 07.03.2018 I still dont have any idea about how to solve this case
Found the answer that was somehow unclear to me that month ago.
("Dynamic" means here "not explicitely declared by the component, but gived by the parent")
Wrapper component
Props and scoped slots can be gived dynamically by the options object of createElement function.
"Simple" Slots can be gived dynamically by the childs array of createElement function.
Wrapped component
Props can't be dynamic unless the component is functional.
Slots can always be retrieved dynamically.
Scoped slots can be retrieved only if the component isn't functional.
Conclusion
It's not possible to have dynamics props and scoped slots at the same time...
But it's possible to declare all the needed props and then to use a "non-functionnal" component as wrapper and as wrapped.
How to
Retrieve from non-functional component
var component = Vue.component('component-name', {
props: ['name', 'of', 'the', 'props'],
// [...]
aMethod: function () {
this._props // all the declared props
this.$slots // all the slots
this.$scopedSlots // all the scoped slots
}
});
Retrieve from functional component
var component = Vue.component('component-name', {
functional: true,
render: function (createElement, context) {
context.props // all the props
context.children // all the slots as an array
context.slots() // all the slots as an object
}
});
Give to child component
var component = Vue.component('component-name', {
render: function (createElement) {
return createElement(
childComponent,
{
props: propsToGive,
scopedSlots: scopedSlotsToGive
},
[
// non-scoped slots to give
createElement('slot-tag-name', {slot: 'slot-name'})
]
);
}
});
References
https://v2.vuejs.org/v2/guide/render-function.html
https://v2.vuejs.org/v2/guide/render-function.html#createElement-Arguments
https://v2.vuejs.org/v2/guide/render-function.html#Functional-Components
Sandbox
https://jsfiddle.net/5umk7p52/
Just make a regular component out of your customized <b-table>.
You'll need to define an items prop for your component to pass as the items for the <b-table> component.
And, to define a slot for your component, you'll need to use the <slot> tag, specifying the name using the name attribute.
If you'd like to make one of the slots from the <b-table> component accessible in the <my-b-table> component, simply pass a <slot> tag as the content of the slot in your custom component.
It would look something like this:
Vue.component('my-b-table', {
template: `
<b-table
show-empty
small
hover
:items="items"
>
<template slot="same-1">
Content to pass to the b-table's slot
</template>
<slot name="not-the-same">
A slot that is only used in this context
</slot>
<template slot="last_edit">
<slot name="last_edit">
A slot to pass content to the b-table component's slot
</slot>
</template>
</b-table>
`,
props: { items: Array },
});

When v-for array created by computed option changs, the DOM doesn't change accordingly

Recently, I've encountered a problem that caused by the computed option of vuejs.
Firstly, I use v-for to loop for an array (soloColImgs) which is created by the computed option.
my HTML
<div class="show-box" v-for="item in soloColImgs" track-by="$index">
<img v-bind:src="item.imgUrl"/>
<a v-bind:href="item.itemUrl" target="_blank"></a>
</div>
my JS
//...
computed: {
soloColImgs :function(){
//....
},
methods: {
change:function(){
this.soloColImgs.pop();
}
}
Secondly, I change the array (soloColImgs) by using pop() or splice() etc...When I look into the console, the array can change accordingly, however, the DOM does't change at all. It would be greatful if anyone can help me out of this.
The point of a computed property is that its determined solely by the function that defines it. You cannot change it directly, you must change it by acting on the dependencies. The dependencies are the properties that are used to calculate the returned value.