Why is my Component not calling its init method? - javascript

I have a UIComponent which has a dependency of a faceless Component.
Main component metadata:
jQuery.sap.declare("MYAPP.Component");
sap.ui.core.UIComponent.extend("MYAPP.Component", {
metadata: {
dependencies: {
libs: [],
components: [
"MYAPP.Component2"
]
}, etc
The app indicates it has reached the faceless component, since if I make some deliberate syntax errors I do get the error messages when loading the webpage. I can also get a console.log("test") to print out from outside the sap.ui.core.Component.extend() code.
jQuery.sap.declare("Component2.Component");
console.log("outside test"); //this prints
sap.ui.core.Component.extend("Component2.Component", {
metadata: {
},
init: function(){
sap.ui.core.Component.prototype.init.apply(this, arguments);
console.log("component2 init test"); //this doesn't print
}
});
Perhaps there's an issue with my declarations of resources?
Some of index.html:
<script id='sap-ui-bootstrap' type='text/javascript'
src='resources/sap-ui-core.js'
data-sap-ui-theme='sap_bluecrystal'
data-sap-ui-libs='sap.m, sap.me'
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-xx-supportedLanguages="en"
data-sap-ui-resourceroots='{"MYAPP":"./"}'></script>
<script>
sap.ui.localResources("view");
sap.ui.localResources("utils");
sap.ui.localResources("control");
sap.ui.localResources("Component2");
My folder structure:
MYAPP
/Component2 //faceless component folder
Component.js
/view //views and controllers folder
Component.js //main component
index.html

The answer I've found with some help from a user at SCN (here) is that the parent component's metadata will call a function to load the new component without instantiating it. So you must explicitly create the component as well.
I have added a simple sap.ui.component in the init() function of my parent component.
sap.ui.component({ name: "MYAPP.Component2" });
Now the new component is loaded and instantiated before the parent component. Hopefully this post will help others using faceless components, as I found little documentation myself when researching this problem.

Related

External javascript from public/index.html not loading in Vue component

I want to use Mathjax on my website. I put in the <head> section of public/index.html:
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax#3/es5/tex-mml-chtml.js"></script>
And, in my component :
<template>
<div v-html="note_content"></div>
</template>
<script>
import { typeset, dummy_typeset } from '../assets/js/text_processing.js';
import showdown from 'showdown';
const converter = new showdown.Converter({ tables: true });
export default {
data () {
return {
}
},
computed: {
note_content: function() {
return typeset(this.exchange_data);
}
},
props: ['exchange_data'],
watch: {
note_content: function() {
Mathjax.typesetPromise();
}
}
}
</script>
However on runtime, I get the error :
Uncaught (in promise) ReferenceError: Mathjax is not defined
What I do not understand is that the css which is located in the head of public/index.html is correctly loaded and rendered in the component. Also I read elsewhere that loading external javascript this way should make it available to all components. What is wrong ?
I think it is connected to webpack config, but i might be wrong. Anyway, have you tried this method?
How to add external JS scripts to VueJS Components?
It enforces the script to be loaded.
Contrarily to what is written at https://docs.mathjax.org/en/latest/web/typeset.html, the syntax for asynchronous rendering is not :
Mathjax.typesetPromise()
But (notice the case):
MathJax.typesetPromise()
The typo could be detected from the surrounding context.
Also not that to use typeset Mathjax in a Vue component, the virtual DOM should be processed before calling the typesetPromise method, so an example of working code would be:
watch: {
note_content: function() {
this.$nextTick(MathJax.typesetPromise);
}
}

How to use an Vanilla Javascript Class in a Vue Component?

I have a laravel project which globally registers vue in the app.js file. Vue works in my project as I have it working in another part of my app. What makes this situation unique is I do not have somewhere, like a blade file, to pass my vue component through as a medium to be used. I have a vanilla js file and a vue component. I want to be able to use the methods created in my test.js file inside of my testing.vue file. I am simply trying to pass some data from my js to my vue and then console.log() it out to ensure the data is being passed properly. I do use npm run dev to compile assets. The code is pretty boiler plate at this point since my main objective right now is to just pass the data properly. I did confirm the import path as well and it is correct. Not sure why the console.log() is not showing in the browser. This is my current code:
Test.js
export class Test {
testing() {
console.log('this is a test');
}
}
Testing.vue
<template>
<div>
</div>
</template>
<script>
import {Test} from '../../js/Test';
export default {
name: 'Testing',
mounted() {
console.log(Test.testing());
}
}
</script>
You must create an instance of the Test class in order to use the method. In your Testing.vue file:
<template>
<div>
</div>
</template>
<script>
import {Test} from '../../js/Test';
export default {
name: 'Testing',
mounted() {
// IMPORTANT to create instance of a class
const myTestInstance = new Test();
console.log(myTestInstance.testing());
}
}
</script>
The method testing() on class Test is not a static method and thus need object to invoke. Alternately, if you don't want to create object, then you can declare the method as static as shown below:
export class Test {
static testing() {
console.log('this is a test');
}
}
You can then us it like this: Test.testing().

In Vue, what parameters does createElement() actually get?

(This is a further question after this one: In Vue, what is the relationship of template, render, VNode?)
Found createElement()'s source code (from here):
function createElement (
context,
tag,
data,
children,
normalizationType,
alwaysNormalize
) { ... }
Code
main.js: (partly)
import App from './App.vue'
new Vue({
el: '#app',
render: h => h(App),
router
})
App.vue:
<template>
<div id="content">
<!-- <img src="./assets/logo.png" alt="">-->
<router-view></router-view>
</div>
</template>
<script>
export default {}
</script>
Questions
A. In App.vue, export default {} will return an empty object, is that correct?
B. In main.js:
B-1. import App from './App.vue', will bind App to the empty object, is that correct?
B-2. render: h => h(App), if the answer to previous two questions were yes, then here h (which is alias of vue's createElement() function) will get a single parameter, for context with value as empty object, is that correct?
B-3. But, that doesn't make much sense, because the template are actually rendered in the browser in my test, it's not empty.
C. So, in the code above, how many parameters are passed to createElement(), and what is the value of each of them?
D. Which part did I misunderstand about Vue or ES6 in above description?
Single-file components, i.e. .vue files, go through special processing by Vue Loader. See:
https://vue-loader.vuejs.org/#what-is-vue-loader
This will interfere in the export/import process so that everything works as expected.
So:
In App.vue, export default {} will return an empty object, is that correct?
It would for a .js file. But for a .vue file it won't, not once it's been through Vue Loader.
Try console logging the value of App in main.js to see what actual gets imported. You should see a render function (compiled from your template) as well a few other little supporting pieces.
Update
To answer your follow-up question from the comments.
createElement just creates a VDOM node. It does not create the component instance for that node. Vue further processes the tree of VDOM nodes after they are returned from render and creates any child component instances it needs. It'll then call render on those components to generate the VDOM nodes that they need and so on until everything is rendered.
I've created a small example to try to illustrate this. It consists of a parent with two component children. Notice that the logging for creating and rendering the child components does not happen straight away when calling createElement. Instead it happens after the render function has returned.
const child = {
render (createElement) {
console.log('rendering the child')
return createElement('div', null, ['hello'])
},
created () {
console.log('child is created')
}
}
new Vue({
el: '#app',
components: {
child
},
created () {
console.log('parent is created')
},
render (createElement) {
console.log('rendering the parent')
const children = [
createElement('child'),
createElement('child')
]
const root = createElement('div', null, children)
console.log('finished calling createElement')
return root
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app"></div>
In App.vue, export default {} will return an empty object, is that correct?
No, .vue files are Single File Components that are compiled by vue-loader.
B. In main.js:
B-1. import App from './App.vue', will bind App to the empty object, is that correct?
No, see above.
B-2. render: h => h(App), if the answer to previous two questions were yes, then here h (which is alias of vue's createElement() function) will get a single parameter, for context with value as empty object, is that correct?
No. App is a component ready to be attached to a Vue Instance. import App is simply importing the compiled file as an ES6 module.
C. So, in the code above, how many parameters are passed to createElement(), and what is the value of each of them?
If you console.log(App) you will see the compiled module (at minimum):
{
render: function render() {}
staticRenderFns: Array[0]
_compiled: true
beforeCreate: Array[2]
__file: "/src/App.vue"
beforeDestroy: Array[1]
_Ctor: Object
}
D. Which part did I misunderstand about Vue or ES6 in above description?
Both. Vue files are compiled to become Vue Instances. ES6 modules encapsulate all sorts of functionality, and expose this functionality to other JavaScript files, as libraries.

How to navigate to a route in Framework7 + Vue app?

How to navigate to a route in Framework7 & VueJs app.
I want to show a slpash screen and navigate to next page after two seconds. Code is pasted below.
<template>
...
</template>
<script>
export default {
name: "splash",
mounted: function() {
this.$router.load({url: '/about/'}); // error
},
data: function () {
return {
msg : "hello"
}
}
}
</script>
Note: This is app component(Main.vue).
Finally this worked for me.
this.$f7.mainView.router.load({url: "/user-settings"})
From Docs
Please note, that $route and $router component properties are only available inside of custom page components (and their child components) that you load according to routes. In parent components (like in View, or where you init your Vue app instance) they are not accessible as your app may have few routers (Views) at a time. So in this case use access to initialized View Instance, e.g. $f7.views.main.router or $f7.mainView.router

VueJs templating. How to load external templates

I'm new to Vue.js, I've used AngularJS for some time and in angular we used to load templates such as,
template: '/sometemplate.html',
controller: 'someCtrl'
How can we do such a thing in Vue, instead of keeping large HTML templates inside JavaScript like this,
new Vue({
el: '#replace',
template: '<p>replaced</p>'
})
This is OK for small templates but for large templates is this practical?
Is there a way to load external template HTML or use HTML template inside a script tag like in Vue?
<script type="x-template" id="template">HTML template goes here</html>
You can use the script tag template by just referring to its id.
{
template: '#some-id'
}
Though, I highly recommend using vueify (if you use browserify) or vue-loader (if you use webpack) so you can have your components stored in nice little .vue files like this.
Also, the author of Vue wrote a nice post about the topic of external template urls:
https://vuejs.org/2015/10/28/why-no-template-url/
You can try this:
for Vue2 : https://github.com/FranckFreiburger/http-vue-loader
for Vue3 : https://github.com/FranckFreiburger/vue3-sfc-loader
Example (Vue2) :
new Vue({
components: {
'my-component': httpVueLoader('my-component.vue')
},
...
Example (Vue3) :
Vue.createApp({
components: {
'my-component': Vue.defineAsyncComponent(() => loadModule('./myComponent.vue', opts))
},
...
David, that is a nice example, but what's the best way to make sure the DOM is compiled?
https://jsfiddle.net/q7xcbuxd/35/
When I simulate an async operation, like in the example above, it works. But as soon as I load an external page "on the fly", Vue complains because the DOM is not ready.
More specifically:
Uncaught TypeError: Cannot set property 'vue' of undefined
Is there a better way to do this than to call $compile when the page has loaded? I've tried with $mount, but that didn't help.
UPDATE:
Never mind, I finally figured out how to do it:
Vue.component('async-component', function (resolve, reject) {
vue.$http.get('async-component.html', function(data, status, request){
var parser = new DOMParser();
var doc = parser.parseFromString(data, "text/html");
resolve({
template: doc
});
});
});
And in the actual template, I removed the
<script id="someTemplate" type="text/x-template"></script>
tags and only included the html.
(This solution requires the http loader from https://cdnjs.cloudflare.com/ajax/libs/vue-resource/0.1.10/vue-resource.min.js)
I've tried http-vue-loader and it works fine.
This library is easy to use and has good documentation and examples
Although you can't load templates from filed directly you still can keep html in separate single-file components. You can even skip <script>...</script> part.
Usage (from loader's documentation)
my-component.vue
<template>
<div class="hello">Hello {{who}}</div>
</template>
index.html
<!doctype html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/http-vue-loader"></script>
</head>
<body>
<div id="my-app">
<my-component></my-component>
</div>
<script type="text/javascript">
new Vue({
el: '#my-app',
components: {
'my-component': httpVueLoader('my-component.vue')
}
});
</script>
</body>
</html>
Both files should be placed in one folder at the same level
1. In Vue 2.x I recommend sticking with convention by using .vue files but instead, inverting the order of imports:
// template.vue
<template>
<div class="helloworld">
<h1>Hello world</h1>
</div>
</template>
<script>
import src from './src'
export default src
</script>
and in a separate file
// src.js
export default {
name: 'helloworld',
props: {},
...
}
Then in your component registration
import helloworld from './helloworld/template.vue'
new Vue({
components: {
'helloworld': helloworld
},
...})
This way you get the best of both worlds and you don't have to force yourself to build templates within a string.
2. If you want to lazy load, apparently there is a way to do so in Vue 2.x
new Vue({
components: {
'helloworld': () => import(/* webpackChunkName: "helloworld" */ './helloworld/template.vue')
},
...})
This will load helloworld.js (which will contain all that component's code) on request of that page in the browser.
Of course, all the above assumes you are using ES6 with import capabilities
there are at least 2 ways to achieve what you want, on of them (x-templates) already mentioned by Bill Criswell, but I think it worth to add an example
Define your component like this
Vue.component('my-checkbox', {
// id of x-template
template: '#my-template'
});
Add html file with your x-template (id should match the one you specified in the component)
<script type="text/x-template" id="my-template">...</script>
Another approach (and I like this one better) would be to use inline template
Define your template like this
Vue.component('my-template', {});
Add html file with your component and template inside it
<my-template inline-template>place for your html</my-template>
Just don't forget to add inline-template attribute, otherwise it won't work
You can use this approach with superagent:
var promise = superagent.get("something.html")
.end(function (error, response) {
if (error) {
console.error("load of something.html failed", error));
return;
}
var parser = new DOMParser()
var doc = parser.parseFromString(response.text, "text/html");
document.body.appendChild(doc.scripts[0]);
});
Just put your <script> tag based template inside of something.html on your server.
If you are using jQuery, .load should work.
Just make sure this completes before the DOM in question is compiled by Vue. Or use $mount to manually set things up.
Use browserify to bundle everything like this:
//Home.js
import Vue from 'vue';
var Home = Vue.extend({
template: require('./Home.vue')
});
export default Home;
//Home.vue
<h1>Hello</h1>
// And for your browserify bundle use a transform called stringify
... .transform(stringify(['.html', '.svg', '.vue', '.template', '.tmpl']));

Categories