Meteor: Iterate over user roles for displaying checkboxes - javascript

With this template I'm showing all results, which are stored in the users-collection:
<template name="user">
{{#each users.roles}}
<input type="checkbox" data-role="{{this}}">{{this}}
{{/each}}
</template>
There are some roles defined like "admin", "editor", "user_manage". But this leads to some problems:
1) If one user has just the role "admin", there will only one checkbox displayed. But I need to display all possible roles. Only if the role is in the profile, the checkbox should be checked.
2) The displayed description of the role should be 'nicer'. I mean the result should be like:
<input type="checkbox" data-role="admin"> Administrator
<input type="checkbox" data-role="editor"> Editor
<input type="checkbox" data-role="user_manage"> Manage users
I guess I need a helper:
Template.user.helpers({
var roles = {admin: 'Administrator', editor: 'Editor', user_manage: 'Manage users'};
return roles;
});
Now I think of iterating over all elements in roles and checking if the role exists (=checked) or not (=unchecked). The values of the elements are for displaying the Label of the checkbox.
1) Would this be the correct way doing that?
2) How do I connect the helper var roles to the template for checking?

You're basically on the right track! Your helper needs to return an array of objects instead of an object. Then you need a helper which returns a boolean based on the whether that role is in the user's role array. For example:
js:
Template.user.helpers({
roles: function(){
return [
{ label: 'admin', name: 'Administrator'},
{ label: 'editor', name: 'Editor'},
{ label: 'user_manage', name: 'Manage users'}
];
},
hasRole: function(role){
return Meteor.user().roles.indexOf(role)>-1; //assuming user.roles is just an array
}
});
html:
<template name="user">
{{#each roles}}
<input type="checkbox" data-role="{{this.label}}"
{{#if hasRole this.label}}checked{{/if}}>{{this.name}}
{{/each}}
</template>

Related

In AngularJS how to uncheck a radio button to make all objects become false

I have an array with many "contact" objects inside. Only one contact can be the primary contact (primary: true).
I also have a radio button to select which contact is the primary.
Q: How can I make one contact primary and deactivate all of the others (primary: false)? so only one object have the property (primary: true) and the rest false?
My example: http://plnkr.co/edit/Y3as4SXv2ZGQSF39W8O6?p=preview
.controller('ExampleController', ['$scope',
function($scope) {
$scope.addContList = [
{
email: "q#q.com",
jobTitle: "clerk",
name: "nico2",
phone: "1",
primary: true
},
{
email: "a#a.com",
jobTitle: "director",
name: "david",
phone: "1",
primary: false
}
];
$scope.$watch('addContList', function() {
console.log('changed', JSON.stringify($scope.addContList, null, 2))
}, true)
}
]);
Here is the view
<tr ng-repeat="contact in addContList">
<td>
<label class="radio-inline">
<input type="radio" value="" name="ui_cont" ng-model="contact.primary" ng-value="true">
</label>
</td>
<td>{{ contact.name }} value = {{contact.primary}} </td>
<td>Edit</td>
<td>Delete</td>
</tr>
You would want to add an ngChange event to your input and change all other inputs to false when one gets set to true. I have updated your Plnkr here: http://plnkr.co/edit/7gxI7if9nC7hAMQES1eu?p=preview
<input type="radio" value="" name="ui_cont" ng-change='changeOnPrimary(contact)' ng-model="contact.primary" ng-value="true">
Then in your controller:
$scope.changeOnPrimary = function(selectedContact) {
// iterate over your whole list
angular.forEach($scope.addContList, function(contact) {
// set primary to false for all contacts excepts selected
if (selectedContact.name !== contact.name) {
contact.primary = false;
}
});
}
Please note: the only reason I'm comparing the name field of the object is because there is no unique identifier to compare with. In real code, you would want to compare against an ID rather than a name field.
You can define a new scope property
$scope.primary = null
Then you can define a listener
$scope.$watch("primary", function(value) {
$scope.addContList.forEach(function(contact) {
contact.primary = angular.equals(value, contact);
})
})
and you can define a default value after defining the list
$scope.primary = $scope.addContList[0];
and in the html you change the input line in
<input type="radio" value="" name="ui_cont" ng-model="$parent.primary" ng-value="contact">
You need to use $parent.primary instead of primary, because ng-repeat defines a new child scope.
see http://plnkr.co/edit/5pvatBNwnrJhGzKhOIys?p=preview

Meteor: Editing and saving subdocuments in a collection

I am learning the meteor-framework for it's ability to make realtime inputs and updates easily. Because it's new to me, I'm not quite sure if this is the most efficient way to do things.
I'm trying to create two forms to save some data in the MongoDB. I'm using SimpleSchema.
First there is a simple input field for adding a title to the DB.:
template
<template name="addForm">
<form>
<input type="text" name="text">
<input type="submit" value="add">
</form>
</template>
events.js
Template.addForm.events({
'submit form': function(event){
event.preventDefault();
var title = event.target.text.value;
MongoArticle.insert({
title: title,
slug: title.toLowerCase()
});
event.target.text.value = "";
}
});
collection.js
MongoArticle = new Mongo.Collection('articles');
var subset = new SimpleSchema({
age: {type: String, optional: true},
value: {type: String, optional: true},
info: {type: String, optional: true},
});
MongoArticle.attachSchema(new SimpleSchema({
title: {type: String},
slug: {type: String},
flag: {type: Boolean, optional: true},
elements: {type: [subset], optional: true}
}));
All titles saved in the DB will be displayed in a list:
<template name="list">
<ul>
{{#each art}}
<li>{{title}}</li>
{{/each}}
</ul>
</template>
If you click on the title you will get the detailed form. Here you can add and edit multiple elements, which are saved in a subdocument (elements -> subset).
<template name="Editor">
<table>
<tbody>
{{#each art.elements}}
<tr>
<td><input type="text" value="{{age}}"></td>
<td><input type="text" value="{{value}}"></td>
<td><input type="text" value="{{info}}"></td>
</tr>
{{/each}}
<tr>
<td><input type="text" value=""></td>
<td><input type="text" value=""></td>
<td><input type="text" value=""></td>
</tr>
</tbody>
</table>
</template>
So I'm trying to get all the elements of the selected article to be displayed in a row of input fields. That will allow them to be edited. I want to have realtime saving, so that (if possible) every change is saved without needing to click a button. Is that possible?
The last row should be used to input new data, which should then be added to the Editor.
I tried to do that with a update() on each row and an input() on the last row, but it didn't work for me, because I don't know how to save the row to the elements-field in the DB, which is a subdocument of the articles-collection.
So I read some websites and I found ´aldeed:autoform´ and ´aldeed:collection2´. Would this be helpful for what I am doing?
If you give your inputs names corresponding to your model fields then updating the existing elements boils down to the following snippet:
'change input[name]': function (e, t) {
var articleId = t.data.art._id; // or something similar
var updates = {};
var $target = $(e.target);
var index = $target.closest('tr').index();
var name = $target.attr('name');
updates['elements.' + index + '.' + name] = $target.val();
MongoArticle.update({ _id: articleId }, { $set: updates });
}
You may also consider using different events, e.g. keyup along with some _.debounce().
Inserting a new element is even simpler because you don't need the index. Also you will probably want to hook to different events (maybe button click?) and replace the $set modifier with $push, i.e.
MongoArticle.update({ _id: articleId }, { $push: {
elements: {
age: "",
value: "",
info: ""
}
}});

Dynamically bind objects to inputs in angular

I have these objects right here that I will use to save data from a form, and later send it to an api as JSON :
$scope.price = {}
$scope.item = {"price":$scope.price, };
I also have these field which will be used to dynamically generate inputs on a html page:
$scope.fields = [
{
name: $scope.item.title,
title: 'Title',
type: {
view: 'input'
}
},
{
name: $scope.price.regular,
title: 'Regualar Price',
type: {
view: 'input'
}
}
];
Now in order to generate the form I use this code:
<div class="form-group" ng-repeat="field in fields">
<label>{{ field.title }}:</label>
<span ng-switch on="field.type.view">
<span ng-switch-when="input">
<input
ng-model=field.name
type="text"
/>
</span>
</span>
</div>
And with this code, it is not assigning the values in the input to the objects. Is there a way to do it? I know I can do it this way:
ng-model="item[field.name]"
But that limits me to only one level of the object. I want to be able to bind nested objects. And I just can't seem to figure it out. Thank You!

AngularJS - Binding radio button selection to model inside ng-repeat

I have a data model persons which takes the following form:
personsInfo = {
name: Adam
dob: 31-FEB-1985
docs: [
{
docType: Drivers License,
number: 121212,
selected: false
id: 1
},
{
selected: true,
docType: None
},
{
docType: State ID,
number: 132345,
selected: false,
id: 2
}
]
}
In my markup I have defined the following to dynamically generate radio buttons.
<div ng-repeat="personDoc in personsInfo.docs">
<input type="radio" name="personDocs" ng-model="personDoc.selected" value=""/>
{{personDoc.docType}} <span ng-hide="personDoc.docType === 'None'">Number: {{personDoc.number}}</span>
</div>
I want to be able to check the documents which have selected as true on page load, and then depending on what the user selects save the selected flag in my personsInfo model.
My intent here is to send the personsInfo model back to another page.
If somebody could point me to a working fiddle it would be greatly appreciated!
Thanks!
You're almost there just missing the binding to show which document is selected. We'll add an object to the scope to represent the selected item, then bind the forms to that model.
JS
app.controller('...', function($scope) {
$scope.personInfo = { ... };
$scope.selectedDoc = {};
$scope.$watch('personInfo',function() {
$scope.selectedDoc = $scope.personInfo.docs[0];
});
});
HTML
<div ng-repeat='doc in personInfo.docs'>
<input type='radio' ng-model='selectedDoc' value='doc' /> {{doc.docType}}
</div>
<form>
<input type='text' ng-model='selectedDoc.number' />
...
</form>

rendered called before DOM completion - meteor blaze

I am creating a dynamic form using json and trying to use jquery-validation plugin to add validation rules to input fields.
Json structure and helper method is :
var fields = [{
label: "Name",
type: {name: 'STRING'},
validationRules: {
required: true,
maxlength: 100,
minlength: 3
}
},{
label: "Currency",
type: {name: 'CHECKBOX'},
defaultValue: ['USD', 'INR'],
validationRules: {
required: true
},
sourceCollection: 'oNLFfi4L3zgNLhScv',
}] ;
Template.eventCreate.helpers({
fields: function(){
console.log("calling fields");
fields.forEach(function(field, index){
field.sourceCollectionData = StaticLists.find({_id: field.sourceCollection});
});
return fields;
}
});
Template looks like:
<template name="eventCreate">
<form id="newEventForm" class="form-horizontal">
{{#each fields}}
<div class="form-group">
<label class="col-xs-2 control-label">{{label}}</label>
<div class="col-xs-6">
{{#if equals type.name 'STRING'}}
<input name="{{label}}" id="{{label}}" class="form-control" placeholder="Enter {{label}}" value="{{defaultValue}}" />
{{/if}}
{{#if equals type.name 'CHECKBOX'}}
{{#each sourceCollectionData}}
{{#if isActive}}
<div class="col-xs-2 checkbox">
<label class="checkbox-inline">
<input type="checkbox" name="{{../label}}" id="{{../label}}" value="{{name}}" {{checked ../defaultValue}}> {{name}}
</label>
</div>
{{/if}}
{{/each}}
{{/if}}
</div>
</div>
{{/each}}
</form>
</template>
Now I am trying to add validation rule in rendered method:
Template.eventCreate.rendered = function(){
$('#newEventForm').validate({
....
});
fields.forEach(function(field, index){
if(field.validationRules){
$('#'+field.label).rules('add', field.validationRules);
}
});
}
It works in case of input text but throws exception for checkbox because checkbox DOM is still not laid out and there is no element with id "Currency".
I assume in meteor blaze rendered is called once only when DOM rendering is complete. Though it is called once here but before DOM rendering completes.
EDIT
Earlier I was hardcoding the JSON in js file itself and now I am retrieving it from mongo.
But its seems that rendered callback is invoked only first time and not invoked every time mongo collection changes(which in turn updates the DOM)
Template.eventCreate.helpers({
fields: function(){
var template = EventTemplate.findOne({name: 'Job Template'});
console.log("template", template);
if(template){
Session.set("template", template);
template.fields.forEach(function(field, index){
field.sourceCollectionData = StaticLists.find({_id: field.sourceCollection});
});
return template.fields;
}
}
});
Template.eventCreate.rendered = function(){
$('#newEventForm').validate({
...
...
console.log("rendering main template");
addValidationRules(Session.get('template'));
}
Now the console output is something like:
template undefined event_create.js?bfeba9389401dac358bc313bd9d4205818e356a1:52
rendering main template event_create.js?bfeba9389401dac358bc313bd9d4205818e356a1:98
template Object {_id: "iFDndmjavtFN8AdGQ", name: "Job Template", description: "Job Template", fields: Array[13]}
which shows that (and I tried it even with a break point in js script) that just when script loads template is undefined and no input field is rendered on front end but rendered callback is invoked. Now later when template gets populated with data, input fields are rendered but callback is not invoked again.
The recommended pattern is to put the contents of your {{#each}} blocks in separate templates and tap into the rendered event of those templates.
By the way, Meteor.defer is no longer required.

Categories

Resources