mirror of
https://github.com/processwire/processwire.git
synced 2025-08-10 08:44:46 +02:00
Updates to FieldtypeRepeater and InputfieldRepeater to support single mode, as used by FieldtypeFieldsetPage
This commit is contained in:
@@ -531,13 +531,15 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
*
|
||||
* @param Page $page
|
||||
* @param Field $field
|
||||
* @param PageArray $value
|
||||
* @param PageArray|Page $value
|
||||
* @param array $notIDs Optional Page IDs that should be excluded from the next ready page
|
||||
* @return Page
|
||||
*
|
||||
*/
|
||||
public function getNextReadyPage(Page $page, Field $field, PageArray $value, array $notIDs = array()) {
|
||||
public function getNextReadyPage(Page $page, Field $field, $value = null, array $notIDs = array()) {
|
||||
$readyPage = null;
|
||||
if($value) {
|
||||
if($value instanceof Page) $value = array($value);
|
||||
foreach($value as $item) {
|
||||
if($item->hasStatus(Page::statusUnpublished)
|
||||
&& $item->hasStatus(Page::statusHidden)
|
||||
@@ -554,6 +556,7 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!$readyPage) {
|
||||
$readyPage = $this->getBlankRepeaterPage($page, $field);
|
||||
$readyPage->sort = count($value);
|
||||
@@ -1040,10 +1043,7 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
$template = $this->wire(new Template());
|
||||
$template->name = $templateName;
|
||||
$template->fieldgroup = $fieldgroup;
|
||||
$template->flags = Template::flagSystem;
|
||||
$template->noChildren = 1;
|
||||
$template->noParents = 1; // prevents users from creating pages with this template, but not us
|
||||
$template->noGlobal = 1;
|
||||
$this->populateRepeaterTemplateSettings($template);
|
||||
$template->save();
|
||||
|
||||
if(!$template->id) throw new WireException("Unable to create repeater template: $templateName");
|
||||
@@ -1057,6 +1057,19 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the settings for a newly created repeater template
|
||||
*
|
||||
* @param Template $template
|
||||
*
|
||||
*/
|
||||
protected function populateRepeaterTemplateSettings(Template $template) {
|
||||
$template->flags = Template::flagSystem;
|
||||
$template->noChildren = 1;
|
||||
$template->noParents = 1; // prevents users from creating pages with this template, but not us
|
||||
$template->noGlobal = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the sanitization and convertion to PageArray value
|
||||
*
|
||||
@@ -1146,8 +1159,6 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
/**
|
||||
* Perform output formatting on the value delivered to the API
|
||||
*
|
||||
* If the repeaterMaxItems setting is 1, then we format the value to dereference as single Page rather than a PageArray.
|
||||
*
|
||||
* This method is only used when $page->outputFormatting is true.
|
||||
*
|
||||
* @param Page $page
|
||||
@@ -1162,13 +1173,6 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
|
||||
if(!$value instanceof PageArray) $value = $this->getBlankValue($page, $field);
|
||||
|
||||
/* TBA
|
||||
if($field->repeaterMaxItems == 1) {
|
||||
if(count($value)) $value = $value->first();
|
||||
else $value = new NullPage();
|
||||
}
|
||||
*/
|
||||
|
||||
// used as a clone if a formatted version of $value is different from non-formatted
|
||||
$formatted = null;
|
||||
$cnt = 0;
|
||||
@@ -1330,7 +1334,9 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
|
||||
if($value instanceof RepeaterPage) {
|
||||
// for FieldsetPage compatibility
|
||||
$pageArray = $this->getBlankValue($page, $field);
|
||||
$pageArrayClass = $this->getPageArrayClass();
|
||||
/** @var RepeaterPageArray $pageArray */
|
||||
$pageArray = $this->wire(new $pageArrayClass($page, $field));
|
||||
$pageArray->add($value);
|
||||
$pageArray->resetTrackChanges();
|
||||
$value = $pageArray;
|
||||
@@ -1565,7 +1571,7 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
*
|
||||
*/
|
||||
public function ___cloneField(Field $field) {
|
||||
throw new WireException("Sorry, repeater fields are not currently cloneable.");
|
||||
throw new WireException($this->className() . " does not currently support field cloning.");
|
||||
/*
|
||||
$field = parent::___cloneField($field);
|
||||
$field->parent_id = null;
|
||||
@@ -1645,37 +1651,43 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
*/
|
||||
public function ___install() {
|
||||
|
||||
$adminRoot = $this->wire('pages')->get($this->wire('config')->adminRootPageID);
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->wire('pages');
|
||||
|
||||
$page = $this->wire('pages')->newPage(array('template' => 'admin'));
|
||||
$adminRoot = $pages->get($this->wire('config')->adminRootPageID);
|
||||
$page = $adminRoot->child("name=repeaters, template=admin, include=all");
|
||||
|
||||
if(!$page->id) {
|
||||
$page = $pages->newPage(array('template' => 'admin'));
|
||||
$page->parent = $adminRoot;
|
||||
$page->status = Page::statusHidden | Page::statusLocked | Page::statusSystemID;
|
||||
$page->name = 'repeaters';
|
||||
$page->title = 'Repeaters';
|
||||
$page->sort = $adminRoot->numChildren;
|
||||
$page->save();
|
||||
|
||||
$configData = array('repeatersRootPageID' => $page->id);
|
||||
$this->wire('modules')->saveModuleConfigData($this, $configData);
|
||||
|
||||
$this->message("Added page {$page->path}", Notice::debug);
|
||||
}
|
||||
|
||||
$configData = array('repeatersRootPageID' => $page->id);
|
||||
$this->wire('modules')->saveModuleConfigData($this, $configData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall the module
|
||||
* Uninstall the module (delete the repeaters page)
|
||||
*
|
||||
*/
|
||||
public function ___uninstall() {
|
||||
|
||||
// delete the repeaters page
|
||||
// don't delete repeaters page unless actually for FieldtypeRepeater
|
||||
if($this->className() != 'FieldtypeRepeater') return;
|
||||
$page = $this->wire('pages')->get($this->repeatersRootPageID);
|
||||
if($page->id) {
|
||||
if($page->id && $page->name == 'repeaters' && $page->template == 'admin') {
|
||||
$page->addStatus(Page::statusSystemOverride);
|
||||
$page->removeStatus(Page::statusSystem);
|
||||
$page->removeStatus(Page::statusSystemID);
|
||||
$page->removeStatus(Page::statusSystemOverride);
|
||||
$page->removeStatus(Page::statusLocked);
|
||||
if($page->id) $this->wire('pages')->delete($page);
|
||||
$this->wire('pages')->delete($page);
|
||||
$this->message("Removed page {$page->path}", Notice::debug);
|
||||
}
|
||||
}
|
||||
|
@@ -283,11 +283,12 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
}
|
||||
|
||||
$minItems = $this->repeaterMinItems;
|
||||
$maxItems = $this->repeaterMaxItems;
|
||||
|
||||
// if there are a minimum required number of items, set them up now
|
||||
if(!$itemID && $minItems > 0) {
|
||||
$notIDs = $value->explode('id');
|
||||
while($value->count() < $this->repeaterMinItems) {
|
||||
while($value->count() < $minItems) {
|
||||
$item = $this->getNextReadyPage($notIDs);
|
||||
$value->add($item);
|
||||
$notIDs[] = $item->id;
|
||||
@@ -299,6 +300,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$numVisible = 0;
|
||||
$numOpen = 0;
|
||||
$isPost = $this->wire('input')->requestMethod('POST');
|
||||
$isSingle = $minItems == 1 && $maxItems == 1;
|
||||
|
||||
// create field for each repeater iteration
|
||||
foreach($value as $key => $page) {
|
||||
@@ -310,7 +312,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$isOn = $page->hasStatus(Page::statusOn);
|
||||
$isReadyItem = $isHidden && $isUnpublished;
|
||||
$isClone = $page->get('_repeater_clone');
|
||||
$isOpen = in_array($page->id, $openIDs) || $isClone;
|
||||
$isOpen = in_array($page->id, $openIDs) || $isClone || $isSingle;
|
||||
$isMinItem = $isReadyItem && $minItems && $cnt < $minItems;
|
||||
|
||||
if($isOpen && $numOpen > 0 && $this->accordionMode) $isOpen = false;
|
||||
@@ -326,6 +328,12 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$inputfields->set('useDependencies', false);
|
||||
$this->wrappers[$page->id] = $inputfields;
|
||||
|
||||
if($isSingle) {
|
||||
$delete = null;
|
||||
$sort = null;
|
||||
$depth = null;
|
||||
$loaded = null;
|
||||
} else {
|
||||
// also add a delete checkbox to the repeater page fields
|
||||
$delete = $this->wire('modules')->get('InputfieldCheckbox');
|
||||
$delete->attr('id+name', "delete_repeater{$page->id}");
|
||||
@@ -356,6 +364,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$loaded->attr('value', $isLoaded ? 1 : 0);
|
||||
$loaded->set('renderValueAsInput', true);
|
||||
$loaded->class = 'InputfieldRepeaterLoaded';
|
||||
}
|
||||
|
||||
$wrap = $this->wire('modules')->get('InputfieldFieldset');
|
||||
$wrap->addClass('InputfieldRepeaterItem InputfieldNoFocus');
|
||||
@@ -379,7 +388,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
if($isClone) $wrap->addClass('InputfieldRepeaterItemClone');
|
||||
if($itemID) $wrap->addClass('InputfieldRepeaterItemRequested');
|
||||
|
||||
if($page->get('_repeater_delete')) {
|
||||
if($delete && $page->get('_repeater_delete')) {
|
||||
// something indicates it should already show delete state in editor
|
||||
$delete->attr('checked', 'checked');
|
||||
$wrap->addClass('InputfieldRepeaterDeletePending');
|
||||
@@ -400,6 +409,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$hasErrors = count($inputfields->getErrors()) > 0;
|
||||
if($hasErrors) $wrap->icon = 'warning';
|
||||
|
||||
if(!$isSingle) {
|
||||
// add a hidden field that will be populated with a positive value for all visible repeater items
|
||||
// this is so that processInput can see this item should be a published item
|
||||
$f = $this->wire('modules')->get('InputfieldHidden');
|
||||
@@ -424,11 +434,13 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
if(!$isOn) $wrap->addClass('InputfieldRepeaterOff');
|
||||
}
|
||||
|
||||
$wrap->add($inputfields);
|
||||
$wrap->prepend($delete);
|
||||
$wrap->prepend($sort);
|
||||
if($depth) $wrap->prepend($depth);
|
||||
$wrap->prepend($loaded);
|
||||
} else {
|
||||
$wrap->add($inputfields);
|
||||
}
|
||||
|
||||
if($isMinItem) {
|
||||
// allow this ready item to be added so that minimum is met
|
||||
@@ -455,7 +467,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$inputfield->appendMarkup .= "<script>ProcessWire.config['$idAttr'] = " . json_encode($jsValue) . ';</script>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if(!$isSingle) {
|
||||
// create a new/blank item to be used as a template for any new items added
|
||||
/** @var InputfieldWrapper $wrap */
|
||||
$wrap = $this->wire('modules')->get('InputfieldFieldset');
|
||||
@@ -715,6 +727,8 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
*/
|
||||
public function ___processInput(WireInputData $input) {
|
||||
|
||||
$isSingle = $this->repeaterMinItems == 1 && $this->repeaterMaxItems == 1;
|
||||
|
||||
/** @var PageArray $value */
|
||||
$value = $this->attr('value');
|
||||
$loadedIDs = array();
|
||||
@@ -722,7 +736,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
// determine which repeater pages have data posted in this request
|
||||
foreach($value as $key => $page) {
|
||||
$loadedName = "loaded_repeater$page->id";
|
||||
if(((int) $input->$loadedName) > 0) $loadedIDs[$page->id] = $page->id;
|
||||
if($isSingle || ((int) $input->$loadedName) > 0) $loadedIDs[$page->id] = $page->id;
|
||||
}
|
||||
|
||||
$this->buildForm(0, $loadedIDs);
|
||||
@@ -742,6 +756,9 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$isUnpublished = $page->isUnpublished();
|
||||
$isOn = $page->hasStatus(Page::statusOn);
|
||||
|
||||
if($isSingle) {
|
||||
$publishName = '';
|
||||
} else {
|
||||
$deleteName = "delete_repeater{$page->id}";
|
||||
$sortName = "sort_repeater{$page->id}";
|
||||
$publishName = "publish_repeater{$page->id}";
|
||||
@@ -770,6 +787,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$numChanges++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var InputfieldWrapper $wrapper */
|
||||
$wrapper = $this->wrappers[$page->id];
|
||||
@@ -780,7 +798,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$page->setQuietly('_repeater_errors', $numErrors); // signal to FieldtypeRepeater::savePageField() that page has errors
|
||||
$page->setQuietly('_repeater_processed', true); // signal to FieldtypeRepeater::savePageField() that page had input processed
|
||||
$this->formToPage($wrapper, $page);
|
||||
$publish = (int) $input->$publishName;
|
||||
$publish = $isSingle ? 0 : (int) $input->$publishName;
|
||||
|
||||
if($publish > 0 && ($isHidden || $isUnpublished)) {
|
||||
// publish requested (publish=1)
|
||||
@@ -808,7 +826,7 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$numChanges++;
|
||||
}
|
||||
|
||||
if($this->field->get('repeaterLoading') == FieldtypeRepeater::loadingOff) {
|
||||
if(!$isSingle && $this->field->get('repeaterLoading') == FieldtypeRepeater::loadingOff) {
|
||||
$numNewItems = (int) $input["_{$this->name}_add_items"];
|
||||
if($numNewItems) {
|
||||
// iterate through each new item added for non-ajax mode
|
||||
@@ -907,6 +925,24 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute
|
||||
*
|
||||
* @param array|string $key
|
||||
* @param array|int|string $value
|
||||
* @return InputfieldRepeater|Inputfield
|
||||
*
|
||||
*/
|
||||
public function setAttribute($key, $value) {
|
||||
if($key === 'value' && $value instanceof Page) {
|
||||
if($this->field && method_exists($this->field->type, 'getRepeaterPageArray')) {
|
||||
if(!$value->id) $value = null;
|
||||
$value = $this->field->type->getRepeaterPageArray($this->page, $this->field, $value);
|
||||
}
|
||||
}
|
||||
return parent::setAttribute($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the repeater wrappers (InputfieldWrappers) indexed by repeater page ID
|
||||
*
|
||||
|
@@ -115,6 +115,13 @@ class RepeaterPage extends Page {
|
||||
return $this->forField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property
|
||||
*
|
||||
* @param string $key
|
||||
* @return int|mixed|null
|
||||
*
|
||||
*/
|
||||
public function get($key) {
|
||||
$value = parent::get($key);
|
||||
if($key === 'depth' && is_null($value)) {
|
||||
@@ -123,6 +130,12 @@ class RepeaterPage extends Page {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get depth
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
*/
|
||||
public function getDepth() {
|
||||
if(is_null($this->depth)) {
|
||||
$this->depth = 0;
|
||||
@@ -132,6 +145,12 @@ class RepeaterPage extends Page {
|
||||
return $this->depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set depth
|
||||
*
|
||||
* @param int $depth
|
||||
*
|
||||
*/
|
||||
public function setDepth($depth) {
|
||||
$name = $this->name;
|
||||
$_name = $name;
|
||||
|
Reference in New Issue
Block a user