Added lazy loading for backend form tabs (#4658)

* Added lazy loading for backend form tabs
This commit is contained in:
Tobias Kündig 2019-12-09 10:45:26 +01:00 committed by Samuel Georges
parent 9b77e8d81a
commit 4704f85096
5 changed files with 120 additions and 9 deletions

View File

@ -27,6 +27,11 @@ class FormTabs implements IteratorAggregate, ArrayAccess
*/
public $fields = [];
/**
* @var array Names of tabs to lazy load.
*/
public $lazy = [];
/**
* @var string Default tab label to use when none is specified.
*/
@ -106,6 +111,10 @@ class FormTabs implements IteratorAggregate, ArrayAccess
if (array_key_exists('paneCssClass', $config)) {
$this->paneCssClass = $config['paneCssClass'];
}
if (array_key_exists('lazy', $config)) {
$this->lazy = $config['lazy'];
}
}
/**

View File

@ -456,6 +456,35 @@ class Form extends WidgetBase
return $result;
}
/**
* Renders all fields of a tab in the target tab-pane.
*
* @return array
*/
public function onLazyLoadTab()
{
$target = post('target');
$tabName = post('name');
$fields = array_get(optional($this->getTab('primary'))->fields, $tabName);
return [
$target => $this->makePartial('form_fields', ['fields' => $fields]),
];
}
/**
* Helper method to convert a field name to a valid ID attribute.
*
* @param $input
*
* @return string
*/
public function nameToId($input)
{
return HtmlHelper::nameToId($input);
}
/**
* Creates a flat array of form fields from the configuration.
* Also slots fields in to their respective tabs.
@ -935,7 +964,7 @@ class Form extends WidgetBase
}
$widgetConfig = $this->makeConfig($field->config);
$widgetConfig->alias = $this->alias . studly_case(HtmlHelper::nameToId($field->fieldName));
$widgetConfig->alias = $this->alias . studly_case($this->nameToId($field->fieldName));
$widgetConfig->sessionKey = $this->getSessionKey();
$widgetConfig->previewMode = $this->previewMode;
$widgetConfig->model = $this->model;

View File

@ -34,6 +34,7 @@
this.bindDependants()
this.bindCheckboxlist()
this.toggleEmptyTabs()
this.bindLazyTabs()
this.bindCollapsibleSections()
this.$el.on('oc.triggerOn.afterUpdate', this.proxy(this.toggleEmptyTabs))
@ -161,6 +162,34 @@
})
}
/*
* Render tab form fields once a lazy tab is selected.
*/
FormWidget.prototype.bindLazyTabs = function() {
this.$el.on('click', '.tab-lazy [data-toggle="tab"]', function() {
var $el = $(this)
$.request('form::onLazyLoadTab', {
data: {
target: $el.data('target'),
name: $el.data('tab-name'),
},
success: function(data) {
this.success(data)
$el.parent().removeClass('tab-lazy')
// Trigger all input presets to populate new fields.
setTimeout(function() {
$('[data-input-preset]').each(function() {
var preset = $(this).data('oc.inputPreset')
if (preset && preset.$src) {
preset.$src.trigger('input')
}
})
}, 0)
}
})
})
}
/*
* Hides tabs that have no content, it is possible this can be
* called multiple times in a single cycle due to input.trigger.
@ -184,7 +213,7 @@
/*
* Check each tab pane for form field groups
*/
$('.tab-pane', tabControl).each(function() {
$('.tab-pane:not(.lazy)', tabControl).each(function() {
$('[data-target="#' + $(this).attr('id') + '"]', tabControl)
.closest('li')
.toggle(!!$('> .form-group:not(:empty):not(.hide)', $(this)).length)

View File

@ -14,7 +14,10 @@
<div class="<?= $navCss ?>">
<ul class="nav nav-tabs">
<?php $index = 0; foreach ($tabs as $name => $fields): ?>
<li class="<?= $index++ == 0 ? 'active' : '' ?>">
<?php
$lazy = in_array($name, $tabs->lazy);
?>
<li class="<?= ($index++ === 0) ? 'active' : '' ?> <?= $lazy ? 'tab-lazy' : '' ?>">
<a href="#<?= $type . 'tab-' . $index ?>">
<span class="title">
<span>
@ -29,11 +32,19 @@
</div>
<div class="tab-content <?= $contentCss ?>">
<?php $index = 0; foreach ($tabs as $name => $fields): ?>
<div
class="tab-pane <?= e($tabs->getPaneCssClass($index, $name)) ?> <?= $index++ == 0 ? 'active' : '' ?> <?= $paneCss ?>"
id="<?= $type . 'tab-' . $index ?>">
<?= $this->makePartial('form_fields', ['fields' => $fields]) ?>
</div>
<?php
$index = 0;
foreach ($tabs as $name => $fields):
$lazy = in_array($name, $tabs->lazy);
?>
<div
class="tab-pane <?= $lazy ? 'lazy' : '' ?> <?= e($tabs->getPaneCssClass($index, $name)) ?> <?= ($index++ === 0) ? 'active' : '' ?> <?= $paneCss ?>"
id="<?= $type . 'tab-' . $index ?>">
<?php if ($lazy): ?>
<?= $this->makePartial('form_tabs_lazy', ['fields' => $fields]) ?>
<?php else: ?>
<?= $this->makePartial('form_fields', ['fields' => $fields]) ?>
<?php endif ?>
</div>
<?php endforeach ?>
</div>

View File

@ -0,0 +1,33 @@
<div class="loading-indicator-container m-t">
<div class="loading-indicator indicator-center">
<span></span>
</div>
</div>
<?php
// Do not create a hidden field for these field types since
// they don't contain any form data.
$ignoredTypes = ['section', 'partial'];
foreach ($fields as $field):
if (in_array($field->type, $ignoredTypes)) continue;
$isMultiValue = is_array($field->value);
foreach (array_wrap($field->value) as $index => $value):
// Use array field names if the field has multiple values (repeater, checkboxlist, etc.).
$fieldName = $isMultiValue ? sprintf('%s[%s]', $field->getName(), $index) : $field->getName();
$valueIsArray = is_array($value);
foreach (array_wrap($value) as $index => $value):
// Set the correct array keys if the value is an array (repeater form fields).
$currentFieldName = $valueIsArray ? sprintf('%s[%s]', $fieldName, $index) : $fieldName;
?>
<input
type="hidden"
name="<?= $currentFieldName ?>"
id="<?= $this->nameToId($currentFieldName) ?>"
value="<?= e($value) ?>"
<?= $field->getAttributes() ?>
/>
<?php endforeach ?>
<?php endforeach ?>
<?php endforeach ?>