I'm trying to use jScrollPane with meteor.js, but it's not behaving as expected. First off, if I give the '.scroll-pane' class to a div, it will work without being initialized explicitly by me. But when I try to initialize it explicitly, it does not work. Is this some sort of meteor magic? or I am missing something obvious.
Second, if I don't initialize it, but I try to access it...it's data is empty, so I can't use the api on it. I've included some sample code below, please let me know if I am doing something wrong.
html
...
<div class="rectangle">
<div class="chat scroll-pane" id="chatWindow">
{{#each Messages}}
{{#if Compare uId UID}}
<div class="bubble me">{{Text}}</div>
{{else}}
<div class="bubble you">{{Text}}</div>
{{/if}}
{{/each}}
</div>
<input class="textinput" type="text" placeholder="Insert Message" id="textToSubmit">
<button class="btn btn-success" id="submit" autofocus="autofocus">Send
<br>
<br>
</button>
</div>
js
if (Meteor.isClient) {
...
var Message = new Meteor.Collection("Message");
Template.Message.rendered = function(){
if(!this._rendered) {
this._rendered = true;
var scroll = $(this.find("#chatWindow"));
var api = scroll.data('jsp');
console.log(api);
}
};
...
}
If you need any more info, please let me know.
Thanks
There are a couple things going on:
(1) you need to wire up the reactivity to your template to ensure that the timing of the incoming Messages is correct. This is achieved by using a Session variable to set the load, and by setting Template.scroll.Messages equal to a function that returns a collection cursor. Typically, you would not need to set a Session this way if your Template.scroll.Messages returns a query that uses a Session object (i.e., a roomId). If that were the case, you could forgo the callback on the Meteor.subscribe call and the "loaded" Session altogether.
(2) you'll want to turn off autopublish (meteor remove autopublish) and explicitly subscribe to the data so you know when your collection loads;
(3) you must declare your messages Collection outside the isClient block to ensure the server knows about it.
HTML:
<template name="scroll">
<div class="rectangle">
<div style="height:100px;overflow:auto;" id="chatWindow">
{{#each Messages}}
{{#if isMe this}}
<div class="bubble me">{{text}}</div>
{{else}}
<div class="bubble you">{{text}}</div>
{{/if}}
{{/each}}
</div>
</div>
</template>
JS:
Messages = new Meteor.Collection("messages");
if (Meteor.isClient) {
Meteor.subscribe("messages", function () {
Session.set("loaded", true);
});
Template.scroll.rendered = function () {
if (Session.get("loaded")) {
$(this.find("#chatWindow")).jScrollPane();
var api = $(this.find("#chatWindow")).data("jsp");
...
}
};
Template.scroll.helpers({
isMe: function (msg) {
return msg.userName === 'me';
}
});
Template.scroll.Messages = function () {
return Messages.find({});
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
if (Messages.find().count() === 0) {
Messages.insert({ text: 'Message 1', userName: 'me' });
Messages.insert({ text: 'Message 2', userName: 'you' });
Messages.insert({ text: 'Message 3', userName: 'me' });
}
});
Meteor.publish("messages", function () { return Messages.find({}); });
}
Related
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
}
};
I've completed the basic leaderboard app and read further documentation and finally decided to make my own app using this tutorial as a guide: http://meteorcapture.com/publishing-data-from-an-external-api/
my current code seems to work up until the point of passing data back to the client. I can't seem to get data from the server. Even though I have my subscribe and publish all set up.
I've cut down and simplified my code but to reduce points of error:
MyMp = new Mongo.Collection('mymp');
if (Meteor.isClient) {
Session.setDefault('searching', false);
Tracker.autorun(function(){
if(Session.get('postcode')){
var twfyHandle = Meteor.subscribe('twfySearch', Session.get('postcode'));
Session.set('searching', ! twfyHandle.ready());
}
});
Template.searchForm.events({
'submit form': function(event, template) {
event.preventDefault();
var postcode = template.$('input[type=text]').val();
if (postcode) {
Session.set('postcode', postcode);
}
}
});
Template.body.helpers({
mymp: function() {
return MyMp.find();
},
searching: function() {
return Session.get('searching');
}
});
}
if (Meteor.isServer) {
Meteor.publish('twfySearch', function(postcode){
console.log(postcode); // this received ok
var self = this;
var mp = {first_name: 'Test Name', party: 'Labour'}
self.added('mymp', Random.id(), mp);
self.ready();
});
}
Templates in my HTML file:
<body>
<h1>Get Details on your MP and Constituency</h1>
<h2>Enter your post code below</h2>
{{> searchForm }}
{{#if searching }}
<p>Searching...</p>
{{else}}
<div class="">
{{> twfyResults }}
</div>
{{/if}}
</body>
<template name="twfyResults">
{{ mp.first_name }}
</template>
<template name="searchForm">
<form>
<input type="text" name="postcode" id="postcode" />
<input type="submit" value="Search" />
</form>
</template>
I'm passing a postcode to the server and the server populates a basic JSON object 'mp' under a publish method and makes it ready().
This is where it fails. Although my console.log() calls show that the server is getting the postcode fine and creating the mp object. The client is not getting anything back!
UPDATE:
I have managed to manually run in the browser console MyMp.findOne() and it returns the object the server created. However, this object seems inaccesible to my template. Also the 'mp' object itself doesnt exist.
I've realised THREE errors in my code.
I assumed the template object used to access the data sent back had the same name on the frontend as it did in the server (mp). Instead I should have been trying to access the helper name "mymp".
This was fixed by changing the twfyResults template to reference the helper method:
<template name="twfyResults">
{{ mymp.first_name }}
</template>
My helper for the twfyResults was in the wrong context. So I rewrote my helpers like so:
Template.body.helpers({
searching: function() {
console.log(this);
return Session.get('searching');
}
});
Template.twfyResults.helpers({
mymp: function() {
return MyMp.findOne();
}
});
But the above alone wasn't enough. I also had to change the "mymp" helper to return just one result as in this case only one result would ever be returned. That way I could access my objects variables in the above way. So my helper was changed to findOne() instead of just find as seen above.
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.
I'm just playing around with different patterns and am very new to programming, however I've got everything to work in my test app so far except this. I've tried a bunch of variations with no luck, but I suspect I'm missing something really simple.
Basically what I want to happen is for a user to click a button and for it to then update the value of two specific attributes of the current object.
In this example I'm wanting the update to occur when the user clicks the "Return" button (the other buttons shown below are working fine).
Here's the HTML template for the button in question:
<template name="bookDetails">
<div class="post">
<div class="post-content">
<h3>{{title}}</h3><span> {{author}}</span>
{{#if onLoan}}
<i class="fa fa-star"></i>
On loan to: {{lender}}{{/if}}
</div>
{{#if ownBook}}
Edit
Lend
<div class="control-group">
<div class="controls">
<a class="discuss btn return" href="">Return </a>
</div>
</div>
{{/if}}
</div>
</template>
Here's the .js file which contains my Template event. Basically I want to set the values for the "lendstatus" and "lender" attributes.
Template.bookDetails.helpers({
ownBook: function() {
return this.userId == Meteor.userId();
},
onLoan: function() {
return this.lendstatus == 'true';
}
});
Template.bookLoan.events({
'click .return': function(e) {
e.preventDefault();
var currentBookId = this._id;
var bookProperties = {
lendstatus: "false",
lender: "",
}
Books.update(currentBookId, {$set: bookProperties}, function(error) {
if (error) {
// display the error to the user
throwError(error.reason);
} else {
Router.go('bookPage', {_id: currentBookId});
}
});
},
});
If I type the following into the Browser console while on the page for the object with id ZLDvXZ9esfp8yEmJu I get the correct behaviour on screen and the database updates so I know I'm close:
Books.update({ _id: "ZLDvXZ9esfp8yEmJu"}, {$set: {lendstatus: "false", lender: ""}});
What am I missing?
OK - so my problem was that I'd defined the event handler in the wrong template. I'd defined it in the bookLoan template instead of the bookDetails template. Thanks #saimeunt for pointing this out!
I am using meteor along with meteor-router for client and server side routing. I'm wondering what a good way to handle site notifications, specifically "flash" type ones.
In the global layout.html I can have a handlebars output a message if a "message" session variable is set, but the message shouldn't stick around once the app is routed to a new url with Meteor.Router.to().
What's a good solution to having "flash" notifications? Or, how can I automatically clear a session variable after routing to a new URL.
layout.html:
<head>
<title>Meteor App</title>
</head>
<body>
{{> global-layout}}
</body>
<template name="global-layout">
{{#if message}}
<div class="message">{{message}}</div>
{{/if}}
{{renderPage}}
</template>
then in layout.js
Template['global-layout'].message = function () {
return Session.get('message');
};
I'm using a Meteor.Router.filter for this. This filter will be applied to all routes, therefore all flashes will be cleared on all url changes.
routes.js
Meteor.Router.filters({
// clearSeenMessages filter clears all seen messages.
// This filters is applied to all pages
clearSeenMessages: function (page) {
flash.clear();
return page;
},
});
// applies to all pages
Meteor.Router.filter('clearSeenMessages');
Here's the rest of the implementaion, aspects were borrowed from telesc.pe
client/views/flashes/flash_item.html
<template name="flashItem">
{{#if show}}
<div class="alert-box {{type}}">
{{message}}
<a class="close" href="">×</a>
</div>
{{/if}}
</template>
client/views/flashes/flash_item.js
// When the template is first created
Template.flashItem.created = function () {
// Get the ID of the messsage
var id = this.data._id;
Meteor.setTimeout(function () {
// mark the flash as "seen" after 100 milliseconds
flash.Flashes.update(id, {$set: {seen: true}});
}, 100);
}
client/views/flashes/flashes.html
<template name="flashes">
{{#each flashes}}
{{> flashItem}}
{{/each}}
</template>
client/views/flashes/flashes.js
Template.flashes.flashes = function () {
return flash.Flashes.find();
}
client/views/app.html
<body>
<!-- add the flashes somewhere in the body -->
{{> flashes}}
</body>
client/lib/flashes.js
// flashes provides an api for temporary flash messages stored in a
// client only collecion
var flash = flash || {};
(function (argument) {
// Client only collection
flash.Flashes = new Meteor.Collection(null);
// create given a message and optional type creates a Flash message.
flash.create = function (message, type) {
type = (typeof type === 'undefined') ? 'error' : type;
// Store errors in the 'Errors' local collection
flash.Flashes.insert({message: message, type: type, seen: false, show: true});
};
// error is a helper function for creating error messages
flash.error = function (message) {
return flash.create(message, 'error');
};
// success is a helper function for creating success messages
flash.success = function (message) {
return flash.create(message, 'success');
};
// info is a helper function for creating info messages
flash.info = function (message) {
return flash.create(message, 'info');
};
// clear hides viewed message
flash.clear = function () {
flash.Flashes.update({seen: true}, {$set: {show: false}}, {multi: true});
};
})();
Usage
flash.success('This is a success message');
flash.error('This is a error message');
flash.info('This is a info message');
You can now use the router-with-flash package available on atmosphere to handle flash notifications. If you use meteorite (which you should), you can do mrt add router-with-flash in the root directory of your project. Then, to display an alert you need to -
Meteor.Router.to("/", { alert: "Some alert..." });
Meteor.Router.notification("alert");
This will display the alert until the next call to Meteor.Router.to().