Meteor client-side transform collections with external data (youtube) - meteor

I'm working on a multi-tenant apps. Each app will have a video module which takes several youtube playlists/channels id as input.
Apps = {_id: "app01", playlists:['PL4IrNZLvgtED4RTH3xCf9085hwqKmb4lM','PLlYqpJ9JE-J8QIlua0KsBIxp-VQLyM_nO']}
Apps = {_id: "app02", playlists:['id22','id23']}
and so on.
When user redirect to address localhost:3000/app/:_id, the app subscribe to Meteor.subscribe('apps',_id).
Now on the client side, i need to render a page that shows a list of playlists (or list of channels) with playlist's name, playlist thumbnail; and then if user clicks on the name/thumbnail, it displays another page with a list of videos belongs to that playlist/channel.
What i am doing is to have a helper to retrieve all youtube playlists's info:
Template.Videos.helpers({
myPlaylist:function(){
return Apps.findOne({}, {
transform: function (doc){
playlists = doc.playlists;
doc.date = new Date(); //for testing
doc.videos = [];
for (var i=0;i<playlists.length;i++){
console.log('find playlist: ',playlists[i]);
var url = 'https://gdata.youtube.com/feeds/api/playlists/'+playlists[i]+'?v=2&alt=json';
$.getJSON(url, function(response){
var video={};
video._id = response.feed.yt$playlistId.$t;
video.title = response.feed.title;
video.entry = response.feed.entry;
videos.push(video);
})//end getJSON
}//end for
doc.videos = videos;
console.log('Result:', doc);
return doc;
}
});
}
});
The problem is in my template, I can see myPlaylist.date as a result of transform, but I cannot see myPlaylist.videos (although I see the console.log result has data)
Does anyone has an idea how to solve this? Very appreciate!

As #Ian Jones commented, $.getJSON is asynchronous so doc.videos will never have a value where you’ve put your console.log. To solve this, use the ReactiveVar package:
Template.Videos.onCreated(function() {
this.videos = new ReactiveVar([]);
var self = this;
Apps.findOne({}, {
transform: function (doc){
playlists = doc.playlists;
for (var i=0;i<playlists.length;i++){
console.log('find playlist: ',playlists[i]);
var url = 'https://gdata.youtube.com/feeds/api/playlists/'+playlists[i]+'?v=2&alt=json';
$.getJSON(url, function(response){
var video={};
video._id = response.feed.yt$playlistId.$t;
video.title = response.feed.title;
video.entry = response.feed.entry;
var videos = self.videos.get();
videos.push(video);
self.videos.set(videos);
})//end getJSON
}//end for
}
});
}
});
Template.Videos.helpers({
myPlaylist:function(){
var instance = Template.instance();
return instance.videos.get();
}
});

Thanks for all your helps. I was unwilling to do reactive-var from the beginning, but your answer motivated me to go for it, big thanks.
This is my final code:
Template.Videos.created = function(){
var instance = this;
instance.videos = new ReactiveVar([]);
instance.autorun(function(){
MenusList.findOne({}, {
transform: function (doc){
playlists = doc.playlists;
for (var i=0;i<playlists.length;i++){
console.log('find playlist: ',playlists[i]);
var url = 'https://gdata.youtube.com/feeds/api/playlists/'+playlists[i]+'?v=2&alt=json';
$.getJSON(url, function(response){
var video={};
video._id = response.feed.yt$playlistId.$t;
video.title = response.feed.title;
video.entry = response.feed.entry;
var videos = instance.videos.get();
videos.push(video);
instance.videos.set(videos);
})//end getJSON
}//end for
return doc;
}//end transform
});//end findOne
});//end autorun
}
Template.Videos.helpers({
myPlaylist:function(){
var instance = Template.instance();
return instance.videos.get();
}
});

Related

google.maps.places.Autocomplete 502 error

I am using the google.maps.places.Autocomplete API and this morning I was getting a 502 Bad Gateway error. It lasted about 10 minutes and started working again. I assume this had to do with the service being unavailable.
I was wondering how I can error handle when this happens. My (javascript) autocomplete code looks like this:
$('#StartAddress').change(function () {
google.maps.event.trigger(startAutocomplete, 'place_changed');
return false;
});
var source, destination;
var directionsDisplay;
var directionsService;
if (typeof google === 'object' && typeof google.maps === 'object') {
directionsService = new google.maps.DirectionsService();
// set up places autocomplete
var start = document.getElementById('StartAddress');
var startAutocomplete = new google.maps.places.Autocomplete(start);
var end = document.getElementById('EndAddress');
var endAutocomplete = new google.maps.places.Autocomplete(end);
// add the places auto complete listener for when the values change
startAutocomplete.addListener('place_changed', function () {
var startAddress = $('#StartAddress').val();
var endAddress = $('#EndAddress').val();
if (endAddress && startAddress) {
GetRoute(startAddress, endAddress, false);
}
});
endAutocomplete.addListener('place_changed', function () {
var endAddress = $('#EndAddress').val();
var startAddress = $('#StartAddress').val();
if (endAddress && startAddress) {
GetRoute(startAddress, endAddress, false);
}
});
directionsDisplay = new google.maps.DirectionsRenderer({ 'draggable': false });
}
The GetRoute(startAddress, endAddress, false) function is a call to the google.maps.Map and that works fine. It was only the autocomplete service that was down.
Also, is it possible this error occurred because I am using the developer key instead of production? Like the googles dev environment is much more resource limited?
The service was down this time. Try again now.

template rendered is not working properly in meteor JS

template rendered is not working
when user successfully login in to system i redirect to profile page that time data is not get but if i visit another page and come back to profile page that time it is working fine. also when i reload page that time also it is not working
here is code
Template.profile.rendered = function(){
var user_email = {};
user_email.mail = Session.get('email');
var imgName = Session.get('image');
Meteor.call("imgSend",imgName,function(error, result){
$('.user_profile_image').attr("src",result)
});
Meteor.call("getLinkMeta",user_email,function(error, result){
var link_all_info = [];
var walldata = [];
var total = result.length;
var processed = 0;
var t = result.forEach(function (entry){
var link_info = {};
link_info.link_id = entry._id;
Meteor.call("getCommentList",link_info, function (error, res){
if(error){
console.log("e");
}else{
entry.comments = res;
}
processed++
if(processed == total){
//walldata=result;
}
});
});
Template.profile.walldata = function(){
return result;
};
//return result;
});
}
Router.route('profile', {
path: '/profile',
data: function() {
/* Meteor.subscribe("Users");
Meteor.subscribe("Link");
Meteor.subscribe("Linkfav");
Meteor.subscribe("LinkLike");
Meteor.subscribe("LinkComment"); */
$("body").removeClass('home');
this.render('profile');
setTimeout(function(){
$('#username').html(Session.get('first_name'));
$('#profile_username').html(Session.get('first_name'));
$('#setting_name').val(Session.get('first_name'));
$('#setting_username').val(Session.get('first_name'));
$('#setting_email').val(Session.get('email'));
$('#user_id').val(Session.get('id'));
$('.setting_day').val(Session.get('day'));
$('.setting_month').val(Session.get('month'));
$('.setting_year').val(Session.get('year'));
if(Session.get('image')!= ''){
$('.user_profile_image').attr("src",Session.get('image'));
}
if(Session.get('gender') == 0){
$('#user_gender').html('Male');
}else{
$('#user_gender').html('Female');
}
$('#day').html(Session.get('day'));
$('#month').html(Session.get('month'));
$('#year').html(Session.get('year'));
},100);
},onBeforeAction:function(){
if(Session.get('email')){
this.next();
}else {
//this.next();
this.redirect('/');
}
}
});
When you refresh/reload the page Session values are get undefined. You can get the current user email using meteor.user(). You just have to replace you session.get('email') like this.
var user_email = {};
user_email.mail = Meteor.user().emails[0].address;
I hope that is what you are looking for.

resize and save files to s3 in meteor

Is there any best way to resize and save files to s3 in meteor.
I though about using cfs packages, but they put too much load on server.
To directly upload images to the s3 I'm using slingshot which is very fine.
but slingshot takes only file objects as inputs it doesn't take streams to store the files.
Is there any option to resize the image in client side and pass it to the slingshot package
package: https://github.com/CulturalMe/meteor-slingshot
issue: https://github.com/CulturalMe/meteor-slingshot/issues/36
Yes it's possible with clientside-image-manipulation, here's my untested interpretation of the docs:
Template.upload.events({
'change #image-upload': function(event, target) {
var uploader = new Slingshot.Upload("myFileUploads");
var file = event.target.files[0];
var img = null;
processImage(file, 300, 300, function(data){
uploader.send(data, function (error, downloadUrl) {
if(error)
throw new Meteor.Error('upload', error);
Meteor.users.update(Meteor.userId(), {$push: {"profile.files": downloadUrl}});
});
});
}
});
There are other image related plugins worth investigating at atmosphere.js
I've found that the following works in order to integrate Clientside Image Manipulation with Slingshot (asynchronous code with ES6 promises):
var uploader;
function b64ToBlob(b64Data, contentType, sliceSize) {
var byteNumbers, i, slice;
var offset = 0;
var byteCharacters = atob(b64Data);
var byteArrays = [];
sliceSize = sliceSize || 512;
byteArrays = [];
while (offset < byteCharacters.length) {
slice = byteCharacters.slice(offset, offset + sliceSize);
byteNumbers = [];
for (i = 0; i < slice.length; ++i) {
byteNumbers.push(slice.charCodeAt(i));
}
byteArrays.push(new Uint8Array(byteNumbers));
offset += sliceSize;
}
return new Blob(byteArrays, {type: contentType});
}
uploader = new Slingshot.Upload("pictures");
new Promise(function (resolve) {
processImage(file, 300, 300, resolve);
}).then(function (dataUri) {
var match = /^data:([^;]+);base64,(.+)$/.exec(dataUri);
return [file.name, match[1], match[2]];
}).then(function (params) {
var name = params[0];
var type = params[1];
var b64 = params[2];
return new Promise(function (resolve, reject) {
var blob = b64ToBlob(b64, type);
blob.name = name;
uploader.send(blob, function (error, downloadUrl) {
if (error != null) {
reject(error.message);
} else {
resolve(downloadUrl);
}
});
});
});
Conversion from Base64 to blob is borrowed from https://stackoverflow.com/a/16245768/1238764.
There are a lot of image manipulation plugins. The one I use is: cropper
It's a jquery based plugin which basically does the thing you want + a bit more. After manipulation you can convert the image to a canvas and with the dataToUrl() method you can pass the data of the new manipulated image to any datasource of your choice.

Firebase trigger on current time

I create something like calendar with events.
I want to popup message for user when the event come up.
My solution:
//this code runs in loop
var offsetRef = new Firebase("https://example.firebaseio-demo.com/.info/serverTimeOffset");
offsetRef.on("value", function(snap) {
var offset = snap.val();
var estimatedServerTimeMs = new Date().getTime() + offset;
fbRef.endAt(estimatedServerTimeMs).once("value", function(ss) {/*remove old events*/});
});
Is´t possible something like this (without loop)?
fbRef.endAt(Firebase.ServerValue.TIMESTAMP).on("child_added", function(snap) {/*...*/});
Thank you for reply
It's best to calculate the serverTimeOffset just once, and then wrap the popup in a setTimeout after you know what the current time (approximately) is:
var timeouts = [];
offsetRef.on("value", function(snap) {
// Cancel all previous timeouts.
timeouts.forEach(function(i) { clearTimeout(i); });
var estTime = new Date().getTime() + snap.val();
var promptTime = /* Get time at which you want to prompt */
timeouts.push(setTimeout(function showPrompt() {
...
}, promptTime));
});

Google Places API autocomplete not working

Right now, the autocomplete box works just fine when I click on the location, but when I press down, highlight the location that I want to go to, and press enter, it simply goes back to the home location of the map. Any insights on this? I call this function in initialize(). I'm lost as to what I possibly did wrong. Is this just a google api bug? If so, any insights as to how to work around it?
function setupAutoComplete() {
var defaultBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(-90, -180),
new google.maps.LatLng(90, 180));
var input = document.getElementById('placeSearch');
var options = {
bounds: defaultBounds,
types: ['(regions)']
};
autocomplete = new google.maps.places.Autocomplete(input, options);
google.maps.event.addListener(autocomplete, 'place_changed', function() {
alert('hi');
removeAllOverlays();
var place = autocomplete.getPlace();
var mapCenter = place.geometry.location;
var colLat = mapCenter.lat() - (halfPoints)*latSeparation;
var colLng = mapCenter.lng() - (halfPoints)*lngSeparation;
var tempStart = new google.maps.LatLng(colLat, colLng);
map.setCenter(mapCenter);
pointArray[0][0] = tempStart;
reService();
mapSearch();
drawBounds();
});
}
Thanks so much!
I guess the input#placeSearch is placed inside a <form>.
You submit the form when you press [ENTER].
You may either remove the surrounding form or cancel the submission by adding:
onsubmit="return false"
...to the form-element.
I just hit this issue and went with the following, as I do want to submit the form at a later stage. This code is from google groups.
var input = document.getElementById('create-location');
var options = {
//types: ['(cities)'],
};
autocomplete = new google.maps.places.Autocomplete(input, options);
google.maps.event.addDomListener(input, 'keydown', function(e) {
if (e.keyCode == 13)
{
if (e.preventDefault)
{
e.preventDefault();
}
else
{
// Since the google event handler framework does not handle early IE versions, we have to do it by our self. :-(
e.cancelBubble = true;
e.returnValue = false;
}
}
});

Resources