Custom
Description
Settings defining the Xbasic and optional JavaScript functions to call to populate a List control based on a custom data source.
Xbasic function name
The Xbasic function name property defines the name of the Xbasic function to call to fetch data for the List control. The function must be defined before defining the List's layout. This is because the Fields for the List control are computed by making a call to the Xbasic function to retrieve the fields in the data.
The Xbasic function can be defined in the UX component's Xbasic Functions code section or in the List control. Defining the Xbasic in the List control will keep the Xbasic function with the List, meaning if the List control is copied between components, the Xbasic function that populates will also be copied. See Define Xbasic in control to learn more.
The Xbasic function is called under the following conditions:
- When the List is originally populated
- When a callback is made to go to the next page of data, fetch more data, refresh data, perform sever-side searching or sorting
The Xbasic function must return data in one of the following formats:
- A CR-LF delimited list of values representing the List data. Columns in each row are pipe ("|") delimited.
- A JSON string with an array of objects.
If specify the data as a CR-LF delimited list of values, the first row in the returned data must be the field names for each column in the List data. The field names can include optional type information. If the data includes "|" or CR-LF characters, you must encode them using \pipe; or \crlf;.
The following Xbasic function returns a CR-LF delimited list:
function myXbFunction as c (e as p) dim values as c values =<<%txt% Firstname|Lastname|Age=N|HireDate=D John|Smith|35|12/15/2005 Henry|Mancini|23|1/1/2004 %txt% myXbFunction = values end function
The type specified in the field header indicates the data type for the field when the field is used in a server-side expression (e.g. a conditional style.) The type does not indicate the type of the field in the List data. All List data is string values unless a Data transformation is defined for a Field.
The next example returns data as a JSON string:
function myXbFunction as c (e as p) dim values as c values =<<%json% [ {"Firstname":"John","Lastname":"Smith","Age":35,"HireDate":Date("12/15/2005")}, {"Firstname":"Henry","Lastname":"Mancini","Age":23,"HireDate":Date("1/1/2004")} ] %json% myXbFunction = values end function
Xbasic Function Arguments
The e object passed to the Xbasic function contains the following parameters. Some parameters are only available if the List includes a search part or if a filter or order have been applied to the List:
Arguments
The following arguments are provided to the Xbasic function when it is called. Some arguments are only available if the Xbasic function is called in an Ajax Callback.
- Argument
- Description
- e.tmpl
The component definition.
- e.rtc
The rtc object.
- e.arguments
Values for any arguments defined in the component.
- e.dataSubmitted
Any data from controls on the component. e.dataSubmitted is only meaningful when the function is called in an Ajax Callback. Use the eval_valid() function to validate that a value exists before you use it. EG: if eval_valid("e.dataSubmitted.myvar1") then ...
- e.__javascriptFunctionResults
If a Javascript function was specified in addition to an Xbasic function, result from the Javascript function
- e._state
State information for the component.
- e.listDefinition
All of the List control's properties.
- e.paginateData
Indicates if data pagination is turned on (.T.) or off (.F.).
- e.targetPage
If pagination is enabled, the target page of data to show.
- e.targetLogicalRecord
The target logical record to show.
- e.getDataMode
The data mode. Can be one of the following:
- Mode
- Description
- PopulateList
When the List is initially populated or refreshed.
- FetchMore
When List pagination is turned on, and pagination method is set to FetchMore and the user has clicked the 'Fetch More' button.
- Navigate
When List pagination is turned on, and the user has navigates to another 'page' of records.
- RefreshRow
When the user refreshes the data in the current row.
- RefreshRowByKey
When the user refreshes a set of rows in the List by specifying key values of the rows to refresh.
- FetchExplicit
When the user appends rows to the List by specifying key values of the rows to append.
- Sort
When the user does a server-side sort on a column.
Output Arguments
The Xbasic function can set the following properties in the e object. These are output variables that can be used to return errors, JavaScript to execute on the client, or other data that is used by other controls.
- Argument
- Description
- e.fatalError
Set to .T. to indicate an error occurred during execution.
- e.errorText
If an error occurs, the error message to display.
- e.javascript
Optional JavaScript to execute when the List is rendered.
- e.recordsInQuery
The number of records returned. This argument is used with the List-record count control to display the total records in the List. If you want to use the List-record count control with a List control that uses a custom data source, you must set this property.
Filter/Order Arguments
If a filter or order has been applied to the List control, the following arguments will be available:
- Argument
- Description
- e.userFilter
If a filter has been applied to the List, the filter expression.
- e.userOrder
If an order has been applied to the List, the order expression.
- e.filterParameters
If a filter as been applied to the List, contains a CR-LF delimited list of arguments used in the e.userFilter expression. The format for this expression is value|||argumentType|argumentName
Search Arguments
If the List control has a Search Part, the following properties are provided. These properties
- Argument
- Description
- e.delayPopulateTillActiveSearch
If the List has a search part, indicates if the List should only be populated if there is an active search. If e.getDataMode = "populateList" and there is no active search (e.flagActiveSearch = .f.) then your function should not return any data (other than column heading.)
- e.flagActiveSearch
If the List has a search part, indicates that the user executed a search to find records with which to populate the List.
- e.maxPayloadAllowed
If the List has a search part, the maximum payload allowed for the search results (size of result in bytes). if this value is -1 then no max was specified.
- e.maxRowsAllowed
If the List has a search part, the maximum number of rows allowed for the search result. if this value is -1 then no max was specified. Your function does not need to test if the search result meets the search maximum settings. The tests will be automatically done using the result returned by your function.
- e.rowsReturned
Output Property. Indicates the number of rows returned by the Xbasic function. This property is required in order to determine if the number of rows returned meets the Maximum search size number of rows property for a List.
Setting the Record Count
You can optionally set the e.recordsInQuery property to the number of records returned. This property is used with the List-record count pre-defined control. If e.recordsInQuery is not set, the List-record count control cannot be used with a List with a custom data source.
dim sqlCount as c = "SELECT * FROM [order details]" if (cn.execute(sqlCount)) then dim recordsInQuery as N recordsInQuery = cn.resultSet.data(1) e.recordsInQuery = recordsInQuery else ' error... end if
Error Reporting
If an error occurs while computing the data, the error can be returned to the List by setting the e.fatalError and e.errorText properties and then exit the function. For example:
if (cn.open("::Name::conn") <> .t.) then ' Unable to establish connection to database: e.fatalError = .t. e.errorText = "Unable to connect to database. Error was: " + cn.callResult.text exit function end if
Returning Javascript from Xbasic
Optional Javascript to execute when the List is rendered can be returned from the Xbasic function in the e.javascript variable. For example:
e.javascript = "alert('List data has been retrieved!');"
Example: SQL Data Source with Pagination
This example will populate the List with data from the 'Order Details' Northwind sample database. The 'Order Details' table is used because it represents a more complex example - the primary key for this table is multi-column (e.g. orderId and productId.) Key values for multi-column keys are ||| delimited. For example 12048|||23 (represents an OrderId of 10248 and a ProductId of 23)
The example includes handling a List with pagination enabled.
function getData as c (e as p) 'Open the connection dim cn as sql::Connection dim flag as l flag = cn.open("::Name::AADemo-Northwind") if flag = .f. then e.fatalError = .t. e.errorText = "Could not connect to database. " + crlf(2) + "Error reported was: " + cn.CallResult.text exit function end if 'Turn on portable SQL cn.PortableSQLEnabled = .t. 'Compute the queryLimit for the SQL query 'e.g. "SELECT FIRST x field1 FROM table1" 'Note: This is not necessary if the List is not paginated dim queryLimit as n queryLimit = ((e.targetPage -1) + 1) * e.pageSize 'Define the SQL queries dim sql as c dim sqlCount as c 'This is the query that returns the data. If the List is not paginated, 'you don't need to add a 'FIRST clause. sql = "SELECT FIRST "+queryLimit+" * FROM [order details]" if e.paginateData = .f. then sql = "SELECT * FROM [order details]" end if 'This is the query that returns the count of the number of records in the List query. sqlCount = "select count(*) from [order details]" dim args as sql::arguments if e.getDataMode = "PopulateList" .or. e.getDataMode = "fetchMore" .or. e.getDataMode = "Navigate" .or. e.getDataMode = "sort" then 'If the List has a search part which is configured to 'delay populate list till active 'search' then exit if there is no active search if e.delayPopulateTillActiveSearch = .t. then if e.getDataMode = "populateList" then if e.flagActiveSearch = "" then cn.close() exit function end if end if end if 'if the user has applied a filter/order, add it in if e.filterParameters <> "" then a5DialogHelper_ParametersToArguments(e.filterParameters,args) end if sqlCount = a5cs_sql_add_filter_order(sqlCount,e.userFilter,"","") dim userOrderClause as c userOrderClause = e.userOrder if left(e.userOrderDirection,1) = "D" then userOrderClause = userOrderClause + " DESC" end if sql = a5cs_sql_add_filter_order(sql,e.userFilter,"",userOrderClause) flag = cn.Execute(sqlCount,args) if flag = .f. then e.fatalError = .t. e.errorText = "Could not execute count records query. " + crlf(2) + "Error reported was: " + cn.CallResult.text cn.close() exit function end if dim recordsInQuery as n recordsInQuery = cn.ResultSet.data(1) e.pageCount = round_up(recordsInQuery / e.pageSize,0) e.recordsInQuery = recordsInQuery 'set the state of the 'Fetch More' button if e.targetPage = e.pageCount then e.flagTurnOffFetchMore = .t. else e.flagTurnOffFetchMore = .f. end if end if if e.getdatamode = "refreshrowByKey" .or. e.getDataMode = "refreshRow" .or. e.getDataMode = "fetchExplicit" then dim where as c dim sqlWhereTemplate as c sqlWhereTemplate = " ( orderId = :orderId_i_ AND productId = :productId_i_ )" dim i as n dim count as n count = e.keys.size() 'create a clause in the WHERE statement for each key in the e.keys[] array. 'since the primary key is multi-column, each key in the array will be delimited 'with 3 pipes. For example, the key for a record with an OrderId of 10248 and 'a part number of 4 is: 10248|||5 for i = 1 to count where = where + stritran(sqlWhereTemplate,"_i_","" + i) + crlf() 'the orderId is the segment to the left of the ||| delimiter. the data in the array 'must be converted to the correct data type. args.add("orderId" + i, convert_type(word(e.keys[i],1,"|||"), "N") ) 'the productId is the segment to the right of the ||| delimiter. the data in the array 'must be converted to the correct data type. args.add("productId", convert_type( word(e.keys[i],2,"|||"), "N") ) next i 'at this point the WHERE variable is a crlf delimited list of clauses. Join the clauses 'with the OR keyword. where = stritran( alltrim(where), crlf()," OR ") 'now, add the where clause to the sql statement sql = a5cs_sql_add_filter_order(sql,where) end if 'execute the query flag = cn.Execute(sql,args) if flag = .f. then e.fatalError = .t. e.errorText = "Could not execute query. " + crlf(2) + "Error reported was: " + cn.CallResult.text cn.close() exit function end if dim rs as sql::ResultSet rs = cn.ResultSet if e.getDataMode = "Navigate" .or.e.getDataMode = "fetchMore" then 'move to the target logical row number rs.GoToRow(e.targetLogicalRecordNumber) end if dim txt 'dump the data from the resultSet in JSON format 'txt = rs.ToJSONObjectSyntax() 'TIP: The .toJSONObjectSyntax() method treats all data as character type. 'In some cases you will want the data to be correctly typed. You can use the rsToJSON() 'function in this case. The disadvantage of rsToJSON() is that it is slightly slower than 'rs.toJSONObjectSyntax() txt = rsToJSON(rs,-1,-1,.f.,.f.) 'The data is a CR-LF delimited string of JSON objects. A comma is needed after each JSON object txt = crlf_to_comma(txt) 'now add the leading and closing [ ] to turn the data in a Javascript array object. txt = "[" + txt + "]" getData = txt 'clean up cn.FreeResult() delete rs cn.close() 'Store the list state information in the e.listState object. 'This is important or else when you navigate to the next page (for example), the filter that 'has been applied to the list (if any) will be lost 'You can store arbitrary information in the e.listState object. This object is available on ' 'all callbacks to this function e.listState.filter = e.userFilter e.listState.order = e.userOrder e.listState.filterParameters = e.filterParameters end function
Javascript function
Before an Ajax callback is made to the Xbasic function that returns the data for the List, an optional Javascript function can be called. The Javascript function can return data that will be available to the Xbasic function that computes the data for the List. The optional Javascript function is only needed if the Xbasic function needs information that is only available on the client. The data returned can be a string, array or object.
Data from the Javascript function will be available in the e.__javascriptFunctionResults variable. The variable will only be defined in an Ajax Callback. It is recommended that you define a default value for the e.__javascriptFunctionResults variable in your Xbasic function. If the variable does not exist when the function is called, it will be created with a default value:
dim e.__javascriptFunctionResults as c = default ""
Example 1: Returning a Single Value
This example demonstrates a Javascript function that returns a single value, 'explodedSlice=3':
function jsData() { return 'explodedSlice=3'; }
In the Xbasic function, xbData, the value returned by the Javascript function jsData() can be accessed via the e.__javascriptFunctionResults variable.
function xbData as c (e as p) dim e.__javascriptFunctionResults as c = default "" dim explodedSlice as c = "" if e.__javascriptFunctionResults <> "" then 'e.__javascriptFunctionResult will be a string like this: "explodedSlice=3" - get the word after the = sign. explodedSlice = word(e.__javascriptFunctionResults,2,"=") end if end function
Example 2: Returning an Object
In addition to a single value, an object can be returned from the Javascript function. In the example below, jsData() returns an object with two properties: explodedPieSlice and name.
function jsData() { var o = {}; o.explodedPieSlice = 3; o.name = 'East'; return o; //function returns an object }
In the Xbasic function xbData(), json_parse() can be used to convert the object from the Javascript function into a dot variable:
function xbData as c (e as p) dim e.__javascriptFunctionResults as c = default "" dim p as p if e.__javascriptFunctionResults <> "" then 'parse the function result - it is in JSON format p = json_parse(e.__javascriptFunctionResults) end if dim p.explodedPieSlice as n = default -1 dim p.name as c = default "" end function
Example 3: Returning an Array
Data can also be returned as an array from the Javascript function. For example, jsData() below returns an array containing 3 values: [1,3,5].
function jsData() { var a = [1,3,5]; return a; //function returns an array }
json_parse() is used in the Xbasic function to convert the data into a dot variable, making it easier to work with:
function xbData as c (e as p) dim e.__javascriptFunctionResults as c = default "" dim p as p if e.__javascriptFunctionResults <> "" then p = json_parse(e.__javascriptFunctionResults) end if dim arraySize as n arraySize = p.size() 'array values are in p[1], p[2], etc. end function