I agree! Being able to start Vue with Node-Red data plasticity helped by uibuilder is really great, but it's a pain to understand at first, too many details and a hard time to tackle problems. Steep learning curve!
Hopefully a great community goes a long way ![]()
Actually I want to call different videos so I guess the container is the best way in my case.
Removing the first player and moving options to inde.js
data: {
player: null,
videoOptions: {
class:"video-js",
controls:true,
preload:"auto",
width:640,
height:264,
sources: [
throws new errors:
[Vue warn]: Error in mounted hook: "TypeError: The element or ID supplied is not valid. (videojs)"
(found in <Root>) vue.js:634:17
VueJS 11
<anonymous> http://192.168.1.236:1880/videojs/index.js:3
TypeError: The element or ID supplied is not valid. (videojs)
Nope, I don't think so, you are over-complecating things. A single player will show as many videos as you like (1 at a time), simply send a new one from Node-RED. Or, better still, send a list of videos with metadata from Node-RED to your front-end, use that list to build a dropdown and use the change event on the dropdown to load the appropriate video and start playing.
Then I still miss something in the setup 
How would I do that?
What about the error?
The error gives a clear indication where to look.
You mentioned that you removed the first video player component from Julian's example.
But in index.js in mounted() the code tries to initialize a player in an element with id 'my-video' and none is found.
app.player = videojs('my-video')
console.log(app.player)
I didnt study the code in depth but try use an id="my-video" on the second player that you have in your modified code.
Thanks @UnborN
In index.html, I'd like to keep <video-player :options="videoOptions"/> to understand the uibuilder-Vue mechanic.
So here's my index.js:
'use strict'
new Vue({
el: '#app',
components: {
'video-player': httpVueLoader('video-player.vue'),
}, // --- End of components --- //
data: {
player: null,
videoOptions: {
id:"video-player",
class:"video-js",
controls:true,
preload:"auto",
width:640,
height:264,
sources: [
{
src:
'//vjs.zencdn.net/v/oceans.mp4',
type: 'video/mp4'
}
]
},
videoUrl: '',
startMsg : 'Vue has started, waiting for messages',
feVersion : '',
counterBtn : 0,
inputText : null,
inputChkBox : false,
socketConnectedState : false,
serverTimeOffset : '[unknown]',
imgProps : { width: 75, height: 75 },
msgRecvd : '[Nothing]',
msgsReceived: 0,
msgCtrl : '[Nothing]',
msgsControl : 0,
msgSent : '[Nothing]',
msgsSent : 0,
msgCtrlSent : '[Nothing]',
msgsCtrlSent: 0,
isLoggedOn : false,
userId : null,
userPw : null,
inputId : '',
}, // --- End of data --- //
computed: {
hLastRcvd: function() {
var msgRecvd = this.msgRecvd
if (typeof msgRecvd === 'string') return 'Last Message Received = ' + msgRecvd
else return 'Last Message Received = ' + this.syntaxHighlight(msgRecvd)
},
hLastSent: function() {
var msgSent = this.msgSent
if (typeof msgSent === 'string') return 'Last Message Sent = ' + msgSent
else return 'Last Message Sent = ' + this.syntaxHighlight(msgSent)
},
hLastCtrlRcvd: function() {
var msgCtrl = this.msgCtrl
if (typeof msgCtrl === 'string') return 'Last Control Message Received = ' + msgCtrl
else return 'Last Control Message Received = ' + this.syntaxHighlight(msgCtrl)
},
hLastCtrlSent: function() {
var msgCtrlSent = this.msgCtrlSent
if (typeof msgCtrlSent === 'string') return 'Last Control Message Sent = ' + msgCtrlSent
//else return 'Last Message Sent = ' + this.callMethod('syntaxHighlight', [msgCtrlSent])
else return 'Last Control Message Sent = ' + this.syntaxHighlight(msgCtrlSent)
},
}, // --- End of computed --- //
methods: {
increment: function(event) {
console.log('Button Pressed. Event Data: ', event)
// Increment the count by one
this.counterBtn = this.counterBtn + 1
var topic = this.msgRecvd.topic || 'uibuilder/vue'
uibuilder.send( {
'topic': topic,
'payload': {
'type': 'counterBtn',
'btnCount': this.counterBtn,
'message': this.inputText,
'inputChkBox': this.inputChkBox
}
} )
}, // --- End of increment --- //
doLogon: function() {
uibuilder.logon( {
'id': this.inputId,
} )
}, // --- End of doLogon --- //
doLogoff: function() {
uibuilder.logoff()
}, // --- End of doLogon --- //
// return formatted HTML version of JSON object
syntaxHighlight: function(json) {
json = JSON.stringify(json, undefined, 4)
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
json = json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
var cls = 'number'
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key'
} else {
cls = 'string'
}
} else if (/true|false/.test(match)) {
cls = 'boolean'
} else if (/null/.test(match)) {
cls = 'null'
}
return '<span class="' + cls + '">' + match + '</span>'
})
return json
}, // --- End of syntaxHighlight --- //
}, // --- End of methods --- //
// Available hooks: beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed, activated,deactivated, errorCaptured
/** Called after the Vue app has been created. A good place to put startup code */
created: function() {
// Example of retrieving data from uibuilder
this.feVersion = uibuilder.get('version')
/** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3
* Pass the namespace and ioPath variables if hosting page is not in the instance root folder
* e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.
* e.g. uibuilder.start('/uib', '/uibuilder/vendor/socket.io') // change to use your paths/names
* @param {Object=|string=} namespace Optional. Object containing ref to vueApp, Object containing settings, or String IO Namespace override. changes self.ioNamespace from the default.
* @param {string=} ioPath Optional. changes self.ioPath from the default
* @param {Object=} vueApp Optional. Reference to the VueJS instance. Used for Vue extensions.
*/
uibuilder.start(this) // Single param passing vue app to allow Vue extensions to be used.
//console.log(this)
},
/** Called once all Vue component instances have been loaded and the virtual DOM built */
mounted: function(){
//console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')
var app = this // Reference to `this` in case we need it for more complex functions
app.player = videojs('video-player')
console.log(app.player)
// If msg changes - msg is updated when a standard msg is received from Node-RED over Socket.IO
// newVal relates to the attribute being listened to.
uibuilder.onChange('msg', function(msg){
//console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
app.msgRecvd = msg
app.msgsReceived = uibuilder.get('msgsReceived')
if ( msg.payload.src ) {
app.player.src(msg.payload)
app.player.ready(function() {
app.player.play();
})
}
})
//#region ---- Debug info, can be removed for live use ---- //
/** You can use the following to help trace how messages flow back and forth.
* You can then amend this processing to suite your requirements.
*/
// If we receive a control message from Node-RED, we can get the new data here - we pass it to a Vue variable
uibuilder.onChange('ctrlMsg', function(msg){
//console.info('[indexjs:uibuilder.onChange:ctrlMsg] CONTROL msg received from Node-RED server:', msg)
app.msgCtrl = msg
app.msgsControl = uibuilder.get('msgsCtrl')
})
/** You probably only need these to help you understand the order of processing
* If a message is sent back to Node-RED, we can grab a copy here if we want to
*/
uibuilder.onChange('sentMsg', function(msg){
//console.info('[indexjs:uibuilder.onChange:sentMsg] msg sent to Node-RED server:', msg)
app.msgSent = msg
app.msgsSent = uibuilder.get('msgsSent')
})
/** If we send a control message to Node-RED, we can get a copy of it here */
uibuilder.onChange('sentCtrlMsg', function(msg){
//console.info('[indexjs:uibuilder.onChange:sentCtrlMsg] Control message sent to Node-RED server:', msg)
app.msgCtrlSent = msg
app.msgsCtrlSent = uibuilder.get('msgsSentCtrl')
})
/** If Socket.IO connects/disconnects, we get true/false here */
uibuilder.onChange('ioConnected', function(connected){
//console.info('[indexjs:uibuilder.onChange:ioConnected] Socket.IO Connection Status Changed to:', connected)
app.socketConnectedState = connected
})
/** If Server Time Offset changes */
uibuilder.onChange('serverTimeOffset', function(serverTimeOffset){
//console.info('[indexjs:uibuilder.onChange:serverTimeOffset] Offset of time between the browser and the server has changed to:', serverTimeOffset)
app.serverTimeOffset = serverTimeOffset
})
/** If user is logged on/off */
uibuilder.onChange('isAuthorised', function(isAuthorised){
//console.info('[indexjs:uibuilder.onChange:isAuthorised] isAuthorised changed. User logged on?:', isAuthorised)
//console.log('authData: ', uibuilder.get('authData'))
//console.log('authTokenExpiry: ', uibuilder.get('authTokenExpiry'))
app.isLoggedOn = isAuthorised
})
//#endregion ---- Debug info, can be removed for live use ---- //
} // --- End of mounted hook --- //
}) // --- End of app1 --- //
// EOF
And in Video-Player.vue:
<template>
<div>
<video ref="videoPlayer" id="video-player" class="video-js"></video>
</div>
</template>
...
As you can see, no more my-video
But still, I get the same error.
I just don't get how to inject <video> options from NR.
Would you or @TotallyInformation please show it to me?
One way to control the <video-player :options="videoOptions"/> which is a sub-component to the main Vue app is to give it a ref (reference) so you can select and then control that element.
<video-player ref="myVideo" :options="videoOptions"/>
Then you can comment out the lines // app.player = videojs('my-video') as these were used for creating a video player for the <video id="my-video" ... example.
Now for accessing and controlling the vue component player modify the lines in uibuilder.onChange ..
uibuilder.onChange('msg', function(msg){
console.info('[indexjs:uibuilder.onChange] msg received from Node-RED server:', msg)
console.log("myVideo", app.$refs.myVideo.player) // log the nested player
let myVideo = app.$refs.myVideo.player // save the nested player in variable
app.msgRecvd = msg
app.msgsReceived = uibuilder.get('msgsReceived')
if ( msg.payload.src ) {
myVideo.src(msg.payload) // change src for player
myVideo.ready(function() {
myVideo.play(); // play
})
}
})
Thank you very much @UnborN !
As I said, I'm learning Vue using uibuilder 
You spotted on the mechanic that is still blurry for me.