I try to use Mixins with Vue.js. But I encounter several issues with them :/
This is my current code for my two test modules :
ErrorBaseMixin.vue
<script>
import ErrorAlert from './ErrorAlert';
export const ErrorBaseMixin = {
data() {
return {
// Errors management
error_display: true,
error_data: {
level: "warning",
time: 0,
status: 200,
message: ""
}
}
},
methods: {
// ------------------------------------------------------------------------
// Errors management functions
// ------------------------------------------------------------------------
error_function_show_error: function() {
try {
this.$refs.error_component.launch();
}
catch {}
},
callback_error_catched: function(e) {
if(e.message === 'Network Error'){
this.error_data.message = "<strong>There was a network error :</strong> The connection is broken or the server is not started.";
this.error_data.level = "danger";
}
else {
this.error_data.message = "An error occured : " + e.message;
this.error_data.level = "warning";
}
this.error_function_show_error();
},
},
components: {
ErrorAlert
}
}
export default ErrorBaseMixin;
</script>
Test.vue
<template>
<ErrorAlert
:error_display="error_display"
:error="error_data"
ref="error_component"
/>
</div>
</template>
<script lang="js">
import {ErrorBaseMixin} from '../../../parts/ErrorBaseMixin.vue';
export default {
mixins: [ErrorBaseMixin],
name: 'Test_elt',
created() {
this.REST_ADDR = "test/test";
},
data() {
return {
field: {
id: '55',
name: 'test'
}
}
},
methods: {
}
}
</script>
But when I compile the last module, I have the following errors in my browser console :
[Vue warn]: Property or method "error_data" is not defined on the
instance but referenced during render. Make sure that this property is
reactive, either in the data option or for class-based components, by
initializing the property.
[Vue warn]: Unknown custom element: - did you register
the component correctly? For recursive components, make sure to
provide the "name" option.
But... Everything is working fine. So I don't understand why I have these errors
You must change ErrorBaseMixin.vue to ErrorBaseMixin.js:
import ErrorAlert from './ErrorAlert';
const ErrorBaseMixin = {
data() {
return {
// Errors management
error_display: true,
error_data: {
level: "warning",
time: 0,
status: 200,
message: ""
}
}
},
methods: {
// ------------------------------------------------------------------------
// Errors management functions
// ------------------------------------------------------------------------
error_function_show_error: function() {
try {
this.$refs.error_component.launch();
}
catch {}
},
callback_error_catched: function(e) {
if(e.message === 'Network Error'){
this.error_data.message = "<strong>There was a network error :</strong> The connection is broken or the server is not started.";
this.error_data.level = "danger";
}
else {
this.error_data.message = "An error occured : " + e.message;
this.error_data.level = "warning";
}
this.error_function_show_error();
},
},
components: {
ErrorAlert
}
}
export default ErrorBaseMixin;
And then import in your component:
import {ErrorBaseMixin} from '../../../parts/ErrorBaseMixin.js';
export default {
mixins: [ErrorBaseMixin],
...
Note: Take care how import and export, I have changed the way.
Related
How can you create a dynamic import of an instance in vue using a parameter?
I would like to dynamically import the language into flatpickr-vue.
import { de } from 'flatpickr/dist/l10n/de.js';
how do I bring the "locale" parameter into the import path dynamically?
<akaunting-date
...
:config="{
...
locale: '{{ language()->getShortCode() }}',
}"
...
></akaunting-date>
Link to original code
<template>
<base-input :label="title"
:name="name"
:class="[
{'readonly': readonly},
{'disabled': disabled},
formClasses
]"
:footer-error="formError"
:prependIcon="icon"
:readonly="readonly"
:disabled="disabled"
>
<flat-picker slot-scope="{focus, blur}"
#on-open="focus"
#on-close="blur"
:config="config"
class="form-control datepicker"
v-model="real_model"
#input="change"
:readonly="readonly"
:disabled="disabled">
</flat-picker>
</base-input>
</template>
<script>
import flatPicker from "vue-flatpickr-component";
import "flatpickr/dist/flatpickr.css";
import { de } from 'flatpickr/dist/l10n/de.js';
export default {
name: 'akaunting-date',
components: {
flatPicker
},
props: {
title: {
type: String,
default: '',
description: "Modal header title"
},
placeholder: {
type: String,
default: '',
description: "Modal header title"
},
readonly: {
type: Boolean,
default: false,
description: "Input readonly status"
},
disabled: {
type: Boolean,
default: false,
description: "Input disabled status"
},
formClasses: null,
formError: null,
name: null,
value: {
default: null,
description: "Input value defalut"
},
model: {
default: null,
description: "Input model defalut"
},
config: null,
icon: {
type: String,
description: "Prepend icon (left)"
}
},
data() {
return {
real_model: this.model
}
},
mounted() {
this.real_model = this.value;
if (this.model) {
this.real_model = this.model;
}
this.$emit('interface', this.real_model);
},
methods: {
change() {
this.$emit('interface', this.real_model);
this.$emit('change', this.real_model);
}
}
}
</script>
Link to original code
i think i'm on the right track ...
computed: {
config() {
return {
locale: require('flatpickr/dist/l10n/' + this.locale + '.js').default.en,
}
}
},
now I would have to change the ".en" in .default dynamically. is that possible?
is not yet completely dynamic and there is still the following error message, which I do not understand
[Vue warn]: The computed property "config" is already defined as a prop.
Because the module you want is not known at runtime, you will have to import it asynchronously, else your script will have to wait for the file to be fetched and parsed..
If you don't want to use the promise.then() route, then you can opt for this:
// do something with the 'locale module' once it is available
function sayHelloTo(locale, name) {
console.log(locale.helloText + ' ' + name)
}
// get the module asyncly
async function loadLocale(countryCode, thenDoThis, ...optionalArgs) => {
const locale = await import(`flatpickr/dist/l10n/${countryCode}.js`)
thenDoThis(locale, ...optionalArgs)
})
loadLocale('DE', sayHelloTo, 'John') // runs asyncly
I'm studying javascript and mithril.js 1.1.6. I'm writing down a simple web app in which users land on a page where he can login. Users who already did login land on a different page. I'm trying this using conditional routing, here is the main component:
const m = require("mithril");
...
import Eventbus from './whafodi/eventbus.js';
import WelcomePage from './ui/welcome.js';
import User from './model/user.js';
var eventbus = new Eventbus();
function MyApp() {
return {
usrAuth: function() {
m.route(document.body, "/", {
"/": { view: () => m("p", "hello")}
})
},
usrNotAuth: function() {
m.route(document.body, "/", {
"/": { render: v => m(WelcomePage, eventbus) }
})
},
oninit: function(vnode) {
vnode.state.user = new User();
eventbus.subscribe({
type: "login",
handle: function(action) {
vnode.state.user.token = action.token;
console.log(JSON.stringify(vnode.state.user));
}
});
},
view: function(vnode) {
if(vnode.state.user.token) {
this.usrAuth();
} else {
this.usrNotAuth();
}
}
}
};
m.mount(document.body, MyApp);
MyApp is the main component. It check if user has a token, then return the proper route. This is the component that is in charge to let users login:
const m = require("mithril");
const hellojs = require("hellojs");
function TopBar(node) {
var bus = node.attrs.eventbus;
function _login() {
hellojs('facebook').login({scope:'email'});
}
return {
oninit: function(vnode) {
hellojs.init({
facebook: XXXXXXX,
}, {
redirect_uri: 'http://localhost'
});
hellojs.on('auth.login', auth => {
var fbtoken = auth.authResponse.access_token;
m.request({
method:"POST",
url:"./myapp/login/fb/token",
data:auth.authResponse,
background: true
}).then(function(result){
console.log(result);
bus.publish({ type: "login", token: result.jwttoken });
m.route.set("/");
}, function(error){
console.log(error);
bus.publish({ type: "login", token: "" });
});
});
},
view: function(vnode) {
return m("div", [
m("button", { onclick: _login }, "Login")
]);
}
}
}
export default TopBar;
TopBar component occurs in the WelcomePage component mentioned in the main one. TopBar renders a button and use hello.js to login. It uses the EventBus bus parameter to tell main component user logged in (there is an handler in main component to update the user model). Once user logins, event is fired and main component updates the user model. Good. Now, how can trigger the main component to load the right route?
I read mithril'docs again and I found that RouteResolvers perfectly suit my needs. Here is an example:
var App = (function() {
var login;
function isLoggedIn(component) {
if(login) {
return component;
} else {
m.route.set("/hey");
}
}
return {
oninit: function(vnode) {
EventBus.subscribe({
type: "login",
handle: function(action) {
console.log("incoming action: " + JSON.stringify(action));
login = action.value;
}
});
},
oncreate: function(vnode) {
Foo.eventbus = EventBus;
Bar.eventbus = EventBus;
Hey.eventbus = EventBus;
m.route(document.body, "/hey", {
"/foo": {
onmatch: function(args, requestedPath, route) { return isLoggedIn(Foo); }
},
"/bar": {
onmatch: function(args, requestedPath, route) { return isLoggedIn(Bar); }
},
"/hey": Hey
});
},
view: function(vnode) {
return m("div", "home..");
}
};
})();
Eventbus is used to let components communicate with App. They fire events (login type events) that App can handle. I found convenient to pass Eventbus the way oncreate method shows, I can use Eventbus in each component's oncreate to let components fire events.
I am in the process of integrating AmazonPay into a React SPA. The classic integration relies on script tags and callbacks (docs).
Here is one example from the button widget:
<head>
<script type='text/javascript'>
window.onAmazonLoginReady = function() {
amazon.Login.setClientId('CLIENT-ID');
};
window.onAmazonPaymentsReady = function() {
showButton();
};
</script>
<script async="async" src='https://static-na.payments-amazon.com/OffAmazonPayments/us/sandbox/js/Widgets.js'>
</script>
</head>
<body>
. . .
<div id="AmazonPayButton">
</div>
...
<script type="text/javascript">
function showButton(){
var authRequest;
OffAmazonPayments.Button("AmazonPayButton", "SELLER-ID", {
type: "TYPE",
color: "COLOR",
size: "SIZE",
authorization: function() {
loginOptions = {scope: "SCOPES",
popup: "POPUP-PARAMETER"};
authRequest = amazon.Login.authorize (loginOptions,
"REDIRECT-URL");
},
onError: function(error) {
// your error handling code.
// alert("The following error occurred: "
// + error.getErrorCode()
// + ' - ' + error.getErrorMessage());
}
});
};
</script>
. . .
<script type="text/javascript">
document.getElementById('Logout').onclick = function() {
amazon.Login.logout();
};
</script>
</body>
When using React, the div with id="AmazonPayButton" isn't on the page until React mounts the div, causing the window.showButton() function to fail.
To circumvent this issue, I've wrapped the function showButton() definition inside window.showButton():
window.onAmazonPaymentsReady = function() {
window.showButton = function () {
var authRequest;
// eslint-disable-next-line no-undef
OffAmazonPayments.Button("AmazonPayButton", "%REACT_APP_AMAZON_SELLER_ID_SANDBOX%", {
type: "PwA",
color: "Gold",
size: "medium",
authorization: function () {
loginOptions = {
scope: "profile postal_code payments:widget payments:shipping_address",
popup: true
};
authRequest = amazon.Login.authorize(loginOptions, "%PUBLIC_URL%/pay-with-amazon");
},
onError: function (error) {
console.log(error.toString())
}
});
};
};
The component which contains the AmazonPay div can now be called on componentDidMount:
import React, {Component} from 'react'
class AmazonMethod extends Component {
componentDidMount () {
window.showButton()
}
render() { return <div id="AmazonPayButton"></div>}
}
export default AmazonMethod
I am confused how to access the onError callback from inside my React component. How do I listen for the callback and respond appropriately?
This question applies to AddressWidget and WalletWidget as well; they all rely on script tag callbacks.
Update:
I've written a post which summarizes how to integrate AmazonPay with client side React.
Why don't you just pass in a function to your showButton function in componentDidMount that onError can call?
Something like this:
window.onAmazonPaymentsReady = function() {
window.showButton = function (errorFunc) {
var authRequest;
// eslint-disable-next-line no-undef
OffAmazonPayments.Button("AmazonPayButton", "%REACT_APP_AMAZON_SELLER_ID_SANDBOX%", {
type: "PwA",
color: "Gold",
size: "medium",
authorization: function () {
loginOptions = {
scope: "profile postal_code payments:widget payments:shipping_address",
popup: true
};
authRequest = amazon.Login.authorize(loginOptions, "%PUBLIC_URL%/pay-with-amazon");
},
onError: function (error) {
console.log(error.toString())
errorFunc(error)
}
});
};
};
import React, {Component} from 'react'
class AmazonMethod extends Component {
componentDidMount () {
window.showButton(this.errorFunc)
}
errorFunc = (error) => {
console.log(error);
this.setState({
amazonError: error
});
}
render() { return <div id="AmazonPayButton"></div>}
}
export default AmazonMethod
I have two demos for same library under repo, with demo.
The main difference is that, one is for browser use and the other is for node use.
However browser one will have error
index.js:1 Uncaught SyntaxError: Identifier 'HeaderComp' has already been declared
What is the main cause?
Update:
Please keep in mind I do not declare a variable twice! I also tried to add console log at the top to ensure the script is executed once!
var HeaderComp = {
name: 'HeaderComp',
template: `
Back
{{ r.title }}
`,
mixins: [VueTopDown.VueTopDownItem],
computed: {
routes () {
return [
{ href: '/page', title: 'Page' },
{ href: '/hello-vue', title: 'HelloVue' }
]
}
}
}
var FooterComp = {
name: 'FooterComp',
template: `{{ vueFooter }}`,
mixins: [VueTopDown.VueTopDownItem],
data () {
return {
vueFooter: 'This is Vue Footer'
}
}
}
var ContentComp = {
name: 'ContentComp',
template: ``,
mixins: [VueTopDown.VueTopDownItem],
computed: {
innerHTML () {
var root = document.createElement('div')
root.innerHTML = this[VTDConstants.OUTER_HTML]
return root.querySelector('*').innerHTML
}
}
}
var HelloVue = {
template: `Hello Vue`,
props: ['clazz'],
inheritAttrs: false
}
var Page = {
template: ``,
props: ['clazz', 'innerHTML'],
inheritAttrs: false
}
var router = new VueRouter([
{ path: '/hello-vue', component: HelloVue },
{ path: '/page', component: Page },
{ path: '*', redirect: '/page' }
])
var inst = new Vue({
router,
mixins: [VueTopDown.VueTopDown],
components: {
HeaderComp: HeaderComp,
FooterComp,
ContentComp
},
data () {
return {
[VueTopDown.VTDConstants]: {
'header': HeaderComp,
'footer': FooterComp,
'.content': ContentComp
}
}
}
})
inst.$mount('#app')
Also keep in mind that similar code works fine in node environment but fails in browser!
Doesn't occur if commenting out inst.$mount('#app')
Expect
The expected behavior of browser should be same as that of node.
I am trying to create a component with Vue.js. My component is currently defined like this:
MyComponent.vue
<template id="my-template">
<div>
<button class="btn" v-on:click="increment">increment</button>
</div>
</template>
<script type="text/javascript">
Vue.component('incrementer', {
template: '#my-template',
props: {
i: {
type: Number,
default: 1,
}
},
data: function() {
return {
count: 0
}
},
methods: {
increment: function() {
this.count = this.count + this.i;
}
}
});
</script>
I am trying to create some automated tests for this component. In an attempt to do this, I have the following:
my-component.spec.js
const MyComponent = require('../src/MyComponent.vue');
describe('my-component', function() {
// Inspect the raw component options
it('has a created hook', () => {
expect(typeof MyComponent .created).toBe('function')
});
});
I am trying to run this test via Jasmine through Gulp. In Gulp, my test task looks like this:
gulpfile.js
gulp.task('test', ['build'], function() {
return gulp.src(['test/**/*spec.js'])
.pipe(jasmine({
timeout: 10000,
includeStackTrace: false,
color: false
}))
;
});
When this task gets executed, I receive the following error:
(function (exports, require, module, __filename, __dirname) { <template id="my-template">
^
SyntaxError: Unexpected token <
I don't understand why I'm receiving this error. What do I need to do to test a component in Vue.js via Jasmine?
Thank you!
According to Vue Docs:
In terms of code structure for testing, you don’t have to do anything special in your components to make them testable. Just export the raw options
When you test that component, all you have to do is import the object along with Vue to make many common assertions
So in you MyComponent.vue file:
<template>
<div>
<button class="btn" v-on:click="increment">increment</button>
</div>
</template>
<script type="text/javascript">
export default {
props: {
i: {
type: Number,
default: 1,
}
},
data: function() {
return {
count: 0
}
},
methods: {
increment: function() {
this.count = this.count + this.i;
}
}
}
</script>
Then in your my-component.spec.js:
const Vue = reuqire("vue")
const MyComponent = require('../src/MyComponent.vue');
describe('my-component', function() {
// Inspect the raw component options
it('has a created hook', () => {
expect(typeof MyComponent.created).toBe('function')
});
});