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

How do I write a Plugin with a Module?

hgtonighthgtonight ∞ · New Moderator
This discussion was created from comments split from: Template Errors.

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.

«1

Comments

  • Oh boy, I wish I could write my own Vanilla plugins! I wanted to learn how to do it for quite some time now, but never got around it.

    I had to implement a countdown button in the side panel for our forum just recently, which was when I discovered that I can't just insert PHP code into the default master template file.

    My search for more info brought me to this discussion, and further on how to implement a custom Smarty hook. So I wrote a function.countdown.php and put it into the library/vendors/SmartyPlugins folder. That solution is working just fine right now on my production site, but of course I know it's just not the proper way to do. If I could convert it to a plugin, I would be able to manage the countdown from the dashboard instead of having to enter the data directly as parameters in the Smarty hook in the template file. Hopefully at one point I find the time to play around with that idea...

  • R_JR_J Ex-Fanboy Munich Admin

    Just start, it's easy! And if you have written the logic and encapsulated it in smarty, you can transform it easily into a module. Look at that tutorial: http://vanillawiki.homebrewforums.net/index.php/How_to_Build_a_Simple_Module_and_Integrate_it_as_a_Plugin

    Come back at any time and ask if you get stuck

  • @R_J, cool, a tutorial is exactly what I was looking for next. Will give it a read in my spare time, thanks. :)

  • R_JR_J Ex-Fanboy Munich Admin
    edited June 2014

    Well, I'm in teaching mode, so here is step2: class.countdownmodule.php

    A module should do/show something and you have already written some code. Mind sharing it with us? We could implement it in the skeleton I'll construct in this post.

    Here we go. As I've said above, we need to create our own class and there is a naming convention we should take into account. Garden, the framework behind Vanilla, has some "base" classes that we normally extend when we are writing a plugin (no one has to start at zero, here). In step 1 we have used Gdn_Plugin and now that we are writing our module, we do this:

    class CountdownModule extends Gdn_Module {
    I guess there's nothing left to say...

    A module should have 2 functions "AssetTarget" that will hold the information where we want our module to show up and "ToString" which handles the output of the module.

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

    That was all we have to do to tell Vanilla that we want our module to show up in the panel. Not much functionality, not much code. I like that.

    public function ToString() {
       $CountdownModuleContent = <_<_<_EOS
    <_div id="MasterOnesCountdown" class="Box CountDownBox">
       <_h4>Fancy countdown timer!<_/h4>
       <_ul class="PanelInfo PanelCountdown">
          <_li>5<_/li>
          <_li>4<_/li>
          <_li>3<_/li>
          <_li>2<_/li>
          <_li>1<_/li>
       <_/ul>
    <_/div>
    EOS;
       echo $CountdownModuleContent;
    }
    

    So what have we done here? We've created the function ToString that I've mentioned above and let it echo some html. Beware! You do not have to return the string, you have to echo it! But maybe it works with a return also - it's up to you to do some testing again...
    Modules headings are h4 and they have the div.Box container so you should simply copy the styling of the standard modules in order to have a nice looking forum.

    Well, that's it. Not very spectacular, so let me simply repeat what I've said before:
    1. extend class Gdn_Module
    2. create a function "AssetTarget" that defines where this module should be shown
    3. create function "ToString" that echos out the html you want to include in your panel

    Not much more to say. It's really that simple. Here's the code for class.countdownmodule.php

    <_?php if (!defined('APPLICATION')) exit();
    class CountdownModule extends Gdn_Module {
       public function AssetTarget() {
          return 'Panel';
       }
    
       public function ToString() {
          $CountdownModuleContent = <_<_<_EOS
    <_div id="MasterOnesCountdown" class="Box CountDownBox">
       <_h4>Fancy countdown timer!<_/h4>
       <_ul class="PanelInfo PanelCountdown">
          <_li>5<_/li>
          <_li>4<_/li>
          <_li>3<_/li>
          <_li>2<_/li>
          <_li>1<_/li>
       <_/ul>
    <_/div>
    EOS;
          echo $CountdownModuleContent;
       }   
    }
    

    You can now try to implement your markup inside of the ToString function already :)
    By the way: modules are very flexible and there are lot of ways to achieve the same. You don't really need a dedicated AssetTarget or ToString function, but it is far more easier to teach it that way.

    Step 3 will be the config screen, but I'll wait until you tell us what should be in there.

    I think we'll do a step 4 which will include localization.

    Edit: sorry, markup is fucked up - just search for "<_" and replace by "<"

  • Wow, you are amazing, @R_J, this will be really a great help not only for me. Right now I really can't start playing around with this (have to work), but in the meantime I can let you know how exactly I have implemented that countdown functionality with my "hacky" style.

    library/vendors/SmartyPlugins/function.countdown.php

    <?php if (!defined('APPLICATION')) exit();
    /**
     * Writes the countdown to the page.
     *
     * @param array The parameters passed into the function. This currently takes no parameters.
     * @param Smarty The smarty object rendering the template.
     * @return The url.
     */
    function smarty_function_countdown($Params, &$Smarty) {
       $url = ArrayValue('url', $Params);
       $title = ArrayValue('title', $Params);
       $days = ArrayValue('days', $Params);
       $date = strtotime(ArrayValue('date', $Params));
       $remaining = $date - time();
       $days_remaining = floor($remaining / 86400) + 1;
       if ($days_remaining <= $days && $days_remaining >= 0) {
         $Result = '<a class="CountDown Day'.$days_remaining.'" href="'.$url.'" title="'.$title.'"></a>';
       } else {
         $Result = '';
       }
       return $Result;
    }
    

    The folder themes/name_of_theme/design contains 31 PNGs named from 00_days.png to 31_days.png and the following addition in custom.css:

    .CountDown {
        display: block;
        width: 200px;
        height: 118px;
        background-repeat: no-repeat;
        margin: 0 0 10px 0;
    }
    
    .Day31 {
        background-image: url("31_days.png");
    }
    
    .Day30 {
        background-image: url("30_days.png");
    }
    
    .
    .
    .
    
    .Day00 {
        background-image: url("00_days.png");
    }
    

    Then in themes/name_of_theme/views/default.master.tpl it was just placing the new Smarty hook at the right place:

          <div class="Row">
            <div id="Panel" class="Column PanelColumn">
              <a href="{link path="/"}">{logo}</a>
              {countdown date="June 28, 2014 2:00 AM" days="31" url="/discussion/617/a-little-something-new" title="Something NEW is coming!"}
    

    Here you can also see the parameters I'd like to have configurable in the dashboard.

  • R_JR_J Ex-Fanboy Munich Admin

    Since you do not know how to use the configuration by now, use something like that in the ToString function:

    public function ToString() {
       $CountdownDate = "June 28, 2014 2:00 AM";
       $CountdownDays = "31";
       $CountdownUrl = "/discussion/617/a-little-something-new"; // I'd prefer "Target" instead of "Url"
       $CountdownTitle = "Something NEW is coming!";
       ...
    

    We'll take care for those vars in the next step.

  • Very well, @R_J, the two files are in place now, I'm fine with $CountdownTarget, so let's stick with that one.

    If possible, can you please also show how to set permissions for accessing and configuring that plugin? I'd like users of a certain role to be able to set and start the countdown.

  • R_JR_J Ex-Fanboy Munich Admin

    We'll do that later on. Let me show you what is "normally" done and then we can tune one or two aspects to your needs.

    For example I wouldn't go with 31 pictures but either only one picture using css sprites (http://csssprites.com/) or using a nicely styled standard font.

    By the way: I will not write any "countdown" code - that's completely up to you. I show you how you could make your code into a module. It will be more satisfying seeing your own code running in your forum ;-)
    Moreover, I see one thing in your code that needs you to understand what happened in step 1&2 in order to implement in a clean way. So doing that by yourself will surely help you getting comfortable with coding for Vanilla.

    I start writing step3 down, but weather is great and I think I will be out most of the day...

  • R_JR_J Ex-Fanboy Munich Admin

    Step 3 will be the hardest and longest part. Not because using the configuration is difficult, but because using forms include more steps by itself:
    1. design a form
    2. insert configuration values when the form is loaded
    3. save form values to configuration when a button is hit

    First let me begin with some general comments about configuration. You can store individual settings anywhere but there is a convention for plugin related settings. They should be saved inside conf/config.php. If you look at that file, you may seem some other plugins configurations. When we are finished, you will find your $CountdownTitle like that inside the config: $Configuration['Plugins']['Countdown']['Title'] = 'Something NEW is coming!';
    Afterwards you'll be able to do something like this echo C('Plugins.Countdown.Title', 'Optional default title if this setting is not yet made in the config');. That will either echo the config setting or - if that could not be found - the default we've specified.
    If you want to initially set a config value or change what is set in there, you can do it like that SaveToConfig('Plugins.Countdown.Title', 'Shiny new title');

    Knowing that, you can already make your plugin configurable! I've told you to set the $CountdownSomething variables in the ToString function. Instead of assigning string values to them, you can get the values from the config: $CountdownSomething = C('Plugins.Countdown.Something');. But you still have to change the config.php manually and we wanted to have a dedicated settings screen.

    Before we start with the form itself, let's take a look at the plugin. We have to add only one single line to the PluginInfo in order to get a (not-yet-functional) settings button to the plugin list where we can see our new plugin.

    $PluginInfo['Countdown'] = array(
       'Name' => 'Countdown',
       'Description' => "I don't know - it's your task to describe what we are doing here, MasterOne...",
       'Version' => '0.1',
       'Author' => 'MasterOne',
       'SettingsUrl' => '/dashboard/plugin/countdown'
    );
    

    Only the last line is new. Check the plugin list to see the new settings button. The url of this button is /dashboard/plugin/countdown and that could be read as "dashboard" = current application, "plugin" = controller and "countdown" = function in the plugin controller. Yes, we point our settings screen to a function inside of Garden controller. This might sound strange, but we have a way to define/create custom functions "inside" of controllers so that they will be treated as if they belong to them. Let's do exactly that:

    public function PluginController_Countdown_Create($Sender) {
       $Sender->Permission('Garden.Settings.Manage');
    
       $Sender->AddSideMenu('dashboard/settings/plugins');
    
       $Sender->Form = new Gdn_Form();
    
       $Validation = new Gdn_Validation();
       $ConfigurationModel = new Gdn_ConfigurationModel($Validation);
       $ConfigurationModel->SetField(array(
          'Plugins.Countdown.Title',
          'Plugins.Countdown.Target',
          'Plugins.Countdown.Date',
          'Plugins.Countdown.Days'
       ));
    
       $Sender->Form->SetModel($ConfigurationModel);
    
       if ($Sender->Form->AuthenticatedPostBack() === FALSE) {
          $Sender->Form->SetData($ConfigurationModel->Data);
       } else {
          if ($Sender->Form->Save() !== FALSE) {
             $Sender->StatusMessage = 'Your settings have been saved.';
          }
       }
    
       $Sender->Render($this->GetView('settings.php'));
    }
    

    public function PluginController_Countdown_Create($Sender) {
    We Create the function Countdown to be a part of the PluginController with that line. If you just put in the line echo 'Overwhelming!'; die; inside of this function and press the settings button in the plugins overview, you'll see how easy it is to create a setting screen! But that's not how we will continue...

    $Sender->Permission('Garden.Settings.Manage');
    By just inserting that line, the current users permissions are checked and if he hasn't the requested right, he'll see an error screen. You've asked for custom permissions and so we will come back later to this line. The requested permission here is a general permission.

    $Sender->AddSideMenu('dashboard/settings/plugins');
    That line will highlight the "Plugins" entry in the side menu, but you do not need to do it.

    $Sender->Form = new Gdn_Form();
    Garden has a lot of helpers that you can use when dealing with forms. A setting screen is a form and that's why we attach a form to our controller.

    $Validation = new Gdn_Validation();
    Validation is one other helper that we will use. You can do a lot with that, but we just need it for the ConfigurationModel...

    $ConfigurationModel = new Gdn_ConfigurationModel($Validation);
    There's magic in there. The "Model" in MVC is the part that stores and retrieves data. ConfigurationModel does exactly that and it's "backend" is the config file. We plan on getting information from the config and write to it and so having a model for that makes accessing the config quite easy. We have to give a special rule set to the ConfigurationModel and that rule set is defined in the Validation. That sounds quite complicated and I have to admit that I can not write such code down by heart. I always have to look at other plugins for code like that. But you do not have to understand each single step in order to start writing your first plugin.

    $ConfigurationModel->SetField(array(
       'Plugins.Countdown.Title',
       'Plugins.Countdown.Target',
       'Plugins.Countdown.Date',
       'Plugins.Countdown.Days'
    ));
    

    As I've said, the ConfigurationModel gives us access to the config.php but we certainly do not want to handle each value in there, but only the fields of your plugin. So we specify what those are.

    $Sender->Form->SetModel($ConfigurationModel);
    We can link a form and a model! Think about it: if there is a connection between a data source and input fields, it is obvious that we will not have much work to do. Displaying the stored values can be done by creating html elements with the correct ids (more on "correct" later) and the values of a form could be easily saved back. That's exactly what we've needed.

    if ($Sender->Form->AuthenticatedPostBack() === FALSE) {
    Here we check if the form already contains data that has been sent back to us or if we see the form for the first time.

    $Sender->Form->SetData($ConfigurationModel->Data);
    If the above check result was that we see the form for the first time, we want to see the current values of our defined fields in the form. I guess we can call that magic again: $ConfigurationModel->Data gets us an array of the desired config values. Form->SetData sets each form element that has an id which is included in the result array to the value of that array element! So that single line will prepopulate our form fields with the config values, great isn't it?

    } else {
       if ($Sender->Form->Save() !== FALSE) {
          $Sender->StatusMessage = 'Your settings have been saved.';
       }
    }
    

    If we do not see form for the first time, then the form has been submitted back to us and we now have to save it. We've linked the ConfigurationModel to the Form and so if we call the forms save function, the values will also be written to the config. A one-liner again! But we also check that the return process hasn't failed. If there was no error, we give a status message back to the user so that he knows that everything worked as expected.

    $Sender->Render($this->GetView('settings.php'));
    We've talked a lot about the form, defined the functionality behind it and now it is time to load and display it. The GetView function looks at several places for the specified file name. One of those places is the views subdirectory of the current files directory.

    That has been a lot of stuff although it has been not much more than a dozen lines of code. The form itself will be a piece of cake now that we have taken that step!

  • R_JR_J Ex-Fanboy Munich Admin

    Here comes the form (/plugins/Countdown/views/settings.php):

    <?php defined('APPLICATION') or die();
    
    $Output = <<<EOS
    <_h1>Countdown Module<_/h1>
    <_div class="Info">Here you can configure all the countdown settings... (ugly description, but it's your job to think of something nice)<_/div>
    
    {$this->Form->Open()}
    {$this->Form->Errors()}
    
    <_ul>
       <_li>
          {$this->Form->Label('Countdown Title', 'Plugins.Countdown.Title')}
          {$this->Form->TextBox('Plugins.Countdown.Title')}
       <_/li>
       <_li>
          {$this->Form->Label('Countdown Target', 'Plugins.Countdown.Target')}
          {$this->Form->TextBox('Plugins.Countdown.Target')}
       <_/li>
       <_li>
          {$this->Form->Label('Countdown Date', 'Plugins.Countdown.Date')}
          {$this->Form->TextBox('Plugins.Countdown.Date')}
       <_/li>
       <_li>
          {$this->Form->Label('Countdown Days', 'Plugins.Countdown.Days')}
          {$this->Form->Date('Plugins.Countdown.Days')}
       <_/li>
    <_/ul>
    
    {$this->Form->Button('Save')}
    {$this->Form->Close()}
    
    EOS;
    
    echo $Output;
    

    (and I had to replace "<" by "<_" again)

    I will not explain every line since there's a lot of repetition and some trivial html in there. If you've read my previous post thoroughly, you'll be able to understand every single line immediately.

    {$this->Form->Open()}
    This will write an opening html form tag with the correct action (the current forms url) and method ("post") for us.

    {$this->Form->Errors()}
    I've spoken about "validation" before and if some of the input fields would be sent with content that doesn't adhere to the validation rules, the error message will be written back here with this line.

    {$this->Form->Label('Countdown Title', 'Plugins.Countdown.Title')} and
    {$this->Form->TextBox('Plugins.Countdown.Title')}
    There are some predefined form elements that we are using here. The Form->Label creates a label html element with the text "Countdown Title" and a for reference to "Plugins.Countdown.Title".
    The above Form->TextBox creates an input type text with the name "Plugins.Countdown.Title". Naming the input field like that is a requisite! Only if the html input elements have the same names as the config settings, the ConfigModel and the Form can work hand in hand.
    (The name "Plugins.Countdown.Title" gets converted to "Plugins-dot-Countdown-dot-Title" for the html element, by the way, but you don't have to care for that)

    We enclose our form elements in an ordered list. Why? Simply because the wise creators of Vanilla have decided to use it like that and so we have a predefined style for that. For this example I started by enclosing the form elements in paragraphs, but ended up looking in official plugins what markup has been used there. Copy whenever you can copy ;)

    {$this->Form->Label('Countdown Days', 'Plugins.Countdown.Days')}
    {$this->Form->Date('Plugins.Countdown.Days')}
    If you look close you'll see that we are using a form element called "Date" that you do not know from html. Correct. Get an inspiration of what form elements are accessible by looking at /library/core/class.form.php. There is some fine tuning possible for the Date "element" that we'll look at later on. What is important for you is to know that it is saved as "YYYY-MM-DD" inside of the config. In your example you've given an exact time. Think if this is really necessary.

    There's a lot more that you can know about each single step, but I think I've told you everything you have to knw to create a plugin that shows a module and has a configuration screen. I'll go through the code and give additional information to a few points. If something is not working as expected, give a short feedback and we'll get it up and running!

  • R_JR_J Ex-Fanboy Munich Admin

    We've started with PluginInfo and I want to elaborate just a little bit on it again. There are quite a lot of options that you can specify here and we started with barebone, but there are some more that I would recommend to you:

    'MobileFriendly' => TRUE
    That decides if your plugin will be loaded for the mobile theme also. Default seems to be FALSE

    `'License' => 'Whatever your preferred license is'
    That will make it easier for other, honest authors to work with your code

    'AuthorEmail' => '...',
    'AuthorUrl' => '...',
    Just in case someone still has a question

    'HasLocale' => TRUE
    Make your plugin translatable! And perhaps include a translation file. We will do this for your plugin, too. But to be honest: that setting has no meaning at all. It is just a convention and not requested at any time ;-)

    'RequiredApplications' => array('Vanilla' => '2.1'),
    'RequiredTheme' => FALSE,
    'RequiredPlugins' => FALSE,
    Those are some other PluginInfo fields that might be of interest at some time. I would include the RequiredApplications in order to stress that you haven't tested you plugin with older versions.

  • OK, I have put it all in place. Some things I'd like to comment on:

    @R_J said:
    For example I wouldn't go with 31 pictures but either only one picture using css sprites (http://csssprites.com/) or using a nicely styled standard font.

    I have implemented it with single pictures because it was the easiest that I can do on a short notice. Have a look at my test setup >>> here <<< to see my hacky-style implemention at work. That's exactly how I wanted it to look like, and I could not figure out a way to place the text in that button-link by just using the empty button as background and put the text in with some formating magic.

    Not sure about the advantage of converting it to a single CSS sprites picture.

    @R_J said:

     <li>
        {$this->Form->Label('Countdown Date', 'Plugins.Countdown.Date')}
        {$this->Form->TextBox('Plugins.Countdown.Date')}
     </li>
     <li>
        {$this->Form->Label('Countdown Days', 'Plugins.Countdown.Days')}
        {$this->Form->Date('Plugins.Countdown.Days')}
     </li>
    

    I think you meant the Date element the other way around:

       <li>
          {$this->Form->Label('Countdown Date', 'Plugins.Countdown.Date')}
          {$this->Form->Date('Plugins.Countdown.Date')}
       </li>
       <li>
          {$this->Form->Label('Countdown Days', 'Plugins.Countdown.Days')}
          {$this->Form->TextBox('Plugins.Countdown.Days')}
       </li>
    

    @R_J said:
    There is some fine tuning possible for the Date "element" that we'll look at later on. What is important for you is to know that it is saved as "YYYY-MM-DD" inside of the config. In your example you've given an exact time. Think if this is really necessary.

    Unfortunately it is necessary to have the time provided for the countdown calculation, because the day-switch has to happen at a certain daytime in a different time zone.

    Maybe the time should be put in its own variable and input field?

    @R_J said:
    'MobileFriendly' => TRUE

    Looks like this will be a little tricky, because in my desired end result the countdown is shown at the top of the side panel column below the site logo when in full mode, but put in the footer in line with the site logo when in mobile mode, because the mobile template has no side panel column (you can see the desired result in my above mentioned test setup).

    I really appreciate your outstanding help with this matter, it would have definitely taken a lot of time to figure this all out by myself if at all.

  • R_JR_J Ex-Fanboy Munich Admin

    CSS sprites only make sense for small graphics, so they wouldn't be the right solution in your case. Look at that short CSS hack: http://codepen.io/anon/pen/wEyLq
    Colors and size are not what you need, but I think it is a start to use pure CSS for what you are doing right now with a picture.

    Yes, I've mixed up Date and Day. If you need the time, you have a lot of options:
    1. use a normal text input and either trust the input or do some validations
    2. use the Date element and add another element: maybe a select field so that you can choose the time with a dropdown?
    3. search for a javascript date time picker like that: http://www.ama3.com/anytime/ It transforms a normal TextBox so that you can use all the advantages of Vanilla.

    I assume there is no problem with the mobile version. Try to set MobileFriendly to FALSE. It should be possible to include the module manually in your mobile themes default.master.tpl. Look at that code snippet: http://vanillaforums.org/discussion/comment/206387/#Comment_206387

  • vrijvlindervrijvlinder Papillon-Sauvage MVP

    Just add the panel to your mobile theme...

  • MasterOneMasterOne ✭✭
    edited June 2014

    @R_J, a plethora of new information I have to process first, really cool stuff! Pure CSS really could be it, but is the shown CSS hack cross-browser compatible? With CSS I'm always afraid having to make specific adaptations for various browsers to have it look the same whichever browser the visitor has in use.

    @vrijvlinder‌, interesting idea, though the solution as it is implemented now and seen in the test installation mentioned above (without panel and logo links / countdown button at the bottom) pretty much already suits our needs. I guess you mean a side panel in a mobile theme that is hidden to have the full screen estate for the discussions and only gets visible on demand? I think that will confuse most users, because they are more used getting a menu when something opens, and not a whole panel. Is there any ready to test mobile theme which has it implemented that way?

  • vrijvlindervrijvlinder Papillon-Sauvage MVP
    edited June 2014

    No, I mean that if your mobile theme does not have the panel, simply add it to the tpl. then anything that is on the panel will also work for the mobile.

    Mobile themes by default don't contain the panel. I added it to my themes because I wanted to have the panel and it's contents also available for mobile.

    Adding the panel to the mobile will not make the page wider. It will simply appear above or bellow the content depending on where you add it in the master. Then add css for it to make it width 100% that will force it to the size of the mobile window.

    All my mobile themes have the panel . Just pick one and see how it's put together.

    Yellow-Mobile and Mobile are tpl based themes

    the others are php based mobile themes in case you want to compare those .

  • R_JR_J Ex-Fanboy Munich Admin
    edited June 2014

    I wanted to go through the code and make some annotations.

    I've told you that your module needs two functions, but gave you a hint that they could be replaced. While I would always stick to the ToString function, I guess it would not be bad to replace AssetTarget.
    AssetTarget determines where your module should be rendered. Gave you following code:

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

    A function that returns only one value is no great function at all and so I think it is wise to think of other options to provide this information. Do you remember how we have added our module to the controller in the plugin code? $Sender->AddModule($CountdownModule); . So let's take a closer look at that "function AddModule". It is always helpful to have the source code at hand and do a search. We will find the following line public function AddModule($Module, $AssetTarget = '') {">public function AddModule($Module, $AssetTarget = '') {

    Obviously we could specify the AssetTarget already when we add our module to the controller! That's why you could replace $Sender->AddModule($CountdownModule); with $Sender->AddModule($CountdownModule, 'Panel'); and don't need that small function AssetTarget inside the module any more!

    Maybe something general again. I've said several times in this posting, that we added the module to the controller while you may be convinced that we've added it to the output. Yes, you are certainly right. But in order to get a full understanding of Vanilla, you need to have at least a few insights of what a MVC framework is. In fact it is no true MVC framework but it is usually called that way. Since it is not really MVC, it should be better called CMV: the browser passes the request to the Controller, the controller gets data from the Model and then gets the appropriate View. Our function AddModule is a function of the controller object that tells the controller how (where) to render our module. The controller is the traffic policeman that tells which code has to go where...

    vrijvlinder already told you that you can add the Panel asset to your mobile theme and that's a good solution, but it might lead to more modules inside of your mobile theme than you wanted. If that is the case, create a new asset in your mobile theme. Something like {asset name="MobileFooter"} .
    Then you can do the following in your module or in the plugin (although I've told you to get rid of the function AssetTarget, I'll use it again for this example because it is a standard function and in this case there will be logic inside of it):

    public function AssetTarget() {
       if(IsMobile()) {
          return 'MobileFooter';
       } else {
          return 'Panel';
       }
    }
    

    edit: using a non standard asset name will make your plugin unusable for others!

  • R_JR_J Ex-Fanboy Munich Admin

    @MasterOne said:
    but is the shown CSS hack cross-browser compatible?

    Don't know much about CSS. Check cross compatibility here: http://caniuse.com/#cats=CSS

  • R_JR_J Ex-Fanboy Munich Admin

    Speaking of CSS: when you want to style your module, you should create a css file and save it in the subfolder "design" of your plugin. Call it "countdown.css" or "custom.css". Now we want to add that css just like we have added our module with $Sender->AddModule, right? Here is how it is done: put that code below the $Sender->AddModule line $Sender->AddCssFile('countdown.css', 'plugins/Countdown');
    Just like we have added the module to the controller, we can add also our css file to it! We pass the name of the css file and the path to our plugin to it and the function will automatically guess that we have saved that file in our "design" folder.

    That's comfortable, isn't it?

    One possible solution for your timestamp problem was the usage of some Javascript - guess what our next step is ;)

    We have seen that there is a convenient function for adding a css file, so wouldn't it be nice if there is something similar for javascript? At first we need the file somewhere. Create the subfolder "js" in your plugins directory. And then, just from the code above, what would you guess is the right function and syntax? $Sender->AddJsFile('yourfile.js', 'plugins/Countdown'); is what you have guessed and you are right!

    But to put that line in the function where we attach the module to the controller doesn't make sense. We need the js file when our settings screen is loaded and that happened in our public function PluginController_Countdown_Create($Sender). So put the line inside of that function and when you take a look at the html source of your settings screen, you will see our file has been loaded.

Sign In or Register to comment.