I've been asked to blog about array support in Metawidget. Displaying and manipulating arrays is fraught with personal choices about UI design. For example: is item deletion done by a right-click menu? Or a delete icon on the end of each row? Or at the start of each row? Is addition done using a blank row at the end of the table? Or as a footer row? And so on. It's the same problem we encountered for server-side rendering.
Because of this, Metawidget only goes so far out-of-the-box. metawidget.widgetbuilder.HtmlWidgetBuilder will render an array as a read-only table, but no further. Here's a complete example:
<!DOCTYPE HTML>
<html>
<head>
<script src="http://metawidget.org/js/3.5/metawidget-core.min.js"></script>
<style>
#metawidget {
border: 1px solid #cccccc;
width: 250px;
border-radius: 10px;
padding: 10px;
margin: 50px auto;
}
</style>
</head>
<body>
<div id="metawidget"/>
<script type="text/javascript">
var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ));
mw.toInspect = {
firstname: 'Homer',
surname: 'Simpson',
age: 36,
family: [ {
id: 0,
firstname: 'Marge',
surname: 'Simpson'
}, {
id: 1,
firstname: 'Bart',
surname: 'Simpson'
} ]
};
mw.buildWidgets();
</script>
</body>
</html>
This will render:
For custom use cases, you can create your own WidgetBuilder and chain it together with the original ones using metawidget.widgetbuilder.CompositeWidgetBuilder. Then your own WidgetBuilder can handle special cases (like arrays) and 'fall back' to the original WidgetBuilders for everything else. Remember Metawidget only strives to automate what you'd normally do manually. Metawidget is not trying to be a big new framework. So the general approach is a) work out how to do something manually; b) write a WidgetBuilder to automate it.
As a shortcut, the existing metawidget.widgetbuilder.HtmlWidgetBuilder also has some methods you can override like createTable and addColumn. Here's a complete example:
<!DOCTYPE HTML>
<html>
<head>
<script src="http://metawidget.org/js/3.5/metawidget-core.min.js"></script>
<style>
#metawidget {
border: 1px solid #cccccc;
width: 350px;
border-radius: 10px;
padding: 10px;
margin: 50px auto;
}
</style>
</head>
<body>
<div id="metawidget"/>
<script type="text/javascript">
var _myWidgetBuilder = new metawidget.widgetbuilder.HtmlWidgetBuilder();
var _superAddRow = _myWidgetBuilder.addRow;
_myWidgetBuilder.addRow = function( tbody, value, columnAttributes, mw ) {
var tr = _superAddRow.call( this, tbody, value, columnAttributes, mw );
var anchor = document.createElement( 'a' );
anchor.setAttribute( 'href', '#' );
anchor.setAttribute( 'onclick', 'this.parentNode.parentNode.parentNode.removeChild( this.parentNode.parentNode )' );
anchor.innerHTML = 'Delete';
var td = document.createElement( 'td' );
td.appendChild( anchor );
tr.appendChild( td );
return tr;
};
var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
widgetBuilder: _myWidgetBuilder
} );
mw.toInspect = {
firstname: 'Homer',
surname: 'Simpson',
age: 36,
family: [ {
id: 0,
firstname: 'Marge',
surname: 'Simpson'
}, {
id: 1,
firstname: 'Bart',
surname: 'Simpson'
} ]
};
mw.buildWidgets();
</script>
</body>
</html>
UPDATE: signature of addRow has changed in newer releases of Metawidget to be addRow( tbody, value, row, columnAttributes, elementName, attributes, mw ). Please update the function declaration and .call lines appropriately.
This will render:
Of course, you may also need fine-grained control over what columns you display, and in what order. For this, you can use JSON Schema to describe the schema of your array items. Here's a complete example:
<!DOCTYPE HTML>
<html>
<head>
<script src="http://metawidget.org/js/3.5/metawidget-core.min.js"></script>
<style>
#metawidget {
border: 1px solid #cccccc;
width: 350px;
border-radius: 10px;
padding: 10px;
margin: 50px auto;
}
</style>
</head>
<body>
<div id="metawidget"/>
<script type="text/javascript">
var mw = new metawidget.Metawidget( document.getElementById( 'metawidget' ), {
inspector: new metawidget.inspector.CompositeInspector( [
new metawidget.inspector.PropertyTypeInspector(),
new metawidget.inspector.JsonSchemaInspector( {
properties: {
family: {
items: {
properties: {
id: {
hidden: true
},
employer: {
type: 'string'
}
}
}
}
}
} )
] )
} );
mw.toInspect = {
firstname: 'Homer',
surname: 'Simpson',
age: 36,
family: [ {
id: 0,
firstname: 'Marge',
surname: 'Simpson'
}, {
id: 1,
firstname: 'Bart',
surname: 'Simpson'
} ]
};
mw.buildWidgets();
</script>
</body>
</html>
This will render:
Note the JSON Schema is being combined with the inspection results from PropertyTypeInspector, so you don't have to re-specify columns like firstname and surname, or attributes like the type of id.
Feedback welcome!