Create initial version of repeater form widget

This commit is contained in:
Samuel Georges 2015-03-04 19:05:32 +11:00
parent 24cbeee959
commit 022eb4d673
7 changed files with 440 additions and 0 deletions

View File

@ -66,6 +66,10 @@ class ServiceProvider extends ModuleServiceProvider
'label' => 'Record Finder',
'code' => 'recordfinder'
]);
$manager->registerFormWidget('Backend\FormWidgets\Repeater', [
'label' => 'Repeater',
'code' => 'repeater'
]);
});
/*

View File

@ -0,0 +1,123 @@
<?php namespace Backend\FormWidgets;
use Backend\Classes\FormField;
use Backend\Classes\FormWidgetBase;
/**
* Repeater Form Widget
*/
class Repeater extends FormWidgetBase
{
const INDEX_PREFIX = '___index_';
//
// Configurable properties
//
/**
* @var array Form field configuration
*/
public $form;
//
// Object properties
//
/**
* {@inheritDoc}
*/
protected $defaultAlias = 'repeater';
protected $indexCount = 0;
protected $formWidgets = [];
/**
* {@inheritDoc}
*/
public function init()
{
$this->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
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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;}
}
}
}

View File

@ -0,0 +1,15 @@
<div class="field-repeater">
<div id="<?= $this->getId('items') ?>" class="field-repeater-items">
</div>
<div class="field-repeater-add-item loading-indicator-container indicator-center">
<a
href="javascript:;"
data-load-indicator
data-request="<?= $this->getEventHandler('onAddItem') ?>">
Add new item
</a>
</div>
</div>

View File

@ -0,0 +1,28 @@
<div class="field-repeater-item">
<div class="repeater-item-handle">
<i class="icon-bars"></i>
</div>
<div class="repeater-item-remove">
<button
type="button"
class="close"
aria-label="Remove"
data-request="<?= $this->getEventHandler('onRemoveItem') ?>"
data-request-data="'index': '<?= $indexValue ?>'"
data-request-success="$el.closest('.field-repeater-item').remove()"
data-request-confirm="Are you sure?">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="field-repeater-form">
<?= $widget->render([
'useContainer' => false
]) ?>
</div>
<input type="hidden" name="<?= $indexName ?>" value="<?= $indexValue ?>" />
</div>