Vanilla 1 Documentation Archive

 
 
vanilla:development:delegation
Table of Contents

Delegation

Click here to go back to the previous section: Creating Custom Controls

Please note that this documentation relates to Vanilla 1

Dictionary.com defines delegate as “1. to entrust or transfer (as power, authority, or responsibility) to another”. That is a perfect description of delegation in Vanilla. The concept is quite simple. I wanted to give extension authors the ability to access core objects of Vanilla and be able to manipulate them at specific times. I wanted the core Vanilla objects to be able to transfer their power, authority, and responsibilities to another place.

Delegation in Vanilla was achieved by creating a base Delegation class from which any other class that requires delegation must be derived. You can examine this class in library/Framework/Framework.Class.Delegation.php. Let’s examine the Delegation class:

The Delegation Class

Property Description
Context A reference to the Context object.
Name The name of the control derived from this delegation class.
Delegates An array of delegates & their associated functions.
DelegateParameters An associative array that may or may not be used to include additional objects in a control so they can be referenced or manipulated from within a delegation function.

Method Description
AddToDelegate Adds a function to the specified delegate. This method should only ever called by the Context object’s AddToDelegate method.
CallDelegate Executes all functions associated with the specified delegate for this control.
Delegation The constructor for this class where all properties are initialized and GetDelegatesFromContext method is called.
GetDelegatesFromContext All of the core objects extended from the Delegation class in Vanilla are loaded after extensions have been included. So, when you attach a function to an object’s delegate, you do it through the Context object, and the context object stores those references. Then, as objects are instantiated further down the page, this method is called and the appropriate delegation references are added to the control.

Controls that extend the Delegation Class

If you’ve read about Controls then you are familiar that all controls in Vanilla are extended from two basic control types: Control and PostBackControl. PostBackControl is actually extended from Control, and Control is extended from the Delegation class. This means that all of the properties and methods of the Delegation class are present in any control you use in Vanilla. That is why you will see $this→CallDelegate(”whatever”) littered throughout controls and classes all through the Vanilla codebase. These CallDelegate methods are going to execute any functions that were written and attached in extensions way before these classes were ever even instantiated in the page. So, if you are writing a control that is extended from Control or PostBackControl, and you think there is a spot where a delegate might want to be called from within your control, just add a $this→CallDelegate(”SomeArbitraryString”) and people will be able to attach delegates to your control.

There are a few non-Control objects that extend Delegation within Vanilla. These are the Comment (library/Vanilla/Vanilla.Class.Comment.php), DiscussionManager (library/Vanilla/Vanilla.Class.DiscussionManager.php), and UserManager (library/People/People.Class.UserManager.php) classes. The reason these use delegation and other classes don’t isn’t because the other classes don’t need delegation, but simply because I thought it might be useful to add it to these classes and delegation in Vanilla was concieved of long after the first beta versions of Vanilla were released.

Delegate Calls in Theme files

Theme files have their delegate calls too, but the corresponding delegation class is the class that includes them. That’s why you’ll see

// Note: This file is included from the library/Vanilla/Vanilla.Control...

at the top of theme files.

In the example in the next section, the delegate call is done in themes/people_apply_form_nopostback.php, the relevant class is library/People/People.Control.ApplyForm.php.

How to Attach to a Delegate

Enough theory, let’s get into the meat of delegation by looking at an example. The ExtendedApplicationForm extension can be found in extensions/ExtendedApplicationForm/default.php. The original beta version of Vanilla had a bunch of required fields on the application form. A lot of users complained that it was too many fields to fill out and made it a hinderance for new users to register. I removed all of the extra fields from the form, but I still wanted the option of having these fields in an application form for my own purposes.

This posed an interesting conundrum: how do I (a) get the inputs into the form, (b) make sure that they are validated on postback, and © saved to the database? The answer to all three questions, of course, was delegation.

The membership application form for Vanilla is located in library/People/People.Control.ApplyForm.php. If you take a look at this file you will see that there is a “PreCreateUser” delegate. If you look at the template file for this control located in themes/people_apply_form_nopostback.php, you will find a “PreInputsRender” delegate. All I needed to do was (a) attach to the PreInputsRender delegate and display my form inputs, and (b) attach to the PreCreateUser delegate to validate my form postback values and add them to the object that is being saved to the database. Check out the code:

if ($Context->SelfUrl == 'people.php' && in_array(ForceIncomingString('PostBackAction', ''), array('ApplyForm', 'Apply'))) {
   
   // Add the Real name inputs to the application form
   function ApplicationForm_AddRealNameInputs($ApplyForm) {
      echo '<dt>'.$ApplyForm->Context->GetDefinition('FirstName').'</dt>
      <dd><input type="text" name="FirstName" value="'.$ApplyForm->Applicant->FirstName.'" class="Input" maxlength="40" /></dd>
      <dt>'.$ApplyForm->Context->GetDefinition('LastName').'</dt>
      <dd><input type="text" name="LastName" value="'.$ApplyForm->Applicant->LastName.'" class="Input" maxlength="40" /></dd>';
   }
   
   $Context->AddToDelegate('ApplyForm',
      'PreInputsRender',
      'ApplicationForm_AddRealNameInputs');
      
      
   // Add the requirements to the membership application processing
   function ApplicationForm_AddRequirements(&$ApplyForm) {
      $SafeUser = $ApplyForm->Applicant;
      $SafeUser->FormatPropertiesForDatabaseInput();
		Validate($ApplyForm->Context->GetDefinition('FirstNameLower'), 1, $SafeUser->FirstName, 50, "", $ApplyForm->Context);
		Validate($ApplyForm->Context->GetDefinition('LastNameLower'), 1, $SafeUser->LastName, 50, "", $ApplyForm->Context);
      // Make sure that they actually read the terms of service
		if (!$SafeUser->ReadTerms) $ApplyForm->Context->WarningCollector->Add($ApplyForm->Context->GetDefinition("ErrReadTOS"));
   }
 
   $Context->AddToDelegate('ApplyForm',
      'PreCreateUser',
      'ApplicationForm_AddRequirements');
      
   // Add the requirement to the identity form on the account page as well
   function IdentityForm_AddRequirements(&$IdentityForm) {
      $SafeUser = $IdentityForm->User;
      $SafeUser->FormatPropertiesForDatabaseInput();
		Validate($IdentityForm->Context->GetDefinition('FirstNameLower'), 1, $SafeUser->FirstName, 50, '', $IdentityForm->Context);
		Validate($IdentityForm->Context->GetDefinition('LastNameLower'), 1, $SafeUser->LastName, 50, '', $IdentityForm->Context);
   }
 
   $Context->AddToDelegate('IdentityForm',
      'PreSaveIdentity',
      'IdentityForm_AddRequirements');   
   
}

As you can see, I gave my delegation functions very unique names: ApplicationForm_AddRealNameInputs, ApplicationForm_AddRequirements, and IdentityForm_AddRequirements (Aside: the third function is used on the account tab’s personal information form and is used to ensure that the first and last name inputs are required). Naming your delegation functions in this way is VERY highly recommended so that as new functions are attached to delegates, there aren’t any name clashes. In my examples above, the part before the underscore is the name of the control that contains the delegate I’m attaching to (ApplyForm for the first two and IdentityForm for the last one), and the part after the underscore is a unique name to describe the purpose of my function (AddRealNameInputs, AddRequirements, and AddRequirements respectively).

Any function called through delegation has one parameter passed in, and that parameter is the control from which the delegate is called, in the case of the first two delegates above, it is the ApplyForm. Now I have access to all of the properties and methods of the ApplyForm control from within my custom function.

Delegate Parameters

One problem you might run into as you work with delegates is that although you have access to all of the properties and methods of the calling control, the function where your delegate was called from may have had custom variables local to that function which are not properties of the object, but you need to have access to them. You can access these values by using the delegation class’ DelegateParameters collection. The DelegateParameters must, however, be explicitly defined before the CallDelegate method is called for the delegate you are attempting to attach to.

The DiscussionManager class (library/Vanilla/Vanilla.Class.DiscussionManager.php), for example, uses DelegateParameters to make SqlBuilder objects accessable from delegation functions like so:

$this->DelegateParameters['SqlBuilder'] = &$s;
$this->CallDelegate('PostGetDiscussionBuilder');

Then, from within your function, you can access the SqlBuilder object and make changes to it like so:

$SqlBuilder = &$DiscussionManager->DelegateParameters['SqlBuilder'];

Because I’ve specified the ampersand, the SqlBuilder is passed by reference and any changes made to it in the function will be reflected within the DiscussionManager object as well.

Getting More Delegates in Vanilla

It is entirely possible that, once you get accustomed to using delegation in Vanilla, you will encounter a time when you need a delegate in a control or a class, but that delegate doesn’t exist yet. You may be tempted to add your own delegates to the core Vanilla libraries, but DO NOT do this. If this ever happens to you, all you need to do is become a member of the Lussumo Community (if you aren’t already) and request your new delegate. We will be adding new delegates all the time, and these minor revisions of added delegates will be released as frequently as necessary so that everyone using Vanilla forums can enjoy newer extensions with added functionality.

Click here to read the next section on developing new extensions: The Object Factory

 
 
 
 
vanilla/development/delegation.txt · Last modified: 2009/03/28 14:19
Vanilla 1 Documentation archive (Wiki by DokuWiki)