Getting the value from a Map with zero or 1 entries? - javascript

I have a Map instance and it will either have zero or 1 entries in it. This is a demo:
class Todo {
constructor(
public id:string,
public title:string,
public description: string ) {}
}
const todo1 = new Todo('1', 't1', 'Sarah OConnor');
const m:Map<string, Todo> = new Map();
m.set('1', todo1);
const todoInMap= m.entries().next().value[1];
console.log("The todo in the map is: ", todoInMap);
This will log the todoInMap instance when it's in the Map, but if we m.clear() we will get an error.
This function can be used to avoid errors, I'm just wondering if Map API has a more simple way of doing this?
function getMapValue<E>(m:Map<any, E>) {
if (!m.entries().next().done) {
return m.entries().next().value;
}
return null;
}
Ended up putting adding a utility method to Slice Utilities
/**
* Gets the current active value from the `active`
* Map.
*
* This is used for the scenario where we are manging
* a single active instance. For example
* when selecting a book from a collection of books.
*
* The selected `Book` instance becomes the active value.
*
* #example
* const book:Book = getActiveValue(bookStore.active);
* #param m
*/
export function getActiveValue<E>(m:Map<any, E>) {
if (m.size) {
return m.entries().next().value[1];
}
return null;
}```

I think this option would be better:
function getMapValue<E>(m:Map<any, E>) {
if (m.size) {
return m.entries().next().value;
}
return null;
}
It's very weird to have size as a property, not length or size() but it how it is :)

Related

How I can simulate the __callStatic property of PHP in Node.js using Proxy?

I'm trying to create the same behavior of PHP __callStatic magic method in Node.js.
I'm trying to use Proxy to do that, but I don't really know if it's the best option.
class Test {
constructor() {
this.num = 0
}
set(num) {
this.num = this.num + num
return this
}
get() {
return this.num
}
}
const TestFacade = new Proxy({}, {
get: (_, key) => {
const test = new Test()
return test[key]
}
})
// Execution method chain ends in get
console.log(TestFacade.set(10).set(20).get())
// expected: 30
// returns: 0
// Start a new execution method chain and should instantiate Test class again in the first set
console.log(TestFacade.set(20).set(20).get())
// expected: 40
// returns: 0
The problem is that the get trap is fired every time that I try to access a property of TestFacade. The behavior that I need is that when the set method is called it will return this of Test class and I can even save the instance for latter usage!
const testInstance = TestFacade.set(10) // set method return this of `Test` not the Proxy
If something isn't clear, please let me know.
I don't know if it's the best option. But I solved it, returning a new Proxy inside get trap that uses the apply trap to bind the test class instance into the method:
class Facade {
static #facadeAccessor
static createFacadeFor(provider) {
this.#facadeAccessor = provider
return new Proxy(this, { get: this.__callStatic.bind(this) })
}
static __callStatic(facade, key) {
/**
* Access methods from the Facade class instead of
* the provider.
*/
if (facade[key]) {
return facade[key]
}
const provider = new this.#facadeAccessor()
const apply = (method, _this, args) => method.bind(provider)(...args)
if (provider[key] === undefined) {
return undefined
}
/**
* Access the properties of the class.
*/
if (typeof provider[key] !== 'function') {
return provider[key]
}
return new Proxy(provider[key], { apply })
}
}
class Test {
num = 0
set(num) {
this.num = this.num + num
return this
}
get() {
return this.num
}
}
const TestFacade = Facade.createFacadeFor(Test)
console.log(TestFacade.set(10).set(20).get()) // 30
console.log(TestFacade.set(5).set(5).get()) // 10
const testInstance = TestFacade.set(10)
console.log(testInstance.num) // 10
console.log(testInstance.get()) // 10
console.log(testInstance.set(10).get()) // 20

Why is my data property being updated by a computed property in Vuejs?

I have a computed property (filteredMeasurementsByAttribute) that is supposed to provide a filtered version of a data property (measurementsByAttribute). However, the changes on the computed property are also taking place on the data property.
The filter is using the data from selectedAttributes (which are the options selected in drop downs on the front end) and then returning the matching data in measurementsByAttribute. This filter is working properly. The issue is that when I run the clearAttribute method which clears the data from selectedAttributes (this part works successfully) the measurementsByAttribute property is the filtered version so I can't get the old data back.
In short, I want to keep an original version of the measurementsByAttribute property so that I clear the selectedAttributes property and have all the original data available for filteredMeasurementsByAttribute to reset the drop down forms.
I've tried saving the data in a regular javascript variable, measurementsByAttributeMaster, and then setting the measurementsByAttribute to the master. Somehow they all end up just having the filtered values.
I've tried changing the way that I'm looping through the data (e.g. using forEach instead of filter or map) just to see if that was causing it to edit the original data. No luck. I've left the "original" versions using the filter and map in commented out code so you can see both versions.
Any insight or help is very much appreciated.
/**
* Select Product
*/
function filterMeasurements(attribute, measurementsAvailable) {
var filteredMeasurements = [];
attribute.measurements.forEach(function(measurement) {
if (measurementsAvailable.includes(measurement.id.toString())) {
filteredMeasurements.push(measurement);
}
});
// return attribute.measurements.filter(function(measurement) {
// return measurementsAvailable.includes(measurement.id.toString());
// });
return filteredMeasurements;
}
/**
* Check if the part matches the selected Attributes
*
* #param {array} selectedAttributes Array of all the currently selected attributes
* #param {object} part The part we are checking
* #retrun {array} the fitlered selectedAttributes
*/
function checkPartAttributes(selectedAttributes, part) {
var partAttributes = JSON.parse(part.attributes_json);
// Loop through each selected attribute to ensure there aren't any conflicts
return selectedAttributes.every(function(measurementID, attributeID) {
// if no measurement has been selected, it is acceptable
if (measurementID == "") {
return true;
}
// if the part does have this attribute, it needs to match
if (attributeID in partAttributes) {
return partAttributes[attributeID] == measurementID;
}
console.log('here');
// If the part doesn't have this attribute, it is acceptable
return true;
});
}
// var SelectSpecifications = require('./components/SelectProduct/SelectSpecifications.vue');
var vm = new Vue({
el: '#select-product',
components: {
SelectSpecifications
},
data () {
return {
allParts: allParts,
measurementsByAttribute: measurementsByAttribute,
selectedAttributes: selectedAttributes,
}
},
computed: {
/**
* combine all the attributes from the matching parts
*
* #return {array} The filtered attributes
*/
filteredAttributes: function() {
var filteredAttributes = JSON.parse("{}");
console.log('filteredAttributes');
// loop through each matching part and create new array of the matching attributes
this.matchingParts.forEach(function(part) {
var partAttributes = JSON.parse(part.attributes_json);
for (attributeID in partAttributes) {
// Add to the index if already exists
if (attributeID in filteredAttributes) {
filteredAttributes[attributeID].push(partAttributes[attributeID]);
filteredAttributes[attributeID] = [...new Set(filteredAttributes[attributeID])];
// create the index if it doesn't already exist
} else {
var tempArr = [partAttributes[attributeID]];
filteredAttributes[attributeID] = tempArr;
}
}
});
return filteredAttributes;
},
/**
* filter the measurements by selected values
* #return {object} All the filtered measurements sorted by attribute
*/
filteredMeasurementsByAttribute: {
get: function() {
console.log('filteredMeasurementsByAttribute');
var selected = this.selectedAttributes;
var filteredMeasurementsByAttribute = [];
var filteredAttributesKeys = Object.keys(this.filteredAttributes);
var filteredAttributes = this.filteredAttributes;
this.measurementsByAttribute.forEach(function (attribute, index) {
console.log(attribute);
var tempAttribute = attribute;
// filteredMeasurementsByAttribute[index] = tempAttribute;
if (filteredAttributesKeys.includes(tempAttribute.id.toString())) {
var filteredMeasurements = filterMeasurements(tempAttribute, filteredAttributes[tempAttribute.id]);
tempAttribute.measurements = filteredMeasurements;
filteredMeasurementsByAttribute[index] = tempAttribute;
}
});
// return measurementsByAttribute.map(function(attribute) {
// if (filteredAttributesKeys.includes(attribute.id.toString())) {
// attribute.measurements = filterMeasurements(attribute, this.filteredAttributes[attribute.id]);
// return attribute;
// }
// }, this);
return filteredMeasurementsByAttribute;
},
set: function(newMeasurementsByAttribute) {
console.log('setter working!');
this.measurementsByAttribute = newMeasurementsByAttribute;
}
},
// returns matching parts depending on what attributes are selected
matchingParts: function() {
console.log('matchingParts');
return this.allParts.filter(checkPartAttributes.bind(this, this.selectedAttributes));
},
},
methods: {
clearAttribute: function(attributeID) {
this.$set(this.selectedAttributes, attributeID, "");
var tempArray = [];
this.filteredMeasurementsByAttribute = tempArray.concat(measurementsByAttributeMaster);
// this.selectedAttributes.splice(0);
},
kebabTitle: function(title) {
if (title == null) {
return '';
}
return title.replace(/([a-z])([A-Z])/g, "$1-$2")
.replace(/\s+/g, '-')
.toLowerCase();
},
}
});
In JavaScript, setting a variable to an object only copies the reference to the object. In data(), you've initialized this.measurementsByAttribute to a local variable named measurementsByAttribute, so any changes to this.measurementsByAttribute would affect the original variable.
The problem is observed in the getter for filteredMeasurementsByAttribute, which modifies this.measurementsByAttribute and thus the original variable:
filteredMeasurementsByAttribute: {
get: function() {
this.measurementsByAttribute.forEach(function (attribute, index) {
// tempAttribute refers to original object in `this.measurementsByAttribute`
var tempAttribute = attribute;
if (filteredAttributesKeys.includes(tempAttribute.id.toString())) {
// ❌ this changes original object's prop
tempAttribute.measurements = filteredMeasurements;
}
});
//...
}
}
One solution would be to:
Apply Array.prototype.filter() to this.measurementsByAttribute in order to get the measurements that match the filter selection.
and use Array.prototype.map() and the spread operator to shallow-clone the original objects with their measurements prop modified.
filteredMeasurementsByAttribute: {
get: function() {
const filteredAttributesKeys = Object.keys(this.filteredAttributes)
return this.measurementsByAttribute
/* 1 */ .filter(m => filteredAttributesKeys.includes(m.id.toString()))
/* 2 */ .map(m => ({
...m,
measurements: filterMeasurements(m, this.filteredAttributes[m.id])
}))
}
}
You are initiating the change to the original attribute when the computed prop is changed.
// ...
filteredMeasurementsByAttribute: {
// ...
set: function(newMeasurementsByAttribute) {
console.log('setter working!');
this.measurementsByAttribute = newMeasurementsByAttribute;
}
},
//...
I would just have the compute prop apply filter and return the result, but do not have any
set on the compute.
If you want the original attribute but have reactive attributes everywhere in the component, it may help to use a different variable.
const measurementsByAttributeClone = {...measurementsByAttribute};
// any changes to `measurementsByAttribute` will not impact `measurementsByAttributeClone`

How do I monitor changes to a specific field in Firebase database?

I have a database stored in Firebase and wanted to use the 'functions' to check whether a record in the database was > n days old. I understand the
onChange()
function in JavaScript could achieve this for me. Thank you!
In my project, we often use this pattern. Use onChange() to detect changes, and then a helper function to check if the change happened to a specific field. This helper function is for example called checkIfChangedHappenedToAtLeastOneOfProvidedPaths, and you can use it by providing the path to check, for example: checkIfChangedHappenedToAtLeastOneOfProvidedPaths(change.before.data(), change.after.data(), ['daysOld'])
checkIfChangedHappenedToAtLeastOneOfProvidedPaths uses 2 helper functions to achieve this (difference & getDifferenceCombine). Let me know what you think, here is the code (in typescript):
checkIfChangedHappenedToAtLeastOneOfProvidedPaths
/**
* The purpose of this function is to analyze the change between two objects.
* If a change happened to the prvoided path(s), return true, otherwise false,
* Usecase: Use this function if you only care about change at a certain path.
* Example: Run only update function if change happened to "stripe" object, at groups
* #export
* #param {*} oldObject
* #param {*} newObject
* #param {string[]} pathsToCheck
* #returns {boolean}
*/
export function checkIfChangedHappenedToAtLeastOneOfProvidedPaths(oldObject: object, newObject: object, pathsToCheck: string[]): boolean {
const combinedChanges = getDifferenceCombine(oldObject, newObject);
const flatObject = flat(combinedChanges);
let changeHappened = false;
pathsToCheck.forEach((path) => {
const inFlatObject = flatObject[path] != null;
const inCombinedObject = get(combinedChanges, path, null) != null;
if (inFlatObject || inCombinedObject) {
changeHappened = true;
}
});
return changeHappened;
}
Helper functions
export function difference(object: any, base: any): any {
return transform(object, (result, value, key) => {
if (base == null || object == null) {
return;
}
if (!isEqual(value, base[key])) {
result[key] = isObject(value) && isObject(base[key]) ? difference(value, base[key]) : value;
}
});
}
export function getDifferenceCombine(oldObject: object, newObject: object): object {
const diffObject = difference(newObject, oldObject);
const diffObject2 = difference(oldObject, newObject);
return {...diffObject, ...diffObject2};
}
Since everything updates in real time, you can query this specific field if you want to monitor it. For example, lets say you have a child node "createdAt" with a value of a Firebase.ServerValue.TIMESTAMP.
var n = 1452508000000; //random timestamp in milliseconds,
//since this is how firebase saves a server value timestamp or
//this can be Date.now() if you convert your createdAt value
//to whatever date format you prefer.
var ref = firebase.database().ref('path/to/your/data');
ref.orderByChild("createdAt").startAt(n).on("child_added", function(snapshot) {
console.log(snapshot.key);
//will list all keys that were created on or after the timestamp value
});
https://firebase.google.com/docs/reference/js/firebase.database.Query#startAt

Getting undefined when I expect otherwise

if you take a look at the following fiddle, you will see that I have created a class called: EventHandler which I then use as below (and in the fiddle):
/**
* Event Handler Class
*/
class EventHandler {
/**
* Core constructor.
*/
constructor() {
this.events = [];
this.beforeEvents = [];
this.afterEvents = [];
}
/**
* Register an event.
*
* #param {string} name
* #param {cb} callback
*/
register(name, cb) {
const event = {
name: name,
cb: cb
}
this.events.push(event);
}
/**
* Trigger an event based on name.
*
* #param {string} name
* #return {various}
*/
trigger(name) {
this.events.forEach((event) => {
if (event.name === name) {
return event.cb();
}
})
}
/**
* Get only the events.
*
* #return {array} events
*/
getEvents() {
return this.events;
}
}
const eventHandler = new EventHandler();
eventHandler.register('hello.world', () => {
return 'hello world';
});
alert(eventHandler.trigger('hello.world'));
When you run the fiddle, you get undefined, I expect to see 'hello world'.
Ideas?
In the trigger function you are returning undefined. Returning from inside a forEach callback does not return the parent function.
Try changing your code to something like this:
trigger(name) {
let msg = '';
this.events.forEach((event) => {
if (event.name === name) {
msg = event.cb();
}
});
return msg;
}
The problem is that you're alerting the response of eventHandler.trigger which doesn't actually return anything, hence the undefined. You could use something like Array.prototype.find to get find your event and then return the callbacks response like so:
trigger(name) {
let event = this.events.find(e => e.name === name);
if (event)
return e.cb();
return null;
}
A better method would be to store a key => value pair of event and callbacks, this way you won't have to find, you could just do something like:
trigger(event) {
return this.events[event] ? this.events[event]() : null;
}

JSDoc and JavaScript property getters and setters

I was hoping that my code, something like below, could generate documents describing each property of the object literal with JSDoc(v2.4.0), but it did not work. Does anyone know how to work with JSDoc to generate documents from code that uses getter/setter?
/** Enum of days of week. */
var Day = {
/** Sunday. */
get Sun() { return 0; },
/** Monday. */
get Mon() { return 1; },
/** Thuesday. */
get Tue() { return 2; },
/** Wednesday. */
get Wed() { return 3; },
/** Thursday. */
get Thu() { return 4; },
/** Friday. */
get Fri() { return 5; },
/** Saturday. */
get Sat() { return 6; }
}
Use #type to document JavaScript get and set accessors. Something like the following should work with JSDoc:
/**
* Sunday.
* #type {number}
*/
get "Sun"() { return 0; },
/**
* Monday.
* #type {number}
*/
get "Mon"() { return 1; },
This documents each property as a member with a type of number.
You could use jQuery style getter / setter methods:
/**
* Get colour of object
* #returns {mixed}
*//**
* Set colour of object
* #param {mixed} val
* #returns {this}
*/
colour: function(val) {
if (val === undefined)
return this.colour;
else {
this.colour = val;
return this;
}
}
I have just been discussing this very issue with Michael himself. It is possible in jsDoc3 (https://github.com/micmath/jsdoc) due to a very cool feature. It is possible to stack multiple docblocks (one for getter and one for setter):
http://groups.google.com/group/jsdoc-users/browse_thread/thread/d4c7794bc8f6648e/94df7339e1fc4c91#94df7339e1fc4c91

Categories

Resources