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

Some unimportant question about ways to build a setting screen

R_JR_J Admin
edited January 2014 in Tutorials

As a preface: this is not a real important question. I'm just curious and don't want that anybody wastes his/her time answering that question. So only do so if you are in a "teacher mood" ;)

What gives me hard times with Vanilla is that there are so many different ways to solve things that seem to me being the same...
I was planning to build a very, very complex setting screen (I've given that up before I even started) but I still want to build one that is parsing the input and writes something to the database.

Whenever I need something more complex or new, I look at the source code of as many similar plugins as I can think of and steal what looks most promising to me. Here is a rough overview of what I found for a setting screen:

1. ConfigurationModule

public function SettingsController_ProfileExtender_Create($Sender) {
   $Conf = new ConfigurationModule($Sender);
   $Conf->Initialize(array(
      'Plugins.ProfileExtender.ProfileFields' => array('Control' => 'TextBox', 'Options' => array('MultiLine' => TRUE)),
      ...
   ));

   $Sender->AddSideMenu('settings/profileextender');
   $Sender->SetData('Title', T('Profile Fields'));
   $Sender->ConfigurationModule = $Conf;
   $Conf->RenderAll();
}

I understand that! :) That's clean, easy and elegant. But that will not be enough for my use case this time :(

2. Dispatch magic

public function SettingsController_Tagging_Create($Sender, $Args) {
   $Sender->Permission('Garden.Settings.Manage');
   return $this->Dispatch($Sender);
}

I have read something about dispatchers, understood that they are some kind of standard routing tunnel that all calls should go through and that picture was okay for me.
This one is calling Controller_Index for any call to /settings/tagging and Controller_Something for /settings/tagging/something, right? Do I understand it correctly that routing through dispatcher is the preferred way?

3. SocialController?!

public function SocialController_Twitter_Create($Sender, $Args) {
  $Sender->Permission('Garden.Settings.Manage');
if ($Sender->Form->IsPostBack()) {
   ...
   what to do when form is saved
   ...
} else {
   ...
   what to do when setting form is loaded
   ...
}

$Sender->AddSideMenu('dashboard/social');
$Sender->SetData('Title', T('Twitter Settings'));
$Sender->Render('Settings', '', 'plugins/Twitter');
}

Okay, it calls a form and handles input of the setting screen. So far so good. But why is that SocialController? Doesn't it has to be Plugin- or SettingsController? Would there have been any difference when Settings- or PluginController has been used here?

4. Settings- or PluginController?

public function SettingsController_TouchIcon_Create($Sender) {
   ...
   all clear here
   ...
}
public function PluginController_Example_Create($Sender) {
   $Sender->Title('Example Plugin');
   $Sender->AddSideMenu('plugin/example');
   $Sender->Form = new Gdn_Form();
   $this->Dispatch($Sender, $Sender->RequestArgs);
}

Do I need PluginController whenever I want to see my settings in the side menu and SettingsController when it is enough to have a Settings button in the plugins overview? Are there any other pros and cons and things to take into consideration when choosing one of them?

I've asked a friend of mine to teach me OOP basics next time but besides of that there are numerous other things where I'm bare of any knowledge :\ That's why I would prefer there would be only one way of doing things in Vanilla: do it this way and it works, do it any other way and you will get an error. That would be much easier for me, but I understand that's like wishing cars could only drive as fast as bicycles do because they would be easier to handle that way... ;)

Comments

  • peregrineperegrine MVP
    edited January 2014

    edited...

    see hgtonight's comment :) and put in the tutorials category.

    I may not provide the completed solution you might desire, but I do try to provide honest suggestions to help you solve your issue.

  • A friend of mine might say this framework "gives you plenty of rope to hang yourself with". ;)

    I am just going to answer the direct questions you had.

    @R_J said:

    This one is calling Controller_Index for any call to /settings/tagging and Controller_Something for /settings/tagging/something, right? Do I understand it correctly that routing through dispatcher is the preferred way?

    Calling the Gdn_Plugin::Dispatch() method in a plugin lets you treat the originating method as a mini controller by setting up a form a calling the appropriate methods. It is essentially letting you create 'clean' urls like /settings/tagging and /settings/tagging/second rather than having to create multiple methods on an actual controller. Without the dispatch you can't have as clean of URLs, e.g. /settings/tagging and /settings/taggingsecond.

    It is important to note that this doesn't use the Gdn_Dispatcher class in anyway. It is merely a convenience method that facilitates clean code.

    @R_J said:

    Okay, it calls a form and handles input of the setting screen. So far so good. But why is that SocialController? Doesn't it has to be Plugin- or SettingsController? Would there have been any difference when Settings- or PluginController has been used here?

    Choosing which controller you want to extend comes down to two things. What functionality do you need, and what do you want the URL to look like. In this case, I guess it is to keep the URLs looking good. To answer your question about the differences between SettingsController, PluginController, and SocialController just look at those controllers.

    All of them extend the Dashboard controller, so they will render the admin view by default and can AddSideMenu(). Extending the Social Controller gives you access to enabling and disabling other social plugins. I don't see any controller specific code, so you could prolly swap those names out and only have to update your URLs.

    @R_J said:

    Do I need PluginController whenever I want to see my settings in the side menu and SettingsController when it is enough to have a Settings button in the plugins overview? Are there any other pros and cons and things to take into consideration when choosing one of them?

    The settings button in the plugin overview can be any URL. The button shows up if you define 'SettingsUrl' in the plugin info array.

    What this all means is that you can do whatever you want!

    I try to keep within the guidelines of the existing application to keep my plugins as 'Vanilla' as possible. This means I tend to put my setting screens on the settings controller. I like to dispatch to group functionality in a 'namespace'.

    Search first

    Check out the Documentation! We are always looking for new content and pull requests.

    Click on insightful, awesome, and funny reactions to thank community volunteers for their valuable posts.

  • Yes, I should have looked at the source - it's all in there :|

    PluginController is very barebone so I think I will use that because I suppose extending SettingsController could be a waste of memory/speed?
    And I really should use dispatch. That's my learning!

    Thanks to you :)

  • edited January 2014

    Think of the Gdn_Plugin::Dispatch() method as a way to add pseudo-controller methods within your plugin's class that behave like real methods in the main controller.

    Likewise, here is another way to create methods with a plugin. This example shows the usage of another class for OOP and organization purposes.

    class.example.plugin.php

    <?php if(!defined('APPLICATION')) exit();
    
    // Define the plugin:
    $PluginInfo['Example'] = array(
        'Name' => 'Example',
        'Description' => 'Shows a way to create controller methods with a plugin.',
        'Version' => '1.0'
    );
    
    // Include required files.
    require_once(__DIR__ . DS . 'class.exampleplugin.settings.php');
    
    class ExamplePlugin extends Gdn_Plugin {
        /**
         * Create Example Method ("/settings/example/") in SettingsController
         */
        public function SettingsController_Example_Create($Sender, $Args) {
            // Set a default method for "/settings/example/".
            if(empty($Args))
                Redirect('/settings/example/advanced/');
    
            // Handle the first argument: "/settings/example/$Args[0]/"
            switch(strtolower($Args[0])) {
                case 'advanced': // "/settings/example/advanced/"
                    ExamplePluginSettings::Advanced($Sender);
                    break;
                case 'new': // "/settings/example/new/"
                    ExamplePluginSettings::New($Sender);
                    break;
                default: // Set a default page for invalid arguments.
                    throw new Exception(T('Page Not Found'), 404);
                    break;
            }
        }
    }
    

    class.exampleplugin.settings.php

    <?php if(!defined('APPLICATION')) exit();
    
    // Contains methods for the plugin's settings.
    class ExamplePluginSettings {
        /**
         * Create Advanced method ("/settings/example/advanced/")
         * in SettingsController Example Method
         */
        public static function Advanced($Sender) {
            // Do stuff.
        }
    
        /**
         * Create New method ("/settings/example/new/")
         * in SettingsController Example Method
         */
        public static function New($Sender) {
            // Do stuff.
        }
    }
    

    Add Pages to Vanilla with the Basic Pages app

  • @Shadowdare: I was asking because I was confused by too many possibilities and you gave me another one: thanks for that! ;)

    Nope, indeed I'm happy to see solutions to whatsoever problem. But I'm always wondering if one of those methods is "cleaner" than the other. I have no coding routine so when I build up habits I want to know which way is a preferable way. I understand that there might be no differences at all and one solution is as good as the other as long as the result is clean, readable and functional code.

    Nevertheless I think there might be some advantages to one or the other way. Concerning which controller to use and how to route to methods, I will go with PluginController and dispatch because of this reasons:

    • PluginController has no overhead. I understand that using SettingsController will show a more intuitive url but it comes with a lot of functions that I do not need for my settings. I prefer performance and economical memory usage in this case - even if there is only a nearly not measurabe advantage by that. It just feels better to me (My mother is a very economical woman and although I'm wasting my money on cheap gadgets from China, I got her point. I think I could thank/blame her for having such thoughts)
    • dispatching the calls seems like having two big advantages to me: all those calls could be hooked and if someone wants to implement some genious functionality, my plugin should profit by that, too! Alright, that's very theoretical, but the second one is very important for me. I have problems when my code gets too long. I cannot see the structure in it easily and so I love patterns that I can recognize. Seeing some "standard" like public function Controller_Index does help me a lot.
  • That is an interesting idea @shadowdare. I was looking for ways to have an application (Yaga) have separate plugin files that were all integrated in the one hooks file. Mostly for organization.

    @R_J I really think using the plugin controller rather than the settings controller for optimization reasons is misguided. After all, you could just extend the dashboard controller and save the extra layer (which contains nothing) of the plugin controller class. You also probably don't need most of the JS files that are included in a standard dashboard controller, so you should probably just extend the base Gdn_Controller. You can keep going down the rabbit hole.

    The dispatch method does not fire any events or contain any magical calls. That is, it doesn't provide more 'hookability' than separate methods.

    I highly suggest using an IDE to read code. It is so helpful to be able to click on a method call and click "Go to definition"

    That said, come up with your own style, 'cause you got raw talent my friend!

    Search first

    Check out the Documentation! We are always looking for new content and pull requests.

    Click on insightful, awesome, and funny reactions to thank community volunteers for their valuable posts.

  • Moved to tutorials on request!

  • @hgtonight: I'm using Netbeans whenever I think debugging is needed. And yes I really love being able to dig into functions! That's something I miss when I work with Notepad++ which I prefer because it is more lightweight.
    I try to switch to Sublime Text which has the Goto Definition feature implemented in version 3 but I miss a well implemented FTP client for ST. There is FTPSync but it doesn't sync both ways. It's more a FTPAutoUpload than a real sync...

    I allege the developers created most of the parts of Garden on purpose ;)
    So I think if there is a dispatch function, I'll use it. If there is a PluginController, I'll use it.
    I've seen that the PluginC. is just a copy (<- wrong term, I know) of the DashboardC. But it exists and it is not used for any other purpose in the source. So when they created it and used it in their example plugin, I'll go with it, too.
    I also think that it is much cleaner from the routing point of view: where do you look for a file of a page like www.yourforum.com/discussions? You know it is part of Vanilla, so you go to /applications/vanilla/discussions. Easy. Where do you go if you see www.yourforum.com/plugin/something? Yep, to /plugins/something. But what if it is /settings/something? It is not in the /application/dashboard as the slug suggests. That's not consistent.
    But all in all that is a highly theoretical question that wouldn't make any plugin better, but I'm way better theoretically than practically ;)

  • One main benefit of the extensibility is that you can create methods on controllers that make sense for the rest of the application suite. Where the methods are defined is usually a constraint of the framework. Say I wanted to create a plugin that adds a new category view that will show discussions that are 'controversial'. The actual algorithm is not important in this case.

    I would argue that a URL like /discussions/controversial is more consistent than /plugin/controversial/discussions. Look to the vanilla settings hooks file for more examples (extending the profile, extending models, extending the settings controller, etc.).

    I suppose it comes down to how you want your methods to appear to be integrated. Also note that there is nothing stopping you from dispatching a method that extends any other controller.

    The biggest issue I take with 'extend plugin controller by default' is it gets into new dev's minds that they must extend the plugin controller.

    I feel like I am talking in circles now. XD

    Search first

    Check out the Documentation! We are always looking for new content and pull requests.

    Click on insightful, awesome, and funny reactions to thank community volunteers for their valuable posts.

  • Agreed. And for any other page than the settings page, I'd prefer to have a custom route, so it doesn't really matter ;)

  • edited January 2014

    If your plugin will have a lot of custom controllers, then it will be better to create an application instead. As you said, "the developers created most of the parts of Garden on purpose," and the application framework in Vanilla allows you to organize code by having the code structured with the MVC pattern easily.

    Add Pages to Vanilla with the Basic Pages app

Sign In or Register to comment.