Callbacks

 

A callback is used to execute code in package A the is provided by package B without package A knowing about package B (and vice versa). It consists of three different pieces in two different packages. 

  • The callback definition is code that defines the name of the callback and the parameters. It resides in package A either in "install-procs.tcl" (old way) or "${package_key}-callback-procs.tcl" (new way). This example comes from /contacts/tcl/contacts-install-procs.tcl (old way). 

    ad_proc -public -callback contact::organization_new { 

    {-package_id:required} 

    {-contact_id:required} 

    {-name:required} 

    } { 

  • The callback hook is the place where all callback implementations for the defined callback are executed. It's location depends on the application you are dealing with, but as a general rule of thumb all "-new" callback hooks can be found right after the object is created. This example comes from /contacts/www/contact-add.tcl 

    # Initialize Party Entry for organization 

    set party_id [organization::new -organization_id $party_id -name $name] 

    callback contact::organization_new -package_id $package_id -contact_id $party_id -name $party_id 

  • The callback implementation is code that is executed when the hook is called. It is located in package B as it relies on procedures of package B to work. This example is hosted in /project-manager/tcl/project-manager-callback-procs.tcl 

    ad_proc -public -callback contact::organization_new -impl project_manager { 

    {-package_id:required} 

    {-contact_id:required} 

    {-name:required} 

    } { 

    create a new project for new organization 

    } { 

    upvar create_project_p create_project_p 

    if {[exists_and_not_null create_project_p] 

    && $create_project_p == "t"} { 

    set creation_user [ad_conn user_id] 

    set creation_ip [ad_conn peeraddr] 

    # Check if we have a .LRN Club linked in. If yes,only create 

    # the project in the .LRN Club, otherwise in the default project 

    # manager instances 

    set dotlrn_club_id [application_data_link::get_linked -from_object_id $contact_id -to_object_type "dotlrn_club"] 

    set pm_package_id [dotlrn_community::get_package_id_from_package_key -package_key "project-manager" -community_id $dotlrn_club_id] 

    if {[empty_string_p $package_id]} { 

    set pm_package_id [lindex [application_link::get_linked \ 

    -from_package_id $package_id \ 

    -to_package_key "project-manager"] 0] 

    set project_id [pm::project::new \ 

    -project_name $name \ 

    -status_id 1 \ 

    -organization_id $contact_id \ 

    -creation_user $creation_user \ 

    -creation_ip $creation_ip \ 

    -package_id $pm_package_id] 

    set project_item_id [pm::project::get_project_item_id \ 

    -project_id $project_id] 

    application_data_link::new -this_object_id $contact_id -target_object_id $project_item_id 



    }

One of the beauties of callbacks is that you do not create a dependency between package A and package B if you follow coding guidelines, despite the fact that the project manager example above suggests otherwise (to cite an OCT member: "It feels wrong"). The feeling comes from the fact that the callback implementation has to use the name of the callback definition (contacts::organization_new), but resides in project-manager. This makes people assume that we suddenly have a dependency on contacts, which we do not (you can install project-manager without contacts and nothing will go wrong). 

 There is only one risk: If you change the callback definition parameters and either reduce the number of parameters or change the name of a parameter, without creating a new callback (as you should do, otherwise feel free to feel dead), then your hook will fail (which you will fix immediately by changing the parameters there) and then the code for all implementations. Therefore keep in mind that "stable interfaces" are your friend!