Grid [examples]

Cell Renderer

You can customise what is displayed inside the grid cells by specifying a Display delegate in the Custom Delegates for the grid columns of a Grid Section or the FormItem of a Form Section.

When the display delegate is called, the record for that particular grid row is active so you can generate dynamic content in the context of that record.

Example code from the example_smartgrid App

////////////////////////////////////////////////////////////////
// Custom display for column 'section1.column5'
function page1_section1_column5_customdisplay(controlsource, row, col)
{
	var grid = Lianja.get("page1.section1").grid;
	var employees = Lianja.getCursor("employees");
	dcont = createObject("dcont", "container");
	dcont.layout = "H";
	dcont.margin = 1;
	dcont.backcolor = "white";
	dcont.border = 1;
	dcont.bordercolor = "lightgray";
	dcont.raisedshadow = true;
	dcont.addObject("leftdcont", "container");
	leftdcont.layout = "V";
	leftdcont.addObject("labtitle", "label");
	labtitle.fixedheight = 20;
	labtitle.caption = "EMPLOYEE DETAILS";  
	labtitle.backcolor = "lightgray";
	labtitle.alignment = "center";
	leftdcont.addObject("lab", "label"); 
	lab.alignment = "center";
	lab.forecolor = "gray";
	lab.backcolor = "white";
	lab.fontbold = 1;
	lab.text = 	"<h3>" + employees.getData("titleofcourtesy") + " " + 
				employees.getData("firstname") + " " + 
				employees.getData("lastname") + "</h3>" +
				"<br>" + 
				employees.getData("address") + 
				"<br>" + 
				employees.getData("city") + 
				"<br>" + 
				employees.getData("region") + 
				"<br>" + 
				employees.getData("postalcode") + 
				"<br>" + 
				employees.getData("country") + 
				"<br>" +
				"Tel# " + employees.getData("homephone") +
				"<br>";
	lab.margin = 25;
	leftdcont.addObject("editbtncont", "container");
	editbtncont.layout = "H";
	editbtncont.backcolor = "white";
	editbtncont.addStretch();
	editbtncont.addObject("editbtn", "commandbutton");
	dcont.setItem("editbtn", editbtn);
	editbtn.setItem("dcont", dcont);
	editbtn.setItem("row", row);
	editbtn.setItem("col", col);
	editbtncont.fixedheight = 45;
	editbtncont.addStretch();
	editbtn.alignment = "center";
	editbtn.fixedwidth = 80;
	editbtn.caption = "EDIT";
	editbtn.stylesheet = "btn-success btn-sm";
	editbtn.click = function()
	{
		var row = this.getItem("row");
		var col = this.getItem("col");
		Lianja.get("page1.section1").grid.activateCell(row, col);
	};
	dcont.fixedheight = 254;
	return dcont;
};

Cell Editor

You can customise what is rendered inside the grid cells when editing by specifying an Editor delegate in the Custom Delegates for the grid columns of a SmartGrid or the FormItem cell of a FormGrid.

As with the display delegate, when the editor delegate is called, the record for that particular grid row is active so you can generate dynamic content in the context of that record. This is typically used to render a form dynamically.

Example code from the example_smartgrid App

////////////////////////////////////////////////////////////////
// Custom editor for column 'section1.column5'
function page1_section1_column5_customeditor(controlsource,row,col) 
{
	var employees = Lianja.getCursor("employees");
	econt = createObject("econt","container");
	econt.layout = "Form";	
	econt.addRow("", "formheading", "label");
	formheading.caption = "EDIT EMPLOYEE DETAILS";
	formheading.fontsize = 12;
	formheading.alignment = "center";
	formheading.backcolor = "lightslategray";
	formheading.forecolor = "white";
	econt.addRow("First Name:", "firstname", "textbox");
	firstname.controlsource = "employees.firstname";
	econt.addRow("Last Name:", "lastname", "textbox");
	lastname.controlsource = "employees.lastname";
	econt.addRow("Address:", "address", "textbox");
	address.controlsource = "employees.address";
	econt.addRow("City:", "city", "textbox");
	city.controlsource = "employees.city";
	econt.addRow("Region:", "region", "textbox"); 
	region.controlsource = "employees.region";
	econt.addRow("Postcode:", "postalcode", "textbox");
	postalcode.controlsource = "employees.postalcode";
	econt.addRow("Country:", "country", "textbox");
	country.controlsource = "employees.country";
	econt.backcolor = "lightgray";
	econt.addRow("", "buttons", "container");
	buttons.backcolor = "lightgray";
	buttons.layout = "Horizontal";
	buttons.spacing = 5;
	buttons.addObject("cancelbtn", "commandbutton"); 
	cancelbtn.caption = "Cancel";
	cancelbtn.stylesheet = "btn btn-sm btn-block btn-default";
	cancelbtn.click = function()
	{
		Lianja.get("page1.section1").grid.cancel();
	};
	buttons.addObject("savebtn", "commandbutton");
	savebtn.caption = "Save";
	savebtn.stylesheet = "btn btn-sm btn-block btn-default";
	savebtn.click = function()
	{
		Lianja.get("page1.section1").grid.save();
	};
	buttons.minheight = 34;
	econt.fixedheight = 254;
	return econt;
};

https://www.lianja.com/doc/index.php/Custom_Renderers_and_Custom_Editors


 

Grid

Q:

I’ve a Form Section, with a Grid Gadget.

If I try to set:

Code:
oGridGadget = lianja.get("mypage.section.gridgadget")
oGridGadget.clear()
oGridGadget.setattr("controlSource", "filterricerca")

I get the error:

gio ott 6 10:10:16 2016
**** Lianja error ****
USE “filterricerca” IN 0
^
File ‘filterricerca.dbf’ does not exist

where filterricerca is a cursor:

Code:
create cursor filterricerca (&cursorField)
select filterricerca
append blank

Is possible to use a cursor as a datasource of a Grid Gadget?

My goal is: create a universal “Search page”.
This must function to look up data on a table or on a query.
Eg:
Item table: search for articles with “screw” in the description, and “12” in size.
Sales Order: search for customer orders of “Bologna”

In the first case is a search on a single table, in the second I have to do a query on Orders and Customers.

In the example I have a table “Articoli” (items) and a VT “VT_Articoli”, accessed via ODBC from a DB FoxPro 2.6 ..

Now..

Name: 2016-10-07 15_16_57-Nuova notifica.jpg Views: 51 Size: 84.6 KB

To do this I set a namespace var:

Code:
namespace MyNameSpace

public m_mainTable
public m_searchtable, m_searchfields, m_searchcaption, m_searchtarget, m_searchcursor, m_searchjoin, m_searchfilename, m_searchorder, p_sRetVal

MyNameSpace.m_searchtable = 'articoli,vt_articoli'
MyNameSpace.m_searchfields = 'articoli.cod_art|;articoli.gruppo|;vt_articoli.ca tmercgam|;articoli.des_web|'
MyNameSpace.m_searchcaption = 'Articolo,Gruppo,Categoria,Descrizione'
MyNameSpace.m_searchtarget = 'cod_art'
MyNameSpace.m_searchcursor = 'cur_articoli'
MyNameSpace.m_searchjoin = 'articoli.cod_art=vt_articoli.cod_art'
MyNameSpace.m_searchfilename = ''
MyNameSpace.m_searchorder = ''

callSupSearch_x(oActivePage.id, oActiveSection.id)


m_searchtable represent the table involved in the query (one or more)
m_searchfields represent the fields displayed by default in the result
m_searchcaption is the relative field caption
m_searchtarget is the returned field
m_searchcursor is the name of the query
m_searchjoin is the relation between the tables

the function callSupSearch_x(oActivePage.id, oActiveSection.id)
call the server page…

Code:
lianja.showDialog("sem_search", "pageSearch", 800)

in the server page I create a cursor, with the “m_searchfields” and the “m_searchcaption”, I create the menu, with the table involved in the query.

Just typing in the Filter cursor, I do a query and refresh the “Results” in the webview.

When the user click on the menu “Articoli”, I update the filtergrid, I create a new cursor with the structure of the table “Articoli” and I display all the columns.. the user can type any search string in any column..
I refresh the query..

For the Filter cursor, I can only use a Custom VFP Section where I change the “Browse or Edit command”…

I can not use the more beautiful “Grid gadget”..

A:

No, it expects a table name.

A2:

I believe you can also use temp tables (at least that how I understand them) instead of a cursor.

If you select into a table that has a name that starts with an underscore, it is not part of the database.
select * from a into _temp1.

You can create a temporary table that is not in the database.

I know of two ways.

1. Create table c:\tempfabio free(Firstname Char(10), Id int)

By using the word free, it keeps it standalone.

2. use a table that starts with an underscore.

Select * from masterfabio into table _tempfabio

 


Q:

On a canvas grid, the selected row has a dotted outline. Is it possible to highlight the selected row with a color?

A:

You can do it through CSS, e.g.

Name: canvasGridCSS.jpg Views: 81 Size: 91.0 KB

Name of control: grdClientList

Attribute
CSS Style: app:/clientlist_section1_grdClientList_cssstyle.css

clientlist_section1_grdClientList_cssstyle.css contents:

Code:
lianja_ui_tree::item:selected {
background:white;
color:black;
border: 3px solid blue;
}

lianja_ui_tree {
font-size: 28px;
}

I changed the setting for “Can select rows” to True and the CSS formatting is working now,

A2:

I was wondering if you were wanting to change the border as you moved the mouse over the rows rather than when you actually selected a row.

If anyone else is interested in this, you can use the ‘hover’ pseudo-state, e.g.

Code:
lianja_ui_tree::item:hover { 
	border: 3px solid blue;
}

Q:

I am trying to sort my grid section in descending order on column 0. below is the code I have tried. It seems like it should work. Does anyone see an obvious problem.

Code:
oGrid = Lianja.get("rfrmain.RFRList").grid
oGrid.sort(0,-1) //sorts grid in decending order by rfr no ---not working
oGrid.refresh()

A:

Make sure the ‘Sortable’ attribute is checked (True) for the Grid Section (in the Details attributes).

You won’t need the refresh().


Q:

How do I specify the background color for a row or cell in custom vfp section grid?

A:

Cell dynamic background/foreground attributes are currently only available on Grid Section Columns.

If you just want to color the selected / hovered over row, this can be done through CSS : http://www.lianja.com/community/show…ighlight-color.


Q:

I have a grid bound to a table in a custom VFP Section.
As users fulfill different actions, I need to be able to change the color of specific cell backgrounds.

I know the cell I want, how do change the backcolor programmaticly ?
I have looked at Dynamic Cell Background, but this is not based on a table value.

would it be something along of the line of

sCell = namespace.grid.item(x,y)
sCell.backcolor=’red’

I am using a custom VFP section and hand creating all of my objects.

Meaning – in my code, I use addobject(), so the attributes need to be exposed on a programmatic level.

A:

As far as I am aware, you can access the backcolor and forecolor of the column, but not an individual cell.

Grid.item(x,y) returns the text from a cell, not an object reference to the cell.

 

 


Q:

I have grid named verGrid in custom VFP section.

How would I reference an individual column to make it read-only or change the header?

A:

vergrid.columns(1).readonly = .t.


Q:

The highlighted row in a grid section seems to always be the first row in the grid when the page is initially displayed.

How can I have the grid highlighted on a row of my choosing when the page is displayed?

I have a page with a grid section on it bound to a table in my database called CLIENT.

In the DblClick delegate for the grid section, I capture the Cid column value from the CLIENT table to a public variable and then ShowDocument() to a different page in my app.

When the other page is closed, I return to the original grid section page and in the Activate delegate, I have the following

Code:
proc clientmaint_section1_activate()     
    Select CLIENT
    Locate for Cid = LastClientID
    Wait Window "Record Number = " + Transform(RECNO("CLIENT")) + "... "+CLIENT.cid Timeout 1.5
    Lianja.GetElementByID("clientmaint.section1").Goto(Recno("CLIENT"))
endproc

The Wait Window command shows the table on the correct record with the correct cid, but when the page appears, the grid’s highlighted row is the top record.

I have also tried to use the following:

Code:
Lianja.GetElementByID("clientmaint.section1").Next()

A:

You are trying to use the Next method on a section rather than on a grid. Try something like…

Lianja.getElementById(“clientmaint.section1.grid1” ).Next

the correct notation is :

Code:
 Lianja.getElementById("clientmaint.section1").grid.Next()

Q:

Add a blank grid section to an application: In a JavaScript application, I am trying to add a blank grid and then populate it later from an array using additems().

A:

Here’s what I did in on app:

1) created a table with the required fields and also had a cSession field of C(26).
2) in the UI, I added a property to a public var oApp of cSessionID
3) this property was, on the INIT of the UI app, set to guid() (this was in VFP, but should work here)
4) I then set a filter on the grid of: cSessionID = oapp.cSessionID — which meant the grid was showing empty
5) Then I could “create” records from anywhere, making certain to use oapp.cSessionID as the cSessionID of the table.

The same could be done with a VT and a parameter for cSessionID of course.

In the close of the database (assuming you don’t close it in the UI), you can delete for cSessionID = oapp.cSessionID.


Q:

grids in a custom VFP section.

using addcolumn, how do I specify the header text and rowsource for the column?

A:

After addcolumn()… you can get objects (column and header)

The Column object (returned from grid.columns(n) has a property called Header1 that returns a reference to the Header object for that column. e.g.

 

Code:
oCol1 = grid1.columns(1)
oHeader = oCol1.header1
oHeader.caption = "This is Column 1"
Code:
proc page1_section1
    page1_section1 = createobject("page1_section1")
    page1_section1.addobject("mygrid", "grid")
    mygrid.addcolumn(1)
    oCol1 = mygrid.columns(1)
    oCol1.width=100
    oCol1.addobject("header1","header")
    oHeader = oCol1.header1
    oHeader.caption = "This is Column 1"

    mygrid.addcolumn(2)
    oCol2 = mygrid.columns(2)
    oCol2.width=100
    oCol2.addobject("header1","header")
    oHeader2 = oCol2.header1
    oHeader2.caption = "This is Column 2"
return page1_section1

Q:

Is there a way to prevent deleted items from showing on a grid.

A:

If SET DELETED is ON, then records that have been marked for deletion are ’invisible’


If the grid is set to “Double click to edit“, you can navigate the records in the grid using the mouse and/or the cursor/page keys as previously.

However, In Lianja 3.2 pressing “Return/Enter” switches the row into edit mode (and displays the record for editing in the split view Form if that is enabled).

While editing, pressing Ctrl+W or Esc in the “Form View” focuses back onto the grid.

 


Q:

How do I set up an SQL statement as a grid source? I can’t find any information on how to get this working.

Name: lianja grid with sql.jpg Views: 44 Size: 31.9 KB

A:

The grid does not adjust columns based on the SQL statement: they are set up in development mode only.

You can make columns non visible at runtime.

You can requery with an entirely new WHERE clause.

A2:

It’s more specific to desktop apps.

It is better to use Virtual Tables if you want to target web and/or mobile.

I’d really recommend going through the examples opening them up and then run them in web and mobile then you will see what can be accomplished with very little coding.

It’s important to realize that Lianja/VFP does not run in the browser but it can be used to generate dynamic content that is rendered in WebViews and DialogPanels.

The “client-side” delegates should be written in JavaScript.

You will find that the majority of VFP functions are available in the Lianja JavaScript framework.

You can play with these in the Web inspector | Console. Remember JavaScript is a case sensitive language.

A3:

 

you can use also a Custom VFP Section and use the “Browse” command.
Name: 2017-01-10 06_10_38-Rina - Dev.jpg Views: 43 Size: 70.4 KB

In this case, bsed on the menu choice, I browse a different table.
Name: 2017-01-10 06_11_09-Lianja App Builder v3.2.0 [sem_search] - UTF-8 - southwind - Licensed to Sof.jpg Views: 38 Size: 29.8 KB

You cau use the “setattr” to change the displayed table at runtime.

loFilter.setAttr(“browseCommand”, “browse noappend noactionbar noedit nodelete fields ” + lsBrowse + lsStyle)

Desktop client only. It’s VFP browse embedded in a page.


how to dynamically load data into forms and grids.

In LIanja 3.5 I have implemented the getJSON() and the setJSON(data) methods on sections in the Web and Mobile to simplify this.

You can dynamically query data using OData_Read() (or other means) and then load the form, canvas or grid section with the data. The setJSON(data) method for a grid is aware of the OData JSON format returned from OData_Read().

Name: Screen Shot 2017-05-31 at 2.14.08 PM.jpg Views: 92 Size: 96.6 KB

After loading a grid you can edit the rows of the grid as normal and save it..

You can always override navigation bar actions with custom actions and these can perform updates Server-side.

Also worth mentioning is that after setJSON(data) on a section, any related child sections and sidebars are refreshed automatically, so it operates in the same way as a standard section.


Q:

In one page, I have canvas section and grid section. Canvas section has 2 numeric fields and a command button.
Grid section had an empty table.
When user input 2 numbers, and click the command button, I insert into the table with the computed (var 1*var 2) but the grid does not refresh! I exit the App and view the table, the var was indeed inserted.
Any ideas to refresh the grid section dynamically?

A:

use (“section1”).refresh

A2:

I thought you wanted to create the grid “on the fly”.

If you do not need to edit the data, you can use a Web view, else you can use a Custom Fox Pro section and you can change the “command”

Name:  2017-05-15 09_13_21-Lianja App Builder v3.4.1 [dd_search] - UTF-8 - southwind - Licensed to Soft.jpg Views: 34 Size:  79.6 KB

browse noappend noactionbar noedit nodelete fields ORDERS__ORDERID :25 :H=”Campo Ordine”:V=pageSearch_ValidFiltro(“{}”,”cur_Orders” ,”ORDERS__ORDERID”),ORDERS__ORDERID__sort :10 :C=”no,up,down”:H=”up/dn”:V=pageSearch_ValidFiltro(“{}”,”cur_Orders”,”OR DERS__ORDERID__sort”),ORDERS__CUSTOMERID :25 :H=”Campo Cliente”:V=pageSearch_ValidFiltro(“{}”,”cur_Orders “,”ORDERS__CUSTOMERID”),ORDERS__CUSTOMERID__so rt :10 :C=”no,up,down”:H=”up/dn”:V=pageSearch_ValidFiltro(“{}”,”cur_Orders”,”OR DERS__CUSTOMERID__sort”),ORDERS__SHIPNAME :25 :H=”Campo Spedire a”:V=pageSearch_ValidFiltro(“{}”,”cur_Orders”,”ORD ERS__SHIPNAME”),ORDERS__SHIPNAME__sort :10 :C=”no,up,down”:H=”up/dn”:V=pageSearch_ValidFiltro(“{}”,”cur_Orders”,”OR DERS__SHIPNAME__sort”),CUSTOMERS__CONTACTNAME :25 :H=”Campo Rag. soc.”:V=pageSearch_ValidFiltro(“{}”,”cur_Orders”,” CUSTOMERS__CONTACTNAME”),CUSTOMERS__CONTACTNAME__s ort :10 :C=”no,up,down”:H=”up/dn”:V=pageSearch_ValidFiltro(“{}”,”cur_Orders”,”CU STOMERS__CONTACTNAME__sort”),ORDERS__ORDERDATE :25 :H=”Campo Data ordine”:V=pageSearch_ValidFiltro(“{}”,”cur_Orders” ,”ORDERS__ORDERDATE”),ORDERS__ORDERDATE__sort :10 :C=”no,up,down”:H=”up/dn”:V=pageSearch_ValidFiltro(“{}”,”cur_Orders”,”OR DERS__ORDERDATE__sort”)style “* { selection-color:yellow; selection-background-color:black;font: bold 15px Arial; gridline-color:green; background:black;color:yellow;} QTableView::item {background:lightgreen; } QHeaderView::section { background-color:green;color:white;border:0px;font: bold 15px/30px Georgia, serif; } ”

 

 

Grid

Q:
I have a varchar field coming from SQL Server that once it is clicked on in the grid, the data is replaced with the word memo.
This field is defined as nvarchar(2000)
From a data modeling perspective, what legnth or condition of a field will make it appear as “memo” in a grid?
I am not sure if this was already discussed.
It seems that any hex value in my grid is coming up as the word object.
Is there a way I see the actual values in the grid?
I also need to see the values that are showing up as Memo, since I am not using a memo field
A:
Anything above 255 I believe.
Any varbinary column with be “object” as it is unknown what it contains.
Any varchar column will be “memo”. Any character fields above the 255 limit will be treated as memos.


using the where clause on the grid seems to work very well for me.
This way, it really is irrelevant the size of the table.

Code:
Lianja.get("PageName.sectionName").where = "value = 5";
Lianja.get("PageName.sectionName").refresh();

Q:
how I can get a record object of the current grid active record?
What I need to do is to allow me to programmatically change the field values of the active record using code
A:
You can update the active record and then refresh that specific record in the grid using oGrid.refreshRecord( recno() )
If the underlying cursor has been dynamically created from a virtual table query you need to specify a primary key and issue the replace statement which will generate the SQL dynamically and execute it.


You can add whatever control you want in the grid.
In this example I create a container with a textbox and a button.

First, create the procedure to create the container and the objects you want.
I did this in the custom library of the page.

Code:
proc mycontainer
mycontainer = createobject("container")
mycontainer.layout = "horizontal"
mycontainer.height = 50
mycontainer.addobject("text1","textbox")
mycontainer.addobject("btn1","commandbutton")
btn1.caption ="Custom Button"

return mycontainer

Then I added a new column in the grid by right clicking the header of an existing column and selecting “Insert Column After”
The in the attributes of the new column, I entered mycontainer in the custom control option.
Then you can see the custom container in my grid.

So – if you need to have a tooltip, You just add it.

Code:
proc mycontainer
mycontainer = createobject("container")
mycontainer.layout = "horizontal"
mycontainer.height = 50
mycontainer.addobject("text1","textbox")
mycontainer.addobject("btn1","commandbutton")
btn1.caption ="Custom Button"
text1.tooltip ="this is tooltip"

return mycontainer


you have some larger field that you want to edit. For those field, use the hyperlink to bring up a dialog box for the edit. This way you have a clean edit, not in a small field, you are using a best practice and properly creating a modern application and you do not lose any functionality.

Have a look at this example.
I concatenated firstname, lastname and address into one field.
If I want to edit that field, I just click on the link and bring up a dialog window.

I accomplish everything I a looking to do in a very intuitive way without trying to re-invent the wheel.

In the linkclick section of my page, I call the dialog window.

Code:
////////////////////////////////////////////////////////////////
// Event delegate for 'linkclick' event
proc page1_section1_linkclick()

Lianja.showdialog("Employee Edit","pgEmployeeEdit",500,500)
// This refreshes the grid to show the new values
Lianja.get("section1").refresh
endproc

The result.

If I was building this for web, I would call the showdialog with a parameter and an action.
In the desktop, it is already on the record pointer.

Try to add some custom delegate to your “text1” (methods like valid() or so…).
That’s easy.

You create your own textbox class with a valid.
Like this.

Code:
define class mytextbox as textbox
proc valid()
wait window "valid"
endproc

enddefine

Then instead of adding textbox, you add mytextbox.


Q:
not found the property that return the number of column..
A:
This should work

Code:
oGrid = Lianja.GetElementByID("page.section").grid
? oGrid.ColumnCount



Q:
If i have filtered grid like this

Code:
Lianja.get("section1").grid.filter = "startsWith(customerid, 'A')"

and I use the code below to move from record to record then it does not work.

Code:
sec = Lianja.get("section1")
        grd = sec.grid
grd.goto(grd.activerow + 1)

It appears that the row is being moved on the filtered gird but I suspect it is moving between rows that are not visible.When the grid is not filtered it works just fine:

Code:
Lianja.get("section1").grid.filter = ""
sec = Lianja.get("section1")
        grd = sec.grid
grd.goto(grd.activerow + 1)



A:
Try doscroll

Code:
Lianja.Get("page1.section1").grid.doscroll(1)

Or

Code:
oGrid = Lianja.get("page1.section1").grid 
lnActiveRow = oGrid.activerow + 1
oGrid.activatecell(lnActiveRow, 1)

In the second example you can specify the column.
In the first one the first column is activated.



 

Grid

Q:
Have a section “section1”, as grid, there’s a test table in it.
Another section “section2” as a canvas with a button on it.
Code in button:

Code:
lianja.get("section1").grid.add 

A:
You could also use

Code:
Lianja.get("section1").add
Lianja.get("section1").refresh
//or
Lianja.showdocument("section:section1?action=add")
Lianja.showdocument("section:section1?action=refresh")

Then click on to the newly added record to populate any fields without defaults.


Q:
I’d like to read some values from a grid section eg. column ‘firstname’ from the currently active row. This should be easy to do once I have a reference to the grid, but this is where I am having trouble.
Both below return some kind of object but accessing eg. loGrid.Recno gives ‘Property/Variable ‘RECNO’ not found‘. It does this for any property.

Code:
local loGrid = Lianja.get("Supplier.section1")
local loGrid = Lianja.get("Supplier.section1").Grid //Is this is only for grid objects in custom sections?

A:
Try it without the ‘local’

“Lianja is data-centric”

Grids are data bound. You can reference the actual data in the cursor as this is based on the active record as you move between records of the grid.
Why futz about referencing grid cells when you can just work with the underlying data.
Cursors are selected dynamically when refreshing each section. Pages are made up of sections. Sections can be data bound and have an underlying cursor.


Q:
in a grid section, I need to correct the value entered by user:
eg:
user type “Carry”, I want to replace it with “Barry”.
How can I do it?
In the validatation delegate of field “artcar.descrizione” I’ve put a function validCar(‘{}’)
In this function I do:

Code:
ValidCar(psValue)
If psValue = "Carry"
  replace artcar.descrizione with "Barry"
endif

but not work… I’ve no error, but the value of the field is every “Carry”…
A:
Remember that the ‘Set data mapping’ can call a user defined function and your code determines the return value. e.g.

Code:
ValidCar(psValue)
If psValue = "Carry"
  return "Barry"
endif

..
Or, use the Section Before or After data update to replace the field value when the row is saved.
Validation is used to check and accept/reject the input value, displaying a message if the input is rejected.

Q2:

I need to perform a query to see if the text inserted from the user is correct, if not, I need to open a browse/rsp/form or other to simplify the user choice..
in this case I’ve 3 value, but I can have a lot of value.. eg the item list.. or the vendor list..
I can not use the “Section Before or After data update”.. if I’ve more “controlled” field in the grid, I need to check one by one.
A2:
So put those operations in the user defined function for the Set data mapping.
e.g. On the orders.customerid column, I specify:

Set data mapping: p_val(“{}”)

Code:
//p_val.prg
param p_typed
++ dmcount  // public memory variable initialized to 0
if mod(dmcount,2) = 0
        m_chosen = p_typed
else
        Lianja.showdialogpanel("Title","customerlist")
        m_chosen = customerid
        select orders
endif
return m_chosen
// end

(The check on the dmcount here prevents the showdialogpanel() being called a second time after a new value is returned.)
I enter a value, here ‘MILNE’ and p_val() is called, firing up the Lianja.showdialogpanel() UI Page.

I select a customer and the customerid is returned by p_val().

The Validation is called after the Set data mapping – though I would think any validation should take place in the Set data mapping UDF in this scenario.
Open the App Inspector to the Log tab and put this in your Validation UDF and you should see the call:

Code:
Lianja.writeLog("In validation")

Q:
for the grid navigation the issue is is that when the grid is not readonly and data fields are readonly, the user can still add new record or delete a record using the buttons (+) and (-). I think that this can cause troubles in the database with the risks of data manipulation when a grid is diplayed like that.
Readonly option on a grid is a good option to prevent record deletion or addition.
A:
Why not uncheck the Show actionbar attribute on the section? If you are using keystrokes, PgUp, PgDn, Ctrl-Home and Ctrl-End provide alternatives to the navigation buttons in the actionbar.


Q:
I have a grid section and I want to move the cursor programmatic to refresh.
Select mytable, seek or locate something, then call the refresh of the grid section, does nothing.
Select mytable, goto 100 then call the refresh of the grid section, does nothing.
grid.goto 100 don’t work
delegate section:grid1.section1?action=goto is not available.
A:
Set the activeRow property to position on a row relative to the grid
Use the activeCell() method to position on a cell of the grid
Use the goto() method to position on a specific record within the grid


Q:
I have a page1 with section1 with a grid in it.
Section2 canvas section with a commandbutton. In the click event;
I try to move the cursor in section1 and refresh the section grid;
Tried seek, locate, goto, nothing works.

Code:
proc page1_section2_field1_click()
Lianja.getElementByID("page1.section1").goto(500)
Lianja.getElementByID("page1.section1").activerow( 500)
Lianja.getElementByID("page1.section1").activecell (500,1)
Lianja.getElementByID("page1.section1").refresh()
endproc

All four lines of code don’t work
I Tried;

Code:
select address
go 500
oGrid = Lianja.get("scout.log").grid
lnActiveRow = oGrid.rowcount
oGrid.refresh()
oGrid.activatecell(lnActiveRow, 1)

this don’t work, but I think it’s maybe a bug. It has something to do with pagination.
If pagination=50 and I run this event, On screen it activates record 50.
If pagination=250 and I run this event, On screen it activates record 250.

A:
You can only set the activeRow or activeCell in the current viewport. If you need to move outside of that then turn pagination off.
Turn pagination off
Use the grid.goto(500) method call


Q:
If i have filtered grid like this

Code:
Lianja.get("section1").grid.filter = "startsWith(customerid, 'A')"

and I use the code below to move from record to record then it does not work.

Code:
sec = Lianja.get("section1")
        grd = sec.grid
        
        grd.goto(grd.activerow + 1)

It appears that the row is being moved on the filtered gird but I suspect it is moving between rows that are not visible.When the grid is not filtered it works just fine:

Code:
Lianja.get("section1").grid.filter = ""

        sec = Lianja.get("section1")
        grd = sec.grid
       
        grd.goto(grd.activerow + 1)

A:
you are confusing record numbers with rows in the grid. They are two completely different things.

To move rows assign a value to the activeRow property. Goto() is for positioning on a record number that is in the grid data which it clearly may not be when using a filter to restrict the data displayed.



Q:

If I change data in a grid section’s cell and hit ‘enter’, the change event doesn’t fire although the database and grid have both been updated. This seems inconsistent because it means grid data can be changed (with the db updated) and if the user navigates away from that page, ‘change’ code would never run.
Navigating to another row or clicking either the grid or page ‘save’ buttons causes the event to fire as expected.
Oh, and out of interest I also looked at the datachanged and afterupdate events – afterupdate behaves as ‘change’ above and datachanged never fires.
A:
Datachanged is fired when you move between rows not when you edit the data. It’s typically used to refresh sections manually based on the current record of the grid.
It fires when the section is related.
The cell is updated. The database is not until the row is flushed as Lianja uses row buffering.
The record in the database is updated when you move off the row or force update it. If you move from one cell to another on the same row it is in the row buffer.

in runtime mode with the App Inspector open. You can see the order of the events,
There is no rowcolchanged delegate in grid sections.
Moving between cells just updates the “dirty” buffer it does not dispatch any delegates nor does it update data in the database.

Clicking the “Save” and “Revert” buttons works as it should so I do not know what you are doing for it not to work, unless you are moving off the record in a delegate.


Q:
If I look at the Grid object in the lianja docs it lists these events: afterRowColChange, beforeRowColChange, dragDrop
and the Section Object list no events except the common ones.

Now if I drag a grid section on to the page then I see some other event delegates, like change, after update etc. but not afterRowColChange and the others from the Grid object.
So my question is: where can I get info about the events for a grid section, and where would one access the eg. ‘afterRowColChange’ (is this maybe only for a grid created in a custom section?)
A:
Grid sections are not exactly the same as grid objects in custom sections. Grid objects are there fir VFP 9 UI framework compatibility.


Q:
I added a grid section to one of my page, but the database and table sections are greyed out and blank.
A:
If you hold down shift and then click on a table, drag’n drop it onto a page, then the database and table will be populated. These are read only attributes.


In v2.1 I have added several new section attributes for grids and attachment sections to handle dynamic grid row colors dependent on an expression. This is similar to dynamic cell colors for grid columns but affects the whole grid row. It is particularly useful when a grid is being refreshed at intervals and you want to hilite certain rows.

Additionally…

Grids and attachment sections now honor the “Pagination” attribute setting for grids in the web/mobile client so all rows fetched are loaded into the grid if pagination is off.


Q:
how calculated columns in a table are refreshed on a grid.
Is the grid aware that the values are auto calculated or does there need to be refreshes in delegates?
A:
in the column attributes of the grid, there is a recalculate checkbox that handles what you mention.


I had a situation where I wanted to populate a grid with a cursor that I had generated; allow the user to edit the data; and have data rules applied from the data dictionary.

What I did was create a table that had a special field in it, cFilterExp C(36). When the user went to the page where that grid resided, I used guid() to create a guid, created the rows in the table which was shared by all users, put the guid() in the cFilterExp of each row, and set the filter of the section to that same guid() value. When the page was used with new data, a routine deleted the records from the table based on the old guid(), etc.

I like working with tables/cursors much more than manually adding data to a grid, and having a real cursor gave me the ability to use dbf() to append the data to the real table. It was actually pretty efficient.


Q:
the way to properly replace grid fields with new grid data
My grid is sourced to a data file. I do the updates in the data file and then a skip 0 and refresh of the grid.

The specific code is as follows

Code:
Testlu1() &&testlkup8b() && &&LKUPL2('','L') &&DO LKUP2

debugout MSTOCK
debugout invdet.STOCK

ENDI

SELE (DETAIL2)

REPLA stock with INV.stock,;
invdet.unit with iif(invdet.unit=' ',iif(INV.unit=' ','EA',INV.unit),invdet.unit),;
invdet.act with iif(EMPT(INV.act),MATCD,INV.act),invdet.COST WITH INV.COST
SKIP 0
GRID1.REFRESH
DEBUGOUT INV.COST
DEBUGOUT INV.RETAIL
DEBUGOUT INV.UNIT

debugout 'REPLACE'
RETU

The result is that the stock field which is the current grid field in focus when my valid is called gets updated but none of the other fields chg.
A:
Your replace is attempting to replace values in two different cursors (detail2 and invdet). Keep to one cursor target per replace statement.
REPLACE is by cursor. Make it one or the other. If you remove the invdet. from the rest of the statement, then, it should work.
you are not doing a skip 0 in the invdet cursor so the buffer is not being flushed.
The following command will force write all dirty buffers:

Code:
flush
//instead of skip 0


Grid

Q:
remove a column in grid section
A:
You might not know that you can also right-click on a Grid Column Header to display an additional menu of options:

This gives you an alternative access to the Column’s Attributes, the options to add a blank Column before or after the current Column and allows you to remove the current Column from the Grid.
Remember you can add the Column back in if you change your mind, by dragging the field from the ‘Column Names’ in the Sidebar into the Grid and you can rearrange the Column order in the Grid by dragging a column by its header.


Q:

Code:
ALTER TABLE "test" MODIFY CONSTRAINT AMOUNT SET CHECK(between(Amount,100,200))
ALTER TABLE "test" MODIFY CONSTRAINT AMOUNT SET ERROR "Error, amount must be between 100 and 200"

No error,
I did check it on the table and it’s correct.
when input in the table, it won’t display the errortest after wrong input, the check itself works correct.
It works when I place the field in a textbox.
It doen’t work in a browse or grid.
A:
Grids do not currently ‘Inherit dictionary rules’ – Form and Canvas Sections can (off by default, check the Attribute to enable).
Your validation check will be evaluated when the data is saved to the table and the update rejected if the validation fails, but the error message will not be shown in the Grid.


Next() and previous() are for skipping pages of records. Your grids don’t have pages of records as you have pagination off.


you don’t need to refresh the whole grid you can just refreshRecno( nRecno) which you obtain just like you did with rowid but reference recno instead.


Q:
can i use cursor in to a grid section
A:
A dynamic grid like that which is not bound to a fixed data source cannot be related from a parent section or have child sections as it is not constant.
So if you want to dynamically have a grid on a dynamically chosen table or query then use a custom section.
If you take a look at the canvas demo example it shows you how to do that without using a temporary table. You can just add rows to the grid and recalc subtotals and grand totals.


(?A):
I had a situation where I wanted to populate a grid with a cursor that I had generated; allow the user to edit the data; and have data rules applied from the data dictionary.
What I did was create a table that had a special field in it, cFilterExp C(36). When the user went to the page where that grid resided, I used guid() to create a guid, created the rows in the table which was shared by all users, put the guid() in the cFilterExp of each row, and set the filter of the section to that same guid() value. When the page was used with new data, a routine deleted the records from the table based on the old guid(), etc.
I like working with tables/cursors much more than manually adding data to a grid, and having a real cursor gave me the ability to use dbf() to append the data to the real table. It was actually pretty efficient.


Q:
The grid is read only.
I plan to move through the grid and edit any record I select in it, in the Edit Grid section.
Clicking any row in the grid shows the record in the Events Edit section as expected.
However I can’t move through the grid using the cursor keys on the keyboard. i.e Focus doesn’t move from the selected row.
A:
If you uncheck the Readonly for the Grid Section then make the Columns in the Grid Reaonly (double-click the Column header to open the Attributes), you will be able to use the cursor keys to move between rows in the Grid.
The editable Grid + readonly columns will only give you cursor movement in the Desktop Client, not in the Web Client. Grid Sections in the Web Client (and Desktop Client) respond to touch and mouse/touchpad movement and clicks.


Q:
If you create a hyperlink for items in a grid, how do you link clicks on that item to open another page/report and go to that record?
A:
With the ‘Hyperlink’ Attribute of a Grid Section Column checked to True (double-click the Column Header to access the Attributes), the containing Grid Section ‘Link Click’ event delegate is called when the link is clicked. The event delegate is passed two parameters: the ‘Data source’ of the Column and the value of the clicked cell.
Lianja.showDocument() can be used for actions such as selecting another Page, searching for a value using the Page Search field etc.
For the Lianja Web Client, the link value will need to be unique to be able to identify the record in the ‘Link Click’ event delegate.


“Cell dynamic background” and “Cell dynamic foreground” are not currently supported on the Lianja Web Client.
On the Desktop Client, they are not exposed as properties, but the attributes take an expression. This could be a comparison to a memory variable or other data or you can specify a Lianja/VFP UDF (user defined function) that returns the color, e.g. for a character column value:
myfunc(“{}”)
Your myfunc.prg or myfunc function in a library is passed the current cell value and can then do any processing or checks required before returning the color. Calling the section.refresh() will re-evaluate the expressions in the attributes.

The Canvas Section grid/TreeGrid is based on the Tree class rather than the Grid Section and does not have “Cell dynamic background” and “Cell dynamic foreground”.

It does have the option of specifying an icon on the row – maybe this would work for you as an alternative?

In the screenshot below, I’ve used the lianjacustomcanvas App and specified an icon at the start of the row in the Cart, changing the icon depending on the Cart item’s total.

Note: Desktop App


Q:
In a page I’ve a custom section, with only one textbox (textboxa) and a grid section, based on a table (tableA).

In the “change” delegate of “textboxa” I wrote:

Code:
select tableA
append blank
replace field1 with "jjj", field2 with "ggg" etc.

in the grid, I see only blank record…
like as the “commit” rewrite my field…
if I remove the gridsection, all work fine…
A:
Try a

Code:
skip 0


after the replace line.



When I press “Enter” I insert a record in the third grid “Game Log”, the lower one, and update even the score in the second (that of Set).
When I do a refresh of the second (SET, to refresh the score), the third grid will always show me the Log of the first set .. even if they are on the second or third ..
A:
You should be able to use grid.activerow and grid.activatecell when you refresh the Grid Section, e.g.:

Code:
oGrid = Lianja.get("scout.set").grid
lnActiveRow = oGrid.activerow
oGrid.refresh()
oGrid.activatecell(lnActive Row, 1)


Grid

Q:
Is there an attribute I can set to create a column calculation such as sum or average, etc?
A:
Grid column subtotals were added in Lianja v1.1.3. You can perform both horizontal and vertical totaling using NoCode.


You can hide and show a grid section dynamically using Lianja.getElementByID(“page1.section1”).hide() or show().


In the v1.3 release I have implemented “MultiSelect rows” for grids.
There are two new attributes for grid and attachment sections.
“MultiSelect rows”
and
“MultiSelect row expression”
Together with a new delegate called “SelectionChanged”.
If you check “MultiSelect rows” in the grid section attributes then the first column of the grid contains a checkbox for each row. Clicking the checkbox calls the “SelectionChanged” delegate passing it a comma separated list of items that are evaluated for each row selected based on the “MultiSelect row expression”.
This provides the ability to select multiple rows in a grid then perform an operation on them (section menu of other action) such as send an email or delete the records, then refresh the grid.


In v1.2.4 I have added the ability to dynamically sort the data displayed in a grid by clicking on the grid column headers. This is implemented in both desktop and web clients. There is a new grid section attribute “Sortable” which you can check to enable this on a grid.
Here is an example in the Web Client. Just click the column header once to sort ascending and again to sort descending. This is all handled automatically for you and the grid is refreshed while still maintaining the parent->child relationship, filters and search condition if active.

To make a grid “Sortable” just check the “Sortable” attribute in the section attributes,


Q:
I am struggling to find info on this, my grid has Price and Quantity columns and I want to add a Total column:
1. Must I have a db column that is marked as calculated? if so, does this column need to be created in code because I don’t see anything in the ide column editor to indicate that the column is calculated.
2. If I don’t have a calculated db column, can I just have a column grid column? if so, where do I enter the formula?
I guess I may need something like PRODUCT.PRICE*PRODUCT.QUANTITY in ‘Get Data Mapping’ but that has no effect.
A:
Just add a column and put the expression as the data source.
Set other columns that affect this new “calculated” column to “Recalculate” and its all done for you.
The “Grid formatting” example App demonstrates this.


Q:
We can change the RecordSource of a grid no problem but removing the columns & starting again seems more difficult.
we can use ogrid.removecolumn(ColumnNo) in a loop to remove the columns but when the RecordSource is changed to a new value, the original columns headers & number of columns return.
How can we get the grid to re-read the column headers in the way that happens when the grid is first created?
A:
Then try clear() on the grid.


Q:
In our app (at the customers request), we have 14 grid gadgets in 7 form sections (2 per section), populated by 14 READWRITE SQL cursorsA
The prg does the SQL & all works fine. The cursors are populated. Logic for the grids, called at each pass through the logic, is :

Code:
SELECT * from xxx WHERE xxx INTO CURSOR zzz READWRITE
oGrid = Lianja.Get("Page.section").GadgetGrid
oGrid.RecordSourceType = 4
oGrid.RecordSource = "zzz"
oGrid.Refresh()

The SQL always produces data for each cursor.
All of the above is so simple and works beautifully SOMETIMES. Sometimes the grids work great, sometime some of them are blank, sometimes they are not. Sometimes they the blank ones are populated & sometimes the populated ones go blank. This is ridiculous.
These grid gadgets really only want tables and when they are blank they error by saying no such table. We have tried every permutation of RecordSourceType and RowSourceType.
A:
yes I do understand the way VFP devs like using cursors like that but that does not work web/mobile client/server so I’ll look and see what you are doing first.
There should be no issue with Lianja closing cursors unless you are selecting into the same cursor name in which case it behaves just like VFP.
it seems that what you want to do is choose multiple items in a list by checking the checkbox in the first column.
what happens as you do that? Does that cause you to perform a query on another grid? Then iterate this process on the child grids…?
i think this is where the design needs looked at.
what you need is a top level parent form e.g. Job#
this then relates to the child table that has records added to it for the job. This then will work multiuser and in the web / mobile.
The trick is to add all the related child records when you create a new job# or proposal# and these can be done in an insert trigger. They are related by job# so you have one child table with all the multiple choices as records.
This is then properly relational and can be properly queried by “joining” the tables together and displaying the related data in the UI.
this will allow you to design the app as form -> grid -> grids and the Lianja engine will do all the heavy lifting.
and all that will work in web/mobile.



Grid

Q:
May I know if I can add a command button in a column of a grid so that user can click on it to popup a form for data entry
A:
The grid section is comprised of columns.
Double click on the column header to slide in the column attributes.
A column has a wide range of attributes including the ability to display as a “Button”, a “Hyperlink” or a “Custom Control”.

Then in the Grid section attributes add your delegate for “Link Clicked“.


any grid section filters that have {…} macros in them, these are expanded at the time of refreshing the grid. This basically means that the filter itself is dynamically evaluated at the time the grid is refreshed. This works in all views; desktop, web and mobile.


A grid is bound to data so IOW there is a corresponding cursor for each grid.
As you navigate records in the grid the active record in the underlying cursor is changed.
You can update the active record and then refresh that specific record in the grid using oGrid.refreshRecord( recno() )

If the underlying cursor has been dynamically created from a virtual table query you need to specify a primary key and issue the replace statement which will generate the SQL dynamically and execute it.


If you make the grid “readonly” it will be highlighted.
Otherwise

Code:
Lianja.get("page1.section2").grid.activeRow = 0

The grid is bound to your data which I understand to be a SQL query so you can update the data and refresh the grid (saving the activerow and positioning back on it).

The ROWID for the base table is a hidden column that you can use to perform the update.
Look in the console and LIST STATUS to see what cursor is bound to your grid them SELECT cursorname and issue a ? rowid. That is the record in the base table that needs updated.


You need to use the doScroll() method like this if you want to move down the rows of the grid.

Code:
Lianja.get("clock_records.list").grid.doScroll( arg )

Note that Lianja.get() does not require “page:”, you are looking up the object.


Q:
I tried the SQL with filter as follows but the grid always show all records
SQL :

select * from ventlrec

Filter :

where clernoee=”{peoplekiosk.emp_no}”

A:
The filter is a condition. Remove the where.
A better way to achieve this would be to apply the whole sql select … Where …
What data type is clernoee? If it’s not a char column remove the quotes.


Q:
I have a desktop app with a page with one grid section
I have set the “Filter” attribute to as follows for top grid section :

clernoee=”{peoplekiosk.emp_no}”

A:
What is “peoplekiosk”? If it is a namespace perhaps it is not declared until later.
Setting a filter on a large number of records is also not a good idea.
Better to have an index key on it and restrict by the index key.
You do not say if this is a table, SQL statement or Virtual Table.
Setting filters to variables requires the grid to be manually refreshed programmatically when you change the variable.

If the grid is not editable use SQL statement.
If it is editable use a Virtual Table.
Look at the Virtual Tables example and you will see how {…} macros are used in the “where” condition of the SQL statement.


Added the ability for a grid section to have “Multi Select Rows“. This displays a checkbox column for each row. As the user clicks a checkbox, the “selectionChanged” delegate is called with a comma separated list of values. These values are determined by the “Multi Select Row Expression” e.g. You can multi select rows in a grid and then call your own business procedure to handle app specific business logic

And these are the attributes you need to set for the grid section to enable this functionality.

The “selectionChanged” delegate itself looks like this.

Code:
////////////////////////////////////////////////////////////////
// Event delegate for 'selectionchanged' event
proc page1_section2_selectionchanged(arg)
    // 'arg' is a comma separated list of items selected
    // It can be used directly to load the selected items into a ListBox
    Lianja.writeOutput("selectionChanged() arg="+arg)
endproc


Grid

Q:
graphical checkbox in a grid column
A: 
Currently not supported. You can however put combo boxes in grids using Static or dynamic choicelists.



Grid Column Attributes are accessed by double-clicking on the Grid Column Header. These Attributes then apply to the Column in the ‘Grid View’ and also in the ‘Form View’ when you are using a Split Grid.



In the Lianja App Builder, you are probably used to the idea of double-clicking on a control or container’s header or caption to access its Attributes and the same is true for a Grid Column:
You might not know that you can also right-click on a Grid Column Header to display an additional menu of options:
This gives you an alternative access to the Column’s Attributes, the options to add a blank Column before or after the current Column and allows you to remove the current Column from the Grid.
Remember you can add the Column back in if you change your mind, by dragging the field from the ‘Column Names’ in the Sidebar into the Grid and you can rearrange the Column order in the Grid by dragging a column by its header.



Web Client Grid sections have a standard Save/Cancel/Add/Edit/Delete actionbar in the header unless the Grid itself is Readonly.



You can call the grid.sort() method

Code:
oGrid = Lianja.get("page1.section1").grid
// sort 3rd column ascending
// columns start from 0
// direction: 1=ascending, -1 = descending
oGrid.sort(2,1)

The Sort delegate is not currently called when clicking on the column headers.
I’ll check on support in JavaScript and the Web Client.



Split Grids are normal Grid Sections with the ability to display a single record Form View within the Section. In the lianjademo App, the Orders and Order Details grids in the ‘Customers Orders’ dashboard are both Split Grids.
You set Split Grid behaviour in the Grid Section Attributes, checking ‘Double click to edit’ and/or ‘Split grid’.
With ‘Double click to edit’ set, double-clicking on a Grid row displays a single record Form in the grid. If you have memo or object (e.g. image) fields, check the ‘Show memo/object panel’ attribute.

double_click_to_edit

Tip: hover your mouse over the join between the grid and the Form View to display a handle allowing your to resize the width of the Form View or hide it completely. There’s also a resize handle for the height of the memo panel, just hover your mouse at the top of the panel header to reveal it.
Checking ‘Split grid’ gives you three possible ‘Views’ for the grid and displays buttons in the grid header allowing you to switch between them.

grid_section_attributes

Split View:

split_view

Form View:

form_view

Grid View:

grid_view

ip: the displayed columns/fields are the same in the Form View as in the Grid View, i.e. a ‘removed’ column will not display in the Form View. They are also displayed in the same order – remember you can reorder the columns in a grid by dragging a column header over another.



For a standard Grid Section, this appears to working fine: 

Code:
Lianja.get("section2").refresh()


Q:
The Sort() method in the Grid takes 2 parameters nColumn, nDirection but where do they come from & how does it work? 
A:
the number refers to the order of columns in a table, as displayed in the data manager. I haven’t tried (yet) to use macros there, with a routine that would use afields() to get the order from the name. Give it a try!
{fnum4fld(“firstname”},{fnum4fld(“lastname”}, etc.
Note that the sort() nColumn argument (Column number) starts from 0 for the first column. There is also some information about the sort() indexing here. 
Also remember when you are manipulating a Grid Section with Lianja.getElementById() and synonyms, you need to work on the Section’s embedded Grid object, e.g.

Code:
oGrid = Lianja.get("page1.section1").grid
oGrid.sort(0,1)  // sort ascending on the first column


Q:

Code:
proc Gamelist_grdGameList_dblclick()
        nRow=Lianja.Get("Gamelist.grdGameList").grid.activerow
        messagebox(str(nRow2))
endproc

A:

Code:
proc Gamelist_grdGameList_dblclick()
       oGrid = Lianja.Get("Gamelist.grdGameList").grid
       nRow = oGrid.activerow
       messagebox(str(nRow))
endproc


Q:
Is it possible to access a grid column heading through code?
For example, how do I determine the column heading for column 3?
A:
The Column object (returned from grid.columns(n) has a property called Header1 that returns a reference to the Header object for that column. e.g.

Code:
oCol1 = grid1.columns(1)
oHeader = oCol1.header1
oHeader.caption = "This is Column 1"


When you create a new grid object, make sure it is a ‘mygrid’ not a standard ‘grid’ or it will not have the colattribs() method defined.

Code:
ui_goalhomecont.addobject("ui_goalhomegrid", "grid")

needs to be

Code:
ui_goalhomecont.addobject("ui_goalhomegrid", "mygrid")


Q:
How do I properly set this column to be read-only and have the grid functional?
A:

Code:
oCol1 = ui_homegrid.columns(1)
oCol1.readonly = .T.


event that fires for a child grid as he moves through the parents. Parentdatachanged() fires before the new child is selected, so can’t be used to pick up the child value (first row in child grid).
The click() firing has been confirmed as expected behaviour – it shouldn’t fire if you are clicking within the same row.



Q:

Code:
?Lianja.Get("test.section2").RowCount
?Lianja.Get("test.section2").item(1,2)

A:
Reference the grid contained by the Grid Section:

Code:
oGrid = Lianja.Get("test.section2").grid
? oGrid.rowcount
? oGrid.item(1,2)


To get information from the Grid, or select a cell:

Code:
oGrid = Lianja.get("mypage.mysection").grid
// what is the value at row 3, column 4
? oGrid.item(3,4)
// what is the current row?
? oGrid.activerow
// what is the current column?
? oGrid.activecolumn
// select row 2, column 5
oGrid.activatecell(2,5)


Q: 
Have a section “section1”, as grid, there’s a test table in it.
Another section “section2” as a canvas with a button on it.
Code in button: lianja.get(“section1”).grid.add 
A:
The grid is bound to the test table so all you need to do is append/insert a record in the table issue a refresh on the grid then position onto the new row using …grid.goto( recno() ).


Q:
May I know if I can add a command button in a column of a grid so that user can click on it to popup a form for data entry
A:
The grid section is comprised of columns.
Double click on the column header to slide in the column attributes.
A column has a wide range of attributes including the ability to display as a “Button”, a “Hyperlink” or a “Custom Control”.

Screen Shot 2015-01-25 at 10.10.21 AM

Then in the Grid section attributes add your delegate for “Link Clicked”.

Screen Shot 2015-01-25 at 10.11.20 AM



any grid section filters that have {…} macros in them, these are expanded at the time of refreshing the grid. This basically means that the filter itself is dynamically evaluated at the time the grid is refreshed. This works in all views; desktop, web and mobile.



A grid is bound to data so IOW there is a corresponding cursor for each grid.
As you navigate records in the grid the active record in the underlying cursor is changed.
You can update the active record and then refresh that specific record in the grid using oGrid.refreshRecord( recno() )
If the underlying cursor has been dynamically created from a virtual table query you need to specify a primary key and issue the replace statement which will generate the SQL dynamically and execute it.



If you make the grid “readonly” it will be highlighted.
Otherwise

Code:
Lianja.get("page1.section2").grid.activeRow = 0


The grid is bound to your data which I understand to be a SQL query so you can update the data and refresh the grid (saving the activerow and positioning back on it).
The ROWID for the base table is a hidden column that you can use to perform the update.
Look in the console and LIST STATUS to see what cursor is bound to your grid them SELECT cursorname and issue a ? rowid. That is the record in the base table that needs updated.



You need to use the doScroll() method like this if you want to move down the rows of the grid.

Code:
Lianja.get("clock_records.list").grid.doScroll( arg )

Note that Lianja.get() does not require “page:”, you are looking up the object.



Q:
I tried the SQL with filter as follows but the grid always show all records
SQL :

select * from ventlrec

Filter :

where clernoee=”{peoplekiosk.emp_no}”

A:
The filter is a condition. Remove the where. 
A better way to achieve this would be to apply the whole sql select … Where …
What data type is clernoee? If it’s not a char column remove the quotes.



Q:
I have a desktop app with a page with one grid section
I have set the “Filter” attribute to as follows for top grid section : 

clernoee=”{peoplekiosk.emp_no}”

A:
What is “peoplekiosk”? If it is a namespace perhaps it is not declared until later.
Setting a filter on a large number of records is also not a good idea.
Better to have an index key on it and restrict by the index key.
You do not say if this is a table, SQL statement or Virtual Table.
Setting filters to variables requires the grid to be manually refreshed programmatically when you change the variable.

If the grid is not editable use SQL statement.
If it is editable use a Virtual Table.
Look at the Virtual Tables example and you will see how {…} macros are used in the “where” condition of the SQL statement.



Added the ability for a grid section to have “Multi Select Rows”. This displays a checkbox column for each row. As the user clicks a checkbox, the “selectionChanged” delegate is called with a comma separated list of values. These values are determined by the “Multi Select Row Expression” e.g. You can multi select rows in a grid and then call your own business procedure to handle app specific business logic

Screen Shot 2014-12-11 at 3.03.53 PM

And these are the attributes you need to set for the grid section to enable this functionality.

Screen Shot 2014-12-11 at 3.04.51 PM

The “selectionChanged” delegate itself looks like this.

Code:
////////////////////////////////////////////////////////////////
// Event delegate for 'selectionchanged' event
proc page1_section2_selectionchanged(arg)
    // 'arg' is a comma separated list of items selected
    // It can be used directly to load the selected items into a ListBox
    Lianja.writeOutput("selectionChanged() arg="+arg)
endproc






Q: 
Is there an attribute I can set to create a column calculation such as sum or average, etc?
A:
Grid column subtotals were added in Lianja v1.1.3. You can perform both horizontal and vertical totaling using NoCode.

Screen Shot 2014-12-11 at 2.58.25 PM

Screen Shot 2014-12-11 at 2.59.19 PM



You can hide and show a grid section dynamically using Lianja.getElementByID(“page1.section1”).hide() or show().



In the v1.3 release I have implemented “MultiSelect rows” for grids.
There are two new attributes for grid and attachment sections.
“MultiSelect rows”
and
“MultiSelect row expression”
Together with a new delegate called “SelectionChanged”.
If you check “MultiSelect rows” in the grid section attributes then the first column of the grid contains a checkbox for each row. Clicking the checkbox calls the “SelectionChanged” delegate passing it a comma separated list of items that are evaluated for each row selected based on the “MultiSelect row expression”.
This provides the ability to select multiple rows in a grid then perform an operation on them (section menu of other action) such as send an email or delete the records, then refresh the grid.



In v1.2.4 I have added the ability to dynamically sort the data displayed in a grid by clicking on the grid column headers. This is implemented in both desktop and web clients. There is a new grid section attribute “Sortable” which you can check to enable this on a grid.
Here is an example in the Web Client. Just click the column header once to sort ascending and again to sort descending. This is all handled automatically for you and the grid is refreshed while still maintaining the parent->child relationship, filters and search condition if active.

Screen Shot 2014-10-07 at 1.50.08 PM

To make a grid “Sortable” just check the “Sortable” attribute in the section attributes,


Screen Shot 2014-10-07 at 1.54.07 PM



Q:
I am struggling to find info on this, my grid has Price and Quantity columns and I want to add a Total column:
1. Must I have a db column that is marked as calculated? if so, does this column need to be created in code because I don’t see anything in the ide column editor to indicate that the column is calculated.
2. If I don’t have a calculated db column, can I just have a column grid column? if so, where do I enter the formula?
I guess I may need something like PRODUCT.PRICE*PRODUCT.QUANTITY in ‘Get Data Mapping’ but that has no effect.
A:
Just add a column and put the expression as the data source.
Set other columns that affect this new “calculated” column to “Recalculate” and its all done for you.
The “Grid formatting” example App demonstrates this.



Q:
We can change the RecordSource of a grid no problem but removing the columns & starting again seems more difficult.
we can use ogrid.removecolumn(ColumnNo) in a loop to remove the columns but when the RecordSource is changed to a new value, the original columns headers & number of columns return.
How can we get the grid to re-read the column headers in the way that happens when the grid is first created?
A:
Then try clear() on the grid.



Q:
In our app (at the customers request), we have 14 grid gadgets in 7 form sections (2 per section), populated by 14 READWRITE SQL cursorsA
The prg does the SQL & all works fine. The cursors are populated. Logic for the grids, called at each pass through the logic, is :

Code:
SELECT * from xxx WHERE xxx INTO CURSOR zzz READWRITE
oGrid = Lianja.Get("Page.section").GadgetGrid
oGrid.RecordSourceType = 4
oGrid.RecordSource = "zzz"
oGrid.Refresh()

The SQL always produces data for each cursor.
All of the above is so simple and works beautifully SOMETIMES. Sometimes the grids work great, sometime some of them are blank, sometimes they are not. Sometimes they the blank ones are populated & sometimes the populated ones go blank. This is ridiculous. 
These grid gadgets really only want tables and when they are blank they error by saying no such table. We have tried every permutation of RecordSourceType and RowSourceType.
A:
yes I do understand the way VFP devs like using cursors like that but that does not work web/mobile client/server so I’ll look and see what you are doing first.
There should be no issue with Lianja closing cursors unless you are selecting into the same cursor name in which case it behaves just like VFP.
it seems that what you want to do is choose multiple items in a list by checking the checkbox in the first column.
what happens as you do that? Does that cause you to perform a query on another grid? Then iterate this process on the child grids…?
i think this is where the design needs looked at.
what you need is a top level parent form e.g. Job# 
this then relates to the child table that has records added to it for the job. This then will work multiuser and in the web / mobile.
The trick is to add all the related child records when you create a new job# or proposal# and these can be done in an insert trigger. They are related by job# so you have one child table with all the multiple choices as records.
This is then properly relational and can be properly queried by “joining” the tables together and displaying the related data in the UI.
this will allow you to design the app as form -> grid -> grids and the Lianja engine will do all the heavy lifting.
and all that will work in web/mobile.