has just started out Yii web app and encountered this problem, any suggestions are welcome:)
What i am trying to achieve:
-To display a form with tabs, each tab content contains a list of checkboxes from the same model.
-so user can select some items from tab 1, some from tab 2, etc and then click submit button to process.
Problem:
But i couldn't think of anyway such that the last tab activecheckboxlist will not clobbered the previous one up.
I am trying to to something similar to this : [www.yiiframework.com/forum/index.php/topic/20388-2-checkboxlist-and-1-model]
but instead of fixing it at 2, mine is dynamic.
What i have done so far:
<?php
$tabArray = array();
foreach ((Product::model()->listParentChild(0)) as $productparent) {
array_push($tabArray, array(
'label' => $productparent['name'],
'content' => CHtml::activeCheckBoxList(
$model, 'products', CHtml::listData(Product::model()->listParentChild($productparent['id']), 'id', 'name'), array(
'labelOptions' => array('style' => 'display:inline'),
'template' => '<div class="check-option">{input} {label}</div>',
'separator' => '',
)
), 'active' => ($productparent['id'] == 1 ? true : false),
));
}
?>
<?php
$this->widget('bootstrap.widgets.TbTabs', array(
'type' => 'tabs', // 'tabs' or 'pills'
'placement' => 'left',
'tabs' => $tabArray,
));
?>
and in my product model:
public function listParentChild($parentid) {
$sql = "SELECT * FROM piki_product WHERE parentid=:parentid";
$productlist = Yii::app()->db->createCommand($sql);
$productlist->bindValue(":parentid", $parentid, PDO::PARAM_INT);
return $productlist->queryAll();
}
any suggestions will be appreciated.. :/
I could be wrong, but I don't think cliffbarnes is on the right track with his comments about dynamic nesting. As far as I can tell, you're only dealing with one level of child products; it's just that there could be multiple sets of these child products.
In that case, the link you sited actually offers the correct solution:
<?php echo CHtml::checkBoxList('array1', CHtml::listData(Atributos::model()-> findAllByAttributes(array('tipo'=>'talla')), 'id_atributo','valor'))?>
<?php echo CHtml::checkBoxList('array2', CHtml::listData(Atributos::model()-> findAllByAttributes(array('tipo'=>'talla')), 'id_atributo','valor'))?>
Each set of checkboxes is given a different name (array1, and array2), so that each field's selected values doesn't override the other. In your case, the solution is the same; you just need to make the field names dynamic. I.E.
foreach ((Product::model()->listParentChild(0)) as $productparent) {
$fieldname = 'product' . $productparent['id'];
echo CHtml::checkBoxList($fieldname, ... (etc)
Within your controller you would check to see whether there are results for each dynamic field name.
foreach ((Product::model()->listParentChild(0)) as $productparent) {
if (isset($_POST['product' . $productparent['id']]) {
// Add values to $model->product
}
}
An even better solution would be to output each checkbox individually, so you can create one array of results, indexed by child ID.
foreach ((Product::model()->listParentChild(0)) as $productparent) {
foreach (Product::model()->listParentChild($productparent['id']) as $child) {
CHtml::checkBox("product[{$child['id']}]", ... (etc)
Then in your controller, all you'd have to do is this:
if (isset($_POST['product']) && count($_POST['product']) > 0) {
$model->product = array_keys($_POST['product']);
}
This solution does not work with activeCheckBoxList(). It would work if you wanted to override the __get() and __set() magic methods to make these dynamic property names available to your model, but that's probably over kill.
Edit (as per request)
If you need to have default selections for your checkboxes, you can just pass them as the second argument of CHtml::checkBoxList(). http://www.yiiframework.com/doc/api/1.1/CHtml#checkBoxList-detail
But if you still want to use __get() and __set(), here's an example:
class YourModel extends CActiveRecord {
// I usually create a placeholder to contain the values of my virtual attribute
protected $_childValues = array();
public function __get($name) {
// The following regular expression finds attributes
// with the name product_{parent ID}
if (preg_match("/^product_\d+$/", $name)) {
// I put the underscore in the name so I could get
// parent ID easier.
list($junk, $id) = explode("_", $name);
if (!isset($this->_childValues[$id])) {
$this->_childValues[$id] = array();
}
return $this->_childValues[$id];
}
else {
// Make sure to still call the parent's __get() method
return parent::__get($name);
}
}
public function __set($name, $value) {
// Same regex as above
if (preg_match("/^product_\d+$/", $name)) {
list($junk, $id) = explode("_", $name);
$this->_childValues[$id] = $value;
}
else {
// Make sure to still call the parent's __set() method
parent::__set($name, $value);
}
}
}
$model = new YourModel;
// Any property in the format of product_{parent ID} is available
// through your model.
echo $model->product_1;
$model->product_300 = array();
You might also consider checking to see if the parent ID in a property name corresponds with a parent ID in the database, instead of just allowing any property in that format to pass through.
Related
Model class MyModel has following behavior
public function behaviors() {
return [
'CTimestampBehavior' => [
'class' => 'zii.behaviors.CTimestampBehavior',
'createAttribute' => null,
'updateAttribute' => 'update_time',
'setUpdateOnCreate' => true
]
];
}
In code, in controller I write something like
$model = new MyModel();
$dataReader = Yii::app()->db->createCommand()
->selectDistinct('some fields')
->from('MyModel')
->leftJoin('some table')
->where('some criteria')
->query();
while ($item = $dataReader->readObject('MyMode', $model->getAttributes())) {
**//!!! HERE item array is with model attributes, which are empty**
$items[] = $item;
}
disaster, it's not working, items is array, each of element holding empty list of attributes, like no data fetched from db
If I write
$dataReader = Yii::app()->db->createCommand()
->selectDistinct('some fields')
->from('MyModel')
->leftJoin('some table')
->where('some criteria')
->query();
while ($item = $dataReader->readObject('MyModel', MyModel::model()->getAttributes())) {
//!!! HERE item array is with model attributes, which hold correct data, taken from db
$items[] = $item;
}
it's working
If I get rid off CTimestamp behavior, both cases work.
If I debug first case, I realize that, after pdo fetchobject is done, it calls constructor with scenario="current_timestamp()". Question is why? And where I missstepped?
If you read readObject() documentation you will find that second argument is not list of fields, but list of constructor arguments. CActiveRecord has only one constructor argument - $scenario. $dataReader->readObject('MyMode', $model->getAttributes()) essentially assigns random value as scenario, since it will get first value from $model->getAttributes(). In your case you probably need:
$item = $dataReader->readObject('MyModel', []);
(tagging with drupal7 because i cannot create a tag for backdrop)
i have a fresh backdrop install, with a new theme duped from bartik, and a new layout duped from moscone_flipped. no code changes to those yet.
i have a module that creates 2 simple blocks, mostly just some html. i have implemented hook_block_info() and hook_block_view(). i can place blocks into regions in the layout using the admin ui. i can see each block on the front end page when i place either one of them. but not both. when i have both blocks placed in the layout, for some reason both regions display the output from the same block. and i have verified that it is always the first block defined in the array returned from hook_block_info(). i have cleared caches, checked code, etc.
has anyone seen this before?
btw, i just applied the recent security upgrade, and the behavior is the same both before and after the upgrade.
i will paste in the module code below, in case i have missed something.
thanks for any help anyone can provide.
// implements hook_block_info()
function mbr_block_info()
{
$info = array();
$info['rate-tables'] = array(
'info' => 'Rate Tables (Buttons)',
'description' => 'The displays the rate table links for the sidebar',
);
$info['mbr-footer'] = array(
'info' => 'MBR Footer',
'description' => 'Displays footer links, disclaimer, copyright',
);
return($info);
}
// implements hook_block_view()
function mbr_block_view($delta = '', $settings = array(), $contexts = array())
{
$block = array();
switch($delta)
{
case 'mbr-footer':
$subject = null;
$mbrFooter = getMBRFooterBlock();
$block = array('subject' => $subject, 'content' => $mbrFooter);
case 'rate-tables':
$subject = null;
$rateTables = getRateTablesBlock();
$block = array('subject' => $subject, 'content' => $rateTables);
}
return($block);
}
I am running into a little snag with combining extensions "EchMultiSelect" and "MultiModelForm" of YII framework.
What I'm trying to do is copy a set of form elements one of which elements is an EchMultiSelect widget.
According to tutorial on the jqRelCopy page, I would need to pass a copy of the element (datePicker in their example) to the 'jsAfterNewId' option:
'jsAfterNewId' => JQRelcopy::afterNewIdDatePicker($datePickerConfig),
So, I tried to modify that to:
'jsAfterNewId' => MultiModelForm::afterNewIdMultiSelect($memberFormConfig['elements']),
Where I also added the following to MultiModelForm.php:
public static function afterNewIdMultiSelect($element)
{
$options = isset($element['options']) ? $element['options'] : array();
$jsOptions = CJavaScript::encode($options);
return "if(this.attr('multiple')=='multiple'){this.multiselect(jQuery.extend({$jsOptions}));};";
}
its copied and working properly when i am using Add Person link but what happens if i am adding/cloning three items for example and when i change the third item multiselct option then its reflecting to the first multiselect dropdown only this is same for other as well also when i am adding new items by clicking on the Add Person link then its cloning the same element to the new row item
here is the code for the form configuration variables and multimodel widget call.
//$userList=array of the userIds from users table
$memberFormConfig = array(
'elements'=>array(
'userId'=>array(
'type'=>'ext.EchMultiSelect.EchMultiSelect',
'model' => $User,
'dropDownAttribute' => 'userId',
'data' => $userList,
'dropDownHtmlOptions'=> array(
'style'=>'width:500px;',
),
),
...
...
));
calling the MultiModelForm widget from the same view file
$this->widget('ext.multimodelform.MultiModelForm',array(
'id' => 'id_member', //the unique widget id
'formConfig' => $memberFormConfig, //the form configuration array
'model' => $model, //instance of the form model
'tableView' => true,
'validatedItems' => $validatedMembers,
'data' => Person::model()->findAll('userId=:userId', array(':userId'=>$model->id)),
'addItemText' => 'Add Person',
'showAddItemOnError' => false, //not allow add items when in validation error mode (default = true)
'fieldsetWrapper' => array('tag' => 'div',
'htmlOptions' => array('class' => 'view','style'=>'position:relative;background:#EFEFEF;')
),
'removeLinkWrapper' => array('tag' => 'div',
'htmlOptions' => array('style'=>'position:absolute; top:1em; right:1em;')
),
'jsAfterNewId' => MultiModelForm::afterNewIdMultiSelect($memberFormConfig['elements']),
));
Can someone help me with this please?
Thanks in advance!
After a long searching and googleing i found the solution for this, just replace the function in your MultiModelForm.php:
public static function afterNewIdMultiSelect($element)
{
$options = isset($element['options']) ? $element['options'] : array();
$jsOptions = CJavaScript::encode($options);
return "if ( this.hasClass('test123456') )
{
var mmfComboBoxParent = this.parent();
// cloning autocomplete and select elements (without data and events)
var mmfComboBoxClone = this.clone();
var mmfComboSelectClone = this.prev().clone();
// removing old combobox
mmfComboBoxParent.empty();
// addind new cloden elements ()
mmfComboBoxParent.append(mmfComboSelectClone);
mmfComboBoxParent.append(mmfComboBoxClone);
// re-init autocomplete with default options
mmfComboBoxClone.multiselect(jQuery.extend({$jsOptions}));
}";
}
Thats It....!!
Thanks...!!!
in cgridview there are a blank field to filter the data above every column how can I filter data depend on multi comparison creteria for example I can put >5 in this field in ID column to filter the data to be only records from id 6 and above.
I want to put something like >5 and <10 how can I do that
You create $filter instance of YourModel and implement custom search() method like its shown here:
Controller:
public function actionIndex() {
$filter = new YourModel('search');
$filter->unsetAttributes();
if (isset($_GET['YourModel']))
$filter->attributes = $_GET['YourModel'];
$this->render('yourview', array(
'filter' => $filter,
));
}
View:
<?php $this->widget('zii.widgets.grid.CGridView', array(
'dataProvider' => $filter->search(),
'filter' => $filter
...
));
As the result, attributes of $filter model contain values that user put in filter input fields of CGridView and its search() method can parse and use these values in any way to create required CDbCriteria and then return filtered CActiveDataProvider.
I didn't test it but to filter some attribute field of your model one could write something like this:
public function search()
{
$criteria = new CDbCriteria();
$matches = array();
if (preg_match('/^(<|>)(.+)$/', $this->field, $matches))
{
$criteria->addCondition('field ' . $matches[1] . ' :field');
$criteria->params[':field'] = $matches[2];
}
else
{
$criteria->addCondition('field = :field');
$criteria->params[':field'] = $this->field;
}
// TODO: add conditions for other fields
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
));
}
If you want to have both < and > conditions in one filter field it's up to you to decide which format to use and how to parse it to prepare search criteria.
In my case i use this code:
$matches=array();
if(preg_match('/(.+)(\s)(.+)/',$this->field,$matches)) {
$criteria->addCondition("field ".$matches[1]." and field ".$matches[3]);
} else {
$criteria->compare('field',$this->field);
}
And in filter input in cgridview i can use "<100 >50" (whitespace character is important in this case)
I've Client [1->N] Delivery
In _view of Client I want Delivery related to my client
This is in my ClientController
public function actionView($id)
{
$client = $this->loadModel($id);
$delivery_provider = new CActiveDataProvider(
'Delivery',
array (
'criteria' => array (
'condition' => 'client_id = :c_id',
'params' => array (':c_id' => $client->id),
), // fine dei criteri
) // fine array di definizione cactiveprovider
); // fine del CActive provider
$this->render('view',array(
'model'=> $client,
'delivery_provider' => $delivery_provider,
));
}
Then modules/admin/views/client/_view.php I add my CGridView. ... but ... it's not searchable and not sortable (but pagination works...)
How to proceed ?
Since Delivery is a model it's better to use the CActiveRecord::search() instead. This method is automatically generated for you if you used Gii.
For searching you have to capture the results of the search form / filters using $this->setAttributes($_GET['Delivery']); assuming your inputs have names of the form Delivery[attribute_name].
public function actionView($id){
$client = $this->loadModel($id);
$delivery = new Delivery('search');
if(isset($_GET['Delivery']))
$delivery->setAttributes($_GET['Delivery']);
$delivery->client_id=$id;
$this->render('view',array(
'model'=> $client,
'delivery_provider' => $delivery->search(),
));
}