How to get table column count when specifying custom bottom-row? - vue.js

I have the following table
<b-table
ref='table'
:fields='fields'
:items='provider'
:busy.sync='isBusy'
>
<template v-slot:bottom-row>
<!-- TODO: Is it possible to not hardcode it? -->
<b-td colspan='7' class='text-center'>
<b-spinner></b-spinner>
</b-td>
</template>
</b-table>
Is it possible to somehow get the column count and use it in the colspan instead of hardcoded 7? I presume I can use some convoluted logic with a custom prop to cover this but there's probably a better approach. I can also use e.g. 999999 which surprisingly works.
I've tried colspan='fields.length' but that wouldn't work.

You were so close with your try
In order for vue to understand that you want to pass a variable as the attribute value, you need to add a colon in front of it, like so:
<b-td :colspan='fields.length' class='text-center'>
If you do it like this:
<b-td colspan='fields.length' class='text-center'>
colspan would parse the fields.length as a string "fields.length", not the javascript computed value for the variable fields.length 7 for example.

Related

How to use `v-if` and `v-for` on the same element?

Hi I'm trying to figure out how to use v-if on a iterated element which also uses v-for. I need to check if the current element has any of a series of classes, which are numbers.
so the classes of each article would be:
<article class="post-item post-guide 12 22 19 30 55">...
this is the HTML that renders all:
<article v-if="showIfHasClass" :class="'post-item post-guide ' + guide.categories.toString().replace(/,/g, ' ')"
v-for="(guide, index) in guides" :key="index">
<header>
<h1 class="post-title">
{{ guide.title.rendered}}
</h1>
</header>
</article>
I have tried with methods that check the class of all elements, that works, but i'm trying to use a clean Vue built-in solution with v-if without success, i'm not able to retrieve the class of this in a successful way.
Should showIfHasClass be a computed property? I have tried with that too... but it seems, I'm missing something along the way.
my data I have to check against is an array:
data:{
guides: [...]
selectedCategories: [1, 22, 33, 100, 30];
}
or maybe it is better to directly loop over the guides and check if they have the selectedCategory or not, then remove the element from the guides data array?
What is more effective?
Besides the option to create an additional filtered computed (effectively eliminating the need to use v-for and v-if on the same element), you also have a template level way of dealing with such edge-cases: the <template> tag.
The <template> tag allows you to use arbitrary template logic without actually rendering an extra element. Just remember that, because it doesn't render any element, you have to place the keys from the v-for on the actual elements, like this:
<template v-for="(guide, index) in guides">
<article v-if="isGuideVisible(guide)"
:key="index"
class="post-item post-guide"
:class="[guide.categories.toString().replace(/,/g, ' ')]">
<header>
<h1 v-text="guide.title.rendered" />
</header>
</article>
</template>
isGuideVisible should be a method returning whether the item is rendered, so you don't have to write that logic inside your markup. One advantage of this method is that you can follow your v-if element with a fallback v-else element, should you want to replace the missing items with fallback content. Just remember to also :key="index" the fallback element as well.
Apart from the above use-case, <template> tags come in handy when rendering additional wrapper elements is not an option (would result in invalid HTML markup) (i.e: table > tr > td relations or ol/ul > li relations).
It's mentioned here as "invisible wrapper", but it doesn't have a dedicated section in the docs.
Side note: since you haven't actually shown what's inside guide.categories, I can't advise on it, but there's probably a cleaner way to deal with it than .toString().replace(). If guide.categories is an array of strings, you could simply go: :class="guide.categories".
I think the most Vue way is to create a computed property with filtered items from selected categories, then use that in v-for loop (the idea is to move the business logic away from template).
computed: {
filteredItems(){
return this.guides.filter(e => this.selectedCategories.includes(e.category))
}
}
Also, as a note, it is not recommended to use v-if and v-for on the same element, as it may interfere with the rendering and ordering of loop elements. If you don't want to add another level of nesting, you can loop on a 'template' element.
<template v-for="item in items">
// Note the key is defined on real element, and not on template
<item-element v-if='condition' :key="item.key"></item-element>
</template>

Vue Dynamic Component Ordering?

Thanks in advance for the help.
I'm using the Vue's dynamic component tag in an application.
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
The component list is provided via a computed property which I simply iterate over creating <component /> elements.
<component v-for="component in myComponents" :key="component" is="component />
The issue i'm running into is the components are loaded asynchronously and some components can take longer at times than others to load. Due to this sometimes the components load out of the desired order.
I was curious if anyone had a suggestion on how I might be able to force the components to display in a desired order?
You can try to use an ordered computed property, for exemple if you want to order your components by name.
In the template :
<component v-for="component in orderedComponents" :key="component" is="component />
In the script
computed: {
orderedComponents: function () {
return _.orderBy(this.myComponents, 'name')
}
}
This exemple is using Lodash but of course you can order it the way you want.
You can check the documentation : https://v2.vuejs.org/v2/guide/migration.html#Replacing-the-orderBy-Filter
Yes, you can enforce the order by setting :key to a number.
First, modify your for loop to use indexes:
v-for="(index, value) in myComponents"
Then set key to the index
:key="index"
This should keep the order in tact - if it doesn't, you could always create placeholders first then replace them in the runtime.

get content slot as string with Vue js 2

I need get string inside slot before that slot is compiled. Is posibble?
Example
<div>
<slot>
<component-test text="test1"></component-test>
</slot>
</div>
I need as result "<component-test text="test1"></component-test>"
hmm seems its treating slot content html so .. in end result if you need it as string and use later on then you have to do some trick.
first you need to put real string there:
escape('<component-test text="test1"></component-test>');
output : %3Ccomponent-test%20text%3D%22test1%22%3E%3C/component-test%3E
use this output content and put that in slot.
this will treat as string.
now if you need to use that string's real html
convert it back to html using :
unescape('%3Ccomponent-test%20text%3D%22test1%22%3E%3C/component-test%3E')
output :
<component-test text="test1"></component-test>
in this way when you need to transfer html/string you can make it real string then when you need to use/compile make it html again.

Aurelia not outputting attribute with string interpolation in repeat

Is there any reason why a repeat.for binding would remove attributes from elements inside the repeater?
<div repeat.for="i of model.someArray.length">
<label>Some Array - Index ${i + 1}</label>
<input value.bind="model.someArray[i]" some-custom-attribute="someArray[${i}]"/>
</div>
and that some-custom-attribute is not being output within the repeat, but if I were to remove the string interpolation within there then it outputs fine.
== Edit ==
I have put it in a comment but just to make sure everyone is on the same page, ideally this is the output I expect:
<input value.bind="model.someArray[i]" some-custom-attribute="someArray[0]"/>
The some-custom-attribute is not an aurelia attribute, its pure HTML that a 3rd party JS library uses, so the goal here is to get the textual value of the index into the textual attribute value.
model.someArray.length is a number, not an array. You need to iterate over the array. If you do need the current index, the repeater provides the $index property for you to use.
Your code should look like this:
<div repeat.for="item of model.someArray">
<label>Some Array - Index ${$index + 1}</label>
<input value.bind="item" some-custom-attribute.bind="item"/>
</div>
To answer your original question, doing some-custom-attribute="model.someArray[${i}]" makes Aurelia think you are trying to pass a string value to the custom attribute. You can see that in the following gist: https://gist.run/?id=eed8ac8623ff4749aa5bb93c82a7b1fb I've created a custom element that just pushes whatever value it is given in to an element on the page. Note!!! Don't ever do what I'm doing here! I just did this this way so you wouldn't have to open the js console. To actually get a value passed in, you would need to use some-custom-attribute.bind="item" or (to do things how you are doing things, some-custom-attribute.bind="someArray[i]"

How to set default value for bindable properties in Aurelia HTML only element

Supposed I have the following html only element:
<template bindable='value: Math.random()'>
${value}
</template>
<!-- In another component {Main} -->
<template>
<require from='./that-element'></require>
<that-element></that-element>
</template>
The result is an empty string in main component. The question is how can I define a default value for html only element?
EDIT: Default value for element will not work if an 2 bindings need to have the same unique default value. I can make it unique myself but then I have to do it manually. Was looking for something like Math.random()
The contents of bindable attribute on <template> tag is handled as comma-separated list of strings. Therefore, you won't be able to set default values there.
I can think of two approaches:
Set default values in parent viewmodel class and use explicit binding. Considering above scenario, this will work for you.
<that-element value.bind="someVarFromParent"></that-element>
In some really simple cases, when default value should be a static string, you can use JavaScript expressions within an interpolation.
${value || 'default value'}
Gist demo: https://gist.run/?id=f0698d5b0066c3674c29c40d850217dc
Just putting my 2 cents in here...I think Marton nailed it on the head with the html only solution. I generally use the value.bind method because I usually always have a vm along with my view. So, to simplify JayDi's response (which is a valid response), I've created a GistRun for you.
Basically, you'll create a custom-elem html file and a custom-elem js file. In the html file, you would have something like this:
<template>
<h1>${value}</h1>
</template>
and in the js file, you'd have something like this:
import {bindable} from "aurelia-framework";
export class CustomElem {
#bindable value = "default value";
}
Finally, you'd just require it in your main parent file like so:
<template>
<require from="./custom-elem"></require>
<custom-elem></custom-elem>
<custom-elem value.bind="'myVal'"></custom-elem>
</template>
I just added a default value for the custom element. You can leave it undefined if you'd like. Also, you can add as many properties as you would like. This is the simplest way to tackle this in my opinion, unless your situation lets you get by with the html only method.
Also, in this case, I'm using a string of "myVal". If you wanted to use a variable that you have in your parent vm, you would just take the single quotes off and use that variable name.
Here's the running Gist so that you can fool around with it:
https://gist.run/?id=2eb14ecd2b0b859fd5d1a33b5ee229bd
You can use || statement in html-only element for default value:
<template bindable="param1, param2, param3">
<h1>${param1 || 'default 1'}</h1>
<h2>${param2 || 'default 2'}</h2>
<h3>${param3 || 'default 3'}</h3>
</template>