/** * Converts an array of Metadata trees to actual content that can be rendered. * * @param self[] $metadata The metadata to be converted. * @param Component $parent The content will be assigned to this component and will inherit its context. * @param bool $prepend When true, children are prepended to the existing content, or the the beginning of a * collection property. */ public static function compile(array $metadata, Component $parent, $prepend = false) { foreach ($metadata as $item) { if (!$item instanceof self) { $parent->addChild($item->cloneWithContext($parent->context), $prepend); continue; } $tag = $item->getTagName(); $propName = lcfirst($tag); // Insert metadata if ($parent->props && !$parent instanceof self && $parent->props->defines($propName)) { if ($parent->props->getTypeOf($propName) == type::collection) { $comp = new self($parent->context, $tag, $parent->props->getRelatedTypeOf($propName), $item->props->getAll(), $item->bindings); if ($prepend) { array_unshift($parent->props->{$propName}, $comp); } else { array_push($parent->props->{$propName}, $comp); } } else { $comp = new self($parent->context, $tag, $parent->props->getTypeOf($propName), $item->props->getAll(), $item->bindings); $parent->props->set($propName, $comp); } } else { $comp = $tag === 'Text' ? Text::from($parent->context, $item->value) : $parent->context->createComponentFromTag($tag, $parent, $item->props->getAll(), $item->bindings); $parent->addChild($comp, $prepend); } // Now, compile the children. self::compile($item->getChildren(), $comp); } }
/** * Returns the value converted to a the data type required by the specified property. * * @param string $name * @param mixed $v * @return bool|float|int|null|string|\Traversable * @throws ComponentException */ function typecastPropertyValue($name, $v) { if ($this->isScalar($name) && $this->isEnum($name)) { $this->validateEnum($name, $v); } $type = $this->getTypeOf($name); if ($type && !type::validate($type, $v)) { throw new ComponentException($this->component, sprintf("%s is not a valid value for the <kbd>{$name}</kbd> property, which is of type <kbd>%s</kbd>", is_scalar($v) ? sprintf("The %s<kbd>%s</kbd>", typeOf($v), var_export($v, true)) : sprintf("A value of PHP type <kbd>%s</kbd>", typeOf($v)), type::getNameOf($type))); } // A special case for content type properties: // Convert a content property specified as attribute=string to a format equivalent to the one generated by // <subtag>string</subtag> if ($type == type::content && is_string($v)) { $content = new Metadata($this->component->context, $name, type::metadata); $content->setChildren([Text::from($this->component->context, $v)]); return $content; } return type::typecast($type, $v); }
function getDefaultValue($name) { if (!$this->macroInstance) { $this->noMacro(); } $param = $this->macroInstance->getParameter($name, $found); if (!$found) { throw new ComponentException($this->component, "Undefined macro parameter <kbd>{$name}</kbd>.\n<p>Available parameters: <b>" . implode(', ', $this->component->props->getPropertyNames()) . '</b>'); } $v = $param->getComputedPropValue('default'); if (isset($v) && $v !== '' && $param->props->type == 'content') { $meta = new Metadata($this->macroInstance->context, ucfirst($name), type::content); $meta->attachTo($this->component); $meta->addChild(Text::from($this->macroInstance->context, $v)); return $meta; } return $v; }
private function text_addComponent($content, $trim = self::NO_TRIM) { $context = $this->current->context; if ($context->condenseLiterals) { switch ($trim) { case self::TRIM_LEFT: $content = preg_replace(self::TRIM_LEFT_CONTENT, '', $content); break; case self::TRIM_RIGHT: $content = preg_replace(self::TRIM_RIGHT_CONTENT, '', $content); break; case self::TRIM: $content = preg_replace(self::TRIM_LITERAL_CONTENT, '', $content); break; } } if ($content != '') { if (isset($this->currentScalarProperty)) { $this->currentScalarValue .= $content; //Note: data binding will be taken care of later. } else { if ($content[0] == '{') { $lit = Text::from($context); $lit->setBindings(['value' => new Expression($content)]); } else { $lit = Text::from($context, $content); } $this->current->addChild($lit); } } }
protected function render() { $prop = $this->props; $inputFlds = $this->getClonedChildren(); if (empty($inputFlds)) { throw new ComponentException($this, "<b>field</b> parameter must define <b>one or more</b> component instances.", true); } // Treat the first child component specially /** @var Component $input */ $input = $inputFlds[0]; $append = $this->getChildren('append'); $prepend = $this->getChildren('prepend'); $fldId = $input->props->get('id', $prop->name); if ($fldId) { foreach ($inputFlds as $counter => $c) { if ($c->isPropertySet('hidden') && !$c->getComputedPropValue('hidden')) { break; } } // Special case for the HtmlEditor component. if ($input->className == 'HtmlEditor') { $forId = $fldId . "-{$counter}_field"; $click = "\$('#{$fldId}-{$counter} .redactor_editor').focus()"; } else { $forId = $fldId . "-{$counter}"; $click = $prop->multilang ? "focusMultiInput(this)" : null; } } else { $forId = $click = null; } if ($input->className == 'Input') { if ($prop->type && !$input->props->type) { $input->props->type = $prop->type; } switch ($input->props->type) { case 'date': case 'time': case 'datetime': $btn = Button::create($this, ['class' => 'btn btn-default', 'icon' => 'glyphicon glyphicon-calendar', 'script' => "\$('#{$input->props->id}-0').data('DateTimePicker').show()", 'tabIndex' => -1]); $append = [$btn]; } } if (exists($prop->icon)) { $append = [Text::from($this->context, "<i class=\"{$prop->icon}\"></i>")]; } $this->beginContent(); // Output a LABEL $label = $prop->label; if (!empty($label)) { $this->tag('label', ['class' => enum(' ', $prop->labelClass, $prop->required ? 'required' : ''), 'for' => $forId, 'onclick' => $click], $label); } // Output child components $hasGroup = $append || $prepend || $prop->groupClass || $prop->multilang; if ($hasGroup) { $this->begin('div', ['id' => "{$forId}-group", 'class' => enum(' ', when($append || $prepend || $prop->multilang, 'input-group'), $prop->groupClass)]); } $this->beginContent(); if ($prepend) { $this->renderAddOns($prepend); } if ($prop->multilang) { foreach ($inputFlds as $i => $input) { foreach ($prop->languages as $lang) { $this->outputField($input, $i, $fldId, $prop->name, $lang); } } } else { foreach ($inputFlds as $i => $input) { $this->outputField($input, $i, $fldId, $prop->name); } } if ($append) { $this->renderAddOns($append); } $shortLang = substr($prop->lang, -2); if ($prop->multilang) { echo html([h('span.input-group-btn', [h('button.btn btn-default dropdown-toggle', ["id" => "langMenuBtn_{$forId}", 'type' => "button", 'data-toggle' => "dropdown", 'aria-haspopup' => "true", 'aria-expanded' => "false"], [h('i.fa fa-flag'), h('span.lang', $shortLang), h('span.caret')]), h("ul.dropdown-menu dropdown-menu-right", ['id' => "langMenu_{$forId}", "aria-labelledby" => "langMenuBtn_{$forId}"], map($prop->languages, function ($l) use($forId) { return h('li', [h('a', ['tabindex' => "1", 'href' => "javascript:selenia.setLang('{$l['name']}','#{$forId}-group')"], $l['label'])]); }))])]); } if ($hasGroup) { $this->end(); } }