Problem making an object iterable using an arrow function - javascript

I am learning JavaScript, I created a class and now I need to make it iterable.
The class Group is a Set of values with methods to add, remove and check the elements of the set.
The part of creating the class was a previous exercise of my textbook, I saw the solution for that part and I tested it, it is correct for sure. My problem is from[Symbol.iterator] on.
Here is the code
class Group {
constructor() {
this.theGroup = [];
}
add( element ) {
if( !this.has(element) ) this.theGroup.push(element);
}
remove( element ) {
this.theGroup = this.theGroup.filter( x => x != element );
}
has( element ) {
return this.theGroup.includes(element);
}
static from( elements ) {
let group = new Group;
for( const x of elements ) {
group.add(x);
}
return group;
}
[Symbol.iterator]() {
let i = 0;
return next = () => {
if( i >= this.theGroup.length ) return { done : true };
else return { value : this.theGroup[i++], done : false };
};
}
}
// the following is here only to show that the code before [Symbol.iterator] works
const group = Group.from([10, 20]);
console.log(group.has(10));
group.add(10);
group.remove(10);
console.log(group.has(10));
group.remove(10);
console.log(group.has(20));
// end of demostration
for (let value of Group.from(["a", "b", "c"])) {
console.log(value);
}
I get the error
return next = () => {
^
ReferenceError: next is not defined
And if I make the arrow function anonymous: return () => { ...code continues then I got this other error
for (let value of Group.from(["a", "b", "c"])) {
^
TypeError: undefined is not a function
I am using an arrow function to avoid changing this.
Someone knows what's going on?
How can I make the class iterable?

You want to return an object with a key of next,.
eg.. { next: () => {}} not return next = () => {}
Fixed example below..
class Group {
constructor() {
this.theGroup = [];
}
add( element ) {
if( !this.has(element) ) this.theGroup.push(element);
}
remove( element ) {
this.theGroup = this.theGroup.filter( x => x != element );
}
has( element ) {
return this.theGroup.includes(element);
}
static from( elements ) {
let group = new Group;
for( const x of elements ) {
group.add(x);
}
return group;
}
[Symbol.iterator]() {
let i = 0;
return {next: () => {
if( i >= this.theGroup.length ) return { done : true };
else return { value : this.theGroup[i++], done : false };
}};
}
}
// the following is here only to show that the code before [Symbol.iterator] works
const group = Group.from([10, 20]);
console.log(group.has(10));
group.add(10);
group.remove(10);
console.log(group.has(10));
group.remove(10);
console.log(group.has(20));
// end of demostration
for (let value of Group.from(["a", "b", "c"])) {
console.log(value);
}

Related

Handling changes in single and multiple select/input in Vanilla JavaScript

I have a webpage with two select menus, one input, three buttons, and a checkbox. Everything is added and manipulated by JavaScript. On page page load, there is an object initiated with no values ( either value correspond either one of select or input ). You can select values and update it ( target is array of objects ) in array. Then, you can choose one of the buttons to copy selected options. Each object has three value (copying means that you copy the segment containing those values) times as many as you typed in input. After that, all objects are shown on screen. You can click a button to check all checkboxes and all objects change value. Value is updated in all selects of changed type and in the corresponding array.
Now my problems begin. When you unselect all checkboxes and want to change only 1 object all objects are updated and I have no idea why. There are conditions to check for each object if the checkbox is unchecked. If a single object is updated (either select or input) all of the other objects are updated ( none of checkbox is checked).
Here is github link to this project to check everything out without problems.
Also one more thing... whenever I update all values handleDOMChange function should fire automatically, but as you can see I have to fire it up manually so the changes are "visible" on my target array
const collectedData = [ {
cableType: "",
calbleLen_m: 0,
deviceType: ""
} ];
const completeData = {};
window.addEventListener('load', () => {
handleButtonEvents();
const segments = document.querySelectorAll('.installationSegment');
segments.forEach((segment, i) => {
handleInputAndSelectChange(segment, i);
});
const targetNode = document.getElementById("installationContainer");
targetNode.append(selectAllCheckboxesButton);
const config = {
childList: true,
subtree: true,
attributes: true,
characterData: true
};
const observer = new MutationObserver(handleDOMChange);
observer.observe(targetNode, config);
}
//Handling DOMChanges
const handleDOMChange = function() {
const powerSupplyElement = document.getElementById('powerSupply');
powerSupplyElement.addEventListener('change', e => completeData.supplyType = e.target.value);
const segments = document.querySelectorAll('.installationSegment');
if( segments.length >= 2 ) {
segments.forEach((segment, i) => {
const checkbox = segment.querySelector('input[type="checkbox"]');
if( !checkbox.checked ) {
//updating value for single change, i is index of element in array and none of checkboxes is checked.
handleInputAndSelectChange(segment, i);
} else if( checkbox.checked ) {
//updating value for every segment exisitng in DOM whose checkbox has been checked.
handleManySegmentsChange(segment);
}
});
}
completeData.bus = [ ...collectedData ];
console.table(completeData.bus);
}
//handling ButtonEvents
handleButtonEvents = function() {
const installationSegment = document.getElementById('installationContainer');
installationSegment.addEventListener('click', e => {
if( e.target.id.includes("Skopiuj") ) {
handleCopyNthTimes(e);
} else if( e.target.id.includes("Usun") ) {
handleDeleteDevice(e);
} else if( e.target.id === 'selectAllCheckboxes' ) {
checkAllCheckboxes(e);
}
});
}
//handling all checkboxes select and unselect
const checkAllCheckboxes = function() {
const segments = document.querySelectorAll('.installationSegment');
segments.forEach((segment, i) => {
const checkbox = segment.querySelector('input[type="checkbox"]');
checkbox.checked = !checkbox.checked;
handleDOMChange();
});
};
//handling single and multiple changes in select/input
handleManySegmentsChange = function(segment) {
segment.addEventListener('change', (e) => {
switch( e.target.name ) {
case 'cableSelect': {
const cableSelect = document.querySelectorAll('.cableSelect');
cableSelect.forEach(cable => cable.value = e.target.value);
collectedData.forEach(cable => cable.cableType = e.target.value);
handleDOMChange();
break;
}
case 'deviceSelect': {
const deviceSelect = document.querySelectorAll('.deviceSelect');
deviceSelect.forEach(device => device.value = e.target.value);
collectedData.forEach(device => device.deviceType = e.target.value);
handleDOMChange();
break;
}
case 'cableInput': {
const cableInput = document.querySelectorAll('input[name="cableInput"]');
cableInput.forEach(input => input.value = e.target.value);
collectedData.forEach(input => input.calbleLen_m = parseInt(e.target.value));
handleDOMChange();
break;
}
}
})
}
handleInputAndSelectChange = function(segment, index) {
segment.addEventListener('change', (event) => {
switch( event.target.name ) {
case 'cableSelect': {
collectedData[index].cableType = event.target.value;
break;
}
case 'deviceSelect': {
collectedData[index].deviceType = event.target.value;
break;
}
case 'cableInput': {
collectedData[index].calbleLen_m = parseInt(event.target.value);
break;
}
}
});
}
All right, I've found the answer. The reason it didn't work is that handleManySegmentsChange function is going to be fired in one circumstance - if user changes many select values at once. But this cannot be the case. My solution to the problem is to detect one change in segment, check for checkbox value, pass it to the function and depending on the checkbox value either update one value or all of them. Like so:
const segments = document.querySelectorAll('.installationSegment');
segments.forEach((segment, i) => {
segment.addEventListener('change', e => {
const checkbox = segment.querySelector('input[type="checkbox"]');
handleInputAndSelectChange(segment, e, i, checkbox.checked);
})
});
and then in function
handleInputAndSelectChange = function(segment, event, index, checked) {
switch( event.target.name ) {
case 'cableSelect': {
if( checked ) {
const cableSelect = document.querySelectorAll('.cableSelect');
cableSelect.forEach(cable => cable.value = event.target.value);
collectedData.forEach(data => data.cableType = event.target.value);
} else if( !checked ) {
collectedData[index].cableType = event.target.value;
}
break;
}
case 'deviceSelect': {
if( checked ) {
const deviceSelect = document.querySelectorAll('.deviceSelect');
deviceSelect.forEach(device => device.value = event.target.value);
collectedData.forEach(device => device.deviceType = event.target.value);
} else if( !checked ) {
collectedData[index].deviceType = event.target.value;
}
break;
}
case 'cableInput': {
if( checked ) {
const cableInput = document.querySelectorAll('input[name="cableInput"]');
cableInput.forEach(input => input.value = event.target.value);
collectedData.forEach(input => input.calbleLen_m = parseInt(event.target.value));
} else if( !checked ) {
collectedData[index].calbleLen_m = parseInt(event.target.value);
}
break;
}
}
}

JavaScript; validateBinaryTree function gives value error on node

A coding challenge in which we are to write a function that determines if a binary tree is valid. The tree is simply a collection of BinaryTreeNodes that are manually linked together. The validateBinaryTree function should return false if any values on the left subtree are greater than the root value or false if any values on the right subtree are less, and true otherwise.
Here is the BinaryTreeNode class:
class BinaryTreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
insertLeft(value) {
this.left = new BinaryTreeNode(value);
return this.left;
}
insertRight(value) {
this.right = new BinaryTreeNode(value);
return this.right;
}
depth_first_print() {
console.log(this.value);
if (this.left) {
this.left.depth_first_print();
}
if (this.right) {
this.right.depth_first_print();
}
}
}
Here is the validateBinaryTree function:
const validateBinaryTree = (rootNode) => {
const rootValue = rootNode.value;
let isValid = true;
const validateLeft = (node) => {
if (node.value > rootValue) isValid = false;
if (node.left) {
validateLeft(node.left);
}
if (node.right) {
validateLeft(node.right);
}
}
const validateRight = (node) => {
if (node.value < rootValue) isValid = false;
if (node.left) {
validateRight(node.left);
}
if (node.right) {
validateRight(node.right);
}
}
validateLeft(rootNode.left);
validateRight(rootNode.right);
return isValid;
}
//Build an invalid binary tree which will look like this:
// 10
// /
// 50
const tree = new BinaryTreeNode(10);
tree.insertLeft(50);
The following function call should print false to the console:
console.log(validateBinaryTree(tree));
But instead I get the following error:
if (node.value < rootValue) isValid = false;
^
TypeError: Cannot read property 'value' of null
Your initial code fails because you try to invoke validateRight on rootNode.right, which is null. That's why it's actually better to place that check (against node === null case) inside validator itself.
Also I'd simplify this code by passing two separate functions inside - one for the left branch, another for the right - closured upon rootNode value. For example:
const validateBinaryTree = (rootNode) => {
const forLeft = val => val < rootNode.value;
const forRight = val => val > rootNode.value;
const validateBranch = (node, branchComparator) => {
return node === null ||
branchComparator(node.value) &&
validateBranch(node.left, branchComparator) &&
validateBranch(node.right, branchComparator);
}
return validateBranch(rootNode.left, forLeft) && validateBranch(rootNode.right, forRight);
}
This version also has a (slight) benefit of immediately stopping the check whenever failing node has been found (because of short-circuit nature of && operator in JS).

Chaining Promises Cross Function Javascript

I suspect I've fundementally misunderstood Javascript promises, any ideas?
I have a pretty function that queries a database containing music that looks like this:
function searchDatabaseForTrack(query,loadedResults){
loadedResults = loadedResults || [];
desiredResults = 100;
if (loadedResults.length < desiredResults) {
try {
databaseApi.searchTracks(query, {"offset":loadedResults.length, "limit":"50", }).then(function(data){
i=0
if (data.tracks.items.length == 0) {
console.log(`Already loaded all ${loadedResults.length} tracks!`)
console.log(loadedResults)
return loadedResults;
}
else {
for (thing in data.tracks.items){
loadedResults.push(data.tracks.items[i]);
i=i+1;
}
console.log(loadedResults.length, " tracks collected");
searchDatabaseForTrack(query,loadedResults)
}
});
} catch(err) {
console.log("ERROR!", err)
console.log(loadedResults)
return loadedResults;
}
} else {
console.log(loadedResults)
return loadedResults;
}
}
And then a bit later, I try to call and use the data retrieved.
function getArtistTracks(artistName){
searchDatabaseForTrack(artistName).then(function(data){
console.log(songs);
songs.sort(function(a,b){
var c = new Date(a.track.album.release_date);
var d = new Date(b.track.album.release_date);
return d-c;
});
console.log("songs", songs);
var newsongs=[];
i=0
for (song in songs) {
newsongs.push(songs[i].track.uri);
i++
};
return newsongs;
});
}
What I'm trying to do is get the second function "getArtistTracks" to wait for the completion of the query in the first function. Now I could just call the databaseApi.searchTracks directly, but there's a limit of 50 tracks returned per result — which kind of screws me over.
searchDatabaseForTrack().then(...) shouldn't work since searchDatabaseForTrack() doesn't return a promise, so you can either return a promise or use an async function.
instead of a recursive function, you could simply call databaseApi in a for loop,
the desiredResult should be an argument and not hardcoded in the function,
async function searchDatabaseForTrack(query, desiredResults){
let loadedResults = [], data, currOffset = 0;
const iterations = Math.ceil(desiredResults / 50);
for(let n = 0 ; n < iterations; n++){
cuurOffset = n * 50;
data = await databaseApi.searchTracks(query, {"offset":currOffset, "limit":"50", });
if (data.tracks.items.length == 0) {
console.log(`Already loaded all ${loadedResults.length} tracks!`)
console.log(loadedResults)
return loadedResults;
}
else {
loadedResults = loadedResults.concat(data.tracks.items);
console.log(loadedResults.length, " tracks collected");
}
}
return loadedResults;
}
the rest should be fine as long as you add .catch() to handle errors ( as mentionned in previous answer ) which are thrown automatically without the need of the try/catch block :
function getArtistTracks(artistName, 100){
searchDatabaseForTrack(artistName).then((songs) => {
// your previous code
})
.catch((err) => {
// handle error
});
});
Have searchDatabaseForTrack use Promise.all to return the loadedResults after all results have been gotten. Also, make sure not to implicitly create global variables as you're doing with thing. For example, try something like this:
async function searchDatabaseForTrack(query) {
const desiredResults = 100;
const trackPromises = Array.from(
({ length: Math.ceil(desiredResults / 50) }),
(_, i) => {
const offset = i * 50;
return databaseApi.searchTracks(query, { offset, limit: 50 });
}
);
const itemChunks = await Promise.all(trackPromises);
const loadedResults = itemChunks.reduce((a, { tracks: { items }}) => (
[...a, ...items]
), []);
return loadedResults;
};
and
searchDatabaseForTrack(artistName).then((loadedResults) => {
// do stuff with loadedResults
})
.catch((err) => {
console.log("ERROR!", err)
// handle error
});

How to refactor this double forLoop with lodash?

I have selectedTags which holds up to 3 tags.
vm.tags Could contain thousands, most likely just hundreds of tags that I need to compare too.
If the ids of the 3 tags match the id of a tag inside of vm.tags I need to turn their borders on. There are 3 borders too: border1, border2, border3.
const tagsColorCheck = () => {
let name, selected, its_ticker;
let selectedTags = TagsFactory.retrieveTickerTags('onlyTags');
if (selectedTags.length > 0) {
for (let i=0; i<vm.tags.length; i++) {
for (let j=0; j<selectedTags.length; j++) {
if (selectedTags[j].term_id == vm.tags[i].term_id) {
name = 'border'+ ( j + 1 );
selected = 'selected';
its_ticker = 'its_ticker';
vm.tags[i][name] = true;
vm.tags[i][selected] = true;
vm.tags[i][its_ticker] = selectedTags[j].its_ticker;
}
}
}
}
};
So far here is what I have in process (_.each):
const tagsColorCheck = () => {
let name, selected, its_ticker, vmTerm, term_1, term_2, term_3, ticker_1, ticker_2, ticker_3;
let selectedTags = TagsFactory.retrieveTickerTags('onlyTags');
if (!_.isEmpty(selectedTags)) {
vmTerm = R.findIndex(R.propEq('term_id', selectedTags[0].term_id))(vm.tags);
}
if (selectedTags[0]) { term_1 = parseInt(selectedTags[0].term_id); ticker_1 = selectedTags[0].its_ticker; }
if (selectedTags[1]) { term_2 = parseInt(selectedTags[1].term_id); ticker_2 = selectedTags[1].its_ticker; }
if (selectedTags[2]) { term_3 = parseInt(selectedTags[2].term_id); ticker_3 = selectedTags[2].its_ticker; }
_.each(vm.tags, (tag) => {
if (tag.term_id === term_1) {
tag.selected = true;
tag.border1 = true;
tag.its_ticker = ticker_1;
}
if (tag.term_id === term_2) {
tag.selected = true;
tag.border2 = true;
tag.its_ticker = ticker_2;
}
if (tag.term_id === term_3) {
tag.selected = true;
tag.border3 = true;
tag.its_ticker = ticker_3;
}
})
};
And this (for of loop):
const tagsColorCheck = () => {
let name, selected, its_ticker, vmTerm, term_1, term_2, term_3, ticker_1, ticker_2, ticker_3;
let selectedTags = TagsFactory.retrieveTickerTags('onlyTags');
const borderRizeTag = (tag) => {
if (tag.term_id === term_1) {
tag.selected = true;
tag.border1 = true;
tag.its_ticker = ticker_1;
}
if (tag.term_id === term_2) {
tag.selected = true;
tag.border2 = true;
tag.its_ticker = ticker_2;
}
if (tag.term_id === term_3) {
tag.selected = true;
tag.border3 = true;
tag.its_ticker = ticker_3;
}
return tag;
}
if (!_.isEmpty(selectedTags)) {
vmTerm = R.findIndex(R.propEq('term_id', selectedTags[0].term_id))(vm.tags);
}
if (selectedTags[0]) { term_1 = parseInt(selectedTags[0].term_id); ticker_1 = selectedTags[0].its_ticker; }
if (selectedTags[1]) { term_2 = parseInt(selectedTags[1].term_id); ticker_2 = selectedTags[1].its_ticker; }
if (selectedTags[2]) { term_3 = parseInt(selectedTags[2].term_id); ticker_3 = selectedTags[2].its_ticker; }
for (let tag of vm.tags) {
console.log(tag);
tag = borderRizeTag(tag);
}
console.log('vmTerm',vmTerm);
};
ES6 fiddle to run: http://www.es6fiddle.net/is0prsq9/ (note, copy the entire text, and paste it inside a browser console or a node REPL, and then examine the value of tags to see the result)
It's not lodash, and you don't really need it with ES6 constructs. Relevant code:
const tagsColorCheck = () => {
let tags = TagsFactory.retrieveTickerTags('onlyTags')
sel.forEach( (s,i) =>
tags.filter(t => t.term_id === s.term_id).forEach( t => {
t['border' + (i+1)] = true
t.selected = true
t.its_ticker = s.its_ticker
})
)
return tags
}
If you were writing this in a functional language, you would have access to a list comprehension and it would be a bit cleaner. Essentially, this is a pretty clear case of (for every x in a and y in b) so a list comprehension is what you need, but you don't have it in javascript (mozilla has it, but not useful outside of that realm).
The result is a somewhat functional approach -- however, it can never really be functional in pure javascript. Possibly the most important benefit of the functional paradigm are immutable data structures where you would compose your new list. Instead, you just modify them in place here, which really is not very functional at all. Still, if you prefer the each approach to a literal incremental one, as you have done above and as I did in my post, then it's a (albeit slower but arguably cleaner) better approach.
Figured out an awesome solution! :D using both _lodash and ramda.
So below, immediately each is quicker to reason about, then using R.equals to compare if the term_ids match. Then setting the values of the keys on the correct tag object.
if (!_.isEmpty(selectedTags)) {
_.each(vm.tags, tag => {
_.each(selectedTags, (selectedTag, index) => {
let areTermIDsSame = R.equals;
if (areTermIDsSame(parseInt(selectedTag.term_id), parseInt(tag.term_id))) {
name = 'border'+ ( index + 1 );
selected = 'selected';
its_ticker = 'its_ticker';
tag[name] = true;
tag[selected] = true;
tag[its_ticker] = selectedTag.its_ticker;
}
});
})
}
The idea is simple - create an index of all tags by term_id. Iterate the selected tags. If a tag is found by id in the tags index, mutate it by assigning an object with the new properties.
btw - The only thing lodash is needed for is _.keyBy(), and you can easily do that using Array.prototype.reduce if you don't want to use lodash.
/** mocked vm **/
const vm = {
tags: [{ term_id: 1 }, { term_id: 2 }, { term_id: 3 }, { term_id: 4 }, { term_id: 5 }, { term_id: 6 }]
}
/** mocked TagsFactory **/
const TagsFactory = {
retrieveTickerTags: () => [{ term_id: 1, its_ticker: 'ticker 1' }, { term_id: 4, its_ticker: 'ticker 4' }, { term_id: 5, its_ticker: 'ticker 5' }]
};
const tagsColorCheck = () => {
const selectedTags = TagsFactory.retrieveTickerTags('onlyTags');
if (selectedTags.length === 0) { // if selectedTags is empty exit
return;
}
const vmTagsIndex = _.keyBy(vm.tags, (tag) => tag.term_id); // create an index of tags by term_id
selectedTags.forEach(({
term_id, its_ticker
}, index) => { // loop through selectd tags and retreive term_id and its_ticker from the current selected tag
const tag = vmTagsIndex[term_id]; // find the tag in the vmTagsIndex
if (!tag) { // if the id doesn't exist in vmTagsIndex exit
return;
}
Object.assign(tag, { // mutate the tag by assigining it an object with the available properties
selected: true,
[`border${index + 1}`]: true,
its_ticker
});
});
};
tagsColorCheck();
console.log(vm.tags);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>

Converting an array to cursor

Cursors can be easily converted to arrays using .toArray(foo) method:
var cursor = col.find({});
cursor.toArray(function (err, itemsArray) {
/* do something */
});
But is it possible to convert itemsArray in a cursor so I will have all cursor functions?
var newCursor = foo (itemsArray);
typeof newCursor.toArray === "function" // true
Well it is all just JavaScript so why not create your own iterator:
var Iterator = function () {
var items = [];
var index = 0;
return {
"createCursor" : function (listing) {
items = listing;
},
"next" : function () {
if ( this.hasNext() ) {
return items[index++];
} else {
return null;
}
},
"hasNext" : function () {
if ( index < items.length ) {
return true;
} else {
return false;
}
}
}
}();
So then you use it like this with an array:
var cursor = new Iterator();
cursor.createCursor( array );
cursor.next(); // returns just the first element of the array
So just a general way to write an iterator. If you want more functionality then just add the other methods to the prototype.

Categories

Resources