First commit
This commit is contained in:
commit
c6e2478c40
13918 changed files with 2303184 additions and 0 deletions
30
sites/all/modules/civicrm/js/AlternateContactSelector.js
Normal file
30
sites/all/modules/civicrm/js/AlternateContactSelector.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
CRM.$(function($) {
|
||||
'use strict';
|
||||
|
||||
function assignAutoComplete(id_field, profileids) {
|
||||
$('#' + id_field).on('change', function (event, data) {
|
||||
var contactID = $(this).val();
|
||||
CRM.api3('profile', 'get', {'profile_id': profileids, 'contact_id': contactID})
|
||||
.done(function (result) {
|
||||
$.each(result.values, function (id, value) {
|
||||
$.each(value, function (fieldname, fieldvalue) {
|
||||
$('#' + fieldname).val(fieldvalue).change();
|
||||
$("[name=" + fieldname + "]").val([fieldvalue]);
|
||||
if ($.isArray(fieldvalue)) {
|
||||
$.each(fieldvalue, function (index, val) {
|
||||
$("#" + fieldname + "_" + val).prop('checked', true);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
$(CRM.form.autocompletes).each(function (index, autocomplete) {
|
||||
assignAutoComplete(autocomplete.id_field, CRM.ids.profile || []);
|
||||
});
|
||||
|
||||
});
|
||||
|
1687
sites/all/modules/civicrm/js/Common.js
Normal file
1687
sites/all/modules/civicrm/js/Common.js
Normal file
File diff suppressed because it is too large
Load diff
88
sites/all/modules/civicrm/js/angular-crmResource/all.js
vendored
Normal file
88
sites/all/modules/civicrm/js/angular-crmResource/all.js
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
// crmResource: Given a templateUrl "~/mymodule/myfile.html", load the matching HTML.
|
||||
// This implementation loads all partials and strings in one batch.
|
||||
(function(angular, $, _) {
|
||||
angular.module('crmResource', []);
|
||||
|
||||
angular.module('crmResource').factory('crmResource', function($q, $http) {
|
||||
var deferreds = {}; // null|object; deferreds[url][idx] = Deferred;
|
||||
var templates = null; // null|object; templates[url] = HTML;
|
||||
|
||||
var notify = function notify() {
|
||||
var oldDfrds = deferreds;
|
||||
deferreds = null;
|
||||
|
||||
angular.forEach(oldDfrds, function(dfrs, url) {
|
||||
if (templates[url]) {
|
||||
angular.forEach(dfrs, function(dfr) {
|
||||
dfr.resolve({
|
||||
status: 200,
|
||||
headers: function(name) {
|
||||
var headers = {'Content-type': 'text/html'};
|
||||
return name ? headers[name] : headers;
|
||||
},
|
||||
data: templates[url]
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
angular.forEach(dfrs, function(dfr) {
|
||||
dfr.reject({status: 500}); // FIXME
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var moduleUrl = CRM.angular.bundleUrl;
|
||||
$http.get(moduleUrl)
|
||||
.success(function httpSuccess(data) {
|
||||
templates = [];
|
||||
angular.forEach(data, function(module) {
|
||||
if (module.partials) {
|
||||
angular.extend(templates, module.partials);
|
||||
}
|
||||
if (module.strings) {
|
||||
CRM.addStrings(module.domain, module.strings);
|
||||
}
|
||||
});
|
||||
notify();
|
||||
})
|
||||
.error(function httpError() {
|
||||
templates = [];
|
||||
notify();
|
||||
});
|
||||
|
||||
return {
|
||||
// @return string|Promise<string>
|
||||
getUrl: function getUrl(url) {
|
||||
if (templates !== null) {
|
||||
return templates[url];
|
||||
}
|
||||
else {
|
||||
var deferred = $q.defer();
|
||||
if (!deferreds[url]) {
|
||||
deferreds[url] = [];
|
||||
}
|
||||
deferreds[url].push(deferred);
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
angular.module('crmResource').config(function($provide) {
|
||||
$provide.decorator('$templateCache', function($delegate, $http, $q, crmResource) {
|
||||
var origGet = $delegate.get;
|
||||
var urlPat = /^~\//;
|
||||
$delegate.get = function(url) {
|
||||
if (urlPat.test(url)) {
|
||||
return crmResource.getUrl(url);
|
||||
}
|
||||
else {
|
||||
return origGet.call(this, url);
|
||||
}
|
||||
};
|
||||
return $delegate;
|
||||
});
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
137
sites/all/modules/civicrm/js/angular-crmResource/byModule.js
vendored
Normal file
137
sites/all/modules/civicrm/js/angular-crmResource/byModule.js
vendored
Normal file
|
@ -0,0 +1,137 @@
|
|||
// crmResource: Given a templateUrl "~/mymodule/myfile.html", load the matching HTML.
|
||||
// This implementation loads partials and strings in per-module batches.
|
||||
// FIXME: handling of CRM.strings not well tested; may be racy
|
||||
(function(angular, $, _) {
|
||||
angular.module('crmResource', []);
|
||||
|
||||
angular.module('crmResource').factory('crmResource', function($q, $http) {
|
||||
var modules = {}; // moduleQueue[module] = 'loading'|Object;
|
||||
var templates = {}; // templates[url] = HTML;
|
||||
|
||||
function CrmResourceModule(name) {
|
||||
this.name = name;
|
||||
this.status = 'new'; // loading|loaded|error
|
||||
this.data = null;
|
||||
this.deferreds = [];
|
||||
}
|
||||
|
||||
angular.extend(CrmResourceModule.prototype, {
|
||||
createDeferred: function createDeferred() {
|
||||
var deferred = $q.defer();
|
||||
switch (this.status) {
|
||||
case 'new':
|
||||
case 'loading':
|
||||
this.deferreds.push(deferred);
|
||||
break;
|
||||
case 'loaded':
|
||||
deferred.resolve(this.data);
|
||||
break;
|
||||
case 'error':
|
||||
deferred.reject();
|
||||
break;
|
||||
default:
|
||||
throw 'Unknown status: ' + this.status;
|
||||
}
|
||||
return deferred.promise;
|
||||
},
|
||||
load: function load() {
|
||||
var module = this;
|
||||
this.status = 'loading';
|
||||
var moduleUrl = CRM.url('civicrm/ajax/angular-modules', {modules: module.name, l: CRM.config.lcMessages, r: CRM.angular.cacheCode});
|
||||
$http.get(moduleUrl)
|
||||
.success(function httpSuccess(data) {
|
||||
if (data[module.name]) {
|
||||
module.onSuccess(data[module.name]);
|
||||
}
|
||||
else {
|
||||
module.onError();
|
||||
}
|
||||
})
|
||||
.error(function httpError() {
|
||||
module.onError();
|
||||
});
|
||||
},
|
||||
onSuccess: function onSuccess(data) {
|
||||
var module = this;
|
||||
this.data = data;
|
||||
this.status = 'loaded';
|
||||
if (this.data.partials) {
|
||||
angular.extend(templates, this.data.partials);
|
||||
}
|
||||
if (this.data.strings) {
|
||||
CRM.addStrings(this.data.domain, this.data.strings);
|
||||
}
|
||||
angular.forEach(this.deferreds, function(deferred) {
|
||||
deferred.resolve(module.data);
|
||||
});
|
||||
delete this.deferreds;
|
||||
},
|
||||
onError: function onError() {
|
||||
this.status = 'error';
|
||||
angular.forEach(this.deferreds, function(deferred) {
|
||||
deferred.reject();
|
||||
});
|
||||
delete this.deferreds;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
// @return Promise<ModuleData>
|
||||
getModule: function getModule(name) {
|
||||
if (!modules[name]) {
|
||||
modules[name] = new CrmResourceModule(name);
|
||||
modules[name].load();
|
||||
}
|
||||
return modules[name].createDeferred();
|
||||
},
|
||||
// @return string|Promise<string>
|
||||
getUrl: function getUrl(url) {
|
||||
if (templates[url]) {
|
||||
return templates[url];
|
||||
}
|
||||
|
||||
var parts = url.split('/');
|
||||
var deferred = $q.defer();
|
||||
this.getModule(parts[1]).then(
|
||||
function() {
|
||||
if (templates[url]) {
|
||||
deferred.resolve({
|
||||
status: 200,
|
||||
headers: function(name) {
|
||||
var headers = {'Content-type': 'text/html'};
|
||||
return name ? headers[name] : headers;
|
||||
},
|
||||
data: templates[url]
|
||||
});
|
||||
}
|
||||
else {
|
||||
deferred.reject({status: 500}); // FIXME
|
||||
}
|
||||
},
|
||||
function() {
|
||||
deferred.reject({status: 500}); // FIXME
|
||||
}
|
||||
);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
angular.module('crmResource').config(function($provide) {
|
||||
$provide.decorator('$templateCache', function($delegate, $http, $q, crmResource) {
|
||||
var origGet = $delegate.get;
|
||||
var urlPat = /^~\//;
|
||||
$delegate.get = function(url) {
|
||||
if (urlPat.test(url)) {
|
||||
return crmResource.getUrl(url);
|
||||
}
|
||||
else {
|
||||
return origGet.call(this, url);
|
||||
}
|
||||
};
|
||||
return $delegate;
|
||||
});
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
8
sites/all/modules/civicrm/js/crm.addNew.js
Normal file
8
sites/all/modules/civicrm/js/crm.addNew.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
// http://civicrm.org/licensing
|
||||
// Opens the "new item" dialog after creating a container/set
|
||||
CRM.$(function($) {
|
||||
var emptyMsg = $('.crm-empty-table');
|
||||
if (emptyMsg.length) {
|
||||
$('.action-link a.button', '#crm-container').click();
|
||||
}
|
||||
});
|
21
sites/all/modules/civicrm/js/crm.admin.js
Normal file
21
sites/all/modules/civicrm/js/crm.admin.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
// https://civicrm.org/licensing
|
||||
(function($) {
|
||||
"use strict";
|
||||
$(document)
|
||||
.on('crmLoad', function(e) {
|
||||
$('.crm-icon-picker', e.target).not('.iconpicker-widget').each(function() {
|
||||
var $el = $(this);
|
||||
CRM.loadScript(CRM.config.resourceBase + 'js/jquery/jquery.crmIconPicker.js').done(function() {
|
||||
$el.crmIconPicker();
|
||||
});
|
||||
// Hack to get the strings in this lazy-loaded file translated
|
||||
ts('None');
|
||||
ts('Normal');
|
||||
ts('Rotate right');
|
||||
ts('Rotate left');
|
||||
ts('Rotate 180');
|
||||
ts('Flip horizontal');
|
||||
ts('Flip vertical');
|
||||
});
|
||||
});
|
||||
})(CRM.$);
|
588
sites/all/modules/civicrm/js/crm.ajax.js
Normal file
588
sites/all/modules/civicrm/js/crm.ajax.js
Normal file
|
@ -0,0 +1,588 @@
|
|||
// https://civicrm.org/licensing
|
||||
/**
|
||||
* @see https://wiki.civicrm.org/confluence/display/CRMDOC/AJAX+Interface
|
||||
* @see https://wiki.civicrm.org/confluence/display/CRMDOC/Ajax+Pages+and+Forms
|
||||
*/
|
||||
(function($, CRM, undefined) {
|
||||
/**
|
||||
* @param string path
|
||||
* @param string|object query
|
||||
* @param string mode - optionally specify "front" or "back"
|
||||
*/
|
||||
var tplURL;
|
||||
CRM.url = function (path, query, mode) {
|
||||
if (typeof path === 'object') {
|
||||
tplURL = path;
|
||||
return path;
|
||||
}
|
||||
if (!tplURL) {
|
||||
CRM.console('error', 'Error: CRM.url called before initialization');
|
||||
}
|
||||
if (!mode) {
|
||||
mode = CRM.config && CRM.config.isFrontend ? 'front' : 'back';
|
||||
}
|
||||
query = query || '';
|
||||
var frag = path.split('?');
|
||||
var url = tplURL[mode].replace("*path*", frag[0]);
|
||||
|
||||
if (!query) {
|
||||
url = url.replace(/[?&]\*query\*/, '');
|
||||
}
|
||||
else {
|
||||
url = url.replace("*query*", typeof query === 'string' ? query : $.param(query));
|
||||
}
|
||||
if (frag[1]) {
|
||||
url += (url.indexOf('?') < 0 ? '?' : '&') + frag[1];
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
$.fn.crmURL = function () {
|
||||
return this.each(function() {
|
||||
if (this.href) {
|
||||
this.href = CRM.url(this.href);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* AJAX api
|
||||
* @link http://wiki.civicrm.org/confluence/display/CRMDOC/AJAX+Interface#AJAXInterface-CRM.api3
|
||||
*/
|
||||
CRM.api3 = function(entity, action, params, status) {
|
||||
if (typeof(entity) === 'string') {
|
||||
params = {
|
||||
entity: entity,
|
||||
action: action.toLowerCase(),
|
||||
json: JSON.stringify(params || {})
|
||||
};
|
||||
} else {
|
||||
params = {
|
||||
entity: 'api3',
|
||||
action: 'call',
|
||||
json: JSON.stringify(entity)
|
||||
};
|
||||
status = action;
|
||||
}
|
||||
var ajax = $.ajax({
|
||||
url: CRM.url('civicrm/ajax/rest'),
|
||||
dataType: 'json',
|
||||
data: params,
|
||||
type: params.action.indexOf('get') < 0 ? 'POST' : 'GET'
|
||||
});
|
||||
if (status) {
|
||||
// Default status messages
|
||||
if (status === true) {
|
||||
status = {success: params.action === 'delete' ? ts('Removed') : ts('Saved')};
|
||||
if (params.action.indexOf('get') === 0) {
|
||||
status.start = ts('Loading...');
|
||||
status.success = null;
|
||||
}
|
||||
}
|
||||
var messages = status === true ? {} : status;
|
||||
CRM.status(status, ajax);
|
||||
}
|
||||
return ajax;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* AJAX api
|
||||
*/
|
||||
CRM.api = function(entity, action, params, options) {
|
||||
// Default settings
|
||||
var settings = {
|
||||
context: null,
|
||||
success: function(result, settings) {
|
||||
return true;
|
||||
},
|
||||
error: function(result, settings) {
|
||||
$().crmError(result.error_message, ts('Error'));
|
||||
return false;
|
||||
},
|
||||
callBack: function(result, settings) {
|
||||
if (result.is_error == 1) {
|
||||
return settings.error.call(this, result, settings);
|
||||
}
|
||||
return settings.success.call(this, result, settings);
|
||||
},
|
||||
ajaxURL: 'civicrm/ajax/rest'
|
||||
};
|
||||
action = action.toLowerCase();
|
||||
// Default success handler
|
||||
switch (action) {
|
||||
case "update":
|
||||
case "create":
|
||||
case "setvalue":
|
||||
case "replace":
|
||||
settings.success = function() {
|
||||
CRM.status(ts('Saved'));
|
||||
return true;
|
||||
};
|
||||
break;
|
||||
case "delete":
|
||||
settings.success = function() {
|
||||
CRM.status(ts('Removed'));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
params = {
|
||||
entity: entity,
|
||||
action: action,
|
||||
json: JSON.stringify(params)
|
||||
};
|
||||
// Pass copy of settings into closure to preserve its value during multiple requests
|
||||
(function(stg) {
|
||||
$.ajax({
|
||||
url: stg.ajaxURL.indexOf('http') === 0 ? stg.ajaxURL : CRM.url(stg.ajaxURL),
|
||||
dataType: 'json',
|
||||
data: params,
|
||||
type: action.indexOf('get') < 0 ? 'POST' : 'GET',
|
||||
success: function(result) {
|
||||
stg.callBack.call(stg.context, result, stg);
|
||||
}
|
||||
});
|
||||
})($.extend({}, settings, options));
|
||||
};
|
||||
|
||||
$.widget('civi.crmSnippet', {
|
||||
options: {
|
||||
url: null,
|
||||
block: true,
|
||||
crmForm: null
|
||||
},
|
||||
_originalContent: null,
|
||||
_originalUrl: null,
|
||||
isOriginalUrl: function() {
|
||||
var
|
||||
args = {},
|
||||
same = true,
|
||||
newUrl = this._formatUrl(this.options.url),
|
||||
oldUrl = this._formatUrl(this._originalUrl);
|
||||
// Compare path
|
||||
if (newUrl.split('?')[0] !== oldUrl.split('?')[0]) {
|
||||
return false;
|
||||
}
|
||||
// Compare arguments
|
||||
$.each(newUrl.split('?')[1].split('&'), function(k, v) {
|
||||
var arg = v.split('=');
|
||||
args[arg[0]] = arg[1];
|
||||
});
|
||||
$.each(oldUrl.split('?')[1].split('&'), function(k, v) {
|
||||
var arg = v.split('=');
|
||||
if (args[arg[0]] !== undefined && arg[1] !== args[arg[0]]) {
|
||||
same = false;
|
||||
}
|
||||
});
|
||||
return same;
|
||||
},
|
||||
resetUrl: function() {
|
||||
this.options.url = this._originalUrl;
|
||||
},
|
||||
_create: function() {
|
||||
this.element.addClass('crm-ajax-container');
|
||||
if (!this.element.is('.crm-container *')) {
|
||||
this.element.addClass('crm-container');
|
||||
}
|
||||
this._handleOrderLinks();
|
||||
// Set default if not supplied
|
||||
this.options.url = this.options.url || document.location.href;
|
||||
this._originalUrl = this.options.url;
|
||||
},
|
||||
_onFailure: function(data, status) {
|
||||
var msg, title = ts('Network Error');
|
||||
if (this.options.block) this.element.unblock();
|
||||
this.element.trigger('crmAjaxFail', data);
|
||||
switch (status) {
|
||||
case 'Forbidden':
|
||||
title = ts('Access Denied');
|
||||
msg = ts('Ensure you are still logged in and have permission to access this feature.');
|
||||
break;
|
||||
default:
|
||||
msg = ts('Unable to reach the server. Please refresh this page in your browser and try again.');
|
||||
}
|
||||
CRM.alert(msg, title, 'error');
|
||||
},
|
||||
_onError: function(data) {
|
||||
this.element.attr('data-unsaved-changes', 'false').trigger('crmAjaxError', data);
|
||||
if (this.options.crmForm && this.options.crmForm.autoClose && this.element.data('uiDialog')) {
|
||||
this.element.dialog('close');
|
||||
}
|
||||
},
|
||||
_formatUrl: function(url, snippetType) {
|
||||
// Strip hash
|
||||
url = url.split('#')[0];
|
||||
// Add snippet argument to url
|
||||
if (snippetType) {
|
||||
if (url.search(/[&?]snippet=/) < 0) {
|
||||
url += (url.indexOf('?') < 0 ? '?' : '&') + 'snippet=' + snippetType;
|
||||
} else {
|
||||
url = url.replace(/snippet=[^&]*/, 'snippet=' + snippetType);
|
||||
}
|
||||
}
|
||||
return url;
|
||||
},
|
||||
// Hack to deal with civicrm legacy sort functionality
|
||||
_handleOrderLinks: function() {
|
||||
var that = this;
|
||||
$('a.crm-weight-arrow', that.element).click(function(e) {
|
||||
if (that.options.block) that.element.block();
|
||||
$.getJSON(that._formatUrl(this.href, 'json')).done(function() {
|
||||
that.refresh();
|
||||
});
|
||||
e.stopImmediatePropagation();
|
||||
return false;
|
||||
});
|
||||
},
|
||||
refresh: function() {
|
||||
var that = this;
|
||||
var url = this._formatUrl(this.options.url, 'json');
|
||||
if (this.options.crmForm) $('form', this.element).ajaxFormUnbind();
|
||||
if (this.options.block) this.element.block();
|
||||
$.getJSON(url, function(data) {
|
||||
if (data.status === 'redirect') {
|
||||
that.options.url = data.userContext;
|
||||
return that.refresh();
|
||||
}
|
||||
if (that.options.block) that.element.unblock();
|
||||
if (!$.isPlainObject(data)) {
|
||||
that._onFailure(data);
|
||||
return;
|
||||
}
|
||||
if (data.status === 'error') {
|
||||
that._onError(data);
|
||||
return;
|
||||
}
|
||||
data.url = url;
|
||||
that.element.trigger('crmUnload').trigger('crmBeforeLoad', data);
|
||||
that._beforeRemovingContent();
|
||||
that.element.html(data.content);
|
||||
that._handleOrderLinks();
|
||||
that.element.trigger('crmLoad', data);
|
||||
if (that.options.crmForm) that.element.trigger('crmFormLoad', data);
|
||||
// This is only needed by forms that load via ajax but submit without ajax, e.g. configure contribution page tabs
|
||||
// TODO: remove this when those forms have been converted to use ajax submit
|
||||
if (data.status === 'form_error' && $.isPlainObject(data.errors)) {
|
||||
that.element.trigger('crmFormError', data);
|
||||
$.each(data.errors, function(formElement, msg) {
|
||||
$('[name="'+formElement+'"]', that.element).crmError(msg);
|
||||
});
|
||||
}
|
||||
}).fail(function(data, msg, status) {
|
||||
that._onFailure(data, status);
|
||||
});
|
||||
},
|
||||
// Perform any cleanup needed before removing/replacing content
|
||||
_beforeRemovingContent: function() {
|
||||
var that = this;
|
||||
// Save original content to be restored if widget is destroyed
|
||||
if (this._originalContent === null) {
|
||||
$('.blockUI', this.element).remove();
|
||||
this._originalContent = this.element.contents().detach();
|
||||
}
|
||||
if (this.options.crmForm) $('form', this.element).ajaxFormUnbind();
|
||||
},
|
||||
_destroy: function() {
|
||||
this.element.removeClass('crm-ajax-container').trigger('crmUnload');
|
||||
this._beforeRemovingContent();
|
||||
if (this._originalContent !== null) {
|
||||
this.element.empty().append(this._originalContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var dialogCount = 0,
|
||||
exclude = '[href^=#], [href^=javascript], [onclick], .no-popup, .cancel';
|
||||
|
||||
CRM.loadPage = function(url, options) {
|
||||
var settings = {
|
||||
target: '#crm-ajax-dialog-' + (dialogCount++),
|
||||
dialog: (options && options.target) ? false : {}
|
||||
};
|
||||
if (options) $.extend(true, settings, options);
|
||||
settings.url = url;
|
||||
// Create new dialog
|
||||
if (settings.dialog) {
|
||||
settings.dialog = CRM.utils.adjustDialogDefaults(settings.dialog);
|
||||
$('<div id="' + settings.target.substring(1) + '"></div>')
|
||||
.dialog(settings.dialog)
|
||||
.parent().find('.ui-dialog-titlebar')
|
||||
.append($('<a class="crm-dialog-titlebar-print ui-dialog-titlebar-close" title="'+ts('Print window')+'" target="_blank" style="right:3.8em;"/>')
|
||||
.button({icons: {primary: 'fa-print'}, text: false}));
|
||||
}
|
||||
// Add handlers to new or existing dialog
|
||||
if ($(settings.target).data('uiDialog')) {
|
||||
$(settings.target)
|
||||
.on('dialogclose', function() {
|
||||
if (settings.dialog && $(this).attr('data-unsaved-changes') !== 'true') {
|
||||
$(this).crmSnippet('destroy').dialog('destroy').remove();
|
||||
}
|
||||
})
|
||||
.on('crmLoad', function(e, data) {
|
||||
// Set title
|
||||
if (e.target === $(settings.target)[0] && data && !settings.dialog.title && data.title) {
|
||||
$(this).dialog('option', 'title', data.title);
|
||||
}
|
||||
// Update print url
|
||||
$(this).parent().find('a.crm-dialog-titlebar-print').attr('href', $(this).data('civiCrmSnippet')._formatUrl($(this).crmSnippet('option', 'url'), '2'));
|
||||
});
|
||||
}
|
||||
$(settings.target).crmSnippet(settings).crmSnippet('refresh');
|
||||
return $(settings.target);
|
||||
};
|
||||
CRM.loadForm = function(url, options) {
|
||||
var formErrors = [], settings = {
|
||||
crmForm: {
|
||||
ajaxForm: {},
|
||||
autoClose: true,
|
||||
validate: true,
|
||||
refreshAction: ['next_new', 'submit_savenext', 'upload_new'],
|
||||
cancelButton: '.cancel',
|
||||
openInline: 'a.open-inline, a.button, a.action-item, a.open-inline-noreturn',
|
||||
onCancel: function(event) {}
|
||||
}
|
||||
};
|
||||
// Move options that belong to crmForm. Others will be passed through to crmSnippet
|
||||
if (options) $.each(options, function(key, value) {
|
||||
if (typeof(settings.crmForm[key]) !== 'undefined') {
|
||||
settings.crmForm[key] = value;
|
||||
}
|
||||
else {
|
||||
settings[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
var widget = CRM.loadPage(url, settings).off('.crmForm');
|
||||
|
||||
// CRM-14353 - Warn of unsaved changes for all forms except those which have opted out
|
||||
function cancelAction() {
|
||||
var dirty = CRM.utils.initialValueChanged($('form:not([data-warn-changes=false])', widget));
|
||||
widget.attr('data-unsaved-changes', dirty ? 'true' : 'false');
|
||||
if (dirty) {
|
||||
var id = widget.attr('id') + '-unsaved-alert',
|
||||
title = widget.dialog('option', 'title'),
|
||||
alert = CRM.alert('<p>' + ts('%1 has not been saved.', {1: title}) + '</p><p><a href="#" id="' + id + '">' + ts('Restore') + '</a></p>', ts('Unsaved Changes'), 'alert unsaved-dialog', {expires: 60000});
|
||||
$('#' + id).button({icons: {primary: 'fa-undo'}}).click(function(e) {
|
||||
widget.attr('data-unsaved-changes', 'false').dialog('open');
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.data('uiDialog')) widget.on('dialogbeforeclose', function(e) {
|
||||
// CRM-14353 - Warn unsaved changes if user clicks close button or presses "esc"
|
||||
if (e.originalEvent) {
|
||||
cancelAction();
|
||||
}
|
||||
});
|
||||
|
||||
widget.on('crmFormLoad.crmForm', function(event, data) {
|
||||
var $el = $(this).attr('data-unsaved-changes', 'false'),
|
||||
settings = $el.crmSnippet('option', 'crmForm');
|
||||
if (settings.cancelButton) $(settings.cancelButton, this).click(function(e) {
|
||||
e.preventDefault();
|
||||
var returnVal = settings.onCancel.call($el, e);
|
||||
if (returnVal !== false) {
|
||||
$el.trigger('crmFormCancel', e);
|
||||
if ($el.data('uiDialog') && settings.autoClose) {
|
||||
cancelAction();
|
||||
$el.dialog('close');
|
||||
}
|
||||
else if (!settings.autoClose) {
|
||||
$el.crmSnippet('resetUrl').crmSnippet('refresh');
|
||||
}
|
||||
}
|
||||
});
|
||||
if (settings.validate) {
|
||||
$("form", this).crmValidate();
|
||||
}
|
||||
$("form:not('[data-no-ajax-submit=true]')", this).ajaxForm($.extend({
|
||||
url: data.url.replace(/reset=1[&]?/, ''),
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.content === undefined) {
|
||||
$el.trigger('crmFormSuccess', response);
|
||||
// Reset form for e.g. "save and new"
|
||||
if (response.userContext && (response.status === 'redirect' || (settings.refreshAction && $.inArray(response.buttonName, settings.refreshAction) >= 0))) {
|
||||
// Force reset of original url
|
||||
$el.data('civiCrmSnippet')._originalUrl = response.userContext;
|
||||
$el.crmSnippet('resetUrl').crmSnippet('refresh');
|
||||
}
|
||||
// Close if we are on the original url or the action was "delete" (in which case returning to view may be inappropriate)
|
||||
else if ($el.data('uiDialog') && (settings.autoClose || response.action === 8)) {
|
||||
$el.dialog('close');
|
||||
}
|
||||
else if (settings.autoClose === false) {
|
||||
$el.crmSnippet('resetUrl').crmSnippet('refresh');
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($el.crmSnippet('option', 'block')) $el.unblock();
|
||||
response.url = data.url;
|
||||
$el.html(response.content).trigger('crmLoad', response).trigger('crmFormLoad', response);
|
||||
if (response.status === 'form_error') {
|
||||
formErrors = [];
|
||||
$el.trigger('crmFormError', response);
|
||||
$.each(response.errors || [], function(formElement, msg) {
|
||||
formErrors.push($('[name="'+formElement+'"]', $el).crmError(msg));
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeSubmit: function(submission) {
|
||||
$.each(formErrors, function() {
|
||||
if (this && this.close) this.close();
|
||||
});
|
||||
if ($el.crmSnippet('option', 'block')) $el.block();
|
||||
$el.trigger('crmFormSubmit', submission);
|
||||
}
|
||||
}, settings.ajaxForm));
|
||||
if (settings.openInline) {
|
||||
settings.autoClose = $el.crmSnippet('isOriginalUrl');
|
||||
$(this).off('.openInline').on('click.openInline', settings.openInline, function(e) {
|
||||
if ($(this).is(exclude + ', .crm-popup')) {
|
||||
return;
|
||||
}
|
||||
if ($(this).hasClass('open-inline-noreturn')) {
|
||||
// Force reset of original url
|
||||
$el.data('civiCrmSnippet')._originalUrl = $(this).attr('href');
|
||||
}
|
||||
$el.crmSnippet('option', 'url', $(this).attr('href')).crmSnippet('refresh');
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
if ($el.data('uiDialog')) {
|
||||
// Show form buttons as part of the dialog
|
||||
var buttonContainers = '.crm-submit-buttons, .action-link',
|
||||
buttons = [],
|
||||
added = [];
|
||||
$(buttonContainers, $el).find('input.crm-form-submit, a.button').each(function() {
|
||||
var $el = $(this),
|
||||
label = $el.is('input') ? $el.attr('value') : $el.text(),
|
||||
identifier = $el.attr('name') || $el.attr('href');
|
||||
if (!identifier || identifier === '#' || $.inArray(identifier, added) < 0) {
|
||||
var $icon = $el.find('.icon, .crm-i'),
|
||||
button = {'data-identifier': identifier, text: label, click: function() {
|
||||
$el[0].click();
|
||||
}};
|
||||
if ($icon.length) {
|
||||
button.icons = {primary: $icon.attr('class')};
|
||||
} else {
|
||||
var action = $el.attr('crm-icon') || ($el.hasClass('cancel') ? 'fa-times' : 'fa-check');
|
||||
button.icons = {primary: action};
|
||||
}
|
||||
buttons.push(button);
|
||||
added.push(identifier);
|
||||
}
|
||||
// display:none causes the form to not submit when pressing "enter"
|
||||
$el.parents(buttonContainers).css({height: 0, padding: 0, margin: 0, overflow: 'hidden'}).find('.crm-button-icon').hide();
|
||||
});
|
||||
$el.dialog('option', 'buttons', buttons);
|
||||
}
|
||||
// Allow a button to prevent ajax submit
|
||||
$('input[data-no-ajax-submit=true]').click(function() {
|
||||
$(this).closest('form').ajaxFormUnbind();
|
||||
});
|
||||
// For convenience, focus the first field
|
||||
$('input[type=text], textarea, select', this).filter(':visible').first().not('.dateplugin').focus();
|
||||
});
|
||||
return widget;
|
||||
};
|
||||
/**
|
||||
* Handler for jQuery click event e.g. $('a').click(CRM.popup);
|
||||
*/
|
||||
CRM.popup = function(e) {
|
||||
var $el = $(this).first(),
|
||||
url = $el.attr('href'),
|
||||
popup = $el.data('popup-type') === 'page' ? CRM.loadPage : CRM.loadForm,
|
||||
settings = $el.data('popup-settings') || {},
|
||||
formData = false;
|
||||
settings.dialog = settings.dialog || {};
|
||||
if (e.isDefaultPrevented() || !CRM.config.ajaxPopupsEnabled || !url || $el.is(exclude + ', .open-inline, .open-inline-noreturn')) {
|
||||
return;
|
||||
}
|
||||
// Sized based on css class
|
||||
if ($el.hasClass('small-popup')) {
|
||||
settings.dialog.width = 400;
|
||||
settings.dialog.height = 300;
|
||||
}
|
||||
else if ($el.hasClass('medium-popup')) {
|
||||
settings.dialog.width = settings.dialog.height = '50%';
|
||||
}
|
||||
var dialog = popup(url, settings);
|
||||
// Trigger events from the dialog on the original link element
|
||||
$el.trigger('crmPopupOpen', [dialog]);
|
||||
// Listen for success events and buffer them so we only trigger once
|
||||
dialog.on('crmFormSuccess.crmPopup crmPopupFormSuccess.crmPopup', function(e, data) {
|
||||
formData = data;
|
||||
});
|
||||
dialog.on('dialogclose.crmPopup', function(e, data) {
|
||||
if (formData) {
|
||||
$el.trigger('crmPopupFormSuccess', [dialog, formData]);
|
||||
}
|
||||
$el.trigger('crmPopupClose', [dialog, data]);
|
||||
});
|
||||
e.preventDefault();
|
||||
};
|
||||
/**
|
||||
* An event callback for CRM.popup or a standalone function to refresh the content around a given element
|
||||
* @param e {event|selector}
|
||||
*/
|
||||
CRM.refreshParent = function(e) {
|
||||
// Use e.target if input smells like an event, otherwise assume it's a jQuery selector
|
||||
var $el = (e.stopPropagation && e.target) ? $(e.target) : $(e),
|
||||
$table = $el.closest('.dataTable');
|
||||
// Call native refresh method on ajax datatables
|
||||
if ($table.length && $.fn.DataTable.fnIsDataTable($table[0]) && $table.dataTable().fnSettings().sAjaxSource) {
|
||||
// Refresh ALL datatables - needed for contact relationship tab
|
||||
$.each($.fn.dataTable.fnTables(), function() {
|
||||
if ($(this).dataTable().fnSettings().sAjaxSource) $(this).unblock().dataTable().fnDraw();
|
||||
});
|
||||
}
|
||||
// Otherwise refresh the nearest crmSnippet
|
||||
else {
|
||||
$el.closest('.crm-ajax-container, #crm-main-content-wrapper').crmSnippet().crmSnippet('refresh');
|
||||
}
|
||||
};
|
||||
|
||||
$(function($) {
|
||||
$('body')
|
||||
.on('click', 'a.crm-popup', CRM.popup)
|
||||
// Close unsaved dialog messages
|
||||
.on('dialogopen', function(e) {
|
||||
$('.alert.unsaved-dialog .ui-notify-cross', '#crm-notification-container').click();
|
||||
})
|
||||
// Destroy old unsaved dialog
|
||||
.on('dialogcreate', function(e) {
|
||||
$('.ui-dialog-content.crm-ajax-container:hidden[data-unsaved-changes=true]').crmSnippet('destroy').dialog('destroy').remove();
|
||||
})
|
||||
// Ensure wysiwyg content is updated prior to ajax submit
|
||||
.on('form-pre-serialize', function(e) {
|
||||
$('.crm-wysiwyg-enabled', e.target).each(function() {
|
||||
CRM.wysiwyg.updateElement(this);
|
||||
});
|
||||
})
|
||||
// Auto-resize dialogs when loading content
|
||||
.on('crmLoad dialogopen', 'div.ui-dialog.ui-resizable.crm-container', function(e) {
|
||||
var
|
||||
$wrapper = $(this),
|
||||
$dialog = $wrapper.children('.ui-dialog-content');
|
||||
// small delay to allow contents to render
|
||||
window.setTimeout(function() {
|
||||
var currentHeight = $wrapper.outerHeight(),
|
||||
padding = currentHeight - $dialog.height(),
|
||||
newHeight = $dialog.prop('scrollHeight') + padding,
|
||||
menuHeight = $('#civicrm-menu').outerHeight(),
|
||||
maxHeight = $(window).height() - menuHeight;
|
||||
newHeight = newHeight > maxHeight ? maxHeight : newHeight;
|
||||
if (newHeight > (currentHeight + 15)) {
|
||||
$dialog.dialog('option', {
|
||||
position: {my: 'center', at: 'center center+' + (menuHeight / 2), of: window},
|
||||
height: newHeight
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
|
||||
}(jQuery, CRM));
|
7
sites/all/modules/civicrm/js/crm.angular.js
Normal file
7
sites/all/modules/civicrm/js/crm.angular.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
(function (angular, $, _) {
|
||||
// DEPRECATED: A variant of angular.module() which uses a dependency list provided by the server.
|
||||
// REMOVE circa v4.7.22.
|
||||
angular.crmDepends = function crmDepends(name) {
|
||||
return angular.module(name, CRM.angRequires(name));
|
||||
};
|
||||
})(angular, CRM.$, CRM._);
|
572
sites/all/modules/civicrm/js/crm.backbone.js
Normal file
572
sites/all/modules/civicrm/js/crm.backbone.js
Normal file
|
@ -0,0 +1,572 @@
|
|||
(function($, _) {
|
||||
if (!CRM.Backbone) CRM.Backbone = {};
|
||||
|
||||
/**
|
||||
* Backbone.sync provider which uses CRM.api() for I/O.
|
||||
* To support CRUD operations, model classes must be defined with a "crmEntityName" property.
|
||||
* To load collections using API queries, set the "crmCriteria" property or override the
|
||||
* method "toCrmCriteria".
|
||||
*
|
||||
* @param method Accepts normal Backbone.sync methods; also accepts "crm-replace"
|
||||
* @param model
|
||||
* @param options
|
||||
* @see tests/qunit/crm-backbone
|
||||
*/
|
||||
CRM.Backbone.sync = function(method, model, options) {
|
||||
var isCollection = _.isArray(model.models);
|
||||
|
||||
var apiOptions, params;
|
||||
if (isCollection) {
|
||||
apiOptions = {
|
||||
success: function(data) {
|
||||
// unwrap data
|
||||
options.success(_.toArray(data.values));
|
||||
},
|
||||
error: function(data) {
|
||||
// CRM.api displays errors by default, but Backbone.sync
|
||||
// protocol requires us to override "error". This restores
|
||||
// the default behavior.
|
||||
$().crmError(data.error_message, ts('Error'));
|
||||
options.error(data);
|
||||
}
|
||||
};
|
||||
switch (method) {
|
||||
case 'read':
|
||||
CRM.api(model.crmEntityName, model.toCrmAction('get'), model.toCrmCriteria(), apiOptions);
|
||||
break;
|
||||
// replace all entities matching "x.crmCriteria" with new entities in "x.models"
|
||||
case 'crm-replace':
|
||||
params = this.toCrmCriteria();
|
||||
params.version = 3;
|
||||
params.values = this.toJSON();
|
||||
CRM.api(model.crmEntityName, model.toCrmAction('replace'), params, apiOptions);
|
||||
break;
|
||||
default:
|
||||
apiOptions.error({is_error: 1, error_message: "CRM.Backbone.sync(" + method + ") not implemented for collections"});
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// callback options to pass to CRM.api
|
||||
apiOptions = {
|
||||
success: function(data) {
|
||||
// unwrap data
|
||||
var values = _.toArray(data.values);
|
||||
if (data.count == 1) {
|
||||
options.success(values[0]);
|
||||
} else {
|
||||
data.is_error = 1;
|
||||
data.error_message = ts("Expected exactly one response");
|
||||
apiOptions.error(data);
|
||||
}
|
||||
},
|
||||
error: function(data) {
|
||||
// CRM.api displays errors by default, but Backbone.sync
|
||||
// protocol requires us to override "error". This restores
|
||||
// the default behavior.
|
||||
$().crmError(data.error_message, ts('Error'));
|
||||
options.error(data);
|
||||
}
|
||||
};
|
||||
switch (method) {
|
||||
case 'create': // pass-through
|
||||
case 'update':
|
||||
params = model.toJSON();
|
||||
if (!params.options) params.options = {};
|
||||
params.options.reload = 1;
|
||||
if (!model._isDuplicate) {
|
||||
CRM.api(model.crmEntityName, model.toCrmAction('create'), params, apiOptions);
|
||||
} else {
|
||||
CRM.api(model.crmEntityName, model.toCrmAction('duplicate'), params, apiOptions);
|
||||
}
|
||||
break;
|
||||
case 'read':
|
||||
case 'delete':
|
||||
var apiAction = (method == 'delete') ? 'delete' : 'get';
|
||||
params = model.toCrmCriteria();
|
||||
if (!params.id) {
|
||||
apiOptions.error({is_error: 1, error_message: 'Missing ID for ' + model.crmEntityName});
|
||||
return;
|
||||
}
|
||||
CRM.api(model.crmEntityName, model.toCrmAction(apiAction), params, apiOptions);
|
||||
break;
|
||||
default:
|
||||
apiOptions.error({is_error: 1, error_message: "CRM.Backbone.sync(" + method + ") not implemented for models"});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect a "model" class to CiviCRM's APIv3
|
||||
*
|
||||
* @code
|
||||
* // Setup class
|
||||
* var ContactModel = Backbone.Model.extend({});
|
||||
* CRM.Backbone.extendModel(ContactModel, "Contact");
|
||||
*
|
||||
* // Use class
|
||||
* c = new ContactModel({id: 3});
|
||||
* c.fetch();
|
||||
* @endcode
|
||||
*
|
||||
* @param Class ModelClass
|
||||
* @param string crmEntityName APIv3 entity name, such as "Contact" or "CustomField"
|
||||
* @see tests/qunit/crm-backbone
|
||||
*/
|
||||
CRM.Backbone.extendModel = function(ModelClass, crmEntityName) {
|
||||
// Defaults - if specified in ModelClass, preserve
|
||||
_.defaults(ModelClass.prototype, {
|
||||
crmEntityName: crmEntityName,
|
||||
crmActions: {}, // map: string backboneActionName => string serverSideActionName
|
||||
crmReturn: null, // array: list of fields to return
|
||||
toCrmAction: function(action) {
|
||||
return this.crmActions[action] ? this.crmActions[action] : action;
|
||||
},
|
||||
toCrmCriteria: function() {
|
||||
var result = (this.get('id')) ? {id: this.get('id')} : {};
|
||||
if (!_.isEmpty(this.crmReturn)) {
|
||||
result.return = this.crmReturn;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
duplicate: function() {
|
||||
var newModel = new ModelClass(this.toJSON());
|
||||
newModel._isDuplicate = true;
|
||||
if (newModel.setModified) newModel.setModified();
|
||||
newModel.listenTo(newModel, 'sync', function(){
|
||||
// may get called on subsequent resaves -- don't care!
|
||||
delete newModel._isDuplicate;
|
||||
});
|
||||
return newModel;
|
||||
}
|
||||
});
|
||||
// Overrides - if specified in ModelClass, replace
|
||||
_.extend(ModelClass.prototype, {
|
||||
sync: CRM.Backbone.sync
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Configure a model class to track whether a model has unsaved changes.
|
||||
*
|
||||
* Methods:
|
||||
* - setModified() - flag the model as modified/dirty
|
||||
* - isSaved() - return true if there have been no changes to the data since the last fetch or save
|
||||
* Events:
|
||||
* - saved(object model, bool is_saved) - triggered whenever isSaved() value would change
|
||||
*
|
||||
* Note: You should not directly call isSaved() within the context of the success/error/sync callback;
|
||||
* I haven't found a way to make isSaved() behave correctly within these callbacks without patching
|
||||
* Backbone. Instead, attach an event listener to the 'saved' event.
|
||||
*
|
||||
* @param ModelClass
|
||||
*/
|
||||
CRM.Backbone.trackSaved = function(ModelClass) {
|
||||
// Retain references to some of the original class's functions
|
||||
var Parent = _.pick(ModelClass.prototype, 'initialize', 'save', 'fetch');
|
||||
|
||||
// Private callback
|
||||
var onSyncSuccess = function() {
|
||||
this._modified = false;
|
||||
if (this._oldModified.length > 0) {
|
||||
this._oldModified.pop();
|
||||
}
|
||||
this.trigger('saved', this, this.isSaved());
|
||||
};
|
||||
var onSaveError = function() {
|
||||
if (this._oldModified.length > 0) {
|
||||
this._modified = this._oldModified.pop();
|
||||
this.trigger('saved', this, this.isSaved());
|
||||
}
|
||||
};
|
||||
|
||||
// Defaults - if specified in ModelClass, preserve
|
||||
_.defaults(ModelClass.prototype, {
|
||||
isSaved: function() {
|
||||
var result = !this.isNew() && !this.isModified();
|
||||
return result;
|
||||
},
|
||||
isModified: function() {
|
||||
return this._modified;
|
||||
},
|
||||
_saved_onchange: function(model, options) {
|
||||
if (options.parse) return;
|
||||
// console.log('change', model.changedAttributes(), model.previousAttributes());
|
||||
this.setModified();
|
||||
},
|
||||
setModified: function() {
|
||||
var oldModified = this._modified;
|
||||
this._modified = true;
|
||||
if (!oldModified) {
|
||||
this.trigger('saved', this, this.isSaved());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Overrides - if specified in ModelClass, replace
|
||||
_.extend(ModelClass.prototype, {
|
||||
initialize: function(options) {
|
||||
this._modified = false;
|
||||
this._oldModified = [];
|
||||
this.listenTo(this, 'change', this._saved_onchange);
|
||||
this.listenTo(this, 'error', onSaveError);
|
||||
this.listenTo(this, 'sync', onSyncSuccess);
|
||||
if (Parent.initialize) {
|
||||
return Parent.initialize.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
save: function() {
|
||||
// we'll assume success
|
||||
this._oldModified.push(this._modified);
|
||||
return Parent.save.apply(this, arguments);
|
||||
},
|
||||
fetch: function() {
|
||||
this._oldModified.push(this._modified);
|
||||
return Parent.fetch.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Configure a model class to support client-side soft deletion.
|
||||
* One can call "model.setDeleted(BOOLEAN)" to flag an entity for
|
||||
* deletion (or not) -- however, deletion will be deferred until save()
|
||||
* is called.
|
||||
*
|
||||
* Methods:
|
||||
* setSoftDeleted(boolean) - flag the model as deleted (or not-deleted)
|
||||
* isSoftDeleted() - determine whether model has been soft-deleted
|
||||
* Events:
|
||||
* softDelete(model, is_deleted) -- change value of is_deleted
|
||||
*
|
||||
* @param ModelClass
|
||||
*/
|
||||
CRM.Backbone.trackSoftDelete = function(ModelClass) {
|
||||
// Retain references to some of the original class's functions
|
||||
var Parent = _.pick(ModelClass.prototype, 'save');
|
||||
|
||||
// Defaults - if specified in ModelClass, preserve
|
||||
_.defaults(ModelClass.prototype, {
|
||||
is_soft_deleted: false,
|
||||
setSoftDeleted: function(is_deleted) {
|
||||
if (this.is_soft_deleted != is_deleted) {
|
||||
this.is_soft_deleted = is_deleted;
|
||||
this.trigger('softDelete', this, is_deleted);
|
||||
if (this.setModified) this.setModified(); // FIXME: ugly interaction, trackSoftDelete-trackSaved
|
||||
}
|
||||
},
|
||||
isSoftDeleted: function() {
|
||||
return this.is_soft_deleted;
|
||||
}
|
||||
});
|
||||
|
||||
// Overrides - if specified in ModelClass, replace
|
||||
_.extend(ModelClass.prototype, {
|
||||
save: function(attributes, options) {
|
||||
if (this.isSoftDeleted()) {
|
||||
return this.destroy(options);
|
||||
} else {
|
||||
return Parent.save.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect a "collection" class to CiviCRM's APIv3
|
||||
*
|
||||
* Note: the collection supports a special property, crmCriteria, which is an array of
|
||||
* query options to send to the API.
|
||||
*
|
||||
* @code
|
||||
* // Setup class
|
||||
* var ContactModel = Backbone.Model.extend({});
|
||||
* CRM.Backbone.extendModel(ContactModel, "Contact");
|
||||
* var ContactCollection = Backbone.Collection.extend({
|
||||
* model: ContactModel
|
||||
* });
|
||||
* CRM.Backbone.extendCollection(ContactCollection);
|
||||
*
|
||||
* // Use class (with passive criteria)
|
||||
* var c = new ContactCollection([], {
|
||||
* crmCriteria: {contact_type: 'Organization'}
|
||||
* });
|
||||
* c.fetch();
|
||||
* c.get(123).set('property', 'value');
|
||||
* c.get(456).setDeleted(true);
|
||||
* c.save();
|
||||
*
|
||||
* // Use class (with active criteria)
|
||||
* var criteriaModel = new SomeModel({
|
||||
* contact_type: 'Organization'
|
||||
* });
|
||||
* var c = new ContactCollection([], {
|
||||
* crmCriteriaModel: criteriaModel
|
||||
* });
|
||||
* c.fetch();
|
||||
* c.get(123).set('property', 'value');
|
||||
* c.get(456).setDeleted(true);
|
||||
* c.save();
|
||||
* @endcode
|
||||
*
|
||||
*
|
||||
* @param Class CollectionClass
|
||||
* @see tests/qunit/crm-backbone
|
||||
*/
|
||||
CRM.Backbone.extendCollection = function(CollectionClass) {
|
||||
var origInit = CollectionClass.prototype.initialize;
|
||||
// Defaults - if specified in CollectionClass, preserve
|
||||
_.defaults(CollectionClass.prototype, {
|
||||
crmEntityName: CollectionClass.prototype.model.prototype.crmEntityName,
|
||||
crmActions: {}, // map: string backboneActionName => string serverSideActionName
|
||||
toCrmAction: function(action) {
|
||||
return this.crmActions[action] ? this.crmActions[action] : action;
|
||||
},
|
||||
toCrmCriteria: function() {
|
||||
var result = (this.crmCriteria) ? _.extend({}, this.crmCriteria) : {};
|
||||
if (!_.isEmpty(this.crmReturn)) {
|
||||
result.return = this.crmReturn;
|
||||
} else if (this.model && !_.isEmpty(this.model.prototype.crmReturn)) {
|
||||
result.return = this.model.prototype.crmReturn;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an object which represents this collection's criteria
|
||||
* as a live model. Any changes to the model will be applied
|
||||
* to the collection, and the collection will be refreshed.
|
||||
*
|
||||
* @param criteriaModelClass
|
||||
*/
|
||||
setCriteriaModel: function(criteriaModel) {
|
||||
var collection = this;
|
||||
this.crmCriteria = criteriaModel.toJSON();
|
||||
this.listenTo(criteriaModel, 'change', function() {
|
||||
collection.crmCriteria = criteriaModel.toJSON();
|
||||
collection.debouncedFetch();
|
||||
});
|
||||
},
|
||||
|
||||
debouncedFetch: _.debounce(function() {
|
||||
this.fetch({reset: true});
|
||||
}, 100),
|
||||
|
||||
/**
|
||||
* Reconcile the server's collection with the client's collection.
|
||||
* New/modified items from the client will be saved/updated on the
|
||||
* server. Deleted items from the client will be deleted on the
|
||||
* server.
|
||||
*
|
||||
* @param Object options - accepts "success" and "error" callbacks
|
||||
*/
|
||||
save: function(options) {
|
||||
if (!options) options = {};
|
||||
var collection = this;
|
||||
var success = options.success;
|
||||
options.success = function(resp) {
|
||||
// Ensure attributes are restored during synchronous saves.
|
||||
collection.reset(resp, options);
|
||||
if (success) success(collection, resp, options);
|
||||
// collection.trigger('sync', collection, resp, options);
|
||||
};
|
||||
wrapError(collection, options);
|
||||
|
||||
return this.sync('crm-replace', this, options);
|
||||
}
|
||||
});
|
||||
// Overrides - if specified in CollectionClass, replace
|
||||
_.extend(CollectionClass.prototype, {
|
||||
sync: CRM.Backbone.sync,
|
||||
initialize: function(models, options) {
|
||||
if (!options) options = {};
|
||||
if (options.crmCriteriaModel) {
|
||||
this.setCriteriaModel(options.crmCriteriaModel);
|
||||
} else if (options.crmCriteria) {
|
||||
this.crmCriteria = options.crmCriteria;
|
||||
}
|
||||
if (options.crmActions) {
|
||||
this.crmActions = _.extend(this.crmActions, options.crmActions);
|
||||
}
|
||||
if (origInit) {
|
||||
return origInit.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
toJSON: function() {
|
||||
var result = [];
|
||||
// filter models list, excluding any soft-deleted items
|
||||
this.each(function(model) {
|
||||
// if model doesn't track soft-deletes
|
||||
// or if model tracks soft-deletes and wasn't soft-deleted
|
||||
if (!model.isSoftDeleted || !model.isSoftDeleted()) {
|
||||
result.push(model.toJSON());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find a single record, or create a new record.
|
||||
*
|
||||
* @param Object options:
|
||||
* - CollectionClass: class
|
||||
* - crmCriteria: Object values to search/default on
|
||||
* - defaults: Object values to put on newly created model (if needed)
|
||||
* - success: function(model)
|
||||
* - error: function(collection, error)
|
||||
*/
|
||||
CRM.Backbone.findCreate = function(options) {
|
||||
if (!options) options = {};
|
||||
var collection = new options.CollectionClass([], {
|
||||
crmCriteria: options.crmCriteria
|
||||
});
|
||||
collection.fetch({
|
||||
success: function(collection) {
|
||||
if (collection.length === 0) {
|
||||
var attrs = _.extend({}, collection.crmCriteria, options.defaults || {});
|
||||
var model = collection._prepareModel(attrs, options);
|
||||
options.success(model);
|
||||
} else if (collection.length == 1) {
|
||||
options.success(collection.first());
|
||||
} else {
|
||||
options.error(collection, {
|
||||
is_error: 1,
|
||||
error_message: 'Too many matches'
|
||||
});
|
||||
}
|
||||
},
|
||||
error: function(collection, errorData) {
|
||||
if (options.error) {
|
||||
options.error(collection, errorData);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
CRM.Backbone.Model = Backbone.Model.extend({
|
||||
/**
|
||||
* Return JSON version of model -- but only include fields that are
|
||||
* listed in the 'schema'.
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
toStrictJSON: function() {
|
||||
var schema = this.schema;
|
||||
var result = this.toJSON();
|
||||
_.each(result, function(value, key) {
|
||||
if (!schema[key]) {
|
||||
delete result[key];
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
setRel: function(key, value, options) {
|
||||
this.rels = this.rels || {};
|
||||
if (this.rels[key] != value) {
|
||||
this.rels[key] = value;
|
||||
this.trigger("rel:" + key, value);
|
||||
}
|
||||
},
|
||||
getRel: function(key) {
|
||||
return this.rels ? this.rels[key] : null;
|
||||
}
|
||||
});
|
||||
|
||||
CRM.Backbone.Collection = Backbone.Collection.extend({
|
||||
/**
|
||||
* Store 'key' on this.rel and automatically copy it to
|
||||
* any children.
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @param initialModels
|
||||
*/
|
||||
initializeCopyToChildrenRelation: function(key, value, initialModels) {
|
||||
this.setRel(key, value, {silent: true});
|
||||
this.on('reset', this._copyToChildren, this);
|
||||
this.on('add', this._copyToChild, this);
|
||||
},
|
||||
_copyToChildren: function() {
|
||||
var collection = this;
|
||||
collection.each(function(model) {
|
||||
collection._copyToChild(model);
|
||||
});
|
||||
},
|
||||
_copyToChild: function(model) {
|
||||
_.each(this.rels, function(relValue, relKey) {
|
||||
model.setRel(relKey, relValue, {silent: true});
|
||||
});
|
||||
},
|
||||
setRel: function(key, value, options) {
|
||||
this.rels = this.rels || {};
|
||||
if (this.rels[key] != value) {
|
||||
this.rels[key] = value;
|
||||
this.trigger("rel:" + key, value);
|
||||
}
|
||||
},
|
||||
getRel: function(key) {
|
||||
return this.rels ? this.rels[key] : null;
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
CRM.Backbone.Form = Backbone.Form.extend({
|
||||
validate: function() {
|
||||
// Add support for form-level validators
|
||||
var errors = Backbone.Form.prototype.validate.apply(this, []) || {};
|
||||
var self = this;
|
||||
if (this.validators) {
|
||||
_.each(this.validators, function(validator) {
|
||||
var modelErrors = validator(this.getValue());
|
||||
|
||||
// The following if() has been copied-pasted from the parent's
|
||||
// handling of model-validators. They are similar in that the errors are
|
||||
// probably keyed by field names... but not necessarily, so we use _others
|
||||
// as a fallback.
|
||||
if (modelErrors) {
|
||||
var isDictionary = _.isObject(modelErrors) && !_.isArray(modelErrors);
|
||||
|
||||
//If errors are not in object form then just store on the error object
|
||||
if (!isDictionary) {
|
||||
errors._others = errors._others || [];
|
||||
errors._others.push(modelErrors);
|
||||
}
|
||||
|
||||
//Merge programmatic errors (requires model.validate() to return an object e.g. { fieldKey: 'error' })
|
||||
if (isDictionary) {
|
||||
_.each(modelErrors, function(val, key) {
|
||||
//Set error on field if there isn't one already
|
||||
if (self.fields[key] && !errors[key]) {
|
||||
self.fields[key].setError(val);
|
||||
errors[key] = val;
|
||||
}
|
||||
|
||||
else {
|
||||
//Otherwise add to '_others' key
|
||||
errors._others = errors._others || [];
|
||||
var tmpErr = {};
|
||||
tmpErr[key] = val;
|
||||
errors._others.push(tmpErr);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
return _.isEmpty(errors) ? null : errors;
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
// Wrap an optional error callback with a fallback error event.
|
||||
var wrapError = function (model, options) {
|
||||
var error = options.error;
|
||||
options.error = function(resp) {
|
||||
if (error) error(model, resp, optio);
|
||||
model.trigger('error', model, resp, options);
|
||||
};
|
||||
};
|
||||
})(CRM.$, CRM._);
|
10
sites/all/modules/civicrm/js/crm.backdrop.js
Normal file
10
sites/all/modules/civicrm/js/crm.backdrop.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
// http://civicrm.org/licensing
|
||||
CRM.$(function($) {
|
||||
$('#admin-bar').css('display', 'none');
|
||||
$('.crm-hidemenu').click(function(e) {
|
||||
$('#admin-bar').css('display', 'block');
|
||||
});
|
||||
$('#crm-notification-container').on('click', '#crm-restore-menu', function() {
|
||||
$('#admin-bar').css('display', 'none');
|
||||
});
|
||||
});
|
33
sites/all/modules/civicrm/js/crm.designerapp.js
Normal file
33
sites/all/modules/civicrm/js/crm.designerapp.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
(function ($, _) {
|
||||
$(function () {
|
||||
/**
|
||||
* FIXME we depend on this being a global singleton, mainly to facilitate vents
|
||||
*
|
||||
* vents:
|
||||
* - resize: the size/position of widgets should be adjusted
|
||||
* - ufUnsaved: any part of a UFGroup was changed; args: (is_changed:bool)
|
||||
* - formOpened: a toggleable form (such as a UFFieldView or a UFGroupView) has been opened
|
||||
*/
|
||||
CRM.designerApp = new Backbone.Marionette.Application();
|
||||
|
||||
/**
|
||||
* FIXME: Workaround for problem that having more than one instance
|
||||
* of a profile on the page will result in duplicate DOM ids.
|
||||
* @see CRM-12188
|
||||
*/
|
||||
CRM.designerApp.clearPreviewArea = function () {
|
||||
$('.crm-profile-selector-preview-pane > *').each(function () {
|
||||
var parent = $(this).parent();
|
||||
CRM.designerApp.DetachedProfiles.push({
|
||||
parent: parent,
|
||||
item: $(this).detach()
|
||||
});
|
||||
});
|
||||
};
|
||||
CRM.designerApp.restorePreviewArea = function () {
|
||||
$.each(CRM.designerApp.DetachedProfiles, function () {
|
||||
$(this.parent).append(this.item);
|
||||
});
|
||||
};
|
||||
});
|
||||
})(CRM.$, CRM._);
|
22
sites/all/modules/civicrm/js/crm.drupal.js
Normal file
22
sites/all/modules/civicrm/js/crm.drupal.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
// http://civicrm.org/licensing
|
||||
CRM.$(function($) {
|
||||
$(document)
|
||||
.on('dialogopen', function(e) {
|
||||
// D7 hack to get the toolbar out of the way (CRM-15341)
|
||||
$('#toolbar').css('z-index', '100');
|
||||
})
|
||||
.on('dialogclose', function(e) {
|
||||
if ($('.ui-dialog-content:visible').not(e.target).length < 1) {
|
||||
// D7 hack, restore toolbar position (CRM-15341)
|
||||
$('#toolbar').css('z-index', '');
|
||||
}
|
||||
})
|
||||
// d8 Hack to hide title when it should be (CRM-19960)
|
||||
.ready(function() {
|
||||
var pageTitle = $('.page-title');
|
||||
var title = $('.page-title').text();
|
||||
if ('<span id="crm-remove-title" style="display:none">CiviCRM</span>' == title) {
|
||||
pageTitle.hide();
|
||||
}
|
||||
});
|
||||
});
|
19
sites/all/modules/civicrm/js/crm.expandRow.js
Normal file
19
sites/all/modules/civicrm/js/crm.expandRow.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
// http://civicrm.org/licensing
|
||||
CRM.$(function($) {
|
||||
$('body')
|
||||
.off('.crmExpandRow')
|
||||
.on('click.crmExpandRow', 'a.crm-expand-row', function(e) {
|
||||
var $row = $(this).closest('tr');
|
||||
if ($(this).hasClass('expanded')) {
|
||||
$row.next('.crm-child-row').children('td').children('div.crm-ajax-container')
|
||||
.slideUp('fast', function() {$(this).closest('.crm-child-row').remove();});
|
||||
} else {
|
||||
var count = $('td', $row).length,
|
||||
$newRow = $('<tr class="crm-child-row"><td colspan="' + count + '"><div></div></td></tr>')
|
||||
.insertAfter($row);
|
||||
CRM.loadPage(this.href, {target: $('div', $newRow).animate({minHeight: '3em'}, 'fast')});
|
||||
}
|
||||
$(this).toggleClass('expanded');
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
52
sites/all/modules/civicrm/js/crm.insert-shortcode.js
Executable file
52
sites/all/modules/civicrm/js/crm.insert-shortcode.js
Executable file
|
@ -0,0 +1,52 @@
|
|||
// https://civicrm.org/licensing
|
||||
|
||||
CRM.$(function($) {
|
||||
var $form = $('form.CRM_Core_Form_ShortCode');
|
||||
|
||||
function changeComponent() {
|
||||
var component = $(this).val(),
|
||||
entities = $(this).data('entities');
|
||||
|
||||
$('.shortcode-param[data-components]', $form).each(function() {
|
||||
$(this).toggle($.inArray(component, $(this).data('components')) > -1);
|
||||
|
||||
if (entities[component]) {
|
||||
$('input[name=entity]')
|
||||
.val('')
|
||||
.data('key', entities[component].key)
|
||||
.data('select-params', null)
|
||||
.data('api-params', null)
|
||||
.crmEntityRef(entities[component]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function close() {
|
||||
$form.closest('.ui-dialog-content').dialog('close');
|
||||
}
|
||||
|
||||
function insert() {
|
||||
var code = '[civicrm';
|
||||
$('.shortcode-param:visible', $form).each(function() {
|
||||
var $el = $('input:checked, select, input.crm-form-entityref', this);
|
||||
code += ' ' + $el.data('key') + '="' + $el.val() + '"';
|
||||
});
|
||||
window.send_to_editor(code + ']');
|
||||
close();
|
||||
}
|
||||
|
||||
$('select[name=component]', $form).each(changeComponent).change(changeComponent);
|
||||
|
||||
$form.closest('.ui-dialog-content').dialog('option', 'buttons', [
|
||||
{
|
||||
text: ts("Insert"),
|
||||
icons: {primary: "fa-check"},
|
||||
click: insert
|
||||
},
|
||||
{
|
||||
text: ts("Cancel"),
|
||||
icons: {primary: "fa-times"},
|
||||
click: close
|
||||
}
|
||||
]);
|
||||
});
|
14
sites/all/modules/civicrm/js/crm.joomla.js
Normal file
14
sites/all/modules/civicrm/js/crm.joomla.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
// http://civicrm.org/licensing
|
||||
CRM.$(function($) {
|
||||
$(document)
|
||||
.on('dialogopen', function(e) {
|
||||
// J3 - Make footer admin bar hide behind popup windows (CRM-15723)
|
||||
$('#status').css('z-index', '100');
|
||||
})
|
||||
.on('dialogclose', function(e) {
|
||||
if ($('.ui-dialog-content:visible').not(e.target).length < 1) {
|
||||
// J3 - restore footer admin bar position (CRM-15723)
|
||||
$('#status').css('z-index', '');
|
||||
}
|
||||
});
|
||||
});
|
12
sites/all/modules/civicrm/js/crm.livePage.js
Normal file
12
sites/all/modules/civicrm/js/crm.livePage.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
// http://civicrm.org/licensing
|
||||
// Adds ajaxy behavior to a simple CiviCRM page
|
||||
CRM.$(function($) {
|
||||
var active = 'a.button, a.action-item:not(.crm-enable-disable), a.crm-popup';
|
||||
$('#crm-main-content-wrapper')
|
||||
// Widgetize the content area
|
||||
.crmSnippet()
|
||||
// Open action links in a popup
|
||||
.off('.crmLivePage')
|
||||
.on('click.crmLivePage', active, CRM.popup)
|
||||
.on('crmPopupFormSuccess.crmLivePage', active, CRM.refreshParent);
|
||||
});
|
26
sites/all/modules/civicrm/js/crm.multilingual.js
Normal file
26
sites/all/modules/civicrm/js/crm.multilingual.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
// http://civicrm.org/licensing
|
||||
// JS needed for multilingual installations
|
||||
CRM.$(function($) {
|
||||
// This is partially redundant with what the CRM.popup function would do,
|
||||
// with a small amount of added functionality,
|
||||
// and the difference that this loads unconditionally regardless of ajaxPopupsEnabled setting
|
||||
$('body').on('click', 'a.crm-multilingual-edit-button', function(e) {
|
||||
var $el = $(this),
|
||||
$form = $el.closest('form'),
|
||||
$field = $('#' + $el.data('field'), $form);
|
||||
|
||||
CRM.loadForm($el.attr('href'), {
|
||||
dialog: {width: '50%', height: '50%'}
|
||||
})
|
||||
// Sync the primary language field with what the user has entered on the main form
|
||||
.on('crmFormLoad', function() {
|
||||
$('.default-lang', this).val($field.val());
|
||||
})
|
||||
.on('crmFormSubmit', function() {
|
||||
// Sync the primary language field with what the user has entered in the popup
|
||||
$field.val($('.default-lang', this).val());
|
||||
$el.trigger('crmPopupFormSuccess');
|
||||
});
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
16
sites/all/modules/civicrm/js/crm.optionEdit.js
Normal file
16
sites/all/modules/civicrm/js/crm.optionEdit.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
// https://civicrm.org/licensing
|
||||
jQuery(function($) {
|
||||
$('body')
|
||||
// Enable administrators to edit option lists in a dialog
|
||||
.on('click', 'a.crm-option-edit-link', CRM.popup)
|
||||
.on('crmPopupFormSuccess', 'a.crm-option-edit-link', function() {
|
||||
$(this).trigger('crmOptionsEdited');
|
||||
var $elects = $('select[data-option-edit-path="' + $(this).data('option-edit-path') + '"]');
|
||||
if ($elects.data('api-entity') && $elects.data('api-field')) {
|
||||
CRM.api3($elects.data('api-entity'), 'getoptions', {sequential: 1, field: $elects.data('api-field')})
|
||||
.done(function (data) {
|
||||
CRM.utils.setOptions($elects, data.values);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
204
sites/all/modules/civicrm/js/crm.searchForm.js
Normal file
204
sites/all/modules/civicrm/js/crm.searchForm.js
Normal file
|
@ -0,0 +1,204 @@
|
|||
// http://civicrm.org/licensing
|
||||
(function($, _, undefined) {
|
||||
"use strict";
|
||||
var selected = 0,
|
||||
form = 'form.crm-search-form',
|
||||
active = 'a.action-item:not(.crm-enable-disable), a.crm-popup';
|
||||
|
||||
function clearTaskMenu() {
|
||||
$('select#task', form).val('').select2('val', '').prop('disabled', true).select2('disable');
|
||||
}
|
||||
|
||||
function enableTaskMenu() {
|
||||
if (selected || $('[name=radio_ts][value=ts_all]', form).is(':checked')) {
|
||||
$('select#task', form).prop('disabled', false).select2('enable');
|
||||
}
|
||||
}
|
||||
|
||||
function displayCount() {
|
||||
$('label[for*=ts_sel] span', form).text(selected);
|
||||
}
|
||||
|
||||
function countCheckboxes() {
|
||||
return $('input.select-row:checked', form).length;
|
||||
}
|
||||
|
||||
function clearSelections(e) {
|
||||
/* jshint validthis: true */
|
||||
if (selected) {
|
||||
var $form = $(this).closest('form');
|
||||
$('input.select-row, input.select-rows', $form).prop('checked', false).closest('tr').removeClass('crm-row-selected');
|
||||
if (usesAjax()) {
|
||||
phoneHome(false, $(this));
|
||||
} else {
|
||||
selected = 0;
|
||||
displayCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function usesAjax() {
|
||||
return $(form).hasClass('crm-ajax-selection-form');
|
||||
}
|
||||
|
||||
// Use ajax to store selection server-side
|
||||
function phoneHome(single, $el, event) {
|
||||
var url = CRM.url('civicrm/ajax/markSelection');
|
||||
var params = {qfKey: 'civicrm search ' + $('input[name=qfKey]', form).val()};
|
||||
if (!$el.is(':checked') || $el.is('input[name=radio_ts][value=ts_all]')) {
|
||||
params.action = 'unselect';
|
||||
params.state = 'unchecked';
|
||||
}
|
||||
if (single) {
|
||||
params.name = $el.attr('id');
|
||||
} else {
|
||||
params.variableType = 'multiple';
|
||||
// "Reset all" button
|
||||
if ($el.is('a')) {
|
||||
event.preventDefault();
|
||||
$("input.select-row, input.select-rows", form).prop('checked', false).closest('tr').removeClass('crm-row-selected');
|
||||
}
|
||||
// Master checkbox
|
||||
else if ($el.hasClass('select-rows')) {
|
||||
params.name = $('input.select-row').map(function() {return $(this).attr('id');}).get().join('-');
|
||||
}
|
||||
}
|
||||
$.post(url, params, function(data) {
|
||||
if (data && data.getCount !== undefined) {
|
||||
selected = data.getCount;
|
||||
displayCount();
|
||||
enableTaskMenu();
|
||||
}
|
||||
}, 'json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the current page
|
||||
*/
|
||||
function refresh() {
|
||||
// Clear cached search results using force=1 argument
|
||||
var location = $('#crm-main-content-wrapper').crmSnippet().crmSnippet('option', 'url');
|
||||
if (!(location.match(/[?&]force=1/))) {
|
||||
location += '&force=1';
|
||||
}
|
||||
$('#crm-main-content-wrapper').crmSnippet({url: location}).crmSnippet('refresh');
|
||||
}
|
||||
|
||||
/**
|
||||
* When initially loading and reloading (paging) the results
|
||||
*/
|
||||
function initForm() {
|
||||
clearTaskMenu();
|
||||
if (usesAjax()) {
|
||||
selected = parseInt($('label[for*=ts_sel] span', form).text(), 10);
|
||||
} else {
|
||||
selected = countCheckboxes();
|
||||
displayCount();
|
||||
}
|
||||
enableTaskMenu();
|
||||
}
|
||||
|
||||
$(function() {
|
||||
initForm();
|
||||
|
||||
// Focus first search field
|
||||
$('.crm-form-text:input:visible:first', 'form.crm-search-form').focus();
|
||||
|
||||
// Handle user interactions with search results
|
||||
$('#crm-container')
|
||||
// When toggling between "all records" and "selected records only"
|
||||
.on('change', '[name=radio_ts]', function() {
|
||||
clearTaskMenu();
|
||||
enableTaskMenu();
|
||||
})
|
||||
.on('click', 'input[name=radio_ts][value=ts_all]', clearSelections)
|
||||
// When making a selection
|
||||
.on('click', 'input.select-row, input.select-rows, a.crm-selection-reset', function(event) {
|
||||
var $el = $(this),
|
||||
$form = $el.closest('form'),
|
||||
single = $el.is('input.select-row');
|
||||
clearTaskMenu();
|
||||
$('input[name=radio_ts][value=ts_sel]', $form).prop('checked', true);
|
||||
if (!usesAjax()) {
|
||||
if (single) {
|
||||
selected = countCheckboxes();
|
||||
} else {
|
||||
selected = $el.is(':checked') ? $('input.select-row', $form).length : 0;
|
||||
}
|
||||
displayCount();
|
||||
enableTaskMenu();
|
||||
} else {
|
||||
phoneHome(single, $el, event);
|
||||
}
|
||||
})
|
||||
// When selecting a task
|
||||
.on('change', 'select#task', function(e) {
|
||||
var $form = $(this).closest('form'),
|
||||
$go = $('input.crm-search-go-button', $form);
|
||||
var $selectedOption = $(this).find(':selected');
|
||||
if (!$selectedOption.val()) {
|
||||
// do not blank refresh the empty option.
|
||||
return;
|
||||
}
|
||||
if ($selectedOption.data('is_confirm')) {
|
||||
var confirmed = false;
|
||||
var refresh_fields = $selectedOption.data('confirm_refresh_fields');
|
||||
var $message = '<tr>' + (($selectedOption.data('confirm_message') !== undefined) ? $selectedOption.data('confirm_message') : '') + '</tr>';
|
||||
if (refresh_fields === undefined) {
|
||||
refresh_fields = {};
|
||||
}
|
||||
$.each(refresh_fields, function (refreshIndex, refreshValue) {
|
||||
var $refresh_field = $(refreshValue.selector);
|
||||
var prependText = (refreshValue.prepend !== undefined) ? refreshValue.prepend : '';
|
||||
var existingInput = $refresh_field.find('input').val();
|
||||
$message = $message + '<tr>' + $refresh_field.html().replace(existingInput, prependText + existingInput) + '</tr>';
|
||||
});
|
||||
|
||||
CRM.confirm({
|
||||
title: $selectedOption.data('confirm_title') ? $selectedOption.data('confirm_title') : ts('Confirm action'),
|
||||
message: '<table class="form-layout">' + $message + '</table>'
|
||||
})
|
||||
.on('crmConfirm:yes', function() {
|
||||
confirmed = true;
|
||||
$.each(refresh_fields, function (refreshIndex, refreshValue) {
|
||||
$('#' + refreshIndex).val($('.crm-confirm #' + refreshIndex).val());
|
||||
});
|
||||
$go.click();
|
||||
})
|
||||
.on('crmConfirm:no', function() {
|
||||
$('#task').val('').change();
|
||||
return;
|
||||
});
|
||||
}
|
||||
else if (!$(this).find(':selected').data('supports_modal')) {
|
||||
$go.click();
|
||||
$('#task').val('').select2('val', '');
|
||||
}
|
||||
// The following code can load the task in a popup, however not all tasks function correctly with this
|
||||
// So it's a per-task opt-in mechanism.
|
||||
else {
|
||||
var data = $form.serialize() + '&' + $go.attr('name') + '=' + $go.attr('value');
|
||||
var url = $form.attr('action');
|
||||
url += (url.indexOf('?') < 0 ? '?' : '&') + 'snippet=json';
|
||||
clearTaskMenu();
|
||||
$.post(url, data, function(data) {
|
||||
CRM.loadForm(data.userContext).on('crmFormSuccess', refresh);
|
||||
enableTaskMenu();
|
||||
}, 'json');
|
||||
}
|
||||
});
|
||||
|
||||
// Add a specialized version of livepage functionality
|
||||
$('#crm-main-content-wrapper')
|
||||
.on('crmLoad', function(e) {
|
||||
if ($(e.target).is(this)) {
|
||||
initForm();
|
||||
}
|
||||
})
|
||||
// Open action links in a popup
|
||||
.off('.crmLivePage')
|
||||
.on('click.crmLivePage', active, CRM.popup)
|
||||
.on('crmPopupFormSuccess.crmLivePage', active, refresh);
|
||||
});
|
||||
|
||||
})(CRM.$, CRM._);
|
14
sites/all/modules/civicrm/js/crm.wordpress.js
Normal file
14
sites/all/modules/civicrm/js/crm.wordpress.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
// http://civicrm.org/licensing
|
||||
CRM.$(function($) {
|
||||
$(document)
|
||||
.on('dialogopen', function(e) {
|
||||
// Make admin bar hide behind popup windows
|
||||
$('#adminmenuwrap').css('z-index', '100');
|
||||
})
|
||||
.on('dialogclose', function(e) {
|
||||
if ($('.ui-dialog-content:visible').not(e.target).length < 1) {
|
||||
// Restore admin bar position
|
||||
$('#adminmenuwrap').css('z-index', '');
|
||||
}
|
||||
});
|
||||
});
|
59
sites/all/modules/civicrm/js/jquery/jquery.crmAjaxTable.js
Normal file
59
sites/all/modules/civicrm/js/jquery/jquery.crmAjaxTable.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
// https://civicrm.org/licensing
|
||||
(function($, _) {
|
||||
"use strict";
|
||||
/* jshint validthis: true */
|
||||
|
||||
$.fn.crmAjaxTable = function() {
|
||||
|
||||
// Strip the ids from ajax urls to make pageLength storage more generic
|
||||
function simplifyUrl(ajax) {
|
||||
// Datatables ajax prop could be a url string or an object containing the url
|
||||
var url = typeof ajax === 'object' ? ajax.url : ajax;
|
||||
return typeof url === 'string' ? url.replace(/[&?]\w*id=\d+/g, '') : null;
|
||||
}
|
||||
|
||||
return $(this).each(function() {
|
||||
// Recall pageLength for this table
|
||||
var url = simplifyUrl($(this).data('ajax'));
|
||||
if (url && window.localStorage && localStorage['dataTablePageLength:' + url]) {
|
||||
$(this).data('pageLength', localStorage['dataTablePageLength:' + url]);
|
||||
}
|
||||
// Declare the defaults for DataTables
|
||||
var defaults = {
|
||||
"processing": true,
|
||||
"serverSide": true,
|
||||
"order": [],
|
||||
"dom": '<"crm-datatable-pager-top"lfp>rt<"crm-datatable-pager-bottom"ip>',
|
||||
"pageLength": 25,
|
||||
"pagingType": "full_numbers",
|
||||
"drawCallback": function(settings) {
|
||||
//Add data attributes to cells
|
||||
$('thead th', settings.nTable).each( function( index ) {
|
||||
$.each(this.attributes, function() {
|
||||
if(this.name.match("^cell-")) {
|
||||
var cellAttr = this.name.substring(5);
|
||||
var cellValue = this.value;
|
||||
$('tbody tr', settings.nTable).each( function() {
|
||||
$('td:eq('+ index +')', this).attr( cellAttr, cellValue );
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
//Reload table after draw
|
||||
$(settings.nTable).trigger('crmLoad');
|
||||
}
|
||||
};
|
||||
//Include any table specific data
|
||||
var settings = $.extend(true, defaults, $(this).data('table'));
|
||||
// Remember pageLength
|
||||
$(this).on('length.dt', function(e, settings, len) {
|
||||
if (settings.ajax && window.localStorage) {
|
||||
localStorage['dataTablePageLength:' + simplifyUrl(settings.ajax)] = len;
|
||||
}
|
||||
});
|
||||
//Make the DataTables call
|
||||
$(this).DataTable(settings);
|
||||
});
|
||||
};
|
||||
|
||||
})(CRM.$, CRM._);
|
244
sites/all/modules/civicrm/js/jquery/jquery.crmEditable.js
Normal file
244
sites/all/modules/civicrm/js/jquery/jquery.crmEditable.js
Normal file
|
@ -0,0 +1,244 @@
|
|||
// https://civicrm.org/licensing
|
||||
(function($, _) {
|
||||
"use strict";
|
||||
/* jshint validthis: true */
|
||||
|
||||
// TODO: We'll need a way to clear this cache if options are edited.
|
||||
// Maybe it should be stored in the CRM object so other parts of the app can use it.
|
||||
// Note that if we do move it, we should also change the format of option lists to our standard sequential arrays
|
||||
var optionsCache = {};
|
||||
|
||||
/**
|
||||
* Helper fn to retrieve semantic data from markup
|
||||
*/
|
||||
$.fn.crmEditableEntity = function() {
|
||||
var
|
||||
el = this[0],
|
||||
ret = {},
|
||||
$row = this.first().closest('.crm-entity');
|
||||
ret.entity = $row.data('entity') || $row[0].id.split('-')[0];
|
||||
ret.id = $row.data('id') || $row[0].id.split('-')[1];
|
||||
ret.action = $row.data('action') || 'create';
|
||||
|
||||
if (!ret.entity || !ret.id) {
|
||||
return false;
|
||||
}
|
||||
$('.crm-editable, [data-field]', $row).each(function() {
|
||||
var fieldName = $(this).data('field') || this.className.match(/crmf-(\S*)/)[1];
|
||||
if (fieldName) {
|
||||
ret[fieldName] = $(this).text();
|
||||
if (this === el) {
|
||||
ret.field = fieldName;
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* @see http://wiki.civicrm.org/confluence/display/CRMDOC/Structure+convention+for+automagic+edit+in+place
|
||||
*/
|
||||
$.fn.crmEditable = function(options) {
|
||||
function checkable() {
|
||||
$(this).off('.crmEditable').on('change.crmEditable', function() {
|
||||
var $el = $(this),
|
||||
info = $el.crmEditableEntity();
|
||||
if (!info.field) {
|
||||
return false;
|
||||
}
|
||||
var params = {
|
||||
sequential: 1,
|
||||
id: info.id,
|
||||
field: info.field,
|
||||
value: $el.is(':checked') ? 1 : 0
|
||||
};
|
||||
CRM.api3(info.entity, info.action, params, true);
|
||||
});
|
||||
}
|
||||
|
||||
return this.each(function() {
|
||||
var $i,
|
||||
fieldName = "",
|
||||
defaults = {
|
||||
error: function(entity, field, value, data) {
|
||||
restoreContainer();
|
||||
$(this).html(originalValue || settings.placeholder).click();
|
||||
var msg = $.isPlainObject(data) && data.error_message;
|
||||
errorMsg = $(':input', this).first().crmError(msg || ts('Sorry an error occurred and your information was not saved'), ts('Error'));
|
||||
},
|
||||
success: function(entity, field, value, data, settings) {
|
||||
restoreContainer();
|
||||
if ($i.data('refresh')) {
|
||||
CRM.refreshParent($i);
|
||||
} else {
|
||||
value = value === '' ? settings.placeholder : _.escape(value);
|
||||
$i.html(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
originalValue = '',
|
||||
errorMsg,
|
||||
editableSettings = $.extend({}, defaults, options);
|
||||
|
||||
if ($(this).hasClass('crm-editable-enabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.nodeName == "INPUT" && this.type == "checkbox") {
|
||||
checkable.call(this, this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Table cell needs something inside it to look right
|
||||
if ($(this).is('td')) {
|
||||
$(this)
|
||||
.removeClass('crm-editable')
|
||||
.wrapInner('<div class="crm-editable" />');
|
||||
$i = $('div.crm-editable', this)
|
||||
.data($(this).data());
|
||||
var field = this.className.match(/crmf-(\S*)/);
|
||||
if (field) {
|
||||
$i.data('field', field[1]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$i = $(this);
|
||||
}
|
||||
|
||||
var settings = {
|
||||
tooltip: $i.data('tooltip') || ts('Click to edit'),
|
||||
placeholder: $i.data('placeholder') || '<i class="crm-i fa-pencil crm-editable-placeholder"></i>',
|
||||
onblur: 'cancel',
|
||||
cancel: '<button type="cancel"><i class="crm-i fa-times"></i></button>',
|
||||
submit: '<button type="submit"><i class="crm-i fa-check"></i></button>',
|
||||
cssclass: 'crm-editable-form',
|
||||
data: getData,
|
||||
onreset: restoreContainer
|
||||
};
|
||||
if ($i.data('type')) {
|
||||
settings.type = $i.data('type');
|
||||
if (settings.type == 'boolean') {
|
||||
settings.type = 'select';
|
||||
$i.data('options', {'0': ts('No'), '1': ts('Yes')});
|
||||
}
|
||||
}
|
||||
if (settings.type == 'textarea') {
|
||||
$i.addClass('crm-editable-textarea-enabled');
|
||||
}
|
||||
$i.addClass('crm-editable-enabled');
|
||||
|
||||
function callback(value, settings) {
|
||||
$i.addClass('crm-editable-saving');
|
||||
var
|
||||
info = $i.crmEditableEntity(),
|
||||
$el = $($i),
|
||||
params = {},
|
||||
action = $i.data('action') || info.action;
|
||||
if (!info.field) {
|
||||
return false;
|
||||
}
|
||||
if (info.id && info.id !== 'new') {
|
||||
params.id = info.id;
|
||||
}
|
||||
if (action === 'setvalue') {
|
||||
params.field = info.field;
|
||||
params.value = value;
|
||||
}
|
||||
else {
|
||||
params[info.field] = value;
|
||||
}
|
||||
CRM.api3(info.entity, action, params, {error: null})
|
||||
.done(function(data) {
|
||||
if (data.is_error) {
|
||||
return editableSettings.error.call($el[0], info.entity, info.field, value, data);
|
||||
}
|
||||
if ($el.data('options')) {
|
||||
value = $el.data('options')[value] || '';
|
||||
}
|
||||
else if ($el.data('optionsHashKey')) {
|
||||
var options = optionsCache[$el.data('optionsHashKey')];
|
||||
value = options && options[value] ? options[value] : '';
|
||||
}
|
||||
$el.trigger('crmFormSuccess', [value]);
|
||||
editableSettings.success.call($el[0], info.entity, info.field, value, data, settings);
|
||||
})
|
||||
.fail(function(data) {
|
||||
editableSettings.error.call($el[0], info.entity, info.field, value, data);
|
||||
});
|
||||
}
|
||||
|
||||
CRM.loadScript(CRM.config.resourceBase + 'packages/jquery/plugins/jquery.jeditable.min.js').done(function() {
|
||||
$i.editable(callback, settings);
|
||||
});
|
||||
|
||||
// CRM-15759 - Workaround broken textarea handling in jeditable 1.7.1
|
||||
$i.click(function() {
|
||||
$('textarea', this).off()
|
||||
// Fix cancel-on-blur
|
||||
.on('blur', function(e) {
|
||||
if (!e.relatedTarget || !$(e.relatedTarget).is('.crm-editable-form button')) {
|
||||
$i.find('button[type=cancel]').click();
|
||||
}
|
||||
})
|
||||
// Add support for ctrl-enter shortcut key
|
||||
.on('keydown', function (e) {
|
||||
if (e.ctrlKey && e.keyCode == 13) {
|
||||
$i.find('button[type=submit]').click();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function getData(value, settings) {
|
||||
// Add css class to wrapper
|
||||
// FIXME: This should be a response to an event instead of coupled with this function but jeditable 1.7.1 doesn't trigger any events :(
|
||||
$i.addClass('crm-editable-editing');
|
||||
|
||||
originalValue = value;
|
||||
|
||||
if ($i.data('type') == 'select' || $i.data('type') == 'boolean') {
|
||||
if ($i.data('options')) {
|
||||
return formatOptions($i.data('options'));
|
||||
}
|
||||
var result,
|
||||
info = $i.crmEditableEntity(),
|
||||
// Strip extra id from multivalued custom fields
|
||||
custom = info.field.match(/(custom_\d+)_\d+/),
|
||||
field = custom ? custom[1] : info.field,
|
||||
hash = info.entity + '.' + field,
|
||||
params = {
|
||||
field: field,
|
||||
context: 'create'
|
||||
};
|
||||
$i.data('optionsHashKey', hash);
|
||||
if (!optionsCache[hash]) {
|
||||
$.ajax({
|
||||
url: CRM.url('civicrm/ajax/rest'),
|
||||
data: {entity: info.entity, action: 'getoptions', json: JSON.stringify(params)},
|
||||
async: false, // jeditable lacks support for async options lookup
|
||||
success: function(data) {optionsCache[hash] = data.values;}
|
||||
});
|
||||
}
|
||||
return formatOptions(optionsCache[hash]);
|
||||
}
|
||||
// Unwrap contents then replace html special characters with plain text
|
||||
return _.unescape(value.replace(/<(?:.|\n)*?>/gm, ''));
|
||||
}
|
||||
|
||||
function formatOptions(options) {
|
||||
if (typeof $i.data('emptyOption') === 'string') {
|
||||
// Using 'null' because '' is broken in jeditable 1.7.1
|
||||
return $.extend({'null': $i.data('emptyOption')}, options);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function restoreContainer() {
|
||||
if (errorMsg && errorMsg.close) errorMsg.close();
|
||||
$i.removeClass('crm-editable-saving crm-editable-editing');
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
})(jQuery, CRM._);
|
120
sites/all/modules/civicrm/js/jquery/jquery.crmIconPicker.js
Normal file
120
sites/all/modules/civicrm/js/jquery/jquery.crmIconPicker.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
// https://civicrm.org/licensing
|
||||
(function($, _) {
|
||||
"use strict";
|
||||
/* jshint validthis: true */
|
||||
var icons = [], loaded;
|
||||
|
||||
$.fn.crmIconPicker = function() {
|
||||
|
||||
function loadIcons() {
|
||||
if (!loaded) {
|
||||
loaded = $.Deferred();
|
||||
CRM.$.get(CRM.config.resourceBase + 'bower_components/font-awesome/css/font-awesome.css').done(function(data) {
|
||||
var match,
|
||||
regex = /\.(fa-[-a-zA-Z0-9]+):before {/g;
|
||||
while((match = regex.exec(data)) !== null) {
|
||||
icons.push(match[1]);
|
||||
}
|
||||
loaded.resolve();
|
||||
});
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
return this.each(function() {
|
||||
if ($(this).hasClass('iconpicker-widget')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $input = $(this),
|
||||
$button = $('<a class="crm-icon-picker-button" href="#" />').button().removeClass('ui-corner-all').attr('title', $input.attr('title')),
|
||||
$style = $('<select class="crm-form-select"></select>'),
|
||||
options = [
|
||||
{key: 'fa-rotate-90', value: ts('Rotate right')},
|
||||
{key: 'fa-rotate-270', value: ts('Rotate left')},
|
||||
{key: 'fa-rotate-180', value: ts('Rotate 180')},
|
||||
{key: 'fa-flip-horizontal', value: ts('Flip horizontal')},
|
||||
{key: 'fa-flip-vertical', value: ts('Flip vertical')}
|
||||
];
|
||||
|
||||
function formatButton() {
|
||||
var val = $input.val().replace('fa ', '');
|
||||
val = val.replace('crm-i ', '');
|
||||
var split = val.split(' ');
|
||||
$button.button('option', {
|
||||
label: split[0] || ts('None'),
|
||||
icons: {primary: val ? val : 'fa-'}
|
||||
});
|
||||
$style.toggle(!!split[0]).val(split[1] || '');
|
||||
}
|
||||
|
||||
$input.hide().addClass('iconpicker-widget').after($style).after(' ').after($button).change(formatButton);
|
||||
|
||||
CRM.utils.setOptions($style, options, ts('Normal'));
|
||||
|
||||
formatButton();
|
||||
|
||||
$style.change(function() {
|
||||
if ($input.val()) {
|
||||
var split = $input.val().split(' '),
|
||||
style = $style.val();
|
||||
$input.val(split[0] + (style ? ' ' + style : '')).change();
|
||||
}
|
||||
});
|
||||
|
||||
$button.click(function(e) {
|
||||
var dialog;
|
||||
|
||||
function displayIcons() {
|
||||
var term = $('input[name=search]', dialog).val().replace(/-/g, '').toLowerCase(),
|
||||
$place = $('div.icons', dialog).html('');
|
||||
$.each(icons, function(i, icon) {
|
||||
if (!term.length || icon.replace(/-/g, '').indexOf(term) > -1) {
|
||||
var item = $('<a href="#" title="' + icon + '"/>').button({
|
||||
icons: {primary: icon}
|
||||
});
|
||||
$place.append(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function displayDialog() {
|
||||
dialog.append('<style type="text/css">' +
|
||||
'#crmIconPicker {font-size: 2em;}' +
|
||||
'#crmIconPicker .icon-search input {font-family: FontAwesome; padding-left: .5em; margin-bottom: 1em;}' +
|
||||
'#crmIconPicker a.ui-button {width: 2em; height: 2em; color: #222;}' +
|
||||
'#crmIconPicker a.ui-button .ui-icon {margin-top: -0.5em; width: auto; height: auto;}' +
|
||||
'</style>' +
|
||||
'<div class="icon-search"><input class="crm-form-text" name="search" placeholder=""/></div>' +
|
||||
'<div class="icons"></div>'
|
||||
);
|
||||
displayIcons();
|
||||
dialog.unblock();
|
||||
}
|
||||
|
||||
function pickIcon(e) {
|
||||
var newIcon = $(this).attr('title'),
|
||||
style = $style.val();
|
||||
$input.val(newIcon + (style ? ' ' + style : '')).change();
|
||||
dialog.dialog('close');
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
dialog = $('<div id="crmIconPicker"/>').dialog({
|
||||
title: $input.attr('title'),
|
||||
width: '80%',
|
||||
height: 400,
|
||||
modal: true
|
||||
}).block()
|
||||
.on('click', 'a', pickIcon)
|
||||
.on('keyup', 'input[name=search]', displayIcons)
|
||||
.on('dialogclose', function() {
|
||||
$(this).dialog('destroy').remove();
|
||||
});
|
||||
loadIcons().done(displayDialog);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
}(CRM.$, CRM._));
|
|
@ -0,0 +1,99 @@
|
|||
(function($, _) {
|
||||
var ufGroupCollection = new CRM.UF.UFGroupCollection(_.sortBy(CRM.initialProfileList.values, 'title'));
|
||||
//var ufGroupCollection = new CRM.UF.UFGroupCollection(CRM.initialProfileList.values, {
|
||||
// comparator: 'title' // no point, this doesn't work with subcollections
|
||||
//});
|
||||
ufGroupCollection.unshift(new CRM.UF.UFGroupModel({
|
||||
id: '',
|
||||
title: ts('- select -')
|
||||
}));
|
||||
|
||||
/**
|
||||
* Example:
|
||||
* <input type="text" value="{$profileId}" class="crm-profile-selector" />
|
||||
* ...
|
||||
* cj('.crm-profile-selector').crmProfileSelector({
|
||||
* groupTypeFilter: "Contact,Individual,Activity;;ActivityType:7",
|
||||
* entities: "contact_1:IndividualModel,activity_1:ActivityModel"
|
||||
* });
|
||||
*
|
||||
* Note: The system does not currently support dynamic entities -- it only supports
|
||||
* a couple of entities named "contact_1" and "activity_1". See also
|
||||
* CRM.UF.guessEntityName().
|
||||
*/
|
||||
$.fn.crmProfileSelector = function(options) {
|
||||
return this.each(function() {
|
||||
// Hide the existing <SELECT> and instead construct a ProfileSelector view.
|
||||
// Keep them synchronized.
|
||||
var matchingUfGroups,
|
||||
$select = $(this).hide().addClass('rendered');
|
||||
|
||||
var validTypesId = [];
|
||||
var usedByFilter = null;
|
||||
if (options.groupTypeFilter) {
|
||||
matchingUfGroups = ufGroupCollection.subcollection({
|
||||
filter: function(ufGroupModel) {
|
||||
//CRM-16915 - filter with module used by the profile
|
||||
if (options.usedByFilter && options.usedByFilter.length) {
|
||||
usedByFilter = options.usedByFilter;
|
||||
}
|
||||
return ufGroupModel.checkGroupType(options.groupTypeFilter, options.allowAllSubtypes, usedByFilter);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
matchingUfGroups = ufGroupCollection;
|
||||
}
|
||||
|
||||
//CRM-15427 check for valid subtypes raise a warning if not valid
|
||||
if (options.allowAllSubtypes && !validTypesId.length) {
|
||||
validTypes = ufGroupCollection.subcollection({
|
||||
filter: function(ufGroupModel) {
|
||||
return ufGroupModel.checkGroupType(options.groupTypeFilter);
|
||||
}
|
||||
});
|
||||
_.each(validTypes.models, function(validTypesattr) {
|
||||
validTypesId.push(validTypesattr.id);
|
||||
});
|
||||
}
|
||||
if (validTypesId.length && $.inArray($select.val(), validTypesId) == -1) {
|
||||
var civiComponent;
|
||||
if (options.groupTypeFilter.indexOf('Membership') !== -1) {
|
||||
civiComponent = 'Membership';
|
||||
}
|
||||
else if (options.groupTypeFilter.indexOf('Participant') !== -1) {
|
||||
civiComponent = 'Event';
|
||||
}
|
||||
else {
|
||||
civiComponent = 'Contribution';
|
||||
}
|
||||
CRM.alert(ts('The selected profile is using a custom field which is not assigned to the "%1" being configured.', {1: civiComponent}), ts('Warning'));
|
||||
}
|
||||
var view = new CRM.ProfileSelector.View({
|
||||
ufGroupId: $select.val(),
|
||||
ufGroupCollection: matchingUfGroups,
|
||||
ufEntities: options.entities
|
||||
});
|
||||
view.on('change:ufGroupId', function() {
|
||||
$select.val(view.getUfGroupId()).change();
|
||||
});
|
||||
view.render();
|
||||
$select.after(view.el);
|
||||
setTimeout(function() {
|
||||
view.doPreview();
|
||||
}, 100);
|
||||
});
|
||||
};
|
||||
|
||||
$('#crm-container').on('crmLoad', function() {
|
||||
$('.crm-profile-selector:not(.rendered)', this).each(function() {
|
||||
$(this).crmProfileSelector({
|
||||
groupTypeFilter: $(this).data('groupType'),
|
||||
entities: $(this).data('entities'),
|
||||
//CRM-15427
|
||||
allowAllSubtypes: $(this).data('default'),
|
||||
usedByFilter: $(this).data('usedfor')
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
})(CRM.$, CRM._);
|
|
@ -0,0 +1,50 @@
|
|||
(function($, CRM) {
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
*
|
||||
* cj('.my-link').crmRevisionLink({
|
||||
* 'reportId': 123, // CRM_Report_Utils_Report::getInstanceIDForValue('logging/contact/summary'),
|
||||
* 'tableName': 'my_table',
|
||||
* 'contactId': 123
|
||||
* ));
|
||||
*
|
||||
* Note: This file is used by CivHR
|
||||
*/
|
||||
$.fn.crmRevisionLink = function(options) {
|
||||
return this.each(function(){
|
||||
var $dialog = $('<div><div class="revision-content"></div></div>');
|
||||
$('body').append($dialog);
|
||||
$(this).on("click", function() {
|
||||
$dialog.show();
|
||||
$dialog.dialog({
|
||||
title: ts("Revisions"),
|
||||
modal: true,
|
||||
width: "680px",
|
||||
bgiframe: true,
|
||||
overlay: { opacity: 0.5, background: "black" },
|
||||
open:function() {
|
||||
var ajaxurl = CRM.url("civicrm/report/instance/" + options.reportId);
|
||||
cj.ajax({
|
||||
data: "reset=1&snippet=4§ion=2&altered_contact_id_op=eq&altered_contact_id_value="+options.contactId+"&log_type_table_op=has&log_type_table_value=" + options.tableName,
|
||||
url: ajaxurl,
|
||||
success: function (data) {
|
||||
$dialog.find(".revision-content").html(data);
|
||||
if (!$dialog.find(".revision-content .report-layout").length) {
|
||||
$dialog.find(".revision-content").html("Sorry, couldn't find any revisions.");
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
buttons: {
|
||||
"Done": function() {
|
||||
$(this).dialog("destroy");
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}); // this.each
|
||||
}; // fn.crmRevisionLink
|
||||
|
||||
})(jQuery, CRM);
|
580
sites/all/modules/civicrm/js/jquery/jquery.dashboard.js
Normal file
580
sites/all/modules/civicrm/js/jquery/jquery.dashboard.js
Normal file
|
@ -0,0 +1,580 @@
|
|||
// https://civicrm.org/licensing
|
||||
/* global CRM, ts */
|
||||
/*jshint loopfunc: true */
|
||||
(function($) {
|
||||
'use strict';
|
||||
// Constructor for dashboard object.
|
||||
$.fn.dashboard = function(options) {
|
||||
// Public properties of dashboard.
|
||||
var dashboard = {};
|
||||
dashboard.element = this.empty();
|
||||
dashboard.ready = false;
|
||||
dashboard.columns = [];
|
||||
dashboard.widgets = {};
|
||||
|
||||
/**
|
||||
* Public methods of dashboard.
|
||||
*/
|
||||
|
||||
// Saves the order of widgets for all columns including the widget.minimized status to options.ajaxCallbacks.saveColumns.
|
||||
dashboard.saveColumns = function(showStatus) {
|
||||
// Update the display status of the empty placeholders.
|
||||
$.each(dashboard.columns, function(c, col) {
|
||||
if ( typeof col == 'object' ) {
|
||||
// Are there any visible children of the column (excluding the empty placeholder)?
|
||||
if (col.element.children(':visible').not(col.emptyPlaceholder).length > 0) {
|
||||
col.emptyPlaceholder.hide();
|
||||
}
|
||||
else {
|
||||
col.emptyPlaceholder.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Don't save any changes to the server unless the dashboard has finished initiating.
|
||||
if (!dashboard.ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a list of params to post to the server.
|
||||
var params = {};
|
||||
|
||||
// For each column...
|
||||
$.each(dashboard.columns, function(c, col) {
|
||||
|
||||
// IDs of the sortable elements in this column.
|
||||
var ids = (typeof col == 'object') ? col.element.sortable('toArray') : [];
|
||||
|
||||
// For each id...
|
||||
$.each(ids, function(w, id) {
|
||||
if (typeof id == 'string') {
|
||||
// Chop 'widget-' off of the front so that we have the real widget id.
|
||||
id = id.substring('widget-'.length);
|
||||
// Add one flat property to the params object that will look like an array element to the PHP server.
|
||||
// Unfortunately jQuery doesn't do this for us.
|
||||
if (typeof dashboard.widgets[id] == 'object') params['columns[' + c + '][' + id + ']'] = (dashboard.widgets[id].minimized ? '1' : '0');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// The ajaxCallback settings overwrite any duplicate properties.
|
||||
$.extend(params, opts.ajaxCallbacks.saveColumns.data);
|
||||
var post = $.post(opts.ajaxCallbacks.saveColumns.url, params, function() {
|
||||
invokeCallback(opts.callbacks.saveColumns, dashboard);
|
||||
});
|
||||
if (showStatus !== false) {
|
||||
CRM.status({}, post);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Private properties of dashboard.
|
||||
*/
|
||||
|
||||
// Used to determine whether two resort events are resulting from the same UI event.
|
||||
var currentReSortEvent = null;
|
||||
|
||||
// Merge in the caller's options with the defaults.
|
||||
var opts = $.extend({}, $.fn.dashboard.defaults, options);
|
||||
|
||||
var localCache = window.localStorage && localStorage.dashboard ? JSON.parse(localStorage.dashboard) : {};
|
||||
|
||||
init(opts.widgetsByColumn);
|
||||
|
||||
return dashboard;
|
||||
|
||||
/**
|
||||
* Private methods of dashboard.
|
||||
*/
|
||||
|
||||
// Initialize widget columns.
|
||||
function init(widgets) {
|
||||
var markup = '<li class="empty-placeholder">' + opts.emptyPlaceholderInner + '</li>';
|
||||
|
||||
// Build the dashboard in the DOM. For each column...
|
||||
// (Don't iterate on widgets since this will break badly if the dataset has empty columns.)
|
||||
var emptyDashboard = true;
|
||||
for (var c = 0; c < opts.columns; c++) {
|
||||
// Save the column to both the public scope for external accessibility and the local scope for readability.
|
||||
var col = dashboard.columns[c] = {
|
||||
initialWidgets: [],
|
||||
element: $('<ul id="column-' + c + '" class="column column-' + c + '"></ul>').appendTo(dashboard.element)
|
||||
};
|
||||
|
||||
// Add the empty placeholder now, hide it and save it.
|
||||
col.emptyPlaceholder = $(markup).appendTo(col.element).hide();
|
||||
|
||||
// For each widget in this column.
|
||||
$.each(widgets[c], function(num, item) {
|
||||
var id = (num+1) + '-' + item.id;
|
||||
col.initialWidgets[id] = dashboard.widgets[item.id] = widget($.extend({
|
||||
element: $('<li class="widget"></li>').appendTo(col.element),
|
||||
initialColumn: col
|
||||
}, item));
|
||||
emptyDashboard = false;
|
||||
});
|
||||
}
|
||||
|
||||
if (emptyDashboard) {
|
||||
emptyDashboardCondition();
|
||||
} else {
|
||||
completeInit();
|
||||
}
|
||||
|
||||
invokeCallback(opts.callbacks.init, dashboard);
|
||||
}
|
||||
|
||||
// function that is called when dashboard is empty
|
||||
function emptyDashboardCondition( ) {
|
||||
$(".show-refresh").hide( );
|
||||
$("#empty-message").show( );
|
||||
}
|
||||
|
||||
// Cache dashlet info in localStorage
|
||||
function saveLocalCache() {
|
||||
localCache = {};
|
||||
$.each(dashboard.widgets, function(id, widget) {
|
||||
localCache[id] = {
|
||||
content: widget.content,
|
||||
lastLoaded: widget.lastLoaded,
|
||||
minimized: widget.minimized
|
||||
};
|
||||
});
|
||||
if (window.localStorage) {
|
||||
localStorage.dashboard = JSON.stringify(localCache);
|
||||
}
|
||||
}
|
||||
|
||||
// Contructors for each widget call this when initialization has finished so that dashboard can complete it's intitialization.
|
||||
function completeInit() {
|
||||
// Only do this once.
|
||||
if (dashboard.ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make widgets sortable across columns.
|
||||
dashboard.sortableElement = $('.column').sortable({
|
||||
connectWith: ['.column'],
|
||||
|
||||
// The class of the element by which widgets are draggable.
|
||||
handle: '.widget-header',
|
||||
|
||||
// The class of placeholder elements (the 'ghost' widget showing where the dragged item would land if released now.)
|
||||
placeholder: 'placeholder',
|
||||
activate: function(event, ui) {
|
||||
var h= $(ui.item).height();
|
||||
$('.placeholder').css('height', h +'px');
|
||||
},
|
||||
|
||||
opacity: 0.2,
|
||||
|
||||
// Maks sure that only widgets are sortable, and not empty placeholders.
|
||||
items: '> .widget',
|
||||
|
||||
forcePlaceholderSize: true,
|
||||
|
||||
// Callback functions.
|
||||
update: resorted,
|
||||
start: hideEmptyPlaceholders
|
||||
});
|
||||
|
||||
// Update empty placeholders.
|
||||
dashboard.saveColumns();
|
||||
dashboard.ready = true;
|
||||
invokeCallback(opts.callbacks.ready, dashboard);
|
||||
|
||||
// Auto-refresh widgets when content is stale
|
||||
window.setInterval(function() {
|
||||
if (!document.hasFocus || document.hasFocus()) {
|
||||
$.each(dashboard.widgets, function (i, widget) {
|
||||
if (!widget.cacheIsFresh()) {
|
||||
widget.reloadContent();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Callback for when any list has changed (and the user has finished resorting).
|
||||
function resorted(e, ui) {
|
||||
// Only do anything if we haven't already handled resorts based on changes from this UI DOM event.
|
||||
// (resorted() gets invoked once for each list when an item is moved from one to another.)
|
||||
if (!currentReSortEvent || e.originalEvent != currentReSortEvent) {
|
||||
currentReSortEvent = e.originalEvent;
|
||||
dashboard.saveColumns();
|
||||
}
|
||||
}
|
||||
|
||||
// Callback for when a user starts resorting a list. Hides all the empty placeholders.
|
||||
function hideEmptyPlaceholders(e, ui) {
|
||||
for (var c in dashboard.columns) {
|
||||
if( (typeof dashboard.columns[c]) == 'object' ) dashboard.columns[c].emptyPlaceholder.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// @todo use an event library to register, bind to and invoke events.
|
||||
// @param callback is a function.
|
||||
// @param theThis is the context given to that function when it executes. It becomes 'this' inside of that function.
|
||||
function invokeCallback(callback, theThis, parameterOne) {
|
||||
if (callback) {
|
||||
callback.call(theThis, parameterOne);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* widget object
|
||||
* Private sub-class of dashboard
|
||||
* Constructor starts
|
||||
*/
|
||||
function widget(widget) {
|
||||
// Merge default options with the options defined for this widget.
|
||||
widget = $.extend({}, $.fn.dashboard.widget.defaults, localCache[widget.id] || {}, widget);
|
||||
|
||||
/**
|
||||
* Public methods of widget.
|
||||
*/
|
||||
|
||||
// Toggles the minimize() & maximize() methods.
|
||||
widget.toggleMinimize = function() {
|
||||
if (widget.minimized) {
|
||||
widget.maximize();
|
||||
}
|
||||
else {
|
||||
widget.minimize();
|
||||
}
|
||||
|
||||
widget.hideSettings();
|
||||
};
|
||||
widget.minimize = function() {
|
||||
$('.widget-content', widget.element).slideUp(opts.animationSpeed);
|
||||
$(widget.controls.minimize.element)
|
||||
.addClass('fa-caret-right')
|
||||
.removeClass('fa-caret-down')
|
||||
.attr('title', ts('Expand'));
|
||||
widget.minimized = true;
|
||||
saveLocalCache();
|
||||
};
|
||||
widget.maximize = function() {
|
||||
$(widget.controls.minimize.element)
|
||||
.removeClass( 'fa-caret-right' )
|
||||
.addClass( 'fa-caret-down' )
|
||||
.attr('title', ts('Collapse'));
|
||||
widget.minimized = false;
|
||||
saveLocalCache();
|
||||
if (!widget.contentLoaded) {
|
||||
loadContent();
|
||||
}
|
||||
$('.widget-content', widget.element).slideDown(opts.animationSpeed);
|
||||
};
|
||||
|
||||
// Toggles whether the widget is in settings-display mode or not.
|
||||
widget.toggleSettings = function() {
|
||||
if (widget.settings.displayed) {
|
||||
// Widgets always exit settings into maximized state.
|
||||
widget.maximize();
|
||||
widget.hideSettings();
|
||||
invokeCallback(opts.widgetCallbacks.hideSettings, widget);
|
||||
}
|
||||
else {
|
||||
widget.minimize();
|
||||
widget.showSettings();
|
||||
invokeCallback(opts.widgetCallbacks.showSettings, widget);
|
||||
}
|
||||
};
|
||||
widget.showSettings = function() {
|
||||
if (widget.settings.element) {
|
||||
widget.settings.element.show();
|
||||
|
||||
// Settings are loaded via AJAX. Only execute the script if the settings have been loaded.
|
||||
if (widget.settings.ready) {
|
||||
getJavascript(widget.settings.script);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Settings have not been initialized. Do so now.
|
||||
initSettings();
|
||||
}
|
||||
widget.settings.displayed = true;
|
||||
};
|
||||
widget.hideSettings = function() {
|
||||
if (widget.settings.element) {
|
||||
widget.settings.element.hide();
|
||||
}
|
||||
widget.settings.displayed = false;
|
||||
};
|
||||
widget.saveSettings = function() {
|
||||
// Build list of parameters to POST to server.
|
||||
var params = {};
|
||||
// serializeArray() returns an array of objects. Process it.
|
||||
var fields = widget.settings.element.serializeArray();
|
||||
$.each(fields, function(i, field) {
|
||||
// Put the values into flat object properties that PHP will parse into an array server-side.
|
||||
// (Unfortunately jQuery doesn't do this)
|
||||
params['settings[' + field.name + ']'] = field.value;
|
||||
});
|
||||
|
||||
// Things get messy here.
|
||||
// @todo Refactor to use currentState and targetedState properties to determine what needs
|
||||
// to be done to get to any desired state on any UI or AJAX event – since these don't always
|
||||
// match.
|
||||
// E.g. When a user starts a new UI event before the Ajax event handler from a previous
|
||||
// UI event gets invoked.
|
||||
|
||||
// Hide the settings first of all.
|
||||
widget.toggleSettings();
|
||||
// Save the real settings element so that we can restore the reference later.
|
||||
var settingsElement = widget.settings.element;
|
||||
// Empty the settings form.
|
||||
widget.settings.innerElement.empty();
|
||||
initThrobber();
|
||||
// So that showSettings() and hideSettings() can do SOMETHING, without showing the empty settings form.
|
||||
widget.settings.element = widget.throbber.hide();
|
||||
widget.settings.ready = false;
|
||||
|
||||
// Save the settings to the server.
|
||||
$.extend(params, opts.ajaxCallbacks.widgetSettings.data, { id: widget.id });
|
||||
$.post(opts.ajaxCallbacks.widgetSettings.url, params, function(response, status) {
|
||||
// Merge the response into widget.settings.
|
||||
$.extend(widget.settings, response);
|
||||
// Restore the reference to the real settings element.
|
||||
widget.settings.element = settingsElement;
|
||||
// Make sure the settings form is empty and add the updated settings form.
|
||||
widget.settings.innerElement.empty().append(widget.settings.markup);
|
||||
widget.settings.ready = true;
|
||||
|
||||
// Did the user already jump back into settings-display mode before we could finish reloading the settings form?
|
||||
if (widget.settings.displayed) {
|
||||
// Ooops! We had better take care of hiding the throbber and showing the settings form then.
|
||||
widget.throbber.hide();
|
||||
widget.showSettings();
|
||||
invokeCallback(opts.widgetCallbacks.saveSettings, dashboard);
|
||||
}
|
||||
}, 'json');
|
||||
|
||||
// Don't let form submittal bubble up.
|
||||
return false;
|
||||
};
|
||||
|
||||
widget.enterFullscreen = function() {
|
||||
// Make sure the widget actually supports full screen mode.
|
||||
if (widget.fullscreenUrl) {
|
||||
CRM.loadPage(widget.fullscreenUrl);
|
||||
}
|
||||
};
|
||||
|
||||
// Adds controls to a widget. id is for internal use and image file name in images/dashboard/ (a .gif).
|
||||
widget.addControl = function(id, control) {
|
||||
var markup = '<a class="crm-i ' + control.icon + '" alt="' + control.description + '" title="' + control.description + '"></a>';
|
||||
control.element = $(markup).prependTo($('.widget-controls', widget.element)).click(control.callback);
|
||||
};
|
||||
|
||||
// Fetch remote content.
|
||||
widget.reloadContent = function() {
|
||||
// If minimized, we'll reload later
|
||||
if (widget.minimized) {
|
||||
widget.contentLoaded = false;
|
||||
widget.lastLoaded = 0;
|
||||
} else {
|
||||
CRM.loadPage(widget.url, {target: widget.contentElement});
|
||||
}
|
||||
};
|
||||
|
||||
// Removes the widget from the dashboard, and saves columns.
|
||||
widget.remove = function() {
|
||||
invokeCallback(opts.widgetCallbacks.remove, widget);
|
||||
widget.element.fadeOut(opts.animationSpeed, function() {
|
||||
$(this).remove();
|
||||
delete(dashboard.widgets[widget.id]);
|
||||
dashboard.saveColumns(false);
|
||||
});
|
||||
CRM.alert(
|
||||
ts('You can re-add it by clicking the "Configure Your Dashboard" button.'),
|
||||
ts('"%1" Removed', {1: widget.title}),
|
||||
'success'
|
||||
);
|
||||
};
|
||||
|
||||
widget.cacheIsFresh = function() {
|
||||
return (((widget.cacheMinutes * 60000 + widget.lastLoaded) > $.now()) && widget.content);
|
||||
};
|
||||
|
||||
/**
|
||||
* Public properties of widget.
|
||||
*/
|
||||
|
||||
// Default controls. External script can add more with widget.addControls()
|
||||
widget.controls = {
|
||||
settings: {
|
||||
description: ts('Configure this dashlet'),
|
||||
callback: widget.toggleSettings,
|
||||
icon: 'fa-wrench'
|
||||
},
|
||||
minimize: {
|
||||
description: widget.minimized ? ts('Expand') : ts('Collapse'),
|
||||
callback: widget.toggleMinimize,
|
||||
icon: widget.minimized ? 'fa-caret-right' : 'fa-caret-down'
|
||||
},
|
||||
fullscreen: {
|
||||
description: ts('View fullscreen'),
|
||||
callback: widget.enterFullscreen,
|
||||
icon: 'fa-expand'
|
||||
},
|
||||
close: {
|
||||
description: ts('Remove from dashboard'),
|
||||
callback: widget.remove,
|
||||
icon: 'fa-times'
|
||||
}
|
||||
};
|
||||
widget.contentLoaded = false;
|
||||
|
||||
init();
|
||||
return widget;
|
||||
|
||||
/**
|
||||
* Private methods of widget.
|
||||
*/
|
||||
|
||||
function loadContent() {
|
||||
var loadFromCache = widget.cacheIsFresh();
|
||||
if (loadFromCache) {
|
||||
widget.contentElement.html(widget.content).trigger('crmLoad', widget);
|
||||
}
|
||||
widget.contentElement.off('crmLoad').on('crmLoad', function(event, data) {
|
||||
if ($(event.target).is(widget.contentElement)) {
|
||||
widget.content = data.content;
|
||||
// Cache for one day
|
||||
widget.lastLoaded = $.now();
|
||||
saveLocalCache();
|
||||
invokeCallback(opts.widgetCallbacks.get, widget);
|
||||
}
|
||||
});
|
||||
if (!loadFromCache) {
|
||||
widget.reloadContent();
|
||||
}
|
||||
widget.contentLoaded = true;
|
||||
}
|
||||
|
||||
// Build widget & load content.
|
||||
function init() {
|
||||
// Delete controls that don't apply to this widget.
|
||||
if (!widget.settings) {
|
||||
delete widget.controls.settings;
|
||||
widget.settings = {};
|
||||
}
|
||||
if (!widget.fullscreenUrl) {
|
||||
delete widget.controls.fullscreen;
|
||||
}
|
||||
var cssClass = 'widget-' + widget.name.replace('/', '-');
|
||||
widget.element.attr('id', 'widget-' + widget.id).addClass(cssClass);
|
||||
// Build and add the widget's DOM element.
|
||||
$(widget.element).append(widgetHTML());
|
||||
// Save the content element so that external scripts can reload it easily.
|
||||
widget.contentElement = $('.widget-content', widget.element);
|
||||
$.each(widget.controls, widget.addControl);
|
||||
|
||||
if (widget.minimized) {
|
||||
widget.contentElement.hide();
|
||||
} else {
|
||||
loadContent();
|
||||
}
|
||||
}
|
||||
|
||||
// Builds inner HTML for widgets.
|
||||
function widgetHTML() {
|
||||
var html = '';
|
||||
html += '<div class="widget-wrapper">';
|
||||
html += ' <div class="widget-controls"><h3 class="widget-header">' + widget.title + '</h3></div>';
|
||||
html += ' <div class="widget-content"></div>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// Initializes a widgets settings pane.
|
||||
function initSettings() {
|
||||
// Overwrite widget.settings (boolean).
|
||||
initThrobber();
|
||||
widget.settings = {
|
||||
element: widget.throbber.show(),
|
||||
ready: false
|
||||
};
|
||||
|
||||
// Get the settings markup and script executables for this widget.
|
||||
var params = $.extend({}, opts.ajaxCallbacks.widgetSettings.data, { id: widget.id });
|
||||
$.getJSON(opts.ajaxCallbacks.widgetSettings.url, params, function(response, status) {
|
||||
$.extend(widget.settings, response);
|
||||
// Build and add the settings form to the DOM. Bind the form's submit event handler/callback.
|
||||
widget.settings.element = $(widgetSettingsHTML()).appendTo($('.widget-wrapper', widget.element)).submit(widget.saveSettings);
|
||||
// Bind the cancel button's event handler too.
|
||||
widget.settings.cancelButton = $('.widget-settings-cancel', widget.settings.element).click(cancelEditSettings);
|
||||
// Build and add the inner form elements from the HTML markup provided in the AJAX data.
|
||||
widget.settings.innerElement = $('.widget-settings-inner', widget.settings.element).append(widget.settings.markup);
|
||||
widget.settings.ready = true;
|
||||
|
||||
if (widget.settings.displayed) {
|
||||
// If the user hasn't clicked away from the settings pane, then display the form.
|
||||
widget.throbber.hide();
|
||||
widget.showSettings();
|
||||
}
|
||||
|
||||
getJavascript(widget.settings.initScript);
|
||||
});
|
||||
}
|
||||
|
||||
// Builds HTML for widget settings forms.
|
||||
function widgetSettingsHTML() {
|
||||
var html = '';
|
||||
html += '<form class="widget-settings">';
|
||||
html += ' <div class="widget-settings-inner"></div>';
|
||||
html += ' <div class="widget-settings-buttons">';
|
||||
html += ' <input id="' + widget.id + '-settings-save" class="widget-settings-save" value="Save" type="submit" />';
|
||||
html += ' <input id="' + widget.id + '-settings-cancel" class="widget-settings-cancel" value="Cancel" type="submit" />';
|
||||
html += ' </div>';
|
||||
html += '</form>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// Initializes a generic widget content throbber, for use by settings form and external scripts.
|
||||
function initThrobber() {
|
||||
if (!widget.throbber) {
|
||||
widget.throbber = $(opts.throbberMarkup).appendTo($('.widget-wrapper', widget.element));
|
||||
}
|
||||
}
|
||||
|
||||
// Event handler/callback for cancel button clicks.
|
||||
// @todo test this gets caught by all browsers when the cancel button is 'clicked' via the keyboard.
|
||||
function cancelEditSettings() {
|
||||
widget.toggleSettings();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper function to execute external script on the server.
|
||||
// @todo It would be nice to provide some context to the script. How?
|
||||
function getJavascript(url) {
|
||||
if (url) {
|
||||
$.getScript(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Public static properties of dashboard. Default settings.
|
||||
$.fn.dashboard.defaults = {
|
||||
columns: 2,
|
||||
emptyPlaceholderInner: '',
|
||||
throbberMarkup: '',
|
||||
animationSpeed: 200,
|
||||
callbacks: {},
|
||||
widgetCallbacks: {}
|
||||
};
|
||||
|
||||
// Default widget settings.
|
||||
$.fn.dashboard.widget = {
|
||||
defaults: {
|
||||
minimized: false,
|
||||
content: null,
|
||||
lastLoaded: 0,
|
||||
settings: false
|
||||
// id, url, fullscreenUrl, title, name, cacheMinutes
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
121
sites/all/modules/civicrm/js/model/crm.designer.js
Normal file
121
sites/all/modules/civicrm/js/model/crm.designer.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
(function($, _) {
|
||||
if (!CRM.Designer) CRM.Designer = {};
|
||||
|
||||
// TODO Optimize this class
|
||||
CRM.Designer.PaletteFieldModel = CRM.Backbone.Model.extend({
|
||||
defaults: {
|
||||
/**
|
||||
* @var {string} required; a form-specific binding to an entity instance (eg 'student', 'mother')
|
||||
*/
|
||||
entityName: null,
|
||||
|
||||
/**
|
||||
* @var {string}
|
||||
*/
|
||||
fieldName: null
|
||||
},
|
||||
initialize: function() {
|
||||
},
|
||||
getFieldSchema: function() {
|
||||
return this.getRel('ufGroupModel').getFieldSchema(this.get('entityName'), this.get('fieldName'));
|
||||
},
|
||||
getLabel: function() {
|
||||
// Note: if fieldSchema were a bit tighter, then we need to get a label from PaletteFieldModel at all
|
||||
return this.getFieldSchema().title || this.get('fieldName');
|
||||
},
|
||||
getSectionName: function() {
|
||||
// Note: if fieldSchema were a bit tighter, then we need to get a section from PaletteFieldModel at all
|
||||
return this.getFieldSchema().section || 'default';
|
||||
},
|
||||
getSection: function() {
|
||||
return this.getRel('ufGroupModel').getModelClass(this.get('entityName')).prototype.sections[this.getSectionName()];
|
||||
},
|
||||
/**
|
||||
* Add a new UFField model to a UFFieldCollection (if doing so is legal).
|
||||
* If it fails, display an alert.
|
||||
*
|
||||
* @param {int} ufGroupId
|
||||
* @param {CRM.UF.UFFieldCollection} ufFieldCollection
|
||||
* @param {Object} addOptions
|
||||
* @return {CRM.UF.UFFieldModel} or null (if the field is not addable)
|
||||
*/
|
||||
addToUFCollection: function(ufFieldCollection, addOptions) {
|
||||
var name, paletteFieldModel = this;
|
||||
var ufFieldModel = paletteFieldModel.createUFFieldModel(ufFieldCollection.getRel('ufGroupModel'));
|
||||
ufFieldModel.set('uf_group_id', ufFieldCollection.uf_group_id);
|
||||
if (!ufFieldCollection.isAddable(ufFieldModel)) {
|
||||
CRM.alert(
|
||||
ts('The field "%1" is already included.', {
|
||||
1: paletteFieldModel.getLabel()
|
||||
}),
|
||||
ts('Duplicate'),
|
||||
'alert'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
ufFieldCollection.add(ufFieldModel, addOptions);
|
||||
// Load metadata and set defaults
|
||||
// TODO: currently only works for custom fields
|
||||
name = this.get('fieldName').split('_');
|
||||
if (name[0] === 'custom') {
|
||||
CRM.api('custom_field', 'getsingle', {id: name[1]}, {success: function(field) {
|
||||
ufFieldModel.set(_.pick(field, 'help_pre', 'help_post', 'is_required'));
|
||||
}});
|
||||
}
|
||||
return ufFieldModel;
|
||||
},
|
||||
createUFFieldModel: function(ufGroupModel) {
|
||||
var model = new CRM.UF.UFFieldModel({
|
||||
is_active: 1,
|
||||
label: this.getLabel(),
|
||||
entity_name: this.get('entityName'),
|
||||
field_type: this.getFieldSchema().civiFieldType,
|
||||
// For some reason the 'formatting' field gets a random number appended in core so we mimic that here.
|
||||
// TODO: Why?
|
||||
field_name: this.get('fieldName') == 'formatting' ? 'formatting_' + (Math.floor(Math.random() * 8999) + 1000) : this.get('fieldName')
|
||||
});
|
||||
return model;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* options:
|
||||
* - ufGroupModel: UFGroupModel
|
||||
*/
|
||||
CRM.Designer.PaletteFieldCollection = CRM.Backbone.Collection.extend({
|
||||
model: CRM.Designer.PaletteFieldModel,
|
||||
initialize: function(models, options) {
|
||||
this.initializeCopyToChildrenRelation('ufGroupModel', options.ufGroupModel, models);
|
||||
},
|
||||
|
||||
/**
|
||||
* Look up a palette-field
|
||||
*
|
||||
* @param entityName
|
||||
* @param fieldName
|
||||
* @return {CRM.Designer.PaletteFieldModel}
|
||||
*/
|
||||
getFieldByName: function(entityName, fieldName) {
|
||||
if (fieldName.indexOf('formatting') === 0) {
|
||||
fieldName = 'formatting';
|
||||
}
|
||||
return this.find(function(paletteFieldModel) {
|
||||
return ((!entityName || paletteFieldModel.get('entityName') == entityName) && paletteFieldModel.get('fieldName') == fieldName);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of all fields, grouped into sections by "entityName+sectionName".
|
||||
*
|
||||
* @return {Object} keys are sections ("entityName+sectionName"); values are CRM.Designer.PaletteFieldModel
|
||||
*/
|
||||
getFieldsByEntitySection: function() {
|
||||
// TODO cache
|
||||
var fieldsByEntitySection = this.groupBy(function(paletteFieldModel) {
|
||||
return paletteFieldModel.get('entityName') + '-' + paletteFieldModel.getSectionName();
|
||||
});
|
||||
return fieldsByEntitySection;
|
||||
}
|
||||
});
|
||||
})(CRM.$, CRM._);
|
|
@ -0,0 +1,9 @@
|
|||
(function($, _) {
|
||||
if (!CRM.ProfileSelector) CRM.ProfileSelector = {};
|
||||
|
||||
CRM.ProfileSelector.DummyModel = CRM.Backbone.Model.extend({
|
||||
defaults: {
|
||||
profile_id: null
|
||||
}
|
||||
});
|
||||
})(CRM.$, CRM._);
|
47
sites/all/modules/civicrm/js/model/crm.schema-mapped.js
Normal file
47
sites/all/modules/civicrm/js/model/crm.schema-mapped.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Dynamically-generated alternative to civi.core.js
|
||||
*/
|
||||
(function($, _) {
|
||||
if (!CRM.Schema) CRM.Schema = {};
|
||||
|
||||
/**
|
||||
* Data models used by the Civi form designer require more attributes than basic Backbone models:
|
||||
* - sections: array of field-groupings
|
||||
* - schema: array of fields, keyed by field name, per backbone-forms; extra attributes:
|
||||
* + section: string, index to the 'sections' array
|
||||
* + civiFieldType: string
|
||||
*
|
||||
* @see https://github.com/powmedia/backbone-forms
|
||||
*/
|
||||
|
||||
CRM.Schema.BaseModel = CRM.Backbone.Model.extend({
|
||||
initialize: function() {
|
||||
}
|
||||
});
|
||||
|
||||
CRM.Schema.loadModels = function(civiSchema) {
|
||||
_.each(civiSchema, function(value, key, list) {
|
||||
CRM.Schema[key] = CRM.Schema.BaseModel.extend(value);
|
||||
});
|
||||
};
|
||||
|
||||
CRM.Schema.reloadModels = function(options) {
|
||||
return $
|
||||
.ajax({
|
||||
url: CRM.url("civicrm/profile-editor/schema"),
|
||||
data: {
|
||||
'entityTypes': _.keys(CRM.civiSchema).join(',')
|
||||
},
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data) {
|
||||
CRM.civiSchema = data;
|
||||
CRM.Schema.loadModels(CRM.civiSchema);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
CRM.Schema.loadModels(CRM.civiSchema);
|
||||
})(CRM.$, CRM._);
|
51
sites/all/modules/civicrm/js/model/crm.schema.js
Normal file
51
sites/all/modules/civicrm/js/model/crm.schema.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
(function($, _) {
|
||||
if (!CRM.Schema) CRM.Schema = {};
|
||||
|
||||
/**
|
||||
* Civi data models require more attributes than basic Backbone models:
|
||||
* - sections: array of field-groupings
|
||||
* - schema: array of fields, keyed by field name, per backbone-forms
|
||||
*
|
||||
* @see https://github.com/powmedia/backbone-forms
|
||||
*/
|
||||
|
||||
CRM.Schema.IndividualModel = CRM.Backbone.Model.extend({
|
||||
sections: {
|
||||
'default': {title: 'Individual'},
|
||||
'custom1': {title: 'Individual: Favorite Things', is_addable: true},
|
||||
'custom2': {title: 'Individual: Custom Things', is_addable: true}
|
||||
},
|
||||
schema: {
|
||||
first_name: { type: 'Text', title: 'First name', civiFieldType: 'Individual' },
|
||||
last_name: { type: 'Text', title: 'Last name', civiFieldType: 'Individual' },
|
||||
legal_name: { type: 'Text', title: 'Legal name', civiFieldType: 'Contact' },
|
||||
street_address: { validators: ['required', 'email'], title: 'Email', civiFieldType: 'Contact', civiIsLocation: true, civiIsPhone: false },
|
||||
email: { validators: ['required', 'email'], title: 'Email', civiFieldType: 'Contact', civiIsLocation: true, civiIsPhone: true },
|
||||
custom_123: { type: 'Checkbox', section: 'custom1', title: 'Likes whiskers on kittens', civiFieldType: 'Individual'},
|
||||
custom_456: { type: 'Checkbox', section: 'custom1', title: 'Likes dog bites', civiFieldType: 'Individual' },
|
||||
custom_789: { type: 'Checkbox', section: 'custom1', title: 'Likes bee stings', civiFieldType: 'Individual' },
|
||||
custom_012: { type: 'Text', section: 'custom2', title: 'Pass phrase', civiFieldType: 'Contact' }
|
||||
},
|
||||
initialize: function() {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
CRM.Schema.ActivityModel = CRM.Backbone.Model.extend({
|
||||
sections: {
|
||||
'default': {title: 'Activity'},
|
||||
'custom3': {title: 'Activity: Questions', is_addable: true}
|
||||
},
|
||||
schema: {
|
||||
subject: { type: 'Text', title: 'Subject', civiFieldType: 'Activity' },
|
||||
location: { type: 'Text', title: 'Location', civiFieldType: 'Activity' },
|
||||
activity_date_time: { type: 'DateTime', title: 'Date-Time', civiFieldType: 'Activity' },
|
||||
custom_789: { type: 'Select', section: 'custom3', title: 'How often do you eat cheese?',
|
||||
options: ['Never', 'Sometimes', 'Often'],
|
||||
civiFieldType: 'Activity'
|
||||
}
|
||||
},
|
||||
initialize: function() {
|
||||
}
|
||||
});
|
||||
})(CRM.$, CRM._);
|
848
sites/all/modules/civicrm/js/model/crm.uf.js
Normal file
848
sites/all/modules/civicrm/js/model/crm.uf.js
Normal file
|
@ -0,0 +1,848 @@
|
|||
(function($, _) {
|
||||
if (!CRM.UF) CRM.UF = {};
|
||||
|
||||
var YESNO = [
|
||||
{val: 0, label: ts('No')},
|
||||
{val: 1, label: ts('Yes')}
|
||||
];
|
||||
|
||||
var VISIBILITY = [
|
||||
{val: 'User and User Admin Only', label: ts('User and User Admin Only'), isInSelectorAllowed: false},
|
||||
{val: 'Public Pages', label: ts('Expose Publicly'), isInSelectorAllowed: true},
|
||||
{val: 'Public Pages and Listings', label: ts('Expose Publicly and for Listings'), isInSelectorAllowed: true}
|
||||
];
|
||||
|
||||
var LOCATION_TYPES = _.map(CRM.PseudoConstant.locationType, function(value, key) {
|
||||
return {val: key, label: value};
|
||||
});
|
||||
LOCATION_TYPES.unshift({val: '', label: ts('Primary')});
|
||||
var DEFAULT_LOCATION_TYPE_ID = '';
|
||||
|
||||
var PHONE_TYPES = _.map(CRM.PseudoConstant.phoneType, function(value, key) {
|
||||
return {val: key, label: value};
|
||||
});
|
||||
|
||||
var WEBSITE_TYPES = _.map(CRM.PseudoConstant.websiteType, function(value, key) {
|
||||
return {val: key, label: value};
|
||||
});
|
||||
var DEFAULT_PHONE_TYPE_ID = PHONE_TYPES[0].val;
|
||||
var DEFAULT_WEBSITE_TYPE_ID = WEBSITE_TYPES[0].val;
|
||||
|
||||
/**
|
||||
* Add a help link to a form label
|
||||
*/
|
||||
function addHelp(title, options) {
|
||||
return title + ' <a href="#" onclick=\'CRM.help("' + title + '", ' + JSON.stringify(options) + '); return false;\' title="' + ts('%1 Help', {1: title}) + '" class="helpicon"></a>';
|
||||
}
|
||||
|
||||
function watchChanges() {
|
||||
CRM.designerApp.vent.trigger('ufUnsaved', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a "group_type" expression
|
||||
*
|
||||
* @param string groupTypeExpr example: "Individual,Activity\0ActivityType:2:28"
|
||||
* Note: I've seen problems where HTML "�" != JS '\0', so we support ';;' as an equivalent delimiter
|
||||
* @return Object example: {coreTypes: {"Individual":true,"Activity":true}, subTypes: {"ActivityType":{2: true, 28:true}]}}
|
||||
*/
|
||||
CRM.UF.parseTypeList = function(groupTypeExpr) {
|
||||
var typeList = {coreTypes: {}, subTypes:{}};
|
||||
// The API may have automatically converted a string with '\0' to an array
|
||||
var parts = _.isArray(groupTypeExpr) ? groupTypeExpr : groupTypeExpr.replace(';;','\0').split('\0');
|
||||
var coreTypesExpr = parts[0];
|
||||
var subTypesExpr = parts[1];
|
||||
|
||||
if (!_.isEmpty(coreTypesExpr)) {
|
||||
_.each(coreTypesExpr.split(','), function(coreType){
|
||||
typeList.coreTypes[coreType] = true;
|
||||
});
|
||||
}
|
||||
|
||||
//CRM-15427 Allow Multiple subtype filtering
|
||||
if (!_.isEmpty(subTypesExpr)) {
|
||||
if (subTypesExpr.indexOf(';;') !== -1) {
|
||||
var subTypeparts = subTypesExpr.replace(/;;/g,'\0').split('\0');
|
||||
_.each(subTypeparts, function(subTypepart) {
|
||||
var subTypes = subTypepart.split(':');
|
||||
var subTypeKey = subTypes.shift();
|
||||
typeList.subTypes[subTypeKey] = {};
|
||||
_.each(subTypes, function(subTypeId) {
|
||||
typeList.subTypes[subTypeKey][subTypeId] = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
var subTypes = subTypesExpr.split(':');
|
||||
var subTypeKey = subTypes.shift();
|
||||
typeList.subTypes[subTypeKey] = {};
|
||||
_.each(subTypes, function(subTypeId) {
|
||||
typeList.subTypes[subTypeKey][subTypeId] = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
return typeList;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function is a hack for generating simulated values of "entity_name"
|
||||
* in the form-field model.
|
||||
*
|
||||
* @param {string} field_type
|
||||
* @return {string}
|
||||
*/
|
||||
CRM.UF.guessEntityName = function(field_type) {
|
||||
switch (field_type) {
|
||||
case 'Contact':
|
||||
case 'Individual':
|
||||
case 'Organization':
|
||||
case 'Household':
|
||||
case 'Formatting':
|
||||
return 'contact_1';
|
||||
case 'Activity':
|
||||
return 'activity_1';
|
||||
case 'Contribution':
|
||||
return 'contribution_1';
|
||||
case 'Membership':
|
||||
return 'membership_1';
|
||||
case 'Participant':
|
||||
return 'participant_1';
|
||||
case 'Case':
|
||||
return 'case_1';
|
||||
default:
|
||||
if (CRM.contactSubTypes.length && ($.inArray(field_type,CRM.contactSubTypes) > -1)) {
|
||||
return 'contact_1';
|
||||
}
|
||||
else {
|
||||
throw "Cannot guess entity name for field_type=" + field_type;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a field in a customizable form.
|
||||
*/
|
||||
CRM.UF.UFFieldModel = CRM.Backbone.Model.extend({
|
||||
/**
|
||||
* Backbone.Form description of the field to which this refers
|
||||
*/
|
||||
defaults: {
|
||||
help_pre: '',
|
||||
help_post: '',
|
||||
/**
|
||||
* @var bool, non-persistent indication of whether this field is unique or duplicate
|
||||
* within its UFFieldCollection
|
||||
*/
|
||||
is_duplicate: false
|
||||
},
|
||||
schema: {
|
||||
'id': {
|
||||
type: 'Number'
|
||||
},
|
||||
'uf_group_id': {
|
||||
type: 'Number'
|
||||
},
|
||||
'entity_name': {
|
||||
// pseudo-field
|
||||
type: 'Text'
|
||||
},
|
||||
'field_name': {
|
||||
type: 'Text'
|
||||
},
|
||||
'field_type': {
|
||||
type: 'Select',
|
||||
options: ['Contact', 'Individual', 'Organization', 'Contribution', 'Membership', 'Participant', 'Activity']
|
||||
},
|
||||
'help_post': {
|
||||
title: addHelp(ts('Field Post Help'), {id: "help", file:"CRM/UF/Form/Field"}),
|
||||
type: 'TextArea'
|
||||
},
|
||||
'help_pre': {
|
||||
title: addHelp(ts('Field Pre Help'), {id: "help", file:"CRM/UF/Form/Field"}),
|
||||
type: 'TextArea'
|
||||
},
|
||||
'in_selector': {
|
||||
title: addHelp(ts('Results Columns?'), {id: "in_selector", file:"CRM/UF/Form/Field"}),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_active': {
|
||||
title: addHelp(ts('Active?'), {id: "is_active", file:"CRM/UF/Form/Field"}),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_multi_summary': {
|
||||
title: ts("Include in multi-record listing?"),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_required': {
|
||||
title: addHelp(ts('Required?'), {id: "is_required", file:"CRM/UF/Form/Field"}),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_reserved': {
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_searchable': {
|
||||
title: addHelp(ts("Searchable"), {id: "is_searchable", file:"CRM/UF/Form/Field"}),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_view': {
|
||||
title: addHelp(ts('View Only?'), {id: "is_view", file:"CRM/UF/Form/Field"}),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'label': {
|
||||
title: ts('Field Label'),
|
||||
type: 'Text',
|
||||
editorAttrs: {maxlength: 255}
|
||||
},
|
||||
'location_type_id': {
|
||||
title: ts('Location Type'),
|
||||
type: 'Select',
|
||||
options: LOCATION_TYPES
|
||||
},
|
||||
'website_type_id': {
|
||||
title: ts('Website Type'),
|
||||
type: 'Select',
|
||||
options: WEBSITE_TYPES
|
||||
},
|
||||
'phone_type_id': {
|
||||
title: ts('Phone Type'),
|
||||
type: 'Select',
|
||||
options: PHONE_TYPES
|
||||
},
|
||||
'visibility': {
|
||||
title: addHelp(ts('Visibility'), {id: "visibility", file:"CRM/UF/Form/Field"}),
|
||||
type: 'Select',
|
||||
options: VISIBILITY
|
||||
},
|
||||
'weight': {
|
||||
type: 'Number'
|
||||
}
|
||||
},
|
||||
initialize: function() {
|
||||
if (this.get('field_name').indexOf('formatting') === 0) {
|
||||
this.schema.help_pre.title = ts('Markup');
|
||||
}
|
||||
this.set('entity_name', CRM.UF.guessEntityName(this.get('field_type')));
|
||||
this.on("rel:ufGroupModel", this.applyDefaults, this);
|
||||
this.on('change', watchChanges);
|
||||
},
|
||||
applyDefaults: function() {
|
||||
var fieldSchema = this.getFieldSchema();
|
||||
if (fieldSchema && fieldSchema.civiIsLocation && !this.get('location_type_id')) {
|
||||
this.set('location_type_id', DEFAULT_LOCATION_TYPE_ID);
|
||||
}
|
||||
if (fieldSchema && fieldSchema.civiIsWebsite && !this.get('website_type_id')) {
|
||||
this.set('website_type_id', DEFAULT_WEBSITE_TYPE_ID);
|
||||
}
|
||||
if (fieldSchema && fieldSchema.civiIsPhone && !this.get('phone_type_id')) {
|
||||
this.set('phone_type_id', DEFAULT_PHONE_TYPE_ID);
|
||||
}
|
||||
},
|
||||
isInSelectorAllowed: function() {
|
||||
var visibility = _.first(_.where(VISIBILITY, {val: this.get('visibility')}));
|
||||
if (visibility) {
|
||||
return visibility.isInSelectorAllowed;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getFieldSchema: function() {
|
||||
return this.getRel('ufGroupModel').getFieldSchema(this.get('entity_name'), this.get('field_name'));
|
||||
},
|
||||
/**
|
||||
* Create a uniqueness signature. Ideally, each UFField in a UFGroup should
|
||||
* have a unique signature.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
getSignature: function() {
|
||||
return this.get("entity_name") +
|
||||
'::' + this.get("field_name") +
|
||||
'::' + (this.get("location_type_id") ? this.get("location_type_id") : this.get("website_type_id") ? this.get("website_type_id") : '') +
|
||||
'::' + (this.get("phone_type_id") ? this.get("phone_type_id") : '');
|
||||
},
|
||||
|
||||
/**
|
||||
* This is like destroy(), but it only destroys the item on the client-side;
|
||||
* it does not trigger REST or Backbone.sync() operations.
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
destroyLocal: function() {
|
||||
this.trigger('destroy', this, this.collection, {});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a list of fields in a customizable form
|
||||
*
|
||||
* options:
|
||||
* - uf_group_id: int
|
||||
*/
|
||||
CRM.UF.UFFieldCollection = CRM.Backbone.Collection.extend({
|
||||
model: CRM.UF.UFFieldModel,
|
||||
uf_group_id: null, // int
|
||||
initialize: function(models, options) {
|
||||
options = options || {};
|
||||
this.uf_group_id = options.uf_group_id;
|
||||
this.initializeCopyToChildrenRelation('ufGroupModel', options.ufGroupModel, models);
|
||||
this.on('add', this.watchDuplicates, this);
|
||||
this.on('remove', this.unwatchDuplicates, this);
|
||||
this.on('change', watchChanges);
|
||||
this.on('add', watchChanges);
|
||||
this.on('remove', watchChanges);
|
||||
},
|
||||
getFieldsByName: function(entityName, fieldName) {
|
||||
return this.filter(function(ufFieldModel) {
|
||||
return (ufFieldModel.get('entity_name') == entityName && ufFieldModel.get('field_name') == fieldName);
|
||||
});
|
||||
},
|
||||
toSortedJSON: function() {
|
||||
var fields = this.map(function(ufFieldModel){
|
||||
return ufFieldModel.toStrictJSON();
|
||||
});
|
||||
return _.sortBy(fields, function(ufFieldJSON){
|
||||
return parseInt(ufFieldJSON.weight);
|
||||
});
|
||||
},
|
||||
isAddable: function(ufFieldModel) {
|
||||
var entity_name = ufFieldModel.get('entity_name'),
|
||||
field_name = ufFieldModel.get('field_name'),
|
||||
fieldSchema = this.getRel('ufGroupModel').getFieldSchema(ufFieldModel.get('entity_name'), ufFieldModel.get('field_name'));
|
||||
if (field_name.indexOf('formatting') === 0) {
|
||||
return true;
|
||||
}
|
||||
if (! fieldSchema) {
|
||||
return false;
|
||||
}
|
||||
var fields = this.getFieldsByName(entity_name, field_name);
|
||||
var limit = 1;
|
||||
if (fieldSchema.civiIsLocation) {
|
||||
limit *= LOCATION_TYPES.length;
|
||||
}
|
||||
if (fieldSchema.civiIsWebsite) {
|
||||
limit *= WEBSITE_TYPES.length;
|
||||
}
|
||||
if (fieldSchema.civiIsPhone) {
|
||||
limit *= PHONE_TYPES.length;
|
||||
}
|
||||
return fields.length < limit;
|
||||
},
|
||||
watchDuplicates: function(model, collection, options) {
|
||||
model.on('change:location_type_id', this.markDuplicates, this);
|
||||
model.on('change:website_type_id', this.markDuplicates, this);
|
||||
model.on('change:phone_type_id', this.markDuplicates, this);
|
||||
this.markDuplicates();
|
||||
},
|
||||
unwatchDuplicates: function(model, collection, options) {
|
||||
model.off('change:location_type_id', this.markDuplicates, this);
|
||||
model.off('change:website_type_id', this.markDuplicates, this);
|
||||
model.off('change:phone_type_id', this.markDuplicates, this);
|
||||
this.markDuplicates();
|
||||
},
|
||||
hasDuplicates: function() {
|
||||
var firstDupe = this.find(function(ufFieldModel){
|
||||
return ufFieldModel.get('is_duplicate');
|
||||
});
|
||||
return firstDupe ? true : false;
|
||||
},
|
||||
/**
|
||||
*
|
||||
*/
|
||||
markDuplicates: function() {
|
||||
var ufFieldModelsByKey = this.groupBy(function(ufFieldModel) {
|
||||
return ufFieldModel.getSignature();
|
||||
});
|
||||
this.each(function(ufFieldModel){
|
||||
var is_duplicate = ufFieldModelsByKey[ufFieldModel.getSignature()].length > 1;
|
||||
if (is_duplicate != ufFieldModel.get('is_duplicate')) {
|
||||
ufFieldModel.set('is_duplicate', is_duplicate);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents an entity in a customizable form
|
||||
*/
|
||||
CRM.UF.UFEntityModel = CRM.Backbone.Model.extend({
|
||||
schema: {
|
||||
'id': {
|
||||
// title: ts(''),
|
||||
type: 'Number'
|
||||
},
|
||||
'entity_name': {
|
||||
title: ts('Entity Name'),
|
||||
help: ts('Symbolic name which referenced in the fields'),
|
||||
type: 'Text'
|
||||
},
|
||||
'entity_type': {
|
||||
title: ts('Entity Type'),
|
||||
type: 'Select',
|
||||
options: ['IndividualModel', 'ActivityModel']
|
||||
},
|
||||
'entity_sub_type': {
|
||||
// Use '*' to match all subtypes; use an int to match a specific type id; use empty-string to match none
|
||||
title: ts('Sub Type'),
|
||||
type: 'Text'
|
||||
}
|
||||
},
|
||||
defaults: {
|
||||
entity_sub_type: '*'
|
||||
},
|
||||
initialize: function() {
|
||||
},
|
||||
/**
|
||||
* Get a list of all fields that can be used with this entity.
|
||||
*
|
||||
* @return {Object} keys are field names; values are fieldSchemas
|
||||
*/
|
||||
getFieldSchemas: function() {
|
||||
var ufEntityModel = this;
|
||||
var modelClass= this.getModelClass();
|
||||
|
||||
if (this.get('entity_sub_type') == '*') {
|
||||
return _.clone(modelClass.prototype.schema);
|
||||
}
|
||||
|
||||
var result = {};
|
||||
_.each(modelClass.prototype.schema, function(fieldSchema, fieldName){
|
||||
var section = modelClass.prototype.sections[fieldSchema.section];
|
||||
if (ufEntityModel.isSectionEnabled(section)) {
|
||||
result[fieldName] = fieldSchema;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
isSectionEnabled: function(section) {
|
||||
//CRM-15427
|
||||
return (!section || !section.extends_entity_column_value || _.contains(section.extends_entity_column_value, this.get('entity_sub_type')) || this.get('entity_sub_type') == '*');
|
||||
},
|
||||
getSections: function() {
|
||||
var ufEntityModel = this;
|
||||
var result = {};
|
||||
_.each(ufEntityModel.getModelClass().prototype.sections, function(section, sectionKey){
|
||||
if (ufEntityModel.isSectionEnabled(section)) {
|
||||
result[sectionKey] = section;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
getModelClass: function() {
|
||||
return CRM.Schema[this.get('entity_type')];
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a list of entities in a customizable form
|
||||
*
|
||||
* options:
|
||||
* - ufGroupModel: UFGroupModel
|
||||
*/
|
||||
CRM.UF.UFEntityCollection = CRM.Backbone.Collection.extend({
|
||||
model: CRM.UF.UFEntityModel,
|
||||
byName: {},
|
||||
initialize: function(models, options) {
|
||||
options = options || {};
|
||||
this.initializeCopyToChildrenRelation('ufGroupModel', options.ufGroupModel, models);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @return {UFEntityModel} if found; otherwise, null
|
||||
*/
|
||||
getByName: function(name) {
|
||||
// TODO consider indexing
|
||||
return this.find(function(ufEntityModel){
|
||||
return ufEntityModel.get('entity_name') == name;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a customizable form
|
||||
*/
|
||||
CRM.UF.UFGroupModel = CRM.Backbone.Model.extend({
|
||||
defaults: {
|
||||
title: ts('Unnamed Profile'),
|
||||
is_active: 1
|
||||
},
|
||||
schema: {
|
||||
'id': {
|
||||
// title: ts(''),
|
||||
type: 'Number'
|
||||
},
|
||||
'name': {
|
||||
// title: ts(''),
|
||||
type: 'Text'
|
||||
},
|
||||
'title': {
|
||||
title: ts('Profile Name'),
|
||||
help: ts(''),
|
||||
type: 'Text',
|
||||
editorAttrs: {maxlength: 64},
|
||||
validators: ['required']
|
||||
},
|
||||
'group_type': {
|
||||
// For a description of group_type, see CRM_Core_BAO_UFGroup::updateGroupTypes
|
||||
// title: ts(''),
|
||||
type: 'Text'
|
||||
},
|
||||
'add_captcha': {
|
||||
title: ts('Include reCAPTCHA?'),
|
||||
help: ts('FIXME'),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'add_to_group_id': {
|
||||
title: ts('Add new contacts to a Group?'),
|
||||
help: ts('Select a group if you are using this profile for adding new contacts, AND you want the new contacts to be automatically assigned to a group.'),
|
||||
type: 'Number'
|
||||
},
|
||||
'cancel_URL': {
|
||||
title: ts('Cancel Redirect URL'),
|
||||
help: ts('If you are using this profile as a contact signup or edit form, and want to redirect the user to a static URL if they click the Cancel button - enter the complete URL here. If this field is left blank, the built-in Profile form will be redisplayed.'),
|
||||
type: 'Text'
|
||||
},
|
||||
'created_date': {
|
||||
//title: ts(''),
|
||||
type: 'Text'// FIXME
|
||||
},
|
||||
'created_id': {
|
||||
//title: ts(''),
|
||||
type: 'Number'
|
||||
},
|
||||
'help_post': {
|
||||
title: ts('Post-form Help'),
|
||||
help: ts('Explanatory text displayed at the end of the form.') +
|
||||
ts('Note that this help text is displayed on profile create/edit screens only.'),
|
||||
type: 'TextArea'
|
||||
},
|
||||
'help_pre': {
|
||||
title: ts('Pre-form Help'),
|
||||
help: ts('Explanatory text displayed at the beginning of the form.') +
|
||||
ts('Note that this help text is displayed on profile create/edit screens only.'),
|
||||
type: 'TextArea'
|
||||
},
|
||||
'is_active': {
|
||||
title: ts('Is this CiviCRM Profile active?'),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_cms_user': {
|
||||
title: ts('Drupal user account registration option?'),// FIXME
|
||||
help: ts('FIXME'),
|
||||
type: 'Select',
|
||||
options: YESNO // FIXME
|
||||
},
|
||||
'is_edit_link': {
|
||||
title: ts('Include profile edit links in search results?'),
|
||||
help: ts('Check this box if you want to include a link in the listings to Edit profile fields. Only users with permission to edit the contact will see this link.'),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_map': {
|
||||
title: ts('Enable mapping for this profile?'),
|
||||
help: ts('If enabled, a Map link is included on the profile listings rows and detail screens for any contacts whose records include sufficient location data for your mapping provider.'),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_proximity_search': {
|
||||
title: ts('Proximity Search'),
|
||||
help: ts('FIXME'),
|
||||
type: 'Select',
|
||||
options: YESNO // FIXME
|
||||
},
|
||||
'is_reserved': {
|
||||
// title: ts(''),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_uf_link': {
|
||||
title: ts('Include Drupal user account information links in search results?'), // FIXME
|
||||
help: ts('FIXME'),
|
||||
type: 'Select',
|
||||
options: YESNO
|
||||
},
|
||||
'is_update_dupe': {
|
||||
title: ts('What to do upon duplicate match'),
|
||||
help: ts('FIXME'),
|
||||
type: 'Select',
|
||||
options: YESNO // FIXME
|
||||
},
|
||||
'limit_listings_group_id': {
|
||||
title: ts('Limit listings to a specific Group?'),
|
||||
help: ts('Select a group if you are using this profile for search and listings, AND you want to limit the listings to members of a specific group.'),
|
||||
type: 'Number'
|
||||
},
|
||||
'notify': {
|
||||
title: ts('Notify when profile form is submitted?'),
|
||||
help: ts('If you want member(s) of your organization to receive a notification email whenever this Profile form is used to enter or update contact information, enter one or more email addresses here. Multiple email addresses should be separated by a comma (e.g. jane@example.org, paula@example.org). The first email address listed will be used as the FROM address in the notifications.'),
|
||||
type: 'TextArea'
|
||||
},
|
||||
'post_URL': {
|
||||
title: ts('Redirect URL'),
|
||||
help: ts("If you are using this profile as a contact signup or edit form, and want to redirect the user to a static URL after they've submitted the form, you can also use contact tokens in URL - enter the complete URL here. If this field is left blank, the built-in Profile form will be redisplayed with a generic status message - 'Your contact information has been saved.'"),
|
||||
type: 'Text'
|
||||
},
|
||||
'weight': {
|
||||
title: ts('Order'),
|
||||
help: ts('Weight controls the order in which profiles are presented when more than one profile is included in User Registration or My Account screens. Enter a positive or negative integer - lower numbers are displayed ahead of higher numbers.'),
|
||||
type: 'Number'
|
||||
// FIXME positive int
|
||||
}
|
||||
},
|
||||
initialize: function() {
|
||||
var ufGroupModel = this;
|
||||
|
||||
if (!this.getRel('ufEntityCollection')) {
|
||||
var ufEntityCollection = new CRM.UF.UFEntityCollection([], {
|
||||
ufGroupModel: this,
|
||||
silent: false
|
||||
});
|
||||
this.setRel('ufEntityCollection', ufEntityCollection);
|
||||
}
|
||||
|
||||
if (!this.getRel('ufFieldCollection')) {
|
||||
var ufFieldCollection = new CRM.UF.UFFieldCollection([], {
|
||||
uf_group_id: this.id,
|
||||
ufGroupModel: this
|
||||
});
|
||||
this.setRel('ufFieldCollection', ufFieldCollection);
|
||||
}
|
||||
|
||||
if (!this.getRel('paletteFieldCollection')) {
|
||||
var paletteFieldCollection = new CRM.Designer.PaletteFieldCollection([], {
|
||||
ufGroupModel: this
|
||||
});
|
||||
paletteFieldCollection.sync = function(method, model, options) {
|
||||
if (!options) options = {};
|
||||
// console.log(method, model, options);
|
||||
switch (method) {
|
||||
case 'read':
|
||||
var success = options.success;
|
||||
options.success = function(resp, status, xhr) {
|
||||
if (success) success(resp, status, xhr);
|
||||
model.trigger('sync', model, resp, options);
|
||||
};
|
||||
success(ufGroupModel.buildPaletteFields());
|
||||
|
||||
break;
|
||||
case 'create':
|
||||
case 'update':
|
||||
case 'delete':
|
||||
throw 'Unsupported method: ' + method;
|
||||
|
||||
default:
|
||||
throw 'Unsupported method: ' + method;
|
||||
}
|
||||
};
|
||||
this.setRel('paletteFieldCollection', paletteFieldCollection);
|
||||
}
|
||||
|
||||
this.getRel('ufEntityCollection').on('reset', this.resetEntities, this);
|
||||
this.resetEntities();
|
||||
|
||||
this.on('change', watchChanges);
|
||||
},
|
||||
/**
|
||||
* Generate a copy of this UFGroupModel and its fields, with all ID's removed. The result
|
||||
* is suitable for a new, identical UFGroup.
|
||||
*
|
||||
* @return {CRM.UF.UFGroupModel}
|
||||
*/
|
||||
deepCopy: function() {
|
||||
var copy = new CRM.UF.UFGroupModel(_.omit(this.toStrictJSON(), ['id','created_id','created_date','is_reserved','group_type']));
|
||||
copy.getRel('ufEntityCollection').reset(
|
||||
this.getRel('ufEntityCollection').toJSON()
|
||||
// FIXME: for configurable entities, omit ['id', 'uf_group_id']
|
||||
);
|
||||
copy.getRel('ufFieldCollection').reset(
|
||||
this.getRel('ufFieldCollection').map(function(ufFieldModel) {
|
||||
return _.omit(ufFieldModel.toStrictJSON(), ['id', 'uf_group_id']);
|
||||
})
|
||||
);
|
||||
var copyLabel = ' ' + ts('(Copy)');
|
||||
copy.set('title', copy.get('title').slice(0, 64 - copyLabel.length) + copyLabel);
|
||||
return copy;
|
||||
},
|
||||
getModelClass: function(entity_name) {
|
||||
var ufEntity = this.getRel('ufEntityCollection').getByName(entity_name);
|
||||
if (!ufEntity) throw 'Failed to locate entity: ' + entity_name;
|
||||
return ufEntity.getModelClass();
|
||||
},
|
||||
getFieldSchema: function(entity_name, field_name) {
|
||||
if (field_name.indexOf('formatting') === 0) {
|
||||
field_name = 'formatting';
|
||||
}
|
||||
var modelClass = this.getModelClass(entity_name);
|
||||
var fieldSchema = modelClass.prototype.schema[field_name];
|
||||
if (!fieldSchema) {
|
||||
CRM.console('warn', 'Failed to locate field: ' + entity_name + "." + field_name);
|
||||
return null;
|
||||
}
|
||||
return fieldSchema;
|
||||
},
|
||||
/**
|
||||
* Check that the group_type contains *only* the types listed in validTypes
|
||||
*
|
||||
* @param string validTypesExpr
|
||||
* @param bool allowAllSubtypes
|
||||
* @return {Boolean}
|
||||
*/
|
||||
//CRM-15427
|
||||
checkGroupType: function(validTypesExpr, allowAllSubtypes, usedByFilter) {
|
||||
var allMatched = true;
|
||||
allowAllSubtypes = allowAllSubtypes || false;
|
||||
usedByFilter = usedByFilter || null;
|
||||
if (_.isEmpty(this.get('group_type'))) {
|
||||
return true;
|
||||
}
|
||||
if (usedByFilter && _.isEmpty(this.get('module'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var actualTypes = CRM.UF.parseTypeList(this.get('group_type'));
|
||||
var validTypes = CRM.UF.parseTypeList(validTypesExpr);
|
||||
|
||||
// Every actual.coreType is a valid.coreType
|
||||
_.each(actualTypes.coreTypes, function(ignore, actualCoreType) {
|
||||
if (! validTypes.coreTypes[actualCoreType]) {
|
||||
allMatched = false;
|
||||
}
|
||||
});
|
||||
|
||||
// CRM-16915 - filter with usedBy module if specified.
|
||||
if (usedByFilter && this.get('module') != usedByFilter) {
|
||||
allMatched = false;
|
||||
}
|
||||
//CRM-15427 allow all subtypes
|
||||
if (!$.isEmptyObject(validTypes.subTypes) && !allowAllSubtypes) {
|
||||
// Every actual.subType is a valid.subType
|
||||
_.each(actualTypes.subTypes, function(actualSubTypeIds, actualSubTypeKey) {
|
||||
if (!validTypes.subTypes[actualSubTypeKey]) {
|
||||
allMatched = false;
|
||||
return;
|
||||
}
|
||||
// actualSubTypeIds is a list of all subtypes which can be used by group,
|
||||
// so it's sufficient to match any one of them
|
||||
var subTypeMatched = false;
|
||||
_.each(actualSubTypeIds, function(ignore, actualSubTypeId) {
|
||||
if (validTypes.subTypes[actualSubTypeKey][actualSubTypeId]) {
|
||||
subTypeMatched = true;
|
||||
}
|
||||
});
|
||||
allMatched = allMatched && subTypeMatched;
|
||||
});
|
||||
}
|
||||
return allMatched;
|
||||
},
|
||||
calculateContactEntityType: function() {
|
||||
var ufGroupModel = this;
|
||||
|
||||
// set proper entity model based on selected profile
|
||||
var contactTypes = ['Individual', 'Household', 'Organization'];
|
||||
var profileType = ufGroupModel.get('group_type') || '';
|
||||
|
||||
// check if selected profile have subtype defined eg: ["Individual,Contact,Case", "caseType:7"]
|
||||
if (_.isArray(profileType) && profileType[0]) {
|
||||
profileType = profileType[0];
|
||||
}
|
||||
profileType = profileType.split(',');
|
||||
|
||||
var ufEntityModel;
|
||||
_.each(profileType, function (ptype) {
|
||||
if ($.inArray(ptype, contactTypes) > -1) {
|
||||
ufEntityModel = ptype + 'Model';
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return ufEntityModel;
|
||||
},
|
||||
setUFGroupModel: function(entityType, allEntityModels) {
|
||||
var ufGroupModel = this;
|
||||
|
||||
var newUfEntityModels = [];
|
||||
_.each(allEntityModels, function (values) {
|
||||
if (entityType && values.entity_name == 'contact_1') {
|
||||
values.entity_type = entityType;
|
||||
}
|
||||
newUfEntityModels.push(new CRM.UF.UFEntityModel(values));
|
||||
});
|
||||
|
||||
ufGroupModel.getRel('ufEntityCollection').reset(newUfEntityModels);
|
||||
},
|
||||
resetEntities: function() {
|
||||
var ufGroupModel = this;
|
||||
var deleteFieldList = [];
|
||||
ufGroupModel.getRel('ufFieldCollection').each(function(ufFieldModel){
|
||||
if (!ufFieldModel.getFieldSchema()) {
|
||||
CRM.alert(ts('This profile no longer includes field "%1"! All references to the field have been removed.', {
|
||||
1: ufFieldModel.get('label')
|
||||
}), '', 'alert', {expires: false});
|
||||
deleteFieldList.push(ufFieldModel);
|
||||
}
|
||||
});
|
||||
|
||||
_.each(deleteFieldList, function(ufFieldModel) {
|
||||
ufFieldModel.destroyLocal();
|
||||
});
|
||||
|
||||
this.getRel('paletteFieldCollection').reset(this.buildPaletteFields());
|
||||
|
||||
// reset to redraw the cancel after entity type is updated.
|
||||
ufGroupModel.getRel('ufFieldCollection').reset(ufGroupModel.getRel('ufFieldCollection').toJSON());
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @return {Array} of PaletteFieldModel
|
||||
*/
|
||||
buildPaletteFields: function() {
|
||||
// rebuild list of fields; reuse old instances of PaletteFieldModel and create new ones
|
||||
// as appropriate
|
||||
// Note: The system as a whole is ill-defined in cases where we have an existing
|
||||
// UFField that references a model field that disappears.
|
||||
|
||||
var ufGroupModel = this;
|
||||
|
||||
var oldPaletteFieldModelsBySig = {};
|
||||
this.getRel('paletteFieldCollection').each(function(paletteFieldModel){
|
||||
oldPaletteFieldModelsBySig[paletteFieldModel.get("entityName") + '::' + paletteFieldModel.get("fieldName")] = paletteFieldModel;
|
||||
});
|
||||
|
||||
var newPaletteFieldModels = [];
|
||||
this.getRel('ufEntityCollection').each(function(ufEntityModel){
|
||||
var modelClass = ufEntityModel.getModelClass();
|
||||
_.each(ufEntityModel.getFieldSchemas(), function(value, key, list) {
|
||||
var model = oldPaletteFieldModelsBySig[ufEntityModel.get('entity_name') + '::' + key];
|
||||
if (!model) {
|
||||
model = new CRM.Designer.PaletteFieldModel({
|
||||
modelClass: modelClass,
|
||||
entityName: ufEntityModel.get('entity_name'),
|
||||
fieldName: key
|
||||
});
|
||||
}
|
||||
newPaletteFieldModels.push(model);
|
||||
});
|
||||
});
|
||||
|
||||
return newPaletteFieldModels;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a list of customizable form
|
||||
*/
|
||||
CRM.UF.UFGroupCollection = CRM.Backbone.Collection.extend({
|
||||
model: CRM.UF.UFGroupModel
|
||||
});
|
||||
})(CRM.$, CRM._);
|
3
sites/all/modules/civicrm/js/noconflict.js
Normal file
3
sites/all/modules/civicrm/js/noconflict.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
if (!window.CRM) window.CRM = {};
|
||||
window.cj = CRM.$ = jQuery.noConflict(true);
|
||||
CRM._ = _.noConflict();
|
878
sites/all/modules/civicrm/js/view/crm.designer.js
Normal file
878
sites/all/modules/civicrm/js/view/crm.designer.js
Normal file
|
@ -0,0 +1,878 @@
|
|||
(function($, _) {
|
||||
if (!CRM.Designer) CRM.Designer = {};
|
||||
|
||||
/**
|
||||
* When rendering a template with Marionette.ItemView, the list of variables is determined by
|
||||
* serializeData(). The normal behavior is to map each property of this.model to a template
|
||||
* variable.
|
||||
*
|
||||
* This function extends that practice by exporting variables "_view", "_model", "_collection",
|
||||
* and "_options". This makes it easier for the template to, e.g., access computed properties of
|
||||
* a model (by calling "_model.getComputedProperty"), or to access constructor options (by
|
||||
* calling "_options.myoption").
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
var extendedSerializeData = function() {
|
||||
var result = Marionette.ItemView.prototype.serializeData.apply(this);
|
||||
result._view = this;
|
||||
result._model = this.model;
|
||||
result._collection = this.collection;
|
||||
result._options = this.options;
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Display a dialog window with an editable form for a UFGroupModel
|
||||
*
|
||||
* The implementation here is very "jQuery-style" and not "Backbone-style";
|
||||
* it's been extracted
|
||||
*
|
||||
* options:
|
||||
* - model: CRM.UF.UFGroupModel
|
||||
*/
|
||||
CRM.Designer.DesignerDialog = Backbone.Marionette.Layout.extend({
|
||||
serializeData: extendedSerializeData,
|
||||
template: '#designer_dialog_template',
|
||||
className: 'crm-designer-dialog',
|
||||
regions: {
|
||||
designerRegion: '.crm-designer'
|
||||
},
|
||||
/** @var bool whether this dialog is currently open */
|
||||
isDialogOpen: false,
|
||||
/** @var bool whether any changes have been made */
|
||||
isUfUnsaved: false,
|
||||
/** @var obj handle for the CRM.alert containing undo link */
|
||||
undoAlert: null,
|
||||
/** @var bool whether this dialog is being re-opened by the undo link */
|
||||
undoState: false,
|
||||
|
||||
initialize: function(options) {
|
||||
CRM.designerApp.vent.on('ufUnsaved', this.onUfChanged, this);
|
||||
CRM.designerApp.vent.on('ufSaved', this.onUfSaved, this);
|
||||
},
|
||||
onClose: function() {
|
||||
if (this.undoAlert && this.undoAlert.close) this.undoAlert.close();
|
||||
CRM.designerApp.vent.off('ufUnsaved', this.onUfChanged, this);
|
||||
},
|
||||
onUfChanged: function(isUfUnsaved) {
|
||||
this.isUfUnsaved = isUfUnsaved;
|
||||
},
|
||||
onUfSaved: function() {
|
||||
CRM.designerApp.vent.off('ufUnsaved', this.onUfChanged, this);
|
||||
this.isUfUnsaved = false;
|
||||
},
|
||||
onRender: function() {
|
||||
var designerDialog = this;
|
||||
designerDialog.$el.dialog({
|
||||
autoOpen: true, // note: affects accordion height
|
||||
title: ts('Edit Profile'),
|
||||
modal: true,
|
||||
width: '75%',
|
||||
height: parseInt($(window).height() * 0.8, 10),
|
||||
minWidth: 500,
|
||||
minHeight: 600, // to allow dropping in big whitespace, coordinate with min-height of .crm-designer-fields
|
||||
open: function() {
|
||||
// Prevent conflicts with other onbeforeunload handlers
|
||||
designerDialog.oldOnBeforeUnload = window.onbeforeunload;
|
||||
// Warn of unsaved changes when navigating away from the page
|
||||
window.onbeforeunload = function() {
|
||||
if (designerDialog.isDialogOpen && designerDialog.isUfUnsaved) {
|
||||
return ts("Your profile has not been saved.");
|
||||
}
|
||||
if (designerDialog.oldOnBeforeUnload) {
|
||||
return designerDialog.oldOnBeforeUnload.apply(arguments);
|
||||
}
|
||||
};
|
||||
if (designerDialog.undoAlert && designerDialog.undoAlert.close) designerDialog.undoAlert.close();
|
||||
designerDialog.isDialogOpen = true;
|
||||
// Initialize new dialog if we are not re-opening unsaved changes
|
||||
if (designerDialog.undoState === false) {
|
||||
if (designerDialog.designerRegion && designerDialog.designerRegion.close) designerDialog.designerRegion.close();
|
||||
designerDialog.$el.block();
|
||||
designerDialog.options.findCreateUfGroupModel({
|
||||
onLoad: function(ufGroupModel) {
|
||||
designerDialog.model = ufGroupModel;
|
||||
var designerLayout = new CRM.Designer.DesignerLayout({
|
||||
model: ufGroupModel,
|
||||
el: '<div class="full-height"></div>'
|
||||
});
|
||||
designerDialog.$el.unblock();
|
||||
designerDialog.designerRegion.show(designerLayout);
|
||||
CRM.designerApp.vent.trigger('resize');
|
||||
designerDialog.isUfUnsaved = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
designerDialog.undoState = false;
|
||||
// CRM-12188
|
||||
CRM.designerApp.DetachedProfiles = [];
|
||||
},
|
||||
close: function() {
|
||||
window.onbeforeunload = designerDialog.oldOnBeforeUnload;
|
||||
designerDialog.isDialogOpen = false;
|
||||
|
||||
if (designerDialog.undoAlert && designerDialog.undoAlert.close) designerDialog.undoAlert.close();
|
||||
if (designerDialog.isUfUnsaved) {
|
||||
designerDialog.undoAlert = CRM.alert('<p>' + ts('%1 has not been saved.', {1: designerDialog.model.get('title')}) + '</p><a href="#" class="crm-undo">' + ts('Restore') + '</a>', ts('Unsaved Changes'), 'alert', {expires: 60000});
|
||||
$('.ui-notify-message a.crm-undo').button({icons: {primary: 'fa-undo'}}).click(function(e) {
|
||||
e.preventDefault();
|
||||
designerDialog.undoState = true;
|
||||
designerDialog.$el.dialog('open');
|
||||
});
|
||||
}
|
||||
// CRM-12188
|
||||
CRM.designerApp.restorePreviewArea();
|
||||
},
|
||||
resize: function() {
|
||||
CRM.designerApp.vent.trigger('resize');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Display a complete form-editing UI, including canvas, palette, and
|
||||
* buttons.
|
||||
*
|
||||
* options:
|
||||
* - model: CRM.UF.UFGroupModel
|
||||
*/
|
||||
CRM.Designer.DesignerLayout = Backbone.Marionette.Layout.extend({
|
||||
serializeData: extendedSerializeData,
|
||||
template: '#designer_template',
|
||||
regions: {
|
||||
buttons: '.crm-designer-buttonset-region',
|
||||
palette: '.crm-designer-palette-region',
|
||||
form: '.crm-designer-form-region',
|
||||
fields: '.crm-designer-fields-region'
|
||||
},
|
||||
initialize: function() {
|
||||
CRM.designerApp.vent.on('resize', this.onResize, this);
|
||||
},
|
||||
onClose: function() {
|
||||
CRM.designerApp.vent.off('resize', this.onResize, this);
|
||||
},
|
||||
onRender: function() {
|
||||
this.buttons.show(new CRM.Designer.ToolbarView({
|
||||
model: this.model
|
||||
}));
|
||||
this.palette.show(new CRM.Designer.PaletteView({
|
||||
model: this.model
|
||||
}));
|
||||
this.form.show(new CRM.Designer.UFGroupView({
|
||||
model: this.model
|
||||
}));
|
||||
this.fields.show(new CRM.Designer.UFFieldCanvasView({
|
||||
model: this.model
|
||||
}));
|
||||
},
|
||||
onResize: function() {
|
||||
if (! this.hasResizedBefore) {
|
||||
this.hasResizedBefore = true;
|
||||
this.$('.crm-designer-toolbar').resizable({
|
||||
handles: 'w',
|
||||
maxWidth: 400,
|
||||
minWidth: 150,
|
||||
resize: function(event, ui) {
|
||||
$('.crm-designer-canvas').css('margin-right', (ui.size.width + 10) + 'px');
|
||||
$(this).css({left: '', height: ''});
|
||||
}
|
||||
}).css({left: '', height: ''});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Display toolbar with working button
|
||||
*
|
||||
* options:
|
||||
* - model: CRM.UF.UFGroupModel
|
||||
*/
|
||||
CRM.Designer.ToolbarView = Backbone.Marionette.ItemView.extend({
|
||||
serializeData: extendedSerializeData,
|
||||
template: '#designer_buttons_template',
|
||||
previewMode: false,
|
||||
events: {
|
||||
'click .crm-designer-save': 'doSave',
|
||||
'click .crm-designer-preview': 'doPreview'
|
||||
},
|
||||
onRender: function() {
|
||||
this.$('.crm-designer-save').button({icons: {primary: 'fa-check'}}).attr({
|
||||
disabled: 'disabled',
|
||||
style: 'opacity:.5; cursor:default;'
|
||||
});
|
||||
this.$('.crm-designer-preview').button({icons: {primary: 'fa-television'}});
|
||||
},
|
||||
initialize: function(options) {
|
||||
CRM.designerApp.vent.on('ufUnsaved', this.onUfChanged, this);
|
||||
},
|
||||
onUfChanged: function(isUfUnsaved) {
|
||||
if (isUfUnsaved) {
|
||||
this.$('.crm-designer-save').removeAttr('style').prop('disabled', false);
|
||||
}
|
||||
},
|
||||
doSave: function(e) {
|
||||
e.preventDefault();
|
||||
var ufGroupModel = this.model;
|
||||
if (ufGroupModel.getRel('ufFieldCollection').hasDuplicates()) {
|
||||
CRM.alert(ts('Please correct errors before saving.'), '', 'alert');
|
||||
return;
|
||||
}
|
||||
var $dialog = this.$el.closest('.crm-designer-dialog'); // FIXME use events
|
||||
$dialog.block();
|
||||
var profile = ufGroupModel.toStrictJSON();
|
||||
profile["api.UFField.replace"] = {values: ufGroupModel.getRel('ufFieldCollection').toSortedJSON(), 'option.autoweight': 0};
|
||||
CRM.api('UFGroup', 'create', profile, {
|
||||
success: function(data) {
|
||||
$dialog.unblock();
|
||||
var error = false;
|
||||
if (data.is_error) {
|
||||
CRM.alert(data.error_message);
|
||||
error = true;
|
||||
}
|
||||
_.each(data.values, function(ufGroupResponse) {
|
||||
if (ufGroupResponse['api.UFField.replace'].is_error) {
|
||||
CRM.alert(ufGroupResponse['api.UFField.replace'].error_message);
|
||||
error = true;
|
||||
}
|
||||
});
|
||||
if (!error) {
|
||||
if (!ufGroupModel.get('id')) {
|
||||
ufGroupModel.set('id', data.id);
|
||||
}
|
||||
CRM.designerApp.vent.trigger('ufUnsaved', false);
|
||||
CRM.designerApp.vent.trigger('ufSaved');
|
||||
$dialog.dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
doPreview: function(e) {
|
||||
e.preventDefault();
|
||||
this.previewMode = !this.previewMode;
|
||||
if (!this.previewMode) {
|
||||
$('.crm-designer-preview-canvas').html('');
|
||||
$('.crm-designer-canvas > *, .crm-designer-palette-region').show();
|
||||
$('.crm-designer-preview').button('option', {icons: {primary: 'fa-television'}}).find('span').text(ts('Preview'));
|
||||
return;
|
||||
}
|
||||
if (this.model.getRel('ufFieldCollection').hasDuplicates()) {
|
||||
CRM.alert(ts('Please correct errors before previewing.'), '', 'alert');
|
||||
return;
|
||||
}
|
||||
var $dialog = this.$el.closest('.crm-designer-dialog'); // FIXME use events
|
||||
$dialog.block();
|
||||
// CRM-12188
|
||||
CRM.designerApp.clearPreviewArea();
|
||||
$.post(CRM.url("civicrm/ajax/inline"), {
|
||||
'qfKey': CRM.profilePreviewKey,
|
||||
'class_name': 'CRM_UF_Form_Inline_Preview',
|
||||
'snippet': 1,
|
||||
'ufData': JSON.stringify({
|
||||
ufGroup: this.model.toStrictJSON(),
|
||||
ufFieldCollection: this.model.getRel('ufFieldCollection').toSortedJSON()
|
||||
})
|
||||
}).done(function(data) {
|
||||
$dialog.unblock();
|
||||
$('.crm-designer-canvas > *, .crm-designer-palette-region').hide();
|
||||
$('.crm-designer-preview-canvas').html(data).show().trigger('crmLoad').find(':input').prop('readOnly', true);
|
||||
$('.crm-designer-preview').button('option', {icons: {primary: 'fa-pencil'}}).find('span').text(ts('Edit'));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Display a selection of available fields
|
||||
*
|
||||
* options:
|
||||
* - model: CRM.UF.UFGroupModel
|
||||
*/
|
||||
CRM.Designer.PaletteView = Backbone.Marionette.ItemView.extend({
|
||||
serializeData: extendedSerializeData,
|
||||
template: '#palette_template',
|
||||
el: '<div class="full-height"></div>',
|
||||
openTreeNodes: [],
|
||||
events: {
|
||||
'keyup .crm-designer-palette-search input': 'doSearch',
|
||||
'change .crm-contact-types': 'doSetPaletteEntity',
|
||||
'click .crm-designer-palette-clear-search': 'clearSearch',
|
||||
'click .crm-designer-palette-toggle': 'toggleAll',
|
||||
'click .crm-designer-palette-add button': 'doNewCustomFieldDialog',
|
||||
'click #crm-designer-add-custom-set': 'doNewCustomSetDialog',
|
||||
'dblclick .crm-designer-palette-field': 'doAddToCanvas'
|
||||
},
|
||||
initialize: function() {
|
||||
this.model.getRel('ufFieldCollection')
|
||||
.on('add', this.toggleActive, this)
|
||||
.on('remove', this.toggleActive, this);
|
||||
this.model.getRel('paletteFieldCollection')
|
||||
.on('reset', this.render, this);
|
||||
CRM.designerApp.vent.on('resize', this.onResize, this);
|
||||
},
|
||||
onClose: function() {
|
||||
this.model.getRel('ufFieldCollection')
|
||||
.off('add', this.toggleActive, this)
|
||||
.off('remove', this.toggleActive, this);
|
||||
this.model.getRel('paletteFieldCollection')
|
||||
.off('reset', this.render, this);
|
||||
CRM.designerApp.vent.off('resize', this.onResize, this);
|
||||
},
|
||||
onRender: function() {
|
||||
var paletteView = this;
|
||||
|
||||
// Prepare data for jstree
|
||||
var treeData = [];
|
||||
var paletteFieldsByEntitySection = this.model.getRel('paletteFieldCollection').getFieldsByEntitySection();
|
||||
|
||||
paletteView.model.getRel('ufEntityCollection').each(function(ufEntityModel){
|
||||
_.each(ufEntityModel.getSections(), function(section, sectionKey){
|
||||
var defaultValue = paletteView.selectedContactType;
|
||||
if (!defaultValue) {
|
||||
defaultValue = paletteView.model.calculateContactEntityType();
|
||||
}
|
||||
|
||||
// set selected option as default, since we are rebuilding palette
|
||||
paletteView.$('.crm-contact-types').val(defaultValue).prop('selected','selected');
|
||||
|
||||
var entitySection = ufEntityModel.get('entity_name') + '-' + sectionKey;
|
||||
var items = [];
|
||||
if (paletteFieldsByEntitySection[entitySection]) {
|
||||
_.each(paletteFieldsByEntitySection[entitySection], function(paletteFieldModel, k) {
|
||||
items.push({data: paletteFieldModel.getLabel(), attr: {'class': 'crm-designer-palette-field', 'data-plm-cid': paletteFieldModel.cid}});
|
||||
});
|
||||
}
|
||||
if (section.is_addable) {
|
||||
items.push({data: ts('+ Add New Field'), attr: {'class': 'crm-designer-palette-add'}});
|
||||
}
|
||||
if (items.length > 0) {
|
||||
treeData.push({
|
||||
data: section.title,
|
||||
children: items,
|
||||
state: _.contains(paletteView.openTreeNodes, sectionKey) ? 'open' : 'closed',
|
||||
attr: {
|
||||
'class': 'crm-designer-palette-section',
|
||||
'data-section': sectionKey,
|
||||
'data-entity': ufEntityModel.get('entity_name')
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.$('.crm-designer-palette-tree').jstree({
|
||||
'json_data': {data: treeData},
|
||||
'search': {
|
||||
'case_insensitive' : true,
|
||||
'show_only_matches': true
|
||||
},
|
||||
themes: {
|
||||
"theme": 'classic',
|
||||
"dots": false,
|
||||
"icons": false,
|
||||
"url": CRM.config.resourceBase + 'packages/jquery/plugins/jstree/themes/classic/style.css'
|
||||
},
|
||||
'plugins': ['themes', 'json_data', 'ui', 'search']
|
||||
}).bind('loaded.jstree', function () {
|
||||
$('.crm-designer-palette-field', this).draggable({
|
||||
appendTo: '.crm-designer',
|
||||
zIndex: $(this.$el).css("zIndex") + 5000,
|
||||
helper: 'clone',
|
||||
connectToSortable: '.crm-designer-fields' // FIXME: tight canvas/palette coupling
|
||||
});
|
||||
paletteView.model.getRel('ufFieldCollection').each(function(ufFieldModel) {
|
||||
paletteView.toggleActive(ufFieldModel, paletteView.model.getRel('ufFieldCollection'));
|
||||
});
|
||||
paletteView.$('.crm-designer-palette-add a').replaceWith('<button>' + $('.crm-designer-palette-add a').first().text() + '</<button>');
|
||||
paletteView.$('.crm-designer-palette-tree > ul').append('<li><button id="crm-designer-add-custom-set">+ ' + ts('Add Set of Custom Fields') + '</button></li>');
|
||||
paletteView.$('.crm-designer-palette-tree button').button();
|
||||
}).bind("select_node.jstree", function (e, data) {
|
||||
$(this).jstree("toggle_node", data.rslt.obj);
|
||||
$(this).jstree("deselect_node", data.rslt.obj);
|
||||
});
|
||||
|
||||
// FIXME: tight canvas/palette coupling
|
||||
this.$(".crm-designer-fields").droppable({
|
||||
activeClass: "ui-state-default",
|
||||
hoverClass: "ui-state-hover",
|
||||
accept: ":not(.ui-sortable-helper)"
|
||||
});
|
||||
|
||||
this.onResize();
|
||||
},
|
||||
onResize: function() {
|
||||
var pos = this.$('.crm-designer-palette-tree').position();
|
||||
var div = this.$('.crm-designer-palette-tree').closest('.crm-container').height();
|
||||
this.$('.crm-designer-palette-tree').css({height: div - pos.top});
|
||||
},
|
||||
doSearch: function(e) {
|
||||
var str = $(e.target).val();
|
||||
this.$('.crm-designer-palette-clear-search').css('visibility', str ? 'visible' : 'hidden');
|
||||
this.$('.crm-designer-palette-tree').jstree("search", str);
|
||||
},
|
||||
doSetPaletteEntity: function(event) {
|
||||
this.selectedContactType = $('.crm-contact-types :selected').val();
|
||||
// loop through entity collection and remove non-valid entity section's
|
||||
var newUfEntityModels = [];
|
||||
this.model.getRel('ufEntityCollection').each(function(oldUfEntityModel){
|
||||
var values = oldUfEntityModel.toJSON();
|
||||
if (values.entity_name == 'contact_1') {
|
||||
values.entity_type = $('.crm-contact-types :selected').val();
|
||||
}
|
||||
newUfEntityModels.push(new CRM.UF.UFEntityModel(values));
|
||||
});
|
||||
this.model.getRel('ufEntityCollection').reset(newUfEntityModels);
|
||||
},
|
||||
doAddToCanvas: function(event) {
|
||||
var paletteFieldModel = this.model.getRel('paletteFieldCollection').get($(event.currentTarget).attr('data-plm-cid'));
|
||||
paletteFieldModel.addToUFCollection(this.model.getRel('ufFieldCollection'));
|
||||
event.stopPropagation();
|
||||
},
|
||||
doNewCustomFieldDialog: function(e) {
|
||||
e.preventDefault();
|
||||
var paletteView = this;
|
||||
var entityKey = $(e.currentTarget).closest('.crm-designer-palette-section').attr('data-entity');
|
||||
var sectionKey = $(e.currentTarget).closest('.crm-designer-palette-section').attr('data-section');
|
||||
var ufEntityModel = paletteView.model.getRel('ufEntityCollection').getByName(entityKey);
|
||||
var sections = ufEntityModel.getSections();
|
||||
var url = CRM.url('civicrm/admin/custom/group/field/add', {
|
||||
reset: 1,
|
||||
action: 'add',
|
||||
gid: sections[sectionKey].custom_group_id
|
||||
});
|
||||
CRM.loadForm(url).on('crmFormSuccess', function(e, data) {
|
||||
paletteView.doRefresh('custom_' + data.id);
|
||||
});
|
||||
},
|
||||
doNewCustomSetDialog: function(e) {
|
||||
e.preventDefault();
|
||||
var paletteView = this;
|
||||
var url = CRM.url('civicrm/admin/custom/group', 'action=add&reset=1');
|
||||
// Create custom field set and automatically go to next step (create fields) after save button is clicked.
|
||||
CRM.loadForm(url, {refreshAction: ['next']})
|
||||
.on('crmFormSuccess', function(e, data) {
|
||||
// When form switches to create custom field context, modify button behavior to only continue for "save and new"
|
||||
if (data.customField) ($(this).data('civiCrmSnippet').options.crmForm.refreshAction = ['next_new']);
|
||||
paletteView.doRefresh(data.customField ? 'custom_' + data.id : null);
|
||||
});
|
||||
},
|
||||
doRefresh: function(fieldToAdd) {
|
||||
var ufGroupModel = this.model;
|
||||
this.getOpenTreeNodes();
|
||||
CRM.Schema.reloadModels()
|
||||
.done(function(data){
|
||||
ufGroupModel.resetEntities();
|
||||
if (fieldToAdd) {
|
||||
var field = ufGroupModel.getRel('paletteFieldCollection').getFieldByName(null, fieldToAdd);
|
||||
field.addToUFCollection(ufGroupModel.getRel('ufFieldCollection'));
|
||||
}
|
||||
})
|
||||
.fail(function() {
|
||||
CRM.alert(ts('Failed to retrieve schema'), ts('Error'), 'error');
|
||||
});
|
||||
},
|
||||
clearSearch: function(e) {
|
||||
e.preventDefault();
|
||||
$('.crm-designer-palette-search input').val('').keyup();
|
||||
},
|
||||
toggleActive: function(ufFieldModel, ufFieldCollection, options) {
|
||||
var paletteFieldCollection = this.model.getRel('paletteFieldCollection');
|
||||
var paletteFieldModel = paletteFieldCollection.getFieldByName(ufFieldModel.get('entity_name'), ufFieldModel.get('field_name'));
|
||||
var isAddable = ufFieldCollection.isAddable(ufFieldModel);
|
||||
if (paletteFieldModel) {
|
||||
this.$('[data-plm-cid='+paletteFieldModel.cid+']').toggleClass('disabled', !isAddable);
|
||||
}
|
||||
},
|
||||
toggleAll: function(e) {
|
||||
if (_.isEmpty($('.crm-designer-palette-search input').val())) {
|
||||
$('.crm-designer-palette-tree').jstree($(e.target).attr('rel'));
|
||||
}
|
||||
e.preventDefault();
|
||||
},
|
||||
getOpenTreeNodes: function() {
|
||||
var paletteView = this;
|
||||
this.openTreeNodes = [];
|
||||
this.$('.crm-designer-palette-section.jstree-open').each(function() {
|
||||
paletteView.openTreeNodes.push($(this).data('section'));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Display all UFFieldModel objects in a UFGroupModel.
|
||||
*
|
||||
* options:
|
||||
* - model: CRM.UF.UFGroupModel
|
||||
*/
|
||||
CRM.Designer.UFFieldCanvasView = Backbone.Marionette.View.extend({
|
||||
initialize: function() {
|
||||
this.model.getRel('ufFieldCollection')
|
||||
.on('add', this.updatePlaceholder, this)
|
||||
.on('remove', this.updatePlaceholder, this)
|
||||
.on('add', this.addUFFieldView, this)
|
||||
.on('reset', this.render, this);
|
||||
},
|
||||
onClose: function() {
|
||||
this.model.getRel('ufFieldCollection')
|
||||
.off('add', this.updatePlaceholder, this)
|
||||
.off('remove', this.updatePlaceholder, this)
|
||||
.off('add', this.addUFFieldView, this)
|
||||
.off('reset', this.render, this);
|
||||
},
|
||||
render: function() {
|
||||
var ufFieldCanvasView = this;
|
||||
this.$el.html(_.template($('#field_canvas_view_template').html()));
|
||||
|
||||
// BOTTOM: Setup field-level editing
|
||||
var $fields = this.$('.crm-designer-fields');
|
||||
this.updatePlaceholder();
|
||||
var ufFieldModels = this.model.getRel('ufFieldCollection').sortBy(function(ufFieldModel) {
|
||||
return parseInt(ufFieldModel.get('weight'));
|
||||
});
|
||||
_.each(ufFieldModels, function(ufFieldModel) {
|
||||
ufFieldCanvasView.addUFFieldView(ufFieldModel, ufFieldCanvasView.model.getRel('ufFieldCollection'), {skipWeights: true});
|
||||
});
|
||||
this.$(".crm-designer-fields").sortable({
|
||||
placeholder: 'crm-designer-row-placeholder',
|
||||
forcePlaceholderSize: true,
|
||||
cancel: 'input,textarea,button,select,option,a,.crm-designer-open',
|
||||
receive: function(event, ui) {
|
||||
var paletteFieldModel = ufFieldCanvasView.model.getRel('paletteFieldCollection').get(ui.item.attr('data-plm-cid'));
|
||||
var ufFieldModel = paletteFieldModel.addToUFCollection(
|
||||
ufFieldCanvasView.model.getRel('ufFieldCollection'),
|
||||
{skipWeights: true}
|
||||
);
|
||||
if (_.isEmpty(ufFieldModel)) {
|
||||
ufFieldCanvasView.$('.crm-designer-fields .ui-draggable').remove();
|
||||
} else {
|
||||
// Move from end to the 'dropped' position
|
||||
var ufFieldViewEl = ufFieldCanvasView.$('div[data-field-cid='+ufFieldModel.cid+']').parent();
|
||||
ufFieldCanvasView.$('.crm-designer-fields .ui-draggable').replaceWith(ufFieldViewEl);
|
||||
}
|
||||
// note: the sortable() update callback will call updateWeight
|
||||
},
|
||||
update: function() {
|
||||
ufFieldCanvasView.updateWeights();
|
||||
}
|
||||
});
|
||||
},
|
||||
/** Determine visual order of fields and set the model values for "weight" */
|
||||
updateWeights: function() {
|
||||
var ufFieldCanvasView = this;
|
||||
var weight = 1;
|
||||
var rows = this.$('.crm-designer-row').each(function(key, row) {
|
||||
if ($(row).hasClass('placeholder')) {
|
||||
return;
|
||||
}
|
||||
var ufFieldCid = $(row).attr('data-field-cid');
|
||||
var ufFieldModel = ufFieldCanvasView.model.getRel('ufFieldCollection').get(ufFieldCid);
|
||||
ufFieldModel.set('weight', weight);
|
||||
weight++;
|
||||
});
|
||||
},
|
||||
addUFFieldView: function(ufFieldModel, ufFieldCollection, options) {
|
||||
var paletteFieldModel = this.model.getRel('paletteFieldCollection').getFieldByName(ufFieldModel.get('entity_name'), ufFieldModel.get('field_name'));
|
||||
var ufFieldView = new CRM.Designer.UFFieldView({
|
||||
el: $("<div></div>"),
|
||||
model: ufFieldModel,
|
||||
paletteFieldModel: paletteFieldModel
|
||||
});
|
||||
ufFieldView.render();
|
||||
this.$('.crm-designer-fields').append(ufFieldView.$el);
|
||||
if (! (options && options.skipWeights)) {
|
||||
this.updateWeights();
|
||||
}
|
||||
},
|
||||
updatePlaceholder: function() {
|
||||
if (this.model.getRel('ufFieldCollection').isEmpty()) {
|
||||
this.$('.placeholder').css({display: 'block', border: '0 none', cursor: 'default'});
|
||||
} else {
|
||||
this.$('.placeholder').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* options:
|
||||
* - model: CRM.UF.UFFieldModel
|
||||
* - paletteFieldModel: CRM.Designer.PaletteFieldModel
|
||||
*/
|
||||
CRM.Designer.UFFieldView = Backbone.Marionette.Layout.extend({
|
||||
serializeData: extendedSerializeData,
|
||||
template: '#field_row_template',
|
||||
expanded: false,
|
||||
regions: {
|
||||
summary: '.crm-designer-field-summary',
|
||||
detail: '.crm-designer-field-detail'
|
||||
},
|
||||
events: {
|
||||
"click .crm-designer-action-settings": 'doToggleForm',
|
||||
"click button.crm-designer-edit-custom": 'doEditCustomField',
|
||||
"click .crm-designer-action-remove": 'doRemove'
|
||||
},
|
||||
modelEvents: {
|
||||
"destroy": 'remove',
|
||||
"change:is_duplicate": 'onChangeIsDuplicate'
|
||||
},
|
||||
onRender: function() {
|
||||
this.summary.show(new CRM.Designer.UFFieldSummaryView({
|
||||
model: this.model,
|
||||
fieldSchema: this.model.getFieldSchema(),
|
||||
paletteFieldModel: this.options.paletteFieldModel
|
||||
}));
|
||||
this.detail.show(new CRM.Designer.UFFieldDetailView({
|
||||
model: this.model,
|
||||
fieldSchema: this.model.getFieldSchema()
|
||||
}));
|
||||
this.onChangeIsDuplicate(this.model, this.model.get('is_duplicate'));
|
||||
if (!this.expanded) {
|
||||
this.detail.$el.hide();
|
||||
}
|
||||
var that = this;
|
||||
CRM.designerApp.vent.on('formOpened', function(event) {
|
||||
if (that.expanded && event != that.cid) {
|
||||
that.doToggleForm(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
doToggleForm: function(event) {
|
||||
this.expanded = !this.expanded;
|
||||
if (this.expanded && event !== false) {
|
||||
CRM.designerApp.vent.trigger('formOpened', this.cid);
|
||||
}
|
||||
this.$el.toggleClass('crm-designer-open', this.expanded);
|
||||
var $detail = this.detail.$el;
|
||||
if (!this.expanded) {
|
||||
$detail.toggle('blind', 250);
|
||||
this.$('button.crm-designer-edit-custom').remove();
|
||||
}
|
||||
else {
|
||||
var $canvas = $('.crm-designer-canvas');
|
||||
var top = $canvas.offset().top;
|
||||
$detail.slideDown({
|
||||
duration: 250,
|
||||
step: function(num, effect) {
|
||||
// Scroll canvas to keep field details visible
|
||||
if (effect.prop == 'height') {
|
||||
if (effect.now + $detail.offset().top - top > $canvas.height() - 9) {
|
||||
$canvas.scrollTop($canvas.scrollTop() + effect.now + $detail.offset().top - top - $canvas.height() + 9);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (this.model.get('field_name').split('_')[0] == 'custom') {
|
||||
this.$('.crm-designer-field-summary > div').append('<button class="crm-designer-edit-custom">' + ts('Edit Custom Field') + '</button>');
|
||||
this.$('button.crm-designer-edit-custom').button({icons: {primary: 'fa-pencil'}}).attr('title', ts('Edit global settings for this custom field.'));
|
||||
}
|
||||
}
|
||||
},
|
||||
doEditCustomField: function(e) {
|
||||
e.preventDefault();
|
||||
var url = CRM.url('civicrm/admin/custom/group/field/update', {
|
||||
action: 'update',
|
||||
reset: 1,
|
||||
id: this.model.get('field_name').split('_')[1]
|
||||
});
|
||||
var form1 = CRM.loadForm(url)
|
||||
.on('crmFormLoad', function() {
|
||||
$(this).prepend('<div class="messages status"><i class="crm-i fa-info-circle"></i> ' + ts('Note: This will modify the field system-wide, not just in this profile form.') + '</div>');
|
||||
});
|
||||
},
|
||||
onChangeIsDuplicate: function(model, value, options) {
|
||||
this.$el.toggleClass('crm-designer-duplicate', value);
|
||||
},
|
||||
doRemove: function(event) {
|
||||
var that = this;
|
||||
this.$el.hide(250, function() {
|
||||
that.model.destroyLocal();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* options:
|
||||
* - model: CRM.UF.UFFieldModel
|
||||
* - fieldSchema: (Backbone.Form schema element)
|
||||
* - paletteFieldModel: CRM.Designer.PaletteFieldModel
|
||||
*/
|
||||
CRM.Designer.UFFieldSummaryView = Backbone.Marionette.ItemView.extend({
|
||||
serializeData: extendedSerializeData,
|
||||
template: '#field_summary_template',
|
||||
modelEvents: {
|
||||
'change': 'render'
|
||||
},
|
||||
|
||||
/**
|
||||
* Compose a printable string which describes the binding of this UFField to the data model
|
||||
* @return {String}
|
||||
*/
|
||||
getBindingLabel: function() {
|
||||
var result = this.options.paletteFieldModel.getSection().title + ": " + this.options.paletteFieldModel.getLabel();
|
||||
if (this.options.fieldSchema.civiIsPhone) {
|
||||
result = result + '-' + CRM.PseudoConstant.phoneType[this.model.get('phone_type_id')];
|
||||
}
|
||||
if (this.options.fieldSchema.civiIsWebsite) {
|
||||
result = result + '-' + CRM.PseudoConstant.websiteType[this.model.get('website_type_id')];
|
||||
}
|
||||
if (this.options.fieldSchema.civiIsLocation) {
|
||||
var locType = this.model.get('location_type_id') ? CRM.PseudoConstant.locationType[this.model.get('location_type_id')] : ts('Primary');
|
||||
result = result + ' (' + locType + ')';
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a string marking if the field is required
|
||||
* @return {String}
|
||||
*/
|
||||
getRequiredMarker: function() {
|
||||
if (this.model.get('is_required') == 1) {
|
||||
return ' <span class="crm-marker">*</span> ';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
onRender: function() {
|
||||
this.$el.toggleClass('disabled', this.model.get('is_active') != 1);
|
||||
if (this.model.get("is_reserved") == 1) {
|
||||
this.$('.crm-designer-buttons').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* options:
|
||||
* - model: CRM.UF.UFFieldModel
|
||||
* - fieldSchema: (Backbone.Form schema element)
|
||||
*/
|
||||
CRM.Designer.UFFieldDetailView = Backbone.View.extend({
|
||||
initialize: function() {
|
||||
// FIXME: hide/display 'in_selector' if 'visibility' is one of the public options
|
||||
var fields = ['location_type_id', 'website_type_id', 'phone_type_id', 'label', 'is_multi_summary', 'is_required', 'is_view', 'visibility', 'in_selector', 'is_searchable', 'help_pre', 'help_post', 'is_active'];
|
||||
if (! this.options.fieldSchema.civiIsLocation) {
|
||||
fields = _.without(fields, 'location_type_id');
|
||||
}
|
||||
if (! this.options.fieldSchema.civiIsWebsite) {
|
||||
fields = _.without(fields, 'website_type_id');
|
||||
}
|
||||
if (! this.options.fieldSchema.civiIsPhone) {
|
||||
fields = _.without(fields, 'phone_type_id');
|
||||
}
|
||||
if (!this.options.fieldSchema.civiIsMultiple) {
|
||||
fields = _.without(fields, 'is_multi_summary');
|
||||
}
|
||||
if (this.options.fieldSchema.type == 'Markup') {
|
||||
fields = _.without(fields, 'is_required', 'is_view', 'visibility', 'in_selector', 'is_searchable', 'help_post');
|
||||
}
|
||||
|
||||
this.form = new Backbone.Form({
|
||||
model: this.model,
|
||||
fields: fields
|
||||
});
|
||||
this.form.on('change', this.onFormChange, this);
|
||||
this.model.on('change', this.onModelChange, this);
|
||||
},
|
||||
render: function() {
|
||||
this.$el.html(this.form.render().el);
|
||||
this.onFormChange();
|
||||
},
|
||||
onModelChange: function() {
|
||||
$.each(this.form.fields, function(i, field) {
|
||||
this.form.setValue(field.key, this.model.get(field.key));
|
||||
});
|
||||
},
|
||||
onFormChange: function() {
|
||||
this.form.commit();
|
||||
this.$('.field-is_multi_summary').toggle(this.options.fieldSchema.civiIsMultiple ? true : false);
|
||||
this.$('.field-in_selector').toggle(this.model.isInSelectorAllowed());
|
||||
|
||||
if (!this.model.isInSelectorAllowed() && this.model.get('in_selector') != "0") {
|
||||
this.model.set('in_selector', "0");
|
||||
if (this.form.fields.in_selector) {
|
||||
this.form.setValue('in_selector', "0");
|
||||
}
|
||||
// TODO: It might be nicer if we didn't completely discard in_selector -- e.g.
|
||||
// if the value could be restored when the user isInSelectorAllowed becomes true
|
||||
// again. However, I haven't found a simple way to do this.
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* options:
|
||||
* - model: CRM.UF.UFGroupModel
|
||||
*/
|
||||
CRM.Designer.UFGroupView = Backbone.Marionette.Layout.extend({
|
||||
serializeData: extendedSerializeData,
|
||||
template: '#form_row_template',
|
||||
expanded: false,
|
||||
regions: {
|
||||
summary: '.crm-designer-form-summary',
|
||||
detail: '.crm-designer-form-detail'
|
||||
},
|
||||
events: {
|
||||
"click .crm-designer-action-settings": 'doToggleForm'
|
||||
},
|
||||
onRender: function() {
|
||||
this.summary.show(new CRM.Designer.UFGroupSummaryView({
|
||||
model: this.model
|
||||
}));
|
||||
this.detail.show(new CRM.Designer.UFGroupDetailView({
|
||||
model: this.model
|
||||
}));
|
||||
if (!this.expanded) {
|
||||
this.detail.$el.hide();
|
||||
}
|
||||
var that = this;
|
||||
CRM.designerApp.vent.on('formOpened', function(event) {
|
||||
if (that.expanded && event !== 0) {
|
||||
that.doToggleForm(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
doToggleForm: function(event) {
|
||||
this.expanded = !this.expanded;
|
||||
if (this.expanded && event !== false) {
|
||||
CRM.designerApp.vent.trigger('formOpened', 0);
|
||||
}
|
||||
this.$el.toggleClass('crm-designer-open', this.expanded);
|
||||
this.detail.$el.toggle('blind', 250);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* options:
|
||||
* - model: CRM.UF.UFGroupModel
|
||||
*/
|
||||
CRM.Designer.UFGroupSummaryView = Backbone.Marionette.ItemView.extend({
|
||||
serializeData: extendedSerializeData,
|
||||
template: '#form_summary_template',
|
||||
modelEvents: {
|
||||
'change': 'render'
|
||||
},
|
||||
onRender: function() {
|
||||
this.$el.toggleClass('disabled', this.model.get('is_active') != 1);
|
||||
if (this.model.get("is_reserved") == 1) {
|
||||
this.$('.crm-designer-buttons').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* options:
|
||||
* - model: CRM.UF.UFGroupModel
|
||||
*/
|
||||
CRM.Designer.UFGroupDetailView = Backbone.View.extend({
|
||||
initialize: function() {
|
||||
this.form = new Backbone.Form({
|
||||
model: this.model,
|
||||
fields: ['title', 'help_pre', 'help_post', 'is_active']
|
||||
});
|
||||
this.form.on('change', this.form.commit, this.form);
|
||||
},
|
||||
render: function() {
|
||||
this.$el.html(this.form.render().el);
|
||||
}
|
||||
});
|
||||
|
||||
})(CRM.$, CRM._);
|
188
sites/all/modules/civicrm/js/view/crm.profile-selector.js
Normal file
188
sites/all/modules/civicrm/js/view/crm.profile-selector.js
Normal file
|
@ -0,0 +1,188 @@
|
|||
(function($, _) {
|
||||
if (!CRM.ProfileSelector) CRM.ProfileSelector = {};
|
||||
|
||||
CRM.ProfileSelector.Option = Backbone.Marionette.ItemView.extend({
|
||||
template: '#profile_selector_option_template',
|
||||
tagName: 'option',
|
||||
modelEvents: {
|
||||
'change:title': 'render'
|
||||
},
|
||||
onRender: function() {
|
||||
this.$el.attr('value', this.model.get('id'));
|
||||
}
|
||||
});
|
||||
|
||||
CRM.ProfileSelector.Select = Backbone.Marionette.CollectionView.extend({
|
||||
tagName: 'select',
|
||||
itemView: CRM.ProfileSelector.Option
|
||||
});
|
||||
|
||||
/**
|
||||
* Render a pane with 'Select/Preview/Edit/Copy/Create' functionality for profiles.
|
||||
*
|
||||
* Note: This view works with a ufGroupCollection, and it creates popups for a
|
||||
* ufGroupModel. These are related but not facilely. The ufGroupModels in the
|
||||
* ufGroupCollection are never passed to the popup, and the models from the
|
||||
* popup are never added to the collection. This is because the popup works
|
||||
* with temporary, local copies -- but the collection reflects the actual list
|
||||
* on the server.
|
||||
*
|
||||
* options:
|
||||
* - ufGroupId: int, the default selection
|
||||
* - ufGroupCollection: the profiles which can be selected
|
||||
* - ufEntities: hard-coded entity list used with any new/existing forms
|
||||
* (this may be removed when the form-runtime is updated to support hand-picking
|
||||
* entities for each form)
|
||||
*/
|
||||
CRM.ProfileSelector.View = Backbone.Marionette.Layout.extend({
|
||||
template: '#profile_selector_template',
|
||||
regions: {
|
||||
selectRegion: '.crm-profile-selector-select'
|
||||
},
|
||||
events: {
|
||||
'change .crm-profile-selector-select select': 'onChangeUfGroupId',
|
||||
'click .crm-profile-selector-edit': 'doEdit',
|
||||
'click .crm-profile-selector-copy': 'doCopy',
|
||||
'click .crm-profile-selector-create': 'doCreate',
|
||||
'click .crm-profile-selector-preview': 'doShowPreview',
|
||||
// prevent interaction with preview form
|
||||
'click .crm-profile-selector-preview-pane': false,
|
||||
'crmLoad .crm-profile-selector-preview-pane': 'disableForm'
|
||||
},
|
||||
/** @var Marionette.View which specifically builds on jQuery-UI's dialog */
|
||||
activeDialog: null,
|
||||
onRender: function() {
|
||||
var view = new CRM.ProfileSelector.Select({
|
||||
collection: this.options.ufGroupCollection
|
||||
});
|
||||
this.selectRegion.show(view);
|
||||
this.setUfGroupId(this.options.ufGroupId, {silent: true});
|
||||
this.toggleButtons();
|
||||
this.$('.crm-profile-selector-select select').css('width', '25em').crmSelect2();
|
||||
this.doShowPreview();
|
||||
},
|
||||
onChangeUfGroupId: function(event) {
|
||||
this.options.ufGroupId = $(event.target).val();
|
||||
this.trigger('change:ufGroupId', this);
|
||||
this.toggleButtons();
|
||||
this.doPreview();
|
||||
},
|
||||
toggleButtons: function() {
|
||||
this.$('.crm-profile-selector-edit,.crm-profile-selector-copy').prop('disabled', !this.hasUfGroupId());
|
||||
},
|
||||
hasUfGroupId: function() {
|
||||
return (this.getUfGroupId() && this.getUfGroupId() !== '') ? true : false;
|
||||
},
|
||||
setUfGroupId: function(value, options) {
|
||||
this.options.ufGroupId = value;
|
||||
this.$('.crm-profile-selector-select select').val(value);
|
||||
this.$('.crm-profile-selector-select select').select2('val', value, (!options || !options.silent));
|
||||
},
|
||||
getUfGroupId: function() {
|
||||
return this.options.ufGroupId;
|
||||
},
|
||||
doPreview: function() {
|
||||
var $pane = this.$('.crm-profile-selector-preview-pane');
|
||||
if (!this.hasUfGroupId()) {
|
||||
$pane.html($('#profile_selector_empty_preview_template').html());
|
||||
} else {
|
||||
CRM.loadPage(CRM.url("civicrm/ajax/inline", {class_name: 'CRM_UF_Form_Inline_PreviewById', id: this.getUfGroupId()}), {target: $pane});
|
||||
}
|
||||
},
|
||||
doShowPreview: function() {
|
||||
var $preview = this.$('.crm-profile-selector-preview');
|
||||
var $pane = this.$('.crm-profile-selector-preview-pane');
|
||||
if ($preview.hasClass('crm-profile-selector-preview-show')) {
|
||||
$preview.removeClass('crm-profile-selector-preview-show');
|
||||
$preview.find('.crm-i').removeClass('fa-television').addClass('fa-times');
|
||||
$pane.show();
|
||||
} else {
|
||||
$preview.addClass('crm-profile-selector-preview-show');
|
||||
$preview.find('.crm-i').removeClass('fa-times').addClass('fa-television');
|
||||
$pane.hide();
|
||||
}
|
||||
},
|
||||
disableForm: function() {
|
||||
this.$(':input', '.crm-profile-selector-preview-pane').not('.select2-input').prop('readOnly', true);
|
||||
},
|
||||
doEdit: function(e) {
|
||||
e.preventDefault();
|
||||
var profileSelectorView = this;
|
||||
var designerDialog = new CRM.Designer.DesignerDialog({
|
||||
findCreateUfGroupModel: function(options) {
|
||||
var ufId = profileSelectorView.getUfGroupId();
|
||||
// Retrieve UF group and fields from the api
|
||||
CRM.api('UFGroup', 'getsingle', {id: ufId, "api.UFField.get": 1}, {
|
||||
success: function(formData) {
|
||||
// Note: With chaining, API returns some extraneous keys that aren't part of UFGroupModel
|
||||
var ufGroupModel = new CRM.UF.UFGroupModel(_.pick(formData, _.keys(CRM.UF.UFGroupModel.prototype.schema)));
|
||||
ufGroupModel.setUFGroupModel(ufGroupModel.calculateContactEntityType(), profileSelectorView.options.ufEntities);
|
||||
ufGroupModel.getRel('ufFieldCollection').reset(_.values(formData["api.UFField.get"].values));
|
||||
options.onLoad(ufGroupModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
CRM.designerApp.vent.on('ufSaved', this.onSave, this);
|
||||
this.setDialog(designerDialog);
|
||||
},
|
||||
doCopy: function(e) {
|
||||
e.preventDefault();
|
||||
// This is largely the same as doEdit, but we ultimately pass in a deepCopy of the ufGroupModel.
|
||||
var profileSelectorView = this;
|
||||
var designerDialog = new CRM.Designer.DesignerDialog({
|
||||
findCreateUfGroupModel: function(options) {
|
||||
var ufId = profileSelectorView.getUfGroupId();
|
||||
// Retrieve UF group and fields from the api
|
||||
CRM.api('UFGroup', 'getsingle', {id: ufId, "api.UFField.get": 1}, {
|
||||
success: function(formData) {
|
||||
// Note: With chaining, API returns some extraneous keys that aren't part of UFGroupModel
|
||||
var ufGroupModel = new CRM.UF.UFGroupModel(_.pick(formData, _.keys(CRM.UF.UFGroupModel.prototype.schema)));
|
||||
ufGroupModel.setUFGroupModel(ufGroupModel.calculateContactEntityType(), profileSelectorView.options.ufEntities);
|
||||
ufGroupModel.getRel('ufFieldCollection').reset(_.values(formData["api.UFField.get"].values));
|
||||
options.onLoad(ufGroupModel.deepCopy());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
CRM.designerApp.vent.on('ufSaved', this.onSave, this);
|
||||
this.setDialog(designerDialog);
|
||||
},
|
||||
doCreate: function(e) {
|
||||
e.preventDefault();
|
||||
var profileSelectorView = this;
|
||||
var designerDialog = new CRM.Designer.DesignerDialog({
|
||||
findCreateUfGroupModel: function(options) {
|
||||
// Initialize new UF group
|
||||
var ufGroupModel = new CRM.UF.UFGroupModel();
|
||||
ufGroupModel.getRel('ufEntityCollection').reset(profileSelectorView.options.ufEntities);
|
||||
options.onLoad(ufGroupModel);
|
||||
}
|
||||
});
|
||||
CRM.designerApp.vent.on('ufSaved', this.onSave, this);
|
||||
this.setDialog(designerDialog);
|
||||
},
|
||||
onSave: function() {
|
||||
CRM.designerApp.vent.off('ufSaved', this.onSave, this);
|
||||
var ufGroupId = this.activeDialog.model.get('id');
|
||||
var modelFromCollection = this.options.ufGroupCollection.get(ufGroupId);
|
||||
if (modelFromCollection) {
|
||||
// copy in changes to UFGroup
|
||||
modelFromCollection.set(this.activeDialog.model.toStrictJSON());
|
||||
} else {
|
||||
// add in new UFGroup
|
||||
modelFromCollection = new CRM.UF.UFGroupModel(this.activeDialog.model.toStrictJSON());
|
||||
this.options.ufGroupCollection.add(modelFromCollection);
|
||||
}
|
||||
this.setUfGroupId(ufGroupId);
|
||||
this.doPreview();
|
||||
},
|
||||
setDialog: function(view) {
|
||||
if (this.activeDialog) {
|
||||
this.activeDialog.close();
|
||||
}
|
||||
this.activeDialog = view;
|
||||
view.render();
|
||||
}
|
||||
});
|
||||
})(CRM.$, CRM._);
|
|
@ -0,0 +1,114 @@
|
|||
// https://civicrm.org/licensing
|
||||
(function($, _) {
|
||||
'use strict';
|
||||
/* jshint validthis: true */
|
||||
|
||||
var configRowTpl = _.template($('#config-row-tpl').html()),
|
||||
options;
|
||||
|
||||
// Weird conflict with drupal styles
|
||||
$('body').removeClass('toolbar');
|
||||
|
||||
function format(item) {
|
||||
var icon = '<span class="ui-icon ui-icon-gear"></span>';
|
||||
if (item.icon) {
|
||||
icon = '<img src="' + CRM.config.resourceBase + item.icon + '" />';
|
||||
}
|
||||
return icon + ' ' + item.text;
|
||||
}
|
||||
|
||||
function initOptions(data) {
|
||||
options = _.filter(data, function(n) {
|
||||
return $.inArray(n.id, CRM.vars.ckConfig.blacklist) < 0;
|
||||
});
|
||||
addOption();
|
||||
$.each(CRM.vars.ckConfig.settings, function(key, val) {
|
||||
if ($.inArray(key, CRM.vars.ckConfig.blacklist) < 0) {
|
||||
var $opt = $('.crm-config-option-row:last input.crm-config-option-name');
|
||||
$opt.val(key).change();
|
||||
$opt.siblings('span').find(':input').val(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function changeOptionName() {
|
||||
var $el = $(this),
|
||||
name = $el.val();
|
||||
$el.next('span').remove();
|
||||
if (name) {
|
||||
if (($('input.crm-config-option-name').filter(function() {return !this.value;})).length < 1) {
|
||||
addOption();
|
||||
}
|
||||
var type = $el.select2('data').type;
|
||||
if (type === 'Boolean') {
|
||||
$el.after('<span> = <select class="crm-form-select" name="config_' + name + '"><option value="false">false</option><option value="true">true</option></select></span>');
|
||||
}
|
||||
else {
|
||||
$el.after('<span> = <input class="crm-form-text ' + (type==='Number' ? 'eight" type="number"' : 'huge" type="text"') + ' name="config_' + name + '"/></span>');
|
||||
}
|
||||
} else {
|
||||
$el.closest('div').remove();
|
||||
}
|
||||
}
|
||||
|
||||
function addOption() {
|
||||
$('#crm-custom-config-options').append($(configRowTpl({})));
|
||||
$('div:last input.crm-config-option-name', '#crm-custom-config-options').crmSelect2({
|
||||
data: {results: options, text: 'id'},
|
||||
formatSelection: function(field) {
|
||||
return '<strong>' + field.id + '</strong> (' + field.type + ')';
|
||||
},
|
||||
formatResult: function(field) {
|
||||
return '<strong>' + field.id + '</strong> (' + field.type + ')' +
|
||||
'<div class="api-field-desc">' + field.description + '</div>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#extraPlugins').crmSelect2({
|
||||
multiple: true,
|
||||
closeOnSelect: false,
|
||||
data: CRM.vars.ckConfig.plugins,
|
||||
escapeMarkup: _.identity,
|
||||
formatResult: format,
|
||||
formatSelection: format
|
||||
});
|
||||
|
||||
var toolbarModifier = new ToolbarConfigurator.ToolbarModifier( 'editor-basic' );
|
||||
|
||||
toolbarModifier.init(_.noop);
|
||||
|
||||
CKEDITOR.document.getById( 'toolbarModifierWrapper' ).append( toolbarModifier.mainContainer );
|
||||
|
||||
$(function() {
|
||||
var selectorOpen = false,
|
||||
changedWhileOpen = false;
|
||||
|
||||
$('#toolbarModifierForm')
|
||||
.on('submit', function(e) {
|
||||
$('.toolbar button:last', '#toolbarModifierWrapper')[0].click();
|
||||
$('.configContainer textarea', '#toolbarModifierWrapper').attr('name', 'config');
|
||||
})
|
||||
.on('change', '.config-param', function(e) {
|
||||
changedWhileOpen = true;
|
||||
if (!selectorOpen) {
|
||||
$('#toolbarModifierForm').submit().block();
|
||||
}
|
||||
})
|
||||
.on('change', 'input.crm-config-option-name', changeOptionName)
|
||||
// Debounce the change event so it only fires after the multiselect is closed
|
||||
.on('select2-open', 'input.config-param', function(e) {
|
||||
selectorOpen = true;
|
||||
changedWhileOpen = false;
|
||||
})
|
||||
.on('select2-close', 'input.config-param', function(e) {
|
||||
selectorOpen = false;
|
||||
if (changedWhileOpen) {
|
||||
$(this).change();
|
||||
}
|
||||
});
|
||||
|
||||
$.getJSON(CRM.config.resourceBase + 'js/wysiwyg/ck-options.json', null, initOptions);
|
||||
});
|
||||
|
||||
})(CRM.$, CRM._);
|
1177
sites/all/modules/civicrm/js/wysiwyg/ck-options.json
Normal file
1177
sites/all/modules/civicrm/js/wysiwyg/ck-options.json
Normal file
File diff suppressed because it is too large
Load diff
146
sites/all/modules/civicrm/js/wysiwyg/crm.ckeditor.js
Normal file
146
sites/all/modules/civicrm/js/wysiwyg/crm.ckeditor.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
// https://civicrm.org/licensing
|
||||
(function($, _) {
|
||||
|
||||
function getInstance(item) {
|
||||
var name = $(item).attr("name"),
|
||||
id = $(item).attr("id");
|
||||
if (name && window.CKEDITOR && CKEDITOR.instances[name]) {
|
||||
return CKEDITOR.instances[name];
|
||||
}
|
||||
if (id && window.CKEDITOR && CKEDITOR.instances[id]) {
|
||||
return CKEDITOR.instances[id];
|
||||
}
|
||||
}
|
||||
|
||||
CRM.wysiwyg.supportsFileUploads = true;
|
||||
|
||||
CRM.wysiwyg._create = function(item) {
|
||||
var deferred = $.Deferred();
|
||||
|
||||
function onReady() {
|
||||
var debounce,
|
||||
editor = this;
|
||||
|
||||
editor.on('focus', function() {
|
||||
$(item).trigger('focus');
|
||||
});
|
||||
editor.on('blur', function() {
|
||||
editor.updateElement();
|
||||
$(item).trigger("blur");
|
||||
$(item).trigger("change");
|
||||
});
|
||||
editor.on('insertText', function() {
|
||||
$(item).trigger("keypress");
|
||||
});
|
||||
_.each(['key', 'pasteState'], function(evName) {
|
||||
editor.on(evName, function(evt) {
|
||||
if (debounce) clearTimeout(debounce);
|
||||
debounce = setTimeout(function() {
|
||||
editor.updateElement();
|
||||
$(item).trigger("change");
|
||||
}, 50);
|
||||
});
|
||||
});
|
||||
editor.on('pasteState', function() {
|
||||
$(item).trigger("paste");
|
||||
});
|
||||
// Hide CiviCRM menubar when editor is fullscreen
|
||||
editor.on('maximize', function (e) {
|
||||
$('#civicrm-menu').toggle(e.data === 2);
|
||||
});
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
var
|
||||
browseUrl = CRM.config.resourceBase + "packages/kcfinder/browse.php?cms=civicrm",
|
||||
uploadUrl = CRM.config.resourceBase + "packages/kcfinder/upload.php?cms=civicrm",
|
||||
preset = $(item).data('preset') || 'default',
|
||||
// This variable is always an array but a legacy extension could be setting it as a string.
|
||||
customConfig = (typeof CRM.config.CKEditorCustomConfig === 'string') ? CRM.config.CKEditorCustomConfig :
|
||||
(CRM.config.CKEditorCustomConfig[preset] || CRM.config.CKEditorCustomConfig.default);
|
||||
|
||||
$(item).addClass('crm-wysiwyg-enabled');
|
||||
|
||||
CKEDITOR.replace($(item)[0], {
|
||||
filebrowserBrowseUrl: browseUrl + '&type=files',
|
||||
filebrowserImageBrowseUrl: browseUrl + '&type=images',
|
||||
filebrowserFlashBrowseUrl: browseUrl + '&type=flash',
|
||||
filebrowserUploadUrl: uploadUrl + '&type=files',
|
||||
filebrowserImageUploadUrl: uploadUrl + '&type=images',
|
||||
filebrowserFlashUploadUrl: uploadUrl + '&type=flash',
|
||||
customConfig: customConfig,
|
||||
on: {
|
||||
instanceReady: onReady
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ($(item).hasClass('crm-wysiwyg-enabled')) {
|
||||
deferred.resolve();
|
||||
}
|
||||
else if ($(item).length) {
|
||||
// Lazy-load ckeditor.js
|
||||
if (window.CKEDITOR) {
|
||||
initialize();
|
||||
} else {
|
||||
CRM.loadScript(CRM.config.resourceBase + 'bower_components/ckeditor/ckeditor.js').done(initialize);
|
||||
}
|
||||
} else {
|
||||
deferred.reject();
|
||||
}
|
||||
return deferred;
|
||||
};
|
||||
|
||||
CRM.wysiwyg.destroy = function(item) {
|
||||
$(item).removeClass('crm-wysiwyg-enabled');
|
||||
var editor = getInstance(item);
|
||||
if (editor) {
|
||||
editor.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
CRM.wysiwyg.updateElement = function(item) {
|
||||
var editor = getInstance(item);
|
||||
if (editor) {
|
||||
editor.updateElement();
|
||||
}
|
||||
};
|
||||
|
||||
CRM.wysiwyg.getVal = function(item) {
|
||||
var editor = getInstance(item);
|
||||
if (editor) {
|
||||
return editor.getData();
|
||||
} else {
|
||||
return $(item).val();
|
||||
}
|
||||
};
|
||||
|
||||
CRM.wysiwyg.setVal = function(item, val) {
|
||||
var editor = getInstance(item);
|
||||
if (editor) {
|
||||
return editor.setData(val);
|
||||
} else {
|
||||
return $(item).val(val);
|
||||
}
|
||||
};
|
||||
|
||||
CRM.wysiwyg.insert = function(item, text) {
|
||||
var editor = getInstance(item);
|
||||
if (editor) {
|
||||
editor.insertText(text);
|
||||
} else {
|
||||
CRM.wysiwyg._insertIntoTextarea(item, text);
|
||||
}
|
||||
};
|
||||
|
||||
CRM.wysiwyg.focus = function(item) {
|
||||
var editor = getInstance(item);
|
||||
if (editor) {
|
||||
editor.focus();
|
||||
} else {
|
||||
$(item).focus();
|
||||
}
|
||||
};
|
||||
|
||||
})(CRM.$, CRM._);
|
69
sites/all/modules/civicrm/js/wysiwyg/crm.wysiwyg.js
Normal file
69
sites/all/modules/civicrm/js/wysiwyg/crm.wysiwyg.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
// https://civicrm.org/licensing
|
||||
(function($, _) {
|
||||
// This defines an interface which by default only handles plain textareas
|
||||
// A wysiwyg implementation can extend this by overriding as many of these functions as needed
|
||||
CRM.wysiwyg = {
|
||||
supportsFileUploads: !!CRM.config.wysisygScriptLocation,
|
||||
create: function(item) {
|
||||
var ret = $.Deferred();
|
||||
// Lazy-load the wysiwyg js
|
||||
if (CRM.config.wysisygScriptLocation) {
|
||||
CRM.loadScript(CRM.config.wysisygScriptLocation).done(function() {
|
||||
CRM.wysiwyg._create(item).done(function() {
|
||||
ret.resolve();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
ret.resolve();
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
destroy: _.noop,
|
||||
updateElement: _.noop,
|
||||
getVal: function(item) {
|
||||
return $(item).val();
|
||||
},
|
||||
setVal: function(item, val) {
|
||||
return $(item).val(val);
|
||||
},
|
||||
insert: function(item, text) {
|
||||
CRM.wysiwyg._insertIntoTextarea(item, text);
|
||||
},
|
||||
focus: function(item) {
|
||||
$(item).focus();
|
||||
},
|
||||
// Fallback function to use when a wysiwyg has not been initialized
|
||||
_insertIntoTextarea: function(item, text) {
|
||||
var itemObj = $(item);
|
||||
var origVal = itemObj.val();
|
||||
var origStart = itemObj[0].selectionStart;
|
||||
var origEnd = itemObj[0].selectionEnd;
|
||||
var newVal = origVal.substring(0, origStart) + text + origVal.substring(origEnd);
|
||||
itemObj.val(newVal);
|
||||
var newPos = (origStart + text.length);
|
||||
itemObj[0].selectionStart = newPos;
|
||||
itemObj[0].selectionEnd = newPos;
|
||||
itemObj.triggerHandler('change');
|
||||
CRM.wysiwyg.focus(item);
|
||||
},
|
||||
// Create a "collapsed" textarea that expands into a wysiwyg when clicked
|
||||
createCollapsed: function(item) {
|
||||
$(item)
|
||||
.hide()
|
||||
.on('blur', function () {
|
||||
CRM.wysiwyg.destroy(item);
|
||||
$(item).hide().next('.replace-plain').show().html($(item).val());
|
||||
})
|
||||
.after('<div class="replace-plain" tabindex="0"></div>');
|
||||
$(item).next('.replace-plain')
|
||||
.attr('title', ts('Click to edit'))
|
||||
.html($(item).val())
|
||||
.on('click keypress', function (e) {
|
||||
// Stop browser from opening clicked links
|
||||
e.preventDefault();
|
||||
$(item).show().next('.replace-plain').hide();
|
||||
CRM.wysiwyg.create(item);
|
||||
});
|
||||
}
|
||||
};
|
||||
})(CRM.$, CRM._);
|
Loading…
Add table
Add a link
Reference in a new issue