dojo/_base/lang¶
since: | v0.9 |
---|
dojo/_base/lang contains functions for supporting Polymorphism and other language constructs that are fundamental to the rest of the toolkit.
Usage¶
As with the rest of dojo/_base
modules, if you are running the Dojo loader in legacy mode (async: false
) this
module is automatically loaded. Even if it is automatically loaded, you should require it in to have access to its
features:
require(["dojo/_base/lang"], function(lang){
// lang now contains the module features
});
Features¶
clone()¶
Clones objects and/or nodes, returning a new anything, versus a reference. Pass something to clone()
, and a new
version of that something will be made:
require(["dojo/_base/lang"], function(lang){
// clone an object
var obj = { a:"b", c:"d" };
var thing = lang.clone(obj);
// clone an array
var newarray = lang.clone(["a", "b", "c"]);
});
Usage¶
Often times, you want to clone a DOM Node. The easiest way to locate a DOM Node is
dojo/dom::byId, though consideration to change the id
after cloning is required (IDs are
unique to the document, and should be used as such).
require(["dojo/_base/lang", "dojo/dom", "dojo/dom-attr"], function(lang, dom, attr){
var node = dom.byId("someNode");
var newnode = lang.clone(node);
attr.set(newnode, "id", "someNewId");
});
If you have a pointer to some node already, or want to avoid IDs all together, dojo/query() may be useful:
require(["dojo/_base/lang", "query()", "dojo/dom-construct", "dojo/_base/window"], function(lang, query, ctr, win){
// get a reference to some node
var n = query(".someNode")[0];
// create 10 clones of this node and append to body
var i = 10;
while(i--){
ctr.place(lang.clone(n), win.body());
}
});
clone()
is always “deep”. Cyclic (e.g., circular or DAG) cases are explicitly not supported due to speed and space
concerns.
- If you want a shallow copy of an object:
y = lang.mixin({}, x)
; - If you want a shallow copy of an array:
y = arrayUtil.map(x, "return value;")
; - The rest will be covered by the deep copy:
y = lang.clone(x)
;
TODOC clone and event objects.
delegate()¶
Returns a new object which “looks” at the passed object for properties which it does not have a value for, or takes a set of properties to seed the returned object with initially.
This is a subset of implementation of the Boodman/Crockford delegation pattern in JavaScript. An intermediate object
constructor mediates the prototype chain for the returned object, using it to delegate down to the supplied object for
property lookup when object-local lookup fails. This can be thought of similarly to ES4’s wrap()
, except that it
does not act on types but rather on pure objects.
require(["dojo/_base/lang", function(lang){
var myNewObject = lang.delegate(anOldObject, { myNewProperty: "value or text"});
});
The signature of the method is:
Name | Type | Description |
---|---|---|
obj | Object | The object to delegate to for properties not found directly on the return object or in props . |
props | Object... | An object containing properties to assign to the returned object. |
Usage¶
require(["dojo/_base/lang", function(lang){
var anOldObject = { bar: "baz" };
var myNewObject = lang.delegate(anOldObject, { thud: "xyzzy"});
myNewObject.bar == "baz"; // delegated to anOldObject
anOldObject.thud == undefined; // by definition
myNewObject.thud == "xyzzy"; // mixed in from props
anOldObject.bar = "thonk";
myNewObject.bar == "thonk"; // still delegated to anOldObject's bar
});
exists()¶
Check if all objects in a dot-separated string object path exist, such as "A.B.C"
.
exists()
is a convenience function, particularly useful for testing long object paths. It accepts a string as its
first parameter, and walks down the path it represents. You can optionally provide a root for the path as a second
parameter, otherwise it will use a default value of the global object. Each portion of the .
delimited string is
tested for defined-ness, returning true only if each object exists as defined in the strong.
require(["dojo/_base/lang"], function(lang){
if( lang.exists("myns.widget.Foo") ){
console.log("myns.widget.Foo exists");
}
});
The second root
parameter is optional, exists()
will use the value of
dojo/_base/kernel::global by default (which is usually the current window
). You
can use it to root the path in a different window object, or a particular namespace:
require(["dojo/_base/lang", "dijit/dijit"], function(lang, dijit){
var widgetType = "form.Button";
var myNamespace = docs;
if( lang.exists(widgetType, myNamespace) ){
console.log("There's a docs.form.Button available");
}else if( lang.exists(widgetType, dijit) ){
console.log("Dijits form.Button class is available");
}else{
console.log("No form.Button classes are available");
}
});
extend()¶
extend()
works much like mixin(), though works directly on an object’s prototype. extend()
mixes members
from the right-most object into the first object, modifying the object directly.
This can be used to extend functionality into existing classes. Consider the following:
require(["dojo/_base/lang", "dijit/TitlePane"], function(lang, TitlePane){
lang.extend(TitlePane, {
randomAttribute:"value"
});
});
The way the dojo/parser works, a custom attribute on the node will be recognized, as in the
interest of performance, only declared members are mixed as part of the parsing process. Before the above extend()
call, this sample would not recognize the follow markup:
<div data-dojo-type="dijit/TitlePane" data-dojo-props="randomAttribute:'newValue'"></div>
After the extend, any new instances of a dijit/TitlePane
will have the randomAttribute
member mixed into the
instance. extend()
affects all future instances of a class or prototyped Object.
Extending dijit/_WidgetBase¶
A potentially confusing result of the above actually provides us a lot of flexibility. All Dijit widgets inherit from
dijit/_WidgetBase in one way or another. Some widgets, like the
dijit/layout/BorderContainer can contain arbitrary widgets, though require a
region
parameter on the contained widget, though rather than manually adding a region
parameter to each
declaration across Dijit, the BorderContainer simply extends dijit/_WidgetBase
with the member, and anyone using
any widget within a BorderContainer can specify a region
:
require(["dojo/_base/lang", "dijit/_WidgetBase"], function(lang, _WidgetBase){
lang.extend(_WidgetBase, {
region: "center"
});
});
The side-effect of this is a documentation nightmare. Now every widget appears to have a region variable, when in fact it is just there for the benefit of BorderContainer. As a side note, this have been addressed in the API Viewer and other documentation as “extension” properties, methods and events and can easily identified and filter out.
extend() vs. mixin()¶
require(["dojo/_base/lang", "dojo/json"], function(lang, json){
// define a class
var myClass = function(){
this.defaultProp = "default value";
};
myClass.prototype = {};
console.log("the class (unmodified):", json.stringify(myClass.prototype));
// extend the class
lang.extend(myClass, {"extendedProp": "extendedValue"});
console.log("the class (modified with lang.extend):", json.stringify(myClass.prototype));
var t = new myClass();
// add new properties to the instance of our class
lang.mixin(t, {"myProp": "myValue"});
console.log("the instance (modified with lang.mixin):", json.stringify(t));
});
getObject()¶
getObject()
returns the property of an object from a dot-separated string such as A.B.C
.
The simplest way to use getObject()
is to pass a dot-separated string as shown below:
require(["dojo/_base/lang"], require(lang){
// define an object (intentionally global to demonstrate)
foo = {
bar: "some value"
};
lang.getObject("foo.bar"); // returns "some value"
});
getObject()
also takes an optional boolean parameter which, if true
, will create the property if it does not exist. Any other properties along the path will also be created along the way. The default value is false
.
require(["dojo/_base/lang"], function(lang){
// define an object (intetionally global to demonstrate)
foo = {
bar: "some value"
};
// get the "foo.baz" property, create it if it doesn't exist
lang.getObject("foo.baz", true); // returns foo.baz - an empty object {}
/*
foo == {
bar: "some value",
baz: {}
}
*/
});
You can also pass an object as the third parameter. This will define the context in which to search for the property. By default, the context is dojo/_base/kernel::global.
require(["dojo/_base/lang"], function(lang){
// define an object
var foo = {
bar: "some value"
};
// get the "bar" property of the foo object
lang.getObject("bar", false, foo); // returns "some value"
});
hitch()¶
hitch()
returns a function that will execute a given function in a given context. This function allows you to control how a function executes, particularly in asynchronous operations. Sometimes code will be written like this:
require(["dojo/on"], function(on){
var processEvent = function(e){
this.something = "else";
};
on(something, "click", processEvent);
});
Only to have it fail with a cryptic error about an unresolved variable? Why does that occur? Well, because in asynchronous callbacks such as above, the context that the code is executing in has changed. It will no longer refer to the object that originally provided it, but its context will now refer to the enclosing object, the callback. To get around this, you can use hitch()
to force the function to retain its original context. The same code done properly will look like:
require(["dojo/on", "dojo/_base/lang"], function(on, lang){
var processEvent = function(e){
this.something = "else";
};
on(something, "click", lang.hitch(this, processEvent));
});
And now when the event fires and runs the function, this
will refer to the context that is expected.
Examples¶
A simple example.
require(["dojo/_base/lang"], function(lang){
var myObj = {
foo: "bar"
};
var func = lang.hitch(myObj, function(){
console.log(this.foo);
});
func();
});
Looking in the console, bar
should be printed. That is because the scope provided in hitch()
was myObj
, so
inside the function, this
refers to myObj
.
To call a method in a given context that is already in scope, just the method name as a string can be passed as the second argument:
Passing method name as string.
require(["dojo/_base/lang"], function(lang){
var myObj = {
foo: "bar",
method: function(someArg){
console.log(this.foo);
}
};
var func = lang.hitch(myObj, "method");
func();
});
The console output should be bar
.
Arguments can also be passed to the function that is being called:
Passing arguments to a function.
require(["dojo/_base/lang"], function(lang){
var myObj = {
foo: "bar",
method: function(someArg){
console.log(someArg + " " + this.foo);
}
};
var func = lang.hitch(myObj, "method", "baz");
func();
});
The output in the console should be baz bar
. Any arguments provided after the first two will be passed to the
function.
mixin()¶
mixin()
is a simple utility function for mixing objects together. Mixin combines two objects from right to left,
overwriting the left-most object, and returning the newly mixed object for use. mixin()
is very similar to
extend() but only works on objects, whereas extend explicitly extends an object’s prototype.
Simple Mixes¶
Merge two objects (join two objects) together with mixin():
require(["dojo/_base/lang"], function(lang){
var a = { b: "c", d: "e" };
lang.mixin(a, { d: "f", g: "h" });
console.log(a); // b: c, d: f, g: h
});
This example overwrites the d
member from the second object, leaving the variable a
with three members: b
,
d
, and g
. To expand on this, we can illustrate how to use mixin to overwrite defaults for some function:
require(["dojo/_base/lang", "dojo/_base/fx"], function(lang, baseFx){
var generatedProps = { node: "someNode", onEnd: function(){ /*code*/ } };
var defaultProps = { duration: 1000 };
baseFx.fadeIn(lang.mixin(generatedProps, defaultProps)).play();
});
This will create and play a fadeIn animation passing and onEnd
function and node, using a default duration.
Creating New Objects¶
Mixin modifies the first object in the list, mixing in second object. If you wish to make an entirely new object from the mixed results, you have a couple options. First, clone the existing object with clone(), and then mix:
require(["dojo/_base/lang"], function(lang){
var newObject = lang.mixin(lang.clone(a), b);
});
Here, the return from clone()
is a new object, then b
is mixed in.
Alternately, you can pass an empty object as the first mix, and mix another object into it. You can then repeat this pattern as often as you’d like:
require(["dojo/_base/lang"], function(lang){
var newObject = lang.mixin({}, b);
lang.mixin(newObject, c);
lang.mixin(newObject, lang.mixin(e, f));
// and so on
});
Just remember the object instance in the first position will always be overwritten, and the right-most object will take precedence in the mix.
Mixins with Classes¶
A common pattern when creating class objects is to pass an object-hash of properties to the constructor. mixin()
provides a technique for easy override of default in you own classes. Consider the follow class declaration:
define(["dojo/_base/lang", "dojo/_base/declare"], function(lang, declare){
var Thinger = declare(null, {
defaultValue: "red",
constructor: function(args){
lang.mixin(this, args);
}
});
return Thinger;
});
Now, any time we create a new instance of a Thinger
, it will have a member variable defaultValue
set to red.
If we provide a new defaultValue
, the constructor will immediately overwrite the existing one:
require(["my/Thinger"], function(Thinger){
var thing = new Thinger({ defaultValue: "blue" });
});
Mixing into Instances¶
Sometimes is it useful to mix custom variables and members into instances of widgets and other objects. Mixing into an instance allows you to easily add arbitrary references or overwrite functionality after instantiation.
require(["dojo/_base/lang", "dijit/layout/ContentPane"], function(lang, ContentPane){
var cp = new ContentPane();
lang.mixin(cp, { _timeCreated: new Date() });
});
Now, that instance of the ContentPane as a Date object attached in the _timeCreated member, which is accessible to the
widget as this._timeCreated
.
Mixing Methods¶
If you want to mix in some methods into an instance using two previous techniques, be aware that
dojo/_base/declare() decorates them, while mixin()
does not, which may affect how
this.inherited()
works, if used in mixed-in methods. Use
dojo/_base/declare::safeMixin(), which correctly handles all properties in
dojo/_base/declare
-compatible way.
partial()¶
partial()
is related to hitch() in that it is a function that returns a function. What it does is allow
manipulation of the arguments being passed to a function. It allows the first n arguments to be fixed to a specific
value, but the remaining arguments to vary.
Let’s take a quick look at a pseudo-code example of using partial:
require(["dojo/request"], function(request){
var dataLoaded = function(someFirstParam, data, ioArgs){};
request.get("foo").then(dataLoaded);
});
Okay, so that will invoke the dataLoaded
function when the request.get()
function is fullfulled... but the
success callback expects to pass on data, ioArgs
. So how the heck do we make sure that the expectations are
honored even with that new first param called someFirstParam
? Use partial()
. Here’s how you would do it:
require(["dojo/_base/lang", "dojo/request"], function(lang, request){
var dataLoaded = function(someFirstParam, data, ioargs){};
request.get("foo").then(lang.partial(dataLoaded, "firstValue"));
});
What that does is create a new function that wraps dataLoaded and affixes the first parameter with the value
firstValue
. Note that partial()
allows you to do n parameters, so you can keep defining as many values as
you want for fixed-value parameters of a function.
Example¶
Let’s look at a quick running example:
require(["dojo/_base/lang", "dojo/dom", "dojo/dom-construct", "dojo/on", "dojo/domReady!"],
function(lang, dom, domConst, on){
var myClick = function(presetValue, event){
domConst.place("<p>" + presetValue + "</p>", "appendLocation");
domConst.place("<br />", "appendLocation");
};
on(dom.byId("myButton"), "click", lang.partial(myClick, "This is preset text!"));
});
<button type="button" id="myButton">Click me to append in a preset value!</button>
<div id="appendLocation"></div>
replace()¶
This function provides a light-weight foundation for substitution-based templating. It is a sane alternative to string concatenation technique, which is brittle and doesn’t play nice with localization.
With Dictionary¶
If the second argument is an object, all names within braces are interpreted as property names within this object. All
names separated by .
(dot) will be interpreted as sub-objects. This default behavior provides greater flexibility:
require(["dojo/_base/lang", "dojo/dom", "dojo/domReady!"], function(lang, dom){
dom.byId("output").innerHTML = lang.replace(
"Hello, {name.first} {name.last} AKA {nick}!",
{
name: {
first: "Robert",
middle: "X",
last: "Cringely"
},
nick: "Bob"
}
);
});
<p id="output"></p>
You don’t need to use all properties of an object, you can list them in any order, and you can reuse them as many times as you like.
With Array¶
In most cases you may prefer an array notation effectively simulating the venerable printf
:
require(["dojo/_base/lang", "dojo/dom", "dojo/domReady!"], function(lang, dom){
dom.byId("output").innerHTML = lang.replace(
"Hello, {0} {2} AKA {3}!",
["Robert", "X", "Cringely", "Bob"]
);
});
<p id="output"></p>
With a Function¶
For ultimate flexibility you can use replace()
with a function as the second argument.
Essentially these arguments are the same as in String.replace() when a function is used. Usually the second argument is the most useful one.
Let’s take a look at example where we are calculating values lazily on demand from a potentially dynamic source.
This code in action:
require(["dojo/_base/array", "dojo/_base/lang", "dojo/dom", "dojo/domReady!"],
function(array, lang, dom){
// helper function
function sum(a){
var t = 0;
array.forEach(a, function(x){ t += x; });
return t;
}
dom.byId("output").innerHTML = lang.replace(
"{count} payments averaging {avg} USD per payment.",
lang.hitch(
{ payments: [11, 16, 12] },
function(_, key){
switch(key){
case "count": return this.payments.length;
case "min": return Math.min.apply(Math, this.payments);
case "max": return Math.max.apply(Math, this.payments);
case "sum": return sum(this.payments);
case "avg": return sum(this.payments) / this.payments.length;
}
}
)
);
});
<p id="output"></p>
With Custom Pattern¶
In some cases you may want to use different braces, for example because your interpolated strings contain patterns
similar to {abc}
, but they should not be evaluated and replaced, or your server-side framework already uses these
patterns for something else. In this case you should replace the pattern:
require(["dojo/_base/lang", "dojo/dom", "dojo/domReady!"], function(lang, dom){
dom.byId("output").innerHTML = lang.replace(
"Hello, %[0] %[2] AKA %[3]!",
["Robert", "X", "Cringely", "Bob"],
/\%\[([^\]]+)\]/g
);
});
<p id="output"></p>
It is advised for the new pattern to be:
- Global
- It should capture one substring, usually some text inside “braces”.
Escaping Substitutions¶
This example escapes substituted text for HTML to prevent possible exploits. Dijit templates implement similar
technique. We will also borrow Dijit syntax: where all names starting with !
are going to be placed as is
(e.g., {!abc}
), while everything else is going to be escaped.
require(["dojo/dom", "dojo/_base/lang", "dojo/domReady!"], function(dom, lang){
function safeReplace(tmpl, dict){
// convert dict to a function, if needed
var fn = lang.isFunction(dict) ? dict : function(_, name){
return lang.getObject(name, false, dict);
};
// perform the substitution
return lang.replace(tmpl, function(_, name){
if(name.charAt(0) == '!'){
// no escaping
return fn(_, name.slice(1));
}
// escape
return fn(_, name).
replace(/&/g, "&").
replace(/</g, "<").
replace(/>/g, ">").
replace(/"/g, '"');
});
}
// we don't want to break the Code Glass widget here
var bad = "{script}alert('Let\' break stuff!');{/script}";
// let's reconstitute the original bad string
bad = bad.replace(/\{/g, "<").replace(/\}/g, ">");
// now the replacement
dom.byId("output").innerHTML = safeReplace("<div>{0}</div", [bad]);
});
<div id="output">Hello</div>
Formatting Substitutions¶
Let’s add a simple formatting to substituted fields. We will use the following notation in this example:
{name}
- use the result of substitution directly.{name:fmt}
- use formatterfmt
to format the result.{name:fmt:a:b:c}
- use formatterfmt
with optional parametersa
,b
, andc
. Any number of parameters can be used. Their interpretation depends on a formatter.
In this example we are going to format numbers as fixed or exponential with optional precision.
require(["dojo/dom", "dojo/_base/lang", "dojo/domReady!"], function(dom, lang){
function format(tmpl, dict, formatters){
// convert dict to a function, if needed
var fn = lang.isFunction(dict) ? dict : function(_, name){
return lang.getObject(name, false, dict);
};
// perform the substitution
return lang.replace(tmpl, function(_, name){
var parts = name.split(":"),
value = fn(_, parts[0]);
if(parts.length > 1){
value = formatters[parts[1]](value, parts.slice(2));
}
return value;
});
}
// simple numeric formatters
var customFormatters = {
f: function(value, opts){
// return formatted as a fixed number
var precision = opts && opts.length && opts[0];
return Number(value).toFixed(precision);
},
e: function(value, opts){
// return formatted as an exponential number
var precision = opts && opts.length && opts[0];
return Number(value).toExponential(precision);
}
};
// that is how we use it:
var output1 = format(
"pi = {pi}<br>pi:f = {pi:f}<br>pi:f:5 = {pi:f:5}",
{pi: Math.PI, big: 1234567890},
customFormatters
);
dom.byId("output1").innerHTML = format(
"pi = {pi}<br>pi:f = {pi:f}<br>pi:f:5 = {pi:f:5}",
{pi: Math.PI, big: 1234567890},
customFormatters
);
dom.byId("output2").innerHTML = format(
"big = {big}<br>big:e = {big:e}<br>big:e:5 = {big:e:5}",
{pi: Math.PI, big: 1234567890},
customFormatters
);
});
<p id="output1"></p>
<p id="output2"></p>
setObject()¶
Set a property from a dot-separated string, such as A.B.C
. In JavaScript, a dot separated string like
obj.parent.child
refers to an item called child
inside an object called parent
inside of obj
.
setObject()
will let you set the value of child, creating the intermediate parent objects if they don’t exist.
Without setObject()
, it is often handle like this:
// ensure that intermediate objects are available
if(!obj["parent"]){ obj.parent ={}; }
if(!obj.parent["child"]){ obj.parent.child={}; }
// now we can safely set the property
obj.parent.child.prop = "some value";
Whereas with setObject(), we can shorten that to:
require(["dojo/_base/lang"], function(lang){
lang.setObject("parent.child.prop", "some value", obj);
});
trim()¶
This function implements a frequently required functionality: it removes white-spaces from both ends of a string. This
functionality is part of ECMAScript 5 standard and implemented by some browsers. In this case trim()
delegates to
the native implementation. More information can be found here: String.trim() at MDC.
trim()
implementation was informed by Steven Levithan’s blog post. It was chosen to implement the compact yet performant version. If your application requires even
more speed, check out dojo/string::trim(), which implements the fastest version.
require(["dojo/dom", "dojo/_base/lang", "dojo/domReady!"], function(dom, lang){
function show(str){
return "|" + lang.trim(str) + "|";
}
dom.byId("output1").innerHTML = show(" one");
dom.byId("output2").innerHTML = show("two ");
dom.byId("output3").innerHTML = show(" three ");
dom.byId("output4").innerHTML = show("\tfour\r\n");
dom.byId("output5").innerHTML = show("\f\n\r\t\vF I V E\f\n\r\t\v");
});
<p id="output1"></p>
<p id="output2"></p>
<p id="output3"></p>
<p id="output4"></p>
<p id="output5"></p>
Deprecated Methods¶
The following methods are deprecated. See Testing Object Types for advice on how to differentiate between different types of objects without using methods(). The methods below are deprecated:
isString()
Checks if the parameter is a String
isArray()
Checks if the parameter is an Array
isFunction()
Checks if the parameter is a Function
isObject()
Checks if the parameter is a Object
isArrayLike()
Checks if the parameter is like an Array
isAlien()
Checks if the parameter is a built-in function
See also¶
- dojox/lang - Additional language extensions