How can I streamline/improve/beautify my auto-form JS code? - javascript

I've got a Javascript (dojo) function that I'm using to auto-populate a form. The idea is to pass in a JS object with keys for the form field ids, and values that help define how to fill in the field.
It works, but it feels a bit ugly to me (the switch statement, the objects within objects syntax). Any thoughts on how can I improve* this code?
/**
* Fill in the form with passed in values
*
* #param {Object} defaults
*
* Can be used to pass in default values to the form. Expects an object like this:
* {<field id>: {type: '<field type>', value:'<value>'}
*
* for example:
* {
* paymethod: {type: 'select', value:'Account Transfer'}, // html select
* fundsource: {type: 'combo', value:'Corporation Account'} // dijit.comboBox
* }
*/
function fillDefaults(defaults) {
var props;
for (var field in defaults) {
props = defaults[field];
switch (props['type']) {
// so far only select and combo have been implemented
// others will be added as needed
// and maybe grouped depending on how they work
// (e.g. all dijits together, <input> with <select>, etc.)
case 'select':
dojo.byId(field).value = props['value'];
dojo.byId(field).onchange()
break;
case 'combo':
dijit.byId(field).attr('value', props['value']);
break;
}
}
}
[*] improve: make prettier, more js-like, more dojo-like, more streamlined

What does pretty mean? What's js-like?
A switch, although memory intensive, is cleaner if you plan to extend with more objects.
Maybe, instead of a switch, have an object containing your functions:
funcs = {
select: function(value) {
dojo.byId(field).value = value;
dojo.byId(field).onchange()
},
combo: function(value) {
dijit.byId(field).attr('value', value);
}
};
function payFillDefaults(defaults) {
var props;
for(var field in defaults) {
props = defaults[field];
if(funcs[props['type']]) {
funcs[props['type']](props['value']);
}
}
}

Improved a bit according JSLint standards:
function fillDefaults(defaults) {
var props, field;
for (field in defaults) {
props = defaults[field];
switch (props.type) {
case 'select':
dojo.byId(field).value = props.value;
dojo.byId(field).onchange();
break;
case 'combo':
dijit.byId(field).attr('value', props.value);
break;
}
}
}

Take a look at dojox.form.manager — it does approximately what you want to achieve. It support existing form widgets ("main" mixin), practically all DOM form elements (node mixin), the unified event processing, and so on. It can be used with unmodified forms.
As you would probably guessed already it is structured as a set of independent mix-ins so you can select only required functionality. For convenience and as an example there a class, which combines all mix-ins together: Manager.
Read what it does, see if it fits your needs, if not, study its code and borrow what you like. If it misses something — share your feedback on the mailing list (on gmane.org). Or, if you can improve it, contribute back.

Related

What's the best way to avoid many if statements in Angular component to call specific function?

I'm creating a temperature conversion app in Angular 7, in my formGroup I have an input/output value, and 2 multi selects where the user can select a unit of temperature 'From' and 'To' to convert(celsius, Fahrenheit or Kelvin).
When the user submit the form in my component I get the input data
submitForm(data) {
this.input = data.input;
this.unitInput= data.unitInput;
this.unitTarget= data.unitTarget;
this.output= data.output;
}
I thought in just adding if statements to call the function that will make the conversion, but now looking at all if statements that i have seems a lot and not a good design.
if (this.inputUnit === 'Celsius' && this.targetUnit === 'Fahrenheit') {
this.celsiusToFahrenheitConversion ();
}
if (this.inputUnit === 'Celsius' && this.targetUnit === 'Kelvin') {
this.celsiusToKelvinConversion ();
}
if (this.inputUnit === 'Celsius' && this.targetUnit === 'Rankine') {
this.celsiusToRankineConversion ();
}
If's for Fahrenheit, If's for Kelvin, If's for Rankine, etc..
and what about if in the feature I want to add a different kind of unit?
What would be a better approach?
Thanks in advance
I think the cleanest way to handle this would be using a switch statement. It doesn't get simpler than that, I personally hate it as well but sometimes there is just no way around it really.
switch(this.inputType) {
case 'Celsius':
switch(this.targetUnit) {
case 'Fahrenheit':
this.celsiusToFahrenheitConversion();
break;
case 'Kelvin':
this.celsiusToKelvinConversion();
break;
case 'Rankine':
this.celsiusToRankineConversion();
break;
}
break;
case 'Fahrenheit':
break;
case 'Rankine':
break;
case 'Kelvin':
break;
}
However! This is a mathematical conversion. F° to C° (32°F − 32) × 5/9, F° to Kelvin is (32°F − 32) × 5/9 + 273.15, F° to Rankine is 32°F + 459.67 and so on. And where is math, there is a system. My approach would be creating a conversion table - an object, and use the form values as lookup values. For example:
const magicalLookupTable = {
celsius: {
fahrenheit: () => {
/** Implementation */
},
kelvin: () => {
/** Implementation */
}
},
fahrenheit: {
celsius: () => {
/** Implementation */
},
kelvin: () => {
/** Implementation */
}
}
}
When I look at this I see compound keys set against a function value.
One way to represent this idea is with an typical object where the keys are the compound and they map to a specific function:
const conversionFunctions = {
'celsius-fahrenheit': this.celsiusToFahrenheitConversion,
'celsius-kelvin': this.celsiusToKelvinConversion,
'celsius-rankine': this.celsiusToRankineConversion
};
const key = `${this.inputUnit.toLowerCase()}-${this.targetUnit.toLowerCase()}`;
const convert = conversionFunctions[key];
convert();

Javascript polymorphism without OOP classes

In JS or OOP language the polymorhpism is created by making different types.
For example:
class Field {...}
class DropdownField extends Field {
getValue() {
//implementation ....
}
}
Imagine I have library forms.js with some methods:
class Forms {
getFieldsValues() {
let values = [];
for (let f of this.fields) {
values.push(f.getValue());
}
return values;
}
}
This gets all field values. Notice the library doesnt care what field it is.
This way developer A created the library and developer B can make new fields: AutocompleterField.
He can add methods in AutocompleterField withouth changing the library code (Forms.js) .
If I use functional programming method in JS, how can I achieve this?
If I dont have methods in object i can use case statements but this violates the principle. Similar to this:
if (field.type == 'DropdownField')...
else if (field.type == 'Autocompleter')..
If developer B add new type he should change the library code.
So is there any good way to solve the issue in javascript without using object oriented programming.
I know Js isnt exactly OOP nor FP but anyway.
Thanks
JavaScript being a multi-purpose language, you can of course solve it in different ways. When switching to functional programming, the answer is really simple: Use functions! The problem with your example is this: It is so stripped down, you can do exactly the same it does with just 3 lines:
// getValue :: DOMNode -> String
const getValue = field => field.value;
// readForm :: Array DOMNode -> Array String
const readForm = formFields => formFields.map(getValue);
readForm(Array.from(document.querySelectorAll('input, textarea, select')));
// -> ['Value1', 'Value2', ... 'ValueN']
The critical thing is: How is Field::getValue() implemented, what does it return? Or more precisely: How does DropdownField::getValue() differ from AutocompleteField::getValue() and for example NumberField::getValue()? Do all of them just return the value? Do they return a pair of name and value? Do they even need to be different?
The question is therefor, do your Field classes and their inheriting classes differ because of the way their getValue() methods work or do they rather differ because of other functionality they have? For example, the "autocomplete" functionality of a textfield isn't (or shouldn't be) tied to the way the value is taken from it.
In case you really need to read the values differently, you can implement a function which takes a map/dictionary/object/POJO of {fieldtype: readerFunction} pairs:
/* Library code */
// getTextInputValue :: DOMNode -> String
const getTextInputValue = field => field.value;
// getDropdownValue :: DOMNode -> String
const getDropdownValue = field => field.options[field.selectedIndex].value;
// getTextareaValue :: DOMNode -> String
const getTextareaValue = field => field.textContent;
// readFieldsBy :: {String :: (a -> String)} -> DOMNode -> Array String
readFieldsBy = kv => form => Object.keys(kv).reduce((acc, k) => {
return acc.concat(Array.from(form.querySelectorAll(k)).map(kv[k]));
}, []);
/* Code the library consumer writes */
const readMyForm = readFieldsBy({
'input[type="text"]': getTextInputValue,
'select': getDropdownValue,
'textarea': getTextareaValue
});
readMyForm(document.querySelector('#myform'));
// -> ['Value1', 'Value2', ... 'ValueN']
Note: I intentionally didn't mention things like the IO monad here, because it would make stuff more complicated, but you might want to look it up.
In JS or OOP language the polymorhpism is created by making different types.
Yes. Or rather, by implementing the same type interface in different objects.
How can I use Javascript polymorphism without OOP classes
You seem to confuse classes with types here. You don't need JS class syntax to create objects at all.
You can just have
const autocompleteField = {
getValue() {
…
}
};
const dropdownField = {
getValue() {
…
}
};
and use the two in your Forms instance.
Depends on what you mean by "polymorphism". There's the so-called ad-hoc polymorphism which type classes in Haskell, Scala, or PureScript provide -- and this kind of dispatch is usually implemented by passing witness objects along as additional function arguments, which then will know how to perform the polymorphic functionality.
For example, the following PureScript code (from the docs), which provides a show function for some types:
class Show a where
show :: a -> String
instance showString :: Show String where
show s = s
instance showBoolean :: Show Boolean where
show true = "true"
show false = "false"
instance showArray :: (Show a) => Show (Array a) where
show xs = "[" <> joinWith ", " (map show xs) <> "]"
example = show [true, false]
It gets compiled to the following JS (which I shortened):
var Show = function (show) {
this.show = show;
};
var show = function (dict) {
return dict.show;
};
var showString = new Show(function (s) {
return s;
});
var showBoolean = new Show(function (v) {
if (v) {
return "true";
};
if (!v) {
return "false";
};
throw new Error("Failed pattern match at Main line 12, column 1 - line 12, column 37: " + [ v.constructor.name ]);
});
var showArray = function (dictShow) {
return new Show(function (xs) {
return "[" + (Data_String.joinWith(", ")(Data_Functor.map(Data_Functor.functorArray)(show(dictShow))(xs)) + "]");
});
};
var example = show(showArray(showBoolean))([ true, false ]);
There's absolutely no magic here, just some additional arguments. And at the "top", where you actually know concrete types, you have to pass in the matching concrete witness objects.
In your case, you would pass around something like a HasValue witness for different forms.
You could use a the factory pattern to ensure you follow the open close principle.
This principle says "Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification".
class FieldValueProviderFactory {
getFieldValue(field) {
return this.providers.find(p => p.type === field.type).provider(field);
}
registerProvider(type, provider) {
if(!this.providers) {
this.providers = [];
}
this.providers.push({type:type, provider:provider});
}
}
var provider = new FieldValueProviderFactory();
provider.registerProvider('DropdownField', (field) => [ 1, 2, 3 ]);
provider.registerProvider('Autocompleter', (field) => [ 3, 2, 1 ]);
class FieldCollection {
getFieldsValues() {
this.fields = [ { type:'DropdownField',value:'1' }, { type:'Autocompleter',value:'2' } ];
let values = [];
for (let field of this.fields) {
values.push(provider.getFieldValue(field));
}
return values;
}
}
Now when you want to register new field types you can register a provider for them in the factory and don't have to modify your field code.
new Field().getFieldsValues();

Getting form controls from FormController

I need a way to loop through the registered controls of an AngularJS form. Essentially, I'm trying to get all the $dirty controls, but there's no array of the controls (the FormController has a number of different properties/functions in addition to containing the controls themselves - each as its' own object).
I've been looking at the source code, and I see that there is a controls array in the FormController that is exactly the array I'm looking for. Is there a way to get access to this value, or extend the FormController to include a function that returns this controls array?
Edit: Plnkr demo
Also, I realized that technically I could check the first character in the key string for "$", but I'd like to avoid that in case the FormController/directive changes in a future version of Angular.
Edit 2: Another clarification: My goal in all of this is to be able to determine which specific fields are $dirty, whether by looping through the entire list of controls (not including the $dirty, $invalid, $error, $name, and other properties that live in the Form object as it is) or by extending the FormController and creating a function that returns only the controls which are currently dirty (and not equal to their starting values)
Edit 3: The solution I'm looking for needs to be applicable to forms/models of different structures. The models on the scope are generated via AJAX, so their structure is already set (I'd like to avoid having to hardcode a new structure for all the data I'm already receiving via AJAX). Also, I'm looking to use this form submission process across multiple forms/models, and each of them have differing JSON structures - as they apply to different entities in our Object Model. This is why I've chosen to ask for a way to get access to the controls object in the FormController (I'll post the code from FormController below), because it's the only place where I can get a flat array of all of my fields.
function FormController(element, attrs) {
var form = this,
parentForm = element.parent().controller('form') || nullFormCtrl,
invalidCount = 0, // used to easily determine if we are valid
errors = form.$error = {},
controls = [];
// init state
form.$name = attrs.name || attrs.ngForm;
form.$dirty = false;
form.$pristine = true;
form.$valid = true;
form.$invalid = false;
parentForm.$addControl(form);
// Setup initial state of the control
element.addClass(PRISTINE_CLASS);
toggleValidCss(true);
// convenience method for easy toggling of classes
function toggleValidCss(isValid, validationErrorKey) {
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
element.
removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
}
/**
* #ngdoc function
* #name ng.directive:form.FormController#$addControl
* #methodOf ng.directive:form.FormController
*
* #description
* Register a control with the form.
*
* Input elements using ngModelController do this automatically when they are linked.
*/
form.$addControl = function(control) {
controls.push(control);
if (control.$name && !form.hasOwnProperty(control.$name)) {
form[control.$name] = control;
}
};
/**
* #ngdoc function
* #name ng.directive:form.FormController#$removeControl
* #methodOf ng.directive:form.FormController
*
* #description
* Deregister a control from the form.
*
* Input elements using ngModelController do this automatically when they are destroyed.
*/
form.$removeControl = function(control) {
if (control.$name && form[control.$name] === control) {
delete form[control.$name];
}
forEach(errors, function(queue, validationToken) {
form.$setValidity(validationToken, true, control);
});
arrayRemove(controls, control);
};
// Removed extra code
}
As you can see, the form itself has the controls array privately available. I'm wondering if there's a way for me to extend the FormController so I can make that object public? Or create a public function so I can at least view the private array?
For a direct solution to the question, modify #lombardo's answer like so;
var dirtyFormControls = [];
var myForm = $scope.myForm;
angular.forEach(myForm, function(value, key) {
if (typeof value === 'object' && value.hasOwnProperty('$modelValue') && value.$dirty)
dirtyFormControls.push(value)
});
The array 'dirtyFormControls' will then contain the form controls that are dirty.
You can also use this trick to show error messages on form submission for 'Required' validations and all others. In your submit() function you will do something like;
if (form.$invalid) {
form.$setDirty();
angular.forEach(form, function(value, key) {
if (typeof value === 'object' && value.hasOwnProperty('$modelValue'))
value.$setDirty();
});
//show user error summary at top of form.
$('html, body').animate({
scrollTop: $("#myForm").offset().top
}, 1000);
return;
}
And in your form you will show error messages with
<span ng-messages="myForm['subject-' + $index].$error" ng-show="myForm['subject-' + $index].$dirty" class="has-error">
<span ng-message="required">Course subject is required.</span>
</span>
The above solution is useful when you have dynamically generated controls using 'ng-repeat' or something similar.
You can use the following code to iterate the controls:
var data = {};
angular.forEach(myForm, function (value, key) {
if (value.hasOwnProperty('$modelValue'))
data[key] = value.$modelValue;
});
try simply with from within your controller:
$scope.checkForm = function(myFormName){
console.log(myFormName.$invalid);
}
UPDATE:
<div ng-controller="MyController">
<form name="form" class="css-form" novalidate>
<input type="text" ng-model="user.uname" name="uname" required /><br />
<input type="text" ng-model="user.usurname" name="usurname" required /><br />
<button ng-click="update(form)">SAVE</button>
</form>
</div>
app.controller('MyController',function($scope){
$scope.user = {};
$scope.update = function (form){
var editedFields = [];
angular.forEach($scope.user, function(value, key){
if(form[key].$dirty){
this.push(key + ': ' + value);
}
}, editedFields);
console.log(editedFields);
}
});
I've come up with a somewhat decent solution, but it still isn't what I was looking for. I've salvaged some code from another problem involving creating JSON objects from strings, and come up with the following:
Essentially I'm naming my fields in the same way they're tied to the model, and then building a new object for submission when the form_submit is called.
Plnkr demo
In the demo, if you change either of the form fields, then hit submit, you'll see the object pop up with only the dirty values.

Javascript: How to build a method dynamically from string data?

I have an XML document that defines a task, which is a list of actions to be performed on certain data. I need to convert this "task list" to a Javascript method which can be called at some later time, which in turn calls a series of pre-defined methods, with the appropriate data. How would you achieve this?
Important Clarification:
I'm not worried about the XML parsing. I'm more interested in how to actually build the Task Method, including binding the essential data to the pre-defined action methods. That's the part I'm struggling with.
Edit: I've revised my example to make it a bit more interesting, and hopefully a bit clearer.
XML:
<task id="enter-castle">
<if holding="castle-key">
<print message="You unlock the castle door and enter." />
<destroy item="castle-key" />
<goto location="castle" />
<else>
<print message="The castle door is locked." />
</else>
</if>
</task>
Javascript:
Game = {
print: function(message) {
// display message
},
destroy: function(item) {
// destroy the object
},
goto: function(location) {
// change player location
},
ifHolding: function(item) {
// return true if player has item
}
};
parseTask(taskNode) {
var taskId = taskNode.getAttribute('id');
// What goes here??
Game.tasks[taskId] = /* ??? */;
}
When I call parseTask() on the <task id="enter-castle"> node, this should create a function that, in effect, does the following when called:
Game.tasks.enterCastle = function() {
if (Game.ifHolding('castle-key')) {
Game.print("You unlock the castle door and enter.");
Game.destroy('castle-key');
Game.goto('castle');
} else {
Game.print("The castle door is locked.");
}
}
What you want are closures.
function createMethod(arguments) {
var task = doSomethingWithYour(arguments);
return function(xmlData) { // <- this is the fundamental part
// do the task with your data
// the "task" vars are still available
// even if the returned function is executed in a different context
}
}
This allows you to create an own method for each task. Don't use the Function constructor or eval.
This is a situation where JavaScript's eval() function will make your life much easier. You can easily build a JavaScript source string matching your desired one and evaluate it to assign the function to the desired property of your Game object.
Of course, there are drawbacks to using "eval", which I won't explore in this answer since you can find countless justifications for why not to use it on the web. However, building and evaluating a simple JS source string will be much easier in the short term than say, a closure based solution, despite any potential drawbacks of performance and security. Moreover, the "eval" based solution will be easy to test since you can simply inspect the source string before it is evaluated.
So try something like this:
function buildTaskFunction(taskXml) {
var source='', depth=0, node /*a visitor to each DOM node*/;
// foreach (node in traverseInOrder(taskXml)) {
switch (node.nodeName) {
case 'TASK':
source += 'Game.tasks.' + makeFunctionName(node.id) + '= function(){';
depth++;
break;
case 'IF':
source += 'if(' + getConditionalAttribute(node) + '){'
depth++;
break;
case 'ELSE':
source += '}else{';
break;
case 'DESTROY':
source += 'Game.destroy("' + node.getAttribute('item') + '");'
break;
case 'PRINT':
source += 'Game.print("' + node.getAttribute('message') + '");'
break;
// case etc...
default: throw new Error('unhandled node type "' + node.nodeName + '"');
}
// end "foreach node".
while (depth-- > 0) { // You'll need to account for nested "if"s somehow...
source += '}';
}
eval(source);
}
And again, there are many potential problems (not definitive ones) with using "eval", so please do read about and try to understand them in the context of your solution. Use your own judgement when deciding if the drawbacks are worth the benefits in your own program -- just because a tool can be dangerous doesn't mean you should not use it.
Example using dojo:
dojo.require("dojox.xml.parser");
dojo.ready(function(){
// Parse text and generate an XML DOM
var xml = "<tnode><node>Some Text</node><node>Some Other Text</node></tnode>";
var dom = dojox.xml.parser.parse(xml);
var docNode = dom.documentElement();
// ...
}
The remainder of the function is non-trivial, but would largely just consist of attribute lookup using ['<attribute-name>'] and child node iteration using dojo.forEach(<node>.childNodes, function(childNode) { /* do stuff */ });

Should we validate method arguments in JavaScript API's?

I'm developing a JavaScript library that will be used by 3rd party developers.
The API includes methods with this signature:
function doSomething(arg1, arg2, options)
arg1, arg2 are 'required' simple type arguments.
options is a hash object containing optional arguments.
Would you recommend to validate that:
- argument types are valid?
- options attributes are correct? For example: that the developer didn't pass by mistake onSucces instead of onSuccess?
why do popular libraries like prototype.js do not validate?
You have the right to decide whether to make a "defensive" vs. a "contractual" API. In many cases, reading the manual of a library can make it clear to it's user that he should provide arguments of this or that type that obey these and those constraints.
If you intend to make a very intuitive, user friendly, API, it would be nice to validate your arguments, at least in debug mode. However, validation costs time (and source code => space), so it may also be nice to leave it out.
It's up to you.
Validate as much as you can and print useful error messages which help people to track down problems quickly and easily.
Quote this validation code with some special comments (like //+++VALIDATE and //--VALIDATE) so you can easily remove it with a tool for a high-speed, compressed production version.
Thanks for the detailed answers.
Below is my solution - a utility object for validations that can easily be extended to validate basically anything...
The code is still short enough so that I dont need to parse it out in production.
WL.Validators = {
/*
* Validates each argument in the array with the matching validator.
* #Param array - a JavaScript array.
* #Param validators - an array of validators - a validator can be a function or
* a simple JavaScript type (string).
*/
validateArray : function (array, validators){
if (! WL.Utils.isDevelopmentMode()){
return;
}
for (var i = 0; i < array.length; ++i ){
WL.Validators.validateArgument(array[i], validators[i]);
}
},
/*
* Validates a single argument.
* #Param arg - an argument of any type.
* #Param validator - a function or a simple JavaScript type (string).
*/
validateArgument : function (arg, validator){
switch (typeof validator){
// Case validation function.
case 'function':
validator.call(this, arg);
break;
// Case direct type.
case 'string':
if (typeof arg !== validator){
throw new Error("Invalid argument '" + Object.toJSON(arg) + "' expected type " + validator);
}
break;
}
},
/*
* Validates that each option attribute in the given options has a valid name and type.
* #Param options - the options to validate.
* #Param validOptions - the valid options hash with their validators:
* validOptions = {
* onSuccess : 'function',
* timeout : function(value){...}
* }
*/
validateOptions : function (validOptions, options){
if (! WL.Utils.isDevelopmentMode() || typeof options === 'undefined'){
return;
}
for (var att in options){
if (! validOptions[att]){
throw new Error("Invalid options attribute '" + att + "', valid attributes: " + Object.toJSON(validOptions));
}
try {
WL.Validators.validateArgument(options[att], validOptions[att]);
}
catch (e){
throw new Error("Invalid options attribute '" + att + "'");
}
}
},
};
Heres a few examples of how I use it:
isUserAuthenticated : function(realm) {
WL.Validators.validateArgument(realm, 'string');
getLocation: function(options) {
WL.Validators.validateOptions{
onSuccess: 'function',
onFailure: 'function'}, options);
makeRequest : function(url, options) {
WL.Validators.validateArray(arguments, ['string',
WL.Validators.validateOptions.carry({
onSuccess : 'function',
onFailure : 'function',
timeout : 'number'})]);
We have to discover and eliminate problems as soon as possible. If don't use TypeScript or Flow, you rather do it with a validation lib. It will help you avoiding spending hours hunting obscure errors caused by invalid types given as arguments. It looks like many take it serious - https://www.npmjs.com/package/aproba gets currently 9M(!) downloads per week.
To me it doesn't suite, explained here http://dsheiko.com/weblog/validating-arguments-in-javascript-like-a-boss
I go with https://www.npmjs.com/package/bycontract that based on JSDoc expressions:
import { validate } from "bycontract";
const PdfOptionsType = {
scale: "?number"
}
function pdf( path, w, h, options, callback ) {
validate( arguments, [
"string",
"!number",
"!number",
PdfOptionsType,
"function=" ] );
//...
return validate( returnValue, "Promise" );
}
pdf( "/tmp/test.pdf", 1, 1, { scale: 1 } ); // ok
pdf( "/tmp/test.pdf", "1", 1, { scale: 1 } ); // ByContractError: Argument #1: expected non-nullable but got string
On methods you can just reuse existing JSDoc comment block:
import { validateJsdoc, typedef } from "bycontract";
typedef("#PdfOptionsType", {
scale: "number"
});
class Page {
#validateJsdoc(`
#param {string} path
#param {!number} w
#param {!number} h
#param {#PdfOptionsType} options
#param {function=} callback
#returns {Promise}
`)
pdf( path, w, h, options, callback ) {
return Promise.resolve();
}
}
However I keep this validation on dev/test environments, but skip it on live:
import { config } from "bycontract";
if ( process.env.NODE_ENV === "production" ) {
config({ enable: false });
}
It depends. How big this library would be? It is said that typed languages are better for big projects with complex API. Since JS is to some extent hybrid, you can choose.
About validation - I don't like defensive programming, the user of the function shall be obliged to pass valid arguments. And in JS size of code matters.
An intermediate way would be to return a reasonable default value (e.g. null) when required arguments are missing. In this way, the user's code will fail, not yours. And it will probably be easier for them to figure out what is the issue in their code rather than in yours.
When I've developed APIs like these in the past, I've validated anything that I feel is a "major" requirement - in your example, I'd verify the first two arguments.
As long as you specify sensible defaults, it should be pretty simple for your user to determine that "optional" arguments aren't specified correctly, since it won't make any change to the application, but everything will still work properly.
If the API is complex, I'd suggest following Aaron's advice - add comments that can be parsed by a compressor around your validation so the developers get the benefit of validation, but can extract the extra dead weight when pushing the code into production.
EDIT:
Here're some examples of what I like to do in the cases where validation is necessary. This particular case is pretty simple; I probably wouldn't bother with validation for it, since it really is trivial. Depending on your needs, sometimes attempting to force types would be better than validation, as demonstrated with the integer value.
Assume extend() is a function that merges objects, and the helper functions exist:
var f = function(args){
args = extend({
foo: 1,
bar: function(){},
biz: 'hello'
}, args || {});
// ensure foo is an int.
args.foo = parseInt(args.foo);
//<validation>
if(!isNumeric(args.foo) || args.foo > 10 || args.foo < 0){
throw new Error('foo must be a number between 0 and 10');
}
if(!isFunction(args.bar)){
throw new Error('bar must be a valid function');
}
if(!isString(args.biz) || args.biz.length == 0){
throw new Error('biz must be a string, and cannot be empty');
}
//</validation>
};
EDIT 2:
If you want to avoid common misspellings, you can either 1) accept and re-assign them or 2) validate the argument count. Option 1 is easy, option 2 could be done like this, although I'd definitely refactor it into its own method, something like Object.extendStrict() (example code works w/ prototype):
var args = {
ar: ''
};
var base = {
foo: 1,
bar: function(){},
biz: 'hello'
};
// save the original length
var length = Object.keys(base).length;
// extend
args = Object.extend(base, args || {});
// detect if there're any extras
if(Object.keys(args).length != length){
throw new Error('Invalid argument specified. Please check the options.')
}
Don't validate. More code is more code the user has to download, so it's a very real cost on the user and production systems. Argument errors are easy enough to catch by the developer; don't burden the user with such.

Categories