Published
Friday, June 11, 2010 - 07:18
Written by
Relationship are a natural way of storing relations between contacts. However, it doesn't work so well if you have several hundreds of related contacts as the realationship tab becomes unreadable quite quickly. One of our client needed to associate each individual in their base to a local branch (we implemented a nice geo lookup based on the postal code to identify the local branch, but that's another story). It means that each local branch has 1000th of individuals. This could happen in other situations, for instance to keep a relationship between a "main teacher" and each pupil or who is the latest volunteer that contacted each person in a GOTV/ Canvassing campaign... We choose to store that relation as a custom field of the type "contact reference". Each individual has a new field "nearest local branch", that is a reference to an organisation (the local branch). It works fine and you have a nice autocomplete field, like the search field in the top left corner in the menu and you can select the branch. However, the list contains all the contacts, not only the branches. To improve that, we replaced the default template (templates/CRM/Contact/Form/Edit/CustomData.tpl) that displays the custom field by a custom one (configure your civicrm and put it in a custom directory, don't modify directly the template provided). In our case, the subset is the list of organisations that belong to a specific group "local branches" (id 71).
  jQuery(document).ready(function($)  
  {  
    var customGroup = "#Your_custom_field_set_id";//depends on the name you gave
    var groupID = 71;//depends on your install

    // the id of the custom fields change between calls, that's always in the form
    // custom_{id of the field}_{a number that changes based on the moon's phase or something}. 
    //In our case, the custom field id is 42, so the id is going to be custom_42_, custom_42_2, custom_42_5 ...

    customid=$(customGroup + " input:first").attr('id');
    t=customid.split("_");
    suffix =  "_"+t[2];

    //first take out the default autocomplete that contains all the contacts,
    // and replaces it with one that contains only a subset,
    $("#custom_42"+suffix).unbind().autocomplete( 
      "/civicrm/ajax/rest?json=1&fnName=civicrm/contact/search&contact_type=organization&group[71]=1",
    {
     dataType:"json",
     extraParams:{sort_name:function () {return $("#custom_42"+suffix).val();}},
     formatItem: function(data,i,max,value,term){ return value;},
     parse: function(data){
         var acd = new Array();
         for(cid in data){
           acd.push({ data:data[cid], value:data[cid].sort_name, result:data[cid].sort_name });
         }
         return acd;
     },
       width: 500,
       delay:50,
       max:200,
       mustMatch: true,
       autoFill:true,
       selectFirst: true
   )};
)};
By simply changing the url of the ajax callback, you can pretty much restrict the list to whatever you need, eg: For the GOTV, it could be the individuals that are tagged either volunteer (tag id= 42) or staff (tag id=43) - /civicrm/ajax/rest?json=1&fnName=civicrm/contact/search&contact_type=Individual&tag[42]=1&tag[43]=1 For the teachers, it could be the contacts of the subtype teacher that leave in france - /civicrm/ajax/rest?json=1&fnName=civicrm/contact/search&contact_sub_type=teacher&country=france Pretty neat, isn't it ? For the next release, I'm working on a new jquery plugin that would make it even easier:
$("#custom_42"+suffix).unbind().crmAutocomplete( {
  "contact_type" : "organization",
  "group[71]" : 1});
To be continued... And as it's been bloged about already, you can use this autocomplete and ajax features in your profile fields as well. X+ P.S. This example works on a drupal install with clean url and installed at the root of your domain, you might need to adjust the url accordingly.

Comments

Might be good to add a link to this blog post on the wiki in the custom fields section and / or a copy of this in the Developer -> Customizing section and / or add as an example in the book dev section!

Isn't this what the contactListQuery hook is designed for?
http://wiki.civicrm.org/confluence/display/CRMDOC/CiviCRM+hook+specification#CiviCRMhookspecification-hookcivicrmcontactListQuery

Or if not -- or if the hook didn't meet your need -- can you explain the difference between your solution and using the hook to control what's available in the list?

Well, I'd argue that it's much easier to add a few lines of jQuery in a template than having to develop a module and add the hook and find the right sql query.

Moreover, the ajax solution is using the power of the api, ie. you have as many filters as you want, essentially for free. I takes 10 seconds to switch from filter "all the contact subtypes teacher & leaving in CA and that are tagged experienced and that belong to the group supervisors or the group elementary teachers" to "organizations in france that have the custom field 'magic pony' set to pink".

With the hook, you have to know civicrm db structure to create that request, and it takes way longer to build it than set a few params to an api call, that's much harder to switch between criteria, and because the db structure changes between versions, an upgrade might break up your sql query, much less likely with the api.

The only benefit of that hook is that you can do queries that aren't available in the api. I personally would in that case create a new custom API instead of the hook, so it is usable as well from the rest api, and crmAPI and other templates.

tl; dr; : me no liking sql, me liking api ;)

Oops, almost forgot something that is really funky: you can use the value of another field in the form to filter further the list.

We did use for mepwatch.eu and when you set the country field for a candidate, the list of parties where restricted to the ones from that country, direct from the browser, without any additional request or page reload.

in 2 lines of jQuery.

X+

The preferred way to do event binding, and anything else that doesn't affect the appearance of the visible portion of the page is to use
$(window).load(function(){});
rather than
$(document).ready(function(){});
This trick makes your JavaScript run after the page is completely finished rendering whereas using document.ready will delay page rendering. This is most important for anything that does an HTTP request (which isn't the case here).

Have to say I put it into ready instead of load without thinking about it and in general your point is very good advice.

In that case, as it's about undoing something that has an impact on the GUI (the autocomplete adds an icon on the field...) and that it doesn't take long (no request on the network to wait for) I'd leave it there, but definitely going to work as well in the load.