From 022eb4d6735e0f6f882457e38d7267a269e4343a Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Wed, 4 Mar 2015 19:05:32 +1100 Subject: [PATCH] Create initial version of repeater form widget --- modules/backend/ServiceProvider.php | 4 + modules/backend/formwidgets/Repeater.php | 123 ++++++++++++++++++ .../repeater/assets/css/repeater.css | 95 ++++++++++++++ .../repeater/assets/js/repeater.js | 79 +++++++++++ .../repeater/assets/less/repeater.less | 96 ++++++++++++++ .../repeater/partials/_repeater.htm | 15 +++ .../repeater/partials/_repeater_item.htm | 28 ++++ 7 files changed, 440 insertions(+) create mode 100644 modules/backend/formwidgets/Repeater.php create mode 100644 modules/backend/formwidgets/repeater/assets/css/repeater.css create mode 100644 modules/backend/formwidgets/repeater/assets/js/repeater.js create mode 100644 modules/backend/formwidgets/repeater/assets/less/repeater.less create mode 100644 modules/backend/formwidgets/repeater/partials/_repeater.htm create mode 100644 modules/backend/formwidgets/repeater/partials/_repeater_item.htm diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index 2f77297e6..5886d3fc7 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -66,6 +66,10 @@ class ServiceProvider extends ModuleServiceProvider 'label' => 'Record Finder', 'code' => 'recordfinder' ]); + $manager->registerFormWidget('Backend\FormWidgets\Repeater', [ + 'label' => 'Repeater', + 'code' => 'repeater' + ]); }); /* diff --git a/modules/backend/formwidgets/Repeater.php b/modules/backend/formwidgets/Repeater.php new file mode 100644 index 000000000..2c2cd19c2 --- /dev/null +++ b/modules/backend/formwidgets/Repeater.php @@ -0,0 +1,123 @@ +fillFromConfig([ + 'form', + ]); + + $this->processExistingItems(); + } + + /** + * {@inheritDoc} + */ + public function render() + { + $this->prepareVars(); + return $this->makePartial('repeater'); + } + + /** + * Prepares the form widget view data + */ + public function prepareVars() + { + $this->vars['indexName'] = self::INDEX_PREFIX.$this->formField->getName(false).'[]'; + } + + /** + * {@inheritDoc} + */ + public function loadAssets() + { + $this->addCss('css/repeater.css', 'core'); + $this->addJs('js/repeater.js', 'core'); + } + + /** + * {@inheritDoc} + */ + public function getSaveValue($value) + { + return array_values($value); + } + + protected function processExistingItems() + { + $itemIndexes = post(self::INDEX_PREFIX.$this->formField->getName(false), $this->getLoadValue()); + + if (!is_array($itemIndexes)) return; + + foreach ($itemIndexes as $itemIndex) { + $this->makeFormWidget($itemIndex); + $this->indexCount = max((int) $itemIndex, $this->indexCount); + } + } + + protected function makeFormWidget($index = 0) + { + $config = $this->makeConfig($this->form); + $config->model = $this->model; + $config->alias = $this->alias . 'Form'.$index; + $config->arrayName = $this->formField->getName().'['.$index.']'; + + $widget = $this->makeWidget('Backend\Widgets\Form', $config); + $widget->bindToController(); + + return $this->formWidgets[$index] = $widget; + } + + public function onAddItem() + { + $this->indexCount++; + + $this->prepareVars(); + $this->vars['widget'] = $this->makeFormWidget($this->indexCount); + $this->vars['indexValue'] = $this->indexCount; + + $itemContainer = '@#'.$this->getId('items'); + return [$itemContainer => $this->makePartial('repeater_item')]; + } + + public function onRemoveItem() + { + // Useful for deleting relations + } + +} diff --git a/modules/backend/formwidgets/repeater/assets/css/repeater.css b/modules/backend/formwidgets/repeater/assets/css/repeater.css new file mode 100644 index 000000000..6341a6eb3 --- /dev/null +++ b/modules/backend/formwidgets/repeater/assets/css/repeater.css @@ -0,0 +1,95 @@ +.field-repeater { + padding-top: 5px; +} +.field-repeater .field-repeater-item { + position: relative; + margin-left: 5px; + padding-left: 15px; + border-left: 1px solid #dbdee0; +} +.field-repeater .field-repeater-item:before { + color: #bdc3c7; + font-family: FontAwesome; + font-weight: normal; + font-style: normal; + text-decoration: inherit; + -webkit-font-smoothing: antialiased; + *margin-right: .3em; + content: "\f111"; + font-size: 8px; + position: absolute; + left: -4px; + top: -2px; +} +.field-repeater .field-repeater-item .field-repeater-form { + position: relative; + top: -7px; +} +.field-repeater .field-repeater-item .repeater-item-handle { + position: absolute; + top: -6px; + left: -10px; + color: #bdc3c7; + background: #f9f9f9; + cursor: move; + opacity: 0; + -webkit-transition: opacity 0.5s; + transition: opacity 0.5s; +} +.field-repeater .field-repeater-item .repeater-item-handle:hover { + color: #999; +} +.field-repeater .field-repeater-item .repeater-item-remove { + position: absolute; + top: -10px; + right: 0; + z-index: 90; +} +.field-repeater .field-repeater-item .repeater-item-handle, +.field-repeater .field-repeater-item .repeater-item-remove { + width: 20px; + height: 20px; + text-align: center; +} +.field-repeater .field-repeater-item:hover .repeater-item-handle, +.field-repeater .field-repeater-item:active .repeater-item-handle, +.field-repeater .field-repeater-item:hover .repeater-item-remove, +.field-repeater .field-repeater-item:active .repeater-item-remove { + opacity: 1; +} +.field-repeater .field-repeater-add-item { + position: relative; + margin-top: 10px; + margin-left: 20px; + border: 2px dotted #e0e0e0; +} +.field-repeater .field-repeater-add-item:before { + color: #bdc3c7; + font-family: FontAwesome; + font-weight: normal; + font-style: normal; + text-decoration: inherit; + -webkit-font-smoothing: antialiased; + *margin-right: .3em; + content: "\f067"; + font-size: 16px; + position: absolute; + left: -23px; + top: -11px; +} +.field-repeater .field-repeater-add-item > a { + color: #bdc3c7; + text-align: center; + display: block; + text-decoration: none; + padding: 20px 10px; +} +.field-repeater .field-repeater-add-item:hover { + border: 2px dotted rgba(0, 0, 0, 0.1); +} +.field-repeater .field-repeater-add-item:hover:before { + color: #999; +} +.field-repeater .field-repeater-add-item:hover > a { + color: #bdc3c7; +} diff --git a/modules/backend/formwidgets/repeater/assets/js/repeater.js b/modules/backend/formwidgets/repeater/assets/js/repeater.js new file mode 100644 index 000000000..b0a255d06 --- /dev/null +++ b/modules/backend/formwidgets/repeater/assets/js/repeater.js @@ -0,0 +1,79 @@ +/* + * Field Repeater plugin + * + * Data attributes: + * - data-control="fieldrepeater" - enables the plugin on an element + * - data-option="value" - an option with a value + * + * JavaScript API: + * $('a#someElement').fieldRepeater({ option: 'value' }) + * + * Dependences: + * - Some other plugin (filename.js) + */ + ++function ($) { "use strict"; + + // FIELD REPEATER CLASS DEFINITION + // ============================ + + var Repeater = function(element, options) { + this.options = options + this.$el = $(element) + + // Init + this.init() + } + + Repeater.DEFAULTS = { + option: 'default' + } + + Repeater.prototype.init = function() { + // Public properties + this.something = false + + // Init with no arguments + } + + Repeater.prototype.someFunction = function() { + // Do stuff + } + + // FIELD REPEATER PLUGIN DEFINITION + // ============================ + + var old = $.fn.fieldRepeater + + $.fn.fieldRepeater = function (option) { + var args = Array.prototype.slice.call(arguments, 1), result + this.each(function () { + var $this = $(this) + var data = $this.data('oc.repeater') + var options = $.extend({}, Repeater.DEFAULTS, $this.data(), typeof option == 'object' && option) + if (!data) $this.data('oc.repeater', (data = new Repeater(this, options))) + if (typeof option == 'string') result = data[option].apply(data, args) + if (typeof result != 'undefined') return false + }) + + return result ? result : this + } + + $.fn.fieldRepeater.Constructor = Repeater + + // FIELD REPEATER NO CONFLICT + // ================= + + $.fn.fieldRepeater.noConflict = function () { + $.fn.fieldRepeater = old + return this + } + + // FIELD REPEATER DATA-API + // =============== + + $(document).on('click.oc.myplugin', '[data-control="fieldrepeater"]', function() { + $(this).fieldRepeater() + }); + +}(window.jQuery); \ No newline at end of file diff --git a/modules/backend/formwidgets/repeater/assets/less/repeater.less b/modules/backend/formwidgets/repeater/assets/less/repeater.less new file mode 100644 index 000000000..18d3090a9 --- /dev/null +++ b/modules/backend/formwidgets/repeater/assets/less/repeater.less @@ -0,0 +1,96 @@ +@import "../../../../assets/less/core/boot.less"; + +.field-repeater { + + padding-top: 5px; + + .field-repeater-item { + position: relative; + margin-left: 5px; + padding-left: 15px; + border-left: 1px solid #dbdee0; + + &:before { + color: #bdc3c7; + .icon(@circle); + font-size: 8px; + position: absolute; + left: -4px; + top: -2px; + } + + .field-repeater-form { + position: relative; + top: -7px; + } + + .repeater-item-handle { + position: absolute; + top: -6px; + left: -10px; + color: #bdc3c7; + background: @color-body-bg; + cursor: move; + opacity: 0; + .transition(~'opacity 0.5s'); + &:hover { + color: #999; + } + } + + .repeater-item-remove { + position: absolute; + top: -10px; + right: 0; + z-index: 90; + } + + .repeater-item-handle, + .repeater-item-remove { + width: 20px; + height: 20px; + text-align: center; + } + + &:hover, &:active { + .repeater-item-handle, + .repeater-item-remove { + opacity: 1; + } + } + } + + .field-repeater-add-item { + position: relative; + margin-top: 10px; + margin-left: 20px; + border: 2px dotted #e0e0e0; + + &:before { + color: #bdc3c7; + .icon(@plus); + font-size: 16px; + position: absolute; + left: -23px; + top: -11px; + } + + > a { + color: #bdc3c7; + text-align: center; + display: block; + text-decoration: none; + padding: 20px 10px; + } + + &:hover { + border: 2px dotted rgba(0,0,0,.1); + &:before { + color: #999; + } + + > a { color: #bdc3c7;} + } + } + +} \ No newline at end of file diff --git a/modules/backend/formwidgets/repeater/partials/_repeater.htm b/modules/backend/formwidgets/repeater/partials/_repeater.htm new file mode 100644 index 000000000..52d69d68a --- /dev/null +++ b/modules/backend/formwidgets/repeater/partials/_repeater.htm @@ -0,0 +1,15 @@ +
+ +
+
+ + +
+ diff --git a/modules/backend/formwidgets/repeater/partials/_repeater_item.htm b/modules/backend/formwidgets/repeater/partials/_repeater_item.htm new file mode 100644 index 000000000..1448ff277 --- /dev/null +++ b/modules/backend/formwidgets/repeater/partials/_repeater_item.htm @@ -0,0 +1,28 @@ +
+ +
+ +
+ +
+ +
+ +
+ render([ + 'useContainer' => false + ]) ?> +
+ + + +
\ No newline at end of file