Advanced Editing and Display

ColorPalette [inline:color_picker.png]
Editor [inline:editor.png]
Grid (1.0) [inline:grid_terms.gif]
InlineEditBox (1.0) For 0.9, see dijit.form.InlineEditBox [inline:inline_edit.png]
Tree [inline:tree.png]

ColorPalette

ColorPalette is a color picker for Web pages. You can do lots of stuff with this kind of widget like allowing your user to choose color for theming interactively. Color palette is an abstraction of popular hexa color codes by dijit.

Example

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ColorPalette Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
          dojo.require("dojo.parser");
          dojo.require("dijit.ColorPalette");
          function reportColor(selectedColor) {
              console.debug(selectedColor);
           }
     </script>
</head>
<body class="tundra">
        <div dojoType="dijit.ColorPalette" onChange="reportColor"></div>
</body></html>

dijit.ColorPalette
Grid showing various colors, so the user can pick a certain color
Attributes
defaultTimeout Number
500
The number of milliseconds before a held key or button becomes typematic
timeoutChangeRate Number
0.90
Fraction of time used to change the typematic timer between events; 1.0 means that each typematic event fires at defaultTimeout intervals < 1.0 means that each typematic event fires at an increasing faster rate
palette String
7x10
Size of grid, either "7x10" or "3x4".
Extension Points
onChange(/* String */color) Callback when a color is selected. Parameter is the hex color.

Accessibility

Keyboard

ActionKey
Navigate colorsArrow keys
Pick a colorSpacebar or enter

Screen Reader

Screen readers will read the name of each color as it is highlighted. For example, "white", "seashell", "cornsilk", and so on.

Grid (1.0)

[inline:grid_terms.gif]

This widget is only available in 1.0. Grid is a DojoX project, but is documented here for user convenience.

Grids are familiar in the client/server development world. Basically a grid is a kind of mini spreadsheet, commonly used to display details on master-detail forms. From HTML terms, a grid is a "super-table" with its own scrollable viewport.

The Dojo grid is fast, robust, and very functional. In particular, grid has:

  • High performance drawing. Rows are rendered "lazily" as the user moves down the grid.
  • Addition or deletion of cells, rows and columns at will.
  • Rows made of multiple sub rows, acting as one selectable unit.
  • Summary rows.
  • Adjustable row and column spans to fit data into different rectangular shapes in the row.
  • Fixed rows and columns that stay still while the variable part of the grid scrolls.
  • A rich event structure, so you can hook into selection and scrolling. Styles can be set from an arbitrary function through the onStyle hook.
  • Support for "expandos" to show or hide detail.
  • Automatic even/odd row coloring
  • Ability to change the structure of rows on the fly.
  • Support for rich in-cell editing of text or non-text data. All Dijit form wdigets can be used in cells. Cells can be set to edit with a single-click.
  • Context menus settable for different cells
  • Support for selecting cells, rows or columns.
  • Option of automatic column sizing
  • Grid nesting, so a grid can be housed in the cell of an outer grid.

A Simple Grid

To begin working with Grid, let's start with a simple example. The Dijit class directory is kept in /dijit/tests/_data/dijits.json. We'll base on a grid on it, and add embellishments throughout the next few sections.

Our First Grid


The Basics

Every page using Grid needs to import the basic grid style sheet. When used with Dijit and Dojo, your combined style loading block should look like:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<style type="text/css">
        /* tundraGrid.css matches Dijit Tundra style.  Others forthcoming.
            Use Grid.css on the same path for a more color-neutral theme */
        @import "http://o.aolcdn.com/dojo/1.0.0/dojox/grid/_grid/tundraGrid.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/resources/dojo.css"
</style>

The Model

Each Grid begins with data, and two DIV tags will define our data source. Because of the same-origin security rule, you will need to place the data file on your web server. You can download this file, dijits.json, at the bottom of this page. Just place it in the same directory as the example: /* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}

<div dojoType="dojo.data.ItemFileReadStore"
                jsId="jsonStore" url="dijits.txt">

        </div>
        <div dojoType="dojox.grid.data.DojoData" jsId="model"
                rowsPerPage="20" store="jsonStore" query="{ namespace: '*' }">

        </div>

The first DIV should look familiar. It's the good ol' dojo.Data definition you use with the Dijit components ComboBox or Tree. The second is new - it's the dojox.grid.data.DojoData adapter that turns a data source into a Grid model. The model has options for which items to select. In this example, we turn the datasource jsonStore into the model named "model".

Models can also be created from JavaScript arrays, which we'll see in: Model Options.

The View

In standard spreadsheet and table terminology, a cell is the basic unit of displayed data. A row is a horizontally aligned contiguous group of cells, and a column is a vertically aligned contiguous group of cells. (Wow, that makes a simple concept sound complex!)

In grid-land, there's a distinction between rows and subrows. A subrow is what people normally think of as a row - it's exactly one cell tall. In Grid, a row may be more than one subrow - but it is selectable as a unit. So you'll notice in our demo grid that logical rows are exactly 2 subrows tall.

A View is a group of contiguous logical rows with the same inner and outer "shape". In our example above, each logical row is two subrows tall, with 2 columns on the top physical row and 1 column on the bottom physical row (the last cell spanning 2 columns). You specify this in JavaScript with an array of arrays. Each array element is an object literal. The most important property of the object is "name", which names the column. The column name always appear as the top logical row of the grid, and unlike other rows, it doesn't scroll up or down.

// a grid view is a group of columns
var view1 = {
	cells: [[
		{name: 'Namespace', field: "namespace"}, 
		{name: 'Class', width: "25em", field: "className"}
	  ],
	  [
		{name: 'Summary', colSpan:"2", field: "summary"}
	  ]
	]
};

Fields in the model are applied across each view cell in order. Property names are ignored. In our example, Namespace holds field 0, Class holds field 1, and so on.

As in good ol' HTML tables, you can specify:

  • colSpan - note the capital "S" here, unlike standard HTML
  • rowSpan
  • width - all the usual CSS measurements are valid here

The Structure

Finally, views can be grouped together into a structure. You can think of a structure as a dijit.layout.LayoutContainer applied to views - you can place views in the top, bottom, left and/or right sides, plus one in the middle. Our simple example only has one view:

var layout = [ view1 ];

The Widget

The model and structure (which is composed of views) come together in the grid widget:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div id="grid" dojoType="dojox.Grid" model="model" structure="layout"></div>

The model and structure attributes point to our JavaScript variables for the model and structure. Nice! And with no other code, the grid is:

  • scrollable
  • sizable in the columns - point between columns on the top and drag left or right
  • row-selectable - just click anywhere on a row

So, here's the entire program:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
        <title>Test dojox.Grid Basic</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
        <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dojox/grid/_grid/tundraGrid.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/resources/dojo.css"
                body {
                        font-size: 0.9em;
                        font-family: Geneva, Arial, Helvetica, sans-serif;
                }
                .heading {
                        font-weight: bold;
                        padding-bottom: 0.25em;
                }
                               
                #grid {
                        border: 1px solid #333;
                        width: 35em;
                        height: 30em;
                }
        </style>
        <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"
                djConfig="isDebug:false, parseOnLoad: true">
</script>
        <script type="text/javascript">
            dojo.require("dojo.data.ItemFileReadStore");
                dojo.require("dojox.grid.Grid");
                dojo.require("dojox.grid._data.model");
                dojo.require("dojo.parser");
       
                // a grid view is a group of columns. 
                var view1 = {
                        cells: [[
                                {name: 'Namespace', field: "namespace"},
                                {name: 'Class', width: "25em", field: "className"}
                          ],
                          [
                                {name: 'Summary', colSpan:"2", field: "summary"}
                          ]
                        ]
                };
                // a grid layout is an array of views.
                var layout = [ view1 ];
</script>
</head>
<body class="tundra">
<div class="heading">Our First Grid</div>
        <div dojoType="dojo.data.ItemFileReadStore"
                jsId="jsonStore" url="dijits.txt">

        </div>
        <div dojoType="dojox.grid.data.DojoData" jsId="model"
                rowsPerPage="20" store="jsonStore" query="{ namespace: '*' }">

        </div>
        <div id="grid" dojoType="dojox.Grid" model="model" structure="layout"></div>
</body>
</html>

Note that normally with CDN, you would need to wrap the view1 initialization code like in a dojo.addOnLoad. But that's not necessary here because no DOM needs to be drawn yet. We're just setting up anonymous objects. Because these are available when the widgets are drawn, we can use the structure property in the Grid tag. The design is nice and clean.

Now let's cover the model, view and structure elements in more depth:

Combining Views: Row Selection and Independent Scrolling

That's nice so far. The column header stays in place while the user scrolls down, making them easy to identify. Can we apply to that rows as well?

Yup. You can make row headers that stay in place and act as selection points.. What's more, you can split your grid into arbitrary scrollable sections that can stay in sync or scroll independently. You do this by gluing more than one view into a structure.

Selectable Rows

By default, when you click on a cell, you select that cell and the entire containing row. A special view called dojox.grid.GridRowView draws a column of empty handles. When clicked, these handles select the row without selecting a particular cell. Unlike most other views, GridRowView has no "cells" property, only a width. So adding this to the structure on our last page:

var rowbar = {
     type: 'dojox.GridRowView', width: '20px'
};
	    
// a grid layout is an array of views.
var layout = [ rowbar, view1 ];

The resulting page (downloadable below as grid1.html), yields:

As you would expect, CTRL+click selects non-contiguous rows and SHIFT+click selects contiguous ones. As it stands, the selection is only for display. In the Events section, we'll actually do something with the rows.

Linked Scrollable Views

You may have noticed that the Selection bar rows and the data rows scroll together. So it's no surprise that when you add another view, it too scrolls vertically in sync.

To see this, we'll split off the Dijit class name into its own view:

// a grid view is a group of columns
var view1 = {
	cells: [[
		{name: 'Namespace', field:0, width: "25em"}
	  ],
	  [
		{name: 'Summary', colSpan:"2", field:2}
	  ]
	]
};

var rowbar = {
   type: 'dojox.GridRowView', width: '20px'
   };

var fixedColumn = {
        cells: [[ {name: 'Class', field:1, width:"25em"} ]]
};

Since we're using fields in a different order, specifying the field numbers in each cell definition is mandatory. Now combine that into a layout like this:

var layout = [ rowbar, fixedColumn, view1 ];

And you get two grids with separate scroll bars. By default each scrollbar moves both views in sync with each other.

Admittedly, the extra scroll bar isn't very useful. But when you add fields to the right hand grid like so:

var view1 = {
	cells: [[
		{name: 'Namespace', field:0, width:"20em"}, 
		{name: 'Description', field:3, width:"20em"}
	  ],
	  [
		{name: 'Summary', field:2},
		{nane: 'Examples', field:4}
	  ]
	]
};

And run the example, you find the bottom scroll bar scrolls the views independently. We say the views are vertically dependent, but horizontally independent.

Now you turn off the scroll bar in the left hand view with the noscroll property:

var fixedColumn = {
    noscroll: true,
    cells: [[

Yields:

Sorting and Other Dojo.Data Considerations

By default, when dojo.data datasources feed a Grid, the columns are not user-sortable. That's easy to rectify. Just set clientSort="true" in the tag:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div dojoType="dojox.grid.data.DojoData" jsId="model"
        rowsPerPage="20" store="jsonStore" query="{ namespace: '*' }"
        clientSort="true">

</div>

Now the user can click on any column to sort it. You may also set the sort order programmatically:

// Sort 4'th field in ascending order
myGrid.setSortIndex(3, true);

Filtering

To filter a list, you can create a new adapter with a different query then attach it to the grid.

//Construct a new model.  The store needs to be passed into the constructor, as the constructor for the
//DojoData model examines the store and configures the model to support various features of the store
//such as Write, Identity, and Notification.  Setting the store after the constructor will leave the
//model in a misconfigured state and be unable to support data writebacks in the case of Write implementing
// datastores.
var newModel = new dojox.grid.data.DojoData(null,null,{rowsPerPage: 20, store: myStore, query: {type: someOtherType}, clientSort: true});
myGrid.setModel(newModel);
// Remember to call newModel.destroy() when you're done.

A popular use of filtering is to display a grid with everything, then let the user chop the list down incrementally. In this case, you can define an initial model with a minimal query. Save it, and then you can setModel back to it for quickly resetting all the filters. But do not keep unused models lying around! They take up memory.

Cell Editing

Up until now, we've showed grid contents in view mode. Now, let's add some interactivity.

An editable cell is handled by a cell editor. To make a cell editable, you simply specify the cell editor class in the column definition. Here is a part of the view definition code in /dojoroot/dojox/grid/tests/test_edit.html.

gridLayout = [
	cells: [[
		{ name: 'Priority', styles: 'text-align: center;', 
                   editor: dojox.grid.editors.select, 
                   options: ["normal", "note", "important"]
                },
		{ name: 'Mark', width: 3, styles: 'text-align: center;', 
                   editor: dojox.grid.editors.bool 
                },
		{ field: 2, name: 'Status', styles: 'text-align: center;', 
                   editor: dojox.grid.editors.select, 
                   options: [ "new", "read", "replied" ] 
                }

In the actual grid, you double-click on a cell to begin editing. Here, we've double clicked on the status box, and a SELECT appears in place of the data:

[inline:gredit_edit1.png]

Some of the cell editors, like dojox.grid.editors.DateTextBox are just wrappers for their Dijit form widget counterparts. All the functionality and properties are available to you. This is an example from /dojoroot/dojox/grid/tests/test_edit_dijit.html.

gridLayout = {
  cells: [[
	{ name: 'Date', width: 10, field: 7, 
           editor: dojox.grid.editors.DateTextBox, 
           formatter: formatDate, 
           constraint: {formatLength: 'long', selector: "date"}
       }
  ]]
};

Editing the Date cell brings up the familiar Dijit date box:

[inline:gredit_edit2.png]

Now, what do you actually do with an edited cell? That's really up to you, and you hook in your desired code by connecting to an event, which we'll cover next.

Available Cell Editors

Cell Editor Class Attributes
dojox.grid.editors.CheckBox See dijit.form.CheckBox
dojox.grid.editors.ComboBox See dijit.form.ComboBox
dojox.grid.editors.DateTextBox See dijit.form.DateTextBox
dojox.grid.editors.Editor See dijit.Editor
dojox.grid.editors.select Similar to dojox.grid.editors.ComboBox, but doesn't allow freeform values
String[] options: text of each item
String[] values: value for each item
Boolean returnIndex: editor returns only the index of the selected option and not the value
dojox.grid.editors.TextBox See dijit.form.TextBox
dojox.grid.editors.TimeTextBox See dijit.form.TimeTextBox

Events

As we alluded to in the last few pages, selection and cell editing is pointless without some kind of background processing. So how do you hook code into these places? Through Dojo's event model, of course!

Editing Changes

If you're using a writable dojo.data datastore, you simply hook your procedures into the dojo.data Notification API. Suppose in our running example, we make the Description field editable:

cells: [[
    {name: 'Namespace', field:0, width:"30em"}, 
    {name: 'Description', field:3, width:"30em",
        editor: dojox.grid.editors.Dijit }
  ]
],

Then, we place the hook into dojo.data.Notification's onSet extension point:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div dojoType="dojo.data.ItemFileWriteStore"
        jsId="jsonStore" url="dijits.txt">

    <script type="dojo/connect" event="onSet" args="item,attr,oldVal,newVal">
       console.debug("About to change "+attr+" from "+oldVal+" to "+newVal);
       // Save the record with dojo.xhrPost or your favorite remote method
    </script>
</div>

Here is the entire source code. Note how we place the layout initialization code in a dojo/method block inside the Grid tag. That's due to the editor class dojox.grid.editors.Dijit in the view definition. When you're using CDN, the dojox.grid.editors package is not available directly after the dojo.require., so we can't place the initialization code after it (as we did in our previous examples). By using a dojo/method, we can place this code close to its use (the Grid tag) and it's guaranteed to run after all dojo.require'd modules have loaded. You can also use dojo.addOnLoad to accomplish this.

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Test dojox.Grid Editing</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dojox/grid/_grid/tundraGrid.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/resources/dojo.css"
        body {
            font-size: 0.9em;
            font-family: Geneva, Arial, Helvetica, sans-serif;
        }
        .heading {
            font-weight: bold;
            padding-bottom: 0.25em;
        }
               
        #grid {
            border: 1px solid #333;
            width: 40em;
            height: 30em;
        }
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"
            djConfig="isDebug:false, parseOnLoad: true">
</script>
    <script type="text/javascript">
        dojo.require("dojo.data.ItemFileWriteStore");
        dojo.require("dojox.grid.Grid");
        dojo.require("dojox.grid._data.model");
        dojo.require("dojox.grid.editors");
        dojo.require("dojo.parser");
   
</script>
</head>
<body class="tundra">
<div class="heading">Grid Events</div>
    <div dojoType="dojo.data.ItemFileWriteStore"
        jsId="jsonStore" url="dijits.txt">

        <script type="dojo/connect" event="onSet" args="item,attr,oldVal,newVal">
            console.debug("About to change "+attr+" from "+oldVal+" to "+newVal);
        </script>
    </div>
    <div dojoType="dojox.grid.data.DojoData" jsId="model"
        rowsPerPage="20" store="jsonStore" query="{ namespace: '*' }"
        clientSort="true">

    </div>
    <div id="grid" elasticView="2" dojoType="dojox.Grid" model="model"
        jsId="thisGrid">

        <script type="dojo/method">
            var view1 = {
                cells: [[
                    {name: 'Namespace', field:0, width:"30em"},
                    {name: 'Description', field:3, width:"30em"}
                  ],
                  [
                    {name: 'Summary', field:2, colspan:2,
                     editor: dojox.grid.editors.Dijit }
                  ]
                ]
            };
            var rowbar = {
               type: 'dojox.GridRowView', width: '20px'
            };
            var fixedColumn = {
                 noscroll: true,
                 cells: [[ {name: 'Class', field:1} ]]
            };
           
            // When you initialize inside the dojo/method script, you must set the
            // structure manually.
            var layout = [ rowbar, fixedColumn, view1 ];
            thisGrid.setStructure(layout);
        </script>
       </div>
</body>
</html>

You can read more on the dojo.data Notification API in Part 3 of the book, but here are the basics for your Grid needs:

  • onSet: function(/* item */ item, /* attribute-name-string */ attribute, /* object | array */ oldValue, /* object | array */ newValue ) - called after any cell is edited and saved.
  • onNew: function(/* item */ newItem,) - called after a row is added to the grid.
  • onDelete: function(/* item */ deletedItem) - called after a row is deleted

Low-Level Events

For more granular event processing, you can hook into Grid events. Each event calls the function you provide, passing back the event object. If e is the event, the interesting stuff is in:

  • e.rowIndex: the row number
  • e.cell.index: the column (cell) number. Taken with e.rowIndex, effectively gives you coordinates of a cell event.
  • e.keyCode: keystroke value, only applicable to keydown event.
Entity click/double click mouse over/out right click
Data Cell onCellClick
onCellDblClick
onCellMouseOver
onCellMouseOut
onCellContextMenu
Hdr Cell onHeaderCellClick
onHeaderCellDblClick
onHeaderCellMouseOver
onHeaderCellMouseOut
onHeaderCellContextMenu
Data Row onRowClick
onRowDblClick
onRowMouseOver
onRowMouseOut
onRowContextMenu
Hdr Row onHeaderClick
onHeaderDblClick
onHeaderMouseOver
onHeaderMouseOut
onHeaderContextMenu

Styles

Cell Styles

Fixed styles that apply to all cells of a column are settable in the view. The properties you need:

  • classes: sets a CSS class for both header and contents
  • styles: sets a CSS style for both header and contents
  • headerClasses: sets a CSS class for column header. Combines with "classes".
  • headerStyles: sets a CSS style for column header. Combines with "styles"
  • cellClasses: sets a CSS class for the contents. Combines with "classes".
  • cellClasses: sets a CSS class for the contents. Combines with "styles"

The onStyleRow Extension Point

The above properties make sweeping style changes across a column. But how do you change styles for individual cells? For example, suppose you want to color negative numbers red and positive numbers black.

The onStyleRow extension point can do this. Grid passes a Row object to your onStyleRow method. You set the properties customStyles and/or customClasses in this object, and Grid will restyle your row accordingly. The Row object has the following properties:

  • selected, true if the row is selected;
  • over: true of the mouse is over the row;
  • odd: true if the row is odd.
  • customClasses: you set this property for the CSS class
  • customStyles: does the same with CSS styles

In the following example, we look at each row and color the row text red if the Dijit contains a description.

function colorDescriptions(inRow) {
   if (model.getRow(inRow.index) === undefined)
      return;
   if (model.getRow(inRow.index).description != '')
      inRow.customStyles = 'color:red';
}

You connect this function to the extension point in the Grid tag:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<div id="grid" elasticView="2" dojoType="dojox.Grid" model="model"
            structure="layout" onStyleRow="colorDescriptions">
</div>

And behold ... red text where the row has a non-blank description

[inline:grid_demo4.png]

Cell Formatters

A cell formatter alters the text in the cell, not the styles. As you might guess, this is useful for formatting numbers, currency, dates, percentages, etc. The cell formatter is specified with the formatter extension point in the cell object:

{ name: 'Amount', formatter: formatCurrency, field: "moola"},

And define the formatter itself separately. (You can also use a function literal inside the cell definition).

function formatCurrency(inDatum){
	return isNaN(inDatum) ? '...' : dojo.currency.format(inDatum, { currency: 'USD' });
}

Liberal use of Dojo i18n for formatting dates and numbers is strongly encouraged!

Summary Rows

Grouping data for summarization requires a simple strategy. We will calculate a summary subrow for every row in the table, then just hide the ones not on a group boundary. So here is some (boring!) numeric data:

{ 
	identifier: 'id',
	label: 'name',
	items: [
        { id:'Q1_06', name: 'Q1 2006', year:2006, quarter:1, sales:345436 },
        { id:'Q2_06', name: 'Q2 2006', year:2006, quarter:2, sales:234525 },
        { id:'Q3_06', name: 'Q3 2006', year:2006, quarter:3, sales:129104 },
        { id:'Q4_06', name: 'Q4 2006', year:2006, quarter:4, sales:-10000 },
        { id:'Q1_07', name: 'Q1 2007', year:2007, quarter:1, sales:-178775 },
        { id:'Q2_07', name: 'Q2 2007', year:2007, quarter:2, sales:286027 },
        { id:'Q3_07', name: 'Q3 2007', year:2007, quarter:3, sales:429546 },
        { id:'Q4_07', name: 'Q4 2007', year:2007, quarter:4, sales:946375 }
    ]
}

To implement our strategy, we first build two functions which supply the total and total label for each totalling subrow.

function getYearlyLabel(inRowIndex) {
            return model.getRow(inRowIndex) ? "Total for " + model.getRow(inRowIndex).year : 'None';
        }
        function getYearlyTotal(inRowIndex) {
            return model.getRow(inRowIndex) ? (yearlyTotal += model.getRow(inRowIndex).sales) : -1;
        }

Next, we make an onAfterRow procedure to hide all the rows that are not after Q4

// inRow is an array of subRows. we hide the summary subRow except for every nth row
function onAfterRow(inDataIndex, inRow) {
    // note that header row inDataIndex == -1
    inRow[1].hidden = true;

    // Before rows 3, 7, 11 turn on display of the total
    if (inDataIndex != -1 && inDataIndex % 4 == 3) {
	yearlyTotal = 0;
        inRow[1].hidden = false;
    }
}

Then we wire it all up in the view definition:

var view1 = {
    onAfterRow: onAfterRow,
	cells: [[
		{name: 'Year/Quarter', field:'name'}, 
		{name: 'Sales', field:'sales'}
	],[
	    // The summary subrow, which will be hidden on most rows
		{name: 'Cell2', get: getYearlyLabel },
		{name: 'Yearly Sales', get:getYearlyTotal, styles:'font-weight:bold;'},
	]]
};

And the summary rows are displayed. Currently this example shows the summary row for all rows. A question is pending on the forums about this, and the example will be fixed accordingly.

Model Options

Array Models

Instead of feeding dojo.data sources to the grid, you may feed it a two-dimensional array. This approach works well for grids with fixed data, e.g. static reference tables. Here's an example culled from the unit test /dojoroot/dojox/grid/tests/test_grid.html. Note the following:

  • Fields are numbered, not named, and start at 0.
  • If the cells are laid out in the same order as the data elements, you may omit the field: property in the cells.
  • Placing the initialization code in addOnLoad is necessary when using CDN because of the dojox.grid.data.Table reference.
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<script type="text/javascript">
        dojo.require("dojox.grid.Grid");
        dojo.require("dojox.grid._data.model");
        dojo.require("dojo.parser");
    // We need to place the initializations here because of CDN.  This way they
//    are run after dojo.require's are loaded (we need dojox.grid.data.Table
    //    in particular, but before grid is drawn.         
        dojo.addOnLoad(function() {
                var data = [
                [ "normal", false, "new", 'But are not followed by two hexadecimal',
                        29.91, 10, false ],
                [ "important", false, "new", 'Because a % sign always indicates', 9.33, -5, false ],
                [ "important", false, "read", 'Signs can be selectively', 19.34, 0, true ],
                [ "note", false, "read", 'However the reserved characters', 15.63, 0, true ],
                [ "normal", false, "replied", 'It is therefore necessary', 24.22, 5.50, true ],
                [ "important", false, "replied", 'To problems of corruption by', 9.12, -3, true ],
                [ "note", false, "replied", 'Which would simply be awkward in', 12.15, -4, false ]
                ];
                // global var "model"
                var model = new dojox.grid.data.Table(null, data);
                var view1 = {
                        cells: [[
                                {name: 'Column 0'}, {name: 'Column 1'}, {name: 'Column 2'},
                                {name: 'Column 3', width: "150px"}, {name: 'Column 4'}
                        ],[
                                {name: 'Column 5'}, {name: 'Column 6'}, {name: 'Column 7'},
                                {name: 'Column 8', field: 3, colSpan: 2}
                        ]]
                };
                var layout = [ view1 ];
           // Now set the model and structure
           gridWidget.setModel(model);
           gridWidget.setStructure(layout);
    });
        </script>
       
</script>
</head>
<body class="tundra">
<div class="heading">dojox.Grid Basic Test</div>
<div id="grid" dojoType="dojox.Grid" jsId="gridWidget"></div>
</body>
</html>
</script>

Observers

You can watch for changes to cells at the model level. This is called setting an observer and it followed the familiar Observer Design Pattern. With array-fed Grids, these are your only hook-in points for sending XHR back to the server. As we saw in Events, you can use dojo.Data's notification API, or the Observer API for the same thing.

To observe a model, you first create an object literal with function properties. These functions must be named:

  • modelChange: called when any cell data changes (due to editing or calling change methods on the model).
  • modelInsertion: called when a row is added
  • modelRemoval: called when a row is removed.
  • modelAllChange: called when entire model needs to be re-rendered.
  • modelRowChange: called when a row is changed
  • modelDatumChange: called when cell data changes

For example, this observer watches all model changes and updates a visible row count.

var modelObservers = {
   modelChange:function(){
	dojo.byId("rowCount").innerHTML = 'Row count: ' + model.count; 
   }
}

Then you register the observer with the model:

model.observer(modelObservers);
dojox.grid.data.Model
The raw data behind a grid. Can be retrieved from a grid by accessing the grid's public 'model' attribute. The model can be set for a grid by calling setModel(newModel).
Properties
clientSort Boolean User is allowed to sort by clicking column headers (dojo.data models only)
count Integer Number of rows currently in the model
query Object dojo.data query for current store (dojo.data stores only)
rowsPerPage Integer Rows to scroll down before another set of rows is rendered.
store String dojo.data store variable (dojo.data stores only)
updating Integer Number of rows in an UPDATING state
Methods
String getDatum(/* Integer */inRowIndex, /* Integer */inColIndex) Return data at the location. Column indexes are id's for dojo.data models, integers for array models.
Integer getColCount() Return number of columns
Object getRow(/* Integer */inRowIndex) Returns item for dojo.data elements, array for array-based elements, at inRowIndex
Integer getRowCount() Returns number of rows in model
notObserver(/* Object */ inObserver) De-register an observer.
observer(/* Object */ inObserver, /* String */inPrefix) Register an observer object with the model. inPrefix is added to each function name before calling, as in myPrefixModelChange. That way you can specify multiple observers in the same object.
Integer pageToRow(/* Integer */inPageIndex) Starting row number on given page
Integer rowToPage(/* Integer */inRowIndex) Return page number for this row
setDatum(/* Object */inDatum, /* Integer */inRowIndex, /* Integer */inColIndex) Set the data at the given position. Normally called by cell editing.
setRow(/* Object */inRow,/* Integer */inRowIndex) Overwrite the row at the given index. Row must be in same object format as other rows, e.g. an item for dojo.data models or array for array models.
swap(/* Integer */inIndexA, /* Integer */inIndexB) Swap rows A and B in model.

Cell Options

Grid neatly separates the model from the view in its MVC implementation. We just covered the model. Now we'll cover the view - that is, everything used to display the model elements.

In the world of Grid, the structure is the largest unit. Structures are composed of views. Views are composed of cells (what we normally think of as a column). We'll start at this lowest level first. As we've seen a cell is defined by a JavaScript object like this:

{ name: 'Apple', field: 'apple', width: '4.5em' }

This defines a column with the heading of Apple and initial width of 4.5em, and mapped to the field 'apple' in the model. The field index can be a string, as is the case for dojo.Data-fed grids, or a number, as in array-fed Grids.

Cells themselves are not directly addressable. You usually get them by starting with a grid variable and work downwards:

// get the cell in the third view, second subrow, fourth column (all indexes are 0-based).
var thisCell = mygrid.structure[2].cells[1][3];

From here, you can access these properties and call these methods on thisCell:

Attributes
cellClasses String CSS class applied to data
cellStyles String CSS Styles applied to data
classes String CSS Classes applied to all column: data and header
colSpan Integer Like colspan in HTML, number of columns each cell occupies. Only meaningful if there are subrows in each row, otherwise ignored.
editor Class For editable cells, this can be either "dojox.grid.editors.Dijit" for a Dijit form control or "dojox.grid.editors.Editor" for the rich text editor. (The rich text editoris essentially Dijit's with some modifications to make it nicer in a grid - like a shared toolbar.) Grid also bundles its own editors like dojox.grid.editors.bool for Booleans, but they are redundant with the Dijit widgets.
editorClass String If editor="dojox.grid.editors.Dijit", this designates the Dijit form widget to use. Note: this is a string with the class name, not the class itself.
extraField Integer Index field like "field", but tacked on
field Integer Index of field data in from model
headerClasses String CSS class applied to column header
headerStyles String CSS Styles applied to column header
name String Name used for the column header
noresize Boolean If true, column cannot be resized.
rowSpan Integer Number of subrows that each cell in this column occupies.
styles String CSS styles applied to all column: data and header
value String Constant value to placed in each column cell. Can contain HTML.
width Number Initial width of the column in ems
Extension Points
formatter(/* String */ inDatum) Function which handles formatting the cell data.
get(/* Integer */ inRowIndex) Name of the function called to get a value.

View Options

Attributes
cells dojox.grid.cell[] Array of cell objects, each defining a column
defaultCell dojox.grid.cell Properties of this cell are used as defaults in all cells
defaultWidth String width of cells (columns), if not specified in the cell itself
noscroll Boolean If true, do not draw scroll bars on right or bottom.
rowPad Integer Space to use between rows, in pixels
type String Name of class for programmatically-generated views like GridRowView.
viewWidth String Width of entire view in valid CSS units.
Methods
Node getCellNode(/* Integer */ inRowIndex, /* Integer */ inCellIndex) Get cell at the specified location
Integer getColumnsWidth() Width of data columns, in px
String getContentWidth() Width of the containing box in CSS units, minus scrollbar (= getColumnsWidth + padding and borders, etc.)
Node getRowNode(/* Integer */ inRowIndex) Get row at the specified location
Integer getScrollbarWidth() Width of scrollbar in px, 0 for noscroll
String getWidth() Total width of scrollbar and columns in CSS units
Boolean hasScrollbar() True if there are enough rows to display a scrollbar.
resize() resizeHeight(), then resizeWidth()
resizeHeight() Resize to fit new height of containing box
resizeWidth() Resize to fit new width of containing box
setColWidth(/* Integer */ inIndex, /* Integer */inWidth) Resize cell (column) at inIndex to inWidth pixels
setSize(/* Integer */ w, /* Integer */ h) Set size of the bounding box. Call resize() after.

Grid Tag

Grid Sizing

By default, the grid fits exactly in the parent DOM node provided for it. If all of the rows do not fit, a vertical scroll bar appears. Likewise, if all the columns don't fit, the horizontal scroll bar appears. Nothing surprising there.

The following properties resize the grid to fit all of the columns. In essence, setting either of these makes the appropriate scroll bar disappear.

  • autoHeight: resize the grid height to fit all of the rows.
  • autoWidth: resize the grid width to fit all the rows.

You may either set these on the Grid tag itself, or set the properties through JavaScript and call dojox.grid.Grid.update() to redraw. You can also resize the width and height of the container (dojo.contentBox is good for this) and call update().

dijit.Grid
A grid widget with virtual scrolling, cell editing, complex rows, sorting, fixed columns, sizeable columns, etc.
Attributes
autoHeight Boolean If autoHeight is true, grid height is automatically set to fit the data.
autoRender Boolean If autoRender is true, grid will render itself after initialization.
autoWidth Boolean If autoWidth is true, grid width is automatically set to fit the data.
defaultHeight string default height of the grid, measured in any valid css unit.
elasticView Integer One of the views in the grid may be "elastic", that is: expanding or contracting to fill the remaining size when all non-elastic elements are placed. By default, the middle view is elastic. Specifying this property makes the indexed grid view elastic.
fastScroll Boolean flag modifies vertical scrolling behavior. Defaults to true but set to false for slower scroll performance but more immediate scrolling feedback
keepRows Integer Number of rows to keep in the rendering cache.
model String or Object Current grid data model. Should only be used for retrieving the model. Setting should be accomplished using setModel(newModel) as outlined below.
rowCount Integer Number of rows to display
rowsPerPage Integer Number of rows to render at a time.
singleClickEdit Boolean Single-click starts editing. Default is double-click
structure Object or String View layout defintion. Can be set to a layout object, or to the (string) name of a layout object.
Methods
addRow(/* Array */ inRowData, /* Integer */ inIndex) Add row inRowData after row[inIndex] in both displayed grid and model
String get(/* Integer */ inRowIndex) Get raw data at row inRowIndex in the current cell position.
dojox.grid.Cell getCell(/* Integer */ inIndex) Get the cell object (the column definition) in column inIndex
String getCellName(/* Integer */ inIndex) Get the column name of inIndex
Boolean canSort(/* Integer */ inSortInfo) Sort information, in sortInfo is 1-based index of column on which to sort, positive for an ascending sort and negative for a descending sort returns true if grid can be sorted on the given column in the given direction
Boolean getSortAsc(/* Integer */ inSortInfo) returns true if grid is sorted in an ascending direction.
Integer getSortIndex() returns index of sorted field
refresh() re-render the grid with the new data model
removeSelectedRows() remove all selected rows in displayed grid and model
render() Render the grid, headers, and views. Edit and scrolling states are reset. To retain edit and scrolling states, see Update.
renderAtIdle() Same as render, but wait until all background processing has completed.
resize() Call after setting width or height
resizeHeight() Call after setting just the height
setCellWidth(/* Integer */ inIndex. /* String */ inUnitWidth) Set column size to a given CSS unit width
setModel(/* dojox.grid.model */ inModel) set the grid's data model
setSortIndex(/* Integer */ inIndex, /* Boolean */ inAsc) Sets a sort column and direction (true=ascending, false=descending).
setStructure(/* dojox.grid.Structure */ inStructure) Install a new structure and rebuild the grid.
scrollTo(/* Integer */ inTop) Vertically scroll the grid to a given pixel position
scrollToRow(/* Integer */ inRowIndex) Scroll the grid to a specific row.
sort() sort on current sort field
update() Update the grid, retaining edit and scrolling states.
updateRow(/* Integer */inRowIndex) Change the number of rows.
updateRowCount(/* Integer */inRowCount) Update row count property to inRowCount
updateRowStyles(/* Integer */inRowIndex) Update the styles for a row after it's state has changed.

InlineEditBox (1.0)

dijit.InlineEditBox is a new widget in 1.0, similar to the dijit.form.InlineEditBox widget in 0.9

InlineEditBox is best described as a behavior on some text on the page, such that clicking that text brings up an editor, and when the text is saved, the screen is reverted to it's original state (but with the new text). The editor is created on-demand, so as to not slow down page load.

Examples

Edit me - I trigger the onChange callback

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>InlineEdit Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.InlineEditBox");
       dojo.require("dijit.form.TextBox");
       function myHandler(idOfBox, value) {
           console.debug("Edited value from "+idOfBox+" is now "+value);
       }
     </script>
</head>
<body class="tundra">
        <h3 id="editable"
                dojoType="dijit.InlineEditBox" title="h3 example"
                onChange="myHandler(this.id,arguments[0])">

Edit me - I trigger the onChange callback
    </h3>
</body></html>

When a user loads the page, they see the text "Edit me - I trigger the onChange callback". If the user clicks the text, a TextBox widget containing the text "Edit me - I trigger the onChange callback" appears. When the user changes the value and clicks away, the TextBox disappears and the TextBox's contents appear inline.

InlineEditBox supports the textarea mode through the Textarea widget. By simply saying editor=dijit.form.Textarea, you can use that editor. Furthermore, by adding renderAsHtml=true, users can enter HTML into the Textarea and have it appear inline as rich text. :

I'm one big paragraph. Go ahead and edit me. I dare you. The quick brown fox jumped over the lazy dog. Blah blah blah blah blah blah blah ...

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>InlineEdit Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.InlineEditBox");
       dojo.require("dijit.form.Textarea");
     </script>
</head>
<body class="tundra">
    <p id="areaEditable" dojoType="dijit.InlineEditBox" title="p example"
         autoSave="false">

                        I'm one big paragraph.  Go ahead and edit me.  I dare you.
                        The quick brown fox jumped over the lazy dog.  Blah blah blah blah blah blah blah ...
       
</body></html>

When a user loads the page, they see the paragraph of rich text. If the user clicks the text, a Textarea widget containing the paragraph in plain text form appears. When the user changes the value and clicks away, the Textarea disappears and the Textarea's contents appear inline.

InlineEditBox can use any arbitrary widget that has a text value, or has the methods get/setDisplayedValue as an editor. DateTextBox is an example of such a widget. This code shows a DateTextBox as the editor:

1/1/2007
/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #b1b100;} .geshifilter .kw2 {color: #000000; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .coMULTI {color: #808080; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #ff0000;} .geshifilter .nu0 {color: #cc66cc;} .geshifilter .sc0 {color: #00bbdd;} .geshifilter .sc1 {color: #ddbb00;} .geshifilter .sc2 {color: #009900;}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
            "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>InlineEdit Demo</title>
    <style type="text/css">
        @import "http://o.aolcdn.com/dojo/1.0.0/dijit/themes/tundra/tundra.css";
        @import "http://o.aolcdn.com/dojo/1.0.0/dojo/resources/dojo.css"
    </style>
    <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"
        djConfig="parseOnLoad: true">
</script>
    <script type="text/javascript">
       dojo.require("dojo.parser");
       dojo.require("dijit.InlineEditBox");
       dojo.require("dijit.form.DateTextBox");
     </script>
</head>
<body class="tundra">
   <span dojoType="dijit.InlineEditBox" editor="dijit.form.DateTextBox" title="date example"
               width="200px" title="purchase date as mm/dd/yy">

        1/1/2007
   </span>
</body></html>

Note that the originally displayed text is generated by the server, and thus must be in the correct locale for the client machine. Since the server is generating the text, that burden of localizing the text falls on the server.

The InlineEditBox can wrap around any widget that implements the following interface:

/* GeSHi (C) 2004 - 2007 Nigel McNie (http://qbnz.com/highlighter) */ .geshifilter {font-family: monospace;} .geshifilter .imp {font-weight: bold; color: red;} .geshifilter .kw1 {color: #000066; font-weight: bold;} .geshifilter .kw2 {color: #003366; font-weight: bold;} .geshifilter .kw3 {color: #000066;} .geshifilter .co1 {color: #009900; font-style: italic;} .geshifilter .coMULTI {color: #009900; font-style: italic;} .geshifilter .es0 {color: #000099; font-weight: bold;} .geshifilter .br0 {color: #66cc66;} .geshifilter .st0 {color: #3366CC;} .geshifilter .nu0 {color: #CC0000;} .geshifilter .me1 {color: #006600;} .geshifilter .re0 {color: #0066FF;}
/* void */ setTextValue(/*String*/ value) { ... }
String value = getTextValue() {... }
/* void */ focus() { ... }

The contained widget's setTextValue() method is called with the previously displayed text. When the Save button is pressed, the editing widget's getTextValue() method is called to retrieve the new text. After which, the editing widget is hidden, and the returned text is displayed. The focus method allows the editing widget to intelligently set focus to an appropriate node.


dijit.InlineEditBox
Edit behavior applied to a node
Attributes
autoSave Boolean
true
Changing the value automatically saves it; don't have to push save button (and save button isn't even displayed)
buttonSave String Save button label
buttonCancel String Cancel button label
editing Boolean
false
Is the node currently in edit mode?
editor String
dijit.form.TextBox
name of widget to use as editor
editorParams Object Parameters to pass to editor (in addition to the value being edited. ex: "{constraints: {places:0} }"
renderAsHTML Boolean
false
true if the editor widget and takes HTML for setValue(), and returns HTML from getValue(). Ex: dijit.Editor
value String Read-only value of box
Methods
cancel(/*Boolean*/ focus) evert to display mode, discarding any changes made in the editor
save(/*Boolean*/ focus) Focus on the display mode text
Extension Points
onChange(/* String */value) User should set this handler to be notified of changes to value

Accessibility


General Behavior

When InlineEditBoxes are "closed" they appear as text but are tab stops in the keyboard focus ring and have an accessible role of button. They can have autoSave or non-autoSave behavior. When an non-autoSave InlineEditBox is open it has associated Save and Cancel buttons. An autoSave InlineEditBox does not have these buttons and they act like miniature forms or dialogs, i.e pressing the Esc key will close the widget and pressing the Enter key will close the widget, saving and displaying the text.

Note that since InlineEditBoxes may be used on the page without a traditional label element, the developer should add a title attribute in order to provide a description that is available to screen reader users. The title will also be displayed by the browser when the user places the mouse over the element.



Keyboard


If the widget is closed.


ActionKey
Navigate to the next widget in the tab order.Tab
Navigate to the prior widget in the tab order.Shift+Tab
Open the widget.Enter or spacebar
Note: The Esc key is ignored.

TextBox with autoSave specified and the TextBox is open:


ActionKeyComments
Navigate to the next widget in the tab order.TabThe data is saved and the widget closes.
Navigate to the prior widget in the tab order.Shift+TabThe data is saved and the widget closes.
Close the TextBox, saving changes.EnterKeyboard focus is on the closed InlineEditBox.
Revert the last entry.EscIf the user has not entered data, the TextBox is closed.
Close the Textarea, discarding changes.EscIf the user has entered data, the Esc must be pressed two times; the first time the data will be reverted; the second time the TextBox will close.

Textarea with autoSave specified and the Textarea is open:


ActionKeyComments
Navigate to the next widget in the tab order.Tab (press twice in Firefox - see the Known Issues below)The data is saved and the widget closes.
Navigate to the prior widget in the tab order.Shift+TabThe data is saved and the widget closes.
Enter a newline into the text.EnterThere is no equivalent to the Enter key behavior of TextBoxes. The user would have to use something like Tab and Shift + Tab.
Revert the last entry.EscIf the user has not entered data, the Textarea is closed.
Close the Textarea, discarding changes.EscIf the user has entered data, the Esc must be pressed two times; the first time the data will be reverted; the second time the Textarea will close.

TextBox without autoSave specified, the TextBox is open, keyboard focus is in the edit field:


ActionKeyComments
Navigate to the Save or Cancel button.TabFocus changes to the Save button if the data has been changed, otherwise it moves to the Cancel button.
Navigate to the prior widget in the tab order.Shift+TabThe TextBox remains open.
Close the TextBox, saving changes.Tab to the Save button, then press the Enter keyKeyboard focus is on the closed InlineEditBox.
Revert the last entry.EscIf the user has not entered data, the Esc key is ignored.
Close the Text Box, discarding changes.Tab to the Cancel button, then press the Enter key.Keyboard focus is on the closed InlineEditBox.
Note: The Enter key is ignored when focus is in the edit field.

Textarea without autoSave specified, the Textarea is open, keyboard focus is in the edit field:


ActionKeyComments
Navigate to the Save or Cancel button.Tab (press twice in Firefox - see the Known Issues below)Focus changes to the Save button if the data has been changed, otherwise it moves to the Cancel button.