Meteor: Reusing a Template With a Chart-Div - javascript

I'm using a single template show which doesn't really have any dynamic elements (yet):
<template name="show">
<div class="row">
<div id="container-pie"></div>
</div>
</template>
I have a sidebar, which pulls from my database and creates <a> tags.
My rendered callback has the code which draws a piegraph:
Template.show.rendered = function() {
var datum = this.data.visitors;
var keys = Object.keys(datum);
newAndReturningPie(keys[0], keys[1], datum.new, datum.returning);
}
(this data is being grabbed from iron:router).
Now here's where I need help (note the sidebar template for reference:)
<template name="sidebar">
<div class="ui left demo vertical inverted labeled sidebar menu">
<a class="item" href="{{pathFor 'root'}}">
<i class="home icon"></i>Home
</a>
{{#if anyProperties}}
{{#each this}}
<a href="{{pathFor 'property.show'}}" class="item">
{{name}}
</a>
{{/each}}
{{else}}
<a class="item">
<i class="settings icon"></i>No properties
</a>
{{/if}}
</div>
</template>
I'm using the same template for every object in the #each block. The problem is that the rendered callback captures the data for the first object, and switching context to another one doesn't actually reload or refresh the page.
If I was using just some HTML with {{objectProperty}}, then I know it would dynamically update. But I'm not using any Spacebars tags, just a single div which contains the graph.
What is a simple, elegant way to have the show template re-render/reload after clicking on an <a> tag in the sidebar?
UPDATE: Thanks to #tarmes
Here's my WORKING code:
Template.show.rendered = function() {
var self = this;
controller = Router.current();
self.autorun(function(){
var params = controller.getParams();
Meteor.call('selectedProperty', params._id, function(err, res){
if (!err) {
var datum = res.visitors;
var keys = Object.keys(datum);
newAndReturningPie(keys[0], keys[1], datum.new, datum.returning);
}
});
});
}
All I did was add a Meteor method to query the DB for the object via ID.

How about something like this:
Template.show.rendered = function() {
self = this;
controller = Router.current();
self.autorun(function() {
var params = controller.getParams(); // Reactive
var datum = self.data.visitors;
var keys = Object.keys(datum);
newAndReturningPie(keys[0], keys[1], datum.new, datum.returning);
});
}
The autorun will rerun everytime the controller's parameters change (which I assume they will do if there's an _id etc. in the route), thus forcing an update.

Related

How to call function dynamically in JQuery

I've set of html codes, I want to have a function for each set where I can pull some data or do some activities, so basically I need to be calling function according to the html codes present in DOM, suppose I've header section I want to collect menu items and I've sliders where I want to collect slider information, So I need to call header function to do the activity accordingly and slider function to do the activity, I went across some info about eval() but I guess it has lot of demerits and is being obsolete. Please suggest me how can I achieve it.
HTML Code:
Header
<div class="header" data-nitsid="1">
<div class="branding">
<h1 class="logo">
<img src="images/logo#2x.png" alt="" width="25" height="26">NitsOnline
</h1>
</div>
<nav id="nav">
<ul class="header-top-nav">
<li class="has-children">
Home
</li>
</ul>
</nav>
</div>
Slider
<div id="slideshow" data-nitsid="2">
<div class="revolution-slider">
<ul> <!-- SLIDE -->
<!-- Slide1 -->
<li data-transition="zoomin" data-slotamount="7" data-masterspeed="1500">
<!-- MAIN IMAGE -->
<img src="http://placehold.it/1920x1200" alt="">
</li>
</ul>
</div>
</div>
Now I want to collect data from both elements and pass the data to my views of laravel framework where it will generate a popup for each section for editing purpose.
$('.page-content-wrapper').find('[data-nitsid]').each(function() {
// getting data to call function
var nits = $(this).data("nitsid");
// calling function
design.nits();
}
var design = {};
design.1 = function() {
// do ajax request to views of header
}
design.2 = function() {
// do ajax request to views of slider
}
You canot use literal number as a property name. If you want to call property 1 of object design use design[1] instead. Also, you cannot assing property to non-initialized variable, you must use var design = {}; to make it object. If your property of design object is stored in nits variable, then call it as design[nits]();. Also, next time don't forget to test your code before posting it here. You've forget ) after your first function.
$('.page-content-wrapper').find('[data-nitsid]').each(function() {
// getting data to call function
var nits = $(this).data("nitsid");
// calling function
design[nits]();
});
var design = {};
design[1] = function() {
// do ajax request to views of header
};
design[2] = function() {
// do ajax request to views of slider
};
You want to use design[nits]();.
This will get the property nits of design and execute it with ().
But there is another problem. Your design will be declared after the each loop, so it is not available inside. You have to place it before.
$(function() {
var design = {};
design.funcOne = function() {
alert("funcOne called");
}
design.funcTwo = function() {
alert("funcTwo called");
}
$('div[data-nitsid]').each(function() {
var nits = $(this).data("nitsid");
design[nits]();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div data-nitsid="funcOne">
I will call 'funcOne'!
</div>
<div data-nitsid="funcTwo">
I will call 'funcTwo'!
</div>

Meteor: show each item from an array from mongo in separate list tag

Note: Whole code can be found here:
https://github.com/Julian-Th/crowducate-platform/tree/feature/courseEditRights
Currently, all items from an array are displayed in one single list instead of a separate list tag:
My JS (I commented out some prior approaches):
Template.modalAddCollaborators.events({
'click #js-addCollaborator' : function (event) {
var collaboratorName = $('#collaboratorName').val(); //
Courses.update(
{ _id: this._id },
{ $addToSet: {canEditCourse: collaboratorName } }
)
$('#collaboratorName').val("");
}
});
Template.modalAddCollaborators.helpers({
'addedCollaborators': function () {
return Courses.find();
//return Courses.find({_id: this._id}, {$in: "canEditCourse"});
//return Courses.distinct("canEditCourse");
}
});
My HTML:
<template name="modalAddCollaborators">
<div id="modalAddCollaborators" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Manage Your Collaborators</h4>
</div>
<div class="modal-body">
<form class="form" role="form">
<ul class="list-group">
{{#each addedCollaborators}}<li class="list-group-item">{{canEditCourse}}</li>{{/each}}
</ul>
<div class="form-group">
<input type="text" id="collaboratorName" placeholder="add a collaborator ...">
<button type="button" id="js-addCollaborator" class="btn btn-success">Add</button>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</template>
MY JSON:
{
"_id" : "rmZEFmfoBwf4NwqX4",
"title" : "Love",
"coverImageId" : "P7PyR6x64uCSX7X9m",
"author" : "test",
"keywords" : [
"test"
],
"published" : "true",
"about" : "test",
"canEditCourse" : [
"wicazRk3EsThE5E8W",
"Jolle",
"jolle",
"vW59A6szZijMDLDNh"
],
"createdById" : "wicazRk3EsThE5E8W",
"dateCreated" : ISODate("2015-12-27T15:06:28.272Z")
}
Any help appreciated, thank you.
Courses.find(); returns a cursor and not an array. Use fetch() method instead:
Template.modalAddCollaborators.helpers({
'addedCollaborators': function () {
return Courses.find().fetch();
}
});
In your template, create nested {{#each}} blocks with the first one iterating over the courses array and the next each block getting the canEditCourse array as the parameter. Inside the block, you can use this to reference the element being iterated over, something like the following for example:
<template name="modalAddCollaborators">
{{#each addedCollaborators}}
<h1>{{title}}</h1>
<ul class="list-group">
{{#each canEditCourse}}
<li class="list-group-item">{{this}}</li>
{{/each}}
</ul>
{{/each}}
</template>
It looks like you are storing two types of values in the canEditCourse:
String - Meteor.userId
String - username
It may be good to store either the userId or the username, but perhaps not both.
UserID solution
In this approach, you store the User IDs in the canEditCourse array, and then use a collection helper to retrieve the username for display:
Courses.helpers({
"getCollaboratorUsernames": function () {
// Get collaborator IDs array
var userIds = this.canEditCourse;
// Get the users, using MongoDB '$in' operator
// https://docs.mongodb.org/v3.0/reference/operator/query/in/
var users = Meteor.users.find({_id: {$in: userIds}).fetch();
// placeholder array for usernames
var collaboratorUsernames = []
// Get username for each user, add it to usernames array
users.forEach(function (user) {
// Add current username to usernames array
collaboratorUsernames.push(user.profile.username);
});
return collaboratorUsernames;
}
});
Also, it may be cleaner if the template helper were only to return the array of userIds, as opposed to a course object (Courses.find().fetch()).
Inputting UserIDs
You may choose a typeahead approach for inputting user IDs, similar to how courses are categorized in Crowducate.
Note: you will need a publication and subscription to make usernames/IDs available for the Selectize input.
Displaying Usernames
The other key component will be how to display the usernames as separate Boodstrap tag elements. You can iterate over the returned collaboratorUsernames array like so:
{{# each getCollaboratorUsernames }}
<span class="label label-info">{{ this }}</span>
{{/ each }}
Note: make sure the course collaborator users are available via a publication/subscription:
In server code:
Meteor.publish('courseCollaborators', function (courseId) {
// Get the course object
var course = Courses.findOne(courseId);
// Get course collaborator IDs
var collaboratorIds = course.canEditCourse;
// Consider renaming the 'canEditCourse' field to 'collaboratorIds'
// Then, it would look like
// var courseCollaboratorIds = course.collaboratorIds;
// Or, you could even skip that, and the code would still be literate
// Get course collaborators
var collaborators = Meteor.users.find({_id: {$in: collaboratorIds}).fetch();
return collaborators;
});
Then, in your template.created callback:
Template.modalAddCollaborators.created = function () {
// Get reference to template instance
var instance = this;
// Get reference to router
var route = Router.current();
// Get course ID from route
var courseId = route.params._id;
// Subscribe to Course Collaborators, template level
instance.subscribe("courseCollaborators", courseId);
};
Be sure to wrap all of your code for creating the Selectize widget in an if (instance.subscriptionsReady()) {} block:
Template.modalAddCollaborators.rendered = function () {
// Get reference to template instance
var instance = this;
// Make sure subscriptions are ready before rendering Selectize
if (instance.subscriptionsReady()) {
// Get course collaborator usernames/IDs
// Render the Selectize widget
// User should see usernames
// UserID is saved to collection
}
};

Reactive problems getting results with Meteor Search-Source package

Im triying implement a reactive search function for my first App in Meteor using Meteor:Search-source package. The search function is working fine. I have a input text search field, and 2 list. The first part called "search result" get the items found when we search for some word (for example try with london), and the second part get all items in my Collection. These items are linked to a reactive helper/template function. The reactivity when I get all items is working fine if I voteUp or Down. I can see in realtime how it is updated. But when I want upvote or downvote some item found in my search, upvote and downvote is not updating, is like the items found, lost the reactivity (I can see that if I upvote or downvote in some item in the first section ("search results") the items in second list (all items) is updating right....
this is my App My App
You can check it out the issue key in "london" in search field and upvote or downvote to see that in second list the item is correctly updated, but is not working if you upvote or downvote in "search result" section (no reactive).
CLIENT
// helper function
Template.searchResult.helpers({
getItems: function() {
return itemSearch.getData({
transform: function(matchText, regExp) {
return matchText.replace(regExp, "$&")
},
sort: {upvote: -1}
});
},
isLoading: function() {
return itemSearch.getStatus().loading;
}
});
//This line return all documents by default (when empty searchbox text is empty)
Template.searchResult.rendered = function() {
itemSearch.search('');
};
SERVER
SearchSource.defineSource('items', function(searchText, options) {
var options = {sort: {upvote: -1}, limit: 20};
// var options = options || {};
if(searchText) {
var regExp = buildRegExp(searchText);
/*var selector = {title: regExp, description: regExp};*/
var selector = {$or: [
{title: regExp},
{description: regExp}
]};
return Websites.find(selector, options).fetch();
} else {
return Websites.find({}, options).fetch();
}
});
function buildRegExp(searchText) {
var words = searchText.trim().split(/[ \-\:]+/);
var exps = _.map(words, function(word) {
return "(?=.*" + word + ")";
});
var fullExp = exps.join('') + ".+";
return new RegExp(fullExp, "i");
}
HTML
<!-- template that displays searched website items -->
<template name="searchResult">
<div class="container">
<div class="jumbotron">
<h3> Search results </h3>
<ol>
{{#each getItems}}
{{> website_item}}
{{/each}}
</ol>
</div>
</div>
<!-- template that displays individual website entries -->
<template name="website_item">
<li>
{{title}}
<p>
{{description}}
</p>
<a href="#" class="btn btn-default js-upvote">
<span class="glyphicon glyphicon-arrow-up" aria-hidden="true"> </span>
</a>
<a href="#" class="btn btn-success">
{{upvote}}
</a>
<a href="#" class="btn btn-default js-downvote">
<span class="glyphicon glyphicon-arrow-down" aria-hidden="true"></span>
</a>
<a href="#" class="btn btn-danger">
{{downvote}}
</a>
<p>
Created On: {{createdOn}}
</p>
<a href="/details/{{_id}}" class="btn btn-success js-description">
view web description
</a>
<!-- you will be putting your up and down vote buttons in here! -->
</li>
Some suggestion?? Many thanks in advance!
The returned data records themselves are not reactive, as the data is fetched internally using a method call (search.source).
You get a snapshot of the data as it was when you searched.
In addition, the data is cached by default, so subsequent searches for the same term don't trigger requests to the server for a certain period of time. You can adjust the time period via the keepHistory option.
Therefore, you will not get reactive changes via the package and it does not seem like an adequate solution for your situation.
You could try and get the data via a subscription and map the data returned from the search to the records in your collection but that seems to be costly.
Also, see this issue, which demonstrates that others had encountered the same type of issue.

Changing template on Link Click Meteor

There is a button :
<a id = "login" class="waves-effect white blue-text text-darken-2 btn"><i class="fa fa-pencil"></i> Write</a>
and there are two templates, firstpage, and secondpage.
Now, I want Meteor to change templates when I click that link, and render the second page.
How can I do this?
I used aldeed:template-extension but it doesnt work.
You can use some helpers here, and template variables.
Lets say you have this template.
<template name="example">
{{#if clicked}}
{{> firstPage}} <!-- the firstPage template will be rendered if the var is == false -->
{{else}}
{{> secondPage}} <!-- this will be render when the session is equals to true -->
{{/if}}
</template>
Now the Javascript.
First on the onCreate function declare the default values (similar so Session.setDefault)
Template.example.onCreated( function(){
var self = this;
self.vars = new ReactiveDict();
self.vars.setDefault( 'clicked' , true ); //default is true
});
Now the event to change the state of the clicked to false (Similar to Session get/set).
Template.example.events({
'click .waves-effect':function(event,template){
var instance = Template.instance();
instance.vars.set('clicked', false) //set to true.
}
})
Now we something to listen our variables changes, lets use a helper
Template.example.helpers({
clicked:function(){
var instance = Template.instance(); //http://docs.meteor.com/#/full/template_instance
return instance.vars.get('clicked') //this will return false(default) | true
}
})
NOTE Here we are using reactive-dict wich share the same syntaxis of a Session.
meteor add reactive-dict
Here is the MeteorPad Example

Is there a way to insert between Meteor templates?

I am trying to insert advertising blocks in a Meteor list template. The code will show it more easily than I can describe:
// dataList.js
Template.dataList.helpers({
dataList : function() {
return DataList.find();
});
// dataList.html
<template name="dataList">
{{#each dataList}}
<div class="col-xs-3">
{{name}} (and other data)
</div>
{{/each}}
</template>
The result I want is something like this
<div class="col-xs-3">
Jon Snow
</div>
<div class="col-xs-3"> <----This inserted programmatically
<div id="ad">
Buy a Destrier - 5 Golden Stags
</div>
</div>
<div class="col-xs-3">
Tyrion Lannister
</div>
<div class="col-xs-3">
The Hound
</div>
The effect is similar to the advertising found on Foodgawker.com. I can't work out how to insert the ads programmatically at random intervals.
This is on an infinite scroll and will need to add the Ad several times.
The previous answers are too complex ; just update your helper :
dataList : function() {
var d = _.map( DataList.find().fetch(), function(v) { return {value: v}; });
return _.union( d.slice(0,2), {
type: 'ad',
value: 'Buy a Destrier - 5 Golden Stags'
} ,d.slice(2));
});
So, dataList will have the ad inserted in third position.
You can randomly choose an _id from your list and just display the ad after that doc in the template.
Like this:
//.js
Template.dataList.created = function(){
var ids = DataList.find().map( function( doc ){ return doc._id });
this.showAdId = Random.choice( ids ); //need to add 'random' package of meteor core
};
Template.dataList.helpers({
dataList: function(){
return DataList.find({},{transform: function( doc ){
doc.showAd = doc._id === Template.instance().showAdId;
return doc;
});
}
});
//.html
<template name="dataList">
{{#each dataList}}
<div class="col-xs-3">
{{name}} (and other data)
</div>
{{#if showAd}} <!-- check new field to see whether this is the doc with an ad -->
<div class="col-xs-3">
<div id="ad">
Buy a Destrier - 5 Golden Stags
</div>
</div>
{{/if}}
{{/each}}
</template>
New Idea
// client side collection, not synchronized with server side...
DataListWithAds= new Mongo.Collection(null);
Template.DataList.destroyed= function(){
this.observer.stop();
}
Template.DataList.created= function(){
// DataList is collection anywhere
// get handler of observer to remove it later...
this.observer = DataList.find().observe({
// observe if any new document is added
// READ : http://docs.meteor.com/#/full/observe
addedAt:function(document, atIndex, before){
console.log(atIndex, document, self);
// immediately insert original document to DataListWithAds collection
DataListWithAds.insert(document);
// if some condition is true, then add ad.
if(atIndex % 5 == 1){
DataListWithAds.insert({
name:"AD", score:document.score, isAd:true
});
}
}
})
}
// use local collection to display items together with ads
Template.dataList.helpers({
dataList : function() {
return DataListWithAds.find();
});
})
Also template should be tweaked a little bit:
<template name="dataList">
{{#each dataList}}
{{#if isAd}}
ADVERTISEMENT
{{else}}
<div class="col-xs-3">
{{name}} (and other data)
</div>
{{/if}}
{{/each}}
</template>
Proof of concept can be found here:
http://meteorpad.com/pad/SoJe9dgp644HKQ7TE/Leaderboard
I think there are still things to resolve, like:
- How to make this working together with sorting ?
Old Idea
Below is an idea how you can achieve result you want.
The approach is that client side waits until your list is rendered (happens only once) and then injects an ad at position of your choice.
Assumptions:
your ad is defined in template adsTemplate
items are in div : #listContainer
Code:
Template.dataList.rendered = function(){
// position after which ads should be added
// you can generate random number
var adsPosition = 10;
// inject HTML from template **Template.adsTemplate**
// with data **adsData**
// to HTML element #listContainer
// after HTML element at position adsPosition
Blaze.renderWithData(
Template.adsTemplate,
adsData,
this.find('#listContainer'),
this.find('#listContainer .col-xs-3:nth-child('+adsPosition+')')
)
}
If you want to inject many ads, because list should be infinite , then it is good idea to use Tracker.autorun to detect new data and inject accordingly.

Categories