Custom Zend_Form element disappearing after validation - php

I've created a custom form element that allows me to place text in an arbitrary location in my form:
<?php
class Plano_Form_Element_Note extends Zend_Form_Element_Xhtml
{
public $helper = 'formNote';
/**
* Default decorators
*
* #return void
*/
public function loadDefaultDecorators()
{
if ($this->loadDefaultDecoratorsIsDisabled()) {
return;
}
$decorators = $this->getDecorators();
if (empty($decorators)) {
$this->addDecorator('ViewHelper')
->addDecorator('Errors')
->addDecorator('Label')
->addDecorator(array('row' => 'HtmlTag'), array('tag' => 'div', 'class' => 'form-row clearfix'));
}
}
}
This works like a charm, but as soon as I hit $form->isValid() the element turns up empty and only the wrapper shows:
<div class="form-row clearfix"></div>
The elements are added using subforms (method in my form class below):
/**
* Setup form elements and generate subforms
*
* #return Event_Form_Feedback_Enter
*/
protected function setupForm()
{
$partMapper = new Event_Model_FeedbackPart_Mapper();
$parts = $partMapper->fetchByFeedbackId($this->getFeedback()->getId(), array('order ASC', 'id DESC'));
foreach ($parts as $part)
{
switch ($part->getType())
{
case Event_Model_FeedbackPart::TYPE_TEXT:
$subform = new Event_Form_Feedback_Enter_Text();
break;
case Event_Model_FeedbackPart::TYPE_QUESTION_OPEN:
$subform = new Event_Form_Feedback_Enter_Question();
break;
case Event_Model_FeedbackPart::TYPE_QUESTION_MC:
$subform = new Event_Form_Feedback_Enter_MultipleChoiceQuestion();
break;
}
$subform->setup($part);
$this->addSubForm($subform, 'part-' . $part->getId());
}
$this->addSubmit();
}
... and here is the element creation in the actual form class (Event_Form_Feedback_Enter_Text):
protected function setupForm()
{
$element = new Plano_Form_Element_Note('description');
$element->setValue($this->getPart()->getDescription());
$this->addElement($element);
}

It turns out the validation was breaking for static form element. To solve this, I have overridden the isValid() method in my Plano_Form_Element_Note class:
public function isValid($value)
{
return true;
}

An alternative approach for placing arbitrary static content into a form is the AnyMarkup decorator.

Related

Custom Page in SilverStripe Admin

I want to create a fully custom Page in SilverStripe's (v3.4) Admin.
It should just display a Form (consisting of an UploadField and a FormAction to submit the File) which submits to a custom action.
So far this is my Code.
class ImporterAdmin extends LeftAndMain {
private static $url_segment = 'importer';
private static $url_rule = '/$Action';
private static $menu_title = 'Produkt Import';
private static $menu_icon = 'mysite/icons/pimport-icon.png';
private static $allowed_actions = array(
"run_import" => true
);
public function init() {
parent::init();
// Gather required client side resources
Requirements::javascript(CMS_DIR . '/javascript/CMSMain.EditForm.js');
}
public function index($req){
return $this->renderWith("ImporterAdmin_Content");
}
public function getResponseNegotiator() {
// Get the reponse negotiator
$negotiator = parent::getResponseNegotiator();
$controller = $this;
// Set the callback template
$negotiator->setCallback('CurrentForm', function() use(&$controller) {
return $controller->renderWith('ImporterAdmin_Content');
});
return $negotiator;
}
public function getImportForm($id = null, $fields = null) {
$fields = new FieldList(
UploadField::create("XMLFile", "XML File")
);
// Get the form actions
$actions = new FieldList(
FormAction::create('run_import', _t('CMSMain.SAVE', 'Save'))->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')
);
// Create the form
$form = CMSForm::create($this, 'ImportForm', $fields, $actions)->setHTMLID('Form_ImportForm');
// Set the response action, mostly for returning the correst template
$form->setResponseNegotiator($this->getResponseNegotiator());
// Add required classes to the form
$form->addExtraClass('cms-content center cms-edit-form');
// Convert buttons to button tags (apprently required for jQuery styling)
$actions = $actions->dataFields();
if($actions) {
foreach($actions as $action) {
$action->setUseButtonTag(true);
}
}
return $form;
}
/**
* Save the settings
* #param array $data The form data
* #param CMSForm $form The form object
* #return SS_HTTPResponse The SilverStripe viewresponse
*/
public function run_import($data, $form) {
print_r($data, $form);
}
But it displays nothing...
The ImportForm.ss file just contains the Variable $ImportForm

SilverStripe 3.6.1 - Use renderWith to display a loop of data objects

I'm working on rendering an array of data objects to a template using renderWith() but having some difficulty. What I have now does not render anything in the specified template except for whatever is in the basic Content WYSIWYG editor.
As a side note, I'm using the shortcodable module for reference, hence why the code is setup the way it is.
This is the function I have setup in a data object called Drawer which is meant to display a list of DrawerContents into the FaqLisitng template:
public function parse_shortcode($attributes, $content, $parser, $shortcode)
{
$drawers = "";
if (isset($attributes['Drawers'])) {
$Idx = explode(',', $attributes['Drawers']);
$drawerIdx = [];
foreach ($Idx as $did) {
$d = Drawer::get()
->filter('ID', $did)
->first();
if ($d->ID) {
array_push($drawerIdx, $d->ID);
}
}
$drawers = implode(',', $drawerIdx);
}
$drawerContents = DrawerContent::get()->filter(
array("DrawerID" => $drawers));
$drawerContentArray = array();
foreach($drawerContents as $dca){
$drawerContentArray = new ArrayData(array(
'FaqQuestion' => $dca->FaqQuestion,
'FaqAnswer' => $dca->FaqAnswer
));
}
return $drawerContentArray->renderWith('FaqListing');
}
And this is the FaqListing template contents:
<% with $getFaqListing %>
<strong>$FaqQuestion</strong><br/>
$FaqAnswer
<% end_with %>
I noticed that if I remove the with block, I do get the last entry for each DrawerContent data object that is being called in the parse_shorcode function, only the first entry--not the entire list for each DrawerContent data object. I get the same results if I use a loop instead of with.
I'm not sure what I'm doing wrong, but I feel like I'm close to getting things setup correctly.
For reference, in case it may help in providing a solution, here is the entire code for the Drawer data object:
<?php
class Drawer extends DataObject {
private static $db = array(
'FaqCategoryName' => 'varchar(250)',
);
private static $summary_fields = array(
'FaqCategoryName' => 'FAQ Category Name',
);
private static $has_many = array(
'DrawerContents' => 'DrawerContent',
);
/**
* Parse the shortcode and render as a string, probably with a template
Add a comment to this line
* #param array $attributes the list of attributes of the shortcode
* #param string $content the shortcode content
* #param ShortcodeParser $parser the ShortcodeParser instance
* #param string $shortcode the raw shortcode being parsed
* #return String
**/
public function parse_shortcode($attributes, $content, $parser, $shortcode) {
$drawers = "";
if (isset($attributes['Drawers'])) {
$Idx = explode(',', $attributes['Drawers']);
$drawerIdx = [];
foreach ($Idx as $did) {
$d = Drawer::get()
->filter('ID', $did)
->first();
if ($d->ID) {
array_push($drawerIdx, $d->ID);
}
}
$drawers = implode(',', $drawerIdx);
}
$drawerContents = DrawerContent::get()->filter(
array("DrawerID" => $drawers));
$drawerContentArray = array();
foreach($drawerContents as $dca){
$drawerContentArray = new ArrayData(array(
'FaqQuestion' => $dca->FaqQuestion,
'FaqAnswer' => $dca->FaqAnswer
));
}
return $drawerContentArray->renderWith('FaqListing');
}
public function getShortcodableRecords() {
return Drawer::get()
->map('ID')
->toArray();
}
/**
* Returns a list of fields for editing the shortcode's attributes
* in the insert shortcode popup window
*
* #return Fieldlist
**/
public function getShortcodeFields()
{
$all = Drawer::get()->map('ID');
$DrawersMultiList = ListboxField::create("Drawers", "Drawers")
->setMultiple(true)
->setSource($all);
return FieldList::create([
$DrawersMultiList,
]);
}
public function canView($member = null){
return true;
}
public function canEdit($member = null) {
return true;
}
public function canCreate($member = null) {
return true;
}
public function getTitle(){
$title = $this->FaqCategoryName;
return $title;
}
}
class DrawerAdmin extends ModelAdmin {
private static $managed_models = array('Drawer');
private static $url_segment = 'Drawers';
private static $menu_title = 'Drawers';
}
And code for the DrawerContent data object:
<?php
class DrawerContent extends DataObject {
private static $db = array(
'FaqQuestion' => 'varchar',
'FaqAnswer' => 'HTMLText',
);
private static $has_one = array(
'Drawer' => 'Drawer',
);
private static $summary_fields = array(
'FaqQuestion' => 'FAQ Question',
);
}
And the $getFaqListing function, which is created in Page.php
public function getFaqListing() {
return DrawerContent::get();
}

Joomla Comonent update data

I'm working on a Joomla 3 component. Currently I'm programming the backend. I'm having a form to add new data and it is working quite well. But when I want to update the data the component creates a new item instead of updating the existing.
I was searching for position which let Joomla know, that this is an update, but without success.
So my question: what is the information that makes Joomla updating the data?
My Code:
Table:ia.php
class mkTableia extends JTable
{
/**
* Constructor
*
* #param object Database connector object
*/
function __construct(&$db)
{
parent::__construct('#__tbl_ia', 'ID', $db);
}
}
Model: ia.php
class mkModelia extends JModelAdmin
{
public function getTable($type = 'ia', $prefix = 'mkTable', $config = array())
{
return JTable::getInstance($type, $prefix, $config);
}
public function getForm($data = array(), $loadData = true)
{
// Get the form.
$form = $this->loadForm('com_mk.ia', 'ia',
array('control' => 'jform', 'load_data' => $loadData));
if (empty($form))
{
return false;
}
return $form;
}
protected function loadFormData()
{
// Check the session for previously entered form data.
$data = JFactory::getApplication()->getUserState('com_mk.edit.ia.data', array());
if (empty($data))
{
$data = $this->getItem();
}
return $data;
}
}
View:view.html.php
class mkViewia extends JViewLegacy
{
/**
* display method of Hello view
* #return void
*/
public function display($tpl = null)
{
// get the Data
$form = $this->get('Form');
$item = $this->get('Item');
// Check for errors.
if (count($errors = $this->get('Errors')))
{
JError::raiseError(500, implode('<br />', $errors));
return false;
}
// Assign the Data
$this->form = $form;
$this->item = $item;
// Set the toolbar
$this->addToolBar();
// Display the template
parent::display($tpl);
}
/**
* Setting the toolbar
*/
protected function addToolBar()
{
$input = JFactory::getApplication()->input;
$input->set('hidemainmenu', true);
$isNew = ($this->item->ID == 0);
JToolBarHelper::title($isNew ? JText::_('COM_MK_MANAGER_MK_NEW')
: JText::_('COM_MK_MANAGER_MK_EDIT'));
JToolBarHelper::save('IA.save');
JToolBarHelper::cancel('IA.cancel', $isNew ? 'JTOOLBAR_CANCEL'
: 'JTOOLBAR_CLOSE');
}
}

ZF2 - Creating custom form view helpers

Some time ago, Matthew Weier O'Phinney posted this article on his blog about creating composite form elements in Zend Framework 1.
I'm trying to create the same element for my custom library in Zend Framewor 2 but I'm having issues finding the form view helper when rendering the form.
Here is my element (DateSegmented.php):
<?php
namespace Si\Form\Element;
use Zend\Form\Element;
use Zend\ModuleManager\Feature\ViewHelperProviderInterface;
class DateSegmented extends Element implements ViewHelperProviderInterface
{
public function getViewHelperConfig(){
return array( 'type' => '\Si\Form\View\Helper\DateSegment' );
}
protected $_dateFormat = '%year%-%month%-%day%';
protected $_day;
protected $_month;
protected $_year;
/**
* Seed attributes
*
* #var array
*/
protected $attributes = array(
'type' => 'datesegmented',
);
public function setDay($value)
{
$this->_day = (int) $value;
return $this;
}
public function getDay()
{
return $this->_day;
}
public function setMonth($value)
{
$this->_month = (int) $value;
return $this;
}
public function getMonth()
{
return $this->_month;
}
public function setYear($value)
{
$this->_year = (int) $value;
return $this;
}
public function getYear()
{
return $this->_year;
}
public function setValue($value)
{
if (is_int($value)) {
$this->setDay(date('d', $value))
->setMonth(date('m', $value))
->setYear(date('Y', $value));
} elseif (is_string($value)) {
$date = strtotime($value);
$this->setDay(date('d', $date))
->setMonth(date('m', $date))
->setYear(date('Y', $date));
} elseif (is_array($value)
&& (isset($value['day'])
&& isset($value['month'])
&& isset($value['year'])
)
) {
$this->setDay($value['day'])
->setMonth($value['month'])
->setYear($value['year']);
} else {
throw new Exception('Invalid date value provided');
}
return $this;
}
public function getValue()
{
return str_replace(
array('%year%', '%month%', '%day%'),
array($this->getYear(), $this->getMonth(), $this->getDay()),
$this->_dateFormat
);
}
}
And here is my form view helper:
<?php
namespace Si\Form\View\Helper;
use Zend\Form\ElementInterface;
use Si\Form\Element\DateSegmented as DateSegmented;
use Zend\Form\Exception;
class DateSegmented extends FormInput
{
/**
* Render a form <input> element from the provided $element
*
* #param ElementInterface $element
* #throws Exception\InvalidArgumentException
* #throws Exception\DomainException
* #return string
*/
public function render(ElementInterface $element)
{
$content = "";
if (!$element instanceof DateSegmented) {
throw new Exception\InvalidArgumentException(sprintf(
'%s requires that the element is of type Si\Form\Input\DateSegmented',
__METHOD__
));
}
$name = $element->getName();
if (empty($name) && $name !== 0) {
throw new Exception\DomainException(sprintf(
'%s requires that the element has an assigned name; none discovered',
__METHOD__
));
}
$view = $element->getView();
if (!$view instanceof \Zend\View\View) {
// using view helpers, so do nothing if no view present
return $content;
}
$day = $element->getDay();
$month = $element->getMonth();
$year = $element->getYear();
$name = $element->getFullyQualifiedName();
$params = array(
'size' => 2,
'maxlength' => 2,
);
$yearParams = array(
'size' => 4,
'maxlength' => 4,
);
$markup = $view->formText($name . '[day]', $day, $params)
. ' / ' . $view->formText($name . '[month]', $month, $params)
. ' / ' . $view->formText($name . '[year]', $year, $yearParams);
switch ($this->getPlacement()) {
case self::PREPEND:
return $markup . $this->getSeparator() . $content;
case self::APPEND:
default:
return $content . $this->getSeparator() . $markup;
}
$attributes = $element->getAttributes();
$attributes['name'] = $name;
$attributes['type'] = $this->getInputType();
$attributes['value'] = $element->getCheckedValue();
$closingBracket = $this->getInlineClosingBracket();
if ($element->isChecked()) {
$attributes['checked'] = 'checked';
}
$rendered = sprintf(
'<input %s%s',
$this->createAttributesString($attributes),
$closingBracket
);
if ($element->useHiddenElement()) {
$hiddenAttributes = array(
'name' => $attributes['name'],
'value' => $element->getUncheckedValue(),
);
$rendered = sprintf(
'<input type="hidden" %s%s',
$this->createAttributesString($hiddenAttributes),
$closingBracket
) . $rendered;
}
return $rendered;
}
/**
* Return input type
*
* #return string
*/
protected function getInputType()
{
return 'datesegmented';
}
}
This question describes adding the view helper as an invokable, but it's already being declared, as my custom library (Si) has been added to the 'StandardAutoLoader'.
OK, figured this one out eventually.
Copy Zend/Form/View/HelperConfig.php to the same location in your custom library. Adjust contents to reflect your view helpers.
Add the following to an event or bootstrap in your Module.php
$app = $e->getApplication();
$serviceManager = $app->getServiceManager();
$phpRenderer = $serviceManager->get('ViewRenderer');
$plugins = $phpRenderer->getHelperPluginManager();
$config = new \Si\Form\View\HelperConfig;
$config->configureServiceManager($plugins);
Update the 'Si' namespace with your custom one.
The 'class already exists' error was actually down to the includes at the top of my view helper file. I have updated it with:
use Zend\Form\View\Helper\FormElement;
use Zend\Form\Element;
use Zend\Form\ElementInterface;
use Zend\Form\Exception;
I also updated the instanceof statement to an absolute location due to the duplicate class names:
if (!$element instanceof \Si\Form\Element\DateSegmented) {
There were further errors in the translation from ZF1 to 2 but they are not related to this issue.
The way i understand your code is: You are creating a new Form\Element as well as a new Form\View\Helper. In this case the following information is needed for you:
The StandardAutoloader only takes care of actually finding the classes. The declaration of the invokables inside the getViewHelperConfig() is there, so the framework knows what Class to load when the ViewHelper is called.
In your case you'd do it like this:
public function getViewHelperConfig()
{
return array(
'invokables' => array(
'dateSegmented' => 'Si\Form\View\Helper\DateSegmented'
)
);
}
Zend Framework 2 does this for it's own ViewHelpers inside /Zend/Form/View/HelperConfig.php

Zend Forms - Element ID modification to allow re-use

I have a Zend_Form object that I want to re-use several times in one page. The problem I'm having is that each time it's rendered it has the same element IDs. I've been unable to find a method for giving all the IDs a unique prefix or suffix each time I render the form.
Complete Solution
Subclass Zend_Form:
class My_Form extends Zend_Form
{
protected $_idSuffix = null;
/**
* Set form and element ID suffix
*
* #param string $suffix
* #return My_Form
*/
public function setIdSuffix($suffix)
{
$this->_idSuffix = $suffix;
return $this;
}
/**
* Render form
*
* #param Zend_View_Interface $view
* #return string
*/
public function render(Zend_View_Interface $view = null)
{
if (!is_null($this->_idSuffix)) {
// form
$formId = $this->getId();
if (0 < strlen($formId)) {
$this->setAttrib('id', $formId . '_' . $this->_idSuffix);
}
// elements
$elements = $this->getElements();
foreach ($elements as $element) {
$element->setAttrib('id', $element->getId() . '_' . $this->_idSuffix);
}
}
return parent::render($view);
}
}
Loop in the view script:
<?php foreach ($this->rows as $row) : ?>
<?php echo $this->form->setDefaults($row->toArray())->setIdSuffix($row->id); ?>
<?php endforeach; ?>
You may subclass Zend_Form and overload render method to generate id's automatically:
public function render()
{
$elements = $this->getElements();
foreach ($elements as $element) {
$element->setAttrib('id', $this->getName() . '_' . $element->getId();
}
}
This is just a pseudo-code. Of course, you may modify this to suit your needs.
You could add a static integer property (let's say self::$counter)to your Zend_Form inherited class.
You increment it on the init() method.
For each element you create on your Zend_Form object you append that property to your element :
$element->setAttrib('id', self::$counter + '_myId');

Categories