I have a need for chained select boxes in my zend framework project: Countries->Regions->Provinces->Towns.
I am using zend form and intend to resubmit to reload the contents of the of the chained select boxes when one of them is changed. I have written some code in a PHPunit test to simulate what I will need in my controllers.
I will require to use this regional structure in quite a few different forms on my site and also plan to enhance with AJAX.
I don't want to be duplicating this code, so where should I store it and how should it be structured so that I can reuse its functionality. I thought perhaps an action helper?
public function testCanGetRegionalStructureFromUser() {
$user = new \Entities\User;
$user = $this->em->getRepository('Entities\User')->findOneByEmail('testuser#yahoo.com');
$town = new \Entities\Town;
$town = $user->getTowns_id();
// if user has not town input, then use the default
if (is_null($town)) {
$config = Zend_Registry::get('config');
$defaulttownid = $config->towns->defaultid;
$town = $this->em->getRepository('Entities\Town')->find($defaulttownid);
}
// get the town id
$townid = $town->getId();
//get the province
$province = $town->getProvinces_id();
$provinceid = $province->getId();
//get the region
$region = $province->getRegions_id();
$regionid = $region->getId();
//get the country
$country = $region->getCountries_id();
$countryid = $country->getId();
$countrylist = $this->em->getRepository('Entities\country')->findActiveCountries();
$regionlist = $this->em->getRepository('Entities\Region')->findActiveRegions($countryid);
$provincelist = $this->em->getRepository('Entities\Province')->findActiveProvinces($regionid);
$townlist = $this->em->getRepository('Entities\Town')->findActiveTowns($provinceid);
}
countrylist,regionlist etc. are ready to be injected into my form as options that will be used to populate the select boxes.
I think that for this case, creating a composite Zend_Form_Element makes the most sense.
This way, you can easily add the element to your forms with only a few lines of code, you can have validators built into the element so you aren't repeating that logic, and with your own decorator you can easily control the layout of the selections and only have to change one file to reflect the changes to all forms.
For one of my projects I created a composite element that had a plain text box that used autocomplete to search customers in real time as the user types. Once a customer is selected from the autocomplete list, an ajax call is made that fetches a list of properties owned by that customer and a dropdown box is updated with the new list.
The element provides access to the data values (customer, property) and the decorator renders the individual elements in a group, and set up the necessary event handlers and ajax calls (most of that code is in an external .js file.
Creating and Rendering Composite Elements
Similar to the above reference, by Matthew Weier O'Phinney
Video: Creating composite elements
Related
In this instance I'm working with two models: Departments, and Users.
Departments belongsTo Users
(In this instance, a user is a department manager [null allowed].)
Using FormHelper, I simply defined the selection of the User id as:
echo $this->Form->input('user_id', array('label'=>'Department/Group Manager (leave blank if none)', 'empty' => true));
By default, FormHelper seems to order the selection items by User.id ASC (the HTML select element's "value" property). To make things nicer in the add.ctp form, I created a virtual field as "Lastname, Firstname" to be used as the User model's display field:
public $virtualFields = array(
'name' => "CONCAT(User.lastName, ', ', User.firstName)"
);
public $displayField = 'name';
This worked great. Unfortunately, I'd love to be able to order the items in the rendered select box by the virtual field's value, ascending (or User.lastName in this case) instead of by User.id. I was unable to figure out a way to do this using FormHelper. Is there another way to do this (if FormHelper can't do it)?
MVC:
The MODEL retrieves the data (business logic).
The CONTROLLER sets the data for the view [$this->set()].
The VIEW simply handles your output, and any logic that is not capable of being handled elsewhere.
Using Cake convention based on how the cake bake output is created, you'd want to set the ORDER BY clause in the call to the model's find() method in your controller, related to the particular view. In this case, your Department's add() method.
public function add(){
// ... other code ...
$users = $this->Department->User->find('list', array('order' => array('lastName' => 'asc'));
$this->set(compact('users'));
}
Be aware that if you are using the Containable Behavior you may need to adjust its settings to achieve the default (most likely working) code example above.
I don't really know how to word the title well, but here's my issue. I decided instead of having 25 controllers to handle pages, I have one PageController with a viewAction that takes in a :page parameter - for example, http://localhost/website/page/about-us would direct to PageController::viewAction() with a parameter of page = about-us. All of the pages are stored in a templates folder, so the viewrenderer is set to render application\templates\default\about-us.phtml.
I did this so I can consolidate and it seemed like a better approach. My question is the following: lets say when the page request is contact-us, I would need a Zend_Form to be used within the contact page. So, I would need a way within PageController::viewAction() to recognize that the page needs to have a form built, build the form, and also upon submission the need to process it (maybe this should be handled in an abstract process method - not sure).
I have no idea how to implement this. I thought maybe I can store a column with the name of a form and a connecting page identifier. Even better, create a one-to-many page to forms, and then in the submission loop through the forms and check if submitted and if so then process it (maybe there is a isSubmitted() method within zend_form. I really don't know how to handle this, and am looking for any help i can get.
Thanks!
Here is something that came to mind that may work or help point you in a direction that works for you.
This may only work well assuming you were to have no more than one form per page, if you need more than one form on a page, you would have to do something beyond this automatic form handling.
Create a standard location for forms that are attached to pages (e.g. application/forms/page). This is where the automatic forms associated with pages will be kept.
In your viewAction, you could take advantage of the autoloader to see if a form for that page exists. For example:
$page = $this->getParam('page');
$page = ucfirst(preg_replace('/-(\w)/ie', "strtoupper('$1')", $page)); // contact-us -> ContactUs
$class = 'Application_Form_Page_' . $page;
// class_exists will invoke the autoloader to map a class to a file
if (class_exists($class)) {
// a form is defined for this page
$form = new $class();
// check if form was posted
if ($this->getRequest()->isPost()) {
if ($form->isValid($this->getRequest()->getPost()) {
// form is valid - determine how to process it
}
}
// assign the form to the view
$this->view->pageForm = $form;
}
All this really leaves out is the action you take to process a specific form. Since the contact form will likely generate an email, and another form may insert data into a database, you will need some sort of callback system or perhaps another class that can be mapped automatically which contains the form processor code.
Anyway something along those lines is what came to mind first, I hope that helps give you some more ideas.
I have created a new content type called protocol. The problem is that when you define a content type that means you also say how in the form the content is to be added and edited, like which form elements there will be.
A protocol is a content type that stores a title, an abstract and instructions. I want to add the title/instructions/abstract through one textarea where you tag the parts of the text like this:
[title]This is a title[/title] [abstract]This is an abstract. [/abstract][instructions]And these are my instructions.[/instructions]
That text is then processed and the content between each tag can be picked out and stored in a variable which should then be stored for the content type just like it had been added through a seperate field/textarea in a add/edit content form.
Is this possible to do? What kind of things should I read up on? Where in the drupal code are the function/functions that describes what happens when you push "Save" for a new content type for the standard add content form?(I just want to read it, not change anything)
Not sure this exactly matches what you're trying to do, but in a basic sense it should get you towards your goal. I wrote a module called endorse for Drupal 6 that provides a custom form feeding the submitted values into a new node:
http://drupal.org/project/endorse
Here's the form definition:
http://drupalcode.org/project/endorse.git/blob/refs/heads/master:/endorse.module#l136
Some basic validation follows and then the actual node save occurs at the top of the submit function, here up to line 231:
http://drupalcode.org/project/endorse.git/blob/refs/heads/master:/endorse.module#l206
The rest in that function is irrelevant except for the thank you and redirect at the very end of the submit function. If you're doing this in D7, it'll change a bit (see api.drupal.org for function definitions and whatnot), but it should look more or les the same.
Steps to solve your problem.
Create a module. Implement hook_menu with your custom add page.
Create a custom form using FORM API that it's gonna be displayed in your new page.
In your hook_form_submit get your values from the variable form state.
Parse the text and create and save a new node (snippet here).
$newNode = (object) NULL;
$newNode->type = 'protocol';
$newNode->title = $parsed_title;
$newNode->uid = 1;
$newNode->created = strtotime("now");
$newNode->changed = strtotime("now");
$newNode->status = 1;
$newNode->comment = 0;
$newNode->promote = 0;
$newNode->moderate = 0;
$newNode->sticky = 0;
// add CCK field data
$newNode->field_{YOUR_CUSTOM_FIELD_1}[0]['value'] = $parsed_data1;
$newNode->field_{YOUR_CUSTOM_FIELD_2}[0]['value'] = $parsed_data2;
// save node
node_save($newNode);
Those are the basic steps. If you have any more questions please ask.
TIP: Install the Devel module and use the function dpm() when you need to know the contents of some variable. You are probably gonna need it when you are implementing hook_form_validate or hook_form_submit for knowing the contents in the variable $form_state.
So just do:
dpm($form_state); //this will give you the variables inside the array with a krumo view.
I am using Symfony 1.4 to create project, and i need to create dynamic forms depending upon the question set type coming from database. i have used Symfony forms in rest of my project, but in this case using symfony forms seems to be tough, as i need dynamic form.
can it be safe to use normal HTML forms..in symfony project, or it is advisable to use Symfony forms. so need your help.
You can use html forms, but it would bypass symfony's form validation system.
You can still build dynamic forms by creating and adding input widgets to the current form, or a new form inside an action. You can then echo the form in the template and the dynamically generated fields will be part of the form as well.
If you start with a MyForm.class.php in the lib/forms, make sure to add:
$this->validatorSchema->setOption('allow_extra_fields', true);
Otherwise, you will automatically get validation errors. If you want to add fields to a form in an action you would do something like this:
$this->form = new MyForm();
$widgetSchema = $this->form->getWidgetSchema();
$widgetSchema['add_field'] = new sfWidgetFormInputText();
When you echo your form the 'add_field' input will be added to it.
It would help to have more information about what you're doing, but here's one way in which forms can be dynamic in Symfony. This code creates widgets and validators for a survey dynamically based on the "type" of a question:
class SurveyAnswerForm extends BaseSurveyAnswerForm
{
public function configure()
{
$question = $this->object->Question;
$method = sprintf('createWidgetAndValidatorFor%sInputType', $question->type);
$this->$method($question);
$this->getWidget('value')->setOption('label', $question->question);
$this->getValidator('value')->setOption('required', $question->required);
}
protected function createWidgetAndValidatorForTextFieldInputType(Question $question)
{
$this->setWidget('value', new sfWidgetFormInputText());
$this->setValidator('value', new sfValidatorString());
}
protected function createWidgetAndValidatorForTextAreaInputType(Question $question)
{
$this->setWidget('value', new wfWidgetFormTextareaAutosize());
$this->setValidator('value', new sfValidatorString());
}
//etc. for as many types as you require
}
Note: while this answer is code from one of my projects, it was heavily influenced by this answer over on SymfonyExperts.
I'm trying to build a form using the Zend_Form component, but the number of elements varies. The information for each Zend_Form element is stored in a database (name, options, validators, filters, etc.).
The application I'm working on consists of building surveys which contain a varying number of questions. Each question is associated with different arrays of answers. Ultimately my goal is to build arrays of radio/checkbox buttons, dynamically, server-side.
I'm looking for a pretty way to generate my form, but I'm not sure of the best way to load the model within the form. Should the model be loaded in the controller then passed (somehow, via a parameter?) directly to the form, or is it better to load the model within the Form init() method? Where's the best place to put the logic, should it be within the form class, or within the controller, or within the model?
My idea is to fetch form element properties (name, rules, filters, etc.) in the database, then iterate and finally render the form. What do you think of this approach? Ultimately, elements will be dynamically added (client-side), this time, using AJAX and a JavaScript library (such as jQuery).
Here are a couple useful links I found via Google, but I think they all answer a slightly different question than mine:
On building dynamic forms, server side:
http://framework.zend.com/wiki/display/ZFPROP/Zend_Form+generation+from+models+-+Jani+Hartikainen
http://weierophinney.net/matthew/archives/200-Using-Zend_Form-in-Your-Models.html
http://codeutopia.net/blog/2009/01/07/another-idea-for-using-models-with-forms/
On building dynamic forms, client side, with AJAX processing:
http://www.jeremykendall.net/2009/01/19/dynamically-adding-elements-to-zend-form/
I think I found a possible solution, it involves passing an array of Zend Form elements to the Zend Form::__construct() method. The constructor takes an array of options, one of them is called "elements". Have a look at the source code within the Zend Framework library.
I coded a new private method within the controller, called buildSurveyForm(). Note : the object, passed as a parameter, is built from a huge SQL query with half a dozen JOIN statements, fetching data from a few tables (surveys, questions, answers, etc.) within the database. One of the public attributes for this class consists of an array of questions, stored as objects (with public methods/attributes as well, etc.). Same for answers. The code for building these classes is pretty trivial and out of topic here.
Here's the code within the survey controller. I copy/pasted and edited/dropped a few lines to make it a lot clearer :
private function buildSurveyForm(MyApp_Object_Survey $survey)
{
foreach ($survey->questions as $question)
{
$element = new Zend_Form_Element_MultiCheckbox($question->order);
$element->addMultiOptions($question->getAnswersLabels());
$element->setName($question->order);
$element->setLabel($question->title);
$elements[] = $element;
}
// Here's the trick :
$formOptions = array('elements' => $elements);
$surveyForm = new MyApp_Survey_Form($formOptions);
$urlHelper = $this->_helper->getHelper('url');
$surveyForm->setAction($urlHelper->url(array(
'controller' => 'survey',
'action' => 'vote'),
'default'
));
$surveyForm->setMethod('post');
$this->_forms['survey'] = $surveyForm;
return $this->_forms['survey'];
}
The MyApp Survey Form class only contains a Submit button within the init() method. The dynamically generated elements with the code above are added BEFORE this submit button (which is unexpected, but useful). This class simply extends Zend_Form.
Then, within survey controller / view action :
public function viewAction()
{
$surveyModel = $this->_model['survey'];
$survey = $surveyModel->getFullSurvey($this->_getParam('id'));
$survey = new MyApp_Object_Survey($survey);
// Calls above private method :
$surveyForm = $this->buildSurveyForm($survey);
$this->view->assign(array(
'surveyForm' => $surveyForm,
));
}
Adding filters, validators and decorators to form elements is now trivial. My proposal is a bit dirty, but I think it gets the job done. I will add a new proposal if I find something more elegant. Feel free to post different answers/solutions.
You could extend Zend_Form.
Zend form is not good place for logic, only form representation.
So, Load all needed elements using model in controller and pass them to the form in contructor as parameters.