GitHub

Adding Ajax Panels to the CakePHP Status Plugin

Posted by Matt on Thu, Aug 13 2009

Previously On...

Last time we made a simple panel that showed the last 10 users that signed up for you web app. It was a good example for using the requestAction approach (or as AD7six pointed out you can skip the requestAction part and go right to the model).

Let's Get Ajaxy Up In This Mofo

This time we'll do a panel that shows the number of sign ups based on a time frame. We'll add a drop down that let's you select day, week, month or year and it'll show a breakdown of new users for that period. The updates will use ajax.

The Element

Just like last time we're going to create a new element: /app/views/elements/panels/signups.ctp. This time, however, the element will contain a form:

<?php echo $form->create('User', array('id' => 'SignupsForm')); ?>
<h1>
  <?php __('Signups'); ?>
  <?php echo $form->input('signup_span', array('type' => 'select', 'div' => false, 'label' => false,
                                               'options' => array('day' => 'day',
                                                                  'week' => 'week',
                                                                  'month' => 'month',
                                                                  'year' => 'year')));
  ?>
</h1>
<?php echo $form->end(); ?>

<div id="signup-table">
  <p><?php echo $html->image('/status/img/ajax-loader.gif') ?></p>
</div>

In addition to the form, there is a div (signup-table) that will be used to hold the result of the Ajax call. For now we'll just stick a spinny icon in there.

The JavaScript

<script type="text/javascript">
  $(function(){
    $("#UserSignupSpan").change(function() {
      $("#signup-table").html("<p><img src=\"/status/img/ajax-loader.gif\" \></p>");
      $.get("/users/signups/" + $(this).val(), function(data) {
        $("#signup-table").html(data);
      });
    }).change();
  });
</script>

Pretty standard jQuery. We catch the change in the span select, set the holder div to the spinny icon and make the request for the updated data.

The Controller

function signups($span=1) {
  if (!Configure::read('Status.allow')) {
    die;
  }

  switch ($span) {
    default:
    case 'day':
      $field = 'SUBSTR(created, 1, 13)';
      $limit = 24;
      break;
    case 'week':
      $field = 'SUBSTR(created, 1, 10)';
      $limit = 7;
      break;
    case 'month':
      $field = 'SUBSTR(created, 1, 7)';
      $limit = 12;
      break;
    case 'year':
      $field = 'SUBSTR(created, 1, 4)';
      $limit = 5;
      break;
  }

  $data = $this->User->find('all', array('fields' => array($field . ' AS span', 'count(*) as cnt'),
                                         'group' => 'span',
                                         'order' => 'span DESC',
                                         'limit' => $limit));

  $this->set('data', $data);
}

In practice I would move most of this code to a custom find type, but for the sake of this example I'll put it all in the controller. An important note: Unlike the example in the previous post, this action has a view associated with it. That means you can access it directly without going through the status panel. To protect against that we add the check at the top to make sure the user is allowed to access the status panel.

Include The New Panel

Remember to add the panel to the list of panels in bootstrap.php:

Configure::write('Status.panels', array('panels/signups'));

What It Looks Like

signups_panel

Posted in GitHub | 2 Comments

Adding Custom Panels to the CakePHP Status Plugin

Posted by Matt on Thu, Aug 06 2009

Intro

A few weeks ago I released a status plugin for CakePHP. It is basically a single page where you can get a quick snapshot of your app. The plugin ships with a few random panels, but it's easy enough to add your own. This post will tell you how.

Make sure to get the latest version from GitHub. Also note that the way to include the default panels has changed. You now have to include the "Status." prefix.

requestAction vs Ajax

There are two different ways to add a panel - using requestAction or Ajax. The general rule is if the panel is pretty fast to load and content is static use requestAction. If the panel takes some time or you want to be able to interact with it then use Ajax. For example, the logs panel uses requestAction since it's pretty quick to grab the log entries and you really don't need to interact with them. The Google Analytics panel, on the other hand, takes a few seconds to query the API and has options to change the timeframe, so it uses Ajax.

Packaging Your Panel

Panels can be packaged as plugins or just files in your app. For this example we'll just put the files as part of the app. Making your panel as a plugin is pretty much the same, you just need to put all the files in the plugin's directory and include the plugin name as a prefix when setting which panels to use. Check out the click plugin for an example. I'm just going to cover the requestAction approach today. Look for another post of using Ajax soon.

The requestAction Way

The Element

Let's build a panel that shows the last 10 users that signed up for your app. The first thing you need is an element. In /app/views/elements/panels (note the extra directory level - this is optional, but helps to keep things clean) create a file called 'latest_users.ctp'. In that file put:

<?php
  $users = $this->requestAction(array('controller' => 'users', 'action' => 'latest'));
?>

The Controller

In the users controller we now need to add an action called latest that will get the user data and pass it back to the element.

function latest() {
  return $this->User->find('all', array('order' => 'created desc', 'limit' => 10));
}

Since there is no view associated with this action anyone trying to access it directly with just get an error. If you wanted to extra sure that no one unauthorized could exploit this action you could add this to the beginning:

if (!Configure::read('Status.allow')) {
  die;
}

Back To The Element

Now you have a list of $users so it's just a matter of displaying them. Here's the complete element:

<?php
  $users = $this->requestAction(array('controller' => 'users', 'action' => 'latest'));
?>

<h1><?php __('Latest Users') ?></h1>
<?php if($users) { ?>
  <table>
    <?php foreach($users as $user) { ?>
      <tr>
        <td><?php echo $user['User']['email'] ?></td>
        <td><?php echo $time->timeAgoInWords($user['User']['created']) ?></td>
      </tr>
    <?php } ?>
  </table>
<?php } else { ?>
  <p>Your app has no users...loser!</p>
<?php } ?>

Including Your Panel

Now you just have to include your panel on the status dashboard. In /app/bootstrap.php add the line:

Configure::write('Status.panels', array('panels/latest_users'));

Here's What It Looks Like

status-latest-users

Posted in GitHub | 2 Comments

CakePHP Status Dashboard - Everything You Need To Know About Your App On One Page

Posted by Matt on Mon, Jul 06 2009

Beta

First off this thing is still pretty rough. I don't have any unit tests at the moment and I'm likely to make huge, wholesale changes on a whim.

Download

Get it at GitHub

What The Heck Is This Thing?

You build a cool CakePHP app and now you have to maintain it. You go to Google to check your analytics; you SSH to your system to check log files; You log into your web admin to check affiliate clicks... Wouldn't it be cool if you could check all this (and more) from one page? That's what this status plugin does. Check out the screenshot:
CakePHP Status Dashboard

Or visit this live (but admittedly boring) example.

What Are The Different Panels Available?

Right now there are four "core" widgets, some of which have multiple options, so they can produce multiple panels.

Google Analytics

Multiple panels for quick info on your visits, referrers and search engine keywords using the Google Analytics API. Adjustable timeframes to get a recent or long range snapshot.

Shells

Add logging to your shells to capture info when they run - great for shells that run from cron. Clicking on the shell link in the panel pops open a window with detailed information. Requires integrating the log task in your shell.

System

Basic information about the system hardware, including disk space and uptime.

Logs

Show Cake's log files. Can be used on any log file in /app/tmp/logs, including custom ones. Just pass the log file name(s).

Extending For Custom Panels

It's pretty easy to write your own custom panels. Check out the click plugin, which tracks outbound clicks and displays the results via panels in the status dashboard. I'll write full post on how to code your own panels in bit.

Posted in GitHub | 11 Comments

CakePHP Book and Code Updates

Posted by Matt on Fri, Jun 12 2009

The Book

For anyone who missed it, I released a free CakePHP book a month ago. Since then it's been downloaded over 2500 times. I've also received some great feedback and have made a bunch of corrections to the code and grammar. The biggest change is that the table of contents is now links and will jump you right to the section. That's reason enough alone to grab the updated version. The new permanent home page for the book is here.

Interactive Console and DebugKit Panel

Github Link

http://github.com/mcurry/interactive

Updates

I fixed a couple issues that were pointed out in the comments of the announcement post.

One of the suggestions was to have the up arrow cycle previous commands when using this in the console. It's a great idea and actually works magically on ForTheWindows. I checked it out and I didn't see a way to make it happen for all systems though. So, I did the next best thing. Hitting enter on a blank line re-runs the previous commands. Entering 'h' brings up a list of the last 20 commands and you can run one by just entering the number. Command history is also saved across sessions, so you can exit and go back in and still run old commands.

Custom Find

Github Link

http://github.com/mcurry/find

Pagination

I pulled in some of the ideas from Daniel Salazar's post and now the custom find types have built in pagination support. It's kind of scary how easy it is. Here's an example:

$this->paginate = am($this->paginate, $this->Post->find('published', array('paginate' => true)));
$this->set('posts', $this->paginate());

This only works if you are still using one of the default find types to run the actual query, as opposed to Model::query();

Extending Model

I struggled with how to make this plugin easy to install. I didn't want to force people to have to copy and paste a bunch of code into their AppModel. I felt like that would make updates harder and would just make AppModel too messy. So instead I recommend that you have your AppModel extend FindAppModel instead of Model. FindAppModel in turn extends Model. Multiple inheritance would come in handy here.

Ajax Chat

Github Link

http://github.com/mcurry/chat

Updates

A couple small changes here. The helper now handles cake installs that aren't running in webroot automatically, so you don't have to set it in the JavaScript manually. Also the posted chat message now appears instantly in the chat window. Before you'd have to wait for an update for the message to appear.

Asset

Github Link

http://github.com/mcurry/asset

Updates

The order that scripts and codeblocks is now maintained. The previous version always put codeblocks after scripts, but there are times when the codeblock needs to go before a script. The new version keeps everything in the order that it was entered. This means if you do script1, codeblock, script2 that the two scripts won't be merged because it is assumed that the codeblock is dependent on script1 and script2 is dependent on the codeblock.

JS Validate

Github Link

http://github.com/mcurry/js_validate

Updates

I merged a couple changes from javierm's fork. These include a fix for the numeric check, matching Cake's default error HTML and including the fieldName when calling custom rules, which should make doing Ajax validation much easier. I'll have a post showing some examples of this next week.

Posted in GitHub | 5 Comments

Interactive Plugin For CakePHP Console and DebugKit

Posted by Matt on Tue, Jun 09 2009

I teased this on twitter last week, so here's the official release. The interactive plugin is a super easy way to run code against your Cake app. You can use it either through a custom panel in the DebugKit or from the command line as a shell.

Download

Get it at GitHub

Instructions

I'll skip the install instructions, since they're included in the README. It's pretty easy to install. If you're using the latest stable release of the DebugKit you do need to apply a patch for better plugin handling. That fix is already in the trunk, so it's not a dirty hack.

What It Can Do

Let's get right to the good stuff.

Run Simple PHP

Pretty much any simple PHP commands will run:

10 % 4 //returns 2
is_array(49) //returns false

Run SQL

SELECT id FROM users WHERE email = 'test@test.com' //returns the id of the user
UPDATE posts SET published = false; //returns "No results found.", but does the update.

Use Cake Functions

__('Test Message', true) //returns the translated text matching the msg id for your current language.
Configure::write('Config.language', 'es');
__('Test Message', true); //returns the translated text in Spanish.

Use Cake Libs

Security::hash('my_pAssw0rd', null, true) //returns the hashed password
Set::extract('/id', array(array('id' => 3), array('name' => 'test', 'id' => 4))) //array(3, 4)

Use Cake Helpers

$html->link('Posts', array('controller' => 'posts')) //the html code for the link and the link itself
$form->input('Post.title'); //the html code for the field and the field itself

Use Your App Code

Post::find('first') //The first record from your posts table
User::findById(3) //The user record for id 3

Debugging

If you have a command that isn't returning the results you expect, you can turn on debug first:

Configure::write('debug', 2);
User::findByIdd(3) //will show error output

Notes

When using through the DebugKit you can stack commands, just separate them with a ";". However you can't define a variable in one command then use it in another. For example this doesn't work:

$i = 10;
$i ++;
echo $i;

You'll also notice I never explicitly declared any of the models I was using. The plugin does this automatically. You can write your model calls statically or just pretend you have an instance. These are all the same and will work:

Post::find('first');
$Post->find('first');
Post->find('first');
$this->Post->find('first');

The DebugKit = Awesomeness

Like most of you, I read this post from Mark Story on the new JavaScript for the DebugKit. Just reading that post, you don't get a full appreciation for how much work he (and others) have put into it. The JavaScript lib is virtually it's own mini-framework. Everything I needed to create this was already included - Ajax, Event handing, Elements...Really great stuff.

That's About It

Let me know if you hit any commands you want to run that aren't working. I tried to cover most of the basics.

Posted in GitHub | 19 Comments
1 | 2 | 3