VideoJS, Meteor and rerendering of templates - meteor

In a project we use the VideoJS player in a Meteor App to play some videos. We have a playlist to navigate though the videos. The problem is, that when the template gets rerendered, the Player cannot be initialized again after that.
I have written a template file and coffescript part for that:
<template name="videoPlayer">
<video id="videoJsPlayer" class="video-js vjs-default-skin"
controls preload="auto" width="572" height="350"
poster="...file.jpg"
>
<source src="...video.mp4" type='video/mp4' />
</video>
</template>
I already tried to work with the ID,
but When I come back to the same video the ID will be the same.
and the coffee-script:
Template.videoPlayer.rendered = ->
videojs.options.flash.swf = "/video-js.swf"
$vid_obj = _V_ "videoJsPlayer", {}, ()->
console.log "Player Loaded"
$vid_obj.ready () ->
console.log("Element ready");
I have also tried to put "vid_obj" somewhere global and calling the videojs "destroy()" method before. That gives an error, that destroy() doesn't exist. Also an V.players = {} to delete all player bindings doesn't work.

I have solved my issue. The Trick was not to apply that styling on the template.
I added the video player via jQuery:
if videojs.players.videoJsPlayer
videojs.players.videoJsPlayer.dispose()
$v = $(".videoPlayerFrame")
$v.html("").append $("<video id=\"videoJsPlayer\" class=\"video-js vjs-default-skin \">")
.attr("controls", true)
.attr("preload", "none")
.attr("width", $v.attr("data-width"))
.attr("height", $v.attr("data-height"))
.attr("poster", $v.attr("data-poster"))
.append("<source src=\""+$v.attr("data-video")+"\" type=\"video/mp4\" />")
$vid_obj = _V_ "videoJsPlayer", {}, ()->
# console.log "video #{vid} is ready.";
console.log "Element Loaded"

Try adding this code,
Template.videoPlayer.destroyed = function () {
var mPlayer = vidoejs("#playerId");
mPlayer.dispose();
}
Each time Meteor render the player template it's destroyed and created. I hope this wont happen in Shark, the new Meteor renderer.

Related

Programatically create a-assets in A-frame

The assets in my index.html are injected on a need basis. I dont know what the assets are until a XHR request has completed.
I used the following part to generate a multi-source video asset
function createStreamAsset() {
var videoAsset = document.createElement("video");
videoAsset.setAttribute("id", "video");
videoAsset.setAttribute("style", "display:none");
videoAsset.setAttribute("autoplay", "");
videoAsset.setAttribute("loop", "");
videoAsset.setAttribute("playsinline", "");
videoAsset.setAttribute("webkit-playsinline", "");
videoAsset.setAttribute("crossorigin", "anonymous");
var sourceDesktop = document.createElement("source");
sourceDesktop.setAttribute("src", "url")
sourceDesktop.setAttribute("type", "video/mp4")
var sourceMobile = document.createElement("source");
sourceMobile.setAttribute("src", "hlsurl")
sourceMobile.setAttribute("type", "application/x-mpegurl")
videoAsset.appendChild(sourceDesktop);
videoAsset.appendChild(sourceMobile);
assetManager.appendChild(videoAsset);
}
At the top a-assets has been appended as a child to the scene (var assetManager)
console.log prints at the top core:propertyTypes:warn "#video" asset not found.
Furtheremore, is the consturction of the video element properly done this way to match this html?
<video id="video" style="display:none"
autoplay loop crossorigin="anonymous"
playsinline webkit-playsinline
>
I solved my issues by removing a pre-defined source on the object that had #video as source. After adding the assets, change the source on the object and it worked fine.

Meteor and Video.js player loading issues

I have probelm running video.js player.
First I put the video.js and video-js.css in client/compatibility folder so it will load first
Then in my client side html I write the following simple code,, It is not working
<video id="example_video_1" class="video-js vjs-default-skin vjs-big-play-centered"
controls width="640" height="264"
poster="{{video.thumbs}}"
data-setup='{"controls": true, "autoplay": true,"techOrder":["flash", "html5"],preload="auto"}'>
<source type='video/flv' src="{{uurl}}" />
</video>
There are no issues with the template helpers they are working fine but
The problem is with the video.js player , It is not loading.
Anyone tried this before? Struck here, Any help appreciated
UPDATE:
dynamic loading solution working fine for me but
when I click on a video it redirects to /video/:_id/:_slug page with subscribing to single video but when I go back to the home page and again click on another video, this time video player is not initializing again
Code
when onclick on video:
'click .videothumb > a':function(e,t){
Session.set("url",e.currentTarget.getAttribute("data-url"));
var url=e.currentTarget.getAttribute("data-url");
var title=e.currentTarget.getAttribute("data-title");
var cid=e.currentTarget.getAttribute("data-id");
Meteor.call("videoData",url,function(er,da){
console.log(cid);
Session.set('vurl',da);
Router.go("video",{_id:cid,slug:title});
});
}
routing
this.route("video",{
path:'/video/:_id/:slug',
waitOn: function() {
// return Meteor.subscribe('singleVideo', this.params._id);
},
onBeforeAction: function () {
},
data:function(){
Session.set("currentVideoId",this.params._id);
var video;
video= Videos.findOne({_id: this.params._id});
return {
video:video
};
}
});
rendered function:
Template.video.rendered=function(){
var id=Session.get("currentVideoId");
Meteor.call("incViews",id,function(e,d){
console.log("view added");
});
videojs("videoId",{"controls": true, "autoplay": false,"techOrder":["html5","flash"],preload:"auto"},function(){
// Player (this) is initialized and ready.
**console.log("videojs initialized");**
console.log(this) ;
}
);
};
that console "videojs initialized" is loging when only first when I route the video page
from next routing(when I click video thumbnail on homepage)The log functions is not loggind(means player is not initializing)
Any suggestions to make it work better
In the time videojs script is initialized there is no template rendered, you need to manually initialize videojs player.
tpl1.html:
<template name="tpl1">
<video id="example_video_1" class="video-js vjs-default-skin vjs-big-play-centered"
controls width="640" height="264"
poster="{{video.thumbs}}">
<source type='video/flv' src="{{uurl}}" />
</video>
</template>
tpl1.js:
Template.tpl1.rendered = function(){
videojs(
"example_video_1",
{"controls": true, "autoplay": true,"techOrder":["flash", "html5"],preload="auto"},
function(){ // Player (this) is initialized and ready. }
);
})
Please read documentation of Video.js:
"Alternative Setup for Dynamically Loaded HTML"
I implemented it in my project w/o using Blaze but using a lower-level Tracker dependencies. When the Template is rendered, I would dynamically start the player and then have a reactive data-source of the current position that I can control: https://github.com/Slava/talk-player/blob/master/client/player.js
This worked for me on Meteor 1.6.1.1.
template html
<template name="tebVideo">
<video class="video-js">
<source src="{{src}}" type="video/mp4">
</video>
</template>
template js
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import 'video.js/dist/video-js.css';
import videojs from 'video.js';
import './video.html';
// prevent analytics
window.HELP_IMPROVE_VIDEOJS = false;
// helper for local dev vs product cdn
const envUrl = (localPath, fileName) => {
if (Meteor.isProduction) {
return Meteor.settings.public.host.cdnUrl+'/'+fileName;
}
else {
return localPath+fileName;
}
};
Template.tebVideo.onRendered(() => {
const instance = Template.instance();
const video = videojs(document.querySelector('.video-js'),
{
controls: true,
autoplay: false,
preload: 'auto',
width: 854,
height: 480,
aspectRatio: "16:9",
poster: envUrl('/images/', instance.data.poster),
function() {
Log.log(['debug', 'video'], 'Video loaded ok');
}
}
);
});
Template.tebVideo.helpers({
src() {
const instance = Template.instance();
return envUrl('/videos/', instance.data.src);
}
});
show it
{{>tebVideo poster="three_robots.png" src="how-knowledge-automation-works.mp4"}}

Bootboxjs: how to render a Meteor template as dialog body

I have the following template:
<template name="modalTest">
{{session "modalTestNumber"}} <button id="modalTestIncrement">Increment</button>
</template>
That session helper simply is a go-between with the Session object. I have that modalTestNumber initialized to 0.
I want this template to be rendered, with all of it's reactivity, into a bootbox modal dialog. I have the following event handler declared for this template:
Template.modalTest.events({
'click #modalTestIncrement': function(e, t) {
console.log('click');
Session.set('modalTestNumber', Session.get('modalTestNumber') + 1);
}
});
Here are all of the things I have tried, and what they result in:
bootbox.dialog({
message: Template.modalTest()
});
This renders the template, which appears more or less like 0 Increment (in a button). However, when I change the Session variable from the console, it doesn't change, and the event handler isn't called when I click the button (the console.log doesn't even happen).
message: Meteor.render(Template.modalTest())
message: Meteor.render(function() { return Template.modalTest(); })
These both do exactly the same thing as the Template call by itself.
message: new Handlebars.SafeString(Template.modalTest())
This just renders the modal body as empty. The modal still pops up though.
message: Meteor.render(new Handlebars.SafeString(Template.modalTest()))
Exactly the same as the Template and pure Meteor.render calls; the template is there, but it has no reactivity or event response.
Is it maybe that I'm using this less packaging of bootstrap rather than a standard package?
How can I get this to render in appropriately reactive Meteor style?
Hacking into Bootbox?
I just tried hacked into the bootbox.js file itself to see if I could take over. I changed things so that at the bootbox.dialog({}) layer I would simply pass the name of the Template I wanted rendered:
// in bootbox.js::exports.dialog
console.log(options.message); // I'm passing the template name now, so this yields 'modalTest'
body.find(".bootbox-body").html(Meteor.render(Template[options.message]));
body.find(".bootbox-body").html(Meteor.render(function() { return Template[options.message](); }));
These two different versions (don't worry they're two different attempts, not at the same time) these both render the template non-reactively, just like they did before.
Will hacking into bootbox make any difference?
Thanks in advance!
I am giving an answer working with the current 0.9.3.1 version of Meteor.
If you want to render a template and keep reactivity, you have to :
Render template in a parent node
Have the parent already in the DOM
So this very short function is the answer to do that :
renderTmp = function (template, data) {
var node = document.createElement("div");
document.body.appendChild(node);
UI.renderWithData(template, data, node);
return node;
};
In your case, you would do :
bootbox.dialog({
message: renderTmp(Template.modalTest)
});
Answer for Meteor 1.0+:
Use Blaze.render or Blaze.renderWithData to render the template into the bootbox dialog after the bootbox dialog has been created.
function openMyDialog(fs){ // this can be tied to an event handler in another template
<! do some stuff here, like setting the data context !>
bootbox.dialog({
title: 'This will populate with content from the "myDialog" template',
message: "<div id='dialogNode'></div>",
buttons: {
do: {
label: "ok",
className: "btn btn-primary",
callback: function() {
<! take some actions !>
}
}
}
});
Blaze.render(Template.myDialog,$("#dialogNode")[0]);
};
This assumes you have a template defined:
<template name="myDialog">
Content for my dialog box
</template>
Template.myDialog is created for every template you're using.
$("#dialogNode")[0] selects the DOM node you setup in
message: "<div id='dialogNode'></div>"
Alternatively you can leave message blank and use $(".bootbox-body") to select the parent node.
As you can imagine, this also allows you to change the message section of a bootbox dialog dynamically.
Using the latest version of Meteor, here is a simple way to render a doc into a bootbox
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,MyCollection.findOne({_id}),box.find(".modal-body")[0]);
If you want the dialog to be reactive use
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,function() {return MyCollection.findOne({_id})},box.find(".modal-body")[0]);
In order to render Meteor templates programmatically while retaining their reactivity you'll want to use Meteor.render(). They address this issue in their docs under templates.
So for your handlers, etc. to work you'd use:
bootbox.dialog({
message: Meteor.render(function() { return Template.modalTest(); })
});
This was a major gotcha for me too!
I see that you were really close with the Meteor.render()'s. Let me know if it still doesn't work.
This works for Meteor 1.1.0.2
Assuming we have a template called changePassword that has two fields named oldPassword and newPassword, here's some code to pop up a dialog box using the template and then get the results.
bootbox.dialog({
title: 'Change Password',
message: '<span/>', // Message can't be empty, but we're going to replace the contents
buttons: {
success: {
label: 'Change',
className: 'btn-primary',
callback: function(event) {
var oldPassword = this.find('input[name=oldPassword]').val();
var newPassword = this.find('input[name=newPassword]').val();
console.log("Change password from " + oldPassword + " to " + newPassword);
return false; // Close the dialog
}
},
'Cancel': {
className: 'btn-default'
}
}
});
// .bootbox-body is the parent of the span, so we can replace the contents
// with our template
// Using UI.renderWithData means we can pass data in to the template too.
UI.insert(UI.renderWithData(Template.changePassword, {
name: "Harry"
}), $('.bootbox-body')[0]);

how to properly handle dom ready for Meteor

I am currently using iron-router and this is my very first attempt to try out the Meteor platform. I has been running into issues where most of the jquery libraries failed to initialized properly because the of the way Meteor renders html, $(document).ready() fires before any templates are rendered. I am wondering is there any callbacks from Meteor/iron-router that allows me to replace the jQuery's dom ready?
Also, how should I (easily and properly) handle the live update of the dom elements if some of them are customized by jQuery/javascript?
This is what i am currently doing, i feel like it is very hackish and probably would run into issues if the elements got updated after the initialization.
var jsInitalized = false;
Router.map(function () {
this.route('', {
path: '/',
layoutTemplate: 'default',
after: function(){
if(!jsInitalized){
setTimeout(function(){
$(document).ready( function() { $$$(); });
}, 0);
jsInitalized = true;
}
}
});
}
With Meteor you generally want to think about when a template is ready, not when the dom is ready.
For example, let's say you want to use the jQuery DataTables plugin to add sorting to a table element that's created by a template. You would listen to the template's rendered event and bind the plugin to the dom:
HTML:
<template name="data_table">
<table class="table table-striped" id="tblData">
</table>
</template>
JavaScript:
Template.data_table.rendered = function () {
$('#tblData').dataTable();
};
Now anytime the template is re-rendered (for example, if the data changes), your handler will be called and you can bind the jQuery plugin to the dom again.
This is the general approach. For a complete example (that includes populating the table with rows) see this answer.
Try making a separate .js file, call it rendered.js if you'd like. and then;
Template.layout.rendered = function ()
{
$(document).ready(function(){console.log('ready')});
}
I use template layout, but you can do Template.default.rendered. I hope that helps.
Also take a look at this part of documentation, especially the Template.events; http://docs.meteor.com/#templates_api
I use Meteor v0.8.0 with Iron Router (under Windows 7) and here is how I handle 'DOM ready':
When I want to modify the DOM after a specific template has been rendered:
I use Template.myTemplateName.rendered on the client side :
Template.blog.rendered = function()
{
$('#addPost').click(function()
{
...
});
}
When I want to modify the DOM after any new path has been rendered:
I use Router.onAfterAction, but there seems to be a trick:
Router.onAfterAction(function()
{
setTimeout(function()
{
$('.clickable').click(function()
{
...
});
}, 0);
});
Notice the setTimeout(..., 0), it doesn't work for me otherwise (DOM empty).
Notice that you can use onAfterAction on specific path, but most of the time I think it is redundant with the Template.myTemplateName.rendered method above.
What seems to be missing:
A way to modify the DOM after any template has been rendered.

How to use AngularUI Dialog for lightbox

I have a partial page that shows a list of files on the server. I want to allow the user to choose a file and display it in a lightbox dialog using AngularUI. I can't figure out how to get the filename that should be displayed into the dialog template correctly. Here's my file list html:
<tr ng-repeat="file in files | orderBy:orderProp">
<td>{{file.name}}</td>
</tr>
And here's the applicable part of that view's controller:
function FileListCtrl($scope, $http, $dialog)
{
.
.
.
$scope.openInLightbox = function(item){
var d = $dialog.dialog({
modalFade: false,
resolve: {item: function(){ return angular.copy(item); } }});
d.open('dialogs/lightboxTemplate.html', 'LightboxController');
}
}
and here's the lightboxController:
app.controller('LightboxController', ['$scope', 'dialog', 'item', function($scope, dialog, item){
$scope.item = item;
console.log("item:" + item);
$scope.submit = function(){
dialog.close('ok');
};
}]);
and here's my dialog template:
<img src={{item}} />
I have two things I don't understand:
1) I get a lightbox on the first image I choose correctly, but the console gives a 404 error getting "(URL path to image)/{{item}}". So I get an error, but the image still appears.
2) When I click outside the lightbox, it disappears and I can't reload a new image. I think this is due to having no close button?
Am I properly binding the "item" scope property into the dialog template? If not, what is the correct way?
Try using ng-src for example:
<img ng-src="{{item}}>
It will likely overcome the weirdness as /> is still valid html (just not html 5)

Resources