I'm creating an admin panel where I am able to change the user roles via a select control while using alanning:roles. The issue I am having is I am unable to get the user that is associated with each select control. I referenced this tutorial on creating an admin panel with Meteor, but when I call the method to changeRoles, the console returns this error:
Original Issue: Solved
Error: Missing 'users' param
I20190218-14:59:27.319(-6)? at Object._updateUserRoles (packages/alanning_roles.js:684:23)
I20190218-14:59:27.319(-6)? at Object.setUserRoles (packages/alanning_roles.js:250:11)
I20190218-14:59:27.320(-6)? at MethodInvocation.changeRole (server/main.js:88:13)`
Changed code from:
Template.userHome.events({
'click #confirmChanges': function (event) {
let currentRole = $(event.target).find('option:selected').val();
let userId = $(event.target.id).val();
console.log(userId);
if (window.confirm("Change User Roles?")) {
Meteor.call("changeRole", {
role: currentRole,
user: userId
})
window.location.reload();
}
}
})
To this (change in how I set userId value):
Template.userHome.events({
'change [name="userRole"]': function (event) {
let currentRole = $(event.target).find('option:selected').val();
let userId = $(event.target.id);
console.log(currentRole);
if (window.confirm("Change User Roles?")) {
Meteor.call("changeRole", {
role: currentRole,
user: userId
})
window.location.reload();
}
}
})
Now, Roles.setUserRoles() is not working yet parameters have values
console.log(options.role) returns the correct value and
console.log(options.user) returns:
I20190219-20:37:37.527(-6)? { length: 0,
I20190219-20:37:37.528(-6)? prevObject:
I20190219-20:37:37.529(-6)? { length: 0,
I20190219-20:37:37.529(-6)? prevObject:
I20190219-20:37:37.529(-6)? { length: 0,
I20190219-20:37:37.530(-6)? prevObject: [Object],
I20190219-20:37:37.530(-6)? context: [Object],
I20190219-20:37:37.531(-6)? selector: '3FzfDhZWcGFg6ggTE' },
I20190219-20:37:37.531(-6)? context: { location: [Object] } },
I20190219-20:37:37.532(-6)? context:
I20190219-20:37:37.532(-6)? { location:
I20190219-20:37:37.532(-6)? { href: 'http://localhost:3000/userHome',
I20190219-20:37:37.533(-6)? ancestorOrigins: {},
I20190219-20:37:37.533(-6)? origin: 'http://localhost:3000',
I20190219-20:37:37.534(-6)? protocol: 'http:',
I20190219-20:37:37.534(-6)? host: 'localhost:3000',
I20190219-20:37:37.534(-6)? hostname: 'localhost',
I20190219-20:37:37.534(-6)? port: '3000',
I20190219-20:37:37.535(-6)? pathname: '/userHome',
I20190219-20:37:37.535(-6)? search: '',
I20190219-20:37:37.536(-6)? hash: '' } } }
Client Code:
Template.userHome.events({
'change [name="userRole"]': function (event) {
let currentRole = $(event.target).find('option:selected').val();
let userId = $(event.target.id);
console.log(currentRole);
if (window.confirm("Change User Roles?")) {
Meteor.call("changeRole", {
role: currentRole,
user: userId
})
window.location.reload();
}
}
})
Server Code:
Meteor.methods({
changeRole( options ) {
console.log("Change User is Being Called");
try {
Roles.setUserRoles( options.user, [ options.role ] );
console.log(options.role)
} catch( exception ) {
console.log(exception);
}
}
});
UserHome Template
<div class="nav">
<p class="welcomeUser">Welcome, {{#with userInfo}}{{profile.firstname}} {{profile.lastname}}{{/with}}</p>
<button id="logout" class="universalButton">Log Out</button>
</div>
<div class="pageContent">
{{#if userIsAdmin}}
<div class="adminPanel">
<table id="userTable">
<caption>Admin Panel</caption>
<tr>
<th class="usertableHead">Username</th>
<th class="usertableHead">Role</th>
</tr>
{{#each users}}
<tr>
<td class="usertableData">{{username}}</td>
<td class="usertableData">
<div class="styled-select purple rounded">
<select id={{_id}} name="userRole">
<option value={{roles}}>{{roles}}</option>
<option value="Teacher">Teacher</option>
</select>
</div>
</td>
</tr>
{{/each}}
</table>
<button id="confirmChanges" class="universalButton">Confirm Changes</button>
</div>
{{/if}}
</div>
console.log(options.user) should print the _id of the user, not a jQuery object. In your change [name="userRole"] event, replace let userId = $(event.target.id); with let userId = event.target.id;
The better way of assigning and retrieving values is as below:
HTML:
...
<select data-id={{_id}} name="userRole">
<option value={{roles}}>{{roles}}</option>
<option value="Teacher">Teacher</option>
</select>
...
JS:
Template.userHome.events({
'change [name="userRole"]': function (event) {
let userId = event.target.dataset.id;
let currentRole = event.target.value;
console.log(userId, currentRole);
...
}
})
Related
I am updating the rocket chat app to have a departments filter on the department list page. I am running into an issue where my filter seems to be tied to the same collection as the result set. So when I update the filter all the other filter options are removed. I'm not sure the best way to make it so the filter only impacts the result list and not both.
Before:
After:
HTML
<template name="livechatDepartments">
{{#requiresPermission 'view-livechat-manager'}}
<fieldset>
<form class="form-inline" method="post">
<div class="form-group">
<label for="department">{{_ "Department"}}</label>
<select name="department">
<option value=""></option>
{{#each departmentsDDL}}
<option value="{{_id}}">{{name}}</option>
{{/each}}
</select>
</div>
<div class="form-group">
<label for="agent">{{_ "Served_By"}}</label>
<select name="agent">
<option value=""></option>
{{#each agents}}
<option value="{{_id}}">{{username}}</option>
{{/each}}
</select>
</div>
<button class="button">{{_ "Filter"}}</button>
</form>
</fieldset>
<div class="list">
<table>
<thead>
<tr>
<th width="20%">{{_ "Name"}}</th>
<th width="30%">{{_ "Description"}}</th>
<th width="10%">{{_ "Num_Agents"}}</th>
<th width="10%">{{_ "Num_Available_Agents"}}</th>
<th width="20%">{{_ "Enabled"}}</th>
<th width="20%">{{_ "Show_on_registration_page"}}</th>
<th>{{_ "Delete"}}</th>
</tr>
</thead>
<tbody>
{{#each departments}}
<tr class="department-info row-link" data-id="{{_id}}">
<td>{{name}}</td>
<td>{{description}}</td>
<td>{{numAgents}}</td>
<!--<td>{{}}</td>-->
<td>{{#if enabled}}{{_ "Yes"}}{{else}}{{_ "No"}}{{/if}}</td>
<td>{{#if showOnRegistration}}{{_ "Yes"}}{{else}}{{_ "No"}}{{/if}}</td>
<td><i class="icon-trash"></i></td>
</tr>
{{/each}}
</tbody>
</table>
</div>
<div class="text-center">
<button class="button load-more">{{_ "Load_more"}}</button>
</div>
{{_ "New_Department"}}
{{/requiresPermission}}
JS:
Template.livechatDepartments.helpers({
departmentsDDL() {
return LivechatDepartment.find({}, { sort: { name: -1 } });
},
departments() {
return LivechatDepartment.find({}, { sort: { name: -1 } });
},
agents() {
return AgentUsers.find({}, { sort: { name: 1 } });
}
});
Template.livechatDepartments.events({
'click .remove-department' (e /*, instance*/ ) {
e.preventDefault();
e.stopPropagation();
swal({
title: t('Are_you_sure'),
type: 'warning',
showCancelButton: true,
confirmButtonColor: '#DD6B55',
confirmButtonText: t('Yes'),
cancelButtonText: t('Cancel'),
closeOnConfirm: false,
html: false
}, () => {
Meteor.call('livechat:removeDepartment', this._id, function(error /*, result*/ ) {
if (error) { return handleError(error); }
swal({
title: t('Removed'),
text: t('Department_removed'),
type: 'success',
timer: 1000,
showConfirmButton: false
});
});
});
},
'click .department-info' (e /*, instance*/ ) {
e.preventDefault();
FlowRouter.go('livechat-department-edit', { _id: this._id });
},
'submit form' (e, instance) {
e.preventDefault();
const filter = {};
$(':input', event.currentTarget)
.each(function() {
if (this.name) {
filter[this.name] = $(this)
.val();
}
});
instance.filter.set(filter);
instance.limit.set(20);
}
});
Template.livechatDepartments.onCreated(function() {
this.limit = new ReactiveVar(20);
this.filter = new ReactiveVar({});
this.subscribe('livechat:agents');
this.autorun(() => {
this.subscribe('livechat:departments', this.filter.get(), 0, this.limit.get());
});
});
Meteor Method:
Meteor.publish("livechat:departments", function(filter = {}, offset = 0, limit = 20) {
if (!this.userId) {
return this.error(
new Meteor.Error("error-not-authorized", "Not authorized", {
publish: "livechat:agents"
})
);
}
if (!RocketChat.authz.hasPermission(this.userId, "view-l-room")) {
return this.error(
new Meteor.Error("error-not-authorized", "Not authorized", {
publish: "livechat:agents"
})
);
}
check(filter, {
agent: Match.Maybe(String), // agent _id who is serving
department: Match.Maybe(String)
});
const query = {};
if (filter.agent) {
const DepartmentFilter = [];
RocketChat.models.LivechatDepartmentAgents
.find({
agentId: filter.agent
})
.forEach(department => {
DepartmentFilter.push(department);
});
var depts = DepartmentFilter.map(function(dep) {
return dep.departmentId;
});
As you stated in the question, your filter is tied to the same collection as your results set. So, how can you fix this?
Solution 1 - Easy, and if data in livechat:departments collection is not too large, probably the best:
Revert back your subscription code to fetch all data (not filtered), and filter in the departments helper function
// in Template.livechatDepartments.onCreated
this.subscribe('livechat:departments');
// in Template.livechatDepartments.helpers
departments() {
const departmentFilter = Template.instance().filter.get().department;
if (departmentFilter){
return LivechatDepartment.find({name: departmentFilter }, { sort: { name: -1 } });
}
else {
return LivechatDepartment.find({}, { sort: { name: -1 } });
}
}
Solution 2 - Keep departments helper with filter from Solution 1 ,
but now subscribe twice to livechat:departments
You can reuse the current publish for the filtered list of departments (add back your filtered subscription), and create a new pub/sub channel that publishes all the departments, but only needs to send the name + _id fields used to populate select options.
I'm getting Uncaught TypeError: pathDef.replace is not a function console error using Flow Router in MeteorJS. I'm new to Flow having used Iron Router before so probably not doing something correctly.
Note that it works fine if I load another page first and then navigate to this page but I get the error if I reload the page.
Below is the faulty code:
Client template
{{#if Template.subscriptionsReady}}
{{#each users}}
<tr>
<td>
{{linkNames profile.firstname profile.lastname}}
</td>
<td>
{{username}}
</td>
<td>
{{emails.[0].address}}
</td>
<td>
{{toUpperCase roles.[0]}}
</td>
<td>
{{getUsernameById createdBy}}
</td>
<td>
<i class="fa fa-edit"></i>
</td>
<td>
<i class="fa fa-times"></i>
</td>
</tr>
{{else}}
<tr>
<td colspan="6">
<p>There are no users</p>
</td>
</tr>
{{/each}}
{{else}}
<p>Loading...</p>
{{/if}}
Pub
/* Users */
Meteor.publish('users', function() {
if (Roles.userIsInRole(this.userId, ['admin', 'team'])) {
return Meteor.users.find({}, {
fields: {
'profile.firstname': 1,
'profile.lastname': 1,
'emails': 1,
'username': 1,
'roles': 1,
'createdBy': 1
},
sort: {'roles': 1}
})
} else if (Roles.userIsInRole(this.userId, ['client'])) {
return Meteor.users.find({}, {
fields: {
'profile.firstname': 1,
'profile.lastname': 1,
'emails': 1,
'username': 1
}
});
}
});
Client JS
/* On created */
Template.users.onCreated(function() {
var instance = this;
instance.autorun(function() {
instance.users = function() {
instance.subscribe(Meteor.users.find({}));
}
});
});
/* Helpers */
Template.users.helpers({
users: function() {
var users = Meteor.users.find({});
return users;
}
});
I also get an error Exception in template helper: TypeError: Cannot read property 'username' of undefined in other templates for the following global helper (although the helper works as expected):
/* Current Username */
Template.registerHelper('currentUsername', function() {
return Meteor.user().username;
});
Your first error is probably happening due to an error in your routing code. Make sure you've defined the parameters in the route and are using them in any routing code correctly.
The second error is because Meteor.user() is not guaranteed to always be defined immediately. Change your helper to:
Template.registerHelper('currentUsername', function() {
var user = Meteor.user()
if( user ) {
return username;
}
});
I have an edit form that is bound to existing data. I have two dropdowns that I would like have update each up depending on specific criteria. For example, is I set the assignedTo to blank, change the status to UnAssigned, and/or if I change the status to UnAssigned, change the AssignedTo to be blank, etc.
Status Dropdown:
<select class="form-control edit-status" id="status" name="status">
<option selected="{{equals status 'UnAssigned'}}" value="UnAssigned">UnAssigned</option>
<option selected="{{equals status 'Open w Vendor'}}" value="Open w Vendor">Ticket with Vendor</option>
<option selected="{{equals status 'Not Started'}}" value="Not Started">Not Started</option>
<option selected="{{equals status 'In Progress'}}" value="In Progress">In Progress</option>
<option selected="{{equals status 'Reopened'}}" value="Reopened">Reopened</option>
</select>
Assigned To Dropdown
<select class="form-control edit-assigned-to" id="assignedTo" name="assignedTo">
<option value="" selected="{{equals assignedTo ''}}">Select a User</option>
{{#each users}}
<option value="{{_id}}" selected="{{equals ../assignedTo _id}}">{{services.auth0.name}}</option>
{{/each}}
</select>
I already have an equality helper on each of them to pick the value that is already set when loading the form data. I was thinking of using Reactive Vars with change events, but I'm unsure how to exactly to implement that or if I'm even on the right track. T
Let's say the collection that contains the status and assignedTo fields is called myCollection:
Template.myTemplate.events({
'change #status': function(ev){
ev.stopPropagation();
var newStatus = $('#status).val();
if ( newStatus == "UnAssigned" ){ // unset the assignedTo value
myCollection.update({ _id: this._id },
{ $set: { status: newStatus}, $unset: { assignedTo: true }});
} else {
myCollection.update{ _id: this._id },{ $set: { status: newStatus}});
}
},
'change #assignedTo': function(ev){
ev.stopPropagation();
var newAssignedTo = $('#assignedTo').val();
if ( newAssignedTo != "None" ){
var userId = Meteor.users.findOne({ _id: newAssignedTo });
myCollection.update({ _id: this._id },{ $set: { assignedTo: newAssignedTo }});
} else { // unset the assignedTo
myCollection.update({ _id: this._id },
{ $set: { status: "UnAssigned" }, $unset { assignedTo: true }});
}
}
});
Note that in your template you're missing a way to select "None" as a value for the assigned user.
I ended up using some jquery in my change events.
'change .edit-status': function(event, template){
var status = event.target.value;
if (status === "UnAssigned") {
$(".edit-assigned-to").val("");
}
return false;
},
'change .edit-assigned-to': function(event, template){
var assignedTo = event.target.value;
if (!template.data.assignedTo && assignedTo !== "") {
$(".edit-status").val("Not Started");
}
if (assignedTo === "") {
$(".edit-status").val("UnAssigned");
}
return false;
}
I'm not sure if there are better approaches or pitfalls to this solution, but it seems to be meeting my needs.
I have a user index and would like to display information on each user. User ID shows up fine, but the app isn't showing emails.
Here is my template:
<template name="users">
<h1>List of all users</h1>
{{#each users}}
<div class="list_item">
<p>ID: {{_id}}</p>
<p>Email: {{email}}</p>
</div>
{{/each}}
</template>
And here are my routes:
Router.route('/users', function () {
this.render('users');
}, {
waitOn: function() {
return [
Meteor.subscribe('users')
]
},
data: {users: Meteor.users.find({})}
});
And finally, my publication:
Meteor.publish('users', function () {
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
});
Any ideas?
The correct way to display the email would be :
<p>Email: {{emails.[0].address}}</p>
Email addresses are stored as an array in the user object.
You can check by typing Meteor.user() in the console :
Object {
...
emails: Array[1]
0: Object{
address: "username#domain.com",
verified: false
}
...
}
{{ currentUser.emails.[0].address }}
I'm having trouble getting Meteor.publish to update in response to a changing form field. The first call to publish seems to stick, so the query operates in that subset until the page is reloaded.
I followed the approach in this post, but am having no luck whatsoever.
Any help greatly appreciated.
In lib:
SearchResults = new Meteor.Collection("Animals");
function getSearchResults(query) {
re = new RegExp(query, "i");
return SearchResults.find({$and: [ {is_active: true}, {id_species: {$regex: re}} ] }, {limit: 10});
}
In client:
Session.set('query', null);
Template.searchQuery.events({
'keyup .query' : function (event, template) {
query = template.find('.query').value
Session.set("query", query);
}
});
Meteor.autosubscribe(function() {
if (Session.get("query")) {
Meteor.subscribe("search_results", Session.get("query"));
}
});
Template.searchResults.results = function () {
return getSearchResults(Session.get("query"));
}
On server:
Meteor.publish("search_results", getSearchResults);
Template:
Search for Animals
<body>
{{> searchQuery}}
{{> searchResults}}
</body>
<template name="searchQuery">
<form>
<label>Search</label>
<input type="text" class="query" />
</form>
</template>
<template name="searchResults">
{{#each results}}
<div>
{{_id}}
</div>
{{/each}}
</template>
Update [WRONG]
Apparently, the issue is that the collection I was working with was (correctly) generated outside of Meteor, but Meteor doesn't properly support Mongo's ObjectIds. Context here and related Stackoverflow question.
Conversion code shown there, courtesy antoviaque:
db.nodes.find({}).forEach(function(el){
db.nodes.remove({_id:el._id});
el._id = el._id.toString();
db.nodes.insert(el);
});
Update [RIGHT]
So as it turns out, it was an issue with RegExp / $regex. This thread explains. Instead of:
function getSearchResults(query) {
re = new RegExp(query, "i");
return SearchResults.find({$and: [ {is_active: true}, {id_species: {$regex: re}} ] }, {limit: 10});
}
At the moment, one needs to do this instead:
function getSearchResults(query) {
// Assumes query is regex without delimiters e.g., 'rot'
// will match 2nd & 4th rows in Tim's sample data below
return SearchResults.find({$and: [ {is_active: true}, {id_species: {$regex: query, $options: 'i'}} ] }, {limit: 10});
}
That was fun.
PS -- The ddp-pre1 branch has some ObjectId functionality (SearchResults = new Meteor.Collection("Animals", {idGeneration: "MONGO"});)
Here's my working example:
UPDATE the original javascript given was correct. The problem, as noted in the comments, turned out to be that meteor doesn't yet support ObjectIds.
HTML:
<body>
{{> searchQuery }}
{{> searchResults}}
</body>
<template name="searchQuery">
<form>
<label>Search</label>
<input type="text" class="query" />
</form>
</template>
<template name="searchResults">
{{#each results}}
<div>
{{id_species}} | {{name}} - {{_id}}
</div>
{{/each}}
</template>
Javascript:
Animals = new Meteor.Collection("Animals");
function _get(query) {
re = new RegExp(query, "i");
console.log("rerunning query: " + query);
return Animals.find({$and: [ {is_active: true}, {id_species: {$regex: re}} ] }, {limit: 10});
};
if (Meteor.isClient) {
Session.set("query", "");
Meteor.autosubscribe(function() {
Meteor.subscribe("animals", Session.get("query"));
});
Template.searchQuery.events({
'keyup .query' : function (event, template) {
query = template.find('.query').value
Session.set("query", query);
}
});
Template.searchResults.results = function () {
return _get(Session.get("query"));
}
}
if (Meteor.isServer) {
Meteor.startup(function() {
if (Animals.find().count() === 0) {
Animals.insert({name: "panda", is_active: true, id_species: 'bear'});
Animals.insert({name: "panda1", is_active: true, id_species: 'bearOther'});
Animals.insert({name: "panda2", is_active: true, id_species: 'bear'});
Animals.insert({name: "panda3", is_active: true, id_species: 'bearOther'});
}
});
Meteor.publish("animals", _get);
}