diff --git a/wire/core/Inputfield.php b/wire/core/Inputfield.php index 72c6a2b3..4b31fe99 100644 --- a/wire/core/Inputfield.php +++ b/wire/core/Inputfield.php @@ -45,6 +45,10 @@ * @property mixed $value HTML 'value' attribute for the Inputfield. #pw-group-attribute-properties * @property string $class HTML 'class' attribute for the Inputfield. #pw-group-attribute-properties * + * @method string|Inputfield name($name = null) Get or set the name attribute. @since 3.0.110 #pw-group-attribute-methods + * @method string|Inputfield id($id = null) Get or set the id attribute. @since 3.0.110 #pw-group-attribute-methods + * @method string|Inputfield class($class = null) Get class attribute or add a class to the class attribute. @since 3.0.110 #pw-group-attribute-methods + * * LABELS & CONTENT * ================ * @property string $label Primary label text that appears above the input. #pw-group-labels @@ -56,6 +60,15 @@ * @property string|null $prependMarkup Optional markup to prepend to the Inputfield content container. #pw-group-other * @property string|null $appendMarkup Optional markup to append to the Inputfield content container. #pw-group-other * + * @method string|Inputfield label($label = null) Get or set the 'label' property via method. @since 3.0.110 #pw-group-labels + * @method string|Inputfield description($description = null) Get or set the 'description' property via method. @since 3.0.110 #pw-group-labels + * @method string|Inputfield notes($notes = null) Get or set the 'notes' property via method. @since 3.0.110 #pw-group-labels + * @method string|Inputfield icon($icon = null) Get or set the 'icon' property via method. @since 3.0.110 #pw-group-labels + * @method string|Inputfield requiredLabel($requiredLabel = null) Get or set the 'requiredLabel' property via method. @since 3.0.110 #pw-group-labels + * @method string|Inputfield head($head = null) Get or set the 'head' property via method. @since 3.0.110 #pw-group-labels + * @method string|Inputfield prependMarkup($markup = null) Get or set the 'prependMarkup' property via method. @since 3.0.110 #pw-group-labels + * @method string|Inputfield appendMarkup($markup = null) Get or set the 'appendMarkup' property via method. @since 3.0.110 #pw-group-labels + * * APPEARANCE * ========== * @property int $collapsed Whether the field is collapsed or visible, using one of the "collapsed" constants. #pw-group-appearance @@ -63,6 +76,12 @@ * @property int $columnWidth Width of column for this Inputfield 10-100 percent. 0 is assumed to be 100 (default). #pw-group-appearance * @property int $skipLabel Skip display of the label? See the "skipLabel" constants for options. #pw-group-appearance * + * @method int|Inputfield collapsed($collapsed = null) Get or set collapsed property via method. @since 3.0.110 #pw-group-appearance + * @method string|Inputfield showIf($showIf = null) Get or set showIf selector property via method. @since 3.0.110 #pw-group-appearance + * @method int|Inputfield columnWidth($columnWidth = null) Get or set columnWidth property via method. @since 3.0.110 #pw-group-appearance + * @method int|Inputfield skipLabel($skipLabel = null) Get or set the skipLabel constant property via method. @since 3.0.110 #pw-group-appearance + * + * * SETTINGS & BEHAVIOR * =================== * @property int|bool $required Set to true (or 1) to make input required, or false (or 0) to make not required (default=0). #pw-group-behavior @@ -78,6 +97,14 @@ * @property string $wrapClass Optional class name (CSS) to apply to the HTML element wrapping the Inputfield. #pw-group-other * @property string $headerClass Optional class name (CSS) to apply to the InputfieldHeader element #pw-group-other * @property string $contentClass Optional class name (CSS) to apply to the InputfieldContent element #pw-group-other + * + * @method string|Inputfield required($required = null) Get or set required state. @since 3.0.110 #pw-group-behavior + * @method string|Inputfield requiredIf($requiredIf = null) Get or set required-if selector. @since 3.0.110 #pw-group-behavior + * + * @method string|Inputfield wrapClass($class = null) Get wrapper class attribute or add a class to it. @since 3.0.110 #pw-group-other + * @method string|Inputfield headerClass($class = null) Get header class attribute or add a class to it. @since 3.0.110 #pw-group-other + * @method string|Inputfield contentClass($class = null) Get content class attribute or add a class to it. @since 3.0.110 #pw-group-other + * * * HOOKABLE METHODS * ================ @@ -305,7 +332,7 @@ abstract class Inputfield extends WireData implements Module { self::$numInstances++; - $this->set('label', ''); // primary clikable label + $this->set('label', ''); // primary clickable label $this->set('description', ''); // descriptive copy, below label $this->set('icon', ''); // optional icon name to accompany label $this->set('notes', ''); // highlighted descriptive copy, below output of input field @@ -321,6 +348,8 @@ abstract class Inputfield extends WireData implements Module { $this->set('contentClass', ''); // optional class to apply to InputfieldContent wrapper $this->set('textFormat', self::textFormatBasic); // format applied to description and notes $this->set('renderValueFlags', 0); // see renderValue* constants, applicable to renderValue mode only + $this->set('prependMarkup', ''); + $this->set('appendMarkup', ''); // default ID attribute if no 'id' attribute set $this->defaultID = $this->className() . self::$numInstances; @@ -463,6 +492,9 @@ abstract class Inputfield extends WireData implements Module { * */ public function setParent(InputfieldWrapper $parent) { + if($this->parent && $this->parent instanceof InputfieldWrapper && $this->parent !== $parent) { + $this->parent->remove($this); + } $this->parent = $parent; return $this; } @@ -497,6 +529,47 @@ abstract class Inputfield extends WireData implements Module { return $parents; } + /** + * Get or set parent of Inputfield + * + * This convenience method performs the same thing as getParent() and setParent(). + * + * To get parent, specify no arguments. It will return null if no parent assigned, or an + * InputfieldWrapper instance of the parent. + * + * To set parent, specify an InputfieldWrapper for the $parent argument. The return value + * is the current Inputfield for fluent interface. + * + * #pw-group-traversal + * + * @param null|InputfieldWrapper $parent + * @return null|Inputfield|InputfieldWrapper + * @since 3.0.110 + * + */ + public function parent($parent = null) { + if($parent === null) { + return $this->getParent(); + } else { + return $this->setParent($parent); + } + } + + /** + * Get array of all parents of this Inputfield + * + * This is identical to and an alias of the getParents() method. + * + * #pw-group-traversal + * + * @return array + * @since 3.0.110 + * + */ + public function parents() { + return $this->getParents(); + } + /** * Get the root parent InputfieldWrapper element (farthest parent, commonly InputfieldForm) * @@ -715,6 +788,39 @@ abstract class Inputfield extends WireData implements Module { return $this->setAttribute('value', $value); } + /** + * If method call resulted in no handler, this hookable method is called. + * + * We use this to allow for attributes and properties to be set via method, useful primarily + * for fluent interface calls. + * + * #pw-internal + * + * @param string $method Requested method name + * @param array $arguments Arguments provided + * @return null|mixed Return value of method (if applicable) + * @throws WireException + * @since 3.0.110 + * + */ + protected function ___callUnknown($method, $arguments) { + $arg = isset($arguments[0]) ? $arguments[0] : null; + if(isset($this->attributes[$method])) { + // get or set an attribute + return $arg === null ? $this->getAttribute($method) : $this->setAttribute($method, $arg); + } else if(($value = $this->getSetting($method)) !== null) { + // get or set a setting + if($arg === null) return $value; + if(stripos($method, 'class') !== false) { + // i.e. class, wrapClass, contentClass, etc. + return $this->addClass($arg, $method); + } else { + return $this->set($method, $arg); + } + } + return parent::___callUnknown($method, $arguments); + } + /** * Get all attributes specified for this Inputfield * diff --git a/wire/core/InputfieldWrapper.php b/wire/core/InputfieldWrapper.php index 5fb20206..dc68e000 100644 --- a/wire/core/InputfieldWrapper.php +++ b/wire/core/InputfieldWrapper.php @@ -24,6 +24,7 @@ * @property InputfieldsArray|null $children Inputfield instances that are direct children of this InputfieldWrapper. #pw-group-properties * * @method string renderInputfield(Inputfield $inputfield, $renderValueMode = false) #pw-group-output + * @method Inputfield new($typeName, $name = '', $label = '', array $settings = array()) #pw-group-manipulation * */ @@ -185,23 +186,87 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre /** * Add an Inputfield item as a child (also accepts array definition) * + * Since 3.0.110: If given a string value, it is assumed to be an Inputfield type that you + * want to add. In that case, it will create the Inputfield and return it instead of $this. + * * #pw-group-manipulation * - * @param Inputfield|array $item - * @return $this + * @param Inputfield|array|string $item + * @return Inputfield|InputfieldWrapper|$this * @see InputfieldWrapper::import() * */ public function add($item) { - if(is_array($item)) { + if(is_string($item)) { + return $this->___new($item); + } else if(is_array($item)) { $this->importArray($item); } else { - $item->setParent($this); $this->children->add($item); + $item->setParent($this); } return $this; } + /** + * Create a new Inputfield, add it to this InputfieldWrapper, and return the new Inputfield + * + * - Only the $typeName argument is required. + * - You may optionally substitute the $settings argument for the $name or $label arguments. + * - You may optionally substitute Inputfield “description” property for $settings argument. + * + * #pw-group-manipulation + * + * @param string $typeName Inputfield type, i.e. “InputfieldCheckbox” or just “checkbox” for short. + * @param string|array $name Name of input (or substitute $settings here). + * @param string|array $label Label for input (or substitute $settings here). + * @param array|string $settings Settings to add to Inputfield (optional). Or if string, assumed to be “description”. + * @return Inputfield|InputfieldSelect|InputfieldWrapper An Inputfield instance ready to populate with additional properties/attributes. + * @throws WireException If you request an unknown Inputfield type + * @since 3.0.110 + * + */ + public function ___new($typeName, $name = '', $label = '', $settings = array()) { + + if(is_array($name)) { + $settings = $name; + $name = ''; + } else if(is_array($label)) { + $settings = $label; + $label = ''; + } + + if(strpos($typeName, 'Inputfield') !== 0) { + $typeName = "Inputfield" . ucfirst($typeName); + } + + /** @var Inputfield|InputfieldSelect|InputfieldWrapper $inputfield */ + $inputfield = $this->wire('modules')->getModule($typeName); + + if(!$inputfield && wireClassExists($typeName)) { + $inputfield = $this->wire(new $typeName()); + } + + if(!$inputfield || !$inputfield instanceof Inputfield) { + throw new WireException("Unknown Inputfield type: $typeName"); + } + + if(strlen($name)) $inputfield->attr('name', $name); + if(strlen($label)) $inputfield->label = $label; + + if(is_array($settings)) { + foreach($settings as $key => $value) { + $inputfield->set($key, $value); + } + } else if(is_string($settings)) { + $inputfield->description = $settings; + } + + $this->add($inputfield); + + return $inputfield; + } + /** * Import the given Inputfield items as children * @@ -941,6 +1006,61 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre } } + /** + * Find an Inputfield below this one that has the given name + * + * This is an alternative to the `getChildByName()` method, with more options for when you need it. + * For instance, it can also accept a selector string or numeric index for the $name argument, and you + * can optionally disable the $recursive behavior. + * + * #pw-group-retrieval-and-traversal + * + * @param string|int $name Name or selector string of child to find, omit for first child, or specify zero-based index of child to return. + * @param bool $recursive Find child recursively? Looks for child in this wrapper, and all other wrappers below it. (default=true) + * @return Inputfield|null Returns Inputfield instance if found, or null if not. + * @since 3.0.110 + * + */ + public function child($name = '', $recursive = true) { + $child = null; + + if(!$this->children->count()) { + // no child possible + + } else if(empty($name)) { + // first child + $child = $this->children->first(); + + } else if(is_int($name)) { + // number index + $child = $this->children->eq($name); + + } else if($this->wire('sanitizer')->name($name) === $name) { + // child by name + $wrappers = array(); + foreach($this->children as $f) { + if($f->getAttribute('name') === $name) { + $child = $f; + break; + } else if($recursive && $f instanceof InputfieldWrapper) { + $wrappers[] = $f; + } + } + if(!$child && $recursive && count($wrappers)) { + foreach($wrappers as $wrapper) { + $child = $wrapper->child($name, $recursive); + if($child) break; + } + } + + } else if(Selectors::stringHasSelector($name)) { + // first child matching selector string + $child = $this->children("$name, limit=1")->first(); + } + + return $child; + } + /** * Return all children Inputfields (alias of children method) *