Uncaught ReferenceError: elementChecked is not defined - javascript

I'm new to Javascript and I'm running into an issue when building a todo list. I'm trying to add a checkbox that will push the #id to handlers.toggleCompleted and in turn mark the item as completed.
I'm able to correctly run the if statement
if (elementChecked.childNodes[3].checked) {
console.log(elementClicked.parentNode.id);
However I get the Uncaught RefferenceError here
if (elementChecked.childNodes[3].checked) {
handlers.toggleCompleted(elementChecked.parentNode.id);
Here is the full code, sorry if this isn't a good question I'm still learning the language.
let todoList = {
todos: [],
addTodo: function(todoText) {
this.todos.push({
todoText: todoText,
completed: false
});
},
changeTodo: function(position, todoText) {
this.todos[position].todoText = todoText;
},
deleteTodo: function(position) {
this.todos.splice(position, 1);
},
toggleCompleted: function(position) {
let todo = this.todos[position];
todo.completed = elementChecked.childNodes[3].checked;
},
toggleAll: function() {
let totalTodos = this.todos.length;
let completedTodos = 0;
// Get number of completed todos.
this.todos.forEach(function(todo) {
if (todo.completed === true) {
completedTodos++;
}
});
this.todos.forEach(function(todo) {
//case 1: if everything is true make everything flase
if (completedTodos === totalTodos) {
todo.completed = false;
} else {
//case 2: if everything is false make everything true
todo.completed = true;
}
});
}
};
let handlers = {
addTodo: function() {
let addTodoTextInput = document.getElementById('addTodoTextInput');
todoList.addTodo(addTodoTextInput.value);
addTodoTextInput.value = '';
view.displayTodos();
},
changeTodo: function(position, todoText){
todoList.changeTodo(position, todoText);
view.displayTodos();
},
deleteTodo: function(position) {
todoList.deleteTodo(position);
view.displayTodos();
},
toggleCompleted: function(position) {
todoList.toggleCompleted(position);
view.displayTodos();
},
toggleAll: function() {
todoList.toggleAll();
view.displayTodos();
}
};
let view = {
displayTodos: function() {
let todosUl = document.querySelector('ul');
todosUl.innerHTML = '';
todoList.todos.forEach(function(todo, position) {
let todoLi = document.createElement('li');
let todoTextWithCompletion = '';
if (todo.completed === true) {
todoTextWithCompletion = '(x) ' + todo.todoText;
} else {
todoTextWithCompletion = '( ) ' + todo.todoText;
}
todoLi.id = position;
todoLi.textContent = todoTextWithCompletion;
todoLi.appendChild(this.createDeleteButton());
todoLi.appendChild(this.createChangeButton());
todoLi.appendChild(this.createCompleteButton());
todosUl.appendChild(todoLi);
}, this);
},
createDeleteButton: function() {
let deleteButton = document.createElement('button');
deleteButton.textContent = 'Delete';
deleteButton.className = 'deleteButton';
return deleteButton;
},
createChangeButton: function() {
let changeButton = document.createElement('button');
changeButton.textContent = 'Change';
changeButton.className = 'changeButton';
return changeButton;
},
createCompleteButton: function() {
let completeButton = document.createElement('input');
completeButton.setAttribute("type", "checkbox");
completeButton.className = 'todoComplete';
return completeButton;
},
setUpEventListeners: function() {
let todosUl = document.querySelector('ul');
todosUl.addEventListener('click', function(event) {
let elementClicked = event.target;
let elementChecked = document.getElementById(parseInt(elementClicked.parentNode.id));
if (elementClicked.className === 'deleteButton') {
handlers.deleteTodo(parseInt(elementClicked.parentNode.id));
}
if (elementClicked.className === 'changeButton') {
let selectedTodoText = parseInt(elementClicked.parentNode.id);
let changedTextValue = prompt("Please enter new text:", selectedTodoText);
handlers.changeTodo(selectedTodoText, changedTextValue);
}
if (elementChecked.childNodes[3].checked) {
handlers.toggleCompleted(elementClicked.parentNode.id);
console.log(elementClicked.parentNode.id);
} else {
elementChecked.style.background = 'transparent';
}
});
}
};
view.setUpEventListeners();

Figured it out (kind of) it was because it wasn't defined in the todoList.toggleCompleted scope.

Related

javascript quiz how to give the answer array a default value?

I'm creating a quiz that requires every answer to be answered. The problem is that you should be able to skip questions if you don't have an answer. I'm trying to set a default answer everytime I press next so when I try to skip one I don't have to answer for it to have a value. The default value I want is each time the last value of my array.
next and previous question
SetQuestion(question) {
if (this.questionNumber >= 0) {
let oldAnswerButton = document.querySelectorAll('.filter_anwser');
// Deletes old question when the next question is clicked
for (let answerButton of oldAnswerButton) {
answerButton.style.display = 'none';
}
}
this.questionNumber = question;
let q = this.quiz[question];
// Check if your at the last question so the next button will stop being displayed.
if (this.questionNumber === Quiz.length - 1) {
this.nextbtn.style.display = 'none';
this.prevbtn.style.display = 'block';
this.resultbtn.style.display = 'grid';
} else if (this.questionNumber === 0) {
this.nextbtn.style.display = 'block';
this.prevbtn.style.display = 'none';
this.resultbtn.style.display = 'none';
} else {
this.nextbtn.style.display = 'block';
this.prevbtn.style.display = 'block';
this.resultbtn.style.display = 'none';
}
// Displays Question
this.questionName.textContent = q.questionText;
this.questionName.id = "questionID";
return q;
console.log(this.getLink())
console.log(this.tmp)
}
IntoArray() {
const UrlVar = new URLSearchParams(this.getLink())
this.UrlArray = [...UrlVar.entries()].map(([key, values]) => (
{[key]: values.split(",")}
)
);
}
NextQuestion() {
// let quizUrl = this.url[this.questionNumber];
let question = this.SetQuestion(this.questionNumber + 1);
let pre = question.prefix;
let prefixEqual = pre.replace('=', '');
let UrlArr = this.UrlArray;
let UrlKeys = UrlArr.flatMap(Object.keys)
let answers = question.chosenAnswer.slice(0, -1);
this.clicked = true;
// Displays answers of the questions
for (let y = 0; y < answers.length; y++) {
let item = answers[y];
// Display answer buttons
if (UrlKeys.includes(prefixEqual)) {
console.log("exists");
let btn = document.querySelector('button[value="' + item.id + '"]');
btn.style.display = 'block';
} else {
let btn = document.createElement('button');
btn.value = item.id;
btn.classList.add("filter_anwser", pre)
btn.id = 'answerbtn';
btn.textContent = item.name;
this.button.appendChild(btn);
}
// let quizUrl = control.url[control.questionNumber];
// // console.log(this.tmp);
// if (quizUrl === undefined) {
// quizUrl.push(question.prefix[y] + '');
// }
// if (quizUrl === undefined){
// this.tmp.push('');
// }
}
this.IntoArray();
}
PrevQuestion() {
let question = this.SetQuestion(this.questionNumber - 1);
let answers = question.chosenAnswer.slice(0, -1);
// Displays answers of the questions
for (let y = 0; y < answers.length; y++) {
let item = answers[y];
// Display answer buttons
let btn = document.querySelector('button[value="' + item.id + '"]');
btn.style.display = 'block';
}
this.IntoArray();
}
Link creator:
/**
* Returns the parameters for the URL.
*
* #returns {string}
*/
getLink() {
this.tmp = [];
for (let i = 0; i < this.url.length; i++) {
// Check if question is from the same quiz part and adds a , between chosen answers and add the right prefix at the beginning
if (this.url[i].length > 0) {
this.tmp.push("" + Quiz[i].prefix + this.url[i].join(","))
// console.log(this.url)
}
if (this.url[i].length === 0) {
this.tmp.push("");
}
}
/// If answers are from different quiz parts add a & between answers.
return "" + this.tmp.join("&");
// console.log(this.url[i].prefix);
};
Answer click event
control.button.addEventListener("click", function (e) {
const tgt = e.target;
control.clicked = true;
// clear the url array if there's nothing clicked
if (control.url.length === control.questionNumber) {
control.url.push([]);
}
let quizUrl = control.url[control.questionNumber];
// Check if a button is clicked. Changes color and adds value to the url array.
if (quizUrl.indexOf(tgt.value) === -1) {
quizUrl.push(tgt.value);
e.target.style.backgroundColor = "orange";
// Check if a button is clicked again. If clicked again changes color back and deletes value in the url array.
} else {
quizUrl.splice(quizUrl.indexOf(tgt.value), 1);
e.target.style.backgroundColor = "white";
}
console.log(control.getLink());
console.log(quizUrl)
})
array:
class QuizPart {
constructor(questionText, chosenAnswer, prefix, questionDescription) {
this.questionText = questionText;
this.chosenAnswer = chosenAnswer;
this.prefix = prefix;
this.questionDescription = questionDescription;
}
}
class ChosenAnswer {
constructor(id, name) {
this.id = id;
this.name = name;
}
}
let Quiz = [
new QuizPart('Whats your size?', [
new ChosenAnswer('6595', '41'),
new ChosenAnswer('6598', '42'),
new ChosenAnswer('6601', '43'),
new ChosenAnswer('', ''),
], 'bd_shoe_size_ids=',
'The size of your shoes is very important. If you have the wrong size, they wont fit.'),
new QuizPart('What color would you like?', [
new ChosenAnswer('6053', 'Red'),
new ChosenAnswer('6044', 'Blue'),
new ChosenAnswer('6056', 'Yellow'),
new ChosenAnswer('6048', 'Green'),
new ChosenAnswer('', ''),
], 'color_ids=',
'Color isn t that important, It looks good tho.'),
new QuizPart('What brand would you like?', [
new ChosenAnswer('5805', 'Adidas'),
new ChosenAnswer('5866', 'Nike'),
new ChosenAnswer('5875', 'Puma'),
new ChosenAnswer('', ''),
], 'manufacturer_ids=',
'Brand is less important. Its just your own preference'),
]
I tried giving the array's in link creator and my eventlistener a default value and replacing it when I get and actual value from one of my buttons, but it just doesn't work. Can anybody help me?
I understand, that it might be a bit far from what you expect for an answer - but why don't you have a look at a reactive tool, like Vue? It has all the tools that you might need for such a task, and maybe more:
the whole quiz can be abstracted to a simple array of objects (the questions)
next, prev, set default answer becomes a breeze
easy to extend (with questions)
simple to update (template, features, etc.)
Vue.component('QuizQuestion', {
props: ['data', 'selected'],
computed: {
valSelected: {
get() {
return this.selected
},
set(val) {
this.$emit('update:selected', val)
}
},
},
template: `
<div>
{{ data.text }}<br />
{{ data.description }}<br />
<div class="quiz-options">
<label
v-for="val in data.options"
:key="val[0]"
>
<input
type="radio"
:name="data.text"
:value="val"
v-model="valSelected"
/>
{{ val[1] }}
</label>
</div>
</div>
`
})
new Vue({
el: "#app",
computed: {
currentQuestion() {
return this.questions[this.current]
},
hasPrev() {
return !!this.current
},
hasNext() {
return this.current < this.questions.length - 1
},
},
data() {
return {
current: 0,
questions: [{
text: 'Whats your size?',
description: 'The size of your shoes is very important. If you have the wrong size, they wont fit.',
options: [
['6595', '41'],
['6598', '42'],
['6601', '43'],
['', ''],
],
selected: null,
}, {
text: 'What color would you like?',
description: 'Color isn\'t that important, It looks good tho.',
options: [
['6053', 'Red'],
['6044', 'Blue'],
['6056', 'Yellow'],
['6048', 'Green'],
['', ''],
],
selected: null,
}, {
text: 'What brand would you like?',
description: 'Brand is less important. Its just your own preference',
options: [
['5805', 'Adidas'],
['5866', 'Nike'],
['5875', 'Puma'],
['', ''],
],
selected: null,
}, ],
}
},
methods: {
selectDefault() {
this.questions[this.current] = {
...this.questions[this.current],
selected: this.questions[this.current].options.slice(-1)[0],
}
},
getPrev() {
if (this.hasPrev) {
if (!this.currentQuestion.selected) {
this.selectDefault()
}
this.current -= 1
}
},
getNext() {
if (this.hasNext) {
if (!this.currentQuestion.selected) {
this.selectDefault()
}
this.current += 1
}
},
},
template: `
<div>
<quiz-question
:data="currentQuestion"
:selected.sync="currentQuestion.selected"
/><br />
<button v-if="hasPrev" #click="getPrev">PREV</button>
<button v-if="hasNext" #click="getNext">NEXT</button>
<button v-if="!hasNext">RESULT</button>
</div>
`
})
.quiz-options {
display: flex;
flex-direction: column;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
EDIT
But, if frameworks/libraries are not to be used, here's a more OOP approach:
class Quiz {
constructor(questions) {
this._current = 0
this._questions = questions
}
get current() {
return this._current
}
set current(val) {
this._current = val
}
get hasNext() {
return this.current < this.questions.length - 1
}
get hasPrev() {
return !!this.current
}
get questions() {
return this._questions
}
get next() {
this.current = this.hasNext ? this.current + 1 : this.current
return this.currentQuestion
}
get prev() {
this.current = this.hasPrev ? this.current - 1 : this.current
return this.currentQuestion
}
get currentQuestion() {
return this.questions[this.current]
}
}
class Question {
constructor({
text,
description,
options,
prefix,
}) {
this.text = text
this.desc = description
this.prefix = prefix
this._options = options.map(([key, val]) => ({
id: key,
value: [key, val],
selected: false,
}))
}
get options() {
return this._options
}
set options(newOptions) {
this._options = newOptions
}
get selected() {
return this.options.find(({
selected
}) => !!selected)
}
set selected(selectedVal) {
this.options = [...this.options.map(({
value: [key, val],
selected,
...rest
}) => {
return {
...rest,
value: [key, val],
selected: key === selectedVal
}
})]
}
get lastOption() {
return this.options.slice(-1)[0]
}
setDefault() {
if (!this.selected) {
this.selected = this.lastOption.id
}
}
}
const urlParser = (quiz) => {
return quiz.questions.map(({
prefix,
selected = {
value: ['']
}
}) => {
const s = selected.value[0] ? selected.value[0] : ''
return `${prefix}${s}`
}).join('&')
}
const qArr = [{
text: 'text1',
description: 'desc1',
options: [
['1_1', '11'],
['1_2', '12'],
['1_3', '13'],
],
prefix: 'prefix_1_',
},
{
text: 'text2',
description: 'desc2',
options: [
['2_1', '21'],
['2_2', '22'],
['2_3', '23'],
],
prefix: 'prefix_2_',
},
{
text: 'text3',
description: 'desc3',
options: [
['3_1', '31'],
['3_2', '32'],
['3_3', '33'],
],
prefix: 'prefix_3_',
},
]
const getOptionsHtml = ({
text,
options
}) => {
let html = ''
options.forEach(({
id,
value,
selected
}, i) => {
html += `
<label>
<input
class="question-input"
type="radio"
name="${text}"
value="${value[0]}"
${selected ? 'checked' : ''}
/>
${value[1]}
</label>
`
})
return html
}
const getSingleQuestionHtml = (q) => {
const optionsHtml = getOptionsHtml({
text: q.text,
options: q.options
})
return `
${q.text}<br />
${q.desc}<br />
${optionsHtml}
`
}
const registerEventHandlers = ({
container,
question
}) => {
const radioBtns = document.querySelectorAll('.question-input')
radioBtns.forEach((input, i) => {
input.addEventListener('change', function(e) {
question.selected = e.target.value
})
})
}
const updateHtml = ({
container,
question
}) => {
container.innerHTML = getSingleQuestionHtml(question)
registerEventHandlers({
container,
question
})
};
const updateContainer = (container) => (question) => updateHtml({
container,
question
});
const setElDisplay = ({
el,
display
}) => {
if (display) {
el.classList.add("d-inline-block")
el.classList.remove("d-none")
} else {
el.classList.remove("d-inline-block")
el.classList.add("d-none")
}
}
const updateBtnVisibility = ({
btnNext,
btnPrev,
btnResult,
quiz
}) => () => {
setElDisplay({
el: btnNext,
display: quiz.hasNext
})
setElDisplay({
el: btnResult,
display: !quiz.hasNext
})
setElDisplay({
el: btnPrev,
display: quiz.hasPrev
})
}
(function() {
const quiz = new Quiz(qArr.map(q => new Question(q)))
const container = document.getElementById('quiz-container')
updateQuizContainer = updateContainer(container)
updateQuizContainer(quiz.currentQuestion)
const btnPrev = document.getElementById('btn-prev')
const btnNext = document.getElementById('btn-next')
const btnResult = document.getElementById('btn-result')
const updateBtns = updateBtnVisibility({
btnPrev,
btnNext,
btnResult,
quiz
})
updateBtns()
btnPrev.addEventListener('click', function() {
if (quiz.hasPrev) {
quiz.currentQuestion.setDefault()
}
updateQuizContainer(quiz.prev)
updateBtns()
})
btnNext.addEventListener('click', () => {
if (quiz.hasNext) {
quiz.currentQuestion.setDefault()
}
updateQuizContainer(quiz.next)
updateBtns()
})
btnResult.addEventListener('click', function() {
quiz.currentQuestion.setDefault()
console.log(urlParser(quiz))
})
})();
.d-inline-block {
display: inline-block;
}
.d-none {
display: none;
}
<div id="quiz-container"></div>
<div id="quiz-controls">
<button id="btn-prev" class="d-inline-block">
PREV
</button>
<button id="btn-next" class="d-inline-block">
NEXT
</button>
<button id="btn-result" class="d-none">
RESULT
</button>
</div>
<div id="result"></div>

Mapbox - filters override each other

I'm creating a map based on this example:
https://labs.mapbox.com/education/impact-tools/finder-with-filters/
In their example, they have two dropdown filters and one checkbox filter. I would like to have three checkbox filters. I created three checkbox filters, and on their own, they seem to work well. The issue is that the filters seem to override each other in the order clicked. In their example, the filters seem to be working together, so I can't figure out why it's not working anymore when I changed the filter type.
Here's the code for my project:
https://codepen.io/flyinginsect2/pen/eYdyqxZ
Here are snippets of the code relevant to filtering:
const config = {
style: "mapbox://styles/mapbox/light-v10",
accessToken: "pk.eyJ1IjoibGF1cmFqZWFudGhvcm5lIiwiYSI6ImNraXl5M29oMDEyMjgzM3BhNTh1MGc1NjkifQ.g4IAFIrXPpl3ricw3f_Onw",
CSV: "https://docs.google.com/spreadsheets/d/106xm254us29hAUEtR7mTo0hwbDJv8dhyQs9rxY601Oc/gviz/tq?tqx=out:csv&sheet=Attributes",
center: [-104.339, 46.869],
zoom: 2,
title: "ENVIROlocity Mapper",
description: "Environmental Networking, Volunteering, Internship, and R.... Opportunities",
sideBarInfo: ["Org_name", "CityState"],
popupInfo: ["Org_name"],
filters: [
{
type: "checkbox",
title: "Sector: ",
columnHeader: "Sector",
listItems: ["Local Government", "Nonprofit"]
},
{
type: "checkbox",
title: "Industry: ",
columnHeader: "Industry_type",
listItems: ["Conservation", "Policy"]
},
{
type: "checkbox",
title: "Internships: ",
columnHeader: "internships_YN",
listItems: ["Yes"]
}
]
};
const selectFilters = [];
const checkboxFilters = [];
function createFilterObject(filterSettings) {
filterSettings.forEach(function (filter) {
if (filter.type === 'checkbox') {
columnHeader = filter.columnHeader;
listItems = filter.listItems;
const keyValues = {};
Object.assign(keyValues, { header: columnHeader, value: listItems });
checkboxFilters.push(keyValues);
}
if (filter.type === 'dropdown') {
columnHeader = filter.columnHeader;
listItems = filter.listItems;
const keyValues = {};
Object.assign(keyValues, { header: columnHeader, value: listItems });
selectFilters.push(keyValues);
}
});
}
function applyFilters() {
const filterForm = document.getElementById('filters');
filterForm.addEventListener('change', function () {
const filterOptionHTML = this.getElementsByClassName('filter-option');
const filterOption = [].slice.call(filterOptionHTML);
const geojSelectFilters = [];
const geojCheckboxFilters = [];
filteredFeatures = [];
filteredGeojson.features = [];
filterOption.forEach(function (filter) {
if (filter.type === 'checkbox' && filter.checked) {
checkboxFilters.forEach(function (objs) {
Object.entries(objs).forEach(function ([key, value]) {
if (value.includes(filter.value)) {
const geojFilter = [objs.header, filter.value];
geojCheckboxFilters.push(geojFilter);
}
});
});
}
if (filter.type === 'select-one' && filter.value) {
selectFilters.forEach(function (objs) {
Object.entries(objs).forEach(function ([key, value]) {
if (value.includes(filter.value)) {
const geojFilter = [objs.header, filter.value];
geojSelectFilters.push(geojFilter);
}
});
});
}
});
if (geojCheckboxFilters.length === 0 && geojSelectFilters.length === 0) {
geojsonData.features.forEach(function (feature) {
filteredGeojson.features.push(feature);
});
} else if (geojCheckboxFilters.length > 0) {
geojCheckboxFilters.forEach(function (filter) {
geojsonData.features.forEach(function (feature) {
if (feature.properties[filter[0]].includes(filter[1])) {
if (
filteredGeojson.features.filter(
(f) => f.properties.id === feature.properties.id
).length === 0
) {
filteredGeojson.features.push(feature);
}
}
});
});
if (geojSelectFilters.length > 0) {
const removeIds = [];
filteredGeojson.features.forEach(function (feature) {
let selected = true;
geojSelectFilters.forEach(function (filter) {
if (
feature.properties[filter[0]].indexOf(filter[1]) < 0 &&
selected === true
) {
selected = false;
removeIds.push(feature.properties.id);
} else if (selected === false) {
removeIds.push(feature.properties.id);
}
});
});
removeIds.forEach(function (id) {
const idx = filteredGeojson.features.findIndex(
(f) => f.properties.id === id
);
filteredGeojson.features.splice(idx, 1);
});
}
} else {
geojsonData.features.forEach(function (feature) {
let selected = true;
geojSelectFilters.forEach(function (filter) {
if (
!feature.properties[filter[0]].includes(filter[1]) &&
selected === true
) {
selected = false;
}
});
if (
selected === true &&
filteredGeojson.features.filter(
(f) => f.properties.id === feature.properties.id
).length === 0
) {
filteredGeojson.features.push(feature);
}
});
}
map.getSource('locationData').setData(filteredGeojson);
buildLocationList(filteredGeojson);
});
}
function filters(filterSettings) {
filterSettings.forEach(function (filter) {
if (filter.type === 'checkbox') {
buildCheckbox(filter.title, filter.listItems);
} else if (filter.type === 'dropdown') {
buildDropDownList(filter.title, filter.listItems);
}
});
}
function removeFilters() {
let input = document.getElementsByTagName('input');
let select = document.getElementsByTagName('select');
let selectOption = [].slice.call(select);
let checkboxOption = [].slice.call(input);
filteredGeojson.features = [];
checkboxOption.forEach(function (checkbox) {
if (checkbox.type == 'checkbox' && checkbox.checked == true) {
checkbox.checked = false;
}
});
selectOption.forEach(function (option) {
option.selectedIndex = 0;
});
map.getSource('locationData').setData(geojsonData);
buildLocationList(geojsonData);
}
function removeFiltersButton() {
const removeFilter = document.getElementById('removeFilters');
removeFilter.addEventListener('click', function () {
removeFilters();
});
}
createFilterObject(config.filters);
applyFilters();
filters(config.filters);
removeFiltersButton();
I read this Mapbox documentation on combining filters, but I can't figure out how to work it in.
https://docs.mapbox.com/mapbox-gl-js/style-spec/other/#other-filter
I know there are many other Stack Exchange posts out there that address filtering on multiple criteria, but I can't find one that seems to address this specific issue.
The issue is in the space in value for "Local Government"
If you look at the generated HTML you will see a space in the id, which is not valid HTML
<input class="px12 filter-option" type="checkbox" id="Local Government" value="Local Government">
Just remove the whitespaces when building the HTML id attribute
input.setAttribute('id', listItems[i].replace(/\s/g,''));

How can I make a Rickshaw legend with multiple series enaber/disabler?

I need to make a Rickshaw legend where I can make groups of time series.
e.g. I have 20 time series displayed in a graph and I want them to be grouped in 4 groups, named series1, series2, series3, series4. Series1 would contain the first 5 time series in the graph, series2 would contain the 6th to the 10th and so on.
Before I try to add a custom object in my .js to solve my problem someone knows if there is a built in functionality that I couldn't find?
I didn't find anything helpful so I created a personalized version of a multiple legend that works along with the traditional legend.
Unfortunately to update the multiple legend when the standard legend is modified I had to create a myLegend object instead of the default one.
You can use this version in this way:
//you must have a traditional Rickshaw seriesData
var graph = new Rickshaw.Graph( {
element: document.getElementById("chart"),
width: 960,
height: 500,
renderer: 'line',
series: seriesData
} );
graph.render();
var legend = new Rickshaw.Graph.Legend( {
graph: graph,
element: document.getElementById('legend')
} );
var mLegend = new multiLegend( {
graph: graph,
element: document.getElementById('multi_legend'),
multiDivision: [
{
name: "Africa",
indexes: [ 0, 1, 2, 3, 4, 5, 6, 7 ]
}
{
name: "Asia",
indexes: [ 8, 9, 10, 11, 12 ]
},
{
name: "Europe",
indexes: [ 13, 14, 15, 16, 17]
},
{
name: "North America",
indexes: [ 18, 19, 20 ]
}
]
} );
new myToggle( {
graph: graph,
legend: legend,
multiLegend: mLegend
} );
new multiToggle( {
graph: graph,
multiLegend: mLegend,
legend: legend
});
Here's the code:
multiLegend = Rickshaw.Class.create( {
className: 'rickshaw_legend',
initialize: function(args) {
this.element = args.element;
this.graph = args.graph;
this.naturalOrder = args.naturalOrder;
this.seriesGroups = args.multiDivision;
this.element.classList.add(this.className);
this.list = document.createElement('ul');
this.element.appendChild(this.list);
this.render();
// we could bind this.render.bind(this) here
// but triggering the re-render would lose the added
// behavior of the series toggle
this.graph.onUpdate( function() {} );
},
render: function() {
var self = this;
while ( this.list.firstChild ) {
this.list.removeChild( this.list.firstChild );
}
this.lines = [];
var allSeries = this.graph.series;
self.seriesGroups.forEach( function(s) {
var series = allSeries.filter( function(value, index) {
return (s.indexes.indexOf(index)!=-1) ? true : false;
});
series = series.reverse();
self.addLine(s.name, series);
} );
},
addLine: function (name, series) {
var line = document.createElement('li');
line.className = 'line';
if (series.disabled) {
line.className += ' disabled';
}
if (series.className) {
d3.select(line).classed(series.className, true);
}
var swatch = document.createElement('div');
swatch.className = 'swatch';
swatch.style.backgroundColor = "#0000FF";
line.appendChild(swatch);
var label = document.createElement('span');
label.className = 'label';
label.innerHTML = name;
line.appendChild(label);
this.list.appendChild(line);
line.series = series;
var _line = { element: line, series: series, disabled: false};
if (this.shelving) {
this.shelving.addAnchor(_line);
this.shelving.updateBehaviour();
}
if (this.highlighter) {
this.highlighter.addHighlightEvents(_line);
}
this.lines.push(_line);
return line;
}
} );
multiToggle = function(args) {
this.graph = args.graph;
this.multiLegend = args.multiLegend;
this.legend = args.legend;
var self = this;
this.addAnchor = function(line) {
var anchor = document.createElement('a');
anchor.innerHTML = '✔';
anchor.classList.add('action');
line.element.insertBefore(anchor, line.element.firstChild);
anchor.onclick = function(e) {
if (line.disabled) {
line.series.forEach( function(serie) {
serie.enable();
});
line.element.classList.remove('disabled');
line.disabled = false;
self.legend.lines
.filter(function(value) {
return (line.series.indexOf(value.series)!=-1) ? true : false;
})
.forEach( function(l) {
l.element.classList.remove('disabled');
l.disabled = false;
});
} else {
if (this.graph.series.filter(function(s) { return !s.disabled }).length <= 1) return;
line.series.forEach( function(serie) {
serie.disable();
});
line.element.classList.add('disabled');
line.disabled = true;
self.legend.lines
.filter(function(value) {
return (line.series.indexOf(value.series)!=-1) ? true : false;
})
.forEach( function(l) {
l.element.classList.add('disabled');
l.disabled = true;
});
}
self.graph.update();
}.bind(this);
var label = line.element.getElementsByTagName('span')[0];
label.onclick = function(e){
var disableAllOtherLines = line.disabled;
if ( ! disableAllOtherLines ) {
for ( var i = 0; i < self.multiLegend.lines.length; i++ ) {
var l = self.multiLegend.lines[i];
if ( line.series === l.series ) {
// noop
} else if ( l.series.disabled ) {
// noop
} else {
disableAllOtherLines = true;
break;
}
}
}
// show all or none
if ( disableAllOtherLines ) {
// these must happen first or else we try ( and probably fail ) to make a no line graph
line.series.forEach( function(serie) {
serie.enable();
});
line.element.classList.remove('disabled');
line.disabled = false;
self.legend.lines
.filter(function(value) {
return (line.series.indexOf(value.series)!=-1) ? true : false;
})
.forEach( function(l) {
l.element.classList.remove('disabled');
l.disabled = false;
});
self.multiLegend.lines.forEach(function(l){
if ( line.series === l.series ) {
// noop
} else {
l.series.forEach( function(serie) {
serie.disable();
});
l.element.classList.add('disabled');
l.disabled = true;
self.legend.lines
.filter(function(value) {
return (l.series.indexOf(value.series)!=-1) ? true : false;
})
.forEach( function(l2) {
l2.element.classList.add('disabled');
l2.disabled = true;
});
}
});
} else {
self.multiLegend.lines.forEach(function(l){
l.series.forEach( function(serie) {
serie.enable();
});
l.element.classList.remove('disabled');
l.disabled = false;
self.legend.lines
.filter(function(value) {
return (l.series.indexOf(value.series)!=-1) ? true : false;
})
.forEach( function(l2) {
l2.element.classList.remove('disabled');
l2.disabled = false;
});
});
}
self.graph.update();
};
};
if (this.multiLegend) {
var $ = jQuery;
if (typeof $ != 'undefined' && $(this.multiLegend.list).sortable) {
$(this.multiLegend.list).sortable( {
start: function(event, ui) {
ui.item.bind('no.onclick',
function(event) {
event.preventDefault();
}
);
},
stop: function(event, ui) {
setTimeout(function(){
ui.item.unbind('no.onclick');
}, 250);
}
});
}
this.multiLegend.lines.forEach( function(l) {
self.addAnchor(l);
} );
}
this._addBehavior = function() {
this.graph.series.forEach( function(s) {
s.disable = function() {
if (self.graph.series.length <= 1) {
throw('only one series left');
}
s.disabled = true;
};
s.enable = function() {
s.disabled = false;
};
} );
};
this._addBehavior();
this.updateBehaviour = function () { this._addBehavior() };
};
myToggle = function(args) {
this.graph = args.graph;
this.legend = args.legend;
this.multiLegend = args.multiLegend;
var self = this;
this.addAnchor = function(line) {
var anchor = document.createElement('a');
anchor.innerHTML = '✔';
anchor.classList.add('action');
line.element.insertBefore(anchor, line.element.firstChild);
anchor.onclick = function(e) {
if (line.series.disabled) {
line.series.enable();
line.element.classList.remove('disabled');
} else {
if (this.graph.series.filter(function(s) { return !s.disabled }).length <= 1) return;
line.series.disable();
line.element.classList.add('disabled');
self.multiLegend.lines.forEach( function(l) {
if(l.series.indexOf(line.series)!=-1) {
l.element.classList.add('disabled');
l.disabled = true;
}
});
}
self.graph.update();
}.bind(this);
var label = line.element.getElementsByTagName('span')[0];
label.onclick = function(e){
var disableAllOtherLines = line.series.disabled;
if ( ! disableAllOtherLines ) {
for ( var i = 0; i < self.legend.lines.length; i++ ) {
var l = self.legend.lines[i];
if ( line.series === l.series ) {
// noop
} else if ( l.series.disabled ) {
// noop
} else {
disableAllOtherLines = true;
break;
}
}
}
// show all or none
if ( disableAllOtherLines ) {
// these must happen first or else we try ( and probably fail ) to make a no line graph
line.series.enable();
line.element.classList.remove('disabled');
self.legend.lines.forEach(function(l){
if ( line.series === l.series ) {
// noop
} else {
l.series.disable();
l.element.classList.add('disabled');
}
});
self.multiLegend.lines.forEach( function(l) {
l.element.classList.add('disabled');
l.disabled = true;
});
} else {
self.legend.lines.forEach(function(l){
l.series.enable();
l.element.classList.remove('disabled');
});
}
self.graph.update();
};
};
if (this.legend) {
var $ = jQuery;
if (typeof $ != 'undefined' && $(this.legend.list).sortable) {
$(this.legend.list).sortable( {
start: function(event, ui) {
ui.item.bind('no.onclick',
function(event) {
event.preventDefault();
}
);
},
stop: function(event, ui) {
setTimeout(function(){
ui.item.unbind('no.onclick');
}, 250);
}
});
}
this.legend.lines.forEach( function(l) {
self.addAnchor(l);
} );
}
this._addBehavior = function() {
this.graph.series.forEach( function(s) {
s.disable = function() {
if (self.graph.series.length <= 1) {
throw('only one series left');
}
s.disabled = true;
};
s.enable = function() {
s.disabled = false;
};
} );
};
this._addBehavior();
this.updateBehaviour = function () { this._addBehavior() };
};

How To Run Controller Functionality Again Using Different Parameters From View ng-model

I have a controller that works fine on initial load. It calls user [0] data and everything processes fine. When I change dropdown, I want to call the same function, but I cannot get the entire controller to reload. It starts from the function call and leaves undefined in places where it pulls correct information (linkToken, etc) on initial load. Is there a way I can get it to reload all data from the controller instead of just from the function?
After calling the testchange() from the view to pass in the new data, I get :
TypeError: Cannot read property 'locations' of undefined
at h.$scope.updateLocations (refillController.js:261)
at refillController.js:73
But, when I call the original getRefills() that is ran on the initial page load I get the same error. How can I define these so the load after the onchange(0)?
angular.module('FinalApp.Controllers').controller('refillController', function ($rootScope, $scope, $location, $modal, userService, dependentsService, accountService, sharedCollections, configurationService, refillsService) {
$scope.user = userService.GetUserInformation();
$scope.userInfoArr = [];
//$scope.tests.push({'Name':$scope.user.FirstName, 'LinkToken':$scope.user.LinkToken});
$scope.userInfoArr = $scope.user.Dependants.map(function(item){return {'Name':item.FirstName, 'LinkToken':item.LinkToken}});
$scope.userInfoArr.splice(0, 0, {'Name': $scope.user.FirstName, 'LinkToken': $scope.user.LinkToken});
console.log($scope.userInfoArr);
$scope.finUserInfoArr = $scope.userInfoArr[0].LinkToken;
$scope.billingInfo = null;
$rootScope.showNavbar = true;
$scope.selectedMethod = null;
$scope.location = null;
$scope.payment = null;
$scope.refills = [];
$scope.deliverTypes = [];
$scope.locations = [];
$scope.payments = [];
$scope.allSelected = false;
$scope.loadingBillingInfo = false;
$scope.isMailOrder = false;
//Detect Mobile Switch Refill List To Grid
if(window.innerWidth <= 800) {
$scope.view = "Grid";
} else {
$scope.view = "List";
}
$scope.changeViewToList = function () {
$scope.view = "List";
};
$scope.changeViewToGrid = function () {
$scope.view = "Grid";
};
$scope.testchange = function(selectedTest) {
$scope.getRefills(selectedTest);
console.log(selectedTest);
};
$scope.getRefills = function (linkToken) {
$scope.allSelected = false;
$scope.loading = true;
refillsService.GetRefills(
linkToken,
$scope.selectedMethod,
$scope.location,
$scope.payment
).then(function (data) {
$scope.refills = [];
_.each(data.Prescriptions, function (item) {
fillRefills(item);
});
fillDeliverTypes(data.DeliveryTypes);
if (!$scope.selectedMethod)
$scope.selectedMethod = data.DeliveryTypeId;
if (!$scope.location)
$scope.location = data.PickupLocationId;
if (!$scope.payment)
$scope.payment = data.PaymentTypeId;
$scope.loading = false;
$scope.updateLocations();
})["catch"](function (error) {
console.log(error);
$scope.loading = false;
alertify.alert(configurationService.ErrorMessage("getting your refills", error.Message));
});
};
var fillRefills = function (item) {
//TODO-CallDoc temp fix
if (item.RefillClass == "CALL_DOCTOR") {
item.NextRefillDate = '1900-01-01T00:00:00'
}
var parsedDate = checkDate(moment(item.NextRefillDate).format('L'));
var lastrefill = checkDate(moment(item.LastDispenseDate).format('L'));
var expireDate = checkDate(moment(item.ExpirationDate).format('L'));
var status = (item.RefillStatus.indexOf("After") == -1) ? item.RefillStatus : "Refill " + item.RefillStatus;
$scope.refills.push({
selected: false,
rx: item.ScriptNo,
name: item.DrugName,
dose: item.UnitsPerDose,
dir: item.Instructions,
nextfill: parsedDate,
lastfill: lastrefill,
refillsLeft: item.NumRefillsLeft,
status: status,
msg: item.RefillMessage,
canSelect: item.IsRefillable,
refillClass: item.RefillClass,
lastDispenseQty: item.LastDispenseQty,
DaysSupply: item.DaysSupply,
expireDate: expireDate,
copayAmt: item.CopayAmt,
drFirstName: item.DoctorFirstName,
drLastName: item.DoctorLastName,
writtenQty: item.WrittenQty
});
};
var checkDate = function (date) {
if (date == "01/01/1900") return "N/A";
if (date == "Invalid Date") return "";
return date;
};
var fillDeliverTypes = function (deliverTypes) {
$scope.deliverTypes = [];
_.each(deliverTypes, function (item) {
$scope.deliverTypes.push({
id: item.DeliveryTypeId,
name: item.DeliveryTypeName,
locations: item.PickupLocations,
payments: item.PaymentTypes
});
});
};
var getBillingInfo = function () {
$scope.loadingBillingInfo = true;
accountService.GetCreditCardInfo().then(function (data) {
$scope.billingInfo = data;
$scope.loadingBillingInfo = false;
})["catch"](function (error) {
$scope.loadingBillingInfo = false;
alertify.alert(configurationService.ErrorMessage("getting account information", error.Message));
});
};
var getAccountInfo = function () {
accountService.GetAccountInfo().then(function (data) {
if (data.StatusCode == "SUCCESS") {
$scope.user = data;
userService.SaveUserInformation(data);
if ($scope.user.IsLinked) {
$rootScope.enableMyRefills = true;
$rootScope.enableMyReports = true;
window.location.hash = "#/refills";
} else {
$rootScope.enableMyRefills = false;
$rootScope.enableMyReports = true;
}
} else {
alertify.alert(configurationService.ErrorMessage("getting account information", data.StatusMessage));
}
})["catch"](function (error) {
alertify.alert(configurationService.ErrorMessage("getting account information", error.Message));
});
};
var openModal = function (viewUrl, controllerUrl, size, payload) {
var modalInstance = $modal.open({
templateUrl: viewUrl,
controller: controllerUrl,
size: size,
resolve: {
data: function () {
return payload;
}
}
});
return modalInstance;
};
$scope.toggleRxSelection = function(rx) {
if (rx.canSelect) {
rx.selected = !rx.selected;
$scope.evaluateAllSelected();
}
};
$scope.selectAll = function (data) {
// $scope.allSelected = allSelected;
_.each($scope.refills, function (x) {
if (x.canSelect) x.selected = data;
});
};
$scope.evaluateAllSelected = function () {
var count = _.countBy(_.where($scope.refills, {canSelect:true}), function(refill) {
return refill.selected ? 'selected' : 'not';
});
$scope.allSelected = (count.not === undefined);
};
$scope.openEditCreditCardInfo = function () {
var payload = ($scope.billingInfo != null && $scope.billingInfo != undefined)
&& $scope.billingInfo.CardNumber != "" ? $scope.billingInfo : {};
if (payload != {}) {
payload.ExpMonth = {id: parseInt(payload.ExpMonth)};
payload.ExpYear = {id: parseInt(payload.ExpYear)};
}
openModal('app/views/editAccount/billingInformation.html', "billingInformationController", "xlg", payload).result.then(function () {
getAccountInfo();
getBillingInfo();
}, function () {
getBillingInfo();
});
};
$scope.openConfirmOrder = function () {
var refillsSelected = _.where($scope.refills, {selected: true});
var location = _.findWhere($scope.locations, {PickupLocationId: $scope.location});
var payment = _.findWhere($scope.payments, {PaymentTypeId: $scope.payment});
var deliver = _.findWhere($scope.deliverTypes, {id: $scope.selectedMethod});
if (refillsSelected.length == 0) {
alertify.error("You need to select at least one refill");
return;
}
if (deliver.id == 10001 && !$scope.user.IsCreditCardOnFile) {
alertify.error("Need credit card on file for mail order");
return;
}
sharedCollections.setRefills(refillsSelected);
sharedCollections.setLocation(location);
sharedCollections.setPayment(payment);
sharedCollections.setDeliver(deliver);
openModal('app/views/refills/confirmOrder.html', "confirmOrderController", "xlg").result.then(function () {
$scope.billingInfo = accountService.GetCreditCardInfo();
$scope.getRefills();
}, function () {
//$scope.billingInfo = accountService.GetCreditCardInfo();
//getRefills();
});
};
$scope.showRefill = function (rx) {
var data = {rx: rx, refills: $scope.refills};
openModal('app/views/refills/showRefills.html', "refillsCarrousel", "xlg", data).result.then(function () {
$scope.evaluateAllSelected();
}, function () {
$scope.evaluateAllSelected();
});
};
$scope.updateLocations = function () {
$scope.locations = _.findWhere($scope.deliverTypes, {id: $scope.selectedMethod}).locations;
$scope.payments = _.findWhere($scope.deliverTypes, {id: $scope.selectedMethod}).payments;
setLocationAndPayment();
};
var setLocationAndPayment = function () {
if ($scope.locations.length == 1) {
$scope.location = $scope.locations[0].PickupLocationId;
}
if ($scope.payments.length == 1) {
$scope.payment = $scope.payments[0].PaymentTypeId;
}
//check for mail order
($scope.selectedMethod == 10001 && !$scope.payment) ? $scope.isMailOrder = true : $scope.isMailOrder = false;
};
$scope.getRefills($scope.finUserInfoArr);
getBillingInfo();
});
Check if your refillsService returns correct data, it could be that $scope.refills remains empty array.

SAPUI5 only update specific binding

In SAPUI5 I have a Model ("sModel") filled with metadata.
In this model I have a property "/aSelectedNumbers".
I also have a panel, of which I want to change the visibility depending on the content of the "/aSelectedNumbers" property.
update
first controller:
var oModelMeta = cv.model.recycleModel("oModelZAPRegistratieMeta", that);
//the cv.model.recycleModel function sets the model to the component
//if that hasn't been done so already, and returns that model.
//All of my views are added to a sap.m.App, which is returned in the
//first view of this component.
var aSelectedRegistratieType = [];
var aSelectedDagdelen = ["O", "M"];
oModelMeta.setProperty("/aSelectedRegistratieType", aSelectedRegistratieType);
oModelMeta.setProperty("/aSelectedDagdelen", aSelectedDagdelen);
First Panel (Which has checkboxes controlling the array in question):
sap.ui.jsfragment("fragments.data.ZAPRegistratie.Filters.RegistratieTypeFilter", {
createContent: function(oInitData) {
var oController = oInitData.oController;
var fnCallback = oInitData.fnCallback;
var oModel = cv.model.recycleModel("oModelZAPRegistratieMeta", oController);
var oPanel = new sap.m.Panel( {
content: new sap.m.Label( {
text: "Registratietype",
width: "120px"
})
});
function addCheckBox(sName, sId) {
var oCheckBox = new sap.m.CheckBox( {
text: sName,
selected: {
path: "oModelZAPRegistratieMeta>/aSelectedRegistratieType",
formatter: function(oFC) {
if (!oFC) { return false; }
console.log(oFC);
return oFC.indexOf(sId) !== -1;
}
},
select: function(oEvent) {
var aSelectedRegistratieType = oModel.getProperty("/aSelectedRegistratieType");
var iIndex = aSelectedRegistratieType.indexOf(sId);
if (oEvent.getParameters().selected) {
if (iIndex === -1) {
aSelectedRegistratieType.push(sId);
oModel.setProperty("/aSelectedRegistratieType", aSelectedRegistratieType);
}
} else {
if (iIndex !== -1) {
aSelectedRegistratieType.splice(iIndex, 1);
oModel.setProperty("/aSelectedRegistratieType", aSelectedRegistratieType);
}
}
// arrays update niet live aan properties
oModel.updateBindings(true); //******** <<===== SEE HERE
if (fnCallback) {
fnCallback(oController);
}
},
width: "120px",
enabled: {
path: "oModelZAPRegistratieMeta>/bChanged",
formatter: function(oFC) {
return oFC !== true;
}
}
});
oPanel.addContent(oCheckBox);
}
addCheckBox("Presentielijst (dag)", "1");
addCheckBox("Presentielijst (dagdelen)", "2");
addCheckBox("Uren (dagdelen)", "3");
addCheckBox("Tijd (dagdelen)", "4");
return oPanel;
}
});
Here is the panel of which the visibility is referred to in the question. Note that it DOES work after oModel.updateBindings(true) (see comment in code above), but otherwise it does not update accordingly.
sap.ui.jsfragment("fragments.data.ZAPRegistratie.Filters.DagdeelFilter", {
createContent: function(oInitData) {
var oController = oInitData.oController;
var fnCallback = oInitData.fnCallback;
var oModel = cv.model.recycleModel("oModelZAPRegistratieMeta", oController);
var oPanel = new sap.m.Panel( {
content: new sap.m.Label( {
text: "Dagdeel",
width: "120px"
}),
visible: {
path: "oModelZAPRegistratieMeta>/aSelectedRegistratieType",
formatter: function(oFC) {
console.log("visibility");
console.log(oFC);
if (!oFC) { return true; }
if (oFC.length === 0) { return true; }
return oFC.indexOf("2") !== -1;
}
}
});
console.log(oPanel);
function addCheckBox(sName, sId) {
var oCheckBox = new sap.m.CheckBox( {
text: sName,
selected: {
path: "oModelZAPRegistratieMeta>/aSelectedDagdelen",
formatter: function(oFC) {
if (!oFC) { return false; }
console.log(oFC);
return oFC.indexOf(sId) !== -1;
}
},
select: function(oEvent) {
var aSelectedDagdelen = oModel.getProperty("/aSelectedDagdelen");
var iIndex = aSelectedDagdelen.indexOf(sId);
if (oEvent.getParameters().selected) {
if (iIndex === -1) {
aSelectedDagdelen.push(sId);
oModel.setProperty("/aSelectedDagdelen", aSelectedDagdelen);
}
} else {
if (iIndex !== -1) {
aSelectedDagdelen.splice(iIndex, 1);
oModel.setProperty("/aSelectedDagdelen", aSelectedDagdelen);
}
}
if (fnCallback) {
fnCallback(oController);
}
},
enabled: {
path: "oModelZAPRegistratieMeta>/bChanged",
formatter: function(oFC) {
return oFC !== true;
}
},
width: "120px"
});
oPanel.addContent(oCheckBox);
}
addCheckBox("Ochtend", "O", true);
addCheckBox("Middag", "M", true);
addCheckBox("Avond", "A");
addCheckBox("Nacht", "N");
return oPanel;
}
});
The reason that the model doesn´t trigger a change event is that the reference to the Array does not change.
A possible way to change the value is to create a new Array everytime you read it from the model:
var newArray = oModel.getProperty("/aSelectedNumbers").slice();
// do your changes to the array
// ...
oModel.setProperty("/aSelectedNumbers", newArray);
This JSBin illustrates the issue.

Categories