diff --git a/demos/form-control/input2.php b/demos/form-control/input2.php index e4b9ad0936..21f539e16a 100644 --- a/demos/form-control/input2.php +++ b/demos/form-control/input2.php @@ -58,6 +58,62 @@ $group->addControl('radio_read', [Form\Control\Radio::class, 'readOnly' => true], ['enum' => ['one', 'two', 'three']])->set('two'); $group->addControl('radio_disb', [Form\Control\Radio::class, 'disabled' => true], ['enum' => ['one', 'two', 'three']])->set('two'); +$group = $form->addGroup('Sliders'); + +$form->addControl( + 'slider_norm', + [ + Form\Control\Slider::class, + 'labeled' => true, + 'ticked' => true, + 'bottom' => true, + 'min' => 0, + 'max' => 1, + 'step' => 0.1, + 'start' => 0.5, + 'smooth' => true, + 'color' => 'green', + 'caption' => 'Normal Slider', + ] +); + +$form->addControl( + 'slider_read', + [ + Form\Control\Slider::class, + 'readOnly' => true, + 'labeled' => true, + 'ticked' => true, + 'bottom' => false, + 'min' => 1, + 'max' => 5, + 'step' => 1, + 'start' => 2, + 'smooth' => true, + 'color' => 'yellow', + 'caption' => 'Read Only Slider', + ] +); + +$disabledSlider = $form->addControl( + 'slider_disb', + [ + Form\Control\Slider::class, + 'disabled' => true, + 'labeled' => true, + 'ticked' => true, + 'bottom' => false, + 'min' => 1, + 'max' => 10, + 'step' => 1, + 'start' => 5, + 'smooth' => true, + 'caption' => 'Disabled Slider', + ] +); +// We can add classes to a slider by doing: +$disabledSlider->input->addClass('red'); // @phpstan-ignore property.notFound + $group = $form->addGroup('File upload'); $onDelete = static function () {}; diff --git a/demos/form-control/slider.php b/demos/form-control/slider.php new file mode 100644 index 0000000000..11c1345be4 --- /dev/null +++ b/demos/form-control/slider.php @@ -0,0 +1,172 @@ + 2]); + +$form = Form::addTo($app); + +$form->addControl( + 'slider_simple1', + [ + Form\Control\Slider::class, + 'min' => 0, + 'max' => 10, + 'step' => 1, + 'start' => 5, + 'caption' => 'Simple Slider', + ] +); + +$slider2 = $form->addControl( + 'slider_simple2', + [ + Form\Control\Slider::class, + 'labeled' => true, + 'ticked' => true, + 'min' => 0, + 'max' => 10, + 'step' => 1, + 'start' => 5, + 'caption' => 'Blue ticked and labeled simple slider', + ] +); +$slider2->input->addClass('blue'); // @phpstan-ignore property.notFound + +$form->addControl( + 'slider_simple3', + [ + Form\Control\Slider::class, + 'labeled' => true, + 'ticked' => true, + 'min' => 0, + 'max' => 10, + 'step' => 1, + 'start' => 5, + 'smooth' => true, + 'caption' => 'Smooth Blue ticked and labeled simple slider', + ] +); + +$form->addControl( + 'slider_ranged', + [ + Form\Control\Slider::class, + 'labeled' => true, + 'ticked' => true, + 'min' => 0, + 'max' => 10, + 'step' => 1, + 'start' => 3, + 'end' => 6, + 'smooth' => true, + 'showThumbTooltip' => true, + 'tooltipConfig' => ['position' => 'top center', 'variation' => 'big blue'], + 'color' => 'blue', + 'caption' => 'Smooth Blue, ticked and labeled with tool-tips Ranged slider', + ] +); + +$form->addControl( + 'slider_custom', + [ + Form\Control\Slider::class, + 'labeled' => true, + 'ticked' => true, + 'min' => 0, + 'max' => 6, + 'step' => 1, + 'start' => 2, + 'smooth' => true, + 'color' => 'green', + 'customLabels' => [ + 'XS', 'S', 'M', 'L', 'XL', '2XL', '3XL', + ], + 'caption' => 'Smooth Green, ticked and custom labeled slider', + ] +); + +$group = $form->addGroup( + [ + 'inline' => false, + // 'width' => 'six', + ], +); + +$h1 = Header::addTo($group, ['Vertical Sliders', 'size' => 3]); + +$group->addControl( + 'slider_vertical', + [ + Form\Control\Slider::class, + 'labeled' => true, + 'ticked' => true, + 'min' => 0, + 'max' => 6, + 'step' => 1, + 'start' => 2, + 'smooth' => true, + 'color' => 'red', + 'vertical' => true, + 'reversed' => true, + 'size' => 'small', + 'caption' => 'Smooth Red, ticked and vertical slider', + 'width' => 'two', + ] +); + +$group->addControl( + 'slider_vertical_right', + [ + Form\Control\Slider::class, + 'labeled' => true, + 'ticked' => true, + 'min' => 0, + 'max' => 6, + 'step' => 1, + 'start' => 3, + 'smooth' => true, + 'color' => 'yellow', + 'vertical' => true, + 'reversed' => true, + 'verticalHeight' => 300, + 'caption' => 'Smooth yellow, ticked, sized, custom height, vertical slider', + 'width' => 'two', + ] +); + +$group->addControl( + 'slider_vertical_right_ranged', + [ + Form\Control\Slider::class, + 'labeled' => true, + 'ticked' => true, + 'min' => 0, + 'max' => 6, + 'step' => 1, + 'start' => 2, + 'end' => 6, + 'smooth' => true, + 'color' => 'green', + 'vertical' => true, + 'reversed' => true, + 'verticalHeight' => 400, + 'verticalRightAligned' => false, + 'size' => 'large', + 'caption' => 'Smooth green, ticked, large, custom heigth, vertical not right aligned slider', + 'width' => 'two', + ] +); + +$form->onSubmit(static function (Form $form) { + print_r($form->entity->get()); +}); diff --git a/demos/init-app.php b/demos/init-app.php index 7052ee0774..eb0177c9cf 100644 --- a/demos/init-app.php +++ b/demos/init-app.php @@ -188,6 +188,7 @@ public static function get_class(\Closure $createAnonymousClassFx): string $layout->addMenuItem(['Multi Line'], [$path . 'multiline'], $menu); $layout->addMenuItem(['Tree Selector'], [$path . 'tree-item-selector'], $menu); $layout->addMenuItem(['Scope Builder'], [$path . 'scope-builder'], $menu); + $layout->addMenuItem(['Slider'], [$path . 'slider'], $menu); $path = $demosUrl . 'collection/'; $menu = $layout->addMenuGroup(['Data Collection', 'icon' => 'table']); diff --git a/src/Form/Control/Slider.php b/src/Form/Control/Slider.php new file mode 100755 index 0000000000..869489ab13 --- /dev/null +++ b/src/Form/Control/Slider.php @@ -0,0 +1,298 @@ +|null An array of label values which restrict the displayed labels to only those which are defined. */ + public $restrictedLabels; + + /** @vat bool Whether a tooltip should be shown to the thumb(s) on hover. Will contain the current slider value. */ + public bool $showThumbTooltip = false; + + /** + * Tooltip configuration used when showThumbTooltip is true + * Refer to Tooltip Variations for possible values. + * + * @var array|null + */ + public $tooltipConfig; + + /** + * Show ticks on a labeled slider. + *'always'will always show the ticks for all labels (even if not shown) + * true will display the ticks only if the related label is also shown. + * + * @var 'always'|bool + */ + public $showLabelTicks = false; + + /** Define smoothness when the slider is moving. */ + public bool $smooth = false; + + /** Whether labels should auto adjust on window resize. */ + public bool $autoAdjustLabels = true; + + /** The distance between labels. */ + public int $labelDistance = 100; + + /** Number of decimals to use with an unstepped slider. */ + public int $decimalPlaces = 2; + + /** Page up/down multiplier. Define how many more times the steps to take on page up/down press. */ + public int $pageMultiplier = 2; + + /** Prevents the lower thumb to crossover the thumb handle. */ + public bool $preventCrossover = true; + + /** Settings for the slider-class */ + /** Whether the slider should be ticked or not. */ + public bool $ticked = false; + + /** Whether the slider should be labeled or not. */ + public bool $labeled = false; + + /** Whether the ticks and labels should be at the bottom. */ + public bool $bottom = false; + + /** Whether the slider should be vertical. */ + public bool $vertical = false; + + /** @var int Default height for vertical slider. */ + public $verticalHeight = 200; + + /** @var bool Right aligned vertical slider. Default true. */ + public bool $verticalRightAligned = true; + + /** @var bool Default reversed slider. */ + public bool $reversed = false; + + /** + * Custom interpreted labels. + * Provide an array which will be used for populating the labels. + * + * @var array|null + */ + public $customLabels; + + /** @var string|null Color of the slider. */ + public $color; + + /** + * Size of the slider. + * Can be: small, large, big, not set is normal size. + * + * @var string + */ + public $size; + + /** @var object The input object so it can be addressed from the code, ie addClass. */ + public object $input; + + /** @var object */ + private $owner; + + /** @var object */ + private $endInput; + + #[\Override] + protected function init(): void + { + parent::init(); + + $this->owner = $this->getOwner(); + + $this->input = View::addTo($this->owner); + + if ($this->reversed) { + $this->input->ui = 'reversed'; + } + + if (!$this->vertical) { + $this->input->ui .= ' slider'; + } else { + $this->owner->defaultInputTemplate = $this->defaultInputTemplateVertical; // @phpstan-ignore property.notFound + $this->owner->inputTemplate = $this->getApp()->loadTemplate($this->defaultInputTemplateVertical); // @phpstan-ignore property.notFound + $this->input->ui .= ' vertical slider'; + $this->input->setAttr(['style' => 'height: ' . $this->verticalHeight . 'px']); + if ($this->verticalRightAligned) { + $this->input->addClass('right aligned'); + } + } + + if ($this->end) { + $this->input->addClass('range'); + } + + if ($this->ticked) { + $this->input->addClass('ticked'); + } + + if ($this->labeled) { + $this->input->addClass('labeled'); + } + + if ($this->bottom) { + $this->input->addClass('bottom aligned'); + } + + if ($this->color) { + $this->input->addClass($this->color); + } + + if ($this->size) { + $this->input->addClass($this->size); + } + + $sliderSettings = []; + $sliderSettings['min'] = $this->min; + $sliderSettings['max'] = $this->max; + if ($this->start) { + $sliderSettings['start'] = $this->start; + } + if ($this->step) { + $sliderSettings['step'] = $this->step; + } + if ($this->end) { + $sliderSettings['end'] = $this->end; + if ($this->minRange) { + $sliderSettings['minRange'] = $this->minRange; + } + if ($this->maxRange) { + $sliderSettings['maxRange'] = $this->maxRange; + } + } + $sliderSettings['labelType'] = $this->labelType; + if ($this->restrictedLabels) { + $sliderSettings['restrictedLabels'] = $this->restrictedLabels; + } + if ($this->showThumbTooltip) { + $sliderSettings['showThumbTooltip'] = $this->showThumbTooltip; + if ($this->tooltipConfig) { + $sliderSettings['tooltipConfig'] = $this->tooltipConfig; + } + } + $sliderSettings['showLabelTicks'] = $this->showLabelTicks; + $sliderSettings['smooth'] = $this->smooth; + $sliderSettings['autoAdjustLabels'] = $this->autoAdjustLabels; + $sliderSettings['labelDistance'] = $this->labelDistance; + $sliderSettings['decimalPlaces'] = $this->decimalPlaces; + $sliderSettings['pageMultiplier'] = $this->pageMultiplier; + $sliderSettings['decimalPlaces'] = $this->decimalPlaces; + $sliderSettings['preventCrossover'] = $this->preventCrossover; + + if ($this->customLabels) { + $sliderSettings['interpretLabel'] = new JsFunction( + [ + 'value', + ], + [ + new JsExpression( + <<<'EOF' + var labels = []; + return labels.slice(value, value + 1); + EOF, + [ + $this->customLabels, + ], + ), + ], + ); + } + + if ($this->disabled || $this->readOnly) { + $this->input->addClass('disabled'); + } + + if (!$this->disabled) { + // Set first input value, always present + $this->set($this->start); + + if (!$this->readOnly) { + $onChange = [ + 'onChange' => new JsFunction( + ['v'], + [ + new JsExpression( + $this->js()->find('input')->jsRender() . '.val($(\'div#\' + [] + \'\').slider(\'get thumbValue\', \'first\'))', + [$this->input->getHtmlId()] + ), + ] + ), + ]; + } + + // End input value, optional, depending on $this->end + if ($this->end) { + $this->endInput = $this->owner->addControl( + $this->shortName . '_end', + [ + Hidden::class, + ] + )->set($this->end); + + if (!$this->readOnly) { + $onChange = [ + 'onChange' => new JsFunction( + ['v'], + [ + new JsExpression( + $this->js()->find('input')->jsRender() . '.val($(\'div#\' + [] + \'\').slider(\'get thumbValue\', \'first\'))', + [$this->input->getHtmlId()] + ), + new JsExpression( + $this->endInput->js()->find('input')->jsRender() . '.val($(\'div#\' + [] + \'\').slider(\'get thumbValue\', \'second\'))', + [$this->input->getHtmlId()] + ), + ] + ), + ]; + } + } + } + if ($this->readOnly) { + $this->addClass('readOnly'); + $this->setAttr(['readOnly' => '']); + } + if (isset($onChange)) { + $sliderSettings += $onChange; + } + $this->input->js(true)->slider( + $sliderSettings, + ); + } +} diff --git a/template/form/layout/slider-vertical.html b/template/form/layout/slider-vertical.html new file mode 100644 index 0000000000..8018911df8 --- /dev/null +++ b/template/form/layout/slider-vertical.html @@ -0,0 +1,16 @@ +{LabeledGroup} +
+ +
{$Content}
{$Hint} +
{/} +{NoLabelGroup} +
+
{$Content}
+
{/} +{LabeledControl} +
+ {$Input} + {$Hint} +
{/} +{NoLabelControl} +
{$Input}{$Hint}
{/} diff --git a/template/form/layout/slider-vertical.pug b/template/form/layout/slider-vertical.pug new file mode 100644 index 0000000000..44486994c2 --- /dev/null +++ b/template/form/layout/slider-vertical.pug @@ -0,0 +1,26 @@ +| {LabeledGroup} +div(class="{$controlClass} field atk-form-group") + label {$label} + div(class="{$width} {$class} fields") + | {$Content} + | {$Hint} +| {/} + +| {NoLabelGroup} +div(class="{$controlClass} field atk-form-group") + div(class="{$width} {$class} fields") + | {$Content} +| {/} + +| {LabeledControl} +div(class="{$controlClass} field" style="padding-left: 60px") + label(for="{$labelFor}") {$label} + | {$Input} + | {$Hint} +| {/} + +| {NoLabelControl} +div(class="{$controlClass} field no-label" style="padding-left: 60px") + | {$Input}{$Hint} +| {/} += "\n" diff --git a/tests-behat/slider.feature b/tests-behat/slider.feature new file mode 100644 index 0000000000..4aeaf7d07c --- /dev/null +++ b/tests-behat/slider.feature @@ -0,0 +1,9 @@ +Feature: Slider + + Scenario: + Given I am on "form-control/slider.php" + + Then I check if input value for //div[@id='atk_layout_maestro_form_form_layout_view']/following::div[contains(@class, 'thumb')]", document.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue.style.left']" match text "5.0" + When I click using selector "//div[@id='atk_layout_maestro_form_form_layout_view']/following::div[contains(@class, 'thumb')]" + Then I check if input value for "//input[@name='slider_simple2_first']" match text "5.0" +