How to use an external non vue script in vue - javascript

I try to use an external script (https://libs.crefopay.de/3.0/secure-fields.js) which is not vue based
I added the script via -tags into index.html
But when I try to intsanciate an object, like in the excample of the script publisher.
let secureFieldsClientInstance =
new SecureFieldsClient('xxxxx',
this.custNo,
this.paymentRegisteredCallback,
this.initializationCompleteCallback,
configuration)
Vue says "'SecureFieldsClient' is not defined"
If I use this.
let secureFieldsClientInstance =
new this.SecureFieldsClient('xxxxx',
this.custNo,
this.paymentRegisteredCallback,
this.initializationCompleteCallback,
configuration)
secureFieldsClientInstance.registerPayment()
Vue says: Error in v-on handler: "TypeError: this.SecureFieldsClient is not a constructor"
My Code:
methods: {
startPayment () {
this.state = null
if (!this.selected) {
this.state = false
this.msg = 'Bitte Zahlungsweise auswählen.'
} else {
localStorage.payment = this.selected
let configuration = {
url: 'https://sandbox.crefopay.de/secureFields/',
placeholders: {
}
}
let secureFieldsClientInstance =
new SecureFieldsClient('xxxxx',
this.custNo,
this.paymentRegisteredCallback,
this.initializationCompleteCallback,
configuration)
secureFieldsClientInstance.registerPayment()
// this.$router.replace({ name: 'payment' })
}
}
}
Where is my mistake?
EDIT:
Updated the hole question

Here is a minimal Vue app for the context your provided, which works:
https://codepen.io/krukid/pen/voxqPj
Without additional details it's hard to say what your specific problem is, but most probably the library gets loaded after your method executes, so window.SecureFieldsClient is expectedly not yet defined. Or, there is some runtime error that crashes your script and prevents your method from executing. There could be some other more exotic issues, but lacking a broader context I can only speculate.
To ensure your library loads before running any code from it, you should attach an onload listener to your external script:
mounted () {
let crefPayApi = document.createElement('script')
crefPayApi.onload = () => this.startPayment()
crefPayApi.setAttribute('src', 'https://libs.crefopay.de/3.0/secure-fields.js')
document.head.appendChild(crefPayApi)
},

I found the solution.
the import was never the problem.
I had just to ignore VUEs/eslints complaining about the missing "this" via // eslint-disable-next-line and it works.
So external fuctions/opbjects should be called without "this" it seems.
let secureFieldsClientInstance =
new SecureFieldsClient('xxxxx',
this.custNo,
this.paymentRegisteredCallback,
this.initializationCompleteCallback,
configuration)

You could download the script and then use the import directive to load the script via webpack. You probably have something like import Vue from 'vue'; in your project. This just imports vue from your node modules.
It's the exact same thing for other external scripts, just use a relative path. When using Vue-CLI, you can do import i18n from './i18n';, where the src folder would contain a i18n.js
If you really want to use a CDN, you can add it like you normally would and then add it to the externals: https://webpack.js.org/configuration/externals/#externals to make it accessible from within webpack

Related

Vue.js 2 + Typescript - global variables becomes undefined after build

I have a Vue.js 2 project with Typescript. In the main.ts file, I've declared 2 variables, that I've wanted to access globally in my project:
// ...
Vue.prototype.$http = http; // this is the library imported from another file, contains various methods such as `get`, `post` etc.
Vue.prototype.$urls = urls; // this is JSON object, also imported from another file
new Vue({
store,
render: (h) => h(App),
}).$mount('#app');
In one of my components, let's call it User I have following mounted code block:
mounted(): void {
this.$http.get(`${this.$urls.getUser}/${this.userId}`);
}
Everything works fine when I'm running a local server (via npm run serve command), but when I create an app build (via npm run build command) and enter the app on the server (or the index.html file on my hdd) I receive following error:
TypeError: Cannot read property 'get' of undefined
at VueComponent.value (user.ts:62) // <-- this line is the one with $http.get from `mounted` hook
I'm not sure how to proceed with this, I've blindly tried to add those global values to various places e.g. in http.d.ts file I have the following:
import { KeyableInterface } from '#/interfaces/HelperInterfaces';
import Vue from 'vue';
declare module 'vue/types/vue' {
interface VueConstructor {
$http: KeyableInterface;
}
}
declare module 'vue/types/vue' {
interface Vue {
$http: KeyableInterface;
}
}
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
http?: KeyableInterface
}
}
(I've also created urls.d.ts with similar code)
UPDATE #1:
I've tried also following approach - in my main.ts file:
const helperModules = {
/* eslint-disable-next-line #typescript-eslint/no-explicit-any */
install: (vueInstance: any) => {
vueInstance.prototype.$http = http;
vueInstance.prototype.$urls = urls;
},
};
Vue.use(helperModules);
But it still doesn't work (same error).
UPDATE #2:
I've also imported http utility into my user component, and added following console.log to existing mounted callback:
console.log(http, this.$http)
And while working on my localhost, it returns me twice the same value, but when I create a build it returns me:
Module {__esModule: true, Symbol(Symbol.toStringTag): "Module"}, undefined
Similar thing happens, when I add console.log(urls, this.$urls) - imported module is being logged, while prototyped one returns undefined.
Any thoughts? Will appreciate any help.
Finally I've overcome the problem by moving the prototyping parts from main.ts to App.ts file.
I'm not 100% sure if it's a valid "the Vue.js way" of solving this, as I've always declared that in main.js file - but I was using then JavaScript & it was "just working" as expected.

How to use external JavaScript objects in Vue.js methods

I'm trying to get Stripe working with my Vue.js 2 application. For PCI-DSS reasons, Stripe requires that their Javascript is always loaded from js.stripe.com. I've followed the instructions in:
How to add external JS scripts to VueJS Components
How to include a CDN to VueJS CLI without NPM or Webpack?
but I get a 'Stripe' is not defined error when I try to use the library. These solutions seemed to be aimed at merely getting a <script> tag into the output HTML (e.g. for analytics), not actually consuming the functions and objects in that script.
Here's what my component Javascript looks like:
<script>
export default {
name: "PaymentPage",
mounted() {
let stripeScript = document.createElement('script');
stripeScript.setAttribute('src', 'https://js.stripe.com/v3/');
document.head.appendChild(stripeScript);
let s = Stripe('pk_test_Fooo');
console.log(s);
}
}
</script>
I also tried adding the script tag to my public/index.html file instead, but I get the same outcome. This would probably be my preferred route, since Stripe encourages developers to import their script on all pages on the site.
<!DOCTYPE html>
<html lang="en">
<head>
// ...
<script src="https://js.stripe.com/v3/"></script>
</head>
How can I pull a script from an external CDN and use it within my component's Javascript?
I'm aware of some libraries to integrate Vue.js with Stripe (e.g. matfish2/vue-stripe and jofftiquez/vue-stripe-checkout), but the former doesn't import properly for me (I'm hitting issue #24) and the latter is built against the older Stripe API and the new version is still in beta.
You aren't giving the script time to load before checking if Stripe is there. What you need is something like this:
<script>
export default {
name: "PaymentPage",
mounted() {
let stripeScript = document.createElement('script');
stripeScript.setAttribute('src', 'https://js.stripe.com/v3/');
stripeScript.onload = () => {
let s = Stripe('pk_test_Fooo');
console.log(s);
};
document.head.appendChild(stripeScript);
}
}
</script>
Thanks to yuriy636's comment, I realised that errors were only from the linter, which presumably can't statically figure out what I'm up to.
I opted to put the script into index.html, then ensured I squashed linter errors with:
// eslint-disable-next-line no-undef
let s = Stripe('pk_test_Fooo');
In my case, I still had errors calling functions of the specific script. So it was required to specify the ¨window¨ scope. Also, if you need to access any Vue element inside the ¨onload¨function, you need a new variable for the ¨this¨ instance.
<script>
export default {
name: "PaymentPage",
mounted() {
let stripeScript = document.createElement('script');
// new variable for Vue elements.
let self = this;
stripeScript.onload = () => {
// call a script function using 'window' scope.
window.Stripe('pk_test_Fooo');
// call other Vue elements
self.otherVueMethod();
};
stripeScript.setAttribute('src', 'https://js.stripe.com/v3/');
document.head.appendChild(stripeScript);
}
}
I worked with this on Vue 2.6.
Just install the npm package npm install #stripe/stripe-js and use it like a regular import
import { loadStripe } from "#stripe/stripe-js";
export default {
async mounted() {
// init stripe
const stripe = await loadStripe('your_stripe_key_here');
this.stripe = stripe; // store the stripe instance
// access the stripe instance in your methods or where you want to use them
},
}
It's working as of 6th Jan 2022.

DOMException when hot-loading Web Audio worklet processor with Wasm modules from a Blob?

I'm trying to load dynamic Web Audio worklets with Wasm module imports transpiled from C++ code using Emscripten.
I want to manipulate worklets code in memory, using Blob to create objects, injecting them as modules into the audio worklet, and hot-swapping them.
I have followed the design pattern suggested in the web-audio-samples solution to implement worklets that import as Wasm module into a worklet. This seems to work well when the processor code is in a file.
Hot-loading and swapping worklets from memory without external Wasm module imports also seems to work well.
This is how I create the code for the Blob. To illustrate I'm using the simple-kernel.wasmmodule.js that is compiled with the same Emscripten configuration as the Wasm design pattern example in the web-audio-samples.
const blobCode = () => {
return `
import Module from './simple-kernel.wasmmodule.js';
import { RENDER_QUANTUM_FRAMES, MAX_CHANNEL_COUNT, HeapAudioBuffer } from '../lib/wasm-audio-helper.js';
class WASMWorkletProcessor extends AudioWorkletProcessor {
constructor() {
super();
// Allocate the buffer for the heap access. Start with stereo, but it can
// be expanded up to 32 channels.
this._heapInputBuffer = new HeapAudioBuffer(Module, RENDER_QUANTUM_FRAMES,
2, MAX_CHANNEL_COUNT);
this._heapOutputBuffer = new HeapAudioBuffer(Module, RENDER_QUANTUM_FRAMES,
2, MAX_CHANNEL_COUNT);
this._kernel = new Module.SimpleKernel();
}
process(inputs, outputs, parameters) {
.
.
.
return true;
}
}
registerProcessor('wasm-worklet-processor', WASMWorkletProcessor);`;
}
And this is how I use the code with Blob to create an object and load it into the worklet.
const workletHotLoading = async (context) => {
const blob = new Blob([ blobCode() ], { type: "application/javascript; charset=utf-8" });
const workletUrl = window.URL.createObjectURL(blob);
await context.audioWorklet.addModule(workletUrl);
const oscillator = new OscillatorNode(context);
const wasmBlobWorkletNode = new AudioWorkletNode(context, 'wasm-worklet-processor');
wasmBlobWorkletNode.onprocessorerror = (event) => {
console.log(`An error from WASMWorkletProcessor.constructor() was detected.`);
};
oscillator.connect(wasmBlobWorkletNode).connect(context.destination);
oscillator.start();
};
I was expecting this to work as the processors with no Wasm imports do, or when I load them from a file. If comment the module imports and module code in the worklet constructor and process method, it works.
However, hot-loading a worklet with a Wasm import does not appear to be working... When I try to do that, I get "Error on loading worklet: DOMException" and no other clue.
I suspect that this might be a bit naive and that it might require more sophistication such as dynamic imports...
I created a fork of web-audio-samples solution where I added a small sample project (wasm-hot-loading) that creates the conditions to illustrate the problem.
It is available here: https://github.com/mimic-sussex/web-audio-samples/tree/master/audio-worklet/design-pattern/wasm-hot-loading
Can anybody help shed some light on what the problem might be and whether this is feasible?
Thanks
FØ
The problem is that your Worklet Processor's origin is opaque.
This means that your relative imports can not be resolved from inside this script.
To circumvent this, you need to use absolute URIs.
So you have to rewrite your code in order to add the correct baseURI to your imports statements. Since this code is called from your main js page, you can then build this blob content with the correct format e.g using the URL constructor and its second parameter base.
const blobCode = () => {
return `
import Module from "${
// convert relative to absolute URL
new URL('./simple-kernel.wasmmodule.js', location.href)
}";
import { RENDER_QUANTUM_FRAMES, MAX_CHANNEL_COUNT, HeapAudioBuffer } from "${
new URL('../lib/wasm-audio-helper.js', location.href)
}";
class WASMWorkletProcessor extends AudioWorkletProcessor {
constructor() {
super();
[...]
`
};
console.log(blobCode());
I had a similar bug. Dom Exception throwned only when import is in the worklet.
the problem was due to a chrome extension called web audio inspector
this extension adds certainly some wrapping function around the worklet, since the import is not authorized to be invoke into a function, the worklet import breaks.
Anyway, uninstalled the extension and problem solved.

Importing javascript file for use within vue component

I am working on a project that requires using a js plugin. Now that we're using vue and we have a component to handle the plugin based logic, I need to import the js plugin file within the vue component in order to initialize the plugin.
Previously, this was handled within the markup as follows:
<script src="//api.myplugincom/widget/mykey.js
"></script>
This is what I tried, but I am getting a compile time error:
MyComponent.vue
import Vue from 'vue';
import * from '//api.myplugincom/widget/mykey.js';
export default {
data: {
My question is, what is the proper way to import this javascript file so I can use it within my vue component?
...
Include an external JavaScript file
Try including your (external) JavaScript into the mounted hook of your Vue component.
<script>
export default {
mounted() {
const plugin = document.createElement("script");
plugin.setAttribute(
"src",
"//api.myplugincom/widget/mykey.js"
);
plugin.async = true;
document.head.appendChild(plugin);
}
};
</script>
Reference: How to include a tag on a Vue component
Import a local JavaScript file
In the case that you would like to import a local JavaScript in your Vue component, you can import it this way:
MyComponent.vue
<script>
import * as mykey from '../assets/js/mykey.js'
export default {
data() {
return {
message: `Hello ${mykey.MY_CONST}!` // Hello Vue.js!
}
}
}
</script>
Suppose your project structure looks like:
src
- assets
- js
- mykey.js
- components
MyComponent.vue
And you can export variables or functions in mykey.js:
export let myVariable = {};
export const MY_CONST = 'Vue.js';
export function myFoo(a, b) {
return a + b;
}
Note: checked with Vue.js version 2.6.10
try to download this script
import * from '{path}/mykey.js'.
or import script
<script src="//api.myplugincom/widget/mykey.js"></script>
in <head>, use global variable in your component.
For scripts you bring in the browser way (i.e., with tags), they generally make some variable available globally.
For these, you don't have to import anything. They'll just be available.
If you are using something like Webstorm (or any of the related JetBrains IDEs), you can add /* global globalValueHere */ to let it know that "hey, this isn't defined in my file, but it exists." It isn't required, but it'll make the "undefined" squiggly lines go away.
For example:
/* global Vue */
is what I use when I am pulling Vue down from a CDN (instead of using it directly).
Beyond that, you just use it as you normally would.
I wanted to embed a script on my component and tried everything mentioned above, but the script contains document.write. Then I found a short article on Medium about using postscribe which was an easy fix and resolved the matter.
npm i postscribe --save
Then I was able to go from there. I disabled the useless escape from eslint and used #gist as the template's single root element id:
import postscribe from 'postscribe';
export default {
name: "MyTemplate",
mounted: function() {
postscribe(
"#gist",
/* eslint-disable-next-line */
`<script src='...'><\/script>`
);
},
The article is here for reference:
https://medium.com/#gaute.meek/how-to-add-a-script-tag-in-a-vue-component-34f57b2fe9bd
For anyone including an external JS file and having trouble accessing the jQuery prototype method(s) inside of the loaded script.
Sample projects I saw in vanilla JS, React and Angular were simply using:
$("#someId").somePlugin(options)
or
window.$("#someId").somePlugin(options)
But when I try either of those in my VueJS component I receive:
Error: _webpack_provided_window_dot$(...).somePluginis not a function
I examined the window object after the resources had loaded I was able to find the jQuery prototype method in the window.self read-only property that returns the window itself:
window.self.$("#someId").somePlugin(options)
Many examples show how to load the external JS file in VueJS but not actually using the jQuery prototype methods within the component.

Browserify and Vue: uncaught reference error: Vue is not defined

This is my first foray into any front-end development beyond basic jQuery stuff, and I'm using Vue.js along with some other packages with Browserify. My main 'app.js' looks like this:
window.$ = window.jQuery = require('jquery');
require('bootstrap');
var moment = require('moment');
var fullCalendar = require('./vendor/fullcalendar.min.js');
var datetimepicker = require('./vendor/bootstrap-datetimepicker.min.js');
var select2 = require('./vendor/select2.min.js');
var VueResource = require('vue-resource');
var Vue = require('vue');
require('./videos/show.js');
require('./home.js');
require('./search.js');
Vue.use(VueResource);
new Vue({
el: '#search',
data: {
message: 'Hello World!'
},
});
...
It works as expected this way, but when I try to create a new Vue instance in another file (in search.js, for instance) I can't do it. I get the 'Uncaught reference error: Vue is not defined' in my console. No problem with using the other required packages elsewhere - although I don't understand why I need to import jQuery the way I'm doing it... it won't work if I do:
var $, jQuery = require('jquery');
I'm sure this is something very basic and fundamental that I am not understanding yet but any help would be greatly appreciated!
The problem you are having is the basics of using modules. In general, a module should always export some behavior or property and then you require that module and use it. For example, say I wanted to add a hidden into to a form on some pages. I would do this:
AddSecretToken.js
module.exports = function(form) {
// code here to add the hidden input to the passed in form
}
Then somewhere else where I had a form that needed the secret input, I would require it:
MyForm.js
var addSecretToken = require('./AddSecretToken');
...
// some code that makes a form
addSecretToken(myForm);
...
Obviously, at some point you need some code that actually runs something but that would be your root module or the page where you require the root module. So maybe you have an app.js at the top and it requires what it needs and then runs app() without exporting anything. That makes sense. But the majority of modules shouldn't be doing that.
Any time you need some behavior, you should make a module and then anywhere you need the behavior, you require the module. Each module should require what it depends on -- it shouldn't depend on any global (sometimes jQuery is an exception).

Categories

Resources