wsssoftware/cakephp-datatables

View on GitHub
src/Table/Column.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php
/**
 * Copyright (c) Allan Carvalho 2020.
 * Under Mit License
 *
 * link: https://github.com/wsssoftware/cakephp-data-renderer
 * author: Allan Carvalho <allan.m.carvalho@outlook.com>
 * license: MIT License https://github.com/wsssoftware/cakephp-datatables/blob/master/LICENSE
 */
declare(strict_types = 1);

namespace DataTables\Table;

use Cake\Database\Expression\FunctionExpression;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use Cake\Utility\Text;
use DataTables\Tools\Validator;
use InvalidArgumentException;

/**
 * Class Column
 *
 * Created by allancarvalho in abril 17, 2020
 */
final class Column {

    public const TYPE_DATE = 'date';
    public const TYPE_NUM = 'num';
    public const TYPE_NUM_FMT = 'num-fmt';
    public const TYPE_HTML_NUM = 'html-num';
    public const TYPE_HTML_NUM_FMT = 'html-num-fmt';
    public const TYPE_HTML = 'html';
    public const TYPE_STRING = 'string';
    public const VALID_TYPES = [
        self::TYPE_DATE,
        self::TYPE_NUM,
        self::TYPE_NUM_FMT,
        self::TYPE_HTML_NUM,
        self::TYPE_HTML_NUM_FMT,
        self::TYPE_HTML,
        self::TYPE_STRING,
    ];
    public const DOM_TEXT = 'dom-text';
    public const DOM_SELECT = 'dom-select';
    public const DOM_CHECKBOX = 'dom-checkbox';
    public const VALID_ORDER_DATA_TYPES = [
        self::DOM_TEXT,
        self::DOM_SELECT,
        self::DOM_CHECKBOX,
    ];
    public const DATA_TABLES_TYPE_MAP = [
        'tinyinteger' => 'num',
        'smallinteger' => 'num',
        'integer' => 'num',
        'biginteger' => 'num',
        'binary' => 'string',
        'binaryuuid' => 'string',
        'boolean' => 'num',
        'date' => 'date',
        'datetime' => 'date',
        'datetimefractional' => 'date',
        'decimal' => 'num',
        'float' => 'num',
        'json' => 'string',
        'string' => 'string',
        'char' => 'string',
        'text' => 'string',
        'time' => 'date',
        'timestamp' => 'date',
        'timestampfractional' => 'date',
        'timestamptimezone' => 'date',
        'uuid' => 'string',
    ];

    /**
     * @var array
     */
    private $_config = [
        'cellType' => null,
        'className' => null,
        'contentPadding' => null,
        'createdCell' => null,
        'name' => null,
        'orderData' => null,
        'orderDataType' => null,
        'orderSequence' => null,
        'orderable' => null,
        'searchable' => null,
        'title' => null,
        'type' => null,
        'visible' => null,
        'width' => null,
    ];

    /**
     * If the column is or not a database column.
     *
     * @var array
     */
    private $_columnSchema;

    /**
     * @var bool
     */
    private $_database;

    /**
     * @var string
     */
    private $_associationPath = '';

    /**
     * @var \Cake\Database\Expression\FunctionExpression|null
     */
    private $_functionExpression;

    /**
     * @param string $name
     * @param bool $database
     * @param array $columnSchema
     * @param string $associationPath
     * @param \Cake\Database\Expression\FunctionExpression|null $functionExpression
     */
    public function __construct(string $name, bool $database = true, array $columnSchema = [], string $associationPath = '', ?FunctionExpression $functionExpression = null) {
        $title = explode('.', $name);
        if (count($title) === 2) {
            $title = $title[1];
        } else {
            $title = $title[0];
        }
        $title = Inflector::humanize($title);
        $this->_config = Hash::insert($this->_config, 'name', $name);
        $this->_config = Hash::insert($this->_config, 'title', $title);
        $this->_database = $database;
        $this->_columnSchema = $columnSchema;
        $this->_associationPath = $associationPath;
        $this->_functionExpression = $functionExpression;
        if ($database === true && !empty($columnSchema['type']) && !empty(static::DATA_TABLES_TYPE_MAP[$columnSchema['type']])) {
            $this->setType(static::DATA_TABLES_TYPE_MAP[$columnSchema['type']]);
        }
    }

    /**
     * @return string
     */
    public function getAssociationPath(): string {
        return $this->_associationPath;
    }

    /**
     * @return \Cake\Database\Expression\FunctionExpression
     */
    public function getFunctionExpression(): ?FunctionExpression {
        return $this->_functionExpression;
    }

    /**
     * @param bool $onlyDirty
     * @param bool $isDefaultColumn
     * @return array
     */
    public function getConfig(bool $onlyDirty = true, bool $isDefaultColumn = false): array {
        $result = [];
        if ($onlyDirty === false) {
            $result = $this->_config;
        }
        if (empty($result)) {
            foreach ($this->_config as $index => $item) {
                if ($item !== null) {
                    $result[$index] = $item;
                }
            }
        }
        if ($isDefaultColumn === true) {
            $result['targets'] = '_all';
            unset($result['name']);
            unset($result['title']);
        }

        return $result;
    }

    /**
     * Get column name
     *
     * @return string
     */
    public function getName(): string {
        return Hash::get($this->_config, 'name');
    }

    /**
     * Check if is a database column or not.
     *
     * @return bool
     */
    public function isDatabase(): bool {
        return $this->_database;
    }

    /**
     * @param string|null $name
     * @return mixed
     */
    public function getColumnSchema(?string $name = null) {
        if (empty($name)) {
            return $this->_columnSchema;
        }

        return Hash::get($this->_columnSchema, $name);
    }

    /**
     * Getter method.
     * Change the cell type created for the column - either TD cells or TH cells.
     * This can be useful as TH cells have semantic meaning in the table body, allowing them to act as a header for a
     * row (you may wish to add scope='row' to the TH elements using columns.createdCell option).
     *
     * @link https://datatables.net/reference/option/columns.cellType
     * @return string|null
     */
    public function getCellType(): ?string {
        return Hash::get($this->_config, 'cellType');
    }

    /**
     * Setter method.
     * Change the cell type created for the column - either TD cells or TH cells.
     * This can be useful as TH cells have semantic meaning in the table body, allowing them to act as a header for a
     * row (you may wish to add scope='row' to the TH elements using columns.createdCell option).
     *
     * @link https://datatables.net/reference/option/columns.cellType
     * @param string|null $cellType
     *
     * @return $this
     */
    public function setCellType(?string $cellType) {
        if (!in_array($cellType, ['td', 'th']) && !empty($cellType)) {
            throw new InvalidArgumentException("\$cellType must be 'td' or 'th'. Found: $cellType.");
        }
        $this->_config = Hash::insert($this->_config, 'cellType', $cellType);

        return $this;
    }

    /**
     * Getter method.
     * Quite simply this option adds a class to each cell in a column, regardless of if the table source is from DOM,
     * Javascript or Ajax. This can be useful for styling columns.
     *
     * @link https://datatables.net/reference/option/columns.className
     * @return string|null
     */
    public function getClassName(): ?string {
        return Hash::get($this->_config, 'className');
    }

    /**
     * Setter method.
     * Quite simply this option adds a class to each cell in a column, regardless of if the table source is from DOM,
     * Javascript or Ajax. This can be useful for styling columns.
     *
     * @link https://datatables.net/reference/option/columns.className
     * @param string|null $className
     *
     * @return $this
     */
    public function setClassName(?string $className) {
        $this->_config = Hash::insert($this->_config, 'className', $className);

        return $this;
    }

    /**
     * Getter method.
     * Quite simply this option adds a class to each cell in a column, regardless of if the table source is from DOM,
     * Javascript or Ajax. This can be useful for styling columns.
     *
     * @link https://datatables.net/reference/option/columns.contentPadding
     * @return string|null
     */
    public function getContentPadding(): ?string {
        return Hash::get($this->_config, 'contentPadding');
    }

    /**
     * Setter method.
     * The first thing to say about this property is that generally you shouldn't need this!
     *
     * Having said that, it can be useful on rare occasions. When DataTables calculates the column widths to assign to
     * each column, it finds the longest string in each column and then constructs a temporary table and reads the
     * widths from that. The problem with this is that "mmm" is much wider then "iiii", but the latter is a longer
     * string - thus the calculation can go wrong (doing it properly and putting it into an DOM object and measuring
     * that is horribly slow!). Thus as a "work around" we provide this option. It will append its value to the text
     * that is found to be the longest string for the column - i.e. padding.
     *
     * @link https://datatables.net/reference/option/columns.contentPadding
     * @param string $contentPadding
     *
     * @return $this
     */
    public function setContentPadding(?string $contentPadding) {
        $this->_config = Hash::insert($this->_config, 'contentPadding', $contentPadding);

        return $this;
    }

    /**
     * Setter method.
     * This is a callback function that is executed whenever a cell is created (Ajax source, etc) or read from a DOM
     * source. It can be used as a complement to columns.render allowing modification of the cell's DOM element (add
     * background colour for example) when the element is created (cells may not be immediately created on table
     * initialisation if deferRender is enabled, or if rows are dynamically added using the API (rows.add()).
     *
     * This is the counterpart callback for rows, which use the createdRow option.
     *
     * Accessible parameters inside js function:
     *  - cell (node) - The TD node that has been created.
     *  - cellData (any) - Cell data. If you use columns.render to modify the data, use $(cell).html() to get and modify
     *    the rendered data. The information given here is the original and unmodified data from the data source.
     *  - rowData (any) - Data source object / array for the whole row.
     *  - rowIndex (integer) - DataTables' internal index for the row.
     *  - colIndex (integer) - DataTables' internal index for the column.
     *
     * @link https://datatables.net/reference/option/columns.createdCell
     * @link https://datatables.net/reference/type/node
     * @link https://datatables.net/reference/type/integer
     * @param array|string $bodyOrParams To use application template file, leave blank or pass an array with params
     *                                   that will be used in file. To use the body mode, pass an js code that will
     *                                   putted inside the js function.
     *
     * @return $this
     */
    public function callbackCreatedCell($bodyOrParams = []) {
        Validator::getInstance()->validateBodyOrParams($bodyOrParams);
        $this->_config = Hash::insert($this->_config, 'createdCell', $bodyOrParams);

        return $this;
    }

    /**
     * Getter method.
     * Allows a column's sorting to take either the data from a different (often hidden) column as the data to sort, or
     * data from multiple columns.
     *
     * A common example of this is a table which contains first and last name columns next to each other, it is
     * intuitive that they would be linked together to multi-column sort. Another example, with a single column, is the
     * case where the data shown to the end user is not directly sortable itself (a column with images in it), but
     * there is some meta data than can be sorted (e.g. file name) - note that orthogonal data is an alternative method
     * that can be used for this.
     *
     * @link https://datatables.net/reference/option/columns.orderData
     * @return int|array|null
     */
    public function getOrderData() {
        return Hash::get($this->_config, 'orderData');
    }

    /**
     * Setter method.
     * Allows a column's sorting to take either the data from a different (often hidden) column as the data to sort, or
     * data from multiple columns.
     *
     * A common example of this is a table which contains first and last name columns next to each other, it is
     * intuitive that they would be linked together to multi-column sort. Another example, with a single column, is the
     * case where the data shown to the end user is not directly sortable itself (a column with images in it), but
     * there is some meta data than can be sorted (e.g. file name) - note that orthogonal data is an alternative method
     * that can be used for this.
     *
     * @link https://datatables.net/reference/option/columns.orderData
     * @param int|array|null $orderData
     *
     * @return $this
     */
    public function setOrderData($orderData) {
        $orderDataType = gettype($orderData);
        $validTypes = [
            'integer',
            'array',
            'NULL',
        ];
        $validTypesString = str_replace(' and ', ' or ', Text::toList($validTypes));
        if (is_array($orderData)) {
            Validator::getInstance()->checkKeysValueTypesOrFail($orderData, 'integer', 'integer', '$orderData');
        } elseif ($orderDataType === 'integer' && $orderData < 0) {
            throw new InvalidArgumentException("In \$orderData must be greater or equal 0. Found: '$orderData'.");
        } elseif (!in_array($orderDataType, $validTypes)) {
            throw new InvalidArgumentException("In \$orderData you can use only $validTypesString. Found: '$orderDataType'.");
        }
        $this->_config = Hash::insert($this->_config, 'orderData', $orderData);

        return $this;
    }

    /**
     * Getter method.
     * DataTables' primary order method (the ordering feature) makes use of data that has been cached in memory rather
     * than reading the data directly from the DOM every time an order is performed for performance reasons (reading
     * from the DOM is inherently slow). However, there are times when you do actually want to read directly from the
     * DOM, acknowledging that there will be a performance hit, for example when you have form elements in the table
     * and the end user can alter the values. This configuration option is provided to allow plug-ins to provide this
     * capability in DataTables.
     *
     * Please note that there are no columns.orderDataType plug-ins built into DataTables, they must be added
     * separately. See the DataTables sorting plug-ins page for further information.
     *
     * @link https://datatables.net/reference/option/columns.orderDataType
     * @link https://datatables.net/plug-ins/sorting/
     * @return string|null
     */
    public function getOrderDataType(): ?string {
        return Hash::get($this->_config, 'orderDataType');
    }

    /**
     * Setter method.
     * DataTables' primary order method (the ordering feature) makes use of data that has been cached in memory rather
     * than reading the data directly from the DOM every time an order is performed for performance reasons (reading
     * from the DOM is inherently slow). However, there are times when you do actually want to read directly from the
     * DOM, acknowledging that there will be a performance hit, for example when you have form elements in the table
     * and the end user can alter the values. This configuration option is provided to allow plug-ins to provide this
     * capability in DataTables.
     *
     * Please note that there are no columns.orderDataType plug-ins built into DataTables, they must be added
     * separately. See the DataTables sorting plug-ins page for further information.
     *
     * @link https://datatables.net/reference/option/columns.orderDataType
     * @link https://datatables.net/plug-ins/sorting/
     * @param string|null $orderDataType
     *
     * @return $this
     */
    public function setOrderDataType(?string $orderDataType) {
        Validator::getInstance()->inArrayOrFail($orderDataType, static::VALID_ORDER_DATA_TYPES);
        $this->_config = Hash::insert($this->_config, 'orderDataType', $orderDataType);

        return $this;
    }

    /**
     * Getter method.
     * You can control the default ordering direction, and even alter the behaviour of the order handler (i.e. only
     * allow ascending sorting etc) using this parameter.
     *
     * @link https://datatables.net/reference/option/columns.orderSequence
     * @return array
     */
    public function getOrderSequence(): ?array {
        return Hash::get($this->_config, 'orderSequence');
    }

    /**
     * Setter method.
     * You can control the default ordering direction, and even alter the behaviour of the order handler (i.e. only
     * allow ascending sorting etc) using this parameter.
     *
     * @link https://datatables.net/reference/option/columns.orderSequence
     * @param array $orderSequence
     *
     * @return $this
     */
    public function setOrderSequence(array $orderSequence = []) {
        Validator::getInstance()->checkKeysValueTypesOrFail($orderSequence, 'integer', 'string', '$orderSequence');
        foreach ($orderSequence as $item) {
            if (!in_array($item, ['asc', 'desc'])) {
                throw new InvalidArgumentException("In \$orderDataType you can use only 'asc' or 'desc'. Found: '$item'.");
            }
        }
        $this->_config = Hash::insert($this->_config, 'orderSequence', $orderSequence);

        return $this;
    }

    /**
     * Checker method.
     * Using this parameter, you can remove the end user's ability to order upon a column. This might be useful for
     * generated content columns, for example if you have 'Edit' or 'Delete' buttons in the table.
     *
     * Note that this option only affects the end user's ability to order a column. Developers are still able to order
     * a column using the order option or the order() method if required.
     *
     * @link https://datatables.net/reference/option/columns.orderable
     * @return bool|null
     */
    public function isOrderable(): ?bool {
        return Hash::get($this->_config, 'orderable');
    }

    /**
     * Setter method.
     * Using this parameter, you can remove the end user's ability to order upon a column. This might be useful for
     * generated content columns, for example if you have 'Edit' or 'Delete' buttons in the table.
     *
     * Note that this option only affects the end user's ability to order a column. Developers are still able to order
     * a column using the order option or the order() method if required.
     *
     * @link https://datatables.net/reference/option/columns.orderable
     * @param bool|null $orderable
     *
     * @return $this
     */
    public function setOrderable(?bool $orderable) {
        $this->_config = Hash::insert($this->_config, 'orderable', $orderable);

        return $this;
    }

    /**
     * Checker method.
     * Using this parameter, you can define if DataTables should include this column in the filterable data in the
     * table. You may want to use this option to disable search on generated columns such as 'Edit' and 'Delete'
     * buttons for example.
     *
     * @link https://datatables.net/reference/option/columns.searchable
     * @return bool|null
     */
    public function isSearchable(): ?bool {
        return Hash::get($this->_config, 'searchable');
    }

    /**
     * Setter method.
     * Using this parameter, you can define if DataTables should include this column in the filterable data in the
     * table. You may want to use this option to disable search on generated columns such as 'Edit' and 'Delete'
     * buttons for example.
     *
     * @link https://datatables.net/reference/option/columns.searchable
     * @param bool|null $searchable
     *
     * @return $this
     */
    public function setSearchable(?bool $searchable) {
        $this->_config = Hash::insert($this->_config, 'searchable', $searchable);

        return $this;
    }

    /**
     * Getter method.
     * The titles of columns are typically read directly from the DOM (from the cells in the THEAD element), but it can
     * often be useful to either override existing values, or have DataTables actually construct a header with column
     * titles for you (for example if there is not a THEAD element in the table before DataTables is constructed). This
     * option is available to provide that ability.
     *
     * Please note that when constructing a header, DataTables can only construct a simple header with a single cell
     * for each column. Complex headers with colspan and rowspan attributes must either already be defined in the
     * document, or be constructed using standard DOM / jQuery methods.
     *
     * @link https://datatables.net/reference/option/columns.title
     * @return string
     */
    public function getTitle(): string {
        return Hash::get($this->_config, 'title');
    }

    /**
     * Setter method.
     * The titles of columns are typically read directly from the DOM (from the cells in the THEAD element), but it can
     * often be useful to either override existing values, or have DataTables actually construct a header with column
     * titles for you (for example if there is not a THEAD element in the table before DataTables is constructed). This
     * option is available to provide that ability.
     *
     * Please note that when constructing a header, DataTables can only construct a simple header with a single cell
     * for each column. Complex headers with colspan and rowspan attributes must either already be defined in the
     * document, or be constructed using standard DOM / jQuery methods.
     *
     * @link https://datatables.net/reference/option/columns.title
     * @param string $title
     *
     * @return $this
     */
    public function setTitle(string $title) {
        $this->_config = Hash::insert($this->_config, 'title', $title);

        return $this;
    }

    /**
     * Getter method.
     * When operating in client-side processing mode, DataTables can process the data used for the display in each cell
     * in a manner suitable for the action being performed. For example, HTML tags will be removed from the strings
     * used for filter matching, while sort formatting may remove currency symbols to allow currency values to be
     * sorted numerically. The formatting action performed to normalise the data so it can be ordered and searched
     * depends upon the column's type.
     *
     * DataTables has a number of built in types which are automatically detected:
     *  - date - Date / time values. Note that DataTables' built in date parsing uses Javascript's Date.parse() method
     *    which supports only a very limited subset of dates. Additional date format support can be added through the
     *    use of plug-ins.
     *     - Sorting - sorted chronologically
     *     - Filtering - no effect
     *  - num - Simple number sorting.
     *     - Sorting - sorted numerically
     *     - Filtering - no effect
     *  - num-fmt - Numeric sorting of formatted numbers. Numbers which are formatted with thousands separators,
     *    currency symbols or a percentage indicator will be sorted numerically automatically by DataTables.
     *     - Supported built-in currency symbols are $, £, € and ¥.
     *     - Supported built-in thousands separators are ' and ,.
     *    Examples:
     *     - $100,000 - sorted as 100000
     *     - £10'000 - sorted as 10000
     *     - 5'000 - sorted as 5000
     *     - 40% - sorted as 40
     *     - Sorting - sorted numerically
     *     - Filtering - no effect
     *  - html-num - As per the num option, but with HTML tags also in the data.
     *     - Sorting - sorted numerically
     *     - Filtering - HTML tags removed from filtering string
     *  - html-num-fmt - As per the num-fmt option, but with HTML tags also in the data.
     *     - Sorting - sorted numerically
     *     - Filtering - HTML tags removed from filtering string
     *  - html - Basic string processing for HTML tags
     *     - Sorting - sorted with HTML tags removed
     *     - Filtering - HTML tags removed from filtering string
     *  - string - Fall back type if the data in the column does not match the requirements for the other data types
     *    (above).
     *     - Sorting - no effect
     *     - Filtering - no effect
     *
     * It is expected that the above options will cover the majority of data types used with DataTables, however, data
     * is flexible and comes in many forms, so additional types with different effects can be added through the use of
     * plug-ins. This provides the ability to sort almost any data format imaginable!
     *
     * As an optimisation, if you know the column type in advance, you can set the value using this option, saving
     * DataTables from running its auto detection routine.
     *
     * Please note that if you are using server-side processing (serverSide) this option has no effect since the
     * ordering and search actions are performed by a server-side script.
     *
     * @link https://datatables.net/reference/option/columns.type
     * @return string|null
     */
    public function getType(): ?string {
        return Hash::get($this->_config, 'type');
    }

    /**
     * Setter method.
     * When operating in client-side processing mode, DataTables can process the data used for the display in each cell
     * in a manner suitable for the action being performed. For example, HTML tags will be removed from the strings
     * used for filter matching, while sort formatting may remove currency symbols to allow currency values to be
     * sorted numerically. The formatting action performed to normalise the data so it can be ordered and searched
     * depends upon the column's type.
     *
     * DataTables has a number of built in types which are automatically detected:
     *  - date - Date / time values. Note that DataTables' built in date parsing uses Javascript's Date.parse() method
     *    which supports only a very limited subset of dates. Additional date format support can be added through the
     *    use of plug-ins.
     *     - Sorting - sorted chronologically
     *     - Filtering - no effect
     *  - num - Simple number sorting.
     *     - Sorting - sorted numerically
     *     - Filtering - no effect
     *  - num-fmt - Numeric sorting of formatted numbers. Numbers which are formatted with thousands separators,
     *    currency symbols or a percentage indicator will be sorted numerically automatically by DataTables.
     *     - Supported built-in currency symbols are $, £, € and ¥.
     *     - Supported built-in thousands separators are ' and ,.
     *    Examples:
     *     - $100,000 - sorted as 100000
     *     - £10'000 - sorted as 10000
     *     - 5'000 - sorted as 5000
     *     - 40% - sorted as 40
     *     - Sorting - sorted numerically
     *     - Filtering - no effect
     *  - html-num - As per the num option, but with HTML tags also in the data.
     *     - Sorting - sorted numerically
     *     - Filtering - HTML tags removed from filtering string
     *  - html-num-fmt - As per the num-fmt option, but with HTML tags also in the data.
     *     - Sorting - sorted numerically
     *     - Filtering - HTML tags removed from filtering string
     *  - html - Basic string processing for HTML tags
     *     - Sorting - sorted with HTML tags removed
     *     - Filtering - HTML tags removed from filtering string
     *  - string - Fall back type if the data in the column does not match the requirements for the other data types
     *    (above).
     *     - Sorting - no effect
     *     - Filtering - no effect
     *
     * It is expected that the above options will cover the majority of data types used with DataTables, however, data
     * is flexible and comes in many forms, so additional types with different effects can be added through the use of
     * plug-ins. This provides the ability to sort almost any data format imaginable!
     *
     * As an optimisation, if you know the column type in advance, you can set the value using this option, saving
     * DataTables from running its auto detection routine.
     *
     * Please note that if you are using server-side processing (serverSide) this option has no effect since the
     * ordering and search actions are performed by a server-side script.
     *
     * @link https://datatables.net/reference/option/columns.type
     * @param string|null $type
     *
     * @return $this
     */
    public function setType(?string $type) {
        Validator::getInstance()->inArrayOrFail($type, static::VALID_TYPES);
        $this->_config = Hash::insert($this->_config, 'type', $type);

        return $this;
    }

    /**
     * Checker method.
     * DataTables and show and hide columns dynamically through use of this option and the column().visible() /
     * columns().visible() methods. This option can be used to get the initial visibility state of the column, with the
     * API methods used to alter that state at a later time.
     *
     * This can be particularly useful if your table holds a large number of columns and you wish the user to have the
     * ability to control which columns they can see, or you have data in the table that the end user shouldn't see
     * (for example a database ID column).
     *
     * @link https://datatables.net/reference/option/columns.visible
     * @return bool|null
     */
    public function isVisible(): ?bool {
        return Hash::get($this->_config, 'visible');
    }

    /**
     * Setter method.
     * DataTables and show and hide columns dynamically through use of this option and the column().visible() /
     * columns().visible() methods. This option can be used to get the initial visibility state of the column, with the
     * API methods used to alter that state at a later time.
     *
     * This can be particularly useful if your table holds a large number of columns and you wish the user to have the
     * ability to control which columns they can see, or you have data in the table that the end user shouldn't see
     * (for example a database ID column).
     *
     * @link https://datatables.net/reference/option/columns.visible
     * @param bool|null $visible
     *
     * @return $this
     */
    public function setVisible(?bool $visible) {
        $this->_config = Hash::insert($this->_config, 'visible', $visible);

        return $this;
    }

    /**
     * Getter method.
     * This parameter can be used to define the width of a column, and may take any CSS value (3em, 20px etc).
     *
     * Please note that pixel perfect column width is virtually impossible to achieve in tables with dynamic content,
     * so do not be surprised if the width of the column if off by a few pixels from what you assign using this
     * property. Column width in tables depends upon many properties such as cell borders, table borders, the
     * border-collapse property, the content of the table and many other properties. Both DataTables and the browsers
     * attempt to lay the table out in an optimal manner taking this options all into account.
     *
     * @link https://datatables.net/reference/option/columns.width
     * @return string|null
     */
    public function getWidth(): ?string {
        return Hash::get($this->_config, 'width');
    }

    /**
     * Setter method.
     * This parameter can be used to define the width of a column, and may take any CSS value (3em, 20px etc).
     *
     * Please note that pixel perfect column width is virtually impossible to achieve in tables with dynamic content,
     * so do not be surprised if the width of the column if off by a few pixels from what you assign using this
     * property. Column width in tables depends upon many properties such as cell borders, table borders, the
     * border-collapse property, the content of the table and many other properties. Both DataTables and the browsers
     * attempt to lay the table out in an optimal manner taking this options all into account.
     *
     * @link https://datatables.net/reference/option/columns.width
     * @param string|null $width
     *
     * @return $this
     */
    public function setWidth(?string $width) {
        $this->_config = Hash::insert($this->_config, 'width', $width);

        return $this;
    }

}