I'm implementing a dynamic navbar in Yii2 which displays a dropdown menu picking items from the database. Now, the problem is that when I call the function in which I fill the array, the system crash with the error:
"Invalid argument supplied for foreach()"
since it doesn't find the variable with the array of items. I don't know which controller should pass the arguments to the main view, I just need an array of all items in a data model (i.e. Course).
I've tried out with this but still doesn't work.
/* #var $courses \app\models\Course[] */
layouts/main
function items($courses)
{
$items = [];
foreach ($courses as $course) {
array_push($items, ['label' => $course->title, 'url' =>
Url::to(['course', 'id' => $course->id])]);
}
return $items;
}
$menuItems = [
// other items ...
'label' => 'Courses', 'items' => items($courses)
];
echo Nav::widget([
'options' => ['class' => 'uk-navbar-item'],
'encodeLabels' => false,
'items' => $menuItems
]);
How can I pass the $courses variable to the layouts/main view? Thanks everyone in advance.
You should extract this code into widget:
class MainMenu extends Widget {
public function run() {
echo Nav::widget([
'options' => ['class' => 'uk-navbar-item'],
'encodeLabels' => false,
'items' => $this->getItems(),
]);
}
protected function getItems() {
return [
// other items ...
['label' => 'Courses', 'items' => $this->getCoursesItems()],
];
}
protected function getCoursesItems() {
$items = [];
foreach (Course::find()->all() as $course) {
$items[] = [
'label' => $course->title,
'url' => Url::to(['/course', 'id' => $course->id]),
];
}
return $items;
}
}
Then in your layout you're just calling:
<?= MainMenu::widget() ?>
In this way you can keep your controller and view clean.
Related
I'm trying to add multiple Select2 widgets dynamically in the TabularForm using Pjax (build-in Ajax component in Yii2). For some reason the Select2 input is rendering on the wrong place at the top of the view (see gif1 below). As I understood, this issue is related specifically to the Select2 widget as everthing is working fine if I use some default component e.g. Html::a (see gif2 below).
gif1: https://i.imgur.com/YMh5dNb.gif
gif2: https://i.imgur.com/sJkTDkO.gif
How I can get rid of that strange behaviour with the Select2 widget? Thanks is advance!
Controller:
class ProfileController extends Controller
{
// ...
public function actionCreate()
{
if (Yii::$app->request->isAjax) {
// some logic here ...
return $this->renderAjax('object/create', [
// ...
]);
}
}
// ...
}
View:
// ...
use kartik\select2\Select2;
use kartik\select2\Select2Asset;
Select2Asset::register($this);
// a bunch of html code
<?php Pjax::begin(['id' => 'product-add']); ?>
$form1 = ActiveForm::begin();
$attribs = [
'name' => [
'type' => TabularForm::INPUT_RAW,
'value' => function($productModel) {
return Select2::widget([
'name' => 'state_10',
'data' => ['1' => '1', '2' => '2'],
'pjaxContainerId' => 'product-add',
'options' => [
'placeholder' => $productModel->tmpId,
'multiple' => true
],
]);
//return Html::a('product ' . $productModel->tmpId); <- works fine if I use this piece of code
},
],
// ...
Html::a("Add", ['profile/create'], ['class' => 'btn btn-primary'])
// ...
<?php ActiveForm::end(); ?>
<?php Pjax::end(); ?>
// ...
After some closer examination I found the solution. For those who will also face the same issue, you need to initialize your widget (Select2 in my case) before the pjax response, e.g. in your Controller:
class ProfileController extends Controller
{
// ...
public function actionCreate()
{
if (Yii::$app->request->isAjax) {
// some logic here ...
// initialize the widget with an appropriate id
$this->view->registerJs("$({$product->tmpId}).select2();");
return $this->renderAjax('object/create', [
// ...
]);
}
}
// ...
}
And somewhere in your View:
Select2::widget([
'id' => $productModel->tmpId, // set your unique id here
'name' => $productModel->tmpId,
'data' => ['1' => '1', '2' => '2'],
// ...
]);
I am trying to implement the 2amigos SelectizeDropDownList widget in a form to add new values to a table directly within the dropdown.
I am using the model Book and the Model Author so basically want to be able to add a new author in the book form.
This is the book controller at the update function:
public function actionUpdate($id) {
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['index']);
} else {
return $this->render('update', [
'model' => $model,
'categories' => BookCategory::find()->active()->all(),
'publishers' => Publisher::find()->all(),
'copirights' => Copiright::find()->all(),
'authors' => Author::find()->all(),
]);
}
}
This is the form:
<?=
$form->field($model, 'author_id')->widget(SelectizeDropDownList::className(), [
// calls an action that returns a JSON object with matched
// tags
'loadUrl' => ['author/list'],
'value' => $authors,
'items' => \yii\helpers\ArrayHelper::map(\common\models\author::find()->orderBy('name')->asArray()->all(), 'id', 'name'),
'options' => [
'class' => 'form-control',
'id' => 'id'
],
'clientOptions' => [
'valueField' => 'id',
'labelField' => 'name',
'searchField' => ['name'],
'autosearch' => ['on'],
'create' => true,
'maxItems' => 1,
],
])
?>
And this is the function author controller:
public function actionList($query) {
$models = Author::findAllByName($query);
$items = [];
foreach ($models as $model) {
$items[] = ['id' => $model->id, 'name' => $model->name];
}
Yii::$app->response->format = \Yii::$app->response->format = 'json';
return $items;
}
The form works fine to load, filter, search and add new items.
But it is not inserting the new typed attribute in the author table.
Do I need to add something in the book controller?
How can I check if it is a new value or a change of an existing author?
Thanks a lot
I made it work with the following code, not sure the most elegant because i am checking the if the author_id is a number or a string.
In my case the author won't be a number anyway.
public function actionUpdate($id) {
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post())) {
$x = Yii::$app->request->post('Book');
$new_author = $x['author_id'];
if (!is_numeric($new_author)) {
$author = new Author();
$author->name = $new_author;
$author->save();
$model->author_id = $author->id;
}
if ($model->save()) {
return $this->redirect(['index']);
}
} else {
return $this->render('update', [
'model' => $model,
'categories' => BookCategory::find()->active()->all(),
'publishers' => Publisher::find()->all(),
'copirights' => Copiright::find()->all(),
'authors' => Author::find()->all(),
]);
}
}
In the project that I want to develop, user can upload an Excel file (xls,xlsx) to system.
The excel sheet has headers in the first row, and value in another row. System has a default excel template that
consist the rule for headers sequence such as (Name, Age, Sex), but sometimes user use their own excel template so sometimes the header sequence become like this (Sex, Name, Age).
To handle this sequence, I've a plan to make a mapping process to handle the headers sequence before save the value to database. I wanna use dual list box.
I've 2 a table in database for this process:
Header -> has to column (header_id, header_name), all the headers from file has been save in here.
Each headers saved with their own header_id and header_name
Info -> the value from the excel file save here.
and I also has a pure (not generated by Gii) CostumizedHeaderController.php, CostumizeHeader.php, Index.php
This is code in CostumizeHeaderController:
class CostumizeHeaderController extends Controller {
//put your code here
public function actionShowHeaders() {
$model = new CostumizeHeader();
$model->loadHeaders();
$items = \backend\models\CostumizeHeader::getAllHeader();
return $this->render('index', [
'model' => $model,
'items' => $items
]);
}}
Code in model (CostumizeHeader.php)
class CostumizeHeader {
//put your code here
/**
* #var array IDs of the favorite foods
*/
public $list_headers = [];
public function rules() {
return [
[['list_headers'], 'string', 'max' => 255],
];
}
/**
* #return array customized attribute labels
*/
public function attributeLabels() {
return [
'list_headers' => 'list Costumized Header',
];
}
public function loadHeaders() {
$this->list_headers = [];
$headers = Header::find()->all();
foreach ($headers as $ff) {
$this->list_headers[] = $ff->header_id;
}
}
public static function getAllHeader() {
$headers = Header::find()->asArray()->all();
$items = ArrayHelper::map($headers, 'header_id', 'nama_header');
return $items;
}
code in index.php
<?php
$form = ActiveForm::begin([
'id' => 'favorite-form',
'enableAjaxValidation' => false,
]);
?>
<?= $form->field($model->list_headers, 'list_headers')->widget(DualListbox::className(), [
'model' => $model,
'items' => $items,
'name'=>'nama_header',
'attribute' => 'list_headers',
'options' => [
'multiple' => true,
'size' => 15,
],
'clientOptions' => [
'nonSelectedListLabel' => 'Available Header',
'selectedListLabel' => 'Final Header',
'moveOnSelect' => false,
],
])
->hint('Select the header according the sequence.');
?>
I've try to var_dump in controller and got this Array ( [1] => age [2] => sex [3] => name .
And I've been check the header table, and all the headers from excel file have been imported to database.
But I still got an error, like this Call to a member function formName() on a non-object.
I wish anybody can help me to solve this problem. Thankyou
Inside your view page I think you are using field incorretly $model->list_headers should be $model only.
your index.php must be as follows:
<?php
$form = ActiveForm::begin([
'id' => 'favorite-form',
'enableAjaxValidation' => false,
]);
?>
<?= $form->field($model, 'list_headers')->widget(DualListbox::className(), [
'model' => $model,
'items' => $items,
'name'=>'nama_header',
'attribute' => 'list_headers',
'options' => [
'multiple' => true,
'size' => 15,
],
'clientOptions' => [
'nonSelectedListLabel' => 'Available Header',
'selectedListLabel' => 'Final Header',
'moveOnSelect' => false,
],
])
->hint('Select the header according the sequence.');
?>
I add another table field in my view , but the search button disappeared , How can I retrieve this form button ?
SaleItems = And a table mysql database.
<?php
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'formatter' => ['class' => 'yii\i18n\Formatter','nullDisplay' => ''],
'columns' => [
['class' => 'yii\grid\SerialColumn'],
**[
'attribute' => 'Data',
'content' => function(SaleItems $model, $key, $index, $column) {
return date('d/m/Y', strtotime($model->sale->date));
},
'header' => 'DATA'
],**
]); ?>
</div>
The search field disappears because in not setted properly in searchModel ..
for this you must extend the related modelSeacrh adding the relation with the related model and the filter for the field you need ..
for this you must set in the base mode, the relation
public function getSale()
{
return $this->hasOne(Sale::className(), ['id' => 'sale_id']);
}
/* Getter for sale data */
public function getSaleData() {
return $this->sale->data;
}
then setting up the search model declaring
/* your calculated attribute */
public $date;
/* setup rules */
public function rules() {
return [
/* your other rules */
[['data'], 'safe']
];
}
and extending
public function search($params) {
adding proper sort, for data
$dataProvider->setSort([
'attributes' => [
.....
'data' => [
'asc' => ['tbl_sale.data' => SORT_ASC],
'desc' => ['tbl_sale.data' => SORT_DESC],
'label' => 'Data'
]
]
]);
proper relation
if (!($this->load($params) && $this->validate())) {
/**
* The following line will allow eager loading with country data
* to enable sorting by country on initial loading of the grid.
*/
$query->joinWith(['sale']);
return $dataProvider;
}
and proper filter condition
// filter by sale data
$query->joinWith(['sale' => function ($q) {
$q->where('sale.data = ' . $this->data . ' ');
}]);
Once you do al this the searchModel contain the information for filter and the search field is showed in gridview
What in this answer is just a list of suggestion
you can find detailed tutorial in this doc (see the scenario 2 and adapt to your need)
http://www.yiiframework.com/wiki/621/filter-sort-by-calculated-related-fields-in-gridview-yii-2-0/
I created a CGridView in Yii whose rows are read from an XML file. I'm not using any models: only a controller (where I read the file) and a view (where I display the grid). What I cannot create is a filter (one input field per column) in the first row of the grid so to visualize only certain rows. How can I do it?
This is what I have until now:
Controller:
<?php
class TestingController extends Controller {
public function actionIndex() {
$pathToTmpFiles = 'public/tmp';
$xmlResultsFile = simplexml_load_file($pathToTmpFiles.'/test.xml');
$resultData = array();
foreach ($xmlResultsFile->result as $entry) {
$chromosome = $entry->chromosome;
$start = $entry->start;
$end = $entry->end;
$strand = $entry->strand;
$crosslinkScore = $entry->crosslinkScore;
$rank = $entry->rank;
$classification = $entry->classification;
$mutation = $entry->mutation;
$copies = $entry->copies;
array_push($resultData, array('Chromosome'=>$chromosome, \
'Start'=>$start, 'End'=>$end, Strand'=>$strand, \
'Crosslink_Score'=>$crosslinkScore,'Rank'=>$rank, \
'Classification'=>$classification, 'Mutation'=>$mutation, \
'Copies'=>$copies));
}
$this->render('index', array('resultData' => $resultData));
}
}
?>
View:
<?php
$dataProvider = new CArrayDataProvider($resultData, \
array('pagination'=>array('pageSize'=>10,),));
$this->widget('zii.widgets.grid.CGridView', array( 'id' => 'mutationResultsGrid',
'dataProvider' => $dataProvider, 'columns' => array(
array(
'name' => 'Chromosome',
'type' => 'raw',
),
array(
'name' => 'Start',
'type' => 'raw',
),
array(
'name' => 'End',
'type' => 'raw',
),
array(
'name' => 'Strand',
'type' => 'raw',
),
array(
'name' => 'Crosslink_Score',
'type' => 'raw',
),
array(
'name' => 'Rank',
'type' => 'raw',
),
array(
'name' => 'Classification',
'type' => 'raw',
),
array(
'name' => 'Mutation',
'type' => 'raw',
),
array(
'name' => 'Copies',
'type' => 'raw',
),
),
));
?>
Thanks for your help
Ale
file: FiltersForm.php (I put it in components folder)
/**
* Filterform to use filters in combination with CArrayDataProvider and CGridView
*/
class FiltersForm extends CFormModel
{
public $filters = array();
/**
* Override magic getter for filters
*/
public function __get($name)
{
if(!array_key_exists($name, $this->filters))
$this->filters[$name] = null;
return $this->filters[$name];
}
/**
* Filter input array by key value pairs
* #param array $data rawData
* #return array filtered data array
*/
public function filter(array $data)
{
foreach($data AS $rowIndex => $row) {
foreach($this->filters AS $key => $value) {
// unset if filter is set, but doesn't match
if(array_key_exists($key, $row) AND !empty($value)) {
if(stripos($row[$key], $value) === false)
unset($data[$rowIndex]);
}
}
}
return $data;
}
}
In your controller:
...
$filtersForm = new FiltersForm;
if (isset($_GET['FiltersForm'])) {
$filtersForm->filters = $_GET['FiltersForm'];
}
$resultData = $filtersForm->filter($resultData);
$this->render('index', array(
'resultData' => $resultData,
'filtersForm' => $filtersForm
)}//end action
And last what need - add filters to CGridView config array:
...
'dataProvider' => $dataProvider,
'enableSorting' => true,
'filter' => $filtersForm,
...
Why don't you store the information from the xml to a database and use YII ActiveRecord?
In your case you would need a live mechanism to filter the resultset on every filter query. Like with the search() method, when you use YII ActiveRecord models.
So you could use something like array_filter() with callback on your array on every filter call. (Edit: or the mechanism used here with stripos to return the matching "rows": Yii Wiki)
Or, second option, you could make the xml parser dependend on your filter inputs, which does not feel good to me :). The parser would habe to parse on every filter input, which could be a problem with big xml files.
Or, as mentioned, save the information to the database and use standard YII mechanisms.
Assuming you can use the data obtained in the objects in your foreach loop as the filter for that particular column, you could then pass these values through to the view, something like:
<?php
class TestingController extends Controller {
public function actionIndex() {
$pathToTmpFiles = 'public/tmp';
$xmlResultsFile = simplexml_load_file($pathToTmpFiles.'/test.xml');
$resultData = array();
foreach ($xmlResultsFile->result as $entry) {
...
$chromosomeFilter[] = $entry->chromosome;
...
}
$this->render('index', array(
'resultData' => $resultData,
'chromosomeFilter' => $chromosomeFilter,
...
);
}
}
?>
And then use that value in the filter for that column;
...
array(
'name' => 'Chromosome',
'type' => 'raw',
'filter' => $chromosomeFilter,
),
...
I've not tested, and it depends a lot on the structure of your xml and $entry->chromosome, but that might help put you on the right path?
I had the same problem
and what I did was I implement http://www.datatables.net/
And pull the data remotely . I pass the sorting and pagination to javascript .