Display User info from Meteor.users, and display with Highcharts - javascript

I'm new in Meteor, that's what I'd like to do: I've some users that will answer to different kind of questions: feels and answers are the answers that I'll store in the mongoDB for each user.
Now this is my HTML:
<p>Select a user to see infos, see answers' progress</p>
{{#each allUsers}}
<div class="patinf">
<br>
<h5><i class="fa fa-user"></i> {{lastName}} {{firstName}}</h5>
<label>
<input type="radio" class="radio-inline" name="{{this.id}}" id="show" value="show" required>
<span><i class="fa fa-bar-chart" aria-hidden="true"></i> Show answers</span>
</label>
<label>
<input type="radio" class="radio-inline" name="{{this.id}}" id="hide" value="hide" checked>
<span><i class="fa fa-times" aria-hidden="true"></i> Hide</span
</label>
</div>
{{#if show}}
<div class="form-group">
<label for="something" class="control-label">Answers' Progress</label>
<div id="something">
<p>Feelings:</p>
<ul>
{{#each allFeelings}}
<li>{{feel}}</li>
{{/each}}
</ul>
<p>Answers:</p>
<ul>
{{#each allAnswers}}
<li>{{answer}}</li>
{{/each}}
</ul>
<br>
</div>
<div id="something" style=" min-width: 310px; height: 400px; margin: 0auto">
{{> highchartsHelper chartId="feels" chartWidth="100%" charHeight="100%" chartObject=topGenresChart}}
<br>
{{> highchartsHelper chartId="answers" chartWidth="100%" charHeight="100%" chartObject=topGenresChart}}
</div>
</div>
{{/if}}
{{/each}}
And this is my js file:
Meteor.subscribe('patientInfos');
Template.patients.helpers({
allAnswers:function(){
return Quests.find({"answer": {$ne:null}});
},
answer: function(){
return this.answer;
}
});
Template.patients.helpers({
allFeelings:function(){
return Quests.find({"feel": {$ne:null}});
},
feel: function(){
return this.feel;
}
});
Template.patients.helpers({
allUsers: function() {
return Meteor.users.find({});
},
id: function(){
ret
},
firstName: function() {
return this.profile.firstName;
},
lastName: function() {
return this.profile.lastName;
}
});
Template.patients.onRendered(function () {
Session.set('show', false);
});
Template.patients.events({
'change #hide': function (event) {
Session.set('show', false);
},
'change #show': function (event) {
Session.set('show', true);
}
});
Template.patients.helpers({
show: function() {
return Session.get('show');
},
});
Template.patients.topGenresChart = function() {
return {
chart: {
type: 'areaspline'
},
title: {
text: 'Answers Progress'
},
legend: {
layout: 'vertical',
align: 'left',
verticalAlign: 'top',
x: 150,
y: 100,
floating: true,
borderWidth: 1,
backgroundColor: (Highcharts.theme && Highcharts.theme.legendBackgroundColor) || '#FFFFFF'
},
xAxis: {
categories: [
'W1',
'W2',
'W3',
'W4',
'W5',
'W6',
'W7'
],
plotBands: [{ // last week
from: 5.5,
to: 7,
color: 'rgba(68, 170, 213, .2)'
}]
},
yAxis: {
title: {
text: 'Answers'
},
categories: [
'0',
'1',
'2',
'3',
'4',
'5',
'6'
],
},
tooltip: {
shared: true,
valueSuffix: ' points'
},
credits: {
enabled: false
},
plotOptions: {
areaspline: {
fillOpacity: 0.5
}
},
series: [{
name: 'Question 1 Progress',
data: [3, 4, 3, 5, 1, 5, 6]
}, {
name: 'Question 2 Progress',
data: [1, 3, 2, 3, 2, 5, 4]
}]}
};
Now the problem is how to put in series.data the data taken from Quests.answer, considering that answer is an array of 10 number.
How to use the #each function to display the answer's data for one user at time: actually if I select show I'll see all the user's answers under every user.
Quests is like:
Schemas.Quests = new SimpleSchema
createdAt:
type: Date
autoValue: ->
if this.isInsert
new Date()
answer:
type: [Number]
optional:true
feel:
type: Number
optional:true
userId:
type: String
patient:
type: String
optional:true
feel could be a number: 0 1 2.
answer is an array of 10 item, numbers from 0 to 6.
EDITED:
Example of MongoDB elements:
- Meteor.users
createdAt: Tue Dec 05 2017 10:56:24 GMT+0100
__proto__: Object
emails : Array(1)
0 : {address: "denise#test.it", verified: false}
length : 1
__proto__ : Array(0)
profile :
firstName : "Denise"
lastName : "Blabla"
__proto__ : Object
services :
password: {bcrypt: "$2a$10$ddJ8F.k2uJ2lZaDfvMNEdObxdMXwAdxSSQRYtHRG6Juoh8HVtC8Ju"}
resume : {loginTokens: Array(0)}
__proto__ : Object
_id: "RTS2LR2jaBjidEiB7"
__proto__:Object
length : 4
__proto__: Array(0)
And this is an example of the Quests:
0 :
answer : (10) ["1", "5", "6", "5", "1", "5", "5", "6", "0", "2"]
patient : "Denise Blabla"
userId : "RTS2LR2jaBjidEiB7"
_id : "7NwjGmGyz7zjbzBqC"
__proto__ : Object
1 :
feel : "0"
patient : "Denise Blabla"
userId : "RTS2LR2jaBjidEiB7"
_id : "5KtDQof3o9gt8CYJg"
__proto__ : Object
2 :
answer : (10) ["0", "4", "4", "0", "4", "5", "0", "1", "3", "6"]
patient : "Denise Blabla"
userId : "RTS2LR2jaBjidEiB7"
_id : "7t46pAihMBNWwmYpN"
__proto__ : Object
What I want is to display something like this in a table:
Denise Blabla
Week || Q1 || Q2 || ... || Q10 || Feel
Week 1 || 6 || 4 || ... || 1 || 0
Week 2 || 3 || 1 || ... || 5 || 2
Anne Smith
Week || Q1 || Q2 || ... || Q10 || Feel
Week 1 || 5 || 5 || ... || 1 || 1
Week 2 || 3 || 2 || ... || 3 || 0

First part of the question, showing the data from the related collection Quests in your template:
Template.patients.helpers({
allAnswers() {
return Quests.find({ userId: this._id, answer: { $exists: true }}).map((el) => el.answer);
},
allFeelings() {
return Quests.find({ userId: this._id, feel: { $exists: true }}).map((el) => el.feel);
}
});
This is basically doing an inline join on the related collection using userId as the matching key.
Your highchart will be done in a similar fashion:
Template.patients.topGenresChart = function() {
return {
... chart parameters as you had them before
series: [{
name: 'Question 1 Progress',
data: () => { Quests.find({ userId: this._id, answer: { $exists: true }}).map((el) => el.answer)}
}, {
name: 'Question 2 Progress',
data: () => { Quests.find({ userId: this._id, feel: { $exists: true }}).map((el) => el.feel)}
}]}
};
However:
In your question you say that answer is an array of 10 numbers but your schema has defines it as a String. Did you mean [String]
You say that feel is a number 0 1 2 but your template is iterating over it with {{#each}}.
It's not at all clear what you're trying to chart in your two series. Your example data has arrays of 7 elements each but what data from Quests is supposed to go in there?
You're referring to the same chart data: chartObject=topGenresChart twice from {{> highchartsHelper ...}} - that means you're going to display the exact same chart twice even though you're giving each a different ID. What is your intent there?
In any case the answer should give you enough to get going.
Note also that your feel and answer helpers are redundant. You could use {{feel}} and {{answer}} directly in your template without going through a helper since those helpers just return this.feel and this.answer respectively.

Related

Svelte on:change runs too late

I have a Svelte app with:
A dropdown that lets you choose a chart to view (pie chart / bar chart / calendar)
A panel of checkboxes with variables to include in the chart. (Different charts have different variables available)
A function that filters my data just for the selected variables, then passes that data to a chart.
Full code that you can run here:
<script>
let rawData = {
LevelTracker: [{ text: "headache" }, { text: "pain" }],
EventType: [{ text: "coffee" }, { text: "aspirin" }],
Event: [
{ time: 1500000000, text: "coffee" },
{ time: 1500030000, text: "aspirin" },
{ time: 1500230000, text: "coffee" },
// etc....
],
LevelChange: [
{ time: 1500000000, text: "headache", level: 2 },
{ time: 1500030000, text: "headache", level: 3 },
{ time: 1500230000, text: "pain", level: 2 },
// etc....
],
};
$: availableLTs = rawData.LevelTracker.map((e) => e.text);
$: availableETs = rawData.EventType.map((e) => e.text);
let schemas = [
{
name: "byTimeOfDay",
vars: [{ name: "X", et: true, lt: true }],
},
{
name: "lagBarChart",
vars: [
{ name: "X", min: 1, et: true, lt: false },
{ name: "Y", min: 1, max: 1, lt: true, et: true },
],
},
{
name: "calendar",
vars: [{ name: "X", et: true, lt: true }],
},
];
let chartsMap = {};
for (let schema of schemas) {
chartsMap[schema.name] = schema;
}
//let selectedChart = "lagBarChart";
//let selectedChart = "byTimeOfDay";
let selectedChart = "calendar";
function getInitSelectedVars(schemaVars) {
let selection = {};
for (let varSchema of schemaVars) {
selection[varSchema.name] = { ets: [], lts: [] };
}
return selection;
}
function initSelectedVars() {
console.log("in initSelectedVars");
selectedVars = getInitSelectedVars(schemaVars);
}
function makeChartData({ selectedVars, rawData }) {
console.log("in makeChartData");
for (let [key, value] of Object.entries(selectedVars)) {
// TODO: we filter rawData for just the selected vars, and return that data...
}
}
// this will be passed to the chart component
$: chartData = makeChartData({
selectedVars,
rawData,
});
$: schemaVars = chartsMap[selectedChart].vars;
$: selectedVars = selectedVars || getInitSelectedVars(schemaVars);
</script>
<main>
<h2>Select chart type</h2>
<select bind:value={selectedChart} on:change={initSelectedVars}>
{#each schemas as chart}
<option value={chart.name}>
{chart.name}
</option>
{/each}
</select>
<h2>Select your vars</h2>
{#each schemaVars as schemaVar}
<h3>
{schemaVar.name}
</h3>
{#if schemaVar.lt}
{#each availableLTs as ele}
<div class="form-check">
<label>
<input
type="checkbox"
class="form-check-input"
bind:group={selectedVars[schemaVar.name].lts}
value={ele}
/>
{ele}
</label>
</div>
{/each}
{/if}
{#if schemaVar.et}
{#each availableETs as ele}
<div class="form-check">
<label>
<input
type="checkbox"
class="form-check-input"
bind:group={selectedVars[schemaVar.name].ets}
value={ele}
/>
{ele}
</label>
</div>
{/each}
{/if}
{/each}
<!-- then we display the selected chart, like:
<calendar {chartData} />
-->
</main>
<style>
</style>
Each time the user changes the dropdown, we need to re-initialize selectedVars to a value that matches the current chart's schema.
For example, if calendar is selected we need to do:
selectedVars = {X: {ets: [], lts: []}}
But if barchart is selected, we need:
selectedVars = {X: {ets: [], lts: []}, Y: {ets: [], lts: []}}
I defined a function that does this, and put on:change={initSelectedVars} in the chart dropdown. However, each time I change the chart type from 'calendar' to 'bar chart', I still get an error in my makeChartData:
Uncaught (in promise) TypeError: Cannot read property 'lts' of undefined
at Object.mount [as m] (Debug.svelte:101)
at Object.mount [as m] (Debug.svelte:95)
at Object.mount [as m] (Debug.svelte:109)
at Object.update [as p] (Debug.svelte:90)
at update (index.mjs:764)
at flush (index.mjs:732)
I think that on:change function would only get run after selectedVars is changed, so it's too late.
Any suggestions? My code is below.
Are you sure, the error occurs in the makeChartData function?
It might be this line: bind:group={selectedVars[schemaVar.name].lts}
I have not fully understand your code yet, but it should be possible to change the value of selectedVars to the desired value with reactive statements. So, I don't think you need a on:change handler.
Edit:
I think you want something like that: https://svelte.dev/repl/57d760278e0e4acfad536c1269bceba3?version=3.37.0

Vue Survey not indexing questions

I have to build a quiz/survey app in vue.js, I'm pretty new to vue and still trying to learn it. I have a quiz/survey that asks different questions depending on what the user answers in the initial question.
so if the user picks yes it will display question 2 if the user picks no it will display question 3 etc.
I'm not sure what the best way of going around it but so far I have this.
Is there anyway i can use the value of my answer as the questionIndex after a person clicks next?
JS file:
"use strict";
let quiz = {
title: "Asbestos Quiz",
questions: [
{
text: 'Do you need help with an Asbestos Survey?',
answers: [
{
text: "Yes",
value: '2'`enter code here`
},
{
text: "No",
value: '3'
},
]
},
{
text: 'Was your property constructed pre 2000',
answers: [
{
text: "Yes",
value: '4'
},
{
text: "No",
value: '5'
},
]
},
{
text: 'Do you need an Asbestos Management plan?',
answers: [
{
text: "Yes",
value: '6'
},
{
text: "No",
value: '7'
},
]
}
]
};
var app = new Vue({
el: "#app",
data: {
quiz: quiz,
questionIndex: 0,
responses: [],
errors: [],
error: ''
},
methods: {
prev: function() {
this.questionIndex--;
},
next: function() {
if (this.responses[this.questionIndex] === undefined) {
this.errors[this.questionIndex] = 1;
this.error = 'Please select your answer';
}
else {
this.errors[this.questionIndex] = 0;
this.questionIndex++;
}
},
score: function() {
},
playAgain: function() {
this.questionIndex = 0;
}
}
});
HTML:
<html lang="en">
<head>
<title>Vue quiz/survey</title>
<meta name="viewport" content="width=device-width"/>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- <link rel="stylesheet" href="index.css"> -->
</head>
<body>
<div id="app">
<div class="container">
<div class="jumbotron mt-3">
<h1 class="mb-5">{{ quiz.title }}</h1>
<hr>
<p v-if="errors[questionIndex]" class="alert alert-danger">
{{ error }}
</p>
<div v-for="(question, index) in quiz.questions">
<div v-show="index === questionIndex">
<h4 class="mt-5 mb-3">{{ question.text }}</h4>
<div v-for="answer in question.answers" class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio"
:value="answer.value"
:name="index"
v-model="responses[index]">
{{answer.text}}
</label>
</div>
<div class="mt-5">
<button
class="btn btn-primary"
v-if="questionIndex > 0"
#click="prev">
prev
</button>
<button class="btn btn-secondary" #click="next">
next
</button>
</div>
</div>
</div>
<div v-show="questionIndex === quiz.questions.length">
<h3>Your Results</h3>
<p>
You are: {{ score() }}
</p>
<button class="btn btn-success" #click="playAgain">
Play Again!
</button>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
I thought that this sounded like a potentially interesting exercise, so I spent some time creating an implementation in a Vue CLI sandbox app that I built and use for trying out various ideas.
I learned a few things, and hopefully you will get something out of it. I left the 'Previous' functionality as a TODO if you decide you like it and want implement that yourself.
QuizQuestions.vue
<template>
<div class="quiz-questions">
<div class="jumbotron mt-3">
<h1 class="mb-5">{{ quiz.title }}</h1>
<hr>
<question v-if="!showResults" :question="currentQuestion" #answer-selected="processAnswer" />
<div class="mt-5">
<button class="btn btn-primary" v-if="currentQuestionId > 1 && !showResults" #click="getPreviousQuestion">
prev
</button>
<button v-if="!showResults" class="btn btn-secondary" #click="getNextQuestion">
{{ nextButtonLabel }}
</button>
</div>
<div v-if="showResults">
<h3>Your Results</h3>
<table class="table table-bordered">
<thead>
<tr>
<th>QUESTION</th>
<th>ANSWER</th>
</tr>
</thead>
<tbody>
<tr v-for="(response, index) in responses" :key="index">
<td>{{ getQuestionText(response.questionId) }}</td>
<td>{{ getAnswerText(response.answerId) }}</td>
</tr>
</tbody>
</table>
<button class="btn btn-success" #click="playAgain">
Play Again!
</button>
</div>
</div>
</div>
</template>
<script>
import quiz from './quiz.js';
import Question from '#/components/stackoverflow/Question'
export default {
components: {
Question
},
data() {
return {
quiz: quiz,
currentQuestionId: 1,
currentAnswerId: 1,
previousQuestionId: 0,
responses: [],
showResults: false,
errors: [],
error: ''
}
},
computed: {
currentQuestion() {
return this.quiz.questions.find( question => {
return question.id === this.currentQuestionId;
})
},
nextQuestionId() {
let retVal = 0;
if (this.currentAnswerId > 0) {
let tempAnswer = this.currentQuestion.answers.find( answer => {
return answer.id === this.currentAnswerId;
});
retVal = tempAnswer.nextQuestionId;
}
return retVal;
},
lastQuestion() {
return this.currentQuestion.answers[0].nextQuestionId === 0;
},
nextButtonLabel() {
return this.lastQuestion ? 'Finish' : 'Next';
}
},
methods: {
getPreviousQuestion() {
this.currentQuestionId = this.previousQuestionId;
},
getNextQuestion() {
// TODO: Look for existing response for this question in case the 'Previous' button was pressed
// If found, update answer
// Store current question id and answer id in responses
let response = { questionId: this.currentQuestionId, answerId: this.currentAnswerId };
this.responses.push(response);
if (this.lastQuestion) {
this.showResults = true;
return;
}
this.previousQuestionId = this.currentQuestionId;
this.currentQuestionId = this.nextQuestionId;
//console.log(this.responses);
},
getQuestionText(id) {
let result = this.quiz.questions.find( question => {
return question.id === id;
});
return result.text;
},
getAnswerText(id) {
// NOTE: Since answers are currently limited to '1 = Yes' and '2 = No',
// this method does not need to involve any look up
return id === 1 ? 'Yes' : 'No';
},
processAnswer(selectedAnswerId) {
this.currentAnswerId = selectedAnswerId;
},
score() {
return 'TODO'
},
playAgain() {
this.currentQuestionId = 1;
this.showResults = false;
this.responses = [];
}
}
}
</script>
Question.vue
<template>
<div class="question">
<h4 class="mt-5 mb-3">{{ question.text }}</h4>
<div class="form-check" v-for="(answer, idx) in question.answers" :key="idx">
<input class="form-check-input" type="radio"
:value="answer.id" v-model="answerId" #change="answerSelected">
<label class="form-check-label">
{{answer.text}}
</label>
</div>
</div>
</template>
<script>
export default {
props: {
question: {
type: Object,
required: true
}
},
data() {
return {
answerId: 1
}
},
watch:{
question() {
// Reset on new question
this.answerId = 1;
}
},
methods: {
answerSelected() {
this.$emit('answer-selected', this.answerId);
}
}
}
</script>
I also modified your test data by adding various ID properties to help with tracking, as well as created a few more placeholder questions.
quiz.js
const quiz = {
title: "Asbestos Quiz",
questions: [
{
id: 1,
text: 'Do you need help with an Asbestos Survey?',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 2
},
{
id: 2,
text: "No",
nextQuestionId: 3
},
]
},
{
id: 2,
text: 'Was your property constructed pre 2000',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 4
},
{
id: 2,
text: "No",
nextQuestionId: 5
},
]
},
{
id: 3,
text: 'Do you need an Asbestos Management plan?',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 6
},
{
id: 2,
text: "No",
nextQuestionId: 7
},
]
},
{
id: 4,
text: 'Question 4',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
},
{
id: 5,
text: 'Question 5',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
},
{
id: 6,
text: 'Question 6',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
},
{
id: 7,
text: 'Question 7',
answers: [
{
id: 1,
text: "Yes",
nextQuestionId: 0
},
{
id: 2,
text: "No",
nextQuestionId: 0
},
]
}
]
};
export default quiz;

Display nested files in children of element ui tree using vue

I am using element ui tree for my vue application. I am implementing 'File browser' type system for my application. In here, files are nested into children.While clicking on child node those nested files or docs will be displaying right side in different container. I am not able to iterate through children and display those files.
**Here is the mocked data :**
data:[{
id: 1,
name: ‘Project A’,
type: ‘folder’,
children: [{
id: 4,
name: 'Project A-1’,
type: ‘folder’,
files: [
{
id: 9,
pid: 4,
name: ‘file 3-A’,
type:’file’,
description: ‘wifi’,
country: ‘USA'
},
{
id: 10,
pid: 4,
name: ‘file 3-B’,
type:’file’,
description: ‘VPN’,
country: ‘USA'
}
]
}
]
},
{
id: 2,
name: 'Services’,
type: 'folder',
children:[],
files: [
{
id: 5,
name: ‘Services-1-A’,
type:’file’,
pid: 2,
description: ‘VPN’,
country: ‘AUS'
},
{
id: 6,
name: ‘Services-1-B’,
type:’file’,
pid: 2,
description: ‘WIFI’,
country: ‘AUS'
}
]
},
{
id: 3,
name: 'Servers',
type: 'folder’,
children:[],
files: [
{
id: 7,
name: ‘Servers-1-A’,
type: ‘file’,
pid: 3,
description: ‘VPN’,
country: ‘CAD'
},
{
id: 8,
name: ‘Servers-1-B',
type: ‘file’,
pid: 3,
description: ‘WIFI’,
country: ‘CAD'
}
]
}]
Here is my UI code
<el-row>
<el-col :span="8" style="background: #f2f2f2">
<div class="folder-content">
<el-tree
node-key="id"
:data="data"
accordion
#node-click="nodeclicked"
ref="tree"
style="background: #f2f2f2"
highlight-current
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span class="icon-folder">
<i class="el-icon-folder" aria-hidden="true"></i>
<span class="icon-folder_text" #click="showFiles(data.id)">{{ data.name }}</span>
</span>
</span>
</el-tree>
</div>
</el-col>
<el-col :span="16"><div class="entry-content">
<ul>
<li aria-expanded="false" v-for="(file,index) in files" :key="index">
<div class="folder__list"><input type="checkbox" :id= "file" :value="file" v-model="checkedFiles" #click="check">
<i class="el-icon-document" aria-hidden="true"></i>
<span class="folder__name">{{file}}</span></div>
</li>
</ul>
</div></el-col>
</el-row>
Show files method:
showFiles(id) {
let f = this.data.filter(dataObject => {
if (dataObject.children && dataObject.children.id === id) {
return false
} else if (!dataObject.children && dataObject.id === id) {
return false
}
return true
})[0]
this.files = f.files
}
}
I am trying to do like this:
I noticed a bug in your filter function. Check line 3 :
showFiles(id) {
let f = this.data.filter(dataObject => {
//isn't this suppose to return true?
if (dataObject.children && dataObject.children.id === id) {
return false
} else if (!dataObject.children && dataObject.id === id) {
return false
}
return true
})[0]
this.files = f.files
}
Why using filter() method to search for single element? It will scan through all the elements. You could just find() instead to improve performance and better readable code.
Try this:
showFiles(id) {
let f = this.data.find(dataObject => dataObject.id == id);
//ensure node was returned
if(f ){
this.files = f.files
}
}
However, You could try and do this in your component instead.
Add another property to the component's data object. Use the new property to hold the selected node.
data(){
//your mock data
tree:[],
//children files being displayed
files:[]
},
methods:{
showFiles(branch){
this.files = branch.files;
}
}
Then pass the whole object to the method
<span class="icon-folder_text" #click="showFiles(data)">{{ data.name }}</span>

With AngularJS, how can I leverage ng-show against an array to find certain values?

I have a model, Program, that looks something like this
var ProgramSchema = new Schema({
permissions: [{
user: {
type: Schema.ObjectId,
ref: 'User'
},
roles: {
type: [{
type: String,
enum: ['reader', 'editor', 'admin', 'requested']
}]
}
}],
type: {
type: String,
enum: ['public', 'private'],
default: 'public',
required: 'Type cannot be blank'
}
});
As display a list of programs on a page, I want to show an icon if the currently authenticated user $scope.authentication.user is in the program.permissions.user and with a role of reader, editor, or admin.
I was thinking of an ng-show but since programs.permissions is an array, I couldn't make it work.
Any help would be great! thanks!
sample program data
{
"_id" : ObjectId("55ab4acd24640cd55097c356"),
"permissions" : [
{
"user" : ObjectId("55a897dfad783baa677e1326"),
"roles" : [
"reader"
]
},
{
"user" : ObjectId("5563f65a84426d913ae8334e"),
"roles" : [
"editor"
]
}
]
}
Thru some help, here is what I ended up doing
I called a function in my ng-show
ng-show="userFollowedProgram(program)"
In my controller
$scope.userFollowedProgram = function(program) {
//loop thru permissions and see if user is in there.
for (var i = 0; i < program.permissions.length; i++) {
if (program.permissions[i].user === $scope.authentication.user._id) {
//loop thru roles and see if user is following.
if (program.permissions[i].roles.indexOf('admin') > -1 ||
program.permissions[i].roles.indexOf('editor') > -1 ||
program.permissions[i].roles.indexOf('reader') > -1) {
return true
}
}
}
return false;
};
While not be the prettiest, you can explicitly check for your values with the || logical operator on your ng-show using indexOf(). I've mocked a simple example, using a <span> as an "icon" - but you can certainly work the idea into your working copy from here. Observe the following...
<li ng-repeat="user in users">
<span>{{ user.name }}</span>
<span class="ico"
ng-show="user.roles.indexOf('reader') > -1 || user.roles.indexOf('editor') > -1 || user.roles.indexOf('admin') >-1">icon
</span>
</li>
$scope.users = [{
id: 1, name: 'bob', roles: ['reader', 'editor', 'admin']
},{
id: 2, name: 'jane', roles: ['reader']
},{
id: 3, name: 'chris', roles: ['editor']
},{
id: 4, name: 'susy', roles: ['requested'] // sorry susy
}];
JSFiddle Link - simple demo
Another interesting way you can accomplish could include testing for their role via regex and return a truthy value. Could be more overhead than it's worth, but check it out if you wish...
<span class="ico" ng-show="isInRole(user)" >icon</span>
$scope.isInRole = function(user) {
return /(reader|editor|admin)/.test(user.roles.join('|'));
}
JSFiddle Link - regex demo
I was able to figure it out.
I called a function in my ng-show
ng-show="userFollowedProgram(program)"
In my controller
$scope.userFollowedProgram = function(program) {
for (var i = 0; i < program.permissions.length; i++) {
if (program.permissions[i].user === $scope.authentication.user._id) {
return true;
}
}
return false;
};

Stacked graph with KendoUI

I'm trying to display data in a stacked graph using kendo ui. Here is my code:
var data = [
// June
{ Start: "2014-06-01T00:00:00", Name : "Series 1", Value: 1 },
{ Start: "2014-06-01T00:00:00", Name : "Series 2", Value: 2 },
{ Start: "2014-06-01T00:00:00", Name : "Series 3", Value: 10 },
// July
{ Start: "2014-07-01T00:00:00", Name : "Series 1", Value: 2 },
{ Start: "2014-07-01T00:00:00", Name : "Series 2", Value: 2 },
{ Start: "2014-07-01T00:00:00", Name : "Series 3", Value: 2 },
// August
{ Start: "2014-08-01T00:00:00", Name : "Series 1", Value: 3 },
{ Start: "2014-08-01T00:00:00", Name : "Series 2", Value: 2 },
{ Start: "2014-08-01T00:00:00", Name : "Series 3", Value: 1 },
// September
{ Start: "2014-09-01T00:00:00", Name : "Series 2", Value: 2 },
{ Start: "2014-09-01T00:00:00", Name : "Series 3", Value: 3 },
// October
{ Start: "2014-10-01T00:00:00", Name : "Series 1", Value: 1 },
{ Start: "2014-10-01T00:00:00", Name : "Series 3", Value: 3 }
]
var stocksDataSource = new kendo.data.DataSource({
data: data,
group: {
field: "Name"
},
sort: [{ field: "Start", dir: "asc"} ]
});
function createChart() {
$("#chart").kendoChart({
dataSource: stocksDataSource,
series: [{
type: "column",
field: "Value",
name: "#= group.value #",
stack: true,
tooltip: {
template: "#=kendo.toString(new Date(category), 'd MMM yyyy')#<br/>" +
"#=dataItem.Name#<br/>"+
"Value: #=dataItem.Value#",
visible: true
},
}],
categoryAxis: {
field: "Start",
type: "date",
labels: {
format: "MMM"
}
}
});
}
$(document).ready(createChart);
$(document).bind("kendo:skinChange", createChart);
Note that September and October data do not have values for some series. This completely screws up the chart display in quite unexplainable way:
As you can see both September and October data do not match the json. It's especially weird with October data because three values are displayed whereas only 2 are given.
Here is JSFiddle: http://jsfiddle.net/12ob7qmx/6/
Are there any settings on the chart that I can set so it works, or will I have to loop through the dataset and fill in missing data with zero values?
The way I solved this issue is by looping through my data and adding missing values with 0's.
I don't think there is a better way, than what you suggested yourself. :(
I found a question concerning this issue on the Telerik forums:
The behavior you have observed is expected as the categorical charts
(bar, area etc.) require a matching set of data points (the value can
be null but it should persist in the data). I am afraid there is no
built-in functionality which will automatically set 0 for the missing
values - you should modify your data.
I am afraid the implementation of this functionality is not in our immediate plans, however we may consider it for future versions of the product.
I haven't found more recent information, but as far as I know this hasn't been fixed.
It' looks like I'm out of luck and I need to fix up the data. In my actual solution I'm doing that server-side, but for posterity if anyone needs a kickstart with a purely js fix, this is the starting point:
function fixData(data) {
var lookup =[], start = [], name = [], result =[];
for (i = 0, len = data.length; i < len; ++i) {
start[data[i].Start] = true;
name[data[i].Name] = true;
lookup[data[i].Start + "," + data[i].Name] = data[i].Value;
}
for (var currentStart in start) {
for (var currentName in name) {
var entry = {Start: currentStart, Name: currentName};
entry.Value = lookup[currentStart + "," + currentName] || 0;
result.push(entry);
}
}
return result;
}
JSFiddle: http://jsfiddle.net/12ob7qmx/8/

Categories

Resources