How to use videojs hotkeys plugin in vue component? - vuejs2

I use videojs within vue component, it works fine. Then I tried to use videojs-hotkeys plugin like
<template>
<video
id="myplayer"
poster="xxx.jpg"
class="video-js"
controls>
<source :src="source.src" :type="source.type">
</video>
</template>
<script>
import $ from 'jquery';
import videojs from 'video.js';
$(function(){
console.log(this); //Output: object: #document
});
export default{
data(){
return {
source: {
src:"xxx.mp4",
type:""
}
}
},
mounted() {
console.log(this); //Output: Vue Component: vue instance
//import external script
let hotkeysScript = document.createElement('script')
hotkeysScript.setAttribute('src', 'http://cdn.sc.gl/videojs-hotkeys/latest/videojs.hotkeys.min.js')
document.head.appendChild(hotkeysScript)
videojs('myplayer').ready(function () {
console.log(this); //Output: player instance
this.hotkeys({ //Error: this.hotkeys is not a function
volumeStep: 0.1,
seekStep: 5,
enableModifiersForNumbers: false
});
})
}
}
</script>
The browser gave me below error information
app.js:107959 Uncaught TypeError: this.hotkeys is not a function
at Player.<anonymous> (app.js:107959)
at Player.<anonymous> (app.js:16325)
at Array.forEach (<anonymous>)
at Player.<anonymous> (app.js:16324)
at bound (app.js:14739)
at app.js:16992
I tried to print the value of "this" in code(please see the comment in code), and gives output for your reference. Please help. Thanks so much.

You need to add plugins to videojs via the plugins property when initialized.
videojs('example-player', {
plugins: {
examplePlugin: {
customClass: 'example-class'
}
}
});
If you need to set some data after the videojs instance is ready you can still modify the config the way you are now. You can also store via this.videoPlayer and then at anytime access the plugin.
source: https://github.com/videojs/video.js/blob/master/docs/guides/plugins.md#setting-up-a-plugin

I fond there is npm package here: https://www.npmjs.com/package/videojs-hotkeys, so just install it like
npm install videojs-hotkeys --save
very simple, for I am not quite family with new features of javascript, just follow the instructions on the home page of the plugin.
Thanks #Justin Kahn all the same, for helping locate the issue scope.

Related

How to correctly register a CMS element with the Shopware 6 Admin SDK

With the Shopware 6 Admin SDK it's possible to add CMS Elements to the Shopware Admin from an external app via iFrame. However, by following the documentation there are some issues one might tackle along the way. If everything is done as stated in the docs following issues occur that I'd like to discuss:
First issue: If the newly registered CMS element is used more than once in a shopping experience layout the same config data is shared between all elements. This is the most severe issue that I need to solve. The second issue sounds kind of the reason for it. Therefore I attached my code at the end.
Second issue: Once the newly registered CMS element's config modal is opened the following error is thrown: The dataset id "swag-dailymotion__config-element" you tried to publish is already registered. The error is thrown in this file. I guess this has something to do in which I set up the element.
Third issue: If e.g. some text element is exchanged with the newly registered element the following console error is thrown once the element switch button is clicked and all element previews are visible: An error was captured in current module: TypeError: this.initElementConfig is not a function. Since there is a // #ts-expect-error in the responsible file I think this is not too important for the extension developer, since Shopware is already aware of it.
I have the following setup for implementing the CMS element. It differs in some way, since I am using .vue files instead of plain .ts as explained in the docs. However, except the mentioned issues, everything seems to be working fine:
This file is the entry point for the <base-app-url>
<template>
<view-renderer v-if="showViewRenderer"></view-renderer>
</template>
<script lang="ts">
import Vue from 'vue';
import { location, cms } from '#shopware-ag/admin-extension-sdk';
import viewRenderer from '#/components/cms/view-renderer.vue';
import { CONSTANTS } from '#/components/cms/index';
export default Vue.extend({
components: {
'view-renderer': viewRenderer
},
data() {
return {
showViewRenderer: false
};
},
created(): void {
if (location.isIframe()) {
if (location.is(location.MAIN_HIDDEN)) {
this.mainCommands();
} else {
this.showViewRenderer = true;
}
}
},
methods: {
mainCommands(): void {
this.registerCmsElements();
},
registerCmsElements(): void {
cms.registerCmsElement({
name: CONSTANTS.CMS_ELEMENT_NAME,
label: this.$t('cms.dailymotion.preview.label') as string,
defaultConfig: {
dailyUrl: {
source: 'static',
value: '',
},
},
});
}
}
});
</script>
#/components/cms/view-renderer.vue
<template>
<div>
<swag-dailymotion-config v-if="location.is('swag-dailymotion-config')"></swag-dailymotion-config>
<swag-dailymotion-element v-else-if="location.is('swag-dailymotion-element')"></swag-dailymotion-element>
<swag-dailymotion-preview v-else-if="location.is('swag-dailymotion-preview')"></swag-dailymotion-preview>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { location } from '#shopware-ag/admin-extension-sdk';
import swagDailymotionConfig from '#/components/cms/swag-dailymotion/swag-dailymotion-config.vue';
import swagDailymotionElement from '#/components/cms/swag-dailymotion/swag-dailymotion-element.vue';
import swagDailymotionPreview from '#/components/cms/swag-dailymotion/swag-dailymotion-preview.vue';
export default Vue.extend({
components: {
'swag-dailymotion-config': swagDailymotionConfig,
'swag-dailymotion-element': swagDailymotionElement,
'swag-dailymotion-preview': swagDailymotionPreview
},
data() {
return {
location
};
},
created(): void {
location.startAutoResizer();
}
});
</script>
The individual components are not doing anything special. They are fetching the element with this.element = await data.get({ id: CONSTANTS.PUBLISHING_KEY }); as stated in the docs and doing some work with it. However, it's always fetching the same element without considering the slot the current element is in, therefore it's always fetching the same data set.
What am I doing wrong?

How to make a dynamic import in Nuxt?

In my nuxt component I want to use the ace editor:
import Ace from "ace-builds/src-noconflict/ace"
when the component is mounted I am doing the following:
this.editor = Ace.edit...
Obviously the window is not defined on the server on page reload. But unfortunately I just can't find a solution to fix this issue.
Is there a way to import a package on the mounted() hook?
I already tried
const Ace = require("ace-builds/src-noconflict/ace")
But that doesn't quite seem to work. Do you have any ideas to solve this issue?
I already tried to register a plugin plugins/ace.js:
import Vue from "vue"
import Ace from "ace-builds/src-noconflict/ace"
Vue.use(Ace)
registered it in nuxt.config.js:
plugins: [
{ src: "~/plugins/ace", mode: "client" }
],
But how do I use Ace in my component now? It is still undefined...
Since the error was thrown during the import statement, I'd recommended using dynamic imports as explained in my other answer here.
async mounted() {
if (process.client) {
const Ace = await import('ace-builds/src-noconflict/ace')
Ace.edit...
}
},
From the official documentation: https://nuxtjs.org/docs/2.x/internals-glossary/context
EDIT: I'm not sure about Ace and it's maybe a drastic change but you may also give a look to vue-monaco which is elbow-to-elbow popularity wise (vanilla Monaco editor).
EDIT2: mounted actually only runs on the client so you could strip the process.client conditional. Meanwhile, I do let it here in case you want to run some logic in other hooks like created (which are run on both server + client). More info here.
EDIT3: not directly related to the question, but some packages expose a component which is only available on the client-side (no SSR support), in those cases you could import the component only on the client side and easily prevent any other errors.
Nuxt Plugin
IMHO you were on the right track with the "plugin" solution. Only mistake was the
Vue.use(Ace) part. This only works for vue plugins.
The plugin file could look somewhat like that:
import Ace from 'ace-builds/src-noconflict/ace'
import Theme from 'ace-builds/src-noconflict/theme-monokai'
export default ({ app }, inject) => {
inject('ace', {
editor: Ace,
theme: Theme
})
}
Then you could use this plugin and initiate the editor in a component this way:
<template>
<div id="editor">
function foo(items) {
var x = "All this is syntax highlighted";
return x;
}
</div>
</template>
<script>
export default {
data () {
return {
editor: {}
}
},
mounted () {
this.editor = this.$ace.editor.edit('editor')
this.editor.setTheme(this.$ace.theme)
}
}
</script>

Vue.js: is it possible to have a SFC factory?

I'm using single-file-components in a larger project and I'm new to Vue.JS. I'm looking for a way to dynamically create components, especially their templates on-the-fly at run-time.
My idea is to create a "component factory" also as a SFC and to parameterize it with props, e.g. one for the template, one for the data and so forth. I get how this works for already specified SFC and that I can simply exchange them with <component v-bind:is= ..., however the task here is different. I need to compose a template string, which potentially is composed of other component instance declarations, on-the-fly and inject it in another SFC. The code below doesn't work unfortunately.
<template>
<div>
<produced-component/>
</div>
</template>
<style></style>
<script>
import Vue from 'vue'
export default {
props: {
template: { type: String, default: '<div>no template prop provided</div>' }
},
components: {
'produced-component': Vue.extend(
{
template: '<div>my runtime template, this I want to be able to compose on-the-fly</div>'
}
)
}
}
</script>
It says:
The following answer: https://stackoverflow.com/a/44648296/4432432 answers the question partially but I can't figure out how to use this concept in the context of single-file-components.
Any help is greatly appreciated.
Edit 2020.03.16:
For future reference the final result of the SFC factory achieved as per the accepted answer looks like this:
<template>
<component:is="loader"/>
</template>
<script>
const compiler = require('vue-template-compiler')
import Vue from 'vue'
export default {
props: {
templateSpec: { type: String, default: '<div>no template provided</div>' }
},
computed: {
loader () {
let compSpec = {
...compiler.compileToFunctions(this.templateSpec)
}
return Vue.extend(compSpec)
}
}
}
</script>
You can surely have a Factory component with SFC; though what you are trying to do is a very rare case scenario and seems more like an anti/awkward pattern.
Whatever, you are doing currently is right. All you need to do is - use Full build of Vue which include the Vue Template Compiler, which will enable you to compile template at run-time (on the browser when app is running). But remember that for the components written in SFC, they must be compiled at build time by vue-loader or rollup equivalent plugin.
Instead of using vue.runtime.min.js or vue.runtime.js, use vue.min.js or vue.js.
Read Vue installation docs for more details.
Assuming you are using Webpack, by default main field of Vue's package.json file points to the runtime build. Thus you are getting this error. You must tell webpack to use full bundle. In the configuration resolve the Vue imports to the following file:
webpack.config.js
module.exports = {
// ... other config
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
};

Vue/Nuxt: How to define a global method accessible to all components?

I just want to be able to call
{{ globalThing(0) }}
in templates, without needing to define globalThing in each .vue file.
I've tried all manner of plugin configurations (or mixins? not sure if Nuxt uses that terminology.), all to no avail. It seems no matter what I do, globalThing and this.globalThing remain undefined.
In some cases, I can even debug in Chrome and see this this.globalThing is indeed defined... but the code crashes anyway, which I find very hard to explain.
Here is one of my many attempts, this time using a plugin:
nuxt.config.js:
plugins: [
{
src: '~/plugins/global.js',
mode: 'client'
},
],
global.js:
import Vue from 'vue';
Vue.prototype.globalFunction = arg => {
console.log('arg', arg);
return arg;
};
and in the template in the .vue file:
<div>gloabal test {{globalFunction('toto')}}</div>
and... the result:
TypeError
_vm.globalFunction is not a function
Here's a different idea, using Vuex store.
store/index.js:
export const actions = {
globalThing(p) {
return p + ' test';
}
};
.vue file template:
test result: {{test('fafa')}}
.vue file script:
import { mapActions } from 'vuex';
export default {
methods: {
...mapActions({
test: 'globalThing'
}),
}
};
aaaaaaaaand the result is.........
test result: [object Promise]
OK, so at least the method exists this time. I would much prefer not to be forced to do this "import mapActions" dance etc. in each component... but if that's really the only way, whatever.
However, all I get is a Promise, since this call is async. When it completes, the promise does indeed contain the returned value, but that is of no use here, since I need it to be returned from the method.
EDIT
On the client, "this" is undefined, except that..... it isn't! That is to say,
console.log('this', this);
says "undefined", but Chrome's debugger claims that, right after this console log, "this" is exactly what it is supposed to be (the component instance), and so is this.$store!
I'm adding a screenshot here as proof, since I don't even believe my own eyes.
https://nuxtjs.org/guide/plugins/
Nuxt explain this in Inject in $root & context section.
you must inject your global methods to Vue instance and context.
for example we have a hello.js file.
in plugins/hello.js:
export default (context, inject) => {
const hello = (msg) => console.log(`Hello ${msg}!`)
// Inject $hello(msg) in Vue, context and store.
inject('hello', hello)
// For Nuxt <= 2.12, also add 👇
context.$hello = hello
}
and then add this file in nuxt.config.js:
export default {
plugins: ['~/plugins/hello.js']
}
Use Nuxt's inject to get the method available everywhere
export default ({ app }, inject) => {
inject('myInjectedFunction', (string) => console.log('That was easy!', string))
}
Make sure you access that function as $myInjectedFunction (note $)
Make sure you added it in nuxt.config.js plugins section
If all else fails, wrap the function in an object and inject object so you'd have something like $myWrapper.myFunction() in your templates - we use objects injected from plugins all over the place and it works (e.g. in v-if in template, so pretty sure it would work from {{ }} too).
for example, our analytics.js plugin looks more less:
import Vue from 'vue';
const analytics = {
setAnalyticsUsersData(store) {...}
...
}
//this is to help Webstorm with autocomplete
Vue.prototype.$analytics = analytics;
export default ({app}, inject) => {
inject('analytics', analytics);
}
Which is then called as $analytics.setAnalyticsUsersData(...)
P.S. Just noticed something. You have your plugin in client mode. If you're running in universal, you have to make sure that this plugin (and the function) is not used anywhere during SSR. If it's in template, it's likely it actually is used during SSR and thus is undefined. Change your plugin to run in both modes as well.
This would be the approach with Vuex and Nuxt:
// store/index.js
export const state = () => ({
globalThing: ''
})
export const mutations = {
setGlobalThing (state, value) {
state.globalThing = value
}
}
// .vue file script
export default {
created() {
this.$store.commit('setGlobalThing', 'hello')
},
};
// .vue file template
{{ this.$store.state.globalThing }}

Problem when importing js-cookies in Main.js

I'm trying import js-cookies in my main.js
Main.js
import * as Cookies from "js-cookie";
Vue.use(Cookies)
Using in component
this.$Cookies.set('name', data.user, { secure: true });
Error
TypeError: Cannot read property 'set' of undefined
what is the problem?
I have tried a thousand ways and it still does not work.
Vue.use(name) is used to install a vue plugin. The package will need an install method that receives a vue instance.
#1
You can use the cookies packages without a plugin importing the module in the component
<script>
import Cookies from 'js-cookie';
export default {
methods: {
addCookie() {
console.log('adding the cookie');
Cookies.set('chocolate', 'chookies');
console.log(Cookies.get());
}
}
}
</script>
#2 you can add a VUE plugin and set a Cookies prototype function to the Cookies module.
(Prototype vue functions will be available for components, it's standard to prefix them with $).
src/CookiesPlugin.js
import Cookies from 'js-cookie';
const CookiesPlugin = {
install(Vue, options) {
Vue.prototype.$Cookies = Cookies;
}
};
export default CookiesPlugin;
src/main.js
import CookiesPlugin from './CookiesPlugin';
Vue.use(CookiesPlugin);
In the component
this.$Cookies.set('chocolate', 'chookies');
console.log(this.$Cookies.get());
You are using a NOT Vue (Vanilla JS library) library and you are trying to use it as a Vue resource.
Try using this one instead