Contacts button

Functionality

This plugin implements the standard Contacts button displayed at the top of every Person or Organisation object. Clicking the menu shows entries for adding people and contact notes, as appropriate.

When a menu entry is chosen, display a new editor form with the appropriate fields pre-filled ready for the user to complete and save.

Permissions are respected, so the menu is only shown when the user is able to create the proposed new object.

How it works

The hObjectDisplay hook function is called whenever an object is displayed, with that object as a argument.

The plugin checks to see if it’s one of the types it’s interested in, and whether the user has permission to create objects of the proposed kind, then adds menu entries as appropriate.

The required types and attributes are declared in the requirements.schema file, which makes them available as easy to read names in the code, such as T.Person. Since the plugin only uses the standard schema, it doesn’t need to define the types or attributes.

With the respond declaration in the plugin.json file, and the P.respond() statements, the plugin responds to two URLs which are used as the links in the menu entries.

Each URL has the object reference as a final element in the path. This is decoded, the object loaded and the user permissions are checked before the handler function is called with the object as an argument, through the arguments declaration passed to the respond() function. See Request handling.

In the handler functions, a template object is built, then the std:new_object_editor standard template is used to display an object editor for the user to fill in and save the new object.

plugin.json

{
	"pluginName": "contacts_button",
	"pluginAuthor": "Haplo",
	"pluginVersion": 100,
	"displayName": "Contacts Button",
	"displayDescription": "Add a Contacts button to People and Organisation objects for helping manage contacts.",
	"apiVersion": 4,
	"load": ["js/contacts_button.js"],
	"respond": ["/do/contacts"]
}

requirements.schema

attribute dc:attribute:date as Date
attribute std:attribute:participant as Participant
attribute std:attribute:works-for as WorksFor

type std:type:organisation as Organisation
type std:type:person as Person
type std:type:contact-note as ContactNote

js/contacts_button.js

// Called when an object's page is requested
P.hook("hObjectDisplay", function(response, object) {
    // Build a list of menu items, which depend on the type of the object being displayed.
    // Menu items link to an object editor for a new object which links to the current object.
    // Only add a menu item if the user has permission to create the proposed object.
    var menuItems = [];
    if((object.isKindOf(T.Organisation) || object.isKindOf(T.Person)) &&
            O.currentUser.canCreateObjectOfType(T.ContactNote)) {
        menuItems.push(["/do/contacts/add-note/"+object.ref, "Add Contact note"]);
    }
    if(object.isKindOf(T.Organisation) && O.currentUser.canCreateObjectOfType(T.Person)) {
        menuItems.push(["/do/contacts/add-person/"+object.ref, "Add Person"]);
    }
    if(menuItems.length > 0) {
        // Create a button at the top of the page with label 'Contacts', which displays the menu items when clicked.
        response.buttons["Contacts"] = menuItems;
    }
});

// Declare that the plugin responds to a URL (which must be below a root URL set in plugin.json)
P.respond("GET", "/do/contacts/add-note", [
    // Define the sources of the values of the arguments to the handler function
    {pathElement:0, as:"object"}        // if the user doesn't have permission to read this object, the handler won"t be called
], function(E, object) {
    // Make a blank object to act as a template
    var templateObject = O.object();
    // Set the type of the object, so the editor knows what fields to display
    // The append* functions are automatically generated from the schema created in the system management web interface.
    templateObject.appendType(T.ContactNote);
    // Add a link to the original object in the participant field
    templateObject.append(object, A.Participant);
    // Add today's date to the note
    templateObject.append(new Date(), A.Date);
    // If the user has a representative object, add them as a participant as well
    if(O.currentUser.ref !== null) {
        templateObject.append(O.currentUser.ref, A.Participant);
    }
    // Copy the "works for" fields from the original object, so the organisation is linked from the contact note
    object.every(A.WorksFor, function(value,desc,qualifier) {
        templateObject.append(value, A.Participant);
    });
    // Render a standard template to show the editor for the new object
    E.render({
        // Pass the templateObject to the std:new_object_editor template
        templateObject:templateObject,
        // Properties for the standard page chrome
        pageTitle:'Add note to '+object.title,          // HTML <title> and <h1>
        backLink:object.url(), backLinkText:'Cancel'    // Add a link in the top left back to the original object
    }, "std:new_object_editor");
});

P.respond("GET", "/do/contacts/add-person", [
    {pathElement:0, as:"object"}
], function(E, object) {
    var templateObject = O.object();
    templateObject.appendType(T.Person);
    templateObject.append(object, A.WorksFor);
    E.render({
        templateObject:templateObject,
        pageTitle:"Add person to "+object.title,
        backLink:object.url(), backLinkText:"Cancel"
    }, "std:new_object_editor");
});