JSDoc and JavaScript property getters and setters - javascript

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

Related

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

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 :)

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;
}

Clarification on basics of OOP in JS and understanding variable scope in objects

I am trying to understand Magento 2's way of using knockoutJs and requireJs. This has brought me to a wider question of trying to understand how variable scope works within objects?
Look at this code picked up directly from Magento 2:
define([
'ko',
'jquery',
'Magento_Ui/js/lib/knockout/template/loader',
'mage/template'
], function (ko, $, templateLoader, template) {
'use strict';
var blockLoaderTemplatePath = 'ui/block-loader',
blockContentLoadingClass = '_block-content-loading',
blockLoader,
blockLoaderClass,
loaderImageHref;
templateLoader.loadTemplate(blockLoaderTemplatePath).done(function (blockLoaderTemplate) {
blockLoader = template($.trim(blockLoaderTemplate), {
loaderImageHref: loaderImageHref
});
blockLoader = $(blockLoader);
blockLoaderClass = '.' + blockLoader.attr('class');
});
/**
* Helper function to check if blockContentLoading class should be applied.
* #param {Object} element
* #returns {Boolean}
*/
function isLoadingClassRequired(element) {
var position = element.css('position');
if (position === 'absolute' || position === 'fixed') {
return false;
}
return true;
}
/**
* Add loader to block.
* #param {Object} element
*/
function addBlockLoader(element) {
element.find(':focus').blur();
element.find('input:disabled, select:disabled').addClass('_disabled');
element.find('input, select').prop('disabled', true);
if (isLoadingClassRequired(element)) {
element.addClass(blockContentLoadingClass);
}
element.append(blockLoader.clone());
}
/**
* Remove loader from block.
* #param {Object} element
*/
function removeBlockLoader(element) {
if (!element.has(blockLoaderClass).length) {
return;
}
element.find(blockLoaderClass).remove();
element.find('input:not("._disabled"), select:not("._disabled")').prop('disabled', false);
element.find('input:disabled, select:disabled').removeClass('_disabled');
element.removeClass(blockContentLoadingClass);
}
return function (loaderHref) {
loaderImageHref = loaderHref;
ko.bindingHandlers.blockLoader = {
/**
* Process loader for block
* #param {String} element
* #param {Boolean} displayBlockLoader
*/
update: function (element, displayBlockLoader) {
element = $(element);
if (ko.unwrap(displayBlockLoader())) {
addBlockLoader(element);
} else {
removeBlockLoader(element);
}
}
};
};
});
If someone could explain the OOP concept at play here and how these functions are part of a single class would be great.Also, any pointers on classes and objects in JS would be great!

Check if html element is supported

How to check if html element is supported, datalist for example? MDC says it is supoorted only in Opera and FireFox.
if ('options' in document.createElement('datalist')) {
// supported!
}
http://diveintohtml5.info/everything.html#datalist
If someone needs to check for support of other HTML5 elements this could also be used.
https://github.com/ryanmorr/is-element-supported
From http://ryanmorr.com/determine-html5-element-support-in-javascript/
/*
* isElementSupported
* Feature test HTML element support
* #param {String} tag
* #return {Boolean|Undefined}
*/
(function(win){
'use strict';
var toString = {}.toString;
win.isElementSupported = function isElementSupported(tag) {
// Return undefined if `HTMLUnknownElement` interface
// doesn't exist
if (!win.HTMLUnknownElement) {
return undefined;
}
// Create a test element for the tag
var element = document.createElement(tag);
// Check for support of custom elements registered via
// `document.registerElement`
if (tag.indexOf('-') > -1) {
// Registered elements have their own constructor, while unregistered
// ones use the `HTMLElement` or `HTMLUnknownElement` (if invalid name)
// constructor (http://stackoverflow.com/a/28210364/1070244)
return (
element.constructor !== window.HTMLUnknownElement &&
element.constructor !== window.HTMLElement
);
}
// Obtain the element's internal [[Class]] property, if it doesn't
// match the `HTMLUnknownElement` interface than it must be supported
return toString.call(element) !== '[object HTMLUnknownElement]';
};
})(this);
Check if the browser support the HTMLDataListElement interface:
if(typeof HTMLDataListElement === 'function') {
// your code here
} else {
// your code here if this feature is not supported
}
This should works, including elements that become HTMLElement rather than HTML{Tag}Element (like <nav> and <ruby>); and also detects custom elements.
/**
*
* #param {string} tag
*/
export function tryCreateElement(tag) {
const element = document.createElement(tag)
const repr = element.toString()
const output = {
element,
tag,
type: 'standard',
isValid: repr === '[object HTMLUnknownElement]' ? null : true
}
if (
[
'annotation-xml',
'color-profile',
'font-face',
'font-face-src',
'font-face-uri',
'font-face-format',
'font-face-name',
'missing-glyph'
].includes(tag)
) {
// These are not valid customElement identifiers
// But if not defined, they will be '[object HTMLUnknownElement]', anyway.
} else if (tag.includes('-')) {
output.type = 'customElement'
if (repr === '[object HTMLElement]') {
output.isValid = false
}
}
return output
}
{ type: 'standard', isValid: null } is an unsupported standard element.
About that repo in the answer, it seems that the code is simply
/*
* Feature test HTML element support
*
* #param {String} tag
* #return {Boolean|Undefined}
*/
export default function isElementSupported(tag) {
return {}.toString.call(document.createElement(tag)) !== '[object HTMLUnknownElement]';
}
It did work well other than that it doesn't invalidate custom elements.

Categories

Resources