How to detect last occurance in dom-repeat in polymer 2 - polymer-2.x

I'm trying to add a block of HTML only after the last block inside a dom-repeat, however, it has to be right after the html blocks inside the dom-repeat, because if I put it outside the dom-repeat there'll be a dom-repeat block between the 2 blocks, something like angularJS's ng-class="{'last':$last}"

You may check the index number if you are trying to set a HTML block just after the last item.
Demo
<template is="dom-repeat" items="[[users]]">
<div>[[item]]</div>
<!--This is the blok after last element in dome-repeat -->
<template is="dom-if" if="{{_isLastItem(index)}}">
<div>This is just after last item</div>
</template>
</template>
class MyTest extends Polymer.Element {
static get is() { return 'my-test'; }
constructor(){
super();
// set ie. users array or import data from parent.
this.set('users',['Kaan', 'Timur', 'Oğuz', 'Aybüke'])
}
_isLastItem(i){
return i===this.users.length -1;
}

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.

Get access to the v-slot value inside of the script tag

I am trying to show a loading indicator which is located inside of a component that contains a slot element (lets call this the wrapper component). To do this, I have a function inside the wrapper that sets the state of the indicator based on an input boolean (setSpinnerVisible()). Now, I would like to execute this function from the component that uses this wrapper. To do this, in the parent component I use the v-slot property to get a reference to the function. I would like to be able to call this function inside the mounted() function, or from a function within methods.
However, I am not able to figure out how to do this. The only way I can think of is by passing this v-slot value into a function that is executed on an event like a button press, which works, but I also want to be able to call this method from a function that is not executed by an action in the layout (e.g. in the mounted() function).
This is (a part of) my wrapper component (the function that toggles the spinner is left out for brevity):
<template>
<slot v-bind:setSpinnerVisible="setSpinnerVisible"></slot>
...
<div class="spinner" v-show="spinnerVisible"></div>
</template>
This is (a part of) the component that uses the wrapper:
<Wrapper v-slot="{ setSpinnerVisible }">
...
</Wrapper>
I would like to be able to use the value of setSpinnerVisible inside the mounted function in one way or another, something like this fictional piece of code:
<script>
export default {
mounted() {
this.setSpinnerVisible(true)
}
}
</script>
I am using Vue 2.6.11
There are several approaches you could take.
For example, you could access the parent instance and call the method you need:
this.$parent.setSpinnerVisible()
Alternatively, you could create a gateway component that uses the Wrapper, gets setSpinnerVisible and passes it as a prop to the component that needs it.
You can use dependency injection. Described here: https://v2.vuejs.org/v2/guide/components-edge-cases.html#Dependency-Injection
So, in Wrapper.vue
<template>
...
</template>
<script>
export default {
provide () {
return {
setSpinnerVisible: this.setSpinnerVisible
}
}
}
</script>
And in your child component:
<Wrapper>
...
</Wrapper>
<script>
export default {
inject: ['setSpinnerVisible'],
mounted() {
this.setSpinnerVisible(true)
}
}
</script>
The last one would be my recommended approach because it's much neater and is not anti-pattern.

How to force Vue to update modified HTML

I use a custom directive to render LaTeX-code with KaTeX' renderMathInElement function. This, obviously, changes the component's innerHTML. I would like to re-run KaTeX once the content changes, but: The content never does!
A simple reproduction of the problem does not need KaTeX or directives and still shows, that reactivity works, but stops to work for the parts of a component with changed innerHTML:
<template>
<div>
{{content}}
<span ref="elem">{{content}}</span>
</div>
</template>
<script lang="ts">
import { Component, Ref, Vue } from "vue-property-decorator";
#Component({})
export default class Test extends Vue {
content = "Hello World!";
#Ref()
elem!: HTMLSpanElement;
mounted(): void {
// Without the following statement, Vue correctly re-renders the whole component after a second with the new content
// With this line, the update does not happen for the span element.
this.elem.innerHTML = "<b>Hello World!</b>";
setTimeout(() => {
this.content = "Greetings!";
}, 1000);
}
}
</script>
I suppose this is intended behavior - but that doesn't solve my problem. Is there some way to force Vue to replace all the component's DOM as soon as a re-render takes place?
You can use a key on your span, but if you don't want to tie it in with content, you can instead set it to a number, and increment it every time you want to make a change. Like so (I am not using TS here):
Set a key on your span:
<span :key="content_key">{{ content }}</span>
Then you can watch content and update the key accordingly:
watch: {
content() {
this.content_key ++;
}
}
In this way you can avoid setting the key to content directly.
Does this work for you?

Aurelia: How to bind viewmodels of child DOM elements to parent's viewmodel?

Is there anyway I can get a reference to all the viewmodels of the child components in my lanes property on the BoardComponent viewmodel? I need to have a reference of al <boardlane></boardlane> in the viewmodel of the <board></board> component. Is this possible?
App.ts
export class App {
public groups: any[] = [
{
title: 'first group'
},
{
title: 'second group'
}
]
}
<template>
<board>
<boardlane repeat.for="group of groups" title.bind="group.title"></boardlane>
</board>
</template>
Board Component
#customElement('board')
export class BoardComponent {
public lanes: BoardLaneComponent[];
}
<template>
<div class="board">
<slot></slot>
</div>
</template>
BoardLane Component
#customElement('boardlane')
export class BoardLaneComponent { }
<template>
<div class="boardlane">
I am a board lane
</div>
</template>
You can try the #children decorator:
Board Component
import {children} from 'aurelia-framework';
#customElement('board')
#children({ name: 'lanes', selector: 'boardlane' })
export class BoardComponent {
public lanes: BoardLaneComponent[];
lanesChanged() {
// Handle any mutations to the array here
}
}
<template>
<div class="board">
<slot></slot>
</div>
</template>
In theory this should maintain a list of child VMs on BoardComponent by using the selector to gather the elements within the view.
If the elements are non-aurelia backed elements they will be represented by Element instances in the specified array, otherwise they will be a reference to the actual Aurelia VM backing the element.
Also, it will by default create a function called <x>Changed where <x> is the name of the backing array. You can use this to be notified of any mutations happening to the tracked elements.
The only issue may be nesting - I believe the original implementation deep-selected into descendants but that was removed later. I'm not sure if it was re-introduced but the details are here:
https://github.com/aurelia/templating/issues/451
Assuming you don't need to go to grandchildren this should work.
Disclaimer: not done any Aurelia dev for a little while :(
Note: I don't think the docs clearly list the API for children and the selectorOrConfig parameter it takes
In the source it looks like this:
constructor(config) {
this.name = config.name;
this.changeHandler = config.changeHandler || this.name + 'Changed';
this.selector = config.selector;
this.all = config.all;
}
So it looks like the object can have those properties - not sure what all does though but interesting that you can change the name of the change handler that's fired when the array contents mutate.

How dynamic add events to a tag in my custom grid component

<template>
<tbody>
<template v-for="(row,index) in datalist">
<tr #click="rowevent != null?rowevent(row,this.$el):''" :class="index % 2 === 0?bodytrclass[0]:bodytrclass[1]">
<td v-if="col.show" v-for="col in collist" #click="eventbus(row,$event)" #mouseover="eventbus(row,$event)">
<template v-if="col.type">
<component v-for="com in col.type" :is="com" :rowdata="row" :colname="col.colname"
:tdcbfun="col.cbfun"></component>
</template>
<template v-else>{{ row[col.colname] }}</template>
</td>
</tr>
</template>
</tbody>
</template>
```
now a question
`<tr #click="rowevent != null?rowevent(row,this.$el):''" :class="index % 2 === 0?bodytrclass[0]:bodytrclass[1]">`
how can i add events by data (props) ? dynamic v-on?
i don't want to write #click #mouseover #.......
i want like this ....
```
props: {
trevent: [{event:'click',eventfun:function (rowdata) {
if(rowdata.age<10){ //#:click=eventfun(rowdata)
alert('children')
}
}},{event:'mouseover',eventfun:function (rowdata) {
if(rowdata.age<10){//#mouseover=eventfun(rowdata)
tip('children')
}
}}]
}
```
another example button component
```
<template>
<div>
<button #click="eventbus" #mouseover="eventbus">{{options.btnname}}</button>
</div>
</template>
methods: {
eventbus: function (rowdata, event) {
var eventname = event.type
var eventpos = event.currentTarget.localName
this.$root.$emit(eventpos + eventname, rowdata)
}
}
vm.$on('trclick',function(){
.......do something
})
```
if sometime emit not $on dont do it ...this kind of settlement so .....
and i also can use component :is but javaer must write component so much
oh v-if
Sorry for my english..
终于可以写中文了
我们公司正在开发一个公共组件,刚开始做现在正在做表格的组件。
这个组件是通用的,想用在公司的不同的系统上,也是开源的。
麻烦大家帮看看 现在如何可以 根据传入的props 数据 ,动态添加事件到某个标签上?
我找不到办法动态添加v-on
想做的功能多一些 还不想总让研发人员写动态的component
我尽量将vue封装成 jquery那种调用形式,大家都比较容易会。
其次是我现在在mainjs 里把vue写好的组件暴露出来window.$grid = grid.vue 然后在引入webpack打包好的js
然后直接使用 请问还有其他更好的关于把vue做成组件在外部调用的例子吗?
还有如果我这种方式引用的话 是否还能使用vue-router ? 最好给个例子
最近半个月狂看Vue 在此感谢下尤大弄出这么好的东西!
借这里给大家拜个早年,祝各位在新的一年里身体健康,生活幸福!
英语不好麻烦各位了
One possible approach could be using the special propr ref and adding the event listener in mounted lifecycle. since it is added manually, you may want to remove it too, so I would add it in beforeDestroy life cycle.
Set the ref to the tag
<tr ref="my-tag" :class="index % 2 === 0?bodytrclass[0]:bodytrclass[1]">
Add and Remove the event in the livecycles
mounted() {
this.$refs['my-tag'].addEventListener(this.myEvent,() => {
// Some logic..
});
},
beforeDestroy() {
this.$refs['my-tag'].addEventListener(this.myEvent,() => {
// Some logic..
});
}
It may not be the nicer approach but would do the trick.
It is maybe not the best concept to modify the event listeners of a components DOM afters it was compiled. If found this quote from Evan You (creator of vuejs) here:
I think you are approaching this with wrong assumptions. Templates for a component is static, once it's defined you can't change it. You need to express the parts that may be changed inside the template.
It is possible to recompile a component template as Elfayer shows here, but it does not improve elegancy for this problem since one has to provide a template for every configuration of the properties. For one event attribute it's no problem, you would need two templates. But for three events you would already need 8 templates and so on...
Option 1: Handle logic within normal event handlers
Use normal event handlers which perform conditional execution of dynamic event listeners.
In your template you could replace
<template>
...
<tr #click="rowevent != null?rowevent(row,this.$el):''" :class="index % ...
...
</template>
with:
<template>
...
<tr #click="tr_handler(row,this.$el)" :class="index % ...
...
</template>
and then use the tr_handler() method to check whether there is an event listener assigned to a certain property or not:
methods: {
//...
tr_handler: function(row,e) {
if (this.rowevent) {
return this.rowevent(row, e)
}
}
//...
}
This approach provides a clean structure and keeps the string template feature of vuejs.
Option 2: Use a render() function
One can render the whole template dynamically by using a render function. Also it is possible to apply event listeners to the nodes as described within the latter link:
on: {
'!click': this.doThisInCapturingMode,
'~keyup': this.doThisOnce,
`~!mouseover`: this.doThisOnceInCapturingMode
}
Both approaches do not avoid declaring the event within the template.
Here is some statement about this which explains how things are done in vue-world.
Since you don’t have to manually attach event listeners in JS, your
ViewModel code can be pure logic and DOM-free. This makes it easier to
test.