I'm trying to accomplish a very simple task using Cakephp and after a couple of days reading and trying I can't find the solution. I'll try to explain it as better as I can:
I have a simple 'has many' relationship between two entities ('Clients' and 'Notes'). A client has many notes.
I have the 'Edit' page for the client and I would like to have in that page this two functionalities:
Read/View all the existing notes (readonly)
Add new note
So, following the documentation and several examples, in my edit form I've written the following code to generate the new note input field:
<?= $this->Form->control( 'client_notes.0.text' ); ?>
This is working just fine if the client has no notes of course. But if the client has any note this field gets populated with the first note so it gets overwritten on the save action.
I've also tried a different approach with no luck either:
I've tried to add a different input field such as
<?= $this->Form->control( 'new_note' ); ?>
and then tried to create a new 'Note' entity on the ClientController but this is where I get most confused. Should I modify the requestData passed to the edit method? How is the best way to do this? I've read that modifying the requestData is no longer doable/advised in cakephp.
Last thing I've tried so far is doing that in the 'beforeMarshal' as I've seen out there but it seems I can't get it right, this is my code:
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
{
if (isset($data['new_note'])) {
$text = $data['new_note'];
$data['client_notes'] = [
['text' => $text ]
];
}
}
I'll appreciate any help or guidance. Not looking for the magical code to paste in my project but for learning how to do it as I may have to do similar things in my current project. Thanks in advance!
Related
Am little confused to follow the efficient way to post and process data in controller or model in CodeIgniter 3.1.5 (latest)
method:1 In the model
public function insert_entry()
{
$this->title = $_POST['title'];
$this->content = $_POST['content'];
$this->date = time();
$this->db->insert('entries', $this);
}
method:2 In Controller
public function insert_entry()
{
$title = $this->input->post("title");
$content = $this->input->post("content");
$date = $this->input->post("date");
$data = array(
'title' => $title,
'content' => $content,
'date' => $date
);
$this->model->insert($data);
}
Then process data and query in the model.
which is the efficient method to follow if we are creating a large scale web application.
Actually, there is no best way. Only there is Good Practice. Either way, you can archive this. But Model and Controller have different jobs.
Model Only interconnected with the database. And the controller is the one own Load it, initialize, pass it and all.
I personally recommend you is if it's a database related thing use model. Other than any perform it in Controller(Like File Upload, Validations, redirect, load URL). If we wrote code in an ethical manner it should understand by another developer. So if it's messy he/she can't understand any of these. So simply make it nice and clear .. always.
Read these
Model–view–controller
You should never use $_POST directly in CodeIgniter like this without good reason. Doing so means the data will not be automatically sanitized by the framework. The second method is therefore the way to go.
Here is some suggestion regarding your question.
First. User Input Class for post and get.
Base controller is also very helpfull for large scale projects. base-controller-and-apply-it-to-all-existing-controller
Codeigniter has a great validation Libraries Form Validation
Mainly I will suggest you is that please read out Codeigniter User Guide. It is great. I have also started From that.
I am adding some helpful links for better understanding.
Here is a Helper link of CodeIgniter forum for best practices
CodeIgniter Documentation
Ok after your comment I will suggest this:
Models meant to be doing all database related functionality. It is not must but as say we always follow the best practices so all DB related functions will be placed in the model.
The posted data form view will come first in the controller. (see input suggestion) then send that data to model and do the further process as required.
I have been going through the gridfield class documentation here;
http://doc.silverstripe.org/framework/en/reference/grid-field
Here is the code in question. While it does display a grid-field it adds a button on each columns. How would I edit this code to not display the buttons? The buttons are links to a non-existent page.
Link to rendered page; http://www.silverstripe.org/assets/Uploads/Capture28.JPG
public function AllPages() {
$gridField = new GridField('pages', 'All pages', SiteTree::get());
$dataColumns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
$dataColumns->setDisplayFields(array(
'Title' => 'Title',
'URLSegment'=> 'URL',
'LastEdited' => 'Changed'
));
return new Form($this, "AllPages", new FieldList($gridField), new FieldList());
}
The cause:
The SilverStripe GridField is pretty well built.
The Basic GridField has pretty much no features at all. Its just a plain table containing the data you want.
All other functionality is added by so called "Components" which are managed by the GridFieldConfig.
When you create a GridField like you did, without specifying a config, it will Create a config for you (GridFieldConfig_Base).
The class GridFieldConfig_Base is just a normal GridFieldConfig with some components already added.
One of those components that is already added for you is called the GridFieldSortableHeader which allows you to press on fields to sort the table (which is what produces those buttons that you see).
The reason the Links of the Buttons are dead is probably because there is some routing problem (The GridField is not that well tested in FrontEnd yet) or you maybe have forgotten to add the action AllPages to $allowed_actions.
Solutions:
Plain table
if you don't really need any feature of GridField, and you just want a plain table, the easiest way is to just set an empty config:
public function AllPages() {
$config = GridFieldConfig::create();
$dataColumns = GridFieldDataColumns::create();
$dataColumns->setDisplayFields(array(
'Title' => 'Title',
'URLSegment'=> 'URL',
'LastEdited' => 'Changed'
));
$config->addComponent($dataColumns);
$gridField = GridField::create('pages', 'All pages', SiteTree::get(), $config);
return Form::create($this, __FUNCTION__, FieldList::create($gridField), FieldList::create());
}
Remove just the Sortable Header
$gridField->getConfig()->removeComponentsByType('GridFieldSortableHeader');
// if you don't have a SortableHeader, you probably also don't want a filter
$gridField->getConfig()->removeComponentsByType('GridFieldFilterHeader');
Replace the Sortable Header with a text only header row
Unfortunately, there is no normal header in SilverStripe at this time, but the great gridfieldextensions module from Andrew Short brings you one.
Get the module on GitHub or Packagist
$gridField->getConfig()->removeComponentsByType('GridFieldSortableHeader');
// if you don't have a SortableHeader, you probably also don't want a filter
$gridField->getConfig()->removeComponentsByType('GridFieldFilterHeader');
$gridField->getConfig()->addComponent(new GridFieldTitleHeader());
fix the Sortable Header
if you wish to have the sort functionality, you will have to fix the routing.
It has been a while since I last used the GridField in frontend. I can only tell you that it did work at some time.
perhaps routing does not work because your form action (AllPages) is not accessible as URL, if that's the case, its pretty easy to fix: just add AllPages to your $allowed_actions of your Controller.
if the form is accessible, then its probably a bug in GridField, I would need to debug that to tell you any more. If that is the case, please reply via comment or contact me on IRC and I will take a look at it.
UPDATE: I just answered another frontend GridField question, and went a bit more in depth. perhaps this is also helpful to you: https://stackoverflow.com/a/22433159/1119263 (see Option 2)
I am newbie to yii. I am doing a small application in Yii.
Lets suppose I have some tables like product, sales, discount, customer,
Now I have done all the Models and Controllers(crud) for these tables. Now when admin wants to enter one new product then he is typing
http://localhost/application/index.php?r=product
. In the same way he has to enter discount to go discount section. Now I want to render all the modules in one application just like dashboard. Where admin can directly make change what he wants from that single page. So can someone kindly tell me how to solve this issue. Any help and suggestions will be really appreciable.
EDIT
I have gone through some links but I did not found any documentation there.
First of all you should think of what should be presented on dashboard, you have choosen some entities already. From that entities there might be different criteria for showing items:
One will show latest products
Other will show last edited posts by user
And maybe sales will show highest sales?
Now, you should choose whenever to allow some actions on theese items.
For product you could have some (sample) quick buttons/link: publish, update
For customer there could be orders
Now, to acomplish this, you would have to define several dataproviders, setup several listviews, and put all that into your DashboardController. No! From Yii conventions, and MVC generally, there should be: fat model, this controller, and wise view.
Taking above into account, you should create widget for each type of data. Widget should be "independent", this will be like "model" for your dashboard. Should contain all logic required for type of entity, and should not require any configuration (for autocreation too).
For dashboard widget you should also create some base class for this purpose, so dashboard widget will look consistently: to have some layout.
A good start for this purpose is CPortLet - this already defines somethink like dashboard widget, with title, and div around it's content.
Here is some example to start with portlets:
class ProductDashboard extends CPortlet // Or intermediate class, ie. DashboardItem
{
protected $_products = array();
public function init()
{
$this->_products = new CActiveDataProvider('Product', array(
'criteria'=>array(
'with'=>array('...'),
'order'=>'t.sort_order ASC',
'condition'=>'...',
'together'=>true,
),
));;
$this->title= 'Newset producs';
parent::init();
}
protected function renderContent()
{
$this->render('productDashboard');
}
}
In portlet view, views/productDashboard.php just place listview:
$this->widget('zii.widgets.CListView', array(
'dataProvider'=>$dataProvider,
'itemView'=>'_productView',
'enablePagination'=>true,
));
In _productView place anything about product:
<h4><?= CHtml::encode($data->name); ?></h4>
<p><?= CHtml::encode($data->description); ?></p>
<p>
<?= CHtml::link('Update', array('/product/update', 'id' => $data->id)); ?>
<?= CHtml::link('View', array('/product/view', 'id' => $data->id)); ?>
... More actions ...
</p>
Finally in your dashboard index view place those portlets:
$this->widget('path.to.ProductDashboard');
$this->widget('path.to.SalesDashboard');
....
Or in some automated way:
// Possibly user defined only etc.
$widgets = array('ProductDashboard', 'SalesDashboard', ...);
foreach($widgets as $name)
$this->widget($name)
First, lets take a look on this. Uh, almost 200 pages, but let me leave it here and refer to it in the following answer.
So, we want a page that can manage edit/delete/update actions with the table, and Yii can help you with it in 2 ways:
1st Is for lazy codders, or a guys who just start to work with framework. Just go to the documentation and find out the 1.6 Creating First Yii Application. This article will helps you to set up the basic configurations with demo models/views/controllers to play with it. The result of this Demo Installation is like your dashboard required with more features to explore
2nd step require a lot of code to show up here, and it will be just an instruction how to build everything step-by-step that you can do in the 1st step automatically with Yii. Just ask if you'd like to know about it more.
Cheers!
It sounds like you want to implement a menu. Assuming that you have at least gone through the Creating First Yii Application mentioned by Ignat B., you can read the CMenu class documentation to learn about them, and your modifications would go in the layout.php file in protected\views.
If its a list of menu that you are looking for, you might try this extension: http://www.yiiframework.com/extension/emetrotile
All you need to do is follow the instructions in the source then call it where you want it to load similar to the one below:
$this->widget('ext.emetrotile.EMetroTile', array(
'Tiles'=>array(
array('title'=>'Test Title', 'tiles'=>array(
array('content'=>array('test1-a','test1-b'), 'liveTileOptions'=>array('data-speed'=>750, 'data-delay'=>3000,'data-stack'=>true)),
array('content'=>'test2', 'position'=>'bottom'),
array('content'=>'test4', 'position'=>'bottom'),
array('content'=>'Blog', 'style'=>'vertical', 'url'=>'http://blog.expressthisout.com'),
array('content'=>'test3', 'style'=>'horizontal'),
array('content'=>'test5', 'position'=>'bottom'),
array('content'=>'test6', 'position'=>'top'),
))
)
));
As Syakur Rahman says, emetrotile is a good extension for creating a dashboard. I've created a nice 3 x 2 menu with it, with an image on the front of each, text on the back, and they flip in a sequence one after the other. It has a very cool effect, but it does have a Windows feel to it.
Drew Green wrote the original js seems to be improving it all the time, See http://www.drewgreenwell.com/projects/metrojs
I've done quite a few Lithium tutorials (links below in case they help someone else, and also to show I've done my homework:) and I understand the most basic parts of creating models, views, controllers and using MVC to create a DB record based on form input.
However, I'm new to MVC for webapps and Lithium, and I'm not sure how I should write my code in more complicated situations. This is a general question, but two specific validation questions that I have are:
How should I validate date data submitted from the form?
How should I check that the two user email fields have the same value?
I would be very grateful for any help with these questions, and concrete examples like this will also really help me understand how to do good MVC coding in other situations as well!
Date entry - validating data split across multiple form inputs
For UI reasons, the sign up form asks users to enter their DOB in three fields:
<?=$this->form->field('birthday', array('type' => 'select', 'list' => array(/*...*/))); ?>
<?=$this->form->field('birthmonth', array('type' => 'select', 'list' => array(/*...*/))); ?>
<?=$this->form->field('birthyear', array('type' => 'select', 'list' => array(/*...*/))); ?>
What is the best way to validate this server-side? I think I should take advantage of the automagic validation, but I'm not sure of the best way do that for a set of variables that aren't really part of the Model. E.g.:
Should I post-process the $this->request->data in UsersController? E.g. modify $this->request->data inside UsersController before passing it to Users::create.
Should I pull the form fields out of $this->request->data and use a static call to Validator::isDate inside UsersController?
Is there a way to write a validation rule in the model for combinations of form variables that aren't part of the model?
should I override Users::create and do all the extra validation and post-processing there?
All of these seem like they could work, although some seem a little bit ugly and I don't know which ones could cause major problems for me in the future.
[EDIT: Closely related to this is the problem of combining the three form fields into a single field to be saved in the model]
Email entry - checking two form fields are identical, but only storing one
For common sense/common practice, the sign up form asks users to specify their email address twice:
<?=$this->form->field('email_address'); ?>
<?=$this->form->field('verify_email_address'); ?>
How can I write an automagic validation rule that checks these two form fields have the same value, but only saves email_address to the database?
This feels like it's pretty much the same question as the above one because the list of possible answers that I can think of is the same - so I'm submitting this as one question, but I'd really appreciate your help with both parts, as I think the solution to this one is going to be subtle and different and equally enlightening!
[EDIT: Closely related to this is the problem of not storing verify_email_address into my model and DB]
Some background reading on Lithium
I've read others, but these three tutorials got me to where I am with users and sign up forms now...
Blog tutorial
Extended blog tutorial
MySQL blog tutorial
Some other StackOverflow questions on closely related topics (but not answering it and also not Lithium-specific)
One answer to this question suggests creating a separate controller (and model and...?) - it doesn't feel very "Lithium" to me, and I'm worried it could be fragile/easily buggy as well
This wonderful story convinced me I was right to be worried about putting it in the controller, but I'm not sure what a good solution would be
This one on views makes me think I should put it in the model somehow, but I don't know the best way to do this in Lithium (see my bulleted list under Date Entry above)
And this Scribd presentation asked the question I'm hoping to answer on the last page... whereupon it stopped without answering it!
NB: CakePHP-style answers are fine too. I don't know it, but it's similar and I'm sure I can translate from it if I need to!
I'd recommend doing this in the Model rather than the Controller - that way it happens no matter where you do the save from.
For the date field issue, in your model, override the save() method and handle converting the multiple fields in the data to one date field before calling parent::save to do the actual saving. Any advanced manipulation can happen there.
The technique described in your comment of using a hidden form field to get error messages to display sounds pretty good.
For comparing that two email fields are equal, I'd recommend defining a custom validator. You can do this in your bootstrap using Validator::add.
use lithium\util\Validator;
use InvalidArgumentException;
Validator::add('match', function($value, $format = null, array $options = array()) {
$options += array(
'against' => '',
'values' => array()
);
extract($options);
if (array_key_exists($against, $values)) {
return $values[$against] == $value;
}
return false;
});
Then in your model:
public $validates = array(
"email" => array(
"match",
"message" => "Please re-type your email address.",
"against" => "email2"
)
);
Edit: Per the comments, here's a way to do custom rule validation in a controller:
public function save() {
$entity = MyModel::create($this->request->data);
$rules = array(
"email" => array(
"match",
"message" => "Please re-type your email address.",
"against" => "email2"
)
);
if (!$entity->validates($rules)) {
return compact('entity');
}
// if your model defines a `$_schema` and sets `$_meta = array('locked' => true)`
// then any fields not in the schema will not be saved to the db
// here's another way using the `'whitelist'` param
$blacklist = array('email2', 'some', 'other', 'fields');
$whitelist = array_keys($entity->data());
$whitelist = array_diff($whitelist, $blacklist);
if ($entity->save(null, compact('whitelist'))) {
$this->redirect(
array("Controller::view", "args" => array($entity->_id)),
array('exit' => true)
);
}
return compact('entity');
}
An advantage of setting the data to the entity is that it will be automatically prefilled in your form if there's a validation error.
I need to perform several post-validations in a Symfony form. First time I came across this issue I wrote this:
$this->validatorSchema->setPostValidator(
new sfValidatorCallback(array(
'callback' => array($this, 'checkStatusHasMethod'))
));
since I only wanted to check a certain situation.
But as the application has grown, I need now to perform additional checks. I would like to keep every validation isolated in different methods, instead of having a big checkX method where everything is kept together.
Is it possible to associate a sfPostValidator with more than one method or create several sfPostValidator instances in validatorSchema?
Thanks!
Try mergePostValidator() (or similar, can't remember the exact method name)
As far as I remember, there's a validator called sfValidatorAnd() that allows you to do that.
Edit : you can do that :
new sfValidatorAnd(
array(
new sfValidatorString(),
new sfValidatorRegex(),
)
);