feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run

- Base: wintercms/winter branch 1.2 (full framework)
- Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS
- Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication
- Partials: hero (offline-first), features, modes (offline/nube toggle),
  screenshots, pricing (3 planes), comparison, FAQ, CTA
- Plugin VivesPOS.Site with ContactForm
- Dockerfile: PHP 8.2 Apache, port 80, healthcheck
- Added winter/wn-pages, blog, sitemap, seo plugins
- Active theme set to vivespos
This commit is contained in:
2026-08-21 19:29:00 -06:00
commit 1f72193a64
3266 changed files with 531480 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
<?php namespace Backend\Widgets\Table;
/**
* The client-memory data source for the Table widget.
*/
class ClientMemoryDataSource extends DataSourceBase
{
/**
* @var array Keeps the data source data.
*/
protected $data = [];
/**
* Initializes records in the data source.
* The method doesn't replace existing records and
* could be called multiple times in order to fill
* the data source.
* @param array $records Records to initialize in the data source.
*/
public function initRecords($records)
{
$this->data = array_merge($this->data, $records);
}
/**
* Returns a total number of records in the data source.
* @return integer
*/
public function getCount()
{
return count($this->data);
}
/**
* Removes all records from the data source.
*/
public function purge()
{
$this->data = [];
}
/**
* Return records from the data source.
* @param integer $offset Specifies the offset of the first record to return, zero-based.
* @param integer $count Specifies the number of records to return.
* @return array Returns the records.
* If there are no more records, returns an empty array.
*/
public function getRecords($offset, $count)
{
return array_slice($this->data, $offset, $count);
}
/**
* Returns all records in the data source.
* This method is specific only for the client memory data sources.
*/
public function getAllRecords()
{
return $this->data;
}
}

View File

@@ -0,0 +1,86 @@
<?php namespace Backend\Widgets\Table;
/**
* Base class for the Table widget data sources.
*/
abstract class DataSourceBase
{
/**
* @var string Specifies a name of record's key column
*/
protected $keyColumn;
/**
* @var integer Internal record offset
*/
protected $offset = 0;
/**
* Class constructor.
* @param string $keyColumn Specifies a name of the key column.
*/
public function construct($keyColumn = 'id')
{
$this->keyColumn = $keyColumn;
}
/**
* Initializes records in the data source.
* The method doesn't replace existing records and
* could be called multiple times in order to fill
* the data source.
* @param array $records Records to initialize in the data source.
*/
abstract public function initRecords($records);
/**
* Returns a total number of records in the data source.
* @return integer
*/
abstract public function getCount();
/**
* Removes all records from the data source.
*/
abstract public function purge();
/**
* Return records from the data source.
* @param integer $offset Specifies the offset of the first record to return, zero-based.
* @param integer $count Specifies the number of records to return.
* @return array Returns the records.
* If there are no more records, returns an empty array.
*/
abstract public function getRecords($offset, $count);
/**
* Identical to getRecords except provided with a search query.
*/
public function searchRecords($query, $offset, $count)
{
return $this->getRecords($offset, $count);
}
/**
* Rewinds the the data source to the first record.
* Use this method with the readRecords() method.
*/
public function reset()
{
$this->offset = 0;
}
/**
* Returns a set of records from the data source.
* @param integer $count Specifies the number of records to return.
* @return array Returns the records.
* If there are no more records, returns an empty array.
*/
public function readRecords($count = 10)
{
$result = $this->getRecords($this->offset, $count);
$this->offset += count($result);
return $result;
}
}

View File

@@ -0,0 +1,412 @@
# Client-side table widget (table.xxx.js)
## Code organization
### OOP pattern
The data source and cell processor JavaScript classes use the simple parasitic combination inheritance pattern described here:
- http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript
// Parent class with a method
var SuperClass = function(params) {}
SuperClass.prototype.someMethod = function() {}
// Child class
var SubClass = function(params) {
// Call the parent constructor
SuperClass.call(this, params)
}
SubClass.prototype = Object.create(SuperClass.prototype)
SubClass.prototype.constructor = SubClass
// Child class methods can be defined only after the prototype
// is updated in the two previous lines
SubClass.prototype.someMethod = function() {
// Call the parent method
SuperClass.prototype.someMethod.call(this)
};
### Namespaces
All classes for the table widget are be defined in the **$.wn.table** namespace. There are several namespaces in this namespace:
- **$.wn.table.processor** - cell processors
- **$.wn.table.datasource** - data sources
- **$.wn.table.helper** - helper classes
- **$.wn.table.validator** - validation classes
### Client-side performance and memory usage considerations
The classes defined for the Table widget should follow the best practices in order to achieve the high performance and avoid memory leaks:
* All references to JavaScript objects and DOM elements should be cleared with the `dispose()` methods.
* All event handlers registered in the control should be unregistered with the `dispose()` method.
* DOM manipulations should only be performed in the detached tree with the `DocumentFragment` objects.
* The number of registered event handlers should be kept as low as possible. Cell processors should rely to delegated events registered for the table.
* Cell processors should have the `dispose()` method that unregisters the event handlers and does all required cleanup actions.
* Do not use closures for event handlers. This gives more control over the variable scope and simplifies the cleanup operations.
* If closures are used for anything, use named closures to simplify the profiling process with Chrome dev tools.
There are several articles that provide a good insight into the high performance JavaScript code and efficient memory management:
* http://www.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
* http://www.toptal.com/javascript/javascript-prototypes-scopes-and-performance-what-you-need-to-know
* http://developer.nokia.com/community/wiki/JavaScript_Performance_Best_Practices (the suggestion about the code comments is doubtful as JS engines compile code now).
## Widget usage
Any `DIV` elements that have the `data-control="table"` attributes are automatically picked up by the control.
```
<div data=control="table" data-columns="{...}"></div>
```
### Options
The options below are listed in the JavaScript notation. Corresponding data attributes would look like `data-client-data-source-class`.
- `clientDataSourceСlass` (default is **client**)- specifies the client-side data source class. There are two data source classes supported on the client side - **client** and **server**.
- `data` - specifies the data in JSON format for the **client**.
- `recordsPerPage` - specifies how many records per page to display. If the value is not defined or `false` or `null`, the pagination feature is disabled and all records are displayed. Pagination and `rowSorting` cannot be used in the same time.
- `columns` - column definitions in JSON format, see the server-side column definition format below.
- `rowSorting` - enables the drag & drop row sorting. The sorting cannot be used with the pagination (`recordsPerPage` is not `null` or `false`).
- `keyColumn` - specifies the name of the key column. The default value is **id**.
- `postback` - post the client-memory data source data to the server automatically when the parent form gets submitted. The default value is `true`. The option is used only with client-memory data sources. When enabled, the data source data is available in the widget's server-side data source: `$table->getDataSource()->getRecords();` The data postback occurs only of the request handler name matches the `postbackHandlerName` option value.
- `postbackHandlerName` - comma seperated list of AJAX data handler names for the automatic data postback. The data will be posted only when the AJAX request posts data matching one of this handler names. The default value is **onSave**.
- `adding` - determines whether users can add new records. Default value is **true**.
- `deleting` - determines whether users can delete records. Default value is **true**.
- `toolbar` - determines whether the toolbar is visible. The default value is **true**.
- `height` - specifies the maximum height of the data table (not including the header, toolbar and pagination). If the table contains more rows than the height could fit, the data table becomes scrollable. The default value is **false** (height is not limited).
## Client-side helper classes
Some auxiliary code is factored out from the table class to helper classes. The helper classes are defined in the **$.wn.table.helper** namespace.
- **table.helper.navigation.js** - implements the keyboard navigation within the table and pagination.
## Data sources ($.wn.table.datasource)
### Adding and removing records
Adding and removing records is an asynchronous process that involves updating records in the dataset.
When a user adds a record, the table object calls the `addRecord(data, offset, count, onSuccess)` method of the data source. The data source adds an empty record to the underlying data set and calls the `onSuccess` callback parameter passed to the method. In the `onSuccess` handler the table object rebuilds the table and focuses a field in the new row.
When user deletes a record, the table object calls the `deleteRecord(index, offset, count, onSuccess)` method of the data source. The data source removes the record from the underlying dataset and calls the `onSuccess` callback parameter, passing records of the current page (determined with the `offset` and `count` parameters) to the callback.
The `onSuccess` callback parameters are: data (records), count.
### Client memory data source ($.wn.table.datasource.client)
The client memory data sources keeps the data in the client memory. The data is loaded from the control element's `data` property (`data-data` attribute) and posted back with the form data.
### Server memory data source ($.wn.table.datasource.server)
**TODO:** document this
## Cell processors ($.wn.table.processor)
Cell processors are responsible for rendering the cell content, creating the cell data editors and updating the cell value in the grid control. There is a single cell processor per the table column. All rows in a specific column are handled with a same cell processor.
Cell processors should use the table's `setCellValue()` method to update the value in the table. The table class, in turn, will commit the changes to the data source when the user navigates to another row, on the pagination event, search or form submit. The `setCellValue()` should be the only way to update the table data by cell processors.
Cell processors should register delegated events to detect user's interaction with the cells they responsible for. The processors should unregister any event handlers in the `dispose()` method. The even handlers should be registered for the widgwet's top element, not for the table, as the table could be rebuilt completely on pagination, search, and other cases. The `click` and `keydown` events are dispatched to cell processors by the table class automatically and don't require extra handlers.
### Removing editors from the table
The table keeps a reference to the currently active cell processor. The cell processor objects have the `activeCell` property that is a reference to the cell which currently has an editor. The table calls the `hideEditor()` method of the active cell processor and the cell processor removes the editor from the active cell.
### Showing editors
The table object calls the `onFocus()` method of the cell processors when a cell is clicked or navigated (with the keyboard). The cell processor can build a cell editor when this method is called, if it's required.
### Drop-down cell processor
The drop-down column type can load options from the column configuration or with AJAX. Example column configuration:
color:
title: Color
type: dropdown
options:
red: Red
green: Green
blue: Blue
width: 15%
If the `options` element is not presented in the configuration, the options will be loaded with AJAX.
**TODO:** Document the AJAX interface
The drop-down options could depend on other columns. This works only with AJAX-based drop-downs. The column a drop-down depends on are defined with the `dependsOn` property:
state:
title: State
type: dropdown
dependsOn: country
Multiple fields are allowed as well:
state:
title: State
type: dropdown
dependsOn: [country, language]
**Note:** Dependent drop-down should always be defined after their master columns.
### Autocomplete cell processor
The autocomplete column type can load options from the column configuration or with AJAX. Example column configuration:
color:
title: Color
type: autocomplete
options:
red: Red
green: Green
blue: Blue
If the `options` element is not presented in the configuration, the options will be loaded with AJAX.
**TODO:** Document the AJAX interface
The editor can have the `dependsOn` property similar to the drop-down editor.
# Server-side table widget (Backend\Widgets\Table)
## Configuration
The widget is configured with YAML file. Required parameters:
* `columns` - the columns definitions, see below.
* `dataSource` - The data source class. Should specify the full qualified data source class name or alias. See the data source aliases below.
* `keyFrom` - name of the key column. The default value is **id**.
* `recordsPerPage` - number of records per page. If not specified, the pagination will be disabled.
* `postbackHandlerName` - comma seperated list of AJAX data handler names for the automatic data postback. The data will be posted only when the AJAX requests posts data to one of this handlers. The default value is **onSave**. This parameter is applicable only with client-memory data sources.
* `adding` - indicates if record deleting is allowed, default is **true**.
* `deleting` - indicates if record deleting is allowed, default is **true**.
* `toolbar` - specifies if the toolbar should be visible, default is **true**.
* `height` - specifies the data table height, in pixels. The default value is **false** - the height is not limited.
* `dynamicHeight` - determines if the `height` parameter should work as a max height. When this option is enabled and the table height is less than the `height` value, it won't be scrollable.
The `dataSource` parameter can take aliases for some data source classes for the simpler configuration syntax. Known aliases are:
* `client` = \Backend\Widgets\Table\ClientMemoryDataSource
### Column definitions
Columns are defined as array with the `columns` property. The array keys correspond the column identifiers. The array elements are associative arrays with the following keys:
- `title`
- `type` (string, checkbox, dropdown, autocomplete)
- `width` - sets the column width, can be specified in percents (10%) or pixels (50px). There could be a single column without width specified, it will be stretched to take the available space.
- `readOnly` - prevents the cell value from being modified. Default: false.
- `options` (for drop-down elements and autocomplete types)
- `dependsOn` (from drop-down elements)
- validation - defines the column client-side validation rules. See the **Client-side validation** section below.
## Events
### table.getDropdownOptions
table.getDropdownOptions - triggered when drop-down options are requested by the client. Parameters:
- `$columnName` - specifies the drop-down column name.
- `$rowData` - an array containing values of all columns in the table row.
Example event handler:
```
$table->bindEvent('table.getDropdownOptions',
function ($columnName, $rowData) {
if ($columnName == 'state')
return ['ca'=>'California', 'wa'=>'Washington'];
...
}
);
```
## Initializing the data
After the table widget is created, its data source optionally could be filled with records. Example code:
```
$table = new Table($this, $config);
$dataSource = $table->getDataSource();
$records = [
['id'=>1, 'first_name'=>'John', 'last_name'=>'Smith'],
['id'=>2, 'last_name'=>'John', 'last_name'=>'Doe']
];
$dataSource->initRecords($records);
```
Note that initializing records in the data source is required only once, when the widget object is first created and not required on the subsequent AJAX calls.
The `initRecords()` method can be called multiple times. Each call adds records to the data source and doesn't replace the existing records.
## Emptying the data source
The `purge` method removes all records from the data source. This method should always be used with server memory data sources. Nonetheless, server side data sources should take care about providing the automatic ways of cleaning data with using the time-to-live mechanisms.
```
$table = new Table($this, $config);
$dataSource = $table->getDataSource();
$dataSource->purge();
```
## Reading data from the data source
The server-side data sources (PHP) automatically maintain the actual data, but that mechanism for the client-memory and server-memory data sources is different.
In case of the client-memory data source, the table widget adds the data records to the POST, when the form is saved using the AJAX Framework (see `postback` and `postbackHandlerName` options). The table data will be injected automatically to the AJAX request when the `postback` value is `true` and the `postbackHandlerName` matches the exact handler name of the request. On the server side the data is inserted to the data source and can be accessed using the PHP example below.
The server-memory data source always automatically maintain its contents in synch with the client using AJAX, and POSTing data is not required.
In PHP reading data from a data source of any type looks like this (it should be in the AJAX handler that saves the data, for the client-memory data source the handler name should match the `postbackHandlerName` option value):
```
public function onSave()
{
// Assuming that the widget was initialized in the
// controller constructor with the "table" alias.
$dataSource = $this->widget->table->getDataSource();
while ($records = $dataSource->readRecords(5)) {
traceLog($records);
}
```
## Validation
There are two ways to validate the table data - with the client-side and server-side validation.
### Client-side validation ($.wn.table.validator)
The client-side validation is performed before the data is sent to the server, or before the user navigates to another page (if the pagination is enabled). Client-side validation is a fast, but simple validation implementation. It can't be used for complex cases like finding duplicating records, or comparing data with records existing in the database.
The client-side validation is configured in the widget configuration file in the column definition with the `validation` key. Example:
state:
title: State
type: dropdown
validation:
required:
message: Please select the state
requiredWith: country
If a validator doesn't have any options (or default option values should be used), the declaration could look like this:
state:
title: State
type: dropdown
validation:
required: {}
The `requiredWith` and `message` parameters are common for all validators.
Currently implemented client-side validation rules:
- required
- integer
- float
- length
- regex
Validation rules can be configured with extra parameters, which depend on a specific validator.
#### required validator ($.wn.table.validator.required)
Checks if the user has provided a value for the cell.
#### integer validator ($.wn.table.validator.integer)
Checks if the value is integer. Parameters:
* `allowNegative` - optional, determines if negative values are allowed.
* `min` - optional object, defines the minimum allowed value and error message. Object fields:
* `value` - defines the minimum value.
* `message` - optional, defines the error message.
* `max` - optional object, defines the maximum allowed value and error message. Object fields:
* `value` - defines the maximum value.
* `message` - optional, defines the error message.
Example of defining the integer validator with the `min` parameter:
length:
title: Length
type: string
validation:
integer:
min:
value: 3
message: "The length cannot be less than 3"
#### float validator ($.wn.table.validator.float)
Checks if the value is a floating point number. The parameters for this validator match the parameters of the **integer** validator.
Valid floating point number formats:
* 10
* 10.302
* -10 (if `allowNegative` is `true`)
* -10.84 (if `allowNegative` is `true`)
#### length validator ($.wn.table.validator.length)
Checks if a string is not shorter or longer than specified values. Parameters:
* `min` - optional object, defines the minimum length and error message. Object fields:
* `value` - defines the minimum length.
* `message` - optional, defines the error message.
* `max` - optional object, defines the maximum length and error message. Object fields:
* `value` - defines the maximum length.
* `message` - optional, defines the error message.
Example column definition:
name:
title: Name
type: string
validation:
length:
min:
value: 3
message: "The name is too short."
#### regex validator ($.wn.table.validator.regex)
Checks a string against a provided regular expression:
* `pattern` - specifies the regular expression pattern string. Example: **^[0-9a-z]+$**
* `modifiers` - optional, a string containing regular expression modifiers (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/RegExp), for example **i** for "case insensitive".
Example:
login:
title: Login
type: string
validation:
regex:
pattern: "^[a-z0-9]+$"
modifiers: "i"
message: "The login name can contain only Latin letters and numbers."
Although the `message` parameter is optional for all validators it's highly recommended to provide a message for the regular expression validator as the default message "Invalid value format." is not descriptive and can be confusing.
### Server-side validation
TODO: document.
Draft. In case of a validation error the AJAX response should contain the following information:
- row key
- row offset in the data set
- error message

View File

@@ -0,0 +1,91 @@
<?php namespace Backend\Widgets\Table;
/**
* The server-event data source for the Table widget.
*/
class ServerEventDataSource extends DataSourceBase
{
use \Winter\Storm\Support\Traits\Emitter;
/**
* Return records from the data source.
* @param integer $offset Specifies the offset of the first record to return, zero-based.
* @param integer $count Specifies the number of records to return.
* @return array Returns the records.
* If there are no more records, returns an empty array.
*/
public function getRecords($offset, $count)
{
return $this->fireEvent('data.getRecords', [$offset, $count], true);
}
/**
* Identical to getRecords except provided with a search query.
*/
public function searchRecords($query, $offset, $count)
{
return $this->fireEvent('data.searchRecords', [$query, $offset, $count], true);
}
/**
* Returns a total number of records in the data source.
* @return integer
*/
public function getCount()
{
return $this->fireEvent('data.getCount', [], true);
}
/**
* Updates a record in the data source.
* @return void
*/
public function createRecord($data, $placement, $relativeToKey)
{
return $this->fireEvent('data.createRecord', [$data, $placement, $relativeToKey]);
}
/**
* Updates a record in the data source.
* @return void
*/
public function updateRecord($key, $data)
{
$this->fireEvent('data.updateRecord', [$key, $data]);
}
/**
* Removes a record from the data source.
* @return array Returns the remaining records.
*/
public function deleteRecord($key)
{
return $this->fireEvent('data.deleteRecord', [$key], true);
}
/**
* Initializes records in the data source.
* The method doesn't replace existing records and
* could be called multiple times in order to fill
* the data source.
* @param array $records Records to initialize in the data source.
*/
public function initRecords($records)
{
}
/**
* Removes all records from the data source.
*/
public function purge()
{
}
/**
* Returns all records in the data source.
* This method is specific only for the client memory data sources.
*/
public function getAllRecords()
{
}
}

View File

@@ -0,0 +1,77 @@
.control-table .table-container{border:1px solid #e0e0e0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;overflow:hidden;margin-bottom:15px}
.control-table .table-container:last-child{margin-bottom:0}
.control-table:not([data-records-per-page="false"]) .table-container{border-bottom-right-radius:0;border-bottom-left-radius:0}
.control-table.active .table-container{border-color:#e0e0e0}
.control-table table{width:100%;border-collapse:collapse;table-layout:fixed}
.control-table table td,
.control-table table th{padding:0;font-size:13px;color:#555}
.control-table table [data-view-container]{padding:5px 10px;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:28px}
.control-table table.headers:after{content:' ';display:block;position:absolute;left:1px;right:1px;margin-top:-1px;border-bottom:1px solid #e0e0e0}
.control-table table.headers th{padding:7px 10px;font-weight:normal;text-transform:uppercase;font-size:11px;color:#333;background:white;border-right:1px solid #ecf0f1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.control-table table.headers th [data-view-container]{padding-bottom:6px}
.control-table table.headers th:last-child{border-right:none}
.control-table.active table.headers:after{border-bottom-color:#e0e0e0}
.control-table table.data td{border:1px solid #ecf0f1}
.control-table table.data td .content-container{position:relative;padding:1px;outline:none}
.control-table table.data td .content-container.readonly{background:#f7f7f7}
.control-table table.data td.active{border-color:#103141 !important}
.control-table table.data td.active .content-container{padding:0;border:1px solid #103141}
.control-table table.data td.active .content-container:before,
.control-table table.data td.active .content-container:after{content:' ';background:#103141;position:absolute;left:-2px;top:-2px}
.control-table table.data td.active .content-container:before{width:1px;bottom:-2px}
.control-table table.data td.active .content-container:after{right:-2px;height:1px}
.control-table table.data tr{background-color:#fff}
.control-table table.data tr.error{background-color:#fbecec !important}
.control-table table.data tr.error td.active.error{border-color:#ec0000 !important}
.control-table table.data tr.error td.active.error .content-container{border-color:#ec0000 !important}
.control-table table.data tr.error td.active.error .content-container:before,
.control-table table.data tr.error td.active.error .content-container:after{background-color:#ec0000 !important}
.control-table table.data tr:nth-child(2n){background-color:#fafafa}
.control-table table.data tr:first-child td{border-top:none}
.control-table table.data tr:last-child td{border-bottom:none}
.control-table table.data td:first-child{border-left:none}
.control-table table.data td:last-child{border-right:none}
.control-table .control-scrollbar>div{border-bottom-right-radius:4px;border-bottom-left-radius:4px;overflow:hidden}
.control-table .control-scrollbar table.data tr:last-child td{border-bottom:1px solid #ecf0f1}
.control-table .toolbar{background:white;border-bottom:1px solid #e0e0e0}
.control-table .toolbar:before,
.control-table .toolbar:after{content:" ";display:table}
.control-table .toolbar:after{clear:both}
.control-table .toolbar a.btn{color:#323e50;padding:8px 10px;opacity:0.5;filter:alpha(opacity=50);-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none}
.control-table .toolbar a.btn:hover{opacity:1;filter:alpha(opacity=100)}
.control-table .toolbar .table-search{float:right;margin:3px 3px 3px 0}
.control-table .toolbar .table-search .table-search-input{height:auto;padding:5px 13px 5px}
.control-table .toolbar a.table-icon:before{display:inline-block;content:' ';width:16px;height:16px;margin-right:8px;position:relative;top:3px;background:transparent url(../images/table-icons.gif) no-repeat 0 0;background-size:32px auto}
.control-table .toolbar a.table-icon.add-table-row-above:before{background-position:0 -56px}
.control-table .toolbar a.table-icon.delete-table-row:before{background-position:0 -113px}
.control-table.active .toolbar{border-bottom-color:#e0e0e0}
.control-table .pagination{margin:-15px 0 15px;padding:7px 10px;background:white;border:1px solid #e0e0e0;border-top:none;border-bottom-right-radius:4px;border-bottom-left-radius:4px}
.control-table .pagination ul{padding:0;margin:0}
.control-table .pagination ul li{list-style:none;display:inline-block;margin-right:5px;font-size:12px;line-height:100%}
.control-table .pagination ul li a{display:inline-block;text-decoration:none;color:#95a5a6;padding:4px 6px;background:#ecf0f1;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;outline:none;line-height:100%}
.control-table .pagination ul li a:focus-visible{outline:auto}
.control-table .pagination ul li.active a{background:#6cc551;color:#fff}
@media only screen and (-moz-min-device-pixel-ratio:1.5),only screen and (-o-min-device-pixel-ratio:1.5),only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min-devicepixel-ratio:1.5),only screen and (min-resolution:1.5dppx){.control-table .toolbar a:before{background-position:0 -9px;background-size:16px auto}.control-table .toolbar a.add-table-row-above:before{background-position:0 -39px}.control-table .toolbar a.delete-table-row:before{background-position:0 -66px}}.control-table td[data-column-type=string] input[type=text],
.control-table td[data-column-type=autocomplete] input[type=text]{width:100%;height:100%;display:block;outline:none;border:none;padding:6px 10px 7px}
html.chrome .control-table td[data-column-type=string] input[type=text],
html.chrome .control-table td[data-column-type=autocomplete] input[type=text]{padding:6px 10px 7px!important}
html.safari .control-table td[data-column-type=string] input[type=text],
html.gecko .control-table td[data-column-type=string] input[type=text],
html.safari .control-table td[data-column-type=autocomplete] input[type=text],
html.gecko .control-table td[data-column-type=autocomplete] input[type=text]{padding:5px 10px 5px}
ul.table-widget-autocomplete{background:white;font-size:13px;margin-top:0;border:1px solid #808c8d;border-top:1px solid #ecf0f1;border-bottom-right-radius:4px;border-bottom-left-radius:4px}
ul.table-widget-autocomplete li a{padding:5px 10px}.control-table td[data-column-type=checkbox] div[data-checkbox-element]{width:16px;height:16px;border-radius:3px;background-color:#FFF;border:1px solid #999;margin:6px 5px 6px 10px;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.control-table td[data-column-type=checkbox] div[data-checkbox-element]:hover{border-color:#808080;color:#4d4d4d}
.control-table td[data-column-type=checkbox] div[data-checkbox-element].checked{border-width:2px}
.control-table td[data-column-type=checkbox] div[data-checkbox-element].checked:before{font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;content:"\f00c";font-size:10px;position:relative;left:1px;top:-4px}
.control-table td[data-column-type=checkbox] div[data-checkbox-element]:focus{border-color:#103141;outline:none}.control-table td[data-column-type=dropdown] .content-container:not(.readonly){-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.control-table td[data-column-type=dropdown] .content-container:not(.readonly) [data-view-container]{padding-right:20px;position:relative;cursor:pointer}
.control-table td[data-column-type=dropdown] .content-container:not(.readonly) [data-view-container]:after{font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;content:"\f107";font-size:13px;line-height:100%;color:#95a5a6;position:absolute;top:8px;right:10px}
.control-table td[data-column-type=dropdown] .content-container:not(.readonly) [data-view-container]:hover:after{color:#2da7c7}
.control-table td[data-column-type=dropdown] [data-dropdown-open=true]{background:white}
.control-table td[data-column-type=dropdown] [data-dropdown-open=true] [data-view-container]:after{font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;content:"\f106"}.widget-field.frameless .control-table .table-container{border-top:none;border-left:none;border-right:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
.widget-field.frameless .control-table .toolbar{background:transparent}html.cssanimations .control-table td[data-column-type=dropdown] [data-view-container].loading:after{background:url('../../../../../../modules/system/assets/ui/images/loader-transparent.svg') 50% 50%;background-size:15px 15px;position:absolute;width:15px;height:15px;top:6px;right:5px;content:' ';-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}
.table-control-dropdown-list{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:absolute;background:white;border:1px solid #e0e0e0;border-top:none;padding-top:1px;overflow:hidden;z-index:1000;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-bottom-right-radius:4px;border-bottom-left-radius:4px}
.table-control-dropdown-list ul{border-top:1px solid #ecf0f1;padding:0;margin:0;max-height:200px;overflow:auto}
.table-control-dropdown-list li{list-style:none;font-size:13px;color:#555;padding:5px 10px;cursor:pointer;outline:none}
.table-control-dropdown-list li:focus{background:#103141;color:white}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,773 @@
+function($){"use strict";if($.wn===undefined)$.wn={}
if($.oc===undefined)$.oc=$.wn
if($.wn.table===undefined)$.wn.table={}
var Table=function(element,options){this.el=element
this.$el=$(element)
this.options=options
this.disposed=false
this.dataSource=null
this.cellProcessors={}
this.activeCellProcessor=null
this.activeCell=null
this.tableContainer=null
this.dataTableContainer=null
this.editedRowKey=null
this.dataTable=null
this.headerTable=null
this.toolbar=null
this.clickHandler=this.onClick.bind(this)
this.keydownHandler=this.onKeydown.bind(this)
this.documentClickHandler=this.onDocumentClick.bind(this)
this.toolbarClickHandler=this.onToolbarClick.bind(this)
if(this.options.postback&&this.options.clientDataSourceClass=='client'){if(!this.options.postbackHandlerName.length){var formHandler=this.$el.closest('form').data('request')
this.options.postbackHandlerName=[formHandler||'onSave']}else if(typeof this.options.postbackHandlerName==='string'){this.options.postbackHandlerName=this.options.postbackHandlerName.split(',')}this.formSubmitHandler=this.onFormSubmit.bind(this)}this.navigation=null
this.search=null
this.recordsAddedOrDeleted=0
this.disposeBound=this.dispose.bind(this)
this.init()
$.wn.foundation.controlUtils.markDisposable(element)}
Table.prototype.init=function(){this.createDataSource()
this.initCellProcessors()
this.navigation=new $.wn.table.helper.navigation(this)
this.search=new $.wn.table.helper.search(this)
this.buildUi()
this.registerHandlers()}
Table.prototype.disposeCellProcessors=function(){for(var i=0,len=this.options.columns.length;i<len;i++){var column=this.options.columns[i].key
this.cellProcessors[column].dispose()
this.cellProcessors[column]=null}this.cellProcessors=null
this.activeCellProcessor=null}
Table.prototype.createDataSource=function(){var dataSourceClass=this.options.clientDataSourceClass
if($.wn.table.datasource===undefined||$.wn.table.datasource[dataSourceClass]==undefined)throw new Error('The table client-side data source class "'+dataSourceClass+'" is not '+'found in the $.wn.table.datasource namespace.')
this.dataSource=new $.wn.table.datasource[dataSourceClass](this)}
Table.prototype.registerHandlers=function(){this.el.addEventListener('click',this.clickHandler)
this.el.addEventListener('keydown',this.keydownHandler)
this.$el.one('dispose-control',this.disposeBound)
document.addEventListener('click',this.documentClickHandler)
if(this.options.postback&&this.options.clientDataSourceClass=='client')this.$el.closest('form').bind('oc.beforeRequest',this.formSubmitHandler)
var toolbar=this.getToolbar()
if(toolbar)toolbar.addEventListener('click',this.toolbarClickHandler);}
Table.prototype.unregisterHandlers=function(){this.el.removeEventListener('click',this.clickHandler);document.removeEventListener('click',this.documentClickHandler)
this.clickHandler=null
this.el.removeEventListener('keydown',this.keydownHandler);this.keydownHandler=null
var toolbar=this.getToolbar()
if(toolbar)toolbar.removeEventListener('click',this.toolbarClickHandler);this.toolbarClickHandler=null
if(this.formSubmitHandler){this.$el.closest('form').unbind('oc.beforeRequest',this.formSubmitHandler)
this.formSubmitHandler=null}}
Table.prototype.initCellProcessors=function(){for(var i=0,len=this.options.columns.length;i<len;i++){var columnConfiguration=this.options.columns[i],column=columnConfiguration.key,columnType=columnConfiguration.type
if(columnType===undefined){columnType='string'
this.options.columns[i].type=columnType}if($.wn.table.processor===undefined||$.wn.table.processor[columnType]==undefined)throw new Error('The table cell processor for the column type "'+columnType+'" is not '+'found in the $.wn.table.processor namespace.')
this.cellProcessors[column]=new $.wn.table.processor[columnType](this,column,columnConfiguration)}}
Table.prototype.getCellProcessor=function(columnName){return this.cellProcessors[columnName]}
Table.prototype.buildUi=function(){this.tableContainer=document.createElement('div')
this.tableContainer.setAttribute('class','table-container')
if(this.options.toolbar){this.buildToolbar()}this.tableContainer.appendChild(this.buildHeaderTable())
this.el.insertBefore(this.tableContainer,this.el.children[0])
if(!this.options.height){this.dataTableContainer=this.tableContainer}else{this.dataTableContainer=this.buildScrollbar()}this.updateDataTable()}
Table.prototype.buildToolbar=function(){if(!this.options.adding&&!this.options.deleting&&!this.options.searching){return}this.toolbar=$($('[data-table-toolbar]',this.el).html()).appendTo(this.tableContainer).get(0)
if(!this.options.adding){$('[data-cmd^="record-add"]',this.toolbar).remove()}else{if(this.navigation.paginationEnabled()||!this.options.rowSorting){$('[data-cmd=record-add-below], [data-cmd=record-add-above]',this.toolbar).remove()}else{$('[data-cmd=record-add]',this.toolbar).remove()}}if(!this.options.deleting){$('[data-cmd="record-delete"]',this.toolbar).remove()}}
Table.prototype.buildScrollbar=function(){var scrollbar=document.createElement('div'),scrollbarContent=document.createElement('div')
scrollbar.setAttribute('class','control-scrollbar')
if(this.options.dynamicHeight)scrollbar.setAttribute('style','max-height: '+this.options.height+'px')
else scrollbar.setAttribute('style','height: '+this.options.height+'px')
scrollbar.appendChild(scrollbarContent)
this.tableContainer.appendChild(scrollbar)
$(scrollbar).scrollbar({animation:false})
return scrollbarContent}
Table.prototype.buildHeaderTable=function(){var headersTable=document.createElement('table'),row=document.createElement('tr')
headersTable.className='headers'
headersTable.appendChild(row)
for(var i=0,len=this.options.columns.length;i<len;i++){var header=document.createElement('th')
if(this.options.columns[i].width)header.setAttribute('style','width: '+this.options.columns[i].width)
header.textContent!==undefined?header.textContent=this.options.columns[i].title:header.innerText=this.options.columns[i].title
row.appendChild(header)}this.headerTable=headersTable
return headersTable}
Table.prototype.updateDataTable=function(onSuccess){var self=this
this.unfocusTable()
this.fetchRecords(function onUpdateDataTableSuccess(records,totalCount){self.buildDataTable(records,totalCount)
if(onSuccess)onSuccess()
if(totalCount==0)self.addRecord('above',true)
self.$el.trigger('oc.tableUpdateData',[records,totalCount])
self=null})}
Table.prototype.updateColumnWidth=function(){var headerCells=this.headerTable.querySelectorAll('th'),dataCells=this.dataTable.querySelectorAll('tr:first-child td')
for(var i=0,len=headerCells.length;i<len;i++){if(dataCells[i])dataCells[i].setAttribute('style',headerCells[i].getAttribute('style'))}}
Table.prototype.buildDataTable=function(records,totalCount){var dataTable=document.createElement('table'),tbody=document.createElement('tbody'),keyColumn=this.options.keyColumn
dataTable.setAttribute('class','data')
for(var i=0,len=records.length;i<len;i++){var row=document.createElement('tr')
if(records[i][keyColumn]===undefined)throw new Error('The row attribute '+keyColumn+' is not set for the row #'+i);row.setAttribute('data-row',records[i][keyColumn])
for(var j=0,colsLen=this.options.columns.length;j<colsLen;j++){var cell=document.createElement('td'),dataContainer=document.createElement('input'),cellContentContainer=document.createElement('div'),column=this.options.columns[j],columnName=column.key,cellProcessor=this.getCellProcessor(columnName)
cell.setAttribute('data-column',columnName)
cell.setAttribute('data-column-type',column.type)
dataContainer.setAttribute('type','hidden')
dataContainer.setAttribute('data-container','data-container')
dataContainer.value=this.formatDataContainerValue(records[i][columnName])
cellContentContainer.setAttribute('class','content-container')
cell.appendChild(cellContentContainer)
row.appendChild(cell)
cell.appendChild(dataContainer)
cellProcessor.renderCell(records[i][columnName],cellContentContainer)}tbody.appendChild(row)}dataTable.appendChild(tbody)
if(this.dataTable!==null)this.dataTableContainer.replaceChild(dataTable,this.dataTable)
else this.dataTableContainer.appendChild(dataTable)
this.dataTable=dataTable
this.updateColumnWidth()
this.updateScrollbar()
this.navigation.buildPagination(totalCount)
this.search.buildSearchForm()}
Table.prototype.formatDataContainerValue=function(value){if(value===undefined){return''}if(typeof value==='boolean'){return value?1:''}return value}
Table.prototype.fetchRecords=function(onSuccess){if(this.search.hasQuery()){this.dataSource.searchRecords(this.search.getQuery(),this.navigation.getPageFirstRowOffset(),this.options.recordsPerPage,onSuccess)}else{this.dataSource.getRecords(this.navigation.getPageFirstRowOffset(),this.options.recordsPerPage,onSuccess)}}
Table.prototype.updateScrollbar=function(){if(!this.options.height)return
$(this.dataTableContainer.parentNode).data('oc.scrollbar').update()}
Table.prototype.scrollCellIntoView=function(){if(!this.options.height||!this.activeCell)return
$(this.dataTableContainer.parentNode).data('oc.scrollbar').gotoElement(this.activeCell)}
Table.prototype.disposeScrollbar=function(){if(!this.options.height)return
$(this.dataTableContainer.parentNode).data('oc.scrollbar').dispose()
$(this.dataTableContainer.parentNode).data('oc.scrollbar',null)}
Table.prototype.setActiveProcessor=function(processor){if(this.activeCellProcessor)this.activeCellProcessor.onUnfocus()
this.activeCellProcessor=processor}
Table.prototype.commitEditedRow=function(){if(this.editedRowKey===null)return
var editedRow=this.dataTable.querySelector('tr[data-row="'+this.editedRowKey+'"]')
if(!editedRow)return
if(editedRow.getAttribute('data-dirty')!=1)return
var cells=editedRow.children,data={}
for(var i=0,len=cells.length;i<len;i++){var cell=cells[i]
data[cell.getAttribute('data-column')]=this.getCellValue(cell)}this.dataSource.updateRecord(this.editedRowKey,data)
editedRow.setAttribute('data-dirty',0)}
Table.prototype.unfocusTable=function(){this.elementRemoveClass(this.el,'active')
if(this.activeCellProcessor)this.activeCellProcessor.onUnfocus()
this.commitEditedRow()
this.activeCellProcessor=null
if(this.activeCell)this.activeCell.setAttribute('class','')
this.activeCell=null}
Table.prototype.focusTable=function(){this.elementAddClass(this.el,'active')}
Table.prototype.focusCell=function(cellElement,isClick){var columnName=cellElement.getAttribute('data-column')
if(columnName===null)return
this.focusTable()
var processor=this.getCellProcessor(columnName)
if(!processor)throw new Error("Cell processor not found for the column "+columnName)
if(this.activeCell!==cellElement){if(this.activeCell)this.elementRemoveClass(this.activeCell,'active')
this.setActiveProcessor(processor)
this.activeCell=cellElement
if(processor.isCellFocusable())this.elementAddClass(this.activeCell,'active')}var rowKey=this.getCellRowKey(cellElement)
if(this.editedRowKey!==null&&rowKey!=this.editedRowKey)this.commitEditedRow()
this.editedRowKey=rowKey
processor.onFocus(cellElement,isClick)
this.scrollCellIntoView()}
Table.prototype.markCellRowDirty=function(cellElement){cellElement.parentNode.setAttribute('data-dirty',1)}
Table.prototype.addRecord=function(placement,noFocus){if(!this.activeCell||this.navigation.paginationEnabled()||!this.options.rowSorting)placement='bottom'
var relativeToKey=null,currentRowIndex=null
if(placement=='above'||placement=='below'){relativeToKey=this.getCellRowKey(this.activeCell)
currentRowIndex=this.getCellRowIndex(this.activeCell)}this.unfocusTable()
if(this.navigation.paginationEnabled()){var newPageIndex=this.navigation.getNewRowPage(placement,currentRowIndex)
if(newPageIndex!=this.navigation.pageIndex){if(!this.validate())return}this.navigation.pageIndex=newPageIndex}this.recordsAddedOrDeleted++
var keyColumn=this.options.keyColumn,recordData={},self=this
recordData[keyColumn]=-1*this.recordsAddedOrDeleted
this.$el.trigger('oc.tableNewRow',[recordData])
this.dataSource.createRecord(recordData,placement,relativeToKey,this.navigation.getPageFirstRowOffset(),this.options.recordsPerPage,function onAddRecordDataTableSuccess(records,totalCount){self.buildDataTable(records,totalCount)
var row=self.findRowByKey(recordData[keyColumn])
if(!row)throw new Error('New row is not found in the updated table: '+recordData[keyColumn])
if(!noFocus)self.navigation.focusCell(row,0)
self=null})}
Table.prototype.deleteRecord=function(){if(!this.activeCell)return
var currentRowIndex=this.getCellRowIndex(this.activeCell),key=this.getCellRowKey(this.activeCell),self=this,paginationEnabled=this.navigation.paginationEnabled(),currentPageIndex=this.navigation.pageIndex,currentCellIndex=this.activeCell.cellIndex
if(paginationEnabled)this.navigation.pageIndex=this.navigation.getPageAfterDeletion(currentRowIndex)
this.recordsAddedOrDeleted++
var keyColumn=this.options.keyColumn,newRecordData={}
newRecordData[keyColumn]=-1*this.recordsAddedOrDeleted
this.dataSource.deleteRecord(key,newRecordData,this.navigation.getPageFirstRowOffset(),this.options.recordsPerPage,function onDeleteRecordDataTableSuccess(records,totalCount){self.buildDataTable(records,totalCount)
if(!paginationEnabled)self.navigation.focusCellInReplacedRow(currentRowIndex,currentCellIndex)
else{if(currentPageIndex!=self.navigation.pageIndex)self.navigation.focusCell('bottom',currentCellIndex)
else self.navigation.focusCellInReplacedRow(currentRowIndex,currentCellIndex)}self=null})}
Table.prototype.notifyRowProcessorsOnChange=function(cellElement){var columnName=cellElement.getAttribute('data-column'),row=cellElement.parentNode
for(var i=0,len=row.children.length;i<len;i++){var column=this.options.columns[i].key
this.cellProcessors[column].onRowValueChanged(columnName,row.children[i])}}
Table.prototype.getToolbar=function(){return this.tableContainer.querySelector('div.toolbar')}
Table.prototype.validate=function(){var rows=this.dataTable.querySelectorAll('tbody tr[data-row]')
for(var i=0,len=rows.length;i<len;i++){var row=rows[i]
this.elementRemoveClass(row,'error')}for(var i=0,rowsLen=rows.length;i<rowsLen;i++){var row=rows[i],rowData=this.getRowData(row)
for(var j=0,colsLen=row.children.length;j<colsLen;j++)this.elementRemoveClass(row.children[j],'error')
for(var columnName in rowData){var cellProcessor=this.getCellProcessor(columnName),message=cellProcessor.validate(rowData[columnName],rowData)
if(message!==undefined){var cell=row.querySelector('td[data-column="'+columnName+'"]'),self=this
this.elementAddClass(row,'error')
this.elementAddClass(cell,'error')
$.wn.flashMsg({text:message,'class':'error'})
window.setTimeout(function(){self.focusCell(cell,false)
cell=null
self=null
cellProcessor=null},100)
return false}}}return true}
Table.prototype.onClick=function(ev){this.focusTable()
if(this.navigation.onClick(ev)===false)return
if(this.search.onClick(ev)===false)return
for(var i=0,len=this.options.columns.length;i<len;i++){var column=this.options.columns[i].key
this.cellProcessors[column].onClick(ev)}var target=this.getEventTarget(ev,'TD')
if(!target){this.unfocusTable();return;}if(target.tagName!='TD'){this.unfocusTable();return;}this.focusCell(target,true)}
Table.prototype.onKeydown=function(ev){if((ev.key==='a'||ev.key==='A')&&ev.altKey&&this.options.adding){if(!ev.shiftKey){this.addRecord('below')}else{this.addRecord('above')}this.stopEvent(ev)
return}if((ev.key==='d'||ev.key==='D')&&ev.altKey&&this.options.deleting){this.deleteRecord()
this.stopEvent(ev)
return}for(var i=0,len=this.options.columns.length;i<len;i++){var column=this.options.columns[i].key
if(this.cellProcessors[column].onKeyDown(ev)===false){return}}if(this.navigation.onKeydown(ev)===false){return}if(this.search.onKeydown(ev)===false){return}}
Table.prototype.onFormSubmit=function(ev,data){if(this.options.postbackHandlerName.indexOf(data.handler)>-1){this.unfocusTable()
if(!this.validate()){ev.preventDefault()
return}data.options.data[this.options.fieldName]=this.dataSource.getAllData()}}
Table.prototype.onToolbarClick=function(ev){var target=this.getEventTarget(ev),cmd=target.getAttribute('data-cmd')
if(!cmd){return}switch(cmd){case'record-add':case'record-add-below':this.addRecord('below')
break
case'record-add-above':this.addRecord('above')
break
case'record-delete':this.deleteRecord()
break}this.stopEvent(ev)}
Table.prototype.onDocumentClick=function(ev){var target=this.getEventTarget(ev)
if(this.parentContainsElement(this.el,target))return
if(this.activeCellProcessor&&this.activeCellProcessor.elementBelongsToProcessor(target))return
this.unfocusTable()}
Table.prototype.dispose=function(){if(this.disposed){return}this.disposed=true
this.disposeBound=true
this.unfocusTable()
this.dataSource.dispose()
this.dataSource=null
this.unregisterHandlers()
this.dataTable=null
this.headerTable=null
this.toolbar=null
this.disposeCellProcessors()
this.navigation.dispose()
this.navigation=null
this.disposeScrollbar()
this.el=null
this.tableContainer=null
this.$el=null
this.dataTableContainer=null
this.activeCell=null}
Table.prototype.setRowValues=function(rowIndex,rowValues){var row=this.findRowByIndex(rowIndex)
if(!row){return false}var dataUpdated=false
for(var i=0,len=row.children.length;i<len;i++){var cell=row.children[i],cellColumnName=this.getCellColumnName(cell)
for(var rowColumnName in rowValues){if(rowColumnName==cellColumnName){this.setCellValue(cell,rowValues[rowColumnName],true)
dataUpdated=true}}}if(dataUpdated){var originalEditedRowKey=this.editedRowKey
this.editedRowKey=this.getRowKey(row)
this.commitEditedRow()
this.editedRowKey=originalEditedRowKey}return true}
Table.prototype.getElement=function(){return this.el}
Table.prototype.getAlias=function(){return this.options.alias}
Table.prototype.getTableContainer=function(){return this.tableContainer}
Table.prototype.getDataTableBody=function(){return this.dataTable.children[0]}
Table.prototype.getEventTarget=function(ev,tag){var target=ev.target?ev.target:ev.srcElement
if(tag===undefined)return target
var tagName=target.tagName
while(tagName!=tag){target=target.parentNode
if(!target)return null
tagName=target.tagName}return target}
Table.prototype.stopEvent=function(ev){if(ev.stopPropagation)ev.stopPropagation()
else ev.cancelBubble=true
if(ev.preventDefault)ev.preventDefault()
else ev.returnValue=false}
Table.prototype.elementHasClass=function(el,className){if(el.classList)return el.classList.contains(className);return new RegExp('(^| )'+className+'( |$)','gi').test(el.className);}
Table.prototype.elementAddClass=function(el,className){if(this.elementHasClass(el,className))return
if(el.classList)el.classList.add(className);else el.className+=' '+className;}
Table.prototype.elementRemoveClass=function(el,className){if(el.classList)el.classList.remove(className);else el.className=el.className.replace(new RegExp('(^|\\b)'+className.split(' ').join('|')+'(\\b|$)','gi'),' ');}
Table.prototype.parentContainsElement=function(parent,element){while(element&&element!=parent){element=element.parentNode}return element?true:false}
Table.prototype.getCellValue=function(cellElement){return cellElement.querySelector('[data-container]').value}
Table.prototype.getCellRowKey=function(cellElement){return parseInt(cellElement.parentNode.getAttribute('data-row'))}
Table.prototype.getRowKey=function(rowElement){return parseInt(rowElement.getAttribute('data-row'))}
Table.prototype.findRowByKey=function(key){return this.dataTable.querySelector('tbody tr[data-row="'+key+'"]')}
Table.prototype.findRowByIndex=function(index){return this.getDataTableBody().children[index]}
Table.prototype.getCellRowIndex=function(cellElement){return parseInt(cellElement.parentNode.rowIndex)}
Table.prototype.getRowCellValueByColumnName=function(row,columnName){var cell=row.querySelector('td[data-column="'+columnName+'"]')
if(!cell)return cell
return this.getCellValue(cell)}
Table.prototype.getRowData=function(row){var result={}
for(var i=0,len=row.children.length;i<len;i++){var cell=row.children[i]
result[cell.getAttribute('data-column')]=this.getCellValue(cell)}return result}
Table.prototype.getCellColumnName=function(cellElement){return cellElement.getAttribute('data-column')}
Table.prototype.setCellValue=function(cellElement,value,suppressEvents){var dataContainer=cellElement.querySelector('[data-container]')
if(dataContainer.value!=value){dataContainer.value=value
this.markCellRowDirty(cellElement)
this.notifyRowProcessorsOnChange(cellElement)
if(suppressEvents===undefined||!suppressEvents){this.$el.trigger('oc.tableCellChanged',[this.getCellColumnName(cellElement),value,this.getCellRowIndex(cellElement)])}}}
Table.DEFAULTS={clientDataSourceClass:'client',keyColumn:'id',recordsPerPage:false,data:null,postback:true,postbackHandlerName:[],adding:true,deleting:true,toolbar:true,searching:false,rowSorting:false,height:false,dynamicHeight:false}
var old=$.fn.table
$.fn.table=function(option){var args=Array.prototype.slice.call(arguments,1),result=undefined
this.each(function(){var $this=$(this)
var data=$this.data('oc.table')
var options=$.extend({},Table.DEFAULTS,$this.data(),typeof option=='object'&&option)
if(!data)$this.data('oc.table',(data=new Table(this,options)))
if(typeof option=='string')result=data[option].apply(data,args)
if(typeof result!='undefined')return false})
return result?result:this}
$.fn.table.Constructor=Table
$.wn.table.table=Table
$.fn.table.noConflict=function(){$.fn.table=old
return this}
$(document).on('render',function(){$('div[data-control=table]').table()})}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.helper===undefined)$.wn.table.helper={}
var Navigation=function(tableObj){this.tableObj=tableObj
this.pageIndex=0
this.pageCount=0
this.init()};Navigation.prototype.init=function(){}
Navigation.prototype.dispose=function(){this.tableObj=null}
Navigation.prototype.paginationEnabled=function(){return this.tableObj.options.recordsPerPage>0;}
Navigation.prototype.getPageFirstRowOffset=function(){return this.pageIndex*this.tableObj.options.recordsPerPage}
Navigation.prototype.buildPagination=function(recordCount){if(!this.paginationEnabled())return
var paginationContainer=this.tableObj.getElement().querySelector('.pagination'),newPaginationContainer=false,curRecordCount=0
this.pageCount=this.calculatePageCount(recordCount,this.tableObj.options.recordsPerPage)
if(!paginationContainer){paginationContainer=document.createElement('div')
paginationContainer.setAttribute('class','pagination')
newPaginationContainer=true}else{curRecordCount=this.getRecordCount(paginationContainer)}if(newPaginationContainer||curRecordCount!=recordCount){paginationContainer.setAttribute('data-record-count',recordCount)
var pageList=this.buildPaginationLinkList(recordCount,this.tableObj.options.recordsPerPage,this.pageIndex)
if(!newPaginationContainer){paginationContainer.replaceChild(pageList,paginationContainer.children[0])}else{paginationContainer.appendChild(pageList)
this.tableObj.getElement().appendChild(paginationContainer)}}else{this.markActiveLinkItem(paginationContainer,this.pageIndex)}}
Navigation.prototype.calculatePageCount=function(recordCount,recordsPerPage){var pageCount=Math.ceil(recordCount/recordsPerPage)
if(!pageCount)pageCount=1
return pageCount}
Navigation.prototype.getRecordCount=function(paginationContainer){var container=paginationContainer?paginationContainer:this.tableObj.getElement().querySelector('.pagination')
return parseInt(container.getAttribute('data-record-count'))}
Navigation.prototype.buildPaginationLinkList=function(recordCount,recordsPerPage,pageIndex){var pageCount=this.calculatePageCount(recordCount,recordsPerPage),pageList=document.createElement('ul')
for(var i=0;i<pageCount;i++){var item=document.createElement('li'),link=document.createElement('a')
if(i==pageIndex)item.setAttribute('class','active')
link.innerText=i+1
link.setAttribute('data-page-index',i)
link.setAttribute('href','#')
item.appendChild(link)
pageList.appendChild(item)
$(link).addClass('pagination-link')}return pageList}
Navigation.prototype.markActiveLinkItem=function(paginationContainer,pageIndex){var activeItem=paginationContainer.querySelector('.active'),list=paginationContainer.children[0]
activeItem.setAttribute('class','')
for(var i=0,len=list.children.length;i<len;i++){if(i==pageIndex){list.children[i].setAttribute('class','active')}}}
Navigation.prototype.gotoPage=function(pageIndex,onSuccess){this.tableObj.unfocusTable()
if(!this.tableObj.validate())return
this.pageIndex=pageIndex
this.tableObj.updateDataTable(onSuccess)}
Navigation.prototype.getRowCountOnPage=function(cellElement){return this.tableObj.getDataTableBody().children.length}
Navigation.prototype.getNewRowPage=function(placement,currentRowIndex){var curRecordCount=this.getRecordCount()
if(placement==='bottom')return this.calculatePageCount(curRecordCount+1,this.tableObj.options.recordsPerPage)-1
if(placement=='above')return this.pageIndex
if(placement=='below'){if(currentRowIndex==(this.tableObj.options.recordsPerPage-1))return this.pageIndex+1
return this.pageIndex}return this.pageIndex}
Navigation.prototype.getPageAfterDeletion=function(currentRowIndex){if(currentRowIndex==0&&this.getRowCountOnPage()==1)return this.pageIndex==0?0:this.pageIndex-1
return this.pageIndex}
Navigation.prototype.navigateDown=function(ev,forceCellIndex){if(!this.tableObj.activeCell)return
if(this.tableObj.activeCellProcessor&&!this.tableObj.activeCellProcessor.keyNavigationAllowed(ev,'down'))return
var row=this.tableObj.activeCell.parentNode,newRow=!ev.shiftKey?row.nextElementSibling:row.parentNode.children[row.parentNode.children.length-1],cellIndex=forceCellIndex!==undefined?forceCellIndex:this.tableObj.activeCell.cellIndex
if(newRow){var cell=newRow.children[cellIndex]
if(cell)this.tableObj.focusCell(cell)}else{if(!this.paginationEnabled())return
if(this.pageIndex<this.pageCount-1){var self=this
this.gotoPage(this.pageIndex+1,function navDownPageSuccess(){self.focusCell('top',cellIndex)
self=null})}}}
Navigation.prototype.navigateUp=function(ev,forceCellIndex,isTab){if(!this.tableObj.activeCell)return
if(this.tableObj.activeCellProcessor&&!this.tableObj.activeCellProcessor.keyNavigationAllowed(ev,'up'))return
var row=this.tableObj.activeCell.parentNode,newRow=(!ev.shiftKey||isTab)?row.previousElementSibling:row.parentNode.children[0],cellIndex=forceCellIndex!==undefined?forceCellIndex:this.tableObj.activeCell.cellIndex
if(newRow){var cell=newRow.children[cellIndex]
if(cell)this.tableObj.focusCell(cell)}else{if(!this.paginationEnabled())return
if(this.pageIndex>0){var self=this
this.gotoPage(this.pageIndex-1,function navUpPageSuccess(){self.focusCell('bottom',cellIndex)
self=null})}}}
Navigation.prototype.navigateLeft=function(ev,isTab){if(!this.tableObj.activeCell)return
if(!isTab&&this.tableObj.activeCellProcessor&&!this.tableObj.activeCellProcessor.keyNavigationAllowed(ev,'left'))return
var row=this.tableObj.activeCell.parentNode,newIndex=(!ev.shiftKey||isTab)?this.tableObj.activeCell.cellIndex-1:0
var cell=row.children[newIndex]
if(cell){this.tableObj.focusCell(cell)}else{this.navigateUp(ev,row.children.length-1,isTab)}}
Navigation.prototype.navigateRight=function(ev,isTab){if(!this.tableObj.activeCell)return
if(!isTab&&this.tableObj.activeCellProcessor&&!this.tableObj.activeCellProcessor.keyNavigationAllowed(ev,'right'))return
var row=this.tableObj.activeCell.parentNode,newIndex=!ev.shiftKey?this.tableObj.activeCell.cellIndex+1:row.children.length-1
var cell=row.children[newIndex]
if(cell){this.tableObj.focusCell(cell)}else{this.navigateDown(ev,0)}}
Navigation.prototype.navigateNext=function(ev){if(!this.tableObj.activeCell)return
if(this.tableObj.activeCellProcessor&&!this.tableObj.activeCellProcessor.keyNavigationAllowed(ev,'tab'))return
if(!ev.shiftKey)this.navigateRight(ev,true)
else this.navigateLeft(ev,true)
this.tableObj.stopEvent(ev)}
Navigation.prototype.focusCell=function(rowReference,cellIndex){var row=null,tbody=this.tableObj.getDataTableBody()
if(typeof rowReference==='object'){row=rowReference}else{if(rowReference=='bottom'){row=tbody.children[tbody.children.length-1]}else if(rowReference=='top'){row=tbody.children[0]}}if(!row)return
var cell=row.children[cellIndex]
if(cell)this.tableObj.focusCell(cell)}
Navigation.prototype.focusCellInReplacedRow=function(rowIndex,cellIndex){if(rowIndex==0){this.focusCell('top',cellIndex)}else{var focusRow=this.tableObj.findRowByIndex(rowIndex)
if(!focusRow)focusRow=this.tableObj.findRowByIndex(rowIndex-1)
if(focusRow)this.focusCell(focusRow,cellIndex)
else this.focusCell('top',cellIndex)}}
Navigation.prototype.onKeydown=function(ev){if(ev.key==='ArrowDown')return this.navigateDown(ev)
else if(ev.key==='ArrowUp')return this.navigateUp(ev)
else if(ev.key==='ArrowLeft')return this.navigateLeft(ev)
if(ev.key==='ArrowRight')return this.navigateRight(ev)
if(ev.key==='Tab')return this.navigateNext(ev)}
Navigation.prototype.onClick=function(ev){var target=this.tableObj.getEventTarget(ev,'A')
if(!target||!$(target).hasClass('pagination-link'))return
var pageIndex=parseInt(target.getAttribute('data-page-index'))
if(pageIndex===null)return
this.gotoPage(pageIndex)
this.tableObj.stopEvent(ev)
return false}
$.wn.table.helper.navigation=Navigation;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.helper===undefined)$.wn.table.helper={}
var Search=function(tableObj){this.tableObj=tableObj
this.searchForm=null
this.searchInput=null
this.inputTrackTimer=null
this.activeQuery=null
this.isActive=false
this.init()};Search.prototype.init=function(){}
Search.prototype.dispose=function(){this.tableObj=null
this.searchForm=null
this.searchInput=null}
Search.prototype.buildSearchForm=function(){if(!this.searchEnabled())return
var el=this.tableObj.getElement(),toolbar=this.tableObj.getToolbar(),searchForm=toolbar.querySelector('.table-search')
if(!searchForm){this.searchForm=$($('[data-table-toolbar-search]',el).html()).appendTo(toolbar).get(0)
this.searchInput=$('.table-search-input',this.searchForm).get(0)}}
Search.prototype.getQuery=function(){return $.trim(this.activeQuery)}
Search.prototype.hasQuery=function(){return this.searchEnabled()&&$.trim(this.activeQuery).length>0}
Search.prototype.searchEnabled=function(){return this.tableObj.options.searching}
Search.prototype.getSearchableColumns=function(){const columns=[];this.tableObj.options.columns.forEach(function(column){if(column.type==='checkbox'){return;}if(!column.searchable){return;}columns.push(column.key);});return columns;}
Search.prototype.performSearch=function(query,onSuccess){var isDirty=this.activeQuery!=query
this.activeQuery=query
if(isDirty){this.tableObj.updateDataTable(onSuccess)}}
Search.prototype.onKeydown=function(ev){if(ev.key==='Tab'){this.onClick(ev)
return}if(!this.isActive){return}var self=this
this.inputTrackTimer=window.setTimeout(function(){self.performSearch(self.searchInput.value)},300)}
Search.prototype.onClick=function(ev){var target=this.tableObj.getEventTarget(ev,'INPUT')
this.isActive=target&&$(target).hasClass('table-search-input')}
$.wn.table.helper.search=Search;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.datasource===undefined)$.wn.table.datasource={}
var Base=function(tableObj){this.tableObj=tableObj}
Base.prototype.dispose=function(){this.tableObj=null}
Base.prototype.getRecords=function(offset,count,onSuccess){onSuccess([])}
Base.prototype.searchRecords=function(query,offset,count,onSuccess){onSuccess([])}
Base.prototype.createRecord=function(recordData,placement,relativeToKey,offset,count,onSuccess){onSuccess([],0)}
Base.prototype.updateRecord=function(key,recordData){}
Base.prototype.deleteRecord=function(key,newRecordData,offset,count,onSuccess){onSuccess([],0)}
$.wn.table.datasource.base=Base;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.datasource===undefined)throw new Error("The $.wn.table.datasource namespace is not defined. Make sure that the table.datasource.base.js script is loaded.");var Base=$.wn.table.datasource.base,BaseProto=Base.prototype
var Client=function(tableObj){Base.call(this,tableObj)
var dataString=tableObj.getElement().getAttribute('data-data')
if(dataString===null||dataString===undefined)throw new Error('The required data-data attribute is not found on the table control element.')
this.data=JSON.parse(dataString)};Client.prototype=Object.create(BaseProto)
Client.prototype.constructor=Client
Client.prototype.dispose=function(){BaseProto.dispose.call(this)
this.data=null}
Client.prototype.getRecords=function(offset,count,onSuccess){if(!count){onSuccess(this.data,this.data.length)}else{onSuccess(this.data.slice(offset,offset+count),this.data.length)}}
Client.prototype.createRecord=function(recordData,placement,relativeToKey,offset,count,onSuccess){if(placement==='bottom'){this.data.push(recordData)}else if(placement=='above'||placement=='below'){var recordIndex=this.getIndexOfKey(relativeToKey)
if(placement=='below')recordIndex++
this.data.splice(recordIndex,0,recordData)}this.getRecords(offset,count,onSuccess)}
Client.prototype.searchRecords=function(query,offset,count,onSuccess){const searchFields=this.tableObj.search.getSearchableColumns();const matched=this.data.filter(function(record){for(let i=0;i<searchFields.length;i++){const value=record[searchFields[i]];if(value===undefined){continue;}if(value.toString().toLowerCase().includes(query.toLowerCase())){return true;}}return false;});if(matched.length===0){onSuccess([]);return;}if(!count){onSuccess(matched,matched.length);}else{onSuccess(matched.slice(offset,offset+count),matched.length);}}
Client.prototype.updateRecord=function(key,recordData){var recordIndex=this.getIndexOfKey(key)
if(recordIndex!==-1){recordData[this.tableObj.options.keyColumn]=key
this.data[recordIndex]=recordData}else{throw new Error('Record with they key '+key+' is not found in the data set')}}
Client.prototype.deleteRecord=function(key,newRecordData,offset,count,onSuccess){var recordIndex=this.getIndexOfKey(key)
if(recordIndex!==-1){this.data.splice(recordIndex,1)
if(this.data.length==0)this.data.push(newRecordData)
this.getRecords(offset,count,onSuccess)}else{throw new Error('Record with they key '+key+' is not found in the data set')}}
Client.prototype.getIndexOfKey=function(key){var keyColumn=this.tableObj.options.keyColumn
return this.data.map(function(record){return record[keyColumn]+""}).indexOf(key+"")}
Client.prototype.getAllData=function(){return this.data}
$.wn.table.datasource.client=Client}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.datasource===undefined)throw new Error("The $.wn.table.datasource namespace is not defined. Make sure that the table.datasource.base.js script is loaded.");var Base=$.wn.table.datasource.base,BaseProto=Base.prototype
var Server=function(tableObj){Base.call(this,tableObj)
var dataString=tableObj.getElement().getAttribute('data-data')
if(dataString===null||dataString===undefined)throw new Error('The required data-data attribute is not found on the table control element.')
this.data=JSON.parse(dataString)};Server.prototype=Object.create(BaseProto)
Server.prototype.constructor=Server
Server.prototype.dispose=function(){BaseProto.dispose.call(this)
this.data=null}
Server.prototype.getRecords=function(offset,count,onSuccess){var handlerName=this.tableObj.getAlias()+'::onServerGetRecords'
this.tableObj.$el.request(handlerName,{data:{offset:offset,count:count}}).done(function(data){onSuccess(data.records,data.count)})}
Server.prototype.searchRecords=function(query,offset,count,onSuccess){var handlerName=this.tableObj.getAlias()+'::onServerSearchRecords'
this.tableObj.$el.request(handlerName,{data:{query:query,offset:offset,count:count}}).done(function(data){onSuccess(data.records,data.count)})}
Server.prototype.createRecord=function(recordData,placement,relativeToKey,offset,count,onSuccess){var handlerName=this.tableObj.getAlias()+'::onServerCreateRecord'
this.tableObj.$el.request(handlerName,{data:{recordData:recordData,placement:placement,relativeToKey:relativeToKey,offset:offset,count:count}}).done(function(data){onSuccess(data.records,data.count)})}
Server.prototype.updateRecord=function(key,recordData){var handlerName=this.tableObj.getAlias()+'::onServerUpdateRecord'
this.tableObj.$el.request(handlerName,{data:{key:key,recordData:recordData}})}
Server.prototype.deleteRecord=function(key,newRecordData,offset,count,onSuccess){var handlerName=this.tableObj.getAlias()+'::onServerDeleteRecord'
this.tableObj.$el.request(handlerName,{data:{key:key,offset:offset,count:count}}).done(function(data){onSuccess(data.records,data.count)})}
$.wn.table.datasource.server=Server}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.processor===undefined)$.wn.table.processor={}
var Base=function(tableObj,columnName,columnConfiguration){this.tableObj=tableObj
this.columnName=columnName
this.columnConfiguration=columnConfiguration
this.activeCell=null
this.validators=[]
this.registerHandlers()
this.initValidators()}
Base.prototype.dispose=function(){this.unregisterHandlers()
this.tableObj=null
this.activeCell=null}
Base.prototype.renderCell=function(value,cellContentContainer){}
Base.prototype.registerHandlers=function(){}
Base.prototype.unregisterHandlers=function(){}
Base.prototype.onFocus=function(cellElement,isClick){}
Base.prototype.onUnfocus=function(){}
Base.prototype.onKeyDown=function(ev){}
Base.prototype.onClick=function(ev){}
Base.prototype.onRowValueChanged=function(columnName,cellElement){}
Base.prototype.keyNavigationAllowed=function(ev,direction){return true}
Base.prototype.isCellFocusable=function(){return true}
Base.prototype.getCellContentContainer=function(cellElement){return cellElement.querySelector('.content-container')}
Base.prototype.createViewContainer=function(cellContentContainer,value){var viewContainer=document.createElement('div')
viewContainer.setAttribute('data-view-container','data-view-container')
viewContainer.textContent=value===undefined?'':value
cellContentContainer.appendChild(viewContainer)
return viewContainer}
Base.prototype.getViewContainer=function(cellElement){return cellElement.querySelector('[data-view-container]')}
Base.prototype.showViewContainer=function(cellElement){return this.getViewContainer(cellElement).setAttribute('class','')}
Base.prototype.hideViewContainer=function(cellElement){return this.getViewContainer(cellElement).setAttribute('class','hide')}
Base.prototype.setViewContainerValue=function(cellElement,value){return this.getViewContainer(cellElement).textContent=value}
Base.prototype.elementBelongsToProcessor=function(element){return false}
Base.prototype.initValidators=function(){if(this.columnConfiguration.validation===undefined)return
for(var validatorName in this.columnConfiguration.validation){if($.wn.table.validator===undefined||$.wn.table.validator[validatorName]==undefined)throw new Error('The table cell validator "'+validatorName+'" for the column "'+this.columnName+'" is not '+'found in the $.wn.table.validator namespace.')
var validator=new $.wn.table.validator[validatorName](this.columnConfiguration.validation[validatorName])
this.validators.push(validator)}}
Base.prototype.validate=function(value,rowData){for(var i=0,len=this.validators.length;i<len;i++){var message=this.validators[i].validate(value,rowData)
if(message!==undefined)return message}}
$.wn.table.processor.base=Base}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.processor===undefined)throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");var Base=$.wn.table.processor.base,BaseProto=Base.prototype
var StringProcessor=function(tableObj,columnName,columnConfiguration){Base.call(this,tableObj,columnName,columnConfiguration)}
StringProcessor.prototype=Object.create(BaseProto)
StringProcessor.prototype.constructor=StringProcessor
StringProcessor.prototype.dispose=function(){BaseProto.dispose.call(this)}
StringProcessor.prototype.renderCell=function(value,cellContentContainer){this.createViewContainer(cellContentContainer,value);if(this.columnConfiguration.readonly||this.columnConfiguration.readOnly){cellContentContainer.classList.add('readonly');cellContentContainer.setAttribute('tabindex',0);}}
StringProcessor.prototype.onFocus=function(cellElement,isClick){if(this.activeCell===cellElement)return
this.activeCell=cellElement
if(!this.columnConfiguration.readonly&&!this.columnConfiguration.readOnly){this.buildEditor(cellElement,this.getCellContentContainer(cellElement))}else{this.getCellContentContainer(cellElement).focus()}}
StringProcessor.prototype.onUnfocus=function(){if(!this.activeCell)return
var editor=this.activeCell.querySelector('.string-input')
if(editor){this.tableObj.setCellValue(this.activeCell,editor.value)
this.setViewContainerValue(this.activeCell,editor.value)
editor.parentNode.removeChild(editor)}this.showViewContainer(this.activeCell)
this.activeCell=null}
StringProcessor.prototype.buildEditor=function(cellElement,cellContentContainer){this.hideViewContainer(this.activeCell)
var input=document.createElement('input')
input.setAttribute('type','text')
input.setAttribute('class','string-input')
input.value=this.tableObj.getCellValue(cellElement)
cellContentContainer.appendChild(input)
input.focus();this.setCaretPosition(input,0);}
StringProcessor.prototype.keyNavigationAllowed=function(ev,direction){if(direction!='left'&&direction!='right')return true
if(!this.activeCell)return true
var editor=this.activeCell.querySelector('.string-input')
if(!editor)return true
var caretPosition=this.getCaretPosition(editor)
if(direction=='left')return caretPosition==0
if(direction=='right')return caretPosition==editor.value.length
return true}
StringProcessor.prototype.onRowValueChanged=function(columnName,cellElement){if(columnName!=this.columnName){return}var value=this.tableObj.getCellValue(cellElement)
this.setViewContainerValue(cellElement,value)}
StringProcessor.prototype.getCaretPosition=function(input){if(document.selection){var selection=document.selection.createRange()
selection.moveStart('character',-input.value.length)
return selection.text.length}if(input.selectionStart!==undefined)return input.selectionStart
return 0}
StringProcessor.prototype.setCaretPosition=function(input,position){if(document.selection){var range=input.createTextRange()
setTimeout(function(){range.collapse(true)
range.moveStart("character",position)
range.moveEnd("character",0)
range.select()},0)}if(input.selectionStart!==undefined){setTimeout(function(){input.selectionStart=position
input.selectionEnd=position},0)}return 0}
$.wn.table.processor.string=StringProcessor;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.processor===undefined)throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");var Base=$.wn.table.processor.base,BaseProto=Base.prototype
var CheckboxProcessor=function(tableObj,columnName,columnConfiguration){Base.call(this,tableObj,columnName,columnConfiguration)}
CheckboxProcessor.prototype=Object.create(BaseProto)
CheckboxProcessor.prototype.constructor=CheckboxProcessor
CheckboxProcessor.prototype.dispose=function(){BaseProto.dispose.call(this)}
CheckboxProcessor.prototype.isCellFocusable=function(){return false}
CheckboxProcessor.prototype.renderCell=function(value,cellContentContainer){var checkbox=document.createElement('div')
checkbox.setAttribute('data-checkbox-element','true')
checkbox.setAttribute('tabindex','0')
if(value&&value!=0&&value!="false"){checkbox.setAttribute('class','checked')}cellContentContainer.appendChild(checkbox)
if(this.columnConfiguration.readonly||this.columnConfiguration.readOnly){cellContentContainer.classList.add('readonly');}}
CheckboxProcessor.prototype.onFocus=function(cellElement,isClick){cellElement.querySelector('div[data-checkbox-element]').focus()}
CheckboxProcessor.prototype.onKeyDown=function(ev){if(ev.key==='(Space character)'||ev.key==='Spacebar'||ev.key===' ')this.onClick(ev)}
CheckboxProcessor.prototype.onClick=function(ev){if(this.columnConfiguration.readonly||this.columnConfiguration.readOnly){return}var target=this.tableObj.getEventTarget(ev,'DIV')
if(target.getAttribute('data-checkbox-element')){var container=this.getCheckboxContainerNode(target)
if(container.getAttribute('data-column')!==this.columnName){return}this.changeState(target)
$(ev.target).trigger('change')}}
CheckboxProcessor.prototype.changeState=function(divElement){var cell=divElement.parentNode.parentNode
if(divElement.getAttribute('class')=='checked'){divElement.setAttribute('class','')
this.tableObj.setCellValue(cell,0)}else{divElement.setAttribute('class','checked')
this.tableObj.setCellValue(cell,1)}}
CheckboxProcessor.prototype.getCheckboxContainerNode=function(checkbox){return checkbox.parentNode.parentNode}
CheckboxProcessor.prototype.onRowValueChanged=function(columnName,cellElement){if(columnName!=this.columnName){return}var checkbox=cellElement.querySelector('div[data-checkbox-element]'),value=this.tableObj.getCellValue(cellElement)
if(value&&value!=0&&value!="false"){checkbox.setAttribute('class','checked')}else{checkbox.setAttribute('class','')}}
$.wn.table.processor.checkbox=CheckboxProcessor;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.processor===undefined)throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");var Base=$.wn.table.processor.base,BaseProto=Base.prototype
var DropdownProcessor=function(tableObj,columnName,columnConfiguration){this.itemListElement=null
this.cachedOptionPromises={}
this.searching=false
this.searchQuery=null
this.searchInterval=null
this.itemClickHandler=this.onItemClick.bind(this)
this.itemKeyDownHandler=this.onItemKeyDown.bind(this)
this.itemMouseMoveHandler=this.onItemMouseMove.bind(this)
Base.call(this,tableObj,columnName,columnConfiguration)}
DropdownProcessor.prototype=Object.create(BaseProto)
DropdownProcessor.prototype.constructor=DropdownProcessor
DropdownProcessor.prototype.dispose=function(){this.unregisterListHandlers()
this.itemClickHandler=null
this.itemKeyDownHandler=null
this.itemMouseMoveHandler=null
this.itemListElement=null
this.cachedOptionPromises=null
BaseProto.dispose.call(this)}
DropdownProcessor.prototype.unregisterListHandlers=function(){if(this.itemListElement){this.itemListElement.removeEventListener('click',this.itemClickHandler)
this.itemListElement.removeEventListener('keydown',this.itemKeyDownHandler)
this.itemListElement.removeEventListener('mousemove',this.itemMouseMoveHandler)}}
DropdownProcessor.prototype.renderCell=function(value,cellContentContainer){let viewContainer;if(this.columnConfiguration.readonly||this.columnConfiguration.readOnly){viewContainer=this.createViewContainer(cellContentContainer,value)
cellContentContainer.classList.add('readonly');cellContentContainer.setAttribute('tabindex',0);return;}viewContainer=this.createViewContainer(cellContentContainer,'...')
this.fetchOptions(cellContentContainer.parentNode,function renderCellFetchOptions(options){if(options[value]!==undefined)viewContainer.textContent=options[value]
cellContentContainer.setAttribute('tabindex',0)})}
DropdownProcessor.prototype.onFocus=function(cellElement,isClick){if(this.activeCell===cellElement){this.showDropdown()
return}this.activeCell=cellElement
if(!this.columnConfiguration.readonly&&!this.columnConfiguration.readOnly){var cellContentContainer=this.getCellContentContainer(cellElement)
this.buildEditor(cellElement,cellContentContainer,isClick)
if(!isClick){cellContentContainer.focus()}}else{this.getCellContentContainer(cellElement).focus()}}
DropdownProcessor.prototype.onUnfocus=function(){if(!this.activeCell)return
this.unregisterListHandlers()
this.hideDropdown()
this.itemListElement=null
this.activeCell=null}
DropdownProcessor.prototype.buildEditor=function(cellElement,cellContentContainer,isClick){var currentValue=this.tableObj.getCellValue(cellElement),containerPosition=this.getAbsolutePosition(cellContentContainer),self=this
this.itemListElement=document.createElement('div')
this.itemListElement.addEventListener('click',this.itemClickHandler)
this.itemListElement.addEventListener('keydown',this.itemKeyDownHandler)
this.itemListElement.addEventListener('mousemove',this.itemMouseMoveHandler)
this.itemListElement.setAttribute('class','table-control-dropdown-list')
this.itemListElement.style.width=cellContentContainer.offsetWidth+'px'
this.itemListElement.style.left=containerPosition.left+'px'
this.itemListElement.style.top=containerPosition.top-2+cellContentContainer.offsetHeight+'px'
this.fetchOptions(cellElement,function renderCellFetchOptions(options){var listElement=document.createElement('ul')
for(var value in options){var itemElement=document.createElement('li')
itemElement.setAttribute('data-value',value)
itemElement.textContent=options[value]
itemElement.setAttribute('tabindex',0)
if(value==currentValue)itemElement.setAttribute('class','selected')
listElement.appendChild(itemElement)}self.itemListElement.appendChild(listElement)
if(isClick)self.showDropdown()
self=null})}
DropdownProcessor.prototype.hideDropdown=function(){if(this.itemListElement&&this.activeCell&&this.itemListElement.parentNode){var cellContentContainer=this.getCellContentContainer(this.activeCell)
cellContentContainer.setAttribute('data-dropdown-open','false')
this.itemListElement.parentNode.removeChild(this.itemListElement)
cellContentContainer.focus()}}
DropdownProcessor.prototype.showDropdown=function(){if(this.itemListElement&&this.itemListElement.parentNode!==document.body){this.getCellContentContainer(this.activeCell).setAttribute('data-dropdown-open','true')
document.body.appendChild(this.itemListElement)
var activeItemElement=this.itemListElement.querySelector('ul li.selected')
if(!activeItemElement){activeItemElement=this.itemListElement.querySelector('ul li:first-child')
if(activeItemElement)activeItemElement.setAttribute('class','selected')}if(activeItemElement){window.setTimeout(function(){activeItemElement.focus()},0)}}}
DropdownProcessor.prototype.fetchOptions=function(cellElement,onSuccess){if(this.columnConfiguration.options){onSuccess(this.columnConfiguration.options)}else{var row=cellElement.parentNode,cachingKey=this.createOptionsCachingKey(row),viewContainer=this.getViewContainer(cellElement)
viewContainer.setAttribute('class','loading')
if(!this.cachedOptionPromises[cachingKey]){var requestData={column:this.columnName,rowData:this.tableObj.getRowData(row)},handlerName=this.tableObj.getAlias()+'::onGetDropdownOptions'
this.cachedOptionPromises[cachingKey]=this.tableObj.$el.request(handlerName,{data:requestData})}this.cachedOptionPromises[cachingKey].done(function onDropDownLoadOptionsSuccess(data){onSuccess(data.options)}).always(function onDropDownLoadOptionsAlways(){viewContainer.setAttribute('class','')})}}
DropdownProcessor.prototype.createOptionsCachingKey=function(row){var cachingKey='non-dependent',dependsOn=this.columnConfiguration.dependsOn
if(dependsOn){if(typeof dependsOn=='object'){for(var i=0,len=dependsOn.length;i<len;i++)cachingKey+=dependsOn[i]+this.tableObj.getRowCellValueByColumnName(row,dependsOn[i])}else cachingKey=dependsOn+this.tableObj.getRowCellValueByColumnName(row,dependsOn)}return cachingKey}
DropdownProcessor.prototype.getAbsolutePosition=function(element){var top=document.body.scrollTop,left=0
do{top+=element.offsetTop||0;top-=element.scrollTop||0;left+=element.offsetLeft||0;element=element.offsetParent;}while(element)return{top:top,left:left}}
DropdownProcessor.prototype.updateCellFromFocusedItem=function(focusedItem){if(!focusedItem){focusedItem=this.findFocusedItem();}this.setSelectedItem(focusedItem);}
DropdownProcessor.prototype.findSelectedItem=function(){if(this.itemListElement)return this.itemListElement.querySelector('ul li.selected')
return null}
DropdownProcessor.prototype.setSelectedItem=function(item){if(!this.itemListElement)return null;if(item.tagName=='LI'&&this.itemListElement.contains(item)){this.itemListElement.querySelectorAll('ul li').forEach(function(option){option.removeAttribute('class');});item.setAttribute('class','selected');}this.tableObj.setCellValue(this.activeCell,item.getAttribute('data-value'))
this.setViewContainerValue(this.activeCell,item.textContent)}
DropdownProcessor.prototype.findFocusedItem=function(){if(this.itemListElement)return this.itemListElement.querySelector('ul li:focus')
return null}
DropdownProcessor.prototype.onItemClick=function(ev){var target=this.tableObj.getEventTarget(ev)
if(target.tagName=='LI'){target.focus();this.updateCellFromFocusedItem(target)
this.hideDropdown()}}
DropdownProcessor.prototype.onItemKeyDown=function(ev){if(!this.itemListElement)return
if(ev.key==='ArrowDown'||ev.key==='ArrowUp'){var focused=this.findFocusedItem(),newFocusedItem=focused.nextElementSibling
if(ev.key==='ArrowUp')newFocusedItem=focused.previousElementSibling
if(newFocusedItem){newFocusedItem.focus()}return}if(ev.key==='Enter'||ev.key==='(Space character)'||ev.key==='Spacebar'||ev.key===' '){this.updateCellFromFocusedItem()
this.hideDropdown()
return}if(ev.key==='Tab'){this.updateCellFromFocusedItem()
this.tableObj.navigation.navigateNext(ev)
this.tableObj.stopEvent(ev)
return}if(ev.key==='Escape'){this.hideDropdown()
return}this.searchByTextInput(ev,true);}
DropdownProcessor.prototype.onItemMouseMove=function(ev){if(!this.itemListElement)return
var target=this.tableObj.getEventTarget(ev)
if(target.tagName=='LI'){target.focus();}}
DropdownProcessor.prototype.onKeyDown=function(ev){if(!this.itemListElement)return
if((ev.key==='(Space character)'||ev.key==='Spacebar'||ev.key===' ')&&!this.searching){this.showDropdown()}else if(ev.key==='ArrowDown'||ev.key==='ArrowUp'){var selected=this.findSelectedItem(),newSelectedItem;if(!selected){if(ev.key==='ArrowUp'){return false}newSelectedItem=this.itemListElement.querySelector('ul li:first-child')}else{newSelectedItem=selected.nextElementSibling
if(ev.key==='ArrowUp')newSelectedItem=selected.previousElementSibling}if(newSelectedItem){this.setSelectedItem(newSelectedItem);}return false}else{this.searchByTextInput(ev);}}
DropdownProcessor.prototype.onRowValueChanged=function(columnName,cellElement){if(!this.columnConfiguration.dependsOn)return
var dependsOnColumn=false,dependsOn=this.columnConfiguration.dependsOn
if(typeof dependsOn=='object'){for(var i=0,len=dependsOn.length;i<len;i++){if(dependsOn[i]==columnName){dependsOnColumn=true
break}}}else{dependsOnColumn=dependsOn==columnName}if(!dependsOnColumn)return
var currentValue=this.tableObj.getCellValue(cellElement),viewContainer=this.getViewContainer(cellElement)
this.fetchOptions(cellElement,function rowValueChangedFetchOptions(options){var value=options[currentValue]!==undefined?options[currentValue]:'...'
viewContainer.textContent=value
viewContainer=null})}
DropdownProcessor.prototype.elementBelongsToProcessor=function(element){if(!this.itemListElement)return false
return this.tableObj.parentContainsElement(this.itemListElement,element)}
DropdownProcessor.prototype.searchByTextInput=function(ev,focusOnly){if(focusOnly===undefined){focusOnly=false;}var character=ev.key;if(character.length===1||character==='Space'){if(!this.searching){this.searching=true;this.searchQuery='';}this.searchQuery+=(character==='Space')?' ':character;var validItem=null;var query=this.searchQuery;this.itemListElement.querySelectorAll('ul li').forEach(function(item){if(validItem===null&&item.dataset.value&&item.dataset.value.toLowerCase().indexOf(query.toLowerCase())===0){validItem=item;}});if(validItem){if(focusOnly===true){validItem.focus();}else{this.setSelectedItem(validItem);}if(this.searchInterval){clearTimeout(this.searchInterval);}this.searchInterval=setTimeout(this.cancelTextSearch.bind(this),1000);}else{this.cancelTextSearch();}}}
DropdownProcessor.prototype.cancelTextSearch=function(){this.searching=false;this.searchQuery=null;this.searchInterval=null;}
$.wn.table.processor.dropdown=DropdownProcessor;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.processor===undefined)throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");var Base=$.wn.table.processor.string,BaseProto=Base.prototype
var AutocompleteProcessor=function(tableObj,columnName,columnConfiguration){this.cachedOptionPromises={}
Base.call(this,tableObj,columnName,columnConfiguration)}
AutocompleteProcessor.prototype=Object.create(BaseProto)
AutocompleteProcessor.prototype.constructor=AutocompleteProcessor
AutocompleteProcessor.prototype.dispose=function(){this.cachedOptionPromises=null
BaseProto.dispose.call(this)}
AutocompleteProcessor.prototype.onUnfocus=function(){if(!this.activeCell)return
this.removeAutocomplete()
BaseProto.onUnfocus.call(this)}
AutocompleteProcessor.prototype.renderCell=function(value,cellContentContainer){BaseProto.renderCell.call(this,value,cellContentContainer)}
AutocompleteProcessor.prototype.buildEditor=function(cellElement,cellContentContainer,isClick){BaseProto.buildEditor.call(this,cellElement,cellContentContainer,isClick)
var self=this
this.fetchOptions(cellElement,function autocompleteFetchOptions(options){self.buildAutoComplete(options)
self=null})}
AutocompleteProcessor.prototype.fetchOptions=function(cellElement,onSuccess){if(this.columnConfiguration.options){if(onSuccess!==undefined){onSuccess(this.columnConfiguration.options)}}else{if(this.triggerGetOptions(onSuccess)===false){return}var row=cellElement.parentNode,cachingKey=this.createOptionsCachingKey(row),viewContainer=this.getViewContainer(cellElement)
$.wn.foundation.element.addClass(viewContainer,'loading')
if(!this.cachedOptionPromises[cachingKey]){var requestData={column:this.columnName,rowData:this.tableObj.getRowData(row)},handlerName=this.tableObj.getAlias()+'::onGetAutocompleteOptions'
this.cachedOptionPromises[cachingKey]=this.tableObj.$el.request(handlerName,{data:requestData})}this.cachedOptionPromises[cachingKey].done(function onAutocompleteLoadOptionsSuccess(data){if(onSuccess!==undefined){onSuccess(data.options)}}).always(function onAutocompleteLoadOptionsAlways(){$.wn.foundation.element.removeClass(viewContainer,'loading')})}}
AutocompleteProcessor.prototype.createOptionsCachingKey=function(row){var cachingKey='non-dependent',dependsOn=this.columnConfiguration.dependsOn
if(dependsOn){if(typeof dependsOn=='object'){for(var i=0,len=dependsOn.length;i<len;i++)cachingKey+=dependsOn[i]+this.tableObj.getRowCellValueByColumnName(row,dependsOn[i])}else cachingKey=dependsOn+this.tableObj.getRowCellValueByColumnName(row,dependsOn)}return cachingKey}
AutocompleteProcessor.prototype.triggerGetOptions=function(callback){var tableElement=this.tableObj.getElement()
if(!tableElement){return}var optionsEvent=$.Event('autocompleteitems.oc.table'),values={}
$(tableElement).trigger(optionsEvent,[{values:values,callback:callback,column:this.columnName,columnConfiguration:this.columnConfiguration}])
if(optionsEvent.isDefaultPrevented()){return false}return true}
AutocompleteProcessor.prototype.getInput=function(){if(!this.activeCell){return null}return this.activeCell.querySelector('.string-input')}
AutocompleteProcessor.prototype.buildAutoComplete=function(items){if(!this.activeCell){return}var input=this.getInput()
if(!input){return}if(items===undefined){items=[]}$(input).autocomplete({source:this.prepareItems(items),matchWidth:true,menu:'<ul class="autocomplete dropdown-menu table-widget-autocomplete"></ul>',bodyContainer:true})}
AutocompleteProcessor.prototype.prepareItems=function(items){var result={}
if($.isArray(items)){for(var i=0,len=items.length;i<len;i++){result[items[i]]=items[i]}}else{result=items}return result}
AutocompleteProcessor.prototype.removeAutocomplete=function(){var input=this.getInput()
$(input).autocomplete('destroy')}
$.wn.table.processor.autocomplete=AutocompleteProcessor;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)$.wn.table.validator={}
var Base=function(options){this.options=options}
Base.prototype.validate=function(value,rowData){if(this.options.requiredWith!==undefined&&!this.rowHasValue(this.options.requiredWith,rowData))return
return this.validateValue(value,rowData)}
Base.prototype.validateValue=function(value,rowData){}
Base.prototype.trim=function(value){if(String.prototype.trim)return value.trim()
return value.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,'')}
Base.prototype.getMessage=function(defaultValue){if(this.options.message!==undefined)return this.options.message
return defaultValue}
Base.prototype.rowHasValue=function(columnName,rowData){if(rowData[columnName]===undefined)return false
if(typeof rowData[columnName]=='boolean')return rowData[columnName]
var value=this.trim(String(rowData[columnName]))
return value.length>0}
$.wn.table.validator.base=Base;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");var Base=$.wn.table.validator.base,BaseProto=Base.prototype
var Required=function(options){Base.call(this,options)};Required.prototype=Object.create(BaseProto)
Required.prototype.constructor=Required
Required.prototype.validateValue=function(value,rowData){value=this.trim(value)
if(value.length===0)return this.getMessage("The value should not be empty.")
return}
$.wn.table.validator.required=Required}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");var Base=$.wn.table.validator.base,BaseProto=Base.prototype
var BaseNumber=function(options){Base.call(this,options)};BaseNumber.prototype=Object.create(BaseProto)
BaseNumber.prototype.constructor=BaseNumber
BaseNumber.prototype.doCommonChecks=function(value){if(this.options.min!==undefined||this.options.max!==undefined){if(this.options.min!==undefined){if(this.options.min.value===undefined)throw new Error('The min.value parameter is not defined in the table validator configuration')
if(value<this.options.min.value){return this.options.min.message!==undefined?this.options.min.message:"The value should not be less than "+this.options.min.value}}if(this.options.max!==undefined){if(this.options.max.value===undefined)throw new Error('The max.value parameter is not defined in the table validator configuration')
if(value>this.options.max.value){return this.options.max.message!==undefined?this.options.max.message:"The value should not be more than "+this.options.max.value}}}return}
$.wn.table.validator.baseNumber=BaseNumber}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");if($.wn.table.validator.baseNumber===undefined)throw new Error("The $.wn.table.validator.baseNumber namespace is not defined. Make sure that the table.validator.baseNumber.js script is loaded.");var Base=$.wn.table.validator.baseNumber,BaseProto=Base.prototype
var Integer=function(options){Base.call(this,options)};Integer.prototype=Object.create(BaseProto)
Integer.prototype.constructor=Integer
Integer.prototype.validateValue=function(value,rowData){value=this.trim(value)
if(value.length==0)return
var testResult=this.options.allowNegative?/^\-?[0-9]*$/.test(value):/^[0-9]*$/.test(value)
if(!testResult){var defaultMessage=this.options.allowNegative?'The value should be an integer.':'The value should be a positive integer';return this.getMessage(defaultMessage)}return this.doCommonChecks(parseInt(value))}
$.wn.table.validator.integer=Integer}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");if($.wn.table.validator.baseNumber===undefined)throw new Error("The $.wn.table.validator.baseNumber namespace is not defined. Make sure that the table.validator.baseNumber.js script is loaded.");var Base=$.wn.table.validator.baseNumber,BaseProto=Base.prototype
var Float=function(options){Base.call(this,options)};Float.prototype=Object.create(BaseProto)
Float.prototype.constructor=Float
Float.prototype.validateValue=function(value,rowData){value=this.trim(value)
if(value.length==0)return
var testResult=this.options.allowNegative?/^[-]?([0-9]+\.[0-9]+|[0-9]+)$/.test(value):/^([0-9]+\.[0-9]+|[0-9]+)$/.test(value)
if(!testResult){var defaultMessage=this.options.allowNegative?'The value should be a floating point number.':'The value should be a positive floating point number';return this.getMessage(defaultMessage)}return this.doCommonChecks(parseFloat(value))}
$.wn.table.validator.float=Float}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");var Base=$.wn.table.validator.base,BaseProto=Base.prototype
var Length=function(options){Base.call(this,options)};Length.prototype=Object.create(BaseProto)
Length.prototype.constructor=Length
Length.prototype.validateValue=function(value,rowData){value=this.trim(value)
if(value.length==0)return
if(this.options.min!==undefined||this.options.max!==undefined){if(this.options.min!==undefined){if(this.options.min.value===undefined)throw new Error('The min.value parameter is not defined in the Length table validator configuration')
if(value.length<this.options.min.value){return this.options.min.message!==undefined?this.options.min.message:"The string should not be shorter than "+this.options.min.value}}if(this.options.max!==undefined){if(this.options.max.value===undefined)throw new Error('The max.value parameter is not defined in the Length table validator configuration')
if(value.length>this.options.max.value){return this.options.max.message!==undefined?this.options.max.message:"The string should not be longer than "+this.options.max.value}}}return}
$.wn.table.validator.length=Length}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");var Base=$.wn.table.validator.base,BaseProto=Base.prototype
var Regex=function(options){Base.call(this,options)};Regex.prototype=Object.create(BaseProto)
Regex.prototype.constructor=Regex
Regex.prototype.validateValue=function(value,rowData){value=this.trim(value)
if(value.length==0)return
if(this.options.pattern===undefined)throw new Error('The pattern parameter is not defined in the Regex table validator configuration')
var regexObj=new RegExp(this.options.pattern,this.options.modifiers)
if(!regexObj.test(value))return this.getMessage("Invalid value format.")
return}
$.wn.table.validator.regex=Regex}(window.jQuery);

View File

@@ -0,0 +1,28 @@
/*
* This is a bundle file, you can compile this by running
*
* php artisan winter:util compile assets
*
* @see build-min.js
*
=require table.js
=require table.helper.navigation.js
=require table.helper.search.js
=require table.datasource.base.js
=require table.datasource.client.js
=require table.datasource.server.js
=require table.processor.base.js
=require table.processor.string.js
=require table.processor.checkbox.js
=require table.processor.dropdown.js
=require table.processor.autocomplete.js
=require table.validator.base.js
=require table.validator.required.js
=require table.validator.basenumber.js
=require table.validator.integer.js
=require table.validator.float.js
=require table.validator.length.js
=require table.validator.regex.js
*/

View File

@@ -0,0 +1,92 @@
/*
* Base class for the table data sources.
*/
+function ($) { "use strict";
// DATASOURCE NAMESPACES
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.datasource === undefined)
$.wn.table.datasource = {}
// CLASS DEFINITION
// ============================
var Base = function(tableObj) {
//
// State properties
//
this.tableObj = tableObj
}
Base.prototype.dispose = function() {
this.tableObj = null
}
/*
* Fetches records from the underlying data source and
* passes them to the onSuccess callback function.
* The onSuccess callback parameters: records, totalCount.
* Each record contains the key field which uniquely identifies
* the record. The name of the key field is defined with the table
* widget options.
*/
Base.prototype.getRecords = function(offset, count, onSuccess) {
onSuccess([])
}
/*
* Identical to getRecords except using a search query.
*/
Base.prototype.searchRecords = function(query, offset, count, onSuccess) {
onSuccess([])
}
/*
* Creates a record with the passed data and returns the updated page records
* to the onSuccess callback function.
*
* - recordData - the record fields
* - placement - "bottom" (the end of the data set), "above", "below"
* - relativeToKey - a row key, required if the placement is not "bottom"
* - offset - the current page's first record index (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Base.prototype.createRecord = function(recordData, placement, relativeToKey, offset, count, onSuccess) {
onSuccess([], 0)
}
/*
* Updates a record with the specified key with the passed data
*
* - key - the record key in the dataset (primary key, etc)
* - recordData - the record fields.
*/
Base.prototype.updateRecord = function(key, recordData) {
}
/*
* Deletes a record with the specified key.
*
* - key - the record key in the dataset (primary key, etc).
* - newRecordData - replacement record to add to the dataset if the deletion
* empties it.
* - offset - the current page's first record key (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Base.prototype.deleteRecord = function(key, newRecordData, offset, count, onSuccess) {
onSuccess([], 0)
}
$.wn.table.datasource.base = Base;
}(window.jQuery);

View File

@@ -0,0 +1,184 @@
/*
* Client memory data source for the table control.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.datasource === undefined)
throw new Error("The $.wn.table.datasource namespace is not defined. Make sure that the table.datasource.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.datasource.base,
BaseProto = Base.prototype
var Client = function(tableObj) {
Base.call(this, tableObj)
var dataString = tableObj.getElement().getAttribute('data-data')
if (dataString === null || dataString === undefined)
throw new Error('The required data-data attribute is not found on the table control element.')
this.data = JSON.parse(dataString)
};
Client.prototype = Object.create(BaseProto)
Client.prototype.constructor = Client
Client.prototype.dispose = function() {
BaseProto.dispose.call(this)
this.data = null
}
/*
* Fetches records from the underlying data source and
* passes them to the onSuccess callback function.
* The onSuccess callback parameters: records, totalCount.
* Each record contains the key field which uniquely identifies
* the record. The name of the key field is defined with the table
* widget options.
*/
Client.prototype.getRecords = function(offset, count, onSuccess) {
if (!count) {
// Return all records
onSuccess(this.data, this.data.length)
}
else {
// Return a subset of records
onSuccess(this.data.slice(offset, offset+count), this.data.length)
}
}
/*
* Creates a record with the passed data and returns the updated page records
* to the onSuccess callback function.
*
* - recordData - the record fields
* - placement - "bottom" (the end of the data set), "above", "below"
* - relativeToKey - a row key, required if the placement is not "bottom"
* - offset - the current page's first record index (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Client.prototype.createRecord = function(recordData, placement, relativeToKey, offset, count, onSuccess) {
if (placement === 'bottom') {
// Add record to the bottom of the dataset
this.data.push(recordData)
}
else if (placement == 'above' || placement == 'below') {
// Add record above or below the passed record key
var recordIndex = this.getIndexOfKey(relativeToKey)
if (placement == 'below')
recordIndex ++
this.data.splice(recordIndex, 0, recordData)
}
this.getRecords(offset, count, onSuccess)
}
/*
* Identical to getRecords except using a search query.
*/
Client.prototype.searchRecords = function(query, offset, count, onSuccess) {
const searchFields = this.tableObj.search.getSearchableColumns();
const matched = this.data.filter(function(record) {
for (let i = 0; i < searchFields.length; i++) {
const value = record[searchFields[i]];
if (value === undefined) {
continue;
}
if (value.toString().toLowerCase().includes(query.toLowerCase())) {
return true;
}
}
return false;
});
if (matched.length === 0) {
onSuccess([]);
return;
}
if (!count) {
// Return all records
onSuccess(matched, matched.length);
} else {
// Return a subset of records
onSuccess(matched.slice(offset, offset + count), matched.length);
}
}
/*
* Updates a record with the specified key with the passed data
*
* - key - the record key in the dataset (primary key, etc)
* - recordData - the record fields.
*/
Client.prototype.updateRecord = function(key, recordData) {
var recordIndex = this.getIndexOfKey(key)
if (recordIndex !== -1) {
recordData[this.tableObj.options.keyColumn] = key
this.data[recordIndex] = recordData
}
else {
throw new Error('Record with they key '+key+ ' is not found in the data set')
}
}
/*
* Deletes a record with the specified key.
*
* - key - the record key in the dataset (primary key, etc).
* - newRecordData - replacement record to add to the dataset if the deletion
* empties it.
* - offset - the current page's first record key (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Client.prototype.deleteRecord = function(key, newRecordData, offset, count, onSuccess) {
var recordIndex = this.getIndexOfKey(key)
if (recordIndex !== -1) {
this.data.splice(recordIndex, 1)
if (this.data.length == 0)
this.data.push(newRecordData)
this.getRecords(offset, count, onSuccess)
}
else {
throw new Error('Record with they key '+key+ ' is not found in the data set')
}
}
Client.prototype.getIndexOfKey = function(key) {
var keyColumn = this.tableObj.options.keyColumn
return this.data.map(function(record) {
return record[keyColumn] + ""
}).indexOf(key + "")
}
Client.prototype.getAllData = function() {
return this.data
}
$.wn.table.datasource.client = Client
}(window.jQuery);

View File

@@ -0,0 +1,146 @@
/*
* Server memory data source for the table control.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.datasource === undefined)
throw new Error("The $.wn.table.datasource namespace is not defined. Make sure that the table.datasource.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.datasource.base,
BaseProto = Base.prototype
var Server = function(tableObj) {
Base.call(this, tableObj)
var dataString = tableObj.getElement().getAttribute('data-data')
if (dataString === null || dataString === undefined)
throw new Error('The required data-data attribute is not found on the table control element.')
this.data = JSON.parse(dataString)
};
Server.prototype = Object.create(BaseProto)
Server.prototype.constructor = Server
Server.prototype.dispose = function() {
BaseProto.dispose.call(this)
this.data = null
}
/*
* Fetches records from the underlying data source and
* passes them to the onSuccess callback function.
* The onSuccess callback parameters: records, totalCount.
* Each record contains the key field which uniquely identifies
* the record. The name of the key field is defined with the table
* widget options.
*/
Server.prototype.getRecords = function(offset, count, onSuccess) {
var handlerName = this.tableObj.getAlias()+'::onServerGetRecords'
this.tableObj.$el.request(handlerName, {
data: {
offset: offset,
count: count
}
}).done(function(data) {
onSuccess(data.records, data.count)
})
}
/*
* Identical to getRecords except using a search query.
*/
Server.prototype.searchRecords = function(query, offset, count, onSuccess) {
var handlerName = this.tableObj.getAlias()+'::onServerSearchRecords'
this.tableObj.$el.request(handlerName, {
data: {
query: query,
offset: offset,
count: count
}
}).done(function(data) {
onSuccess(data.records, data.count)
})
}
/*
* Creates a record with the passed data and returns the updated page records
* to the onSuccess callback function.
*
* - recordData - the record fields
* - placement - "bottom" (the end of the data set), "above", "below"
* - relativeToKey - a row key, required if the placement is not "bottom"
* - offset - the current page's first record index (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Server.prototype.createRecord = function(recordData, placement, relativeToKey, offset, count, onSuccess) {
var handlerName = this.tableObj.getAlias()+'::onServerCreateRecord'
this.tableObj.$el.request(handlerName, {
data: {
recordData: recordData,
placement: placement,
relativeToKey: relativeToKey,
offset: offset,
count: count
}
}).done(function(data) {
onSuccess(data.records, data.count)
})
}
/*
* Updates a record with the specified key with the passed data
*
* - key - the record key in the dataset (primary key, etc)
* - recordData - the record fields.
*/
Server.prototype.updateRecord = function(key, recordData) {
var handlerName = this.tableObj.getAlias()+'::onServerUpdateRecord'
this.tableObj.$el.request(handlerName, {
data: {
key: key,
recordData: recordData
}
})
}
/*
* Deletes a record with the specified key.
*
* - key - the record key in the dataset (primary key, etc).
* - newRecordData - replacement record to add to the dataset if the deletion
* empties it.
* - offset - the current page's first record key (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Server.prototype.deleteRecord = function(key, newRecordData, offset, count, onSuccess) {
var handlerName = this.tableObj.getAlias()+'::onServerDeleteRecord'
this.tableObj.$el.request(handlerName, {
data: {
key: key,
offset: offset,
count: count
}
}).done(function(data) {
onSuccess(data.records, data.count)
})
}
$.wn.table.datasource.server = Server
}(window.jQuery);

View File

@@ -0,0 +1,424 @@
/*
* Navigation helper for the table widget.
* Implements the keyboard navigation within the current page
* and pagination.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.helper === undefined)
$.wn.table.helper = {}
// NAVIGATION CLASS DEFINITION
// ============================
var Navigation = function(tableObj) {
// Reference to the table object
this.tableObj = tableObj
// The current page index
this.pageIndex = 0
// Event handlers
// Number of pages in the pagination
this.pageCount = 0
this.init()
};
Navigation.prototype.init = function() {
}
Navigation.prototype.dispose = function() {
// Remove the reference to the table object
this.tableObj = null
}
// PAGINATION
// ============================
Navigation.prototype.paginationEnabled = function() {
return this.tableObj.options.recordsPerPage > 0;
}
Navigation.prototype.getPageFirstRowOffset = function() {
return this.pageIndex * this.tableObj.options.recordsPerPage
}
Navigation.prototype.buildPagination = function(recordCount) {
if (!this.paginationEnabled())
return
var paginationContainer = this.tableObj.getElement().querySelector('.pagination'),
newPaginationContainer = false,
curRecordCount = 0
this.pageCount = this.calculatePageCount(recordCount, this.tableObj.options.recordsPerPage)
if (!paginationContainer) {
paginationContainer = document.createElement('div')
paginationContainer.setAttribute('class', 'pagination')
newPaginationContainer = true
}
else {
curRecordCount = this.getRecordCount(paginationContainer)
}
// Generate the new page list only if the record count has changed
if (newPaginationContainer || curRecordCount != recordCount) {
paginationContainer.setAttribute('data-record-count', recordCount)
var pageList = this.buildPaginationLinkList(
recordCount,
this.tableObj.options.recordsPerPage,
this.pageIndex
)
if (!newPaginationContainer) {
paginationContainer.replaceChild(pageList, paginationContainer.children[0])
}
else {
paginationContainer.appendChild(pageList)
this.tableObj.getElement().appendChild(paginationContainer)
}
}
else {
// Do not re-generate the pages if the record count hasn't changed,
// but mark the new active item in the pagination list
this.markActiveLinkItem(paginationContainer, this.pageIndex)
}
}
Navigation.prototype.calculatePageCount = function(recordCount, recordsPerPage) {
var pageCount = Math.ceil(recordCount/recordsPerPage)
if (!pageCount)
pageCount = 1
return pageCount
}
Navigation.prototype.getRecordCount = function(paginationContainer) {
var container = paginationContainer ? paginationContainer : this.tableObj.getElement().querySelector('.pagination')
return parseInt(container.getAttribute('data-record-count'))
}
Navigation.prototype.buildPaginationLinkList = function(recordCount, recordsPerPage, pageIndex) {
// This method could be refactored and moved to a pagination
// helper if we want to support other pagination markup options.
var pageCount = this.calculatePageCount(recordCount, recordsPerPage),
pageList = document.createElement('ul')
for (var i=0; i < pageCount; i++) {
var item = document.createElement('li'),
link = document.createElement('a')
if (i == pageIndex)
item.setAttribute('class', 'active')
link.innerText = i+1
link.setAttribute('data-page-index', i)
link.setAttribute('href', '#')
item.appendChild(link)
pageList.appendChild(item)
$(link).addClass('pagination-link')
}
return pageList
}
Navigation.prototype.markActiveLinkItem = function(paginationContainer, pageIndex) {
// This method could be refactored and moved to a pagination
// helper if we want to support other pagination markup options.
var activeItem = paginationContainer.querySelector('.active'),
list = paginationContainer.children[0]
activeItem.setAttribute('class', '')
for (var i=0, len = list.children.length; i < len; i++) {
if (i == pageIndex) {
list.children[i].setAttribute('class', 'active')
}
}
}
Navigation.prototype.gotoPage = function(pageIndex, onSuccess) {
this.tableObj.unfocusTable()
if (!this.tableObj.validate())
return
this.pageIndex = pageIndex
this.tableObj.updateDataTable(onSuccess)
}
Navigation.prototype.getRowCountOnPage = function(cellElement) {
return this.tableObj.getDataTableBody().children.length
}
Navigation.prototype.getNewRowPage = function(placement, currentRowIndex) {
var curRecordCount = this.getRecordCount()
if (placement === 'bottom')
return this.calculatePageCount(curRecordCount + 1, this.tableObj.options.recordsPerPage) - 1
// When a row is added above a current row, the current row just moves down,
// so it's safe to return the current page index
if (placement == 'above')
return this.pageIndex
if (placement == 'below') {
if (currentRowIndex == (this.tableObj.options.recordsPerPage - 1))
return this.pageIndex + 1
return this.pageIndex
}
return this.pageIndex
}
Navigation.prototype.getPageAfterDeletion = function(currentRowIndex) {
if (currentRowIndex == 0 && this.getRowCountOnPage() == 1)
return this.pageIndex == 0 ? 0 : this.pageIndex - 1
return this.pageIndex
}
// KEYBOARD NAVIGATION
// ============================
Navigation.prototype.navigateDown = function(ev, forceCellIndex) {
if (!this.tableObj.activeCell)
return
if (this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'down'))
return
var row = this.tableObj.activeCell.parentNode,
newRow = !ev.shiftKey
? row.nextElementSibling
: row.parentNode.children[row.parentNode.children.length - 1],
cellIndex = forceCellIndex !== undefined
? forceCellIndex
: this.tableObj.activeCell.cellIndex
if (newRow) {
var cell = newRow.children[cellIndex]
if (cell)
this.tableObj.focusCell(cell)
}
else {
// Try to switch to the next page if that's possible
if (!this.paginationEnabled())
return
if (this.pageIndex < this.pageCount - 1) {
var self = this
this.gotoPage(this.pageIndex + 1, function navDownPageSuccess() {
self.focusCell('top', cellIndex)
self = null
})
}
}
}
Navigation.prototype.navigateUp = function(ev, forceCellIndex, isTab) {
if (!this.tableObj.activeCell)
return
if (this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'up'))
return
var row = this.tableObj.activeCell.parentNode,
newRow = (!ev.shiftKey || isTab)
? row.previousElementSibling
: row.parentNode.children[0],
cellIndex = forceCellIndex !== undefined
? forceCellIndex
: this.tableObj.activeCell.cellIndex
if (newRow) {
var cell = newRow.children[cellIndex]
if (cell)
this.tableObj.focusCell(cell)
}
else {
// Try to switch to the previous page if that's possible
if (!this.paginationEnabled())
return
if (this.pageIndex > 0) {
var self = this
this.gotoPage(this.pageIndex - 1, function navUpPageSuccess(){
self.focusCell('bottom', cellIndex)
self = null
})
}
}
}
Navigation.prototype.navigateLeft = function(ev, isTab) {
if (!this.tableObj.activeCell)
return
if (!isTab && this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'left'))
return
var row = this.tableObj.activeCell.parentNode,
newIndex = (!ev.shiftKey || isTab)
? this.tableObj.activeCell.cellIndex - 1
: 0
var cell = row.children[newIndex]
if (cell) {
this.tableObj.focusCell(cell)
}
else {
// Try to navigate up if that's possible
this.navigateUp(ev, row.children.length - 1, isTab)
}
}
Navigation.prototype.navigateRight = function(ev, isTab) {
if (!this.tableObj.activeCell)
return
if (!isTab && this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'right'))
return
var row = this.tableObj.activeCell.parentNode,
newIndex = !ev.shiftKey
? this.tableObj.activeCell.cellIndex + 1
: row.children.length - 1
var cell = row.children[newIndex]
if (cell) {
this.tableObj.focusCell(cell)
}
else {
// Try to navigate down if that's possible
this.navigateDown(ev, 0)
}
}
Navigation.prototype.navigateNext = function(ev) {
if (!this.tableObj.activeCell)
return
if (this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'tab'))
return
if (!ev.shiftKey)
this.navigateRight(ev, true)
else
this.navigateLeft(ev, true)
this.tableObj.stopEvent(ev)
}
Navigation.prototype.focusCell = function(rowReference, cellIndex) {
var row = null,
tbody = this.tableObj.getDataTableBody()
if (typeof rowReference === 'object') {
row = rowReference
}
else {
if (rowReference == 'bottom') {
row = tbody.children[tbody.children.length-1]
}
else if (rowReference == 'top') {
row = tbody.children[0]
}
}
if (!row)
return
var cell = row.children[cellIndex]
if (cell)
this.tableObj.focusCell(cell)
}
Navigation.prototype.focusCellInReplacedRow = function(rowIndex, cellIndex) {
if (rowIndex == 0) {
this.focusCell('top', cellIndex)
}
else {
var focusRow = this.tableObj.findRowByIndex(rowIndex)
if (!focusRow)
focusRow = this.tableObj.findRowByIndex(rowIndex-1)
if (focusRow)
this.focusCell(focusRow, cellIndex)
else
this.focusCell('top', cellIndex)
}
}
// EVENT HANDLERS
// ============================
Navigation.prototype.onKeydown = function(ev) {
// The navigation object uses the table's keydown handler
// and doesn't register own handler.
if (ev.key === 'ArrowDown')
return this.navigateDown(ev)
else if (ev.key === 'ArrowUp')
return this.navigateUp(ev)
else if (ev.key === 'ArrowLeft')
return this.navigateLeft(ev)
if (ev.key === 'ArrowRight')
return this.navigateRight(ev)
if (ev.key === 'Tab')
return this.navigateNext(ev)
}
Navigation.prototype.onClick = function(ev) {
// The navigation object uses the table's click handler
// and doesn't register own click handler.
var target = this.tableObj.getEventTarget(ev, 'A')
if (!target || !$(target).hasClass('pagination-link'))
return
var pageIndex = parseInt(target.getAttribute('data-page-index'))
if (pageIndex === null)
return
this.gotoPage(pageIndex)
this.tableObj.stopEvent(ev)
return false
}
$.wn.table.helper.navigation = Navigation;
}(window.jQuery);

View File

@@ -0,0 +1,134 @@
/*
* Search helper for the table widget.
* Implements searching within the table.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.helper === undefined)
$.wn.table.helper = {}
// SEARCH CLASS DEFINITION
// ============================
var Search = function(tableObj) {
// Reference to the table object
this.tableObj = tableObj
// The search form element
this.searchForm = null
this.searchInput = null
// Timer used for tracking input changes
this.inputTrackTimer = null
// Event handlers
// Active search query
this.activeQuery = null
this.isActive = false
this.init()
};
Search.prototype.init = function() {
}
Search.prototype.dispose = function() {
// Remove the reference to the table object
this.tableObj = null
this.searchForm = null
this.searchInput = null
}
Search.prototype.buildSearchForm = function() {
if (!this.searchEnabled())
return
var el = this.tableObj.getElement(),
toolbar = this.tableObj.getToolbar(),
searchForm = toolbar.querySelector('.table-search')
if (!searchForm) {
this.searchForm = $($('[data-table-toolbar-search]', el).html()).appendTo(toolbar).get(0)
this.searchInput = $('.table-search-input', this.searchForm).get(0)
}
}
Search.prototype.getQuery = function() {
return $.trim(this.activeQuery)
}
Search.prototype.hasQuery = function() {
return this.searchEnabled() && $.trim(this.activeQuery).length > 0
}
Search.prototype.searchEnabled = function() {
return this.tableObj.options.searching
}
Search.prototype.getSearchableColumns = function() {
const columns = [];
this.tableObj.options.columns.forEach(function(column) {
if (column.type === 'checkbox') {
return;
}
if (!column.searchable) {
return;
}
columns.push(column.key);
});
return columns;
}
Search.prototype.performSearch = function(query, onSuccess) {
var isDirty = this.activeQuery != query
this.activeQuery = query
if (isDirty) {
this.tableObj.updateDataTable(onSuccess)
}
}
// EVENT HANDLERS
// ============================
Search.prototype.onKeydown = function(ev) {
// The navigation object uses the table's keydown handler
// and doesn't register own handler.
// Tab pressed
if (ev.key === 'Tab') {
this.onClick(ev)
return
}
if (!this.isActive) {
return
}
var self = this
this.inputTrackTimer = window.setTimeout(function() {
self.performSearch(self.searchInput.value)
}, 300)
}
Search.prototype.onClick = function(ev) {
var target = this.tableObj.getEventTarget(ev, 'INPUT')
this.isActive = target && $(target).hasClass('table-search-input')
}
$.wn.table.helper.search = Search;
}(window.jQuery);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,213 @@
/*
* Autocomplete cell processor for the table control.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.processor === undefined)
throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.processor.string,
BaseProto = Base.prototype
var AutocompleteProcessor = function(tableObj, columnName, columnConfiguration) {
//
// State properties
//
this.cachedOptionPromises = {}
//
// Parent constructor
//
Base.call(this, tableObj, columnName, columnConfiguration)
}
AutocompleteProcessor.prototype = Object.create(BaseProto)
AutocompleteProcessor.prototype.constructor = AutocompleteProcessor
AutocompleteProcessor.prototype.dispose = function() {
this.cachedOptionPromises = null
BaseProto.dispose.call(this)
}
/*
* Forces the processor to hide the editor when the user navigates
* away from the cell. Processors can update the sell value in this method.
* Processors must clear the reference to the active cell in this method.
*/
AutocompleteProcessor.prototype.onUnfocus = function() {
if (!this.activeCell)
return
this.removeAutocomplete()
BaseProto.onUnfocus.call(this)
}
/*
* Renders the cell in the normal (no edit) mode
*/
AutocompleteProcessor.prototype.renderCell = function(value, cellContentContainer) {
BaseProto.renderCell.call(this, value, cellContentContainer)
}
AutocompleteProcessor.prototype.buildEditor = function(cellElement, cellContentContainer, isClick) {
BaseProto.buildEditor.call(this, cellElement, cellContentContainer, isClick)
var self = this
this.fetchOptions(cellElement, function autocompleteFetchOptions(options) {
self.buildAutoComplete(options)
self = null
})
}
AutocompleteProcessor.prototype.fetchOptions = function(cellElement, onSuccess) {
if (this.columnConfiguration.options) {
if (onSuccess !== undefined) {
onSuccess(this.columnConfiguration.options)
}
} else {
// If options are not provided and not found in the cache,
// request them from the server. For dependent autocomplete editors
// the caching key contains the master column values.
if (this.triggerGetOptions(onSuccess) === false) {
return
}
var row = cellElement.parentNode,
cachingKey = this.createOptionsCachingKey(row),
viewContainer = this.getViewContainer(cellElement)
// Request options from the server. When the table widget builds,
// multiple cells in the column could require loading the options.
// The AJAX promises are cached here so that we have a single
// request per caching key.
$.wn.foundation.element.addClass(viewContainer, 'loading')
if (!this.cachedOptionPromises[cachingKey]) {
var requestData = {
column: this.columnName,
rowData: this.tableObj.getRowData(row)
},
handlerName = this.tableObj.getAlias()+'::onGetAutocompleteOptions'
this.cachedOptionPromises[cachingKey] = this.tableObj.$el.request(handlerName, {data: requestData})
}
this.cachedOptionPromises[cachingKey].done(function onAutocompleteLoadOptionsSuccess(data){
if (onSuccess !== undefined) {
onSuccess(data.options)
}
}).always(function onAutocompleteLoadOptionsAlways(){
$.wn.foundation.element.removeClass(viewContainer, 'loading')
})
}
}
AutocompleteProcessor.prototype.createOptionsCachingKey = function(row) {
var cachingKey = 'non-dependent',
dependsOn = this.columnConfiguration.dependsOn
if (dependsOn) {
if (typeof dependsOn == 'object') {
for (var i = 0, len = dependsOn.length; i < len; i++ )
cachingKey += dependsOn[i] + this.tableObj.getRowCellValueByColumnName(row, dependsOn[i])
} else
cachingKey = dependsOn + this.tableObj.getRowCellValueByColumnName(row, dependsOn)
}
return cachingKey
}
AutocompleteProcessor.prototype.triggerGetOptions = function(callback) {
var tableElement = this.tableObj.getElement()
if (!tableElement) {
return
}
var optionsEvent = $.Event('autocompleteitems.oc.table'),
values = {} // TODO - implement loading values from the current row.
$(tableElement).trigger(optionsEvent, [{
values: values,
callback: callback,
column: this.columnName,
columnConfiguration: this.columnConfiguration
}])
if (optionsEvent.isDefaultPrevented()) {
return false
}
return true
}
AutocompleteProcessor.prototype.getInput = function() {
if (!this.activeCell) {
return null
}
return this.activeCell.querySelector('.string-input')
}
AutocompleteProcessor.prototype.buildAutoComplete = function(items) {
if (!this.activeCell) {
return
}
var input = this.getInput()
if (!input) {
return
}
if (items === undefined) {
items = []
}
$(input).autocomplete({
source: this.prepareItems(items),
matchWidth: true,
menu: '<ul class="autocomplete dropdown-menu table-widget-autocomplete"></ul>',
bodyContainer: true
})
}
AutocompleteProcessor.prototype.prepareItems = function(items) {
var result = {}
if ($.isArray(items)) {
for (var i = 0, len = items.length; i < len; i++) {
result[items[i]] = items[i]
}
}
else {
result = items
}
return result
}
AutocompleteProcessor.prototype.removeAutocomplete = function() {
var input = this.getInput()
$(input).autocomplete('destroy')
}
$.wn.table.processor.autocomplete = AutocompleteProcessor;
}(window.jQuery);

View File

@@ -0,0 +1,210 @@
/*
* Base class for the table cell processors.
*/
+function ($) { "use strict";
// PROCESSOR NAMESPACES
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.processor === undefined)
$.wn.table.processor = {}
// CLASS DEFINITION
// ============================
var Base = function(tableObj, columnName, columnConfiguration) {
//
// State properties
//
this.tableObj = tableObj
this.columnName = columnName
this.columnConfiguration = columnConfiguration
this.activeCell = null
this.validators = []
// Register event handlers
this.registerHandlers()
// Initialize validators
this.initValidators()
}
Base.prototype.dispose = function() {
// Register event handlers
this.unregisterHandlers()
// Remove references to the DOM
this.tableObj = null
this.activeCell = null
}
/*
* Renders the cell in the normal (no edit) mode
*/
Base.prototype.renderCell = function(value, cellContentContainer) {
}
/*
* Registers event handlers required for the cell processor.
* Event handers should be bound to the container control element
* (not to the table element).
*/
Base.prototype.registerHandlers = function() {
}
/*
* Unregisters event handlers previously registered with
* registerHandlers().
*/
Base.prototype.unregisterHandlers = function() {
}
/*
* This method is called when the cell managed by the processor
* is focused (clicked or navigated with the keyboard).
*/
Base.prototype.onFocus = function(cellElement, isClick) {
}
/*
* Forces the processor to hide the editor when the user navigates
* away from the cell. Processors can update the sell value in this method.
* Processors must clear the reference to the active cell in this method.
*/
Base.prototype.onUnfocus = function() {
}
/*
* Event handler for the keydown event. The table class calls this method
* for all processors.
*/
Base.prototype.onKeyDown = function(ev) {
}
/*
* Event handler for the click event. The table class calls this method
* for all processors.
*/
Base.prototype.onClick = function(ev) {
}
/*
* This method is called when a cell value in the row changes.
*/
Base.prototype.onRowValueChanged = function(columnName, cellElement) {
}
/*
* Determines if the keyboard navigation in the specified direction is allowed
* by the cell processor. Some processors could reject the navigation, for example
* the string processor could cancel the left array navigation if the caret
* in the text input is not in the beginning of the text.
*/
Base.prototype.keyNavigationAllowed = function(ev, direction) {
return true
}
/*
* Determines if the processor's cell is focusable.
*/
Base.prototype.isCellFocusable = function() {
return true
}
/*
* Returns the content container element of a cell
*/
Base.prototype.getCellContentContainer = function(cellElement) {
return cellElement.querySelector('.content-container')
}
/*
* Creates a cell view data container (a DIV element that contains
* the current cell value). This functionality is required for most
* of the processors, perhaps except the checkbox cell processor.
*/
Base.prototype.createViewContainer = function(cellContentContainer, value) {
var viewContainer = document.createElement('div')
viewContainer.setAttribute('data-view-container', 'data-view-container')
viewContainer.textContent = value === undefined ? '' : value
cellContentContainer.appendChild(viewContainer)
return viewContainer
}
/*
* Returns the cell's view container element.
*/
Base.prototype.getViewContainer = function(cellElement) {
return cellElement.querySelector('[data-view-container]')
}
/*
* Displays the view container
*/
Base.prototype.showViewContainer = function(cellElement) {
return this.getViewContainer(cellElement).setAttribute('class', '')
}
/*
* Hides the view container
*/
Base.prototype.hideViewContainer = function(cellElement) {
return this.getViewContainer(cellElement).setAttribute('class', 'hide')
}
/*
* Sets visual value for the view container
*/
Base.prototype.setViewContainerValue = function(cellElement, value) {
return this.getViewContainer(cellElement).textContent = value
}
/*
* Determines whether the specified element is some element created by the
* processor.
*/
Base.prototype.elementBelongsToProcessor = function(element) {
return false
}
Base.prototype.initValidators = function() {
if (this.columnConfiguration.validation === undefined)
return
for (var validatorName in this.columnConfiguration.validation) {
if ($.wn.table.validator === undefined || $.wn.table.validator[validatorName] == undefined)
throw new Error('The table cell validator "'+validatorName+'" for the column "'+this.columnName+'" is not ' +
'found in the $.wn.table.validator namespace.')
var validator = new $.wn.table.validator[validatorName](
this.columnConfiguration.validation[validatorName]
)
this.validators.push(validator)
}
}
Base.prototype.validate = function(value, rowData) {
for (var i=0, len=this.validators.length; i<len; i++) {
var message = this.validators[i].validate(value, rowData)
if (message !== undefined)
return message
}
}
$.wn.table.processor.base = Base
}(window.jQuery);

View File

@@ -0,0 +1,140 @@
/*
* Checkbox cell processor for the table control.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.processor === undefined)
throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.processor.base,
BaseProto = Base.prototype
var CheckboxProcessor = function(tableObj, columnName, columnConfiguration) {
//
// Parent constructor
//
Base.call(this, tableObj, columnName, columnConfiguration)
}
CheckboxProcessor.prototype = Object.create(BaseProto)
CheckboxProcessor.prototype.constructor = CheckboxProcessor
CheckboxProcessor.prototype.dispose = function() {
BaseProto.dispose.call(this)
}
/*
* Determines if the processor's cell is focusable.
*/
CheckboxProcessor.prototype.isCellFocusable = function() {
return false
}
/*
* Renders the cell in the normal (no edit) mode
*/
CheckboxProcessor.prototype.renderCell = function(value, cellContentContainer) {
var checkbox = document.createElement('div')
checkbox.setAttribute('data-checkbox-element', 'true')
checkbox.setAttribute('tabindex', '0')
if (value && value != 0 && value != "false") {
checkbox.setAttribute('class', 'checked')
}
cellContentContainer.appendChild(checkbox)
if (this.columnConfiguration.readonly || this.columnConfiguration.readOnly) {
cellContentContainer.classList.add('readonly');
}
}
/*
* This method is called when the cell managed by the processor
* is focused (clicked or navigated with the keyboard).
*/
CheckboxProcessor.prototype.onFocus = function(cellElement, isClick) {
cellElement.querySelector('div[data-checkbox-element]').focus()
}
/*
* Event handler for the keydown event. The table class calls this method
* for all processors.
*/
CheckboxProcessor.prototype.onKeyDown = function(ev) {
if (ev.key === '(Space character)' || ev.key === 'Spacebar' || ev.key === ' ')
this.onClick(ev)
}
/*
* Event handler for the click event. The table class calls this method
* for all processors.
*/
CheckboxProcessor.prototype.onClick = function(ev) {
if (this.columnConfiguration.readonly || this.columnConfiguration.readOnly) {
return
}
var target = this.tableObj.getEventTarget(ev, 'DIV')
if (target.getAttribute('data-checkbox-element')) {
// The method is called for all processors, but we should
// update only the checkbox in the clicked column.
var container = this.getCheckboxContainerNode(target)
if (container.getAttribute('data-column') !== this.columnName) {
return
}
this.changeState(target)
$(ev.target).trigger('change')
}
}
CheckboxProcessor.prototype.changeState = function(divElement) {
var cell = divElement.parentNode.parentNode
if (divElement.getAttribute('class') == 'checked') {
divElement.setAttribute('class', '')
this.tableObj.setCellValue(cell, 0)
}
else {
divElement.setAttribute('class', 'checked')
this.tableObj.setCellValue(cell, 1)
}
}
CheckboxProcessor.prototype.getCheckboxContainerNode = function(checkbox) {
return checkbox.parentNode.parentNode
}
/*
* This method is called when a cell value in the row changes.
*/
CheckboxProcessor.prototype.onRowValueChanged = function(columnName, cellElement) {
if (columnName != this.columnName) {
return
}
var checkbox = cellElement.querySelector('div[data-checkbox-element]'),
value = this.tableObj.getCellValue(cellElement)
if (value && value != 0 && value != "false") {
checkbox.setAttribute('class', 'checked')
}
else {
checkbox.setAttribute('class', '')
}
}
$.wn.table.processor.checkbox = CheckboxProcessor;
}(window.jQuery);

View File

@@ -0,0 +1,538 @@
/*
* Drop-down cell processor for the table control.
*/
/*
* TODO: implement the search
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.processor === undefined)
throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.processor.base,
BaseProto = Base.prototype
var DropdownProcessor = function(tableObj, columnName, columnConfiguration) {
//
// State properties
//
this.itemListElement = null
this.cachedOptionPromises = {}
this.searching = false
this.searchQuery = null
this.searchInterval = null
// Event handlers
this.itemClickHandler = this.onItemClick.bind(this)
this.itemKeyDownHandler = this.onItemKeyDown.bind(this)
this.itemMouseMoveHandler = this.onItemMouseMove.bind(this)
//
// Parent constructor
//
Base.call(this, tableObj, columnName, columnConfiguration)
}
DropdownProcessor.prototype = Object.create(BaseProto)
DropdownProcessor.prototype.constructor = DropdownProcessor
DropdownProcessor.prototype.dispose = function() {
this.unregisterListHandlers()
this.itemClickHandler = null
this.itemKeyDownHandler = null
this.itemMouseMoveHandler = null
this.itemListElement = null
this.cachedOptionPromises = null
BaseProto.dispose.call(this)
}
DropdownProcessor.prototype.unregisterListHandlers = function() {
if (this.itemListElement)
{
// This processor binds custom click handler to the item list,
// the standard registerHandlers/unregisterHandlers functionality
// can't be used here because the element belongs to the document
// body, not to the table.
this.itemListElement.removeEventListener('click', this.itemClickHandler)
this.itemListElement.removeEventListener('keydown', this.itemKeyDownHandler)
this.itemListElement.removeEventListener('mousemove', this.itemMouseMoveHandler)
}
}
/*
* Renders the cell in the normal (no edit) mode
*/
DropdownProcessor.prototype.renderCell = function(value, cellContentContainer) {
let viewContainer;
if (this.columnConfiguration.readonly || this.columnConfiguration.readOnly) {
viewContainer = this.createViewContainer(cellContentContainer, value)
cellContentContainer.classList.add('readonly');
cellContentContainer.setAttribute('tabindex', 0);
return;
}
viewContainer = this.createViewContainer(cellContentContainer, '...')
this.fetchOptions(cellContentContainer.parentNode, function renderCellFetchOptions(options) {
if (options[value] !== undefined)
viewContainer.textContent = options[value]
cellContentContainer.setAttribute('tabindex', 0)
})
}
/*
* This method is called when the cell managed by the processor
* is focused (clicked or navigated with the keyboard).
*/
DropdownProcessor.prototype.onFocus = function(cellElement, isClick) {
if (this.activeCell === cellElement) {
this.showDropdown()
return
}
this.activeCell = cellElement
if (!this.columnConfiguration.readonly && !this.columnConfiguration.readOnly) {
var cellContentContainer = this.getCellContentContainer(cellElement)
this.buildEditor(cellElement, cellContentContainer, isClick)
if (!isClick) {
cellContentContainer.focus()
}
} else {
this.getCellContentContainer(cellElement).focus()
}
}
/*
* Forces the processor to hide the editor when the user navigates
* away from the cell. Processors can update the sell value in this method.
* Processors must clear the reference to the active cell in this method.
*/
DropdownProcessor.prototype.onUnfocus = function() {
if (!this.activeCell)
return
this.unregisterListHandlers()
this.hideDropdown()
this.itemListElement = null
this.activeCell = null
}
DropdownProcessor.prototype.buildEditor = function(cellElement, cellContentContainer, isClick) {
// Create the select control
var currentValue = this.tableObj.getCellValue(cellElement),
containerPosition = this.getAbsolutePosition(cellContentContainer),
self = this
this.itemListElement = document.createElement('div')
this.itemListElement.addEventListener('click', this.itemClickHandler)
this.itemListElement.addEventListener('keydown', this.itemKeyDownHandler)
this.itemListElement.addEventListener('mousemove', this.itemMouseMoveHandler)
this.itemListElement.setAttribute('class', 'table-control-dropdown-list')
this.itemListElement.style.width = cellContentContainer.offsetWidth + 'px'
this.itemListElement.style.left = containerPosition.left + 'px'
this.itemListElement.style.top = containerPosition.top - 2 + cellContentContainer.offsetHeight + 'px'
this.fetchOptions(cellElement, function renderCellFetchOptions(options) {
var listElement = document.createElement('ul')
for (var value in options) {
var itemElement = document.createElement('li')
itemElement.setAttribute('data-value', value)
itemElement.textContent = options[value]
itemElement.setAttribute('tabindex', 0)
if (value == currentValue)
itemElement.setAttribute('class', 'selected')
listElement.appendChild(itemElement)
}
self.itemListElement.appendChild(listElement)
if (isClick)
self.showDropdown()
self = null
})
}
/*
* Hide the drop-down, but don't delete it.
*/
DropdownProcessor.prototype.hideDropdown = function() {
if (this.itemListElement && this.activeCell && this.itemListElement.parentNode) {
var cellContentContainer = this.getCellContentContainer(this.activeCell)
cellContentContainer.setAttribute('data-dropdown-open', 'false')
this.itemListElement.parentNode.removeChild(this.itemListElement)
cellContentContainer.focus()
}
}
DropdownProcessor.prototype.showDropdown = function() {
if (this.itemListElement && this.itemListElement.parentNode !== document.body) {
this.getCellContentContainer(this.activeCell).setAttribute('data-dropdown-open', 'true')
document.body.appendChild(this.itemListElement)
var activeItemElement = this.itemListElement.querySelector('ul li.selected')
if (!activeItemElement) {
activeItemElement = this.itemListElement.querySelector('ul li:first-child')
if (activeItemElement)
activeItemElement.setAttribute('class', 'selected')
}
if (activeItemElement) {
window.setTimeout(function(){
activeItemElement.focus()
}, 0)
}
}
}
DropdownProcessor.prototype.fetchOptions = function(cellElement, onSuccess) {
if (this.columnConfiguration.options) {
onSuccess(this.columnConfiguration.options)
}
else {
// If options are not provided and not found in the cache,
// request them from the server. For dependent drop-downs
// the caching key contains the master column values.
var row = cellElement.parentNode,
cachingKey = this.createOptionsCachingKey(row),
viewContainer = this.getViewContainer(cellElement)
// Request options from the server. When the table widget builds,
// multiple cells in the column could require loading the options.
// The AJAX promises are cached here so that we have a single
// request per caching key.
viewContainer.setAttribute('class', 'loading')
if (!this.cachedOptionPromises[cachingKey]) {
var requestData = {
column: this.columnName,
rowData: this.tableObj.getRowData(row)
},
handlerName = this.tableObj.getAlias()+'::onGetDropdownOptions'
this.cachedOptionPromises[cachingKey] = this.tableObj.$el.request(handlerName, {data: requestData})
}
this.cachedOptionPromises[cachingKey].done(function onDropDownLoadOptionsSuccess(data){
onSuccess(data.options)
}).always(function onDropDownLoadOptionsAlways(){
viewContainer.setAttribute('class', '')
})
}
}
DropdownProcessor.prototype.createOptionsCachingKey = function(row) {
var cachingKey = 'non-dependent',
dependsOn = this.columnConfiguration.dependsOn
if (dependsOn) {
if (typeof dependsOn == 'object') {
for (var i = 0, len = dependsOn.length; i < len; i++ )
cachingKey += dependsOn[i] + this.tableObj.getRowCellValueByColumnName(row, dependsOn[i])
} else
cachingKey = dependsOn + this.tableObj.getRowCellValueByColumnName(row, dependsOn)
}
return cachingKey
}
DropdownProcessor.prototype.getAbsolutePosition = function(element) {
// TODO: use the foundation library
var top = document.body.scrollTop,
left = 0
do {
top += element.offsetTop || 0;
top -= element.scrollTop || 0;
left += element.offsetLeft || 0;
element = element.offsetParent;
} while(element)
return {
top: top,
left: left
}
}
DropdownProcessor.prototype.updateCellFromFocusedItem = function(focusedItem) {
if (!focusedItem) {
focusedItem = this.findFocusedItem();
}
this.setSelectedItem(focusedItem);
}
DropdownProcessor.prototype.findSelectedItem = function() {
if (this.itemListElement)
return this.itemListElement.querySelector('ul li.selected')
return null
}
DropdownProcessor.prototype.setSelectedItem = function(item) {
if (!this.itemListElement)
return null;
if (item.tagName == 'LI' && this.itemListElement.contains(item)) {
this.itemListElement.querySelectorAll('ul li').forEach(function (option) {
option.removeAttribute('class');
});
item.setAttribute('class', 'selected');
}
this.tableObj.setCellValue(this.activeCell, item.getAttribute('data-value'))
this.setViewContainerValue(this.activeCell, item.textContent)
}
DropdownProcessor.prototype.findFocusedItem = function() {
if (this.itemListElement)
return this.itemListElement.querySelector('ul li:focus')
return null
}
DropdownProcessor.prototype.onItemClick = function(ev) {
var target = this.tableObj.getEventTarget(ev)
if (target.tagName == 'LI') {
target.focus();
this.updateCellFromFocusedItem(target)
this.hideDropdown()
}
}
DropdownProcessor.prototype.onItemKeyDown = function(ev) {
if (!this.itemListElement)
return
if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp')
{
// Up or down keys - find previous/next list item and select it
var focused = this.findFocusedItem(),
newFocusedItem = focused.nextElementSibling
if (ev.key === 'ArrowUp')
newFocusedItem = focused.previousElementSibling
if (newFocusedItem) {
newFocusedItem.focus()
}
return
}
if (ev.key === 'Enter' || ev.key === '(Space character)' || ev.key === 'Spacebar' || ev.key === ' ') {
// Return or space keys - update the selected value and hide the editor
this.updateCellFromFocusedItem()
this.hideDropdown()
return
}
if (ev.key === 'Tab') {
// Tab - update the selected value and pass control to the table navigation
this.updateCellFromFocusedItem()
this.tableObj.navigation.navigateNext(ev)
this.tableObj.stopEvent(ev)
return
}
if (ev.key === 'Escape') {
// Esc - hide the drop-down
this.hideDropdown()
return
}
this.searchByTextInput(ev, true);
}
/*
* Event handler for mouse movements over options in the dropdown menu
*/
DropdownProcessor.prototype.onItemMouseMove = function(ev) {
if (!this.itemListElement)
return
var target = this.tableObj.getEventTarget(ev)
if (target.tagName == 'LI') {
target.focus();
}
}
/*
* Event handler for the keydown event. The table class calls this method
* for all processors.
*/
DropdownProcessor.prototype.onKeyDown = function(ev) {
if (!this.itemListElement)
return
if ((ev.key === '(Space character)' || ev.key === 'Spacebar' || ev.key === ' ') && !this.searching) { // Spacebar
this.showDropdown()
} else if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') { // Up and down arrow keys
var selected = this.findSelectedItem(),
newSelectedItem;
if (!selected) {
if (ev.key === 'ArrowUp') {
// Only show an initial item when the down array key is pressed
return false
}
newSelectedItem = this.itemListElement.querySelector('ul li:first-child')
} else {
newSelectedItem = selected.nextElementSibling
if (ev.key === 'ArrowUp')
newSelectedItem = selected.previousElementSibling
}
if (newSelectedItem) {
this.setSelectedItem(newSelectedItem);
}
return false // Stop propogation of event
} else {
this.searchByTextInput(ev);
}
}
/*
* This method is called when a cell value in the row changes.
*/
DropdownProcessor.prototype.onRowValueChanged = function(columnName, cellElement) {
// Determine if this drop-down depends on the changed column
// and update the option list if necessary
// TODO: setting drop-down values with table.setRowValues() is not implemented currently
if (!this.columnConfiguration.dependsOn)
return
var dependsOnColumn = false,
dependsOn = this.columnConfiguration.dependsOn
if (typeof dependsOn == 'object') {
for (var i = 0, len = dependsOn.length; i < len; i++ ) {
if (dependsOn[i] == columnName) {
dependsOnColumn = true
break
}
}
}
else {
dependsOnColumn = dependsOn == columnName
}
if (!dependsOnColumn)
return
var currentValue = this.tableObj.getCellValue(cellElement),
viewContainer = this.getViewContainer(cellElement)
this.fetchOptions(cellElement, function rowValueChangedFetchOptions(options) {
var value = options[currentValue] !== undefined
? options[currentValue]
: '...'
viewContainer.textContent = value
viewContainer = null
})
}
/*
* Determines whether the specified element is some element created by the
* processor.
*/
DropdownProcessor.prototype.elementBelongsToProcessor = function(element) {
if (!this.itemListElement)
return false
return this.tableObj.parentContainsElement(this.itemListElement, element)
}
/*
* Provides auto-complete like functionality for typing in a query and selecting
* a matching list option
*/
DropdownProcessor.prototype.searchByTextInput = function(ev, focusOnly) {
if (focusOnly === undefined) {
focusOnly = false;
}
var character = ev.key;
if (character.length === 1 || character === 'Space') {
if (!this.searching) {
this.searching = true;
this.searchQuery = '';
}
this.searchQuery += (character === 'Space') ? ' ' : character;
// Search for a valid option in dropdown
var validItem = null;
var query = this.searchQuery;
this.itemListElement.querySelectorAll('ul li').forEach(function(item) {
if (validItem === null && item.dataset.value && item.dataset.value.toLowerCase().indexOf(query.toLowerCase()) === 0) {
validItem = item;
}
});
if (validItem) {
// If a valid item is found, select item and allow for fine-tuning the search query
if (focusOnly === true) {
validItem.focus();
} else {
this.setSelectedItem(validItem);
}
if (this.searchInterval) {
clearTimeout(this.searchInterval);
}
this.searchInterval = setTimeout(this.cancelTextSearch.bind(this), 1000);
} else {
this.cancelTextSearch();
}
}
}
DropdownProcessor.prototype.cancelTextSearch = function() {
this.searching = false;
this.searchQuery = null;
this.searchInterval = null;
}
$.wn.table.processor.dropdown = DropdownProcessor;
}(window.jQuery);

View File

@@ -0,0 +1,187 @@
/*
* String cell processor for the table control.
* The string processor allows to edit cell values with a simple
* input control.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.processor === undefined)
throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.processor.base,
BaseProto = Base.prototype
var StringProcessor = function(tableObj, columnName, columnConfiguration) {
//
// Parent constructor
//
Base.call(this, tableObj, columnName, columnConfiguration)
}
StringProcessor.prototype = Object.create(BaseProto)
StringProcessor.prototype.constructor = StringProcessor
StringProcessor.prototype.dispose = function() {
BaseProto.dispose.call(this)
}
/*
* Renders the cell in the normal (no edit) mode
*/
StringProcessor.prototype.renderCell = function(value, cellContentContainer) {
this.createViewContainer(cellContentContainer, value);
if (this.columnConfiguration.readonly || this.columnConfiguration.readOnly) {
cellContentContainer.classList.add('readonly');
cellContentContainer.setAttribute('tabindex', 0);
}
}
/*
* This method is called when the cell managed by the processor
* is focused (clicked or navigated with the keyboard).
*/
StringProcessor.prototype.onFocus = function(cellElement, isClick) {
if (this.activeCell === cellElement)
return
this.activeCell = cellElement
if (!this.columnConfiguration.readonly && !this.columnConfiguration.readOnly) {
this.buildEditor(cellElement, this.getCellContentContainer(cellElement))
} else {
this.getCellContentContainer(cellElement).focus()
}
}
/*
* Forces the processor to hide the editor when the user navigates
* away from the cell. Processors can update the sell value in this method.
* Processors must clear the reference to the active cell in this method.
*/
StringProcessor.prototype.onUnfocus = function() {
if (!this.activeCell)
return
var editor = this.activeCell.querySelector('.string-input')
if (editor) {
// Update the cell value and remove the editor
this.tableObj.setCellValue(this.activeCell, editor.value)
this.setViewContainerValue(this.activeCell, editor.value)
editor.parentNode.removeChild(editor)
}
this.showViewContainer(this.activeCell)
this.activeCell = null
}
StringProcessor.prototype.buildEditor = function(cellElement, cellContentContainer) {
// Hide the view container
this.hideViewContainer(this.activeCell)
// Create the input control
var input = document.createElement('input')
input.setAttribute('type', 'text')
input.setAttribute('class', 'string-input')
input.value = this.tableObj.getCellValue(cellElement)
cellContentContainer.appendChild(input)
input.focus();
this.setCaretPosition(input, 0);
}
/*
* Determines if the keyboard navigation in the specified direction is allowed
* by the cell processor. Some processors could reject the navigation, for example
* the string processor could cancel the left array navigation if the caret
* in the text input is not in the beginning of the text.
*/
StringProcessor.prototype.keyNavigationAllowed = function(ev, direction) {
if (direction != 'left' && direction != 'right')
return true
if (!this.activeCell)
return true
var editor = this.activeCell.querySelector('.string-input')
if (!editor)
return true
var caretPosition = this.getCaretPosition(editor)
if (direction == 'left')
return caretPosition == 0
if (direction == 'right')
return caretPosition == editor.value.length
return true
}
/*
* This method is called when a cell value in the row changes.
*/
StringProcessor.prototype.onRowValueChanged = function(columnName, cellElement) {
if (columnName != this.columnName) {
return
}
var value = this.tableObj.getCellValue(cellElement)
this.setViewContainerValue(cellElement, value)
}
StringProcessor.prototype.getCaretPosition = function(input) {
// TODO: use the foundation library
if (document.selection) {
var selection = document.selection.createRange()
selection.moveStart('character', -input.value.length)
return selection.text.length
}
if (input.selectionStart !== undefined)
return input.selectionStart
return 0
}
StringProcessor.prototype.setCaretPosition = function(input, position) {
// TODO: use the foundation library
if (document.selection) {
var range = input.createTextRange()
setTimeout(function() {
// Asynchronous layout update, better performance
range.collapse(true)
range.moveStart("character", position)
range.moveEnd("character", 0)
range.select()
}, 0)
}
if (input.selectionStart !== undefined) {
setTimeout(function() {
// Asynchronous layout update
input.selectionStart = position
input.selectionEnd = position
}, 0)
}
return 0
}
$.wn.table.processor.string = StringProcessor;
}(window.jQuery);

View File

@@ -0,0 +1,77 @@
/*
* Base class for the table validators.
*/
+function ($) { "use strict";
// VALIDATOR NAMESPACES
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
$.wn.table.validator = {}
// CLASS DEFINITION
// ============================
var Base = function(options) {
//
// State properties
//
this.options = options
}
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Base.prototype.validate = function(value, rowData) {
if (this.options.requiredWith !== undefined && !this.rowHasValue(this.options.requiredWith, rowData))
return
return this.validateValue(value, rowData)
}
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined. This method should be redefined
* in descendant classes.
* The rowData parameter is an object containing all values in the
* target row.
*/
Base.prototype.validateValue = function(value, rowData) {
}
Base.prototype.trim = function(value) {
if (String.prototype.trim)
return value.trim()
return value.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')
}
Base.prototype.getMessage = function(defaultValue) {
if (this.options.message !== undefined)
return this.options.message
return defaultValue
}
Base.prototype.rowHasValue = function(columnName, rowData) {
if (rowData[columnName] === undefined)
return false
if (typeof rowData[columnName] == 'boolean')
return rowData[columnName]
var value = this.trim(String(rowData[columnName]))
return value.length > 0
}
$.wn.table.validator.base = Base;
}(window.jQuery);

View File

@@ -0,0 +1,57 @@
/*
* Base class for number validators.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.base,
BaseProto = Base.prototype
var BaseNumber = function(options) {
Base.call(this, options)
};
BaseNumber.prototype = Object.create(BaseProto)
BaseNumber.prototype.constructor = BaseNumber
BaseNumber.prototype.doCommonChecks = function(value) {
if (this.options.min !== undefined || this.options.max !== undefined) {
if (this.options.min !== undefined) {
if (this.options.min.value === undefined)
throw new Error('The min.value parameter is not defined in the table validator configuration')
if (value < this.options.min.value) {
return this.options.min.message !== undefined ?
this.options.min.message :
"The value should not be less than " + this.options.min.value
}
}
if (this.options.max !== undefined) {
if (this.options.max.value === undefined)
throw new Error('The max.value parameter is not defined in the table validator configuration')
if (value > this.options.max.value) {
return this.options.max.message !== undefined ?
this.options.max.message :
"The value should not be more than " + this.options.max.value
}
}
}
return
}
$.wn.table.validator.baseNumber = BaseNumber
}(window.jQuery);

View File

@@ -0,0 +1,59 @@
/*
* Float table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
if ($.wn.table.validator.baseNumber === undefined)
throw new Error("The $.wn.table.validator.baseNumber namespace is not defined. Make sure that the table.validator.baseNumber.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.baseNumber,
BaseProto = Base.prototype
var Float = function(options) {
Base.call(this, options)
};
Float.prototype = Object.create(BaseProto)
Float.prototype.constructor = Float
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Float.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
var testResult = this.options.allowNegative ?
/^[-]?([0-9]+\.[0-9]+|[0-9]+)$/.test(value) :
/^([0-9]+\.[0-9]+|[0-9]+)$/.test(value)
if (!testResult) {
var defaultMessage = this.options.allowNegative ?
'The value should be a floating point number.' :
'The value should be a positive floating point number';
return this.getMessage(defaultMessage)
}
return this.doCommonChecks(parseFloat(value))
}
$.wn.table.validator.float = Float
}(window.jQuery);

View File

@@ -0,0 +1,59 @@
/*
* Integer table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
if ($.wn.table.validator.baseNumber === undefined)
throw new Error("The $.wn.table.validator.baseNumber namespace is not defined. Make sure that the table.validator.baseNumber.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.baseNumber,
BaseProto = Base.prototype
var Integer = function(options) {
Base.call(this, options)
};
Integer.prototype = Object.create(BaseProto)
Integer.prototype.constructor = Integer
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Integer.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
var testResult = this.options.allowNegative ?
/^\-?[0-9]*$/.test(value) :
/^[0-9]*$/.test(value)
if (!testResult) {
var defaultMessage = this.options.allowNegative ?
'The value should be an integer.' :
'The value should be a positive integer';
return this.getMessage(defaultMessage)
}
return this.doCommonChecks(parseInt(value))
}
$.wn.table.validator.integer = Integer
}(window.jQuery);

View File

@@ -0,0 +1,68 @@
/*
* String length table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.base,
BaseProto = Base.prototype
var Length = function(options) {
Base.call(this, options)
};
Length.prototype = Object.create(BaseProto)
Length.prototype.constructor = Length
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Length.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
if (this.options.min !== undefined || this.options.max !== undefined) {
if (this.options.min !== undefined) {
if (this.options.min.value === undefined)
throw new Error('The min.value parameter is not defined in the Length table validator configuration')
if (value.length < this.options.min.value) {
return this.options.min.message !== undefined ?
this.options.min.message :
"The string should not be shorter than " + this.options.min.value
}
}
if (this.options.max !== undefined) {
if (this.options.max.value === undefined)
throw new Error('The max.value parameter is not defined in the Length table validator configuration')
if (value.length > this.options.max.value) {
return this.options.max.message !== undefined ?
this.options.max.message :
"The string should not be longer than " + this.options.max.value
}
}
}
return
}
$.wn.table.validator.length = Length
}(window.jQuery);

View File

@@ -0,0 +1,52 @@
/*
* Regex length table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.base,
BaseProto = Base.prototype
var Regex = function(options) {
Base.call(this, options)
};
Regex.prototype = Object.create(BaseProto)
Regex.prototype.constructor = Regex
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Regex.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
if (this.options.pattern === undefined)
throw new Error('The pattern parameter is not defined in the Regex table validator configuration')
var regexObj = new RegExp(this.options.pattern, this.options.modifiers)
if (!regexObj.test(value))
return this.getMessage("Invalid value format.")
return
}
$.wn.table.validator.regex = Regex
}(window.jQuery);

View File

@@ -0,0 +1,44 @@
/*
* Required table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.base,
BaseProto = Base.prototype
var Required = function(options) {
Base.call(this, options)
};
Required.prototype = Object.create(BaseProto)
Required.prototype.constructor = Required
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Required.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length === 0)
return this.getMessage("The value should not be empty.")
return
}
$.wn.table.validator.required = Required
}(window.jQuery);

View File

@@ -0,0 +1,509 @@
@import "../../../../../backend/assets/less/core/boot.less";
/*
* General control styling
*/
@table-active-border: #e0e0e0;
@table-inactive-border: #e0e0e0;
@table-odd-row: #ffffff;
@table-even-row: #fafafa;
@table-disabled-field: #f7f7f7;
.control-table {
.table-container {
border: 1px solid @table-inactive-border;
.border-radius(4px);
overflow: hidden;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
&:not([data-records-per-page="false"]) .table-container {
.border-bottom-radius(0);
}
&.active .table-container {
border-color: @table-active-border;
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
td, th {
padding: 0;
font-size: 13px;
color: #555555;
}
[data-view-container] {
padding: 5px 10px;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-height: 28px;
}
}
table.headers {
&:after {
content: ' ';
display: block;
position: absolute;
left: 1px;
right: 1px;
margin-top: -1px;
border-bottom: 1px solid @table-inactive-border;
}
th {
padding: 7px 10px;
font-weight: normal;
text-transform: uppercase;
font-size: @font-size-base - 3;
color: #333333;
background: white;
border-right: 1px solid #ecf0f1;
[data-view-container] {
padding-bottom: 6px;
}
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:last-child {
border-right: none;
}
}
}
&.active table.headers:after {
border-bottom-color: @table-active-border;
}
table.data {
td {
border: 1px solid #ecf0f1;
.content-container {
position: relative;
padding: 1px;
outline: none;
&.readonly {
background: @table-disabled-field;
}
}
&.active {
border-color: @color-focus!important;
.content-container {
padding: 0;
border: 1px solid @color-focus;
&:before, &:after {
content: ' ';
background: @color-focus;
position: absolute;
left: -2px;
top: -2px;
}
&:before {
width: 1px;
bottom: -2px;
}
&:after {
right: -2px;
height: 1px;
}
}
}
}
tr {
background-color: @table-odd-row;
&.error {
background-color: #fbecec!important;
td.active.error {
border-color: #ec0000!important;
.content-container {
border-color: #ec0000!important;
&:before, &:after {
background-color: #ec0000!important;
}
}
}
}
}
tr:nth-child(2n) {
background-color: @table-even-row;
}
}
table.data {
tr:first-child td {
border-top: none;
}
tr:last-child td {
border-bottom: none;
}
td:first-child {
border-left: none;
}
td:last-child {
border-right: none;
}
}
.control-scrollbar {
> div {
.border-bottom-radius(4px);
overflow: hidden;
}
table.data {
tr:last-child td {
border-bottom: 1px solid #ecf0f1;
}
}
}
.toolbar {
background: white;
border-bottom: 1px solid @table-inactive-border;
.clearfix();
a.btn {
color: #323e50;
padding: 8px 10px;
.opacity(0.5);
.box-shadow(none) !important;
text-shadow: none;
&:hover {
.opacity(1);
}
}
.table-search {
float: right;
margin: 3px 3px 3px 0;
.table-search-input {
height: auto;
padding: 5px 13px 5px;
}
}
a.table-icon {
&:before {
display: inline-block;
content: ' ';
width: 16px;
height: 16px;
margin-right: 8px;
position: relative;
top: 3px;
background: transparent url(../images/table-icons.gif) no-repeat 0 0;
background-size: 32px auto;
}
&.add-table-row-above:before {
background-position: 0 -56px;
}
&.delete-table-row:before {
background-position: 0 -113px;
}
}
}
&.active .toolbar {
border-bottom-color: @table-active-border;
}
.pagination {
margin: -15px 0 15px;
padding: 7px 10px;
background: white;
border: 1px solid @table-inactive-border;
border-top: none;
.border-bottom-radius(4px);
ul {
padding: 0;
margin: 0;
li {
list-style: none;
display: inline-block;
margin-right: 5px;
font-size: 12px;
line-height: 100%;
a {
display: inline-block;
text-decoration: none;
color: #95a5a6;
padding: 4px 6px;
background: #ecf0f1;
.border-radius(2px);
outline: none;
line-height: 100%;
}
a:focus-visible {
outline: auto;
}
&.active a {
background: @brand-accent;
color: #ffffff;
}
}
}
}
}
@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-devicepixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) {
.control-table .toolbar {
a {
&:before {
background-position: 0px -9px;
background-size: 16px auto;
}
&.add-table-row-above:before {
background-position: 0 -39px;
}
&.delete-table-row:before {
background-position: 0 -66px;
}
}
}
}
/*
* String and autocomplete editors
*/
.control-table {
td[data-column-type=string],
td[data-column-type=autocomplete] {
input[type=text] {
width: 100%;
height: 100%;
display: block;
outline: none;
border: none;
padding: 6px 10px 7px;
}
}
}
html.chrome {
.control-table {
td[data-column-type=string],
td[data-column-type=autocomplete] {
input[type=text] {
padding: 6px 10px 7px!important;
}
}
}
}
html.safari, html.gecko {
.control-table {
td[data-column-type=string],
td[data-column-type=autocomplete] {
input[type=text] {
padding: 5px 10px 5px;
}
}
}
}
ul.table-widget-autocomplete {
background: white;
font-size: 13px;
margin-top: 0;
border: 1px solid #808c8d;
border-top: 1px solid #ecf0f1;
.border-bottom-radius(4px);
li a {
padding: 5px 10px;
}
}
/*
* Checkbox editor
*/
.control-table {
td[data-column-type=checkbox] {
div[data-checkbox-element] {
width: 16px;
height: 16px;
border-radius: @border-radius-base;
background-color: #FFFFFF;
border: 1px solid @color-custom-input-border;
margin: 6px 5px 6px 10px;
cursor: pointer;
.user-select(none);
&:hover {
border-color: darken(@color-custom-input-border, 10%);
color: darken(@color-custom-input-icon, 10%);
}
&.checked {
border-width: 2px;
&:before {
.icon(@check);
font-size: 10px;
position: relative;
left: 1px;
top: -4px;
}
}
&:focus {
border-color: @color-focus;
outline: none;
}
}
}
}
/*
* Dropdown editor
*/
.control-table {
td[data-column-type=dropdown] {
.dropdown-arrow() {
.icon(@angle-down);
font-size: 13px;
line-height: 100%;
color: #95a5a6;
position: absolute;
top: 8px;
right: 10px;
}
.content-container:not(.readonly) {
.user-select(none);
[data-view-container] {
padding-right: 20px;
position: relative;
cursor: pointer;
&:after {
.dropdown-arrow();
}
&:hover:after {
color: @link-color;
}
}
}
[data-dropdown-open=true] {
background: white;
[data-view-container]:after {
.icon(@angle-up);
}
}
}
}
/* Frameless control styles start */
.widget-field.frameless .control-table {
.table-container {
border-top: none;
border-left: none;
border-right: none;
.border-radius(0);
}
.toolbar {
background: transparent;
}
}
/* Frameless control styles end */
html.cssanimations {
.control-table td[data-column-type=dropdown] {
[data-view-container].loading:after {
background: url('../../../../../../modules/system/assets/ui/images/loader-transparent.svg') 50% 50%;
background-size: 15px 15px;
position: absolute;
width: 15px;
height: 15px;
top: 6px;
right: 5px;
content: ' ';
.animation(spin 1s linear infinite);
}
}
}
.table-control-dropdown-list {
.user-select(none);
position: absolute;
background: white;
border: 1px solid @table-active-border;
border-top: none;
padding-top: 1px;
overflow: hidden;
z-index: 1000;
.box-sizing(border-box);
.border-bottom-radius(4px);
ul {
border-top: 1px solid #ecf0f1;
padding: 0;
margin: 0;
max-height: 200px;
overflow: auto;
}
li {
list-style: none;
font-size: 13px;
color: #555555;
padding: 5px 10px;
cursor: pointer;
outline: none;
&:focus {
background: @color-focus;
color: white;
}
}
}

View File

@@ -0,0 +1,50 @@
<div
id="<?= $this->getId() ?>"
data-control="table"
class="control-table"
data-columns="<?= e(json_encode($columns)) ?>"
data-data="<?= e($data) ?>"
data-alias="<?= e($this->alias) ?>"
data-field-name="<?= e($this->fieldName) ?>"
<?php if (!empty($postbackHandlerName)): ?>
data-postback-handler-name="<?= e($postbackHandlerName) ?>"
<?php endif; ?>
data-adding="<?= e($adding) ?>"
data-searching="<?= e($searching) ?>"
data-deleting="<?= e($deleting) ?>"
data-toolbar="<?= e($toolbar) ?>"
data-height="<?= e($height) ?>"
data-records-per-page="<?= e($recordsPerPage) ?>"
data-key-column="<?= e($recordsKeyFrom) ?>"
data-client-data-source-class="<?= e($clientDataSourceClass) ?>"
data-dynamic-height="<?= e($dynamicHeight) ?>"
>
<script type="text/template" data-table-toolbar>
<div class="toolbar">
<a class="btn table-icon add-table-row-below" data-cmd="record-add">
<?= e($btnAddRowLabel) ?>
</a>
<a class="btn table-icon add-table-row-below" data-cmd="record-add-below">
<?= e($btnAddRowBelowLabel) ?>
</a>
<a class="btn table-icon add-table-row-above" data-cmd="record-add-above">
Add row above
</a>
<a class="btn table-icon delete-table-row" data-cmd="record-delete">
<?= e($btnDeleteRowLabel) ?>
</a>
</div>
</script>
<script type="text/template" data-table-toolbar-search>
<div class="table-search">
<input
placeholder="<?= e(trans('backend::lang.list.search_prompt')) ?>"
name="search"
id="search"
value="<?= e(get('search')); ?>"
type="text"
autocomplete="off"
class="table-search-input form-control icon search" />
</div>
</script>
</div>