- 1 - jsm An introduction to jsm Ronald Plöger Version: 1.03 September, 25th 2006.

Post on 01-Apr-2015

217 views 0 download

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("&nbsp").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.