/**
* Checks if an entity has at least one item for a given field name. Optionally pass in a
* language code and/or delta value, otherwise they default to 'und' and 0 respectively.
* @param {Object} entity
* @param {String} fieldName
* @param {String} language
* @param {Number} delta
* @returns {Boolean}
*/
jDrupal.fieldHasItem = function(entity, fieldName, language, delta) {
if (!language) { language = language_default(); }
if (typeof delta === 'undefined') { delta = 0; }
return entity[fieldName] &&
entity[fieldName][language] &&
entity[fieldName][language].length &&
entity[fieldName][language][delta];
};
/**
* Gets an item from an entity given field name. Optionally pass in a language code and/or
* delta value, otherwise they default to 'und' and 0 respectively.
* @param {Object} entity
* @param {String} fieldName
* @param {String} language
* @param {Number} delta
* @returns {*}
*/
jDrupal.fieldGetItem = function(entity, fieldName, language, delta) {
if (!language) { language = language_default(); }
if (typeof delta === 'undefined') { delta = 0; }
return entity[fieldName][language][delta];
};
/**
* Given an entity and field name, this will return how many items are on the field. Optionally
* pass in a language code otherwise it defaults to 'und'.
* @param {Object} entity
* @param {String} fieldName
* @param {String} language
* @returns {Number}
*/
jDrupal.fieldGetItemCount = function(entity, fieldName, language) {
return !language ?
jDrupal.fieldGetItems(entity, fieldName).length :
jDrupal.fieldGetItems(entity, fieldName, language).length;
};
/**
* Gets items from an entity given a field name. Optionally pass in a language code otherwise
* it defaults to 'und'.
* @param {Object} entity
* @param {String} fieldName
* @param {String} language
* @returns {*}
*/
jDrupal.fieldGetItems = function(entity, fieldName, language) {
if (!language) { language = language_default(); }
return entity[fieldName][language];
};
/**
* Sets an item's property on an entity given a field and property name.
* @param {Object} entity The entity object.
* @param {String} fieldName The field name on the entity.
* @param {String|null} propertyName The property name to set, usually 'value', or null to set the entire
* item (advanced users, don't forget language code and delta value).
* @param {*} value The value to set.
* @param {String} language Optional language code, defaults to 'und'
* @param {Number} delta Optional delta value, defaults to 0
*/
jDrupal.fieldSetItem = function(entity, fieldName, propertyName, value, language, delta) {
if (!language) { language = language_default(); }
if (typeof delta === 'undefined') { delta = 0; }
if (!entity[fieldName]) { entity[fieldName] = {}; }
if (propertyName) {
if (!entity[fieldName][language]) { entity[fieldName][language] = []; }
if (!entity[fieldName][language][delta]) { entity[fieldName][language][delta] = {}; }
entity[fieldName][language][delta][propertyName] = value;
}
else { entity[fieldName][language][delta] = value; }
};
/**
* Returns an array of entity type machine names configured with Services Entity in settings.js
* @returns {Array}
*/
function services_entity_types() {
var entityTypes = [];
if (Drupal.services_entity && Drupal.services_entity.types) {
for (var entityType in Drupal.services_entity.types) {
if (!Drupal.services_entity.types.hasOwnProperty(entityType)) { continue; }
entityTypes.push(entityType);
}
}
return entityTypes;
}
/**
* Readies the Drupal.services_queue object with Services Entity configuration.
*/
function _services_entity_queue_init() {
for (var entityType in Drupal.services_entity.types) {
if (!Drupal.services_entity.types.hasOwnProperty(entityType)) { continue; }
if (Drupal.services_queue[entityType]) { continue; }
Drupal.services_queue[entityType] = {
retrieve: {}
};
}
}
/**
* Given an entity type and entity, this will return the bundle name as a
* string for the given entity, or null if the bundle is N/A.
* @param {String} entity_type The entity type.
* @param {Object} entity The entity JSON object.
* @return {*}
*/
function entity_get_bundle(entity_type, entity) {
try {
// @TODO This isn't dynamic at all.
var bundle = null;
switch (entity_type) {
case 'node': bundle = entity.type; break;
case 'taxonomy_term': bundle = entity.vid; break;
case 'comment':
case 'file':
case 'user':
case 'taxonomy_vocabulary':
// These entity types don't have a bundle.
break;
default:
if (in_array(entity_type, services_entity_types())) { return entity.type; }
var msg = 'WARNING: entity_get_bundle - unsupported entity type (' +
entity_type + ')';
console.log(msg);
break;
}
return bundle;
}
catch (error) { console.log('entity_get_bundle - ' + error); }
}
function entity_get_bundle_name(entity_type) {
// @TODO Should be dynamic.
var bundle = null;
switch (entity_type) {
case 'node': return 'type'; break;
case 'taxonomy_term': return 'vid'; break;
case 'comment': // @TODO comment has a node bundle, kind of
case 'file':
case 'user':
case 'taxonomy_vocabulary':
default:
if (in_array(entity_type, services_entity_types())) { return 'type'; }
return null;
break;
}
}
/**
* Parses an entity id and returns it as an integer (not a string).
* @param {*} entity_id
* @return {Number}
*/
function entity_id_parse(entity_id) { return typeof entity_id === 'string' ? parseInt(entity_id) : entity_id; }
/**
* Given an entity type and the entity id, this will return the local storage
* key to be used when saving/loading the entity from local storage.
* @param {String} entity_type
* @param {Number} id
* @return {String}
*/
function entity_local_storage_key(entity_type, id) { return entity_type + '_' + id; }
/**
* A placeholder function used to provide a local storage key for entity index
* queries.
* @param {String} path
* @return {String}
*/
function entity_index_local_storage_key(path) { return path; }
/**
* Loads an entity.
* @param {String} entity_type
* @param {Number|Array} ids
* @param {Object} options
*/
function entity_load(entity_type, ids, options) {
try {
var servicesEntityType = in_array(entity_type, services_entity_types());
if (servicesEntityType) { _services_entity_queue_init(); }
// If an array of entity ids was passed in, use the entity index resource to load them all.
if (is_array(ids)) {
var query = {
parameters: {},
options: {
entity_load: true
}
};
query.parameters[entity_primary_key(entity_type)] = ids.join(',');
window[entity_type + '_index'](query, options);
return;
}
// A single id was passed in, convert the id to an int, if it's a string.
var entity_id = ids;
entity_id = entity_id_parse(entity_id);
var caching_enabled = entity_caching_enabled(entity_type);
// If this entity is already queued for retrieval, set the success and
// error callbacks aside, and return. Unless entity caching is enabled and
// we have a copy of the entity in local storage, then send it to the
// provided success callback.
if (_services_queue_already_queued(entity_type, 'retrieve', entity_id, 'success')) {
if (caching_enabled) {
entity = _entity_local_storage_load(entity_type, entity_id, options);
if (entity) {
if (options.success) { options.success(entity); }
return;
}
}
if (typeof options.success !== 'undefined') {
_services_queue_callback_add(entity_type, 'retrieve', entity_id, 'success', options.success);
}
if (typeof options.error !== 'undefined') {
_services_queue_callback_add(entity_type, 'retrieve', entity_id, 'error', options.error);
}
return;
}
// This entity has not been queued for retrieval, queue it and its callback.
_services_queue_add_to_queue(entity_type, 'retrieve', entity_id);
_services_queue_callback_add(entity_type, 'retrieve', entity_id, 'success', options.success);
// If entity caching is enabled, try to load the entity from local storage.
// If a copy is available in local storage, bubble it to the success callback(s).
var entity = false;
if (caching_enabled) {
entity = _entity_local_storage_load(entity_type, entity_id, options);
if (entity) {
_entity_callback_bubble(entity_type, entity_id, entity);
return;
}
}
// Verify the entity type is supported.
if (!in_array(entity_type, entity_types())) {
var message = 'WARNING: entity_load - unsupported type: ' + entity_type;
console.log(message);
if (options.error) { options.error(null, null, message); }
return;
}
// We didn't load the entity from local storage. Let's grab it from the
// Drupal server instead. First, let's build the call options.
var primary_key = entity_primary_key(entity_type);
var call_options = {
success: function(data) {
try {
// Set the entity equal to the returned data.
entity = data;
// If entity caching is enabled, set its expiration time and save it to local storage.
if (entity_caching_enabled(entity_type, entity_get_bundle(entity_type, entity))) {
_entity_set_expiration_time(entity_type, entity);
_entity_local_storage_save(entity_type, entity_id, entity);
}
_entity_callback_bubble(entity_type, entity_id, entity);
}
catch (error) { console.log('entity_load - success - ' + error); }
},
error: function(xhr, status, message) {
try {
// Since we had a problem loading the entity, clear out the success queue.
_services_queue_clear(entity_type, 'retrieve', entity_id, 'success');
// Pass along the error if anyone wants to handle it.
if (options.error) { options.error(xhr, status, message); }
}
catch (error) { console.log('entity_load - error - ' + error); }
}
};
// Finally, determine the entity's retrieve function and call it.
var function_name = !servicesEntityType ? entity_type + '_retrieve' : 'entity_retrieve';
if (function_exists(function_name)) {
call_options[primary_key] = entity_id;
var fn = window[function_name];
if (servicesEntityType) {
services_resource_defaults(call_options, entity_type, 'retrieve');
fn(entity_type, ids, call_options);
}
else { fn(ids, call_options); }
}
else { console.log('WARNING: ' + function_name + '() does not exist!'); }
}
catch (error) { console.log('entity_load - ' + error); }
}
function _entity_callback_bubble(entity_type, entity_id, entity) {
// Send the entity back to the queued callback(s), then clear out the callbacks.
var _success_callbacks = Drupal.services_queue[entity_type]['retrieve'][entity_id].success;
for (var i = 0; i < _success_callbacks.length; i++) { _success_callbacks[i](entity); }
_services_queue_clear(entity_type, 'retrieve', entity_id, 'success');
}
/**
* An internal function used by entity_load() to attempt loading an entity
* from local storage.
* @param {String} entity_type
* @param {Number} entity_id
* @param {Object} options
* @return {Object}
*/
function _entity_local_storage_load(entity_type, entity_id, options) {
try {
var entity = false;
// Process options if necessary.
if (options) {
// If we are resetting, remove the item from localStorage.
if (options.reset) {
_entity_local_storage_delete(entity_type, entity_id);
}
}
// Attempt to load the entity from local storage.
var local_storage_key = entity_local_storage_key(entity_type, entity_id);
entity = window.localStorage.getItem(local_storage_key);
if (entity) {
entity = JSON.parse(entity);
// We successfully loaded the entity from local storage. If it expired
// remove it from local storage then continue onward with the entity
// retrieval from Drupal. Otherwise return the local storage entity copy.
if (entity_has_expired(entity_type, entity)) {
_entity_local_storage_delete(entity_type, entity_id);
entity = false;
}
else {
// @TODO - this code belongs to DrupalGap! Figure out how to bring the
// idea of DrupalGap modules into jDrupal that way jDrupal can provide
// a hook for DrupalGap to take care of this code!
// The entity has not yet expired. If the current page options
// indicate reloadingPage is true (and the reset option wasn't set to
// false) then we'll grab a fresh copy of the entity from Drupal.
// If the page is reloading and the developer didn't call for a reset,
// then just return the cached copy.
if (drupalgap && drupalgap.page.options &&
drupalgap.page.options.reloadingPage) {
// Reloading page... cached entity is still valid.
if (typeof drupalgap.page.options.reset !== 'undefined' &&
drupalgap.page.options.reset === false) {
// We were told to not reset it, so we'll use the cached copy.
return entity;
}
else {
// Remove the entity from local storage and reset it.
_entity_local_storage_delete(entity_type, entity_id);
entity = false;
}
}
}
}
return entity;
}
catch (error) { console.log('_entity_local_storage_load - ' + error); }
}
/**
* An internal function used to save an entity to local storage.
* @param {String} entity_type
* @param {Number} entity_id
* @param {Object} entity
*/
function _entity_local_storage_save(entity_type, entity_id, entity) {
try {
var key = entity_local_storage_key(entity_type, entity_id);
window.localStorage.setItem(key, JSON.stringify(entity));
if (typeof Drupal.cache_expiration.entities === 'undefined') { Drupal.cache_expiration.entities = {}; }
Drupal.cache_expiration.entities[key] = entity.expiration;
window.localStorage.setItem('cache_expiration', JSON.stringify(Drupal.cache_expiration));
}
catch (error) { console.log('_entity_local_storage_save - ' + error); }
}
/**
* An internal function used to delete an entity from local storage.
* @param {String} entity_type
* @param {Number} entity_id
*/
function _entity_local_storage_delete(entity_type, entity_id) {
try {
var storage_key = entity_local_storage_key(
entity_type,
entity_id
);
window.localStorage.removeItem(storage_key);
}
catch (error) { console.log('_entity_local_storage_delete - ' + error); }
}
/**
* Returns an entity type's primary key.
* @param {String} entity_type
* @return {String}
*/
function entity_primary_key(entity_type) {
try {
var key;
switch (entity_type) {
case 'comment': key = 'cid'; break;
case 'file': key = 'fid'; break;
case 'node': key = 'nid'; break;
case 'taxonomy_term': key = 'tid'; break;
case 'taxonomy_vocabulary': key = 'vid'; break;
case 'user': key = 'uid'; break;
default:
if (in_array(entity_type, services_entity_types())) { return 'id'; }
// Is anyone declaring the primary key for this entity type?
var function_name = entity_type + '_primary_key';
if (function_exists(function_name)) {
var fn = window[function_name];
key = fn(entity_type);
}
else {
var msg = 'entity_primary_key - unsupported entity type (' +
entity_type + ') - to add support, declare ' + function_name +
'() and have it return the primary key column name as a string';
console.log(msg);
}
break;
}
return key;
}
catch (error) { console.log('entity_primary_key - ' + error); }
}
/**
* Saves an entity.
* @param {String} entity_type
* @param {String} bundle
* @param {Object} entity
* @param {Object} options
*/
function entity_save(entity_type, bundle, entity, options) {
try {
var function_name;
switch (entity_type) {
case 'comment':
if (!entity.cid) { function_name = 'comment_create'; }
else { function_name = 'comment_update'; }
break;
case 'file':
function_name = 'file_create';
break;
case 'node':
if (!entity.language) { entity.language = language_default(); }
if (!entity.nid) { function_name = 'node_create'; }
else { function_name = 'node_update'; }
break;
case 'user':
if (!entity.uid) { function_name = 'user_create'; }
else { function_name = 'user_update'; }
break;
case 'taxonomy_term':
if (!entity.tid) { function_name = 'taxonomy_term_create'; }
else { function_name = 'taxonomy_term_update'; }
break;
case 'taxonomy_vocabulary':
if (!entity.vid) { function_name = 'taxonomy_vocabulary_create'; }
else { function_name = 'taxonomy_vocabulary_update'; }
break;
default:
if (in_array(entity_type, services_entity_types())) {
function_name = !entity[entity_primary_key(entity_type)] ? 'entity_create' : 'entity_update';
window[function_name](entity_type, bundle, entity, options);
return;
}
break;
}
if (function_name && function_exists(function_name)) {
var fn = window[function_name];
fn(entity, options);
}
else { console.log('WARNING: entity_save - unsupported type: ' + entity_type); }
}
catch (error) { console.log('entity_save - ' + error); }
}
/**
* Returns true or false depending on whether caching is enabled or not. You
* may optionally pass in an entity type as the first argument, and optionally
* pass in a bundle name as a second argument to see if that particular cache
* is enabled.
* @return {Boolean}
*/
function entity_caching_enabled() {
try {
// First make sure entity caching is at least defined, then
// make sure it's enabled.
if (
typeof Drupal.settings.cache === 'undefined' ||
typeof Drupal.settings.cache.entity === 'undefined' ||
!Drupal.settings.cache.entity.enabled
) { return false; }
// Entity caching is enabled globally...
// Did they provide an entity type? If not, caching is enabled.
var entity_type = arguments[0];
if (!entity_type) { return true; }
// Are there any entity type caching configs present? If not, caching is enabled.
if (
!Drupal.settings.cache.entity.entity_types ||
!Drupal.settings.cache.entity.entity_types[entity_type]
) { return true; }
// Grab the cache config for this entity type.
var cache = Drupal.settings.cache.entity.entity_types[entity_type];
// Is caching explicitly disabled for this entity type?
var entity_type_caching_disabled = typeof cache.enabled !== 'undefined' && cache.enabled === false;
if (entity_type_caching_disabled) { return false; }
// Did they provide a bundle? If not, then this entity type's caching is
// enabled.
var bundle = arguments[1];
if (!bundle) { return true; }
// Is caching explicitly disabled for this bundle?
if (typeof cache.bundles !== 'undefined' && typeof cache.bundles[bundle] !== 'undefined') {
return typeof cache.bundles[bundle].enabled !== 'undefined' ?
cache.bundles[bundle].enabled : cache.enabled;
}
// We didn't prove caching to be disabled, so it must be enabled.
return true;
}
catch (error) { console.log('entity_caching_enabled - ' + error); }
}
/**
*
* @param entity_type
* @param entity
*/
function entity_has_expired(entity_type, entity) {
return typeof entity.expiration !== 'undefined' && entity.expiration != 0 && time() > entity.expiration;
}
/**
* Looks for expired entities and remove them from local storage.
*/
function entity_clean_local_storage() {
if (!Drupal.cache_expiration.entities) { return; }
for (var key in Drupal.cache_expiration.entities) {
if (!Drupal.cache_expiration.entities.hasOwnProperty(key)) { continue; }
var expiration = Drupal.cache_expiration.entities[key];
if (expiration > time()) { continue; }
delete Drupal.cache_expiration.entities[key];
var parts = key.split('_');
var entity_type = parts[0];
var entity_id = parts[1];
_entity_local_storage_delete(entity_type, entity_id);
window.localStorage.setItem('cache_expiration', JSON.stringify(Drupal.cache_expiration));
}
}
/**
* An internal function used to get the expiration time for entities.
* @param entity_type
* @param entity
* @returns {null|Number}
* @private
*/
function _entity_get_expiration_time(entity_type, entity) {
try {
var expiration = null;
var bundle = entity_get_bundle(entity_type, entity);
if (entity_caching_enabled(entity_type, bundle)) {
var expiration = 0;
var cache = Drupal.settings.cache;
if (cache.entity.expiration !== 'undefined') {
expiration = cache.entity.expiration;
}
if (cache.entity.entity_types !== 'undefined') {
if (
cache.entity.entity_types[entity_type] &&
typeof cache.entity.entity_types[entity_type].expiration !== 'undefined'
) { expiration = cache.entity.entity_types[entity_type].expiration; }
if (
bundle &&
cache.entity.entity_types[entity_type] &&
cache.entity.entity_types[entity_type].bundles &&
cache.entity.entity_types[entity_type].bundles[bundle] &&
typeof cache.entity.entity_types[entity_type].bundles[bundle].expiration !== 'undefined'
) { expiration = cache.entity.entity_types[entity_type].bundles[bundle].expiration; }
}
}
if (expiration) { expiration += time(); }
return expiration;
}
catch (error) { console.log('_entity_get_expiration_time - ' + error); }
}
/**
* An internal function used to set the expiration time onto a given entity.
* @param {String} entity_type The entity type.
* @param {Object} entity The entity object.
*/
function _entity_set_expiration_time(entity_type, entity) {
try {
entity.expiration = _entity_get_expiration_time(entity_type, entity);
}
catch (error) { console.log('_entity_set_expiration_time - ' + error); }
}
/**
* Returns an array of entity type names.
* @return {Array}
*/
function entity_types() {
// Start with core entity types.
var entityTypes = [
'comment',
'file',
'node',
'taxonomy_term',
'taxonomy_vocabulary',
'user'
];
// Append and Services Entity types.
var servicesEntityTypes = services_entity_types();
if (servicesEntityTypes.length) {
entityTypes.push.apply(entityTypes, servicesEntityTypes);
}
return entityTypes;
}
/**
* An internal function used by entity_index() to attempt loading a specific
* query's results from local storage.
* @param {String} entity_type
* @param {String} path The URL path used by entity_index(), used as the cache
* key.
* @param {Object} options
* @return {Object}
*/
function _entity_index_local_storage_load(entity_type, path, options) {
try {
var _entity_index = false;
// Process options if necessary.
if (options) {
// If we are resetting, remove the item from localStorage.
if (options.reset) {
_entity_index_local_storage_delete(path);
}
}
// Attempt to load the entity_index from local storage.
var local_storage_key = entity_index_local_storage_key(path);
_entity_index = window.localStorage.getItem(local_storage_key);
if (_entity_index) {
_entity_index = JSON.parse(_entity_index);
// We successfully loaded the entity_index result ids from local storage.
// If it expired remove it from local storage then continue onward with
// the entity_index retrieval from Drupal. Otherwise return the local
// storage entity_index copy.
if (typeof _entity_index.expiration !== 'undefined' &&
_entity_index.expiration != 0 &&
time() > _entity_index.expiration) {
_entity_index_local_storage_delete(path);
_entity_index = false;
}
else {
// The entity_index has not yet expired, so pull each entity out of
// local storage, add them to the result array, and return the array.
var result = [];
for (var i = 0; i < _entity_index.entity_ids.length; i++) {
result.push(
_entity_local_storage_load(
entity_type,
_entity_index.entity_ids[i],
options
)
);
}
_entity_index = result;
}
}
return _entity_index;
}
catch (error) { console.log('_entity_index_local_storage_load - ' + error); }
}
/**
* An internal function used to save an entity_index result entity ids to
* local storage.
* @param {String} entity_type
* @param {String} path
* @param {Object} result
*/
function _entity_index_local_storage_save(entity_type, path, result) {
try {
var index = {
entity_type: entity_type,
expiration: _entity_get_expiration_time(),
entity_ids: []
};
for (var i = 0; i < result.length; i++) {
index.entity_ids.push(result[i][entity_primary_key(entity_type)]);
}
window.localStorage.setItem(
entity_index_local_storage_key(path),
JSON.stringify(index)
);
}
catch (error) { console.log('_entity_index_local_storage_save - ' + error); }
}
/**
* An internal function used to delete an entity_index from local storage.
* @param {String} path
*/
function _entity_index_local_storage_delete(path) {
try {
var storage_key = entity_index_local_storage_key(path);
window.localStorage.removeItem(storage_key);
}
catch (error) {
console.log('_entity_index_local_storage_delete - ' + error);
}
}