Introducing populate_list in Flex Relate (parent) fields

Here's a guest blog post from experienced Sugar Developer and active DevClub member André Lopes.

Hi there,

The Flex Relate field type (aka parent field) is pretty useful as everybody knows, but it has an important limitation: It doesn't support the out of the box the populate_list vardefs attribute. Even on version 10.0.1 that feature is missing as per some comments in methods format and setValue inside its JS controller.

I just got the following requirement:

The Signer under a Signature Request is related to a parent record using the flex relate field and, when selected, it must auto populate the respective e-mail address from that parent record.

In this scenario Signer is in a GRID under Signature Request, just like RLI under Opportunities. Signer has parent, name, email address and some other fields. On selecting a parent_type and parent_name the respective primary e-mail address must be copied to Signer bean.

So how could I address such a requirement?

Step 1: Create a custom parent field controller (custom/clients/base/fields/parent/parent.js)

/**
 * @class View.Fields.Base.ParentField
 * @alias SUGAR.App.view.fields.BaseParentField
 * @extends View.Fields.Base.ParentField
 */
({
	extendsFrom: 'ParentField',

	initialize: function(options) {
		this._super('initialize', [options]);

		if(_.has(this.def, "parent_populate_list")) {
			var populateMetadata = this._getParentPopulateMetadata();

			_.each(this.def.parent_populate_list, function(populateList, module) {
				_.each(populateList, function(target, source) {
					if(_.isUndefined(populateMetadata[module].fields[source])) {
						app.logger.error('Fail to populate the related attributes: attempt to access undefined key - ' + module + '::' + source);
					}
				}, this);
			}, this);
		}
	},

	_getParentPopulateMetadata: function() {
		var populateMetadata = {};

		_.each(this.def.parent_populate_list, function(populateList, module) {
			populateMetadata[module] = app.metadata.getModule(module);
		}, this);

		return populateMetadata;
	},

	setValue: function(models) {
		this._super('setValue', [models]);

		var updateRelatedFields = true, forceUpdate = _.isEmpty(this.model.get(this.def.name));

		if(_.isArray(models)) {
			updateRelatedFields = false;
		} else {
			models = [models];
		}

		if(updateRelatedFields) {
			if(this.fieldDefs.link && forceUpdate) this.model.trigger('change:' + this.fieldDefs.link);

			this.updateRelatedFields(models[0]);
		}
	},

	updateRelatedFields: function(model) {
		var newData = {}, self = this;

		if(!_.has(this.def, "parent_populate_list") || !_.has(this.def.parent_populate_list, model._module)) return;

		_.each(this.def.parent_populate_list[model._module], function(target, source) {
			source = _.isNumber(source) ? target : source;

			if(!_.isUndefined(model[source]) && app.acl.hasAccessToModel('edit', this.model, target)) {
				var before = this.model.get(target), after = model[source];

				if(before !== after) newData[target] = model[source];
			}
		}, this);

		if(_.isEmpty(newData)) return;

		if(!_.isUndefined(this.def.auto_populate) && this.def.auto_populate == true) {
			if(!_.isUndefined(newData.currency_id)) {
				this.model.set({currency_id: newData.currency_id});
				delete newData.currency_id;
			}

			this._setRelated(newData);

			return;
		}

		var messageTplKey = this.def.populate_confirm_label || 'TPL_OVERWRITE_POPULATED_DATA_CONFIRM',
			messageTpl = Handlebars.compile(app.lang.get(messageTplKey, this.getSearchModule())),
			fieldMessageTpl = app.template.getField(this.type, 'overwrite-confirmation', this.model.module), messages = [], relatedModuleSingular = app.lang.getModuleName(this.def.module);

		_.each(newData, function(value, field) {
			var before = this.model.get(field), after = value;

			if(before !== after) {
				var def = this.model.fields[field];
				messages.push(fieldMessageTpl({before: before, after: after, field_label: app.lang.get(def.label || def.vname || field, this.module)}));
			}
		}, this);

		app.alert.show('overwrite_confirmation', {
			level: 'confirmation',
			messages: messageTpl({values: new Handlebars.SafeString(messages.join(', ')), moduleSingularLower: relatedModuleSingular.toLowerCase()}),
			onConfirm: function() {
				if(!_.isUndefined(newData.currency_id)) {
					self.model.set({currency_id: newData.currency_id});
					delete newData.currency_id;
				}

				self._setRelated(newData);
			}
		});
	},

	getSearchFields: function() {
		var parentPopulateList = (_.has(this.def, "parent_populate_list") && _.has(this.def.parent_populate_list, this.def.module)) ? _.keys(this.def.parent_populate_list[this.def.module]) : {};

		return _.union(['id', this.getRelatedModuleField()], parentPopulateList);
	}
})

Step 2: Create a vardefs extension

$dictionary["lgs_HelloSignTarget"]["fields"]["parent_name"]['auto_populate'] = true;
$dictionary["lgs_HelloSignTarget"]["fields"]["parent_name"]['parent_populate_list'] = [
	'Accounts' => [
		'email1' => 'target_email',
	],
	'Contacts' => [
		'email1' => 'target_email',
	],
	'Leads' => [
		'email1' => 'target_email',
	],
	'Users' => [
		'email1' => 'target_email',
	],
];

Step 3: Go to Admin -> Repair and run Quick Repair and Rebuild.

That implementation is quite useful for some other requests like:

  • Copy phone number from some specific parent records to Calls record
  • Copy address street from some specific parent records to Meetings record

Additionally it doesn't affect other Flex Relate (parent) fields that do not contain the parent_populate_list vardefs attribute.

Enjoy it!