Yii Framework: Pagination with HAS_MANY, BELONGS_TO - yii

I have been facing this problem all day long.
I have 2 tables Service and Log. Each Service can have many Logs and each Log belongs to a Service.
I managed to generate the CRUD functionality for both.
Here is what I got:
app/models/Log.php
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array
(
'services' => array(self::BELONGS_TO, 'Service', 'sv_ident_nr'),
);
}
app/models/Service.php
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array
(
'logs' => array(self::HAS_MANY, 'Log', 'sv_ident_nr'),
);
}
app/controlelrs/ServiceController.php
/**
* Displays a particular model.
* #param integer $id the ID of the model to be displayed
*/
public function actionView($id)
{
$this->render('view', array
(
'model' => $this->loadModel($id),
));
}
app/views/service/view.php
<h1>Service: <?php echo $model->ident_nr; ?></h1>
<table class="dataGrid">
<tr>
<th class="label"><?php echo CHtml::encode($model->getAttributeLabel('proj_nr')); ?></th>
<td><?php echo CHtml::encode($model->proj_nr); ?></td>
</tr>
<tr>
<th class="label"><?php echo CHtml::encode($model->getAttributeLabel('customer')); ?></th>
<td><?php echo CHtml::encode($model->customer); ?></td>
</tr>
<h1>Comments</h1>
<?php foreach ($model->logs as $log): ?>
<div class="comment" id="c<?php echo $log->id; ?>">
<div class="actions">
<?php
echo CHtml::link('View',array($this->createUrl('../log/view', array('id'=>$log['id']))));
echo('&nbsp');
echo CHtml::link('Update',array($this->createUrl('../log/update', array('id'=>$log['id']))));
echo('&nbsp');
echo CHtml::link('Delete',array($this->createUrl('../log/delete', array('id'=>$log['id']))));
?>
</div>
<div class="author">
<?php
echo $log->logger;
?>
</div>
<div class="time">
<?php echo date('j F Y \a\t h:i a', $log->created_at); ?>
</div>
<div class="content">
<?php echo nl2br(CHtml::encode($log->comment)); ?>
</div>
</div><!-- comment -->
<?php endforeach; ?>
The question is : How do I go about paginating the results in the 'foreach loop'?
Thanks

Instead of a CGridView, look at the docs for CListView. It allows you to do paging, Ajax retrieval, etc while also using your own template for the data. It's well worth your time ...

based upon my discussion with you earlier I think this is what you are after.
Yii Simple Pagination

Related

adding multiple model records a form within a form - Collecting Tabular Input yii

I am kind of lost. I am following this Collecting Tabular Input. I have a product form, within the product form I have a attribute form, like colors/sizes. I am having trouble understanding this tutorial because of the loop in controller. So I get that it checks the number of records and loop it but what if there's no record. It seems like the tutorial is saying to create a new Model. Yes, of course. But how can you possibly loop it in view if there's no record and telling it to create a new one. Maybe I am not understanding this correctly.
I am just looking to add 3 records, blank or filled. No js to add additional record. Trying to understand this first.
controller:
public function getProdAttr($id) {
$items = array();
if (isset($_POST['ProductAttribute']) ) {
$items = ProductAttribute::model()->findAllByAttributes(array('product_id',$id));
if($items!==null){
foreach ($_POST['ProductAttribute'] as $item) {
$items[] = ProductAttribute::model()->findByPk($item['product_attribute_id']);
}
}
else $items[] = new ProductAttribute();
}
return $items;
}
public function actionUpdate($id)
{
$model=$this->loadModel($id);
$attributes=$this->getProdAttr($id);
if(isset($_POST['ProductAttribute']))
{
$valid=true;
foreach($attributes as $i=>$attribute)
{
if(isset($_POST['ProductAttribute'][$i]))
$attribute->attributes=$_POST['ProductAttribute'][$i];
$valid=$attribute->validate() && $valid;
}
if($valid)
$attributes->save();
}
//stuff from product model
}
view:
//stuff for product model form
<div class="form">
<?php echo CHtml::beginForm(); ?>
<table>
<tr><th>Name</th><th>Description</th><th>Price</th></tr>
<?php foreach($attributes as $i=>$attribute): ?> //lost here, what if there's no records?
<tr>
<td><?php echo CHtml::activeTextField($attribute,"[$i]name"); ?></td>
<td><?php echo CHtml::activeTextArea($attribute,"[$i]description"); ?></td>
<td><?php echo CHtml::activeTextField($attribute,"[$i]cost"); ?></td>
</tr>
<?php endforeach; ?>
</table>
</div>

CodeIgniter Conditional - Multiple Tables

I'm stuck on the logic for a conditional that I need some help with. I am building a project collaboration platform where users can view open projects and join them to form a team. I've got the joining functionality working fine but I want to have a conditional set to not show the 'Join This Team' button if they are already a member of that project group. I may already have some of what is needed to do this but I'm not entirely sure how to go about it. (I had help writing the code I've already implemented) Here is the code below for joining a project:
//controller
public function join($project){
// Set Var
$email = $this->session->userdata('email');
// Join User
$this->project_model->join_project($project, $email);
//Redirect to Project
redirect('projects');
}
//functions in model to get the members already joined and insert
public function get_team_members($id){
$this->db->select('team_members.user_id, users.first_name, users.last_name, users.location');
$this->db->from('team_members');
$this->db->where('team_members.project_id', $id);
$this->db->join('users', 'users.id = team_members.user_id');
return $this->db->get()->result_array();
}
public function join_project($id, $email){
// Get UserID
$this->db->select('id');
$this->db->from('users');
$this->db->where('email', $email);
$user = $this->db->get()->row_array();
// Set Insert Data
$data = array(
'project_id' => $id,
'user_id' => $user['id']
);
// Insert into Team DB
$this->db->insert('team_members', $data);
}
//view
<div class="large-4 columns widget">
<div class="row collapse top-margin">
<div class="large-4 columns"></div>
<div class="large-8 column bottom-spacer">
<?php $link = base_url()."project/join/".$project['project_id']; ?>
<h5>Join this Team</h5>
</div>
</div>
//view for team members
foreach ($team as $member){ ?>
<!-- Team Member #1 -->
<div class="row collapse">
<div class="large-4 columns">
<img class="left" src="<?php echo base_url();?>assets/img/user.png" alt="user img">
</div>
<div class="large-8 columns member-name">
<p class="name"><strong><?php echo $member['first_name']." ".$member['last_name']; ?></strong></p>
<p><span><?php echo $member['location'];?></span></p>
</div>
</div>
<?php } ?>
Any help would be greatly appreciated.
You can do with two method.
Method 1. Create function is_team_member($user_email, $team_id) in your model. If user exist, true will be return else false. Pass the returned value to view via controller. Based on the value you can hide the button with use of if condition.
Method 2. You can check the user email in view foreach loop. If $email == $member["email"], set flag to hide button. But in this case, your button should be under the member list. Bur, as we discussed in comment, your member list is above list. So create a variable to store the foreach html content as string. you can run the foreach loop above the button and cho the content after the button. Now based on the flag, you can hide the button.
Ex for method - 1:
In Controller, Team member listing function.
public function index($team_id){
// Set Var
$email = $this->session->userdata('email');
$data["team"] = $this->project_model->get_team_members($team_id);
$data["is_joined_user"] = $this->project_model->is_team_members($team_id,$email);
//load view
$this->view->load("view_name", $data);
}
In model:
public function is_team_members($team_id,$email)
{
$this->db->select('*');
$this->db->from('team');
$this->db->join('users', 'users.id = team.user_id');
$this->db->where("user.email",$email);
$res = $this->db->get();
if($res->num_rows()>0)
return true;
else
return false;
}
In view:
<?php
if(! $is_joined_user)
{
?>
<h5>Join this Team</h5>
<?php
}
?>

Yii/Giix - Standard 2 Column Layout

The code below shows the two column layout in Yii. The $content variable holds a search form and a gridview form.
I'm trying to get the gridview to appear to the right of the Advanced Search Section in this two-column grid format. Kind of brain farting here, where in the standard Giix structure is the variable $content given it's contents? I didn't see it in the basemodel or controller.
Thanks in advance.
<?php /* #var $this Controller */ ?>
<?php $this->beginContent('//layouts/main'); ?>
<div class="span-24">
<div id="content">
<?php echo $content; ?>
</div><!-- content -->
</div>
<div class="span-5 last">
<div id="sidebar">
<?php
$this->beginWidget('zii.widgets.CPortlet', array(
'title'=>'Operations',
));
$this->widget('zii.widgets.CMenu', array(
'items'=>$this->menu,
'htmlOptions'=>array('class'=>'operations'),
));
$this->endWidget();
?>
</div><!-- sidebar -->
</div>
<?php $this->endContent(); ?>
$content is given its content when your controller call $this->render() at the end of its action.
public function actionIndex() {
// renders the view file 'protected/views/site/index.php'
// using the default layout 'protected/views/layouts/main.php'
[some code...]
$this->render('index');
}
The process involved is a bit obfuscated but you can easily trace it down by setting a breakpoint and looking at the stack in your debugger.
You can also read the code :
render() is a method of the CController class :
public function render($view, $data = null, $return = false) {
if ($this->beforeRender($view)) {
$output = $this->renderPartial($view, $data, true); // (1)
if (($layoutFile = $this->getLayoutFile($this->layout)) !== false)
$output = $this->renderFile($layoutFile, array('content' => $output), true); // (2)
[snip...]
}
}
(1) If no error occurs before rendering, the view is populated and its HTML code assigned to $output : $output = $this->renderPartial($view, $data, true);
(2) Then, unless you stated in your action that the view must not be decorated by the layout by colling $this->setLayout(false), the Decorator pattern is applied and the internal view set in the layout :
$output = $this->renderFile($layoutFile, array('content' => $output), true)
Here, you shall notice that the second argument is an array : array('content' => $output)
renderfile() is a method of CBaseController which, at some point, will call
public function renderInternal($_viewFile_, $_data_ = null, $_return_ = false) {
// we use special variable names here to avoid conflict when extracting data
if (is_array($_data_))
extract($_data_, EXTR_PREFIX_SAME, 'data'); // (1)
else
$data = $_data_;
if ($_return_) {
ob_start();
ob_implicit_flush(false);
require($_viewFile_); // (2)
return ob_get_clean();
}
else
require($_viewFile_);
}
And that's where your answer lies :
(1) $data is still our array('content' => $output). The extract function will build and initialize variables from this array, namely your $content variable.
(2) The layout file is now required. $content exists in its scope, as well, of course, as your controller wich lies behind $this
Use a grid layout in the specific view. It should something like
<div class='span-10'>
//search form
</div>
<div class='span-9'>
//grid
</div>

Dynamically show required textfield depending on dropdownlist value

Good day. My problem is I have a dropDownlist with three options: A, B, C. What I need to do is to show a textfield depending on what the user chose.
Example: user chose A => it will show textfield AA (which should be required and not empty)
user chose B => it will show textfield BB (which should be required and not empty)
user chose C => it will show textfield CC (which should be required and not empty)
Can anyone please help me? Thank you.
EDIT: It's working now. The only problem now is when an I leave the chosen textfield as blank and it shows the error, the textfield that was shown (the one which is dependent on the value of dropdownlist) disappears or goes back to state display:hidden. This happens right after the 'textfield cannot be blank' error message. The initial chosen value of dropdownList is still there and is still on focus but the textfield disappears.
HERE ARE MY UPDATED CODES:
VIEW:
<div class="row">
<?php echo $form->labelEx($model,'org_type'); ?>
<?php echo $form->dropDownList($model,'org_type', $model::getOrgType(), array('prompt'=>'', 'id'=>'orgType')); ?>
<?php echo $form->error($model,'org_type'); ?>
</div>
<div class="row" style="display:none" id="sec">
<?php echo $form->labelEx($model,'sec_ref'); ?>
<?php echo $form->textField($model,'sec_ref', array('id'=>'secField')); ?>
<?php echo $form->error($model,'sec_ref'); ?>
</div>
<div class="row" style="display:none" id="dti">
<?php echo $form->labelEx($model,'dti_ref'); ?>
<?php echo $form->textField($model,'dti_ref', array('id'=>'dtiField')); ?>
<?php echo $form->error($model,'dti_ref'); ?>
</div>
<div class="row" style="display:none" id="cda">
<?php echo $form->labelEx($model,'cda_ref'); ?>
<?php echo $form->textField($model,'cda_ref', array('id'=>'cdaField')); ?>
<?php echo $form->error($model,'cda_ref'); ?>
</div>
MODEL:
public function addCustomError($attribute, $error) {
$this->customErrors[] = array($attribute, $error);
}
/**
*/
protected function beforeValidate() {
$r = parent::beforeValidate();
if ($this->org_type == 'Single') {//this is the checkbox
$this->validatorList->add(CValidator::createValidator('required',$this,'dti_ref',array()));
}
if ($this->org_type == 'Partnership') {//this is the checkbox
$this->validatorList->add(CValidator::createValidator('required',$this,'sec_ref',array()));
}
if ($this->org_type == 'Corporation') {//this is the checkbox
$this->validatorList->add(CValidator::createValidator('required',$this,'sec_ref',array()));
}
if ($this->org_type == 'Cooperative') {//this is the checkbox
$this->validatorList->add(CValidator::createValidator('required',$this,'cda_ref',array()));
}
foreach ($this->customErrors as $param) {
$this->addError($param[0], $param[1]);
}
return $r;
}
Javascript on View:
<script>
"$('#orgType').change(function(){
if($('#orgType').val() == 'Single')
{
$('#dti').show();
$('#dtiField').addClass('required');
$('#cda').hide();
$('#sec').hide();
}
if(($('#orgType').val() == 'Partnership') || ($('#orgType').val() == 'Corporation'))
{
$('#sec').show();
$('#secField').addClass('required');
$('#dti').hide();
$('#cda').hide();
}
if($('#orgType').val() == 'Cooperative')
{
$('#cda').show();
$('#cdaField').addClass('required');
$('#dti').hide();
$('#sec').hide();
}
return false;
})";
You can try this:
After Every change event of DropDown You can set value to the text Filed ..

Yii: model attribute's comments/hints

I need to have comments/hints for some fields in my Form. My idea is to describe it in model, just like attributeLabels. How can I do it?
And then it would be ideal, if the Gii Model (and Crud) generator would take it directly from mysql column's comment
So I see two questions here:
Describe hints in the model and display on the form.
Get hints from mysql comment
Displaying Hints from Model
Here's a slightly modified version of the default login.php file generated by Yiic
<div class="form">
<?php $form=$this->beginWidget('CActiveForm', array(
'id'=>'login-form',
'enableClientValidation'=>true,
'clientOptions'=>array(
'validateOnSubmit'=>true,
),
)); ?>
<p class="note">Fields with <span class="required">*</span> are required.</p>
<div class="row">
<?php echo $form->labelEx($model,'username'); ?>
<?php echo $form->textField($model,'username'); ?>
<?php echo $form->error($model,'username'); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'password'); ?>
<?php echo $form->passwordField($model,'password'); ?>
<?php echo $form->error($model,'password'); ?>
<p class="hint">
Hint: You may login with <kbd>demo</kbd>/<kbd>demo</kbd> or <kbd>admin</kbd>/<kbd>admin</kbd>.
</p>
</div>
<div class="row rememberMe">
<?php echo $form->checkBox($model,'rememberMe'); ?>
<?php echo $form->label($model,'rememberMe'); ?>
<?php echo $form->error($model,'rememberMe'); ?>
</div>
<div class="row buttons">
<?php echo CHtml::submitButton('Login'); ?>
</div>
<?php $this->endWidget(); ?>
</div><!-- form -->
Let's move that password hint into the model by adding a attributeHints() method and a getHint() method to the LoginForm.php model.
/**
* Declares attribute hints.
*/
public function attributeHints()
{
return array(
'password'=>'Hint: You may login with <kbd>demo</kbd>/<kbd>demo</kbd> or <kbd>admin</kbd>/<kbd>admin</kbd>.',
);
}
/**
* Return a hint
*/
public function getHint( $attribute )
{
$hints = $this->attributeHints();
return $hints[$attribute];
}
As you can see, we;ve moved the hint text from the view into the model and added a way to access it.
Now, back in login.php, let's add the hint tag based on data from the model.
<div class="row">
<?php echo $form->labelEx($model,'password'); ?>
<?php echo $form->passwordField($model,'password'); ?>
<?php echo $form->error($model,'password'); ?>
<?php echo CHtml::tag('p', array('class'=>'hint'), $model->getHint('password')); ?>
</div>
So now we've changed the hardcoded hint into a generated element populated with data from the model.
Now, moving on to the second question.
Getting hints from mySQL comments
Unforunately, I am not fammiliar enough with Gii to know how to automatically generate the hints from mySQL comments. However, getting the mySQL comment data into the model is fairly easy.
To do this we can use the following mySQL query
SHOW FULL COLUMNS FROM `tbl_user`
So let's add the comment to the password field
ALTER TABLE `tbl_user`
CHANGE `password` `password` VARCHAR( 256 )
CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL
COMMENT 'Hint: You may login with <kbd>demo</kbd>/<kbd>demo</kbd> or <kbd>admin</kbd>/<kbd>admin</kbd>.';
And let's add the code to fetch it into our attributeHints() method.
/**
* Declares attribute hints.
*/
public function attributeHints()
{
$columns= Yii::app()->db->createCommand('SHOW FULL COLUMNS FROM `tbl_user`')->queryAll();
$comments=array();
foreach($columns as $column){
if( isset( $column['Comment'] ) )
{
$comments[ $column['Field'] ] = $column['Comment'];
}
}
//Add any hardcoded hints here
$hints = array(
'username'=>'Enter username above',
);
//Return merged array
return array_merge( $comments, $hints );
}
We now have two ways to set comments, through mySQL comments or through hard coded statements.
Let's update our login.php file quick to include hints of both types.
<div class="row">
<?php echo $form->labelEx($model,'username'); ?>
<?php echo $form->textField($model,'username'); ?>
<?php echo $form->error($model,'username'); ?>
<?php echo CHtml::tag('p', array('class'=>'hint'), $model->getHint('username')); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'password'); ?>
<?php echo $form->passwordField($model,'password'); ?>
<?php echo $form->error($model,'password'); ?>
<?php echo CHtml::tag('p', array('class'=>'hint'), $model->getHint('password')); ?>
</div>
And that's it!
Now our login page will look like this, with a username hint from the model and a password hint from the mySQL comment.
As of Yii-1.1.13, a new attribute, comment, has been added to CMysqlColumnSchema which is defined in parent CDbColumnSchema:
comment of this column. Default value is empty string which means that no comment has been set for the column. Null value means that RDBMS does not support column comments at all (SQLite) or comment retrieval for the active RDBMS is not yet supported by the framework.
So we can use it to access the comment. Somewhat like this:
$modelObject->tableSchema->columns['column_name']->comment
// or if you are doing this within your model use
$this->tableSchema->columns['column_name']->comment
Note that the tableSchema is already set when any CActiveRecord model is constructed or even if the static model is used, so there are no new queries executed when we access the property.
A sample implementation within your model:
class MyModel extends CActiveRecord {
// hints array will store the hints
public $hints=array();
public function init() {
parent::init();
$this->hints=self::initHints($this);
}
public static function initHints($model) {
$comments=array();
foreach ($model->tableSchema->columns as $aColumn){
if(!empty($aColumn->comment))
$comments["$aColumn->name"]=$aColumn->comment;
}
return $comments;
}
public static function model($className=__CLASS__) {
$model=parent::model($className);
$model->hints=self::initHints($model);
return $model;
}
}
Now you can access hints as:
$model = new MyModel;
$single_column_hint=$model->hints['column_name'];
$model = MyModel::model();
$single_column_hint=$model->hints['column_name'];
For doing this through gii templates, you just need to copy the above code to your custom model code template, read the guide to see how this can be done.
I would recommend having the code in a custom base class, from which you can derive your active records.