/// crmUi: Sundry UI helpers
(function (angular, $, _) {
var uidCount = 0,
pageTitle = 'CiviCRM',
documentTitle = 'CiviCRM';
angular.module('crmUi', CRM.angRequires('crmUi'))
// example
...content...
// WISHLIST: crmCollapsed should support two-way/continuous binding
.directive('crmUiAccordion', function() {
return {
scope: {
crmUiAccordion: '='
},
template: '',
transclude: true,
link: function (scope, element, attrs) {
scope.cssClasses = {
'crm-accordion-wrapper': true,
collapsed: scope.crmUiAccordion.collapsed
};
scope.help = null;
scope.$watch('crmUiAccordion', function(crmUiAccordion) {
if (crmUiAccordion && crmUiAccordion.help) {
scope.help = crmUiAccordion.help.clone({}, {
title: crmUiAccordion.title
});
}
});
}
};
})
// Examples:
// crmUiAlert({text: 'My text', title: 'My title', type: 'error'});
// crmUiAlert({template: 'Hello', scope: $scope.$new()});
// var h = crmUiAlert({templateUrl: '~/crmFoo/alert.html', scope: $scope.$new()});
// ... h.close(); ...
.service('crmUiAlert', function($compile, $rootScope, $templateRequest, $q) {
var count = 0;
return function crmUiAlert(params) {
var id = 'crmUiAlert_' + (++count);
var tpl = null;
if (params.templateUrl) {
tpl = $templateRequest(params.templateUrl);
}
else if (params.template) {
tpl = params.template;
}
if (tpl) {
params.text = ''; // temporary stub
}
var result = CRM.alert(params.text, params.title, params.type, params.options);
if (tpl) {
$q.when(tpl, function(html) {
var scope = params.scope || $rootScope.$new();
var linker = $compile(html);
$('#' + id).append($(linker(scope)));
});
}
return result;
};
})
// Simple wrapper around $.crmDatepicker.
// example with no time input:
// example with custom date format:
.directive('crmUiDatepicker', function () {
return {
restrict: 'AE',
require: 'ngModel',
scope: {
crmUiDatepicker: '='
},
link: function (scope, element, attrs, ngModel) {
ngModel.$render = function () {
element.val(ngModel.$viewValue).change();
};
element
.crmDatepicker(scope.crmUiDatepicker)
.on('change', function() {
var requiredLength = 19;
if (scope.crmUiDatepicker && scope.crmUiDatepicker.time === false) {
requiredLength = 10;
}
if (scope.crmUiDatepicker && scope.crmUiDatepicker.date === false) {
requiredLength = 8;
}
ngModel.$setValidity('incompleteDateTime', !($(this).val().length && $(this).val().length !== requiredLength));
});
}
};
})
// Display debug information (if available)
// For richer DX, checkout Batarang/ng-inspector (Chrome/Safari), or AngScope/ng-inspect (Firefox).
// example:
.directive('crmUiDebug', function ($location) {
return {
restrict: 'AE',
scope: {
crmUiDebug: '@'
},
template: function() {
var args = $location.search();
return (args && args.angularDebug) ? '' : '';
},
link: function(scope, element, attrs) {
var args = $location.search();
if (args && args.angularDebug) {
scope.ts = CRM.ts(null);
scope.$parent.$watch(attrs.crmUiDebug, function(data) {
scope.data = data;
});
}
}
};
})
// Display a field/row in a field list
// example: {{mydata}}
// example:
// example:
// example: {{mydata}}
.directive('crmUiField', function() {
// Note: When writing new templates, the "label" position is particular. See/patch "var label" below.
var templateUrls = {
default: '~/crmUi/field.html',
checkbox: '~/crmUi/field-cb.html'
};
return {
require: '^crmUiIdScope',
restrict: 'EA',
scope: {
// {title, name, help, helpFile}
crmUiField: '='
},
templateUrl: function(tElement, tAttrs){
var layout = tAttrs.crmLayout ? tAttrs.crmLayout : 'default';
return templateUrls[layout];
},
transclude: true,
link: function (scope, element, attrs, crmUiIdCtrl) {
$(element).addClass('crm-section');
scope.help = null;
scope.$watch('crmUiField', function(crmUiField) {
if (crmUiField && crmUiField.help) {
scope.help = crmUiField.help.clone({}, {
title: crmUiField.title
});
}
});
}
};
})
// example:
.directive('crmUiId', function () {
return {
require: '^crmUiIdScope',
restrict: 'EA',
link: {
pre: function (scope, element, attrs, crmUiIdCtrl) {
var id = crmUiIdCtrl.get(attrs.crmUiId);
element.attr('id', id);
}
}
};
})
// for example, see crmUiHelp
.service('crmUiHelp', function(){
// example: var h = new FieldHelp({id: 'foo'}); h.open();
function FieldHelp(options) {
this.options = options;
}
angular.extend(FieldHelp.prototype, {
get: function(n) {
return this.options[n];
},
open: function open() {
CRM.help(this.options.title, {id: this.options.id, file: this.options.file});
},
clone: function clone(options, defaults) {
return new FieldHelp(angular.extend({}, defaults, this.options, options));
}
});
// example: var hs = crmUiHelp({file: 'CRM/Foo/Bar'});
return function(defaults){
// example: hs('myfield')
// example: hs({id: 'myfield', title: 'Foo Bar', file: 'Whiz/Bang'})
return function(options) {
if (_.isString(options)) {
options = {id: options};
}
return new FieldHelp(angular.extend({}, defaults, options));
};
};
})
// Display a help icon
// Example: Use a default *.hlp file
// scope.hs = crmUiHelp({file: 'Path/To/Help/File'});
// HTML:
// Example: Use an explicit *.hlp file
// HTML:
.directive('crmUiHelp', function() {
return {
restrict: 'EA',
link: function(scope, element, attrs) {
setTimeout(function() {
var crmUiHelp = scope.$eval(attrs.crmUiHelp);
var title = crmUiHelp && crmUiHelp.get('title') ? ts('%1 Help', {1: crmUiHelp.get('title')}) : ts('Help');
element.attr('title', title);
}, 50);
element
.addClass('helpicon')
.attr('href', '#')
.on('click', function(e) {
e.preventDefault();
scope.$eval(attrs.crmUiHelp).open();
});
}
};
})
// example:
.directive('crmUiFor', function ($parse, $timeout) {
return {
require: '^crmUiIdScope',
restrict: 'EA',
template: '*',
transclude: true,
link: function (scope, element, attrs, crmUiIdCtrl) {
scope.crmIsRequired = false;
scope.cssClasses = {};
if (!attrs.crmUiFor) return;
var id = crmUiIdCtrl.get(attrs.crmUiFor);
element.attr('for', id);
var ngModel = null;
var updateCss = function () {
scope.cssClasses['crm-error'] = !ngModel.$valid && !ngModel.$pristine;
};
// Note: if target element is dynamically generated (eg via ngInclude), then it may not be available
// immediately for initialization. Use retries/retryDelay to initialize such elements.
var init = function (retries, retryDelay) {
var input = $('#' + id);
if (input.length === 0 && !attrs.crmUiForceRequired) {
if (retries) {
$timeout(function(){
init(retries-1, retryDelay);
}, retryDelay);
}
return;
}
if (attrs.crmUiForceRequired) {
scope.crmIsRequired = true;
return;
}
var tgtScope = scope;//.$parent;
if (attrs.crmDepth) {
for (var i = attrs.crmDepth; i > 0; i--) {
tgtScope = tgtScope.$parent;
}
}
if (input.attr('ng-required')) {
scope.crmIsRequired = scope.$parent.$eval(input.attr('ng-required'));
scope.$parent.$watch(input.attr('ng-required'), function (isRequired) {
scope.crmIsRequired = isRequired;
});
}
else {
scope.crmIsRequired = input.prop('required');
}
ngModel = $parse(attrs.crmUiFor)(tgtScope);
if (ngModel) {
ngModel.$viewChangeListeners.push(updateCss);
}
};
$timeout(function(){
init(3, 100);
});
}
};
})
// Define a scope in which a name like "subform.foo" maps to a unique ID.
// example:
.directive('crmUiIdScope', function () {
return {
restrict: 'EA',
scope: {},
controllerAs: 'crmUiIdCtrl',
controller: function($scope) {
var ids = {};
this.get = function(name) {
if (!ids[name]) {
ids[name] = "crmUiId_" + (++uidCount);
}
return ids[name];
};
},
link: function (scope, element, attrs) {}
};
})
// Display an HTML blurb inside an IFRAME.
// example:
// example:
.directive('crmUiIframe', function ($parse) {
return {
scope: {
crmUiIframeSrc: '@', // expression which evaluates to a URL
crmUiIframe: '@' // expression which evaluates to HTML content
},
link: function (scope, elm, attrs) {
var iframe = $(elm)[0];
iframe.setAttribute('width', '100%');
iframe.setAttribute('height', '250px');
iframe.setAttribute('frameborder', '0');
var refresh = function () {
if (attrs.crmUiIframeSrc) {
iframe.setAttribute('src', scope.$parent.$eval(attrs.crmUiIframeSrc));
}
else {
var iframeHtml = scope.$parent.$eval(attrs.crmUiIframe);
var doc = iframe.document;
if (iframe.contentDocument) {
doc = iframe.contentDocument;
}
else if (iframe.contentWindow) {
doc = iframe.contentWindow.document;
}
doc.open();
doc.writeln(iframeHtml);
doc.close();
}
};
// If the iframe is in a dialog, respond to resize events
$(elm).parent().on('dialogresize dialogopen', function(e, ui) {
$(this).css({padding: '0', margin: '0', overflow: 'hidden'});
iframe.setAttribute('height', '' + $(this).innerHeight() + 'px');
});
$(elm).parent().on('dialogresize', function(e, ui) {
iframe.setAttribute('class', 'resized');
});
scope.$parent.$watch(attrs.crmUiIframe, refresh);
}
};
})
// Example:
//
.directive('crmUiRichtext', function ($timeout) {
return {
require: '?ngModel',
link: function (scope, elm, attr, ngModel) {
var editor = CRM.wysiwyg.create(elm);
if (!ngModel) {
return;
}
if (attr.ngBlur) {
$(elm).on('blur', function() {
$timeout(function() {
scope.$eval(attr.ngBlur);
});
});
}
ngModel.$render = function(value) {
editor.done(function() {
CRM.wysiwyg.setVal(elm, ngModel.$viewValue || '');
});
};
}
};
})
// Display a lock icon (based on a boolean).
// example:
// example:
.directive('crmUiLock', function ($parse, $rootScope) {
var defaultVal = function (defaultValue) {
var f = function (scope) {
return defaultValue;
};
f.assign = function (scope, value) {
// ignore changes
};
return f;
};
// like $parse, but accepts a defaultValue in case expr is undefined
var parse = function (expr, defaultValue) {
return expr ? $parse(expr) : defaultVal(defaultValue);
};
return {
template: '',
link: function (scope, element, attrs) {
var binding = parse(attrs.binding, true);
var titleLocked = parse(attrs.titleLocked, ts('Locked'));
var titleUnlocked = parse(attrs.titleUnlocked, ts('Unlocked'));
$(element).addClass('crm-i lock-button');
var refresh = function () {
var locked = binding(scope);
if (locked) {
$(element)
.removeClass('fa-unlock')
.addClass('fa-lock')
.prop('title', titleLocked(scope))
;
}
else {
$(element)
.removeClass('fa-lock')
.addClass('fa-unlock')
.prop('title', titleUnlocked(scope))
;
}
};
$(element).click(function () {
binding.assign(scope, !binding(scope));
//scope.$digest();
$rootScope.$digest();
});
scope.$watch(attrs.binding, refresh);
scope.$watch(attrs.titleLocked, refresh);
scope.$watch(attrs.titleUnlocked, refresh);
refresh();
}
};
})
// CrmUiOrderCtrl is a controller class which manages sort orderings.
// Ex:
// JS: $scope.myOrder = new CrmUiOrderCtrl(['+field1', '-field2]);
// $scope.myOrder.toggle('field1');
// $scope.myOrder.setDir('field2', '');
// HTML: ...
.service('CrmUiOrderCtrl', function(){
//
function CrmUiOrderCtrl(defaults){
this.values = defaults;
}
angular.extend(CrmUiOrderCtrl.prototype, {
get: function get() {
return this.values;
},
getDir: function getDir(name) {
if (this.values.indexOf(name) >= 0 || this.values.indexOf('+' + name) >= 0) {
return '+';
}
if (this.values.indexOf('-' + name) >= 0) {
return '-';
}
return '';
},
// @return bool TRUE if something is removed
remove: function remove(name) {
var idx = this.values.indexOf(name);
if (idx >= 0) {
this.values.splice(idx, 1);
return true;
}
else {
return false;
}
},
setDir: function setDir(name, dir) {
return this.toggle(name, dir);
},
// Toggle sort order on a field.
// To set a specific order, pass optional parameter 'next' ('+', '-', or '').
toggle: function toggle(name, next) {
if (!next && next !== '') {
next = '+';
if (this.remove(name) || this.remove('+' + name)) {
next = '-';
}
if (this.remove('-' + name)) {
next = '';
}
}
if (next == '+') {
this.values.unshift('+' + name);
}
else if (next == '-') {
this.values.unshift('-' + name);
}
}
});
return CrmUiOrderCtrl;
})
// Define a controller which manages sort order. You may interact with the controller
// directly ("myOrder.toggle('fieldname')") order using the helper, crm-ui-order-by.
// example:
//
// My Field |
// ...
//