This repository has been archived on 2023-02-03. You can view files and clone it, but cannot push or open issues or pull requests.
autonomic.zone/src/_posts/2019-08-30-civicrm-mailing-validation.md

137 lines
4.8 KiB
Markdown

---
layout: post
title: CiviCRM AngularJS extension
description: Adding custom validation to the CiviCRM Mailing form
image: 2019-08-30_civicrm_validation_header.jpg
category: howto
date: 2019-08-30
---
We support [Campaign Against Arms Trade](https://caat.org.uk), a right-on group
that works to end the international arms trade, with their technology –
including [CiviCRM](https://civicrm.org/), a popular open source
"constituent relationship management" platform.
Among other things, CAAT uses CiviCRM's "Mailing" features to send out emails to
their supporters, and they told us that they're experiencing an annoying bug: if
a user sends out a mailing with the same name (not subject line, just the
internal identifier 🙄) as an existing one, it'll cause the CRM to freeze up
elsewhere.
As an added challenge, the mailing features of CiviCRM [now use
AngularJS](https://docs.civicrm.org/dev/en/latest/framework/angular/) following
a recent rebuild by the developers, and there aren't many tutorials or examples
out there to customise it. Luckily, the CiviCRM developer community was
super-helpful, and we managed to sort out some in-form validation to prevent
duplicate mailing names for our client… and now for you, too!
## Create a new extension
Using [`civix`](https://github.com/totten/civix), we set up a new CiviCRM extension for our code:
```sh
$ civix generate:module mailing
```
(We called our extension `mailing`, because our creative director was occupied
with an Art at the time)
Our client sensibly keeps their custom CiviCRM extensions in `git`, so at this
stage we initialised a repository, added the boilerplate template code, and
pushed.
## Set up the AngularJS hook
A function called `mailing_civicrm_alterAngular()` will get executed whenever
an AngularJS page loads, and you can use a `ChangeSet` to edit an AngularJS
template. Because AngularJS templates specify form logic, this also lets you
change the validation behaviour. Our hook function looks like this:
```php
function mailing_civicrm_alterAngular($angular) {
$changeSet = \Civi\Angular\ChangeSet::create('mailing_name_unique')
->alterHtml('~/crmMailing/BlockSummary.html', function(phpQueryObject $doc) {
// name validation
$doc->find('.crm-group:has([crm-ui-id="subform.mailingName"])')->attr('ng-controller', 'NameValidateCtrl');
$doc->find('[crm-ui-id="subform.mailingName"]')->attr('ng-blur', 'validateName(mailing, \'name\')');
$doc->find('[crm-ui-id="subform.mailingName"]')->attr('crm-ui-validate', 'isValid');
});
$angular->add($changeSet);
CRM_Core_Resources::singleton()->addScriptFile('mailing', 'js/disallow-duplicate-names.js');
}
```
Setting `crm-ui-validate` to `validateName` directly fired the event _way_ too
many times, so instead `validateName` is only called when focus leaves the
field, `ng-blur`, which then sets the `isValid` variable that's checked by
`crm-ui-validate`.
## Create the `validateName` function
Then, the code which queries the CiviCRM API to check for mailings with
duplicate names:
```javascript
var validating = false;
(function(angular, $) {
var crmMailing = angular.module('crmMailing');
crmMailing.controller('NameValidateCtrl', function($scope) {
$scope.isValid = false;
$scope.validateName = function(mailing, field) {
if (!validating) {
validating = true;
CRM.api3('Mailing', 'get', {
"sequential": 1,
"name": mailing[field],
"id": {"!=": mailing.id}
}).then(function(result) {
// do something with result
if (result.count > 0 ) {
$scope.isValid = false;
CRM.alert(ts('There is already a mailing with this name; sending this one will crash CiviCRM!'));
} else {
$scope.isValid = true;
}
}, function(error) {
// oops
console.log(error);
});
validating = false;
}
};
});
})(angular, CRM.$);
```
(saved as `js/disallow-duplicate-names.js`)
## Conclusion
Activate the extension, e.g. with `cv`
```
$ cv en mailing
```
Now, open a mailing and try to give it the same name as an existing one – you
should see the field border turn red, and you'll be prevented from continuing or
sending the mailing:
!["Mailing name" field showing the field with a red border and red label](/assets/images/2019-08-30_civicrm_validation.png)
(As a bonus, the extension also sends a notification to the user using
`CRM.alert` to explain the error)
It does seem like a red border sometimes hangs around the field label even after
the value is valid again… but apart from that, the feature is working great!
Lastly, props to CAAT for being a great member of the CiviCRM community and
supporting us writing this post to share our work with y'all.