For everybody who’s having trouble with WAMP and similar, I suggest VirtualBox.
It works well here. And having a Linux box to play with is pure joy.
For everybody who’s having trouble with WAMP and similar, I suggest VirtualBox.
It works well here. And having a Linux box to play with is pure joy.
The generated views are only examples.
I have not adapted to the view files generated by giixcrud.
Whenever I created a new relationship had to remember to add it in the area of dependent records.
So I created a modification that does the work automatically and with a more efficient display.
It remains to be improved. Mentel, what do you think about it? Is it possible to use something similar in Giix?
The changes I made:
New file TreeRelationsWidget.php
<?php
class TreeRelationsWidget extends CTreeView
{
public $model;
public $animated = 'fast';
public $collapsed = 'false';
public function init()
{
$array_relation = array();
foreach ($this->model->baseRelations() as $relationName => $relationParams)
{
$aviso_relacionam = '';
if ($relationParams[0] == CActiveRecord::HAS_ONE || $relationParams[0] == CActiveRecord::HAS_MANY || $relationParams[0] == CActiveRecord::MANY_MANY)
{
if ($relationParams[0] == CActiveRecord::MANY_MANY)
$aviso_relacionam = ' - referenciado';
$routeName = lcfirst($relationParams[1]);
$children = array();
$count_itens = count($this->model->$relationName);
if ($count_itens > 0)
{
$pode_consultar = Yii::app()->user->checkAccess($routeName . ".Consultar");
if ($relationParams[0] == CActiveRecord::HAS_ONE)
{
$children[] = $this->getNomeItem($this->model->$relationName, $routeName, $pode_consultar);
}
else
{
foreach ($this->model->$relationName as $data)
{
$children[] = $this->getNomeItem($data, $routeName, $pode_consultar);
}
}
}
$array_relation[] = array(
'text' => $relationParams[1]::label(2, false) . "$aviso_relacionam ($count_itens)",
'children' => $children
);
}
}
if (count($array_relation) > 0)
{
echo "<h2>Registros Dependentes</h2>";
$this->data = $array_relation;
parent::init();
}
}
private function getNomeItem($data, $routeName, $pode_consultar)
{
$nome = (method_exists($routeName, "get_display") ? $data->get_display() : GxHtml::encode(GxHtml::valueEx($data)));
if ($pode_consultar)
{
$id = GxActiveRecord::extractPkValue($data);
if (!is_array($id))
$nome = GxHtml::link($nome, array($routeName . '/consultar', 'id' => $id));
}
return array('text' => $nome);
}
}
Add in GxActiveRecord.php file:
public function relations() {
return $this->baseRelations();
}
In basemodel.php
Change:
public function relations()
To:
public function baseRelations()
In view.php files:
<?php $this->widget('widgets.TreeRelationsWidget', array(
'model' => $model,
));
?>
Athos,
I want to see it running, seems to be a good idea.
I also have some ideas on handling the related fields, but I currently have no time to implement it.
What do you think about having one extension (or more) extending giix? It could be templates, widgets, new generation engines etc.
thanks for you’r response.Great extensions by the way.
It’s not in the vue, it’s in the Crud generator, how can i extend my own class and use the giix generator ? (i don’t want to edit the file each time you make an update)
"protected\extensions\giix-core\giix\CrudGiixCrudCode.php"
I see.
Unfortunately I don’t think this is possible currently.
But this is a great idea for giix 2.0.
Hi Rodrigo,
My sugestion for saveMultiple :
/**
* Saves multiple active records.
* This method automatically fill fk atributes for active records
* having a BELONGS_TO relation (to HAS_ONE or to HAS_MANY).
* The order of the active records in the $models array parameter is
* important to make it work. The models that need to be saved first
* should come first in the array.
* @param GxActiveRecord|array $models A model or an array of models.
* The array should follow the format:
* <pre>
* array(
* array(
* 'model' => $theModelInstance,
* 'modelOptions' => array( ... ),
* ),
* array(
* 'model' => $anotherModelInstance,
* 'modelOptions' => array( ... ),
* ),
* )
* </pre>
* The following modelOptions are available:
* <ul>
* <li>'runValidation', boolean: see {@link CActiveRecord::save} for details. Defauls to true.</li>
* <li>'attributes', array: see {@link CActiveRecord::save} for details. Defauls to null.</li>
* <li>'relatedData', array: see {@link saveWithRelated} for details. Defauls to null.</li>
* <li>'relatedModels', array: Instances of Models to fill fk values. Defauls to array()</li>
* <li>'batch', boolean: see {@link saveWithRelated} for details. Applies only to the related data. Defauls to true.</li>
* <li></li>
* </ul>
* @param boolean $runValidation Whether to perform validation before saving all the records.
* If the validation fails, the record will not be saved to database.
* Optional. If true, forces the validation on all records. If false,
* disables the validation on all records. If null, the options for
* each record will be followed. Defaults to true.
* @param array $options Additional options. Valid options are:
* <ul>
* <li>'withTransaction', boolean: Whether to use a transaction.
* Defaults to true.</li>
* </ul>
* @return boolean Whether the saving succeeds.
* @throws Exception If "detectRelations" is true and the related model is not found.
* @see CActiveRecord::save
* @see saveWithRelated
*/
public function saveMultiple($models, $runValidation = true, $options = array()) {
// Merge the specified options with the default options.
$defaultOptions = array(
'withTransaction' => true,
);
$options = CMap::mergeArray($defaultOptions, $options);
// Define the default model options.
$defaultModelOptions = array(
'runValidation' => true,
'attributes' => null,
'relatedData' => null,
'batch' => true,
'relations' => array()
);
// If $models is a single record, make it an array.
if (!is_array($models))
$models = array($models);
// The saved models array.
$savedModels = array();
try {
// Start the transaction if required.
if ($options['withTransaction'] && ($this->getDbConnection()->getCurrentTransaction() === null)) {
$transacted = true;
$transaction = $this->getDbConnection()->beginTransaction();
} else
$transacted = false;
foreach ($models as $modelItem) {
// Get the model instance.
$model = $modelItem['model'];
// Merge the options.
if (isset($modelItem['modelOptions']) && ($modelItem['modelOptions'] !== array()))
$modelOptions = CMap::mergeArray($defaultModelOptions, $modelItem['modelOptions']);
else
$modelOptions = $defaultModelOptions;
// If set, the global "runValidation" value overrides the model setting.
if ($runValidation !== null)
$modelOptions['runValidation'] = $runValidation;
// Fill the FK value, for relations .
if(isset($modelOptions['relatedModels'])){
$relatedModels = (is_array($modelOptions['relatedModels'])) ? $modelOptions['relatedModels'] : array($modelOptions['relatedModels']);
foreach ($relatedModels as $relatedModel) {
$relatedRelations = $relatedModel->relations();
foreach ($relatedRelations as $relationData){
if ($relationData[1] === get_class($model) && ($relationData[0] === GxActiveRecord::HAS_MANY || $relationData[0] === GxActiveRecord::HAS_ONE )) {
//TODO: Fix for composite keys
$fkName = $relationData[2];
//$model->$fkName =
$tbs = $model->getTableSchema();
$tbs_fks = $tbs->foreignKeys;
$relatedPk = $tbs_fks[$fkName][1];
if ($relatedModel->$relatedPk === null) {
throw new Exception(Yii::t('giix', 'Related model ('.get_class($relatedModel).') don\'t have a value for primary key ('.$relatedPk.'). Save he before of '.$relationData[1].'.'));
}
$model->$fkName = $relatedModel->$relatedPk;
}
}
}
}
// Save the model
if (!$model->save($modelOptions['runValidation'], $modelOptions['attributes'])) {
if ($transacted)
$transaction->rollback();
return false;
}
// If there is related data, use saveRelated.
if (!empty($modelOptions['relatedData'])) {
if (!$model->saveRelated($modelOptions['relatedData'], $modelOptions['runValidation'], $modelOptions['batch'])) {
if ($transacted)
$transaction->rollback();
return false;
}
}
// Add the model to the saved models array.
// Only the last model of each class is recorded.
if ($options['detectRelations'])
$savedModels[get_class($model)] = $model;
}
// If transacted, commit the transaction.
if ($transacted)
$transaction->commit();
} catch (Exception $ex) {
// If there is an exception, roll back the transaction...
if ($transacted)
$transaction->rollback();
// ... and rethrow the exception.
throw $ex;
}
return true;
}
Best Regards,
Fernando Santana
How to use:
$relatedData = array(
'users' => $_POST['Client']['users'] === '' ? null : $_POST['Client']['users'],
);
$models = array(
array('model'=>$model , 'modelOptions' => array('relatedData'=>$relatedData)),
array('model'=>$address,'modelOptions' =>array('relatedModels'=>array($model))),
array('model'=>$addressContact,'modelOptions' =>array('relatedModels'=>array($address))),
);
$options = array(
'withTransaction' => true
);
$model->saveMultiple($models,true,$options);
Hello, here’s a n00b question (sorry if this is the wrong place):
How do I get giix to generate views using a list of specified input types, as opposed to types based on the data type (eg http://www.yiiframework.com/wiki/303/drop-down-list-with-enum-values-for-column-of-type-enum-incorporate-into-giix/)? I’d like to avoid having to go in and manually change all of the generic <input type=text> tags, but I would also like to avoid having all enums forced into drop down (I might want radio, multiple checkboxes, some other wacky gizmo). I might want some fields to be omitted or just read-only depending on attributes the user may possess.
I’m thinking ideally there would be a set of tables (or arrays, I suppose) that I could configure, but I haven’t found any mention of something like this.
Hi lxvi, welcome to the forum!
giix doesn’t support enums OOTB.
I suspect that you should follow that wiki.
Hello mentel, thanks for your reply
OK, I understand, but what do you think about the broader question: would it make sense to have an extension (to giix or otherwise) that would allow one to configure the kinds of inputs that get generated, as opposed to having them all be text inputs (as from gii) or having them be based on the SQL column type (as in giix)?
I ask because I am about to start a project, and if I have to choose between manually editing dozens of files that I generate vs creating such an extension, I think I’d have more fun trying to create the extension, unless of course such an extension already exists (although I haven’t found one yet) or the time to create a first version of such an extension would grossly exceed all of the manual (tedious) editing.
Best regards.
<EDIT>
Looks like I failed to see post #184 above, where it appears as if you’ve asked very nearly the same question. Well, if you’d care to message me, I have the weekend to take a stab at something, and it couldn’t hurt to have your input
</EDIT>
Hi lxvi,
You can modify or add to the default template.
Also, giix supported enums in an early version. Check the changelog for more information.
You can start with the code that was there.
Hello mentel,
It turns out I didn’t get very far last weekend, but I am starting to dink around today, and I’ve run into a situation where the generated models have their representingColumn functions returning a column that was defined as a timestamp in the schema. I’m not sure this was the intent of this function. When I look at the code, getRepresentingColumn is looking for anything that is not pk, fk, null, nor defined as an int, which would mean that date, datetime, timestamp, time, and year (as well as the OpenGIS WKT stuff?) could potentially be found.
Wouldn’t it be better to explicitly search for a dbType of char, binary, text, enum, and set?
Or if I’m failing to understand the intent, would you please explain it more fully?
Thanks!
Check the code and the comments again.
It first looks for a string, then falls back to other types if your table doesn’t have one:
// First we look for a string, not null, not pk, not fk column, not original number on db.
// Then a string, not null, not fk column, not original number on db.
// Then the first string column, not original number on db.
And goes on to PK and to the first column.
To fully understand the method, please read the documentation for representingColumn and for __toString. There are more references to it through the other files, do a search and check them.
giix is well commented, you will understand what it is and how it is used.
Also, you can set it yourself in your model.
Hi.
I really don’t like to have the Id column and value shown (and be used as a mean to access the record) in the _view.
I want the representingColumn name and value to be the reference and already managed to do that by creating a new template and replacing the "id" row with this:
<?php echo '<?php'; ?> echo GxHtml::encode($data->getAttributeLabel($data->representingColumn())); <?php echo '?>'; ?>:
<?php echo '<?php'; ?> echo GxHtml::link(GxHtml::encode($data->__toString()), array('view', 'id' => $data-><?php echo $this->tableSchema->primaryKey; ?>)); <?php echo "?>\n"; ?>
My problem is, I can’t find a way to avoid repeating the representingColumn in the following rows of the model.
Is there a way to get access to the representingColumn name in GiixCrudCore?
Thank you!
Hi jmariani,
You can research about slugs, unique indexes and the unique validator.
Hi, Mentel.
I think what I need is simpler, something like tableSchema->isPrimaryKey, tableSchema->isRepresentingColumn maybe?
I mean, what I want to do is to know the name of the representingColumn at the GiixCrudCore code.
Thank you!
But I will do some research also.
Thank you for the tip.
I’m not sure I understand what you need.
getRepresentingColumn’s code is here.
Hi.
This is my code for the _view.php template:
<?php
/**
* The following variables are available in this template:
* - $this: the CrudCode object
*/
?>
<div class="view">
<?php echo '<?php'; ?> echo GxHtml::encode($data->getAttributeLabel($data->representingColumn())); <?php echo '?>'; ?>:
<?php echo '<?php'; ?> echo GxHtml::link(GxHtml::encode($data->__toString()), array('view', 'id' => $data-><?php echo $this->tableSchema->primaryKey; ?>)); <?php echo "?>\n"; ?>
<br />
<?php
$count=0;
foreach ($this->tableSchema->columns as $column):
if ($column->isPrimaryKey) continue;
if (++$count == 7)
echo "\t<?php /*\n";
if ($count < 2) continue;
?>
<?php echo '<?php'; ?> echo GxHtml::encode($data->getAttributeLabel('<?php echo $column->name; ?>')); <?php echo '?>'; ?>:
<?php if (!$column->isForeignKey): ?>
<?php echo '<?php'; ?> echo GxHtml::encode($data-><?php echo $column->name; ?>); <?php echo "?>\n"; ?>
<?php else: ?>
<?php
$relations = $this->findRelation($this->modelClass, $column);
$relationName = $relations[0];
?>
<?php echo '<?php'; ?> echo GxHtml::encode(GxHtml::valueEx($data-><?php echo $relationName; ?>)); <?php echo "?>\n"; ?>
<?php endif; ?>
<br />
<?php endforeach; ?>
<?php
if($count>=7)
echo "\t*/ ?>\n";
?>
</div>
Right now, I’m skipping the rendering of the repeated “representingColumn” because in my models, id is always column 0 and the representing column is always column 1, so I skip them in the
if ($count < 2) continue;
code.
You are already skipping the id column by using the
if ($column->isPrimaryKey) continue;
.
What I want to do is something like
if ($column->name == <tableRepresentingColumnName>) continue;
so I can skip the rendering of the row in the _view.
Hope I explained my case clear, english is not my mother tongue.
Thanks in advance!