Meteor: Building admin frontend to manage users - delay in showing data - javascript

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?

Related

Send true or false to database wether checkbox is checked or not

i got an issue regarding checkboxes with nedb. I want to send true or false if the checkbox is checked or not to the database i cannot solve this issue. i am working with node.js and nedb. please help!
client js eventlistener:
var taskDone = document.querySelectorAll('.taskDone');
taskDone.forEach(btn => {
btn.addEventListener('click', (e) => {
var done = e.target.attributes[1].value;
let id = e.target.getAttribute('data-id');
let isDone = document.querySelector(`input[data-id=${id}]`).value;
console.log(isDone + "isdone")
if ($(taskDone).is(':checked')) {
$('.text').addClass('line-through')
console.log("trues")
$.ajax({
url: 'http://localhost:3000/done/' + id,
type: 'PUT',
data: { isDone }
}).done(function (data) {
//location.reload()
console.log(data)
})
} else {
console.log('falses')
$('.text').removeClass('line-through')
}
})
})
update function to nedb:
function taskIsDone (id, done) {
return new Promise((resolve, reject) => {
db.update({ _id: id }, { $set: done }, { returnUpdatedDocs: true }, (err, num, updateDocs) => {
if (err) {
reject(err)
} else {
resolve(updateDocs)
}
})
})
}
server:
app.put('/done/:_id', async(req, res) => {
try {
var id = req.params._id;
let done = {
title: req.body.isDone,
}
const updateToDo = await taskIsDone(id, done)
console.log(updateToDo + " Todo done");
res.json(updateToDo);
} catch (error) {
res.json({error: error.message});
}
})
html/ejs:
<% for ( var i = 0; i < row.length; i++) { %>
<div class="edit-container" >
<input type="text" name="editTask" value="<%=row[i].title %>" data-id="<%=row[i]._id %>">
<button name="<%= row[i]._id %>" class="edit" data-id="<%=row[i]._id %>">save edit</button>
</div>
<div>
<input type="checkbox" name="isDone" class="taskDone" data-id="<%=row[i]._id %>">
<span class="text"><%= row[i].title %></span>
<button class="delete" name="<%= row[i]._id %>">delete</button>
</div>
<br>
<% } %>
i could really need some help with this! thanks
I have recreated a minimal example of what you are trying to do with checkbox checked state. I have added three checkboxes with same class name .taskDone
And i have using a change function not a click function. Every-time you clicked on the checkbox and check it will show the console log with checked and the data-id of that checkbox as well.
To get the data-id you can simply use .data function of jQuery and just specify what you want after the data-** to get it stored value.
In addition, do not use fat arrow - => function with jQuery. Use normal function statements so you can access you things by using $(this) instead of specifying each class or id
Live Working Demo:
let taskDone = document.querySelectorAll('.taskDone'); //get all the chechbox with same class .taskDone
taskDone.forEach(function(btn) { //use normal function
btn.addEventListener('change', function() {
let id = $(this).data('id') //get the data id of checkbox
if ($(this).is(':checked')) { //check if the clicked checkbox is checked or not
console.log(id + ' is Checked - Updating neDB') //console.log
$.ajax({
url: 'http://localhost:3000/done/' + id,
type: 'PUT',
data: 'isDone'
}).done(function(data) {
console.log(data)
})
} else {
console.log("Not Checked")
}
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="checkbox" name="isDone" class="taskDone" data-id="1">
<input type="checkbox" name="isDone" class="taskDone" data-id="2">
<input type="checkbox" name="isDone" class="taskDone" data-id="3">

Link MongoDB collections

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');

Meteor Single collection Field Editable Content

I am attempting to make a element in a meteor template editable via a update function. The data changes when it is inserted from a server side code in the fixture.js code. However I have no luck updating it via a editable form with some Template.name.events({}); code and, creating a collection, publishing and subscribing to it. The very last piece of code is the fixture.js file. So in some regard I can insert into the collection and update it, but I have no luck with the edit financialsEdit template. The router.js file I included only contains parts regarding the financials template. If needed I will post more.
Basically I can't update a collection value with a update function using $set and passing a key value pair.
UPDATE: I added the permissions.js file in the lib directory to show what ownsDocument returns.
Here is my code.
client Directory
client/editable/edit_financial.js
Template.financialsEdit.events({
'submit .financialsEdit': function(e) {
e.preventDefault();
var currentFinanceId = this._id;
var financialsProperties = {
issuedOutstanding: $('#issuedOutstanding').val()
}
Financials.update(currentFinanceId, {$set: financialsProperties}, function(error) {
if (error) {
alert(error.reason);
} else {
console.log(financialsProperties);
// Router.go('financials');
Router.go('financials');
}
});
}
});
client/editable/financials_helpers.js
Template.financials.helpers({
financials: function() {
return Financials.find();
},
ownFinancial: function() {
return this.userId === Meteor.userId();
}
});
client/editable/financials
<template name="financials">
<div id="finance">
{{#each financials}}
<h2>Issued Outstand : {{issuedOutstanding}}</h2>
{{/each}}
</div>
</template>
client/editable/financials_edit.html
<template name="financialsEdit">
<form class="main form financialsEdit">
<input id="issuedOutstanding" type="number" value="{{issuedOutstanding}}" placeholder="{{issuedOutstanding}}" class="form-control">
<input type="submit" value="Submit" class="submit"/>
</form>
</template>
lib Directory
lib/router.js
Router.route('/financials', function () {
this.render('financials');
});
Router.route('/financialsedit', {name: 'financialsEdit'});
lib/collections/financials.js
Financials = new Mongo.Collection('financials');
Financials.allow({
update: function(userId, financial) { return ownsDocument(userId, financial); },
remove: function(userId, financial) { return ownsDocument(userId, financial); },
});
Financials.deny({
update: function(userId, financial, fieldNames) {
// may only edit the following two fields:
return (_.without(fieldNames, 'issuedOutstanding').length > 0);
}
});
lib/permissions.js
// check that the userId specified owns the documents
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
server/publications.js
Meteor.publish('financials', function() {
return Financials.find();
});
server/fixture.js
if (Financials.find().count() === 0) {
Financials.insert({
issuedOutstanding: '43253242'
});
}

how do i toggle the visibility of an html element using knockoutjs ?

i am using knockoutjs v3.1.0. i am trying to build a master-detail like view. the problem i am having is that elements are not showing (though they are hiding). my mock code is at http://jsfiddle.net/jwayne2978/qC4RF/3/
this is my html code.
<div data-bind="foreach: users">
<div>Row</div>
<div data-bind="text: username"></div>
<div data-bind="visible: showDetails">
<div data-bind="text: address"></div>
</div>
<div>
<a href="#"
data-bind="click: $root.toggleDetails">
Toggle Div
</a>
</div>
this is my javascript code
var usersData = [
{ username: "test1", address: "123 Main Street" },
{ username: "test2", address: "234 South Street" }
];
var UsersModel = function (users) {
var self = this;
self.users = ko.observableArray(
ko.utils.arrayMap(users, function (user) {
return {
username: user.username,
address: user.address,
showDetails: false
};
}));
self.toggleDetails = function (user) {
user.showDetails = !user.showDetails;
console.log(user);
};
};
ko.applyBindings(new UsersModel(usersData));
what's supposed to happen is that when a user clicks on the link, the corresponding HTML div should show. the console clearly shows that the property is being changed on the user object, but the HTML element's visibility is not changing. i also explicitly made the showDetails property observable, but that did not help.
showDetails : ko.observable(false)
any help is appreciated.
var UsersModel = function (users) {
var self = this;
//var flag=ko.observable(true);
self.users = ko.observableArray(
ko.utils.arrayMap(users, function (user) {
return {
username: user.username,
address: user.address,
showDetails: ko.observable(false) //it should be observable
};
}));
self.toggleDetails = function (user) {
user.showDetails(!user.showDetails());
console.log(user);
};
};
Fiddle Demo

Why Does a Template That Renders for One User Not Render For Another User?

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}}.

Categories