multiple views the right way

the question is, how to render multiple views on a single page the right way?

all the examples I’ve seen so far are about simple views that take no input data from controller,

or the first view takes data and the others are very simple/without any input.

I have a couple of views that render some GridView with data from models that is passed to them by controller,

now I need to render all those GridViews on a single page, what is the solution here? to pass all data to a third view, that will render all other views, or is there a more aesthetic way which conform to MVC rules (no business logic in controller or views)?

If I need content from multiple sources I ajax load them. The actions your ajax loading from can just pertain to that content so you don’t have to join a bunch of stuff together in a third controller to get it to work.

a very straight forward approach would be render your main view with data and render other views from within, consider the following example I have a site/index action which render site/index.php view which renders additional views with data sent down from controller. hope this helps




    /**

     * Displays homepage.

     *

     * @return string

     */

    public function actionIndex()

    {

        $user = 'foobar';

        $product = 'some product data';

        return $this->render('index', [

            'user' => $user,

            'product' => $product

        ]);

    }


    // in your site/index view you can render your other views with data 

    // passed from controller like so

    $this->render('@app/views/product/view', ['product' => $product])

    $this->render('@app/views/user/view', ['user' => $user])

I was using the method with a third view that gets all data from controller and then passes it to views when rendering them.

I think it’s not the right way because it adds unneeded code and complicate future changes to those views ,in case some view is changed to take new data from model, I need to change that in controller (two actions) and in that third view which also renders this one.

Can you show a short example of doing that?

I’d rather agree with @alrazi. It’s simpler and easier.

There’s no reason to avoid using multiple models in a single controller and its view.

If you don’t want to repeat yourself, you can consider using a sub view that renders the gridview for a specific model as @alrazi’s code suggests.

It sounded like you were trying to do some sort of dashboard and that is why I recommended ajax loading them. If you have some queries that take longer than others I prefer to show some data then to wait for all of it to load and then render.

I’m very particular about render times so for me doing some things it in straight PHP sometimes isn’t as fast as it could be and sometimes it makes almost no difference.

I’ll also do things like not loading certain content unless a user scrolls close to it as it wouldn’t be needed to save calls, load times, bandwidth and user interactions (think infinite scrolling for this last point). However, this practice can be extreme and unnecessary in almost all cases but doing optimizations like this saved my company a lot of money in AWS hosting fees.

If you are not familiar with JS or the above seems like overkill in your case I would absolutely go with what @alrazi said.

Ajax loading things can cause a lot of headaches if your not familiar with it and how Yii widgets work. If you wrap your grids in PJAX on their pages you could get rid of some of the potential headaches. Also, ajax loading stuff is essentially doing the same thing that @softark said just via jQuery/JS and not PHP.

Regardless of the approach you use you should be using at a minimum renderAjax (should be renderPartial) [size=“2”]for your sub views[/size][size=“2”], [/size][size=“2”]If you don’t know the difference here is a stack answer that explains it very clearly [/size]https://stackoverflow.com/a/43049230[size=“2”])[/size][size=“2”]. [/size]

You is you will also need to assign an unique ID to each Yii widget (GridView, Pjax, etc) that is unique for the page that renders all of the items. If you don’t manually assign one Yii will assign the same ID to multiple widgets if they aren’t on the same page when it’s being rendered (the IDs will be something like w0). I don’t know if doing it the way they suggested above elevates this issue, you would need to verify.

[size="2"]Word of [/size][size="2"]caution…[/size][size="2"]using renderPartial will require you to register the required scripts for all of the Yii widgets you use in your parent views and manually [/size]placing[size="2"] the required code on your child views. [/size]

[size=“2”]Using renderPartial will prevent the [/size]unnecessary[size=“2”] loading of widget script files over and over. For example, If you have 3 groups of ajax loaded Pjax widgets wrapping a GridView it will load yii.js, yii.gridview.js, jquery.pjax.js, etc for each group of widgets. So it would load 9 files instead of 3 for the ladder example. This optimization practice can lead to [/size]extreme[size=“2”] frustration, I would start with renderAjax and then graduate to renderPartial after everything works and it’s needed. If you aren’t using any yii widget on the ajax loaded pages I would [/size]absolutely[size=“2”] use renderPartial as it should be hassle free.[/size]

[size="2"]With that said if you still want to do it via ajax [/size]

The simplest way to do it is just a jQuery get or for vanilla JS a XMLHttpRequest get.

You should place this in a JS file then in an Asset bundle for the page and register the asset bundle on the page. Alternatively, just to see it work you could register it in your view wrapping it in $this->registerJS();. See Working with client scripts page.

The most basic of examples would be something like





jQuery(document).ready(function () {

 var container = jQuery(".ajaxContent");

 jQuery.get(container.attr("data-url"), null, function (data) {

  container.html(data);

 });

});



your view would be something like




<?php 

use yii\helpers\Url;

?>

<div class="ajaxContent" data-url="<?= Url::to(['/site/page1']); ?>"></div>

<div class="ajaxContent" data-url="<?= Url::to(['/site/page2']); ?>"></div>

<div class="ajaxContent" data-url="<?= Url::to(['/site/page3']); ?>"></div>



The jQuery function above really isn’t a complete solution it’s just to get you in the right direction if you choose to go that route. It should be converted into a few different functions to handle errors, loading spinners etc. If you really start using a lot of ajax calls you should look into debouncing and throttling (lodash or underscore) links to prevent a lot of unnecessary ajax calls (think of users clicking the sort, pagination etc a bunch of times repeatedly for no reason). As you see it can get really complicated very quickly.

Getting something done quickly is almost always better than the most optimized way. Check your project requirements and pick your poison.

thanks everybody for a clear answer, especially skworden, that helped me a lot, I will do it the “right” way and use one view “to rule them all” without much optimization :)

Actually it is the right way. If your secondary view (sub-view) changes because of added data column in the model, the controller doesn’t know, or care. It just gets data from the model and passes it on. So then the ‘index’ view will just pass it along to the sub-view. The sub-view is the only one that cares.