HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

Discussions AND categories on homepage

I am trying to build a theme, that has the discussions list and the categroy list on the same page.

Like this:

In the backend I can only choose either controller as standard route.
I could set this to “discussions” and embed this in my template: {module name="CategoriesModule"} but that will only give me that limited table from the side panel.

It would be awesome if there was an easy way to get access to the $CategoryTree variable somewhere else than the categories controller.
This way I could use Smarty to run over the tree and build whatever view I like.

Comments

  • The way to do so would be to create a module for that. You could use the CategoriesModule as a start. You would need two files, copy

    /vanilla/modules/class.categoriesmodule.php
    /vanilla/views/modules/categories.php

    to

    /themes/whatever/modules/class.detailedcategoriesmodule.php
    /themes/whatever/views/modules/detailedcategories.php

    and start customizing them. I wouldn't be surprised if all the data you need is already included and you only have to customize the view.

  • Thanks, I will have a look.

    Anyway, reading into it, I wonder whether it isn’t possible to write a Themehook adding the categories data into the discussions view.

    Is that possible or even easier?

  • this works like a charm :-)

    I added a ThemHooks file and stole the category tree code from applications/vanilla/modules/class.categoriesmodule.php:

    /**
    * Fetches the categories tree and sets the data for the theme view.
    * Render the locale in a smarty template using {$locale}
    *
    * @param  Controller $sender The sending controller object.
    */
    public function base_render_before($sender) {
        // Bail out if we're in the dashboard
        if (inSection('Dashboard')) {
            return;
        }
    
        if(!val('CategoryTree', $sender->Data)) {
            $categoryModel = new CategoryModel();
            $categories = $categoryModel
                ->setJoinUserCategory(true)
                ->getChildTree(null, ['collapseCategories' => true]);
            $categories = CategoryModel::flattenTree($categories);
    
            $categories = array_filter($categories, function ($category) {
                return val('PermsDiscussionsView', $category) && val('Following', $category);
            });
    
            $data = new Gdn_DataSet($categories, DATASET_TYPE_ARRAY);
            $data->datasetType(DATASET_TYPE_OBJECT);
            $sender->setData('CategoryTree', $data);
        }
    
    }
    

    I am happy with my solution, but being new to Vanilla, I wonder where the pro and cons are to this and your suggested solution.

    Thanks

  • For your task, your solution is best! You should improve it by making the code only run when on the discussions view:

    Replace:

    public function base_render_before($sender) {
        // Bail out if we're in the dashboard
        if (inSection('Dashboard')) {
            return;
        }
    

    with

    public function discussionsController_render_before($sender) {
    

    That way the code will only be run when the discussions page is shown and not on each and every page (that happens when you use base_render_before).


    The advantage of my solution would be the reusability: making the part that you want to insert into the discussions view a module, makes it easily distributable and reusable. But you do not want to do that and therefore it would be bloated :wink:

    The advantage of your solution is that it only fetches the data you need to write your Smarty theme.


    But there are always ways to optimize: you are relying on code that might change in the future: say there is a security problem in there or the code will be optimized to run faster. You will miss that. Therefore re-using as much as possible is always a good approach. You have copied part of the CategoriesModule. Why not use that object directly?

    Vanilla has "Assets" and the side bar is called the "Panel" in Vanilla and is one of the assets. The assets are part of the controller and $sender is the variable which represents the controller. Therefore you can find the modules which are in the panel with $sender->Assets['Panel'] and the CategoriesModule is $sender->Assets['Panel']['CategoriesModule'].
    The data should be accessible with $sender->Assets['Panel']['CategoriesModule']->Data but at the time of "base_render_before" the code which fills the data hasn't been run yet. Therefore you have to do that.

    public function discussionsController_render_before($sender, $args) {
        // ...->getData() would be best, but sadfully you cannot run that directly.
        // The data is filled when another function is called.
        $sender->Assets['Panel']['CategoriesModule']->toString();
        $data = $sender->Assets['Panel']['CategoriesModule']->Data->resultArray();
        $sender->setData('CategoryTree', $data);
    }
    

    Another positive side effect of that would be that you fetch the data only once from the database: with the code above you fill the Data property of the object and when the module is shown, it will not be done again.

  • ah sweet, thanks :-)

  • had to wrap it in

    if ($sender->Assets['Panel']['CategoriesModule']) {
    

    otherwise I’m getting an error when opening the bookmarks flyout in the MeBox

    "Call to a member function toString() on null"
    
Sign In or Register to comment.