My debug value is set to 2 and I think because there's no data being passed here I'm getting a validation error, which is why this was first appearing to be a phantom error. I'm not sure though as I'm still new to Cakephp.
I have an Addresses controller that is passing data to my model that looks like so:
public function add() {
if ($this->request->is('post')) {
$this->Address->create();
if ($this->Address->save($this->request->data)) {
$this->Session->setFlash(__('The address has been saved.'));
return $this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The address could not be saved. Please, try again.'));
}
}
$users = $this->Address->User->find('list');
$this->set(compact('users'));
}
However when I debug using die(debug($this->request->data)))) I get the following result, revealing that there is nothing being passed in my id field:
/app/Controller/AddressesController.php (line 61)
array(
'Address' => array(
'id' => '',
'building_number' => '1',
'street_name' => 'Test Street',
'city' => 'Cityville',
'post_code' => 'N113AQ',
'user_id' => '1'
)
)
Any ideas as to why nothing is being passed into the id field? I have controllers working in exactly the same way for different tables that go off perfectly fine.
My Address.id field in my database is set to auto increment and is named correctly, $this->Address->save() returns false too.
If $this->request->data contains an array element named after the form’s model, and that array contains a empty value of the model’s primary key(here the id), then the FormHelper will create an add form for that record. For add method HTTP POST verb is ok. POST is for create and PUT is for edit in CakePHP. You can check validations and change the request type to non-GET. It may work.
if (!$this->request->is('get')){
//Save logic here
}
Related
The app I'm working on (an order form) allows the user to enter multiple sub-records within an iframe. These sub-records are joined to the main record via a foreign key.
main_records line_items
----------- ----------
id int(11) PK etc. id int(11) PK etc.
main_record_id (FK)
I need the app to check whether at least one line item exists within this iframe before form submission. I would like to take advantage of the $validate functionality within the model, but I'm unsure how to proceed. Here's what I've tried in the Main model:
App::uses('AppModel', 'Model', 'LineItem');
public $hasMany = array(
'LineItem' => array(
'className' => 'LineItem',
'foreignKey' => 'main_record_id',
'dependent' => false
)
);
public $validate = array(
'main_record_id' = array(
'allowEmpty' => false,
'rule' => 'checkForLineItem',
'message' => 'You must enter at least one line item!'
)
);
//Check to make sure there is at least one line item before saving changes/submitting for approval
function checkForLineItem($id) {
$lines = $this->LineItem->find('all', array(
'fields' => array('LineItem.main_record_id'),
'conditions' => array('LineItem.main_record_id'=>$id, 'LineItem.deleted_record'=>0))
);
if(!empty($lines)) {
return true;
} else {
return false;
}
}
I also track whether the line item has been deleted. If it has, then it is not added to $lines.
I know I can accomplish this in the Controller, but as far as I know, that would require the form to post, and the user would lose any changes upon postback (I haven't yet implemented jQuery on this form). Am I on the right track with how to do this? What changes should I make to get this to work?
Your code looks about right, but validation indeed happens in form submit. If you want to check it prior to that you have to do in JavaScript (jquery). E.g. create a controller action that return if there are existing line items for given main record id and call it via AJAX.
I have a SilverStripe site with some code to display a search form. The for allows you to search for something based on 3 things. Problem is, I'm not sure how to get the results to display correctly on a separate page.
My code:
class InstitutionSearchPage extends Page {
}
class InstitutionSearchPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'Search'
);
public function Form() {
$fields = new FieldList(array(
DropdownField::create('DegreeType', 'Degree', QualificationType::get()->filter('ParentID', 0)->map()),
DropdownField::create('Course', 'Course', Qualification::get()->map()),
DropdownField::create('City', 'City', City::get()->map()),
));
$actions = new FieldList(array(
FormAction::create('Search')->setTitle('Find a College')
));
$validator = ZenValidator::create();
$validator->addRequiredFields(array(
'DegreeType' => 'Please select a Degree',
'Course' => 'Please select a course',
'City' => 'Please select a city',
));
$form = new Form($this, 'Search', $fields, $actions, $validator);
$form->setLegend('Criteria')->addExtraClass('form-horizontal')->setAttribute('data-toggle', 'validator');
// Load the form with previously sent data
$form->loadDataFrom($this->request->postVars());
return $form;
}
public function Search() {
return array('Content' => print_r($this->getRequest()->postVars(), true));
}
}
It seems to be displaying results on the same page but gives me a bunch of weird data. For example, I got this when I tested the form: Array ( [DegreeType] => 53 [Course] => 1 [City] => 1 [SecurityID] => 02718d0283e27eeb539eff19616e0b23eadd6b94 [action_Search] => Find a College )
The result is supposed to be an organized list of colleges (along with other data).
I guess that what you are seeing is expected behavior, as the form will post to the Search function, and that one is just returning a print_r of an array with the post vars which will be picked up by the template.
Otherwise, there are a lot of things not corresponding with the Silverstripe default way to handle forms. Please take a look here and change your form accordingly: https://docs.silverstripe.org/en/3.4/tutorials/forms/
For example, give the form the same name as your function (or in your case, change the function name to the form name). Then implement the action function.
I need help with tracing this web application. I'm very new to Yii, and I'm trying to dissect an existing app to understand it better. I'm trying to create an edit function, which video tutorials lead me to believe has the exact same process as an add function [save()], except you specify the ID to be overwritten (and I very well could be wrong about this).
Near as I can tell, the following files are in play:
views/forum/view.php
views/forum/_commentform.php
views/forum/_comments.php
controllers/ForumController.php
models/Forum.php
models/Comment.php
I can't really change much of the existing, though I can add my own. It starts with view.php, where much of the stuff is displayed. At the bottom of it is this:
<?php
$this->widget('zii.widgets.CListView',
array('dataProvider'=>$dataProvider, 'itemView'=>'_comments', 'summaryText'=>'',));
?>
_comments.php displays all the usual elements of a comment, like say, from Facebook. There's an edit button there that I made, code here:
<?php echo CHtml::link(CHtml::encode('Edit'),array('forum/editcomment','reply'=>$data->id,'topic'=>$data->content_id)); ?>
That edit button gets the ID of the current comment from the database. Near as the application logs can tell me, this does work.
That calls this particular function in ForumController.php:
public function actionEditComment() {
if(isset($_GET['reply'])) {
$comment=Comment::model()->findByAttributes(array('id'=>$_GET['reply']));
$topic=Forum::model()->findByAttributes(array('id'=>$comment->content_id));
$this->renderPartial('_commentform', array('forum'=>$topic, 'model'=>$comment, 'view'=>'view',));
}
}
Next is the _commentform.php. Nothing much, just a textbox, though it does check if an ID is present; if it is, it is editing an existing comment, otherwise, it is creating a new one. A submit button also changes from Reply to Update, depending on the value of isNewRecord.
EDIT: There's also a CActiveForm, in case that of any help. Might have something to do with routing?
<?php $form=$this->beginWidget('CActiveForm', array(
'id'=>'comment-form',
'action'=>Yii::app()->createUrl('forum/view/id/'.$forum->id),
'enableAjaxValidation'=>false,
)); ?>
<?php
if ($view == 'view') {
echo CHtml::submitButton($model->isNewRecord ? 'Reply' : 'Update', array('id'=>'comment'.$model->id));
}?>
Again, confirmed via application logs, the ID of the comment is being passed through, albeit as id => comment<commentID>. Then this is where things get hazy. I assume the flow goes back to ForumController.php, where, per my logging, the ID is lost.
Here's the parts of the ForumController.php that I deem responsible:
public function actionView() {
$post=$this->loadModel();
$comment=$this->newComment($post);
$viewcount=$post->view_count+1;
$post->view_count=$viewcount;
$post->save();
$this->render('view',array('model'=>$post, 'dataProvider'=>$dataProvider,));
}
private $_model;
public function loadModel() {
if($this->_model===null) {
if(isset($_GET['id'])) {
$this->_model=Forum::model()->findByPk($_GET['id'], $condition);
}
if($this->_model===null)
throw new CHttpException(404,'The requested page does not exist.');
}
return $this->_model;
}
protected function newComment($post) {
$comment=new Comment;
if(isset($_POST['Comment'])) {
$comment->attributes=$_POST['Comment'];
$post->addComment($comment);
}
return $comment;
}
Interestingly, if I write out $comment from newComment() out to the log, it does print out the edited comment (i.e., it prints out "john cena" if I edited the existing comment "who is champ?"), but $comment->id yields a null, which I assume is why instead of updating, the edited comment is saved as a new one.
As for the models, Forum.php and Comment.php strangely point to the same database table, because for some reason they decided to put Forums and Comments into one table. Forum.php also contains the actual addComment function (a placement I find weird), though by the time the flow gets there, the Comment ID is of course null, though the edited comment itself is there.
Where did I go wrong? Did I miss anything?
EDIT: Here's the attributes and rules for the Comment model:
public function attributeLabels() {
return array(
'id' => 'ID',
'node_type' => 'Node Type',
'party_id' => 'Party',
'category' => 'Category',
'title' => 'Title',
'content' => 'Content',
'date_created' => 'Create Time',
'date_modified' => 'Update Time',
'status' => 'Status',);
}
public function rules()
{
/* combine parent and own rules */
$parentRules = parent::rules();
$myRules = array(
array('node_type_id', 'default', 'value'=>'7'), /* set type to Person */
array('node_type_id', 'in', 'range'=>array('7')), /* allow only Person type */
array('party_id, date_created, date_modified, status', 'numerical', 'integerOnly'=>true),
array('category, title, content', 'safe'),
);
/* you want to apply parent rules last, delete them here if necessary */
return array_merge($myRules);
}
Could you post Comment class defenition here?
I think you don't have rule for "id" in Comment::rules(),
When rule for attribute is not defined attribute will be unsafe and you can't set it value by $comment->attributes, or you can change you code to something like:
if(isset($_POST['Comment']) && isset($_POST['Comment']['id'])) {
$comment = Comment::model()->findByPk($_POST['Comment']['id']);
$comment->attributes=$_POST['Comment'];
$post->addComment($comment);
}
I've got problem with field validation.
I would like to validate form through model. I want to check if field with some value exists.
I would like to block using some titles more than once.
For example
if field "Site" with title "Main" exists in database, you can't validate form.
If it doesn't exist, you can pass it.
I would like to allow user to add just one "Site" with title "Main", but he can add "Site" with any other title in any case.
Have you got some idea how to solve it?
I think you have two options.
(1) Setup an Ajax request to the server.
To do so:
Create a function, that responds to an Ajax request, in your SiteController named checkName()
public function checkName($name) {
// allow ajax requests
$this->request->allowMethod(['ajax']);
// perform your check within the db
$isExistent = [...];
// prepare the response
$response = ['name' => $name, 'isExistent' => $isExistent];
if ($this->request->isAjax()){
$this->autoRender = false;
$this->response->disableCache();
$this->response->type(['json' => 'application/json']);
$this->response->body(json_encode($response));
}
}
Add the route to your routes file with the option '_ext' => 'json'
Prepare your Javascript Ajax function that call the route you have defined and attach it on the onchange attribute of your input field. (see this link for a simple example: http://www.w3schools.com/jquery/ajax_ajax.asp)
(2) Make the 'name' field of the Site table unique.
To do so you could add the following function to your SiteTable class
public function buildRules(
RulesChecker $rules
) {
$rules->add($rules->isUnique(['name']));
return $rules;
}
Assume I'm in my items controller.
Ok say I am in my view action (the url would be something like /items/view/10012?date=2013-09-30) which lists a list of items that belongs to a client on a given date.
I want to link to add a new item. I would use the htmlhelper like so:
echo $this->Html('action'=>'add');
In my add action I have a form which has fields like client_id and item_date.
When I'm in my view action I know these values as I am viewing the items for a specific client on a specific date. I want to pass these variables to my add action so it will prefill those fields on the form.
If I add a query string in my link ('?' => array('client_id'=>$client_id)) it breaks the add action as it will give an error if the request is not POST. If I use a form->postLink I get another error as the add action's POST data must only be used for adding the record, not passing data to prefill the form.
I basically want to make my link on the view page pass those 2 variables to the add action in the controller so I can define some variables to prefill the form. Is there a way to do this?
Here is my add controller code. It may differ in content a bit from my question above as I have tried to simplify the question a bit but the concept should still apply.
public function add(){
if ($this->request->is('post')) {
$this->Holding->create();
if ($this->Holding->save($this->request->data)) {
$this->Session->setFlash(__('Holding has been saved.'), 'default', array('class' => 'alert alert-success'));
return $this->redirect(array('action' => 'index'));
}
$this->Session->setFlash(__('Unable to add your holding.'), 'default', array('class' => 'alert alert-danger'));
}
$this->set('accounts', $this->Holding->Account->find('list'));
$sedol_list = $this->Holding->Sedol->find('all', array(
'fields' => array(
'id', 'sedol_description'
),
'recursive' => 0,
'order' => 'description'
)
);
$this->set('sedols', Hash::combine($sedol_list, '{n}.Sedol.id', '{n}.Sedol.sedol_description') );
}
Why not use proper Cake URL parameters?
echo $this->Html->link('Add Item', array(
'action' => 'add',
$client_id,
$item_date
));
This will give you a much nicer URL like:
http://www.example.com/items/add/10012/2013-09-30
And then in your controller, you modify the function to receive those parameters:
public function add($client_id, $item_date) {
// Prefill the form on this page by manually setting the values
// in the request data array. This is what Cake uses to populate
// the form inputs on your page.
if (empty($this->request->data)) {
$this->request->data['Item']['client_id'] = $client_id;
$this->request->data['Item']['item_date'] = $item_date;
} else {
// In here process the form data normally from when the
// user has submitted it themselves...
}
}