Creating a record archive

From Achievo/ATK Wiki

Jump to: navigation, search

ATK Howto: Creating a record archive

Complexity: Advanced
Author: Ivo Jansch <ivo@achievo.org>

List of other Howto's

Contents

Intro

When you have a lot of records in a table, you will sometimes want to split the records into active records and archived records, so older records aren't in the way of your day to day work. For example, if you have projects, you may want to move completed projects to an archive. Since 'news' is an ideal example for this howto, we'll create a small application to manage newsitems (for the impatient, the complete code for this howto is listed at the end.)

Getting started

Let's start by defining a table to store the news articles. We'll keep it really basic for the sake of this howto. Basically, our news item only has a title and some text.

  CREATE TABLE article
  (
    id INT(10) NOT NULL,
    title VARCHAR(100) NOT NULL,
    content TEXT NOT NULL,
    PRIMARY KEY (id)
  );

In a normal situation I would use an atkMetaNode to make an ATK application around this, but the examples will be clearer if we use a regular atkNode. Here is a simple node that works with our article table:

  useattrib("atktextattribute");
  
  class article extends atkNode
  {
    function article()
    {
      $this->atkNode("article");
      
      $this->add(new atkAttribute("id", AF_AUTOKEY));
      $this->add(new atkAttribute("title", AF_OBLIGATORY|AF_SEARCHABLE));
      $this->add(new atkTextAttribute("content", AF_OBLIGATORY|AF_HIDE_LIST));
  
      $this->setTable("article");
    }
  }

I won't explain how to place this in a menu and so on, the atkdemo and basis application demonstrate this. If you are reading this howto because you want to archive records, you already know how the basics work.

Making records archivable

Depending on the application, archiving can work in several ways. The most common ones are archival by a date field (put a record in the archive automatically when a certain date has been reached), and manual archival by setting some field to a certain value (archived=1, display=0, etc.).

I'll cover both in this howto. Our news items will be archived if a date has been reached, or when a display field has been set to '0'. If you only need one of these methods, you'll find it easy to strip out the other from the examples.

We want to archive based on a date, and based on a status field, so let's add both fields to the database:

  ALTER TABLE article ADD 
  (
    visible INT(1),
    enddate DATE
  );

And in our node:

  useattrib("atkboolattribute");
  useattrib("atkdateattribute");
  
  ...
  
  $this->add(new atkBoolAttribute("visible", AF_HIDE_LIST));
  $this->add(new atkDateAttribute("enddate"));

Now, if we would not use the AF_HIDE_LIST but AF_SEARCHABLE instead, we would already have a very basic archiving feature. We can edit the record and set visible to true and false, and in the recordlist, we can search on either value. But by default it would still display all fields, so we want a more visual distinction between the archive and the active records.

Hiding archived records

Let's start by making sure that our regular admin page contains only active records from now on. Since we are modifying the admin screen, we override the admin action, by adding this method to the node:

  function action_admin(&$handler)
  {
    $this->addFilter("(visible=1 AND enddate>=NOW())");
   
    return $handler->action_admin();
  }

What this basically does is tell ATK to put a filter on all records before displaying the admin page. We call the original admin handler because other than the filter, for now we don't want to change anything.

If you now browse your application, and put some records in archived mode, you will notice that only not-archived records are displayed.

Accessing the archive

Creating an archive view

To access archived records, we basically want the same admin screen, but this time for archived records only.

We could do this by passing a parameter to our admin screen, which we use to determine which records to show. For example, we could access it with ?view=archive or with ?view=active. Let's add a small utility method to our node that retrieves this variable from the url:

  function getView()
  {
    if (isset($_GET["view"]) && $_GET["view"] == "archive")
    {
      return "archive";
    }
    return "active";
  }

This method returns "archive" if someone passes ?view=archive in the url, and "active" in all other cases. This makes sure that if we pass nothing at all (the default), we will only show active records.

Using the view to filter records

Now we need to modify our action_admin method so it takes this view into account:

  function action_admin(&$handler)
  {
    if ($this->getView()=="archive")
    {
      $this->addFilter("(visible=0 OR enddate<NOW())");
    }
    else
    {
      $this->addFilter("(visible=1 AND enddate>=NOW())");
    }
    return $handler->action_admin();
  }

When either visible = 0, or the article has expired, it is listed in the archive.

The above code effectively displays either active or archived records in the recordlist, based on which view is passed in the url.

Adding links that change the view

Since our users do not pass variables in the url directly, we have to give them some links in the screen to switch views. I personally think such links should be placed below the recordlist, so they are not intrusive but still easily accesible.

To put a link below the recordlist, we can implement an adminFooter method:

  function adminFooter()
  {
    if ($this->getView()=="archive")
    {
      // We are looking at the archive. Display a link to view active records.
      return href("?view=active", "View active records");
    }
    else
    {
      // We are looking at active records. Display a link to view the archive.
      return href("?view=archive", "View the archive");
    }
  }

Note how we only pass our view variables. the href() method will take care of the rest.

When you now use the application, you'll see that you can actively switch between active and archive view, and you can archive records. There is one annoying bug though. If you are looking at the archive and edit a record, you'll come back into active view when you return from the edit screen.

This is quite logical; we've made the switch based on a variable in the url, and the edit page, upon returning to the admin page, does not know how to pass this variable. ATK's session manager comes to the rescue.

Using the atkSessionManager to remember our view

We could add logic to the edit page that passes the ?view along. But that would be evil. We would have to modify every (custom) action that returns to the admin page. Luckily, we won't have to.

It's the admin page that should remember its state. It should remember whether it was in archive view or active view.

The key to enabling this is in the ?view= variable we introduced. ATK has a way of making a page remember this variable, even if the user leaves the page and returns. We call these variables a 'pagevar' in atk. Pagevars have 2 important features:

  • When passed in the url, they are set to the value that is passed. In other words, ?view=archive in the url will set the pagevar "view" to "archive"
  • When NOT passed in the url, it takes the last value it had from the session.

This means that we can make our ?view= variable behave in such a way that we can flip the view using the url like we did before, but if we don't pass ?view=, we'll take it from the session. In other words, if a user edits a record and returns, 'view' will automatically have the value it had before.

The code to accomplish this is easy. We accessed the value of 'view' through the getView() method we created. We only need to rewrite that method slightly, into:

  function getView()
  {
    $sm = &atkGetSessionManager();
  
    if ($sm->pageVar("view")=="archive") 
    {
      return "archive";
    }
    return "active";
  }

This change makes the getView method return "archive" if either ?view=archive was passed in the url, or if the page has remembered that view was archive when it was last viewed. In other cases it just returns "active".

Conclusion

In this howto I have tried to demonstrate how using filters and the sessionmanager, you can switch between views. The complete code for this howto is listed below.

Complete code

SQL

  CREATE TABLE article
  (
    id INT(10) NOT NULL,
    title VARCHAR(100) NOT NULL,
    content TEXT NOT NULL,
    visible INT(1),
    enddate DATE,
    PRIMARY KEY (id)
  );

Node

  useattrib("atktextattribute");
  useattrib("atkboolattribute");
  useattrib("atkdateattribute");
  
  class article extends atkNode
  {
    function article()
    {
      $this->atkNode("article");
      
      $this->add(new atkAttribute("id", AF_AUTOKEY));
      $this->add(new atkAttribute("title", AF_OBLIGATORY|AF_SEARCHABLE));
      $this->add(new atkTextAttribute("content", AF_OBLIGATORY|AF_HIDE_LIST));
      $this->add(new atkBoolAttribute("visible", AF_HIDE_LIST));
      $this->add(new atkDateAttribute("enddate"));
  
      $this->setTable("article");
    }
  
    function action_admin(&$handler)
    {
      if ($this->getView()=="archive")
      {
        $this->addFilter("(visible=0 OR enddate<NOW())");
      }
      else
      {
        $this->addFilter("(visible=1 AND enddate>=NOW())");
      }
      return $handler->action_admin();
    }
  
    function adminFooter()
    {
      if ($this->getView()=="archive")
      {
        // We are looking at the archive. Display a link to view active records.
        return href("?view=active", "View active records");
      }
      else
      {
        // We are looking at active records. Display a link to view the archive.
        return href("?view=archive", "View the archive");
      }
    }
  
    function getView()
    {
      $sm = &atkGetSessionManager();
  
      if ($sm->pageVar("view")=="archive") 
      {
        return "archive";
      }
      return "active";
    }
  }
Personal tools
Navigation