- 1 - jsm an introduction to jsm ronald plöger version: 1.03 september, 25th 2006
TRANSCRIPT
- 1 -
jsm
An introduction to jsm
http://js-m.sourceforge.net/
Ronald PlögerVersion: 1.03
September, 25th 2006
- 2 -
What is jsm
jsm is a JavaScript library
Currently focusing on displaying/editing information in tables
Java classes to bridge the gap from the server to the client side.
Initialy created to build highly responsive and userfriendly CRUD screens in a rapid way
- 3 -
Advantages of jsm
Rapid development of screens with table based information, eg. auto generated filter
Unified look & feel of tables, eg. developer can not forget to add title-attribute on table cell
User experiences ‘fast’ screens
- 4 -
How to set jsm up
<!–- Include jsm.css--><link rel=stylesheet type="text/css" href="/css/jsm.css"><!–- Set image path and include jsm.js--><script type="text/javascript">jsmImageFolder='/images/';</script><script src="/scripts/jsm.js" type="text/javascript"></script>
<!–- Define HTML element to hold table (div, td, …) --><div id="myTableContainer"></div><script> //create table objectvar myTable = new JsmTable();//add header columnmyTable.setHeaderRow(new JsmTr().addCell(new JsmTh("my header");//add rowsmyTable.addRow(new JsmTr(1).addCell(new JsmTd("lalalala")));…//render tablemyTable.render();
</script>
- 5 -
How to set jsm up (2)
<div id="tt"></div>
<script>var lala = new JsmTable("tt", "lala");…
JsmTable needs: An empty HTML element (specified by its id) to render itself
into (default: ‘myTableContainer’) The name of the variable holding it (default: ‘myTable’)
If your setup differs, for example because you need to display multiple JsmTables on a page, you have to hand over the element id and the variable name in the constructor
- 6 -
Column index and name
In many functions you have to specify a column index or name.
The index is zero based, i.e. the 1st columns index is 0.
You can also add names to columns using the JsmTable method addColumnName(columnName, columnIdx).
If you add a header-row the header cells value will be used as a column names, if no column name has been specified using addColumnName(columnName, columnIdx).
Therefore you can only refer to columns by name after adding the header row or after specifying the column names explicitly.
- 7 -
Attributes and meta data
Every object derived from JsmObject can have attributes and meta data.
Attributes will be attached to the rendered HTML element objects (the view component).
Meta data can be used to store information for any purpose.
new JsmTable().setAttribute("cellspacing", "2")
new JsmTh("one").setAttribute("width", "300")
new JsmTd("whatever").setAttribute("colspan", "2");
new JsmTd("whatever").setAttribute("onclick", "alert('hello world')").setAttribute("class", "jsmHand");
new JsmTr().addCell(new JsmTd("one")).setMetaData("id", "1"));
- 8 -
Header & Footer
Every JsmTable can have one header and multiple footer which will not participate in sorting.
myTable.setHeaderRow(new JsmTr().addCell(new JsmTh("one").setAttribute("width", "300")).addCell(new JsmTh("two").setAttribute("width", "150")));
myTable.addFooter(new JsmTr(new JsmTd("This is a footer cell ").setAttribute("colspan", "2")));
- 9 -
Events
The following onclick events will be triggert when clicking on a header cell, a data cell or a row respectively. Empty (except for ‘jsmOnTableHeaderClick’) stub functions exist. The user can implement its own version (after the include of jsm.js) which will be called instead. Parameters are:
table: the JsmTable object rowIdx: the index of the clicked-on row columnIdx: the index of the clicked-on column
function jsmOnTableHeaderClick(table, columnIdx)
function jsmOnTableCellClick(table, rowIdx, columnIdx)
function jsmOnTableRowClick(table, rowIdx)
- 10 -
Events (2)
Instead of the default functions a user can specify the name of a function to be called (which will be called with the same parameters)
myTdObject.setOnClick(„nameOfMyFunctionToCall“);//--> will call nameOfMyFunctionToCall(table, rowIdx, columnIdx)myThObject.setOnClick(„nameOfMyFunctionToCall2“);//--> will call nameOfMyFunctionToCall2(table, columnIdx)myTrObject.setOnClick(„nameOfMyFunctionToCall3“);//--> will call nameOfMyFunctionToCall3(table, rowIdx)
- 11 -
Events (3)
One can also add javascript-code to be called on certain events by specifying special attributes (case sensitive): 'onkeyup', 'onchange', 'onclick', 'onmousedown', 'onmouseup', 'onmousemove'
myCell.setAttribute("onkeyup", "myFunction()"); //--> will call myFunction with no parameters
myCell.setAttribute("onmousemove", "myFunction(‚lala‘, ‚1‘)"); //--> will call myFunction with the specified parameters
myCell.setAttribute("onclick", "self.status=‚hello world‘;alert(‚hello world‘);");
//--> will execute the specified JavaScript
- 12 -
Layout
The layout of the table can be controlled using CSS. A default jsm.css comes with jsm, which you can adapt to your needs.
CSS class Used for
jsmTable Table
jsmHeader Table row (only on the header row)
jsmFooter Table row (only on footer rows)
jsmEven Table row (only on data rows)
jsmOdd Table row (only on data rows)
jsmHand JsmDoubleSelectField / Can be used to indicate that a cell is clickable
jsmButton Filter button
jsmLabel Filter label
- 13 -
JsmActionCells
Action cells are adding functionallity to a table. Depending on this functionallity they can be displayed in
the header row (e.g. jsmAddRowActionCell) the footer row (e.g. jsmGotoFirstPageActionCell) every data row (e.g. jsmEditRowActionCell)
myTable.addFooterActionCell(jsmGotoFirstPageActionCell).addFooterActionCell(jsmGotoPreviousPageActionCell).addFooterActionCell(jsmPageCounter).addFooterActionCell(jsmGotoNextPageActionCell).addFooterActionCell(jsmGotoLastPageActionCell)
myTable.addHeaderActionCell(jsmAddRowActionCell)
myTable.addRowActionCell(jsmEditRowActionCell).addRowActionCell(jsmDeleteRowActionCell);
- 14 -
JsmActionCells (2)
There are default JsmActionCell objects coming with jsm. If additional functionallity is needed it should be easy to create your own action cells.
Before rendering the init(table, rowIdx, columnIdx) method is called.
By default action cells are rendered on the right hand side of the table. Using myTable.setActionCellSide(„left“) the action cells can be displayed on the left hand side.
- 15 -
JsmActionCells (3)
Action cell object Events
jsmEditRowActionCell jsmOnEditRow(table, rowIdx)
jsmOnSaveRow(table, rowIdx)
jsmDeleteRowActionCell jsmOnDeleteRow(table, rowIdx)
jsmAddRowActionCell jsmOnAddRow(table, rowIdx)
jsmShowOnlyHeaderActionCell
jsmHideTableActionCell
jsmGotoFirstPageActionCell
jsmGotoPreviousPageActionCell
jsmGotoNextPageActionCell
jsmGotoLastPageActionCell
jsmPageCounter
- 16 -
Sortability
By default every table column is sortable by clicking on the columns header cell. This is due to the default implementation of the user event ‘jsmOnTableHeaderClick‘ (which can be overwritten).
myTable.sort("two");myTable.render();
function jsmOnTableHeaderClick(table, columnIdx) { table.sort(columnIdx); table.render();}
To sort manually you simply have to call the sort() method and afterwards re-render the table. The sort() methods accepts the column index or the column name as a parameter
- 17 -
Comparators
Sorting of columns are dependent on comparators. The default comparator is ‘jsmAlphaComparator’. Other available comparators are ‘jsmNumberComparator’ and ‘jsmDateComparator’ (depends on the date.js library)
It is easy to write and plug in your own comparators if needed.
//add comparators to the table specifying the column index or namemyTable.addComparator("two", JsmNumberComparator);myTable.addComparator("three", JsmDateComparator);
- 18 -
Resizability
The table is resizable if specified during construction of the JsmTable object
myTable = new JsmTable("myTableContainer", "myTable", true);
- 19 -
Pageability
Using JsmActionCells a table can be made pageable. The action cells could be set in the header or in the footer.
…myTable.addFooterActionCell(jsmGotoFirstPageActionCell).addFooterActionCell(jsmGotoPreviousPageActionCell).addFooterActionCell(jsmPageCounter).addFooterActionCell(jsmGotoNextPageActionCell).addFooterActionCell(jsmGotoLastPageActionCell)
myTable.setPageingMaxRows(4);…myTable.addFooter(new JsmTr(new JsmTd(" ").setTitle("")));
- 20 -
Editability
Using JsmActionCells a table can be made editable.
myTable.addWidget(0, new JsmTextField()).addWidget(1, new JsmSelectField([{'value':0, 'label':"All"},{'value':1, 'label':"UK"},{'value':2, 'label':"Ireland"}]).addOption(3, 'Germany')).addWidget(2, new JsmCheckBox(false, "yes", "no"));
myTable.addHeaderActionCell(jsmAddRowActionCell);myTable.addRowActionCell(jsmEditRowActionCell).addRowActionCell(jsmDeleteRowActionCell);
…
function jsmOnSaveRow(table, rowIdx) { //do whatever you want to do on a save row table.getRow(rowIdx).setDirty(false);}…
- 21 -
Widgets
To make a table editable and to generate a filter widgets are needed. There are standard widgets coming with jsm. It should be easy to create your own widgets if needed.
JsmTextField JsmCheckBox JsmSelectField JsmDoubleSelectField
new JsmTextField();new JsmSelectField([{'value':0, 'label':"All"},{'value':1, 'label':"UK"}]).addOption(2, 'Germany');
new JsmCheckBox(false, "yes", "no");new JsmDoubleSelectField([{'value':0, 'label':"All"},{'value':1, 'label':"UK"},{'value':2, 'label':"Ireland"}], null, 3)
- 22 -
Create your own widgets
Each widget extends JsmWidget. Depending on the usage you will want to implement the following methods:
init(jsmTdObject): Will be called when a JsmTd object of a JsmTr object, which isEditMode() returns true, is rendered.
getNode(): Return the HTML element object
updateModel(table, rowIdx, columnIdx, theViewComponent): Called on save by the jsmEditRowActionCell
- 23 -
Validators
One or more validators can be attached to a widget. The updateModel() method of the widget will call each of the validators before updating the model.
Currently jsm comes with the JsmNumbersOnlyValidator. It should be easy to add your own ones as needed.
A validator has to implement the validate(table, rowIdx, columnIdx, theViewComponent) method which returns true or false.
new JsmTextField().addValidator(new JsmNumbersOnlyValidator());
- 24 -
Filters
Filters can be attached to a JsmTable. These filters will then be consulted before rendering the table.
Currently jsm comes with JsmExactFilter and JsmRegExFilter. It should be easy to implement your own filters if needed.
//remove existing filtersmyTable.removeFilters(); //add new RegEx filter myTable.addFilter(new JsmRegExFilter(0, ".*" + document.getElementById('myFilter').value + ".*", true));
//re-render the table myTable.render();
- 25 -
Auto-generated filter area
A filter area can be auto-generated. You can define the columns for which filter elements will be generated and how many filter elements to display in one row.
The widgets associated with a column will be used as filter elements (using a case insensitive RegEx-Filter with .* at the start and the end).
If this is not sufficient you can add a filter config for a column, specifying a widget and filter.
<div id="myFilterContainer"></div><script>myTable.addWidget(0, new JsmTextField())); myTable.addFilterConfig(1, new JsmSelectField([{'value':'', 'label':"Please select..."}, {'value':0, 'label':"USA"},…]), new JsmExactFilter().setIgnoreCase(false)); myTable.generateFilter('myFilterContainer', [0,1], 2);
</script>
- 26 -
Evaluate expressions
A JsmTd can evaluate JavaScript expressions and will display the return value as the cell value. The expression has to be contained in %{}
//calculate the sum of the values of the previous two cellsnew JsmTd("%{Number(this.tr.table.getValue(this.tr.getIndex(), this.getIndex()-1)) + Number(this.tr.table.getValue(this.tr.getIndex(), this.getIndex()-2))}"))
- 27 -
Java bridge classes
Altough it is possible to write the JavaScript by hand, if the data comes from the server, the Java bridge classes should be used to generate it.
The server side:JsmTable table = new JsmTable().setSortColumn("0").addAttribute("cellSpacing", "1");
JsmTr header = new JsmTr();header.addCell(new JsmTh("one"));header.addCell(new JsmTh("two"));table.setHeader(header);//…add rows, etc. then put JsmTable object into requestrequest.setAttribute("myTable", table);
The client side:<%JsmTable table = (JsmTable) request.getAttribute("myTable");%><script type="text/javascript" language="JavaScript"><%=table.generateJavaScript()%>myTable.render();
</script>
- 28 -
Generate table rows
It is possible, using reflection, to generate JsmTr objects from a collection of business objects. To give the user the opportunity to add information there are post/pre-processing callback methods.
table.addRows(JsmTableUtil.generateJsmTrs(myValueObjects,new String[] {"id", "name"},new String[] { "id" }, new AbstractGenerateJsmTrsCallback() {
public void postProcessing(JsmTr tr, Object valueObject) { ...
}public void preProcessing(JsmTr tr, Object valueObject)
{ ...}
}));
- 29 -
jsm JSP-Tags
To create JavaScript code for simple tables you can use the jsm JSP-Tags. Specify the header names, the java.util.Collection to be used as rows-objects and the property path for each column. You can also specify the containe-id, the JavaScript variable name and if the generated code should contain a .render() call.
<%@ taglib uri="/WEB-INF/jsm.tld" prefix="jsm"%>…<jsm:table rows="<%=function.getChangeComments()%>" containerId="ChangeCommentsContainer" variableName="ChangeComments" render="true"><jsm:th name="Change date" /><jsm:th name="User" /><jsm:th name="Comment" /><jsm:propertyPath path="date"/><jsm:propertyPath path="user.name"/><jsm:propertyPath path="comment"/>
</jsm:table>
- 30 -
RemoteCall
A RemoteCall object can be used to send data to the server. The RemoteCall object is instantated with the following params:
URL to call JSON Map of request parameters A renderer
The renderer will be called upon the server response. It has to have a render(requestParams, response) method.
requestParams: The JSON Map of request parameters send to the server response: The evaluated JSON string the server has responded with
- 31 -
RemoteCall (2)
new RemoteCall('/dealMemo/deleteDraftLog.do', {'id':id, 'rowIdx':rowIdx}, new RemoveDraftLogRenderer());
function RemoveDraftLogRenderer() {}RemoveDraftLogRenderer.prototype.render = function(requestParams, response){//requestParams: A HashMap of the request parameters//response: The parsed JSON structure returned by the called url if ("error" == response['state']) {
addUserErrorMessage(response['message']);}if ("ok" == response['state']) {
myTable.removeRow(requestParams['rowIdx']);myTable.render();addUserMessage("The draft log has been deleted");
}}
- 32 -
Renderers
jsm comes with the standard renders SaveRowRenderer and DeleteRowRenderer.
Both expect the request parameter ‚rowIdx‘ and the response object properties ‚state‘ and ‚message‘.
For further details see the JavaScriptDoc.
- 33 -
Sending data to the server
On the client side:
Send the row’s data as a JSON Map to the server using a RemoteCall object
The keys of the JSON Map are the property paths of the columns or, if not specified, the column names.
The values of the JSON Map are the value meta data ‘value’ or, if not specified, the value of the cells
new RemoteCall('<%=request.getContextPath()%>/saveUser', {'rowIdx':rowIdx, 'jsonizedRow':row.getJSONized(true), 'id':row.getMetaData('id')}, new SaveRowRenderer(table.getVariableName()));
The request parameter 'jsonizedRow' could for example be:{"country":"6", 'lastName':'Caley'}
- 34 -
Sending data to the server (2)
On the server side:
Retrieve the request parameters Load the business object Create a new JSONObject Assign converters to property paths as needed Call JsmTableUtil.setValuesOnObject
String id = request.getParameter("id");User user = userDAO.getById(id);String jsonizedRow = request.getParameter("jsonizedRow");JSONObject json = new JSONObject(jsonizedRow);HashMap converters = new HashMap();converters.put("companyCar", new BooleanConverter());JsmTableUtil.setValuesOnObject(user, json, converters);
- 35 -
Sending data to the server (3)
Note:
You can add the columns property path by using
myJsmTable.addPropertyPath(String columnIdxOrName, String propertyPath)
on the JsmTable object (server-side)
- 36 -
Converters
A converter has to implement the net.sf.jsm.utils.Coverter interface which defines the method:
public Object convert(Object objectToConvert) throws ConversionException
Jsm comes with a BooleanConverter.
private class CountryConverter implements Converter { public Object convert(Object objectToConvert) {
return new CountryDAO().getById(objectToConvert.toString()); }
}
- 37 -
jsmMessage
The following JavaScript methods were created to present messages and warnings to the user. You need to define an HTML element with the id ‘jsmMessage’ and include the jsm.css where the style #jsmMessage is defined.
jsmAppendMessage(theHTML, timeout, warn) jsmAddMessage(theHTML, timeout, warn) jsmClearMessage()
<p id="jsmMessage"> </p>
<script>function jsmOnSaveRow(table, rowIdx) {…jsmAddMessage(„Saving row“);…
- 38 -
Lazy load
To lazily load table data:
On the client: Specify the div’s overflow style to ‘scroll’ Add an onscroll event handler In the event handler function use a RemoteCall to retrieve more JsmTr objects to append to the table when the user has scrolled to the current bottom
On the server: Use JsmTableUtil.getJSONRows to get a JSON array of JsmTr objects
On the client: In the Renderer add the rows setting the ‘renderImmediately’ to true
- 39 -
Lazy load (2)
<div id="myTableContainer" style="height:200px; overflow:scroll;"></div> <script>//add the onscroll eventhandler $('myTableContainer').onscroll=lazyLoad;
function lazyLoad(e) { if ((this.scrollTop + this.offsetHeight)>=(this.scrollHeight)) {
jsmAddMessage('Try to load next 100 words starting from: ' + myTable.getRowCount());
new RemoteCall('/lazyLoading/loadWords', {'start‚ : myTable.getRowCount()}, new LazyLoadRenderer());
}}
function LazyLoadRenderer(tableVariableName) {} LazyLoadRenderer.prototype.render = function(requestParams, response) {myTable.addRows(eval(response['rows']), true);// render immediately
}
//note: the array of JsmTr object contained in response['rows'] was filled//on the server side using://JsmTableUtil.getJSONRows(JsmTableUtil.generateJsmTrs(words), new String[] //{"word"}, null, null))
- 40 -
Tips and Tricks
The rendered HTML element object (view component) has access to its underlying model via the property ‘model’
new JsmTd("click me to show model").setAttribute("onclick", "alert(this.model);")
A JsmCell object has access to its parent JsmTr object which in turn has access to its parent JsmTable objectJsmTd: this.tr.tableJsmTr: this.table
Widgets with values and labels (e.g. JsmSelectField, JsmDoubleSelectField) hold the selected value in a meta-data called ‘value’
- 41 -
Tips and Tricks (2)
To support multiple languages all labels, texts, images, etc. are defined in the file ‘jsmResourceBundle.js’. You can translate those and include the translation, for example depending on the browser locale, into the page.