ES6 How to ensure updated clone properties doesn't effect original? - ecmascript-6

I must be doing something wrong.
Instantiate Person class as Bob with name 'Bob'
Clone Bob as new var Alice
Rename Alice with name 'Alice'
Log names of Bob & Alice
I expect Bob's name will remain 'Bob', but it has updated to 'Alice', despite not updating Bob...?
class Person {
constructor(attr) {
this.attr = attr;
}
talk() {
console.log('My name is ' + this.attr.name);
}
}
function clone(obj) {
return Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);
}
var Bob = new Person({
name: 'Bob'
});
var Alice = clone(Bob);
Alice.attr.name = 'Alice';
Alice.talk();
Bob.talk();
Thanks in advance.

Object.assign performs a shallow copy, so Bob and Alice will have their own copy of the attr reference, but they refer to the same nested object. attr.name is still a shared string.
You need to perform a deep(er) copy, or else reassign the attr property:
Alice.attr = { name: 'Alice' };

The simplest solution for cloning is:
var cloned = JSON.parse(JSON.stringify(objectToClone));
But there is a catch in this solution, this will fail when your object's attribute value is a function.
var a = {name: 'a', exec: function() {return true;}};
var b = JSON.parse(JSON.stringify(a));
console.log(b); // {name: 'a'}
for better insight on cloning, you can refer this article: deep cloning

Related

How to get Course Id from Classroom URL?

Goal: I want to quickly connect to a Google Classroom using a Google Classroom URL via Google Apps Script.
Problem: Need help filtering Map of courses by URL.
Background:
Classroom API has little documentation for GAS. Furthermore, COURSE_ID is used for nearly all connections. I can map the active courses, but I cannot filter the map. The code below originated from Yagisanatode with modifications in an attempt to map active courses by URL. Changing the Logger to (courseData) reveals the creation of the double array.
function findCourseByUrl() {
const courseList = Classroom.Courses.list({"courseStates":["ACTIVE"]}).courses;
const courseData = courseList.map(course => {
let ownerName = Classroom
.Courses
.Teachers
.get(course.id, course.ownerId)
.profile
.name
.fullName;
return `[${course.name}, ${course.id}, ${ownerName}, ${course.alternateLink}]`;
});
const link = 'https://classroom.google.com/c/YOUCLASSROOMURL'; //change this
const data = courseData.filter(function(item){return item[4] === link;});
Logger.log(data);
};
Any help would be appreciated. I'm stuck.
Answer:
link is not defined since it is outside of the courseData.filter(function(item){}). The solution is to call a global variable or create a conditional with the declared variable within the function(item).
The toString is looking for an exact match for the URL text, which is naturally unique.
Video reference: https://youtu.be/PT_TDhMhWsE
Code:
function findCourseByUrl() {
const courseList = Classroom.Courses.list({"courseStates":["ACTIVE"]}).courses;
const courseData = courseList.map(course => {
let ownerName = Classroom
.Courses
.Teachers
.get(course.id, course.ownerId)
.profile
.name
.fullName;
return `[${course.name}, ${course.id}, ${ownerName}, ${course.alternateLink}]`;
});
const filterCourse = function(item){
let link = 'https://classroom.google.com/c/YOURCOURSEURL' ///Change this or replace with a global variable
if(item.toString().indexOf(link) === -1){
return false;
} else {
return true
}
};
let theCourse = courseData.filter(filterCourse); //this could be a return if called by function in Test.gs
Logger.log(theCourse); //remove if using a function with console.log in Test.gs
};

Immutable.js reference equality

given the following trivial code:
const Immutable = require('immutable');
const a = Immutable.fromJS({
a: 1,
b: [2, 3, 4],
c: {
d: 1
}
});
const b = a.setIn(['c', 'd'], "Something else");
const c = b.setIn(['c', 'd'], 1);
console.log(a.equals(b)); // true
console.log(Immutable.is(a, c)); // true
console.log(a === c); // false?
And for the final comparison I'd expect it to return true since I'm setting the path ['c', 'd'] to something else and then back to the original value, and with structural sharing I would expect that it results in c holding a reference to the original data structure?
Do I mis-understand how this works?
First, this one console.log(a.equals(b)); returns false actually:
Now for your question, as documented in Immutable.js here at "Return self on no-op optimization" sub-chapter:
When possible, Immutable.js avoids creating new objects for updates
where no change in value occurred, to allow for efficient reference
equality checking to quickly determine if no change occurred.
There's that example:
const { Map } = require('immutable');
const originalMap = Map({ a: 1, b: 2, c: 3 });
const updatedMap = originalMap.set('b', 2);
updatedMap === originalMap; // No-op .set() returned the original reference.
However updates which do result in a change will return a new
reference. Each of these operations occur independently, so two
similar updates will not return the same reference:
And that example:
const { Map } = require('immutable');
const originalMap = Map({ a: 1, b: 2, c: 3 });
const updatedMap = originalMap.set('b', 1000);
// New instance, leaving the original immutable.
updatedMap !== originalMap;
const anotherUpdatedMap = originalMap.set('b', 1000);
// Despite both the results of the same operation, each created a new reference.
anotherUpdatedMap !== updatedMap;
// However the two are value equal.
anotherUpdatedMap.equals(updatedMap);
Since you are changing the value, setIn returns a new reference. Therefore they are not equal by reference.
Hope I helped :)

Polymer 2 access file uploaded in another page

I am using a simple polymer application with few pages in iron-pages. I am uploading a file in one page and then I want to access this uploaded file in another page.
I tried several things but nothing seems to work, here is the sample code
Page in which file is uploaded
<dom-module id="file-upload-page">
<template>
<form method="post" enctype="multipart/form-data" action="/someation" disable-native-validation-ui no-validate>
<my-input file={{file}} id="sampleFileInput" btn-style="primary" max-files="1" accept=".xls, .xlsx" on-drop="fileUploadChangeListener"
label="[[localize('open_invoices_file')]]" help-text="[[localize('open_invoices_file_help')]]" no-auto required>
</my-input>
</form>
</template>
<script>
class FileUploadPge extends Polymer.mixinBehaviors([], Polymer.Element) {
static get is() {
return 'file-upload-page';
}
static get properties() {
return {
}
}
}
customElements.define(FileUploadPge.is, FileUploadPge);
</script>
</dom-module>
Page in which file is accessed
<dom-module id="consumer-page">
<template>
//some code
</template>
<script>
class ConsumerPage extends Polymer.mixinBehaviors([], Polymer.Element) {
static get is() {
return 'consumer-page';
}
constructor() {
super();
}
static get properties() {
return {
//some properties
}
}
ready() {
super.ready();
var temp2 = this.$.sampleFileInput; // returns null
var temp3 = this.shadowRoot.querySelector("#sampleFileInput"); // returns null
var temp4 = this.root.querySelector('#sampleFileInput'); // returns null
var temp5 = this.$$('#sampleFileInput'); // returns null
this._refreshSelections();
};
_proceed() {
var test1 = Polymer.dom(this).querySelector("#sampleFileInput"); // returns null
var test2 = this.$.sampleFileInput; //returns null
var test3 = document.getElementById("sampleFileInput"); //returns null
var test4 = this.$$("sampleFileInput"); //returns null
var test5 = this.shadowRoot; //returns some object
var test6 = this.$$.sampleFileInput; //returns null
var test7 = document.querySelector('sampleFileInput'); //returns null
var test8 = document.querySelector('file-upload-page::shadow .FileUploadPge'); //returns null
var temp4 = this.root.querySelector('#sampleFileInput');//returns null
var temp5 = this.$$('#sampleFileInput');//returns null
var temp6 = this.shadowRoot.querySelector('#sampleFileInput'); // returns null
};
}
customElements.define(ConusmerPage.is, ConusmerPage);
</script>
</dom-module>
The same code works in polymer1.0 with this
document.getElementById("sampleFileInput")
Can somebody help what wrong am I doing in accessing this file in other page, and how can I handle this scenario in Polymer 2.0?
As you said in consumer-page you're trying to access the #sampleFileInput element which is a child of another component.
All of these attempts:
var temp2 = this.$.sampleFileInput;
var temp3 = this.shadowRoot.querySelector("#sampleFileInput");
var temp4 = this.root.querySelector('#sampleFileInput');
var temp5 = this.$$('#sampleFileInput');
var test1 = Polymer.dom(this).querySelector("#sampleFileInput");
fail because you're trying to access an element which is not present inside consumer-page's template, while these:
var test7 = document.querySelector('sampleFileInput');
var test8 = document.querySelector('file-upload-page::shadow .FileUploadPage');
fail respectively because document.querySelector() cannot select inside shadow dom and ::shadow and /deep/ selectors were deprecated (see here).
Technically you should be able to select #sampleFileInput inside consumer-page this way:
this.parentElement // Goes back to iron-pages
.querySelector('file-upload-page') // Selects file-upload-page
.shadowRoot // Enters its shadow root
.querySelector('#sampleFileInput'); // Selects the file uploader
however accessing elements inside others' shadow root is considered a not so good practice not to mention that if you're using lazy loading for iron-pages pages this will fail if file-upload-page wasn't loaded.
There are instead many other ways to expose information outside of custom elements such as events or properties.
You could, if it can fit with your implementation, use the component holding iron-pages as coordinator of your procedure and use attributes bindings to notify it with the data it needs from the different pages as the user goes on filling.
IE in file-upload-page bind the uploaded file url to a property, and observe it in the parent:
<iron-pages>
<file-upload-page url="{{url}}"></file-upload-page>
<consumer-page></consumer-page>
</iron-pages>
<script>
class Parent extends PolymerElement {
// ...
static get properties() {
return {
url: {
type: String,
reflectToAttribute: true,
observer: '_urlChanged',
},
};
}
_urlChanged() {
console.log(this.url);
}
// ...
}
</script>

Functions being returned as keys

Newbie question here...I'm building a simple stack using a functional pattern and returning the push and pop functions as keys(I ofcourse don't want this)...I really am not sure why. The function is operational, just returning those two extra keys...
This is what the return looks like...
{ size: 2,
storage: { '1': 'test0', '2': 'test1' },
push: [Function], <== don't want
pop: [Function] } <== don't want
[Finished in 0.1s]
function Stack () {
var obj = {};
obj.size = 0;
obj.storage = {};
obj.push = function(data) {
var newSize = ++obj.size;
obj.storage[newSize] = data;
};
obj.pop = function() {
var newSize = obj.size;
var deletedData;
if (newSize) {
deletedData = obj.storage[newSize];
delete obj.storage[newSize];
obj.size--;
return deletedData;
}
};
return obj;
};
var stack = new Stack();
stack.push('test0')
stack.push('test1')
stack.push('test2')
stack.pop()
console.log(stack)
You say "obviously" you don't want the object to include keys for the functions, but I guess to me it's not so obvious... So what is it you do want to end up with? In JavaScript a function reference is just another piece of data, and a method is just a function reference stored as the value for some key on an object.
If you want the user to be able to say obj.push(...) then you do want a push key on obj (so that obj.push means something), and you want its value to be a function (so that the () operator can be applied to it).
Now I am curious because your output block says the keys are xPush and xPop but those aren't the values you show in the code block. Is that because of editing in the question? If not I don't see how that could be the way you've shown it.

Read records from firebase based on a previously saved value

I took an angularjs + firebase example and modified it for an app where I can register some kids for a small cross-country race.
I'm able to register kids (participants), races, locations, clubs etc. using a basic structure:
FIREBASE_URL/races
FIREBASE_URL/clubs
and so forth. When the active race is selected, I save the raceId and race json-object and can add participants to the active race.
Example:
FIREBASE_URL/active_race/-JI6H9VQewd444na_CQY
FIREBASE_URL/active_race/json-object
What I'd like to do is to get all the participants, if any, based on raceId:
FIREBASE_URL/races/-JI6H9VQewd444na_CQY/participants
I tried the following
'use strict';
app.factory('Race', function ($firebase, FIREBASE_URL, User) {
var ref = new Firebase(FIREBASE_URL + 'races');
var races = $firebase(ref);
var Race = {
all: races,
getParticipantsInRace: function () {
var fb = new Firebase(FIREBASE_URL);
fb.child('active_race/raceId').once('value', function (activeSnap) {
races.$child('/' + activeSnap.val() + '/participants');
});
}
};
return Race;
But I believe I'm doing it wrong. I tried to prepend return before races.$child and fb.child but it did not solve my problem.
I tried to hardcode the following json-array and this is shown on the webpage:
return [{name: 'Claus', born: '1967'}, {name: 'John', born: '1968'}];
How do I get all the participants into $scope.participantsInRace?
I believe I have a solution, but I'm not sure if it's wise to do it this way. But it may be that simple. Prepending $rootScope.participantsInRace = to put it into rootScope:
$rootScope.participantsInRace = races.$child('/' + activeSnap.val() + '/participants');
The code is already synchronizing all data in all races when it declares $firebase(URL+'races');. Additionally, you never assigned your races.$child(...) to anything, so it's not possible to reference that data later.
app.factory('Race', function ($firebase, FIREBASE_URL, User) {
var ref = new Firebase(FIREBASE_URL + 'races');
var races = $firebase(ref);
var Race = {
all: races,
getParticipantsInRace: function (raceId) {
return races[raceId]? races[raceId].participants || {};
}
};
return Race;
});
Keep in mind that the race data won't be available until races.$on('loaded') is invoked (when the data returns from the server).
Thank you for the input. I know a bit more about angularjs and javascript now so I did some refactoring and cleanup. Hardcoding raceId works:
getParticipantsInRace: function () {
return races.$child('-JIecmbdDa4kUT2L51iS').$child('participants');
}
When I wrap it in a call to Firebase I can't seem to return the desired data, probably due to my somewhat limited knowledge of javascript on how to return data. Example:
getParticipantsInRace: function () {
ref.child('activeRace').child('raceId').once('value', function (activeSnap) {
return races.$child(activeSnap.val()).$child('participants');
});
}
My idea is to get the raceId and then return all participants. I tried to prepend return to ref.child() but still no data was returned. So not really an answer.
Regards
Claus
This works. I changed $rootScope.participantsInRace to $scope.participantsInRace and the following:
getParticipantsInRace: function () {
if (User.signedIn()) {
var t = [];
var user = User.getCurrent();
var fb = new Firebase(FIREBASE_URL + 'users');
fb.child(user.username).child('activeRace/raceId').once('value', function (userSnap) {
t = races.$child(userSnap.val()).$child('participants');
});
return t;
}
},