A DataView displays rows of data. It is similar to a DataGrid form, a control available in the LiveCode IDE.
LiveCode Version: 8.x or higher
Platforms: Tested on Windows, macOS, and iOS.
A DataView is responsible for taking row data that your code provides and rendering it in a highly customizable way using row templates. Row data is an array of key=>value pairs. Out of the box you can assign a numerically indexed array of arrays with key=>value pairs to a DataView (see example below). But you can customize the data source any way you would like.
The DataView Demo application includes an example of using this helper:
https://github.com/trevordevore/dataview_demo
If your LiveCode application uses the Levure framework then the DataView can be added as a helper.
- Download the latest release of Source Code.zip|tar.gz from https://github.com/trevordevore/levurehelper-dataview/releases
- Unzip the contents, rename the resulting folder to dataview, and add the folder to the ./app/helpers folder in your application folder.
If your LiveCode application is not using the Levure framework then you can use the dataview_loader.livecodescript file to load the necessary stacks into memory.
- Download the latest release of Source Code.zip|tar.gz from https://github.com/trevordevore/levurehelper-dataview/releases
- Unzip the contents, rename the resulting folder to dataview, and add the folder to your application folder.
- Using the property inspector, add the dataview_behavior.livecodescript stack file to the
mainstacks
property of your application stack. - Now add all of the remaining stack files in the dataview folder to the
mainstacks
property of your application stack. - In your application code start using the "DataView Assets Loader" stack and then remove it from memory.
start using stack "DataView Assets Loader"
delete stack "DataView Assets Loader"
The DataView helper includes two commands which can create DataView group controls in the LiveCode IDE:
dvIdeCreateDataViewControl pName, pTargetCard, pBehavior
dvIdeCreateDataViewControlUsingDialog pTargetCard, pBehavior, pRowStyleTemplateGroupsA
dvIdeCreateDataViewControl
will create a DataView with no additional input. The only required parameter is pName
which will be the name of the group that is created. If pTargetCard
is empty then the DataView group control will be added to the current card of the topStack
. If you would like to assign a behavior to the DataView group control other than the "DataView Behavior" stack then pass a reference to it in pBehavior
. For example, if you want to create a new DataView group control in the current card of the topStack
that uses the DataView Array controller then you would make the following call:
dvIdeCreateDataViewControl "My DataView", empty, the long id of stack "DataView Array Controller Behavior"
dvIdeCreateDataViewControlUsingDialog
will display a dialog with additional options. pTargetCard
and pBehavior
behave the same way as for dvIdeCreateDataViewControl
.
The modal dialog that appears has a couple of additional options:
- There is a field for entering a name for the DataView group control.
- There is a checkbox for creating a row template in the ./templates folder of your Levure application.
- There is a filed where you can enter a comma-delimited list of row template styles you want to create. One row template group will be created for each style in the list.
- There is a checkbox for specifying that you want to create a new behavior to assign to the DataView group control. This new behavior is used for your application specific code that you want to attach to the DataView. It will be saved as a script only stack in a "behaviors" folder sitting alongside the stack that the DataView is created in. The script only stack file will also be added to the
stackFiles
property of the stack.
If you pass in a value for pBehavior
then pBehavior
will be assigned as the behavior of the new behavior that is created. If you do not pass in pBehavior
then the "DataView Behavior" stack will be assigned as the behavior of the new behavior.
Note: If you plan on adding your application specific logic directly to the DataView group control script then you do not need to create an additional behavior script. Creating the additional behavior script is only necessary if you are using version control with your application or if prefer editing your LiveCode scripts in a separate text editor.
Here is an example of creating a new DataView that uses the array controller and populating it with some test data. It is assumed that in the dialog you opted to create a row behavior. The row template that is created is coded to display a "label" key in each row.
dvIdeCreateDataViewControlUsingDialog empty, the long id of stack "DataView Array Controller Behavior"
put "Line 1" into tA[1]["label"]
put "Line 2" into tA[2]["label"]
put "Line 3" into tA[3]["label"]
put "Line 4" into tA[4]["label"]
set the dvData of group "My DataView" to tA
If you make changes to the row template group and want to see the change reflected in the DataView then issue the following calls:
dispatch "ResetView" to group "My DataView"
dispatch "RenderView" to group "My DataView"
To test that a new DataView is working you can create a simple numerically indexed array and assign it to it's dvData
property. The default row template displays a "title" key in a field so a simple test would look like this:
put "Row 1" into tDataA[1]["title"]
put "Row 2" into tDataA[2]["title"]
put "Row 3" into tDataA[3]["title"]
set the dvData of group "MyDataView" to tDataA
Each row in a DataView is rendered using a row template. A row template is simply a group
that contains all of the controls necessary to display the data for a row. The group has a behavior script assigned to it that contains all of the logic for displaying row data in the template and positioning the controls in the template.
Each row in a DataView has a style assigned to it. The default style is default
and that is the only style that is supported if you are using the data controller behavior script assigned to a DataView by default. If you create a custom data controller script then you can use style names of your choosing.
Each style needs to be mapped to a group serving as a row template. You map a row template group to a row style through the row style templates
property of a DataView. This property is an array whose keys are style names and values are a reference to a group control. Here is a simple example:
put the long id of group "MyRowTemplate" of stack "MyRowTemplateStack" \
into tStyleTemplatesA["default"]
set the viewProp["row style templates"] of group "MyDataView" to tStyleTemplatesA
After running the above code all rows in the "MyDataView" DataView would use the "MyRowTemplate" group as the row template.
If your DataView needs more than one row template than you can use multiple styles. For example:
put the long id of group "HeadingTemplate" of stack "MyRowTemplateStack" \
into tStyleTemplatesA["heading"]
put the long id of group "ParagraphTemplate" of stack "MyRowTemplateStack" \
into tStyleTemplatesA["paragraph"]
set the viewProp["row style templates"] of group "MoreComplexView" to tStyleTemplatesA
After running the above code you could assign either the heading
or paragraph
style to each row in the "MoreComplexView" DataView and the appropriate row template group would be used.
Row templates should be stored in the .app/templates
folder of a Levure project. Stacks in this folder are treated like a ui
stack in that they are not loaded into memory at startup but will be loaded when the stack name is referenced in code. What is different is that stacks in the ./app/template
folder will not be password protected which means their contents can be copied into a DataView in a standalone that is otherwise password protected.
Like the ui
folder, each folder in the templates
folder contains one or more LiveCode stacks as well as the supporting behavior script files. Here is an example of a ./app/templates/my-row-template/
folder that stores the behavior in a script only stack:
./app/templates/my-row-template/my-row-template.livecode
./app/templates/my-row-template/my-row-template-behavior.livecodescript
Each stack should have the following properties:
- The stack has one card.
- The card has one or more groups serving as a row template.
- Each group has a behavior assigned to it.
- If the behavior is stored in a script only stack then then assign the script only stack file to the
stackFiles
property of the stack the group is a part of.
InitializeTemplate
FillInData pDataA, pRow
LayoutControl pControlRect, pRow
HideRowControl
# if cache is none
CleanupAfterControl
ShowRowControl
EditKey pKey
PreOpenFieldEditor pEditor
pDirection: next/previous
OpenNextFieldEditor pRow, pKey, pDirection, pActionTriggeredBy
CloseFieldEditor pEditor, pRow, pKey, pEventTriggeredByEvent
ExitFieldEditor pEditor, pRow, pKey, pEventTriggeredByEvent
selectedRowChanged
DataViewDidUpdateView
HeightsForRows
swipeRight
swipeLeft
DragReorderRows pTargetRows, pEffectiveDroppedAfterRow, pActualDroppedAfterRow
PositionDropIndicator pBeingDroppedAfterRow
HideDropIndicator
ShowDropIndicator
A DataView doesn't have any internal knowledge of the data that it is displaying. Each time it displays a row it asks the outside world to provide the data for that row. When it needs to know how many total rows it should display it also asks the outside world. The code that provides that data can be thought of a data controller. The data controller script orchestrates moving data from a data source into a DataView and saving any changes made to data within the DataView back to the data source.
The DataView helper comes with a data controller script (dataview_controller.livecodescript
) that is assigned as a behavior of a new DataView. This data controller script allows you to assign a numerically indexed array of data to the dvData
property of the DataView. It will then handle feeding the data in that array to the DataView.
For more advanced cases you can remove this data controller script as the behavior and use your own data controller code. That code can reside in a different behavior script that you assign to the DataView or it might exist in another script in the message hierarchy – e.g. a group
that the DataView is in or in the card
or stack
script. Regardless of where your data controller code is located, you will need to handle one message and two functions. The message is DataForRow
and the functions are NumberOfRows()
and CacheKeyForRow()
. These handlers will be sent and called when you send the RenderView
command to the DataView.
Let's look at each in turn. In the example code for each handler assume that sData
is a numerically indexed array.
The DataForRow
message is sent whenever the DataView needs data to associate with a row. It takes three parameters:
DataForRow pRow, @rDataA, @rTemplateStyle
pRow
is the number of the row that you should provide data for.rDataA
is an array that you populate with the data needed to display the row.rTemplateStyle
is the style to associate with the row. If you don't provide a value thendefault
will be used.
Example 1:
command DataForRow pRow, @rDataA, @rTemplateStyle
put sDataA[pRow] into rDataA
put "default" into rTemplateStyle
end DataForRow
Example 2:
command DataForRow pRow, @rDataA, @rTemplateStyle
sqlquery_moveToRecord sQueryA, pRow
put sqlquery_currentRowToArray(sQueryA) into rDataA
put "default" into rTemplateStyle
end DataForRow
The NumberOfRows()
function must return the number of rows being displayed in the DataView.
Example 1:
function NumberOfRows
return the number of elements of sDataA
end NumberOfRows
Example 2:
function NumberOfRows
return sqlquery_get(sQueryA, "number of records")
end NumberOfRows
The CacheKeyForRow()
function must return a unique identifier for each row. If you don't define the CacheKeyForRow()
function in the message path then the DataView will use the row number to uniquely identify each row. If your DataView is displaying a flat list of data that cannot be reordered and that never toggles the visibility of rows then there is nothing further that needs to be done.
If, however, the row that data in your data source is associated with can change between calls to ResetView
then you must handle CacheKeyForRow()
and return a unique identifier for the row. For example, the primary key column from a database table will be adequate in most cases. If your DataView is displaying records from multiple tables then the primary key might not be sufficient as the primary keys from two different tables aren't necessarily unique.
CacheKeyForRow pRow
Example 1:
function CacheKeyForRow pRow
return pRow
end CacheKeyForRow
Example 2:
function CacheKeyForRow pRow
return sDataA[pRow]["id"]
end CacheKeyForRow
Example 3:
function CacheKeyForRow pRow
return revDatabaseColumnNamed(sCursorId, "id")
end CacheKeyForRow
If your DataView has rows that should not be selectable by the user then return false for the dvCanSelect
property of the row template. The following script can be added to a row template behavior:
getProp dvCanSelect
return false
end dvCanSelect
lazy
, eager
, none
- Dispatch
EditKeyOfRow
to the DataView - DataView dispatches
EditKey
to the row control. - Call
CreateFieldEditorForField
and pass in the field to edit in the row control.
Possible values for pEventThatTriggeredClose are close control
,
closeField
, exitField
, returnInField
, enterInField
, and tabKey
.
close control
is sent when the user scrolls the row that is being edited out of view
and caching is not turned on.
The DataView can animate selections of rows that are not currently in view. You need to set the viewProp["animate selections"]
property to true and have the animationEngine library in use.
The DataView has a built in API for drag reordering. To start a drag operation do the following:
- Define a
dragStart
handler in your instance of the DataView. - In the handler set
the dvDragImageRow of me
to the first row that is being dragged. In most cases you can set the property toitem 1 of the dvHilitedRows of me
of the DataView. - In the handler set
the dragData["private"]
to a string that contains the necessary information for the drop. For example, line 1 of the string might be an identifier such as "file nodes" and line 2 would bethe dvHilitedRows of me
. - In the handler set
the dvTrackDragReorder of me to true
At this point you will see visual feedback. A snapshot of the dvDragImageRow
will follow the mouse around and a drop indicator will show where the drop will occur.
Each time dragMove
is called, the DataView will calculate a proposed drop row and drop operation based on the position of the mouse. It will then dispatch ValidateRowDrop
to the DataView and pass the following parameters:
- pDraggingInfoA: Array with
action
(the value ofdragAction
),mouseH
, andmouseV
keys. ThemouseH
andmouseV
keys are the same parameters passed todragMove
. - pProposedRow: The proposed row that the drop should occur on based on the mouse vertical position.
- pProposedDropOperation: The proposed drop operation based on the mouse vertical position.
on
orabove
.
Note: If the drop will occur after the last row in the DataView then the proposed row will be the number of rows in the DataView + 1 and the proposed operation will be "above".
The ValidateRowDrop
handler can accept any of the above parameters by reference (parameter name prefixed with @
)
and modify them. Modifying the proposed row and drop operation can be done if needed in order to redirect a drop.
If the drop should not occur over the proposed row then return false
from ValidateRowDrop
.
When dragDrop
is called as a result of the user "dropping" a row on the DataView your instance of the DataView will be notified with the AcceptRowDrop
message. It will be sent the following parameters:
- pDraggingInfoA: Array with an
action
(thedragAction
). In addition, if keys were added to thepDraggingInfoA
array passed by reference toValidateRowDrop
the those keys will be present as well. - pRow: The row that the drop occurred on.
- pDropOperation: The drop operation.
on
orabove
.
This handler is where you write your application specific logic that reorders the data in the data source and refreshes the DataView.
When the dvDragImageRow
property is set a snapshot of the corresponding row control is taken and assigned to the dragImage
property. Three messages are sent to the row control which allow you to customize the the snapshot that is taken - PreDragImageSnapshot
, CreateDragImageSnapshot
, and PostDragImageSnapshot
.
PreDragImageSnapshot
: Make any customizations to the row control prior to the snapshot being taken. For example, you might hide controls in the row group control such as action menus and just leave a label field visible.CreateDragImageSnapshot pTargetSnapshotImageId
: If your code handles this message (meaning your code defines he handler and does not pass it) then it is your responsibility to export a snapshot to image idpTargetSnapshotImageId
. Example:export snapshot from the target to image id pTargetSnapshotImageId as PNG
. If your code doesn't handle this message then a snapshot of the row control will be exported for you.- In
PostDragImageSnapshot
you restore the row control to it's normal state.
These handlers will typically be defined in the row control behavior script or in the DataView instance script.
The drop indicator (the line that shows you where the drop will occur) is determined by the viewProp["drop indicator template"]
custom property of the DataView. This property can be assigned the long id of a control (it will be converted to a rugged id when stored). If the property is empty then the drop indicator that is included with the DataView is used.
If you define your own control to use as a drop indicator then it needs a script with the following handler:
command PositionDropIndicator pDraggingInfoA, pRow, pDropOperation
# Resize the control...
end PositionDropIndicator
Before this handler is called, the control will be have it's width resized to the width of the DataView.
The default drop indicator template is a group with a script similar to the following:
command PositionDropIndicator pDraggingInfoA, pRow, pDropOperation
local tViewRect, tRect
put the rect of me into tViewRect
put the rect of graphic 1 of me into tRect
put item 1 of tViewRect into item 1 of tRect
put item 3 of tViewRect + 1 into item 3 of tRect
set the rect of graphic 1 of me to tRect
end PositionDropIndicator
This is mainly for folks who aren't using the Levure framework as they are more likely to run into the situation described below. Levure makes sure that behaviors are loaded into memory before any stacks try to load them.
Behaviors are wonderful things. Except when they don't resolve properly. Then they are terrible things because they trick you into thinking they really did resolve properly. Here is what you need to know so you don't go down a troubleshooting rabbit hole and never come out.
When the LiveCode engine finds a control which has its behavior property set it tries to resolve the behavior reference in order to find the script. If, for any reason, the engine cannot find the script that is referenced by the behavior property then the engine will silently move along as if nothing bad has happened. But something bad has happened. The behavior script wasn't loaded so any functionality that the behavior script provided will not work. When might something like this happen?
If you are storing your behaviors in script only stacks or a control in a stack that is not part of your main application then this might happen to you.
A full-proof solution is to add each stack file that contains a behavior script to the stackfiles
property of your main application stack. You can do this using the stack property inspector. By doing this the engine will always be able to track down the behavior when your application stack opens.
Be aware that if you use dvIdeCreateRowTemplates
to create row templates that a behavior will be stored in a script only stack. While this handler will assign the script only stack file to the stackfiles
property of the stack that contains the row template, the script only stack file will not be assigned to the stackfiles
property of your application stack.
For example, if your application stack 1) has a DataView in it and 2) you save the stack with row template groups loaded into the DataView then you must assign the row template behavior script only stack files to the stackfiles
of your application stack. Otherwise when your application stack is loaded by the LiveCode engine it sees the row template groups, tries to resolve the behaviors assigned to them, can't find them, and you start wondering why your DataView is no longer functioning properly.