How to autoscroll in Meteor? - meteor

I am trying to build a chat in Meteor. But I am struggling to make the messages part(which is a div that takes up 80% of the screen) scroll down to the newest message when a) the page is loaded or b) a new message is sent/received
I have found various ways to do this in html/js like e.g. this:
function scrollToBottom(){
window.scrollTo(0, document.body.scrollHeight);
}
scrollToBottom();
But I cannot figure out how to integrate this into the Meteor app. I have tried to put this into the 'click' event of sending a message and into the helper that loads the messages. It did not work though. Any help is appreciated :)

For the infinite scrolling you can use alethes:pages.
You just need to define:
this.Pages = new Meteor.Pagination(YourCollectionName, {
debug: true,
availableSettings: {
limit: true,
sort: true,
filters: true,
settings: true
},
templateName: "items",
infinite: true,
infiniteTrigger: .9,
infiniteRateLimit: 1,
infiniteStep: 1,
itemTemplate: "item",
pageSizeLimit: 1000,
perPage: 5,
maxSubscriptions: 500,
dataMargin: 30,
sort: {
created_at: 1
}
});
And on the template render just set:
Pages.set({
filters: {
//Custom filters
}
});
Inside template:
<template name="items">
<div class="chat-discussion">
{{> pagesNav}}
{{> pages}}
</div>
</template>
You also need to define your itm template like this:
<template name="item">
//You can write you item (message body) here
</template>

Related

Meteor with ViewModel package not updating accross multiple child templates

I am new to meteor, and have a basic understanding of what is going on, but I am stuck with this example (the problem has been simplified as much as possible):
I have a template, and a child template:
<template name="test">
{{#each items}}
{{> testItem}}
{{/each}}
{{#each items}}
{{> testItem}}
{{/each}}
</template>
<template name="testItem">
<div {{ b "click: toggle"}}>{{value}}</div>
</template>
Template.test.viewmodel({
items: [],
onCreated: function() {
this.items().push({ value: 'test' });
}
})
Template.testItem.viewmodel({
toggle: function() {
this.value("changed");
}
});
The thing here is we have a single array of items in the viewmodel, and we render it through a child template multiple times.
When we toggle the item, it only toggles the single item template, not the other representation of it. It is behaving like it is copying the value, or some sort of scoping is taking place.
My expectation would be the second item to also change, but this is not the case - what am I missing, or misunderstanding here?
EDIT - Additional Investigation
If I change the item through the parent, and notify it has changed, the changes propogate throughout the child templates
Template.testItem.viewmodel({
toggle: function () {
this.parent().items()[0].value = "changed";
this.parent().items().changed();
}
});
Thanks!
You're right, when you do this.value("changed"); you're changing the value of the testItem view model, not the parent array. If you're going to modify the properties of objects in an array I highly recommend you use a client side Mongo collection instead. It will save you a lot of headaches.
Items = new Mongo.Collection(null);
Template.test.viewmodel({
items: function() {
return Items.find();
},
onCreated: function() {
Items.insert({ _id: "1", value: 'test' });
}
})
Template.testItem.viewmodel({
toggle: function() {
Items.update({ _id: this._id() }, { value: 'changed' });
}
});
btw, I rarely check SO. You will get quicker responses on viewmodelboard.meteor.com

Gridster add_widget with Meteor

I have some troubles wihth gridster & meteor. At first, I loaded the whole widgets into my template and then recalculate the grid with a method below.
I have a template named dashboard, in this template I do a loop through my widgets and call a second template called widgetTmpl that contains all the formatted html
<template name="dashboard">
<div id="dashboardBody">
<button id="configMode" class="btn btn-primary"> <i class="fa fa-pencil"></i>Configuration </button>
<div class="gridster">
<ul id="widgetItemList" class="widget_item">
{{#each activeWidgets}}
{{> widgetTmpl}}
{{/each}}
</ul>
</div>
</div>
</template>
I execute this code with the callback onRendered
Template.dashboard.onRendered(function(){
var gridsterUl = jQuery(".gridster ul");
gridsterUl.gridster({
widget_margins: [5, 5],
widget_base_dimensions: [25, 25],
resize : {
enabled : true
},
draggable: {
start: overlay_fix_start,
stop: overlay_fix_stop
},
serialize_params : function($w, wgd){
return {
id : $w.prop('id'),
col : wgd.col,
row : wgd.row,
size_x : wgd.size_x,
size_y : wgd.size_y
};
}
});
});
This works fine, but when I add or reload a widget, I have to refresh the page due to the onRedered callback.
I heard about a gridster.add_widget methods, that does the perfect job but I don't know how to implement it to my code.
Where should I use the gridster.add_widget ?
There is methods like Blaze.insert and Blaze.renderWithData but I have no idea how to use it
I've never tried using Gridster, however I have integrated d3 into quite a few of my meteor apps. When I want the d3 context to reactivity change without a page refresh, I use a Tracker.autorun in my onRendered callback with a reactive datasource. For example my simplified code would look like this:
Template.d3Plot.onRendered( function () {
Tracker.autorun(function() {
// Function That Draws and ReDraws due to Tracker and Reactive Data
drawPlot(Plots.find());
});
});
drawPlot = function (plotItems) {
.....
}
Where Plots is my mongo collection so that whenever a new item is inserted/updated in the collection, the drawPlot function re-fires.

Assemble - How do I limit a collect to display X posts?

Details:
Assemble: 0.4.4
Grunt: 0.4.1
Question:
I'm designing a blog where I want to put 5 of the most recent posts on the front page. I've created a collection for my posts based on keywords:
assemble: {
options: {
flatten: false,
partials: '<%= build.src %>/_partials/*.hbs',
layoutdir: '<%= build.src %>/_layouts',
data: ['<%= build.src %>/_data/*.{json,yml}', 'package.json'],
assets: '<%= build.out %>/',
helpers: [ 'helper-moment','<%= build.src %>/helpers/helper-*.js'],
collections: [
{ name: 'keywords', inflection: 'keyword' }
]
},
YAML front matter on the various posts look similar to this:
--
layout: default.hbs
title: <%= site.title %>
description: "Adult Redeploy All Sites Summit 2015"
dateCreated: 06-23-2014
slug: "Welcome"
breadCrumbs: false
posted: 01-12-2014
keywords:
news
navSort: 100
--
My code to display the titles and summaries is this:
<div>
{{#each keywords}}
{{#is keyword "news"}}
{{#withSort pages "data.posted" dir="desc"}}
<div>
<h2>{{data.title}}</h2>
<p>{{formatDate data.posted "%F"}}</p>
<div>
{{#markdown}}{{data.summary}}{{/markdown}}
</div>
<p>more...</p>
</div>
{{/withSort}}
{{/is}}
{{/each}}
</div>
This works. It displays all the blogs no problem. But I want to limit to 5 -- the five most recent.
I've looked at this issue:
https://github.com/assemble/assemble/issues/463
But I'm not sure how to incorporate it into the example above. Is there a way to limit the pages #withSort?
Confused.
Had the same problem and ended up creating a helper for it.
var _ = require('underscore');
var helpers = {
latest: function(array, amount, fn) {
var buffer = "";
_.chain(array)
.filter(function(i) {
return i.data.date;
})
.sortBy(function(i) {
return i.data.date;
})
.reverse()
.first(amount)
.forEach(function(i) {
buffer += fn.fn(i);
});
return buffer;
},
};
module.exports.register = function(Handlebars, options) {
options = options || {};
for (var helper in helpers) {
Handlebars.registerHelper(helper, helpers[helper]);
}
return this;
};
Add it somewhere along assemble.helpers search path. Make sure you have underscore dependency installed
npm install underscore --save-dev --save-exact
Then you can use the helper like this
<ul>
{{#latest pages 5}}
<li>{{data.title}}</li>
{{/latest}}
</ul>
There's good news and bad news. The bad news is that I don't believe there is a built-in helper that both sorts and limits the pages collection, nor can you piece two of them together in a pipeline.
Edited: I was wrong, there may be a built-in way combining collection sorting and the withFirst helper. I'll make a separate answer.
The good news is that you can write your own custom Handlebars Helper to do this. I wrote a sample helper below based on the withSort Helper. You would use it like this:
<div>
{{#each keywords}}
{{#is keyword "news"}}
{{#withSortLimit pages sortBy="data.posted" dir="desc" limit=5}}
<div>
<h2>{{data.title}}</h2>
<p>{{formatDate data.posted "%F"}}</p>
<div>
{{#markdown}}{{data.summary}}{{/markdown}}
</div>
<p>more...</p>
</div>
{{/withSortLimit}}
{{/is}}
{{/each}}
</div>
withSortLimit.js
Here is the no-frills source to the withSortLimit helper. You will need to register this in your Gruntfile's Assemble configuration as described in the options.helpers docs.
/*
* withSortLimit Handlebars Helper for Assemble
* Sample usage:
* {{#withSortLimit pages sortBy="data.posted" dir="desc" limit=5}}
* <li>{{formatDate data.posted "%F"}}: {{data.title}}</li>
* {{/withSortLimit}}
*/
(function() {
var _ = require("lodash");
function getPropertyFromSpec(obj, propertySpec) {
var properties = propertySpec.split('.');
var resultObj = obj;
_.each(properties, function (property) {
if (resultObj[property]) {
resultObj = resultObj[property];
}
});
return resultObj;
}
module.exports.register = function(Handlebars, options) {
Handlebars.registerHelper("withSortLimit", function(collection, options) {
var result = "";
var selectedPages = collection;
// Sorting
var sortProperty = options.hash.sortBy || "basename";
selectedPages = _.sortBy(collection, function (item) {
return getPropertyFromSpec(item, sortProperty);
});
if (options.hash.dir && options.hash.dir === "desc") {
selectedPages = selectedPages.reverse();
}
// Limit
if (options.hash.limit && options.hash.limit > 0) {
selectedPages = _.first(selectedPages, options.hash.limit);
}
// Rendering
_.each(selectedPages, function (page, index, pages) {
result += options.fn(page);
});
return result;
});
};
}).call(this);
It should be possible to accomplish your goal with a combination of the following:
Specify custom sorting rules for your keywords collection in the Gruntfile.
Use the {{#withFirst pages 5}} helper to restrict the now-sorted list to your first five posts.
Gruntfile
assemble: {
options: {
flatten: false,
partials: '<%= build.src %>/_partials/*.hbs',
layoutdir: '<%= build.src %>/_layouts',
data: ['<%= build.src %>/_data/*.{json,yml}', 'package.json'],
assets: '<%= build.out %>/',
helpers: [ 'helper-moment','<%= build.src %>/helpers/helper-*.js'],
collections: [
{ name: 'keywords', inflection: 'keyword', sortby: 'posted', sortorder: 'desc' }
]
},
Page Template
<div>
{{#each keywords}}
{{#is keyword "news"}}
{{#withFirst pages 5}}
<div>
<h2>{{data.title}}</h2>
<p>{{formatDate data.posted "%F"}}</p>
<div>
{{#markdown}}{{data.summary}}{{/markdown}}
</div>
<p>more...</p>
</div>
{{/withFirst}}
{{/is}}
{{/each}}
</div>
Well, I finally got this working, but it's probably not the best way to go about it. In fact, it's brute-force all the way. But I'm going to post what finally worked.
Here's the setup -- slightly modified from my original question.
I wanted to create a partial -- sidebar.hbs -- that lists the 5 most recent posts. I then wanted to call that partial inside my "normal" pages when I wanted a sidebar with recent content. Okay, so here's the setup.
My assemble routine in grunt.js:
grunt.js
assemble: {
options: {
flatten: false,
partials: '<%= build.src %>/_partials/*.hbs',
layoutdir: '<%= build.src %>/_layouts',
data: ['<%= build.src %>/_data/*.{json,yml}', 'package.json'],
assets: '<%= build.out %>/',
helpers: [ 'helper-moment','<%= build.src %>/helpers/helper-*.js'],
collections: [
{ name: 'keywords',
inflection: 'keyword',
sortby: 'posted',
sortorder: 'desc',
}
]
},
I then realized -- much like you kindly responded above -- that I can loop through the collection:
sidebar.hbs (not working -- {{relativeLink}} isn't defined -- but sorted!)
{{#each keywords}}
{{#is keyword "news"}}
{{#withFirst pages 3}}
<h5 style="padding-left: 7px; padding: 0; margin: 0;">
{{data.slug}}</h5>
{{/withFirst}}
{{/is}}
{{/each}}
Except that -- and here's the rub: {{relativeLink}} is not properly set here. It returns nothing -- it's blank. I suspect I'm confusing contexts by calling the sidebar.hbs partial from a template page. I'm not sure, but I do know that the code below works as a partial -- and returns the proper {{relativeLink}}:
sidebar.hbs (working, no sorting -- simply displays all pages in site)
{{#each pages}}
{{data.slug}} -- {{relativeLink}}
<br/>
{{/each}}
The problem there, of course, is that it returns all the pages -- and they're not sorted.
So, in order to get my sidebar partial working -- and returning the proper links no matter where we are in the site hierarchy -- I created my links like this (and I'm embarrassed to post this -- but it works). I used the 'page.src' variable in the #withFirst loop. This works for what I want but seems awkward:
sidebar.hbs (working, sorting, but doesn't seem the right way to do this):
{{#each keywords}}
{{#is keyword "news"}}
{{#withFirst pages 4}}
<div class="myArticle">
<article class="recent-posts" >
<h5 style="padding-left: 7px; padding: 0; margin: 0;">{{data.slug}}</h5>
<p>
<span >
{{moment data.posted format ="DD MMM YYYY"}}
</span>
</p>
</article>
</div>
{{/withFirst}}
{{/is}}
{{/each}}
What I'm essentially do is calling a helper -- 'constructPostLinks' and hacking together a URL from the site's site's folder on my website (again, defined in data.yml), and the 'src' page variable (which I pass to my custom handlebars template). The 'src' page variable is not anything I need -- it usually looks like /src/about/index.hbs or something like that -- so I strip off the 'src' and replace the '.hbs' with 'ext' (in this case, '.html').
Here's the handlebars helper:
helper-constructPostLinks.js
module.exports.register = function (Handlebars) {
Handlebars.registerHelper('constructPostLinks', function(page,ext) {
pageLink = page.replace('src/','').replace('.hbs',ext);
return new Handlebars.SafeString(pageLink);
});
};
I don't know. This seems like an awfully clumsy way to get the links generated, but it works. I can write a page -- index.hbs -- and then include the sidebar partial {{>sidebar}} -- and then everything is pulled together in the default page template (page.hbs) where the links are properly generated for the most recently posted articles. It works. But I wish the collection sort routine included the proper {{relativeLinks}}.
As I say, I'm sure I'm doing something wrong and confusing contexts -- but at least I've got it going.

Use Meteor collections for Typeahead Bloodhound, preferably without making my own API

I want to build a tags input like the one in StackOverflow. I am trying to use Meteor collections as the remote or prefetch data for Typeahead Bloodhound because I want to eventually use Bootstrap Tokenfield.
According to their documentation and examples, a url to the JSON data is absolutely required. How can I provide the data, preferably reactively, to Bloodhound? I have looked into the Meteor Typeahead package, but I can't figure out how to use it with the Meteor Tokenfield package.
Below is what I've tried to do, but it doesn't work. :(
<div class="control-group">
<label class="control-label" for="users">Users</label>
<div class="controls">
<input type="text" class="form-control" id="tokenfield-typeahead-users" value="" />
</div>
</div>
Template.viewUsers.rendered = function() {
var users = new Bloodhound({
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.username);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 20,
remote: {
// url points to a json file that contains an array of tokens
url: function() {
return Meteor.users.find().fetch().map(function(user){ return user.username; });
}
}
});
users.initialize();// kicks off the loading/processing of `local` and `prefetch`
// passing in `null` for the `options` arguments will result in the default
// options being used
$('#tokenfield-typeahead-users').tokenfield({
typeahead: [null, {
name: 'users',
displayKey: 'username',
source: users.ttAdapter()
// `ttAdapter` wraps the suggestion engine in an adapter that
// is compatible with the typeahead jQuery plugin
}]
});
};
I prefer not to build an API, but if I have to, how do I provide the data?
This code posting uses:
local: [{ val: 'dog' }, { val: 'pig' }, { val: 'moose' }],
Spend quite some time trying to get the tokenfield to work reactively with my Meteor collection, so I'll just post my solution here as well.
I ended up not using Bloodhound at all, but instead just using Meteor directly.
I realize that the RegEx search is pretty primitive, but if what you're searching is a collection of tags, it does the job.
var currentTags = []; // Handle this however you wish
$('#tokenfield').tokenfield({
typeahead: [null, {
name: 'tags',
displayKey: 'value',
source: function(query, syncResults, asyncResults) {
var suggestedTags = Tags.find({value: {
$regex: "^"+query,
$options: "i",
$nin: currentTags
}}).fetch();
syncResults(suggestedTags);
//Optionally do some server side lookup using asyncResults
}
}]
});

How to pass functions to capture events in custom component in Meteor with Blaze

I want to know how to bind/set template-passed-parameter-value to click event of an item in the template in Meteor.
I'm using Meteor 0.7.0.1 with Blaze UI package. My main idea is to build a re-usable custom components in Meteor with Blaze template engine.
I have the following component which is working fine at the moment but I want this to be more customizable and remove some dependencies.
This is my component template named postLinks
<template name="postLinks">
<div id="link-popover-wrapper" >
<ul class="link-popover">
{{#each linkOptions}}
<li><a tabindex="-1" class="link-action" id="link-{{value}}" href="#">{{label}}</a>
</li>
{{/each}}
</ul>
</div>
</template>
This postLinks component is used in the myPostItem helper.
Template.myPostItem.events({
'click .post-item-link-picker': function (evt, tmpl) {
var tmpPostId = this._id;
var tempData = {linkOptions:[{label:'Favorite', value : 'favorite'},{label:'Wish list', value : 'wishlist'},{label:'Later', value : 'later'}, {label:"Read", value:"read"}]};
var linkContent = Template.postLinks(tempData);
$(".item-link-picker").popover({
content: linkContent, html: true, placement: 'bottom', trigger: "manual",
template: "UI_POPOVER_TEMPLATE"});
$("#item-link-picker-"+tmpPostId).popover('show');
},
'click .link-action': function (evt, tmpl) {
//.... some code here to update link selection in db
}
});
Above code is working fine and I want to improve it to have following
Pass item click event externally to be bind to link-action like
After above two changes it will look like :
Template.myPostItem.events({
'click .post-item-link-picker': function (evt, tmpl) {
var tmpPostId = this._id;
var tempData = { itemClick:function(){}, linkOptions:[{label:'Favorite', value : 'favorite'},...]};
var linkContent = Template.postLinks(tempData);
$(".item-link-picker").popover({
content: linkContent, html: true, placement: 'bottom', trigger: "manual",
template: "UI_POPOVER_TEMPLATE"});
$("#item-link-picker-"+tmpPostId).popover('show');
}
});
I lack knowledge how/where to bind that passed event handling function to link-action elements in template or helper. I really appreciate if anybody could help to find a way to do that.
You go the other way around and use jQuery event triggering system, so
Template.myPostItem.events({
'click .link-action': function (evt, tmpl) {
$(evn.target).trigger('post-link-action', this /* extra data */);
},
});
This event can be easily catched in any parent template:
<template name="someOtherTamplate">
{{> myPostItem}}
</template>
Template.someOtherTemplate.events({
'post-link-action': function (evt, tmpl, extra) {
// the user of your component can define their custom behavior here
},
});
Please note that the event extra parameter will only be supported in the next Meteor release. Currently (0.8.0) it is included in the devel branch.

Resources