feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
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:
62
modules/backend/widgets/table/ClientMemoryDataSource.php
Normal file
62
modules/backend/widgets/table/ClientMemoryDataSource.php
Normal 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;
|
||||
}
|
||||
}
|
||||
86
modules/backend/widgets/table/DataSourceBase.php
Normal file
86
modules/backend/widgets/table/DataSourceBase.php
Normal 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;
|
||||
}
|
||||
}
|
||||
412
modules/backend/widgets/table/README.md
Normal file
412
modules/backend/widgets/table/README.md
Normal 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
|
||||
91
modules/backend/widgets/table/ServerEventDataSource.php
Normal file
91
modules/backend/widgets/table/ServerEventDataSource.php
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
77
modules/backend/widgets/table/assets/css/table.css
Normal file
77
modules/backend/widgets/table/assets/css/table.css
Normal 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}
|
||||
BIN
modules/backend/widgets/table/assets/images/table-icons.gif
Normal file
BIN
modules/backend/widgets/table/assets/images/table-icons.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
773
modules/backend/widgets/table/assets/js/build-min.js
vendored
Normal file
773
modules/backend/widgets/table/assets/js/build-min.js
vendored
Normal 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);
|
||||
28
modules/backend/widgets/table/assets/js/build.js
Normal file
28
modules/backend/widgets/table/assets/js/build.js
Normal 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
|
||||
|
||||
*/
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
134
modules/backend/widgets/table/assets/js/table.helper.search.js
Normal file
134
modules/backend/widgets/table/assets/js/table.helper.search.js
Normal 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);
|
||||
1170
modules/backend/widgets/table/assets/js/table.js
Normal file
1170
modules/backend/widgets/table/assets/js/table.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
210
modules/backend/widgets/table/assets/js/table.processor.base.js
Normal file
210
modules/backend/widgets/table/assets/js/table.processor.base.js
Normal 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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
509
modules/backend/widgets/table/assets/less/table.less
Normal file
509
modules/backend/widgets/table/assets/less/table.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
modules/backend/widgets/table/partials/_table.php
Normal file
50
modules/backend/widgets/table/partials/_table.php
Normal 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>
|
||||
Reference in New Issue
Block a user