michael@0: michael@0: # Writing Commands michael@0: michael@0: ## Basics michael@0: michael@0: GCLI has opinions about how commands should be written, and it encourages you michael@0: to do The Right Thing. The opinions are based on helping users convert their michael@0: intentions to commands and commands to what's actually going to happen. michael@0: michael@0: - Related commands should be sub-commands of a parent command. One of the goals michael@0: of GCLI is to support a large number of commands without things becoming michael@0: confusing, this will require some sort of namespacing or there will be michael@0: many people wanting to implement the ``add`` command. This style of michael@0: writing commands has become common place in Unix as the number of commands michael@0: has gone up. michael@0: The ```context``` command allows users to focus on a parent command, promoting michael@0: its sub-commands above others. michael@0: michael@0: - Each command should do exactly and only one thing. An example of a Unix michael@0: command that breaks this principle is the ``tar`` command michael@0: michael@0: $ tar -zcf foo.tar.gz . michael@0: $ tar -zxf foo.tar.gz . michael@0: michael@0: These 2 commands do exactly opposite things. Many a file has died as a result michael@0: of a x/c typo. In GCLI this would be better expressed: michael@0: michael@0: $ tar create foo.tar.gz -z . michael@0: $ tar extract foo.tar.gz -z . michael@0: michael@0: There may be commands (like tar) which have enough history behind them michael@0: that we shouldn't force everyone to re-learn a new syntax. The can be achieved michael@0: by having a single string parameter and parsing the input in the command) michael@0: michael@0: - Avoid errors. We try to avoid the user having to start again with a command michael@0: due to some problem. The majority of problems are simple typos which we can michael@0: catch using command metadata, but there are 2 things command authors can do michael@0: to prevent breakage. michael@0: michael@0: - Where possible avoid the need to validate command line parameters in the michael@0: exec function. This can be done by good parameter design (see 'do exactly michael@0: and only one thing' above) michael@0: michael@0: - If there is an obvious fix for an unpredictable problem, offer the michael@0: solution in the command output. So rather than use request.error (see michael@0: Request Object below) output some HTML which contains a link to a fixed michael@0: command line. michael@0: michael@0: Currently these concepts are not enforced at a code level, but they could be in michael@0: the future. michael@0: michael@0: michael@0: ## How commands work michael@0: michael@0: This is how to create a basic ``greet`` command: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: description: 'Show a greeting', michael@0: params: [ michael@0: { michael@0: name: 'name', michael@0: type: 'string', michael@0: description: 'The name to greet' michael@0: } michael@0: ], michael@0: returnType: 'string', michael@0: exec: function(args, context) { michael@0: return 'Hello, ' + args.name; michael@0: } michael@0: }); michael@0: michael@0: This command is used as follows: michael@0: michael@0: : greet Joe michael@0: Hello, Joe michael@0: michael@0: Some terminology that isn't always obvious: a function has 'parameters', and michael@0: when you call a function, you pass 'arguments' to it. michael@0: michael@0: michael@0: ## Internationalization (i18n) michael@0: michael@0: There are several ways that GCLI commands can be localized. The best method michael@0: depends on what context you are writing your command for. michael@0: michael@0: ### Firefox Embedding michael@0: michael@0: GCLI supports Mozilla style localization. To add a command that will only ever michael@0: be used embedded in Firefox, this is the way to go. Your strings should be michael@0: stored in ``browser/locales/en-US/chrome/browser/devtools/gclicommands.properties``, michael@0: And you should access them using ``gcli.lookup(...)`` or ``gcli.lookupFormat()`` michael@0: michael@0: For examples of existing commands, take a look in michael@0: ``browser/devtools/webconsole/GcliCommands.jsm``, which contains most of the michael@0: current GCLI commands. If you will be adding a number of new commands, then michael@0: consider starting a new JSM. michael@0: michael@0: Your command will then look something like this: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: description: gcli.lookup("greetDesc") michael@0: ... michael@0: }); michael@0: michael@0: ### Web Commands michael@0: michael@0: There are 2 ways to provide translated strings for web use. The first is to michael@0: supply the translated strings in the description: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: description: { michael@0: 'root': 'Show a greeting', michael@0: 'fr-fr': 'Afficher un message d'accueil', michael@0: 'de-de': 'Zeige einen Gruß', michael@0: 'gk-gk': 'Εμφάνιση ένα χαιρετισμό', michael@0: ... michael@0: } michael@0: ... michael@0: }); michael@0: michael@0: Each description should contain at least a 'root' entry which is the michael@0: default if no better match is found. This method has the benefit of being michael@0: compact and simple, however it has the significant drawback of being wasteful michael@0: of memory and bandwidth to transmit and store a significant number of strings, michael@0: the majority of which will never be used. michael@0: michael@0: More efficient is to supply a lookup key and ask GCLI to lookup the key from an michael@0: appropriate localized strings file: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: description: { 'key': 'demoGreetingDesc' } michael@0: ... michael@0: }); michael@0: michael@0: For web usage, the central store of localized strings is michael@0: ``lib/gcli/nls/strings.js``. Other string files can be added using the michael@0: ``l10n.registerStringsSource(...)`` function. michael@0: michael@0: This method can be used both in Firefox and on the Web (see the help command michael@0: for an example). However this method has the drawback that it will not work michael@0: with DryIce built files until we fix bug 683844. michael@0: michael@0: michael@0: ## Default argument values michael@0: michael@0: The ``greet`` command requires the entry of the ``name`` parameter. This michael@0: parameter can be made optional with the addition of a ``defaultValue`` to the michael@0: parameter: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: description: 'Show a message to someone', michael@0: params: [ michael@0: { michael@0: name: 'name', michael@0: type: 'string', michael@0: description: 'The name to greet', michael@0: defaultValue: 'World!' michael@0: } michael@0: ], michael@0: returnType: 'string', michael@0: exec: function(args, context) { michael@0: return "Hello, " + args.name; michael@0: } michael@0: }); michael@0: michael@0: Now we can also use the ``greet`` command as follows: michael@0: michael@0: : greet michael@0: Hello, World! michael@0: michael@0: michael@0: ## Positional vs. named arguments michael@0: michael@0: Arguments can be entered either positionally or as named arguments. Generally michael@0: users will prefer to type the positional version, however the named alternative michael@0: can be more self documenting. michael@0: michael@0: For example, we can also invoke the greet command as follows: michael@0: michael@0: : greet --name Joe michael@0: Hello, Joe michael@0: michael@0: michael@0: ## Short argument names michael@0: michael@0: GCLI allows you to specify a 'short' character for any parameter: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: params: [ michael@0: { michael@0: name: 'name', michael@0: short: 'n', michael@0: type: 'string', michael@0: ... michael@0: } michael@0: ], michael@0: ... michael@0: }); michael@0: michael@0: This is used as follows: michael@0: michael@0: : greet -n Fred michael@0: Hello, Fred michael@0: michael@0: Currently GCLI does not allow short parameter merging (i.e. ```ls -la```) michael@0: however this is planned. michael@0: michael@0: michael@0: ## Parameter types michael@0: michael@0: Initially the available types are: michael@0: michael@0: - string michael@0: - boolean michael@0: - number michael@0: - selection michael@0: - delegate michael@0: - date michael@0: - array michael@0: - file michael@0: - node michael@0: - nodelist michael@0: - resource michael@0: - command michael@0: - setting michael@0: michael@0: This list can be extended. See [Writing Types](writing-types.md) on types for michael@0: more information. michael@0: michael@0: The following examples assume the following definition of the ```greet``` michael@0: command: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: params: [ michael@0: { name: 'name', type: 'string' }, michael@0: { name: 'repeat', type: 'number' } michael@0: ], michael@0: ... michael@0: }); michael@0: michael@0: Parameters can be specified either with named arguments: michael@0: michael@0: : greet --name Joe --repeat 2 michael@0: michael@0: And sometimes positionally: michael@0: michael@0: : greet Joe 2 michael@0: michael@0: Parameters can be specified positionally if they are considered 'important'. michael@0: Unimportant parameters must be specified with a named argument. michael@0: michael@0: Named arguments can be specified anywhere on the command line (after the michael@0: command itself) however positional arguments must be in order. So michael@0: these examples are the same: michael@0: michael@0: : greet --name Joe --repeat 2 michael@0: : greet --repeat 2 --name Joe michael@0: michael@0: However (obviously) these are not the same: michael@0: michael@0: : greet Joe 2 michael@0: : greet 2 Joe michael@0: michael@0: (The second would be an error because 'Joe' is not a number). michael@0: michael@0: Named arguments are assigned first, then the remaining arguments are assigned michael@0: to the remaining parameters. So the following is valid and unambiguous: michael@0: michael@0: : greet 2 --name Joe michael@0: michael@0: Positional parameters quickly become unwieldy with long parameter lists so we michael@0: recommend only having 2 or 3 important parameters. GCLI provides hints for michael@0: important parameters more obviously than unimportant ones. michael@0: michael@0: Parameters are 'important' if they are not in a parameter group. The easiest way michael@0: to achieve this is to use the ```option: true``` property. michael@0: michael@0: For example, using: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: params: [ michael@0: { name: 'name', type: 'string' }, michael@0: { name: 'repeat', type: 'number', option: true, defaultValue: 1 } michael@0: ], michael@0: ... michael@0: }); michael@0: michael@0: Would mean that this is an error michael@0: michael@0: : greet Joe 2 michael@0: michael@0: You would instead need to do the following: michael@0: michael@0: : greet Joe --repeat 2 michael@0: michael@0: For more on parameter groups, see below. michael@0: michael@0: In addition to being 'important' and 'unimportant' parameters can also be michael@0: optional. If is possible to be important and optional, but it is not possible michael@0: to be unimportant and non-optional. michael@0: michael@0: Parameters are optional if they either: michael@0: - Have a ```defaultValue``` property michael@0: - Are of ```type=boolean``` (boolean arguments automatically default to being false) michael@0: michael@0: There is currently no way to make parameters mutually exclusive. michael@0: michael@0: michael@0: ## Selection types michael@0: michael@0: Parameters can have a type of ``selection``. For example: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: params: [ michael@0: { name: 'name', ... }, michael@0: { michael@0: name: 'lang', michael@0: description: 'In which language should we greet', michael@0: type: { name: 'selection', data: [ 'en', 'fr', 'de', 'es', 'gk' ] }, michael@0: defaultValue: 'en' michael@0: } michael@0: ], michael@0: ... michael@0: }); michael@0: michael@0: GCLI will enforce that the value of ``arg.lang`` was one of the values michael@0: specified. Alternatively ``data`` can be a function which returns an array of michael@0: strings. michael@0: michael@0: The ``data`` property is useful when the underlying type is a string but it michael@0: doesn't work when the underlying type is something else. For this use the michael@0: ``lookup`` property as follows: michael@0: michael@0: type: { michael@0: name: 'selection', michael@0: lookup: { michael@0: 'en': Locale.EN, michael@0: 'fr': Locale.FR, michael@0: ... michael@0: } michael@0: }, michael@0: michael@0: Similarly, ``lookup`` can be a function returning the data of this type. michael@0: michael@0: michael@0: ## Number types michael@0: michael@0: Number types are mostly self explanatory, they have one special property which michael@0: is the ability to specify upper and lower bounds for the number: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'volume', michael@0: params: [ michael@0: { michael@0: name: 'vol', michael@0: description: 'How loud should we go', michael@0: type: { name: 'number', min: 0, max: 11 } michael@0: } michael@0: ], michael@0: ... michael@0: }); michael@0: michael@0: You can also specify a ``step`` property which specifies by what amount we michael@0: should increment and decrement the values. The ``min``, ``max``, and ``step`` michael@0: properties are used by the command line when up and down are pressed and in michael@0: the input type of a dialog generated from this command. michael@0: michael@0: michael@0: ## Delegate types michael@0: michael@0: Delegate types are needed when the type of some parameter depends on the type michael@0: of another parameter. For example: michael@0: michael@0: : set height 100 michael@0: : set name "Joe Walker" michael@0: michael@0: We can achieve this as follows: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'set', michael@0: params: [ michael@0: { michael@0: name: 'setting', michael@0: type: { name: 'selection', values: [ 'height', 'name' ] } michael@0: }, michael@0: { michael@0: name: 'value', michael@0: type: { michael@0: name: 'delegate', michael@0: delegateType: function() { ... } michael@0: } michael@0: } michael@0: ], michael@0: ... michael@0: }); michael@0: michael@0: Several details are left out of this example, like how the delegateType() michael@0: function knows what the current setting is. See the ``pref`` command for an michael@0: example. michael@0: michael@0: michael@0: ## Array types michael@0: michael@0: Parameters can have a type of ``array``. For example: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: params: [ michael@0: { michael@0: name: 'names', michael@0: type: { name: 'array', subtype: 'string' }, michael@0: description: 'The names to greet', michael@0: defaultValue: [ 'World!' ] michael@0: } michael@0: ], michael@0: ... michael@0: exec: function(args, context) { michael@0: return "Hello, " + args.names.join(', ') + '.'; michael@0: } michael@0: }); michael@0: michael@0: This would be used as follows: michael@0: michael@0: : greet Fred Jim Shiela michael@0: Hello, Fred, Jim, Shiela. michael@0: michael@0: Or using named arguments: michael@0: michael@0: : greet --names Fred --names Jim --names Shiela michael@0: Hello, Fred, Jim, Shiela. michael@0: michael@0: There can only be one ungrouped parameter with an array type, and it must be michael@0: at the end of the list of parameters (i.e. just before any parameter groups). michael@0: This avoids confusion as to which parameter an argument should be assigned. michael@0: michael@0: michael@0: ## Sub-commands michael@0: michael@0: It is common for commands to be groups into those with similar functionality. michael@0: Examples include virtually all VCS commands, ``apt-get``, etc. There are many michael@0: examples of commands that should be structured as in a sub-command style - michael@0: ``tar`` being the obvious example, but others include ``crontab``. michael@0: michael@0: Groups of commands are specified with the top level command not having an michael@0: exec function: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'tar', michael@0: description: 'Commands to manipulate archives', michael@0: }); michael@0: gcli.addCommand({ michael@0: name: 'tar create', michael@0: description: 'Create a new archive', michael@0: exec: function(args, context) { ... }, michael@0: ... michael@0: }); michael@0: gcli.addCommand({ michael@0: name: 'tar extract', michael@0: description: 'Extract from an archive', michael@0: exec: function(args, context) { ... }, michael@0: ... michael@0: }); michael@0: michael@0: michael@0: ## Parameter groups michael@0: michael@0: Parameters can be grouped into sections. michael@0: michael@0: There are 3 ways to assign a parameter to a group. michael@0: michael@0: The simplest uses ```option: true``` to put a parameter into the default michael@0: 'Options' group: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: params: [ michael@0: { name: 'repeat', type: 'number', option: true } michael@0: ], michael@0: ... michael@0: }); michael@0: michael@0: The ```option``` property can also take a string to use an alternative parameter michael@0: group: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: params: [ michael@0: { name: 'repeat', type: 'number', option: 'Advanced' } michael@0: ], michael@0: ... michael@0: }); michael@0: michael@0: An example of how this can be useful is 'git' which categorizes parameters into michael@0: 'porcelain' and 'plumbing'. michael@0: michael@0: Finally, parameters can be grouped together as follows: michael@0: michael@0: gcli.addCommand({ michael@0: name: 'greet', michael@0: params: [ michael@0: { name: 'name', type: 'string', description: 'The name to greet' }, michael@0: { michael@0: group: 'Advanced Options', michael@0: params: [ michael@0: { name: 'repeat', type: 'number', defaultValue: 1 }, michael@0: { name: 'debug', type: 'boolean' } michael@0: ] michael@0: } michael@0: ], michael@0: ... michael@0: }); michael@0: michael@0: This could be used as follows: michael@0: michael@0: : greet Joe --repeat 2 --debug michael@0: About to send greeting michael@0: Hello, Joe michael@0: Hello, Joe michael@0: Done! michael@0: michael@0: Parameter groups must come after non-grouped parameters because non-grouped michael@0: parameters can be assigned positionally, so their index is important. We don't michael@0: want 'holes' in the order caused by parameter groups. michael@0: michael@0: michael@0: ## Command metadata michael@0: michael@0: Each command should have the following properties: michael@0: michael@0: - A string ``name``. michael@0: - A short ``description`` string. Generally no more than 20 characters without michael@0: a terminating period/fullstop. michael@0: - A function to ``exec``ute. (Optional for the parent containing sub-commands) michael@0: See below for more details. michael@0: michael@0: And optionally the following extra properties: michael@0: michael@0: - A declaration of the accepted ``params``. michael@0: - A ``hidden`` property to stop the command showing up in requests for help. michael@0: - A ``context`` property which defines the scope of the function that we're michael@0: calling. Rather than simply call ``exec()``, we do ``exec.call(context)``. michael@0: - A ``manual`` property which allows a fuller description of the purpose of the michael@0: command. michael@0: - A ``returnType`` specifying how we should handle the value returned from the michael@0: exec function. michael@0: michael@0: The ``params`` property is an array of objects, one for each parameter. Each michael@0: parameter object should have the following 3 properties: michael@0: michael@0: - A string ``name``. michael@0: - A short string ``description`` as for the command. michael@0: - A ``type`` which refers to an existing Type (see Writing Types). michael@0: michael@0: Optionally each parameter can have these properties: michael@0: michael@0: - A ``defaultValue`` (which should be in the type specified in ``type``). michael@0: The defaultValue will be used when there is no argument supplied for this michael@0: parameter on the command line. michael@0: If the parameter has a ``defaultValue``, other than ``undefined`` then the michael@0: parameter is optional, and if unspecified on the command line, the matching michael@0: argument will have this value when the function is called. michael@0: If ``defaultValue`` is missing, or if it is set to ``undefined``, then the michael@0: system will ensure that a value is provided before anything is executed. michael@0: There are 2 special cases: michael@0: - If the type is ``selection``, then defaultValue must not be undefined. michael@0: The defaultValue must either be ``null`` (meaning that a value must be michael@0: supplied by the user) or one of the selection values. michael@0: - If the type is ``boolean``, then ``defaultValue:false`` is implied and michael@0: can't be changed. Boolean toggles are assumed to be off by default, and michael@0: should be named to match. michael@0: - A ``manual`` property for parameters is exactly analogous to the ``manual`` michael@0: property for commands - descriptive text that is longer than than 20 michael@0: characters. michael@0: michael@0: michael@0: ## The Command Function (exec) michael@0: michael@0: The parameters to the exec function are designed to be useful when you have a michael@0: large number of parameters, and to give direct access to the environment (if michael@0: used). michael@0: michael@0: gcli.addCommand({ michael@0: name: 'echo', michael@0: description: 'The message to display.', michael@0: params: [ michael@0: { michael@0: name: 'message', michael@0: type: 'string', michael@0: description: 'The message to display.' michael@0: } michael@0: ], michael@0: returnType: 'string', michael@0: exec: function(args, context) { michael@0: return args.message; michael@0: } michael@0: }); michael@0: michael@0: The ``args`` object contains the values specified on the params section and michael@0: provided on the command line. In this example it would contain the message for michael@0: display as ``args.message``. michael@0: michael@0: The ``context`` object has the following signature: michael@0: michael@0: { michael@0: environment: ..., // environment object passed to createTerminal() michael@0: exec: ..., // function to execute a command michael@0: update: ..., // function to alter the text of the input area michael@0: createView: ..., // function to help creating rich output michael@0: defer: ..., // function to create a deferred promise michael@0: } michael@0: michael@0: The ``environment`` object is opaque to GCLI. It can be used for providing michael@0: arbitrary data to your commands about their environment. It is most useful michael@0: when more than one command line exists on a page with similar commands in both michael@0: which should act in their own ways. michael@0: An example use for ``environment`` would be a page with several tabs, each michael@0: containing an editor with a command line. Commands executed in those editors michael@0: should apply to the relevant editor. michael@0: The ``environment`` object is passed to GCLI at startup (probably in the michael@0: ``createTerminal()`` function). michael@0: michael@0: The ``document`` object is also passed to GCLI at startup. In some environments michael@0: (e.g. embedded in Firefox) there is no global ``document``. This object michael@0: provides a way to create DOM nodes. michael@0: michael@0: ``defer()`` allows commands to execute asynchronously. michael@0: michael@0: michael@0: ## Returning data michael@0: michael@0: The command meta-data specifies the type of data returned by the command using michael@0: the ``returnValue`` setting. michael@0: michael@0: ``returnValue`` processing is currently functioning, but incomplete, and being michael@0: tracked in [Bug 657595](http://bugzil.la/657595). Currently you should specify michael@0: a ``returnType`` of ``string`` or ``html``. If using HTML, you can return michael@0: either an HTML string or a DOM node. michael@0: michael@0: In the future, JSON will be strongly encouraged as the return type, with some michael@0: formatting functions to convert the JSON to HTML. michael@0: michael@0: Asynchronous output is achieved using a promise created from the ``context`` michael@0: parameter: ``context.defer()``. michael@0: michael@0: Some examples of this is practice: michael@0: michael@0: { returnType: "string" } michael@0: ... michael@0: return "example"; michael@0: michael@0: GCLI interprets the output as a plain string. It will be escaped before display michael@0: and available as input to other commands as a plain string. michael@0: michael@0: { returnType: "html" } michael@0: ... michael@0: return "
Hello
"; michael@0: michael@0: GCLI will interpret this as HTML, and parse it for display. michael@0: michael@0: { returnType: "dom" } michael@0: ... michael@0: return util.createElement(context.document, 'div'); michael@0: michael@0: ``util.createElement`` is a utility to ensure use of the XHTML namespace in XUL michael@0: and other XML documents. In an HTML document it's functionally equivalent to michael@0: ``context.document.createElement('div')``. If your command is likely to be used michael@0: in Firefox or another XML environment, you should use it. You can import it michael@0: with ``var util = require('util/util');``. michael@0: michael@0: GCLI will use the returned HTML element as returned. See notes on ``context`` michael@0: above. michael@0: michael@0: { returnType: "number" } michael@0: ... michael@0: return 42; michael@0: michael@0: GCLI will display the element in a similar way to a string, but it the value michael@0: will be available to future commands as a number. michael@0: michael@0: { returnType: "date" } michael@0: ... michael@0: return new Date(); michael@0: michael@0: { returnType: "file" } michael@0: ... michael@0: return new File(); michael@0: michael@0: Both these examples return data as a given type, for which a converter will michael@0: be required before the value can be displayed. The type system is likely to michael@0: change before this is finalized. Please contact the author for more michael@0: information. michael@0: michael@0: { returnType: "string" } michael@0: ... michael@0: var deferred = context.defer(); michael@0: setTimeout(function() { michael@0: deferred.resolve("hello"); michael@0: }, 500); michael@0: return deferred.promise; michael@0: michael@0: Errors can be signaled by throwing an exception. GCLI will display the message michael@0: property (or the toString() value if there is no message property). (However michael@0: see *3 principles for writing commands* above for ways to avoid doing this). michael@0: michael@0: michael@0: ## Specifying Types michael@0: michael@0: Types are generally specified by a simple string, e.g. ``'string'``. For most michael@0: types this is enough detail. There are a number of exceptions: michael@0: michael@0: * Array types. We declare a parameter to be an array of things using ``[]``, michael@0: for example: ``number[]``. michael@0: * Selection types. There are 3 ways to specify the options in a selection: michael@0: * Using a lookup map michael@0: michael@0: type: { michael@0: name: 'selection', michael@0: lookup: { one:1, two:2, three:3 } michael@0: } michael@0: michael@0: (The boolean type is effectively just a selection that uses michael@0: ``lookup:{ 'true': true, 'false': false }``) michael@0: michael@0: * Using given strings michael@0: michael@0: type: { michael@0: name: 'selection', michael@0: data: [ 'left', 'center', 'right' ] michael@0: } michael@0: michael@0: * Using named objects, (objects with a ``name`` property) michael@0: michael@0: type: { michael@0: name: 'selection', michael@0: data: [ michael@0: { name: 'Google', url: 'http://www.google.com/' }, michael@0: { name: 'Microsoft', url: 'http://www.microsoft.com/' }, michael@0: { name: 'Yahoo', url: 'http://www.yahoo.com/' } michael@0: ] michael@0: } michael@0: michael@0: * Delegate type. It is generally best to inherit from Delegate in order to michael@0: provide a customization of this type. See settingValue for an example. michael@0: michael@0: See below for more information. michael@0: