Add Database Row Button Cakephp - forms

The Controller:
function add(){
if (!empty($this->data)) {
$qnote = $this->Qnote->save($this->data);
if (!empty($qnote)) {
$this->data['Step']['qnote_id'] = $this->Qnote->id;
$this->Qnote->Step->save($this->data);
}
$this->Session->setFlash('Your note has been saved.');
$this->redirect(array('action' => 'index'));
}
}
The Form.
<?php
$userID = Authsome::get('id');
echo $form->create('Qnote', array('action'=>'add'));
echo $form->input('Qnote.id', array('type' => 'hidden'));
echo $form->input('Qnote.user_id', array('value' => $userID, 'type' => 'hidden'));
echo $form->input('Qnote.subject');
echo $form->input('Qnote.body', array('rows' => '3'));
echo $form->input('Step.id', array('type' => 'hidden'));
echo $form->input('Step.user_id', array('value' => $userID, 'type' => 'hidden'));
echo $form->input('Step.body', array('rows' => '3'));
echo $form->end('Save Notes');
?>
This Form Adds Data in 2 Models.
Model 1 = Qnote;
Model 2 = Step;
I am able to add Data to the Models.
I was wondering I could add a button to the form
The Button would allow users to add multiple Step.data to the Step model.
Some like a +1 Button.
Basically I want to add multiple steps Per Qnote.
Could someone point me in the right direction how i can achieve this.

This is something I would do with jQuery. Basically all you need to do is using jQuery to dynamically add more inputs in the CakePHPs conventions: Step.0.user_id for example.
What you need to do now on a +1: you need to count the zero up, so you will get Step.1.user_id and so on.
First option: Use a jQuery-Script for doing this
var count = 1;
$('#add_step').click(function() {
var new_form = $('.Step').eq(0).clone();
$('input, textarea, select, radio', new_form).filter('[name^="data"]').each(function() {
var name = $(this).attr('name');
var new_name = name.replace(/\[\d*\]/, '['+count+']');
$(this).attr('name', new_name).attr('value', '');
});
$('#YourForm').after(new_form);
count+;
return false;
});
In this case you're cloning a div with the class step which holds your inputs for the model Step. You then replace the name-attribute to replace the zeros through the new value of the variable count. count++ enables you to add as many steps as you want.
This is an jQuery only solution and may require additional work for your environment.
Second option: Use AJAX with an element
You could also write a function in your StepsController which renders an element which holds your form and takes care of the counter.
Third option: Use a URL-parameter to decide how many options you want
If you have an URL like /qnote/add/3 you could use the 3 as a parameter in a for-loop to iterate through these form-inputs.
You need to take care, that eventually already typed in values are sent with the form when adding another Step so that these don't get lost.
Hope this helps to get on the right way.

Related

Yii2 One field in different tabs of buttflattery\yii2-formwizard

Now I am workin on complex quiz App on Yii2. Here is MCQ test that contains more than 100 questions. I want to separate this questions into 5 form tabs (so that questions from 1 to 20 in tab1, from 21 to 40 in tab2 etc). Could someone explain what is the way to do this? So, there is only one model and one form submit.
I thought about using the buttflattery\yii2-formwizard. In the documentation, I have found the Single Model Across Steps tutorial, but it is not really suitable for my case because all questions are written in one field as many rows.
For now Answers Model is following:
class Answers extends ActiveRecord
{
public function rules(){
return[
[['id','question_id', 'option_id', 'user_id'], 'required'],
];
}
}
index view:
//start form
<?php $form = ActiveForm::begin([
'id' => 'my-form-id',
'action' => ['answers/save'],
'options' =>['class'=>['t-form']]
]);
?>
//foreach question:
<?php for ($i=0; $i<count($questions); $i++): ?>
<div class="input-title">
<?= Html::encode("{$questions[$i]->title}") ?>
</div>
<?php $options = Options::find()-> where
(['question_id'=>$questions[$i]->id]) ->all();
$options = ArrayHelper::map($options,'id', 'title');?>
//print options:
<div class="radio__wrapper">
<?= $form->field($model, 'option_id')->radioList(
$options,
['name'=>'Questions['.$questions[$i]->id.']',
'separator' => '<br>',
'required'=>true],)->label(false) ?>
</div>
//submit form
<?= Html::submitButton('Save', ['class' => 'submit']) ?>
<?php ActiveForm::end(); }
AnswersController:
public function actionSave(){
$request = \Yii::$app->request;
foreach($request->post('Questions') as $key=>$value) {
$model = new Answers();
$model->load($request->post());
$model->option_id = $value;
$model->question_id = $key;
$model->user_id = \Yii::$app->user->id;
$model->save(false);
}
if( $model->save(false)){
return $this->redirect(['result/index']);
}
}
If FormWizard is not suitable variant please explain me what is the most efficient way?
yii2-formwizard does provide you with a lot of options, which create a form wizard using the ActiveForm and Models.
Prominent Features
You can use a single model across all steps.
Separate model dedicated to every step.
Multiple models to a single step.
Disable/Enable validation.
Customize & order form fields.
Tabular Steps with Add Row button to add fields dynamically like Adress book.
Form Persistence (saves the un-saved form to be restored later using localstorage).
Preview Step ( Previews all the form input with labels as the last step, and navigates to the step when clicked).
Multiple Themes
Demos
You can see the DEMOS with all available variations and for Documentation use the Wiki
SETUP
use composer to install the extension
php composer.phar require buttflattery/yii2-formwizard "#dev"
or add into the composer.json file under require section
"buttflattery/yii2-formwizard":"#dev"
Sample Code
use buttflattery\formwizard\FormWizard;
$shootsModel = new Shoots();
$shootTagModel = new ShootTag();
echo FormWizard::widget([
'steps'=>[
[
'model'=>$shootsModel,
'title'=>'My Shoots',
'description'=>'Add your shoots',
'formInfoText'=>'Fill all fields'
],
[
'model'=> $shootTagModel,
'title'=>'My Shoots',
'description'=>'Add your shoots',
'formInfoText'=>'Fill all fields'
],
]
]);

CSRF field is missing when I embed my form with a requestAction in CakePHP 3

I want to embed a contact form in multiple places on my website.
I developed a contact form in a contact() function within my MessagesController.php:
// MessagesController.php
public function contact()
{
$this->set('title', 'Contact');
$message = $this->Messages->newEntity();
... // shortened for brevity
$this->set(compact('message'));
$this->set('_serialize', ['message']);
}
I loaded the CSRF component in the initialize() function of the AppController.php:
// AppController.php
public function initialize()
{
parent::initialize();
$this->loadComponent('Csrf');
... // shortened for brevity
}
The form is rendered with a contact.ctp and it works fine.
I followed CakePHP's cookbook which suggests using requestAction() within an element, then echoing the element where I want it:
// contact_form.ctp
<?php
echo $this->requestAction(
['controller' => 'Messages', 'action' => 'contact']
);
?>
And:
// home.ctp
<?= $this->element('contact_form'); ?>
The problem is that the form is rendered fine, but the CSRF hidden field is missing. It should be automatically added to the form since the CSRF component is called in the AppController.php.
I guess either using an element with a requestAction() isn't the solution for this particular case, or I am doing something wrong.
Any ideas? Thanks in advance for the input!
Request parameters need to be passed manually
requestAction() uses a new \Cake\Network\Request instance, and it doesn't pass the _Token and _csrf parameters to it, so that's why things break.
While you could pass them yourself via the $extra argument, like
$this->requestAction(
['controller' => 'Messages', 'action' => 'contact'],
[
'_Token' => $this->request->param('_Token'),
'_csrf' => $this->request->param('_csrf')
]
);
Use a cell instead
I would suggest using a cell instead, which is way more lightweight than requesting an action, also it operates in the current request and thus will work with the CSRF component out of the box.
You'd pretty much just need to copy your controller action code (as far as the code is concerned that you are showing), and add a loadModel() call to load the Messages table, something like
src/View/Cell/ContactFormCell.php
namespace App\View\Cell;
use Cake\View\Cell;
class ContactFormCell extends Cell
{
public function display()
{
$this->loadModel('Messages');
$this->set('title', 'Contact');
$message = $this->Messages->newEntity();
// ... shortened for brevity
$this->set(compact('message'));
$this->set('_serialize', ['message']);
}
}
Create the form in the corresponding cell template
src/Template/Cell/ContactForm/display.ctp
<?php
echo $this->Form->create(
/* ... */,
// The URL needs to be set explicitly, as the form is being
// created in the context of the current request
['url' => ['controller' => 'Messages', 'action' => 'contact']]
);
// ...
And then wherever you want to place the form, just use <?= $this->cell('ContactForm') ?>.
See also
API > \Cake\Routing\RequestActionTrait::requestAction()
Cookbook > Views > Cells

Form with managed files weird behaviour on form error

I have created a content type with an associated image field.
Each user is able to see a list of all the nodes of this content type and should be able to upload a new image in the appropriate field.
I've tried many solutions, but in the end I'm trying with a form and managed files.
In the page with the list of all the nodes I'm creating a lightbox with a form for each node.
Each form is created like this:
function coverupload_form($form, &$form_state, $uid, $relid) {
$form['#attributes']['id'] = 'coverup-'.$relid;
$form_state['storage']['rid'] = $relid;
$cliente = cataloghi_user_edit_get_cliente($uid);
$release = node_load($relid);
$form['cover'] = array(
'#title' => 'Carica la cover per la release '.$release->title,
'#description' => 'I file caricati devono avere estensione \'.jpeg\', risoluzione di 1440x1440 e dimensione massima di 5MB',
'#type' => 'managed_file',
'#upload_location' => 'public://clienti/'.$cliente->title.'/cover',
'#upload_validators' => array(
'file_validate_extensions' => array('jpeg, jpg'),
// Pass the maximum file size in bytes
'file_validate_size' => array(5*1024*1024),
'file_validate_image_resolution' =>array('1440x1440', '1440x1440'),
),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('CARICA'),
);
return $form;
}
function coverupload_form_submit($form, &$form_state) {
$file = file_load($form_state['values']['cover']);
// Change status to permanent.
$file->status = FILE_STATUS_PERMANENT;
// Save.
file_save($file);
$nodo = node_load($form_state['storage']['rid']);
$nodo->field_release_copertina['und'][0] = (array)$file;
if($nodo = node_submit($nodo)) { // Prepare node for saving
node_save($nodo);
}
}
All the forms have display: none, and when the user click on the cover upload button only the corresponding form is showed in the lightbox.
Well, everything works fine when the image is validated.
The problems start when the image is not validated (like if it's below 1440x1440px).
If I check the lightbox with the inspector, the correct number of forms is generated but they all refer to the same node (so they all have id 'coverup-17' for example).
I have checked everything, and it seems like I pass the correct values to the form everytime, so I'm starting to think that it may be a problem connected with my poor understanding of forms.
Would it be better to try a different type of approach?
Thanks and sorry for if this is a bit messy...
I managed to solve the problem.
It depended on the fact that I had multiple instances of the same form on the same page.
I implemented hook_forms().
function mymodule_forms($form_id, $args) {
$forms = array();
if(strpos($form_id, 'coverupload_form_') === 0) {
$forms[$form_id] = array(
'callback' => 'coverupload_form',
'callback arguments' => array($args[0], $args[1]),
);
}
return $forms;
}
and then I changed my form call to
drupal_render(drupal_get_form('coverupload_form_'.$relid, $arg1, $arg2));

CakePHP: allowing database update with button click

I have a product search page with the form below. The search result is displayed on the same page with search bar at the top.
echo $this->Form->create('Searches', array('action'=>'products', 'type' => 'get', 'name' => 'textbox1'));
echo $form->input($varName1, array('label' => false));
echo $form->end('Locate');
I also have a little box next to the search result that allows (it doesn't work yet) the user to flag using checkboxes a product and accordingly update its database (table products and using model Product) with a button click. Note that I have a Searches controller for this search page.
<form method="link" action="/myapp/product/test_update_db>
<label><input type="checkbox" name="flag1" <?php echo $preCheckBox1; ?>>Flag 1</input></label>
<label><input type="checkbox" name="flag2" <?php echo $preCheckBox2; ?>>Flag 2</input></label>
<input type="submit" value="Update">
</form>
I'm having difficulty with this approach figuring out how to perform this check-box-and-DB-update routine. I'm getting to the link I'd like to go (/myapp/product/test_update_db), but I don't know how to take variables flag1 and flag2, along with row ID of this result ($results['Product']['id'])) to the new page.
Could someone guide me on how to perform this neatly? Is this general approach correct? If not, what route should I be taking? I'd prefer not to use javascript at this time, if possible.
EDIT: I think I can make this work if I use the URL for passing data.. but I'd still like to know how this could be done "under the hood" or in MVC. I feel like I'm hacking at the CakePHP platform.
UPDATE: So, I ended up using the URL parameters for retrieving information pieces like flag1 and flag2. I'm still looking for an alternative method.
To see where your is-checkbox-checked data is located, do the following in your controller:
// Cake 2.0+
debug($this->request->data);
// previous versions
debug($this->data);
If you want to pass data to your search controller from the current page, you can always add the data to your form:
$this->input
(
'Product.id',
array
(
'type' => 'hidden',
'value' => $yourProductId
)
);
I ended up using information embedded in the URL for getting submission data. Something like below..
In Products controller, when the form with flag1 and flag2 are submitted:
public function test_update_db() {
// Get variables from URL, if any, and save accordingly
$result = $this->Product->updateProduct($this->params['url'], 'url');
if ($result) {
$this->Session->setFlash('Successfully updated!', 'default', array('class' => 'success'));
$this->redirect($this->referer());
}
else {
$this->Session->setFlash('Update was unsuccessful!', 'default', array('class' => 'error'));
$this->redirect($this->referer());
}
}
This works for doing what I needed to do. I feel like there's a more proper way to do this though.
if ($result) {
$this->Session->setFlash('Successfully updated!', 'default', array('class' => 'success'));
$this->redirect($this->referer());
}

form with no form_id(Drupal 6.x)

I have this form placed inside a block and it is assigned to right region of my site. Form is displayed just fine. But the submit button doesn't work as intended - to call submit function. So, I did some debugging and found an anomaly that there is no essential data - such as form_id and tokens - drupal normally injected to every form. As I can't figure out the root cause of this, I'm here for pointers of friends from here. Here's an excerpt of my code -
function mymodule_block($op = 'list', $delta = '', $edit = array()) {
switch ($op) {
case 'list':
$blocks['quick_search'] = array(
'info' => t('Quick Search'),
);
return $blocks;
case 'view':
switch ($delta) {
case 'quick_search':
$block['subject'] = t('Quick Search');
$block['content'] = drupal_get_form("block_quick_search");
break;
}
return $block;
}
}
function block_quick_search(&$form_state){
$form = array();
.
.
.
$form['quick_search_submit'] = array(
'#type' => 'submit',
'#value' => t('Search'),
'#submit' => array('mymodule_quick_search'),
);
return $form;
}
function mymodule_quick_search($form, &$form_state){
drupal_goto($base_path,"..............");
}
Thanks in advance
there is no essential data - such as form_id and tokens
This is indeed the reason why form submissions are not processed correctly. Check whether drupal_prepare_form is called on your form and whether it adds those items correctly. It is called by drupal_get_form if he form is not posted (and thusly not retrieved from the cache).
If $form['#token'] and $form['form_id'] are added correctly, I suspect something is wrong with translating the form to HTML. Do you use any custom theming for the form?
Try to pass your submit handler to the main form and not on the item submit like that:
$form['#submit'][] = 'mymodule_quick_search';
it should work.