Blog Archive

Thursday, 24 January 2013

Adding autocomplete to a simple module field in FUEL CMS

This quick tip follows on from Add a JQuery plugin to FUEL CMS module admin page.

FUEL CMS v1.0 uses JQuery's UI library  in the admin by default. This means features like auto-complete, or auto-suggest if you prefer, are very easy to add to simple modules.

In your model, add the form_fields() function if it is not already present, and choose the field in the model you wish to add autocomplete to - in this example, I'm going to suggest emails in an email field called "contact_email":

$fields['contact_email'] = array(
    'class' => 'email_suggest', 
    'comment' => 'Known emails will be suggested as you type'
); 
 
The array configuration here is minimal, we are just attaching a CSS class to the field, and adding a comment so that hovering over the label in the form will show the comment as a tooltip. More field options are shown on this page.

Since suggesting emails to the user when filling in an email field is so useful, I want to do it for any module that has an appropriate field, so instead of adding the JQuery function to the model's Controller.js, I'm going to add it to the parent class, BaseFuelController.js, then it will be available to call from any extended class.

Find a suitable location in BaseFuelController.js to put the following (I put it at the very end, don't forget to extend the array by putting a comma before it)

email_suggest : function() { 
    $('.email_suggest').autocomplete({ 
         source: '/suggest_emails' 
    });
}
 
As per the Autocomplete documentation, this function (email_suggest) will apply the autocomplete to an element with a class of "email_suggest", and will look for data remotely in the url '/suggest_emails'.

I made a controller to return emails in a JSON array from that url, but you could use the opt-in view functionality of FUEL CMS and create a view file of the same name, thus saving a bit of typing! If the emails are private, you might want to protect them from being viewed by just anybody (ie outside of the admin). You could opt to put the url inside of the Fuel module, but that's another project.

In the Controller.js of your module (mine was an "events" module, so it was named EventsController.js), you then just need to add something like this:

add_edit: function(){
    this._super(); 
    this.email_suggest(); 
} 
 
Since the module's Controller.js is an extension of the BaseFuelController.js, the function is called from the parent class. So for other modules that need the same feature, just add this.email_suggest(); to the add_edit() function in their Controller.js, not forgetting to define the js_controller key in MY_fuel_modules for each module you need to.

And that's pretty much it!

Monday, 7 January 2013

Codeigniter Upload class and mime types

It's been a while since I last used Codeigniter's Upload class, and recently, whilst testing an upload form that used it, I found some file types were receiving the message:

The filetype you are attempting to upload is not allowed.

A perfectly reasonable error in some cases, but I was trying to upload Word file types like .doc and .docx, as well as rich text format (.rtf), and all these types were defined in config/mimes.php

Or so it seemed. Looking at the Upload class in Codeigniter, there is a method is_allowed_filetype() that attempts to match the uploaded file with an entry in mimes.php. This is where it would be possible to add a log_message() call to determine what the issue was.

The version of Codeigniter in the install I was using was 2.1 (it was a FUEL CMS application) and I had noticed that the very latest CI (2.13) had a slightly modified Upload class. Rather than replace the class wholesale, I created a MY_Upload class in application/lbraries, extending the Upload class with a new is_allowed_filetype() method copied from v2.13, with added log_message() entries to print the file type and expected mime type, particularly before the last return so that the ultimate issue was recorded.

Doing so revealed the vagaries of browser mime type detection. Sometimes, for reasons I'm yet to understand, the Upload class was not receiving a particular mime type (as expected in mimes.php) but the browser default 'application/octet-stream'.

So the solution was simple, add that mime to file types in mimes.php that are generating an otherwise unreasonable

The filetype you are attempting to upload is not allowed.

How many types need this entry added to will be determined by your needs and or testing regime. I tested the upload on different browsers, on different OS's, and on local and live servers and found several different behaviours even with the same file.

Thursday, 8 November 2012

Creating an advanced module in FUEL CMS v1

Creating an advanced module in FUEL CMS v1 (beta) has become rather easier, since this release provides some “scaffolding” from the PHP CLI which creates the core files you will need to begin your project.

If it’s not clear what an “advanced module” is, it is a mini application folder under the fuel/modules path. If you never intend to repeat the simple modules you create as per Codeigniter, then you might not find the extra effort worthwhile. If you have a mind to portability, or just like tidy modularity, then trying an advanced module is time well spent.

For this tutorial, I’m going to take a well known library from Codeigniter, and ease it into a FUEL CMS advanced module. The library is Ion Auth, an authentication application that creates a secure login with the further ability to associate logins with groups. I’m going to use that library’s users table as my “members” table, but keep groups more or less the same.

Step one

The first task is to decide what to name the module. This is important, as you will not want any clashes with sub controller names, which will cause grief later on. I chose “membership”, which means that using the Ion Auth controller methods, urls will look like “your-site/membership/login”, “your-site/membership/logout” and so on.

STEP TWO

Having decided on a name, it’s time to generate the scaffold files. You will need to use the command line, so navigate to your installation and issue a command like:

php index.php fuel/generate/advanced/ membership

This will create a folder under the fuel/modules path called “membership”, and populate it with config, controller and helper files, and much else. But, it won’t create models, as these are much more likely to be unique to any given project. So the next CLI command will create the models

php index.php fuel/generate/simple members:groups membership

That command deserves some explanation: although the path includes “simple”, the arguments, the last especially, instruct the creation of models “members” and  “groups” in the “membership” advanced module folder. It is possible to chain more model names together using a semicolon as a separator, if you need to. The database tables “members” and “groups” also get created (provided you have set up a database connection already), but only in a rudimentary fashion.

So, after that you should have a “members_model.php” and “groups_model.php” in the modules /models subfolder.The models are quite basic, but functional. They can be made more sophisticated, as per the tutorial here.

Step tHREE

Next we will configure the module to play nicely in FUEL’s admin.

The previous CLI commands will have added the “membership” module to the FUEL CMS admin for you, with a link to an admin url “membership”. If you follow this link, it will be horribly broken. That’s to be expected. The admin doesn’t know what to do with “/membership”. Open the module’s routes file (membership_routes.php) and add this:

   1:  $route[FUEL_ROUTE.'membership'] = 'membership';
   2:   
   3:  $membership_controllers = array('members', 'groups');
   4:   
   5:  foreach($membership_controllers as $c)
   6:  {
   7:    $route[FUEL_ROUTE.$route[FUEL_ROUTE.'membership'].'/'.$c] = FUEL_FOLDER.'/module';
   8:    $route[FUEL_ROUTE.$route[FUEL_ROUTE.'membership'].'/'.$c.'/(.*)'] = FUEL_FOLDER.'/module/$1';
   9:  }



This is going to map 2 new links we will be creating, “members” and “groups”, on to FUEL’s Module controller class. Doing that will hook into the CRUD facilities of the admin. Note that we don’t need to actually create controllers for “members” and “groups”,  we are just mapping routes on to a class that is itself a controller, and will do all the admin stuff for us.


STEP FOUR


To create the links we need to edit the module’s config file (config.php)


   1:  $config['nav']['membership'] = array(
   2:    'membership/members' => 'Members',
   3:    'membership/groups' => 'Groups'
   4:  );
   5:  // tables for membership
   6:  $config['tables']['members'] = 'members';
   7:  $config['tables']['groups'] = 'groups';



Now when we follow these new links in the admin, there is a new error: “unable to locate the file…”. The model’s are missing! This is because we need to do some more configuring, this time in “membership_fuel_modules.php”. We need to specify where the models are:


   1:  $config['modules']['members'] = array(
   2:    'model_location' => 'membership',
   3:    'module_name' => 'Members',
   4:    'module_uri' => 'membership/members',
   5:    'model_name' => 'members_model',
   6:    'nav_selected' => 'membership/members',
   7:  );
   8:  $config['modules']['groups'] = array(
   9:    'model_location' => 'membership',
  10:    'module_name' => 'Member Groups',
  11:    'module_uri' => 'membership/groups',
  12:    'model_name' => 'groups_model',
  13:    'nav_selected' => 'membership/groups',
  14:  );



Actually, we do a lot more here than specify the model location, but it’s all pretty obvious.


At this point you should be able to use the admin navigation links (members, groups) and see list & create pages served up by FUEL’s Module class. Sweet.


STEP FIVE


Return to your SQL editor! Although the scaffold process created database tables, they need to be more specific. For the “members” table I repurposed the “users” table from the Ion Auth library. Likewise, the “groups” table is the same as the one in that package.


STEP SIX


The last step requires adding and configuring the Ion Auth library. The files simply go in the module’s folder, just as they would if they were in the application directory.


I repurposed the scaffolded membership controller to be the equivalent of the auth controller supplied with the library. Note however that the generated class is intended to be an admin compatible one, based on Module. For a front-end facing controller, we can just use CI_Controller, MY_Controller or whatever. It’s also important to note that some of the loading methods need altering: eg instead of regular

$this->load->library('ion_auth');



we need to use the one specifically for modules:

$this->load->module_library('membership', 'ion_auth');



which explicitly tells FUEL what module to look in (eg membership/libraries in this instance).


There are various paths that need to be edited from the original Ion Auth code: the controller path “auth” is used as a target for redirect() quite often, and the views all use “auth” in their actions. We can make use of a constant found in “membership_constants.php”, and substitute MEMBERSHIP_FOLDER. Of course if you elected to call you module “auth” in the first place, there would be no need.


The Ion Auth library has its own config file, and we can assign the tables variables to the database names we are using  eg

$config['tables']['users'] = 'members';



By visiting /membership/login from the front end, we should now see the Ion Auth views, and a functioning authentication system.


AFTERWORD


Although most aspects of this demonstration are working, there are lots of holes. Ion Auth uses some crafty hashing to create its passwords, so although you can create users from /membership/create_user (if you are logged in as the ion Auth admin) you will not create them properly from the FUEL admin. This is because the hashing process is not hooked in to the members_model.


Additionally there are other fields on display by default that should either be hidden, or be a different type.


Also, the groups table isn’t even used by the members model! However, v1 of FUEL CMS introduces an ingenious “has_many” relationship, and it is possible to add a multi-select field to the members_model so that group associations can be stored in the fuel_relationships table, without fiddling around with separate lookups.


These deficiencies are rectifiable though, and I will leave these to the reader to solve.

Friday, 24 August 2012

Advanced modules with simple module admin in FUEL CMS

I’ve been tempted lately to turn some of the simple FUEL CMS modules I’ve created into “advanced” modules. Partly I’ve wanted to do this to improve my understanding of Fuel, and partly because the concept of advanced modules make a lot of sense from a modularity and re-use point of view. Advanced modules are mini-applications, with their own contained controllers, models, views and assets. By copying their root folder, you have the entire file-set, rather than having to rummage in the applications directory for config, libraries, models and so on. It’s just so much more tidy!

When I began doing this with a simple “news” module, I reached a point where I thought I might have to re-create the simple module admin CRUD and I despaired a bit. I’d written a dashboard for an advanced module before (the configuration form for an import of the Sphider index engine) but that really required a custom input. Would the simple module admin (handled by FUEL’s module controller) really need some ugly, complicated retrofitting?

Fortunately no!! Here’s what I found, and again, FUEL allows an elegant stress free solution. If you follow the tutorials on www.getfuelcms.com, you’ll know that simple modules are recognised by the FUEL admin system when the appropriate array is added to MY_fuel_modules.php, eg:

   1:  $config['modules']['news'] = array(
   2:          'display_field' => 'title',
   3:          'instructions' => '',
   4:          'archivable' => TRUE,
   5:          'preview_path' => 'news/item/{id}'
   6:          ); 

By default, the model for this module will be found in /application/models. By specifying a “model_location” key, the model can instead be found in its advanced module location in /fuel/modules/news/models (in this example) eg:

   1:  $config['modules']['news'] = array(
   2:          'model_location' => 'news', // Looks for model in /modules/
   3:          ...
   4:          ); 

So now, the admin navigation link for Modules/News points to the ordinary CRUD pages, just as if the module were just a “simple” one.

You may already have an array in MY_fuel.php for the same advanced module to appear as a navigable module in its own right too eg:

   1:  $config['modules_allowed'] = array(
   2:      'user_guide',
   3:      'news'
   4:  );

Well, simply remove that module name. Be aware that doing so will remove advanced module functionality (eg prevent config & constants being loaded) from the admin, but if you have no use for these, it's no loss. Then you will have a simple module admin CRUD for an advanced module. The navigation should look something like:

module_nav_01




Note that you can reverse this navigation arrangement if you want. That is, you can retain the advanced module entry in the admin, and not have the simple module navigation, but still make use of the simple modules controller CRUD.

To do this, hide the simple module by adding this key to the module array in MY_fuel_modules.php:

   1:  $config['modules']['news'] = array(
   2:          'hidden' => TRUE,
   3:          ...
   4:          ); 

and add the module name back to the “modules_allowed” array in MY_fuel.php. This will automatically create an admin link as per the {module}.php file array in the advanced modules /config folder eg:

   1:  $config['nav']['news'] = array(
   2:          'news' => 'news'
   3:  );

which will point to url fuel/news, thus being an identical route to the default CRUD pages. The navigation will look like:

module_nav_02

However, since that link is pretty much all that’s needed, a navigation entry of News/news seems redundant. But if you do need the advanced module for custom dashboards as well as a link to the default CRUD system, this is the way to go.

I used v1.0 beta of FUEL CMS for this investigation, but I think the same methods hold true for v0.93.

I've written a more in-depth guide to FUEL advanced modules here

Saturday, 4 August 2012

Caching assets in FUEL CMS

FUEL CMS has several enhancements in terms of classes over plain vanilla Codeigniter. One which I have recently been making more use of is the Asset class.

I’ve been using Andy Clarke’s 320 & Up and in the early version of this, there were a number of CSS and JS files, some conditional for IE.

The Asset class in FUEL CMS allows some neat optimisations regarding CSS & JS that are worth stressing. Typically you can use the css() method to do something simple like:

<?php echo css('base'); ?>

and this will create a <link> tag with an href to the (now cached) base.css file. However, if you have a conditional file for IE you can set the method like this:

<?php echo css('legacy-ie', NULL, 
  array('output' => TRUE, 'attrs' => 'media="screen"', 'ie_conditional' => 'lte IE 6')); ?>

and this will surround the tag with IE conditional comments, and add attributes as specified in the 'attrs' array key.

But there’s more! By combining several css file names as arguments to the css() method, the files can be combined by the Asset class, and with the ‘output’ key set to ‘gzip’, the resulting cache file will be gzipped too. Eg:

<?php echo css('base, home', NULL, array('output' => 'gzip')); ?>   

your server environment permitting of course!

Most of the functionality for the css() method is also available to the js() method too. Eg to combine Imgsizer & Selectivizr scripts and gzip them with IE conditional tags around them:

<?php 
echo js('imgsizer, selectivizr', NULL,
  array('output' => 'gzip', 'ie_conditional' => '(lt IE 9) & (!IEMobile) '));
 ?>

results in the following being rendered in a cached file:

<!--[if (lt IE 9) & (!IEMobile) ]>
<script src="/assets/cache/d370bce01d8e837e3253b2d90ac8c4fb_1325372400.php"
 type="text/javascript" charset="utf-8"></script>
<![endif]—>

If you want to, the ‘output’ array key value can simply be set to TRUE and this will not only gzip but will also clear out whitespace in the source files too. Consult the Asset class guide for more information.
My top artists