Custom (record) actions

From Achievo/ATK Wiki

Jump to: navigation, search

ATK Howto: Custom (record) actions

Complexity: Advanced
Author: Boy Baukema

List of other Howto's

Contents

Defining your own action

Defining your own action for your node is quite easy in ATK, you can do so simply by implementing a function called action_actionname.
Example where we also output our own 'Hello World' page, just like a normal ATK action page:

  function action_showhelloworld()
  {
    $output = $this->getUI()->renderBox(array("title"=>"My Hello World Page",
                                              "content"=>"Hello World!"));
  
    $this->addStyle("style.css");
    $this->getPage()->addContent($this->renderActionPage("showhelloworld", $output));
  }

Don't forget to also register the node in your module.inc so you can assign rights and let 'normal' users (I'm not saying your users are normal, but you know what I mean :)) use your action as well, instead of only the administrator user.
In our example this would be:

  function getNodes()
  {
    registerNode("worlds.world", array("admin", "add", "edit", "delete","showhelloworld"));
  }

Worlds being the module name and world the node name.

Your own record action

Now say we wanna go a bit further and we want to perform our own action on a record. Say we have a node with data on worlds, and we wanna make a fun action that we could click that would say "Hello $worldname!", so for earth it would be "Hello earth!". We'd have to define it as a recordaction:

  function recordActions($record, &$actions, &$mraactions)
  {
    $actions["showhelloworld"]= session_url(dispatch_url($this->atknodetype(),
                                                         "showhelloworld",
                                                         array("worldname"=>$record["worldname"])));
  }

And change:

  $output = $ui->renderBox(array("title"=>'My Hello World Page',
                                 "content"=>"Hello World!"));
<?php>
 
to:
 
<php>
  $output = $ui->renderBox(array("title"=>'My Hello World Page',
                                 "content"=>"Hello {$this->m_postvars['worldname']}!"));

Now we can say hello to all the worlds in our node!

Your own record action icon

As a finishing touch we have an icon of a hand (hand.gif) we want to use for this action in the node.
To do this, we need to rename the icon to the actionname, making it showhelloworld.gif.
Now we need somewhere to put this icon where ATK will find it.
In the appropriate module directory (modules/worlds/) create a directory structure like the following:
modules/worlds/themes/default/icons/recordlist/

Now we need to put the icon (showhelloworld.gif) in the directory structure we just created (modules/worlds/themes/default/icons/recordlist/).
And finally, all we need to do is throw away the theme cache (as user with appropriate rights or root user in the application root 'rm -vf atktmp/themes/*') and voila!

Overriding existing actions

To override existing actions (for example, the edit action), do:

function recordActions($record, &$actions, &$mraactions) {
 
  $actions["edit"] = dispatch_url(
    $this->atknodetype(),
    "edit",
    array(
      "foo" => "bar",
      "atkselector" => "mytable.id = {$record["id"]}"
    )
  );
 
}
 
function action_edit($handler) {
  $foo = $this->m_postvars['foo']; // "bar"
  $handler->action_edit();
}

Note that you *must* define the atkselector key--if you don't, the edit link will (silently) produce an edit page of the first row in the associated table, which probably isn't what you want. You also need to explicitly set the selector: setting it to "[pk]" doesn't work, because it gets encoded.

Returning to the record

If you have an action that updates your record, after doing the update you can go back to the edit screen by using this code

$this->redirect($record);

Your own multi record action

Here is a simple example of creating a mra-action.

First, add the NF_MRA flag to your node:

class foo extends atkMetaNode {
 
  protected $flags = array(NF_MRA); 
 
  // ...
 
}

Adding the flag will change the "admin" (list) display so that a checkbox is displayed next to each row, and add some text ("Select all", etc.) beneath the table.

Add the action to $mraactions:

 function recordActions($record, &$actions, &$mraactions)
 {
   $mraactions['my_mra_action'] = "my_mra_action";
 }

Implement the action_XXX function:

 function action_my_mra_action()
 {
   atk_var_dump($this->m_postvars["atkselector"], 'selected records'); 
 }

atkselector will contain the primary keys of all records that are selected. You can use this to retrieve the rows and perform the actual action on it.

Add the action via the registerNode function in module.inc:

function getNodes() {
    registerNode('projects.foo', array('my_mra_action'));
}

(After calling registerNode(), go into the web interface and enable this action for the appropriate user.)

Finally, if your action does not appear in the drop-down, ensure that the allow function isn't inhibiting the display of your action. (Hardcode return true to make sure.) For multi-record actions, the allow() function is not passed a populated $record--use recordAction() to determine which actions can be performed on a per-record basis.

If you want to, for example, update the status of multiple records, your code would look something like this:

 function action_my_mra_validate_action()
 {
   $this->updateStatus('valid', $this->m_postvars["atkselector"]);
    
   return $this->redirect();
 }
  
 function updateStatus($status, $primkeys)
 {
   foreach ($primkeys as $pk)
   {
     $record = array("atkprimkey"=>$pk,
                     "status"=>$status);
     
     $this->updateDb($record);
   }      
 }


Customizing MRA actions

Sometimes simply performing an action is not enough. For instance, you want the user to be able to select multiple records to set an attribute or perform an action with a parmeter on.


Like setting a population status (uninhabitable, unpopulated, populated) for several worlds at once.

You can do this by creating a getCustomMraHtml method in your node. The recordlist will then use the output from this method and place it directly in between the list of actions and the action button. Example:

  public function recordActions($record, $actions, &$mraactions)
  {
    $mraactions['update_population'] = "update_population";
  }
 
  public function getCustomMraHtml()
  {
     $list = $this->getAttribute('population_status')->edit();
     return $list;
  }
 
  public function action_update_population()
  {
     $status = $this->getAttribute('population_status')->fetchValue($this->m_postvars);
     foreach ($this->m_postvars['atkselector'] as $pk)
     {
       $record = array("atkprimkey"=>$pk,
                       "population_status"=>$status);
     
       $this->updateDb($record);
     }
  }


NOTE: If you are playing around with NF_MRA you will inevitably notice NF_MRPA (Multi Record Priority Actions) which is a feature that SHOULD allow you to select the order in which records are processed by a multi record action. However this feature should be considered deprecated as no real world usage cases has ever been found beyond the initial one.

A custom form button

Now say you want to have an extra button in your node 'Save and say hello!' besides 'Save and close' and 'Save'.
This is useful in situations where you want to perform a special action on a record besides saving it.
You could add the following method to your node:

  function getFormButtons($mode, $record)
  {
    // Get the normal buttons
    $buttons = parent::getFormButtons($mode,$record);
    if ($mode==='edit')
    {
      // If the user is editting, add a 'saveandsayhello' button 
      // (you should also add a translation to your language file for saveandsayhello)
      array_unshift($buttons, '<input type="submit" class="btn_save" name="saveandsayhello" value="'.
                                   atktext("saveandsayhello", "worlds").'">');
    }
    return $buttons;
  }
  function postUpdate($record)
  {
    if ($this->m_postvars['saveandsayhello'])
    {
      $this->redirect(dispatch_url($this->atknodetype(),'showhelloworld'));
    }
  }
  function action_update(&$handler)
  {
    // Trick the update handler into thinking that when we clicked
    // the saveandsayhello button, we also clicked the save and close button.
    // You could also use atknoclose if you just want to save
    if ($handler->m_postvars['saveandsayhello']) $handler->m_postvars['atksaveandclose']=1;
    return $handler->action_update();
  }

For more documentation please see:
http://www.atk-framework.com/docs/atk/trunk/atk/atkNode.html#getFormButtons

Removing Actions

Via recordActions()

As shown above, a common way to add actions is via the $actions array that is provided as an argument to, for example, the recordActions(). Actions can similarly be removed, by deleting values from this array. For example, all actions can be removed from a node with the following recordActions() method:

function recordActions($record, &$actions, &$mraactions) {
    $actions = array();
}

Individual actions can also be removed by deleting the appropriate key:

public function recordActions($record, &$actions, &$mraactions) {
    unset($actions['delete']);
}

Via allow()

atkDataGrid Actions

Adding actions

e.g.

$grid->setDefaultActions(array(
  "view" => dispatch_url("projects.quote", "view", array("atkselector" => "[pk]"))
));

These actions apply to all rows in the table; note that the "[pk]", is essentially interpolated. You may find an atk_var_dump() of $grid->getDefaultActions()useful to see what actions are currently configured.

Removing actions

Actions can be removed from an atkDataGrid by using the setDefaultActions() method:

$grid->setDefaultActions(array()); // remove all actions
$grid->setDefaultActions(array_diff_key($grid->getDefaultActions(),
                array("delete" => true))); // remove just the "delete" action
Personal tools
Navigation