canjs component tempate dom live binding - canjs

My code is to realize a paginate page like this example, https://github.com/bitovi/canjs/blob/master/component/examples/paginate.html .
I found the {#messages}...{/messages} in message.mustache template was not been inserted into page , while messagelist component inserted event has been triggered, so i can not do any binding to {#messages} dom in the event, because it ‘not exists in the page.
Are there other ways to fix this problem?
The Templates:
message_list.mustache:
<app>
<messagelist deferredData='messagesDeferred'></messagelist>
<next-prev paginate='paginate'></next-prev>
<page-count page='paginate.page' count='paginate.pageCount'></page-count>
</app>
message.mustache:
{#messages}}
<dl>
<dt>.....</dt>
<dd>....</dd>
</dl>
{/messages}
The Component:
can.Component.extend({
tag: "messagelist",
template: can.view('/static/web/tpl/mobile/message.mustache'), // to load message template
scope: {
messages: [],
waiting: true,
},
events: {
init: function () {
this.update();
},
"{scope} deferreddata": "update",
update: function () {
var deferred = this.scope.attr('deferreddata'),
scope = this.scope,
el = this.element;
if (can.isDeferred(deferred)) {
this.scope.attr("waiting", true);
deferred.then(function (messages) {
scope.attr('messages').replace(messages);
});
} else {
scope.attr('messages').attr(deferred, true);
}
},
"{messages} change": function () {
this.scope.attr("waiting", false);
},
inserted: function(){
// can't operate the dom in message.mustache template
}
}
});
//to load component template
can.view("/static/web/tpl/mobile/message_list.mustache",{}, function(content){
$("#message-list").html(content)
});

I have solved the problem, but not the best, Maybe someone have a better way.
I changed my template, add a new component called <messageitem>
<messageitem> will load another template: message.mustache
Every <messageitem> will trigger inserted event when inserted into <messagelist>
The new component:
can.Component.extend({
tag: "messageitem",
template:can.view('/static/web/tpl/mobile/message.mustache'),
events: {
inserted: function(el, ev){
// Can-click can not satisfy my needs,
// because i call the third-party module to bind click event
// this module will be called repeatedly, not the best way
reloadModules(['accordion']);
}
}
});
// To load message_list.mustache
can.view("/static/web/tpl/mobile/message_list.mustache",{}, function(content){
$("#message-list").html(content)});
Static html:
<body>
<div id="message-list">
....
</div>
</body>
message_list.mustache:
<app>
<messagelist deferredData='messagesDeferred'>
{{#messages}}
<messageitem></messageitem>
{{/messages}}
</messagelist>
<next-prev paginate='paginate'></next-prev>
<page-count page='paginate.page' count='paginate.pageCount'></page-count>
</app>
message.mustache:
<dl class="am-accordion-item" >
...
</dl>

Related

vue not loading data into child component

I've a hard time in understanding the methods of vue. In my put-request users can edit, delete images. In parent component the get-request loads the images and the are pushed to an image-gallery (the child-component) via properties. In my set up the console.log is always empty.
//PARENT COMPONENT
<template>
<div class="form-group">
<image-gallery :serverData="serverMap"/>
</div>
</template>
<script>
import ImageGallery from './ImageGallery.vue';
export default {
components:{ImageGallery},
data: () => ({
serverMap: {
title: '',
file: ''
}
}),
mounted () {
//AJAX ETC get servermap
.then((response) => {
this.serverMap = response.data
})
}
Just a normal straight parent-child situation. Here under the child-component
<template>
</template>
<script>
export default {
name: 'ImageGallery',
//incoming data
props: {
serverData: {
type: Object,
default () {
return {
hasLabels: true,
isHorizontal: false
}
}
}
},
created: function () {
this.loadImages()
},
methods: {
loadImages () {
console.log(this.serverData.file)
//do something with the serverData
//prepare for fileReader function
//together with new image validation
}
}
The method 'loadImages' should be automatically delevering the serverData via computed.But is doesn t. Who can help?
There is race condition.
Either not render a child until data is available; serverMap needs to be null instead of empty object in order to be distinguished from populated object:
<image-gallery v-if="serverMap" :serverData="serverMap"/>
Or delay data access in a child until it's available instead of doing this immediately in created:
watch: {
serverData(data) {
if (data)
this.loadImages()
}
}

How to pass an object from axios catch block to parent component with Vue.js

I am using Laravel 7 and Vue.js 2.
I want to pass an object of validation errors from the catch block of an axios call to a parent component but for some reasons it doesn't work.
This is the code of the axios call:
runReport: function() {
let self = this;
const url = "api/get_report?room="+this.formReport['room']+"&participant="+this.formReport['participant']+"&start="+this.formReport['start']+"&end="+this.formReport['end'];
axios.get(url)
.then((response) => {
console.log(response.data.data);
this.meetingsReport = response.data.data;
this.$emit('passMeetings', this.meetingsReport);
this.$emit('success');
this.errors = {};
})
.catch(function(error) {
console.log(error.response.data);
self.errors = error.response.data;
alert(self.errors);
self.$emit('failure');
self.$emit('passErrors', self.errors); //problem
console.log('call ended');
});
}
This is the code in the parent component:
<template>
<div>
<report-meeting #passMeetings="onPassMeetings" #failure="displayTable=false" #success="displayTable=true"></report-meeting>
<hr>
<validated-errors :errorsMeeting="errorsMeeting" #passErrors="onPassErrors" v-if="displayTable===false"></validated-errors>
<table-report :meetingsSelected="meetingsSelected" v-if="displayTable===true"></table-report>
</div>
</template>
<script>
import TableReport from "./TableReport.vue"
import ReportMeeting from "./ReportMeeting.vue"
import ValidatedErrors from "./ValidatedErrors.vue"
export default {
components: {
'table-report': TableReport,
'report-meeting': ReportMeeting,
'validated-errors': ValidatedErrors
},
mounted() {
console.log('Component mounted.');
},
data: function() {
return {
displayTable: false,
meetingsSelected: {},
errorsMeeting: {}
}
},
methods: {
onPassMeetings(value) {
console.log(value);
this.meetingsSelected = value;
},
onPassErrors(value) {
console.log('errors passed'); //never used
this.errorsMeeting = value;
}
}
}
</script>
In the console I visualize no errors (except an 422 Unprocessable Entity). The strange thing is that the first emit works (failure), but the second one doesn't work (passErrors).
In the parent function onPassErrors I put a console.log that is never used so I suppose that the function is never called.
Can help?
This is likely caused by an event name mismatch, which can occur when using in-DOM templates because HTML attributes are automatically lower-cased (#passErrors becomes #passerrors in the DOM).
When using the development build of Vue, you'd see a warning in the browser's console:
[Vue tip]: Event "passerrors" is emitted in component but the handler is registered for "passErrors". Note that HTML attributes are case-insensitive and you cannot use v-on to listen to camelCase events when using in-DOM templates. You should probably use "pass-errors" instead of "passErrors".
This is not a problem in single file components (demo 1) or string templates (demo 2), but if you must stick with in-DOM templates, custom event names are recommended to be kebab-case:
<!-- Parent.vue -->
<MyComponent #pass-errors="onPassEvent" />
// MyComponent.vue
runReport() {
this.$emit('pass-errors', /*...*/)
}
demo 3

Render content with Vue syntax / component string through AJAX call?

I have this HTML pattern:
<div id="New"> == ajax loaded content == </div>
It was easy to render HTML at server side and use innerHTML to inject the content into the right place.
Now I am trying to use Vue.js to do the same thing but render HTML at the client side. I can make this pattern into a component, let's say componentA, with template:
componentA
template:
`<div><slot></slot></div>`
It works if the HTML page content is something like:
<componentA>
<componentB></componentB> and some other none component content
</componentA>
The componentB is rendered and replaced the slot in componentA.
The problem is how do I use AJAX call (the call is made outside of componentA) to load
<componentB></componentB> and some other none component content
into the slot of componentA, and still make componentB to render correctly?
In real situation, the content from AJAX call can be
<componentB>, <componentC>, <componentD> ...
The following will treat componentB as regular string
in HTML:
<componentA>
<div id="New"></div>
</componentA>
in JS:
document.getElementById('New').innerHTML =
'<componentB></componentB> And some other none component content';
Is there a proper way to render string from AJAX return with Vue syntax as Vue?
One solution is put the ajax response like <component></component> to Component.template inside render function (Vue Guide: Render Function).
Like below demo:
const Foo = Vue.component('foo', {template: '<p>Foo - {{flag}}</p>', props: ['flag']})
const Bar = Vue.component('bar', {template: '<p>Bar - {{flag}}</p>', props: ['flag']})
const Generic = Vue.component('generic', {
render: function (createElement) {
return createElement('div', [
createElement('h3', 'Title'),
createElement('button', {on: {click: this.loadComponent}}, 'Load Component'),
this.dynamicComponent
&& createElement(Vue.component('v-fake-slot', {template:this.dynamicComponent, props: ['flag']}), {
props: {
flag: this.parent
}
})
])
},
props: ['parent'],
data () {
return {
components: ['<foo :flag="flag"></foo>', '<bar :flag="flag"></bar>'],
index: 0,
dynamicComponent: ''
}
},
methods: {
loadComponent: function () {
setTimeout(() => {
this.index += 1
this.dynamicComponent = this.components[this.index % 2]
}, 1000)
}
}
})
new Vue({
el: '#app',
data () {
return {
test: 'root'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<generic :parent="test"></generic>
</div>

Vue JS: this.$on event doesn't contain passed parameters

Here are my components. I've successfully emitted parameters from the child component to the parent, but not from the parent to the child. The console.log('In initDetail()'); line fires, which means the $on event is triggered, but param is undefined. I'm wondering why.
Parent component:
components: {
"child-component": ChildComponent
}
methods: {
loadDetail() {
this.$emit('loadDetailEvent', 'test');
}
}
Child Component:
mounted() {
this.$on('loadDetailEvent', this.initDetail(param));
}
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
}
I've also tried calling a function right away. Neither the console.log('$on event'); nor the console.log(param); lines print.
this.$on('loadDetailEvent', function(param){
console.log('$on event');
console.log(param);
});
As pointed out in the comments, there are a couple misconceptions in your code:
Your parent component is emitting a loadDetailEvent event when the loadDetail method is called. Your child component is also listening for a loadDetailEvent event, but it will only handle this event if it is emitted by its own Vue instance. If the Vue instance of the parent emits an event, the child will have no knowledge of it.
You are attempting to handle the loadDetailEvent by firing initDetail and passing the parameters of the event to that method. But, in your code you are simply setting the loadDetailEvent handler to be the result of calling this.initDetail(param) within the scope of the mounted hook. What you would want to do in this case would be to specify an anonymous function as the handler, which receives the param value and passes it to this.initDetail:
this.$on('loadDetailEvent', (param) => this.initDetail(param))
But, to get at the root of your issue: it seems like you want to call a child method when a parent method is called. You could do this a few different ways:
As #Ohgodwhy suggested, you could create a separate, global Vue instance to use as an event bus.
You could set a ref (say ref="child") on the child component tag in your parent's template and then call the child component's method directly via this.$refs.child.initDetail('test').
As #B.Fleming suggested, you could set a watcher on the child component to react to a changing prop value passed by the parent component. This gets a little tricky since you would need to maintain the value of the variable being passed as the prop (I would use a .sync modifier, as you can see below).
Here are example snippets of the above solutions:
Using an event bus:
let EventBus = new Vue();
Vue.component('child', {
template: `<div>Child</div>`,
mounted() {
EventBus.$on('loadDetailEvent', (param) => this.initDetail(param));
},
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
}
});
new Vue({
el: '#app',
methods: {
loadDetail() {
EventBus.$emit('loadDetailEvent', 'test');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<child></child>
<button #click="loadDetail">load</button>
</div>
Using a ref:
Vue.component('child', {
template: `<div>Child</div>`,
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
}
});
new Vue({
el: '#app',
methods: {
loadDetail() {
this.$refs.child.initDetail('test');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<child ref="child"></child>
<button #click="loadDetail">load</button>
</div>
Using a prop value and a watcher:
Vue.component('child', {
template: `<div>Child</div>`,
props: { loading: String },
methods: {
initDetail(param) {
console.log('In initDetail()');
console.log(param);
}
},
watch: {
loading(val) {
if (val) {
this.initDetail(val);
this.$emit('update:loading', '');
}
}
}
});
new Vue({
el: '#app',
data() {
return { payload: '' }
},
methods: {
loadDetail() {
this.payload = 'test';
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<child :loading.sync="payload"></child>
<button #click="loadDetail">load</button>
</div>

Unknown custom element: - did you register the component correctly?

I'm new to vue.js so I know this is a repeated issue but cannot sort this out.
the project works but I cannot add a new component. Nutrition component works, profile does not
My main.js
import Nutrition from './components/nutrition/Nutrition.vue'
import Profile from './components/profile/Profile.vue'
var Vue = require('vue');
var NProgress = require('nprogress');
var _ = require('lodash');
// Plugins
Vue.use(require('vuedraggable'));
// Components
Vue.component('nutrition', Nutrition);
Vue.component('profile', Profile);
// Partials
Vue.partial('payment-fields', require('./components/forms/PaymentFields.html'));
// Filters
Vue.filter('round', function(value, places) {
return _.round(value, places);
});
Vue.filter('format', require('./filters/format.js'))
// Transitions
Vue.transition('slide', {enterClass: 'slideInDown', leaveClass: 'slideOutUp', type: 'animation'})
// Send csrf token
Vue.http.options.headers['X-CSRF-TOKEN'] = Laravel.csrfToken;
// Main Vue instance
new Vue({
el: '#app',
components: {
},
events: {
progress(progress) {
if (progress === 'start') {
NProgress.start();
} else if (progress === 'done') {
NProgress.done();
} else {
NProgress.set(progress);
}
},
'flash.success': function (message) {
this.$refs.flash.showMessage(message, 'success');
},
'flash.error': function (message) {
this.$refs.flash.showMessage(message, 'error');
}
}
});
Profile.vue
<template>
<div class="reddit-list">
<h3>Profile </h3>
<ul>
</ul>
</div>
</template>
<script type="text/babel">
export default {
name: 'profile', // this is what the Warning is talking about.
components: {
},
props: {
model: Array,
}
}
</script>
profile.blade.php
#extends('layouts.app')
#section('title', 'Profile')
#section('body-class', 'profile show')
#section('content')
<script>
window.Laravel.profileData = []
</script>
<profile></profile>
#endsection
Whenever I try to go to this page I get:
[Vue warn]: Unknown custom element: <profile> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I tried doing a local component such as
Vue.components('profile', {
template: '<div>A custom component!</div>'
});
or even I tried adding the profile into the components in vue but still no luck, can anyone point me in the right direction?
Simply clear the cache on your browser if you run into this problem. Worked pretty well for me
I didn't fixed it but it was fixed by itself it appears some kind of magic called (CACHE). i did have my gulp watch running but i powered off my computer, and then ON again and it works.

Resources