= Data Table Interactive

:Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at. http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

.Developer Note
****
This article is a _Developer Note_.

_Developer Notes_ don't claim to be complete or fully accurate, hence are not necessarily published at the project's main documentation site.
Their purpose is rather to document diagrams and design decisions close to the relevant code. 
And also for code authors to quickly get an overview or refresher on the topic.  
****

`DataTableInteractive` is an interactive tabular data model where each row corresponds to a `ManagedObject`,
which represents either a domain object or value object.

[plantuml,fig-DataTableInteractive-1,svg]
.Data Table Interactive (simplified diagram)
----
@startuml

class DataTableInteractive {
    managedMember: ManagedMember
    where: Where
    title: Observable<String>
    dataElements: Can<ManagedObject>
    searchArgument: Bindable<String>
	selectAllToggle: Bindable<Boolean>
}

class ColumnSort {
	columnIndex: int
}

enum SortDirection {
	ASCENDING
	DESCENDING
}

class DataColumn {
    columnId: String 
    associationMetaModel: ObjectAssociation
    columnFriendlyName: Observable<String>
    columnDescription: Observable<Optional<String>>
}

class DataRow {
	rowElement: ManagedObject
	selectToggle: Bindable<Boolean> 
}

DataTableInteractive ..> "*" DataColumn : "dataColumns²"
DataTableInteractive .> "*" DataRow : "dataRowsVisible²\ndataRowsFilteredAndSorted²\ndataRowsSelected²"
DataTableInteractive <- DataRow : "parentTable"

DataTableInteractive ..> ColumnSort : "columnSort¹"
ColumnSort --> SortDirection : "sortDirection"

@enduml
----

<.> Bindable
<.> Observable

== Initial Fetch and Follow-Ups

Elements of a `DataTableInteracive` originate either 
from an _Action_ *invocation* 
or the *execution* of an _OneToManyAssociation_. 
Those elements are 'live' during the request cycle that fetches them.

Any follow-up *selection*, *sorting* or *filtering* on that table (via end-user interaction)
will result in partial page updates (AJAX) which happen in their own request-cycle.

Consequently elements that were 'live' previously are now hollow. 
Hence, if the framework were to act on some of those elements, 
like feeding a selection into an associated action's `ParameterNegotiationModel`, 
those elements would need to be re-hydrated for the action invocation. 

== Search Argument Interaction (Filtering)

The model supports filtering of data rows based on a given *search argument*.

[plantuml,fig-DataTableInteractive-2,svg]
.Search Argument Interaction (Filtering)
----
@startuml

:UI **search argument** change
triggers partial page update;

:update table **memento**
with new search argument; 

:tabular data model gets **recreated** from memento;
:client's DOM gets updated and table **re-rendered**;

@enduml
----

== Data Row Select Toggle Interaction (Selection)

The model supports selection of data rows.

[plantuml,fig-DataTableInteractive-3,svg]
.Data Row Select Toggle Interaction (Selection)
----
@startuml

:UI **row select toggle**
triggers partial page update;

:update table **memento**
with snapshot of 
currently selected rows;

:tabular data model gets **recreated** from memento;
:client's DOM gets updated and table **re-rendered**;

@enduml
----

== Data Column Sort Interaction (Sorting)

The model supports sorting by single columns.

[plantuml,fig-DataTableInteractive-4,svg]
.Data Column Sort Interaction (Sorting)
----
@startuml

:UI **column sort select toggle**
triggers partial page update;

:update table **memento**
with new ColumnSort;

:tabular data model gets **recreated** from memento;
:client's DOM gets updated and table **re-rendered**;

@enduml
----

== Table Serialization

[plantuml,fig-DataTableInteractive-5,svg]
.Table Memento
----
@startuml

class Memento {
	featureId: Identifier
	where: Where
	dataTable: DataTable
	searchArgument: String
	selectedRowIndexes: Set<Integer>
}

@enduml
----

For each collection rendered, we create an immutable `Can<ManagedObject>` 
of *elements*. If those are entities, these must be live (*attached*). 
If those are viewmodels, these must be *refreshed* to not reference any hollow entities. 

Lifecycle of this `Can` is for exactly one request-cycle. (This is to avoid any hollow entity state issues.)

*Selection*, *sorting* and *filtering* work on top of this immutable `Can`. 
Which are all subject to partial page updates (AJAX) and happen in their own request-cycle.

Consequently it should be sufficient to serialize the table's state by memoizing the

* *elements* as some equivalent of `List<Bookmark>`
* *elements selected* as some equivalent of `Set<Integer>` that represents indexes into the elements (above)
* *searchArgument* as `String` used for the new table filtering SPI
* *columnSort* as `ColumnSort` introduced to capture by which column to sort and in what direction/order

== Performance Optimization

If for a given collection of elements, all information required for 

* rendering of cells and tooltips (text, html) 
* sorting of rows (list of int to calculate for each column)
* filtering of rows (generate search tokens for each row using SPI)

is gathered up-front during the initial fetch phase (while all elements are 'live'), 
then *sorting*, *filtering* and *selection* 
can be done without refreshing or re-hydrating any elements.

In other words: we could do the information gathering required for *sorting* and *filtering* 
during the first full page request-cycle, 
where all elements are 'live' naturally. Such that we don't have to do it again later for the partial page updates.

