410 likes | 571 Views
jsm. An introduction to jsm http://js-m.sourceforge.net/ Ronald Plöger Version: 1.03 September, 25th 2006. 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.
E N D
jsm An introduction to jsm http://js-m.sourceforge.net/ Ronald Plöger Version: 1.03 September, 25th 2006
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
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
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 object var myTable = new JsmTable(); //add header column myTable.setHeaderRow(new JsmTr().addCell(new JsmTh("my header"); //add rows myTable.addRow(new JsmTr(1).addCell(new JsmTd("lalalala"))); … //render table myTable.render(); </script>
How to set jsm up (2) 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 <div id="tt"></div> <script> var lala = new JsmTable("tt", "lala"); …
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.
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"));
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")));
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)
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)
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
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.
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);
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.
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). 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 myTable.sort("two"); myTable.render();
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 name myTable.addComparator("two", JsmNumberComparator); myTable.addComparator("three", JsmDateComparator);
Resizability The table is resizable if specified during construction of the JsmTable object myTable = new JsmTable("myTableContainer", "myTable", true);
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("")));
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); } …
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)
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
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());
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 filters myTable.removeFilters(); //add new RegEx filter myTable.addFilter(new JsmRegExFilter(0, ".*" + document.getElementById('myFilter').value + ".*", true)); //re-render the table myTable.render();
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>
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 cells new JsmTd("%{Number(this.tr.table.getValue(this.tr.getIndex(), this.getIndex()-1)) + Number(this.tr.table.getValue(this.tr.getIndex(), this.getIndex()-2))}"))
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 request request.setAttribute("myTable", table); The client side: <%JsmTable table = (JsmTable) request.getAttribute("myTable");%> <script type="text/javascript" language="JavaScript"> <%=table.generateJavaScript()%> myTable.render(); </script>
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) { ... } }));
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:tablerows="<%=function.getChangeComments()%>" containerId="ChangeCommentsContainer" variableName="ChangeComments" render="true"> <jsm:thname="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>
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
RemoteCall (2) newRemoteCall('/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"); } }
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.
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'}
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);
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)
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()); } }
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“); …
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
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))
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 object JsmTd: this.tr.table JsmTr: this.table Widgets with values and labels (e.g. JsmSelectField, JsmDoubleSelectField) hold the selected value in a meta-data called ‘value’
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.