Reusing the same block helper in Meteor causes odd context issue - javascript

I'm attempting to reuse a custom Block Helper that I wrote to provide basic carousel functionality to some of my templates.
simple-carousel.html
<template name="SimpleCarousel">
<div class="simple-carousel {{class}}">
<div class="slides">
{{#each slides}}
{{> UI.contentBlock this}}
{{/each}}
</div>
{{#if showControls}}
{{> SimpleCarouselControls}}
{{/if}}
</div>
</template>
<template name="SimpleCarouselControls">
// control structure here
</template>
simple-carousel.js
var actions = {
back: function() {
// move slide back once
},
forward: function() {
// move slide forward once
}
};
var showSlide = function() {
// code to show the next slide
};
Template.SimpleCarousel.onRendered(function() {
// set up carousel logic here
});
Template.SimpleCarousel.events({
'click [data-sc-move="forward"]': function() {
actions.forward();
},
'click [data-sc-move="back"]': function() {
actions.back();
}
});
breaking_stories.html
<template name="BreakingStories">
{{#SimpleCarousel class="breaking-stories" showControls=false autoForward=8000 slides=breakingStories}}
{{> BreakingStorySlide}}
{{/SimpleCarousel}}
</template>
<template name="BreakingStorySlide">
<div class="breaking-story slide">
<div class=breaking-story-title">{{title}}</div>
</div>
</template>
breaking_stories.js
Template.BreakingStories.helpers({
breakingStories: function() {
return BreakingStories.find();
}
});
daily_summary.html
<template name="DailySummary">
{{#with thisDailySummary}}
{{#SimpleCarousel class="daily-summaries" showControls=true slides=items}}
{{> DailySummarySlide}}
{{/SimpleCarousel}}
{{/with}}
</template>
<template name="DailySummarySlide">
<div class="daily-summary slide">
<div class="daily-summary-title">{{title}}</div>
</div>
</template>
I've tried to simplify the code as there is a lot more HTML involved in the templates. Anyway, as you can see I've defined the #SimpleCarousel block helper and used it in two places: the breaking stories section, and the daily summaries section. These two templates happen to be on the same page (route), so they are near each other on the page. I need one of them to auto cycle through, in which I've provided the autoForward property to the helper, and the other one should just show controls.
Both templates render fine and show the correct data, but the problem lies in that instead of the breaking news template doing any automatic cycling, the other one does (and does it twice), as if they are sharing the same context.
My question is, can I use custom Block Helpers multiple times on the same route safely? I'm open to any suggestions on how to do this a better/different way.

Thanks to #JeremyK for pointing me in the right direction; it happened to be the exact code I left out which was the problem. Of course!
Here's what I had in the old version:
simple_carousel.js
var $slideContainer, $controls, $markers, $activeSlide, $nextSlide;
var actions = {
back: function() {
// move slide back
},
forward: function() {
// move slide forward
}
};
function showSlide() {
// show the "next" slide
}
Template.SimpleCarousel.onRendered(function() {
var data = this.data;
$slideContainer = this.$('.sc-slides');
// rest of this code is irrelevant
});
I had thought that the variables I had declared on the first line were independent of multiple instantiations of the templates I was using, but I was wrong. The first use of $slideContainer = this.$('.sc-slides'); worked fine, but $slideContainer and all the others are shared.
To fix this, I simply moved the local variables/actions into Template.SimpleCarousel.onRendered
Template.SimpleCarousel.onRendered(function() {
var $slideContainer, $markers, ...
this.actions = {
//...
};
});
Template.SimpleCarousel.events({
'click [data-sc-move="forward"]': function( event, template ) {
template.actions.forward();
}
//...
});

Related

Document does not display only on the first page

I am experiencing this weird scenario that I am unable to figure out what the problem is. There is a pagination for a collection which works fine when navigating. I have 5 documents in a collection with each to display per 2 on a page sing the pagination. Each document has a url link that when clicked it displays the full page for the document.
The challenge now is that if I click a document on the first page, it displays the full record, but if I navigate to the next page and click a document, it displays a blank page. I have tried all I could but haven't gotten what is to be made right.
These earlier posts are a build up to this present one: Publish and subscribe to a single object Meteor js, Meteor js custom pagination.
This is the helper
singleSchool: function () {
if (Meteor.userId()) {
let myslug = FlowRouter.getParam('myslug');
var subValues = Meteor.subscribe('SingleSchool', myslug );
if (myslug ) {
let Schools = SchoolDb.findOne({slug: myslug});
if (Schools && subValues.ready()) {
return Schools;
}
}
}
},
This is the blaze template
<template name="view">
{{#if currentUser}}
{{#if Template.subscriptionsReady }}
{{#with singleSchool}}
{{singleSchool._id}}
{{singleSchool.addschoolname}}
{{/with}}
{{/if}}
{{/if}}
</template>
try this;
onCreated function:
Template.view.onCreated(function(){
this.dynamicSlug = new ReactiveVar("");
this.autorun(()=>{
// When `myslug` changes, subscription will change dynamically.
this.dynamicSlug.set(FlowRouter.getParam('myslug'));
Meteor.subcribe('SingleSchool', this.dynamicSlug.get());
});
});
Helper
Template.view.helpers({
singleSchool(){
if (Meteor.userId()) {
let school = SchoolDb.findOne({slug: Template.instance().dynamicSlug.get()});
if (school) {
return school;
}
}
}
});

Accessing parent 'onRendered' js code from childern generated in each statement

I am quite new to Meteor and Blaze so I am sorry if something is unclear here... But I want to implement this 'Product Quick View' https://codyhouse.co/gem/css-product-quick-view/ in my Meteor project.
The animation is working great when I am not using each statement. In each statement the data context change and when I click cd-trigger nothing happens. How can I access the code inside ParentTemplate.onRendered for this child Template? Is there some workaround to this problem?
<template name="ParentTemplate">
{{> Product}} // works properly
{{#each products}} // does not work
{{> Product}}
{{/each}}
</template>
<template name="Product">
<li class="cd-item">
<img src="img/item-1.jpg" alt="Item Preview">
Quick View
</li> <!-- cd-item -->
</template>
and the .js file
Template.ParentTemplate.onRendered( function () {
//open the quick view panel
$('.cd-trigger').on('click', function(event){
console.log('Hello!');
var selectedImage = $(this).parent('.cd-item').children('img'),
slectedImageUrl = selectedImage.attr('src');
/the code continues but it is very long/
This fixed the problem
Template.ParentTemplate.onRendered( function () {
this.autorun(() => {
if (this.subscriptionsReady()) {
//open the quick view panel
$('.cd-trigger').on('click', function(event){
console.log('Hello!');
var selectedImage = $(this).parent('.cd-item').children('img'),
slectedImageUrl = selectedImage.attr('src');
/the code continues but it is very long/

Cannot get a div's height on Template.onRendered() Meteor

I'm trying to get the higher height of some the "big-card" in my DOM to put them all at the same height.
{{#each skills}}
<div class="big-card">
<div class="card-grid add-option-part">
<div class="card-text">
<p>{{this}}</p>
</div>
</div>
<div class="option-part">
<div class="half-option-part white-line-part"><img class="seemore-button" src="/img/expand.png"/></div>
<div class="half-option-part">{{> StarsRating}}</div>
</div>
</div>
{{/each}}
The function to take get their heights is :
function boxContentNormal(){
var elementHeights = [];
$('.big-card').map(function() {
var currentItem = $(this).find('.card-text');
var currentItemHeight = currentItem.height();
var currentItemPaddingTop = parseInt(currentItem.css('padding-top').replace("px", ""));
var currentItemPaddingBottom = parseInt(currentItem.css('padding-bottom').replace("px", ""));
elementHeights.push(currentItemHeight + currentItemPaddingBottom + currentItemPaddingTop);
});
var maxHeight = 0;
$.each(elementHeights, function(i, element){
maxHeight = (element > maxHeight) ? element : maxHeight;
});
console.log("Max height : "+maxHeight);
}
It's called by that :
Template.MyTemplate.onRendered(function(){
boxContentNormal();
$(window).resize(function(){
boxContentNormal();
});
});
This function is used when a new route is called and the template will be displayed at the same time.
It works like that:
I click on a link that goes to a new route
Once arrived to the route, the template will be displayed
When the template is rendered, the function is called for the first time
After that, if the window resizes the function will be called again
The problem is at the third step, when the function is called it doesn't get the height of the cards. Then all the heights are equal to 0. And when I resize the window, it works fine.
So I think the function is called too early and the "cards" don't exist yet. Do you know how I can "wait" for them or another solution ?
Thanks :)
I suppose your skills helper is returning a cursor from a client side collection synced with the server via the Pub/Sub mechanism.
You can use the template controller pattern along with template subscriptions to make sure your template is initially rendered after the published data made its way to the client.
HTML
<template name="skillsController">
{{#if Template.subscriptionsReady}}
{{> skillsList items=skills}}
{{/if}}
</template>
<template name="skillsList">
{{#each items}}
{{! skill item}}
{{/each}}
</template>
JS
Template.skillsController.onCreated(function(){
this.subscribe("skills");
});
Template.skillsController.helpers({
skills: function(){
return Skills.find();
}
});
Template.skillsList.onRendered(function(){
console.log(this.$(".big-card").length == this.data.items.count());
});
Using this pattern, the skillsList template onRendered life cycle event is executed after the data is already there so the {{#each}} block helper will correctly render its initial list of skill items.
If you don't wait for the subscription to be ready, the initial template rendering will run using an {{#each}} fed with an empty cursor. Once the data arrives, the {{#each}} will rerun and correctly render the items, but the onRendered hook won't.

couldn't get Bootstrap carousel to work with Ember

trying to understand why its not working.
I have something like this.
<div class="carousel slide" id="new-prospect-container">
<div class="carousel-inner">
{{#each model}}
<div class="item">
...
</div>
{{/each}}
</div>
</div>
But Botostrap's first class api means that we don't need to execute any JS methods and their widgets will work automatically. The problem is I suspect Bootstrap would have executed this prior to my {{model}} being filled up by an Ajax requests. So this Carousel won't work.
What's annoying is i already tried turning off their data-api - $(document).off('.data-api'); and manually call their carousel method - still won't work.
The carousel works with hard coded data - but once I try to populate the carousel div items from my Ember model, it just stops working.
Any idea?
Why does this exist - https://github.com/emberjs-addons/ember-bootstrap ? does it exist to exactly solve my issue here? (although there's no carousel)
1 - I hope that this jsfiddle solve your problem.
App.CarouselView = Ember.View.extend({
templateName: 'carousel',
classNames: ['carousel', 'slide'],
init: function() {
this._super.apply(this, arguments);
// disable the data api from boostrap
$(document).off('.data-api');
// at least one item must have the active class, so we set the first here, and the class will be added by class binding
var obj = this.get('content.firstObject');
Ember.set(obj, 'isActive', true);
},
previousSlide: function() {
this.$().carousel('prev');
},
nextSlide: function() {
this.$().carousel('next');
},
didInsertElement: function() {
this.$().carousel();
},
indicatorsView: Ember.CollectionView.extend({
tagName: 'ol',
classNames: ['carousel-indicators'],
contentBinding: 'parentView.content',
itemViewClass: Ember.View.extend({
click: function() {
var $elem = this.get("parentView.parentView").$();
$elem.carousel(this.get("contentIndex"));
},
template: Ember.Handlebars.compile(''),
classNameBindings: ['content.isActive:active']
})
}),
itemsView: Ember.CollectionView.extend({
classNames: ['carousel-inner'],
contentBinding: 'parentView.content',
itemViewClass: Ember.View.extend({
classNames: ['item'],
classNameBindings: ['content.isActive:active'],
template: Ember.Handlebars.compile('\
<img {{bindAttr src="view.content.image"}} alt=""/>\
<div class="carousel-caption">\
<h4>{{view.content.title}}</h4>\
<p>{{view.content.content}}</p>\
</div>')
})
})
});
2 - I don't know why the carousel isn't include in ember-boostrap.
So I have a solution for this, but it's not for the squeamish.
Bootstrap isn't specific enough about what elements it looks for in the case of the Carousel. When the carousel function goes to inventory what elements it's to manipulate, it chokes on the metamorph tags that Ember injects into the DOM. Basically, when it goes to see how many images there are, it will always find 2 more than there actually are.
I made changes to the underlying code of the carousel in the bootstrap library, here's what I did.
Line 337, change:
this.$items = this.$active.parent().children()
TO
this.$items = this.$active.parent().children('.item')
Line 379, change:
var $next = next || $active[type]()
TO
var $next = next || $active[type]('.item')
Line 401, change:
var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
TO
var $nextIndicator = $(that.$indicators.children('li')[that.getActiveIndex()])
This helps the carousel plugin ignore the metamorph tags.
Hope this helps.
I had the same issue and solved it by using the following method. Note that I'm using ember-cli but it's fairly easy to adapt.
This is the templates/components/photo-carousel.hbs file:
<div id="my-carousel" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
{{#each photo in photos}}
<li data-target="#my-carousel" data-slide-to=""></li>
{{/each}}
</ol>
<div class="carousel-inner" role="listbox">
{{#each photo in photos}}
<div class="item">
<img {{bind-attr src="photo.completeUrl" title="photo.caption" alt="photo.caption"}} />
<div class="carousel-caption">
{{photo.caption}}
</div>
</div>
{{/each}}
</div>
<!-- removed right and left controls for clarity -->
</div>
This is the components/photo-carousel.js:
export default Ember.Component.extend({
didInsertElement: function () {
// Add the active classes (required by the carousel to work)
Ember.$('.carousel-inner div.item').first().addClass('active');
Ember.$('.carousel-indicators li').first().addClass('active');
// Set the values of data-slide-to attributes
Ember.$('.carousel-indicators li').each(function (index, li) {
Ember.$(li).attr('data-slide-to', index);
});
// Start the carousel
Ember.$('.carousel').carousel();
}
});
Note that setting the active classes manually will not be required with future versions of Ember since the each helper will provide the index of the current item.

Meteor: Hide or remove element? What is the best way

I am quite new with Meteor but have really been enjoying it and this is my first reactive app that I am building.
I would like to know a way that I can remove the .main element when the user clicks or maybe a better way would be to remove the existing template (with main content) and then replace with another meteor template? Something like this would be simple and straightforward in html/js app (user clicks-> remove el from dom) but here it is not all that clear.
I am just looking to learn and for some insight on best practice.
//gallery.html
<template name="gallery">
<div class="main">First run info.... Only on first visit should user see this info.</div>
<div id="gallery">
<img src="{{selectedPhoto.url}}">
</div>
</template>
//gallery.js
firstRun = true;
Template.gallery.events({
'click .main' : function(){
$(".main").fadeOut();
firstRun = false;
}
})
if (Meteor.isClient) {
function showSelectedPhoto(photo){
var container = $('#gallery');
container.fadeOut(1000, function(){
Session.set('selectedPhoto', photo);
Template.gallery.rendered = function(){
var $gallery = $(this.lastNode);
if(!firstRun){
$(".main").css({display:"none"});
console.log("not");
}
setTimeout(function(){
$gallery.fadeIn(1000);
}, 1000)
}
});
}
Deps.autorun(function(){
selectedPhoto = Photos.findOne({active : true});
showSelectedPhoto(selectedPhoto);
});
Meteor.setInterval(function(){
selectedPhoto = Session.get('selectedPhoto');
//some selections happen here for getting photos.
Photos.update({_id: selectedPhoto._id}, { $set: { active: false } });
Photos.update({_id: newPhoto._id}, { $set: { active: true } });
}, 10000 );
}
If you want to hide or show an element conditionaly you should use the reactive behavior of Meteor: Add a condition to your template:
<template name="gallery">
{{#if isFirstRun}}
<div class="main">First run info.... Only on first visit should user see this info.</div>
{{/if}}
<div id="gallery">
<img src="{{selectedPhoto.url}}">
</div>
</template>
then add a helper to your template:
Template.gallery.isFirstRun = function(){
// because the Session variable will most probably be undefined the first time
return !Session.get("hasRun");
}
and change the action on click:
Template.gallery.events({
'click .main' : function(){
$(".main").fadeOut();
Session.set("hasRun", true);
}
})
you still get to fade out the element but then instead of hiding it or removing it and having it come back on the next render you ensure that it will never come back.
the render is triggered by changing the Sessionvariable, which is reactive.
I think using conditional templates is a better approach,
{{#if firstRun }}
<div class="main">First run info.... Only on first visit should user see this info.</div>
{{else}}
gallery ...
{{/if}}
You'll have to make firstRun a session variable, so that it'll trigger DOM updates.
Meteor is reactive. You don't need to write the logic for redrawing the DOM when the data changes. Just write the code that when X button is clicked, Y is removed from the database. That's it; you don't need to trouble yourself with any interface/DOM changes or template removal/redrawing or any of that. Whenever the data that underpins a template changes, Meteor automatically rerenders the template with the updated data. This is Meteor’s core feature.

Categories