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.

Put a Debug(1) statement in your Xbasic function and then run the UX Component in Working Preview. This will open the debugger and allow you to to inspect the contents of the e object.

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