I've gotten stuck on something that I believe is down to my in-experience with the many to many relationship.
In my application I am mapping interests to products with a many to many. I wish to create the following scenario whereby the full list of interests is listed under a specific product with check boxes. For every check box that is selected when the form is submitted, a row is added to the InterestProductAssignment table.
In my product controller I call the full list of interests-
$interests = Interest::model()->findAll();
Actually I don't get a lot further than this as my mind is in knots wondering where to proceed from here. What I've tried so far is building an array of InterestProductAssignment objects to match the interests array I've returned above. I've tried passing it to the view and building out the form, but I've gotten myself fairly confused by trying to match the two up and I can't believe I'm using Yii correctly as it's messy.
Is anyone able to outline a solution for this problem that enables me to have a checkbox against every interest that would add a link between the product and interest upon submitting? I'd be interested to see what you'd write in the controller and view.
Just to clarify, this page is the subject of just one product.
EXTENSION
For the supplementary problem I'm having, I'm posting the code I've got, this may also help others implement a similar thing, once we find the mistake.
My relation within my product controller reads like this-
'interests'=>array(self::MANY_MANY, 'Interest', 'interest_product_assignment(interest_id, product_id)')
Controller
public function actionUpdate($id)
{
// loadModel as been adapted to be called "->with('interests')"
$model=$this->loadModel($id);
// Uncomment the following line if AJAX validation is needed
// $this->performAjaxValidation($model);
if(isset($_POST['Product']))
{
$model->attributes=$_POST['Product'];
if($model->save())
foreach($_POST['ProductInterest'] as $i => $interest_id){
$this_interest = new InterestProductAssignment;
$this_interest->product_id = $model->id;
$this_interest->interest_id = $interest_id;
$this_interest->save();
}
$this->redirect(array('view','id'=>$model->id));
}
$this->render('update',array(
'model'=>$model,
));
}
Relevant part of view _form.php
<div class="row">
<?php echo CHtml::checkBoxList('ProductInterest', CHtml::listData($model->interests, 'interest_id', true), CHtml::listData(Interest::model()->findAll(), 'id', 'interest'));?>
</div>
The problem is that the second field in checkBoxList does not seem to properly fill in the check boxes that are already selected. I suspect that the root cause of this is likely a daft mistake. I can't spot it though and I'm not familiar enough with checkBoxList.
Thanks in advance.
Goose,
I think what you are looking for here is the CheckBoxList.
In your form you can use the code
echo CHtml::checkBoxList('Product_Interests', $model->getInterests(), CHtml::listData(Interest::model()->findAll(), 'interest_id', 'interest_title'));
//$model->getInterests() is a method that should returns an array of IDs of all the currently selected interests
Then in your controller you can use the code:
foreach($_POST['Product_Interests'] as $interest_id=>$checked)
if($checked)
$model->addInterest($interest_id); //Add Interest
else
$model->removeInterest($interest_id); //Remove an Interest if it exists
//Note: If you have a model that connects these tables those can be created or destroyed here
Related
In my controllers that Gii creates it is common to see the following:
if($model->load(Yii::$app->request->post()) && $model->save()){
//.....do something such as redirect after save....//
}else
{
//.....render the form in initial state.....//
}
This works to test whether a POST is sent from my form && the model that I am specifying has saved the posted information (as I understand it).
I've done this similarly in controllers that I have created myself but in some situations this conditional gets bypassed because one or both of these conditions is failing and the form simply gets rendered in the initial state after I have submitted the form and I can see the POST going over the network.
Can someone explain why this conditional would fail? I believe the problem is with the 'Yii::$app->request->post()' because I have removed the '$model->save()' piece to test and it still bypasses the conditional.
Example code where it fails in my controller:
public function actionFreqopts()
{
$join = new FreqSubtypeJoin();
$options = new Frequency();
$model = new CreateCrystal();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
$model->insertFreqopts();
return $this->redirect(['fieldmap', 'id' => $join->id]);
} else {
return $this->render('freqopts', ['join' => $join, 'options' => $options]);
}
}
My initial thought was that I'm not specifying the correct "$model" in that I'm trying to save the posted data to FreqSubtypeJoin() in this case and the $model is CreateCrystal(); however, even when I change the model in this conditional it still fails. It would be helpful if someone could briefly explain what the method 'load' is actually doing in layman's terms if possible.
The load() method of Model class is basically populating the model with data from the user, e.g. a post query.
To do this it firstly loads your array of data in a form that matches how Yii stores your record. It assumes that the data you are trying to load is in the form
_POST['Model name']['attribute name']
This is the first thing to check, and, as long as your _POST data is actually getting to the controller, is often where load() fails, especially if you've set your own field names in the form. This is why if you change the model, the model will not load.
It then check to see what attributes can be massively assigned. This just means whether the attributes can be assigned en-mass, like in the $model->load() way, or whether they have to be set one at a time, like in
$model->title = "Some title";
To decide whether or not an attribute can be massively assigned, Yii looks at your validation rules and your scenarios. It doesn't validate them yet, but if there is a validation rule present for that attribute, in that scenario, then it assumes it can be massively assigned.
So, the next things to check is scenarios. If you've not set any, or haven't used them, then there should be no problem here. Yii will use the default scenario which contains all the attributes that you have validation rules for. If you have used scenarios, then Yii will only allow you to load the attributes that you have declared in your scenario.
The next thing to check is your validation rules. Yii will only allow you to massively assign attributes that have associated rules.
These last two will not usually cause load() to fail, you will just get an incomplete model, so if your model is not loading then I'd suggest looking at the way the data is being submitted from the form and check the array of _POST data being sent. Make sure it has the form I suggested above.
I hope this helps!
thanks for reading, I know the question might sound fairly common, and well I wont deny the fact that maybe I'm just formulating what i want wrongly.
Lets start by shooting it straight, then specifying.
I have a cakephp app with 2 layouts, one layout renders the whole "public" page, and lets call it admin "admin" layout that will render only actions to authenticated users.
in my admin layout, I am printing an element which is the navigation bar.
what I want to do is, without setting on every single controller, the options to set a variable containing the navigation bar values (yes dynamically filled from a specific model)
I want to be able, to set a variable, which will contain a list of values gotten from a model.
The Model is called "Section", which is a table, that contains a list of "sections" of the application.
this navigation bar, needs to print the values of each section, so I need them to be dynamic, hence, I think (again I might be wrong) i need to set that variable somewhere, to make it available to the element, so when the layout "admin" is rendered, the menu bar, is actually filled with the available values of the sections.
I tried doing a Configure::write on AppController, but no dice, it just allowes me to use a variable on controllers, when what I want to do is, loop through the arrah "sections_for_menu" or whatever we call it, and then print the options avaliable on the menu bar.
so you are a bit more comfortable with the idea, the nav bar is a bootstrap based "navbar" with "dropdowns".
i should be able to
<ul class="mycoolclass">
<?php
foreach($sections as $section) {
echo '<li>" . $section . "</li>';
}
</ul>
and thus, printing each value on a new list with its link, and whatnot.
I have been reading with no luck, and have to admin that I am myself fairly new to cakephp, been using it for no longer than 2 weeks.
Any help reaching a solution to this need, is highly appreciated.
UPDATE:
Hi #nunser, thank you very much for your reply.
indeed I'm using an element
this is my layout "base"
<body>
<?php echo $this->element('admin_navbar'); ?>
<!-- container -->
<div class="container-fluid">
<!-- , array('element' => 'flash') -->
<?php echo $this->Session->flash('flash'); ?>
<?php /*echo $this->fetch('content');*/echo $content_for_layout; ?>
</div>
</body>
I'll try your suggestion and see how it goes
I have a controller "SectionsController" which is in charge of well all Section related actions on the app, what I need is to set in the global variable, a list of sections, so I can print the link inside my navbar!
lets assume the following scenario
$this->set('sections_for_navigation', $this->Section->find('list', array('fields' => array('id', 'name'))));
so then i can access the variable $sections_for_navigation from my element, and render the list of sections.
[19-02-2014 - update] tried it.
based on what #nunser suggested, im actually able to, beforeFilter, setting the value, as if i var_dump it, it actually gives me the array as i expected.
public function beforeFilter(){
$this->loadModel('Section');
$secciones = $this->Section->find(
'list', array(
'fields' => array(
'id',
'name'
)
)
);
$this->set('navigation', $secciones);
Then call
public function beforeFilter() {
parent::beforeFilter();
}
from the controllers, did the trick!, now, do i have to add a beforeFilter to every controller to share the navigation through all the app?, is there a way to avoid having to add that method to every single controller? (not that it bothers me, so far it does what i need,which is actually great)
For those kind of navigation things, I do it in beforeFilter or beforeRender in the AppController.
public function beforeFilter() {
//queries and stuff that gets the array for navigation
$this->set('mainNavigationBar', $navigation)
//remember to pass that variable to the element in the view
}
As of to where, beforeFilter or beforeRender, it depends on what type of rule you want to stablish. For example, if you will have same elements for navigation everywhere, be it that the user has permission or not, or if you might want to alter the navigation variable depending on the action being executed, I'd go with doing it in beforeFilter. That way you can tweak things in navigation if, for example, the user doesn't have permission to access any Section (totally making up the model relation here). Though for that, you might want to keep access to the navigation array in the controller.
Tweaked example
protected $_mainNav = array();
public function beforeFilter() {
//queries and stuff that gets the array for navigation
$this->set('mainNavigationBar', $navigation)
//remember to pass that variable to the element in the view
$_mainNav = $navigation;
}
//other controller
public function randomAction() {
//some reason that makes you modify the navigation
unset($_mainNav[0]); //making that up...
$this->set('mainNavigationBar', $navigation)
}
Now, if you won't change the navigation values (once you've added them dynamically), then go with beforeRender, that way you can check permissions and other stuff before bothering with queries for navigation (example to follow would be the first one).
If values of the navigation change per controller, overwrite the function like
RandomController
public function beforeFilter() {
parent::beforeFilter();
$_mainNav = array('no nav'); //example
}
And that's it. Don't know if you need more detail than that, if so, please explain you problem a little more. Oh, and you mention "to make it available to the element", so I'm guessing you're using an element for the navigation bar. If not, please do.
I am having problem displaying values set by select elements created by FormHelper. I have searched the internet but doesn't seem to be able to find the answer. Here is the scene:
I have a questionnaire form that has many options. In the model I put those items into an array, say ($frequencyOptions) and when formhelper is used,
$this->Form->input('frequency',array("options"=> $frequencyOptions));
Currently, the option value is the array index, which looks like:
<option value="">(choose one)</option>
<option value="0">Rare</option>
<option value="1">Frequent</option>
<option value="2">Moderate</option>
Of course I know that if I set the key as well when constructing the $frequencyOptions variable like
$frequencyOptions = array("Rare" => "Rare", ...
I will be able to store the value in text.
However, since some of these options are very very long, I would prefer to save them in INT in the database.
Yet the challenge I have at this moment is how to display those fields in the "list" in the index page. When I use the form field to display in the view or edit action, it is okay because the select element will be used again. However, if I want to display it in plain text, how should I "translate" it?
One thing I can think of is to create these "conversion" methods in the Model, but I think calling model method in views is not a good practice in MVC.
Any idea?
I'd think adding a method to your Model would be the way to go. How about this:
// In your Model class
// Store the index-to-name map in the Model as well
var $frequencyOptions = array(...);
public function translateFrequency(&$data) {
foreach ($data as &$record) {
$index = $record['ModelName']['frequency'];
$record['ModelName']['frequency'] = $this->frequencyOptions[$index];
}
}
// In your Controller action:
$data = $this->ModelName->find(...);
$this->ModelName->translateFrequency($data);
And then it should display the human-readable value when passed to the index page. If preferred, the above method could be changed so it works on $this->data inside the Model.
Note: The method has to be changed if it should work for a single record data array as well.
I'm wondering how can I insert tabular data in Yii.
Of course, I've followed docs in this aspect however there are few differences in my situation.
First of all, I want to save two models, exactly as in the docs article. The main difference is that there might be more that one element for second model (simple one to many relation in database).
I use CHtml to build my forms. I implemented a jQuery snippet to add more input groups dynamically.
I'm unable to show my code now as it's totally messed up and not working currently.
My main question is: how to handle the array of elements for second model in Yii?
Define your two models in controller
$model1= new Model1();
$model2= new Model2();
//massive assignments
$model1->attributes=$_POST['Model1']
$model2->attributes=$_POST['Model2']
//validation
$valid= $model1->validate();
$valid =$valid && $model2->validate();
if($valid){
$model1->save(false);
$model1->save(false);
}
if you want to access fields individually dump your post and you can view the the
post array format or instead of doing massive assignments you can manually assign like this
$model1->field1 =$_POST['Model1']['field1'];
//validation logic
...
if($valid){
$model1->save(false);
$model1->save(false);
}
How to create a multi-model form in Yii? I searched the entire documentation of Yii, but got no interesting results. Can some one give me some direction or thoughts about that? Any help will be appreciable.
In my expirience i got this solution to work and quickly understandable
You have two models for data you wish collect. Let's say Person and Vehicle.
Step 1 : Set up controller for entering form
In your controller create model objects:
public function actionCreate() {
$Person = new Person;
$Vehicle = new Vehicle;
//.. see step nr.3
$this->render('create',array(
'Person'=>$Person,
'Vehicle'=>$Vehicle)
);
}
Step 2 : Write your view file
//..define form
echo CHtml::activeTextField($Person,'name');
echo CHtml::activeTextField($Person,'address');
// other fields..
echo CHtml::activeTextField($Vehicle,'type');
echo CHtml::activeTextField($Vehicle,'number');
//..enter other fields and end form
put some labels and design in your view ;)
Step 3 : Write controller on $_POST action
and now go back to your controller and write funcionality for POST action
if (isset($_POST['Person']) && isset($_POST['Vehicle'])) {
$Person = $_POST['Person']; //dont forget to sanitize values
$Vehicle = $_POST['Vehicle']; //dont forget to sanitize values
/*
Do $Person->save() and $Vehicle->save() separately
OR
use Transaction module to save both (or save none on error)
http://www.yiiframework.com/doc/guide/1.1/en/database.dao#using-transactions
*/
}
else {
Yii::app()->user->setFlash('error','You must enter both data for Person and Vehicle');
// or just skip `else` block and put some form error box in the view file
}
You can find some examples in these two Yii wiki articles:
Yii 1.1: How to use a single form to collect data for two or more models?
Yii 1.1: How to use single form to collect data for two or more models (CActiveForm and Ajax Validation edition).
You don`t need a multi-model. The right use of the MVC pattern requires a Model that reflects your UI.
To solve it, you'll have to use a CFormModel instead of an ActiveRecord to pass the data from View to Controller. Then inside your Controller you`ll parse the model, the CFormModel one, and use the ActiveRecord classes (more than one) to save in database.
Forms Overview and Form Model chapters in Yii Definitive Guide contains some details and samples.
Another suggestions -
Also we can use Wizard Behavior, It's an extension that simplifies the handling of multi-step forms. In which we can use multi model forms for registration process flow or others.
Demo - http://wizard-behavior.pbm-webdev.co.uk/