Spreadsheet
In this guide you will learn how to develop a program to generate and use spreadsheets, giving you the possibility to create new ones, to load and to copy existing ones.
By referring to this guide, you will be able to:
- Use filters to simulate a server to fecth data from an external xml file.
- Use the submitJsonRequest to request data.
- Use the aria templates asynch paradigm.
- Use partial refresh mechanism with repeaters.
- Use aria templates widget @aria:TextField.
Tutorial
Step 1
Create a file using your favourite editor and call it index.html. This will be the bootstrap for your app.
Save it inside the root of your project (e.g. /guides/spreadsheet/).
Step 2
Insideindex.html you will have to load the aria templates framework, define the container div and load the template that you will create.
To load the framework:
<script type="text/javascript" src="/aria/css/atskin-1.2.0.js"></script>
To define the div container:
Aria.loadTemplate({
classpath:'ariadoc.guides.spreadsheet.view.Main',
div:"root",
// In this sample, we associate a controller to our template
moduleCtrl: {
classpath : "ariadoc.guides.spreadsheet.Controller"
}
});
</script>
Step 3
Create a file and call it Main.tpl. This is the template that will be used to display the spreadsheet interface.
Save it inside the view folder inside your project (/guides/spreadsheet/view/).
Step 4
Define the classpath of your template and declare that it has a script and a CSS template.$classpath:'ariadoc.guides.spreadsheet.view.Main',
$hasScript: true,
$css: ['ariadoc.guides.spreadsheet.view.MainStyle']
}}
macro main():
{call mainLinks()/}
{call mainLayout()/}
{/macro}
mainLinks() the links to load, save, copy, cancel and create a new dataset using the @aria:Link widgets:
{var links=[
{name:"Load Dataset",cbfn:"loadDataset"},
{name:"New Dataset",cbfn:"createNewDataset"},
{name:"Copy Dataset",cbfn:"copyDataset"},
{name:"Save",cbfn:"saveChanges"},
{name:"Cancel",cbfn:"cancelChanges"}
]/}
{foreach lnk in links}
{separator} | {/separator}
{@aria:Link {
label:lnk.name,
onclick:{
fn:lnk.cbfn,
scope:moduleCtrl
}
}/}
{/foreach}
id:"dsList",
bindRefreshTo: [
{to:"datasets", inside:data, recursive:false},
{to:"currentDs", inside:data}
]
}}
{if data.datasets.length==0}
No dataset loaded yet..
{else/}
// Display list of currently loaded datasets
Datasets:
{foreach ds in data.datasets}
{separator} - {/separator}
{if ds==data.currentDs}
<b>${ds.name}</b>
{else/}
<a>
${ds.name}
</a>
{/if}
{/foreach}
{/if}
{/section}
mainLayout() a section to display the spreadsheet inside a table.
The table will have this structure:
- for the first row call the
dataDiv()macro to generate a div containing the data specified by the dataCat attribute; - for the second row call the
scrollbarDiv()macro to generate a div that will handle the scrollbar; - for the third row call the
dataDiv()macro to generate a div containing the data specified by the dataCat attribute; - for the fourth row call the
scrollbarDiv()macro to generate a div that will handle the second scrollbar;
id:"mainLayout",
bindRefreshTo: [{to:"currentDs", inside:data}]
}}
// process table width to have fixed column sizes
// otherwise column size might change when content is updated
{if data.currentDs!=null}
{var borderWidth=1/}
{var tblWidthFixed=borderWidth/}
{var tblWidthScroll=borderWidth/}
{foreach col in data.currentDs["fixedColumns"]}
{set tblWidthFixed+=borderWidth+col.width/}
{/foreach}
{foreach col in data.currentDs["scrollableColumns"]}
{set tblWidthScroll+=borderWidth+col.width/}
{/foreach}
<table class="mainLayout">
<tbody>
// query row
<tr>
<td class="dataBlock">{call dataDiv("fixedColumns","filter1","filter",tblWidthFixed)/}</td>
<td class="dataBlock">{call dataDiv("scrollableColumns","filter2","filter",tblWidthScroll)/}</td>
<td class="vscroll"> </td>
</tr>
// scrollbar row
<tr>
<td class="hscroll"> </td>
<td class="hscroll">{call scrollbarDiv("hscroll1","hscrollbar","width:"+tblWidthScroll+"px")/}</td>
<td class="hscroll"> </td>
</tr>
// data row
<tr>
<td class="dataBlock">{call dataDiv("fixedColumns","part1","data",tblWidthFixed)/}</td>
<td class="dataBlock">{call dataDiv("scrollableColumns","part2","data",tblWidthScroll)/}</td>
<td class="vscroll">{call scrollbarDiv("vscroll","vscrollbar","height:2400px")/}</td>
</tr>
// 2nd scrollbar row
<tr>
<td class="hscroll"> </td>
<td class="hscroll">{call scrollbarDiv("hscroll2","hscrollbar","width:"+tblWidthScroll+"px")/}</td>
<td class="hscroll"> </td>
</tr>
</tbody>
<table>
{/if}
{/section}
scrollbarDiv() to generate a div that will habdle the scrollbar using the onScroll event:
<div {id id/} class="${cssClass}" {on scroll {fn:"onScroll",args:id}/}>
<div {id id+"Content"/} class="${cssClass}Content" {if contentStyle} style="${contentStyle}" {/if}>
</div>
</div>
{/macro}
dataDiv() to generate a div containing the data specified by the dataCat attribute:
<div class="${colType} ${cssClass}">
<div class="dataDiv">
{if !data.currentDs || !data.currentDs.items || data.currentDs.items.length==0}
{else/}
<table class="datasheet">
<thead>
<tr>
{foreach col in data.currentDs[colType]}
<th>${col.title}</th>
{/foreach}
</tr>
</thead>
{repeater {
loopType: "array",
content: data.currentDs.items,
type: "TBODY",
childSections : {
id: "dsrow_"+colType,
bindRefreshTo: {fn:"getRowBindRefresh", scope:this},
macro: {
name: "dsRow",
args: [data.currentDs[colType]],
scope:this
},
type: "TR"
}
}/}
</table>
{/if}
</div>
</div>
{/macro}
dsRow() macro to display a row of the dataset table:
{var item=itm.item/}
{var cellItm=null/}
{var cls="cell"/}
{if item.editMode}
{set cls="selected"/}
{/if}
{foreach col in cols}
{set cellItm=item[col.colId]/}
<td class="${cls}">
{if item.editMode}
{@aria:TextField {
autoselect:true,
width: col.width-2,
bind:{
value:{to:"editValue",inside:cellItm}
}
}/}
{else/}
${cellItm.value}
{/if}
</td>
{/foreach}
{/macro}
Step 5
Create a file and call it MainScript.js.
Save it inside the view folder inside your project (/guides/spreadsheet/view/).
Step 6
Define the classpath and the constructor:$classpath : 'ariadoc.guides.spreadsheet.view.MainScript',
$constructor : function () {
}
onModuleEvent to refresh the dataset table:
return;
if (evt.name=="methodCallBack") {alert(123);}
if (evt.name=="methodCallEnd") {
if (evt.method=="xloadDataset"
|| evt.method=="createNewDataset"
|| evt.method=="copyDataset"
|| evt.method=="selectDataset") {
this.$refresh();
}
}
}
Note: To get more info about the onModuleEvent take a look at this page.
Define theonScroll() function to manage the vertical and horizontal scroll inside the dataset table:
* Event Handler called when a scrollbar is used
* Will change the content scroll and synchronize all scrollbars
*/
onScroll: function(evt,id) {
if (id=="vscroll") {
// vertical scroll
var vs=this.$getElementById("vscroll");
if (vs) {
var sTop=vs.getScroll().scrollTop;
vs.$dispose();
var arr=["dataCont_fixedColumns_data","dataCont_scrollableColumns_data"];
for (sid in arr) {
div=this.$getElementById(arr[sid]);
if (div) {
div.setScroll({scrollTop:sTop});
div.$dispose();
}
}
}
} else {
// horizontal scroll
var hIdA=id, hIdB=(id=="hscroll1")? "hscroll2":"hscroll1";
var hA=this.$getElementById(hIdA), div;
if (hA) {
var sLeft=hA.getScroll().scrollLeft;
hA.$dispose();
var arr=["dataCont_scrollableColumns_data","dataCont_scrollableColumns_filter",hIdB];
for (sid in arr) {
div=this.$getElementById(arr[sid]);
if (div) {
div.setScroll({scrollLeft:sLeft});
div.$dispose();
}
}
}
}
}
onDsDataCellClick() function used when a data cell is clicked to allow the user to edit the cell content:
* Event Handler called when a data cell is clicked
*/
onDsDataCellClick:function(evt) {
var id=evt.target.getExpando("id",true);
if (id=="TABLE") return; // event did not hapen in a cell
var colId=evt.target.getExpando("colId",true);
this.moduleCtrl.editCell(parseInt(id,10),colId);
}
getRowBindRefresh() function to return the bindRefreshTo for each data table row section and the selectDataset() function to call the controller method to select a different dataset:
* Return the bindRefreshTo value for each data table row section
* @param {Object} itm the item passed as argument of each Repeater childSection callback
* cf. Repeater documentation
*/
getRowBindRefresh:function(itm) {
var item=itm.item;
return [{to:"editMode", inside:item}];
},
/**
* Event Handler called when a link/tab is clicked to change the current dataset
* @param {Integer} idx the dataset index that should become the current one
*/
selectDataset:function(evt,idx) {
this.moduleCtrl.selectDataset(idx);
}
}
Step 7
Create a file and call it ControllerInterface.js.
Save it inside the root folder inside your project (/guides/spreadsheet/).
Step 8
Define the classpath, which class the controller interface extends, that is alwaysaria.templates.IModuleCtrl and write the signature of all the methods of your controller:
$classpath : 'ariadoc.guides.spreadsheet.ControllerInterface',
$extends : 'aria.templates.IModuleCtrl',
$interface : {
createNewDataset : {$type : "Function"},
copyDataset : {$type : "Function"},
loadDataset : {$type : "Function",$callbackParam: 1},
editCell : {$type : "Function"},
selectDataset : {$type : "Function"},
saveChanges : {$type : "Function"},
cancelChanges : {$type : "Function"}
}
});
Step 9
Create a file and call it Controller.js.
Save it inside the root folder inside your project (/guides/spreadsheet/).
Step 10
Define the classpath, which class the controller extends, that is alwaysaria.templates.ModuleCtrl, which controller interface implements, in our case ariadoc.guides.spreadsheet.ControllerInterface (that's the controller interface defined in the previous step), define the constructor and the destructor. Remember to add the filter to fetch the data from the xml file in order to simulate the server requests:
$classpath : 'ariadoc.guides.spreadsheet.Controller',
$extends : 'aria.templates.ModuleCtrl',
$implements : ['ariadoc.guides.spreadsheet.ControllerInterface'],
$constructor : function () {
this.$ModuleCtrl.constructor.call(this);
// add test filter for response mocks
aria.core.IOFiltersMgr.addFilter('ariadoc.guides.spreadsheet.mocks.TestMsgHandler');
this._data = {
currentDs:null, // current dataset - cf. createNewDataset for the structure
datasets:[] // list of loaded datasets
};
},
$destructor : function () {
this.$ModuleCtrl.$destructor.call(this);
}
copyDataset() function to copy an existing dataset:
* Copy the current dataset into a new dataset
* and set is as current dataset
*/
copyDataset:function() {
if (!this._data.currentDs) return;
var ds=this.json.copy(this._data.currentDs,true);
// change name
nm=ds.name.replace(/\(.*\)$/,'');
ds.name=nm+"("+this._data.datasets.length+")";
// add dataset to the list and select it
this.json.add(this._data.datasets,ds);
this.json.setValue(this._data,"currentDs",ds);
}
createNewDataset() function to create a new dataset:
* Create a new dataset and add it to the dataset list (but will not be selected by default)
*/
createNewDataset:function(nm,createData,setAsCurrentDataset) {
if (typeof(nm)!='string') nm="New "+this._data.datasets.length;
if (typeof(createData)!='boolean') createData=true;
if (typeof(setAsCurrentDataset)!='boolean') setAsCurrentDataset=true;
var ds={
name:nm,
fixedColumns:[],
scrollableColumns:[],
items:[]
}
this.json.add(this._data.datasets,ds);
if (createData) {
// create Columns
var nbrOfColumns=20, nbrOfFixedColumns=3, nbrOfLines=100;
var colIds=[], d, colId;
for (var i=0;nbrOfColumns>i;i++) {
colId="c"+i;
colIds.push(colId);
d={
colId:colId,
title:"Col "+i,
width:90
}
if (ii;i++) {
d={editMode:false};
for (var j=0, maxJ=colIds.length;maxJ>j;j++) {
d[colIds[j]]={
id:i,
value: "", //+i+":"+j,
editMode:false
};
}
ds.items.push(d);
}
}
if (setAsCurrentDataset) this.json.setValue(this._data,"currentDs",ds);
return ds;
}
selectDataset() function to set a dataset as current dataset:
* Set a dataset as current dataset - i.e. referenced by 'currentDs'
* in the data model
* @param {Integer} dsIdx the dataset index in the datasets array
*/
selectDataset : function (dsIdx) {
if (dsIdx>-1 && this._data.datasets.length>dsIdx) {
this.json.setValue(this._data,"currentDs",this._data.datasets[dsIdx]);
}
}
}
cancelChanges() function to cancel the changes made in the current dataset:
* Cancel the changes made in the current dataset
*/
cancelChanges: function () {
var ds=this._data.currentDs;
var cols=ds.fixedColumns.concat(ds.scrollableColumns);
var items=ds.items, itm, colId;
for (var i=0, iMax=items.length;iMax>i;i++) {
itm=items[i];
if (itm.editMode) {
for (var k in cols) {
colId=cols[k].colId;
this.json.setValue(itm[colId],"editMode",false);
}
this.json.setValue(itm,"editMode",false);
}
}
// TODO submit changes
}
saveChanges() function to save the changes made in the current dataset:
* Save the changes made in the current dataset
*/
saveChanges: function () {
var ds=this._data.currentDs;
var cols=ds.fixedColumns.concat(ds.scrollableColumns);
var items=ds.items, itm, colId;
for (var i=0, iMax=items.length;iMax>i;i++) {
itm=items[i];
if (itm.editMode) {
for (var k in cols) {
colId=cols[k].colId;
this.json.setValue(itm[colId],"editMode",false);
this.json.setValue(itm[colId],"value",itm[colId].editValue);
}
this.json.setValue(itm,"editMode",false);
}
}
}
editCell() function to set a cell and its associated row to editable mode:
* Set a cell and its associated row to editable mode
* @param {Integer} itemId line id
* @param {String} colId column id
*/
editCell : function (itemId,colId) {
// set the cell in edit mode
// itemId is not necessarily the index in the array
var ds=this._data.currentDs;
var cols=ds.fixedColumns.concat(ds.scrollableColumns);
var items=ds.items, itm, tmpId;
for (var i=0, iMax=items.length;iMax>i;i++) {
itm=items[i];
if (itm[colId].id==itemId) {
if (!itm.editMode) {
// set all editValues (used to not change the value until save is called
for (var k in cols) {
tmpId=cols[k].colId;
this.json.setValue(itm[tmpId],"editMode",true);
this.json.setValue(itm[tmpId],"editValue",itm[tmpId].value);
}
//this.json.setValue(itm[colId],"hasFocus",true);
this.json.setValue(itm,"editMode",true);
}
break;
}
}
}
loadDataset() function to make a Json request to load a dataset inside the data model and the _loadDataset() function to manage the answer from the server:
* Load a dataset in the data model
*/
loadDataset : function (ref,cb) {
var requestData = {datasetRef:ref};
this.submitJsonRequest("getDataset", requestData, {
fn : "_loadDataset",
scope : this,
args : cb
});
},
/**
* loadDataset callback - called when server response is received
*/
_loadDataset: function (res, args) {
var cb=args;
if (res.error) {
this.$callback(cb);
return;
}
var ds = res.response;
// modify the cell values to use a structure instead of a string
var item, tmp;
for (var i=0, iMax=ds.items.length;iMax>i;i++) {
item=ds.items[i];
for (var k in item) {
if (k=="id") continue;
tmp=item[k];
item[k]={
id:i,
value:tmp
}
}
}
// add dataset to the list and select it
this.json.add(this._data.datasets,ds);
this.json.setValue(this._data,"currentDs",ds);
this.$callback(cb);
}
Step 11
Create a file and call it MainStyle.tpl.css.
Save it inside the view folder inside your project (/guides/spreadsheet/view/).
Step 12
Give some style to your todo app.
Step 13
That's it! Open the index.html with your favourite browser (e.g. Chrome) and enjoy.
