Related
I am trying to add tasks for each todo list that has a specific title.
Can I get a specific todo list by its id and add some tasks to it?
I am new to javascript, so I searched google about adding lists for a specific list with no results :(
class Model {
constructor() {}
this.todos = [
{
id: 1,
title: 'Outside',
text: 'Running',
complete: false,
tasks: [
{ id: 1, text: 'Run a marathon', complete: false},
{ id: 2, text: 'Run with freinds', complete: false}
]
},
{
id: 2,
title: 'Garden',
text: 'Plant',
complete: false,
tasks: [
{ id: 1, text: 'Plant a garden', complete: false},
{ id: 2, text: 'Water the garden', complete: false}
]
}];
addTodo(todoText) {
const todo = {
id: this.todos.length > 0 ? this.todos[this.todos.length - 1].id + 1 : 1,
text: todoText,
complete: false,
tasks: []
}
this.todos.push(todo)
}
}
Is it true to do like addTodo function for adding a tasks for a specific todo list like this?
addTodoTask(todoTaskText) {
const todoTask = {
id: this.todos.tasks.length > 0 ? this.todos[this.todos.tasks.length - 1].id + 1 : 1,
text: todoText,
complete: false,
}
this.todos.tasks.push(todoTask)
}
and how to add a list of a list in javascript like:
<ul>
<li>Running
<ul>
<li>Run a marathon</li>
<li>Run with freind</li>
</ul>
</li>
</ul>
You could make each class handle rendering its own content and just map the list items consecutively while rendering from the top-down.
Edit: The render() methods make use of ES6 template literals. These are special strings that allow you embed variabes and expressions without the use of string concatenation.
const main = () => {
let todoList = new TodoList({ todos : getData() })
document.body.innerHTML = todoList.render()
}
class TodoTask {
constructor(options) {
this.id = options.id
this.text = options.text
this.complete = options.complete
}
render() {
return `<li>[${this.id}] ${this.text} (${this.complete})</li>`
}
}
class TodoEntry {
constructor(options) {
this.id = options.id
this.title = options.title
this.text = options.text
this.complete = options.complete
this.tasks = []
if (options.tasks) {
options.tasks.forEach(task => this.addTask(task))
}
}
addTask(task) {
this.tasks.push(new TodoTask(Object.assign({
id : (this.tasks.length || 0) + 1
}, task)))
}
render() {
return `<li>
[${this.id}] ${this.title} (${this.complete})
<ul>${this.tasks.map(task => task.render()).join('')}</ul>
</li>`
}
}
class TodoList {
constructor(options) {
this.todos = []
if (options.todos) {
options.todos.forEach(todo => this.addTodo(todo))
}
}
addTodo(todo) {
this.todos.push(new TodoEntry(Object.assign({
id : (this.todos.length || 0) + 1
}, todo)))
}
render() {
return `<ul>${this.todos.map(todo => todo.render()).join('')}</ul>`
}
}
function getData() {
return [{
id: 1,
title: 'Outside',
text: 'Running',
complete: false,
tasks: [{
id: 1,
text: 'Run a marathon',
complete: false
}, {
id: 2,
text: 'Run with freinds',
complete: false
}]
}, {
id: 2,
title: 'Garden',
text: 'Plant',
complete: false,
tasks: [{
id: 1,
text: 'Plant a garden',
complete: false
}, {
id: 2,
text: 'Water the garden',
complete: false
}]
}]
}
main() // entry
To add a task your todo, you should have a way of knowing which todo list you're updating. Like using the todo's id.
For example your addTaskToTodo will looks like so.
addTask(todoId, taskObject) {
// find that todos index
const todoIndex = this.todos.findIndex(todo => todo.id ===todoId);
// using that index update the tasks
this.todos[todoIndex].tasks.push(taskObject)
}
This assumes your taskObject already has all the properties. If you need to manually update its id, you can also do that before pushing by checking the length of the tasks and incrementing by 1.
I made an example of how to use dictionaries instead of arrays, and also a random ID. I think you will find it much cleaner and simpler:
class Model {
constructor() { }
todos = {
1: {
id: 1,
title: 'Outside',
text: 'Running',
complete: false,
tasks: {
1: { id: 1, text: 'Run a marathon', complete: false },
2: { id: 2, text: 'Run with freinds', complete: false }
}
},
2: {
id: 2,
title: 'Garden',
text: 'Plant',
complete: false,
tasks: {
1: { id: 1, text: 'Plant a garden', complete: false },
2: { id: 2, text: 'Water the garden', complete: false }
}
}
}
getRandomId = () => {
return '_' + Math.random().toString(36).substr(2, 9);
}
addTodo(todoText) {
const id = this.getRandomId();
const todo = {
id,
text: todoText,
complete: false,
tasks:{}
}
this.todos[id] = todo;
}
addTodoTask(todoTaskText,todoId) {//Pass also the id of the todo, to know where this task belongs to.
const id = this.getRandomId();
const todoTask = {
id,
text: todoTaskText,
complete: false,
}
this.todos[todoId].tasks[id] = todoTask
}
}
This way you could easily edit/remove both todos and tasks, just by their id, without using any messy Array.filter and such
I was following a course and when i reached the challenge section the teacher asked to write down a code that sorts this objects array which contains some 'todos' with the properties: 'text, and completed (boolean)'
and the sorting should work like this: the uncompleted todos (false) should be first and the the completed one should be the last.
i wrote down a code and it actually worked and sorted the array as requested but when i saw how the instructor solved this challenge I was a bit confused because he used a different way so i wanted to ask the internet about which one (my code or the instructor's) is the best and more accurate.
I'm actually confused because my code worked however I gave sort() function only one argument, or parameter (a) and it sorted the array as i wanted, so please i need an explanation
This is the basic objects-array:
// The basic objects array:
const todos = [{
text: 'wake up',
completed: true
}, {
text: 'get some food',
completed: false
}, {
text: 'play csgo',
completed: false
}, {
text: 'play minecraft',
completed: true
}, {
text: 'learn javascript',
completed: false
}];
This is the code i wrote:
// My code
let sortTodos = function(todos) {
todos.sort(function(a) {
if (a.completed === false) {
return -1;
}
else if (a.completed === true){
return 1;
}
else {
return 0;
}
})
}
sortTodos(todos);
console.log(todos);
the instructor's code
// The instructor's code
const sortTodos = function (todos) {
todos.sort(function (a, b) {
if (!a.completed && b.completed) {
return -1
} else if (!b.completed && a.completed) {
return 1
} else {
return 0
}
})
}
the output is :
[ { text: 'get some food', completed: false },
{ text: 'play csgo', completed: false },
{ text: 'learn javascript', completed: false },
{ text: 'play minecraft', completed: true },
{ text: 'wake up', completed: true } ]
Although sort generally should use both arguments, your code happens to work because, as your logic shows, you only need to check the value of one of the items being compared, because you're only sorting the array into two segments, where the completed ones come first. If you know whether just one object being compared is completed or not, that's sufficient to know whether whether it should come before or after the other object being compared, no matter what the other object is.
That said, while using such an algorithm is possible, it's weird and confusing - better to explicitly sort by the difference in the completed property in both objects being compared:
const todos = [{
text: 'wake up',
completed: true
}, {
text: 'get some food',
completed: false
}, {
text: 'play csgo',
completed: false
}, {
text: 'play minecraft',
completed: true
}, {
text: 'learn javascript',
completed: false
}];
todos.sort((a, b) => a.completed - b.completed);
console.log(todos);
If you want less computational complexity, you can do this in O(n) time rather than O(n log n) time pretty easily, just separate the array into two parts and put it back together:
const todos = [{
text: 'wake up',
completed: true
}, {
text: 'get some food',
completed: false
}, {
text: 'play csgo',
completed: false
}, {
text: 'play minecraft',
completed: true
}, {
text: 'learn javascript',
completed: false
}];
const trues = [];
const falses = [];
todos.forEach((obj) => {
(obj.completed ? trues : falses).push(obj);
});
console.log([...falses, ...trues]);
I am having a bit of trouble implementing the sideMenu to the following code: (see the startTabs).
I call this after "login" is clicked on my root screen. The root screen looks like the following:
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
name: "navigation.playground.WelcomeScreen",
passProps: {
text: "stack with one child"
},
alignment: "center",
options: {
topBar: {
visible: true,
title: {
text: "main screen"
}
}
}
}
}]
}
}
});
const startTabs = () => {
Promise.all([
Icon.getImageSource("md-map", 30),
Icon.getImageSource("ios-share-alt", 30)
]).then(sources => {
Navigation.setRoot({
root: {
bottomTabs: {
children: [{
stack: {
children: [{
component: {
name: "navigation.playground.FindPlaceScreen",
options: {
bottomTab: {
text: "Find Place",
icon: sources[0]
},
topBar: {
visible: true,
title: {
text: "Find Place"
}
}
}
}
}
]
}
},
{
stack: {
children: [{
component: {
name: "navigation.playground.SharePlaceScreen",
options: {
bottomTab: {
text: "Share Place",
icon: sources[1]
},
topBar: {
// visible: true,
title: {
text: "Share Place"
}
}
}
}
}]
}
}
]
}
}
});
});
};
Now in order for me to implement sideMenu after login, Would I implement it in the "startTabs"? or elsewhere?
Solved this. Sorry I am a new programmer, so I had a spelling mistake in my sideDrawer component where "render" was spelled "redner". Took me the longest time to figure this out!!!
Otherwise the code I pasted in initial question is correct (for anyone who looks at this for reference). Thanks!
I have an array of objects that have deeply nested children and sometimes children within children. I am attempting to handle this recursively, but I am getting stuck.
The goal of the function is to return a single data object that matches the id.
My Data looks like this:
data: [
{
id: 'RAKUFNUBNY00UBZ40950',
name: 'Grade 1 Cover',
activityId: 'RAKUFNUBNY00UBZ40950',
nodeType: 'activity',
suppressed: false,
hidden: false
},
{
children: [
{
id: 'SLWDYEQHTZAFA3ALH195',
name: 'Build Background Video',
activityId: 'SLWDYEQHTZAFA3ALH195',
nodeType: 'activity',
suppressed: false,
hidden: false,
assetReference: {
referenceId: 'UWFHA5A1E0EGKCM0W899',
assetType: 'image'
}
},
{
children: [
{
id: 'HQUCD2SSRKMYC2PJM636',
name: 'Eat or Be Eaten Splash Card',
activityId: 'HQUCD2SSRKMYC2PJM636',
nodeType: 'activity',
suppressed: false,
hidden: true
},
{
children: [
{
id: 'ZDTWEZFL13L8516VY480',
name: 'Interactive Work Text: Eat or Be Eaten',
activityId: 'ZDTWEZFL13L8516VY480',
nodeType: 'activity',
suppressed: false,
hidden: true,
defaultLaunchMode: 'modal'
}
],
My attempt at solving this is like this:
findNode(id, currentNode) {
console.log('id', id);
console.log('findNode', currentNode);
var i, currentChild, result, counter;
counter = 0;
console.log('first conditional statement', currentNode);
if (id && currentNode.id === id) {
return currentNode[0];
} else {
counter++;
// Use a for loop instead of forEach to avoid nested functions
// Otherwise "return" will not work properly
console.log('counter', counter);
console.log('currentNode', currentNode[counter]);
console.log('currentNode Children', currentNode.children);
for (i = counter; i < currentNode.children.length; i += 1) {
console.log(currentNode[i].children[i]);
currentChild = currentNode[i].children[i];
// Search in the current child
result = this.findNode(id, currentChild);
// Return the result if the node has been found
if (result !== false) {
return result;
}
}
// The node has not been found and we have no more options
return false;
}
}
The code above fails because I having an extremely difficult time keeping track of a counter to loop through everything.
I also added a sample picture of my data output to give you a better example of how my data is structured. Any help will be greatly appreciated.
You shouldn't need a counter to locate a single node with a matching id. Try this simpler approach:
function findNode (id, array) {
for (const node of array) {
if (node.id === id) return node;
if (node.children) {
const child = findNode(id, node.children);
if (child) return child;
}
}
}
It will return undefined if there is no match.
To avoid the need for manual iteration, you might consider using an array method like reduce instead - return the accumulator if it's truthy (that is, an object was found already), or return the object being iterated over if the ID matches, or recursively iterate over the object's children to find a match.
const data=[{id:'RAKUFNUBNY00UBZ40950',name:'Grade 1 Cover',activityId:'RAKUFNUBNY00UBZ40950',nodeType:'activity',suppressed:!1,hidden:!1},{children:[{id:'SLWDYEQHTZAFA3ALH195',name:'Build Background Video',activityId:'SLWDYEQHTZAFA3ALH195',nodeType:'activity',suppressed:!1,hidden:!1,assetReference:{referenceId:'UWFHA5A1E0EGKCM0W899',assetType:'image'}},{children:[{id:'HQUCD2SSRKMYC2PJM636',name:'Eat or Be Eaten Splash Card',activityId:'HQUCD2SSRKMYC2PJM636',nodeType:'activity',suppressed:!1,hidden:!0},{children:[{id:'ZDTWEZFL13L8516VY480',name:'Interactive Work Text: Eat or Be Eaten',activityId:'ZDTWEZFL13L8516VY480',nodeType:'activity',suppressed:!1,hidden:!0,defaultLaunchMode:'modal'}],}],}],}]
function findId(id, arr) {
return arr.reduce((a, item) => {
if (a) return a;
if (item.id === id) return item;
if (item.children) return findId(id, item.children);
}, null);
}
console.log(findId('HQUCD2SSRKMYC2PJM636', data));
If your ids are unique and finding an object by id is a common task, you might want to consider creating a lookup object to improve performance. Creating the lookup object is an O(n) task; afterwards, looking up an object by id is O(1).
const data = [ { id: 'RAKUFNUBNY00UBZ40950', name: 'Grade 1 Cover', activityId: 'RAKUFNUBNY00UBZ40950', nodeType: 'activity', suppressed: false, hidden: false }, { children: [ { id: 'SLWDYEQHTZAFA3ALH195', name: 'Build Background Video', activityId: 'SLWDYEQHTZAFA3ALH195', nodeType: 'activity', suppressed: false, hidden: false, assetReference: { referenceId: 'UWFHA5A1E0EGKCM0W899', assetType: 'image' } }, { children: [ { id: 'HQUCD2SSRKMYC2PJM636', name: 'Eat or Be Eaten Splash Card', activityId: 'HQUCD2SSRKMYC2PJM636', nodeType: 'activity', suppressed: false, hidden: true }, { children: [ { id: 'ZDTWEZFL13L8516VY480', name: 'Interactive Work Text: Eat or Be Eaten', activityId: 'ZDTWEZFL13L8516VY480', nodeType: 'activity', suppressed: false, hidden: true, defaultLaunchMode: 'modal' } ] } ] } ] } ];
const lookup = {};
const registerIds = a => {
a.forEach(o => {
if ('id' in o) {
lookup[o.id] = o;
} else if ('children' in o) {
registerIds(o.children)
}
});
}
registerIds(data);
console.log(lookup)
Sorry for my two cents, just want to add a universal method that includes nested arrays
const cars = [{
id: 1,
name: 'toyota',
subs: [{
id: 43,
name: 'supra'
}, {
id: 44,
name: 'prius'
}]
}, {
id: 2,
name: 'Jeep',
subs: [{
id: 30,
name: 'wranger'
}, {
id: 31,
name: 'sahara'
}]
}]
function searchObjectArray(arr, key, value) {
let result = [];
arr.forEach((obj) => {
if (obj[key] === value) {
result.push(obj);
} else if (obj.subs) {
result = result.concat(searchObjectArray(obj.subs, key, value));
}
});
console.log(result)
return result;
}
searchObjectArray(cars, 'id', '31')
searchObjectArray(cars, 'name', 'Jeep')
I hope this helps someone
Is there a way to reset the questions or have a certain answer direct the question to another previous question?
var questions = [{
{
name: 'morefood',
message: 'Do you want more food?',
type: 'list',
choices: [ 'Yes', 'No'],
},{
name: 'choiceoffood',
message: 'Which food do you want more of?',
type: 'list',
choices: [ 'Hamburgers', 'Fries', 'Hotdogs']
when: function(answers) {
return answers.morefood === 'Yes';
}
}, {
name: 'quantityoffood',
message: 'How much more do you want?',
type: 'input',
when: function(answers) {
return answers.quantityoffood === 'Yes';
}
},{
name: 'confirmfood',
message: 'Do you still want more food?',
type: 'list',
choices: [ 'Yes', 'No'], <=========== if yes then goes back to choiceoffood
},
]
It seems a little hacky, but I believe you have to handle that in your application logic. For example:
const questionA = {
type: 'input',
message: 'Do you like fruit?',
name: 'questionA',
}
const questionB = {
type: 'input',
message: 'What is your favorite fruit?',
name: 'questionB',
}
const questionC = {
type: 'input',
message: 'what is your favorite candy?',
name: 'questionC',
}
inquirer
.prompt(questionA)
.then(answers => {
if (answers.questionA === 'yes') {
return inquirer.prompt(questionB)
} else {
return inquirer.prompt(questionC)
}
})
.then(answers => {
if (answers.questionB) {
return console.log(answers.questionB, 'is a great fruit');
}
if (answers.questionC) {
return console.log(answers.questionC, 'is a great candy');
}
})
UPDATE:
After looking at the docs a bit more, it seems 'when' is the correct solution for this.
when: (Function, Boolean) Receive the current user answers hash and
should return true or false depending on whether or not this question
should be asked. The value can also be a simple boolean.