I am trying to show one of the headers according to the value of isComplex$ observable in *ngIf:
<complex-header *ngIf="isComplex$ | async; else ordinaryHeader"></complexheader>
<ng-template #ordinaryHeader>
<ordinary-header></ordinary-header>
</ng-template>
The problem is *ngIf goes with else case without waiting for observable to emit value. How can I accomplish both of headers wait for observable to emit its first value.
Use angular5 *ngIf else directive.
<div class="course-detail" *ngIf="isComplex$ | async as isComplex else ordinary">
<complex-header *ngIf="isComplex"></complex-header>
<ordinary-header *ngIf="!isComplex"></ordinary-header>
</div>
<!-- Showing ordinary header for cases when observable is not returned this can be replaced wuth loader as well-->
<ng-template #ordinary>
<ordinary-header></ordinary-header>
</ng-template>
Also see https://blog.angular-university.io/angular-reactive-templates/
The solution to not use async pipe multiple times is to wrap everything in a ng-container with the async pipe and use *ngFor. But to use *ngFor isComplex$ must always emit an itterable. So we need a new observable to use in the template:
isComplexIterable$ = isComplex$.pipe(map(isComplex => [isComplex]))
<ng-container *ngFor="let isComplex of isComplexIterable$ | async">
<complex-header *ngIf="isComplex"></complexheader>
<ordinary-header *ngIf="!isComplex"></ordinary-header>
</ng-container>
Related
I have the following html div. The {{ }} represent liquid syntax and it renders server side.
<div class="two columns">
<button
data-value='{{ value }}'
class="button-selection"
:class="selectionButtonClass($event)"
#click="selectionButton($event)"
>
<span class="text">{{ value }}</span>
</button>
</div>
In a vue 3 instance I have the following method in the same page.
selectionButtonClass(el) {
console.log('checking val');
console.log(el);
}
My goal is to set a conditional class in the selectionButton method but I can't get the element to get the data attribute. The above appears in the console log as undefined. However the #click does show the event obviously it's recognize the onclick but not the class method check.
$event is only available to event handlers. #click="selectionButton($event)" defines an inline event handler, while :class="selectionButtonClass($event)" is not an event handler.
To get the element, you need to add a ref attribute to the <button>:
<button
ref="selectionButton"
data-value='{{ value }}'
class="button-selection"
:class="selectionButtonClass($event)"
#click="selectionButton($event)"
>
And access it by this.$refs.selectionButton, assuming you are using the options API. However, the ref is available only after the component is mounted. Thus you need to handle the case where the ref is null.
More on template refs: https://vuejs.org/guide/essentials/template-refs.html
Since you are using server side rendering, I think it would be better to render the value as a parameter of the selectionButton function on the server side.
In Laravel Nova, action modals are rendered in Vue by retrieving a list of fields to display through a dynamic component. I have replaced the action modal with own custom component, but am struggling to achieve the effect I want without also extending the entire set of components for rendering form fields.
I have my CustomResourceIndex.vue, containing a conditionally loaded (via v-if) ActionModal.vue, in which the form fields are rendered like so:
<div class="action" v-for="field in action.fields" :key="field.attribute">
<component
:is="'form-' + field.component"
:resource-name="resourceName"
:field="field"
/>
</div>
where the actual form field component is chosen based on the field.component value.
Those form fields (which I ideally do not want to have to extend and edit) are rendered like so:
<template>
<default-field :field="field" :errors="errors">
<template slot="field">
<input
class="w-full form-control form-input form-input-bordered"
:id="field.attribute"
:dusk="field.attribute"
v-model="value"
v-bind="extraAttributes"
:disabled="isReadonly"
/>
</template>
</default-field>
</template>
I would like to watch the value of specific fields and run methods when they change. Unfortunately due to a lack of ref attribute on the input elements or access to the value that the form element is bound to, I'm not sure how I can accomplish that from within ActionModal.vue.
I am hoping that because I have access to the ids still, there is some potential way for me to emulate this behavior.
Many resources I've found on my own have told me that anything with an ID is accessible via this.$refs but that does not seem to be true. I can only see elements that have an explicitly declared ref attribute in this.$refs, so I am not sure if I've misunderstood something there.
I would recommend looking into VueJS watch property.
You can listen to function calls, value changes etc.
watch: {
'field.component': function(newVal, oldVal) {
console.log('value changed from ' + oldVal + ' to ' + newVal);
},
},
Are those components triggering events? Try looking into the events tab of the Vue DevTools to see if some events are triggered from the default-field component when you update the value.
My guess is that you could write something like:
<div class="action" v-for="field in action.fields" :key="field.attribute">
<component
:is="'form-' + field.component"
:resource-name="resourceName"
:field="field"
#input="doSomething($event)"
/>
</div>
The $event value being the new value of the field.
Hit me on the comments if you have more info on the behavior of the default form fields (Are their complete code accessible somewhere?).
I'm trying to wrap up an auto-complete feature on my app and can't seem to get #keyup.enter to execute a function. The documentation doesn't touch on this. Here's my code below.
<md-autocomplete
v-model="selected"
:md-options="users"
:md-fuzzy-search="false"
#keyup.enter="click_select()"
>
<label id="placehold" v-if="selected == null || selected == ''">Start typing...</label>
<template slot="md-autocomplete-item" slot-scope="{ item, term }">
<md-highlight-text :md-term="term">{{ item }}</md-highlight-text>
</template>
<template slot="md-autocomplete-empty" slot-scope="{ term }" v-if="term != null">
"{{ term }}" is not currently on file. <a #click="noop()">You can add them here</a>.
</template>
</md-autocomplete>
<div class="md-layout md-gutter">
<transition name="fade">
<a class="selectlink" v-if="selected != null && selected !='' && view_link" id="link-effect-4" v-on:click="show_data()">Select</a>
<a class="selectlink" v-if="selected != null && selected !='' && view_next_link" id="link-effect-4">Next</a>
</transition>
</div>
Why your solution isn’t working
In order to listen on an event on a component (md-autocomplete), that component must emit the event. From a glimpse at the source it doesn’t appear to do so. If any child component (for instance, an <input>) emits the event, but the outer component doesn’t pass it through, you will never see it.
A Vue solution that might not work here
You can, however, still attach event listeners to the slots you pass into the component. It would look like this:
<template slot="md-autocomplete-item" #keyup.enter="click_select()" …>
I haven’t looked thoroughly through the docs, but I cannot find a suitable location for that, either.
Vanilla JS will work
As a last resort, you can attach an event listener to a DOM node that is a parent of the component you actually want to listen to. Suppose md-autocomplete was mounted in a <div id="parentDiv">, you could do
function listenerFunction(event) {
if (event.keyCode === 13) {
// keycode 13 is the return key
click_select()
}
}
document.getElementById('parentDiv').addEventListener('keyup', listenerFunction)
Caveats
It might also be appropriate to check whether the event originated where you think it should (that is, in the component’s search bar) by means of event.target or event.composedPath
In any case you should remember to detach the event listener when the component is destroyed, or else you might have the function called multiple times when the component is later re-mounted:
beforeDestroy() {
document.getElementById('parentDiv').removeEventListener('keyup', listenerFunction)
}
This is also why listenerFunction must be given a name. You cannot remove an event listener that was attached using an anonymous function.
<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.
I had the following in Vue 1.x
<tr v-for="product in products">
<td><img src="{{ product.image_url | thumbnail }}" class="thumbnail"></td>
</tr>
But in Vue 2 I tried:
<tr v-for="product in products">
<td><img :src="product.image_url | thumbnail" class="thumbnail"></td>
</tr>
and got "Property or method "thumbnail" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option"
note: The regular mustache interpolation filter doesn't work when I'm assigning it to a property on a html element (ie: {{ data | filter }} works fine as plain text but not when trying to do src="{{ data | filter }}".
I tried a computed property but it didn't work as the element I'm trying to get a computed value is each element within an array (and I'm looping through each element in the array).
All thumbnail does is do some regex and fancy text replacement. Not sure the best way to do this in vue2.
Vue.js 2.0
Filters can now only be used inside text interpolations ({{}} tags). In the past we've found using filters with directives such as v-model, v-on etc. led to more complexity than convenience, and for list filtering on v-for it is more appropriate to move that logic into JavaScript as computed properties.
Using computed property:
new Vue({
el: '#app',
data: {
products: [],
},
computed: {
filterProducts() {
return this.products.filter(function(product) {
...
})
}
}
})
Vue.js expects you to define your filters before you use them in templates. Here is an example of how you would define a date formatting filter:
// Define the date time format filter
Vue.filter("formatDate", function(date) {
return moment(date).format("MMMM D, YYYY")
})
Once you have that, you are allowed to use it in code as follows:
<div class="due-date">{{dueDate | formatDate}}</div>
Can you tell me what the thumbnail filter is supposed to do? For images, I don't think there is any processing you can do on the client side. You can show thumbnails in some pre-defined size, which is something you would do in CSS, not using filters.