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

Using a View from within a Module

R_JR_J Ex-FanboyMunich Admin

Preface

I tried to clone the categories module (because I want to create a pimped version) and found it exceptionally hard to get it working. Finally it was just one additional line to the module but I'd like to share what I've found out because I haven't seen a nice solution in other plugins that are using modules.

When you have a module and try to use FetchView($MyModuleView, '', 'plugins/MyPlugin') you'll most probably end up with a fatal error, that your view can not be found in application vanilla. Obviously FetchView does not use the application path you've specified and uses the $Senders application!

The reason is that GDN_Module has its own FetchView function that takes no parameters at all. That is an inconsistency that should not exist to my opinion. If I have two identical named functions, I'd expect them to take the same parameters and return the same values.

So without the correct ApplicationFolder the FetchView has to fail.

Solution

As I've said before, you cannot pass the ApplicationFolder to FetchView directly as a parameter but when your module is constructed, you can add that path!

So here is the minimal and really elegant code:

<?php if (!defined('APPLICATION')) exit();
class YourPluginModule extends Gdn_Module  {
   public function __construct($Sender = '', $ApplicationFolder = '') {
      $ApplicationFolder = 'plugins/YourModule';
      parent::__construct($Sender, $ApplicationFolder);
   }

   public function AssetTarget() {
      return 'Panel';
   }

    public function ToString() {
        return $this->FetchView();
        // or simply: return parent::ToString();
   }
}

The GDN_Modules __construct function accepts the application path as a parameter (beware not to use PATH_PLUGINS.DS.'YourPlugin' here - that's what I have done at first...). So when you pass it to parrent::__construct, the moduls protected variable $_ApplicationFolder will be set to your plugins path.

Our Modules ToString function returns the view that is called exactly like your plugin and it needs it to be saved exactly like that: /plugins/YourPlugin/views/modules/yourplugin.php.

That way, you can save a more complex layout in a special view and separate design from code.

Funny Findings

Our ToString function did nothing else than calling its parents ToString function and why should we define a function that has no extra value at all? There is no need for it, so we could just delete it from our code ;)

Obviously you don't have to implement a __construct in your module, when you don't need it for anything special. Our main goal was to pass an ApplicationFolder when we create the module. But it is created in the class.yourplugin.plugin.php file, where we can do it like that:

$ApplicationFolder = 'plugins/YourPlugin';   
$YourPluginModule = new YourPluginModule($Sender, $ApplicationFolder);
$Sender->AddModule($YourPluginModule);

So the __construct function could be killed from our module, too.

What's left inside is the AssetTarget. We have to specify that. But again there's a shortcut for that - we can specify the AssetTarget in the AddModule function. Change the code above to

$ApplicationFolder = 'plugins/YourPlugin';   
$YourPluginModule = new YourPluginModule($Sender, $ApplicationFolder);
$Sender->AddModule($YourPluginModule, 'Panel');

and we end up with a class.yourpluginmodule.php that contains no function at all! :D

But that is more funny than recommended ;)
AssetTarget and ToString are the most important functions for modules and I would expect to see them in every module!

Comments

  • hgtonighthgtonight ∞ · New Moderator

    This is a great write up!

    We can play with it a bit more by preventing the automatic override of the application folder:

    public function __construct($Sender = '', $ApplicationFolder = '') {
      if($ApplicationFolder == '') {
        $ApplicationFolder = 'plugins/YourModule';
      }
      parent::__construct($Sender, $ApplicationFolder);
    }
    

    PHP also allows you to completely disregard any 'extra' arguments passed to functions:

    public function __construct($Sender = '') {
      parent::__construct($Sender, 'plugins/YourModule');
    }
    

    Modules are pretty cool and lots of fun when you start grokking what is actually happening. I am slowly moving from the mindset of "this is PFM" to "this is PFC".

    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.

  • R_JR_J Ex-Fanboy Munich Admin

    I'm not very happy with the outcome. When I try to press plugins into MVC patterns, I see class.myplugin.php as Controller, class.mypluginmodel.php as Model (when needed) and views/whatsoever as View.

    What it is a module? It is a piece of html shown at a special place.

    • Where it is shown could be defined in the controller (AddModule(...,'Panel')) with simply adding a string to a function that is needed anyway.
    • What data should be shown could be externally computed in the model or if the need for a dedicated model is not given, it could be done in the controller (as it is done with any other plugin)
    • The output should come from a view if we want to have a clear separation from code and logic (needed especially for theming)

    And why must there be a second implementation for FetchView? Okay, I know why, but I having the need for that is awkward. If I understood the class right, it's only purpose is to provide a string. Does this legitimate a class of its own? And an own way of using that? I think it is not worth it.

    If I could reinvent that feature in Garden, I would make function AddModule($String, $AssetTarget, $ModuleAlias) add the given $String to $AssetTarget. That's it. No additional class.mypluginmodule.php needed. I have my plugin, model and views like before, but no module files any more.

    I admit that I have no understanding of the framework as such but as far as I can tell, GDN_Module is nothing but a stripped down version of class Gdn_Plugin. That design might be helpful in many other different places, but for plugins I feel it is unnecessary complicated. You could easily create fully functional modules with lots of features without implementing one single function in your module class. That i suspicious...

  • hgtonighthgtonight ∞ · New Moderator

    You can already do that.

    Use Gdn_Controller::AddAsset($AssetContainer, $Asset, $AssetName = '') to add a string to a specific asset container.

    I use it in my Jump To Top plugin rather than create a whole module. (Shout out to @kasper for pointing this method out!).

    If you are creating modules that are not dynamic (in the controller/model sense), go ahead and add the view as an asset directly if it makes you feel better.

    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.

  • R_JR_J Ex-Fanboy Munich Admin

    @hgtonight said:
    I use it in my Jump To Top plugin rather than create a whole module.

    Though you have already added a module without a class module you still are a fan of the modules (at least that is what I understand from your abbreviation* progression)

    @hgtonight said:
    Modules are pretty cool and lots of fun when you start grokking what is actually happening. I am slowly moving from the mindset of "this is PFM" to "this is PFC".

    Could you elaborate why you feel them to plugins that just ignore their corset?

    * = before I have seen that there is an explanation in the source to your abbreviations, I had googled for them. Didn't know that the explanation was enclosed. Only recently I searched for "PITA" which interested me because here in Germany you can go to a Greek snack bar and if you order a Pita, you'll get a delicious snack. Thinking of what is referenced as "Greek" in some special way, I had a hysterical laughter when I imagined I would go into a Greek takeaway somewhere in the US and say "please sir can you help me, i need a PITA"

  • vrijvlindervrijvlinder Papillon-Sauvage MVP

    I would like a way to be able to create an asset Body or if there was an asset Body.

    It is what is missing to hook a module after the Head div between the Head div and the Content div . I still don't understand that. Maybe it is just the names that are misleading.

    They could be called something else for clarity purposes.

    If the Body div is not an asset, the it is an asset container of other assets. What is that asset called if not Body asset ?

    Gdn_Controller::AddAsset($AssetContainer, $Asset, $AssetName = '')

    You still can't add the asset where you want it. There would need to be a target and in theory it should work to be able to target any asset into any other asset. Maybe they do and I can't see it because the css has height restraints on the areas.

    But they don't show up in the source so they are being ignored.

  • hgtonighthgtonight ∞ · New Moderator

    As you well know, there are many different ways to accomplish a task.

    Modules show dynamic ancillary information that depends on the controller. Plugins could go in and add in the assets manually, but modules have themeable views baked in.

    You might think of modules as little microcosms of a simpler system controller/view since the data displayed depends on a separate model.

    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.

  • hgtonighthgtonight ∞ · New Moderator

    @vrijvlinder said:
    I would like a way to be able to create an asset Body or if there was an asset Body.

    You can render any asset you want by adding {asset name="Body"} in your TPL file. Then you need to add modules to that asset via plugin or theme hooks. For example:

    public function Base_Render_Before($Sender) {
      $Sender->AddModule('NewDiscussionModule', 'Body');
    }
    

    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.

  • vrijvlindervrijvlinder Papillon-Sauvage MVP

    Yes I understand that, what I am saying is that there already is an asset Body which is the container of content asset and panel asset. I want to target that container.

    If I make a new asset Body it will conflict with the already Body div asset container for content and panel.
    That area between the Header and the content and panel is a gray area , a no mans land that can't be targeted by assets like modules.

    I can render anything I want if I get inside the tpl or php. That is not my problem. I suppose I can create a theme (I have) with those parts added. I wanted to create a module that targets that area without having to alter the master .

    In these cases jquery works much better with less code.

    There should be an area in between the header and content and panel that can be targeted.

  • peregrineperegrine MVP
    edited May 2014

    There should be an area in between the header and content and panel that can be targeted.

    you mean you wish there were placeholders. because there aren't any, unless you add them in your tpl. or change the dom

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

  • vrijvlindervrijvlinder Papillon-Sauvage MVP
    edited May 2014

    you mean you wish there were placeholders.

    Just one , like the foot asset but above the content and panel.

    A spare asset if you will. For that spot specifically . I thought Row was an asset too that could be targeted but it is just part of the layout not an asset just like Body

  • LincLinc Detroit Admin

    Ah, yes, modules seem spurious if you're a plugin developer. But what if you're theming? Do this in your default.master.tpl:

    {module name="NewDiscussionModule"}

    Ohhh, fancy! But what about....

    {module name="NewDiscussionModule" QueryString="boop=1"}

    Now I have a New Discussion button with ?boop=1 on the end of the URL. So with the power of Smarty, suddenly we have really dynamic ways of using modules in our theme (and passing them properties!) without ever using a theme hook or plugin.

Sign In or Register to comment.