From dd6615ffab2a5dcf5006523000862716e084e1d0 Mon Sep 17 00:00:00 2001 From: Makeila Lundy Date: Wed, 14 Mar 2018 11:44:02 -0700 Subject: [PATCH 01/18] initial commit --- src/fields/Table.php | 68 ++++-- .../fieldtypes/Table/settings.html | 65 ++++-- .../_includes/forms/editableTable.html | 194 +++++++++--------- 3 files changed, 197 insertions(+), 130 deletions(-) diff --git a/src/fields/Table.php b/src/fields/Table.php index 1845dad76c6..9bb01330d92 100644 --- a/src/fields/Table.php +++ b/src/fields/Table.php @@ -1,8 +1,8 @@ - * @since 3.0 + * @since 3.0 */ class Table extends Field { @@ -41,6 +41,18 @@ public static function displayName(): string // Properties // ========================================================================= + /** + * @var string|null Custom add row button label + */ + public $addRowLabel = 'Add a row'; + /** + * @var int|null Maximum number of Rows allowed + */ + public $maxRows; + /** + * @var int|null Minimum number of Rows allowed + */ + public $minRows; /** * @var array|null The columns that should be shown in the table */ @@ -66,10 +78,6 @@ public function init() { parent::init(); - if ($this->defaults === '') { - $this->defaults = []; - } - // Convert default date cell values to ISO8601 strings if (!empty($this->columns) && $this->defaults !== null) { foreach ($this->columns as $colId => $col) { @@ -82,6 +90,18 @@ public function init() } } + /** + * @inheritdoc + */ + public function rules() + { + $rules = parent::rules(); + $rules[] = [['minRows', 'maxRows'], 'integer', 'min' => 0]; + + return $rules; + } + + /** * @inheritdoc */ @@ -158,8 +178,10 @@ public function getSettingsHtml() Json::encode($this->columns, JSON_UNESCAPED_UNICODE).', '. Json::encode($this->defaults, JSON_UNESCAPED_UNICODE).', '. Json::encode($columnSettings, JSON_UNESCAPED_UNICODE). + ($this->maxRows ?: 'null'). ');'); + $columnsField = $view->renderTemplateMacro('_includes/forms', 'editableTableField', [ [ 'label' => Craft::t('app', 'Table Columns'), @@ -197,6 +219,7 @@ public function getSettingsHtml() */ public function getInputHtml($value, ElementInterface $element = null): string { + Craft::$app->getView()->registerAssetBundle(TimepickerAsset::class); $input = ''; @@ -303,8 +326,9 @@ public function getStaticHtml($value, ElementInterface $element): string /** * Normalizes a cell’s value. * - * @param string $type The cell type - * @param mixed $value The cell value + * @param string $type The cell type + * @param mixed $value The cell value + * * @return mixed * @see normalizeValue() */ @@ -343,9 +367,10 @@ private function _normalizeCellValue(string $type, $value) /** * Validates a cell’s value. * - * @param string $type The cell type - * @param mixed $value The cell value + * @param string $type The cell type + * @param mixed $value The cell value * @param string|null &$error The error text to set on the element + * * @return bool Whether the value is valid * @see normalizeValue() */ @@ -365,9 +390,10 @@ private function _validateCellValue(string $type, $value, string &$error = null) /** * Returns the field's input HTML. * - * @param mixed $value + * @param mixed $value * @param ElementInterface|null $element - * @param bool $static + * @param bool $static + * * @return string|null */ private function _getInputHtml($value, ElementInterface $element = null, bool $static) @@ -387,8 +413,10 @@ private function _getInputHtml($value, ElementInterface $element = null, bool $s // Explicitly set each cell value to an array with a 'value' key $checkForErrors = $element && $element->hasErrors($this->handle); + $count = 0; if (is_array($value)) { foreach ($value as &$row) { + $count = $count + 1; foreach ($this->columns as $colId => $col) { if (isset($row[$colId])) { $hasErrors = $checkForErrors && !$this->_validateCellValue($col['type'], $row[$colId]); @@ -401,6 +429,14 @@ private function _getInputHtml($value, ElementInterface $element = null, bool $s } } unset($row); + $minRows = $this->minRows; + $maxRows = $this->maxRows; + $staticRows = false; + + if($count >= $maxRows or $minRows === $maxRows){ + $staticRows = true; + } + $view = Craft::$app->getView(); $id = $view->formatInputId($this->handle); @@ -410,7 +446,9 @@ private function _getInputHtml($value, ElementInterface $element = null, bool $s 'name' => $this->handle, 'cols' => $this->columns, 'rows' => $value, - 'static' => $static + 'static' => $static, + 'staticRows' => $staticRows, + 'addRowLabel'=> $this->addRowLabel, ]); } -} +} \ No newline at end of file diff --git a/src/templates/_components/fieldtypes/Table/settings.html b/src/templates/_components/fieldtypes/Table/settings.html index bf95f4aa607..1f929f8ae7a 100644 --- a/src/templates/_components/fieldtypes/Table/settings.html +++ b/src/templates/_components/fieldtypes/Table/settings.html @@ -1,25 +1,54 @@ -{% import '_includes/forms' as forms %} - +{% import '_includes/forms' as forms %} {{ columnsField|raw }} {{ defaultsField|raw }} +{{ forms.textField({ +label: "Min Rows"|t('app'), +instructions: "The minimum number of rows the field is allowed to have."|t('app'), +id: 'minRows', +name: 'minRows', +value: field.minRows, +size: 3, +errors: field.getErrors('minRows') +}) }} + +{{ forms.textField({ +label: "Max Rows"|t('app'), +instructions: "The maximum number of rows the field is allowed to have."|t('app'), +id: 'maxRows', +name: 'maxRows', +value: field.maxRows, +size: 3, +errors: field.getErrors('maxRows') +}) }} + +{{ forms.textField({ +label: "Add Row Label"|t('app'), +instructions: "Insert the button label for adding a new row to the table."|t('app'), +id: 'addRowLabel', +name: 'addRowLabel', +value: field.addRowLabel, +size: 20, +errors: field.getErrors('addRowLabel') +}) }} + {% if craft.app.db.isMysql %} -
- {{ "Advanced"|t('app') }} - +
+{{ "Advanced"|t('app') }} + {% endif %} diff --git a/src/templates/_includes/forms/editableTable.html b/src/templates/_includes/forms/editableTable.html index 14cfb21797f..4b154ce2dc8 100644 --- a/src/templates/_includes/forms/editableTable.html +++ b/src/templates/_includes/forms/editableTable.html @@ -6,105 +6,105 @@ - - - {% for col in cols %} - - {% endfor %} - {% if not staticRows %} - - {% endif %} - - - - {% for rowId, row in rows %} - - {% for colId, col in cols %} - {% set cell = row[colId] ?? null %} - {% set value = cell.value is defined ? cell.value : cell %} - {% if col.type == 'heading' %} - - {% else %} - {% set hasErrors = cell.hasErrors ?? false %} - {% set cellName = name~'['~rowId~']['~colId~']' %} - {% set textual = (col.type in ['color', 'date', 'multiline', 'number', 'singleline', 'time']) %} - {% set isCode = col.code is defined or col.type == 'color' %} - - {% endif %} - {% endfor %} - {% if not staticRows %} - - - {% endif %} - - {% endfor %} - + {%- if block('attr') is defined %} {{ block('attr') }}{% endif %}> + + + {% for col in cols %} + + {% endfor %} + {% if not staticRows %} + + {% endif %} + + + +{% for rowId, row in rows %} + + {% for colId, col in cols %} + {% set cell = row[colId] ?? null %} + {% set value = cell.value is defined ? cell.value : cell %} + {% if col.type == 'heading' %} + + {% else %} + {% set hasErrors = cell.hasErrors ?? false %} + {% set cellName = name~'['~rowId~']['~colId~']' %} + {% set textual = (col.type in ['color', 'date', 'multiline', 'number', 'singleline', 'time']) %} + {% set isCode = col.code is defined or col.type == 'color' %} + + {% endif %} + {% endfor %} + {% if not staticRows %} + + + {% endif %} + +{% endfor %} +
- {%- if col.heading is defined and col.heading %}{{ col.heading }}{% else %} {% endif %} - {%- if col.info is defined -%} - {{ col.info|md|raw }} - {%- endif -%} -
{{ value|raw }} - {%- switch col.type -%} - {%- case 'checkbox' -%} - {% include "_includes/forms/checkbox" with { - name: cellName, - value: col.value ?? 1, - checked: value is not empty, - disabled: static - } only %} - {%- case 'color' -%} - {% include "_includes/forms/color" with { - name: cellName, - value: value, - small: true, - disabled: static - } only %} - {%- case 'date' -%} - {% include "_includes/forms/date" with { - name: cellName, - value: value, - disabled: static - } only %} - {%- case 'lightswitch' -%} - {% include "_includes/forms/lightswitch" with { - name: cellName, - on: value, - value: col.value ?? 1, - small: true, - disabled: static - } only %} - {% case 'select' -%} - {% include "_includes/forms/select" with { - class: 'small', - name: cellName, - options: cell.options ?? col.options, - value: value, - disabled: static - } only %} - {%- case 'time' -%} - {% include "_includes/forms/time" with { - name: cellName, - value: value, - disabled: static - } only %} - {%- default -%} - - {%- endswitch -%} -
+ {%- if col.heading is defined and col.heading %}{{ col.heading }}{% else %} {% endif %} + {%- if col.info is defined -%} + {{ col.info|md|raw }} + {%- endif -%} +
{{ value|raw }} + {%- switch col.type -%} + {%- case 'checkbox' -%} + {% include "_includes/forms/checkbox" with { + name: cellName, + value: col.value ?? 1, + checked: value is not empty, + disabled: static + } only %} + {%- case 'color' -%} + {% include "_includes/forms/color" with { + name: cellName, + value: value, + small: true, + disabled: static + } only %} + {%- case 'date' -%} + {% include "_includes/forms/date" with { + name: cellName, + value: value, + disabled: static + } only %} + {%- case 'lightswitch' -%} + {% include "_includes/forms/lightswitch" with { + name: cellName, + on: value, + value: col.value ?? 1, + small: true, + disabled: static + } only %} + {% case 'select' -%} + {% include "_includes/forms/select" with { + class: 'small', + name: cellName, + options: cell.options ?? col.options, + value: value, + disabled: static + } only %} + {%- case 'time' -%} + {% include "_includes/forms/time" with { + name: cellName, + value: value, + disabled: static + } only %} + {%- default -%} + + {%- endswitch -%} +
-{% if not staticRows %} -
{{ addRowLabel is defined ? addRowLabel : "Add a row"|t('app') }}
+{% if not staticRows %} +
{{ addRowLabel is defined ? addRowLabel : "Add a row"|t('app') }}
{% endif %} {% if initJs %} - {% set jsId = id|namespaceInputId|e('js') %} - {% set jsName = name|namespaceInputName|e('js') %} - {% set jsCols = cols|json_encode %} - {% set defaultValues = defaultValues ?? null %} - {% js %} - new Craft.EditableTable("{{ jsId }}", "{{ jsName }}", {{ jsCols|raw }}, { - defaultValues: {{ defaultValues ? defaultValues|json_encode|raw : '{}' }} - }); - {% endjs %} +{% set jsId = id|namespaceInputId|e('js') %} +{% set jsName = name|namespaceInputName|e('js') %} +{% set jsCols = cols|json_encode %} +{% set defaultValues = defaultValues ?? null %} +{% js %} +new Craft.EditableTable("{{ jsId }}", "{{ jsName }}", {{ jsCols|raw }}, { +defaultValues: {{ defaultValues ? defaultValues|json_encode|raw : '{}' }} +}); +{% endjs %} {% endif %} From c9737aa89336886dbdd692ebb5039193ff8e9344 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 14 Mar 2018 13:32:36 -0700 Subject: [PATCH 02/18] clean up --- .../_includes/forms/editableTable.html | 194 +++++++++--------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/src/templates/_includes/forms/editableTable.html b/src/templates/_includes/forms/editableTable.html index 4b154ce2dc8..80c66b317bd 100644 --- a/src/templates/_includes/forms/editableTable.html +++ b/src/templates/_includes/forms/editableTable.html @@ -5,106 +5,106 @@ {%- set initJs = not static and (initJs ?? true) -%} - - - - {% for col in cols %} - - {% endfor %} - {% if not staticRows %} - - {% endif %} - - - -{% for rowId, row in rows %} - - {% for colId, col in cols %} - {% set cell = row[colId] ?? null %} - {% set value = cell.value is defined ? cell.value : cell %} - {% if col.type == 'heading' %} - - {% else %} - {% set hasErrors = cell.hasErrors ?? false %} - {% set cellName = name~'['~rowId~']['~colId~']' %} - {% set textual = (col.type in ['color', 'date', 'multiline', 'number', 'singleline', 'time']) %} - {% set isCode = col.code is defined or col.type == 'color' %} - - {% endif %} - {% endfor %} - {% if not staticRows %} - - - {% endif %} - -{% endfor %} - +
- {%- if col.heading is defined and col.heading %}{{ col.heading }}{% else %} {% endif %} - {%- if col.info is defined -%} - {{ col.info|md|raw }} - {%- endif -%} -
{{ value|raw }} - {%- switch col.type -%} - {%- case 'checkbox' -%} - {% include "_includes/forms/checkbox" with { - name: cellName, - value: col.value ?? 1, - checked: value is not empty, - disabled: static - } only %} - {%- case 'color' -%} - {% include "_includes/forms/color" with { - name: cellName, - value: value, - small: true, - disabled: static - } only %} - {%- case 'date' -%} - {% include "_includes/forms/date" with { - name: cellName, - value: value, - disabled: static - } only %} - {%- case 'lightswitch' -%} - {% include "_includes/forms/lightswitch" with { - name: cellName, - on: value, - value: col.value ?? 1, - small: true, - disabled: static - } only %} - {% case 'select' -%} - {% include "_includes/forms/select" with { - class: 'small', - name: cellName, - options: cell.options ?? col.options, - value: value, - disabled: static - } only %} - {%- case 'time' -%} - {% include "_includes/forms/time" with { - name: cellName, - value: value, - disabled: static - } only %} - {%- default -%} - - {%- endswitch -%} -
+ {%- if block('attr') is defined %} {{ block('attr') }}{% endif %} + + + {% for col in cols %} + + {% endfor %} + {% if not staticRows %} + + {% endif %} + + + + {% for rowId, row in rows %} + + {% for colId, col in cols %} + {% set cell = row[colId] ?? null %} + {% set value = cell.value is defined ? cell.value : cell %} + {% if col.type == 'heading' %} + + {% else %} + {% set hasErrors = cell.hasErrors ?? false %} + {% set cellName = name~'['~rowId~']['~colId~']' %} + {% set textual = (col.type in ['color', 'date', 'multiline', 'number', 'singleline', 'time']) %} + {% set isCode = col.code is defined or col.type == 'color' %} + + {% endif %} + {% endfor %} + {% if not staticRows %} + + + {% endif %} + + {% endfor %} +
+ {%- if col.heading is defined and col.heading %}{{ col.heading }}{% else %} {% endif %} + {%- if col.info is defined -%} + {{ col.info|md|raw }} + {%- endif -%} +
{{ value|raw }} + {%- switch col.type -%} + {%- case 'checkbox' -%} + {% include "_includes/forms/checkbox" with { + name: cellName, + value: col.value ?? 1, + checked: value is not empty, + disabled: static + } only %} + {%- case 'color' -%} + {% include "_includes/forms/color" with { + name: cellName, + value: value, + small: true, + disabled: static + } only %} + {%- case 'date' -%} + {% include "_includes/forms/date" with { + name: cellName, + value: value, + disabled: static + } only %} + {%- case 'lightswitch' -%} + {% include "_includes/forms/lightswitch" with { + name: cellName, + on: value, + value: col.value ?? 1, + small: true, + disabled: static + } only %} + {% case 'select' -%} + {% include "_includes/forms/select" with { + class: 'small', + name: cellName, + options: cell.options ?? col.options, + value: value, + disabled: static + } only %} + {%- case 'time' -%} + {% include "_includes/forms/time" with { + name: cellName, + value: value, + disabled: static + } only %} + {%- default -%} + + {%- endswitch -%} +
{% if not staticRows %} -
{{ addRowLabel is defined ? addRowLabel : "Add a row"|t('app') }}
+
{{ addRowLabel is defined ? addRowLabel : "Add a row"|t('app') }}
{% endif %} {% if initJs %} -{% set jsId = id|namespaceInputId|e('js') %} -{% set jsName = name|namespaceInputName|e('js') %} -{% set jsCols = cols|json_encode %} -{% set defaultValues = defaultValues ?? null %} -{% js %} -new Craft.EditableTable("{{ jsId }}", "{{ jsName }}", {{ jsCols|raw }}, { -defaultValues: {{ defaultValues ? defaultValues|json_encode|raw : '{}' }} -}); -{% endjs %} + {% set jsId = id|namespaceInputId|e('js') %} + {% set jsName = name|namespaceInputName|e('js') %} + {% set jsCols = cols|json_encode %} + {% set defaultValues = defaultValues ?? null %} + {% js %} + new Craft.EditableTable("{{ jsId }}", "{{ jsName }}", {{ jsCols|raw }}, { + defaultValues: {{ defaultValues ? defaultValues|json_encode|raw : '{}' }} + }); + {% endjs %} {% endif %} From 4e06d5edcc764158b280ca50c9c55b4d672c462f Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 14 Mar 2018 13:33:25 -0700 Subject: [PATCH 03/18] clean up --- src/templates/_includes/forms/editableTable.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/templates/_includes/forms/editableTable.html b/src/templates/_includes/forms/editableTable.html index 80c66b317bd..d79b82fa1f3 100644 --- a/src/templates/_includes/forms/editableTable.html +++ b/src/templates/_includes/forms/editableTable.html @@ -86,8 +86,8 @@ {% endif %} {% endfor %} {% if not staticRows %} - - + + {% endif %} {% endfor %} From 3e4e3edd8a1c3282d8c510d08e93bb2e8e962300 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 19 Mar 2018 15:34:30 -0700 Subject: [PATCH 04/18] Min Max row progress The table now counts the number of rows, and maxRows is pulled into the js from the table field settings. --- src/fields/Table.php | 15 +------ .../_includes/forms/editableTable.html | 9 ++-- src/web/assets/cp/dist/js/Craft.js | 43 ++++++++++++++++--- src/web/assets/cp/dist/js/Craft.min.js | 2 +- src/web/assets/cp/dist/js/Craft.min.js.map | 2 +- src/web/assets/cp/src/js/EditableTable.js | 41 +++++++++++++++--- 6 files changed, 80 insertions(+), 32 deletions(-) diff --git a/src/fields/Table.php b/src/fields/Table.php index 9bb01330d92..c800a09f279 100644 --- a/src/fields/Table.php +++ b/src/fields/Table.php @@ -178,7 +178,6 @@ public function getSettingsHtml() Json::encode($this->columns, JSON_UNESCAPED_UNICODE).', '. Json::encode($this->defaults, JSON_UNESCAPED_UNICODE).', '. Json::encode($columnSettings, JSON_UNESCAPED_UNICODE). - ($this->maxRows ?: 'null'). ');'); @@ -413,10 +412,8 @@ private function _getInputHtml($value, ElementInterface $element = null, bool $s // Explicitly set each cell value to an array with a 'value' key $checkForErrors = $element && $element->hasErrors($this->handle); - $count = 0; if (is_array($value)) { foreach ($value as &$row) { - $count = $count + 1; foreach ($this->columns as $colId => $col) { if (isset($row[$colId])) { $hasErrors = $checkForErrors && !$this->_validateCellValue($col['type'], $row[$colId]); @@ -429,14 +426,6 @@ private function _getInputHtml($value, ElementInterface $element = null, bool $s } } unset($row); - $minRows = $this->minRows; - $maxRows = $this->maxRows; - $staticRows = false; - - if($count >= $maxRows or $minRows === $maxRows){ - $staticRows = true; - } - $view = Craft::$app->getView(); $id = $view->formatInputId($this->handle); @@ -446,9 +435,9 @@ private function _getInputHtml($value, ElementInterface $element = null, bool $s 'name' => $this->handle, 'cols' => $this->columns, 'rows' => $value, + 'maxRows' => $this->maxRows, 'static' => $static, - 'staticRows' => $staticRows, - 'addRowLabel'=> $this->addRowLabel, + 'addRowLabel'=> $this->addRowLabel ]); } } \ No newline at end of file diff --git a/src/templates/_includes/forms/editableTable.html b/src/templates/_includes/forms/editableTable.html index d79b82fa1f3..79a5602536d 100644 --- a/src/templates/_includes/forms/editableTable.html +++ b/src/templates/_includes/forms/editableTable.html @@ -5,8 +5,8 @@ {%- set initJs = not static and (initJs ?? true) -%} - - {%- if block('attr') is defined %} {{ block('attr') }}{% endif %} +
{% for col in cols %} @@ -93,7 +93,7 @@ {% endfor %}
-{% if not staticRows %} +{% if not staticRows %}
{{ addRowLabel is defined ? addRowLabel : "Add a row"|t('app') }}
{% endif %} @@ -101,10 +101,11 @@ {% set jsId = id|namespaceInputId|e('js') %} {% set jsName = name|namespaceInputName|e('js') %} {% set jsCols = cols|json_encode %} + {% set jsMaxRows = maxRows %} {% set defaultValues = defaultValues ?? null %} {% js %} new Craft.EditableTable("{{ jsId }}", "{{ jsName }}", {{ jsCols|raw }}, { defaultValues: {{ defaultValues ? defaultValues|json_encode|raw : '{}' }} - }); + }, {{ jsMaxRows }}); {% endjs %} {% endif %} diff --git a/src/web/assets/cp/dist/js/Craft.js b/src/web/assets/cp/dist/js/Craft.js index e1c1476923d..a604ed294ae 100644 --- a/src/web/assets/cp/dist/js/Craft.js +++ b/src/web/assets/cp/dist/js/Craft.js @@ -1,4 +1,4 @@ -/*! - 2018-03-12 */ +/*! - 2018-03-19 */ (function($){ /** global: Craft */ @@ -12591,6 +12591,8 @@ Craft.EditableTable = Garnish.Base.extend( columns: null, sorter: null, biggestId: -1, + maxRows: null, + rowCount: null, $table: null, $tbody: null, @@ -12598,12 +12600,14 @@ Craft.EditableTable = Garnish.Base.extend( radioCheckboxes: null, - init: function(id, baseName, columns, settings) { + init: function(id, baseName, columns, settings, maxRows) { this.id = id; this.baseName = baseName; this.columns = columns; this.setSettings(settings, Craft.EditableTable.defaults); this.radioCheckboxes = {}; + this.maxRows = maxRows; + this.rowCount = 0; this.$table = $('#' + id); this.$tbody = this.$table.children('tbody'); @@ -12653,7 +12657,36 @@ Craft.EditableTable = Garnish.Base.extend( } }, + canAddRow: function() { + if (this.rowCount >= this.maxRows) { + this.removeListener(this.$addRowBtn, 'activate'); + return false; + } + this.removeListener(this.$addRowBtn, 'activate'); + this.$addRowBtn = this.$table.next('.add'); + this.addListener(this.$addRowBtn, 'activate', 'addRow'); + return true; + }, + + deleteRow: function(row) { + + this.sorter.removeItems(row.$tr); + row.$tr.remove(); + this.rowCount--; + console.log("Row count: ", this.rowCount); + + // onDeleteRow callback + this.settings.onDeleteRow(row.$tr); + }, + addRow: function() { + if (!this.canAddRow()) { + return; + } + + this.rowCount++; + console.log("Row count: ", this.rowCount); + var rowId = this.settings.rowIdPrefix + (this.biggestId + 1), $tr = this.createRow(rowId, this.columns, this.baseName, $.extend({}, this.settings.defaultValues)); @@ -12977,11 +13010,7 @@ Craft.EditableTable.Row = Garnish.Base.extend( }, deleteRow: function() { - this.table.sorter.removeItems(this.$tr); - this.$tr.remove(); - - // onDeleteRow callback - this.table.settings.onDeleteRow(this.$tr); + this.table.deleteRow(this); } }, { diff --git a/src/web/assets/cp/dist/js/Craft.min.js b/src/web/assets/cp/dist/js/Craft.min.js index 921eea1498c..49b01276789 100644 --- a/src/web/assets/cp/dist/js/Craft.min.js +++ b/src/web/assets/cp/dist/js/Craft.min.js @@ -1,2 +1,2 @@ -!function(t){t.extend(Craft,{navHeight:48,asciiCharMap:{a:["à","á","ả","ã","ạ","ă","ắ","ằ","ẳ","ẵ","ặ","â","ấ","ầ","ẩ","ẫ","ậ","ä","ā","ą","å","α","ά","ἀ","ἁ","ἂ","ἃ","ἄ","ἅ","ἆ","ἇ","ᾀ","ᾁ","ᾂ","ᾃ","ᾄ","ᾅ","ᾆ","ᾇ","ὰ","ά","ᾰ","ᾱ","ᾲ","ᾳ","ᾴ","ᾶ","ᾷ","а","أ"],b:["б","β","Ъ","Ь","ب"],c:["ç","ć","č","ĉ","ċ"],d:["ď","ð","đ","ƌ","ȡ","ɖ","ɗ","ᵭ","ᶁ","ᶑ","д","δ","د","ض"],e:["é","è","ẻ","ẽ","ẹ","ê","ế","ề","ể","ễ","ệ","ë","ē","ę","ě","ĕ","ė","ε","έ","ἐ","ἑ","ἒ","ἓ","ἔ","ἕ","ὲ","έ","е","ё","э","є","ə"],f:["ф","φ","ف"],g:["ĝ","ğ","ġ","ģ","г","ґ","γ","ج"],h:["ĥ","ħ","η","ή","ح","ه"],i:["í","ì","ỉ","ĩ","ị","î","ï","ī","ĭ","į","ı","ι","ί","ϊ","ΐ","ἰ","ἱ","ἲ","ἳ","ἴ","ἵ","ἶ","ἷ","ὶ","ί","ῐ","ῑ","ῒ","ΐ","ῖ","ῗ","і","ї","и"],j:["ĵ","ј","Ј"],k:["ķ","ĸ","к","κ","Ķ","ق","ك"],l:["ł","ľ","ĺ","ļ","ŀ","л","λ","ل"],m:["м","μ","م"],n:["ñ","ń","ň","ņ","ʼn","ŋ","ν","н","ن"],o:["ó","ò","ỏ","õ","ọ","ô","ố","ồ","ổ","ỗ","ộ","ơ","ớ","ờ","ở","ỡ","ợ","ø","ō","ő","ŏ","ο","ὀ","ὁ","ὂ","ὃ","ὄ","ὅ","ὸ","ό","ö","о","و","θ"],p:["п","π"],r:["ŕ","ř","ŗ","р","ρ","ر"],s:["ś","š","ş","с","σ","ș","ς","س","ص"],t:["ť","ţ","т","τ","ț","ت","ط"],u:["ú","ù","ủ","ũ","ụ","ư","ứ","ừ","ử","ữ","ự","ü","û","ū","ů","ű","ŭ","ų","µ","у"],v:["в"],w:["ŵ","ω","ώ"],x:["χ"],y:["ý","ỳ","ỷ","ỹ","ỵ","ÿ","ŷ","й","ы","υ","ϋ","ύ","ΰ","ي"],z:["ź","ž","ż","з","ζ","ز"],aa:["ع"],ae:["æ"],ch:["ч"],dj:["ђ","đ"],dz:["џ"],gh:["غ"],kh:["х","خ"],lj:["љ"],nj:["њ"],oe:["œ"],ps:["ψ"],sh:["ш"],shch:["щ"],ss:["ß"],th:["þ","ث","ذ","ظ"],ts:["ц"],ya:["я"],yu:["ю"],zh:["ж"],"(c)":["©"],A:["Á","À","Ả","Ã","Ạ","Ă","Ắ","Ằ","Ẳ","Ẵ","Ặ","Â","Ấ","Ầ","Ẩ","Ẫ","Ậ","Ä","Å","Ā","Ą","Α","Ά","Ἀ","Ἁ","Ἂ","Ἃ","Ἄ","Ἅ","Ἆ","Ἇ","ᾈ","ᾉ","ᾊ","ᾋ","ᾌ","ᾍ","ᾎ","ᾏ","Ᾰ","Ᾱ","Ὰ","Ά","ᾼ","А"],B:["Б","Β"],C:["Ć","Č","Ĉ","Ċ"],D:["Ď","Ð","Đ","Ɖ","Ɗ","Ƌ","ᴅ","ᴆ","Д","Δ"],E:["É","È","Ẻ","Ẽ","Ẹ","Ê","Ế","Ề","Ể","Ễ","Ệ","Ë","Ē","Ę","Ě","Ĕ","Ė","Ε","Έ","Ἐ","Ἑ","Ἒ","Ἓ","Ἔ","Ἕ","Έ","Ὲ","Е","Ё","Э","Є","Ə"],F:["Ф","Φ"],G:["Ğ","Ġ","Ģ","Г","Ґ","Γ"],H:["Η","Ή"],I:["Í","Ì","Ỉ","Ĩ","Ị","Î","Ï","Ī","Ĭ","Į","İ","Ι","Ί","Ϊ","Ἰ","Ἱ","Ἳ","Ἴ","Ἵ","Ἶ","Ἷ","Ῐ","Ῑ","Ὶ","Ί","И","І","Ї"],K:["К","Κ"],L:["Ĺ","Ł","Л","Λ","Ļ"],M:["М","Μ"],N:["Ń","Ñ","Ň","Ņ","Ŋ","Н","Ν"],O:["Ó","Ò","Ỏ","Õ","Ọ","Ô","Ố","Ồ","Ổ","Ỗ","Ộ","Ơ","Ớ","Ờ","Ở","Ỡ","Ợ","Ö","Ø","Ō","Ő","Ŏ","Ο","Ό","Ὀ","Ὁ","Ὂ","Ὃ","Ὄ","Ὅ","Ὸ","Ό","О","Θ","Ө"],P:["П","Π"],R:["Ř","Ŕ","Р","Ρ"],S:["Ş","Ŝ","Ș","Š","Ś","С","Σ"],T:["Ť","Ţ","Ŧ","Ț","Т","Τ"],U:["Ú","Ù","Ủ","Ũ","Ụ","Ư","Ứ","Ừ","Ử","Ữ","Ự","Û","Ü","Ū","Ů","Ű","Ŭ","Ų","У"],V:["В"],W:["Ω","Ώ"],X:["Χ"],Y:["Ý","Ỳ","Ỷ","Ỹ","Ỵ","Ÿ","Ῠ","Ῡ","Ὺ","Ύ","Ы","Й","Υ","Ϋ"],Z:["Ź","Ž","Ż","З","Ζ"],AE:["Æ"],CH:["Ч"],DJ:["Ђ"],DZ:["Џ"],KH:["Х"],LJ:["Љ"],NJ:["Њ"],PS:["Ψ"],SH:["Ш"],SHCH:["Щ"],SS:["ẞ"],TH:["Þ"],TS:["Ц"],YA:["Я"],YU:["Ю"],ZH:["Ж"]," ":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "]},t:function(t,e,i){if(void 0!==Craft.translations[t]&&void 0!==Craft.translations[t][e]&&(e=Craft.translations[t][e]),i)for(var s in i)i.hasOwnProperty(s)&&(e=e.replace("{"+s+"}",i[s]));return e},formatDate:function(e){return"object"!=typeof e&&(e=new Date(e)),t.datepicker.formatDate(Craft.datepickerOptions.dateFormat,e)},formatNumber:function(t,e){return void 0===e&&(e=",.0f"),d3.formatLocale(d3FormatLocaleDefinition).format(e)(t)},escapeHtml:function(e){return t("
").text(e).html()},getText:function(e){return t("
").html(e).text()},encodeUriComponent:function(t){t=encodeURIComponent(t);var e={"!":"%21","*":"%2A","'":"%27","(":"%28",")":"%29"};for(var i in e){var s=new RegExp("\\"+i,"g");t=t.replace(s,e[i])}return t},selectFullValue:function(e){var i=t(e),s=i.val();if(void 0!==i[0].setSelectionRange){var n=2*s.length;i[0].setSelectionRange(0,n)}else i.val(s)},formatInputId:function(t){return this.rtrim(t.replace(/[\[\]\\]+/g,"-"),"-")},getUrl:function(e,i,s){if("string"!=typeof e&&(e=""),-1!==e.search("://")||"/"===e[0])return e;e=Craft.trim(e,"/");var n="";if(t.isPlainObject(i)){var a=[];for(var r in i)if(i.hasOwnProperty(r)){var o=i[r];"#"===r?n=o:null!==o&&""!==o&&a.push(r+"="+o)}i=a}i=Garnish.isArray(i)?i.join("&"):Craft.trim(i,"&?");var l=e.indexOf("?");-1!==l&&(i=e.substr(l+1)+(i?"&"+i:""),e=e.substr(0,l));var h;if(s){if(h=s,e){var d=h.match(/[&\?]p=[^&]+/);d&&(h=h.replace(d[0],d[0]+"/"+e),e="")}}else h=Craft.baseUrl;if(-1!==(l=h.indexOf("?"))&&(i=h.substr(l+1)+(i?"&"+i:""),h=h.substr(0,l)),!Craft.omitScriptNameInUrls&&e)if(Craft.usePathInfo)-1===h.search(Craft.scriptName)&&(h=Craft.rtrim(h,"/")+"/"+Craft.scriptName);else{if(i&&"p="===i.substr(0,2)){var c,u=i.indexOf("&");-1!==u?(c=i.substring(2,u),i=i.substr(u+1)):(c=i.substr(2),i=null),e=(c=Craft.rtrim(c))+(e?"/"+e:"")}i="p="+e+(i?"&"+i:""),e=null}return e&&(h=Craft.rtrim(h,"/")+"/"+e),i&&(h+="?"+i),n&&(h+="#"+n),h},getCpUrl:function(t,e){return this.getUrl(t,e,Craft.baseCpUrl)},getSiteUrl:function(t,e){return this.getUrl(t,e,Craft.baseSiteUrl)},getActionUrl:function(t,e){return Craft.getUrl(t,e,Craft.actionUrl)},redirectTo:function(t){document.location.href=this.getUrl(t)},getCsrfInput:function(){return Craft.csrfTokenName?'':""},postActionRequest:function(e,i,s,n){"function"==typeof i&&(n=s,s=i,i={});var a={};Craft.csrfTokenValue&&Craft.csrfTokenName&&(a["X-CSRF-Token"]=Craft.csrfTokenValue);var r=t.ajax(t.extend({url:Craft.getActionUrl(e),type:"POST",dataType:"json",headers:a,data:i,success:s,error:function(t,e){s&&s(null,e,t)},complete:function(t,e){"success"!==e&&(void 0!==Craft.cp?Craft.cp.displayError():alert(Craft.t("app","An unknown error occurred.")))}},n));return n&&"function"==typeof n.send&&n.send(r),r},_waitingOnAjax:!1,_ajaxQueue:[],queueActionRequest:function(t,e,i,s){"function"==typeof e&&(s=i,i=e,e=void 0),Craft._ajaxQueue.push([t,e,i,s]),Craft._waitingOnAjax||Craft._postNextActionRequestInQueue()},_postNextActionRequestInQueue:function(){Craft._waitingOnAjax=!0;var t=Craft._ajaxQueue.shift();Craft.postActionRequest(t[0],t[1],function(e,i,s){t[2]&&"function"==typeof t[2]&&t[2](e,i,s),Craft._ajaxQueue.length?Craft._postNextActionRequestInQueue():Craft._waitingOnAjax=!1},t[3])},stringToArray:function(e){if("string"!=typeof e)return e;for(var i=e.split(","),s=0;s=32&&s<128)e+=n;else for(var a in Craft.asciiCharMap)if(Craft.asciiCharMap.hasOwnProperty(a))for(var r=0;r]*href="(?:'+s.join("|")+')".*?><\/script>',"g");e=e.replace(r,"")}t("head").append(e)}},appendFootHtml:function(e){if(e){var i=t("script[src]");if(i.length){for(var s=[],n=0;n]*src="(?:'+s.join("|")+')".*?><\/script>',"g");e=e.replace(r,"")}Garnish.$bod.append(e)}},initUiElements:function(e){t(".grid",e).grid(),t(".info",e).infoicon(),t(".checkbox-select",e).checkboxselect(),t(".fieldtoggle",e).fieldtoggle(),t(".lightswitch",e).lightswitch(),t(".nicetext",e).nicetext(),t(".pill",e).pill(),t(".formsubmit",e).formsubmit(),t(".menubtn",e).menubtn()},_elementIndexClasses:{},_elementSelectorModalClasses:{},_elementEditorClasses:{},registerElementIndexClass:function(t,e){if(void 0!==this._elementIndexClasses[t])throw"An element index class has already been registered for the element type “"+t+"”.";this._elementIndexClasses[t]=e},registerElementSelectorModalClass:function(t,e){if(void 0!==this._elementSelectorModalClasses[t])throw"An element selector modal class has already been registered for the element type “"+t+"”.";this._elementSelectorModalClasses[t]=e},registerElementEditorClass:function(t,e){if(void 0!==this._elementEditorClasses[t])throw"An element editor class has already been registered for the element type “"+t+"”.";this._elementEditorClasses[t]=e},createElementIndex:function(t,e,i){return new(void 0!==this._elementIndexClasses[t]?this._elementIndexClasses[t]:Craft.BaseElementIndex)(t,e,i)},createElementSelectorModal:function(t,e){return new(void 0!==this._elementSelectorModalClasses[t]?this._elementSelectorModalClasses[t]:Craft.BaseElementSelectorModal)(t,e)},createElementEditor:function(t,e,i){return new(void 0!==this._elementEditorClasses[t]?this._elementEditorClasses[t]:Craft.BaseElementEditor)(e,i)},getLocalStorage:function(t,e){return t="Craft-"+Craft.systemUid+"."+t,"undefined"!=typeof localStorage&&void 0!==localStorage[t]?JSON.parse(localStorage[t]):e},setLocalStorage:function(t,e){if("undefined"!=typeof localStorage){t="Craft-"+Craft.systemUid+"."+t;try{localStorage[t]=JSON.stringify(e)}catch(t){}}},getElementInfo:function(e){var i=t(e);return i.hasClass("element")||(i=i.find(".element:first")),{id:i.data("id"),siteId:i.data("site-id"),label:i.data("label"),status:i.data("status"),url:i.data("url"),hasThumb:i.hasClass("hasthumb"),$element:i}},setElementSize:function(e,i){var s=t(e);if("small"!==i&&"large"!==i&&(i="small"),!s.hasClass(i)){var n="small"===i?"large":"small";if(s.addClass(i).removeClass(n),s.hasClass("hasthumb")){var a=s.find("> .elementthumb > img"),r=t("",{sizes:("small"===i?"30":"100")+"px",srcset:a.attr("srcset")||a.attr("data-pfsrcset")});a.replaceWith(r),picturefill({elements:[r[0]]})}}}}),t.extend(t.fn,{animateLeft:function(t,e,i,s){return"ltr"===Craft.orientation?this.velocity({left:t},e,i,s):this.velocity({right:t},e,i,s)},animateRight:function(t,e,i,s){return"ltr"===Craft.orientation?this.velocity({right:t},e,i,s):this.velocity({left:t},e,i,s)},disable:function(){return this.each(function(){var e=t(this);e.addClass("disabled"),e.data("activatable")&&e.removeAttr("tabindex")})},enable:function(){return this.each(function(){var e=t(this);e.removeClass("disabled"),e.data("activatable")&&e.attr("tabindex","0")})},grid:function(){return this.each(function(){var e=t(this),i={};e.data("item-selector")&&(i.itemSelector=e.data("item-selector")),e.data("cols")&&(i.cols=parseInt(e.data("cols"))),e.data("max-cols")&&(i.maxCols=parseInt(e.data("max-cols"))),e.data("min-col-width")&&(i.minColWidth=parseInt(e.data("min-col-width"))),e.data("mode")&&(i.mode=e.data("mode")),e.data("fill-mode")&&(i.fillMode=e.data("fill-mode")),e.data("col-class")&&(i.colClass=e.data("col-class")),e.data("snap-to-grid")&&(i.snapToGrid=!!e.data("snap-to-grid")),new Craft.Grid(this,i)})},infoicon:function(){return this.each(function(){new Craft.InfoIcon(this)})},checkboxselect:function(){return this.each(function(){t.data(this,"checkboxselect")||new Garnish.CheckboxSelect(this)})},fieldtoggle:function(){return this.each(function(){t.data(this,"fieldtoggle")||new Craft.FieldToggle(this)})},lightswitch:function(e,i,s){return"settings"===e?("string"==typeof i?(e={})[i]=s:e=i,this.each(function(){var i=t.data(this,"lightswitch");i&&i.setSettings(e)})):(t.isPlainObject(e)||(e={}),this.each(function(){var i=t.extend({},e);Garnish.hasAttr(this,"data-value")&&(i.value=t(this).attr("data-value")),t.data(this,"lightswitch")||new Craft.LightSwitch(this,i)}))},nicetext:function(){return this.each(function(){t.data(this,"nicetext")||new Garnish.NiceText(this)})},pill:function(){return this.each(function(){t.data(this,"pill")||new Garnish.Pill(this)})},formsubmit:function(){this.on("click",function(e){var i=t(e.currentTarget);if(!i.attr("data-confirm")||confirm(i.attr("data-confirm"))){var s=i.data("menu")?i.data("menu").$anchor:i,n=s.attr("data-form")?t("#"+s.attr("data-form")):s.closest("form");i.attr("data-action")&&t('').val(i.attr("data-action")).appendTo(n),i.attr("data-redirect")&&t('').val(i.attr("data-redirect")).appendTo(n),i.attr("data-param")&&t('').attr({name:i.attr("data-param"),value:i.attr("data-value")}).appendTo(n),n.trigger("submit")}})},menubtn:function(){return this.each(function(){var e=t(this);if(!e.data("menubtn")&&e.next().hasClass("menu")){var i={};e.data("menu-anchor")&&(i.menuAnchor=e.data("menu-anchor")),new Garnish.MenuBtn(e,i)}})}}),Garnish.$doc.ready(function(){Craft.initUiElements()}),Craft.BaseElementEditor=Garnish.Base.extend({$element:null,elementId:null,siteId:null,$form:null,$fieldsContainer:null,$cancelBtn:null,$saveBtn:null,$spinner:null,$siteSelect:null,$siteSpinner:null,hud:null,init:function(e,i){void 0===i&&t.isPlainObject(e)&&(i=e,e=null),this.$element=t(e),this.setSettings(i,Craft.BaseElementEditor.defaults),this.loadHud()},setElementAttribute:function(t,e){this.settings.attributes||(this.settings.attributes={}),null===e?delete this.settings.attributes[t]:this.settings.attributes[t]=e},getBaseData:function(){var e=t.extend({},this.settings.params);return this.settings.siteId?e.siteId=this.settings.siteId:this.$element&&this.$element.data("site-id")&&(e.siteId=this.$element.data("site-id")),this.settings.elementId?e.elementId=this.settings.elementId:this.$element&&this.$element.data("id")&&(e.elementId=this.$element.data("id")),this.settings.elementType&&(e.elementType=this.settings.elementType),this.settings.attributes&&(e.attributes=this.settings.attributes),e},loadHud:function(){this.onBeginLoading();var e=this.getBaseData();e.includeSites=this.settings.showSiteSwitcher,Craft.postActionRequest("elements/get-editor-html",e,t.proxy(this,"showHud"))},showHud:function(e,i){if(this.onEndLoading(),"success"===i){var s=t();if(e.sites){var n=t('
'),a=t('
').appendTo(n);this.$siteSelect=t("').appendTo(h),this.$spinner=t('