Please upgrade here. These earlier versions are no longer being updated and have security issues.
HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

Adding Custom Navigation Links to Keystone on Mobile [RESOLVED]

mmckinnmmckinn New
edited February 2019 in Vanilla 2.0 - 2.8

Like the title says, I am wanting to add a couple of custom links within the navigation menu when a user taps the hamburger button, one to an external site and one to another directory on the same subdomain. For desktop view, I am using the Bitter Sweet theme with some custom CSS through the CSSEditor plugin. However, I have Keystone enabled for mobile. This is where I am having a hard time figuring it out.

Where to add the custom nav items in the desktop theme was pretty straight forward once I figured it out. However, I see that Keystone is written quite differently and requires modification elsewhere than the default.master.tpl best I can tell. If not, I will gladly take any schooling offered. I am VERY green to developing, but I do read and attempt to try things myself before bothering anyone about it.

As a side note:

So as to not modify core files, I have created copies of the Bitter Sweet and Keystone right off the bat because I don't want any future updates to wipe what I have done, in particular adding Swiper to my desktop theme and mobile theme. So, I do know that much. Most of the other modifications I have done have come from extensive trial and error. Fortunately, I was proactive enough to set up a development site to play around with until I get everything the way I want. Then I will copy everything over to my production site.

Thanks in advance to anyone offering insight (or even criticisms). I am really enjoying fooling around with this and learning more about website development and modification. 😃

Comments

  • R_JR_J Ex-Fanboy Munich Admin

    Actually it is pretty easy and you do not need to modify any files for that. You have two options: one is modifying your themes default.master.tpl and the other is to write a themehooks file to your theme or a plugin (themehooks and plugins are the same, so I count it as one).

    In the default.master.tpl you'll find the following lines:

    {categories_link format=$linkFormat}
    {discussions_link format=$linkFormat}
    {activity_link format=$linkFormat}
    {custom_menu format=$linkFormat}
    

    As you can expect, a link added to those lines will be displayed also in the theme. I think by adding "{debug}" somewhere in the default.master.tpl you will be able to see what variables are defined and there should be one that helps deciding if this is mobile or desktop and based on that you would be able to write some Smarty if-condition. I bet that is the most efficient way to do it.


    But I prefer to write plugins, so here is how you can do it that way in a little more detail (by the way, I use exactly that on my local development installation). In the lines above you find "{custom_menu...}" and you might wonder if you are able to customize it and yes, you can.

    This is a custom Smarty function and it is defined "/library/SmartyPlugins/function.custom_menu.php"

    If you look at that file, you will find "if (is_object($menu = val('Menu', $controller))) {" which means that if the current controller has an objet called "Menu", its items will be displayed. Exactly what you've asked for.

    There is a function that is called on every page load, it is called "base_render_before", so let's use that:

    public function base_render_before($sender) {
       // The first argument passed to that function is the current controller, the instance calling the method is named "$sender" by default in Vanilla.
       // Most probably you do not want to do anything in the dashboard.
       if (inSection('Dashboard')) {
          return;
       }
       // Now simply add the menu items you want to see
       $sender->Menu->Items[] = [
           [
               'Permission' => 'Garden.Settings.Manage',
               'Url' => 'dashboard/settings/themes',
               'Text' => 'Themes',
               'Attributes' => ''
           ],
           [
               'Permission' => 'Garden.Settings.Manage',
               'Url' => 'dashboard/settings/plugins',
               'Text' => 'Plugins',
               'Attributes' => ''
           ]
       ];
    }
    


    That's it.

  • mmckinnmmckinn New
    edited February 2019

    Thanks so much for the prompt response and solution @R_J !!! 😃 Very excited to see this. I know exactly where you are talking about in the .tpl and look forward to trying it. More so, I am looking forward to trying to plugin suggestion!

    You are the 💩

    I will play around with this when I get in from work a little later this evening and update with the results.

  • mmckinnmmckinn New
    edited February 2019

    Ok I'm still stuck. Please bear with my extreme ignorance. As I stated earlier, I am very green with all this.

    I first tried just inserting an "a href" in the following lines in the .tpl file (if that's what you initially meant), and that didn't work. =/

    1. {categories_link format=$linkFormat}
    2. {discussions_link format=$linkFormat}
    3. {activity_link format=$linkFormat}
    4. {custom_menu format=$linkFormat}

    (👆️ Not sure how to put code into a box in the post or I would have)

    Therefore, I moved on to the next recommendation you made. I very much like the idea of making a plugin too. I located the function.custom_menu.php file you mention, and found the line "if (is_object($menu = val('Menu', $controller))) {"

    Where I get hung up is when and where to apply the "base_render_before" code that you so graciously provided. Am I creating a new .php file for this and making a plugin budle like the many others installed with Vanilla, or am I applying this inside the function.custom.menu.php file or what?

    Finally, when using "$send->Menu->Items[] = [" where exactly do I add my url and menu item name?

    Many thanks!

  • R_JR_J Ex-Fanboy Munich Admin

    In order to mark something as code you have to mark those lines and click on the paragraph sign on the left of the text field

    Don't touch core files if not needed - and it is needed only in edge cases.

    You want your theme to look different than the default theme? Well, than it is a custom theme, nor? It should behave differently than default Vanilla, than it is an extension/plugin. Simple as that. Keep it clean and that will make your life more easy.

    Creating a custom theme is easy. Just copy the theme you like and change the plugins info in the addon.json file

    The problem with the link in the .tpl file is that Smarty caches compiled templates. There is a subfolder for that in your /cache folder. Delete all entries in the /cache/Smarty folder and you should see what you have added to the .tpl file.


    A plugin is such a great thing because you do not need to touch existing files. Writing a plugin which does nothing is very easy. I would recommend copying some simple plugin like the vanillicon plugin. Look at all files and folders and change every reference to "vanillicon" to the name of your plugin. The "key" in the addon.json must exactly match the spelling of the folder, that's important. And the plugin class must match "FolderName"+"Plugin" => class FolderNamePlugin extends Gdn_Plugin {

    Simply delete everything below that line, add a closing "}" and your (completely useless) plugin is ready.


    That plugin can now be extended by adding methods to it. And that's also where the base_render_before of my previous posting has to inserted.


    You should take some time to go through the documentation. It is more detailed than this small posting.

  • Thank you for your explanation. I have some time later today and will spend that going through the documentation and experimenting with what you have suggested.

    I sincerely appreciate it. 😃

  • Got my first plugin working @R_J !!! Thank you so much for the patience and guidance.

  • Ok. Next dilemma.....

    Plugin is successfully adding menu items with working hyperlinks (yay!)

    Is only showing custom menu items when user is logged in. Not showing for guests. 😐️

    Pretty sure it has something to do with the "{if $User.SignedIn} argument.

    <!---------- Mobile Navigation ---------->
              <nav class="Navigation js-nav needsInitialization">
                <div class="Container">
                  {if $User.SignedIn}
                    <div class="Navigation-row NewDiscussion">
                      <div class="NewDiscussion mobile">
                        {module name="NewDiscussionModule"}
                      </div>
                    </div>
                  {else}
                    <div class="Navigation-row">
                      <div class="SignIn mobile">
                        {module name="MeModule"}
                      </div>
                    </div>               
                  {/if}
                  {categories_link format=$linkFormat}
                  {discussions_link format=$linkFormat}
                  {activity_link format=$linkFormat}
                  {custom_menu format=$linkFormat}
                </div>
              </nav>
              <nav class="mobileMebox js-mobileMebox needsInitialization">
                <div class="Container">
                  {module name="MeModule"}
                  <button class="mobileMebox-buttonClose Close">
                    <span>×</span>
                  </button>
                </div>
              </nav>
              <!---------- Mobile Navigation END ---------->
    

    Here's my plugin info. Not sure if I need to add something here or in .tpl to make them viewable by all.

    class MenuItemPlugin extends Gdn_Plugin {
    
    public function base_render_before($sender) {
      // The first argument passed to that function is the current controller, the instance calling the method is named "$sender" by default in Vanilla.
      // Most probably you do not want to do anything in the dashboard.
      if (inSection('Dashboard')) {
       return;
      }
    
      // Now simply add the menu items you want to see
      $sender->Menu->Items[] = [
        [
          'Permission' => 'Garden.Settings.Manage',
               'Url' => 'my_link_url_1',
          'Text' => 'My Menu Item 1',
          'Attributes' => ''
        ],
        [
          'Permission' => 'Garden.Settings.Manage',
               'Url' => 'my_link_url_2.com',
          'Text' => 'My Menu Item 2',
          'Attributes' => ''
        	]
      		];
    	}
    }
    


  • R_JR_J Ex-Fanboy Munich Admin
    edited February 2019

    Reading code is normally much easier than writing code. So the code with the User.SignedIn reads like that if you leave some lines out:

                               {if $User.SignedIn}
                                   ...
                               {else}
                                   ...
                               {/if}
                               {categories_link format=$linkFormat}
                               {discussions_link format=$linkFormat}
                               {activity_link format=$linkFormat}
                               {custom_menu format=$linkFormat}
    

    That conditional statement is finished when all links are inserted and guests do see Categories, Discussions and Activity, don't they?

    Take a look at one of your menu entries and try to guess what each line could be needed for:

          'Permission' => 'Garden.Settings.Manage',
          'Url' => 'my_link_url_1',
          'Text' => 'My Menu Item 1',
          'Attributes' => ''
    


    If you are unsure, try to find the code that munches those lines, it is /library/SmartyPlugins/function.custom_menu.php:

    function smarty_function_custom_menu($params, &$smarty) {
       $controller = Gdn::controller();
       if (is_object($menu = val('Menu', $controller))) {
           $format = val('format', $params, wrap('<a href="%url" class="%class">%text</a>', val('wrap', $params, 'li')));
           $result = '';
           foreach ($menu->Items as $group) {
               foreach ($group as $item) {
                   // Make sure the item is a custom item.
                   if (valr('Attributes.Standard', $item)) {
                       continue;
                   }
    
                   // Make sure the user has permission for the item.
                   if ($permission = val('Permission', $item)) {
                       if (!Gdn::session()->checkPermission($permission)) {
                           continue;
                       }
                   }
    
                   if (($url = val('Url', $item)) && ($text = val('Text', $item))) {
                       $attributes = val('Attributes', $item);
                       $result .= Gdn_Theme::link($url, $text, $format, $attributes)."\r\n";
                   }
               }
           }
           return $result;
       }
       return '';
    }
    


    I'll provide a commented version of that function so that you can read what each element in a menu entry is used for, but in order to learn something, try to figure it out by yourself, first...

    So here it is:

    function smarty_function_custom_menu($params, &$smarty) {

       $controller = Gdn::controller();


       // rj: This line checks if there is a custom menu, if not (which means that no plugin has defined a custom menu)

    // the complete following code is skipped since nothing has to be done

       if (is_object($menu = val('Menu', $controller))) {


           // rj: The html of the link can also be provided with an array element "format" if you need something special,

    // but normally that is not needed

           // rj: What might of interest in some cases is that you can also give a "wrap" element which determines

    // the html element in which the anchor tag "a" should be wrapped. By default it is a list element "li"

           // rj: And you can (and should) add a "text" and "url" key in your array, but you can also give a "class"

    // if you need that for CSS purposes.

           $format = val('format', $params, wrap('<a href="%url" class="%class">%text</a>', val('wrap', $params, 'li')));


           $result = '';

           foreach ($menu->Items as $group) {

               foreach ($group as $item) {

                   // rj: Beats me. I guess if you would add 'Attributes' => ['Standard' => true'] to your menu entry,

    // it would _not_ be shown. Simply ignore this.

                   // Make sure the item is a custom item.

                   if (valr('Attributes.Standard', $item)) {

                       continue;

                   }


                   // rj: if there is a "Permission" entry in the menu item array, ensure that only users with that

    // permission will see the link

                   // Make sure the user has permission for the item.

                   if ($permission = val('Permission', $item)) {

                       if (!Gdn::session()->checkPermission($permission)) {

                           continue;

                       }

                   }


                   // rj: "Attributes" are the htmal attributes you can give to a link. They must be provided as

    // an array: 'Attributes' => ['rel' => 'nofollow']

                   if (($url = val('Url', $item)) && ($text = val('Text', $item))) {

                       $attributes = val('Attributes', $item);

                       $result .= Gdn_Theme::link($url, $text, $format, $attributes)."\r\n";

                   }

               }

           }

           return $result;

       }

       return '';

    }

    As you can see, the problem is that you have used the "Permission" key from my example without thinking about what that might be good for. Guests certainly do not have the Garden.Settings.Manage permission


    Sorry, no code in spoilers allowed, therefore it looks terrible...

  • Thank you, @R_J, for all that. I'm still trying to get my head completely wrapped around the Smarty plugin structure and get little "a-ha" moments the more I study over this and revisit the documentation, as well as your insights.

    I did end up managing to get them to show in the dropdown for guests by changing the permission to " 'Permission' => 'Garden.Settings.View',.Settings.Manage' " and adding the "View" permission for the guest role in the Dashboard. Upon doing that, I realized I would need to do that for every user role throughout the forum when a tester (member role) stated they could see the menu when logged in.

    I do realize this is probably not the best way to accomplish my goal and is a rather clunky way of doing it, especially after reading your reply. I had gotten off on another project, trying to figure out how to create a custom sidebar menu for my keystone theme. However, I will revisit the menu item project now based on your reply.

  • R_JR_J Ex-Fanboy Munich Admin

    Revert that, users shouldn't have that permission!


    Simply leave out that "Permission" line and no Permission check will be made at all => everybody can see that

  • Ooops! Done! Removed that. Now anyone can see it, which is exactly what I was going for. 😉

Sign In or Register to comment.