i have a leaderboard meteor sample app consisting of names and scores:
see--> http://leaderboardtry.meteor.com/
i want the ordering of elements to toggle between the score and the name when a toggle button is clicked. Here's what I've tried:
Javascript:
// Set up a collection to contain player information. On the server,
// it is backed by a MongoDB collection named "players".
Players = new Meteor.Collection("players");
Session.set("sort_by", "score");
if (Meteor.isClient) {
Template.leaderboard.players = function () {
return Players.find({}, {sort: {score: -1}});
};
Template.leaderboard.selected_name = function () {
var player = Players.findOne(Session.get("selected_player"));
return player && player.name;
};
Template.player.selected = function () {
return Session.equals("selected_player", this._id) ? "selected" : '';
};
Template.leaderboard.events({
'click input.inc': function () {
Players.update(Session.get("selected_player"), {$inc: {score: 5}});
},
'click input.toggle': function(){
var switch_sort = Session.equals("sort_by", "name")?"score":"name";
Session.set("sort_by", switch_sort);
var togg = Session.get("sort_by");
if (togg == "score"){
Players.find({}, {sort: {score: -1}});
} else {
Players.find({}, {sort: {name: -1}});
}
}
});
Template.player.events({
'click': function () {
Session.set("selected_player", this._id);
}
});
}
// On server startup, create some players if the database is empty.
if (Meteor.isServer) {
Meteor.startup(function () {
if (Players.find().count() === 0) {
var names = ["Ada Lovelace",
"Grace Hopper",
"Marie Curie",
"Carl Friedrich Gauss",
"Nikola Tesla",
"Claude Shannon"];
for (var i = 0; i < names.length; i++)
Players.insert({name: names[i], score: Math.floor(Math.random()*10)*5});
}
});
}
HTML
<head>
<title>Leaderboard</title>
</head>
<body>
<div id="outer">
{{> leaderboard}}
</div>
</body>
<template name="leaderboard">
<input type="button" class="toggle" value="toggle sort" />
<div class="leaderboard">
{{#each players}}
{{> player}}
{{/each}}
</div>
{{#if selected_name}}
<div class="details">
<div class="name">{{selected_name}}</div>
<input type="button" class="inc" value="Give 5 points" />
</div>
{{/if}}
{{#unless selected_name}}
<div class="none">Click a player to select</div>
{{/unless}}
</template>
<template name="player">
<div class="player {{selected}}">
<span class="name">{{name}}</span>
<span class="score">{{score}}</span>
</div>
</template>
Problem's certainly in my attempt in JS. Assistance is appreciated as I am new to meteor/mongo
Thanks!
It looks to me like leaderboard uses the function stored in Template.leaderboard.players to populate the list; so do you need to put the sort_by code in there? Like this?
Template.leaderboard.players = function () {
var togg = Session.get("sort_by");
if (togg == "score"){
return Players.find({}, {sort: {score: -1}});
} else {
return Players.find({}, {sort: {name: -1}});
}
};
This is just a suggestion - I don't know meteor, so I don't know how it knows to re-run the function.
I have managed to do it like this.
// Set a session var to store current sort order
// Do this first thing in the Meteor.isClient *if*
Meteor.startup(function(){
Session.set("sort_by", {score: -1, name: 1});
});
Notice that the players sort is taken care of here:
Template.leaderboard.players = function () {
// The commented line below is the original line in the code
// return Players.find({}, {sort: {score: -1, name: 1}});
// Replace line above for the line below
return Players.find({}, {sort: Session.get("sort_by")});
};
Once you have that you should handle an event from a button, so:
Create button in the HTML file (for example):
<input type="button" class="sort" value="Order by Name"/>
Handle the event in JS file (notice this handler already exists, I am just adding code):
Template.leaderboard.events({
'click input.inc': function () {
Players.update(Session.get("selected_player"), {$inc: {score: 5}});},
// This is the code you need
'click input.sort': function () {
var sort_by = Session.get("sort_by");
if(Object.keys(sort_by)[0] == "score"){
Session.set("sort_by", { name: 1, score: -1 });
}
else{
Session.set("sort_by", { score: -1, name: 1 });
}
},
});
That should be enough.
Related
I am trying to convert the Meteor tutorial app, simple-todo, into a forum. I have a list that populates with Topics with a delete button and a radio button.
What I want to do, is when a Topic's radio button is selected, display all the comments associated with it. I am trying to accomplish this by putting the topicId(originally taskId), used in the tutorial, in a new Collection called Comments. I then query the Comments for that topicId.the comments are mostly copied and pasted topics, so the attributes and functionality of the two is mostly the same.
I, right now, don't know how to get to the topicId from my Template.body.events. If you can help me tie these two DB's together, that would be very helpful.
HTML:
<head>
<title>Forum</title>
</head>
<body>
<div class="login">
{{> loginButtons}}
</div>
<nav class="mainMenu">
<ul>
<li>Home</li>
<li>Forum</li>
<li>About Us</li>
</ul>
</nav>
<div class="container">
<header>
<h1>Forum</h1>
<h2>Post a topic</h2>
{{#if currentUser}}
<form class="new-topic">
<input type="topicalContent" name="topicalContent" placeholder="Type to add new topics" />
</form>
{{/if}}
</header>
<table>
<td class="topicsCol">
<h3>Topics</h3>
<ul class="topicsList">
{{#each topics}}
{{> topic}}
{{/each}}
</ul>
</td>
<td class="commentsCol">
<h3>Comments</h3>
<ul class="commentsList">
{{#each comments}}
{{> comment}}
{{/each}}
</ul>
<input type="commentContent" name="commentContent" placeholder="Type to Comment" />
</td>
</table>
</div>
</body>
<template name="topic">
<li class="{{#if selected}}select{{/if}}">
<button class="delete">×</button>
<span class="topicalContent">{{topicalContent}} -<strong>{{username}}</strong></span>
<input type="radio" name="curTopic" value="{{curTopic}}" />
</li>
</template>
<template name="commentsList">
<li class="comment">
<button class="delete">×</button>
<span class="responseContent">
<strong>
<!-- {{#if username === owner}}
<style="color:red;">OP
{{else}}-->
{{username}}
<!--{{/if}}-->
</strong>: {{responseContent}}</span>
</li>
</template>
JavaScript:
Topics = new Mongo.Collection("topics");
Comments = new Mongo.Collection("comments");
if(Meteor.isServer){
Meteor.publish("topics", function(){
return Topics.find({
$or: [
{ owner: this.userId }
]
});
});
Meteor.publish("comments", function(){
return Comments.find({
$or: [
{ parent: topicId },
{ owner: this.userId }
]
});
});
}
if (Meteor.isClient) {
// This code only runs on the client
Meteor.subscribe("topics");
Meteor.subscribe("comments");
Template.body.helpers({
topics: function() {
return Topics.find({}, {sort: {createdAt: -1}});
},
comments: function () {
return Comments.find({parent: {parent: topicId}}, {sort: {createdAt: -1}});
}
});
Template.body.events({
"submit .new-topic": function (event) {
//Prevent default browser form submit
event.preventDefault();
//Get value from form element
var topicalContent = event.target.topicalContent.value;
if (topicalContent == "") {
throw new Meteor.Error("Empty Input");
}
//Insert a topic into the collection
Meteor.call("addTopic", topicalContent);
//Clear form
event.target.topicalContent.value = "";
},
"submit .commentContent": function (event) {
event.preventDefault();
var commentContent = event.target.commentContent.value;
Meteor.call("addComment", this.topicId);
event.target.commentContent.value= "";
}
});
Template.topic.helpers({
isOwner: function(){
return this.owner === Meteor.userId();
}
});
Template.topic.events({
"click .curTopic": function() {
//Show Comments of selected radio button
Meteor.call("showComments", this._id);
},
"click .delete":function () {
Meteor.call("deleteTopic", this._id);
}
});
Template.comment.helpers({
isOwner: function(){
return this.owner === Meteor.userId();
}
});
Template.comment.events({
"click .delete":function () {
Meteor.call("deleteComment", this._id);
}
});
Accounts.ui.config({
passwordSignupFields: "USERNAME_ONLY"
});
}
Meteor.methods({
addTopic:function(topicalContent){
if (!Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
Topics.insert({
topicalContent,
createdAt: new Date(),
owner: Meteor.userId(),
username: Meteor.user().username
});
},
deleteTopic:function(topicId) {
var topic = Topics.findOne(topicId);
if (topic.owner !== Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
else {
Topics.remove(topicId);
}
},
showComments:function (topicId) {
Comments.find({"parent":topicId});
},
addComment:function(commentContent, topicId){
if (!Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
Comments.insert({
commentContent,
createdAt: new Date(),
owner: Meteor.userId(),
username: Meteor.user().username,
parent: topicId
});
},
deleteComment:function(commentId) {
var comment = Comments.findOne(commentId);
if (comment.owner !== Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
else {
Comments.remove(commentId);
}
}
});
One way (and there are many) is to use a session variable to track the current topic. In your topic event handler:
Template.topic.events({
"click .curTopic": function() {
Session.set('topicId',this._id); // set the Session variable
Meteor.call("showComments", this._id);
});
Now everywhere else you're looking for the current topicId you can just use Session.get('topicId');
I want to create a little admin frontend on which I can display a list of all users and to edit user-data (like roles).
So what I did so far is to create a new publication (allUsers) and create methods for displaying and changeing the roles of the user:
shared/collections.js
Users = Meteor.users;
server.js
Meteor.methods({
'updateRole': function(userId, role, value) {
if (value) Roles.addUsersToRoles(userId, role);
else Roles.removeUsersFromRoles(userId, role);
return userId;
},
'loadRoles': function(userId, role) {
var result = { role: role, value: Roles.userIsInRole(userId, role) };
return result;
}
});
Meteor.publish('allUsers', function(){
return Users.find();
});
router.js
Router.route('/users', {
name: 'users',
waitOn: function () {
Meteor.subscribe('allUsers');
},
data: function() {
return {
users: Users.find({})
}
}
});
Router.route('/user/:_id', {
name: 'userEdit',
waitOn: function () {
Meteor.subscribe('allUsers');
Session.set("userEditId", this.params._id);
},
data: function () {
return {
users: Users.findOne({ _id: this.params._id })
};
}
});
client.js
Template.userEdit.onRendered(function() {
var role,
userId = Session.get("userEditId"),
elements = document.getElementById('formRoles').elements;
for(var i = 0; i < elements.length; i++) {
var dom = elements[i];
if(dom.getAttribute('type') == 'checkbox') {
role = dom.getAttribute('name');
Meteor.call('loadRoles', userId, role, function (error, result) {
$("input[name=" + result.role + "]").prop('checked', result.value);
});
}
}
});
Template.userEdit.events({
'change input[type="checkbox"]': function(event) {
var userId = document.getElementById('formRoles').getAttribute('data-id'),
role = event.target.getAttribute('name');
value = event.target.checked,
Meteor.call('updateRole', userId, role, value, function (error, result) {
console.log(result);
});
}
});
template
<template name="users">
<ul class="list">
{{#each users}}
<li>{{profile.name}}, {{profile.surname}}</li>
{{/each}}
</ul>
</template>
<template name="userEdit">
<h1>{{users.profile.name}}, {{users.profile.surname}}</h1>
{{#if isInRole 'admin'}}
<form id="formRoles" data-id="{{users._id}}">
<label><input type="checkbox" name="admin"> Administrator</label>
<label><input type="checkbox" name="role2"> Role</label>
<label><input type="checkbox" name="role3"> Role</label>
<label><input type="checkbox" name="role4"> Role</label>
<label><input type="checkbox" name="role5"> Role</label>
</form>
{{/if}}
</template>
Update
This is how I display all users in a list and display all roles of a selected user. The problem is, that the display works in two steps with a delay:
That means, when the page is loaded, first there is just one user and after a second all other users are added to the list. Same to a userEdit: First I just see "," and after a second it is filled to "Name, Firstname".
Can somebody explain this behaviour to me? And how can I improve the code to avoid these two steps?
Because of the "two-step-display", I think I wrote the code much to complicated...
Update 2
I think the delay results from the subscription of allUsers in the route-waitOn. Is it possible to improve that?
In my to-do-app I have a static <select> dropdown list, which I like to use to find specific tasks from the database. But how to get it working? I wired up an event and tried to set a variable assortment but it seems the template doesn't get re-rendered with the new values! Please have a look at the fallowing code:
HTML
<head>
<title>eisenhower</title>
</head>
<body>
{{> eisenhower}}
</body>
<template name="eisenhower">
<div class="eisenhower">
<select id="assortment"> <!-- HERE IS THE DROPDOWN LIST -->
<option value="">all tasks</option>
<option value="true">done tasks</option>
<option value="false" selected>undone tasks</option>
</select>
<div>
<h1>{{doTasksCount}} to do</h1>
<ul>
<li><form><input type="input" name="new" section="do" /></form></li>
{{#each doTasks}}
<li><input type="checkbox" checked="{{checked}}" /> {{task}}</li>
{{/each}}
</ul>
</div>
<div>
<h1>{{scheduleTasksCount}} to schedule</h1>
<ul>
<li><form><input type="input" name="new" section="schedule" /></form></li>
{{#each scheduleTasks}}
<li><input type="checkbox" checked="{{checked}}" /> {{task}}</li>
{{/each}}
</ul>
</div>
<div>
<h1>{{delegateTasksCount}} to delegate</h1>
<ul>
<li><form><input type="input" name="new" section="delegate" /></form></li>
{{#each delegateTasks}}
<li><input type="checkbox" checked="{{checked}}" /> {{task}}</li>
{{/each}}
</ul>
</div>
<div>
<h1>{{abandonTasksCount}} to abandon</h1>
<ul>
<li><form><input type="input" name="new" section="abandon" /></form></li>
{{#each abandonTasks}}
<li><input type="checkbox" checked="{{checked}}" /> {{task}}</li>
{{/each}}
</ul>
</div>
</div>
</template>
JS
Tasks = new Mongo.Collection("tasks");
assortment = true; // HERE IS MY VARIABLE FOR CHANGING THE .find() CLAUSE!
if (Meteor.isClient) {
Template.eisenhower.helpers({
//tasks
doTasks: function() {
return Tasks.find({section: "do", done: assortment}, {sort: {createdAt: -1}});
},
scheduleTasks: function() {
return Tasks.find({section: "schedule", done: assortment}, {sort: {createdAt: -1}});
},
delegateTasks: function() {
return Tasks.find({section: "delegate", done: assortment}, {sort: {createdAt: -1}});
},
abandonTasks: function() {
return Tasks.find({section: "abandon", done: assortment}, {sort: {createdAt: -1}});
},
checked: function() {
return Tasks.findOne(this._id).done;
},
//tasks counter
//doTasksCount: function() { return dos.count(); },
//scheduleTasksCount: function() { return schedules.count(); },
//delegateTasksCount: function() { return delegates.count(); },
//abandonTasksCount: function() { return abandons.count(); },
});
Template.eisenhower.events({
"submit form": function(event, template) {
event.preventDefault();
var obj = event.target.new; //input reference
var task = obj.value;
var section = obj.getAttribute("section"); //obj.section doesnt work here
Tasks.insert({task: task, section: section, done: false, createdAt: new Date()}, function(error, id) {
if(!error) {
event.target.new.value = ""; //clear form
}
});
},
"change checkbox": function(event) {
Tasks.update(this._id, {$set: {done: event.target.checked}});
},
"change select": function(event) {
assortment = (event.target.value) ? event.target.value : true; // SET VARIABLE! BUT DATA DOESNT CHANGE THOUGH!?
}
});
}
The key to get this working is to change the assortment variable to a reactive data source. When a reactive data source is reset in Meteor, the functions depending on it are rerun. When a plain vanilla variable is reset, functions are not rerun.
Your two options here are to make assortment a Session key, or to make it a ReactiveVar. You should take a look at the documentation for each and choose the one that fits best for you:
http://docs.meteor.com/#/full/session
http://docs.meteor.com/#/full/reactivevar
There are some noteworthy differences between the two, e.g., ReactiveVar requires a package to be installed and will not rerun when the value is reset to the same value as existed previously. But to demonstrate the pattern with Session, you will want to set it initially:
Session.setDefault('assortment', true);
In your template helpers, get the value:
doTasks: function() {
return Tasks.find({section: "do", done: Session.get('assortment')}, {sort: {createdAt: -1}});
}
And set it in your template event:
"change select": function(event) {
var assortment = (event.target.value) ? event.target.value : true;
Session.set('assortment', assortment);
}
How to use hogan templates with backbone code?How to combine the values of a model into the Hogan templates?
My HTML page:
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Backbone.js • TodoMVC</title>
<link rel="stylesheet" href="js/assets/base.css">
</head>
<body>
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
</header>
<section id="main">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"></ul>
</section>
<footer id="footer"></footer>
</section>
<div id="info">
<p>Double-click to edit a todo</p>
<p>Written by Addy Osmani</p>
<p>Part of TodoMVC</p>
</div>
<script type="text/template" id="item-template">
<div class="view">
<input class="toggle" type="checkbox" {{ completed ? 'checked' : '' }}>
<label>{{ title }}</label>
<button class="destroy"></button>
</div>
<input class="edit" value="{{ title }}">
</script>
<script type="text/template" id="stats-template">
<span id="todo-count"><strong>{{ remaining }}</strong> {{ remaining === 1 ? 'item' : 'items' }} left</span>
<ul id="filters">
<li>
<a class="selected" href="#/">All</a>
</li>
<li>
Active
</li>
<li>
Completed
</li>
</ul>
{[ if (completed) { ]}
<button id="clear-completed">Clear completed ({{ completed }})</button>
{[ } ]}
</script>
<script src="js/assets/base.js"></script>
<script src="js/lib/jquery.js"></script>
<script src="js/lib/underscore.js"></script>
<script src="js/lib/backbone.js"></script>
<script src="js/lib/backbone.localStorage.js"></script>
<script src="js/models/todo.js"></script>
<script src="js/collections/todos.js"></script>
<script src="js/views/todos.js"></script>
<script src="js/views/app.js"></script>
<script src="js/routers/router.js"></script>
<script src="js/app.js"></script>
</body>
</html>
My js code
/*global Backbone, jQuery, _, ENTER_KEY */
var app = app || {};
(function ($) {
'use strict';
_.templateSettings = {
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
};
// The Application
// ---------------
// Our overall **AppView** is the top-level piece of UI.
app.AppView = Backbone.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: '#todoapp',
// Our template for the line of statistics at the bottom of the app.
statsTemplate: _.template($('#stats-template').html()),
// Delegated events for creating new items, and clearing completed ones.
events: {
'keypress #new-todo': 'createOnEnter',
'click #clear-completed': 'clearCompleted',
'click #toggle-all': 'toggleAllComplete'
},
// At initialization we bind to the relevant events on the `Todos`
// collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*.
initialize: function () {
this.allCheckbox = this.$('#toggle-all')[0];
this.$input = this.$('#new-todo');
this.$footer = this.$('#footer');
this.$main = this.$('#main');
this.$list = $('#todo-list');
this.listenTo(app.todos, 'add', this.addOne);
this.listenTo(app.todos, 'reset', this.addAll);
this.listenTo(app.todos, 'change:completed', this.filterOne);
this.listenTo(app.todos, 'filter', this.filterAll);
this.listenTo(app.todos, 'all', this.render);
// Suppresses 'add' events with {reset: true} and prevents the app view
// from being re-rendered for every model. Only renders when the 'reset'
// event is triggered at the end of the fetch.
app.todos.fetch({reset: true});
},
// Re-rendering the App just means refreshing the statistics -- the rest
// of the app doesn't change.
render: function () {
var completed = app.todos.completed().length;
var remaining = app.todos.remaining().length;
if (app.todos.length) {
this.$main.show();
this.$footer.show();
this.$footer.html(this.statsTemplate({
completed: completed,
remaining: remaining
}));
this.$('#filters li a')
.removeClass('selected')
.filter('[href="#/' + (app.TodoFilter || '') + '"]')
.addClass('selected');
} else {
this.$main.hide();
this.$footer.hide();
}
this.allCheckbox.checked = !remaining;
},
// Add a single todo item to the list by creating a view for it, and
// appending its element to the `<ul>`.
addOne: function (todo) {
var view = new app.TodoView({ model: todo });
this.$list.append(view.render().el);
},
// Add all items in the **Todos** collection at once.
addAll: function () {
this.$list.html('');
app.todos.each(this.addOne, this);
},
filterOne: function (todo) {
todo.trigger('visible');
},
filterAll: function () {
app.todos.each(this.filterOne, this);
},
// Generate the attributes for a new Todo item.
newAttributes: function () {
return {
title: this.$input.val().trim(),
order: app.todos.nextOrder(),
completed: false
};
},
// If you hit return in the main input field, create new **Todo** model,
// persisting it to *localStorage*.
createOnEnter: function (e) {
if (e.which !== ENTER_KEY || !this.$input.val().trim()) {
return;
}
app.todos.create(this.newAttributes());
this.$input.val('');
},
// Clear all completed todo items, destroying their models.
clearCompleted: function () {
_.invoke(app.todos.completed(), 'destroy');
return false;
},
toggleAllComplete: function () {
var completed = this.allCheckbox.checked;
app.todos.each(function (todo) {
todo.save({
'completed': completed
});
});
}
});
})(jQuery);
it is from the ToDo example. I used Mustache templates here by changing the template settings. But i want to convert the template into Hogan.
my Edited js:
/*global Backbone, jQuery, _, ENTER_KEY, ESC_KEY */
var app = app || {};
(function ($) {
'use strict';
// Todo Item View
// --------------
_.templateSettings = {
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
};
// The DOM element for a todo item...
app.TodoView = Backbone.View.extend({
tagName: 'li',
// Cache the template function for a single item.
template1: _.template($('#item-template').html()),
template:Hogan.compile(template1),
// The DOM events specific to an item.
events: {
'click .toggle': 'toggleCompleted',
'dblclick label': 'edit',
'click .destroy': 'clear',
'keypress .edit': 'updateOnEnter',
'keydown .edit': 'revertOnEscape',
'blur .edit': 'close'
},
initialize: function () {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
this.listenTo(this.model, 'visible', this.toggleVisible);
},
render: function () {
// Backbone LocalStorage is adding `id` attribute instantly after creating a model.
// This causes our TodoView to render twice. Once after creating a model and once on `id` change.
// We want to filter out the second redundant render, which is caused by this `id` change.
// It's known Backbone LocalStorage bug, therefore we've to create a workaround.
// https://github.com/tastejs/todomvc/issues/469
if (this.model.changed.id !== undefined) {
return;
}
console.log(this.template);
this.$el.html(this.template(this.model.toJSON()));
this.$el.toggleClass('completed', this.model.get('completed'));
this.toggleVisible();
this.$input = this.$('.edit');
return this;
},
toggleVisible: function () {
this.$el.toggleClass('hidden', this.isHidden());
},
isHidden: function () {
var isCompleted = this.model.get('completed');
return (// hidden cases only
(!isCompleted && app.TodoFilter === 'completed') ||
(isCompleted && app.TodoFilter === 'active')
);
},
toggleCompleted: function () {
this.model.toggle();
},
edit: function () {
this.$el.addClass('editing');
this.$input.focus();
},
close: function () {
var value = this.$input.val();
var trimmedValue = value.trim();
if (!this.$el.hasClass('editing')) {
return;
}
if (trimmedValue) {
this.model.save({ title: trimmedValue });
if (value !== trimmedValue) {
this.model.trigger('change');
}
} else {
this.clear();
}
this.$el.removeClass('editing');
},
updateOnEnter: function (e) {
if (e.which === ENTER_KEY) {
this.close();
}
},
revertOnEscape: function (e) {
if (e.which === ESC_KEY) {
this.$el.removeClass('editing');
}
},
clear: function () {
this.model.destroy();
}
});
})(jQuery);
New Meteor User.
Wanting to modify the leaderboard example for a story sizing tool where members in a team can rate a story simultaneously.
Very similar to leaderboard, but want to add a flag/event/button for an admin user to be able to turn on and off the leaderboard displayed.
Here is my feeble attempt at this -
// Set up a collection to contain member information. On the server,'
// it is backed by a MongoDB collection named "members".
Members = new Meteor.Collection("members");
Flags = new Meteor.Collection("Flags");
if (Meteor.isClient) {
Meteor.startup(function() {
Session.set("viewall", false);
});
Template.leaderboard.members = function () {
if (Session.get("viewall"))
{
return Members.find({}, {sort: {score: -1, name: 1}});
}
};
Template.size.members = function() {
return Members.find({}, {sort: {name: 1}});
};
Template.size.events ({
'click input.submit': function(){
var memname = document.getElementById('select_member').value;
//alert(memname);
var member = Members.findOne({_id: memname});
if(member._id)
{
Session.set("selected_player", member._id);
//alert(member.name);
}
var memsize = document.getElementById('select_size').value;
alert(memsize);
Members.update(Session.get("selected_player"), {$set: {score: memsize}});
}
});
Template.leaderboard.isAdmin = function() {
var member=Members.findOne(Session.get("selected_player"));
var memtype = member.utype;
if (memtype=== "admin")
{
return true;
}
else
{
return false;
}
};
Template.leaderboard.selected_name = function () {
var member = Members.findOne(Session.get("selected_player"));
return member && member.name;
};
Template.leaderboard.viewAll = function() {
var flag = Flags.findOne({name:"showAll"});
if (flag)
{
alert("View All flag set for current user : " + flag.value);
return flag && flag.value;
}
else return false;
};
Template.leaderboard.selected_size = function () {
var member = Members.findOne(Session.get("selected_player"));
return member && member.score;
};
Template.member.selected = function () {
return Session.equals("selected_player", this._id) ? "selected" : '';
};
Template.leaderboard.events({
'click input.inc1': function () {
alert('setting it to 1');
updatePrevScore();
// Members.find({_id: Session.get("selected_player")}).foreach(function(doc){
// doc.prev_score = doc.score;
// Member.save(doc);
// });
//Members.update(Session.get("selected_player"), {$set: {prev_score: score}});
Members.update(Session.get("selected_player"), {$set: {score: 1}});
},
'click input.inc2': function () {
updatePrevScore();
Members.update(Session.get("selected_player"), {$set: {score: 2}});
},
'click input.inc3': function () {
updatePrevScore();
Members.update(Session.get("selected_player"), {$set: {score: 3}});
},
'click input.inc5': function () {
updatePrevScore();
Members.update(Session.get("selected_player"), {$set: {score: 5}});
},
'click input.inc8': function () {
updatePrevScore();
Members.update(Session.get("selected_player"), {$set: {score: 8}});
},
'click input.inc13': function () {
updatePrevScore();
Members.update(Session.get("selected_player"), {$set: {score: 13}});
},
'click input.inc20': function(){
updatePrevScore();
Members.update(Session.get("selected_player"),{$set: {score:20}});
},
'click input.reset': function(){
if (confirm('Are you sure you want to reset the points?')) {
resetScores();
}
},
'click input.showAll': function() {
setFlag();
},
'click input.hideAll': function() {
resetFlag();
}
});
Template.member.events({
'click': function () {
Session.set("selected_player", this._id);
}
});
function resetFlag() {
Meteor.call("resetFlag", function(error, value) {
Session.set("viewall", false);
});
};
function setFlag() {
Meteor.call("setFlag", function(error, value) {
Session.set("viewall", true);
});
};
function resetScores() {
//alert('resetting scores');
Members.find().forEach(function (member) {
Members.update(member._id, {$set: {prev_score: member.score}});
Members.update(member._id, {$set: {score: 0}});
});
Session.set("selected_player", undefined);
};
function updatePrevScore() {
//alert('resetting scores');
Members.find().forEach(function (member) {
if (member._id === Session.get("selected_player"))
{
Members.update(member._id, {$set: {prev_score: member.score}});
Members.update(member._id, {$set: {score: 0}});
}
});
};
}
// On server startup, create some members if the database is empty.
if (Meteor.isServer) {
Members.remove({});
Meteor.startup(function () {
if (Members.find().count() === 0) {
var names = ["Member 1",
"Member 2",
"Member 3",
"Member 4",
"Member 5"
];
var type;
for (var i = 0; i < names.length; i++)
{
if (i===0)
type = "admin";
else
type="user";
Members.insert({name: names[i], score: 0, prev_score:0, utype:type});
}
}
else resetScores();
if (Flags.find().count() === 0) {
Flags.insert({name: "showAll", value: false});
}
else Flags.update({name:"showAll"}, {$set: {value:false}});
}
);
Meteor.methods({
setFlag: function() {
Flags.update({name:"showAll"}, {$set: {value:true}});
console.log("Updated flag to true");
return true;
},
resetFlag: function() {
Flags.update({name:"showAll"}, {$set: {value:false}});
console.log("Updated flag to false");
return false;
},
}
);
HTML =>
<head>
<title>Story Points Exercise</title>
</head>
<body>
<div id="story_id">
Story Sizing for Story ID: 78972
</div>
<div id="outer">
{{> leaderboard}}
</div>
</body>
<template name="size">
<div class="select_member">
<select id="select_member">
{{#each members}}
<option value={{_id}}> {{name}} </option>
{{/each}}
</select>
</div>
<div class="select_size">
<select id="select_size"> Select Size:
<option value=1>1</option>
<option value=2>2</option>
<option value=3>3</option>
<option value=5>5</option>
<option value=8>8</option>
<option value=13>13</option>
<option value=20>20</option>
</select>
</div>
<div class="submitbutton">
<input type="button" class="submit" value="Submit" />
</div>
</template>
<template name="leaderboard">
<div class="leaderboard">
{{#if selected_name}}
<div class="details">
<div class="name">{{selected_name}}</div>
<div class="size">{{selected_size}}</div>
<div class="details">
<input type="button" class="inc1" value="1 points" />
<input type="button" class="inc2" value="2 points" />
<input type="button" class="inc3" value="3 points" />
<input type="button" class="inc5" value="5 points" />
<input type="button" class="inc8" value="8 points" />
<input type="button" class="inc13" value="13 points" />
<input type="button" class="inc20" value="20 points" />
</div>
{{#if isAdmin}}
<input type="button" class="showAll" value="showAll" />
<input type="button" class="hideAll" value="hideAll" />
<input type="button" class="reset" value="Reset"/>
{{/if}}
{{#if viewAll}}
{{#each members}}
{{> member}}
{{/each}}
{{/if}}
</div>
{{/if}}
{{#unless selected_name}}
{{>size}}
{{/unless}}
</div>
</template>
<template name="member">
<div class="member {{selected}}">
<span class="name">{{name}}</span>
<span class="score">{{score}}</span>
<span class="prevscore">{{prev_score}}</span>
</div>
</template>
Its working on one browser and one user (Admin type is able to enable the viewAll flag for the template to show all members.)
But, not working for mulitple users.
So, if I open a browser, select my name and a story size and I am the admin, I click Submit - I see buttons for changing the story size and to showAll, hideAll and Resetting the leaderbaord.
when I click on showAll, I as admin can see the leaderboard. But another user is not able to see the leaderboard. I verified that the change display event (showAll flag=true) is being received by that user's client.
Any ideas?
I was able to solve this by using a new collection called Views and observing for a specific document called showAll.
The showAll was set to be updated by the admin only.
It was hooked up to the template view with an {{#if}}.