Dynamic Multilevel Drop-down menu in Yii2 - yii2

I want to create a dynamic menu with my table (db). I have followed some instructions which are given below:
Table : "menupanal"
Step 01: I just create a super controller in app\components\Controller.php
Here is the code:
namespace app\components;
use app\models\MenuPanal;
class Controller extends \yii\web\Controller
{
public $menuItems = [];
public function init(){
$items = MenuPanal::find()
->where(['c_type' => 'MENU'])
->orderBy('id')
->all();
$menuItems = [];
foreach ($items as $key => $value) {
$this->menuItems[] =
['label' => $value['c_name'],
'items'=> [
['label' => $value['c_redirect'], 'url' => ['#']],
],
];
}
parent::init();
}
Step 02: Changed n main layout page:
echo Nav::widget([
'options' => ['class' => 'navbar-nav navbar-right'],
'items' => Yii::$app->controller->menuItems,
]);
It is working in only one level. My question::
Question : how can I add multilevel menu using Super controller ?
I am new in Yii2. Helps are highly appreciated.

Create New MenuHelper in Component folder. There is no default component folder. Please create by yourself.
<?php
namespace app\components;
use app\models\MenuPanel;
use app\models\Zuser;
use app\models\Vwrole;
use app\assets\AppAsset;
class MenuHelper
{
public static function getMenu()
{
$role_id = 1;
$result = static::getMenuRecrusive($role_id);
return $result;
}
private static function getMenuRecrusive($parent)
{
$items = MenuPanel::find()
->where(['c_parentid' => $parent])
->orderBy('c_sortord')
->asArray()
->all();
$result = [];
foreach ($items as $item) {
$result[] = [
'label' => $item['c_name'],
'url' => ['#'],
'items' => static::getMenuRecrusive($item['id']),
'<li class="divider"></li>',
];
}
return $result;
}
}
in Main Layout Page put the following code
echo Nav::widget([
'options' => ['class' => 'navbar-nav navbar-right'],
'items' => app\components\MenuHelper::getMenu(),
Enjoy Coding!!

You may use nested sets. Look at this extension for Yii: http://www.yiiframework.com/extension/nestedsetbehavior/ and its documentation. All you need to do is component with dynamic creation of menu items array for nested sets.
I found that there is a Yii2 extension version: http://www.yiiframework.com/extension/yii2-nestedsetbehavior/
Good luck

You may use this extension for multilevel dropdownMulti level Dropdown

Related

Save multiples checkboxList

I have a form with a CheckboxList generated through a model "Candidates" and I need to make a vote where the voter can select multiple candidates and record.
How do I 'pick up' the selected candidates and write to the votes table / model ??
Form "votos"
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'candidato_id')->checkboxList(ArrayHelper::map(Candidatos::find()->where(['status' => 1])->orderBy("nome ASC")->all(), 'id', 'nome')); ?>
<?= Html::activeHiddenInput($model, 'eleicao_id', ['value' => 1]) ?>
<?= Html::activeHiddenInput($model, 'cargo_id', ['value' => 1]) ?>
<?= Html::activeHiddenInput($model, 'urna_id', ['value' => 1]) ?>
<div class="form-group">
<?= Html::submitButton('Save', ['class' => 'btn btn-success']) ?>
</div>
<?php ActiveForm::end(); ?>
Model "Votos"
namespace app\models;
use Yii;
class Votos extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'votos';
}
public function rules()
{
return [
[['eleicao_id', 'candidato_id', 'cargo_id', 'urna_id', 'data'], 'required'],
[['eleicao_id', 'candidato_id', 'cargo_id', 'urna_id'], 'integer'],
[['data'], 'safe'],
];
}
public function attributeLabels()
{
return [
'id' => 'ID',
'eleicao_id' => 'Eleicao ID',
'candidato_id' => 'Candidato ID',
'cargo_id' => 'Cargo ID',
'urna_id' => 'Urna ID',
'data' => 'Data',
];
}
}
Controller "VotosControllers"
public function actionVotacao()
{
$model = new Votos();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('votacao', [
'model' => $model,
]);
}
Slightly unrelated, but if you haven't already I would strongly suggest making sure you have something like xdebug set up so you can quickly see what your code is doing as you make your changes. Being able to set breakpoints and see what your form has submitted can go a long way to helping you solve a problem like this on your own and the framework becomes less mysterious. With that out of the way, something like the following MIGHT help as far as the controller goes. There is other validation you would want to do as well I should add. Maybe the each validator which you can read up on here. For actionUpdate() you would need to look at deleting all the values that relate to the related id and re-populate with the new ones, checkout deleteAll. Hopefully I don't get smashed too hard for providing this solution which is not a drop in solution.
public function actionVotacao()
{
$model = new Votos();
if (Yii::$app->request->isPost) {
$model->load(Yii::$app->request->post());
if ($model->save()) {
// Save the checkbox values.
if (!empty(Yii::$app->request->post()['Votos']['XXXX'])) { // Your form should give you an idea of what the XXXX should be, xdebug is also your friend.
foreach (Yii::$app->request->post()['Votos']['XXXX'] as $candidato_id) {
$candidato = new Candidato();
$candidato->id = $candidato_id;
if (!$candidato->save()) print_r($candidato->errors);
}
}
}
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('create', [
'model' => $model,
]);
}

Cakephp dynamic homepage without slug

I am trying to build a dynamic page system with cakephp 3.
Using slugs I can show pages and content. But on the homepage, it is just using the default view template.
I have the routes as followed:
$routes->connect('/', ['controller' => 'pages', 'action' => 'display', 'home']);
$routes->connect('/:slug', ['controller' => 'pages', 'action' => 'view'], ['pass' => ['slug'], 'slug' => '[^\?/]+']);
Which works for the none homepage pages.
But I want to use the homepage as / (e.g. localhost:8000/)
And not /home (e.g. localhost:8000/home).
Currently the view function in the pages controller looks like this:
public function view($slug = null)
{
$pages = TableRegistry::getTableLocator()->get('webpages');
$page = $pages->findBySlug($slug)->firstOrFail();
$this->set(compact('page'));
}
Any idea?
Seems I already found the solution.
I changed the routing to just the following line:
$routes->connect('/*', ['controller' => 'pages', 'action' => 'view']);
Then I changed the view as followed:
public function view($slug = null)
{
$pages = TableRegistry::getTableLocator()->get('webpages');
if($slug == null){
$query = $pages->find('all', [
'conditions' => ['ishome' => 1]
]);
} else {
$query = $pages->find('all', [
'conditions' => ['slug' => $slug]
]);
}
$page = $query->first();
$this->set(compact('page'));
}
I use the answer from the following comment, but had to modify it a bit, since that code was used for an older version of cakephp (I am using cakekphp 3.8).
https://stackoverflow.com/a/3975923/6181243

Yii2 virtual attribute out of an attribute of a SqlDataProvider

My AddressController:
public function actionIndex() {
$searchModel = new AddressSearch;
$dataProvider = $searchModel->search($_GET);
return $this->render('index', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]);
}
My model AddressSearch:
class AddressSearch extends Model {
public function search($params) {
$dataProvider = new SqlDataProvider([
'sql' => '
SELECT
name
FROM...
View:
GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
'name2',
I have an attribute called name in this dataProvider. I would like to create a new virtual attribute called name2 out of name by preg_replace()-ing a few parts of it.
The function itself is working. I have tried a lot of different things, but I still can't make it to fill the attribute name2 with data. name2 is always empty. Can you please point me to the right direction? Many thanks!
UPDATE: based on the brilliant ideas of #Imaginaroom and #rob006 I've done the following:
moved getName2() and the attributes I'm filling with SqlDataProvider from base model Address to AddressSearch
deleted the empty base model Address because I don't need it anyway. Less is more!
in search() I've added:
foreach ($dataProvider->getModels() as $row) {
$model = new AddressSearch;
$model->setAttributes($row, false);
$models[] = $model;
}
$dataProvider->setModels($models);
It works! Many thanks guys! It's fantastic that you are there and help!!!
The problem is that you're using SqlDataProvider which returns rows from the table as array instead of model instance. So that's why your getter (virtual-attribute) does not work in GridView - GridView does not work on Address model instance, but on raw array without name2 field.
You should change your SearchModel to use ActiveQuery:
$dataProvider = ActiveDataProvider([
'query' => Address::find()->select(['name']) //and whatever your query contains besides this
])
...
UPDATE: If you don't want to do it like this, you can add your logic directly in GridView like so:
GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
[
'header' => 'Name 2',
'value' => function($model) {
if (isset($this->_name2)) {
return $this->_name2;
}
return $this->_name2 = preg_replace([...
}
]
If you really need Address model instance and use SqlDataProvider at the same time, you may convert array to model instance manually:
$models = [];
foreach ($dataProvider->getModels() as $row) {
$model = Address::instantiate($row);
Address::populateRecord($model, $row);
$models[] = $model;
}
$dataProvider->setModels($models);
You can place this in AddressSearch::search() or create custom SqlDataProvider and override prepareModels().

Yii2 UploadedFile::getInstance() returns null

When my form is sent UploadedFile::getInstance($model, 'images') returns null. I also tried UploadedFile::getInstanceByName('images'). In the $_POST array the images key is empty e.g. 'images' => ['']. The file exists in $_FILES array.
My code is pretty simple. My view:
<?php $form = ActiveForm::begin([
'options' => [
'class' => 'validation-wizard wizard-circle floating-labels',
'enctype'=>'multipart/form-data'
],
]); ?>
<?= $form->field($model, 'images[]')->fileInput([
'id' => 'image_0',
'class' => 'dropify',
'data-default-file' => ''
]) ?>
<?php ActiveForm::end() ?>
In my model I have:
public $images;
public function rules()
{
return [
['images', 'each', 'rule' => ['file']],
];
}
If you want to access an array of files, you need to use UploadedFile::getInstances() instead of UploadedFile::getInstance().
$files = UploadedFile::getInstances($model, 'images');
Good example of handling multiple files can be found in guide in Uploading Multiple Files section.

Yii2 Kartik-V Typeahead Advanced Widget: How to store result in Mysql

Tearing my hair out at this point, hopefully someone can help me out!
I am using the Kartik-V Typeahead Advanced widget with Yii2.
The plugin works, in that the functionality is working perfectly on the page, I search and the results appear in the auto complete list.
Unfortunately, I am unable to store the result in my database. I am seeing an issue on the following line:
->where([ 'name' => $model->name ])//This variable is returning null
Am I trying to store the data incorrectly? I have tried everything I can think of, but I am sure someone here will come up with something better!
See below for the full code.
My controller:
public function actionIndex()
{
$model = new Member();
if ($model->load(Yii::$app->request->post())) {
$test = Test::find()
->where([ 'name' => $model->name ])//This variable is returning null
->one();
$test->updateCounters(['times_used' => 1]);
}
return $this->render('index', [
'model' => $model,
]);
}
/*************
* Initial prefetch of results
*************/
public function actionPrefetchlist() {
$query = new Query;
$query->select('name')
->from('test_table')
->limit(10)
->orderBy('times_used');
$command = $query->createCommand();
$data = $command->queryAll();
$out = [];
foreach ($data as $d) {
$out[] = ['value' => $d['name']];
}
echo Json::encode($out);
}
/*************
* Remote results
*************/
public function actionRemotelist() {
$query = new Query;
$query->select('name')
->from('test_table')
->where('name LIKE "%' . $q .'%"')
->limit(10)
->orderBy('times_used');
$command = $query->createCommand();
$data = $command->queryAll();
$out = [];
foreach ($data as $d) {
$out[] = ['value' => $d['name']];
}
echo Json::encode($out);
}
The view file:
echo $form->field($model, 'name')->label(false)->widget(Typeahead::classname(), [
'name' => 'name',
'options' => ['placeholder' => 'Filter as you type ...'],
'pluginOptions' => ['highlight'=>true],
'dataset' => [
[
'datumTokenizer' => "Bloodhound.tokenizers.obj.whitespace('value')",
'display' => 'value',
'prefetch' => Url::to(['prefetchlist']),
'remote' => [
'url' => Url::to(['remotelist']) . '?q=%QUERY',
'wildcard' => '%QUERY'
]
]
]
]);
you ask here for a new model:
$model = new Member();
so you get a empty model
so the $model->name is empty
if you set the model $model->name='test';
than it will be filled so, fill the model first
So it turns out it was a massive rookie error.
If anyone else stumbles upon something similar, I removed the attribute name from the model's "rules()"
I need an integer in the database but I wanted users to enter in a string (I would then convert it in the controller). Removing it from the rule broke everything.
Hopefully this helps someone else :)