How can I create a wizard-like experience with Meteor? - javascript

I want to break a big web page up into bite-size chunks for the user, so that they are not overwhelmed by the size of the page / the amount of information they have to enter (all at once, anyway).
I'm using Meteor, and would like to know how to accomplish this with that platform.
The first idea that comes to mind is to create a separate template for each "sub page" I want to show the user, and then present the templates one after another. I'm thinking maybe something like this:
<body>
<h2>Thirteen Ways of Looking at a Duckbilled Platypus</h2>
<br/>
<br/>
<div class="container">
{{> whizzardBlizzard}}
</div>
</body>
<template name="whizzardBlizzard">
<form>
{{#if firstStep}}
{{> firstStepTemplate}}
{{/if}}
{{#if secondStep}}
{{> secondStepTemplate}}
{{/if}}
{{#if thirdStep}}
{{> thirdStepTemplate}}
{{/if}}
<input type="submit" value="Submit" class="button">
</form>
</template>
<template name="firstStepTemplate">
<h2>Step 1</h2>
</template>
<template name="secondStepTemplate">
<h2>Step 2</h2>
</template>
<template name="thirdStepTemplate">
<h2>Step 3</h2>
</template>
The submit event handler would be a template event method that would save the data entered at each step and update the value of a "counter", something like:
Template.whizzardBlizzard.events({
"submit form": function (event) {
//event.preventDefault(); <= should this be uncommented?
// save the vals to a new document in a collection
Session.set('stepNum', Session.get('stepNum') + 1);
}
});
The "firstStep," "secondStep," etc. would perhaps be template helpers, such as:
Template.whizzardBlizard.helpers({
firstStep: function() {
return (Session.get('stepNum') == 1);
},
secondStep: function() {
return (Session.get('stepNum') == 2)
},
thirdStep: function() {
return (Session.get('stepNum') == 3)
}
. . . // etc.
});
...but am not sure if this will really fill the bill or be the best approach. Is there a "blessed" way of doing this with Meteor, or an accepted pattern?

Related

I cant seem to find the error in Meteor

I am new at meteor and I am having some issues. I am creating a social network app and what it basically does is signs up user and the user can post and follow others. Thats the basic functionality it does right now. I want to add something that when a user clicks on other users profile it shows that users post. But the code isnt working and doesnt show any error at all
Template
<template name="profileArea">
{{#if currentUser}}
<div id="side-profile" class="side-box">
<a class="filter-user">{{currentUser.username}}</a>
</div>
{{/if}}
<div id="side-all" class="side-box">
<a class="community">Community</a>
</div>
{{#if currentUser}}
<div id="side-like" class="side-box">
<h3>Following</h3>
<div class="boxcontent">
{{#each username in following}}
<div>
<a class="filter-user">{{username}}</a>
</div>
{{/each}}
</div>
</div>
<div id="side-likeyou" class="side-box">
<h3>Follows You</h3>
<div class="boxcontent">
{{# each followers}}
<div>
<a class="filter-user">{{username}}</a>
</div>
{{/each}}
</div>
</div>
{{/if}}
</template>
Code:
Template.profileArea.events({
'click .filter-user': function(event){
event.preventDefault();
var selectedUser = event.target.text;
Session.set('username', selectedUser);
},
'click .community': function(event){
event.preventDefault();
Session.set('username', null);
}
});
Template.postsList.helpers({
posts: function() {
//Stuff should happen here but its not -_-
var result;
if(Session.get('username')){
result = Post.find({username: Session.get('username')}, {sort:{created: -1}});
}
else{
result = Post.find({}, {sort:{created: -1}});
}
return result;
}
});
The problem in this case is that you have never actually rendered your postsList template (you have only just defined it).
If you want to actually see postsList you need to call {{> postsList }} from somewhere in your profileArea's HTML. This will render that template and then your postsList.posts helper will execute (and change reactively when Session.get('username')) changes.
Maybe add this after the 'Follows You' section of the profile.
<div id="side-posts" class="side-box">
<h3>Posts</h3>
<div class="boxcontent"> {{> postsList }} <div>
</div>

Running Javascript at end of loading all Meteor templates load

I have the following template code:
<template name="dashboard">
<div class="content-container-with-sidebar clearfix">
<div class="dashboard-container">
{{#if Template.subscriptionsReady}}
<div id=masonry-grid class="masonry-grid clearfix">
{{> timePieChartCard}}
{{> expensePieChartCard}}
{{> HighlightsCard}}
{{> initializeMasonry}}
</div>
{{/if}}
</div>
</div>
</template>
I'd like to run the initializer for the masonry library after all the templates and their subscriptions/helpers load within the main dashboard template.
Each card has code similar to this:
<template name="HighlightsCard">
{{#if highlightsExist}}
<div class="col-md-6 masonry-grid-item">
<div class="card highlights-card">
</div>
</div>
{{/if}}
</template>
Template.highlightsCard.onCreated(function() {
this.autorun(() => {
this.subscribe('userOwnClientHighlightsData');
});
});
Template.highlightsCard.helpers({
highlights() {
return Highlights.find({}, {
limit: 4,
sort: {createdAt: -1}
}).fetch();
},
highlightsExist() {
return (Highlights.find().count() > 0);
}
});
I currently am setting a session that increases when each onRendered is run within the card templates, but once they all render their contents have still not loaded. I need the class masonry-grid-item to be visible in order for me to initialize masonry.
What's the best way to get a similar result as jquery's $(document).ready() ?
Update: each individual card in the dashboard template has it's own subscription to a different dataset.
Look at Tracker.afterFlush
Schedules a function to be called during the next flush
For example:
Template.dashboard.onRendered(()=>{
Tracker.afterFlush(()=>{
...your code here
});
});
If I understand the problem correctly, you want to initialize your component after the subscription is complete and after all sub-templates (that also depend on the subscription data) are rendered.
Since I don't know what your JavaScript looks like, here is an example of what you can try.
Template.dashboard.onRendered(function() {
this.isReady = new ReactiveVar(false);
this.subscribe('subscriptionName', () => {
this.isReady.set(true);
Tracker.afterFlush(() => {
// Initialize component here
});
});
});
Template.dashboard.helpers({
isReady: () => {
return Template.instance().isReady.get();
},
});
And then use the isReady ReactiveVar in your template. In this case, isReadymay not be necessary (e.g. you could probably use the Template.subscriptionsReady helper), but I like to have explicit control of when to render incase my logic gets more complex in the future. Note, I removed {{> initializeMasonry}} because it looked like you were attempting to initialize your component via a helper.
<template name="dashboard">
<div class="content-container-with-sidebar clearfix">
<div class="dashboard-container">
{{#if isReady}}
<div id=masonry-grid class="masonry-grid clearfix">
{{> timePieChartCard}}
{{> expensePieChartCard}}
{{> HighlightsCard}}
</div>
{{/if}}
</div>
</div>
</template>
The callback to Tracker.afterFlush will get executed on the next Blaze render cycle, which will be after your sub-templates are rendered.

Ember JS Enable dropdown only on element which was clicked

I am an Ember newbie and can't get my head around how to run events on specific elements populated via records on my ember model.
Here is my Template
{{#each user in model }}
{{#each activecredit in user.activecredits}}
<div class="col-lg-2 hive-credit-box active-credit">
<div class="credit-brandname">
{{activecredit.Brandname}}
</div>
<div class="credit-brand-image-container">
<img src="http://localhost:3000/{{activecredit.Imglocation}}" class="credit-image"/>
</div>
<div class="hive-credit-percent"><img class="hive-filled-container" src="imgs/hivefilled9.png"/></div>
<div class="hive-credit-dollars">$xx.xx</div>
<div {{bind-attr class=activecredit.Brandname}} {{action 'enableTrade'}}><img src="imgs/trade_button.png" class="credit-trade-button" /></div>
<div class="credit-brand-amount">
xx%
</div>
<!-- Trade button click dropdown -->
{{#if isSelected}}
<div class="hivetrade">
<div class="arrow_box">
Hi there
</div>
<div class=""></div>
</div>
{{/if}}
</div>
{{/each}}
{{/each}}
Now I want to show a drop down on each element on click of a button .
When I set the enableTrade action , all dropdowns of all the divs show up.
actions:{
enableTrade:function(){
this.set('isSelected',true);
}
}
How do I enable only the dropdown in the particular div that was created.
I suppose I need to bind certain attributes to each div,but how do I access which div was clicked in my controller?
Any help would be appreciated .
Thanks
You can pass params through the action helper, and use them to set isSelected on the appropriate item.
{{#each model as |user| }}
{{#each user.activeCredits as |activeCredit|}}
<button {{action 'enableTrade' activeCredit}}>Enable Trade</button>
{{#if activeCredit.isSelected}}
<div class="hivetrade">Hello World</div>
{{/if}}
{{/each}}
{{/each}}
To handle it:
actions:{
enableTrade:function(credit) {
credit.set('isSelected',true);
}
}
If you need to allow only one credit to be selected at a time, your controller and action could be modified like this:
selectedCredit: null,
actions:{
enableTrade:function(credit) {
// unselect the previously selected credit
if (this.get('selectedCredit')) {
this.set('selectedCredit.isSelected', false);
}
// select, and cache the selection choice
credit.set('isSelected',true);
this.set('selectedCredit', credit);
}
}

Using summernote with meteor (Discover Meteor)

So, I've been working with Discover Meteor, and I'm having problems.
I'm trying to insert what content inputted in summernote into mongo but I'm having a few problems.
post_submit.html
<div class="form-group {{errorClass 'content'}}">
<textarea class="form-control" name="content" id="summernote"></textarea>
</div>
post_submit.js
var post = {
url: checkURLPrefix( $(e.target).find('[name=url]').val() ),
title: $(e.target).find('[name=title]').val(),
content: $(e.target).find('[name=content]').val()
};
lib/posts.js (will say Match Failed error when submitting)
meteor.methods({
postInsert: function(postAttributes) {
check(this.userId, String);
check(postAttributes, {
title: String,
url: String,
content: function(content){$('[name=content]').html($('#summernote').code());}
});
I've tried content: String to input the data into mongo. It would work but when I tried to load {{content}} in the post_page.html file, it would just show the unrendered HTML codes. {{{content}}} would show to content properly rendered but would mess up the functionality of the sorting system based on votes.
I'm really lost here and I wish I could find a solution soon.
Thank you in advance!
EDIT1: Here is my post_page.html and what I see when I insert content: String and load with {{content}}
<template name="postPage">
{{> postItem}}
<div class="col-md-12" style="background:blue;">
{{content}}
</div>
<ul class="comments">
{{#each comments}}
{{> commentItem}}
{{/each}}
</ul>
{{#if currentUser}}
{{> commentSubmit}}
{{else}}
<p>Please log in to leave a comment.</p>
{{/if}}
</template>
http://i.imgur.com/Fm0CXAN.png
First: you should use summernote in a div, not a textarea.
And in post_submit.js you have to leave it like this:
var post = {
...
content: $(e.target).find('#summernote').code()
};
In your post_page.html use triple brackets, because its HTML.
<div class="col-md-12" style="background:blue;">
{{{content}}}
</div>

Meteor using different Sessions with {{#each}} for click event

I want to use different Sessions for a specific click event within a {{#each}} block. The problem is that HTML elements will be displayed for all {{#each}} block members, but I only want it for the one, a user performs the click event on.
Here is my code:
...
{{#each articles}}
<tr id="{{_id}}" class="article-row">
<td class="articles">
{{> article}}
</td>
</tr>
{{/each}}
...
<template name="article">
{{#if editArticle}}
<div class="input-group">
<span class="input-group-addon">Edit article</span>
<input type="text" name="edit-article-input" class="form-control" placeholder="Name your article." value="{{title}}">
<span class="input-group-btn">
<button class="btn btn-success glyphicon glyphicon-ok edit-article-submit" data-type="last"></button>
</span>
</div>
{{else}}
<div class="panel panel-default article">
<div class="panel-heading">
<h3 class="panel-title">{{title}}</h3>
</div>
<div class="panel-body">
<button class="glyphicon glyphicon-pencil btn btn-default btn-xs edit-article"></button>
</div>
</div>
{{/if}}
</template>
Helper methods and events:
Template.article.helpers({
editArticle: function() {
return Session.equals('editArticle', true);
}
});
In the template with the {{#each}} block I use this helper method:
'click .edit-article': function(e, t) {
e.preventDefault();
Session.set('editArticle', true);
}
Now the problem is, that if I click on the button .edit-article the {{#if editArticle}} block appears on every article. I just want it for the one I clicked on.
Any help would be greatly appreciated.
This is a perfect example of how Session isn't always the right tool for the job when it comes to template reactivity. Unless you actually need to know which article is being edited outside of the template, I'd strongly recommend using a ReactiveVar or ReactiveDict rather than a global variable like Session. It's a little more to write but, in my opinion, a much more encapsulated and elegant solution. Here's the outline of the code you'd need:
Template.article.created = function() {
this.isEditing = new ReactiveVar(false);
};
Template.article.events({
'click .edit-article': function(e, template) {
e.preventDefault();
template.isEditing.set(true);
},
submit: function(e, template) {
e.preventDefault();
template.isEditing.set(false);
}
});
Template.article.helpers({
editArticle: function() {
return Template.instance().isEditing.get();
}
});
Note that you'd need to do meteor add reactive-var for this to work. For more details, see my post on scoped reactivity.

Categories