how to add progress bar for when each quiz is complete? - javascript

I have created some quizzes. Each box as a total of 5 questions and a complete button. I want to have a progress bar which increases when each section is complete (1-5). Also, I want to have each section greyed out so you can unlock onto the next section. This is access when a using is logged in, I have using PHP and MySQL. I wondered if anyone could suggest a way of achieving this.
HTML
<div class="grid-container">
<div class="grid-item item1">1. Discover HTML Basics and Tags</div>
<div class="grid-item item2">2. Styling HTML with Tags</div>
<div>3. Creating Links and Images</div>
<div class="grid-item item4">4. Building Features</div>
<div class="grid-item item5">5. Building Lists</div>
</div>
JS
$(document).ready(function() {
const startButton = document.getElementById('start-btn')
const nextButton = document.getElementById('next-btn')
const completeButton = document.getElementById('complete-btn')
const questionContainerElement = document.getElementById('question-container')
const questionElement = document.getElementById('question')
const answerButtonsElement = document.getElementById('answer-buttons')
let shuffledQuestions, currentQuestionIndex
startButton.addEventListener('click', startGame)
nextButton.addEventListener('click', () => {
currentQuestionIndex++
setNextQuestion()
})
function startGame() {
startButton.classList.add('hide')
shuffledQuestions = questions.sort(() => Math.random() - .5)
currentQuestionIndex = 0
questionContainerElement.classList.remove('hide')
setNextQuestion()
}
function setNextQuestion() {
resetState()
showQuestion(shuffledQuestions[currentQuestionIndex])
}
function showQuestion(question) {
questionElement.innerText = question.question
question.answers.forEach(answer => {
const button = document.createElement('button')
button.innerText = answer.text
button.classList.add('btn')
if (answer.correct) {
button.dataset.correct = answer.correct
}
button.addEventListener('click', selectAnswer)
answerButtonsElement.appendChild(button)
})
}
function resetState() {
clearStatusClass(document.body)
nextButton.classList.add('hide')
while (answerButtonsElement.firstChild) {
answerButtonsElement.removeChild(answerButtonsElement.firstChild)
}
}
function selectAnswer(e) {
const selectedButton = e.target
const correct = selectedButton.dataset.correct
setStatusClass(document.body, correct)
Array.from(answerButtonsElement.children).forEach(button => {
setStatusClass(button, button.dataset.correct)
})
if (shuffledQuestions.length > currentQuestionIndex + 1) {
nextButton.classList.remove('hide')
} else {
completeButton.innerText = 'Complete'
completeButton.classList.remove('hide')
}
}
function setStatusClass(element, correct) {
clearStatusClass(element)
if (correct) {
element.classList.add('correct')
} else {
element.classList.add('wrong')
}
}
function clearStatusClass(element) {
element.classList.remove('correct')
element.classList.remove('wrong')
}
function completeprogress {
if ()
}
const questions = [
{
question: 'What does HTML stand for?',
answers: [
{ text: 'Hyperlinks and Text Markup Language', correct: true },
{ text: 'Hyper Text Markup Language', correct: false },
{ text: 'Home Tool Markup Language', correct: false }
]
},
{
question: 'Which character is used to indicate an end tag?',
answers: [
{ text: '<', correct: false },
{ text: '*', correct: false },
{ text: '/', correct: true },
{ text: ';', correct: false }
]
},
{
question: 'Who is making the Web standards?',
answers: [
{ text: 'Google', correct: false },
{ text: 'Mozilla', correct: false },
{ text: 'Microsoft', correct: false },
{ text: 'The World Wide Web Consortium', correct: true }
]
},
{
question: 'What is the correct HTML for making a text input field?',
answers: [
{ text: '<input type="textfield">', correct: false },
{ text: '<input type="text">', correct: true },
{ text: '<textfield>', correct: false },
{ text: '<textinput type="text">', correct: false }
]
},
{
question: 'Choose the correct HTML element for the largest heading:',
answers: [
{ text: '<head>', correct: false },
{ text: '<h6>', correct: false },
{ text: '<heading>', correct: false },
{ text: '<h1>', correct: true }
]
}
]
});

The following logic will work. Just got to work out if you want to do it client side or in php.
<?php
// once a quiz is complete, add a status to the session. This could also be stored client side
session_start();
if(!isset($_SESSION['NumberComplete'])){
$_SESSION['NumberComplete'] = 0;
}
// run this when the quiz is completed to increment the counter
$_SESSION['NumberComplete'] = (isset($_SESSION['NumberComplete']) ? $_SESSION['NumberComplete']++ : 1);
// read out the data on page load into a js var
echo '<script>var numberComplete='.$_SESSION['NumberComplete'].';</script>';
?>
<div id="NumberComplete" style="width:100%;height:50px">
<div id="percent" style="background-color: green;width:10%;height: 50px;"></div>
</div>
<script src="https://code.jquery.com/jquery-3.5.0.min.js"></script>
<script>
var totalQuizes = 5;
// run this either on page load, or dynamically when you have a new percentage to update
$(function(){
$("#NumberComplete #percent").css( {"width": (numberComplete / totalQuizes) * 100 + "%" } );
});
</script>

Related

How to handle the change of the previous object

I'm currently making a nav bar.
I create an array storing the info of the tabs in the nav bar.
tabs: [
{ name: 'All',id: "dash.courses.all", to: 'all', current: false },
{ name: 'Self-paced Learning',id: "dash.courses.oneToOneClasses", to: 'selfPacedLearningComp', current: false },
{ name: '30 Days Challenge',id: "dash.courses.selfPacedLearningComp", to: 'thirtyDaysChallenge', current: false },
{ name: 'Group Classes',id: "dash.courses.groupClasses", to: 'groupClasses', current: false },
{ name: '1-to-1 Classes',to: "dash.courses.thirtyDaysChallenge", to: 'oneToOneClasses', current: false },
]
When a new route is clicked it updates the newly clicked tab to allow the current property to be true.
How would you change the previous nav item to false. As currently they all change to true one by one as they are clicked.
I think if I store a value as previous
setCurrent(tab)
{
let newArr = this.tabs.map(obj => {
if (obj.id === tab) {
return {...obj, current: true};
}
return obj;
})
this.tabs = newArr
console.log(newArr)
}
},
This is what i've got atm, it has to go around 3 routes ahead till the one before vanishes...
<script>
export default {
components: {
},
data()
{
return {
prevTab: null,
tabs: [
{ name: 'All',id: "dash.courses.all", to: 'all', current: false },
{ name: 'Self-paced Learning',id: "dash.courses.oneToOneClasses", to: 'selfPacedLearningComp', current: false },
{ name: '30 Days Challenge',id: "dash.courses.selfPacedLearningComp", to: 'thirtyDaysChallenge', current: false },
{ name: 'Group Classes',id: "dash.courses.groupClasses", to: 'groupClasses', current: false },
{ name: '1-to-1 Classes',to: "dash.courses.thirtyDaysChallenge", to: 'oneToOneClasses', current: false },
]
}
},
methods: {
setCurrent(tab)
{
this.prevTab = this.$route.name
let newArr = this.tabs.map(obj => {
if (obj.id === tab) {
return {...obj, current: true};
}
if(obj.id === this.prevTab) {
return {...obj, current: false}
}
return obj;
})
console.log('previous ',this.prevTab)
console.log('route name', this.$route.name)
this.tabs = newArr
}
},
mounted()
{
this.prevTab = this.$route.name
const newArr = this.tabs.map(obj => {
if (obj.id === this.$route.name) {
return {...obj, current: true};
}
return obj;
});
this.tabs = newArr
}
}
Create a watcher on the route name
watch: {
"$route.name": {
handler(routeName) {
this.tabs.forEach((tab) => (tab.current = routeName === tab.name));
},
// force eager callback execution
immediate: true,
},
}
Usually you can just use the watcher routeName value above to run whatever side effect you want but if tracking current on each tab is really necessary the above code will get the job done.

Setting button class back to initial after pressing next button

const btn_start = document.getElementById("start");
let container = document.getElementById("container");
let questionTag = document.getElementById("question");
let answerTag = document.getElementsByClassName("answer");
const nxt_question_btn = document.getElementById("next");
const end_quiz_btn = document.getElementById("end");
btn_start.addEventListener("click", startQuiz);
nxt_question_btn.addEventListener("click", nextQuestion);
let currentQuestionIndex = 0;
let myQuestions = [
{
question: "What's 2+2?",
answers: [
{ text: "4", correct: true },
{ text: "2", correct: false },
{ text: "10", correct: false },
{ text: "1", correct: false },
],
},
{
question: "What's 10+10?",
answers: [
{ text: "20", correct: true },
{ text: "2", correct: false },
{ text: "18", correct: false },
{ text: "0", correct: false },
],
},
{
question: "What's 30+30?",
answers: [
{ text: "60", correct: true },
{ text: "24", correct: false },
{ text: "100", correct: false },
{ text: "50", correct: false },
],
},
{
question: "What's 10+30?",
answers: [
{ text: "40", correct: true },
{ text: "44", correct: false },
{ text: "70", correct: false },
{ text: "10", correct: false },
],
},
];
function startQuiz() {
container.style.visibility = "visible";
btn_start.style.visibility = "hidden";
end.style.visibility = "hidden";
showQuestion(myQuestions[0]);
}
function showQuestion(questionAndAnswers) {
const shuffledAnswers = _.shuffle(questionAndAnswers.answers);
questionTag.innerText = questionAndAnswers.question;
shuffledAnswers.forEach(({ text, correct }, i) => {
answerTag[i].innerText = text;
answerTag[i].dataset.correct = correct;
});
}
document.querySelectorAll(".answer").forEach((answer) => {
answer.addEventListener("click", (event) => {
if (event.target.dataset) {
answer.style.border = "1px solid black";
}
});
});
function nextQuestion() {
const nextIndex = currentQuestionIndex + 1;
if (nextIndex <= myQuestions.length - 1) {
showQuestion(myQuestions[nextIndex]);
currentQuestionIndex = nextIndex;
} else {
end.style.visibility = "visible";
nxt_question_btn.style.visibility = "hidden";
}
}
<button id="start" type="button">Start quiz</button>
<div id="container">
<h2>Quiz</h2>
<div class="time">
<span>Time left:</span>
<p id="time"> 30</p>
</div>
<h3 id="question"></h3>
<div class="answers">
<button id="answer1" class="answer"></button>
<button id="answer2" class="answer"></button>
<button id="answer3" class="answer"></button>
<button id="answer4" class="answer"></button>
</div>
<button id="next" class="btns">Next Question</button>
<button id="end" class="btns">End Quiz</button>
</div>
In my Javascript quiz game, when an answer is selected, we load up another question with other answers but the problem that occurs is the selected answers get a black border but when the next question is loaded up, the border stays. How do I make it go back to normal when the next question is loaded?
You could use border: initial to revert is back to default.
The initial CSS keyword applies the initial (or default) value of a property to an element. It can be applied to any CSS property. This includes the CSS shorthand all, with which initial can be used to restore all CSS properties to their initial state.
In your code, you could use:
let buttons = document.querySelectorAll('.answer')
buttons.forEach(button => {
button.addEventListener('click', function() {
this.style.border = "1px solid black";
buttons.forEach(each => {
if (each != button) each.style.border = "initial"
})
})
})
<button id="answer1" class="answer"></button>
<button id="answer2" class="answer"></button>
<button id="answer3" class="answer"></button>
<button id="answer4" class="answer"></button>
Or if the style of border you doesn't satisfy with, you could define a new class and use toggle to add / remove the classList.
*Also, note that your code is currently displaying reference error
In your "next question" function, set the button's style properties to none. Then put a setTimeout function where you set the same element's style property to '' (empty string). This should reset it.
const btn_start = document.getElementById("start");
let container = document.getElementById("container");
let questionTag = document.getElementById("question");
let answerTag = document.getElementsByClassName("answer");
const nxt_question_btn = document.getElementById("next");
const end_quiz_btn = document.getElementById("end");
btn_start.addEventListener("click", startQuiz);
nxt_question_btn.addEventListener("click", nextQuestion);
let currentQuestionIndex = 0;
let myQuestions = [
{
question: "What's 2+2?",
answers: [
{ text: "4", correct: true },
{ text: "2", correct: false },
{ text: "10", correct: false },
{ text: "1", correct: false },
],
},
{
question: "What's 10+10?",
answers: [
{ text: "20", correct: true },
{ text: "2", correct: false },
{ text: "18", correct: false },
{ text: "0", correct: false },
],
},
{
question: "What's 30+30?",
answers: [
{ text: "60", correct: true },
{ text: "24", correct: false },
{ text: "100", correct: false },
{ text: "50", correct: false },
],
},
{
question: "What's 10+30?",
answers: [
{ text: "40", correct: true },
{ text: "44", correct: false },
{ text: "70", correct: false },
{ text: "10", correct: false },
],
},
];
function startQuiz() {
container.style.visibility = "visible";
btn_start.style.visibility = "hidden";
end.style.visibility = "hidden";
showQuestion(myQuestions[0]);
}
function showQuestion(questionAndAnswers) {
const shuffledAnswers = _.shuffle(questionAndAnswers.answers);
questionTag.innerText = questionAndAnswers.question;
shuffledAnswers.forEach(({ text, correct }, i) => {
answerTag[i].innerText = text;
answerTag[i].dataset.correct = correct;
});
}
document.querySelectorAll(".answer").forEach((answer) => {
answer.addEventListener("click", (event) => {
if (event.target.dataset) {
answer.style.border = "1px solid black";
}
});
});
function nextQuestion() {
document.querySelectorAll('.answer').forEach((answer) => {
answer.style.border = 'none';
setTimeout(function() {
answer.style.border = ''
})
})
const nextIndex = currentQuestionIndex + 1;
if (nextIndex <= myQuestions.length - 1) {
showQuestion(myQuestions[nextIndex]);
currentQuestionIndex = nextIndex;
} else {
end.style.visibility = "visible";
nxt_question_btn.style.visibility = "hidden";
}
}
<button id="start" type="button">Start quiz</button>
<div id="container">
<h2>Quiz</h2>
<div class="time">
<span>Time left:</span>
<p id="time"> 30</p>
</div>
<h3 id="question"></h3>
<div class="answers">
<button id="answer1" class="answer"></button>
<button id="answer2" class="answer"></button>
<button id="answer3" class="answer"></button>
<button id="answer4" class="answer"></button>
</div>
<button id="next" class="btns">Next Question</button>
<button id="end" class="btns">End Quiz</button>
</div>

$scope variable not loading the values

$scope.loadSeletedTemplate = function () {
$scope.itemEditor = document.getElementsByTagName("item-editor")[0];
angular.forEach($scope.questionTypesList, function (item) {
if ($scope.questionTypeText === item.Description) {
$scope.selectedItemTemplate = {
IsNew: $scope.questionJson ? false : true,
ItemId: '',
ItemJson: $scope.questionJson,
ParentId: '',
TemplateId: item.TemplateID,
ItemEntityTypeID: 4,
ItemParentTypeID: 3,
UserID: ''
};
}
});
if ($scope.itemEditor.loadComponent) {
$scope.itemEditor.loadComponent($scope.selectedItemTemplate, true);
}
else {
setTimeout(() => {
$scope.loadSeletedTemplate();
}, 1000);
}
}
The $scope.selectedItemTemplate is not Loaded with the values. But when I declared in my code.
It's not loading the values. So This is not giving a proper response.
Can anyone help me with this?

How to update particular object in array of multiple objects Dynamically

How to update a particular object in array of objects. For example,
have an object like this
tabs: [{
name: "Step 1",
DroppedDetails: [
{id:1,name:Step1,
DroppedDetails:[
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-039e3a8f03f0"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}
]
},
{
name: "Step 2",
DroppedDetails: [
{DraggedItemRandomId: "70d19a39-7e6e-4eb2-b974-03e3a82f03f0"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}]
}
],
and my new value should update is
{
DraggedItemRandomId: "70d19a9f-739e-4eb2-b974-03e3a8f032d1",
draggedItem:{
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Hey Sagar" // updated value
readOnly: true //updated value
required: true //updated value
width: 200}
}
}
How can i Update this object in state (0th or 1st object dynamically)
object like
how can i do setState for inner loop of array of objects dynamically???
i have tried so many examples but no result ..please help me guyz
final output:
tabs: [{
name: "Step 1",
DroppedDetails: [
{id:1,name:Step1,
DroppedDetails:[
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Hey Sagar" // updated value
readOnly: true //updated value
required: true //updated value
width: 200
}
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}
]
},
{
name: "Step 2",
DroppedDetails: [
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}]
}
],
You can get, first of all, the tabs of the state:
const { tabs } = this.state;
// here you code to decide what tab do you want to update
const tabSelected = tabs[0];
const { DroppedDetails } = tabSelected;
DroppedDetails[0]= {
name: "sagar111"
};
// You can edit another one or add a new one to the array also.
DroppedDetails[1]= {
name: "NameEdited"
};
DroppedDetails.push({ name: "New Name" })
And set state a new state:
this.setState(
{ tabs: tabs.map(t => t === tabSelected ? { ...tabSelected, DroppedDetails }) : t });
But it could be in this way too:
this.setState(tabs);
Because the original references were updated.
At the end DroppedDetails and tabs[0].DroppedDetails[0] are the same object.
how can i do setState for inner loop of array of objects???
It's not recommended the use of setState in a forloop.
Check this Calling setState in a loop only updates state 1 time
You can easily do that (this changes the name to lower case):
const { tabs } = this.state;
tabs.map(tab => {
// Do something with your tab value like the following
const newDroppedDetails = tab.map(({ name }) => ({ name: name.toLowerCase() });
return {
...tab,
DroppedDetails: newDroppedDetails
}
});
The key point here is to not mutate objects or arrays, but to create a new reference to objects that you modify, so the shallow compare on PureComponents will always work properly.
Applying the same concepts from your original codesandbox, we can do something like this to edit each individual Dropdown Detail.
working sandbox https://codesandbox.io/s/tab-creator-v2-8hc7c
import React from "react";
import ReactDOM from "react-dom";
import uuid from "uuid";
import "./styles.css";
class App extends React.Component {
state = {
tabs: [
{
id: 1,
name: "Tab 1",
content: "Wow this is tab 1",
DroppedDetails: [
{ name: "Bhanu", editing: false },
{ name: "Sagar", editing: false }
]
},
{
id: 2,
name: "Tab 2",
content: "Look at me, it's Tab 2",
DroppedDetails: [
{ name: "Christopher", editing: false },
{ name: "Ngo", editing: false }
]
}
],
currentTab: {
id: 1,
name: "Tab 1",
content: "Wow this is tab 1",
DroppedDetails: [
{ name: "Bhanu", editing: false },
{ name: "Sagar", editing: false }
]
},
editMode: false,
editTabNameMode: false
};
handleDoubleClick = () => {
this.setState({
editTabNameMode: true
});
};
handleEditTabName = e => {
const { currentTab, tabs } = this.state;
const updatedTabs = tabs.map(tab => {
if (tab.id === currentTab.id) {
return {
...tab,
name: e.target.value
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs,
currentTab: {
...currentTab,
name: e.target.value
}
});
};
handleOnBlur = () => {
this.setState({
editTabNameMode: false
});
};
handleDetailChange = (e, id, index) => {
const { tabs, currentTab } = this.state;
const updatedCurrentTab = { ...currentTab };
updatedCurrentTab.DroppedDetails = updatedCurrentTab.DroppedDetails.map(
(detail, detailIndex) => {
if (index == detailIndex) {
return {
...detail,
name: e.target.value
};
} else {
return detail;
}
}
);
const updatedTabs = tabs.map(tab => {
if (tab.id == id) {
return {
...tab,
DroppedDetails: tab.DroppedDetails.map((detail, detailIndex) => {
if (detailIndex == index) {
return {
...detail,
name: e.target.value
};
} else {
return detail;
}
})
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs,
currentTab: updatedCurrentTab
});
};
createTabs = () => {
const { tabs, currentTab, editTabNameMode } = this.state;
const allTabs = tabs.map(tab => {
return (
<li>
{editTabNameMode && currentTab.id === tab.id ? (
<input
value={tab.name}
onBlur={this.handleOnBlur}
onChange={this.handleEditTabName}
/>
) : (
<button
className={currentTab.id === tab.id ? "tab active" : "tab"}
onClick={() => this.handleSelectTab(tab)}
onDoubleClick={() => this.handleDoubleClick(tab)}
>
{tab.name}
</button>
)}
</li>
);
});
return <ul className="nav nav-tabs">{allTabs}</ul>;
};
handleSelectTab = tab => {
this.setState({
currentTab: tab,
editMode: false,
editTabNameMode: false
});
};
handleAddTab = () => {
const { tabs } = this.state;
const newTabObject = {
id: uuid(),
name: `Tab ${tabs.length + 1}`,
content: `This is Tab ${tabs.length + 1}`,
DroppedDetails: []
};
this.setState({
tabs: [...tabs, newTabObject],
currentTab: newTabObject,
editMode: false,
editTabNameMode: false
});
};
handleDeleteTab = tabToDelete => {
const { tabs } = this.state;
const tabToDeleteIndex = tabs.findIndex(tab => tab.id === tabToDelete.id);
const updatedTabs = tabs.filter((tab, index) => {
return index !== tabToDeleteIndex;
});
const previousTab =
tabs[tabToDeleteIndex - 1] || tabs[tabToDeleteIndex + 1] || {};
this.setState({
tabs: updatedTabs,
editMode: false,
editTabNameMode: false,
currentTab: previousTab
});
};
setEditMode = () => {
this.setState({
editMode: !this.state.editMode
});
};
handleContentChange = e => {
const { tabs, currentTab } = this.state;
const updatedTabs = tabs.map(tab => {
if (tab.name === currentTab.name) {
return {
...tab,
content: e.target.value
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs,
currentTab: {
...currentTab,
content: e.target.value
}
});
};
handleOnDetailBlur = (id, index) => {
const { tabs, currentTab } = this.state;
const updatedCurrentTab = { ...currentTab };
updatedCurrentTab.DroppedDetails = updatedCurrentTab.DroppedDetails.map(
(detail, detailIndex) => {
if (index == detailIndex) {
return {
...detail,
editing: false
};
} else {
return detail;
}
}
);
const updatedTabs = tabs.map(tab => {
if (tab.id == id) {
return {
...tab,
DroppedDetails: tab.DroppedDetails.map((detail, detailIndex) => {
if (detailIndex == index) {
return {
...detail,
editing: false
};
} else {
return detail;
}
})
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs || [],
currentTab: updatedCurrentTab
});
};
handleDoubleClickDetail = (id, index) => {
const { tabs, currentTab } = this.state;
const updatedCurrentTab = { ...currentTab };
updatedCurrentTab.DroppedDetails = updatedCurrentTab.DroppedDetails.map(
(detail, detailIndex) => {
if (index == detailIndex) {
return {
...detail,
editing: true
};
} else {
return detail;
}
}
);
const updatedTabs = tabs.map(tab => {
if (tab.id == id) {
return {
...tab,
DroppedDetails: tab.DroppedDetails.map((detail, detailIndex) => {
if (detailIndex == index) {
return {
...detail,
editing: true
};
} else {
return detail;
}
})
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs || [],
currentTab: updatedCurrentTab
});
};
createContent = () => {
const { currentTab } = this.state;
return (
<div>
<div>
<p>{currentTab.content}</p>
<div>
<h4>Dropped Details</h4>
{currentTab.DroppedDetails ? (
<div>
{currentTab.DroppedDetails.map((detail, index) => {
if (detail.editing) {
return (
<div>
<input
value={detail.name}
onChange={e =>
this.handleDetailChange(e, currentTab.id, index)
}
onBlur={() =>
this.handleOnDetailBlur(currentTab.id, index)
}
/>
</div>
);
} else {
return (
<p
onDoubleClick={() =>
this.handleDoubleClickDetail(currentTab.id, index)
}
>
{detail.name}
</p>
);
}
})}
</div>
) : (
""
)}
</div>
</div>
{currentTab.id ? (
<div style={{ display: "flex", justifyContent: "space-between" }}>
<button className="edit-mode-button" onClick={this.setEditMode}>
Edit
</button>
<button onClick={() => this.handleDeleteTab(currentTab)}>
Delete
</button>
</div>
) : (
""
)}
</div>
);
};
render() {
const { currentTab, editMode } = this.state;
return (
<div className="container">
<div className="well">
<button className="add-tab-button" onClick={this.handleAddTab}>
<i className="text-primary fas fa-plus-square" /> Add Tab
</button>
{this.createTabs()}
<div className="tab-content">
{editMode ? (
<div>
<textarea
onChange={this.handleContentChange}
value={currentTab.content}
/>
<button className="save-button" onClick={this.setEditMode}>
Done
</button>
</div>
) : (
this.createContent()
)}
</div>
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
To activate "edit-mode" double-click a dropped-detail. The input should appear in its place and you can type in the new text. When complete, click out of the input and it will finalize the updated text :)

How to extract paths to 'enabled' objects in nested object arrays

I'm a novice to recursion and I have a JSON structure with arrays of nested objects. Some of these objects have a boolean enabled: true. I'm trying to figure out how to extract the paths to all enabled objects and their children.
I tried both cleaning up the original object by removing unused paths but I got lost in accessing the parents. I also tried building a separate array of paths using dot-notation, as I can probably build a new nested object from that. My latest attempt at the dot-notation extract:
const sourceData = {
title: "Work",
tags: [
{
title: "Cleaning",
tags: [
{
title: "Floors"
},
{ title: "Windows", enabled: true },
{ title: "Ceilings", enabled: true }
]
},
{
title: "Maintenance",
tags: [
{
title: "Walls",
enabled: true,
tags: [
{
title: "Brickwall"
},
{
title: "Wooden wall"
}
]
},
{
title: "Roof"
}
]
},
{
title: "Gardening"
}
]
};
function getEnabledPaths(level, acc) {
for (const tag of level.tags) {
if (tag.enabled) {
return tag.title;
} else if (tag.hasOwnProperty("tags")) {
var path = this.getEnabledPaths(tag);
if (path) acc.push(tag.title + "." + path);
}
}
return acc;
}
console.log(getEnabledPaths(sourceData, []));
I only get:
[
"Cleaning.Windows",
"Maintenance.Walls"
]
I would ideally end up with something like this:
[
'Work.Cleaning.Windows',
'Work.Cleaning.Ceilings',
'Work.Maintenance.Walls.Brickwall',
'Work.Maintenance.Walls.Wooden Wall'
]
In a perfect world (but I tried for days and went back to getting the dot notation results):
{
title: "Work",
tags: [
{
title: "Cleaning",
tags: [
{
title: "Windows",
enabled: true
},
{
title: "Ceilings",
enabled: true
}
]
},
{
title: "Maintenance",
tags: [
{
title: "Walls",
enabled: true,
tags: [
{
title: "Brickwall"
},
{
title: "Wooden wall"
}
]
}
]
}
]
};
The key to the recursion function is to both a) deal with children and b) the item itself.
Here's my take, which seems to work:
const sourceData = {title:"Work",tags:[{title:"Cleaning",tags:[{title:"Floors"},{title:"Windows",enabled:true},{title:"Ceilings",enabled:true}]},{title:"Maintenance",tags:[{title:"Walls",enabled:true,tags:[{title:"Brickwall"},{title:"Woodenwall"}]},{title:"Roof"}]},{title:"Gardening"}]};
function itemFilter(item) {
// enabled? done with this item
if (item.enabled) return item;
// not enabled and no tags? set to null
if (!item.tags) return null;
// filter all children, remove null children
item.tags = item.tags.map(child => itemFilter(child)).filter(child => child);
return item;
}
console.log(itemFilter(sourceData));
.as-console-wrapper {
max-height: 100vh !important;
}
You could pass enabled parameter down to lower levels of recursion if true value is found on some of the upper levels and based on that add path to the results or not.
const data ={"title":"Work","tags":[{"title":"Cleaning","tags":[{"title":"Floors"},{"title":"Windows","enabled":true},{"title":"Ceilings","enabled":true}]},{"title":"Maintenance","tags":[{"title":"Walls","enabled":true,"tags":[{"title":"Brickwall"},{"title":"Wooden wall"}]},{"title":"Roof"}]},{"title":"Gardening"}]}
function paths(data, prev = '', enabled = false) {
const result = [];
prev += (prev ? "." : '') + data.title;
if (!enabled && data.enabled) enabled = true;
if (!data.tags) {
if (enabled) {
result.push(prev);
}
} else {
data.tags.forEach(el => result.push(...paths(el, prev, enabled)))
}
return result;
}
const result = paths(data)
console.log(result)

Categories

Resources