Reduce the pain of navigating apps on Yii2


    Good day! We write most of the projects in Yii2 because it is cool and we love it.
    However, there is always something to improve (the benefit of this is not prevented by the Yii architecture). I want to share a solution that simplifies prescribing navigation in applications on Yii2.


    Problem


    When we add a page to the application, we need to prescribe the following things for it (after creating the controller and view):


    • Page Title ( $this->title = ...);
    • Bread crumbs ( $this->params['breadcrumbs'][] = ...);
    • Rights for action in the controller ( \yii\base\ActionFilterin the behaviorscontroller);
    • Parameter visiblewith access control in all menus where there is a link to this page;
    • Add a rule in \yii\web\UrlManager::rulesfor a nice reference;
    • Add page to sitemap.xml.


    Isn't it bold for "one more page"? The worst part is that all these points must be kept in mind and not forgotten. And if the navigation in the project starts to change, then it becomes even easier to break something, most often you forget about bread crumbs and they just become not working.


    Decision


    We assumed that any page of the application should be included in the general site map. So, if you create such a site map (in the form of a multi-level tree) with comprehensive information about the page (see paragraphs from the "Problem" section), then adding a page will be reduced to describing it in the site map, in only one place! We can register there headings, and the rights and rules of the link, and having a site map it is easy to get bread crumbs and sitemap.xml.


    Thus, the component [MegaMenu] () was obtained, which I represent to the habrasociety.


    Installation


    The component is installed via Composer:


    $ composer require ExtPoint/yii2-megamenu

    Next, we need to add the component to the application configuration:


    As an application component:


    'components' => [
        'megaMenu'=> [
            'class' => '\extpoint\megamenu\MegaMenu',
            'items' => [
                // You sitemap
                [
                    'label' => 'Главная',
                    'url' => ['/site/index'],
                    'urlRule' => '/',
                ],
                ...
            ],
        ],
        ...
    ],

    And load it before the application starts (to add rules to UrlManager):


    ...
    'bootstrap' => ['log', 'megamenu'],
    ...

    API


    The API component was created as close as possible to Yii2, often repeating it 1 in 1.


    Page description format (parameter \extpoint\megamenu\MegaMenu::items)


    Each item in most corresponds to the format for the navigation task \yii\bootstrap\Nav::items, where each item has attributes label, url, visible, active, encode, items, options, linkOptions. Each item is defined as an array, from which an instance of the class is then created \extpoint\megamenu\MegaMenuItem.
    Below we list the newly introduced parameters that are not in \yii\bootstrap\Nav::items:


    • urlRule(string, array, or instance \yii\rest\UrlRule). The format follows the rule from \yii\web\UrlManager::rules;
    • roles(string or array of strings). The format is identical \yii\filters\AccessRule::roles. Values ​​are supported "?", "@"and the role is indicated as a string.
    • order(number) Each menu level is sorted according to this parameter. The default value is 0.

    Component Methods \extpoint\megamenu\MegaMenu


    • setItems(array $items) Adds menu items to the end of the list;
    • addItems() Adds menu items;
    • getItems() Returns menu items;
    • getActiveItem()Returns the current route, similarly \Yii::$app->requestedRoute, but with parsed parameters;
    • getMenu(array $item, $custom)Finds a nested menu item ( null= root) and returns a nested menu with children. In the custom parameter, you can override the menu configuration if you set it as an array. If set by a number, then this will indicate the returned menu nesting. For example, it \Yii::$app->megaMenu->getMenu(null, 2)will return a two-level menu, even if the menu itself has a greater number of nesting.
    • getTitle($url = null)Finds the item for the specified url(default is the current page) and returns its title
    • getFullTitle($url = null, $separator = ' — ') Similar to the previous one, but also adds all parent item names
    • getBreadcrumbs($url = null) Returns breadcrumbs for a widget \yii\widgets\Breadcrumbs::links
    • getItem($item, &$parents = []) Finds item by url / route, in parents adds items of all parents for the found item
    • getItemUrl($item) Finds item and returns its url

    Item search logic


    The logic of comparing two item is implemented in the method \extpoint\megamenu\MegaMenu::isUrlEquals. Compare links by comparing two strings.
    Routes are compared a little more complicated: first they are normalized (getting a full route, indicating the module, controller and action), then only the routes are compared. If the routes match, then the parameters are compared.
    If the parameter is different from null, then both its key and value are compared. If the value is specified as null, this means that there can be any value, only the presence of keys is compared.
    Examples:


    • isUrlEquals (' http://ya.ru ', ' http://ya.ru ') // true
    • isUrlEquals (['qq / ww / ee'], ['aa / bb / cc']) // false
    • isUrlEquals (['aa / bb / cc', 'foo' => null], ['aa / bb / cc']) // false
    • isUrlEquals (['aa / bb / cc', 'foo' => null], ['aa / bb / cc', 'foo' => null]) // true
    • isUrlEquals (['aa / bb / cc', 'foo' => 'qwe'], ['aa / bb / cc', 'foo' => null]) // true
    • isUrlEquals (['aa / bb / cc', 'foo' => 'qwe'], ['aa / bb / cc', 'foo' => '555']) // false

    Example


    An example of a small web application with MegaMenu installed can be found in the test folder:



    C'mon, this will not work in real projects!



    However, there will be. MegaMenu is already successfully used in several large projects. In our projects, we always break the functionality into modules and MegaMenu does not resist this.
    An example of such a breakdown and a more realistic example can be seen in our boilerplate . A menu of pieces is assembled from modules or controllers .


    Todo


    The component is still developing, here are some features that should be expected in the near future:


    • Access control for the controller (behaviors analyzing the site map for access control);
    • Getting a sitemap for sitemap.xml;
    • UI for customizing a site map while saving changes to the database.

    End


    Thanks to everyone who read / leafed to the end. Any suggestions and wishes write to affka@affka.ru


    Put stars on github - ExtPoint / yii2-megamenu


    Have a nice day everyone!


    Also popular now: