/*! drupalgap 2017-10-18 */
// Initialize the drupalgap json object.
var drupalgap = drupalgap || drupalgap_init(); // Do not remove this line.
// Init _GET for url path query strings.
var _dg_GET = _dg_GET || {};
/**
* Initializes the drupalgap json object.
* @return {Object}
*/
function drupalgap_init() {
var dg = {
modules: {
core: [
{ name: 'comment' },
{ name: 'contact' },
{ name: 'entity' },
{ name: 'field' },
{ name: 'file' },
{ name: 'image' },
{ name: 'menu' },
{ name: 'mvc' },
{ name: 'node' },
{ name: 'search' },
{ name: 'system' },
{ name: 'taxonomy' },
{ name: 'user' },
{ name: 'views' }
]
},
module_paths: [],
includes: [
{ name: 'block' },
{ name: 'common' },
{ name: 'form' },
{ name: 'go' },
{ name: 'menu' },
{ name: 'page' },
{ name: 'region' },
{ name: 'theme' },
{ name: 'title' }
],
online: false,
destination: '',
api: {},
back: false, /* moving backwards or not */
back_path: [], /* paths to move back to */
blocks: [],
connected: false, // Becomes true once DrupalGap performs a System Connect call.
content_types_list: {}, /* holds info about each content type */
date_formats: { }, /* @see system_get_date_formats() in Drupal core */
date_types: { }, /* @see system_get_date_types() in Drupal core */
entity_info: {},
field_info_fields: {},
field_info_instances: {},
field_info_extra_fields: {},
form_errors: {},
form_states: [],
loading: false, /* indicates if the loading message is shown or not */
loader: 'loading', /* used to determine the jQM loader mode */
locale: {}, /* holds onto language json objects, keyed by language code */
messages: [],
menus: {},
menu_links: {},
menu_router: {}, /* @todo - doesn't appear to be used at all, remove it */
mvc: {
models: {},
views: {},
controllers: {}
},
output: '', /* hold output generated by menu_execute_active_handler() */
page: {
jqm_events: [],
title: '',
variables: {},
process: true,
options: {} /* holds the current page's options, eg. reloadPage, etc. */
},
pages: [], /* Collection of page ids that are loaded into the DOM. */
path: '', /* The current menu path. */
remote_addr: null, /* php's $_SERVER['REMOTE_ADDR'] via system connect */
router_path: '', /* The current menu router path. */
services: {},
sessid: null,
settings: {},
site_settings: {}, /* holds variable settings from the Drupal site */
taxonomy_vocabularies: false, /* holds vocabs from system connect */
theme_path: '',
themes: [],
theme_registry: {},
toast: {
shown: false
},
views: {
ids: []
},
views_datasource: {}
};
// Extend jDrupal as needed...
// Forms will expire upon install and don't have an expiration time.
if (!Drupal.cache_expiration) { Drupal.cache_expiration = {}; }
if (!Drupal.cache_expiration.forms) { Drupal.cache_expiration.forms = {}; }
// Finally return the JSON object.
return dg;
}
/**
* This is called once the
element's onload is fired.
*/
function drupalgap_onload() {
try {
// At this point, the Drupal object has been initialized by jDrupal and the
// app/settings.js file was loaded in . Let's add DrupalGap's modules
// onto the Drupal JSON object. Remember, all of the module source code is
// included via the makefile's bin generation. However, the core modules
// hook_install() implementations haven't been called yet, so we add them to
// the module listing so they can be invoked later on.
var modules = [
'drupalgap',
'block',
'comment',
'contact',
'entity',
'field',
'file',
'image',
'menu',
'mvc',
'node',
'search',
'system',
'taxonomy',
'user',
'views'
];
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
Drupal.modules.core[module] = module_object_template(module);
}
// Depending on the mode, we'll move on to _drupalgap_deviceready()
// accordingly. By default we'll assume the mode is for phonegap, unless
// otherwise specified by the settings.js file. If it is for phonegap, we'll
// attach its device ready listener, otherwise we'll just move on.
if (typeof drupalgap.settings.mode === 'undefined') {
drupalgap.settings.mode = 'phonegap';
}
switch (drupalgap.settings.mode) {
case 'phonegap':
document.addEventListener('deviceready', _drupalgap_deviceready, false);
break;
case 'web-app':
_drupalgap_deviceready();
break;
default:
console.log(
'drupalgap_onload - unknown mode (' + drupalgap.settings.mode + ')'
);
return;
break;
}
}
catch (error) { console.log('drupalgap_onload - ' + error); }
}
/**
* Implements PhoneGap's deviceready().
*/
function _drupalgap_deviceready() {
try {
// Set some jQM properties to better handle the back button on iOS9.
if (
typeof device !== 'undefined' &&
device.platform === "iOS" &&
parseInt(device.version) === 9
) {
$.mobile.hashListeningEnabled = false;
$.mobile.pushStateEnabled = false;
}
// The device is now ready, it is now safe for DrupalGap to start...
drupalgap_bootstrap();
// Verify site path is set.
if (!Drupal.settings.site_path || Drupal.settings.site_path == '') {
var msg = t('No site_path to Drupal set in the app/settings.js file!');
drupalgap_alert(msg, {
title: t('Error')
});
return;
}
// Device is ready, let's call any implementations of hook_deviceready(). If any implementation returns
// false, that means they would like to take over the rest of the deviceready procedure (aka the System
// Connect call)
var proceed = true;
var invocation_results = module_invoke_all('deviceready');
if (invocation_results && invocation_results.length > 0) {
for (var i = 0; i < invocation_results.length; i++) {
if (!invocation_results[i]) {
proceed = false;
break;
}
}
}
// If the device is offline, warn the user and then go to the offline page, unless someone implemented
// hook_offline, then let them handle it.
if (!drupalgap_has_connection()) {
if (!module_implements('device_offline')) {
if (drupalgap.settings.offline_message) {
drupalgap_alert(drupalgap.settings.offline_message, {
title: t('Offline'),
alertCallback: function() { drupalgap_goto('offline'); }
});
}
else { drupalgap_goto('offline'); }
}
else { setTimeout(function() { module_invoke_all('device_offline'); }, 1); }
}
else if (proceed) {
// Device is online and no one has taken over the deviceready, continue with the System Connect call.
system_connect(_drupalgap_deviceready_options());
}
}
catch (error) { console.log('_drupalgap_deviceready - ' + error); }
}
/**
* Builds the default system connect options.
* @return {Object}
*/
function _drupalgap_deviceready_options() {
try {
var pageOptions = arguments[0] ? arguments[0] : {};
return {
success: function(result) {
// Set the connection and invoke hook_device_connected().
drupalgap.connected = true;
module_invoke_all('device_connected');
// If there is a hash url present and it can be routed go directly to that page,
// otherwise go to the app's front page.
var path = '';
var hash = window.location.hash;
if (hash.indexOf('#') != -1) {
hash = hash.replace('#', '');
_drupalgap_goto_prepare_path(hash, true);
var routedPath = drupalgap_get_path_from_page_id(hash);
if (routedPath) { path = routedPath; }
}
drupalgap_goto(path, pageOptions);
},
error: function(jqXHR, textStatus, errorThrown) {
// Build an informative error message and display it.
var msg = t('Failed connection to') + ' ' + Drupal.settings.site_path;
if (errorThrown != '') { msg += ' - ' + errorThrown; }
msg += ' - ' + t('Check your device\'s connection and check that') +
' ' + Drupal.settings.site_path + ' ' + t('is online.');
drupalgap_alert(msg, {
title: t('Unable to Connect'),
alertCallback: function() { drupalgap_goto('offline'); }
});
}
};
}
catch (error) { console.log('_drupalgap_deviceready_options - ' + error); }
}
/**
* Loads up all necessary assets to make DrupalGap ready.
*/
function drupalgap_bootstrap() {
try {
// Load up any contrib and/or custom modules (the DG core moodules have
// already been loaded at this point), load the theme and all blocks. Then
// build the menu router, load the menus, and build the theme registry.
drupalgap_load_modules();
drupalgap_load_theme();
drupalgap_load_blocks();
drupalgap_load_locales();
menu_router_build();
drupalgap_menus_load();
drupalgap_theme_registry_build();
// Attach device back button handler (Android).
document.addEventListener('backbutton', function(e) {
drupalgap_back();
e.preventDefault();
}, false);
}
catch (error) { console.log('drupalgap_bootstrap - ' + error); }
}
/**
* Loads any contrib or custom modules specifed in the settings.js file. Then
* invoke hook_install() on all modules, including core.
*/
function drupalgap_load_modules() {
try {
var module_types = ['contrib', 'custom'];
// We only need to load contrib and custom modules because core modules are
// already included in the binary.
for (var index in module_types) {
if (!module_types.hasOwnProperty(index)) { continue; }
var bundle = module_types[index];
// Let's be nice and try to load any old drupalgap.modules declarations
// in developers settings.js files for a while, but throw a warning to
// encourage them to update. This code can be removed after a few
// releases to help developers get caught up without angering them.
if (
drupalgap.modules &&
drupalgap.modules[bundle] &&
drupalgap.modules[bundle].length != 0
) {
for (var index in drupalgap.modules[bundle]) {
if (!drupalgap.modules[bundle].hasOwnProperty(index)) { continue; }
var module = drupalgap.modules[bundle][index];
if (module.name) {
var msg = 'WARNING: The module "' + module.name + '" defined ' +
'in settings.js needs to be added to ' +
'Drupal.modules[\'' + bundle + '\'] instead! See ' +
'default.settings.js for examples on the new syntax!';
console.log(msg);
Drupal.modules[bundle][module.name] = module;
}
}
}
for (var module_name in Drupal.modules[bundle]) {
if (!Drupal.modules[bundle].hasOwnProperty(module_name)) { continue; }
var module = Drupal.modules[bundle][module_name];
// If the module object is empty, initialize a module object.
if ($.isEmptyObject(module)) {
Drupal.modules[bundle][module_name] =
module_object_template(module_name);
module = Drupal.modules[bundle][module_name];
}
// If the module's name isn't set, set it.
if (!module.name) {
Drupal.modules[bundle][module_name].name = module_name;
module = Drupal.modules[bundle][module_name];
}
// If the module is already loaded via the index.html file, just continue.
if (typeof module.loaded !== 'undefined' && module.loaded) { continue; }
// Determine module directory.
var dir = drupalgap_modules_get_bundle_directory(bundle);
module_base_path = dir + '/' + module.name;
// Add module .js file to array of paths to load.
var extension = module.minified ? '.min.js' : '.js';
module_path = module_base_path + '/' + module.name + extension;
modules_paths = [module_path];
// If there are any includes with this module, add them to the
// list of paths to include.
if (module.includes != null && module.includes.length != 0) {
for (var include_index in module.includes) {
if (!module.includes.hasOwnProperty(include_index)) { continue; }
var include_object = module.includes[include_index];
modules_paths.push(
module_base_path + '/' + include_object.name + '.js'
);
}
}
// Now load all the paths for this module.
for (var modules_paths_index in modules_paths) {
if (!modules_paths.hasOwnProperty(modules_paths_index)) { continue; }
var modules_paths_object = modules_paths[modules_paths_index];
jQuery.ajax({
async: false,
type: 'GET',
url: modules_paths_object,
data: null,
success: function() {
if (Drupal.settings.debug) { console.log(modules_paths_object); }
},
dataType: 'script',
error: function(xhr, textStatus, errorThrown) {
console.log(
t('Failed to load module!') + ' (' + module.name + ')',
textStatus,
errorThrown.message
);
}
});
}
}
}
// Now invoke hook_install on all modules, including core.
module_invoke_all('install');
}
catch (error) { console.log('drupalgap_load_modules - ' + error); }
}
/**
* Load the theme specified by drupalgap.settings.theme into drupalgap.theme
* Returns true on success, false if it fails.
* @return {Boolean}
*/
function drupalgap_load_theme() {
try {
if (!drupalgap.settings.theme) {
var msg = 'drupalgap_load_theme - ' +
t('no theme specified in settings.js');
drupalgap_alert(msg);
}
else {
// Pull the theme name from the settings.js file.
var theme_name = drupalgap.settings.theme;
var theme_path = 'themes/' + theme_name + '/' + theme_name + '.js';
if (theme_name != 'easystreet3' && theme_name != 'ava') {
theme_path = 'app/themes/' + theme_name + '/' + theme_name + '.js';
}
if (!drupalgap_file_exists(theme_path)) {
var error_msg = 'drupalgap_theme_load - ' + t('Failed to load theme!') +
' ' + t('The theme\'s JS file does not exist') + ': ' + theme_path;
drupalgap_alert(error_msg);
return false;
}
// We found the theme's js file, add it to the page.
drupalgap_add_js(theme_path);
// Call the theme's template_info implementation.
var template_info_function = theme_name + '_info';
if (function_exists(template_info_function)) {
var fn = window[template_info_function];
drupalgap.theme = fn();
// For each region in the name, set the 'name' value on the region JSON.
for (var name in drupalgap.theme.regions) {
if (!drupalgap.theme.regions.hasOwnProperty(name)) { continue; }
var region = drupalgap.theme.regions[name];
drupalgap.theme.regions[name].name = name;
}
// Make sure the theme implements the required regions.
var regions = system_regions_list();
for (var i = 0; i < regions.length; i++) {
var region = regions[i];
if (typeof drupalgap.theme.regions[region] === 'undefined') {
console.log('WARNING: drupalgap_load_theme() - The "' +
theme_name + '" theme does not have the "' + region +
'" region specified in "' + theme_name + '_info()."');
}
}
// Theme loaded successfully! Set the drupalgap.theme_path and return
// true.
drupalgap.theme_path = theme_path.replace('/' + theme_name + '.js', '');
return true;
}
else {
var error_msg = 'drupalgap_load_theme() - ' + t('failed') + ' - ' +
template_info_function + '() ' + t('does not exist!');
drupalgap_alert(error_msg);
}
}
return false;
}
catch (error) { console.log('drupalgap_load_theme - ' + error); }
}
/**
* Given a path to a javascript file relative to the app's www directory,
* this will load the javascript file so it will be available in scope.
*/
function drupalgap_add_js() {
try {
var data;
if (arguments[0]) { data = arguments[0]; }
jQuery.ajax({
async: false,
type: 'GET',
url: data,
data: null,
success: function() {
if (Drupal.settings.debug) {
// Print the js path to the console.
console.log(data);
}
},
dataType: 'script',
error: function(xhr, textStatus, errorThrown) {
console.log(
'drupalgap_add_js - error - (' +
data + ' : ' + textStatus +
') ' + errorThrown
);
}
});
}
catch (error) {
console.log('drupalgap_add_js - ' + error);
}
}
/**
* Given a path to a css file relative to the app's www directory, this will
* attempt to load the css file so it will be available in scope.
*/
function drupalgap_add_css() {
try {
var data;
if (arguments[0]) { data = arguments[0]; }
$('', {rel: 'stylesheet', href: data}).appendTo('head');
}
catch (error) { console.log('drupalgap_add_css - ' + error); }
}
/**
* Rounds up all blocks defined by hook_block_info and places them in the
* drupalgap.blocks array.
*/
function drupalgap_load_blocks() {
try {
drupalgap.blocks = module_invoke_all('block_info');
}
catch (error) { console.log('drupalgap_load_blocks - ' + error); }
}
/**
* Loads language files.
*/
function drupalgap_load_locales() {
try {
// Load any drupalgap.settings.locale specified language files.
if (typeof drupalgap.settings.locale === 'undefined') { return; }
for (var language_code in drupalgap.settings.locale) {
if (!drupalgap.settings.locale.hasOwnProperty(language_code)) {
continue;
}
var language = drupalgap.settings.locale[language_code];
var file_path = 'locale/' + language_code + '.json';
if (!drupalgap_file_exists(file_path)) { continue; }
drupalgap.locale[language_code] = drupalgap_file_get_contents(
file_path,
{ dataType: 'json' }
);
}
// Load any language files specified by modules, and merge them into the
// global language file (or create a new one if it doesn't exist).
var modules = module_implements('locale');
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
var fn = window[module + '_locale'];
var languages = fn();
for (var j = 0; j < languages.length; j++) {
var language_code = languages[j];
var file_path = drupalgap_get_path('module', module) + '/locale/' + language_code + '.json';
var translations = drupalgap_file_get_contents(
file_path,
{ dataType: 'json' }
);
if (typeof drupalgap.locale[language_code] === 'undefined') {
drupalgap.locale[language_code] = translations;
}
else {
$.extend(
drupalgap.locale[language_code],
drupalgap.locale[language_code],
translations
);
}
}
}
}
catch (error) { console.log('drupalgap_load_locales - ' + error); }
}
/**
* Checks for an Internet connection, returns true if connected, false otherwise.
* @returns {boolean}
*/
function drupalgap_has_connection() {
try {
drupalgap_check_connection();
module_invoke_all('device_connection');
return drupalgap.online;
}
catch (error) { console.log('drupalgap_has_connection - ' + error); }
}
/**
* Checks the devices connection and sets drupalgap.online to true if the
* device has a connection, false otherwise.
* @return {String}
* A string indicating the type of connection according to PhoneGap.
*/
function drupalgap_check_connection() {
try {
// If we're not in phonegap, just use the navigator.onLine value.
if (drupalgap.settings.mode != 'phonegap' || typeof parent.window.ripple === 'function' ) {
drupalgap.online = navigator.onLine;
return 'Ethernet connection'; // @TODO detect real connection type.
}
// Determine what connection phonegap has.
var networkState = navigator.connection.type;
var states = {};
states[Connection.UNKNOWN] = 'Unknown connection';
states[Connection.ETHERNET] = 'Ethernet connection';
states[Connection.WIFI] = 'WiFi connection';
states[Connection.CELL_2G] = 'Cell 2G connection';
states[Connection.CELL_3G] = 'Cell 3G connection';
states[Connection.CELL_4G] = 'Cell 4G connection';
states[Connection.NONE] = 'No network connection';
drupalgap.online = states[networkState] != 'No network connection';
return states[networkState];
}
catch (error) { console.log('drupalgap_check_connection - ' + error); }
}
/**
* @deprecated Use empty() instead.
* Returns true if given value is empty. A generic way to test for emptiness.
* @param {*} value
* @return {Boolean}
*/
function drupalgap_empty(value) {
try {
console.log(
'WARNING: drupalgap_empty() is deprecated! ' +
'Use empty() instead.'
);
return empty(value);
}
catch (error) { console.log('drupalgap_empty - ' + error); }
}
/**
* Checks if a given file exists, returns true or false.
* @param {string} path
* A path to a file
* @return {bool}
* True if file exists, else false.
*/
function drupalgap_file_exists(path) {
try {
var file_exists = false;
jQuery.ajax({
async: false,
type: 'HEAD',
dataType: 'text',
url: path,
success: function() { file_exists = true; },
error: function(xhr, textStatus, errorThrown) { }
});
return file_exists;
}
catch (error) { console.log('drupalgap_file_exists - ' + error); }
}
/**
* Reads entire file into a string and returns the string. Returns false if
* it fails.
* @param {String} path
* @param {Object} options
* @return {String}
*/
function drupalgap_file_get_contents(path, options) {
try {
var file = false;
var default_options = {
type: 'GET',
url: path,
dataType: 'html',
data: null,
async: false,
success: function(data) { file = data; },
error: function(xhr, textStatus, errorThrown) {
console.log(
'drupalgap_file_get_contents - failed to load file (' + path + ')'
);
}
};
$.extend(default_options, options);
jQuery.ajax(default_options);
return file;
}
catch (error) { console.log('drupalgap_file_get_contents - ' + error); }
}
/**
* @see https://api.drupal.org/api/drupal/includes!common.inc/function/format_interval/7
* @param {Number} interval The length of the interval in seconds.
* @return {String}
*/
function drupalgap_format_interval(interval) {
try {
// @TODO - deprecate this and move it to jDrupal as format_interval().
var granularity = 2; if (arguments[1]) { granularity = arguments[1]; }
var langcode = null; if (arguments[2]) { langcode = langcode[2]; }
var units = {
'1 year|@count years': 31536000,
'1 month|@count months': 2592000,
'1 week|@count weeks': 604800,
'1 day|@count days': 86400,
'1 hour|@count hours': 3600,
'1 min|@count min': 60,
'1 sec|@count sec': 1
};
var output = '';
for (var key in units) {
if (!units.hasOwnProperty(key)) { continue; }
var value = units[key];
var key = key.split('|');
if (interval >= value) {
var count = Math.floor(interval / value);
output +=
(output ? ' ' : '') +
drupalgap_format_plural(
count,
key[0],
key[1]
);
if (output.indexOf('@count') != -1) {
output = output.replace('@count', count);
}
interval %= value;
granularity--;
}
if (granularity == 0) { break; }
}
return output ? output : '0 sec';
}
catch (error) { console.log('drupalgap_format_interval - ' + error); }
}
/**
* @see http://api.drupal.org/api/drupal/includes%21common.inc/function/format_plural/7
* @param {Number} count
* @param {String} singular
* @param {String} plural
* @return {String}
*/
function drupalgap_format_plural(count, singular, plural) {
// @TODO - deprecate this and move it to jDrupal as format_plural().
if (count == 1) { return singular; }
return plural;
}
/**
* @deprecated - Use function_exists() instead.
* @param {String} name
* @return {Boolean}
*/
function drupalgap_function_exists(name) {
try {
console.log('WARNING - drupalgap_function_exists() is deprecated. ' +
'Use function_exists() instead!');
return function_exists(name);
}
catch (error) { console.log('drupalgap_function_exists - ' + error); }
}
/**
* Given an html string from a *.tpl.html file, this will extract all of the
* placeholders names and return them in an array. Returns false otherwise.
* @param {String} html
* @return {*}
*/
function drupalgap_get_placeholders_from_html(html) {
try {
var placeholders = false;
if (html) {
placeholders = html.match(/(?!{:)([\w]+)(?=:})/g);
}
return placeholders;
}
catch (error) {
console.log('drupalgap_get_placeholders_from_html - ' + error);
}
}
/**
* Returns the current page's title.
* @return {String}
*/
function drupalgap_get_title() {
try {
return drupalgap.page.title;
}
catch (error) { console.log('drupalgap_get_title - ' + error); }
}
/**
* Returns the IP Address of the current user as reported by PHP via the last
* System Connect call's $_SERVER['REMOTE_ADDR'] value.
* @return {String|Null}
*/
function drupalgap_get_ip() {
try {
return drupalgap.remote_addr;
}
catch (error) { console.log('drupalgap_get_ip - ' + error); }
}
/**
* Given a router path, this will return an array containing the indexes of
* where the wildcards (%) are present in the router path. Returns false if
* there are no wildcards present.
* @param {String} router_path
* @return {Boolean}
*/
function drupalgap_get_wildcards_from_router_path(router_path) {
// @todo - Is this function even used? Doesn't look like it.
var wildcards = false;
return wildcards;
}
/**
* Given a drupal image file uri, this will return the path to the image on the
* Drupal site.
* @param {String} uri
* @return {String}
*/
function drupalgap_image_path(uri) {
try {
var altered = false;
// If any modules want to alter the path, let them do it.
var modules = module_implements('image_path_alter');
if (modules) {
for (var index in modules) {
if (!modules.hasOwnProperty(index)) { continue; }
var module = modules[index];
var result = module_invoke(module, 'image_path_alter', uri);
if (result) {
altered = true;
uri = result;
break;
}
}
}
if (!altered) {
// No one modified the image path, we'll use the default approach to
// generating the image src path.
var src = Drupal.settings.site_path + Drupal.settings.base_path + uri;
if (src.indexOf('public://') != -1) {
src = src.replace('public://', Drupal.settings.file_public_path + '/');
}
else if (src.indexOf('private://') != -1) {
src = src.replace(
'private://',
Drupal.settings.file_private_path + '/'
);
}
return src;
}
else { return uri; }
}
catch (error) { console.log('drupalgap_image_path - ' + error); }
}
/**
* @deprecated - This is no longer needed since the includes are built via the
* makefile. Loads the js files in includes specified by drupalgap.includes.
*/
function drupalgap_includes_load() {
try {
if (drupalgap.includes != null && drupalgap.includes.length != 0) {
for (var index in drupalgap.includes) {
if (!drupalgap.includes.hasOwnProperty(index)) { continue; }
var include = drupalgap.includes[index];
var include_path = 'includes/' + include.name + '.inc.js';
jQuery.ajax({
async: false,
type: 'GET',
url: include_path,
data: null,
success: function() {
if (Drupal.settings.debug) {
// Print the include path to the console.
dpm(include_path);
}
},
dataType: 'script',
error: function(xhr, textStatus, errorThrown) {
console.log(errorThrown);
}
});
}
}
}
catch (error) { console.log('drupalgap_includes_load - ' + error); }
}
/**
* Given an html list element id and an array of items, this will clear the
* list, populate it with the items, and then refresh the list.
* @param {String} list_css_selector
* @param {Array} items
*/
function drupalgap_item_list_populate(list_css_selector, items) {
try {
// @todo - This could use some validation and alerts for improper input.
$(list_css_selector).html('');
for (var i = 0; i < items.length; i++) {
$(list_css_selector).append($('', { html: items[i] }));
}
$(list_css_selector).listview('refresh').listview();
}
catch (error) { console.log('drupalgap_item_list_populate - ' + error); }
}
/**
* Given an html table element id and an array of rows, this will clear the
* table, populate it with the rows, and then refresh the table.
* @param {String} table_css_selector
* @param {Array} rows
* rows follow the.
*/
function drupalgap_table_populate(table_css_selector, rows) {
try {
// Select only the body. Other things are already setup
table_css_selector = table_css_selector + '> tbody ';
$(table_css_selector).html('');
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var rowhtml = '';
for (var j = 0; j < row.length; j++) {
rowhtml = rowhtml + '
' + row[j] + '
';
}
$('
').html(rowhtml).appendTo($(table_css_selector));
}
$(table_css_selector).rebuild();
}
catch (error) { console.log('drupalgap_table_populate - ' + error); }
}
/**
* Given a jQM page event, and the corresponding callback function name that
* handles the event, this function will call the callback function, if it has
* not already been called on the current page. This really is only used by
* menu_execute_active_handler() to prevent jQM from firing inline page event
* handlers more than once. You may optionally pass in a 4th argument, a string,
* to append to the suffix of the unique key of recorded fired page events.
* @param {String} event
* @param {String} callback
* @param {*} page_arguments
*/
function drupalgap_jqm_page_event_fire(event, callback, page_arguments) {
try {
// Concatenate the event name and the callback name together into a unique
// key so multiple callbacks can handle the same event.
var key = event + '-' + callback;
// Is there an optional 4th argument coming in (the suffix)?
if (typeof arguments[3] !== 'undefined') {
if (arguments[3]) { key += '-' + arguments[3]; }
}
if ($.inArray(key, drupalgap.page.jqm_events) == -1 &&
function_exists(callback)) {
drupalgap.page.jqm_events.push(key);
var fn = window[callback];
if (page_arguments) {
// If the page arguments aren't an array, place them into an array so
// they can be applied to the callback function.
if (!$.isArray(page_arguments)) { page_arguments = [page_arguments]; }
fn.apply(null, Array.prototype.slice.call(page_arguments));
}
else { fn(); }
}
}
catch (error) { console.log('drupalgap_jqm_page_event_fire - ' + error); }
}
/**
* Returns array of jQM Page event names.
* @return {Array}
* @see http://api.jquerymobile.com/category/events/
*/
function drupalgap_jqm_page_events() {
return [
'pagebeforechange',
'pagebeforecreate',
'pagebeforehide',
'pagebeforeload',
'pagebeforeshow',
'pagechange',
'pagechangefailed',
'pagecreate',
'pagehide',
'pageinit',
'pageload',
'pageloadfailed',
'pageremove',
'pageshow'
];
}
/**
* Given a JSON object with a page id, a jQM page event name, a callback
* function to handle the jQM page event and any page arguments (as a JSON
* string), this function will return the inline JS code needed to handle the
* event. You may optionally pass in a unique second argument (string) to
* allow the same page event handler to be fired more than once on a page.
* @param {Object} options
* @return {String}
*/
function drupalgap_jqm_page_event_script_code(options) {
try {
if (!options.page_id) { options.page_id = drupalgap_get_page_id(); }
if (!options.jqm_page_event) { options.jqm_page_event = 'pageshow'; }
// Build the arguments to send to the event fire handler.
var event_fire_args = '"' + options.jqm_page_event + '", "' +
options.jqm_page_event_callback + '", ' +
options.jqm_page_event_args;
if (arguments[1]) { event_fire_args += ', "' + arguments[1] + '"'; }
// Build the inline JS and return it.
return '';
}
catch (error) {
console.log('drupalgap_jqm_page_event_script_code - ' + error);
}
}
/**
* Returns the suggested max width for elements within the content area.
* @return {Number}
*/
function drupalgap_max_width() {
try {
var padding = parseInt($('.ui-content').css('padding'));
if (isNaN(padding)) { padding = 16; } // use a 16px default if needed
return $(document).width() - padding * 2;
}
catch (error) { console.log('drupalgap_max_width - ' + error); }
}
/**
* Checks to see if the current user has access to the given path. Returns true
* if the user has access, false otherwise. You may optionally pass in a user
* account object as the second argument to check access on a specific user.
* Also, you may optionally pass in an entity object as the third argument, if
* that entity needs to be passed along to an 'access_callback' handler.
* @param {String} routerPath The router path of the destination.
* @param {String} path The destination path.
* @return {Boolean}
*/
function drupalgap_menu_access(routerPath, path) {
try {
// User #1 is allowed to do anything, I mean anything.
if (Drupal.user.uid == 1) { return true; }
// Everybody else will not have access unless we prove otherwise.
var access = false;
if (drupalgap.menu_links[routerPath]) {
// Check to see if there is an access callback specified with the menu
// link.
if (typeof drupalgap.menu_links[routerPath].access_callback === 'undefined') {
// No access call back specified, if there are any access arguments
// on the menu link, then it is assumed they are user permission machine
// names, so check that user account's role(s) for that permission to
// grant access.
if (drupalgap.menu_links[routerPath].access_arguments) {
if ($.isArray(drupalgap.menu_links[routerPath].access_arguments)) {
for (var index in drupalgap.menu_links[routerPath].access_arguments) {
if (!drupalgap.menu_links[routerPath].access_arguments.hasOwnProperty(index)) { continue; }
var permission = drupalgap.menu_links[routerPath].access_arguments[index];
access = user_access(permission);
if (access) { break; }
}
}
}
else {
// There is no access callback and no access arguments specified with
// the menu link, so we'll assume everyone has access.
access = true;
}
}
else {
// An access callback function is specified for this routerPath...
var function_name = drupalgap.menu_links[routerPath].access_callback;
if (function_exists(function_name)) {
// Grab the access callback function. If there are any access args
// send them along, or just call the function directly.
// access arguments.
var fn = window[function_name];
if (drupalgap.menu_links[routerPath].access_arguments) {
var access_arguments =
drupalgap.menu_links[routerPath].access_arguments.slice(0);
// If we have an entity loaded, replace the first integer we find
// in the page arguments with the loaded entity.
if (arguments[2]) {
var entity = arguments[2];
for (var index in access_arguments) {
if (!access_arguments.hasOwnProperty(index)) { continue; }
var page_argument = access_arguments[index];
if (is_int(parseInt(page_argument))) {
access_arguments[index] = entity;
break;
}
}
}
else {
// Replace any integer arguments with the corresponding path argument.
for (var i = 0; i < access_arguments.length; i++) {
if (is_int(access_arguments[i])) { access_arguments[i] = arg(i, path); }
}
}
return fn.apply(null, Array.prototype.slice.call(access_arguments));
}
else { return fn(); }
}
else {
console.log('drupalgap_menu_access - access call back (' +
function_name + ') does not exist'
);
}
}
}
else {
console.log('drupalgap_menu_access - routerPath (' + routerPath + ') does not exist');
}
return access;
}
catch (error) { console.log('drupalgap_menu_access - ' + error); }
}
/**
* @deprecated Use module_load() instead.
* @param {String} name
* @return {Object}
*/
function drupalgap_module_load(name) {
try {
return module_load(name);
}
catch (error) { console.log('drupalgap_module_load - ' + error); }
}
/**
* Given a module bundle type, this will return the path to that module bundle's
* directory.
* @param {String} bundle
* @return {String}
*/
function drupalgap_modules_get_bundle_directory(bundle) {
try {
dir = '';
if (bundle == 'core') { dir = 'modules'; }
else if (bundle == 'contrib') { dir = 'app/modules'; }
else if (bundle == 'custom') { dir = 'app/modules/custom'; }
return dir;
}
catch (error) {
console.log('drupalgap_modules_get_bundle_directory - ' + error);
}
}
/**
* Given a router path (and optional path, defaults to current drupalgap path if
* one isn't provided), this takes the path's arguments and replaces any
* wildcards (%) in the router path with the corresponding path argument(s). It
* then returns the assembled path. Returns false otherwise.
* @param {String} input_path
* @return {*}
*/
function drupalgap_place_args_in_path(input_path) {
try {
var assembled_path = false;
if (input_path) {
// Determine path to use and break it up into its args.
var path = drupalgap_path_get();
if (arguments[1]) { path = arguments[1]; }
var path_args = arg(null, path);
// Grab wild cards from router path then replace each wild card with
// the corresponding path arg.
var wildcards;
var input_path_args = arg(null, input_path);
if (input_path_args && input_path_args.length > 0) {
for (var index in input_path_args) {
if (!input_path_args.hasOwnProperty(index)) { continue; }
var _arg = input_path_args[index];
if (_arg == '%') {
if (!wildcards) { wildcards = []; }
wildcards.push(index);
}
}
if (wildcards && wildcards.length > 0) {
for (var index in wildcards) {
if (!wildcards.hasOwnProperty(index)) { continue; }
var wildcard = wildcards[index];
if (path_args[wildcard]) {
input_path_args[wildcard] = path_args[wildcard];
}
}
assembled_path = input_path_args.join('/');
}
}
}
return assembled_path;
}
catch (error) {
console.log('drupalgap_place_args_in_path - ' + error);
}
}
/**
* Given an args array, this returns true if the path in the array will have an
* entity (id) present in it.
* @param {Array} args
* @return {Boolean}
*/
function drupalgap_path_has_entity_arg(args) {
try {
if (args.length > 1 &&
(
args[0] == 'comment' ||
args[0] == 'file' ||
args[0] == 'node' ||
(args[0] == 'taxonomy' &&
(args[1] == 'vocabulary' || args[1] == 'term')
) ||
args[0] == 'user' ||
args[0] == 'item'
)
) { return true; }
return false;
}
catch (error) { console.log('drupalgap_path_has_entity_arg - ' + error); }
}
/**
* Given a page id, this will remove it from the DOM.
* @param {String} page_id
*/
function drupalgap_remove_page_from_dom(page_id) {
try {
$('#' + page_id).empty().remove();
}
catch (error) { console.log('drupalgap_remove_page_from_dom - ' + error); }
}
/**
* Restart the app.
*/
function drupalgap_restart() {
try {
location.reload();
}
catch (error) { console.log('drupalgap_restart - ' + error); }
}
/**
* Implementation of drupal_set_title().
* @param {String} title
*/
function drupalgap_set_title(title) {
try {
if (title) { drupalgap.page.title = title; }
}
catch (error) { console.log('drupalgap_set_title - ' + error); }
}
/**
* Returns true if the loader spinner is enabled, false otherwise. Defaults to true if no config for it is present.
* @returns {Boolean}
*/
function drupalgap_loader_enabled() {
if (!drupalgap.settings.loader) { drupalgap.settings.loader = {}; }
return typeof drupalgap.settings.loader.enabled !== 'undefined' ?
drupalgap.settings.loader.enabled : true;
}
/**
* Toggle on or off the loader spinner, send true to turn it on, false to turn it off.
* @param {Boolean} enable
*/
function drupalgap_loader_enable(enable) {
drupalgap.settings.loader.enabled = enable;
}
/**
* Implements hook_services_preprocess().
* @param {Object} options
*/
function drupalgap_services_preprocess(options) {
if (drupalgap_loader_enabled()) { drupalgap_loading_message_show(); }
}
/**
* Implements hook_services_postprocess().
* @param {Object} options
* @param {Object} result
*/
function drupalgap_services_postprocess(options, result) {
if (drupalgap_loader_enabled()) { drupalgap_loading_message_hide(); }
}
/**
* Implements hook_services_request_pre_postprocess_alter().
* @param {Object} options
* @param {*} result
*/
function drupalgap_services_request_pre_postprocess_alter(options, result) {
try {
// Extract drupalgap system connect service resource results.
if (options.service == 'system' && options.resource == 'connect') {
drupalgap.remote_addr = result.remote_addr;
drupalgap.entity_info = result.entity_info;
drupalgap.field_info_instances = result.field_info_instances;
drupalgap.field_info_fields = result.field_info_fields;
drupalgap.field_info_extra_fields = result.field_info_extra_fields;
drupalgap.taxonomy_vocabularies =
drupalgap_taxonomy_vocabularies_extract(
result.taxonomy_vocabularies
);
drupalgap_service_resource_extract_results({
service: options.service,
resource: options.resource,
data: result
});
}
// Whenever a user logs in, out or registers, remove all pages from the DOM.
else if (options.service == 'user' &&
(options.resource == 'logout' || options.resource == 'login' ||
options.resource == 'register')) {
drupalgap_remove_pages_from_dom();
}
// Whenever an entity is created, updated or deleted, remove the
// corresponing DrupalGap core page(s) from the DOM so the pages will be
// rebuilt properly next time they are loaded.
else if (
in_array(options.resource, ['create', 'update', 'delete']) &&
in_array(options.service, entity_types())
) {
var entity_type = options.entity_type;
var entity_id = options.entity_id;
var bundle = options.bundle || null;
var paths = [];
if (options.resource != 'create') {
var prefix = entity_type;
if (in_array(entity_type, ['taxonomy_vocabulary', 'taxonomy_term'])) {
prefix = prefix.replace('_', '/', prefix);
}
paths.push(prefix + '/' + entity_id);
paths.push(prefix + '/' + entity_id + '/view');
// @todo This page won't get removed since it is the current page...
// maybe when an entity is updated or deleted, we need a transitional
// page that says "Deleting [entity]..." that way we can remove this
// page (since it is not possible to remove the current page in jQM).
// Actually now that I think about, we should have a confirmation page
// when deleting an entity just like Drupal, and that should take care
// of the deletion case. Not sure what to do about the update case...
// maybe some type of pageshow handler on entity view pages that can
// remove the edit form for the entity.
paths.push(prefix + '/' + entity_id + '/edit');
}
else {
switch (entity_type) {
case 'node':
// @todo This page won't get removed since it is the current page...
paths.push('node/add/' + bundle);
break;
}
}
// Add extras depending on the entity type.
switch (entity_type) {
case 'node': paths.push('node'); break;
case 'taxonomy_vocabulary': paths.push('taxonomy/vocabularies'); break;
case 'user': paths.push('user-listing'); break;
}
// Convert the paths to page ids, then remove them from the DOM.
var pages = [];
for (var index in paths) {
if (!paths.hasOwnProperty(index)) { continue; }
var path = paths[index];
pages.push(drupalgap_get_page_id(path));
}
for (var index in pages) {
if (!pages.hasOwnProperty(index)) { continue; }
var page_id = pages[index];
drupalgap_remove_page_from_dom(page_id);
}
}
}
catch (error) {
console.log('drupalgap_services_request_pre_postprocess_alter - ' + error);
}
}
/**
* @deprecated - Loads the settings specified in app/settings.js into the app.
*/
function drupalgap_settings_load() {
try {
console.log('WARNING: drupalgap_settings_load() is deprecated!');
//drupal_settings_load();
}
catch (error) {
console.log('drupalgap_settings_load - ' + error);
}
}
/**
* This calls all implementations of hook_theme and builds the DrupalGap theme
* registry.
*/
function drupalgap_theme_registry_build() {
try {
var modules = module_implements('theme');
for (var index in modules) {
if (!modules.hasOwnProperty(index)) { continue; }
var module = modules[index];
var function_name = module + '_theme';
var fn = window[function_name];
var hook_theme = fn();
for (var element in hook_theme) {
if (!hook_theme.hasOwnProperty(element)) { continue; }
var variables = hook_theme[element];
variables.path = drupalgap_get_path(
'theme',
drupalgap.settings.theme
);
drupalgap.theme_registry[element] = variables;
}
}
}
catch (error) { console.log('drupalgap_theme_registry_build - ' + error); }
}
/**
* Given a variable name and value, this will save the value to local storage,
* keyed by its name.
* @param {String} name
* @param {*} value
* @return {*}
*/
function variable_set(name, value) {
try {
if (!value) { value = ' '; } // store null values as a single space*
else if (is_int(value)) { value = value.toString(); }
else if (typeof value === 'object') { value = JSON.stringify(value); }
return window.localStorage.setItem(name, value);
// * phonegap won't store an empty string in local storage
}
catch (error) { drupalgap_error(error); }
}
/**
* Given a variable name and a default value, this will first attempt to load
* the variable from local storage, if it can't then the default value will be
* returned.
* @param {String} name
* @param {*} default_value
* @return {*}
*/
function variable_get(name, default_value) {
try {
var value = window.localStorage.getItem(name);
if (!value) { value = default_value; }
if (value == ' ') { value = ''; } // Convert single spaces to empty strings.
return value;
}
catch (error) { drupalgap_error(error); }
}
/**
* Given a variable name, this will remove the value from local storage.
* @param {String} name
* @return {*}
*/
function variable_del(name) {
try {
return window.localStorage.removeItem(name);
}
catch (error) { drupalgap_error(error); }
}
/**
* Returns the current time as a string with the format: "yyyy-mm-dd hh:mm:ss".
* @return {String}
*/
function date_yyyy_mm_dd_hh_mm_ss() {
try {
var result;
if (arguments[0]) { result = arguments[0]; }
else { result = date_yyyy_mm_dd_hh_mm_ss_parts(); }
return result['year'] + '-' + result['month'] + '-' + result['day'] + ' ' +
result['hour'] + ':' + result['minute'] + ':' + result['second'];
}
catch (error) { console.log('date_yyyy_mm_dd_hh_mm_ss - ' + error); }
}
/**
* Returns an array with the parts for the current time. You may optionally
* pass in a JS date object to use that date instead.
* @return {Array}
*/
function date_yyyy_mm_dd_hh_mm_ss_parts() {
try {
var result = [];
var now = null;
if (arguments[0]) { now = arguments[0]; }
else { now = new Date(); }
var year = '' + now.getFullYear();
var month = '' + (now.getMonth() + 1);
if (month.length == 1) { month = '0' + month; }
var day = '' + now.getDate();
if (day.length == 1) { day = '0' + day; }
var hour = '' + now.getHours();
if (hour.length == 1) { hour = '0' + hour; }
var minute = '' + now.getMinutes();
if (minute.length == 1) { minute = '0' + minute; }
var second = '' + now.getSeconds();
if (second.length == 1) { second = '0' + second; }
result['year'] = year;
result['month'] = month;
result['day'] = day;
result['hour'] = hour;
result['minute'] = minute;
result['second'] = second;
return result;
}
catch (error) { console.log('date_yyyy_mm_dd_hh_mm_ss_parts - ' + error); }
}
/**
* Given a year and month (0-11), this will return the number of days in that
* month.
* @see http://stackoverflow.com/a/1810990/763010
* @param {Number} year
* @param {Number} month
* @return {Number}
*/
function date_number_of_days_in_month(year, month) {
try {
var d = new Date(year, month, 0);
return d.getDate();
}
catch (error) { console.log('date_number_of_days_in_month - ' + error); }
}
/**
* @see http://www.dconnell.co.uk/blog/index.php/2012/03/12/scroll-to-any-element-using-jquery/
*/
function scrollToElement(selector, time, verticalOffset) {
try {
time = typeof(time) != 'undefined' ? time : 1000;
verticalOffset = typeof(verticalOffset) != 'undefined' ? verticalOffset : 0;
element = $(selector);
offset = element.offset();
offsetTop = offset.top + verticalOffset;
$('html, body').animate({
scrollTop: offsetTop
}, time);
}
catch (error) { console.log('scrollToElement - ' + error); }
}
/**
* Autocomplete global variables. Used to hold onto various global variables
* needed for an autocomplete.
*/
// The autocomplete text field input selector.
var _theme_autocomplete_input_selector = {};
// The autocomplete remote boolean.
var _theme_autocomplete_remote = {};
// The theme autocomplete variables.
var _theme_autocomplete_variables = {};
// The theme autocomplete variables.
var _theme_autocomplete_success_handlers = {};
/**
* Themes an autocomplete.
* @param {Object} variables
* @return {String}
*/
function theme_autocomplete(variables) {
try {
var html = '';
// We need to have a unique identifier for this autocomplete. If it is a
// field, use the field name. Otherwise use the id attribute if it is
// provided or generate a random one. Then finally attach the autocomplete
// id to the variables so it can be passed along.
var autocomplete_id = null;
if (typeof variables.field_info_field !== 'undefined') {
autocomplete_id = variables.field_info_field.field_name + '_' + variables.delta;
}
else if (typeof variables.attributes.id !== 'undefined') {
autocomplete_id = variables.attributes.id;
}
else { autocomplete_id = user_password(); }
variables.autocomplete_id = autocomplete_id;
// Hold onto a copy of the variables.
_theme_autocomplete_variables[autocomplete_id] = {};
$.extend(true, _theme_autocomplete_variables[autocomplete_id], variables);
// Are we dealing with a remote data set?
var remote = false;
if (variables.remote) { remote = true; }
variables.remote = remote;
_theme_autocomplete_remote[autocomplete_id] = variables.remote;
// Make sure we have an id to use on the list.
var id = null;
if (variables.attributes.id) { id = variables.attributes.id; }
else {
id = 'autocomplete_' + user_password();
variables.attributes.id = id;
}
// We need a hidden input to hold the value. If a default value
// was provided by a form element, use it.
var hidden_attributes = {};
$.extend(hidden_attributes, variables.attributes);
if (
variables.element &&
typeof variables.element.default_value !== 'undefined'
) { hidden_attributes.value = variables.element.default_value; }
html += theme('hidden', { attributes: hidden_attributes });
// Now we need an id for the list.
var list_id = id + '-list';
// Build the widget variables.
var widget = {
attributes: {
'id': list_id,
'data-role': 'listview',
'data-filter': 'true',
'data-inset': 'true',
'data-filter-placeholder': '...'
}
};
// Handle a remote data set.
var js = '';
if (variables.remote) {
widget.items = [];
// We have a remote data set.
js += '';
}
else {
// Prepare the items then set the data filter reveal attribute.
widget.items = _theme_autocomplete_prepare_items(variables);
widget.attributes['data-filter-reveal'] = true;
}
// Save a reference to the autocomplete text field input.
var selector = '#' + drupalgap_get_page_id() +
' #' + id + ' + form.ui-filterable' +
' input[data-type="search"]';
js += '';
// If there was a default value, set it's key title in the autocomplete's
// text field.
if (variables.default_value_label) {
js += drupalgap_jqm_page_event_script_code({
page_id: drupalgap_get_page_id(),
jqm_page_event: 'pageshow',
jqm_page_event_callback:
'_theme_autocomplete_set_default_value_label',
jqm_page_event_args: JSON.stringify({
selector: selector,
default_value_label: variables.default_value_label
})
}, id);
}
// Theme the list and add the js to it, then return the html.
html += theme('item_list', widget);
html += js;
return html;
}
catch (error) { console.log('theme_autocomplete - ' + error); }
}
/**
* An internal function used to handle remote data for an autocomplete.
* @param {Object} list The unordered list that displays the items.
* @param {Object} e
* @param {Object} data
* @param {String} autocomplete_id
*/
function _theme_autocomplete(list, e, data, autocomplete_id) {
try {
var autocomplete = _theme_autocomplete_variables[autocomplete_id];
// Make sure a filter is present.
if (typeof autocomplete.filter === 'undefined') {
console.log(
'_theme_autocomplete - A "filter" was not supplied.'
);
return;
}
// Make sure a value and/or label has been supplied so we know how to render
// the items in the autocomplete list.
var value_provided = typeof autocomplete.value !== 'undefined';
var label_provided = typeof autocomplete.label !== 'undefined';
if (!value_provided && !label_provided) {
console.log(
'_theme_autocomplete - A "value" and/or "label" was not supplied.'
);
return;
}
else {
// We have a value and/or label. If one isn't provided, set it equal to
// the other.
if (!value_provided) { autocomplete.value = autocomplete.label; }
else if (!label_provided) { autocomplete.label = autocomplete.value; }
}
// Setup the vars to handle this widget.
var $ul = $(list),
$input = $(data.input),
value = $input.val(),
html = '';
// Clear the list.
$ul.html('');
// If a value has been input, start the autocomplete search.
if (value && value.length > 0 && !autocomplete._searching) {
autocomplete._searching = true;
// Show the loader icon.
$ul.html('
' +
'' +
'
');
$ul.listview('refresh');
// Let's first build the success handler that will place the items into
// the autocomplete list.
_theme_autocomplete_success_handlers[autocomplete_id] = function(
_autocomplete_id, result_items, _wrapped, _child) {
try {
autocomplete._searching = false;
// If there are no results, and then if an empty callback handler was
// provided, call it.
// empty callback handler, then call it and return.
if (result_items.length == 0) {
if (autocomplete.empty_callback) {
var fn = window[autocomplete.empty_callback];
fn(value);
}
}
else {
// Convert the result into an items array for a list. Each item will
// be a JSON object with a "value" and "label" properties.
var items = [];
var _value = autocomplete.value;
var _label = autocomplete.label;
for (var index in result_items) {
if (!result_items.hasOwnProperty(index)) { continue; }
var object = result_items[index];
var _item = null;
if (_wrapped) { _item = object[_child]; }
else { _item = object; }
var item = {
value: _item[_value],
label: _item[_label]
};
items.push(item);
}
// Now render the items, add them to list and refresh the list.
if (items.length != 0) {
autocomplete.items = items;
var _items = _theme_autocomplete_prepare_items(autocomplete);
for (var index in _items) {
if (!_items.hasOwnProperty(index)) { continue; }
var item = _items[index];
html += '
' + item + '
';
}
$ul.html(html);
$ul.listview('refresh');
$ul.trigger('updatelayout');
}
}
// Anybody want to act on the completion of the autocomplete?
if (autocomplete.finish_callback) {
var fn = window[autocomplete.finish_callback];
fn(value);
}
}
catch (error) {
console.log('_theme_autocomplete_success_handlers[' +
_autocomplete_id +
'] - ' + error);
}
};
// Depending on the handler, build the path and call the Drupal site for
// the data. If it's a custom path, see if a handler was provided,
// otherwise just default to views.
var handler = null;
if (autocomplete.custom) {
if (autocomplete.handler) { handler = autocomplete.handler; }
else if (autocomplete.field_info_field && autocomplete.field_info_field.settings.handler) {
handler = autocomplete.field_info_field.settings.handler;
}
else { handler = 'views'; }
}
else if (autocomplete.field_info_field) {
handler = autocomplete.field_info_field.settings.handler;
}
else { handler = 'views'; }
switch (handler) {
// Views (and Organic Groups)
case 'views':
// Prepare the path to the view.
var path = autocomplete.path + '?' + autocomplete.filter + '=' +
encodeURIComponent(value);
// Any extra params to send along?
if (autocomplete.params) { path += '&' + autocomplete.params; }
// Retrieve JSON results. Keep in mind, we use this for retrieving
// Views JSON results and custom hook_menu() path results in Drupal.
views_datasource_get_view_result(path, {
success: function(results) {
// If this was a custom path, don't use a wrapper around the
// results like the one used by Views Datasource.
var wrapped = true;
if (autocomplete.custom) { wrapped = false; }
// Extract the result items based on the presence of the wrapper
// or not.
var result_items = null;
if (wrapped) { result_items = results[results.view.root]; }
else { result_items = results; }
// Finally call the success handler. Note, since we route custom Drupal hook_menu() item JSON page
// callbacks through this Views handler, we don't attempt to send along the view to the handler. Hack.
var fn = _theme_autocomplete_success_handlers[autocomplete_id];
if (results.view) { fn(autocomplete_id, result_items, wrapped, results.view.child); }
else { fn(autocomplete_id, result_items, wrapped); }
}
});
break;
// Simple entity selection mode (provided by the entity reference
// module), use the Index resource for the entity type.
case 'base':
case 'og':
var field_settings =
autocomplete.field_info_field.settings;
var index_resource = field_settings.target_type + '_index';
if (!function_exists(index_resource)) {
console.log('WARNING - _theme_autocomplete - ' +
index_resource + '() does not exist!'
);
return;
}
var options = {
fields: [autocomplete.value, autocomplete.filter],
parameters: { },
parameters_op: { }
};
options.parameters[autocomplete.filter] = '%' + value + '%';
options.parameters_op[autocomplete.filter] = 'like';
var bundles = entityreference_get_target_bundles(field_settings);
if (bundles) { options.parameters[entity_get_bundle_name(field_settings.target_type)] = bundles.join(','); }
window[index_resource](options, {
success: function(results) {
_theme_autocomplete_success_handlers[autocomplete_id](autocomplete_id, results, false);
}
});
break;
// An entity index resource call. Figure out which entity type index
// to call, and build a default query if one wasn't provided.
case 'index':
if (!autocomplete.entity_type) {
console.log(
'WARNING - _theme_autocomplete - no entity_type provided'
);
return;
}
var function_name = autocomplete.entity_type + '_index';
var fn = window[function_name];
var query = null;
if (autocomplete.query) { query = autocomplete.query; }
else {
query = {
parameters: { },
parameters_op: { }
};
var fields = [
entity_primary_key(autocomplete.entity_type),
entity_primary_key_title(autocomplete.entity_type)
];
if (autocomplete.entity_type == 'taxonomy_term') {
if (autocomplete.vid) { query.parameters['vid'] = autocomplete.vid; }
if (autocomplete.parent) { query.parameters['parent'] = autocomplete.parent; }
}
query.fields = fields;
query.parameters[autocomplete.filter] = '%' + value + '%';
query.parameters_op[autocomplete.filter] = 'like';
}
fn.apply(null, [query, {
success: function(results) {
var fn = _theme_autocomplete_success_handlers[autocomplete_id];
fn(autocomplete_id, results, false, null);
}
}]);
break;
// The default handler...
default:
// If we made it this far, and don't have a handler, then warn the
// developer.
if (!handler) {
console.log('WARNING - _theme_autocomplete - no handler provided');
return;
}
break;
}
}
else {
// The autocomplete text field was emptied, clear out the hidden value.
$('#' + autocomplete.id).val('');
}
}
catch (error) { console.log('_theme_autocomplete - ' + error); }
}
/**
* An internal function used to prepare the items for an autocomplete list.
* @param {Object} variables
* @return {*}
*/
function _theme_autocomplete_prepare_items(variables) {
try {
// Make sure we have an items array.
var items = [];
if (variables.items) { items = variables.items; }
// Prepare the items, and return them.
var _items = [];
if (items.length > 0) {
for (var index in items) {
if (!items.hasOwnProperty(index)) { continue; }
var item = items[index];
var value = '';
var label = '';
if (typeof item === 'string') {
value = item;
label = item;
}
else {
value = item.value;
label = item.label;
}
var options = {
attributes: {
value: value,
onclick: '_theme_autocomplete_click(\'' +
variables.attributes.id +
'\', this, \'' + variables.autocomplete_id + '\')'
}
};
var _item = l(label, null, options);
_items.push(_item);
}
}
return _items;
}
catch (error) { console.log('_theme_autocomplete_prepare_items - ' + error); }
}
/**
* An internal function used to handle clicks on items in autocomplete results.
* @param {String} id The id of the hidden input that holds the value.
* @param {Object} item The list item anchor that was just clicked.
* @param {String} autocomplete_id
*/
function _theme_autocomplete_click(id, item, autocomplete_id) {
try {
// Set the hidden input with the value, and the text field with the text.
var list_id = id + '-list';
$('#' + id).val($(item).attr('value'));
$(_theme_autocomplete_input_selector[autocomplete_id]).val($(item).html());
if (_theme_autocomplete_remote[autocomplete_id]) {
$('#' + list_id).html('');
}
else {
$('#' + list_id + ' li').addClass('ui-screen-hidden');
$('#' + list_id).listview('refresh');
}
// Now fire the item onclick handler, if one was provided.
if (
_theme_autocomplete_variables[autocomplete_id].item_onclick &&
function_exists(
_theme_autocomplete_variables[autocomplete_id].item_onclick
)
) {
var fn =
window[_theme_autocomplete_variables[autocomplete_id].item_onclick];
fn(id, $(item));
}
}
catch (error) { console.log('_theme_autocomplete_click - ' + error); }
}
/**
* Used to set a default value in an autocomplete's text field.
* @param {Object} options
*/
function _theme_autocomplete_set_default_value_label(options) {
try {
setTimeout(function() {
$(options.selector).val(options.default_value_label).trigger('create');
}, 250);
}
catch (error) {
console.log('_theme_autocomplete_set_default_value_label - ' + error);
}
}
/**
* Given a block delta, this will return the corresponding
* block from drupalgap.blocks.
* @param {String} delta
* @return {Object}
*/
function drupalgap_block_load(delta) {
try {
var block = null;
if (drupalgap.blocks) {
for (var index in drupalgap.blocks) {
if (!drupalgap.blocks.hasOwnProperty(index)) { continue; }
var object = drupalgap.blocks[index];
if (object[delta]) {
block = object[delta];
break;
}
}
}
if (block == null) {
var msg = 'drupalgap_block_load - ' + t('failed to load') + ' "' + delta +
'" ' + t('block!');
drupalgap_alert(msg);
}
return block;
}
catch (error) { console.log('drupalgap_block_load - ' + error); }
}
/**
* Renders the html string for a block.
* @param {Object} region
* @param {String} current_path
* @param {String} block_delta
* @param {Object} block_settings
* @param {Object} block_counts
* @return {String}
*/
function drupalgap_block_render(region, current_path, block_delta,
block_settings, block_counts) {
try {
var content = '';
// Check the block's visibility settings. If an access_callback
// function is specified on the block's settings, we'll call that
// to determine the visibility, otherwise we'll fall back to the
// default visibility determination mechanism.
var render_block = false;
if (
block_settings.access_callback &&
function_exists(block_settings.access_callback)
) {
var fn = window[block_settings.access_callback];
render_block = fn({
path: current_path,
delta: block_delta,
region: region.name,
theme: drupalgap.settings.theme,
settings: block_settings
});
}
else if (drupalgap_check_visibility('block', block_settings)) {
render_block = true;
// The 'offline' and 'error' pages only have the 'main' system
// block visible.
if (block_delta != 'main' && (
current_path == 'offline' || current_path == 'error')
) { render_block = false; }
}
if (render_block) {
var block = drupalgap_block_load(block_delta);
if (block_counts) { block_counts.block_count++; }
if (menu_load(block_delta) && block_counts) {
block_counts.block_menu_count++;
}
if (block) {
content = module_invoke(
block.module,
'block_view',
block_delta,
region
);
}
}
return typeof content === 'string' ? content : drupalgap_render(content);
}
catch (error) { console.log('drupalgap_block_render - ' + error); }
}
/**
* Converts a JSON object to an XML/HTML tag attribute string and returns the
* string.
* @param {Object} attributes
* @return {String{
*/
function drupalgap_attributes(attributes) {
try {
var attribute_string = '';
if (attributes) {
for (var name in attributes) {
if (!attributes.hasOwnProperty(name)) { continue; }
var value = attributes[name];
if (value != '') {
// @todo - if someone passes in a value with double quotes, this
// will break. e.g.
// 'onclick':'_drupalgap_form_submit("' + form.id + "');'
// will break, but
// 'onclick':'_drupalgap_form_submit(\'' + form.id + '\');'
// will work.
attribute_string += name + '="' + value + '" ';
}
else {
// The value was empty, just place the attribute name on the
// element.
attribute_string += name + ' ';
}
}
}
return attribute_string;
}
catch (error) { console.log('drupalgap_attributes - ' + error); }
}
/**
* Used by drupalgap_render_region to check the visibility settings on region
* links and blocks. Just like Drupal Blocks, this function checks the
* visibility rules specified by role or pages specified in data. Returns true
* by default, otherwise it will return true or false depending on the first
* visibility setting present in data.
* @param {String} type
* @param {Object} data
* @return {Boolean}
*/
function drupalgap_check_visibility(type, data) {
try {
var visible = true;
if (typeof data === 'undefined') {
console.log(
'drupalgap_check_visibility - WARNING - no data provided for type (' +
type + ')'
);
}
// Roles.
else if (typeof data.roles !== 'undefined' &&
data.roles && data.roles.value && data.roles.value.length != 0) {
for (var role_index in data.roles.value) {
if (!data.roles.value.hasOwnProperty(role_index)) { continue; }
var role = data.roles.value[role_index];
if (drupalgap_user_has_role(role)) {
// User has role, show/hide the block accordingly.
if (data.roles.mode == 'include') { visible = true; }
if (data.roles.mode == 'exclude') { visible = false; }
}
else {
// User does not have role, show/hide the block accordingly.
if (data.roles.mode == 'include') { visible = false; }
if (data.roles.mode == 'exclude') { visible = true; }
}
// Break out of the loop if already determined to be visible.
if (visible) { break; }
}
}
// Pages.
else if (typeof data.pages !== 'undefined' && data.pages &&
data.pages.value && data.pages.value.length != 0) {
var current_path = drupalgap_path_get();
var current_path_parts = current_path.split('/');
for (var page_index in data.pages.value) {
if (!data.pages.value.hasOwnProperty(page_index)) { continue; }
var path = data.pages.value[page_index];
if (path == '') { path = drupalgap.settings.front; }
if (path == current_path) {
if (data.pages.mode == 'include') { visible = true; }
else if (data.pages.mode == 'exclude') { visible = false; }
break;
}
else {
// It wasn't a direct path match, is there a wildcard that matches
// the router path?
if (path.indexOf('*') != -1) {
var router_path =
drupalgap_get_menu_link_router_path(current_path);
if (router_path.replace(/%/g, '*') == path) {
if (data.pages.mode == 'include') { visible = true; }
else if (data.pages.mode == 'exclude') { visible = false; }
break;
}
else {
var path_parts = path.split('/');
var match = true;
if (path_parts.length == 0) { match = false; }
else if (path_parts.length == current_path_parts.length) {
for (var i = 0; i < path_parts.length; i++) {
if (path_parts[i] != current_path_parts[i]) {
match = false;
break;
}
}
}
if (match) {
if (data.pages.mode == 'include') { visible = false; }
else if (data.pages.mode == 'exclude') { visible = true; }
}
}
}
else {
// There's no wildcard in the rule, and it wasn't a direct path
// match.
if (data.pages.mode == 'include') { visible = false; }
else if (data.pages.mode == 'exclude') { visible = true; }
}
}
}
}
return visible;
}
catch (error) { console.log('drupalgap_check_visibility - ' + error); }
}
/**
* @deprecated
* @ see entity_get_bundle()
*/
function drupalgap_get_bundle(entity_type, entity) {
try {
var msg = 'WARNING - drupalgap_get_bundle() is deprecated, use ' +
'entity_get_bundle() instead!';
console.log(msg);
return entity_get_bundle(entity_type, entity);
}
catch (error) { console.log('drupalgap_get_bundle - ' + error); }
}
/**
* Returns the path to a system item (module, theme, etc.), returns false if it
* can't find it.
* @param {String} type
* @param {String} name
* @return {*}
*/
function drupalgap_get_path(type, name) {
try {
var path = null;
if (type == 'module') {
var found_module = false;
for (var bundle in Drupal.modules) {
if (!Drupal.modules.hasOwnProperty(bundle)) { continue; }
var modules = Drupal.modules[bundle];
if (found_module) { break; }
else {
for (var index in modules) {
if (!modules.hasOwnProperty(index)) { continue; }
var module = modules[index];
if (module.name == name) {
found_module = true;
path = '';
if (bundle == 'core') { path += 'modules'; }
else if (bundle == 'contrib') { path += 'app/modules'; }
else if (bundle == 'custom') { path += 'app/modules/custom'; }
else {
var msg = 'drupalgap_get_path - unknown module bundle (' +
bundle +
')';
drupalgap_alert(msg);
break;
}
path += '/' + name;
break;
}
}
}
}
}
else if (type == 'theme') {
if (name == 'easystreet3' || name == 'ava') { path = 'themes/' + name; }
else { path = 'app/themes/' + name; }
}
else {
console.log(
'WARNING: drupalgap_get_path - unsupported type (' + type + ')'
);
}
return path;
}
catch (error) { console.log('drupalgap_get_path - ' + error); }
}
/**
* Given an error message, this will log the message to the console and goto
* the error page, if it isn't there already. If Drupal.settings.debug is set
* to true, this function will also alert the error. You may optionally send in
* a second message that will be displayed to the user via an alert dialog box.
* @param {String} message
*/
function drupalgap_error(message) {
try {
// Generate a developer error message, log it to the console, then alert
// the message if debugging is enabled.
var error_message = 'drupalgap_error() - ' +
arguments.callee.caller.name + ' - ' +
message;
dpm(error_message);
if (Drupal.settings.debug) { drupalgap_alert(error_message); }
// If a message for the user was passed in, display it to the user.
if (arguments[1]) { drupalgap_alert(arguments[1]); }
// Goto the error page if we are not already there.
if (drupalgap_path_get() != 'error') { drupalgap_goto('error'); }
}
catch (error) { console.log('drupalgap_error - ' + error); }
}
/**
* Given a link JSON object, this will return its attribute class value, or null
* if it isn't set.
* @param {Object} link
* @return {String}
*/
function drupalgap_link_get_class(link) {
try {
var css_class = null;
if (
link.options && link.options.attributes &&
link.options.attributes['class'] &&
!empty(link.options.attributes['class'])
) { css_class = link.options.attributes['class']; }
return css_class;
}
catch (error) { console.log('drupalgap_link_get_class - ' + error); }
}
/**
* Get the current DrupalGap path.
* @return {String}
*/
function drupalgap_path_get() {
try { return drupalgap.path; }
catch (error) { console.log('drupalgap_path_get - ' + error); }
}
/**
* Set the current DrupalGap path.
* @param {String} path
*/
function drupalgap_path_set(path) {
try { drupalgap.path = path; }
catch (error) { console.log('drupalgap_path_set - ' + error); }
}
/**
* Get the current DrupalGap router_path.
* @return {String}
*/
function drupalgap_router_path_get() {
try { return drupalgap.router_path; }
catch (error) { console.log('drupalgap_router_path_get - ' + error); }
}
/**
* Set the current DrupalGap router_path.
* @param {String} router_path
*/
function drupalgap_router_path_set(router_path) {
try { drupalgap.router_path = router_path; }
catch (error) { console.log('drupalgap_router_path_set - ' + error); }
}
/**
* Returns the path used for a given page id. Essentially a reverse path look up, given a page id.
* @param {String} page_id
* @returns {String|null}
*/
function drupalgap_get_path_from_page_id(page_id) {
if (!page_id) { page_id = drupalgap_get_page_id(); }
// SUPPORTS THE FOLLOWING PATH PATTERNS:
// * foo
// */* foo/bar, node/123
// *-*/* foo-bar/chew, foo-bar/123
// */*/*/... foo/bar/chew, foo/123/bar, foo/bar/chew/you, etc
// First check to see if there is an exact router match, if there is use it. If there is no exact match, split the
// page id by underscore into arguments so we can attempt to lookup a potential router.
if (drupalgap.menu_links[page_id]) { return drupalgap.menu_links[page_id].path; }
else {
var args = page_id.split('_');
if (args.length < 2) { return null; }
var slashPath = '';
var hyphenPath = '';
for (var i = 0; i < args.length; i++) {
slashPath += args[i];
if (i != args.length -1) { slashPath += '/'; }
hyphenPath += args[i];
if (i == 0) { hyphenPath += '-'; }
else if (i != 0 && i != args.length -1) { hyphenPath += '/'; }
}
var potentialPaths = [slashPath, hyphenPath];
for (var j = 0; j < potentialPaths.length; j++) {
var routerPath = drupalgap_get_menu_link_router_path(potentialPaths[j]);
if (!drupalgap.menu_links[routerPath]) { continue; }
return potentialPaths[j];
}
}
return null;
}
/**
* Implementation of arg(index = null, path = null).
* @return {*}
*/
function arg() {
try {
var result = null;
// If there were zero or one arguments provided.
if (arguments.length == 0 || arguments.length == 1) {
// Split the path into parts.
var drupalgap_path = drupalgap_path_get();
var args = drupalgap_path.split('/');
// If no arguments were provided just return the split array, otherwise
// return whichever argument was requested.
if (arguments.length == 0) { result = args; }
else if (args[arguments[0]]) { result = args[arguments[0]]; }
}
else {
// A path was provided, split it into parts, then return the split array
// if they didn't request a specific index, otherwise return the value of
// the specific index inside the split array.
var path = arguments[1];
var args = path.split('/');
if (arguments[0] && args[arguments[0]]) { result = args[arguments[0]]; }
else { result = args; }
}
return result;
}
catch (error) { console.log('arg - ' + error); }
}
/**
* Returns a link.
* @return {String}
*/
function l() {
try {
// Grab the text and the path from the arguments and then build a simple
// link object.
var text = arguments[0];
var path = arguments[1];
var link = {'text': text, 'path': path};
// Determine if there are any incoming link options, if there are, attach
// them to the link object. If there are any attributes, extract them from
// the options and attach them directly to the link object.
var options = null;
if (arguments[2]) {
options = arguments[2];
if (options.attributes) { link.attributes = options.attributes; }
link.options = options;
}
return theme('link', link);
}
catch (error) { console.log('l - ' + error); }
}
/**
* Returns a button link.
* @return {String}
*/
function bl() {
try {
// Grab the text and the path.
var text = arguments[0];
var path = arguments[1];
// Build the default options and attributes, if necessary.
var options = null;
if (arguments[2]) {
options = arguments[2];
}
else { options = {}; }
if (!options.attributes) { options.attributes = { }; }
options.attributes['data-role'] = 'button';
return l(text, path, options);
}
catch (error) { console.log('bl - ' + error); }
}
/**
* Returns translated text.
* @param {String} str The string to translate
* @return {String}
*/
function t(str) {
var lang = arguments[3] ? arguments[3] : Drupal.settings.language_default;
if (
lang != 'und' &&
typeof drupalgap.locale[lang] !== 'undefined' &&
drupalgap.locale[lang][str]
) { return drupalgap.locale[lang][str]; }
return str;
}
// Holds onto any query string during the page go process.
var _drupalgap_goto_query_string = null;
/**
* Given a path, this will change the current page in the app.
* @param {String} path
* @return {*}
*/
function drupalgap_goto(path) {
try {
// Extract any incoming options, set any defaults that weren't provided,
// then populate the global page options variable.
var options = {};
if (arguments[1]) {
options = arguments[1];
if (typeof options.form_submission === 'undefined') {
options.form_submission = false;
}
}
drupalgap.page.options = options;
// Prepare the path.
path = _drupalgap_goto_prepare_path(path, true);
if (!path) { return false; }
// Invoke all implementations of hook_drupalgap_goto_preprocess().
module_invoke_all('drupalgap_goto_preprocess', path);
// Determine the router path.
var router_path = drupalgap_get_menu_link_router_path(path);
// Make sure we have a menu link item that can handle this router path,
// otherwise we'll goto the 404 page.
if (!drupalgap.menu_links[router_path]) {
// Is anyone trying to handle this 404?
var new_path = false;
var invocation_results = module_invoke_all('404', router_path);
if (invocation_results) {
for (var index in invocation_results) {
if (!invocation_results.hasOwnProperty(index)) { continue; }
var result = invocation_results[index];
if (result !== false) {
new_path = result;
break;
}
}
}
// If a 404 handler provided a new path use it, otherwise just use the
// system 404 page. Either way, update the router path before continuing
// with a normal page build.
if (new_path) { path = new_path; }
else { path = '404'; }
router_path = drupalgap_get_menu_link_router_path(path);
}
// Make sure the user has access to this router path, if they don't send
// them to the 401 page.
// @TODO - for now we're going to skip access checks on local tasks, since
// they are covered by menu_block_view(), but if someone were to navigate
// directly to e.g. a node's edit page, they would be able to see the page.
// Of course Drupal would actually prevent them from updating the node on
// the live site, but nonetheless this needs to be fixed. It's a tough issue
// though and related to https://github.com/signalpoint/DrupalGap/issues/257
if (
drupalgap.menu_links[router_path].type != 'MENU_DEFAULT_LOCAL_TASK' &&
drupalgap.menu_links[router_path].type != 'MENU_LOCAL_TASK' &&
!drupalgap_menu_access(router_path, path)
) {
path = '401';
router_path = drupalgap_get_menu_link_router_path(path);
}
// If the new router path is the same as the current router path and the new
// path is the same as the current path, we may need to cancel the
// navigation attempt (i.e. don't go anywhere), unless it is a
// form submission, then continue.
if (
router_path == drupalgap_router_path_get() &&
path == drupalgap_path_get()
) {
// If it's a form submission, we'll continue onward...
if (options.form_submission) { }
// If we're reloading the current page, we need to set aside this path
// and navigate to the system's _reload page, which will then handle the
// actual reloading of the page.
// @see system_drupalgap_goto_post_process()
else if (options.reloadPage) {
_system_reload_page = path;
path = '_reload';
router_path = drupalgap_get_menu_link_router_path(path);
}
// Otherwise, just stop the navigation attempt.
else { return false; }
}
// Grab the page id.
var page_id = drupalgap_get_page_id(path);
// Return if we are trying to go to the path we are already on, unless this
// was a form submission, then we'll let the page rebuild itself. For
// accuracy we compare the jQM active page url with the destination page
// id.
// @todo - this boolean doesn't match the comment description of the code
// block, i.e. the form_submission check is opposite of what it says
if (drupalgap_jqm_active_page_url() == page_id && options.form_submission) {
// Clear any messages from the page before returning.
drupalgap_clear_messages();
return false;
}
// @TODO when drupalgap_goto() is called during the bootstrap, we shouldn't
// put the path onto the back_path array.
// Save the back path.
var back_paths_to_ignore = ['user/logout', '_reload'];
if (!in_array(drupalgap_path_get())) {
drupalgap.back_path.push(drupalgap_path_get());
}
// Set the current menu path to the path input.
drupalgap_path_set(path);
// Set the drupalgap router path.
drupalgap_router_path_set(router_path);
// If the page is already in the DOM and we're asked to reload it, then
// remove the page and let it rebuild itself. If we're not reloading the
// page and we're not in the middle of a form submission, prevent the page
// from processing then change to it.
if (drupalgap_page_in_dom(page_id)) {
// If there are any hook_menu() item options for this router path, bring
// them into the current options without overwriting any existing values.
if (drupalgap.menu_links[router_path].options) {
options = $.extend(
{},
drupalgap.menu_links[router_path].options,
options
);
}
// Reload the page? If so, remove the page from the DOM, delete the
// reloadPage option, then set the reloadingPage option to true so others
// down the line will know the page is reloading. We can't pass along the
// actual reloadPage option since it may collide with jQM later on. We
// have to use 'force' when removing the page from the DOM since DG won't
// remove it since it thinks we are already on the page, so it won't
// remove it.
if (typeof options.reloadPage !== 'undefined' && options.reloadPage) {
// @TODO we may need to leave the query ONLY if the above call to
// _drupalgap_goto_prepare_path() yielded a query string, otherwise we
// may be leaving unwanted queries in _GET()!
var leaveQuery = _drupalgap_goto_query_string ? true : false;
drupalgap_remove_page_from_dom(page_id, {
force: true,
leaveQuery: leaveQuery
});
delete options.reloadPage;
options.reloadingPage = true;
}
else if (!options.form_submission) {
drupalgap_clear_messages();
_drupalgap_goto_query_string = null;
drupalgap.page.process = false;
// @TODO changePage has been deprecated as of jQM 1.4.0 and will be
// removed in 1.50.
// @see https://api.jquerymobile.com/jQuery.mobile.changePage/
$.mobile.changePage('#' + page_id, options);
module_invoke_all('drupalgap_goto_post_process', path);
return;
}
}
else if (typeof options.reloadPage !== 'undefined' && options.reloadPage) {
// The page is not in the DOM, and we're being asked to reload it, so
// we'll just delete the reloadPage option.
delete options.reloadPage;
}
// Generate the page.
drupalgap_goto_generate_page_and_go(path, page_id, options, drupalgap.menu_links[router_path]);
}
catch (error) { console.log('drupalgap_goto - ' + error); }
}
/**
* Generate a JQM page by running it through the theme then attach the
* page to the of the document, then change to the page. Remember,
* the rendering of the page does not take place here, that is covered by
* the pagebeforechange event in theme.inc.js which happens after we change
* the page here.
* @param {String} path
* @param {String} page_id
* @param {Object} options
* @param {Object} menu_link The menu link object from drupalgap.menu_links.
*/
function drupalgap_goto_generate_page_and_go(path, page_id, options, menu_link) {
try {
// First see if the theme implements hook_page_tpl_html() and use its string for the page placeholders,
// otherwise use the file read method from DrupalGap's early days, and notify the developer to upgrade
// their theme.
var html = '';
var themeName = drupalgap.settings.theme;
var pageTplHtmlFunctionName = themeName + '_page_tpl_html';
if (function_exists(pageTplHtmlFunctionName)) { html = window[pageTplHtmlFunctionName](); }
else {
var pageTemplatePath = path_to_theme() + '/page.tpl.html';
console.log('@deprecated: ' + pageTemplatePath + ' - use ' + pageTplHtmlFunctionName + '() in ' +
themeName + '.js instead, see: http://docs.drupalgap.org/7/Themes/Create_a_Custom_Theme');
html = drupalgap_file_get_contents(pageTemplatePath);
}
// Reset the internal query string.
_drupalgap_goto_query_string = null;
// If options wasn't set, set it as an empty JSON object.
if (typeof options === 'undefined') { options = {}; }
// Add page to DOM.
drupalgap_add_page_to_dom({
page_id: page_id,
html: html,
menu_link: menu_link
});
// Setup change page options if necessary.
if (drupalgap_path_get() == path && options.form_submission) {
options.allowSamePageTransition = true;
}
// Let's change to the page. Web apps and the ripple emulator do not
// seem to like the 'index.html' prefix, so we'll remove that.
var destination = 'index.html#' + page_id;
if (
drupalgap.settings.mode != 'phonegap' ||
typeof parent.window.ripple === 'function'
) { destination = '#' + page_id; }
$.mobile.changePage(destination, options); // @see the pagebeforechange handler in page.inc.js
// Invoke all implementations of hook_drupalgap_goto_post_process().
module_invoke_all('drupalgap_goto_post_process', path);
}
catch (error) { console.log('drupalgap_goto_generate_page_and_go - ' + error); }
}
/**
* Prepares a drupalgap page path.
* @deprecated Use _drupalgap_goto_prepare_path() instead.
* @param {String} path
* @return {String}
*/
function drupalgap_goto_prepare_path(path) {
try {
console.log('WARNING - drupalgap_goto_prepare_path() is deprecated, ' +
'use _drupalgap_goto_prepare_path() instead!');
return _drupalgap_goto_prepare_path(path);
}
catch (error) { console.log('drupalgap_goto_prepare_path - ' + error); }
}
/**
* An internal function used to prepare the path for menu routing. An optional
* second parameter (boolean) may be passed in, and if it is set to true it will
* process any _GET query string parameters.
* @param {String} path
* @return {String}
*/
function _drupalgap_goto_prepare_path(path) {
try {
// Pull out any query string parameters and populate them into _GET, if we
// were instructed to do so. Hold onto a global copy of the query string
// for page go processing.
if (typeof arguments[1] !== 'undefined' && arguments[1]) {
var pos = path.indexOf('?');
if (pos != -1 && pos != path.length - 1) {
var query = path.substr(pos + 1, path.length - pos);
_drupalgap_goto_query_string = query;
path = path.substr(0, pos);
var parts = query.split('&');
for (var i = 0; i < parts.length; i++) {
pos = parts[i].indexOf('=');
if (pos == -1) { continue; }
query = parts[i].split('=');
if (query.length != 2) { continue; }
_GET(
decodeURIComponent(query[0]),
decodeURIComponent(query[1]),
path
);
}
}
}
// If the path is an empty string, change it to the front page path.
if (path == '') {
if (!drupalgap.settings.front) {
drupalgap_alert(
'drupalgap_goto_prepare_path - ' +
t('no front page specified in settings.js!')
);
return false;
}
else { path = drupalgap.settings.front; }
}
// Change 'user' to 'user/login' for anonymous users, or change it to e.g.
// 'user/123' for authenticated users.
else if (path == 'user') {
if (Drupal.user.uid != 0) { path = 'user/' + Drupal.user.uid; }
else { path = 'user/login'; }
}
// Finally return the path.
return path;
}
catch (error) { console.log('drupalgap_goto_prepare_path - ' + error); }
}
/**
* Change the page to the previous page.
*/
function drupalgap_back() {
try {
var active_page_id = $('.ui-page-active').attr('id');
if (active_page_id == drupalgap.settings.front) {
var msg = t('Exit') + ' ' + drupalgap.settings.title + '?';
if (drupalgap.settings.exit_message) {
msg = drupalgap.settings.exit_message;
}
drupalgap_confirm(msg, {
confirmCallback: _drupalgap_back_exit
});
}
else if (active_page_id == '_drupalgap_splash') { return; }
else { _drupalgap_back(); }
}
catch (error) { console.log('drupalgap_back - ' + error); }
}
/**
* The DrupalGap "Back to the Future" function goes forward to the previous page by first
* removing that page from the DOM, then going "forward" to that page using drupalgap_goto()
* with a reloadPage option set to true.
*/
function drupalgap_back_2tf() {
var back = drupalgap_back_path();
drupalgap_remove_page_from_dom(drupalgap_get_page_id(back));
drupalgap_goto(back, { reloadPage: true });
}
/**
* Returns the previous page path, or the front page if there is none.
* @returns {String}
*/
function drupalgap_back_path() {
return drupalgap.back_path.length ?
drupalgap.back_path[drupalgap.back_path.length - 1] :
drupalgap.settings.front;
}
/**
* An internal function used to change the page to the previous page.
*/
function _drupalgap_back() {
try {
// @WARNING - any changes here (except the history.back() call) need to be
// reflected into the window "navigate" handler below
drupalgap.back = true;
// Properly handle iOS9 back button clicks, and default back button clicks.
if (typeof device !== 'undefined' && device.platform === "iOS" && parseInt(device.version) === 9) {
$.mobile.back();
}
else { history.back(); }
// Update the path and router path.
var from = drupalgap_path_get();
drupalgap_path_set(drupalgap.back_path.pop());
var to = drupalgap_path_get();
drupalgap_router_path_set(drupalgap_get_menu_link_router_path(to));
module_invoke_all('drupalgap_back', from, to);
}
catch (error) { console.log('_drupalgap_back - ' + error); }
}
/**
* An internal function used to exit the app when the back button is clicked.
* @param {Number} button Which button was pressed.
*/
function _drupalgap_back_exit(button) {
try {
button === 1 ? navigator.app.exitApp() : '';
}
catch (error) { console.log('_drupalgap_back_exit - ' + error); }
}
$(window).on('navigate', function(event, data) {
// If we're already moving backwards (aka from a "soft" back button click),
// then don't do anything.
if (drupalgap.back) { return; }
// In web-app mode, clicking the back button on your browser (or Android
// device browser), does not actually fire drupalgap_back(), so we mimic
// it here, but skip the history.back() call because that's already
// happened via the hardware button.
if (drupalgap.settings.mode == 'web-app') {
// Grab the direction we're travelling.
// back, forward (or undefined, aka moving from splash to front page)
var direction = data.state.direction;
if (direction == 'back' && drupalgap.back_path.length > 0) {
// @WARNING - any changes here should be reflected into _drupalgap_back().
drupalgap.back = true;
// Update the path and router path.
var from = drupalgap_path_get();
drupalgap_path_set(drupalgap.back_path.pop());
var to = drupalgap_path_get();
drupalgap_router_path_set(drupalgap_get_menu_link_router_path(to));
module_invoke_all('drupalgap_back', from, to);
}
}
});
/**
* Execute the page callback associated with the current path and return its
* content.
* @return {Object}
*/
function menu_execute_active_handler() {
try {
// Determine the path and then grab the page id.
var path = null;
if (arguments[0]) { path = arguments[0]; }
if (!path) { path = drupalgap_path_get(); }
var page_id = drupalgap_get_page_id(path);
// @todo - Make sure the user has access to this DrupalGap menu path!
// Get the router path.
var router_path = drupalgap_router_path_get();
if (!router_path) { return; }
// Call the page call back for this router path and send along any
// arguments.
var function_name = drupalgap.menu_links[router_path].page_callback;
var page_arguments = [];
if (function_exists(function_name)) {
// Grab the page callback function and get ready to build the html.
var fn = window[function_name];
var content = '';
// Are there any arguments to send to the page callback?
if (drupalgap.menu_links[router_path].page_arguments) {
// For each page argument, if the argument is an integer, grab the
// corresponding arg(#), otherwise just push the arg onto the page
// arguments. Then try to prepare any entity that may be present in
// the url so the entity is sent via the page arguments to the page
// callback, instead of just sending the integer.
var args = arg(null, path);
for (var index in drupalgap.menu_links[router_path].page_arguments) {
if (!drupalgap.menu_links[router_path].page_arguments.hasOwnProperty(index)) { continue; }
var object = drupalgap.menu_links[router_path].page_arguments[index];
if (is_int(object) && args[object]) {
page_arguments.push(args[object]);
}
else { page_arguments.push(object); }
}
// Call the page callback function with the page arguments.
content = fn.apply(null, Array.prototype.slice.call(page_arguments));
}
else {
// There are no arguments, just return the page callback result.
content = fn();
}
// If the content came back as a string, convert it to a render object
// so any jQM page events can be attached to the content if necessary.
if (typeof content === 'string') {
content = {
content: {
markup: content
}
};
}
// Clear out any previous jQM page events, then if there are any jQM
// event callback functions attached to the menu link for this page, set
// each event up to be fired with inline JS on the page. We pass along
// any page arguments to the jQM event handler function.
drupalgap.page.jqm_events = [];
var jqm_page_events = drupalgap_jqm_page_events();
var jqm_page_event_args = null;
if (page_arguments.length > 0) {
jqm_page_event_args = JSON.stringify(page_arguments);
}
for (var i = 0; i < jqm_page_events.length; i++) {
if (drupalgap.menu_links[router_path][jqm_page_events[i]]) {
var jqm_page_event = jqm_page_events[i];
var jqm_page_event_callback =
drupalgap.menu_links[router_path][jqm_page_event];
if (function_exists(jqm_page_event_callback)) {
var options = {
'page_id': page_id,
'jqm_page_event': jqm_page_event,
'jqm_page_event_callback': jqm_page_event_callback,
'jqm_page_event_args': jqm_page_event_args
};
content[jqm_page_event] = {
markup: drupalgap_jqm_page_event_script_code(options)
};
}
else {
console.log(
'menu_execute_active_handler (' + path + ') - the jQM ' +
jqm_page_event + ' call back function ' +
jqm_page_event_callback + ' does not exist!'
);
}
}
}
// Add a pageshow handler for the page title.
if (typeof content === 'object') {
var options = {
'page_id': page_id,
'jqm_page_event': 'pageshow',
'jqm_page_event_callback': '_drupalgap_page_title_pageshow',
'jqm_page_event_args': jqm_page_event_args
};
content['drupalgap_page_title_pageshow'] = {
markup: drupalgap_jqm_page_event_script_code(options)
};
}
// And finally return the content.
return content;
}
else {
// No page call back specified.
console.log(
'menu_execute_active_handler - no page callback (' + router_path + ')'
);
console.log(JSON.stringify(drupalgap.menu_links[router_path]));
}
}
catch (error) {
console.log('menu_execute_active_handler(' + path + ') - ' + error);
}
}
/**
* Gets a router item.
* @return {Object}
*/
function menu_get_item() {
try {
var path = null;
var router_item = null;
if (arguments[0]) { path = arguments[0]; }
if (arguments[1]) { router_item = arguments[1]; }
if (path && drupalgap.menu_links[path]) {
return drupalgap.menu_links[path];
}
else { return null; }
}
catch (error) { console.log('menu_get_item - ' + error); }
}
/**
* Returns default menu item options.
* @return {Object}
*/
function menu_item_default_options() {
return { attributes: menu_item_default_attributes() };
}
/**
* Returns default menu item attributes.
* @return {Object}
*/
function menu_item_default_attributes() {
return { 'class': '' };
}
/**
* Returns an array containing the names of system-defined (default) menus.
* @return {Object}
*/
function menu_list_system_menus() {
try {
var system_menus = {
'user_menu_anonymous': {
'title': t('User menu authenticated')
},
'user_menu_authenticated': {
'title': t('User menu authenticated')
},
'main_menu': {
'title': t('Main menu')
},
'primary_local_tasks': {
'title': t('Primary Local Tasks')
}
};
// Add the menu_name to each menu as a property.
for (var menu_name in system_menus) {
if (!system_menus.hasOwnProperty(menu_name)) { continue; }
var menu = system_menus[menu_name];
menu.menu_name = menu_name;
}
return system_menus;
}
catch (error) { console.log('menu_list_system_menus - ' + error); }
}
/**
* Collects and alters the menu definitions.
*/
function menu_router_build() {
// Calls all hook_menu implementations and builds a collection of menu links.
try {
// For each module that implements hook_menu, iterate over each of the
// menu links defined by the hook, then add each menu item to
// drupalgap.menu_links keyed by the path.
var modules = module_implements('menu');
var function_name;
var fn;
var menu_links;
for (var index in modules) {
if (!modules.hasOwnProperty(index)) { continue; }
var module = modules[index];
// Determine the hook function name, grab the function, and call it
// to retrieve the hook's menu links.
function_name = module + '_menu';
fn = window[function_name];
menu_links = fn();
// Iterate over each item.
for (var path in menu_links) {
if (!menu_links.hasOwnProperty(path)) { continue; }
var menu_item = menu_links[path];
// Attach module name to item.
menu_item.module = module;
// Set a default type for the item if one isn't provided.
if (typeof menu_item.type === 'undefined') {
menu_item.type = 'MENU_NORMAL_ITEM';
}
// Set default options and attributes if none have been provided.
// @TODO - Now that we are doing this here, there may be a few
// places throughout the code that were checking for these
// undefined objects that will no longer need to be checked.
if (typeof menu_item.options === 'undefined') {
menu_item.options = menu_item_default_options();
}
else if (typeof menu_item.options.attributes === 'undefined') {
menu_item.options.attributes = menu_item_default_attributes();
}
// Make the path available as a property in the menu link.
menu_item.path = path;
// Determine any parent, sibling, and child paths for the item.
drupalgap_menu_router_build_menu_item_relationships(
path,
menu_item
);
// Attach item to menu links.
drupalgap.menu_links[path] = menu_item;
}
}
}
catch (error) { console.log('menu_router_build - ' + error); }
}
/**
* Given a menu link path, this determines and returns the router path as a
* string.
* @param {String} path
* @return {String}
*/
function drupalgap_get_menu_link_router_path(path) {
try {
// @TODO - Why is this function called twice sometimes? E.G. via an MVC item
// view item/local_users/user/0, this function gets called twice in one page
// load, that can't be good.
// @TODO - this function has a limitation in the types of menu paths it can
// handle, for example a menu path of 'collection/%/%/list' with a path of
// 'collection/local_users/user/list' can't find eachother. So we had to
// change the mvc_menu() item path to be collection/list/%/%.
// @TODO - each time this function is called, we should create a static
// record of the result router path, keyed by the incoming path, that way
// this heavy function can be called more often with less resource.
// Is this path defined in drupalgap.menu_links? If it is, use it's router
// path if it is defined, otherwise just set its router path to its own
// path.
if (drupalgap.menu_links[path]) {
if (typeof drupalgap.menu_links[path].router_path === 'undefined') {
return path;
}
else {
return drupalgap.menu_links[path].router_path;
}
}
// Let's figure out where to route this menu item, and attach the
// router to the item.
var router_path = null;
var args = arg(null, path);
// If there is an integer in the path, replace it with the wildcard
// defined via hook_menu implementation.
if (args) {
var args_size = args.length;
switch (args[0]) {
case 'comment':
case 'user':
case 'node':
if (args_size > 1 && is_int(parseInt(args[1]))) {
args[1] = '%';
router_path = args.join('/');
}
break;
case 'taxonomy':
if (args_size > 2 && (args[1] == 'vocabulary' || args[1] == 'term') &&
is_int(parseInt(args[2]))
) {
args[2] = '%';
router_path = args.join('/');
}
break;
default:
if (args_size > 1 && is_int(parseInt(args[1]))) {
args[1] = '%';
router_path = args.join('/');
}
break;
}
}
// If we haven't found a router path yet, try some other techniques to find
// it. If all else fails, just set the router path to the path itself.
if (!router_path) {
// Are there any paths in drupalgap.menu_links that would be good
// candidates as the router for this path? Let's start at the back of the
// argument list and start replacing each with a wildcard (%) and then
// see if there is a path in drupalgap.menu_links that can handle it.
if (args && args.length > 1) {
var temp_router_path;
for (var i = args.length - 1; i != -1; i--) {
temp_router_path = '';
for (var j = 0; j < args.length; j++) {
if (j < i) {
temp_router_path += args[j];
}
else {
temp_router_path += '%';
}
if (j != args.length - 1) {
temp_router_path += '/';
}
}
// If we found a router path, let's use it.
if (drupalgap.menu_links[temp_router_path]) {
router_path = temp_router_path;
break;
}
}
}
}
// If the router path is a default menu local task, inherit its parent
// router path.
if (drupalgap.menu_links[router_path] &&
drupalgap.menu_links[router_path].type == 'MENU_DEFAULT_LOCAL_TASK' &&
drupalgap.menu_links[router_path].parent) {
router_path = drupalgap.menu_links[router_path].parent;
}
// If there isn't a router and we couldn't find one, we'll just route
// to the path itself.
if (!router_path) { router_path = path; }
// Finally, return the router path.
return router_path;
}
catch (error) {
console.log('drupalgap_get_menu_link_router_path - ' + error);
}
}
/**
* Loads all of the menus specified in drupalgap.settings.menus into
* drupalgap.menus. This is called after menu_router_build(), so any system
* defined menus will already be present and should be overwritten with any
* customizations present in the settings. It then iterates over the menu links
* specified in drupalgap.menu_links and attaches any of them that have a
* menu_name to their corresponding menu in drupalgap.menus. Any menu link items
* that have a 'region' property specified will be added to
* drupalgap.theme.regions[region].
*/
function drupalgap_menus_load() {
try {
if (drupalgap.settings.menus) {
// Process each menu defined in the settings.
for (var menu_name in drupalgap.settings.menus) {
if (!drupalgap.settings.menus.hasOwnProperty(menu_name)) { continue; }
var menu = drupalgap.settings.menus[menu_name];
// If the menu does not already exist, it is a custom menu, so create
// the menu and its corresponding block.
if (!drupalgap.menus[menu_name]) {
// If the custom menu doesn't have its machine name set, set it.
if (!menu.menu_name) { menu.menu_name = menu_name; }
// Save the custom menu, as long is its name isn't 'regions',
// because that is a special "system" menu that allows menu links to
// be placed directly in regions via settings.js. Keep in mind the
// 'regions' menu is in fact NOT a system menu.
if (menu_name != 'regions') {
menu_save(menu);
// Make a block for this custom menu.
var block_delta = menu.menu_name;
drupalgap.blocks[0][block_delta] = {
name: block_delta,
delta: block_delta,
module: 'menu'
};
}
}
else {
// The menu is a system defined menu, merge it together with any
// custom settings.
$.extend(true, drupalgap.menus[menu_name], menu);
}
}
// Now that we have all of the menus loaded up, and the menu router is
// built, let's iterate over all the menu links and perform various
// operations on them.
for (var path in drupalgap.menu_links) {
if (!drupalgap.menu_links.hasOwnProperty(path)) { continue; }
var menu_link = drupalgap.menu_links[path];
// Let's grab any links from the router that have a menu specified,
// and add the link to the router.
if (menu_link.menu_name) {
if (drupalgap.menus[menu_link.menu_name]) {
// Create a links array for the menu if one doesn't exist already.
if (!drupalgap.menus[menu_link.menu_name].links) {
drupalgap.menus[menu_link.menu_name].links = [];
}
// Add the path to the menu link inside the menu.
menu_link.path = path;
// Now push the link onto the menu. We only care about the title,
// path and options, as this is just a link. The rest of the
// menu link data can be retrieved from drupalgap.menu_links.
var link =
drupalgap_menus_load_convert_menu_link_to_link_json(menu_link);
drupalgap.menus[menu_link.menu_name].links.push(link);
}
else {
console.log(
'drupalgap_menus_load - menu does not exist (' +
menu_link.menu_name + '), cannot attach link to it (' +
path + ')'
);
}
}
// If the menu link is set to a specific region, create a links array
// for the region if one doesn't exist already, then add the menu item
// to the links array as a link.
if (menu_link.region) {
if (!drupalgap.theme.regions[menu_link.region.name].links) {
drupalgap.theme.regions[menu_link.region.name].links = [];
}
drupalgap.theme.regions[menu_link.region.name].links.push(
menu_link
);
}
}
// If there are any region menu links defined in settings.js, create a
// links array for the region if one doesn't exist already, then add the
// menu item to the links array as a link.
if (typeof drupalgap.settings.menus.regions !== 'undefined') {
for (var region in drupalgap.settings.menus.regions) {
if (!drupalgap.settings.menus.regions.hasOwnProperty(region)) { continue; }
var menu = drupalgap.settings.menus.regions[region];
if (
typeof menu.links !== 'undefined' &&
$.isArray(menu.links) &&
menu.links.length > 0
) {
if (!drupalgap.theme.regions[region].links) {
drupalgap.theme.regions[region].links = [];
}
for (var index in menu.links) {
if (!menu.links.hasOwnProperty(index)) { continue; }
var link = menu.links[index];
drupalgap.theme.regions[region].links.push(link);
}
}
}
}
}
}
catch (error) { console.log('drupalgap_menus_load - ' + error); }
}
/**
* Given a menu link item from drupalgap_menus_load(), this will return a JSON
* object representing a link object compatible with theme_link(). It contains
* the link title, path and options.
* @param {Object} menu_link
* @return {Object}
*/
function drupalgap_menus_load_convert_menu_link_to_link_json(menu_link) {
try {
var link = {};
if (menu_link.title) {
// TODO - this is strange, we have to fill the 'text' value so theme_link
// will play nice. These two properties, and their usage, need a thorough
// review, only one should probably be used.
// UPDATE - the link.text property is probably no longer used, it should
// be safe to get rid of. In fact, this whole function is dumb and should
// go away.
link.title = menu_link.title;
link.text = menu_link.title;
}
if (menu_link.path) { link.path = menu_link.path; }
if (menu_link.options) { link.options = menu_link.options; }
// If it is a menu link on a region, and it has options, set the link
// options to the ones provided in the menu link region settings.
if (menu_link.region && menu_link.region.options) {
link.options = menu_link.options = menu_link.region.options;
}
return link;
}
catch (error) {
console.log(
'drupalgap_menus_load_convert_menu_link_to_link_json - ' + error
);
}
}
/**
* Given a path, and its corresponding menu item, this will determine any
* parent, sibling, and/or child menu item paths and set the references on each
* so they are all aware of eachother's paths.
* @param {String} path
* @param {Object} menu_item
*/
function drupalgap_menu_router_build_menu_item_relationships(path, menu_item) {
try {
// Split up the path arguments.
var args = arg(null, path);
// Any parent?
if (args.length > 1) {
// Set the parent path.
var parent = args.splice(0, args.length - 1).join('/');
menu_item.parent = parent;
// Make sure the parent exists.
if (drupalgap.menu_links[parent]) {
// Now tell the parent about this child. If the parent doesn't yet have
// any children, setup the children array on the parent.
if (typeof drupalgap.menu_links[parent].children === 'undefined') {
drupalgap.menu_links[parent].children = [];
}
drupalgap.menu_links[parent].children.push(path);
// Now tell any siblings about this item, and tell this item about any
// siblings.
if (typeof menu_item.siblings === 'undefined') {
menu_item.siblings = [];
}
for (var index in drupalgap.menu_links[parent].children) {
if (!drupalgap.menu_links[parent].children.hasOwnProperty(index)) { continue; }
var sibling = drupalgap.menu_links[parent].children[index];
if (sibling != path && drupalgap.menu_links[sibling]) {
if (
typeof drupalgap.menu_links[sibling].siblings === 'undefined'
) {
drupalgap.menu_links[sibling].siblings = [];
}
drupalgap.menu_links[sibling].siblings.push(path);
menu_item.siblings.push(sibling);
}
}
}
}
}
catch (error) {
console.log('drupalgap_menu_router_build_relationships - ' + error);
}
}
/**
* Show the jQueryMobile loading message.
* @see http://stackoverflow.com/a/16277865/763010
*/
function drupalgap_loading_message_show() {
try {
// Backwards compatibility for versions prior to 7.x-1.6-alpha
if (drupalgap.loading === 'undefined') { drupalgap.loading = false; }
// Return if the loading message is already shown.
if (drupalgap.loading || drupalgap_toast_is_shown()) { return; }
var options = drupalgap_loader_options();
if (arguments[0]) { options = arguments[0]; }
// Show the loading message.
//$.mobile.loading('show', options);
//drupalgap.loading = true;
setTimeout(function() {
$.mobile.loading('show', options);
drupalgap.loading = true;
}, 1);
}
catch (error) { console.log('drupalgap_loading_message_show - ' + error); }
}
/**
* Hide the jQueryMobile loading message.
*/
function drupalgap_loading_message_hide() {
try {
if (drupalgap_toast_is_shown()) { return; }
setTimeout(function() {
$.mobile.loading('hide');
drupalgap.loading = false;
drupalgap.loader = 'loading';
}, 100);
}
catch (error) { console.log('drupalgap_loading_message_hide - ' + error); }
}
/**
* Returns the jQM loader options based on the current mode and settings.js.
* @return {Object}
*/
function drupalgap_loader_options() {
try {
var mode = drupalgap.loader;
var text = t('Loading') + '...';
var textVisible = true;
if (mode == 'saving') { var text = t('Saving') + '...'; }
var options = {
text: text,
textVisible: textVisible
};
if (drupalgap.settings.loader && drupalgap.settings.loader[mode]) {
options = $.extend(true, options, drupalgap.settings.loader[mode]);
if (options.text) { options.text = t(options.text); }
}
return options;
}
catch (error) { console.log('drupalgap_loader_options - ' + error); }
}
/**
* Sets a message to display to the user. Optionally pass in a second argument
* to specify the message type: status, warning, error
* @param {String} message
*/
function drupalgap_set_message(message) {
try {
if (empty(message)) { return; }
var type = 'status';
if (arguments[1]) { type = arguments[1]; }
var msg = {
message: message,
type: type
};
drupalgap.messages.push(msg);
}
catch (error) { console.log('drupalgap_set_message - ' + error); }
}
/**
* Sets the current messages.
* @param {Array} messages
*/
function drupalgap_set_messages(messages) {
try {
drupalgap.messages = messages;
}
catch (error) { console.log('drupalgap_set_messages - ' + error); }
}
/**
* Returns the current messages.
* @return {Array}
*/
function drupalgap_get_messages() {
try {
return drupalgap.messages;
}
catch (error) { console.log('drupalgap_get_messages - ' + error); }
}
/**
* Clears the messages from the current page. Optionally pass in a page id to
* clear messages from a particular page.
*/
function drupalgap_clear_messages() {
try {
var page_id = arguments[0];
if (empty(page_id)) { page_id = drupalgap_get_page_id(); }
$('#' + page_id + ' div.messages').remove();
}
catch (error) { console.log('drupalgap_clear_messages - ' + error); }
}
/**
* Alerts a message to the user using PhoneGap's alert. It is important to
* understand this is an async function, so code will continue to execute while
* the alert is displayed to the user.
* You may optionally pass in a second argument as a JSON object with the
* following properties:
* alertCallback - the function to call after the user presses OK
* title - the title to use on the alert box, defaults to 'Alert'
* buttonName - the text to place on the button, default to 'OK'
* @param {String} message
*/
function drupalgap_alert(message) {
try {
var options = null;
if (arguments[1]) { options = arguments[1]; }
var alertCallback = function() { };
var title = t('Alert');
var buttonName = t('OK');
if (options) {
if (options.alertCallback) { alertCallback = options.alertCallback; }
if (options.title) { title = options.title; }
if (options.buttonName) { buttonName = options.buttonName; }
}
if (
drupalgap.settings.mode != 'phonegap' ||
typeof navigator.notification === 'undefined'
) {
alert(message);
alertCallback();
}
else {
navigator.notification.alert(message, alertCallback, title, buttonName);
}
}
catch (error) { console.log('drupalgap_alert - ' + error); }
}
/**
* Displays a confirmation message to the user using PhoneGap's confirm. It is
* important to understand this is an async function, so code will continue to
* execute while the confirmation is displayed to the user.
* You may optionally pass in a second argument as a JSON object with the
* following properties:
* confirmCallback - the function to call after the user presses a button, the
* button's label is passed to this function.
* title - the title to use on the alert box, defaults to 'Confirm'
* buttonLabels - the text to place on the OK, and Cancel buttons, separated
* by comma.
* @param {String} message
* @return {Boolean}
*/
function drupalgap_confirm(message) {
try {
var options = null;
if (arguments[1]) { options = arguments[1]; }
var confirmCallback = function(button) { };
var title = t('Confirm');
var buttonLabels = [t('OK'), t('Cancel')];
if (options) {
if (options.confirmCallback) {
confirmCallback = options.confirmCallback;
}
if (options.title) { title = options.title; }
if (options.buttonLabels) { buttonLabels = options.buttonLabels; }
}
// The phonegap confirm dialog doesn't seem to work in Ripple, so just use
// the default one, and it definitely doesn't work in a web app, so
// otherwise just use the default confirm.
if (
typeof parent.window.ripple === 'function' ||
drupalgap.settings.mode == 'web-app'
) {
var r = confirm(message);
if (r == true) { confirmCallback(1); } // OK button.
else { confirmCallback(2); } // Cancel button.
}
else {
navigator.notification.confirm(
message,
confirmCallback,
title,
buttonLabels
);
}
return false;
}
catch (error) { console.log('drupalgap_confirm - ' + error); }
}
/**
* Show a non intrusive alert message. You may optionally pass in an
* integer value as the second argument to specify how many milliseconds
* to wait before closing the message. Likewise, you can pass in a
* third argument to specify how long to wait before opening the
* message.
* @param {string} html - The html to display.
*/
function drupalgap_toast(html) {
try {
var open = arguments[2] ? arguments[2] : 750;
var close = arguments[1] ? arguments[1] : 1500;
setTimeout(function() {
drupalgap.toast.shown = true;
$.mobile.loading('show', {
textVisible: true,
html: html
});
var interval = setInterval(function () {
$.mobile.loading('hide');
drupalgap.toast.shown = false;
clearInterval(interval);
}, close);
}, open);
}
catch (error) {
console.log('drupalgap_toast - ' + error);
}
}
/**
* Returns true if the toast is currently shown, false otherwise.
* @returns {Boolean}
*/
function drupalgap_toast_is_shown() {
return drupalgap.toast.shown;
}
/**
* Prompt's the user to input some text.
* @param {String} msg The message to prompt about
* @param {Object} options
* {Function} onPrompt - the callback function to invoke. Sends an object in phonegap mode, or a string in web-app mode.
* {String} title - the title of the box
* {Array} buttonLabels - the button labels, separated by comma, defaults to ['Ok', 'Exit']]
* {String} defaultText - the default text to place in the box
*/
function drupalgap_prompt(msg, options) {
var onPrompt = options.onPrompt ? options.onPrompt : function() {};
var title = options.title ? options.title : drupalgap.settings.title;
var buttonLabels = options.buttonLabels ? options.buttonLabels : [t('Ok'), t('Exit')];
var defaultText = options.defaultText ? options.defaultText : '';
var done = function(result) {
if (typeof result === 'object' && result != null) { onPrompt(result); }
else {
var send = {
buttonIndex: 2,
input1: ''
};
if (result != null) {
send.buttonIndex = 1;
send.input1 = result;
}
onPrompt(send);
}
};
if (navigator && navigator.notification) {
navigator.notification.prompt(msg, done, title, buttonLabels, defaultText);
}
else { done(prompt(msg, defaultText)); }
}
/**
* This will return the query string arguments for the page. You may optionally
* pass in a key to get its value, pass in a key then a value to set the key
* equal to the value, and you may optionally pass in a third argument to use
* a specific page id, otherwise DrupalGap will automatically use the
* appropriate page id.
* @return {String|NULL}
*/
function _GET() {
try {
// Set up defaults.
var get = false;
var set = false;
var key = null;
var value = null;
// Are we setting? If so, grab the value and key to set.
if (typeof arguments[1] !== 'undefined') {
set = true;
value = arguments[1];
if (typeof arguments[0] !== 'undefined') { key = arguments[0]; }
else {
console.log('WARNING: _GET - missing key for value (' + value + ')');
return null;
}
}
// Are we getting a certain value? If so, grab the key to get.
else if (typeof arguments[0] !== 'undefined') {
get = true;
key = arguments[0];
}
// Otherwise we are getting the whole page.
else { get = true; }
// Now perform the get or set...
// Get.
if (get) {
// If a page id was provided use it, otherwise use the current page's id.
var id = null;
if (typeof arguments[2] !== 'undefined') { id = arguments[2]; }
else { id = drupalgap_get_page_id(); }
// Now that we know the page id, lets return the value if a key was
// provided, otherwise return the whole query string object for the page.
if (typeof _dg_GET[id] !== 'undefined') {
if (!key) { return _dg_GET[id]; }
else if (typeof _dg_GET[id][key] !== 'undefined') {
return _dg_GET[id][key];
}
return null;
}
}
// Set.
else if (set) {
// If we were given a path, use its page id as the property index, other
// wise we'll use the current page (which is different than the
// destination page!).
var id = null;
if (typeof arguments[2] !== 'undefined') {
id = drupalgap_get_page_id(arguments[2]);
}
else { id = drupalgap_get_page_id(); }
// If the id hasn't been instantiated, do so. Then set the key and value
// onto it.
if (typeof _dg_GET[id] === 'undefined') { _dg_GET[id] = {}; }
if (value) { _dg_GET[id][key] = value; }
}
return null;
}
catch (error) { console.log('_GET - ' + error); }
}
/**
* Each time we use drupalgap_goto to change a page, this function is called on
* the pagebeforehange event. If we're not moving backwards, or navigating to
* the same page, this will preproccesses the page, then processes it.
*/
$(document).on('pagebeforechange', function(e, data) {
try {
// If we're moving backwards, reset drupalgap.back and return.
if (drupalgap && drupalgap.back) {
drupalgap.back = false;
return;
}
// If the jqm active page url is the same as the page id of the current
// path, return.
if (
drupalgap_jqm_active_page_url() ==
drupalgap_get_page_id(drupalgap_path_get())
) { return; }
// We only want to process the page we are going to, not the page we are
// coming from. When data.toPage is a string that is our destination page.
if (typeof data.toPage === 'string') {
// If drupalgap_goto() determined that it is necessary to prevent the
// default page from reloading, then we'll skip the page
// processing and reset the prevention boolean.
if (drupalgap && !drupalgap.page.process) {
drupalgap.page.process = true;
}
else if (drupalgap) {
// Pre process, then process the page.
template_preprocess_page(drupalgap.page.variables);
template_process_page(drupalgap.page.variables);
}
}
}
catch (error) { console.log('pagebeforechange - ' + error); }
});
/**
* Implementation of template_preprocess_page().
* @param {Object} variables
*/
function template_preprocess_page(variables) {
try {
// Set up default attributes for the page's div container.
if (typeof variables.attributes === 'undefined') {
variables.attributes = {};
}
// @todo - is this needed?
// @UPDATE - this should be used, but these page attributes are ignored
// by drupalgap_add_page_to_dom()!
variables.attributes['data-role'] = 'page';
module_invoke_all('preprocess_page', variables);
// Place the variables into drupalgap.page
drupalgap.page.variables = variables;
}
catch (error) { console.log('template_preprocess_page - ' + error); }
}
/**
* Implementation of template_process_page().
* @param {Object} variables
*/
function template_process_page(variables) {
try {
var drupalgap_path = drupalgap_path_get();
// Execute the active menu handler to assemble the page output. We need to
// do this before we render the regions below.
drupalgap.output = menu_execute_active_handler();
// For each region, render it, then replace the placeholder in the page's
// html with the rendered region.
var page_id = drupalgap_get_page_id(drupalgap_path);
var page = $('#' + page_id);
var page_html = $(page).html();
if (!page_html) { return; }
for (var index in drupalgap.theme.regions) {
if (!drupalgap.theme.regions.hasOwnProperty(index)) { continue; }
var region = drupalgap.theme.regions[index];
var _region = {};
$.extend(true, _region, region);
page_html = page_html.replace(
'{:' + region.name + ':}',
drupalgap_render_region(_region)
);
}
$(page).html(page_html);
module_invoke_all('post_process_page', variables);
}
catch (error) { console.log('template_process_page - ' + error); }
}
/**
* Given a path, this will return the id for the page's div element.
* For example, a string path of 'foo/bar' would result in an id of 'foo_bar'.
* If no path is provided, it will return the current page's id.
* @param {String} path
* @return {String}
*/
function drupalgap_get_page_id(path) {
try {
if (!path) { path = drupalgap_path_get(); }
var id = path.toLowerCase().replace(/\//g, '_').replace(/-/g, '_');
return id;
}
catch (error) { console.log('drupalgap_get_page_id - ' + error); }
}
/**
* Given a page id, the theme's hook_TYPE_tpl_html() string, and the menu link object
* (all bundled in options) this takes the page template html and adds it to the
* DOM. It doesn't actually render the page, that is taken care of by the
* pagebeforechange handler.
* @param {Object} options
*/
function drupalgap_add_page_to_dom(options) {
try {
// Prepare the default page attributes, then merge in any customizations
// from the hook_menu() item, then inject the attributes into the
// placeholder. We have to manually add our default class name after the
// extend until this issue is resolved:
// https://github.com/signalpoint/DrupalGap/issues/321
var attributes = {
id: options.page_id,
'data-role': 'page'
};
attributes = $.extend(true, attributes, options.menu_link.options.attributes);
attributes['class'] += ' ' + drupalgap_page_class_get(drupalgap.router_path);
module_invoke_all('add_page_to_dom_alter', attributes, options);
options.html = options.html.replace(
/{:drupalgap_page_attributes:}/g,
drupalgap_attributes(attributes)
);
// Add the html to the page and the page id to drupalgap.pages.
$('body').append(options.html);
drupalgap.pages.push(options.page_id);
}
catch (error) { console.log('drupalgap_add_page_to_dom - ' + error); }
}
/**
* Attempts to remove given page from the DOM, will not remove the current page.
* You may force the removal by passing in a second argument as a JSON object
* with a 'force' property set to true. You may pass in a third argument to
* specify the current page, otherwise it will default to what DrupalGap thinks
* is the current page. No matter what, the current page (specified or not)
* can't be removed from the DOM, because jQM always needs one page in the DOM.
* @param {String} page_id
*/
function drupalgap_remove_page_from_dom(page_id) {
try {
var current_page_id = null;
if (typeof arguments[2] !== 'undefined') { current_page_id = arguments[2]; }
else { current_page_id = drupalgap_get_page_id(drupalgap_path_get()); }
var options = {};
if (typeof arguments[1] !== 'undefined') { options = arguments[1]; }
if (current_page_id != page_id || options.force) {
var currentPage = $('#' + current_page_id);
// Preserve and re-apply style to current page, @see https://github.com/signalpoint/DrupalGap/issues/837
var style = $(currentPage).attr('style');
$('#' + page_id).empty().remove();
if (style) { $(currentPage).attr('style', style); }
var page_index = drupalgap.pages.indexOf(page_id);
if (page_index > -1) { drupalgap.pages.splice(page_index, 1); }
// We'll remove the query string, unless we were instructed to leave it.
if (
typeof _dg_GET[page_id] !== 'undefined' &&
(typeof options.leaveQuery === 'undefined' || !options.leaveQuery)
) { delete _dg_GET[page_id]; }
// Remove any embedded view for the page.
views_embedded_view_delete(page_id);
}
else {
console.log('WARNING: drupalgap_remove_page_from_dom() - not removing ' +
'the current page (' + page_id + ') from the DOM!');
}
}
catch (error) { console.log('drupalgap_remove_page_from_dom - ' + error); }
}
/**
* Removes all pages from the DOM except the current one.
*/
function drupalgap_remove_pages_from_dom() {
try {
var current_page_id = drupalgap_get_page_id(drupalgap_path_get());
var pages = drupalgap.pages.slice(0);
for (var index in pages) {
if (!pages.hasOwnProperty(index)) { continue; }
var page_id = pages[index];
if (current_page_id != page_id) {
drupalgap_remove_page_from_dom(page_id, null, current_page_id);
}
}
// Reset drupalgap.pages to only contain the current page id.
drupalgap.pages = [current_page_id];
// Reset the drupalgap.views.ids array.
drupalgap.views.ids = [];
// Reset the jQM page events.
drupalgap.page.jqm_events = [];
// Reset the back path.
drupalgap.back_path = [];
}
catch (error) { console.log('drupalgap_remove_pages_from_dom - ' + error); }
}
/**
* Given a router path, this will return the CSS class name that can be used for
* the page container.
* @param {String} router_path The page router path.
* @return {String} A css class name.
*/
function drupalgap_page_class_get(router_path) {
try {
// Replace '/' and '%' with underscores, then trim any trailing underscores.
var class_name = router_path.replace(/[\/%]/g, '_');
while (class_name.lastIndexOf('_') == class_name.length - 1) {
class_name = class_name.substr(0, class_name.length - 1);
}
return class_name;
}
catch (error) { console.log('drupalgap_page_class_get - ' + error); }
}
/**
* Returns true if the given page id's page div already exists in the DOM.
* @param {String} page_id
* @return {Boolean}
*/
function drupalgap_page_in_dom(page_id) {
try {
var pages = $("body div[data-role$='page']");
var page_in_dom = false;
if (pages && pages.length > 0) {
for (var index in pages) {
if (!pages.hasOwnProperty(index)) { continue; }
var page = pages[index];
if (($(page).attr('id')) == page_id) {
page_in_dom = true;
break;
}
}
}
return page_in_dom;
}
catch (error) { console.log('drupalgap_page_in_dom - ' + error); }
}
/**
* Returns true if the current page is the front page, false otherwise.
* @return {Boolean}
*/
function drupalgap_is_front_page() {
try {
return drupalgap_path_get() == drupalgap.settings.front;
}
catch (error) { console.log('drupalgap_is_front_page - ' + error); }
}
/**
* Returns the URL of the active jQuery Mobile page.
* @return {String}
*/
function drupalgap_jqm_active_page_url() {
try {
// WARNING: when the app first loads, this value may be much different than
// you expect. It certainly is not the front page path, because on Android
// for example it returns '/android_asset/www/index.html'. Also, when the
// app first loads, activePage is null, so just return an empty string.
if (!$.mobile.activePage) { return ''; }
return $.mobile.activePage.data('url');
}
catch (error) { console.log('drupalgap_jqm_active_page_url - ' + error); }
}
/**
* Renders the html string of the page content that is stored in
* drupalgap.output.
* @return {String}
*/
function drupalgap_render_page() {
try {
module_invoke_all('page_build', drupalgap.output);
return drupalgap_render(drupalgap.output);
}
catch (error) { console.log('drupalgap_render_page - ' + error); }
}
/**
* Given a region, this renders it and all the blocks in it. The blocks are
* specified in the settings.js file, they are bundled under a region, which in
* turn is bundled under a theme name. Returns an empty string if it fails.
* @param {Object} region
* @return {String}
*/
function drupalgap_render_region(region) {
try {
// @TODO - this function is getting huge. Break it up into many more
// manageable functions.
// Make sure there are blocks specified for this theme in settings.js.
if (!drupalgap.settings.blocks[drupalgap.settings.theme]) {
var msg = 'WARNING: drupalgap_render_region() - there are no blocks ' +
'for the "' + drupalgap.settings.theme + '" theme in the settings.js ' +
'file!';
console.log(msg);
return '';
}
// Grab the current path.
var current_path = drupalgap_path_get();
// Let's render the region...
var region_html = '';
region_html +=
_drupalgap_region_render_zone('_prefix', region, current_path);
// Verify the region has any blocks.
var hasBlocks = drupalgap.settings.blocks[drupalgap.settings.theme][region.name];
if (hasBlocks) {
var blockCount = 0;
$.each(drupalgap.settings.blocks[drupalgap.settings.theme][region.name], function(index, block) {
if (!in_array(index, ['_prefix', '_suffix'])) {
blockCount++;
}
});
hasBlocks = blockCount;
}
// If the region has blocks specified for it in the theme in settings.js...
if (hasBlocks) {
// If a class attribute hasn't yet been provided, set a default, then
// append a system class name for the region onto its attributes array.
if (!region.attributes['class']) { region.attributes['class'] = ''; }
region.attributes['class'] += ' region_' + region.name + ' ';
// Open the region container.
region_html += '
';
// If there are any links attached to this region, render them first.
var region_link_count = 0;
var region_link_popup_count = 0;
if (region.links && region.links.length > 0) {
// Let's first iterate over all of the region links and keep counts of
// any links that use the ui-btn-left and ui-btn-right class attribute.
// This will allow us to properly wrap region links in a control group.
var ui_btn_left_count = 0;
var ui_btn_right_count = 0;
for (var index in region.links) {
if (!region.links.hasOwnProperty(index)) { continue; }
var link = region.links[index];
var data = menu_region_link_get_data(link);
if (!drupalgap_check_visibility('region', data)) { continue; }
region_link_count++;
var css_class = drupalgap_link_get_class(link);
if (css_class) {
var side = menu_region_link_get_side(css_class);
if (side == 'left') { ui_btn_left_count++; }
else if (side == 'right') { ui_btn_right_count++; }
}
}
// We need to separately render each side of the header (left, right).
// That allows us to properly wrap the links with a control group if
// it is needed.
var region_link_html = '';
var ui_btn_left_html = '';
var ui_btn_right_html = '';
for (var i = 0; i < region.links.length; i++) {
// Grab the link and its data.
var region_link = region.links[i];
var data = menu_region_link_get_data(region_link);
// Check link's region visiblity settings. Links will not be rendered
// on certain system pages.
// @TODO - this additional call to drupalgap_check_visibility() here
// may be expensive, consider setting aside the results from the call
// above, and using them here.
if (drupalgap_check_visibility('region', data)) {
// Don't render the link on certain system pages.
if (in_array(current_path, ['offline', 'error', 'user/logout'])) {
continue;
}
// If this is a popup region link, set the jQM attributes to make
// this link function as a popup (dropdown) menu. Set the default
// link icon, if it isn't set.
var link_text = region_link.title;
var link_path = region_link.path;
if (data.options.popup) {
region_link_popup_count++;
// If the link text isn't set, and the data icon pos isn't set,
// set it the data icon pos so the button and icon are rendered
// properly.
if (
(!link_text || empty(link_text)) &&
typeof data.options.attributes['data-iconpos'] === 'undefined'
) { data.options.attributes['data-iconpos'] = 'notext'; }
// If data-rel, data-icon, data-role aren't set, set them.
if (
typeof data.options.attributes['data-rel'] === 'undefined'
) { data.options.attributes['data-rel'] = 'popup'; }
if (
typeof data.options.attributes['data-icon'] === 'undefined'
) { data.options.attributes['data-icon'] = 'bars'; }
if (
typeof data.options.attributes['data-role'] === 'undefined'
) { data.options.attributes['data-role'] = 'button'; }
// Popup menus need a dynamic href value on the link, so we
// always overwrite it.
link_path = null;
data.options.attributes['href'] =
'#' + menu_container_id(data.options.popup_delta);
}
else {
// Set the data-role to a button, if one isn't already set.
if (typeof data.options.attributes['data-role'] === 'undefined') {
data.options.attributes['data-role'] = 'button';
}
}
// If it has notext for the icon position, force the text to be
// an nbsp.
if (data.options.attributes['data-iconpos'] == 'notext') {
link_text = ' ';
}
// Render the link on the proper side.
var css_class = drupalgap_link_get_class(region_link);
var side = menu_region_link_get_side(css_class);
var link_html = l(link_text, link_path, data.options);
if (side == 'left') { ui_btn_left_html += link_html; }
else if (side == 'right') { ui_btn_right_html += link_html; }
}
}
// If there was more than one link on a side, wrap it in a control
// group, and remove the ui-btn class from the links.
if (ui_btn_left_count > 1) {
var attrs = {
'data-type': 'horizontal',
'data-role': 'controlgroup',
'class': 'ui-btn-left'
};
ui_btn_left_html = '
';
}
// Finally render the ui sides on the region.
region_html += ui_btn_left_html + ui_btn_right_html;
}
// Render each block in the region. Determine how many visible blocks are
// in the region.
var block_counts = {
block_count: 0,
block_menu_count: 0
};
var blocks = drupalgap.settings.blocks[drupalgap.settings.theme][region.name];
for (var block_delta in blocks) {
if (!blocks.hasOwnProperty(block_delta)) { continue; }
var block_settings = blocks[block_delta];
// Ignore region _prefix and _suffix.
if (block_delta == '_prefix' || block_delta == '_suffix') { continue; }
// Render the block.
region_html += drupalgap_block_render(
region,
current_path,
block_delta,
block_settings,
block_counts
);
}
// If this was a header or footer, and there were only region links
// rendered, place an empty header in the region.
if (
in_array(region.attributes['data-role'], ['header', 'footer']) &&
(
block_counts.block_count == 0 && region_link_count > 0 ||
block_counts.block_count - block_counts.block_menu_count == 0
) ||
(
region_link_count > 0 &&
region_link_popup_count >= block_counts.block_menu_count &&
block_counts.block_count == 0
)
) {
// Show an empty header if we're not collapsing on an empty region.
if (
typeof region.collapse_on_empty === 'undefined' ||
region.collapse_on_empty === false
) { region_html += '
'; }
}
// Close the region container.
region_html += '
';
}
region_html +=
_drupalgap_region_render_zone('_suffix', region, current_path);
return region_html;
}
catch (error) { console.log('drupalgap_render_region - ' + error); }
}
/**
* Renders the given zone (_prefix, _suffix) if any for a region.
* @param {String} zone
* @param {Object} region
* @param {String} current_path
* @return {String}
*/
function _drupalgap_region_render_zone(zone, region, current_path) {
try {
var html = '';
var theme_name = drupalgap.settings.theme;
if (typeof drupalgap.settings.blocks[theme_name][region.name] ===
'undefined'
) { return html; }
var region_settings =
drupalgap.settings.blocks[theme_name][region.name];
if (typeof region_settings[zone] === 'undefined') { return html; }
var blocks = region_settings[zone];
for (var block_delta in blocks) {
if (!blocks.hasOwnProperty(block_delta)) { continue; }
var block_settings = blocks[block_delta];
html += drupalgap_block_render(
region,
current_path,
block_delta,
block_settings
);
}
return html;
}
catch (error) { console.log('_drupalgap_region_render_zone - ' + error); }
}
/**
* Given a key (typically a block delta), this will generate a unique ID that
* can be used for the panel. It will be fused with the current page id.
* @param {String} key
* @return {String}
*/
function drupalgap_panel_id(key) {
try {
return key + '_' + drupalgap_get_page_id();
}
catch (error) { console.log('drupalgap_panel_id - ' + error); }
}
function drupalgap_render(content) {
var output_type = $.type(content);
var html = '';
// If the output came back as a string, we can render it as is. If the
// output came back as on object, render each element in it through the
// theme system.
if (output_type === 'string') { html = content; }
else if (output_type === 'object') {
// The page came back as a render object. Let's define the names of
// variables that are reserved for theme processing.
var render_variables = ['theme', 'view_mode', 'language'];
if (content.markup) { return content.markup; }
if (content.theme && !drupalgap.theme_registry[content.theme]) {
return theme(content.theme, content);
}
// Is there a theme value specified in the content and the registry?
if (content.theme && drupalgap.theme_registry[content.theme]) {
var themeName = drupalgap.settings.theme;
var tplHtmlFunctionName = themeName + '_' + content.theme + '_tpl_html';
template_file_html = '';
if (function_exists(tplHtmlFunctionName)) { template_file_html = window[tplHtmlFunctionName](); }
else {
var template = drupalgap.theme_registry[content.theme];
var template_file_name = content.theme.replace(/_/g, '-') + '.tpl.html';
var template_file_path = template.path + '/' + template_file_name;
console.log('@deprecated: ' + template_file_path + ' - use ' + tplHtmlFunctionName + '() in ' +
themeName + '.js instead, see: http://docs.drupalgap.org/7/Themes/Create_a_Custom_Theme');
template_file_html = drupalgap_file_get_contents(template_file_path);
}
// What variable placeholders are present in the template file?
var placeholders = drupalgap_get_placeholders_from_html(template_file_html);
if (placeholders) {
// Replace each placeholder with html.
for (var index in placeholders) {
if (!placeholders.hasOwnProperty(index)) { continue; }
var placeholder = placeholders[index];
var _html = '';
if (content[placeholder]) {
// Grab the element variable from the content.
var element = content[placeholder];
// If it is markup, render it as is, if it is themeable, then theme it.
if (content[placeholder].markup) {
_html = content[placeholder].markup;
}
else if (content[placeholder].theme) {
_html = theme(content[placeholder].theme, element);
}
// Now remove the variable from the content.
delete content[placeholder];
}
// Now replace the placeholder with the html, even if it was empty.
template_file_html = template_file_html.replace('{:' + placeholder + ':}', _html);
}
}
// Finally add the rendered template file to the html.
html += template_file_html;
}
else {
var weighted = {};
var weightedCount = 0;
for (var index in content) {
if (!content.hasOwnProperty(index)) { continue; }
var piece = content[index];
var _type = typeof piece;
if (_type === 'object' && piece !== null) {
if (piece.theme && drupalgap.theme_registry[piece.theme]) { continue; }
var weight = typeof piece.weight !== 'undefined' ? piece.weight : 0;
if (typeof weighted[weight] === 'undefined') { weighted[weight] = []; }
weighted[weight].push(drupalgap_render(piece));
weightedCount++;
}
else if (_type === 'array') {
for (var i = 0; i < piece.length; i++) {
html += drupalgap_render(piece[i]);
}
}
else if (_type === 'string') { html += piece; }
}
if (weightedCount) {
for (var weight in weighted) {
if (!weighted.hasOwnProperty(weight)) { continue; }
for (var i = 0; i < weighted[weight].length; i++) {
html += weighted[weight][i];
}
}
}
}
}
// Now that we are done assembling the content into an html string, we can
// return it.
return html;
}
/**
* Returns the path to the current DrupalGap theme, false otherwise.
* @return {String|Boolean}
*/
function path_to_theme() {
try {
if (drupalgap.theme_path) {
return drupalgap.theme_path;
}
else {
console.log('path_to_theme - drupalgap.theme_path is not set!');
return false;
}
}
catch (error) { console.log('path_to_theme - ' + error); }
}
/**
* Implementation of theme().
* @param {String} hook
* @param {Object} variables
* @return {String}
*/
function theme(hook, variables) {
try {
// If there is HTML markup present, just return it as is. Otherwise, run
// the theme hook and send along the variables.
if (!variables) { variables = {}; }
if (typeof variables.access !== 'undefined' && !variables.access) { return ''; }
if (variables.markup) { return variables.markup; }
var content = '';
if (!hook) { return content; }
// First see if the current theme implements the hook, if it does use it, if
// it doesn't fallback to the core theme implementation of the hook.
var theme_function = drupalgap.settings.theme + '_' + hook;
if (!function_exists(theme_function)) {
theme_function = 'theme_' + hook;
// Fail safely an informative message if a bogus hook was passed in.
if (!function_exists(theme_function)) {
var caller = null;
if (arguments.callee.caller) { caller = arguments.callee.caller.name; }
var msg = 'WARNING: ' + theme_function + '() does not exist.';
if (caller) { msg += ' Called by: ' + caller + '().' }
console.log(msg);
return content;
}
}
// If no attributes are coming in, look to variables.options.attributes
// as a secondary option, otherwise setup an empty JSON object for them.
if (
typeof variables.attributes === 'undefined' ||
!variables.attributes
) {
if (variables.options && variables.options.attributes) {
variables.attributes = variables.options.attributes;
}
else {
variables.attributes = {};
}
}
// If there is no class name, set an empty one.
if (!variables.attributes['class']) {
variables.attributes['class'] = '';
}
var fn = window[theme_function];
content = fn.call(null, variables);
return content;
}
catch (error) { console.log('theme - ' + error); }
}
/**
* Themes a button.
* @param {Object} variables
* @return {String}
*/
function theme_button(variables) {
try {
variables.attributes['data-role'] = 'button';
var html = '' +
variables.text +
'';
return html;
}
catch (error) { console.log('theme_button_link - ' + error); }
}
/**
* Themes a button link.
* @param {Object} variables
* @return {String}
*/
function theme_button_link(variables) {
try {
variables.attributes['data-role'] = 'button';
return theme_link(variables);
}
catch (error) { console.log('theme_button_link - ' + error); }
}
/**
* Themes a collapsible widget.
* @param {Object} variables
* @return {String}
*/
function theme_collapsible(variables) {
try {
variables.attributes['data-role'] = 'collapsible';
var header_type = 'h2';
if (variables.header_type) { header_type = variables.header_type; }
var header_attributes = {};
if (variables.header_attributes) {
header_attributes = variables.header_attributes;
}
var html = '
';
return html;
}
catch (error) { console.log('theme_collapsible - ' + error); }
}
/**
* Themes a collapsibleset widget.
* @param {Object} variables
* @return {String}
*/
function theme_collapsibleset(variables) {
try {
variables.attributes['data-role'] = 'collapsible-set';
var html = '
';
for (var index in variables.items) {
if (!variables.items.hasOwnProperty(index)) { continue; }
var item = variables.items[index];
html += theme('collapsible', item);
}
html += '
';
return html;
}
catch (error) { console.log('theme_collapsibleset - ' + error); }
}
/**
* Themes a controlgroup widget.
* @param {Object} variables
* @return {String}
*/
function theme_controlgroup(variables) {
try {
variables.attributes['data-role'] = 'controlgroup';
var html = '
';
for (var index in variables.items) {
if (!variables.items.hasOwnProperty(index)) { continue; }
var item = variables.items[index];
html += item;
}
html += '
';
return html;
}
catch (error) { console.log('theme_header - ' + error); }
}
/**
* Implementation of theme_image().
* @param {Object} variables
* @return {String}
*/
function theme_image(variables) {
try {
// Turn the path, alt and title into attributes if they are present.
if (variables.path) { variables.attributes.src = variables.path; }
if (variables.alt) { variables.attributes.alt = variables.alt; }
if (variables.title) { variables.attributes.title = variables.title; }
// Render the image.
return '';
}
catch (error) { console.log('theme_image - ' + error); }
}
/**
* Implementation of theme_audio().
* @param {Object} variables
* @return {String}
*/
function theme_audio(variables) {
try {
// Turn the path, alt and title into attributes if they are present.
if (variables.path) { variables.attributes.src = variables.path; }
if (variables.alt) { variables.attributes.alt = variables.alt; }
if (variables.title) { variables.attributes.title = variables.title; }
// Render the audio player.
return '';
}
catch (error) { console.log('theme_audio - ' + error); }
}
/**
* Implementation of theme_video().
* @param {Object} variables
* @return {String}
*/
function theme_video(variables) {
try {
// Turn the path, alt and title into attributes if they are present.
if (variables.path) { variables.attributes.src = variables.path; }
if (variables.alt) { variables.attributes.alt = variables.alt; }
if (variables.title) { variables.attributes.title = variables.title; }
// Add the 'webkit-playsinline' attribute on iOS devices if no one made a
// decision about it being there or not.
if (
typeof device !== 'undefined' &&
device.platform == 'iOS' &&
typeof variables.attributes['webkit-playsinline'] === 'undefined'
) { variables.attributes['webkit-playsinline'] = ''; }
// Render the video player.
return '';
}
catch (error) { console.log('theme_video - ' + error); }
}
/**
* Implementation of theme_image_style().
* @param {Object} variables
* @return {String}
*/
function theme_image_style(variables) {
try {
variables.path = image_style_url(variables.style_name, variables.path);
return theme_image(variables);
}
catch (error) { console.log('theme_image - ' + error); }
}
/**
* Theme's an item from an MVC collection.
* @param {Object} variables
* @return {String}
*/
function theme_item(variables) {
try {
var html = '';
for (var field in variables.item) {
if (!variables.item.hasOwnProperty(field)) { continue; }
var value = variables.item[field];
html +=
'
' + variables.model.fields[field].title + '
' +
'
' + value + '
';
}
return html;
}
catch (error) { console.log('theme_item - ' + error); }
}
/**
* Implementation of theme_item_list().
* @param {Object} variables
* @return {String}
*/
function theme_item_list(variables) {
try {
// We'll theme an empty list unordered list by default, if there is a type
// of list specified we'll use that, and if there are some items we'll
// theme them too.
var type = 'ul';
if (variables.type) { type = variables.type; }
var html = '';
if (variables.title) { html += '
' + variables.title + '
'; }
html += '<' + type + ' ' +
drupalgap_attributes(variables.attributes) + '>';
if (variables.items && variables.items.length > 0) {
var listview = typeof variables.attributes['data-role'] !== 'undefined' &&
variables.attributes['data-role'] == 'listview';
for (var index = 0; index < variables.items.length; index++) {
var item = variables.items[index];
if (typeof item === 'string') {
var icon = null;
html += '
' + item + '
';
}
else if (typeof item === 'object') {
var attributes = item.attributes ? item.attributes : {};
var content = item.content ? item.content : '';
html += '
' + drupalgap_render(content) + '
';
}
}
}
html += '' + type + '>';
return html;
}
catch (error) { console.log('theme_item_list - ' + error); }
}
/**
* Identical to theme_item_list, except this turns the list into a jQM listview.
* @param {Object} variables
* @return {String}
*/
function theme_jqm_item_list(variables) {
try {
if (variables.attributes) {
if (
variables.attributes['data-role'] &&
variables.attributes['data-role'] != 'listview'
) { }
else {
variables.attributes['data-role'] = 'listview';
}
}
else {
variables.attributes['data-role'] = 'listview';
}
return theme_item_list(variables);
}
catch (error) { console.log('theme_jqm_item_list - ' + error); }
}
/**
* Implementation of theme_link().
* @param {Object} variables
* @return {String}
*/
function theme_link(variables) {
try {
var text = '';
if (variables.text) { text = variables.text; }
if (typeof variables.path !== 'undefined' && variables.path != null) {
// If the path begins with a hashtag, just render the link as is with the
// hashtag for the href.
if (variables.path.indexOf('#') == 0) {
variables.attributes['href'] = variables.path;
return '' +
text +
'';
}
// By default our onclick will use a drupalgap_goto(). If we have any
// incoming link options, then modify the link accordingly.
var onclick = 'drupalgap_goto(\'' + variables.path + '\');';
if (variables.options) {
// Use an InAppBrowser?
if (variables.options.InAppBrowser) {
onclick =
"window.open('" + variables.path + "', '_blank', 'location=yes');";
}
else {
// Prepare the path.
variables.path = _drupalgap_goto_prepare_path(variables.path);
// All other options need to be extracted into a JSON string for the
// onclick handler.
var goto_options = '';
for (var option in variables.options) {
if (!variables.options.hasOwnProperty(option)) { continue; }
var value = variables.options[option];
if (option == 'attributes') { continue; }
if (typeof value === 'string') { value = "'" + value + "'"; }
goto_options += option + ':' + value + ',';
}
onclick =
'drupalgap_goto(\'' +
variables.path + '\', ' +
'{' + goto_options + '});';
}
}
// Is this link active?
if (variables.path == drupalgap_path_get()) {
if (variables.attributes['class'].indexOf('ui-btn-active') == -1) {
variables.attributes['class'] += ' ui-btn-active ';
}
if (variables.attributes['class'].indexOf('ui-state-persist') == -1) {
variables.attributes['class'] += ' ui-state-persist ';
}
}
// Finally, return the link.
return '' + text + '';
}
else {
// The link has no path, so just render the text and attributes.
if (typeof variables.attributes.href === 'undefined') {
variables.attributes.href = '#';
}
return '' +
text +
'';
}
}
catch (error) { console.log('theme_link - ' + error); }
}
/**
* Themes the logout button.
* @param {Object} variables
* @return {String}
*/
function theme_logout(variables) {
try {
return bl(
t('Logout'),
'user/logout',
{
attributes: {
'data-icon': 'action',
'data-iconpos': 'right'
}
}
);
}
catch (error) { console.log('theme_logout - ' + error); }
}
/**
* Themes a popup widget.
* @param {Object} variables
* @return {String}
*/
function theme_popup(variables) {
try {
variables.attributes['data-role'] = 'popup';
var button_attributes = {};
if (variables.button_attributes) {
button_attributes = variables.button_attributes;
}
button_attributes.href = '#' + variables.attributes.id;
button_attributes['data-rel'] = 'popup';
var html = bl(variables.button_text, null, {
attributes: button_attributes
}) +
'
';
for (var index in variables.header) {
if (!variables.header.hasOwnProperty(index)) { continue; }
var column = variables.header[index];
if (column.data) {
html += '
' + column.data + '
';
}
}
html += '
';
}
html += '';
if (variables.rows) {
for (var row_index in variables.rows) {
if (!variables.rows.hasOwnProperty(row_index)) { continue; }
var row = variables.rows[row_index];
html += '
';
if (row) {
for (var column_index in row) {
if (!row.hasOwnProperty(column_index)) { continue; }
var column = row[column_index];
html += '
' + column + '
';
}
}
html += '
';
}
}
return html + '
';
}
catch (error) { console.log('theme_table - ' + error); }
}
/**
* Theme a jQueryMobile table.
* @param {Object} variables
* @return {String}
*/
function theme_jqm_table(variables) {
try {
variables.attributes['data-role'] = 'table';
variables.attributes['data-mode'] = 'reflow';
return theme_table(variables);
}
catch (error) { console.log('theme_jqm_table - ' + error); }
}
/**
* An interal callback used to handle the setting of the page title during the
* pageshow event.
* @param {*} page_arguments
*/
function _drupalgap_page_title_pageshow(page_arguments) {
try {
var router_path = drupalgap_router_path_get();
// Set the page title. First we'll see if the hook_menu() item has a
// title variable set, then check for a title_callback function.
var title_arguments = [];
if (typeof drupalgap.menu_links[router_path].title !== 'undefined') {
drupalgap_set_title(drupalgap.menu_links[router_path].title);
}
if (
typeof drupalgap.menu_links[router_path].title_callback !== 'undefined'
) {
var function_name = drupalgap.menu_links[router_path].title_callback;
if (function_exists(function_name)) {
// Grab the title callback function.
var fn = window[function_name];
// Place the internal success callback handler on the front of the
// title arguments.
title_arguments.unshift(_drupalgap_page_title_pageshow_success);
// Are there any additional arguments to send to the title callback?
if (drupalgap.menu_links[router_path].title_arguments) {
// For each title argument, if the argument is an integer, grab the
// corresponding arg(#), otherwise just push the arg onto the title
// arguments.
var args = arg(null, drupalgap_path_get());
var _title_arguments = drupalgap.menu_links[router_path].title_arguments;
for (var index in _title_arguments) {
if (!_title_arguments.hasOwnProperty(index)) { continue; }
var object = _title_arguments[index];
if (is_int(object) && args[object]) {
title_arguments.push(args[object]);
}
else { title_arguments.push(object); }
}
}
// Call the title callback function with the title arguments.
drupalgap_set_title(
fn.apply(
null,
Array.prototype.slice.call(title_arguments)
)
);
}
}
else {
_drupalgap_page_title_pageshow_success(drupalgap_get_title());
}
}
catch (error) { console.log('_drupalgap_page_title_pageshow - ' + error); }
}
/**
* An internal function used to set the page title when the page title callback
* function is successful.
* @param {String} title
*/
function _drupalgap_page_title_pageshow_success(title) {
try {
var id = system_title_block_id(drupalgap_path_get());
$('h1#' + id).html(title);
}
catch (error) {
console.log('_drupalgap_page_title_pageshow_success - ' + error);
}
}
function theme_jqm_grid(variables) {
if (!variables.items || !variables.items.length) { return ''; }
var html = '';
// Determine columns, and jqm grid type and css class..
var columns = jqm_grid_verify_columns(variables);
var grid = jqm_grid_get_type(columns);
variables.attributes.class += ' ui-grid-' + grid + ' ';
var open = '
';
var close = '
';
var open_row = '
'; // This class will be replaced dynamically.
var close_row = '
';
html += open;
for (var i = 0; i < variables.items.length; i++) {
var className = jqm_grid_get_item_class(i, columns);
var openRow = jqm_grid_set_item_row_class(open_row, className);
html += openRow + variables.items[i] + close_row;
}
html += close;
return html;
}
function jqm_grid_verify_columns(variables) {
var columns = variables.columns ? variables.columns : 2;
var msg = null;
if (columns < 2) {
msg = columns + ' columns is not enough, a minimum of 2 is needed';
columns = 2;
}
if (columns > 5) {
msg = columns + ' columns is too many enough, a maximum of 5 is allowed';
columns = 5;
}
variables.columns = columns;
if (msg) { console.log('jqm_grid_verify_columns - ' + msg); }
return variables.columns;
}
function jqm_grid_get_type(columns) {
var grid = null;
switch (columns) {
case 2: grid = 'a'; break;
case 3: grid = 'b'; break;
case 4: grid = 'c'; break;
case 5: grid = 'd'; break;
}
return grid;
}
function jqm_grid_get_item_class(index, columns) {
var className = null;
switch (index % columns) {
case 0: className = 'ui-block-a'; break;
case 1: className = 'ui-block-b'; break;
case 2: className = 'ui-block-c'; break;
case 3: className = 'ui-block-d'; break;
case 4: className = 'ui-block-e'; break;
}
return className
}
function jqm_grid_set_item_row_class(open_row, className) {
var openRow = JSON.parse(JSON.stringify(open_row)); // Make a copy of the string.
if (className) { openRow = openRow.replace('ui-block', className); }
return openRow;
}
/**
* Given a form element, this will return true if access to the element is
* permitted, false otherwise.
* @param {Object} element
* @return {Boolean}
*/
function drupalgap_form_element_access(element) {
try {
var access = true;
if (element.access == false) { access = false; }
return access;
}
catch (error) { console.log('drupalgap_form_element_access - ' + error); }
}
/**
* Given a form element type, this will return the name of the module that
* implements the hook_field_widget_form() for the element. Keep in mind for now
* some of the module names don't exist, and are actually implemented inside
* the field module. If no module is found, it returns false.
* @param {String} type
* @return {String}
*/
function drupalgap_form_element_get_module_name(type) {
try {
var module = false;
switch (type) {
case 'checkbox':
case 'radios':
case 'select':
module = 'options';
break;
case 'image':
module = 'image';
break;
}
return module;
}
catch (error) {
console.log('drupalgap_form_element_get_module_name - ' + error);
}
}
/**
* Given a form element name and the form_id, this generates an html id
* attribute value to be used in the DOM. An optional third argument is a
* string language code to use. An optional fourth argument is an integer delta
* value to use on field elements.
* @param {String} name
* @param {String} form_id
* @return {String}
*/
function drupalgap_form_get_element_id(name, form_id) {
try {
if (name == null || name == '') { return ''; }
var id =
'edit-' +
form_id.toLowerCase().replace(/_/g, '-') + '-' +
name.toLowerCase().replace(/_/g, '-');
// Any language code to append to the id?
if (arguments[2]) { id += '-' + arguments[2]; }
// Any delta value to append to the id?
if (typeof arguments[3] !== 'undefined') {
id += '-' + arguments[3] + '-value';
}
return id;
}
catch (error) { console.log('drupalgap_form_get_element_id - ' + error); }
}
/**
* Given an element name, this will return the class name to use on the
* element's container.
* @param {String} name
* @return {String}
*/
function drupalgap_form_get_element_container_class(name) {
try {
return 'form-item field-name-' + name.replace(/_/g, '-');
}
catch (error) {
console.log('drupalgap_form_get_element_container_class - ' + error);
}
}
/**
* Renders all the input elements in a form.
* @param {Object} form
* @return {String}
*/
function _drupalgap_form_render_elements(form) {
try {
var content = '';
var content_sorted = '';
var content_weighted = [];
// For each form element, if the element objects name property isn't set,
// set it, then render the element if access is permitted. While rendering
// the elements, set them aside according to their widget weight so they
// can be appended to the content string in the correct order later.
for (var name in form.elements) {
if (!form.elements.hasOwnProperty(name)) { continue; }
var element = form.elements[name];
if (!element.name) { element.name = name; }
if (drupalgap_form_element_access(element)) {
if (
element.is_field &&
typeof element.field_info_instance.widget.weight !== 'undefined'
) {
var weight = element.field_info_instance.widget.weight;
while (typeof content_weighted[weight] !== 'undefined') { weight += .1; }
content_weighted['' + weight] = _drupalgap_form_render_element(form, element);
}
else {
// Extract the bundle. Note, on comments the bundle is prefixed with
// 'comment_node_' so we need to remove that to correctly map to the
// potential extra fields data.
var bundle = null;
if (form.bundle) {
bundle = form.bundle;
if (
form.entity_type == 'comment' &&
form.bundle.indexOf('comment_node_') != -1
) { bundle = form.bundle.replace('comment_node_', ''); }
}
// This is not a field, if it has it's own weight use it, or see if
// there is a weight in field_info_extra_fields, otherwise just
// append it to the element content.
// Elements with a weight defined.
if (typeof element.weight !== 'undefined') {
if (content_weighted[element.weight]) {
var msg = 'WARNING: _drupalgap_form_render_elements - the ' +
'weight of ' + element.weight + ' for ' + element.name +
' is already in use by ' +
content_weighted[element.weight].name;
console.log(msg);
// Just render it.
var _content = _drupalgap_form_render_element(form, element);
if (typeof _content !== 'undefined') { content += _content; }
}
else {
content_weighted[element.weight] =
_drupalgap_form_render_element(form, element);
}
}
// Extra fields.
else if (
form.entity_type && bundle &&
typeof drupalgap.field_info_extra_fields[bundle][name] !==
'undefined' &&
typeof
drupalgap.field_info_extra_fields[bundle][name].weight !==
'undefined'
) {
var weight =
drupalgap.field_info_extra_fields[bundle][name].weight;
if (content_weighted[weight]) {
var msg = 'WARNING: _drupalgap_form_render_elements - the ' +
'weight of ' + weight + ' for ' + element.name + ' is ' +
'already in use by ' + content_weighted[weight].name;
console.log(msg);
// Just render it.
var _content = _drupalgap_form_render_element(form, element);
if (typeof _content !== 'undefined') { content += _content; }
}
else {
content_weighted[weight] =
_drupalgap_form_render_element(form, element);
}
}
// No weight, just render it.
else {
var _content = _drupalgap_form_render_element(form, element);
if (typeof _content !== 'undefined') { content += _content; }
}
}
}
}
// Prepend the weighted elements to the content.
if (!empty(content_weighted)) {
for (var weight in content_weighted) {
content_sorted += content_weighted[weight] + '\n';
}
// Attach sorted content.
content = content_sorted + '\n' + content;
}
// Add any form buttons to the form elements html, if access to the button
// is permitted.
if (form.buttons && form.buttons.length != 0) {
for (var name in form.buttons) {
if (!form.buttons.hasOwnProperty(name)) { continue; }
var button = form.buttons[name];
if (drupalgap_form_element_access(button)) {
var attributes = {
type: 'button',
id: drupalgap_form_get_element_id(name, form.id)
};
if (button.attributes) { $.extend(attributes, button.attributes); }
content += '';
}
}
}
return content;
}
catch (error) { console.log('_drupalgap_form_render_elements - ' + error); }
}
/**
* Renders an input element for a form.
* @param {Object} form
* @param {Object} element
* @return {String}
*/
function _drupalgap_form_render_element(form, element) {
try {
var html = '';
if (!element) { return html; }
// Extract the element name.
var name = element.name;
// Grab the language.
var language = language_default();
// We'll assume the element has no items (e.g. title, nid, vid, etc), unless
// we determine later that this element is a field, then it'll have items.
var items = false;
// If this element is a field, extract the items from the language code and
// determine what module and hook will handle the items. If the element is
// not a field, just flatten it into a single item collection and determine
// which module handles this element type. Keep in mind not all the modules
// actually exist, and we've placed implementations into the field module.
var module = false;
var field_widget_form_function_name = false;
var field_widget_form_function = false;
if (element.is_field) {
items = element[language];
module = element.field_info_instance.widget.module;
}
else {
items = {0: element};
module = drupalgap_form_element_get_module_name(element.type);
}
if (module) {
field_widget_form_function_name = module + '_field_widget_form';
if (function_exists(field_widget_form_function_name)) {
field_widget_form_function = window[field_widget_form_function_name];
}
else {
console.log(
'WARNING: _drupalgap_form_render_element() - ' +
field_widget_form_function_name +
'() does not exist for the "' + element.type + '" form element!'
);
}
}
// If there were no items, just return.
if (!items || items.length == 0) { return html; }
// Generate default variables.
var variables = {
attributes: {}
};
// Grab the info instance and info field for the field, then attach them
// both to the variables object so all theme functions will have access
// to that data.
variables.field_info_field = element.field_info_field;
variables.field_info_instance = element.field_info_instance;
// Render the element item(s). Remember the final delta value for later.
var delta = 0;
var item_html = '';
var item_label = '';
var render_item = null;
for (var delta in items) {
if (!items.hasOwnProperty(delta)) { continue; }
var item = items[delta];
// We'll render the item, unless we prove otherwise.
render_item = true;
// Overwrite the variable's attributes id with the item's id.
variables.attributes.id = item.id;
// Attach the item as the element onto variables.
variables.element = item;
// Create an array for the item's children if it doesn't exist already.
// This is used by field widget forms to extend form elements.
if (!items[delta].children) { items[delta].children = []; }
// Generate the label for field items on delta zero only. Keep in mind
// rendered labels, with an element title_placeholder set to true,
// will not be appended to the result html later.
if (element.is_field && delta == 0) {
item.title = element.title;
item_label = theme('form_element_label', {'element': item});
}
// If the element's title is set to be a placeholder, set the
// placeholder attribute equal to the title on the current item, unless
// someone already set it. If it is a required element, mark it as such.
if (
delta == 0 && typeof element.title_placeholder !== 'undefined' &&
element.title_placeholder &&
typeof variables.attributes['placeholder'] === 'undefined'
) {
var placeholder = element.title;
// @TODO show a better required marker for placeholders.
/*if (element.required) {
placeholder += ' ' + theme('form_required_marker', { });
}*/
variables.attributes['placeholder'] = placeholder;
}
// If there wasn't a default value provided, set one. Then set the default value into the variables' attributes,
// if it wasn't already set, otherwise set it to the item's value.
if (!item.default_value) { item.default_value = ''; }
variables.attributes.value = item.default_value;
if (
typeof item.value !== 'undefined' &&
(typeof variables.attributes.value === 'undefined' || empty(variables.attributes.value))
) { variables.attributes.value = item.value; }
// Call the hook_field_widget_form() if necessary. Merge any changes
// to the item back into this item.
if (field_widget_form_function) {
field_widget_form_function.apply(
null, [
form,
null,
element.field_info_field,
element.field_info_instance,
language,
items,
delta,
element
]);
item = $.extend(true, item, items[delta]);
// If the item type got lost, replace it.
if (!item.type && element.type) { item.type = element.type; }
}
// Merge element attributes into the variables object.
if (item.options && item.options.attributes) {
variables.attributes = $.extend(
true,
variables.attributes,
item.options.attributes
);
}
// Render the element item, unless it wasn't supported. Before rendering, clear out any default values so they
// aren't stale for the next delta item.
item_html += _drupalgap_form_render_element_item(
form,
element,
variables,
item
);
if (typeof variables.default_value !== 'undefined') { delete variables.default_value; }
if (typeof variables.default_value_label !== 'undefined') { delete variables.default_value_label; }
if (typeof variables.value !== 'undefined') { delete variables.value; }
if (typeof item_html === 'undefined') {
render_item = false;
break;
}
}
// Are we skipping the render of the item?
if (!render_item) { return ''; }
// Show the 'Add another item' button on unlimited value fields.
/*if (element.field_info_field &&
element.field_info_field.cardinality == -1) {
var add_another_item_variables = {
text: 'Add another item',
attributes: {
'class': 'drupalgap_form_add_another_item',
onclick:
"javascript:_drupalgap_form_add_another_item('" +
form.id + "', '" +
element.name + "', " +
delta +
')'
}
};
html += theme('button', add_another_item_variables);
}*/
// Is this element wrapped? We won't wrap hidden inputs by default, unless
// someone is overriding it.
var wrapped = true;
if (typeof element.wrapped !== 'undefined' && !element.wrapped) {
wrapped = false;
}
if (element.type == 'hidden') {
wrapped = false;
if (element.wrapped) { wrapped = true; }
}
// If there is an element prefix, place it in the html.
if (element.prefix) { html += element.prefix; }
// Open the element container.
var container_attributes = {
'class': drupalgap_form_get_element_container_class(name)
};
if (wrapped) {
html += '
';
}
// Add a label to the element, except submit and hidden elements. Any field
// labels have already been rendered, other element labels must be manually
// rendered here. Don't attach the label if the element's title_placeholder
// is set to true.
if (element.type != 'submit' && element.type != 'hidden') {
if (
typeof element.title_placeholder !== 'undefined' &&
element.title_placeholder
) { /* Skip label for placeholders. */ }
else {
if (element.is_field) { html += item_label; }
else {
html += theme('form_element_label', {'element': element});
}
}
}
// Add the item html if it isn't empty. Place the description "before" or "after" the item, exclude it if set
// to "none".
if (item_html != '') {
if (element.description && element.type != 'hidden') {
var descriptionDisplay = element.description_display ? element.description_display : 'after';
var descriptionHtml = '
' + t(element.description) + '
';
switch (descriptionDisplay) {
case 'before': html += descriptionHtml + item_html; break;
case 'after': html += item_html + descriptionHtml; break;
case 'none': html += item_html; break;
}
}
else { html += item_html; }
}
// Close the element container.
if (wrapped) { html += '
'; }
// If there is an element suffix, place it in the html.
if (element.suffix) { html += element.suffix; }
// Return the element html.
return html;
}
catch (error) { console.log('_drupalgap_form_render_element - ' + error); }
}
/**
* Given a form, an element, the variables for a theme function, and the element
* item, this will return the html rendering of the element item.
* @param {Object} form
* @param {Object} element
* @param {Object} variables
* @param {Object} item
* @return {*}
*/
function _drupalgap_form_render_element_item(form, element, variables, item) {
try {
var html = '';
// Depending on the element type, if necessary, adjust the variables and/or
// theme function to be used, then render the element by calling its theme
// function.
// @TODO - this block of code should be moved into their respective
// implementations of hook_field_widget_form().
switch (item.type) {
case 'text':
item.type = 'textfield';
break;
case 'list_text':
case 'list_float':
case 'list_integer':
item.type = 'select';
break;
}
// Set the theme function.
var theme_function = item.type;
// If the element is disabled, add the 'disabled' attribute.
if (element.disabled) { variables.attributes.disabled = ''; }
// Make any preprocess modifications to the elements so they will map
// cleanly to their theme function.
// @todo A hook_field_widget_form() should be used instead here.
if (item.type == 'submit') {
variables.attributes.onclick =
'_drupalgap_form_submit(\'' + form.id + '\');';
if (!variables.attributes['data-theme']) {
variables.attributes['data-theme'] = 'b';
}
if (typeof variables.attributes.type === 'undefined') {
variables.attributes.type = 'button';
}
if (typeof variables.attributes['class'] === 'undefined') {
variables.attributes['class'] = '';
}
variables.attributes['class'] += ' dg_form_submit_button ';
}
// Merge the item into variables.
$.extend(true, variables, item);
// If a value isn't set on variables, try to set it with the default value
// on the item.
if (typeof variables.value === 'undefined' || variables.value == null) {
if (typeof item.default_value !== 'undefined') {
variables.value = item.default_value;
}
}
// Run the item through the theme system if a theme function exists, or try
// to use the item markup, or let the user know the field isn't supported.
if (function_exists('theme_' + theme_function)) {
html += theme(theme_function, variables);
}
else {
if (item.markup || item.markup == '') { html += item.markup; }
else {
// @todo - the reason for this warning sometimes happens because the
// item.type is lost with $.extend in _drupalgap_form_render_element().
// @update - if an item doesn't have a type, it gets set by the parent
// element, so we should now always have a type available here.
var msg = 'Field ' + item.type + ' not supported.';
console.log('WARNING: _drupalgap_form_render_element_item() - ' + msg);
dpm(item);
return null;
}
}
// Render any item children. If the child has markup, just use the html,
// otherwise run the child through theme().
if (item.children && item.children.length > 0) {
for (var i = 0; i < item.children.length; i++) {
if (item.children[i].markup) { html += item.children[i].markup; }
else if (item.children[i].type || item.children[i].theme) {
var theme_type = item.children[i].type;
if (!theme_type) { theme_type = item.children[i].theme; }
// Is there a title for a label?
if (item.children[i].title) {
html += theme('form_element_label', {
element: item.children[i]
});
}
// Render the child with the theme system.
if (item.children[i].prefix) { html += item.children[i].prefix; }
html += theme(theme_type, item.children[i]);
if (item.children[i].suffix) { html += item.children[i].suffix; }
}
else {
console.log(
'WARNING: _drupalgap_form_render_element_item - ' +
'failed to render child ' + i + ' for ' + element.name
);
}
}
}
return html;
}
catch (error) {
console.log('_drupalgap_form_render_element_item - ' + error);
}
}
/**
* Given an element name, the form, a language code and a delta value, this
* will return default values that can be used to place an item element into a
* Forms API object.
* @param {String} name
* @param {Object} form
* @param {String} language
* @param {Number} delta
* @return {Object}
*/
function drupalgap_form_element_item_create(name, form, language, delta) {
try {
// Generate the id for this element field item and set it and
// some default options onto the element item.
var id = drupalgap_form_get_element_id(name, form.id, language, delta);
return {
id: id,
options: {
attributes: {
id: id
}
},
required: form.elements[name].required
};
}
catch (error) {
console.log('drupalgap_form_element_item_create - ' + error);
}
}
/**
*
* @param {Object} form
* @param {Object} form_state
* @param {Object} element
* @param {String} language
* @param {Number} delta
* @return {Array}
*/
function _drupalgap_form_element_items_widget_arguments(form, form_state,
element, language, delta) {
try {
var widget_arguments = [];
widget_arguments.push(form); // form
widget_arguments.push(form_state); // form state
widget_arguments.push(element.field_info_field); // field
widget_arguments.push(element.field_info_instance); // instance
widget_arguments.push(language); // language
widget_arguments.push(form.elements[element.name][language]); // items
widget_arguments.push(delta); // delta
widget_arguments.push(element); // element
return widget_arguments;
}
catch (error) {
console.log('_drupalgap_form_element_items_widget_arguments - ' + error);
}
}
/**
* Internal function used to dynamically add another element item to a form for
* unlimited value fields.
* @param {String} form_id
* @param {String} name
* @param {Number} delta
*/
function _drupalgap_form_add_another_item(form_id, name, delta) {
try {
// Locate the last item, load the form, extract the element from
// the form, generate default variables for the new item, determine the next
// delta value.
var selector = '.' + drupalgap_form_get_element_container_class(name) +
' .drupalgap_form_add_another_item';
var add_another_item_button = $(selector);
var form = drupalgap_form_local_storage_load(form_id);
var language = language_default();
var item = drupalgap_form_element_item_create(
name,
form,
language,
delta + 1
);
form.elements[name][language][delta + 1] = item;
var element = form.elements[name];
var variables = {
attributes: {},
field_info_field: element.field_info_field,
field_info_instance: element.field_info_instance
};
var field_widget_form_function =
element.field_info_instance.widget.module + '_field_widget_form';
window[field_widget_form_function].apply(
null,
_drupalgap_form_element_items_widget_arguments(
form,
null,
element,
language,
delta + 1
)
);
drupalgap_form_local_storage_save(form);
$(add_another_item_button).before(
_drupalgap_form_render_element_item(form, element, variables, item)
);
}
catch (error) { console.log('_drupalgap_form_add_another_item - ' + error); }
}
/**
* Returns a 'Cancel' button object that can be used on most forms.
* @return {Object}
*/
function drupalgap_form_cancel_button() {
return {
'title': t('Cancel'),
attributes: {
onclick: 'javascript:drupalgap_back();'
}
};
}
/**
* Given a jQuery selector to a form, this will clear all the elements on
* the UI.
* @see http://stackoverflow.com/a/6364313/763010
*/
function drupalgap_form_clear(form_selector) {
try {
$(':input', form_selector)
.not(':button, :submit, :reset, :hidden')
.val('')
.removeAttr('checked')
.removeAttr('selected');
}
catch (error) { console.log('drupalgap_form_clear - ' + error); }
}
/**
* Given a form id, this will assemble and return the default form JSON object.
* @param {String} form_id
* @return {Object}
*/
function drupalgap_form_defaults(form_id) {
try {
var form = {};
// Set the form id, elements, buttons, options and attributes.
form.id = form_id;
form.elements = {};
form.buttons = {};
form.options = {
attributes: {
'class': ''
}
};
// Create a prefix and suffix.
form.prefix = '';
form.suffix = '';
// Create empty arrays for the form's validation and submission handlers,
// then add the default call back functions to their respective array, if
// they exist.
form.validate = [];
form.submit = [];
var validate_function_name = form.id + '_validate';
if (function_exists(validate_function_name)) {
form.validate.push(validate_function_name);
}
var submit_function_name = form.id + '_submit';
if (function_exists(submit_function_name)) {
form.submit.push(submit_function_name);
}
// Finally, return the form.
return form;
}
catch (error) { console.log('drupalgap_form_defaults - ' + error); }
}
/**
* Given a drupalgap form, this renders the form html and returns it.
* @param {Object} form
* @return {String}
*/
function drupalgap_form_render(form) {
try {
// @TODO - we may possibly colliding html element ids!!! For example, I
// think the node edit page gets an id of "node_edit" and possibly so does
// the node edit form, which also may get an id of "node_edit". We may want
// to prefix both the template page and form ids with prefixes, e.g.
// drupalgap_page_* and drupalgap_form_*, but adding these prefixes could
// get annoying for css selectors used in jQuery and CSS. What to do?
// If no form id is provided, warn the user.
if (!form.id) {
return '
drupalgap_form_render() - missing form id!
' +
JSON.stringify(form);
}
// If the form already exists in the DOM, remove it.
if ($('form#' + form.id).length) { $('form#' + form.id).remove(); }
// Render the prefix and suffix and wrap them in their own div.
var prefix = form.prefix;
if (!empty(prefix)) {
prefix = '
' + prefix + '
';
}
var suffix = form.suffix;
if (!empty(suffix)) {
suffix = '
' + suffix + '
';
}
// Render the form's input elements.
var form_elements = _drupalgap_form_render_elements(form);
var form_attributes = drupalgap_attributes(form.options.attributes);
// Return the form html.
var form_html = '';
return form_html;
}
catch (error) { console.log('drupalgap_form_render - ' + error); }
}
/**
* Given a form element name and an error message, this attaches the error
* message to the drupalgap.form_errors array, keyed by the form element name.
* @param {String} name
* @param {String} message
*/
function drupalgap_form_set_error(name, message) {
try {
drupalgap.form_errors[name] = message;
}
catch (error) { console.log('drupalgap_form_set_error - ' + error); }
}
/**
* Given a form id, this will render the form and return the html for the form.
* Any additional arguments will be sent along to the form.
* @param {String} form_id
* @return {String}
*/
function drupalgap_get_form(form_id) {
try {
var html = '';
var form = drupalgap_form_load.apply(
null,
Array.prototype.slice.call(arguments)
);
if (form) {
// Render the form.
html = drupalgap_form_render(form);
}
else {
var msg = 'drupalgap_get_form - ' + t('failed to get form') +
' (' + form_id + ')';
drupalgap_alert(msg);
}
return html;
}
catch (error) { console.log('drupalgap_get_form - ' + error); }
}
/**
* Given a form id, this will delete the form from local storage.
* If the form isn't in local storage, this returns false.
* @param {String} form_id
* @return {Object}
*/
function drupalgap_form_local_storage_delete(form_id) {
try {
var result = window.localStorage.removeItem(
drupalgap_form_id_local_storage_key(form_id)
);
return result;
}
catch (error) {
console.log('drupalgap_form_local_storage_delete - ' + error);
}
}
/**
* Given a form id, this will load the form from local storage and return it.
* If the form isn't in local storage, this returns false.
* @param {String} form_id
* @return {Object}
*/
function drupalgap_form_local_storage_load(form_id) {
try {
var form = false;
form = window.localStorage.getItem(
drupalgap_form_id_local_storage_key(form_id)
);
if (!form) { form = false; }
else { form = JSON.parse(form); }
return form;
}
catch (error) { console.log('drupalgap_form_local_storage_load - ' + error); }
}
/**
* Given a form, this will save the form to local storage, overwriting any
* previously saved forms.
* @param {Object} form
*/
function drupalgap_form_local_storage_save(form) {
try {
window.localStorage.setItem(
drupalgap_form_id_local_storage_key(form.id),
JSON.stringify(form)
);
}
catch (error) { console.log('drupalgap_form_local_storage_save - ' + error); }
}
/**
* Given a form id, this will return the local storage key used by DrupalGap
* to save the assembled form to the device's local storage.
* @param {String} form_id
* @return {String}
*/
function drupalgap_form_id_local_storage_key(form_id) {
return 'drupalgap_form_' + form_id;
}
/**
* Given a form id, this will return the form JSON object assembled by the
* form's call back function. If the form fails to load, this returns false.
* @param {String} form_id
* @return {Object}
*/
function drupalgap_form_load(form_id) {
try {
var form = drupalgap_form_defaults(form_id);
// The form's call back function will be equal to the form id.
var function_name = form_id;
if (function_exists(function_name)) {
// Grab the form's function.
var fn = window[function_name];
// Determine the language code.
var language = language_default();
// Build the form arguments by iterating over each argument then adding
// each to to the form arguments, afterwards remove the argument at index
// zero because that is the form id.
var form_arguments = [];
for (var index in arguments) {
if (!arguments.hasOwnProperty(index)) { continue; }
var argument = arguments[index];
form_arguments.push(argument);
}
form_arguments.splice(0, 1);
// Attach the form arguments to the form object.
form.arguments = form_arguments;
// If there were no arguments to pass along, call the function directly to
// retrieve the form, otherwise call the function and pass along any
// arguments to retrieve the form.
if (form_arguments.length == 0) { form = fn(form, null); }
else {
// We must consolidate the form, form_state and arguments into one array
// and then pass it along to the form builder.
var consolidated_arguments = [];
var form_state = null;
consolidated_arguments.push(form);
consolidated_arguments.push(form_state);
for (var index in form_arguments) {
if (!form_arguments.hasOwnProperty(index)) { continue; }
var argument = form_arguments[index];
consolidated_arguments.push(argument);
}
form = fn.apply(
null,
Array.prototype.slice.call(consolidated_arguments)
);
}
// Set default values across elements in preparation for alteration.
_drupalgap_form_load_set_element_defaults(form, language);
// Give modules an opportunity to alter the form.
module_invoke_all('form_alter', form, null, form_id);
// Set default values across elements again in case any elements were added in preparation for alteration.
_drupalgap_form_load_set_element_defaults(form, language);
// Place the assembled form into local storage so _drupalgap_form_submit
// will have access to the assembled form.
drupalgap_form_local_storage_save(form);
Drupal.cache_expiration.forms[form_id] = 1;
window.localStorage.setItem('cache_expiration', JSON.stringify(Drupal.cache_expiration));
}
else {
var error_msg = 'drupalgap_form_load - ' + t('no callback function') +
' (' + function_name + ') ' + t('available for form') +
' (' + form_id + ')';
drupalgap_alert(error_msg);
}
return form;
}
catch (error) { console.log('drupalgap_form_load - ' + error); }
}
/**
* An internal function used to set default values onto all form elements' properties, such as an id and name.
* @param form
* @param language
* @private
*/
function _drupalgap_form_load_set_element_defaults(form, language) {
try {
// Set empty options and attributes properties on each form element if the
// element does not yet have any. This allows others to more easily modify
// options and attributes on an element without having to worry about
// testing for nulls and creating empty properties first.
for (var name in form.elements) {
if (!form.elements.hasOwnProperty(name)) { continue; }
var element = form.elements[name];
//console.log(name);
//console.log(element);
// If we haven't already, check to see f this element is a field, load its field_info_field and
// field_info_instance onto the element.
if (typeof element.is_field === 'undefined') {
var element_is_field = false;
var field_info_field = drupalgap_field_info_field(name);
if (field_info_field) {
element_is_field = true;
form.elements[name].field_info_field = field_info_field;
form.elements[name].field_info_instance =
drupalgap_field_info_instance(
form.entity_type,
name,
form.bundle
);
}
form.elements[name].is_field = element_is_field;
}
// Set the name property on the element if it isn't already set.
if (!form.elements[name].name) { form.elements[name].name = name; }
// If the element is a field, we'll append a language code and delta
// value to the element id, along with the field items appended
// onto the element using the language code and delta values.
var id = null;
if (element_is_field) {
// What's the number of allowed values (cardinality) on this field?
// A cardinality of -1 means the field has unlimited values.
var cardinality = parseInt(element.field_info_field.cardinality);
if (cardinality == -1) {
cardinality = 1; // we'll just add one element for now, until we
// figure out how to handle the 'add another
// item' feature.
}
// Initialize the item collections language code if it hasn't been.
if (!form.elements[name][language]) {
form.elements[name][language] = {};
}
// Prepare the item(s) for this element.
for (var delta = 0; delta < cardinality; delta++) {
// Prepare some item defaults.
var item = drupalgap_form_element_item_create(
name,
form,
language,
delta
);
// If the delta for this item hasn't been created on the element,
// create it using the default item values. Otherwise, merge the
// default values into the pre existing item on the element.
if (!form.elements[name][language][delta]) {
form.elements[name][language][delta] = item;
}
else {
$.extend(true, form.elements[name][language][delta], item);
}
}
}
else {
if (element.name == 'user_roles') { console.log('defaults', element); }
// This element is not a field, setup default options if none
// have been provided. Then set the element id.
if (!element.options) {
form.elements[name].options = {attributes: {}};
}
else if (!element.options.attributes) {
form.elements[name].options.attributes = {};
}
id = drupalgap_form_get_element_id(name, form.id);
form.elements[name].id = id;
form.elements[name].options.attributes.id = id;
}
}
}
catch (error) { console.log('_drupalgap_form_load_set_element_defaults - ' + error); }
}
/**
* Optionally use this function as an HTML DOM onkeypress handler, and it will
* attempt to listen for the enter key being pressed and submit the form at that
* time.
* @param {String} form_id
* @param {Object} event
* @return {Boolean}
*/
function drupalgap_form_onkeypress(form_id, event) {
try {
var event = event ? event : window.event;
if (!event) { return; }
var charCode = event.which || event.keyCode;
if (charCode != '13') { return; }
$('#' + form_id + ' button.dg_form_submit_button').click();
event.preventDefault();
return false;
}
catch (error) { console.log('drupalgap_form_onkeypress - ' + error); }
}
/**
* Handles a drupalgap form's submit button click.
* @param {String} form_id
* @return {*}
*/
function _drupalgap_form_submit(form_id) {
try {
// Load the form from local storage.
var form = drupalgap_form_local_storage_load(form_id);
if (!form) {
var msg = '_drupalgap_form_submit - ' + t('failed to load form') + ': ' +
form_id;
drupalgap_alert(msg);
return false;
}
// Assemble the form state values.
var form_state = drupalgap_form_state_values_assemble(form);
// Clear out previous form errors.
drupalgap.form_errors = {};
// Build the form validation wrapper function.
var form_validation = function() {
try {
// Call the form's validate function(s), if any.
for (var index in form.validate) {
if (!form.validate.hasOwnProperty(index)) { continue; }
var function_name = form.validate[index];
var fn = window[function_name];
fn.apply(null, Array.prototype.slice.call([form, form_state]));
}
// Call drupalgap form's api validate.
_drupalgap_form_validate(form, form_state);
// If there were validation errors, show the form errors and stop the
// form submission. Otherwise submit the form.
if (!jQuery.isEmptyObject(drupalgap.form_errors)) {
var html = '';
for (var name in drupalgap.form_errors) {
if (!drupalgap.form_errors.hasOwnProperty(name)) { continue; }
var message = drupalgap.form_errors[name];
html += message + '\n\n';
}
drupalgap_alert(html);
}
else { form_submission(); }
}
catch (error) {
console.log('_drupalgap_form_submit - form_validation - ' + error);
}
};
// Build the form submission wrapper function.
var form_submission = function() {
try {
// Call the form's submit function(s), if any.
for (var index in form.submit) {
if (!form.submit.hasOwnProperty(index)) { continue; }
var function_name = form.submit[index];
var fn = window[function_name];
fn.apply(null, Array.prototype.slice.call([form, form_state]));
}
// Remove the form from local storage.
// @todo - we can't do this here because often times a form's submit
// handler makes asynchronous calls (i.e. user login) and although the
// form validated, server side may say the input was invalid, so the
// user will still be on the form, except we already removed the form.
//drupalgap_form_local_storage_delete(form_id);
}
catch (error) {
console.log('_drupalgap_form_submit - form_submission - ' + error);
}
};
// Get ready to validate and submit the form, but first...
// If this is an entity form, and there is an image field on the form, we
// need to asynchronously process the image field, then continue onward
// with normal form validation and submission.
if (form.entity_type &&
image_fields_present_on_entity_type(form.entity_type, form.bundle)
) {
_image_field_form_process(form, form_state, {
success: form_validation
});
}
else {
// There were no image fields on the form, proceed normally with form
// validation, which will in turn process the submission if there are no
// validation errors.
form_validation();
}
}
catch (error) { console.log('_drupalgap_form_submit - ' + error); }
}
/**
* An internal function used by the DrupalGap forms api to validate all the
* elements on a form.
* @param {Object} form
* @param {Object} form_state
*/
function _drupalgap_form_validate(form, form_state) {
try {
for (var name in form.elements) {
if (!form.elements.hasOwnProperty(name)) { continue; }
var element = form.elements[name];
if (name == 'submit') { continue; }
if (element.required) {
var valid = true;
var value = null;
if (element.is_field) {
value = form_state.values[name][language_default()][0];
}
else { value = form_state.values[name]; }
// Check for empty values.
if (empty(value)) { valid = false; }
// Validate a required select list.
else if (
element.type == 'select' && element.required && value == ''
) { valid = false; }
else if (element.type == 'checkboxes' && element.required) {
var has_value = false;
for (var key in form_state.values[name]) {
if (!form_state.values[name].hasOwnProperty(key)) { continue; }
var value = form_state.values[name][key];
if (value) { has_value = true; break; }
}
if (!has_value) { valid = false; }
}
if (!valid) {
var field_title = name;
if (element.title) { field_title = element.title; }
drupalgap_form_set_error(
name,
t('The') + ' ' + field_title + ' ' + t('field is required') + '.'
);
}
}
}
}
catch (error) { console.log('_drupalgap_form_validate - ' + error); }
}
/**
* Given a form, this function iterates over the form's elements and assembles
* each element and value and places them into the form state's values. This
* is similar to $form_state['values'] in Drupal.
* @param {Object} form
* @return {Object}
*/
function drupalgap_form_state_values_assemble(form) {
try {
var lng = language_default();
var form_state = { values: {} };
for (var name in form.elements) {
if (!form.elements.hasOwnProperty(name)) { continue; }
var element = form.elements[name];
if (name == 'submit') { continue; } // Always skip the form 'submit'.
var id = null;
if (element.is_field) {
form_state.values[name] = {};
form_state.values[name][lng] = {};
var allowed_values = element.field_info_field.cardinality;
if (allowed_values == -1) {
allowed_values = 1; // Convert unlimited value field to one for now...
}
for (var delta = 0; delta < allowed_values; delta++) {
id = drupalgap_form_get_element_id(name, form.id, lng, delta);
form_state.values[name][lng][delta] =
_drupalgap_form_state_values_assemble_get_element_value(
id,
element
);
}
}
else {
id = drupalgap_form_get_element_id(name, form.id);
form_state.values[name] =
_drupalgap_form_state_values_assemble_get_element_value(
id,
element
);
}
}
// Attach the form state to drupalgap.form_states keyed by the form id.
drupalgap.form_states[form.id] = form_state;
return form_state;
}
catch (error) {
console.log('drupalgap_form_state_values_assemble - ' + error);
}
}
/**
*
* @param {String} id
* @param {Object} element
* @return {String|Number}
*/
function _drupalgap_form_state_values_assemble_get_element_value(id, element) {
try {
// If a value_callback is specified on the element, call that function to
// retrieve the element's value. Ohterwise, we'll use the default techniques
// implemented to extract a value for the form state.
if (element.value_callback && function_exists(element.value_callback)) {
var fn = window[element.value_callback];
return fn(id, element);
}
// Figure out the value, depending on the element type.
var value = null;
var selector = '';
if (element.type == 'radios') {
selector = 'input:radio[name="' + id + '"]:checked';
}
else { selector = '#' + id; }
switch (element.type) {
case 'checkbox':
var _checkbox = $(selector);
if ($(_checkbox).is(':checked')) { value = 1; }
else { value = 0; }
break;
case 'checkboxes':
// Iterate over each option, and see if it is checked, then pop it onto
// the value array. It's possible for values to be updated dynamically
// by a developer (i.e. through a pageshow handler), so it isn't safe to
// look at the options in local storage. Instead we'll directly iterate
// over the element option(s) in the DOM.
value = {};
var options = $('label[for="' + id + '"]').siblings('.ui-checkbox');
$.each(options, function(index, option) {
var checkbox = $(option).children('input');
var _value = $(checkbox).attr('value');
if ($(checkbox).is(':checked')) { value[_value] = _value; }
else { value[_value] = 0; }
});
break;
case 'list_boolean':
var _checkbox = $(selector);
if ($(_checkbox).is(':checked')) { value = $(_checkbox).attr('on'); }
else { value = $(_checkbox).attr('off'); }
break;
case 'list_text':
// Radio buttons.
if (
element.field_info_instance &&
element.field_info_instance.widget.type == 'options_buttons'
) {
selector = 'input:radio[name="' + id + '"]:checked';
}
break;
}
if (value === null) { value = $(selector).val(); }
if (typeof value === 'undefined') { value = null; }
return value;
}
catch (error) {
console.log(
'_drupalgap_form_state_values_assemble_get_element_value - ' +
error
);
}
}
/**
* When a service call results in an error, this function is used to extract the
* request's response form errors into a human readble string and returns it. If
* there are no form errors, it will return false.
* @param {Object} form
* @param {Object} form_state
* @param {Object} xhr
* @param {String} status
* @param {String} message
* @return {String|Boolean}
*/
function _drupalgap_form_submit_response_errors(form, form_state, xhr, status,
message) {
try {
var responseText = JSON.parse(xhr.responseText);
if (typeof responseText === 'object' && responseText.form_errors) {
var msg = '';
for (var element_name in responseText.form_errors) {
if (!responseText.form_errors.hasOwnProperty(element_name)) { continue; }
var error_msg = responseText.form_errors[element_name];
if (error_msg != '') {
// The element name tends to come back weird, e.g.
// "field_art_type][und", so let's trim anything at and after
// the first "]".
var pos = element_name.indexOf(']');
if (pos != -1) {
element_name = element_name.substr(0, pos);
}
var label = element_name;
if (
form.elements[element_name] &&
form.elements[element_name].title
) { label = form.elements[element_name].title; }
msg += $('
(' + label + ') - ' +
error_msg + '
').text() + '\n';
}
}
if (msg != '') { return msg; }
}
return false;
}
catch (error) {
console.log('_drupalgap_form_submit_response_errors - ' + error);
}
}
/**
* Themes a checkbox input.
* @param {Object} variables
* @return {String}
*/
function theme_checkbox(variables) {
try {
variables.attributes.type = 'checkbox';
// Check the box?
if (variables.checked) {
variables.attributes.checked = 'checked';
}
var output = '';
return output;
}
catch (error) { console.log('theme_checkbox - ' + error); }
}
/**
* Themes checkboxes input.
* @param {Object} variables
* @return {String}
*/
function theme_checkboxes(variables) {
try {
var html = '';
variables.attributes.type = 'checkboxes';
for (var value in variables.options) {
if (!variables.options.hasOwnProperty(value)) { continue; }
var label = variables.options[value];
if (value == 'attributes') { continue; } // Skip attributes.
var _label = value;
if (!empty(label)) { _label = label; }
var checkbox = {
value: value,
attributes: {
name: variables.name + '[' + value + ']',
'class': variables.name,
value: value
}
};
if (variables.value && variables.value[value]) { checkbox.checked = true; }
html += '';
}
// Check the box?
/*if (variables.checked) {
variables.attributes.checked = 'checked';
}*/
return html;
}
catch (error) { console.log('theme_checkbox - ' + error); }
}
/**
* Themes a email input.
* @param {Object} variables
* @return {String}
*/
function theme_email(variables) {
try {
variables.attributes.type = 'email';
var output = '';
return output;
}
catch (error) { console.log('theme_email - ' + error); }
}
/**
* Themes a file input.
* @param {Object} variables
* @return {String}
*/
function theme_file(variables) {
try {
variables.attributes.type = 'file';
var output = '';
return output;
}
catch (error) { console.log('theme_file - ' + error); }
}
/**
* Themes a form element label.
* @param {Object} variables
* @return {String}
*/
function theme_form_element_label(variables) {
try {
var element = variables.element;
if (empty(element.title)) { return ''; }
// Any elements with a title_placeholder set to true
// By default, use the element id as the label for, unless the element is
// a radio, then use the name.
var label_for = '';
if (element.id) { label_for = element.id; }
else if (element.attributes && element.attributes['for']) {
label_for = element.attributes['for'];
}
if (element.type == 'radios') { label_for = element.name; }
// Render the label.
var html =
'';
return html;
}
catch (error) { console.log('theme_form_element_label - ' + error); }
}
/**
* Themes a marker for a required form element label.
* @param {Object} variables
* @return {String}
*/
function theme_form_required_marker(variables) {
return '*';
}
/**
* Themes a number input.
* @param {Object} variables
* @return {String}
*/
function theme_number(variables) {
try {
variables.attributes.type = 'number';
return '';
}
catch (error) { console.log('theme_number - ' + error); }
}
/**
* Themes a hidden input.
* @param {Object} variables
* @return {String}
*/
function theme_hidden(variables) {
try {
variables.attributes.type = 'hidden';
if (!variables.attributes.value && variables.value != null) {
variables.attributes.value = variables.value;
}
return '';
}
catch (error) { console.log('theme_hidden - ' + error); }
}
/**
* Themes a password input.
* @param {Object} variables
* @return {String}
*/
function theme_password(variables) {
try {
variables.attributes.type = 'password';
return '';
}
catch (error) { console.log('theme_password - ' + error); }
}
/**
* Themes radio buttons.
* @param {Object} variables
* @return {String}
*/
function theme_radios(variables) {
try {
var radios = '';
if (variables.options) {
variables.attributes.type = 'radio';
// Determine an id prefix to use.
var id = 'radio';
if (variables.attributes.id) {
id = variables.attributes.id;
delete variables.attributes.id;
}
// Set the radio name equal to the id if one doesn't exist.
if (!variables.attributes.name) {
variables.attributes.name = id;
}
// Init a delta value so each radio button can have a unique id.
var delta = 0;
for (var value in variables.options) {
if (!variables.options.hasOwnProperty(value)) { continue; }
var label = variables.options[value];
if (value == 'attributes') { continue; } // Skip the attributes.
var checked = '';
if (variables.value && variables.value == value) {
checked = ' checked="checked" ';
}
var input_id = id + '_' + delta.toString();
var input_label =
'';
radios += '' + input_label;
delta++;
}
}
return radios;
}
catch (error) { console.log('theme_radios - ' + error); }
}
/**
* Themes a range input.
* @param {Object} variables
* @return {String}
*/
function theme_range(variables) {
try {
variables.attributes.type = 'range';
if (typeof variables.attributes.value === 'undefined') {
variables.attributes.value = variables.value;
}
var output = '';
return output;
}
catch (error) { console.log('theme_range - ' + error); }
}
/**
* Themes a search input.
* @param {Object} variables
* @return {String}
*/
function theme_search(variables) {
try {
variables.attributes.type = 'search';
var output = '';
return output;
}
catch (error) { console.log('theme_search - ' + error); }
}
/**
* Themes a select list input.
* @param {Object} variables
* @return {String}
*/
function theme_select(variables) {
try {
var options = '';
if (variables.options) {
for (var value in variables.options) {
if (!variables.options.hasOwnProperty(value)) { continue; }
var label = variables.options[value];
if (value == 'attributes') { continue; } // Skip the attributes.
// Is the option selected?
var selected = '';
if (typeof variables.value !== 'undefined') {
if (
($.isArray(variables.value) && in_array(value, variables.value)) ||
variables.value == value
) { selected = ' selected '; }
}
// Render the option.
options += '';
}
}
return '';
}
catch (error) { console.log('theme_select - ' + error); }
}
/**
* Themes a telephone input.
* @param {Object} variables
* @return {String}
*/
function theme_tel(variables) {
try {
variables.attributes['type'] = 'tel';
return '';
}
catch (error) { console.log('theme_tel - ' + error); }
}
/**
* Themes a text input.
* @param {Object} variables
* @return {String}
*/
function theme_textfield(variables) {
try {
variables.attributes.type = 'text';
return '';
}
catch (error) { console.log('theme_textfield - ' + error); }
}
/**
* Themes a textarea input.
* @param {Object} variables
* @return {String}
*/
function theme_textarea(variables) {
try {
var output =
'';
return output;
}
catch (error) { console.log('theme_textarea - ' + error); }
}
/**
* Implements hook_add_page_to_dom_alter().
*/
function hook_add_page_to_dom_alter(attributes, options) {
// Use this hook to make alterations to a page's attributes or options before it is added to the DOM.
// @param {Object} attributes
// The page attributes on a page before it is added to the DOM.
// @param {Object} options
// The page options object containing the rendered html, the menu link object, and the internal page id.
// Add a custom class to the page indicating the device size. Place
// empty spaces around the class name so we don't collide with others.
var size = null;
var width = $(window).width();
if (width <= 414) { size = 'small'; }
else if (width < 1024) { size = 'medium'; }
else { size = 'large'; }
attributes.class += ' ' + size + ' ';
// Add a custom class to a particular route.
if (drupalgap_router_path_get() == 'channel/%') {
attributes.class += ' foo-bar ';
}
}
/**
* When a form submission for an entity is assembling the entity json object to
* send to the server, some form element fields need to be assembled in unique
* ways to match the entity's structure in Drupal. Modules that implement fields
* can use this hook to properly assemble the item value (by delta) and return
* it.
* @param {Object} entity_type
* @param {String} bundle
* @param {String} form_state_value
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Number} delta
* @param {Object} field_key Set the 'value' string property on this object to
* use a custom property name on the field value.
* Defaults to 'value'.
* Set the 'use_key' boolean property on this object
* to false to not use a key when assembling the
* result into the field. Defaults to true.
* Set the 'use_wrapper' boolean property on this
* object to false to not use the default wrapper
* placed around the result object. Defaults to true.
* Set the 'use_delta' boolean property to false when
* a delta value is not needed. Defaults to true.
* @param {Object} form
*
* @return {*}
*/
function hook_assemble_form_state_into_field(entity_type, bundle,
form_state_value, field, instance, langcode, delta, field_key, form) {
try {
// Listed below are example use cases. Each show how to assemble the result,
// and what the resulting field object will look like when assembled by the
// DrupalGap Forms API. We'll use a field machine name of 'field_my_field'
// in all of the examples.
// Optional helpful values.
// Grab the form element's name, the form id, and the element's id.
// var element_name = field_key.name;
// var form_id = field_key.form_id;
// var element_id = field_key.element_id;
// Example 1 - Here's a simple example using all the defaults.
var result = {
foo: 'bar'
};
return result;
// This in turn gets assembled by the DG FAPI.
// {
// /* ... other entity values and fields ... */
// field_my_field: {
// und: [
// { value: { foo: "bar" } }
// ]
// }
// }
// Example 2 - Setting the 'value' property.
field_key.value = "foo";
var result = "bar";
return result;
// {
// /* ... other entity values and fields ... */
// field_my_field: {
// und: [
// { foo: "bar" }
// ]
// }
// }
// Example 3 - Setting the 'use_key' to false.
field_key.use_key = false;
var result = "bar";
return result;
// {
// /* ... other entity values and fields ... */
// field_my_field: {
// und: ["bar"]
// }
// }
// Example 4 - Setting the 'use_delta' to false.
field_key.use_delta = false;
var result = "bar";
return result;
// {
// /* ... other entity values and fields ... */
// field_my_field: {
// und: ["bar"]
// }
// }
// Example 5 - Setting the 'use_delta' and 'use_wrapper' to false.
field_key.use_delta = false;
field_key.use_wrapper = false;
var result = "bar";
return result;
// {
// /* ... other entity values and fields ... */
// field_my_field: {
// und: "bar"
// }
// }
}
catch (error) {
console.log('hook_assemble_form_state_into_field - ' + error);
}
}
/**
* When the app is first loading up, DrupalGap checks to see if the device has a connection, if it does then this hook
* is called. If DrupalGap doesn't have a connection, then hook_device_offline() is called. Implementations of
* hook_deviceready() need to return true if they'd like DrupalGap to continue, or return false if you'd like DrupalGap
* to NOT continue. If DrupalGap continues, it will perform a System Connect resource call then go to the App's front
* page. This is called during DrupalGap's "deviceready" implementation for PhoneGap. Note, the Drupal.user object is
* not initialized at this point, and will always be an anonymous user.
*/
function hook_deviceready() {}
/**
* When someone calls drupalgap_has_connection(), this hook has an opportunity to set drupalgap.online to true or false.
* The value of drupalgap.online is returned to anyone who calls drupalgap_has_connection(), including DrupalGap core.
*/
function hook_device_connection() {
// If it is Saturday, take the app offline and force the user to go outside and play.
var d = new Date();
if (d.getDay() == 6) { drupalgap.online = false; }
}
/**
* Called during app startup if the device does not have a connection. Note, the Drupal.user object is ot initialized at
* this point, and will always be an anonymous user.
*/
function hook_device_offline() {
// Even though we're offline, let's just go to the front page.
drupalgap_goto('');
}
/**
* Take action when the user presses the "back" button. This includes the soft,
* hardware and browser back buttons. The browser back button is only available
* in web app mode, the hardware back button is typically only on compiled
* Android devices, whereas the soft back button actually appears within the UX
* of the app.
* @param {String} from
* @param {String} to
* @see http://docs.drupalgap.org/7/Widgets/Buttons/Back_Button
*/
function hook_drupalgap_back(from, to) {
// When the user navigates from the front page to the login page, show them
// a message (a toast).
if (from == drupalgap.settings.front && to == 'user/login') {
drupalgap_toast('Please login to continue');
}
}
/**
* Each time a page is navigated to within the app, drupalgap_goto() is called.
* Use this hook to do some preprocessing before drupalgap_goto() gets started.
* @param {String} path The current page path.
*/
function hook_drupalgap_goto_preprocess(path) {
try {
// Pre process the front page.
if (path == drupalgap.settings.front) {
drupalgap_alert(t('Preprocessing the front page!'));
}
}
catch (error) {
console.log('hook_drupalgap_goto_preprocess - ' + error);
}
}
/**
* Each time a page is navigated to within the app, drupalgap_goto() is called.
* Use this hook to do some post processing after drupalgap_goto() has finished.
* @param {String} path The current page path.
*/
function hook_drupalgap_goto_post_process(path) {
try {
// Post process the front page.
if (path == drupalgap.settings.front) {
drupalgap_alert(t('Post processing the front page!'));
}
}
catch (error) {
console.log('hook_drupalgap_goto_post_process - ' + error);
}
}
/**
* Called after a successful services API call to a Drupal site. Do not call
* any services from within your implementation, you may run into an infinite
* loop in your code. See http://drupalgap.org/project/force_authentication for
* example usage.
* @deprecated - use hook_services_postprocess() instead.
*/
function hook_services_success(url, data) { }
/**
* A hook used to declare custom block information.
*/
function hook_block_info() {}
/**
* A hook used to render custom blocks.
*/
function hook_block_view(delta, region) {
}
/**
* A hook used to handle a 404 in the app.
*/
function hook_404(router_path) {}
/**
* Implements hook_entity_pre_build_content().
*/
function hook_entity_pre_build_content(entity, entity_type, bundle) {
// Change some weights on nodes with a date field.
if (entity_type == 'node' && typeof entity.field_date !== 'undefined') {
entity.body.weight = 0;
entity.field_date.weight = 1;
}
}
/**
* Implements hook_entity_post_build_content().
*/
function hook_entity_post_build_content(entity, entity_type, bundle) {
}
/**
* Implements hook_entity_pre_render_content().
* Called before drupalgap_entity_render_content() assembles the entity.content
* string. Use this to make modifications to an entity before its' content is rendered.
*/
function hook_entity_pre_render_content(entity, entity_type, bundle) {
try {
// Remove access to the date field on all nodes.
if (entity_type == 'node' && typeof entity.field_date !== 'undefined') {
entity.field_date.access = false;
}
}
catch (error) {
console.log('hook_entity_pre_render_content - ' + error);
}
}
/**
* Called after drupalgap_entity_render_content() assembles the entity.content
* string. Use this to make modifications to the HTML output of the entity's
* content before it is displayed.
*/
function hook_entity_post_render_content(entity, entity_type, bundle) {
try {
if (entity.type == 'article') {
entity.content += '
' + t('Example text on every article!') + '
';
}
}
catch (error) {
console.log('hook_entity_post_render_content - ' + error);
}
}
/**
* Implements hook_entity_view_alter().
* Called immediately before a page is rendered and injected into its waiting
* container. Use this hook to modifications to the build object by adding or
* editing render arrays (widgets) on the build object.
*/
function hook_entity_view_alter(entity_type, entity_id, mode, build) {
try {
if (entity_type == 'user' && mode == 'view') {
if (entity_id == Drupal.user.uid) {
build['foo'] = { markup: '
' };
}
}
}
catch (error) { console.log('hook_entity_view_alter - ' + error); }
}
/**
* Implements hook_field_info_instance_add_to_form().
* Used by modules that provide custom fields to operate on a form or its
* elements before the form gets saved to local storage. This allows extra
* data be attached to the form that way things like hook_field_widget_form(),
* which takes place at render time, can have access to any extra data it may
* need.
* @param {String} entity_type
* @param {String} bundle
* @param {Object} form
* @param {Object} entity
* @param {Object} element
*/
function hook_field_info_instance_add_to_form(entity_type, bundle, form, entity, element) {
try {
// Attach a value_callback to the element so we can manually build its form
// state value.
element.value_callback = 'example_field_value_callback';
}
catch (error) { console.log('hook_field_info_instance_add_to_form - ' + error); }
}
/**
* Implements hook_field_formatter_view().
* @param {String} entity_type
* @param {Object} entity
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {Object} display
*/
function hook_field_formatter_view(entity_type, entity, field, instance, langcode, items, display) {
try {
// Use this hook to render a field's content on an entity. Use dpm() to
// inspect the incoming arguments. The arguments contain field display
// settings from Drupal.
//console.log(entity_type);
//console.log(entity);
//console.log(field);
//console.log(instance);
//console.log(langcode);
//console.log(items);
//console.log(display);
// Iterate over each item, and place a widget onto the render array.
var content = {};
for (var delta in items) {
if (!items.hasOwnProperty(delta)) { continue; }
var item = items[delta];
content[delta] = {
markup: '
' + t('Hello!') + '
'
};
}
return content;
}
catch (error) { console.log('hook_field_formatter_view - ' + error); }
}
/**
* Implements hook_field_widget_form().
* @param {Object} form
* @param {Object} form_state
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {Number} delta
* @param {Object} element
*/
function hook_field_widget_form(form, form_state, field, instance, langcode, items, delta, element) {
try {
// Use this hook to provide field widgets for form element items. This hook
// is called for each delta value on the field. Make modifications to the
// items collection using the provided delta value. The object contained
// within is a standard DrupalGap Forms API object, so you may assemble the
// field (and any children widgets) as needed.
// Very simple example, make the widget for the field a text field.
items[delta].type = 'textfield';
}
catch (error) { console.log('hook_field_widget_form - ' + error); }
}
/**
* Called after a form element is assembled. Use it to alter a form element.
*/
//function hook_form_element_alter(form, element, variables) { }
/**
* Implements hook_entity_post_render_field().
* Called after drupalgap_entity_render_field() assembles the field content
* string. Use this to make modifications to the HTML output of the entity's
* field before it is displayed. The field content will be inside of
* reference.content, so to make modifications, change reference.content. For
* more info: http://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language
*/
function hook_entity_post_render_field(entity, field_name, field, reference) {
if (field_name == 'field_my_image') {
reference.content += '
' + entity.title + '
';
}
}
/**
* Implements hook_form_alter().
* This hook is used to make alterations to existing forms.
*/
function hook_form_alter(form, form_state, form_id) {
// Change the description of the name element on the user login form
if (form_id == 'user_login_form') {
form.elements['name'].description = t('Enter your login name');
}
}
/**
* Implements hook_image_path_alter().
* Called after drupalgap_image_path() assembles the image path. Use this hook
* to make modifications to the image path. Return the modified path, or false
* to allow the default path to be generated.
*/
function hook_image_path_alter(src) { }
/**
* Implements hook_install().
* This hook is used by modules that need to execute custom code when the module
* is loaded. Note, the Drupal.user object is not initialized at this point, and
* always appears to be an anonymous user.
*/
function hook_install() { }
/**
* Implements hook_locale().
* Used to declare language code .json files that should be loaded by DrupalGap.
* @see http://drupalgap.org/translate
*/
function hook_locale() {
// Tell DrupalGap to load our custom Spanish and Italian language files
// located here:
// app/modules/custom/my_module/locale/es.json
// app/modules/custom/my_module/locale/it.json
return ['es', 'it'];
}
/**
* Implements hook_menu()
* This hook is used to declare menu paths for custom pages.
*/
function hook_menu() {
try {
var items = {};
items['hello_world'] = {
title: t('Hello World'),
page_callback: 'my_module_hello_world_page'
};
return items;
}
catch (error) { console.log('hook_menu - ' + error); }
}
function hook_mvc_model() {
var models = {};
return models;
}
function hook_mvc_view() {}
function hook_mvc_controller() {}
/**
* Implements hook_node_page_view_alter_TYPE().
* @param {Object} node The fully loaded node object.
* @param {Object} options The options object, containing the success callback.
*/
function hook_node_page_view_alter_TYPE(node, options) {
try {
// Use this hook to completely take over the content that is shown when a
// user views a certain content type's page within the app. Pass your
// content (render array or html string) to the sucess callback provided in
// options to have it automatically injected in the page.
var content = {};
content['my_markup'] = {
markup: '
'+t('Click below to see the node!')+'
'
};
content['my_collapsible'] = {
theme: 'collapsible',
header: node.title,
content: node.content
};
options.success(content);
}
catch (error) { console.log('hook_node_page_view_alter_TYPE() - ' + error); }
}
/**
* Implements hook_page_build().
* @param {Object} output The page build output object.
*/
function hook_page_build(output) {
try {
// Remove all titles from article node pages.
if (output.node && output.node.type == 'article') {
delete output.title;
}
}
catch (error) { console.log('hook_page_build - ' + error); }
}
/**
* Implements hook_preprocess_page().
* Take action before the page is processed and shown to the user.
* @param {Object} variables The page variables.
*/
function hook_preprocess_page(variables) {
try {
}
catch (error) {
console.log('hook_preprocess_page - ' + error);
}
}
/**
* Implements hook_post_process_page().
* Take action after the page is processed and shown to the user.
* @param {Object} variables The page variables.
*/
function hook_post_process_page(variables) {
try {
}
catch (error) {
console.log('hook_post_process_page - ' + error);
}
}
/**
* Implements hook_views_exposed_filter().
* @param {Object} form
* @param {Object} form_state
* @param {Object} element
* @param {Object} filter
* @param {Object} field
*/
function hook_views_exposed_filter(form, form_state, element, filter, field) {
try {
// This hook is used to assemble the form element for an exposed filter.
// Make modifications to the element JSON object to control how it is used
// on the form. The element comes prepopulated with some basic values. The
// majority of the data you need to assemble the field should be contained
// within the filter and field JSON objects, so use dpm() to insepct them
// and assemble your filter's element.
//dpm(filter);
//dpm(field);
}
catch (error) { console.log('hook_views_exposed_filter - ' + error); }
}
/**
* Implements hook_menu().
* @return {Object}
*/
function comment_menu() {
var items = {
'comment/%': {
title: t('Comment'),
page_callback: 'comment_page_view',
page_arguments: [1],
pageshow: 'comment_page_view_pageshow',
title_callback: 'comment_page_title',
title_arguments: [1]
},
'comment/%/view': {
title: t('View'),
type: 'MENU_DEFAULT_LOCAL_TASK',
weight: -10
},
'comment/%/edit': {
title: t('Edit'),
page_callback: 'entity_page_edit',
pageshow: 'entity_page_edit_pageshow',
page_arguments: ['comment_edit', 'comment', 1],
weight: 0,
type: 'MENU_LOCAL_TASK',
access_callback: 'comment_access',
access_arguments: [1],
options: { reloadPage: true }
}
};
return items;
}
/**
* Given a comment, this determines if the current user has access to it.
* Returns true if so, false otherwise.
* @param {Object} comment
* @return {Boolean}
*/
function comment_access(comment) {
try {
if (comment.uid == Drupal.user.uid && user_access('edit own comments') ||
user_access('administer comments')) {
return true;
}
else { return false; }
}
catch (error) { console.log('comment_access - ' + error); }
}
/**
* Given a node id, this will return the id to use on the comments wrapper.
* @param {Number} nid
* @return {String}
*/
function comments_container_id(nid) {
return 'comments_container_' + nid;
}
/**
* Given a comment id, this will return the id to use on the comment wrapper.
* @param {Number} cid
* @return {String}
*/
function comment_container_id(cid) {
return 'comment_container_' + cid;
}
/**
* Given a node id, this will return the id to use on the html list for the
* comments.
* @param {Number} nid
* @return {String}
* @deprecated Use comments_container_id() instead.
*/
function comment_list_id(nid) {
try {
return comments_container_id(nid);
}
catch (error) { console.log('comment_list_id - ' + error); }
}
/**
* Page callback for comment/%.
* @param {Number} cid
* @return {Object}
*/
function comment_page_view(cid) {
try {
if (cid) {
var content = {
container: _drupalgap_entity_page_container('comment', cid, 'view')
};
return content;
}
else { drupalgap_error(t('No comment id provided!')); }
}
catch (error) { console.log('comment_page_view - ' + error); }
}
/**
* jQM pageshow handler for comment/% pages.
* @param {Number} cid
*/
function comment_page_view_pageshow(cid) {
try {
comment_load(cid, {
success: function(comment) {
var item = theme('comment', { comment: comment });
var content = theme('jqm_item_list', {items: [item]});
_drupalgap_entity_page_container_inject(
'comment',
cid,
'view',
content
);
}
});
}
catch (error) { console.log('comment_page_view_pageshow - ' + error); }
}
/**
* The title call back function for the comment view page.
* @param {Function} callback
* @param {Number} cid
*/
function comment_page_title(callback, cid) {
try {
// Try to load the comment subject, then send it back to the given callback.
var title = '';
var comment = comment_load(cid, {
success: function(comment) {
if (comment && comment.subject) { title = comment.subject; }
callback.call(null, title);
}
});
}
catch (error) { console.log('comment_page_title - ' + error); }
}
/**
* The comment edit form.
* @param {Object} form
* @param {Object} form_state
* @param {Object} comment
* @param {Object} node
* @return {Object}
*/
function comment_edit(form, form_state, comment, node) {
try {
// If there is no comment object coming in, make an empty one with a node
// id. Note, once the form.js submit handler is aware of its own entity and
// can pass it along to this function (and others) when loading a form
// during the submission process, this little chunk of code will no longer
// be needed.
if (!comment) { comment = {'nid': arg(1)}; }
// Determine the comment bundle from the node type.
var node_type = null;
if (node && node.type) { node_type = node.type; }
else { node_type = comment.node_type.replace('comment_node_', ''); }
var bundle = 'comment_node_' + node_type;
// Setup form defaults.
form.id += '_' + comment.nid; // Append the node id the comment form id.
form.entity_type = 'comment';
form.bundle = bundle;
form.action = 'node/' + comment.nid;
// Add the entity's core fields to the form.
drupalgap_entity_add_core_fields_to_form(
'comment',
bundle,
form,
comment
);
// Add the fields for this content type to the form.
drupalgap_field_info_instances_add_to_form(
'comment',
bundle,
form,
comment
);
// Add submit to form.
form.elements.submit = {
'type': 'submit',
'value': t('Save')
};
// Add cancel and delete button to form if we're editing a comment. Also
// figure out a form title to use in the prefix.
var form_title = t('Add comment');
if (comment && comment.cid) {
form_title = t('Edit comment');
form.buttons['cancel'] = drupalgap_form_cancel_button();
form.buttons['delete'] =
drupalgap_entity_edit_form_delete_button('comment', comment.cid);
}
// Add a prefix.
form.prefix += '
' + form_title + '
';
return form;
}
catch (error) { console.log('comment_edit - ' + error); }
}
/**
* The comment edit submit function.
* @param {Object} form
* @param {Object} form_state
*/
function comment_edit_submit(form, form_state) {
try {
var comment = drupalgap_entity_build_from_form_state(form, form_state);
drupalgap_entity_form_submit(form, form_state, comment);
}
catch (error) { console.log('comment_edit_submit - ' + error); }
}
/**
* Implements hook_services_postprocess().
* @param {Object} options
* @param {Object} result
*/
function comment_services_postprocess(options, result) {
try {
if (options.service == 'comment' && options.resource == 'create') {
// If we're on the node view page, inject the comment into the comment
// listing (if it is in the DOM), then scroll the page to the newly
// inserted/rendered comment, then clear the form input.
var path = drupalgap_path_get();
var router_path = drupalgap_get_menu_link_router_path(path);
if (router_path == 'node/%') {
var nid = arg(1);
var container_id = comments_container_id(nid);
var container = $('#' + container_id);
if (
typeof container.length !== 'undefined' &&
container.length == 0
) { return; }
node_load(nid, {
reset: true,
success: function(node) {
comment_load(result.cid, {
success: function(comment) {
$(container).append(
theme('comment', { comment: comment })
).trigger('create');
scrollToElement('#' + container_id + ' #' + comment_container_id(comment.cid), 500);
var form_selector = '#' + drupalgap_get_page_id() +
' #comment_edit' + '_' + comment.nid;
drupalgap_form_clear(form_selector);
}
});
}
});
}
}
}
catch (error) { console.log('comment_services_postprocess - ' + error); }
}
/**
* Theme's a comment container.
* @param {Object} variables
* @return {String}
*/
function theme_comments(variables) {
try {
// Set the container id and append default attributes.
variables.attributes.id = comments_container_id(variables.node.nid);
variables.attributes['class'] +=
'comments comments-node-' + variables.node.type;
variables.attributes['data-role'] = 'collapsible-set';
// Open the container.
var html = '
';
// Show a comments title if there are any comments.
if (variables.node.comment_count > 0) {
html += '
Comments
';
}
// If the comments are already rendered, show them.
if (variables.comments) { html += variables.comments; }
// Close the container and return the html.
html += '
';
return html;
}
catch (error) { console.log('theme_comments - ' + error); }
}
/**
* Theme's a comment.
* @param {Object} variables
* @return {String}
*/
function theme_comment(variables) {
try {
var comment = variables.comment;
// Set the container id and append default attributes.
variables.attributes.id = comment_container_id(comment.cid);
variables.attributes['class'] += 'comment ';
variables.attributes['data-role'] = 'collapsible';
variables.attributes['data-collapsed'] = 'false';
var html = '
';
var comment_content = '';
// Any user picture?
// @TODO - the user picture doesn't use an image style here, it uses the
// original picture uploaded by the user, which can be varying sizes.
var picture = '';
if (comment.picture_uri) {
picture += theme(
'image', { path: drupalgap_image_path(comment.picture_uri) }
);
}
// Comment date.
var created = new Date(comment.created * 1000);
created = created.toLocaleDateString() + ' at ' +
created.toLocaleTimeString();
// Append comment extra fields and content. The user info will be rendered
// as a list item link.
var author = picture +
'
' + comment.content;
html += comment_content;
// Add an edit link if necessary.
if (
user_access('administer comments') ||
(user_access('edit own comments') && comment.uid == Drupal.user.uid)
) {
html += theme('button_link', {
text: t('Edit'),
path: 'comment/' + comment.cid + '/edit',
attributes: {
'data-icon': 'gear'
}
});
}
// Close the container and return the html.
html += '
';
return html;
}
catch (error) { console.log('theme_comment - ' + error); }
}
/**
* Implements hook_menu().
* @return {Object}
*/
function contact_menu() {
try {
var items = {};
items['contact'] = {
title: t('Contact'),
page_callback: 'drupalgap_get_form',
page_arguments: ['contact_site_form'],
pageshow: 'contact_site_form_pageshow',
access_arguments: ['access site-wide contact form']
};
items['user/%/contact'] = {
title: t('User contact'),
page_callback: 'drupalgap_get_form',
page_arguments: ['contact_personal_form', 1],
pageshow: 'contact_personal_form_pageshow',
access_arguments: ['access user contact forms'],
weight: 10,
type: 'MENU_LOCAL_TASK'
};
return items;
}
catch (error) { console.log('contact_menu - ' + error); }
}
/**
* The contact index service resource.
* @param {Object} options
*/
function contact_index(options) {
try {
options.method = 'GET';
options.path = 'contact.json';
options.service = 'contact';
options.resource = 'index';
Drupal.services.call(options);
}
catch (error) { console.log('contact_index - ' + error); }
}
/**
* The contact site service resource.
* @param {Object} options
*/
function contact_site(options) {
try {
options.method = 'POST';
options.path = 'contact/site.json';
options.service = 'contact';
options.resource = 'site';
Drupal.services.call(options);
}
catch (error) { console.log('contact_site - ' + error); }
}
/**
* The contact personal service resource.
* @param {Object} options
*/
function contact_personal(options) {
try {
options.method = 'POST';
options.path = 'contact/personal.json';
options.service = 'contact';
options.resource = 'personal';
Drupal.services.call(options);
}
catch (error) { console.log('contact_personal - ' + error); }
}
/**
* The site contact form.
* @param {Object} form
* @param {Object} form_state
* @return {Object}
*/
function contact_site_form(form, form_state) {
try {
form.elements.name = {
title: t('Your name'),
type: 'textfield',
required: true
};
form.elements.mail = {
title: t('Your e-mail address'),
type: 'email',
required: true
};
form.elements.subject = {
title: t('Subject'),
type: 'textfield',
required: true
};
form.elements.cid = {
title: t('Category'),
type: 'select',
required: true
};
form.elements.message = {
title: t('Message'),
type: 'textarea',
required: true
};
form.elements.copy = {
title: t('Send yourself a copy?'),
type: 'checkbox',
default_value: 0,
access: false
};
form.elements.submit = {
type: 'submit',
value: t('Send message')
};
// If the user is logged in, set the default values.
if (Drupal.user.uid != 0) {
form.elements.name.default_value = Drupal.user.name;
form.elements.name.disabled = true;
form.elements.mail.default_value = Drupal.user.mail;
form.elements.mail.disabled = true;
form.elements.copy.access = true;
}
return form;
}
catch (error) { console.log('contact_site_form - ' + error); }
}
/**
* The pageshow callback for the contact site form.
*/
function contact_site_form_pageshow() {
try {
contact_index({
success: function(results) {
var select = $('#edit-contact-site-form-cid');
if (!results || !results.length) { return; }
for (var index in results) {
if (!results.hasOwnProperty(index)) { continue; }
var result = results[index];
var selected = result.selected == 1 ? 'selected' : '';
var option = '';
$(select).append(option);
}
$(select).selectmenu('refresh');
if (results.length == 1) { $(select).hide(); }
}
});
}
catch (error) { console.log('contact_site_form_pageshow - ' + error); }
}
/**
* The site wide contact form submit handler.
* @param {Ojbect} form
* @param {Ojbect} form_state
*/
function contact_site_form_submit(form, form_state) {
var data = {
name: form_state.values['name'],
mail: form_state.values['mail'],
subject: form_state.values['subject'],
cid: form_state.values['cid'],
message: form_state.values['message'],
copy: form_state.values['copy']
};
contact_site({
data: JSON.stringify(data),
success: function(result) {
if (result[0]) {
drupalgap_alert(t('Your message has been sent!'));
}
else {
drupalgap_alert(
t('There was a problem sending your message!'),
{ title: t('Error') }
);
}
drupalgap_form_clear();
},
error: function(xhr, status, message) {
if (message) {
message = JSON.parse(message);
if (message.form_errors) {
var errors = '';
for (var element in message.form_errors) {
if (!message.form_errors.hasOwnProperty(element)) { continue; }
var error = message.form_errors[element];
errors += error + '\n';
}
if (errors != '') { drupalgap_alert(errors); }
}
}
}
});
}
/**
* The personal contact form.
* @param {Object} form
* @param {Object} form_state
* @param {Number} recipient
* @return {Object}
*/
function contact_personal_form(form, form_state, recipient) {
try {
// @TODO - when providing a personal contact form, make sure the user has
// their personal contact form enabled.
form.elements.name = {
title: t('Your name'),
type: 'textfield',
required: true
};
form.elements.mail = {
title: t('Your e-mail address'),
type: 'email',
required: true
};
form.elements.to = {
type: 'hidden',
required: true
};
var container_id = contact_personal_form_to_container_id(recipient);
form.elements.to_display = {
title: 'To',
markup: ''
};
form.elements.subject = {
title: t('Subject'),
type: 'textfield',
required: true
};
form.elements.message = {
title: t('Message'),
type: 'textarea',
required: true
};
form.elements.copy = {
title: t('Send yourself a copy?'),
type: 'checkbox',
default_value: 0,
access: false
};
form.elements.submit = {
type: 'submit',
value: t('Send message')
};
// If the user is logged in, set the default values.
if (Drupal.user.uid != 0) {
form.elements.name.default_value = Drupal.user.name;
form.elements.name.disabled = true;
form.elements.mail.default_value = Drupal.user.mail;
form.elements.mail.disabled = true;
form.elements.copy.access = true;
}
return form;
}
catch (error) { console.log('contact_personal_form - ' + error); }
}
/**
* The pageshow callback for the personal contact form.
* @param {Object} form
* @param {Number} recipient
*/
function contact_personal_form_pageshow(form, recipient) {
try {
user_load(recipient, {
success: function(account) {
// Make sure the user has their contact form enabled.
if (!account.data.contact) {
$('#' + drupalgap_get_page_id() + ' #drupalgap_form_errors').html(
"
" +
t("Sorry, this user's contact form is disabled.") +
'
'
);
return;
}
// Populate hidden value for the 'to' field.
var container_id = contact_personal_form_to_container_id(recipient);
$('#' + container_id).html(l(account.name, 'user/' + account.uid));
var hidden_selector = '#' + drupalgap_get_page_id() +
' #edit-contact-personal-form-to';
$(hidden_selector).val(account.name);
}
});
}
catch (error) { console.log('contact_personal_form_pageshow - ' + error); }
}
/**
* The personal contact form submit handler.
* @param {Ojbect} form
* @param {Ojbect} form_state
*/
function contact_personal_form_submit(form, form_state) {
var data = {
name: form_state.values['name'],
mail: form_state.values['mail'],
to: form_state.values['to'],
subject: form_state.values['subject'],
message: form_state.values['message'],
copy: form_state.values['copy']
};
contact_personal({
data: JSON.stringify(data),
success: function(result) {
if (result[0]) { drupalgap_alert(t('Your message has been sent!')); }
else {
drupalgap_alert(
t('There was a problem sending your message!'),
{ title: t('Error') }
);
}
drupalgap_form_clear();
},
error: function(xhr, status, message) {
if (message) {
message = JSON.parse(message);
if (message.form_errors) {
var errors = '';
for (var element in message.form_errors) {
if (!message.form_errors.hasOwnProperty(element)) { continue; }
var error = message.form_errors[element];
errors += error + '\n';
}
if (errors != '') { drupalgap_alert(errors); }
}
}
}
});
}
/**
* Returns the div container id to use on the "to" markup on the personal
* contact form.
* @param {Number} recipient
* @return {String}
*/
function contact_personal_form_to_container_id(recipient) {
return 'contact_personal_form_user_' + recipient;
}
/**
* Given an entity type and optional bundle, this will return the view mode machine name to use.
* It defaults to "drupalgap", but can be configured.
* @param {String} entity_type
* @param {String} bundle
* @returns {string}
* @see http://docs.drupalgap.org/7/Entities/Display_Modes
*/
function drupalgap_entity_view_mode(entity_type, bundle) {
var view_mode = 'drupalgap';
if (typeof drupalgap.settings.view_modes !== 'undefined') {
if (entity_type && bundle) {
if (
drupalgap.settings.view_modes[entity_type] &&
drupalgap.settings.view_modes[entity_type][bundle] &&
drupalgap.settings.view_modes[entity_type][bundle].view_mode
) { view_mode = drupalgap.settings.view_modes[entity_type][bundle].view_mode; }
}
else if (entity_type) {
if (
drupalgap.settings.view_modes[entity_type] &&
drupalgap.settings.view_modes[entity_type].view_mode
) { view_mode = drupalgap.settings.view_modes[entity_type].view_mode; }
}
}
return view_mode;
}
/**
* Given an entity type, id and optional context, this will return a container id to be used
* when constructing a placeholder to load/display/edit an entity.
* @param {String} entity_type
* @param {Number} entity_id
* @param {String} context An optional context to use, e.g. "edit"
* @returns {string}
*/
function drupalgap_get_entity_container_id(entity_type, entity_id, context) {
var id = 'dg-entity-container-' + entity_type + '-' + entity_id;
if (context) { id += '-' + context; }
return id;
}
/**
* A page_callback function used to build an empty placeholder for an entity and inline
* JavaScript to retrieve the entity for display.
* @param {String} handler
* @param {String} entity_type
* @param {Number} entity_id
* @returns {string}
*/
function drupalgap_get_entity(handler, entity_type, entity_id, context) {
return '' + drupalgap_jqm_page_event_script_code({
jqm_page_event_callback: 'drupalgap_get_entity_pageshow',
jqm_page_event_args: JSON.stringify({
handler: handler,
entity_type: entity_type,
entity_id: entity_id
})
});
}
/**
* A pageshow function used to retrieve an entity, pass it along to its handler for rendering,
* and then inject the handler's render array into the waiting placeholder.
* @param {Object} options
*/
function drupalgap_get_entity_pageshow(options) {
var context = drupalgap.menu_links[drupalgap_router_path_get()].page_arguments.length == 4 ?
drupalgap.menu_links[drupalgap_router_path_get()].page_arguments[3] : null;
entity_load(options.entity_type, options.entity_id, {
success: function(entity) {
var id = drupalgap_get_entity_container_id(options.entity_type, options.entity_id, context);
$('#' + id).html(drupalgap_render(window[options.handler](entity))).trigger('create');
if (drupalgap.page.options.success) { drupalgap.page.options.success(entity); }
},
error: drupalgap.page.options.error ? drupalgap.page.options.error : null
});
}
/**
* A page_callback function used to build an empty placeholder for an entity and inline
* JavaScript to retrieve the entity for editing.
* @param {String} handler
* @param {String} entity_type
* @param {Number} entity_id
* @returns {string}
*/
function drupalgap_get_entity_form(handler, entity_type, entity_id, context) {
context = !context ? 'edit' : context;
return '' + drupalgap_jqm_page_event_script_code({
jqm_page_event_callback: 'drupalgap_get_entity_form_pageshow',
jqm_page_event_args: JSON.stringify({
handler: handler,
entity_type: entity_type,
entity_id: entity_id
})
});
}
/**
* A pageshow function used to retrieve an entity, pass it along to a form builder, and then
* inject the form into the waiting placeholder.
* @param {Object} options
*/
function drupalgap_get_entity_form_pageshow(options) {
var context = drupalgap.menu_links[drupalgap_router_path_get()].page_arguments.length == 4 ?
drupalgap.menu_links[drupalgap_router_path_get()].page_arguments[3] : 'edit';
var done = function(entity) {
var id = drupalgap_get_entity_container_id(options.entity_type, options.entity_id, context);
$('#' + id).html(drupalgap_get_form(options.handler, entity)).trigger('create');
if (drupalgap.page.options.success) { drupalgap.page.options.success(entity); }
};
if (options.entity_id == 'add') { done({}); }
else {
entity_load(options.entity_type, options.entity_id, {
success: done,
error: drupalgap.page.options.error ? drupalgap.page.options.error : null
});
}
}
/**
* Implements hook_install().
*/
function entity_install() {
entity_clean_local_storage();
}
/**
* Given an entity type, bundle name, form and entity, this will add the
* entity's core fields to the form via the DrupalGap forms api.
* @param {String} entity_type
* @param {String} bundle
* @param {Object} form
* @param {Object} entity
*/
function drupalgap_entity_add_core_fields_to_form(entity_type, bundle,
form, entity) {
try {
// Grab the core fields for this entity type and bundle.
var fields = drupalgap_entity_get_core_fields(entity_type, bundle);
// Iterate over each core field in the entity and add it to the form. If
// there is a value present in the entity, then set the field's form element
// default value equal to the core field value.
for (var name in fields) {
if (!fields.hasOwnProperty(name)) { continue; }
var field = fields[name];
var default_value = field.default_value;
if (entity && entity[name]) { default_value = entity[name]; }
form.elements[name] = field;
form.elements[name].default_value = default_value;
}
}
catch (error) {
console.log('drupalgap_entity_add_core_fields_to_form - ' + error);
}
}
/**
* Deprecated! Given an entity type, the bundle, the entity (assembled from form
* state values) and any options, this assembles the ?data= string for the
* entity service resource call URLs.
* @param {String} entity_type
* @param {String} bundle
* @param {Object} entity
* @param {Object} options
*/
function drupalgap_entity_assemble_data(entity_type, bundle, entity, options) {
try {
console.log('WARNING: drupalgap_entity_assemble_data() has been ' +
'deprecated! Now just call e.g. node_save() for auto assembly.');
return;
}
catch (error) { console.log('drupalgap_entity_assemble_data - ' + error); }
}
/**
* Returns the 'Delete' button object that is used on entity edit forms.
* @param {String} entity_type
* @param {Number} entity_id
* @return {Object}
*/
function drupalgap_entity_edit_form_delete_button(entity_type, entity_id) {
return {
title: t('Delete'),
attributes: {
onclick: "javascript:drupalgap_entity_edit_form_delete_confirmation('" +
entity_type + "', " + entity_id +
');'
}
};
}
/**
* Given an entity type and id, this will display a confirmation dialogue and
* will subsequently delete the entity if the user confirms the dialogue box.
* The Services module retains Drupal user permissions so users without proper
* permissions will not be able to delete the entities from the server.
* @param {String} entity_type
* @param {Number} entity_id
* @return {*}
*/
function drupalgap_entity_edit_form_delete_confirmation(entity_type,
entity_id) {
try {
var confirm_msg =
t('Delete this content, are you sure? This action cannot be undone...');
drupalgap_confirm(confirm_msg, {
confirmCallback: function(button) {
if (button == 2) { return; }
// Change the jQM loader mode to deleting.
drupalgap.loader = 'deleting';
// Set up the api call arguments and success callback.
var call_arguments = {};
call_arguments.success = function(result) {
// Remove the entities page from the DOM, if it exists.
var entity_page_path = entity_type + '/' + entity_id;
var entity_page_id = drupalgap_get_page_id(entity_page_path);
if (drupalgap_page_in_dom(entity_page_id)) {
drupalgap_remove_page_from_dom(entity_page_id);
}
// Remove the entity from local storage.
// @todo - this should be moved to jDrupal.
window.localStorage.removeItem(
entity_local_storage_key(entity_type, entity_id)
);
// Go to the front page, unless a form action path was specified.
var form = drupalgap_form_local_storage_load('node_edit');
var destination = form.action ? form.action : '';
drupalgap_goto(destination, {
reloadPage: true,
form_submission: true
});
};
// Call the delete function.
var name = services_get_resource_function_for_entity(
entity_type,
'delete'
);
var fn = window[name];
fn(entity_id, call_arguments);
}
});
}
catch (error) {
console.log('drupalgap_entity_edit_form_delete_confirmation - ' + error);
}
}
/**
* Given an entity, this will render the content of the entity and place it in
* the entity JSON object as the 'content' property.
* @param {String} entity_type
* @param {Object} entity
*/
function drupalgap_entity_render_content(entity_type, entity) {
try {
entity.content = '';
// Figure out the bundle.
var bundle = entity.type;
if (entity_type == 'comment') { bundle = entity.bundle; }
else if (entity_type == 'taxonomy_term') { bundle = entity.vocabulary_machine_name; }
// Load the field info for this entity and bundle combo.
var field_info = drupalgap_field_info_instances(entity_type, bundle);
if (!field_info) { return; }
// Give modules a chance to pre build the content.
module_invoke_all('entity_pre_build_content', entity, entity_type, bundle);
// Render each field on the entity, using the drupalgap or default display.
var field_weights = {};
var field_displays = {};
for (var field_name in field_info) {
if (!field_info.hasOwnProperty(field_name)) { continue; }
var field = field_info[field_name];
// Determine which display mode to use. The default mode will be used if the drupalgap display mode is not
// present, unless a view mode has been specified in settings.js then we'll use that config for the current
// entity/bundle combo. If a module isn't listed on a custom display, use the default display's module.
if (!field.display) { break; }
var display = field.display['default'];
var view_mode = drupalgap_entity_view_mode(entity_type, bundle);
if (field.display[view_mode]) {
display = field.display[view_mode];
if (typeof display.module === 'undefined' && typeof field.display['default'].module !== 'undefined'
) { display.module = field.display['default'].module; }
}
// Skip hidden fields.
if (display.type == 'hidden') { continue; }
// Save the field display and weight. Use the weight from the field's render element if it's available,
// otherwise fallback to the weight mentioned in the display.
field_displays[field_name] = display;
field_weights[field_name] = entity[field_name] && typeof entity[field_name].weight !== 'undefined' ?
entity[field_name].weight : display.weight;
}
// Give modules a chance to alter the build content.
module_invoke_all('entity_post_build_content', entity, entity_type, bundle);
// Extract the field weights and sort them.
var extracted_weights = [];
for (var field_name in field_weights) {
if (!field_weights.hasOwnProperty(field_name)) { continue; }
var weight = field_weights[field_name];
extracted_weights.push(weight);
}
extracted_weights.sort(function(a, b) { return a - b; });
// Give modules a chance to pre alter the content.
module_invoke_all('entity_pre_render_content', entity, entity_type, bundle);
// For each sorted weight, locate the field with the corresponding weight,
// then render it's field content.
var completed_fields = [];
for (var weight_index in extracted_weights) {
if (!extracted_weights.hasOwnProperty(weight_index)) { continue; }
var target_weight = extracted_weights[weight_index];
for (var field_name in field_weights) {
if (!field_weights.hasOwnProperty(field_name) || typeof entity[field_name] === 'undefined') { continue; }
if (typeof entity[field_name].access !== 'undefined' && !entity[field_name].access) { continue; }
var weight = field_weights[field_name];
if (target_weight == weight) {
if (completed_fields.indexOf(field_name) == -1) {
completed_fields.push(field_name);
entity.content += drupalgap_entity_render_field(
entity_type,
entity,
field_name,
field_info[field_name],
field_displays[field_name]
);
break;
}
}
}
}
// Give modules a chance to alter the content.
module_invoke_all('entity_post_render_content', entity, entity_type, bundle);
// Update this entity in local storage so the content property sticks.
if (entity_caching_enabled(entity_type, bundle)) {
_entity_set_expiration_time(entity_type, entity);
_entity_local_storage_save(
entity_type,
entity[entity_primary_key(entity_type)],
entity
);
}
}
catch (error) {
console.log('drupalgap_entity_render_content - ' + error);
}
}
/**
* Given an entity_type, the entity, a field name, and the field this will
* render the field using the appropriate hook_field_formatter_view().
* @param {String} entity_type
* @param {Object} entity
* @param {String} field_name
* @param {Object} field
* @param {*} display
* @return {String}
*/
function drupalgap_entity_render_field(entity_type, entity, field_name,
field, display) {
try {
var content = '';
// Determine module that implements the hook_field_formatter_view,
// then determine the hook's function name, then render the field content.
// If there wasn't a module specified in the display, look to the module
// specified in the field widget. If we still don't find it, then just
// return.
var module = display['module'];
if (!module) {
if (!field.widget.module) {
var msg = 'drupalgap_entity_render_field - ' +
'unable to locate the module for the field (' + field_name + ')';
console.log(msg);
return content;
}
else { module = field.widget.module; }
}
var function_name = module + '_field_formatter_view';
if (function_exists(function_name)) {
// Grab the field formatter function, then grab the field items
// from the entity, then call the formatter function and append its result
// to the entity's content.
var fn = window[function_name];
var items = null;
// Check to see if translated content based on app's language setting
// is present or not. If yes, then use that language as per setting.
// Determine the language code. Note, multi lingual sites may have a
// language code on the entity, but still have 'und' on the field, so
// fall back to 'und' if the field's language code doesn't match the
// entity's language code.
var default_lang = language_default();
var language = entity.language;
if (entity[field_name]) {
if (entity[field_name][default_lang]) {
items = entity[field_name][default_lang];
}
else if (entity[field_name][language]) {
items = entity[field_name][language];
}
else if (entity[field_name]['und']) {
items = entity[field_name]['und'];
language = 'und';
}
else { items = entity[field_name]; }
}
// @TODO - We've been sending 'field' as the instance
// (drupalgap_field_info_instance), and the 'instance' as the field
// (drupalgap_field_info_field). This is backwards, and should be
// reversed. All contrib modules with field support will need to be
// udpated to reflect this. Lame.
var elements = fn(
entity_type,
entity,
field, /* This is actually the instance, doh! (I think) */
drupalgap_field_info_field(field_name),
language,
items,
display
);
for (var delta in elements) {
if (!elements.hasOwnProperty(delta)) { continue; }
var element = elements[delta];
// If the element has markup, render it as is, if it is
// themeable, then theme it.
var element_content = '';
if (element.markup) { element_content = element.markup; }
else if (element.theme) {
element_content = theme(element.theme, element);
}
content += element_content;
}
}
else {
console.log(
'WARNING: drupalgap_entity_render_field - ' + function_name + '() ' +
'does not exist! (' + field_name + ')'
);
}
// Render the field label, if necessary.
if (content != '' && display['label'] != 'hidden') {
var label = '
' + field.label + '
';
// Place the label above or below the field content.
label = '
' + label + '
';
switch (display['label']) {
case 'below':
content += label;
break;
case 'above':
default:
content = label + content;
break;
}
}
// Finally, wrap the rendered field in a div, and set the field name as the
// class name on the wrapper.
content = '
' + content + '
';
// Give modules a chance to alter the field content.
var reference = {'content': content};
module_invoke_all(
'entity_post_render_field', entity, field_name, field, reference
);
if (reference.content != content) { return reference.content; }
return content;
}
catch (error) { console.log('drupalgap_entity_render_field - ' + error); }
}
/**
* Given a form and form_state, this will assemble an entity from the form_state
* values and return the entity as a JSON object.
* @param {Object} form
* @param {Object} form_state
* @return {Object}
*/
function drupalgap_entity_build_from_form_state(form, form_state) {
try {
var entity = {};
var language = language_default();
for (var name in form_state.values) {
if (!form_state.values.hasOwnProperty(name)) { continue; }
var value = form_state.values[name];
// Skip elements with restricted access.
if (
typeof form.elements[name].access !== 'undefined' &&
!form.elements[name].access
) { continue; }
// Determine wether or not this element is a field. If it is, determine
// it's module and field assembly hook.
var is_field = false;
var module = false;
var hook = false;
if (form.elements[name].is_field) {
is_field = true;
module = form.elements[name].field_info_field.module;
hook = module + '_assemble_form_state_into_field';
if (!function_exists(hook)) { hook = false; }
}
// Retrieve the potential key for the element, if we don't get one
// then it is a flat field that should be attached as a property to the
// entity. Otherwise attach the key and value to the entity.
var key = drupalgap_field_key(name); // e.g. value, fid, tid, nid, etc.
if (key) {
// Determine how many allowed values for this field.
var allowed_values = form.elements[name].field_info_field.cardinality;
// Convert unlimited value fields to one, for now...
if (allowed_values == -1) { allowed_values = 1; }
// Make sure there is at least one value before creating the form
// element on the entity.
if (typeof value[language][0] === 'undefined') { continue; }
// Create an empty object to house the field on the entity.
entity[name] = {};
// Some fields do not use a delta value in the service call, so we
// prepare for that here.
// @todo - Do all options_select widgets really have no delta value?
// Or is it only single value fields that don't have it? We need to
// test this.
var use_delta = true;
if (
form.elements[name].type ==
'taxonomy_term_reference' ||
form.elements[name].field_info_instance.widget.type ==
'options_select'
) {
use_delta = false;
entity[name][language] = {};
}
else { entity[name][language] = []; }
// Now iterate over each delta on the form element, and add the value
// to the entity.
for (var delta = 0; delta < allowed_values; delta++) {
if (typeof value[language][delta] !== 'undefined') {
// @TODO - the way values are determined here is turning into
// spaghetti code. Every form element needs its own
// value_callback, just like Drupal's FAPI. Right now DG has
// something similar going on with the use of
// hook_assemble_form_state_into_field(). So replace any spaghetti
// below with a value_callback. Provide a deprecated hook warning
// for any fields not haven't caught up yet, and fallback to the
// hook for a while.
// @UPDATE - Actually, the DG FAPI
// hook_assemble_form_state_into_field() is a good idea, and
// should be used by all field form elements, then in
// drupalgap_field_info_instances_add_to_form(), that function
// should use the value_callback idea to properly map entity data
// to the form element's value.
// Extract the value.
var field_value = value[language][delta];
// By default, we'll assume we'll be attaching this element item's
// value according to a key (usually 'value' is the default key
// used by Drupal fields). However, we'll give modules that
// implement hook_assemble_form_state_into_field() an opportunity
// to specify no usage of a key if their item doesn't need one.
// The geofield module is an example of field that doesn't use a
// key. The use_wrapper flag allows others to completely override
// the use of a wrapper around the field value, e.g. taxonomy term
// reference autocomplete. We'll attach any other helpful
// variables here as well (element name, form id, etc).
var field_key = {
value: 'value',
use_key: true,
use_wrapper: true,
use_delta: use_delta,
name: name,
form_id: form.id,
element_id: form.elements[name][language][delta].id
};
// If this element is a field, give the field's module an
// opportunity to assemble its own value, otherwise we'll just
// use the field value extracted above.
if (is_field && hook) {
var fn = window[hook];
field_value = fn(form.entity_type,
form.bundle,
field_value,
form.elements[name].field_info_field,
form.elements[name].field_info_instance,
language,
delta,
field_key,
form
);
}
// If someone updated the key, use it.
if (key != field_key.value) { key = field_key.value; }
// If we don't need a delta value, place the field value using the
// key, if posible. If we're using a delta value, push the key
// and value onto the field to indicate the delta.
if (!field_key.use_delta) {
if (!field_key.use_wrapper) {
entity[name][language] = field_value;
}
else {
if ($.isArray(entity[name][language])) {
console.log(
'WARNING: drupalgap_entity_build_from_form_state - ' +
'cannot use key (' + key + ') on field (' + name + ') ' +
'language code array, key will be ignored.'
);
entity[name][language].push(field_value);
}
else { entity[name][language][key] = field_value; }
}
}
else {
if (field_key.use_key) {
var item = {};
item[key] = field_value;
entity[name][language].push(item);
}
else {
entity[name][language].push(field_value);
}
}
// If the field value was null, we won't send along the field, so
// just remove it. Except for list_boolean fields, they need a
// null value to set the field value to false.
if (
field_value === null &&
typeof entity[name] !== 'undefined' &&
form.elements[name].type != 'list_boolean'
) {
if (is_field) {
if (delta == 0) { delete entity[name]; }
else if (typeof entity[name][language][delta] !== 'undefined') {
delete entity[name][language][delta];
}
}
else { delete entity[name]; }
}
// If we had an optional select list, and no options were
// selected, delete the empty field from the assembled entity.
// @TODO - will this cause multi value issues?
if (
is_field && !use_delta &&
form.elements[name].field_info_instance.widget.type ==
'options_select' && !form.elements[name].required &&
field_value === '' && typeof entity[name] !== 'undefined'
) { delete entity[name]; }
}
}
}
else if (typeof value !== 'undefined') { entity[name] = value; }
}
return entity;
}
catch (error) {
console.log('drupalgap_entity_build_from_form_state - ' + error);
}
}
/**
* Given a form, form_state and entity, this will call the appropriate service
* resource to create or update the entity.
* @param {Object} form
* @param {Object} form_state
* @param {Object} entity
* @return {*}
*/
function drupalgap_entity_form_submit(form, form_state, entity) {
try {
// Grab the primary key name for this entity type.
var primary_key = entity_primary_key(form.entity_type);
// Determine if we are editing an entity or creating a new one.
var editing = false;
if (entity[primary_key] && entity[primary_key] != '') {
editing = true;
}
// Let's set up the api call arguments.
var call_arguments = {};
// Setup the success call back to go back to the entity page view.
call_arguments.success = function(result) {
try {
// If no one has provided a form.action to submit this form to,
// by default we'll try to redirect to [entity-type]/[entity-id] to view
// the entity. For taxonomy, we replace the underscore with a forward
// slash in the path.
var destination = form.action;
if (!destination) {
var prefix = form.entity_type;
if (prefix == 'taxonomy_vocabulary' || prefix == 'taxonomy_term') {
prefix = prefix.replace('_', '/');
}
destination = prefix + '/' + result[primary_key];
}
// Is there a destination URL query parameter overwriting the action?
if (_GET('destination')) { destination = _GET('destination'); }
// Set up the default goto options, and use any options provided by the
// form.
var goto_options = { form_submission: true };
if (form.action_options) {
goto_options = $.extend({}, goto_options, form.action_options);
}
// Finally goto our destination.
drupalgap_goto(destination, goto_options);
}
catch (error) {
console.log('drupalgap_entity_form_submit - success - ' + error);
}
};
// Setup the error call back.
call_arguments.error = function(xhr, status, message) {
try {
// If there were any form errors, display them in an alert.
var msg = _drupalgap_form_submit_response_errors(form, form_state, xhr,
status, message);
if (msg) { drupalgap_alert(msg); }
}
catch (error) {
console.log('drupalgap_entity_form_submit - error - ' + error);
}
};
// Change the jQM loader mode to saving.
drupalgap.loader = 'saving';
// Depending on if we are creating a new entity, or editing an existing one,
// call the appropriate service resource.
var crud = 'create';
if (editing) {
crud = 'update';
// Remove the entity from local storage.
// @todo This should be moved to jDrupal.
window.localStorage.removeItem(
entity_local_storage_key(form.entity_type, entity[primary_key])
);
}
var fn = window[
services_get_resource_function_for_entity(form.entity_type, crud)
];
fn(entity, call_arguments);
}
catch (error) { console.log('drupalgap_entity_form_submit - ' + error); }
}
/**
* Given an entity type, this returns its core fields as forms api elements.
* @param {String} entity_type
* @param {String} bundle
* @return {Object}
*/
function drupalgap_entity_get_core_fields(entity_type, bundle) {
try {
// @todo - was this function what we were tyring to accomplish with the
// early entity_info hook imitations?
// @todo - And why is this function not populated dynamically via Drupal?
var fields = {};
switch (entity_type) {
case 'comment':
var content_type = bundle.replace('comment_node_', '');
// Add each schema field to the field collection.
var base_table = drupalgap.entity_info[entity_type].schema_fields_sql['base table'];
for (var index in base_table) {
if (!base_table.hasOwnProperty(index)) { continue; }
var name = base_table[index];
var field = {
type: 'hidden',
required: false,
default_value: '',
title: ucfirst(name)
};
fields[name] = field;
}
// Make the node id required.
fields['nid'].required = true;
// Only anonymous users can fill out the name field, authenticated users
// have their name auto filled and disabled.
fields['name'].type = 'textfield';
if (Drupal.user.uid != 0) {
fields['name'].default_value = Drupal.user.name;
fields['name'].disabled = true;
}
// If the 'Allow comment title' is enabled on the content type, show
// the comment subject field.
if (drupalgap.content_types_list[content_type].comment_subject_field) {
fields['subject'].type = 'textfield';
}
// Depending on this content type's comment settings, let's make
// modifications to the form elements.
// admin/structure/types/manage/article
// 0 = Anonymous posters may not enter their contact information
// 1 = Anonymous posters may leave their contact information
// 2 = Anonymous posters must leave their contact information
var comment_anonymous =
drupalgap.content_types_list[content_type].comment_anonymous;
switch (comment_anonymous) {
case '0':
delete(fields['mail']);
delete(fields['homepage']);
break;
case '1':
break;
case '2':
fields['mail'].required = true;
fields['homepage'].required = true;
break;
}
// Only anonymous users get the mail and homepage fields.
if (Drupal.user.uid == 0) {
if (fields['mail']) { fields['mail'].type = 'textfield'; }
if (fields['homepage']) { fields['homepage'].type = 'textfield'; }
}
break;
case 'node':
fields.nid = {
'type': 'hidden',
'required': false,
'default_value': ''
};
fields.title = {
'type': 'textfield',
'title': t('Title'),
'required': true,
'default_value': '',
'description': ''
};
fields.type = {
'type': 'hidden',
'required': true,
'default_value': ''
};
fields.language = {
'type': 'hidden',
'required': true,
'default_value': language_default()
};
break;
case 'user':
fields.uid = {
'type': 'hidden',
'required': false,
'default_value': ''
};
fields.name = {
'type': 'textfield',
'title': t('Username'),
'required': true,
'default_value': '',
'description': ''
};
fields.mail = {
'type': 'email',
'title': t('E-mail address'),
'required': true,
'default_value': '',
'description': ''
};
fields.picture = {
'type': 'image',
'widget_type': 'imagefield_widget',
'title': t('Picture'),
'required': false,
'value': t('Add Picture')
};
break;
case 'taxonomy_term':
fields = {
'vid': {
'type': 'hidden',
'required': true,
'default_value': ''
},
'tid': {
'type': 'hidden',
'required': false,
'default_value': ''
},
'name': {
'type': 'textfield',
'title': t('Name'),
'required': true,
'default_value': ''
},
'description': {
'type': 'textarea',
'title': t('Description'),
'required': false,
'default_value': ''
}
};
break;
case 'taxonomy_vocabulary':
fields = {
'vid': {
'type': 'hidden',
'required': false,
'default_value': ''
},
'name': {
'type': 'textfield',
'title': t('Name'),
'required': true,
'default_value': ''
},
'machine_name': {
'type': 'textfield',
'title': t('Machine Name'),
'required': true,
'default_value': ''
},
'description': {
'type': 'textarea',
'title': t('Description'),
'required': false,
'default_value': ''
}
};
break;
default:
console.log(
'drupalgap_entity_get_core_fields - entity type not supported yet (' +
entity_type +
')'
);
break;
}
return fields;
}
catch (error) { console.log('drupalgap_entity_get_core_fields - ' + error); }
}
/**
* Given an entity_type, this returns the entity JSON info, if it exists, false
* otherwise. You may optionally call this function with no arguments to
* retrieve the JSON info for all entity types. See also
* @see http://api.drupal.org/api/drupal/includes%21common.inc/function/entity_get_info/7
* @return {Object|Boolean}
*/
function drupalgap_entity_get_info() {
try {
if (arguments[0]) {
var entity_type = arguments[0];
if (entity_type && drupalgap.entity_info[entity_type]) {
return drupalgap.entity_info[entity_type];
}
else {
return false;
}
}
return drupalgap.entity_info;
}
catch (error) { console.log('drupalgap_entity_get_info - ' + error); }
}
/**
* @deprecated Since 7.x-1.7-alpha you should use entity_primary_key() instead.
* Given an entity type, this returns the primary key identifier for it.
* @param {String} entity_type
* @return {String}
*/
function drupalgap_entity_get_primary_key(entity_type) {
try {
console.log(
'WARNING: drupalgap_entity_get_primary_key() is deprecated! ' +
'Use entity_primary_key() instead.'
);
return entity_primary_key(entity_type);
}
catch (error) { console.log('drupalgap_entity_get_primary_key - ' + error); }
}
/**
* Given an entity type, an entity id and a mode, this will return a render
* object for the entity's page container.
* @param {String} entity_type
* @param {Number} entity_id
* @param {String} mode
* @return {Object}
*/
function _drupalgap_entity_page_container(entity_type, entity_id, mode) {
try {
var id = _drupalgap_entity_page_container_id(entity_type, entity_id, mode);
var attrs = {
id: id,
'class': entity_type + ' ' + entity_type + '-' + mode
};
return {
markup: ''
};
}
catch (error) { console.log('_drupalgap_entity_page_container - ' + error); }
}
/**
* Given an entity type, an entity id, and a mode, this will return the unique
* id to be used for the entity's page container.
* @param {String} entity_type
* @param {Number} entity_id
* @param {String} mode
* @return {String}
*/
function _drupalgap_entity_page_container_id(entity_type, entity_id, mode) {
return entity_type + '_' + entity_id + '_' + mode + '_container';
}
/**
* Given an entity type, id, mode and page build, this will render the page
* build and inject it into the container on the page.
* @param {String} entity_type
* @param {Number} entity_id
* @param {String} mode
* @param {Object} build
*/
function _drupalgap_entity_page_container_inject(entity_type, entity_id, mode, build) {
try {
// Get the container id, set the drupalgap.output to the page build, then
// inject the rendered page into the container.
var id = _drupalgap_entity_page_container_id(entity_type, entity_id, mode);
module_invoke_all('entity_view_alter', entity_type, entity_id, mode, build);
drupalgap.output = build;
$('#' + id).html(drupalgap_render_page()).trigger('create');
_drupalgap_entity_page_add_css_class_names(entity_type, entity_id, build);
}
catch (error) {
console.log('_drupalgap_entity_page_container_inject - ' + error);
}
}
/**
* An internal function used to add css class names to an entity's jQM page container.
* @param {String} entity_type
* @param {Number} entity_id
* @param {Object} build
* @private
*/
function _drupalgap_entity_page_add_css_class_names(entity_type, entity_id, build) {
try {
var className = entity_type;
var bundleName = entity_get_bundle(entity_type, build[entity_type]);
if (bundleName) { className += '-' + bundleName; }
className += ' ' + entity_type.replace(/_/g, '-') + '-' + entity_id;
$('#' + drupalgap_get_page_id()).addClass(className);
}
catch (error) { console.log('_drupalgap_entity_page_add_css_class_names - ' + error); }
}
/**
* The page callback for entity edit forms.
* @param {String} form_id
* @param {String} entity_type
* @param {Number} entity_id
* @return {Object}
*/
function entity_page_edit(form_id, entity_type, entity_id) {
try {
var content = {
container: _drupalgap_entity_page_container(
entity_type,
entity_id,
'edit'
)
};
return content;
}
catch (error) { console.log('entity_page_edit - ' + error); }
}
/**
* The pageshow callback for entity edit forms.
* @param {String} form_id
* @param {String} entity_type
* @param {Number} entity_id
*/
function entity_page_edit_pageshow(form_id, entity_type, entity_id) {
try {
entity_load(entity_type, entity_id, {
success: function(entity) {
_drupalgap_entity_page_container_inject(
entity_type,
entity_id,
'edit',
drupalgap_get_form(form_id, entity)
);
}
});
}
catch (error) { console.log('entity_page_edit_pageshow - ' + error); }
}
/**
* Returns an entity type's primary title key.
* @param {String} entity_type
* @return {String}
*/
function entity_primary_key_title(entity_type) {
try {
var key;
switch (entity_type) {
case 'comment': key = 'subject'; break;
case 'file': key = 'filename'; break;
case 'node': key = 'title'; break;
case 'taxonomy_term': key = 'name'; break;
case 'taxonomy_vocabulary': key = 'name'; break;
case 'user': key = 'name'; break;
default:
console.log(
'entity_primary_key_title - unsupported entity type (' +
entity_type +
')'
);
break;
}
return key;
}
catch (error) { console.log('entity_primary_key_title - ' + error); }
}
/**
* Implements hook_services_request_pre_postprocess_alter().
* @param {Object} options
* @param {*} result
*/
function entity_services_request_pre_postprocess_alter(options, result) {
try {
// If we're retrieving an entity, render the entity's content, if it isn't
// already set.
if (
options.resource == 'retrieve' &&
in_array(options.service, entity_types()
)) {
// @TODO - does this condition ever evaluate to true?
if (typeof result.content !== 'undefined') { return; }
drupalgap_entity_render_content(options.service, result);
}
// If we're indexing comments, render its content, if it isn't already set.
else if (options.service == 'comment' && options.resource == 'index') {
for (var index in result) {
if (!result.hasOwnProperty(index)) { continue; }
var object = result[index];
// @TODO - does this condition ever evaluate to true?
if (typeof object.content !== 'undefined') { continue; }
drupalgap_entity_render_content(options.service, result[index]);
}
}
}
catch (error) {
console.log('entity_services_request_pre_postprocess_alter - ' + error);
}
}
/**
* Given a field name, this will return its field info or null if it doesn't exist.
* @param {String} field_name
* @return {Object}
*/
function drupalgap_field_info_field(field_name) {
return drupalgap.field_info_fields && drupalgap.field_info_fields[field_name] ?
drupalgap.field_info_fields[field_name] : null;
}
/**
* Returns info on all fields or null if they don't exist.
* @return {Object}
*/
function drupalgap_field_info_fields() {
return drupalgap.field_info_fields ? drupalgap.field_info_fields : null;
}
/**
* Given an entity type, field name, and bundle name this will return a JSON
* object with data for the specified field name.
* @param {String} entity_type
* @param {String} field_name
* @param {String} bundle_name
* @return {Object}
*/
function drupalgap_field_info_instance(entity_type, field_name, bundle_name) {
try {
var instances = drupalgap_field_info_instances(entity_type, bundle_name);
var warningPrefix = 'WARNING: drupalgap_field_info_instance - ';
if (!instances) {
console.log(
warningPrefix +
'instance was null for entity (' + entity_type + ') bundle (' + bundle_name + ') using field (' + field_name + ')'
);
return null;
}
if (!instances[field_name]) {
console.log(
warningPrefix +
'"' + field_name + '" does not exist for entity (' + entity_type + ') bundle (' + bundle_name + ')'
);
return null;
}
return instances[field_name];
}
catch (error) { console.log('drupalgap_field_info_instance - ' + error); }
}
/**
* Given an entity type and/or a bundle name, this returns the field info
* instances for the entity or the bundle.
* @param {String} entity_type
* @param {String} bundle_name
* @return {Object}
*/
function drupalgap_field_info_instances(entity_type, bundle_name) {
try {
var instances = drupalgap.field_info_instances;
if (!instances) { return null; }
var field_info_instances = null;
// If there is no bundle, pull the fields out of the wrapper.
// @TODO there appears to be a special case with commerce_products, in that
// they aren't wrapped like normal entities (see the else statement when a
// bundle name isn't present). Or do we have a bug here, and we shouldn't
// be expecting the wrapper in the first place?
if (!bundle_name) {
field_info_instances = entity_type == 'commerce_product' ?
instances[entity_type] : instances[entity_type][entity_type];
}
else if (typeof instances[entity_type] !== 'undefined') {
field_info_instances = instances[entity_type][bundle_name];
}
return field_info_instances;
}
catch (error) { console.log('drupalgap_field_info_instances - ' + error); }
}
/**
* Given an entity type, bundle, form and entity, this will add the
* entity's fields to the given form.
* @param {String} entity_type
* @param {String} bundle
* @param {Object} form
* @param {Object} entity
*/
function drupalgap_field_info_instances_add_to_form(entity_type, bundle, form, entity) {
try {
// Grab the field info instances for this entity type and bundle.
var fields = drupalgap_field_info_instances(entity_type, bundle);
// If there is no bundle, pull the fields out of the wrapper.
//if (!bundle) { fields = fields[entity_type]; }
// Use the default language, unless the entity has one specified.
var language = language_default();
if (entity && entity.language) { language = entity.language; }
// Iterate over each field in the entity and add it to the form. If there is
// a value present in the entity, then set the field's form element default
// value equal to the field value.
if (fields) {
for (var name in fields) {
if (!fields.hasOwnProperty(name)) { continue; }
var field = fields[name];
// The user registration form is a special case, in that we only want
// to place fields that are set to display on the user registration
// form. Skip any fields not set to display.
if (form.id == 'user_register_form' &&
!field.settings.user_register_form) {
continue;
}
var field_info = drupalgap_field_info_field(name);
if (field_info) {
form.elements[name] = {
type: field_info.type,
title: field.label,
required: field.required,
description: field.description
};
var default_value = field.default_value;
var cardinality = parseInt(field_info.cardinality);
if (cardinality == -1) {
cardinality = 1; // we'll just add one element for now, until we
// figure out how to handle the 'add another
// item' feature.
}
if (entity && entity[name] && entity[name].length != 0) {
// Make sure the field has some type of language code on it, or just skip it. An entity will sometimes have
// a language code that a field doesn't have, so fall back to und on the field if the language code isn't
// present.
if (!entity[name][language]) {
if (!entity[name].und) { continue; }
language = 'und';
}
if (!form.elements[name][language]) { form.elements[name][language] = {}; }
for (var delta = 0; delta < cardinality; delta++) {
// @TODO - is this where we need to use the idea of the
// value_callback property present in Drupal's FAPI? That way
// each element knows how to map the entity data to its element
// value property.
if (
entity[name][language][delta] &&
typeof entity[name][language][delta].value !== 'undefined'
) { default_value = entity[name][language][delta].value; }
// If the default_value is null, set it to an empty string.
if (default_value == null) { default_value = ''; }
// Note, not all fields have a language code to use here, e.g. taxonomy term reference fields do not.
form.elements[name][language][delta] = {
value: default_value
};
// Place the field item onto the element.
if (entity[name][language][delta]) {
form.elements[name][language][delta].item =
entity[name][language][delta];
}
}
// Set the language back to the entity's language in case it was temporarily changed because of an un
// translated field.
if (entity && entity.language) { language = entity.language; }
}
// Give module's a chance to alter their own element during the form
// build, that way element properties will be saved to local storage
// and then available during hook_field_widget_form() and the form
// submission process.
var fn = field.widget.module + '_field_info_instance_add_to_form';
if (function_exists(fn)) {
window[fn](entity_type, bundle, form, entity, form.elements[name]);
}
}
}
}
}
catch (error) {
console.log('drupalgap_field_info_instances_add_to_form - ' + error);
}
}
/**
* Given a field name, this will return the key that should be used when
* setting its value on an entity. If the field name is not a field, it returns
* false.
* @param {String} field_name
* @return {String}
*/
function drupalgap_field_key(field_name) {
try {
// Determine the key to use for the value. By default, most fields
// use 'value' as the key.
var key = false;
var field_info = drupalgap_field_info_field(field_name);
if (field_info) {
key = 'value';
// Images use fid as the key.
if (field_info.module == 'image' && field_info.type == 'image') {
key = 'fid';
}
else if (
field_info.module == 'taxonomy' &&
field_info.type == 'taxonomy_term_reference'
) { key = 'tid'; }
}
return key;
}
catch (error) { console.log('drupalgap_field_key - ' + error); }
}
/**
* Implements hook_field_formatter_view().
* @param {String} entity_type
* @param {Object} entity
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {*} display
* @return {Object}
*/
function list_field_formatter_view(entity_type, entity, field, instance, langcode, items, display) {
try {
var element = {};
if (!empty(items)) {
for (var delta in items) {
if (!items.hasOwnProperty(delta)) { continue; }
var item = items[delta];
var markup = '';
// list_default or list_key
if (display.type == 'list_default') {
markup = instance.settings.allowed_values[item.value];
// Single on/off checkboxes need an empty space as their markup so
// just the label gets rendered.
if (
instance.type == 'list_boolean' &&
field.widget.type == 'options_onoff'
) { markup = ' '; }
}
else { markup = item.value; }
element[delta] = { markup: markup };
}
}
return element;
}
catch (error) { console.log('list_field_formatter_view - ' + error); }
}
/**
* Implements hook_assemble_form_state_into_field().
* @param {Object} entity_type
* @param {String} bundle
* @param {String} form_state_value
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Number} delta
* @param {Object} field_key
*
* @return {*}
*/
function list_assemble_form_state_into_field(entity_type, bundle, form_state_value, field, instance, langcode, delta, field_key) {
try {
var result = form_state_value;
switch (field.type) {
case 'list_boolean':
// For single on/off checkboxes, if the checkbox is unchecked, then we
// send a null value on the language code. We know the checkbox is
// unchecked if the form_state_value is equal to the first allowed value
// on the field.
if (instance.widget.type == 'options_onoff') {
var index = 0;
var on = true;
for (var value in field.settings.allowed_values) {
if (!field.settings.allowed_values.hasOwnProperty(value)) { continue; }
var label = field.settings.allowed_values[value];
if (form_state_value == value && index == 0) {
on = false;
break;
}
index++;
}
if (!on) {
field_key.use_delta = false;
field_key.use_wrapper = false;
result = null;
}
}
else {
console.log(
'WARNING: list_assemble_form_state_into_field - unknown widget (' +
field.type +
') on list_boolean'
);
}
break;
case 'list_text':
// For radio buttons on the user entity form, field values must be
// "flattened", i.e. this field_foo: { und: [ { value: 123 }]}, should
// be turned into field_foo: { und: 123 }
if (
entity_type == 'user' &&
instance.widget.type == 'options_buttons'
) {
field_key.use_delta = false;
field_key.use_wrapper = false;
}
break;
default:
console.log(
'WARNING: list_assemble_form_state_into_field - unknown type (' +
field.type +
')'
);
break;
}
return result;
}
catch (error) {
console.log('list_assemble_form_state_into_field - ' + error);
}
}
/**
* Implements hook_views_exposed_filter().
* @param {Object} form
* @param {Object} form_state
* @param {Object} element
* @param {Object} filter
* @param {Object} field
*/
function list_views_exposed_filter(form, form_state, element, filter, field) {
try {
//console.log('list_views_exposed_filter');
//console.log(form);
//console.log(form_state);
//console.log(element);
//console.log(filter);
//console.log(field);
var widget = filter.options.group_info.widget;
// List fields.
if (widget == 'select') {
// Set the element value if we have one in the filter.
if (!empty(filter.value)) { element.value = filter.value[0]; }
// Set the options, then depending on whether or not it is required, set
// the default value accordingly.
element.options = filter.value_options;
if (!element.required) {
element.options['All'] = '- ' + t('Any') + ' -';
if (typeof element.value === 'undefined') { element.value = 'All'; }
}
}
else {
console.log('WARNING: list_views_exposed_filter - unsupported widget:' + widget);
}
}
catch (error) { console.log('list_views_exposed_filter - ' + error); }
}
/**
* Implements hook_field_formatter_view().
* @param {String} entity_type
* @param {Object} entity
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {*} display
* @return {Object}
*/
function number_field_formatter_view(entity_type, entity, field, instance, langcode, items, display) {
try {
var element = {};
// If items is a string, convert it into a single item JSON object.
if (typeof items === 'string') {
items = {0: {value: items}};
}
if (!empty(items)) {
var prefix = '';
if (!empty(field.settings.prefix)) { prefix = field.settings.prefix; }
var suffix = '';
if (!empty(field.settings.suffix)) { suffix = field.settings.suffix; }
for (var delta in items) {
if (!items.hasOwnProperty(delta)) { continue; }
var item = items[delta];
element[delta] = {
markup: prefix + item.value + suffix
};
}
}
return element;
}
catch (error) { console.log('number_field_formatter_view - ' + error); }
}
/**
* Implements hook_field_widget_form().
* @param {Object} form
* @param {Object} form_state
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {Number} delta
* @param {Object} element
*/
function number_field_widget_form(form, form_state, field, instance, langcode, items, delta, element) {
try {
switch (element.type) {
case 'number_integer':
case 'number_float':
case 'number_decimal':
case 'range':
// Change the form element into a number, unless we're using a range
// slider. Then set its min/max attributes along with the step.
if (element.type != 'range') { items[delta].type = 'number'; }
if (!empty(instance.settings.max)) {
items[delta].options.attributes['min'] = instance.settings.min;
}
if (!empty(instance.settings.max)) {
items[delta].options.attributes['max'] = instance.settings.max;
}
var step = 1;
if (element.type == 'number_float') { step = 0.01; }
if (element.type == 'number_decimal') { step = 0.01; }
items[delta].options.attributes['step'] = step;
break;
default:
console.log(
'number_field_widget_form - element type not supported (' +
element.type +
')'
);
break;
}
}
catch (error) { console.log('number_field_widget_form - ' + error); }
}
/**
* Implements hook_field_widget_form().
* @param {Object} form
* @param {Object} form_state
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {Number} delta
* @param {Object} element
* @return {*}
*/
function options_field_widget_form(form, form_state, field, instance, langcode, items, delta, element) {
try {
switch (element.type) {
case 'checkbox':
// If the checkbox has a default value of 1, check the box.
if (items[delta].default_value == 1) { items[delta].checked = true; }
break;
case 'radios':
break;
case 'list_boolean':
switch (instance.widget.type) {
case 'options_onoff':
// Switch an on/off boolean to a checkbox and place its on/off
// values as attributes. Depending on the allowed values, we may
// have to iterate over an array, or an object to get the on/off
// values.
items[delta].type = 'checkbox';
var off = null;
var on = null;
if ($.isArray(field.settings.allowed_values)) {
for (var key in field.settings.allowed_values) {
if (off === null) { off = key; }
else { on = key; }
}
}
else {
for (var value in field.settings.allowed_values) {
if (!field.settings.allowed_values.hasOwnProperty(value)) { continue; }
var label = field.settings.allowed_values[value];
if (off === null) { off = value; }
else { on = value; }
}
}
items[delta].options.attributes['off'] = off;
items[delta].options.attributes['on'] = on;
// If the value equals the on value, then check the box.
if (
typeof items[delta] !== 'undefined' && items[delta].value == on
) { items[delta].options.attributes['checked'] = 'checked'; }
break;
default:
console.log(
'WARNING: options_field_widget_form list_boolean with ' +
'unsupported type (' + instance.widget.type + ')'
);
break;
}
break;
case 'select':
case 'list_text':
case 'list_float':
case 'list_integer':
if (instance) {
switch (instance.widget.type) {
case 'options_select':
items[delta].type = 'select';
// If the select list is required, add a 'Select' option and set
// it as the default. If it is optional, place a "none" option
// for the user to choose from.
var text = '- None -';
if (items[delta].required) {
text = '- ' + t('Select a value') + ' -';
}
items[delta].options[''] = text;
if (empty(items[delta].value)) { items[delta].value = ''; }
// If more than one value is allowed, turn it into a multiple
// select list.
if (field.cardinality != 1) {
items[delta].options.attributes['data-native-menu'] = 'false';
items[delta].options.attributes['multiple'] = 'multiple';
}
break;
case 'options_buttons':
// If there is one value allowed, we turn this into radio
// button(s), otherwise they will become checkboxes.
var type = 'checkboxes';
if (field.cardinality == 1) { type = 'radios'; }
items[delta].type = type;
break;
default:
console.log(
'WARNING: options_field_widget_form - unsupported widget (' +
instance.widget.type + ')'
);
return false;
break;
}
// If there are any allowed values, place them on the options
// list. Then check for a default value, and set it if necessary.
if (field && field.settings.allowed_values) {
for (var key in field.settings.allowed_values) {
if (!field.settings.allowed_values.hasOwnProperty(key)) { continue; }
var value = field.settings.allowed_values[key];
// Don't place values that are objects onto the options
// (i.e. commerce taxonomy term reference fields).
if (typeof value === 'object') { continue; }
// If the value already exists in the options, then someone
// else has populated the list (e.g. commerce), so don't do
// any processing.
if (typeof items[delta].options[key] !== 'undefined') {
break;
}
// Set the key and value for the option.
items[delta].options[key] = value;
}
if (instance.default_value && instance.default_value[delta] &&
typeof instance.default_value[delta].value !== 'undefined') {
items[delta].value = instance.default_value[delta].value;
if (items[delta].required) {
delete items[delta].options[''];
}
if (items[delta].item && typeof items[delta].item.value !== 'undefined') {
items[delta].value = items[delta].item.value;
}
}
}
}
break;
case 'taxonomy_term_reference':
// Change the item type to a hidden input.
items[delta].type = 'hidden';
// What vocabulary are we using?
var machine_name = field.settings.allowed_values[0].vocabulary;
var taxonomy_vocabulary =
taxonomy_vocabulary_machine_name_load(machine_name);
var widget_type = false;
if (instance.widget.type == 'options_select') {
widget_type = 'select';
}
else {
console.log(
'WARNING: options_field_widget_form() - ' + instance.widget.type +
' not yet supported for ' + element.type + ' form elements!'
);
return false;
}
var widget_id = items[delta].id + '-' + widget_type;
// If the select list is required, add a 'Select' option and set
// it as the default. If it is optional, place a "none" option
// for the user to choose from.
var text = '- ' + t('None') + ' -';
if (items[delta].required) {
text = '- ' + t('Select a value') + ' -';
}
items[delta].children.push({
type: widget_type,
attributes: {
id: widget_id,
onchange: "_theme_taxonomy_term_reference_onchange(this, '" +
items[delta].id +
"');"
},
options: { '': text }
});
// Attach a pageshow handler to the current page that will load the
// terms into the widget.
var options = {
'page_id': drupalgap_get_page_id(drupalgap_path_get()),
'jqm_page_event': 'pageshow',
'jqm_page_event_callback':
'_theme_taxonomy_term_reference_load_items',
'jqm_page_event_args': JSON.stringify({
'taxonomy_vocabulary': taxonomy_vocabulary,
'widget_id': widget_id
})
};
// Pass the field name so the page event handler can be called for
// each item.
items[delta].children.push({
markup: drupalgap_jqm_page_event_script_code(
options,
field.field_name
)
});
break;
default:
var msg = 'options_field_widget_form - unknown widget type: ' + element.type;
console.log(msg);
break;
}
}
catch (error) { console.log('options_field_widget_form - ' + error); }
}
/**
* Implements hook_field_formatter_view().
* @param {String} entity_type
* @param {Object} entity
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {*} display
* @return {Object}
*/
function text_field_formatter_view(entity_type, entity, field, instance, langcode, items, display) {
try {
var element = {};
if (items.length) {
for (var delta in items) {
if (!items.hasOwnProperty(delta)) { continue; }
// Grab the item, then grab the field value or its safe_value if we have it.
var item = items[delta];
var value = typeof item.safe_value !== 'undefined' ? item.safe_value : item.value;
// Any trim?
if (display.type == 'text_summary_or_trimmed') {
var length = display.settings.trim_length;
value = value.length > length ? value.substring(0, length - 3) + "..." : value.substring(0, length);
}
element[delta] = { markup: value };
}
}
return element;
}
catch (error) { console.log('text_field_formatter_view - ' + error); }
}
/**
* Implements hook_field_widget_form().
* @param {Object} form
* @param {Object} form_state
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {Number} delta
* @param {Object} element
*/
function text_field_widget_form(form, form_state, field, instance, langcode, items, delta, element) {
try {
// Determine the widget type, then set the delta item's type property.
var type = null;
switch (element.type) {
case 'search': type = 'search'; break;
case 'text': type = 'textfield'; break;
case 'textarea':
case 'text_long':
case 'text_with_summary':
case 'text_textarea':
type = 'textarea';
break;
}
items[delta].type = type;
// If the item has a value and its attribute value hasn't yet been set, then set the attribute value.
if (
typeof items[delta].value !== 'undefined' &&
typeof items[delta].options.attributes.value === 'undefined'
) { items[delta].options.attributes.value = items[delta].value; }
}
catch (error) { console.log('text_field_widget_form - ' + error); }
}
/**
* Implements hook_field_formatter_view().
*/
function file_field_formatter_view(entity_type, entity, field, instance, langcode, items, display) {
try {
//console.log(entity_type);
//console.log(entity);
//console.log(field);
//console.log(instance);
//console.log(langcode);
//console.log(items);
//console.log(display);
// Iterate over each item, and place a widget onto the render array.
var content = {};
for (var delta in items) {
if (!items.hasOwnProperty(delta)) { continue; }
var item = items[delta];
switch (display.type) {
case 'file_table':
// Instantiate the table only once.
if (!content['file_table']) {
content['file_table'] = {
theme: 'jqm_table',
header: [{ data: t('Attachment') }, { data: t('Size') }],
rows: [],
attributes: {
border: 1
}
};
}
// Build the path to the file.
var file_path = drupalgap_image_path(item.uri);
// Android can't open .pdf files in the in app browser, so have
// Google Docs open it instead.
if (item.filemime == 'application/pdf' &&
typeof device !== 'undefined' && device.platform == 'Android'
) { file_path = 'https://docs.google.com/gview?embedded=true&url=' + file_path; }
// Add the row to the table.
content.file_table.rows.push([
l(item.filename, file_path, { InAppBrowser: true }),
Math.round(item.filesize/1000).toFixed(2) + ' KB'
]);
break;
default:
console.log('file_field_formatter_view() - unsupported display type: ' + display.type);
break;
}
}
return content;
}
catch (error) { console.log('file_field_formatter_view - ' + error); }
}
/**
* Implements hook_field_formatter_view().
*/
function file_entity_field_formatter_view(entity_type, entity, field, instance, langcode, items, display) {
try {
//console.log(entity_type);
//console.log(entity);
//console.log(field);
//console.log(instance);
//console.log(langcode);
//console.log(items);
//console.log(display);
// Special case for media module.
if (display.type == 'file_rendered') {
return media_field_formatter_view(entity_type, entity, field, instance, langcode, items, display);
}
// Iterate over each item, and place a widget onto the render array.
var content = {};
for (var delta in items) {
if (!items.hasOwnProperty(delta)) { continue; }
var item = items[delta];
switch (display.type) {
default:
console.log('file_entity_field_formatter_view() - unsupported display type: ' + display.type);
break;
}
}
return content;
}
catch (error) { console.log('file_entity_field_formatter_view - ' + error); }
}
// Holds onto the phonegap getPicture success image data. It is keyed by field
// name, then delta value.
var image_phonegap_camera_options = {};
/**
* Implements hook_field_formatter_view().
/**
* Implements hook_field_formatter_view().
* @param {String} entity_type
* @param {Object} entity
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {*} display
* @return {Object}
*/
function image_field_formatter_view(entity_type, entity, field, instance,
langcode, items, display) {
try {
var element = {};
// Toss on the default image if we one is specified and we have no items.
// "In addition, any code which programmatically generates a link to an
// image derivative without using the standard image_style_url() API
// function will no longer work correctly if the image does not already
// exist in the file system, since the necessary token will not be present
// in the URL." @see http://drupal.stackexchange.com/a/76827/10645
if (empty(items) && instance.settings.default_image) {
items = [{
uri: instance.settings.default_image_uri
}];
}
if (!empty(items)) {
for (var delta in items) {
if (!items.hasOwnProperty(delta)) { continue; }
var item = items[delta];
var theme = empty(display.settings.image_style) ?
'image' : 'image_style';
var image = {
theme: theme,
alt: item.alt,
title: item.title
};
if (theme == 'image_style') {
image.style_name = display.settings.image_style;
image.path = item.uri;
}
else { image.path = drupalgap_image_path(item.uri); }
element[delta] = image;
}
}
return element;
}
catch (error) { console.log('image_field_formatter_view - ' + error); }
}
/**
* Implements hook_field_widget_form().
* @param {Object} form
* @param {Object} form_state
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {Number} delta
* @param {Object} element
*/
function image_field_widget_form(form, form_state, field, instance, langcode,
items, delta, element) {
try {
// Change the item type to a hidden input to hold the file id.
items[delta].type = 'hidden';
// If we're dealing with the user profile 'picture' it isn't a real field,
// so we need to spoof some field settings to get the widget to render
// properly.
// @TODO the field label doesn't show up.
if (form.id == 'user_profile_form' && element.name == 'picture') {
field = { field_name: 'picture' };
}
// If we already have an image for this item, show it.
if (typeof items[delta].item !== 'undefined' && items[delta].item.fid) {
// Set the hidden input's value equal to the file id.
items[delta].value = items[delta].item.fid;
// Show the image on the form, the file name as a link to the actual file,
// the file size, and a remove button.
var path = drupalgap_image_path(items[delta].item.uri);
// @TODO - show the filesize.
// @TODO - show the remove button.
var html = theme('image', { path: path }) +
'
';
// Add html to the item's children.
items[delta].children.push({markup: html});
return; // No further processing required.
}
// Set the default button text, and if a value was provided,
// overwrite the button text.
var button_text = t('Take Photo');
if (items[delta].value) { button_text = items[delta].value; }
var browse_button_text = t('Browse');
if (items[delta].value2) { browse_button_text = items[delta].value2; }
// Place variables into document for PhoneGap image processing.
var item_id_base = items[delta].id.replace(/-/g, '_');
var image_field_source = item_id_base + '_imagefield_source';
var imagefield_destination_type =
item_id_base + '_imagefield_destination_type';
var imagefield_data = item_id_base + '_imagefield_data';
eval('var ' + image_field_source + ' = null;');
eval('var ' + imagefield_destination_type + ' = null;');
eval('var ' + imagefield_data + ' = null;');
// Build an imagefield widget with PhoneGap. Contains a message
// div, an image item, a button to add an image, and a button to browse for
// images.
var browse_button_id = items[delta].id + '-browse-button';
var html = '
';
// Open extra javascript declaration.
html += '';
// Add html to the item's children.
items[delta].children.push({markup: html});
}
catch (error) { console.log('image_field_widget_form - ' + error); }
}
/**
* On an entity edit form, this removes an image file from the server, then from
* the form elements and user interface.
*/
function _image_field_widget_form_remove_image() {
try {
alert('_image_field_widget_form_remove_image');
}
catch (error) {
console.log('_image_field_widget_form_remove_image - ' + error);
}
}
/**
* Given an entity type and optional bundle name, this will return an array
* containing any image field names present, false otherwise.
* @param {String} entity_type
* @param {String} bundle
* @return {Object}
*/
function image_fields_present_on_entity_type(entity_type, bundle) {
try {
var results = [];
var fields = drupalgap_field_info_instances(entity_type, bundle);
if (!fields) { return false; }
for (var name in fields) {
if (!fields.hasOwnProperty(name)) { continue; }
var field = fields[name];
if (
field.widget &&
field.widget.type &&
field.widget.type == 'image_image'
) { results.push(name); }
}
if (results.length == 0) { return false; }
return results;
}
catch (error) {
console.log('image_fields_present_on_entity_type - ' + error);
}
}
/**
* Implements hook_form_alter().
* @param {Object} form
* @param {Object} form_state
* @param {String} form_id
*/
function image_form_alter(form, form_state, form_id) {
try {
// Make potential alterations to any entity edit form that has an image
// field element(s).
if (form.entity_type) {
var bundle = form.bundle;
var image_fields =
image_fields_present_on_entity_type(form.entity_type, bundle);
if (image_fields) {
// Attach the image field names to the form for later reference.
form.image_fields = image_fields;
// For each image field, create a place for it in the global var.
if ($.isArray(image_fields)) {
for (var index in image_fields) {
if (!image_fields.hasOwnProperty(index)) { continue; }
var name = image_fields[index];
image_phonegap_camera_options[name] = { 0: null };
}
}
}
}
}
catch (error) { console.log('image_form_alter - ' + error); }
}
/**
* Given an image style name and image uri, this will return the absolute URL
* that can be used as a src value for an img element.
* @param {String} style_name
* @param {String} path
* @return {String}
*/
function image_style_url(style_name, path) {
try {
// @TODO - bug: the trailing slash on public and private is breaking images
// that don't live in a sub directory in sites/default/files.
var src =
Drupal.settings.site_path + Drupal.settings.base_path + path;
if (src.indexOf('public://') != -1) {
src = src.replace(
'public://',
Drupal.settings.file_public_path +
'/styles/' +
style_name +
'/public/'
);
}
else if (src.indexOf('private://') != -1) {
src = src.replace(
'private://',
Drupal.settings.file_private_path +
'/styles/' +
style_name +
'/private/'
);
}
return src;
}
catch (error) { console.log('image_style_url - ' + error); }
}
/**
* The success callback function used when handling PhoneGap's camera
* getPicture() call.
* @param {Object} options
*/
function _image_phonegap_camera_getPicture_success(options) {
try {
// Hold on to the image options in the global var.
image_phonegap_camera_options[options.field_name] = {0: options};
// Hide the 'Add image' button and show the 'Upload' button.
//$('#' + options.id + '-button').hide();
//$('#' + options.id + '_upload').show();
// Show the captured photo as a thumbnail. When the photo is loaded, resize
// it to fit the content area, then show it.
var image_element_id = options.id + '-imagefield';
var image = document.getElementById(image_element_id);
image.src = 'data:image/jpeg;base64,' +
image_phonegap_camera_options[options.field_name][0].image;
image.onload = function() {
var width = this.width;
var height = this.height;
var ratio = width / drupalgap_max_width();
var new_width = width / ratio;
var new_height = height / ratio;
image.width = new_width;
image.height = new_height;
$('#' + image_element_id).show();
};
}
catch (error) {
console.log('_image_phonegap_camera_getPicture_success - ' + error);
}
}
/**
* An internal function used to upload images to the server, retreive their file
* id and then populate the corresponding form element's value with the file id.
* @param {Object} form
* @param {Object} form_state
* @param {Object} options
*/
function _image_field_form_process(form, form_state, options) {
try {
// @TODO needs mutli value field support (delta)
// @see https://www.drupal.org/node/2224803
var lng = language_default();
var processed_an_image = false;
// For each image field on the form...
for (var index in form.image_fields) {
if (!form.image_fields.hasOwnProperty(index)) { continue; }
var name = form.image_fields[index];
// Skip empty images and ones that already have their field id set.
if (!image_phonegap_camera_options[name][0] || form_state.values[name][lng][0] != '') { continue; }
// Create a unique file name using the UTC integer value.
var d = new Date();
var image_file_name = Drupal.user.uid + '_' + d.valueOf() + '.jpg';
// Build the data for the file create resource. If it's private, adjust the filepath.
var image_file_path = form.elements[name].field_info_instance.settings.file_directory;
if (image_file_path !== "") {
image_file_path += "/";
}
var file = {
file: {
file: image_phonegap_camera_options[name][0].image,
filename: image_file_name,
filepath: 'public://' + image_file_path + image_file_name
}
};
if (!empty(Drupal.settings.file_private_path)) {
file.file.filepath = 'private://' + image_file_path + image_file_name;
}
// Change the loader mode to saving, and save the file.
drupalgap.loader = 'saving';
processed_an_image = true;
file_save(file, {
async: false,
success: function(result) {
try {
// Set the hidden input and form state values with the file id.
var element_id = drupalgap_form_get_element_id(name, form.id);
$('#' + element_id).val(result.fid);
form_state.values[name][lng][0] = result.fid;
if (options.success) { options.success(); }
}
catch (error) {
console.log('_image_field_form_process - success - ' + error);
}
}
});
}
// If no images were processed, we need to continue onward anyway.
if (!processed_an_image && options.success) { options.success(); }
}
catch (error) { console.log('_image_field_form_validate - ' + error); }
}
/**
* Implements hook_assemble_form_state_into_field().
* @param {Object} entity_type
* @param {String} bundle
* @param {String} form_state_value
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Number} delta
* @param {Object} field_key
* @return {*}
*/
function image_assemble_form_state_into_field(entity_type, bundle,
form_state_value, field, instance, langcode, delta, field_key) {
try {
field_key.value = 'fid';
return form_state_value;
}
catch (error) {
console.log('image_assemble_form_state_into_field - ' + error);
}
}
/**
* Implements hook_block_view().
* @param {String} delta
* @param {Object} region
* @return {String}
*/
function menu_block_view(delta, region) {
// NOTE: When rendering a jQM data-role="navbar" you can't place an
// empty list (
) in it, this will cause an error:
// https://github.com/jquery/jquery-mobile/issues/5141
// So we must check to make sure we have any items before rendering the
// menu since our theme_item_list implementation returns empty lists
// for jQM pageshow async list item data retrieval and display.
try {
// Load the menu.
var menu = drupalgap.menus[delta];
// Since menu link paths may have an 'access_callback' handler that needs
// to make an async call to the server (e.g. local tasks), we'll utilize a
// pageshow handler to render the menu, so for now just render an empty
// placeholder and pageshow handler.
var container_id = menu_container_id(delta);
var data_role = null;
if (region.attributes && region.attributes['data-role']) {
data_role = region.attributes['data-role'];
}
// If the menu is wrapped, check to see if any wrap_options attributes were
// attached. If any were provided use them to build the div container
// attributes. We will always overwrite the container id though, since it is
// generated by the system (and used to dynamically inject the menu html).
var container_attributes = {};
if (
typeof menu.options !== 'undefined' &&
typeof menu.options.wrap !== 'undefined' &&
menu.options.wrap && menu.options.wrap_options &&
menu.options.wrap_options.attributes
) { container_attributes = menu.options.wrap_options.attributes; }
container_attributes.id = container_id;
return '' +
drupalgap_jqm_page_event_script_code({
page_id: drupalgap_get_page_id(),
jqm_page_event: 'pageshow',
jqm_page_event_callback: 'menu_block_view_pageshow',
jqm_page_event_args: JSON.stringify({
menu_name: delta,
container_id: container_id,
'data-role': data_role
})
}, delta);
}
catch (error) { console.log('menu_block_view - ' + error); }
}
/**
* The pageshow handler for menu blocks.
* @param {Object} options
*/
function menu_block_view_pageshow(options) {
try {
var html = '';
// Grab current path so we can watch out for any menu links that match it.
var path = drupalgap_path_get();
// Are we about to view a normal menu, or the local task menu?
var delta = options.menu_name;
if (delta == 'primary_local_tasks') {
// LOCAL TASKS MENU LINKS
// For the current page's router path, grab any local task menu links add
// them into the menu. Note, local tasks are located in a menu link item's
// children, or its parent's children (including itself), if there are
// any. Local tasks typically have argument wildcards in them, so we'll
// replace their wildcards with the current args.
var router_path = drupalgap_router_path_get();
if (drupalgap.menu_links[router_path]) {
// Determine the parent path, if any.
var parent = null;
if (drupalgap.menu_links[router_path].parent) {
parent = drupalgap.menu_links[router_path].parent;
}
// Then extract the local tasks paths array.
var local_tasks = null;
if (drupalgap.menu_links[router_path].children) {
local_tasks = drupalgap.menu_links[router_path].children;
}
else if (
parent && drupalgap.menu_links[parent] &&
drupalgap.menu_links[parent].children
) { local_tasks = drupalgap.menu_links[parent].children; }
var args = arg();
// Define a success callback that will be called later on...
var _success = function(result) {
try {
var menu_items = [];
var link_path = '';
if (local_tasks && !empty(local_tasks)) {
for (var index in local_tasks) {
if (!local_tasks.hasOwnProperty(index)) { continue; }
var local_task = local_tasks[index];
if (drupalgap.menu_links[local_task] && (
drupalgap.menu_links[local_task].type ==
'MENU_DEFAULT_LOCAL_TASK' ||
drupalgap.menu_links[local_task].type ==
'MENU_LOCAL_TASK'
)) {
if (drupalgap_menu_access(local_task, null, result)) {
menu_items.push(drupalgap.menu_links[local_task]);
}
}
}
}
// If there was only one local task menu item, and it is the default
// local task, don't render the menu, otherwise render the menu as
// an item list as long as there are items to render.
if (menu_items.length == 1 &&
menu_items[0].type == 'MENU_DEFAULT_LOCAL_TASK'
) { html = ''; }
else {
var items = [];
for (var index in menu_items) {
if (!menu_items.hasOwnProperty(index)) { continue; }
var item = menu_items[index];
// Make a deep copy of the menu link so we don't modify it.
var link = jQuery.extend(true, {}, item);
// If there are no link options, set up defaults.
if (!link.options) { link.options = { attributes: { } }; }
else if (!link.options.attributes) {
link.options.attributes = { };
}
// If the link points to the current path, set it as active.
// We first need to figure out which path to check, by default
// use the link path, but if its a default local task, use its
// parent path.
var path_to_check = link.path;
if (link.type == 'MENU_DEFAULT_LOCAL_TASK' && link.parent) {
path_to_check = link.parent;
link.path = arg(null, link.parent).join('/');
}
if (path_to_check == router_path) {
if (!link.options.attributes['class']) {
link.options.attributes['class'] = '';
}
link.options.attributes['class'] +=
' ui-btn ui-btn-active ui-state-persist ';
}
items.push(
l(
link.title,
drupalgap_place_args_in_path(link.path),
link.options
)
);
}
if (items.length > 0) {
html = theme('item_list', {'items': items});
}
}
// Inject the html.
$('#' + options.container_id).html(html).trigger('create');
// If the block's region is a jQM navbar, refresh the navbar.
if (options['data-role'] && options['data-role'] == 'navbar') {
$('#' + options.container_id).navbar();
}
// Optionally remove the placeholder wrapper.
var menu = drupalgap.menus[options.menu_name];
if (
typeof menu.options !== 'undefined' &&
(typeof menu.options.wrap === 'undefined' || !menu.options.wrap)
) { $('#' + options.container_id).children().unwrap(); }
}
catch (error) {
console.log('menu_block_view_pageshow - success - ' + error);
}
};
// First, determine if any child has an entity arg in the path, and/or
// an access_callback handler.
var has_entity_arg = false;
var has_access_callback = false;
if (local_tasks) {
for (var index in local_tasks) {
if (!local_tasks.hasOwnProperty(index)) { continue; }
var local_task = local_tasks[index];
if (drupalgap.menu_links[local_task] &&
(
drupalgap.menu_links[local_task].type ==
'MENU_DEFAULT_LOCAL_TASK' ||
drupalgap.menu_links[local_task].type ==
'MENU_LOCAL_TASK'
)
) {
if (drupalgap_path_has_entity_arg(arg(null, local_task))) {
has_entity_arg = true;
}
if (
typeof
drupalgap.menu_links[local_task].access_callback !==
'undefined'
) { has_access_callback = true; }
}
}
}
// If we have an entity arg, and an access_callback, let's load up the
// entity asynchronously.
if (has_entity_arg && has_access_callback) {
var found_int_arg = false;
var int_arg_index = null;
for (var i = 0; i < args.length; i++) {
if (is_int(parseInt(args[i]))) {
// Save the arg index so we can replace it later.
int_arg_index = i;
found_int_arg = true;
break;
}
}
if (!found_int_arg) { _success(null); return; }
// Determine the naming convention for the entity load function.
var load_function_prefix = args[0]; // default
if (args[0] == 'taxonomy') {
if (args[1] == 'vocabulary' || args[1] == 'term') {
load_function_prefix = args[0] + '_' + args[1];
}
}
var load_function = load_function_prefix + '_load';
// If the load function exists, load the entity.
if (function_exists(load_function)) {
var entity_fn = window[load_function];
// Load the entity. MVC items need to pass along the module name and
// model type to its load function. All other entity load functions
// just need the entity id.
var entity_id = parseInt(args[int_arg_index]);
if (args[0] == 'item') {
entity = entity_fn(args[1], args[2], entity_id);
_success(entity);
}
else {
// Force a reset if we are editing the entity.
var reset = false;
if (arg(2) == 'edit') { reset = true; }
// Load the entity asynchronously.
entity_fn(entity_id, { reset: reset, success: _success });
}
}
else {
console.log('menu_block_view_pageshow - load function not ' +
'implemented! ' + load_function
);
}
}
else { _success(null); }
}
}
else {
// ALL OTHER MENU LINKS
// If the block's corresponding menu exists, and it has links, iterate
// over each link, add it to an items array, then theme an item list.
var menu = false;
if (drupalgap.menus[delta] && drupalgap.menus[delta].links) {
menu = drupalgap.menus[delta];
var items = [];
for (var index in menu.links) {
if (!menu.links.hasOwnProperty(index)) { continue; }
var menu_link = menu.links[index];
// Make a deep copy of the menu link so we don't modify it.
var link = jQuery.extend(true, {}, menu_link);
// If there are no link options, set up defaults.
if (!link.options) { link.options = {attributes: {}}; }
else if (!link.options.attributes) { link.options.attributes = {}; }
if (!link.options.attributes['class']) {
link.options.attributes['class'] = '';
}
// Extract the link's class attribute.
var class_names = link.options.attributes['class'];
// If the link points to the current path, set it as active.
if (link.path == path) {
if (class_names.indexOf('ui-btn') == -1) {
class_names += ' ui-btn';
}
if (class_names.indexOf('ui-btn-active') == -1) {
class_names += ' ui-btn-active';
}
if (class_names.indexOf('ui-state-persist') == -1) {
class_names += ' ui-state-persist';
}
}
// If there was a data-icon attibute on the link, let's add its
// equivalent css class name to the link (if it isn't already
// present), otherwise jQM won't render the icon properly. Sounds
// like a jQM bug.
if (
link.options.attributes['data-icon'] &&
class_names.indexOf(link.options.attributes['data-icon']) == -1
) {
class_names +=
' ui-icon-' + link.options.attributes['data-icon'] + ' ';
}
// Finally toss the class attribute back on the link and add the
// link to the items array.
link.options.attributes['class'] = class_names + ' ';
items.push(l(t(link.title), link.path, link.options));
}
if (items.length > 0) {
// Pass along any menu attributes.
var attributes = null;
if (menu.options && menu.options.attributes) {
attributes = drupalgap.menus[delta].options.attributes;
}
html = theme('item_list', {'items': items, 'attributes': attributes});
}
}
// Inject the html.
$('#' + options.container_id).html(html).trigger('create');
// Remove the placeholder wrapper, unless we were instructed not to.
var wrap = false;
if (
menu && typeof menu.options !== 'undefined' &&
typeof menu.options.wrap !== 'undefined' && menu.options.wrap
) { wrap = true; }
if (!wrap) { $('#' + options.container_id).children().unwrap(); }
}
}
catch (error) { console.log('menu_block_view_pageshow - ' + error); }
}
/**
* Implements hook_install().
*/
function menu_install() {
try {
// Grab the list of system menus and save each.
var system_menus = menu_list_system_menus();
for (var menu_name in system_menus) {
if (!system_menus.hasOwnProperty(menu_name)) { continue; }
var menu = system_menus[menu_name];
menu_save(menu);
}
}
catch (error) { console.log('menu_install - ' + error); }
}
/**
* Returns a JSON object that can be used as default options for a menu object.
* @return {Object}
*/
function menu_popup_get_default_options() {
return {
attributes: {
'data-role': 'listview'
},
wrap: true,
wrap_options: {
attributes: {
'data-role': 'popup'
}
}
};
}
/**
* Given a menu region link, this will return its data JSON object, or null if
* no data exists.
* @param {Object} region_link
* @return {*)
*/
function menu_region_link_get_data(region_link) {
try {
// Extract the data associated with this link. If it has a 'region'
// property then it is coming from a hook_menu, if it doesn't then it
// is coming from settings.js.
var data = null;
if (typeof region_link.region === 'undefined') {
data = region_link; // link defined in settings.js
// @TODO - we need to warn people that they can't make a custom menu
// with a machine name of 'regions' now that this machine name is a
// "system" name for rendering links in regions.
}
// link defined via hook_menu()
else { data = region_link.region; }
return data;
}
catch (error) { console.log('menu_region_link_get_data - ' + error); }
}
/**
* Given a menu region link's class name, this will return what side of the ui
* it is on, returns left by default, unless it specifically contains the
* ui-btn-right class.
* @param {String} class_name
* @return {String)
*/
function menu_region_link_get_side(class_name) {
try {
var side = 'left';
if (class_name.indexOf('ui-btn-right') != -1) { side = 'right'; }
return side;
}
catch (error) { console.log('menu_region_link_get_side - ' + error); }
}
/**
* Given a menu, this adds it to drupalgap.menus. See menu_list_system_menus
* for examples of a menu JSON object.
* @param {Object} menu
*/
function menu_save(menu) {
try {
drupalgap.menus[menu.menu_name] = menu;
}
catch (error) { console.log('menu_save - ' + error); }
}
/**
* Given a menu name, this will return it from drupalgap.menus, or return null
* if it doesn't exist.
* @param {String} name
* @return {*}
*/
function menu_load(name) {
try {
if (typeof drupalgap.menus[name] !== 'undefined') {
return drupalgap.menus[name];
}
return null;
}
catch (error) { console.log('menu_load - ' + error); }
}
/**
* Given a menu name, this will return its container id for that page. You may
* optionally pass in a page id as the second argument, otherwise it will use
* the current page id.
* @param {String} menu_name
* @return {String}
*/
function menu_container_id(menu_name) {
try {
var page_id = null;
if (arguments[1]) { page_id = arguments[1]; }
else { page_id = drupalgap_get_page_id(); }
return page_id + '_menu_' + menu_name;
}
catch (error) { console.log('menu_container_id - ' + error); }
}
/**
* The page callback for mvc/collection/list/%/%.
* @param {String} module
* @param {String} type
* @return {Object}
*/
function collection_list_page(module, type) {
try {
var content = {
'collection_list': {
'theme': 'jqm_item_list',
'title': t('Collection')
}
};
var items = [];
var collection = collection_load(module, type);
if (collection) {
for (var id in collection) {
if (!collection.hasOwnProperty(id)) { continue; }
var item = collection[id];
var path = 'mvc/item/' + module + '/' + type + '/' + id;
items.push(l(item.name, path));
}
content.collection_list.items = items;
}
return content;
}
catch (error) { console.log('collection_list_page - ' + error); }
}
/**
* Given a module name and model type, this will return the collection
* JSON object.
* @param {String} module
* @param {String} type
* @return {*}
*/
function collection_load(module, type) {
try {
return JSON.parse(
window.localStorage.getItem(
mvc_get_collection_key('collection', module, type)
)
);
}
catch (error) { console.log('collection_load - ' + error); }
}
/**
* Given a module name, model type and item collection, this will save the
* collection JSON object to local storage.
* @param {String} module
* @param {String} type
* @param {Object} collection
*/
function collection_save(module, type, collection) {
try {
window.localStorage.setItem(
mvc_get_collection_key('collection', module, type),
JSON.stringify(collection)
);
}
catch (error) { console.log('collection_save - ' + error); }
}
/**
* Given a bucket (e.g. collection, settings), module name and mvc model type,
* this will return the local storage key used for the model type's item
* collection.
* @param {String} bucket
* @param {String} module
* @param {String} model_type
* @return {String}
*/
function mvc_get_collection_key(bucket, module, model_type) {
return 'mvc_' + bucket + '_' + module + '_' + model_type;
}
/**
* Implements hook_install().
*/
function mvc_install() {
try {
// Load models...
// For each module that implements hook_mvc_model(), iterate over each model
// placing it into drupalgap.mvc.models, each model will be placed into
// a namespace according to the module that implements it, to avoid
// namespace collisions.
var modules = module_implements('mvc_model');
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
var models = module_invoke(module, 'mvc_model');
if (models) {
// Create namespace for model, keyed by module name.
if (!drupalgap.mvc.models[module]) {
drupalgap.mvc.models[module] = {};
}
// For each model type...
for (var model_type in models) {
if (!models.hasOwnProperty(model_type)) { continue; }
var model = models[model_type];
// Set the primary key 'id', the module name, and model type
// on the model fields. These are the mvc_model_system_fields().
model.fields.id = {
'type': 'hidden',
'title': t('ID'),
'required': false
};
model.fields.module = {
'type': 'hidden',
'title': t('Module'),
'required': true,
'default_value': module
};
model.fields.type = {
'type': 'hidden',
'title': t('Model Type'),
'required': true,
'default_value': model_type
};
// Add each model type to its namespace within drupalgap.mvc.models
drupalgap.mvc.models[module][model_type] = model;
// Save an empty collection to local storage for this model type, if
// one doesn't already exist.
var collection_key = mvc_get_collection_key(
'collection',
module,
model_type
);
if (!window.localStorage.getItem(collection_key)) {
window.localStorage.setItem(collection_key, '[]');
// Save settings for the collection to local storage. The auto
// increment value represents an item id, we start counting at
// zero since the collection is an array.
window.localStorage.setItem(
mvc_get_collection_key('settings', module, model_type),
'{"auto_increment":0}'
);
}
}
}
}
//console.log(JSON.stringify(drupalgap.mvc.models));
//drupalgap_alert('drupalgap_mvc_init');
// These may not be needed. Perhaps we should just call assumed hooks that
// should be implemented for any custom views and controllers at the time
// they are needed, probably no need to bundle them inside drupalgap.mvc.
//var views = module_invoke_all('mvc_view');
//var controllers = module_invoke_all('mvc_controller');
}
catch (error) { console.log('mvc_install - ' + error); }
}
/**
* Implements hook_menu().
* @return {Object}
*/
function mvc_menu() {
var items = {
'mvc/collection/list/%/%': {
'page_callback': 'collection_list_page',
'page_arguments': [3, 4]
},
'mvc/item/%/%/%': {
'page_callback': 'item_view_page',
'page_arguments': [2, 3, 4]
},
'mvc/item-add/%/%': {
title: t('Add'),
page_callback: 'drupalgap_get_form',
page_arguments: ['item_create_form', 2, 3]
}
};
return items;
}
/**
* Returns an array of system fields (properties) to be used on item JSON
* object.
* @return {Array}
*/
function mvc_model_system_fields() {
return ['id', 'module', 'type'];
}
// We'll need developer friendly front end functions, e.g.
// model_load();
// model_save();
// model_delete();
// item_load();
// item_save();
// item_delete();
// etc...
/**
* Given a module name and a corresponding model name, this will load the model
* from drupalgap.mvc.models.
* @param {String} module
* @param {String} name
* @return {Object}
*/
function model_load(module, name) {
try {
if (
drupalgap.mvc.models[module] === 'undefined' ||
drupalgap.mvc.models[module][name] === 'undefined'
) { return false; }
return drupalgap.mvc.models[module][name];
}
catch (error) { console.log('model_load - ' + error); }
}
/**
* Given a module type, and model type, this generates and returns the form JSON
* object to create a model item.
* @param {Object} form
* @param {Object} form_state
* @param {String} module
* @param {String} type
* @return {Object}
*/
function item_create_form(form, form_state, module, type) {
try {
//var form = drupalgap_form_defaults('item_create_form');
var model = model_load(module, type);
if (model) {
// @todo - this could be dangerous just overriding the elements variable,
// we should iterate over the model fields and add them one by one
// instead.
form.elements = model.fields;
form.buttons.cancel = drupalgap_form_cancel_button();
form.elements.submit = {
type: 'submit',
value: t('Create')
};
}
return form;
}
catch (error) { console.log('item_create_form - ' + error); }
}
/**
* Handles the submission of an mvc model item creation form.
* @param {Object} form
* @param {Object} form_state
*/
function item_create_form_submit(form, form_state) {
try {
// Save the item and then view it.
if (item_save(form_state.values)) {
/*var path = 'item/' +
form_state.values.module + '/' +
form_state.values.type + '/' +
form_state.values.id;*/
var path = 'mvc/collection/list/' +
form_state.values.module + '/' +
form_state.values.type;
// If there is a form action path set, use that instead.
if (form.action) { path = form.action; }
// Go to our destination path, and force a reload on the page.
drupalgap_goto(path, {reloadPage: true});
}
else {
var msg = 'item_create_form_submit - failed to save item!';
drupalgap_alert(msg);
}
}
catch (error) {
console.log('item_create_form_submit - ' + error);
}
}
/**
* Given a module name, mvc model type, and item id, this will return the item,
* or false if the item fails to load.
* @param {String} module
* @param {String} type
* @param {String} id
* @return {Object}
*/
function item_load(module, type, id) {
try {
var item = false;
var collection = collection_load(module, type);
if (collection && typeof collection[id] !== 'undefined') {
item = collection[id];
}
return item;
}
catch (error) { console.log('item_load - ' + error); }
}
/**
* Given an mvc item, this saves it to local storage.
* @param {Object} item
* @return {Boolean}
*/
function item_save(item) {
try {
if (typeof item === 'undefined') { return false; }
// Grab the settings for this collection.
var settings_key = mvc_get_collection_key(
'settings',
item.module,
item.type
);
var settings = JSON.parse(window.localStorage.getItem(settings_key));
// If there is no id, then this is a new item, grab the next id to use.
if (!item.id) {
item.id = settings.auto_increment;
}
// Load the collection from local storage.
var collection = collection_load(item.module, item.type);
// Add the item onto the collection.
collection[item.id] = item;
// Save the collection to local storage.
collection_save(item.module, item.type, collection);
// Increment to the next item id and save the settings.
settings.auto_increment = item.id + 1;
window.localStorage.setItem(settings_key, JSON.stringify(settings));
return true;
}
catch (error) { console.log('item_save - ' + error); }
}
/**
* The page callback for mvc/item/%/%/%.
* @param {String} module
* @param {String} type
* @param {Object} item
* @return {String}
*/
function item_view_page(module, type, item) {
try {
var html = '';
var model = model_load(module, type);
if (model) { html = theme('item', {'model': model, 'item': item}); }
return html;
}
catch (error) { console.log('item_view_page - ' + error); }
}
/**
* Given a node, this determines if the current user has access to it. Returns
* true if so, false otherwise. This function implementation is incomplete, use
* with caution.
* @param {Object} node
* @return {Boolean}
*/
function node_access(node) {
try {
if (
(
node.uid == Drupal.user.uid &&
user_access('edit own ' + node.type + ' content')
) ||
user_access('edit any ' + node.type + ' content')
) { return true; }
else { return false; }
}
catch (error) { console.log('node_access - ' + error); }
}
/**
* Page call back for node/add.
* @return {Object}
*/
function node_add_page() {
try {
var content = {
header: { markup: '
' + t('Create Content') + '
' },
node_type_listing: {
theme: 'jqm_item_list',
title: t('Content Types'),
attributes: { id: 'node_type_listing_items' }
}
};
var items = [];
var user_permissions = Drupal.user.content_types_user_permissions;
for (var type in user_permissions) {
if (!user_permissions.hasOwnProperty(type)) { continue; }
var permissions = user_permissions[type];
if (permissions.create) {
items.push(l(drupalgap.content_types_list[type].name,
'node/add/' + type));
}
}
content.node_type_listing.items = items;
return content;
}
catch (error) { console.log('node_add_page - ' + error); }
}
/**
* Page call back function for node/add/[type].
* @param {String} type
* @return {Object}
*/
function node_add_page_by_type(type) {
try {
return drupalgap_get_form('node_edit', {'type': type});
}
catch (error) { console.log('node_add_page_by_type - ' + error); }
}
/**
* Title call back function for node/add/[type].
* @param {Function} callback
* @param {String} type
* @return {Object}
*/
function node_add_page_by_type_title(callback, type) {
try {
var title = t('Create') + ' ' + drupalgap.content_types_list[type].name;
return callback.call(null, title);
}
catch (error) { console.log('node_add_page_by_type_title - ' + error); }
}
/**
* The node edit form.
* @param {Object} form
* @param {Object} form_state
* @param {Object} node
* @return {Object}
*/
function node_edit(form, form_state, node) {
try {
// Setup form defaults.
form.entity_type = 'node';
form.bundle = node.type;
// Add the entity's core fields to the form.
drupalgap_entity_add_core_fields_to_form('node', node.type, form, node);
// Add the fields for this content type to the form.
drupalgap_field_info_instances_add_to_form('node', node.type, form, node);
// Add submit to form.
form.elements.submit = {
'type': 'submit',
'value': t('Save')
};
// Add cancel button to form.
form.buttons['cancel'] = drupalgap_form_cancel_button();
// Add delete button to form if we're editing a node.
if (node && node.nid) {
form.buttons['delete'] =
drupalgap_entity_edit_form_delete_button('node', node.nid);
}
return form;
}
catch (error) { console.log('node_edit - ' + error); }
}
/**
* The node edit form's submit function.
* @param {Object} form
* @param {Object} form_state
*/
function node_edit_submit(form, form_state) {
try {
var node = drupalgap_entity_build_from_form_state(form, form_state);
drupalgap_entity_form_submit(form, form_state, node);
}
catch (error) { console.log('node_edit_submit - ' + error); }
}
/**
* Implements hook_menu().
* @return {Object}
*/
function node_menu() {
var items = {
'node': {
'title': t('Content'),
'page_callback': 'node_page',
'pageshow': 'node_page_pageshow'
},
'node/add': {
'title': t('Add content'),
'page_callback': 'node_add_page'
},
'node/add/%': {
title: t('Add content'),
title_callback: 'node_add_page_by_type_title',
title_arguments: [2],
page_callback: 'node_add_page_by_type',
page_arguments: [2],
options: { reloadPage: true }
},
'node/%': {
'title': t('Node'),
'page_callback': 'node_page_view',
'page_arguments': [1],
'pageshow': 'node_page_view_pageshow',
'title_callback': 'node_page_title',
'title_arguments': [1]
},
'node/%/view': {
'title': t('View'),
'type': 'MENU_DEFAULT_LOCAL_TASK',
'weight': -10
},
'node/%/edit': {
'title': t('Edit'),
'page_callback': 'entity_page_edit',
'pageshow': 'entity_page_edit_pageshow',
'page_arguments': ['node_edit', 'node', 1],
'weight': 0,
'type': 'MENU_LOCAL_TASK',
'access_callback': 'node_access',
'access_arguments': [1],
options: {reloadPage: true}
}
};
return items;
}
/**
* Page callback for node.
* @return {Object}
*/
function node_page() {
var content = {
'create_content': {
'theme': 'button_link',
'path': 'node/add',
'text': t('Create Content')
},
'node_listing': {
'theme': 'jqm_item_list',
'title': t('Content List'),
'items': [],
'attributes': {'id': 'node_listing_items'}
}
};
return content;
}
/**
* The jQM pageshow callback for the node listing page.
*/
function node_page_pageshow() {
try {
// Grab some recent content and display it.
views_datasource_get_view_result(
'drupalgap/views_datasource/drupalgap_content', {
success: function(content) {
// Extract the nodes into items, then drop them in the list.
var items = [];
for (var index in content.nodes) {
if (!content.nodes.hasOwnProperty(index)) { continue; }
var object = content.nodes[index];
items.push(l(object.node.title, 'node/' + object.node.nid));
}
drupalgap_item_list_populate('#node_listing_items', items);
}
}
);
}
catch (error) { console.log('node_page_pageshow - ' + error); }
}
/**
* Page callback for node/%.
* @param {Number} nid
* @return {Object}
*/
function node_page_view(nid) {
try {
if (nid) {
var content = {
container: _drupalgap_entity_page_container('node', nid, 'view')
};
return content;
}
else { drupalgap_error(t('No node id provided!')); }
}
catch (error) { console.log('node_page_view - ' + error); }
}
/**
* jQM pageshow handler for node/% pages.
* @param {Number} nid
*/
function node_page_view_pageshow(nid) {
try {
node_load(nid, {
success: function(node) {
// By this point the node's content has been assembled into an html
// string. This is because when a node is retrieved from the server,
// we use a services post processor to render its content.
// @see entity_services_request_pre_postprocess_alter()
// Does anyone want to take over the rendering of this content type?
// Any implementors of hook_node_page_view_alter_TYPE()?
// @TODO this should probably be moved up to the entity level.
var hook = 'node_page_view_alter_' + node.type;
var modules = module_implements(hook);
if (modules.length > 0) {
if (modules.length > 1) {
var msg = 'node_page_view_pageshow - WARNING - there is more ' +
'than one module implementing hook_' + hook + '(), we will ' +
'use the first one: ' + modules[0];
console.log(msg);
}
var function_name = modules[0] + '_' + hook;
var fn = window[function_name];
fn(node, {
success: function(content) {
_drupalgap_entity_page_container_inject(
'node', node.nid, 'view', content
);
}
});
return;
}
// Build a done handler which will inject the given build into the page container. If there was a success
// callback attached to the page options call it.
var done = function(build) {
_drupalgap_entity_page_container_inject(
'node', node.nid, 'view', build
);
if (drupalgap.page.options.success) { drupalgap.page.options.success(node); }
};
// Figure out the title, and watch for translation.
var default_language = language_default();
var node_title = node.title;
if (node.title_field && node.title_field[default_language]) {
node_title = node.title_field[default_language][0].safe_value;
}
// Build the node display. Set the node onto the build so it makes it to the theme layer variables.
var build = {
'theme': 'node',
'node': node,
// @todo - this is a core field and should by fetched from entity.js
'title': { markup: node_title },
'content': { markup: node.content }
};
// If comments are undefined, just inject the page.
if (typeof node.comment === 'undefined') { done(build); }
// If the comments are closed (1) or open (2), show the comments.
else if (node.comment != 0) {
if (node.comment == 1 || node.comment == 2) {
// Render the comment form, so we can add it to the content later.
var comment_form = '';
if (node.comment == 2) {
comment_form = drupalgap_get_form(
'comment_edit',
{ nid: node.nid },
node
);
}
// If there are any comments, load them.
if (node.comment_count != 0) {
var query = {
parameters: {
nid: node.nid
}
};
comment_index(query, {
success: function(results) {
try {
// Render the comments.
var comments = '';
for (var index in results) {
if (!results.hasOwnProperty(index)) { continue; }
var comment = results[index];
comments += theme('comment', { comment: comment });
}
build.content.markup += theme('comments', {
node: node,
comments: comments
});
// If the comments are open, show the comment form.
if (node.comment == 2 && user_access('post comments')) {
build.content.markup += comment_form;
}
// Finally, inject the page.
done(build);
}
catch (error) {
var msg = 'node_page_view_pageshow - comment_index - ' +
error;
console.log(msg);
}
},
error: function(xhr, status, msg) {
if (drupalgap.page.options.error) { drupalgap.page.options.error(xhr, status, msg); }
}
});
}
else {
// There weren't any comments, append an empty comments wrapper
// and show the comment form if comments are open, then inject
// the page.
if (node.comment == 2) {
build.content.markup += theme('comments', { node: node });
if (user_access('post comments')) { build.content.markup += comment_form; }
}
done(build);
}
}
}
else {
// Comments are hidden (0), append an empty comments wrapper to the
// content and inject the content into the page.
build.content.markup += theme('comments', { node: node });
done(build);
}
},
error: function(xhr, status, msg) {
if (drupalgap.page.options.error) { drupalgap.page.options.error(xhr, status, msg); }
}
});
}
catch (error) { console.log('node_page_view_pageshow - ' + error); }
}
/**
* The title call back function for the node view page.
* @param {Function} callback
* @param {Number} nid
*/
function node_page_title(callback, nid) {
try {
// Try to load the node title, then send it back to the given callback.
var title = '';
var node = node_load(nid, {
success: function(node) {
if (node && node.title) { title = node.title; }
callback.call(null, title);
}
});
}
catch (error) { console.log('node_page_title - ' + error); }
}
/**
* Implements hook_theme().
* @return {Object}
*/
function node_theme() {
return { node: { template: 'node' } };
}
/**
* Implements hook_block_info().
* @return {Object}
*/
function search_block_info() {
try {
var blocks = {};
blocks['search'] = {
delta: 'search',
module: 'search'
};
return blocks;
}
catch (error) { console.log('search_block_info - ' + error); }
}
/**
* Implements hook_block_view().
* @param {String} delta
* @param {String} region
* @return {String}
*/
function search_block_view(delta, region) {
try {
var content = '';
if (delta == 'search') {
if (user_access('search content')) {
content = drupalgap_get_form('search_block_form');
}
}
return content;
}
catch (error) { console.log('search_block_view - ' + error); }
}
/**
* Implements hook_menu().
* @return {Object}
*/
function search_menu() {
try {
var items = {};
items['search/%/%'] = {
title: t('Search'),
'page_callback': 'drupalgap_get_form',
'pageshow': 'search_form_pageshow',
'page_arguments': ['search_form'],
'access_arguments': ['search content']
};
return items;
}
catch (error) { console.log('search_menu - ' + error); }
}
/**
* The search block form.
* @param {Object} form
* @param {Object} form_state
* @return {Object}
*/
function search_block_form(form, form_state) {
try {
form.elements['type'] = {
type: 'hidden',
default_value: 'node'
};
form.elements['keys'] = {
type: 'search',
title: '',
title_placeholder: true,
required: true,
default_value: ''
};
// Since there is no submit button on the form, we'll catch the onsubmit
// action and the trigger the form submission.
form.options.attributes['onsubmit'] =
"_drupalgap_form_submit('" + form.id + "'); return false;";
return form;
}
catch (error) { console.log('search_block_form - ' + error); }
}
/**
* The search block form submit handler.
* @param {Object} form
* @param {Object} form_state
*/
function search_block_form_submit(form, form_state) {
try {
var type = form_state.values['type'];
var keys = form_state.values['keys'];
drupalgap_goto('search/' + type + '/' + keys);
}
catch (error) { console.log('search_block_form_submit - ' + error); }
}
/**
* The search form.
* @param {Object} form
* @param {Object} form_state
* @return {Object}
*/
function search_form(form, form_state) {
try {
var type = arg(1);
var keys = arg(2);
form.elements.type = {
type: 'hidden',
default_value: type ? type : 'node'
};
form.elements.keys = {
type: 'textfield',
title: t('Enter your keywords'),
required: true,
default_value: keys ? keys : ''
};
form.elements.submit = {
type: 'submit',
value: t('Go'),
options: {
attributes: {
'data-icon': 'search'
}
}
};
form.suffix += theme('jqm_item_list', {
title: t('Search results'),
items: [],
options: {
attributes: {
id: 'search_form_results'
}
}
});
return form;
}
catch (error) { console.log('search_form - ' + error); }
}
/**
* The search form submit handler.
* @param {Object} form
* @param {Object} form_state
*/
function search_form_submit(form, form_state) {
try {
var type = form_state.values['type'];
var keys = form_state.values['keys'];
switch (type) {
case 'node':
search_node(keys, {
success: function(results) {
var items = [];
for (var index in results) {
if (!results.hasOwnProperty(index)) { continue; }
var result = results[index];
var link = theme('search_result_node', result);
items.push(link);
}
drupalgap_item_list_populate('#search_form_results', items);
}
});
break;
default:
console.log('search_form_submit - unsupported type (' + type + ')');
break;
}
}
catch (error) { console.log('search_form_submit - ' + error); }
}
/**
* The pageshow callback for the search form page.
* @param {String} form_id
*/
function search_form_pageshow(form_id) {
try {
var type = arg(1);
var keys = arg(2);
switch (type) {
case 'node':
search_node(keys, {
success: function(results) {
var items = [];
for (var index in results) {
if (!results.hasOwnProperty(index)) { continue; }
var result = results[index];
var link = theme('search_result_node', result);
items.push(link);
}
drupalgap_item_list_populate('#search_form_results', items);
}
});
break;
default:
console.log('search_form_pageshow - unsupported type (' + type + ')');
break;
}
}
catch (error) { console.log('search_form_pageshow - ' + error); }
}
/**
* The search node service.
* @param {String} keys The keyword(s) to search for.
* @param {Object} options
*/
function search_node(keys, options) {
try {
options.method = 'GET';
options.path = 'search_node/retrieve.json&keys=' + encodeURIComponent(keys);
options.service = 'search_node';
options.resource = 'retrieve';
Drupal.services.call(options);
}
catch (error) { console.log('search_node - ' + error); }
}
/**
* Themes a search result node.
* @param {Object} variables
* @return {String}
*/
function theme_search_result_node(variables) {
try {
return l(
'
' + variables.title + '
' +
'
' + variables.snippet + '
',
'node/' + variables.node.nid
);
}
catch (error) { console.log('theme_search_result_node - ' + error); }
}
/**
* Given an entity type, this will return its corresponding service resource, or
* null if the resource doesn't exist.
* @param {String} entity_type
* @return {?Type|Object|null}
*/
function drupalgap_services_get_entity_resource(entity_type) {
try {
console.log(
'WARNING: drupalgap_services_get_entity_resource() is deprecated! ' +
'Use services_get_resource_function_for_entity() instead.'
);
// @todo - deprecate this function, it is no longer needed now that entity
// c.r.u.d. is built into jDrupal.
if (drupalgap.services[entity_type]) {
return drupalgap.services[entity_type];
}
else { return null; }
}
catch (error) {
console.log('drupalgap_services_get_entity_resource - ' + error);
}
}
/**
* Returns the name of the jDrupal function to be used when in need of an entity
* C.R.U.D. operation.
* @param {String} entity_type
* @param {String} crud
* @return {String}
*/
function services_get_resource_function_for_entity(entity_type, crud) {
var name = entity_type + '_';
switch (crud) {
case 'create': name += 'save'; break;
case 'retrieve': name += 'load'; break;
case 'update': name += 'save'; break;
case 'delete': name += 'delete'; break;
default: name += 'load'; break;
}
return name;
}
/**
* Given a json drupalgap options array from a service resource results call,
* this extracts data based on the resource and populates necessary global vars.
* @param {Object} options
*/
function drupalgap_service_resource_extract_results(options) {
try {
if (options.service == 'system' && options.resource == 'connect') {
// The system connect resource's success function places what is in
// options.data.user to overwrite Drupal.user, so anything we want in
// the Drupal.user object must be added to options.data.user isntead.
// Extract and build the user's permissions.
options.data.user.permissions = [];
var permissions = options.data.user_permissions;
for (var permission in permissions) {
options.data.user.permissions.push(permissions[permission]);
}
// Pull out the content types, and set them by their type.
var content_types_list = options.data.content_types_list;
for (var index in content_types_list) {
if (!content_types_list.hasOwnProperty(index)) { continue; }
var object = content_types_list[index];
drupalgap.content_types_list[object.type] = object;
}
// Pull out the content types user permissions.
options.data.user.content_types_user_permissions =
options.data.content_types_user_permissions;
// Pull out the site settings.
drupalgap.site_settings = options.data.site_settings;
// Pull out the date formats and types.
if (typeof options.data.date_formats !== 'undefined') {
drupalgap.date_formats = options.data.date_formats;
}
if (typeof options.data.date_types !== 'undefined') {
drupalgap.date_types = options.data.date_types;
}
}
}
catch (error) {
console.log('drupalgap_service_resource_extract_results - ' + error);
}
}
/**
* Given the result of a drupalgap.services.rss.retrieve.call, this will iterate
* over the RSS items and assemble them into a nice array of JSON objects and
* return them. Returns null if it fails.
* @param {Object} data
* @return {?Type|Array|null}
*/
function drupalgap_services_rss_extract_items(data) {
try {
var items = null;
var $xml = $(data);
if ($xml) {
// Extract the feeds items, then drop them in the list.
var items = [];
$xml.find('item').each(function() {
var $this = $(this), item = {
title: $this.find('title').text(),
link: $this.find('link').text(),
description: $this.find('description').text(),
pubDate: $this.find('pubDate').text(),
author: $this.find('author').text()
};
items.push(item);
});
}
return items;
}
catch (error) {
console.log('drupalgap_services_rss_extract_items - ' + error);
}
}
var _system_reload_page = null;
var _system_reload_messages = null;
/**
* Implements hook_install().
*/
function system_install() {
// Remove any old forms from local storage, then purge the form expiration tracker.
for (var form_id in Drupal.cache_expiration.forms) {
if (!Drupal.cache_expiration.forms.hasOwnProperty(form_id)) { continue; }
drupalgap_form_local_storage_delete(form_id);
}
Drupal.cache_expiration.forms = {};
window.localStorage.setItem('cache_expiration', JSON.stringify(Drupal.cache_expiration));
}
/**
* Implements hook_block_info().
* @return {Object}
*/
function system_block_info() {
// System blocks.
var blocks = {
'main': {
'delta': 'main',
'module': 'system'
},
messages: {
delta: 'messages',
module: 'system'
},
'logo': {
'delta': 'logo',
'module': 'system'
},
logout: {
delta: 'logout',
module: 'system'
},
'title': {
'delta': 'title',
'module': 'system'
},
'powered_by': {
'delta': 'powered_by',
'module': 'system'
},
'help': {
'delta': 'help',
'module': 'system'
}
};
// Make additional blocks for each system menu.
var system_menus = menu_list_system_menus();
for (var menu_name in system_menus) {
if (!system_menus.hasOwnProperty(menu_name)) { continue; }
var menu = system_menus[menu_name];
var block_delta = menu.menu_name;
blocks[block_delta] = {
name: block_delta,
delta: block_delta,
module: 'menu'
};
}
return blocks;
}
/**
* Implements hook_block_view().
* @param {String} delta
* @return {String}
*/
function system_block_view(delta) {
try {
switch (delta) {
case 'main':
// This is the main content block, it is required to be in a theme's
// region for the content of a page to show up (nodes, users, taxonomy,
// comments, etc). Depending on the menu link router, we need to route
// this through the appropriate template files and functions.
return drupalgap_render_page();
break;
case 'messages':
// If there are any messages waiting to be displayed, render them, then
// clear out the messages array.
var html = '';
if (drupalgap.messages.length == 0) { return html; }
for (var index in drupalgap.messages) {
if (!drupalgap.messages.hasOwnProperty(index)) { continue; }
var msg = drupalgap.messages[index];
html += '
' +
msg.message +
'
';
}
drupalgap.messages = [];
return html;
break;
case 'logo':
if (drupalgap.settings.logo) {
return '
' +
t("Check your device's network settings and try again.") +
'
'
}
};
return content;
}
catch (error) { console.log('system_offline_page - ' + error); }
}
/**
* When the 'try again' button is clicked, check for a connection and if it has
* one make a call to system connect then go to the front page, otherwise just
* inform user the device is still offline.
* @return {*}
*/
function offline_try_again() {
try {
var connection = drupalgap_check_connection();
if (drupalgap.online) {
system_connect({
success: function() {
drupalgap_goto('');
}
});
}
else {
var msg = t('Sorry, no connection found!') + ' (' + connection + ')';
drupalgap_alert(msg, {
title: 'Offline'
});
return false;
}
}
catch (error) { console.log('offline_try_again - ' + error); }
}
/**
* Returns an array of region names defined by the system that themes must use.
* We do this so Core and Contrib Modules can use these regions for UI needs.
* @return {Array}
*/
function system_regions_list() {
var regions = ['header', 'content', 'footer'];
return regions;
}
/**
* Add default buttons to a form and set its prefix.
* @param {Object} form
* @param {Object} form_state
* @return {Object}
*/
function system_settings_form(form, form_state) {
try {
// Add submit button to form if one isn't present.
if (!form.elements.submit) {
form.elements.submit = {
type: 'submit',
value: t('Save configuration')
};
}
// Add cancel button to form if one isn't present.
if (!form.buttons.cancel) {
form.buttons['cancel'] = drupalgap_form_cancel_button();
}
// Attach submit handler.
form.submit.push('system_settings_form_submit');
return form;
}
catch (error) { console.log('system_settings_form - ' + error); }
}
/**
* Execute the system_settings_form.
* @param {Object} form
* @param {Object} form_state
*/
function system_settings_form_submit(form, form_state) {
try {
if (form_state.values) {
for (var variable in form_state.values) {
if (!form_state.values.hasOwnProperty(variable)) { continue; }
var value = form_state.values[variable];
variable_set(variable, value);
}
}
}
catch (error) { console.log('system_settings_form_submit - ' + error); }
}
/**
* Returns the block id used on the system's title block.
* @param {String} path
* @return {String}
*/
function system_title_block_id(path) {
try {
var id = 'drupalgap_page_title_' + drupalgap_get_page_id(path);
return id;
}
catch (error) { console.log('system_title_block_id - ' + error); }
}
/**
* The default access callback function for the logout block. Allows the block
* to only be shown when a user is viewing their own profile.
* @param {Object} options
* @return {Boolean}
*/
function system_logout_block_access_callback(options) {
try {
var args = arg(null, options.path);
if (
args &&
args.length == 2 &&
args[0] == 'user' &&
args[1] == Drupal.user.uid
) { return true; }
return false;
}
catch (error) {
console.log('system_logout_block_access_callback - ' + error);
}
}
// Used to hold onto the terms once they've been loaded into a widget, keyed by
// the form element's id, this allows (views exposed filters particularly) forms
// to easily retrieve the terms after they've been fetch from the server.
var _taxonomy_term_reference_terms = {};
/**
* Extracts the taxonomy vocabularies JSON objects bundled by the Drupal module
* into the system connect's resource results.
* @param {Object} taxonomy_vocabularies
* @return {Object}
*/
function drupalgap_taxonomy_vocabularies_extract(taxonomy_vocabularies) {
try {
var results = false;
if (taxonomy_vocabularies && taxonomy_vocabularies.length > 0) {
results = {};
for (var index in taxonomy_vocabularies) {
if (!taxonomy_vocabularies.hasOwnProperty(index)) { continue; }
var vocabulary = taxonomy_vocabularies[index];
results[vocabulary.machine_name] = vocabulary;
}
}
return results;
}
catch (error) {
console.log('drupalgap_taxonomy_vocabularies_extract - ' + error);
}
}
/**
* Implements hook_field_formatter_view().
* @param {String} entity_type
* @param {Object} entity
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {*} display
* @return {Object}
*/
function taxonomy_field_formatter_view(entity_type, entity, field, instance,
langcode, items, display) {
try {
var element = {};
// If items is a string, convert it into a single item JSON object.
if (typeof items === 'string') { items = {0: {tid: items}}; }
// It's possible the term items are wrapped in a language code, if they are,
// pull them out.
if (typeof items[language_default()] !== 'undefined') {
items = items[language_default()];
}
if (!empty(items)) {
for (var delta in items) {
if (!items.hasOwnProperty(delta)) { continue; }
var item = items[delta];
var text = item.tid;
if (item.name) { text = item.name; }
var content = null;
switch (display.type) {
case 'taxonomy_term_reference_link':
content = {
theme: 'button_link',
text: text,
path: 'taxonomy/term/' + item.tid
};
break;
case 'taxonomy_term_reference_plain':
content = { markup: text };
break;
default:
content = { markup: text };
break;
}
element[delta] = content;
}
}
return element;
}
catch (error) { console.log('taxonomy_field_formatter_view - ' + error); }
}
/**
* Implements hook_field_widget_form().
* @param {Object} form
* @param {Object} form_state
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Object} items
* @param {Number} delta
* @param {Object} element
*/
function taxonomy_field_widget_form(form, form_state, field, instance, langcode,
items, delta, element) {
try {
items[delta].type = 'hidden';
// Build the widget and attach it to the item.
var list_id = items[delta].id + '-list';
var widget = {
theme: 'item_list',
items: [],
attributes: {
'id': list_id,
'data-role': 'listview',
'data-filter': 'true',
'data-inset': 'true',
'data-filter-placeholder': '...'
}
};
items[delta].children.push(widget);
// Attach JS to handle the widget's data fetching.
var machine_name = field.settings.allowed_values[0].vocabulary;
var vocabulary = taxonomy_vocabulary_machine_name_load(machine_name);
var vid = vocabulary.vid;
var js = '';
items[delta].children.push({
markup: js
});
}
catch (error) { console.log('taxonomy_field_widget_form - ' + error); }
}
var _taxonomy_field_widget_form_autocomplete_input = null;
/**
* Handles the remote data fetching for taxonomy term reference autocomplete
* tagging widget.
* @param {String} id The id of the hidden input that will hold the term id.
* @param {Number} vid
* @param {Object} list The unordered list that displays the terms.
* @param {Object} e
* @param {Object} data
*/
function _taxonomy_field_widget_form_autocomplete(id, vid, list, e, data) {
try {
// Setup the vars to handle this widget.
var $ul = $(list),
$input = $(data.input),
value = $input.val(),
html = '';
// Save a reference to this text input field. Then attach an on change
// handler that will set the hidden input's value when the text field
// changes. Keep in mind, later on we listen for clicks on autocomplete
// results to populate this same hidden input's field.
_taxonomy_field_widget_form_autocomplete_input = $input;
$(_taxonomy_field_widget_form_autocomplete_input).on('change', function() {
$('#' + id).val($(this).val());
});
// Clear the list, then set up its input handlers.
$ul.html('');
if (value && value.length > 0) {
$ul.html('
' +
'' +
'
');
$ul.listview('refresh');
var query = {
fields: ['tid', 'name'],
parameters: {
vid: vid,
name: '%' + value + '%'
},
parameters_op: {
name: 'like'
}
};
taxonomy_term_index(query, {
success: function(terms) {
if (terms.length != 0) {
// Extract the terms into items, then drop them in the list.
var items = [];
for (var index in terms) {
if (!terms.hasOwnProperty(index)) { continue; }
var term = terms[index];
var attributes = {
tid: term.tid,
vid: vid,
name: term.name,
onclick: '_taxonomy_field_widget_form_click(' +
"'" + id + "', " +
"'" + $ul.attr('id') + "', " +
'this' +
')'
};
html += '
' +
term.name +
'
';
}
$ul.html(html);
$ul.listview('refresh');
$ul.trigger('updatelayout');
}
}
});
}
}
catch (error) {
console.log('_taxonomy_field_widget_form_autocomplete - ' + error);
}
}
/**
* Handles clicks on taxonomy term reference autocomplete widgets.
* @param {String} id The id of the hidden input that will hold the term name.
* @param {String} list_id The id of the list that holds the terms.
* @param {Object} item The list item that was just clicked.
*/
function _taxonomy_field_widget_form_click(id, list_id, item) {
try {
var tid = $(item).attr('name');
$('#' + id).val(tid);
$(_taxonomy_field_widget_form_autocomplete_input).val($(item).attr('name'));
$('#' + list_id).html('');
}
catch (error) { console.log('_taxonomy_field_widget_form_click - ' + error); }
}
/**
* Implements hook_assemble_form_state_into_field().
* @param {Object} entity_type
* @param {String} bundle
* @param {String} form_state_value
* @param {Object} field
* @param {Object} instance
* @param {String} langcode
* @param {Number} delta
* @param {Object} field_key
* @return {Object}
*/
function taxonomy_assemble_form_state_into_field(entity_type, bundle,
form_state_value, field, instance, langcode, delta, field_key) {
try {
var result = null;
switch (instance.widget.type) {
case 'taxonomy_autocomplete':
field_key.use_wrapper = false;
result = form_state_value;
break;
case 'options_select':
result = form_state_value;
break;
}
return result;
}
catch (error) {
console.log('taxonomy_assemble_form_state_into_field - ' + error);
}
}
/**
* Implements hook_menu().
* @return {Object}
*/
function taxonomy_menu() {
var items = {
'taxonomy/vocabularies': {
'title': t('Taxonomy'),
'page_callback': 'taxonomy_vocabularies_page',
'pageshow': 'taxonomy_vocabularies_pageshow'
},
'taxonomy/vocabulary/%': {
'title': t('Taxonomy vocabulary'),
'page_callback': 'taxonomy_vocabulary_page',
'page_arguments': [2],
'pageshow': 'taxonomy_vocabulary_pageshow'
},
'taxonomy/vocabulary/%/view': {
'title': t('View'),
'type': 'MENU_DEFAULT_LOCAL_TASK',
'weight': -10
},
'taxonomy/vocabulary/%/edit': {
'title': t('Edit'),
'page_callback': 'entity_page_edit',
'pageshow': 'entity_page_edit_pageshow',
'page_arguments': [
'taxonomy_form_vocabulary',
'taxonomy_vocabulary',
2
],
'weight': 0,
'type': 'MENU_LOCAL_TASK',
'access_arguments': ['administer taxonomy'],
options: {reloadPage: true}
},
'taxonomy/term/%': {
'title': t('Taxonomy term'),
'page_callback': 'taxonomy_term_page',
'page_arguments': [2],
'pageshow': 'taxonomy_term_pageshow'
},
'taxonomy/term/%/view': {
'title': t('View'),
'type': 'MENU_DEFAULT_LOCAL_TASK',
'weight': -10
},
'taxonomy/term/%/edit': {
'title': t('Edit'),
'page_callback': 'entity_page_edit',
'pageshow': 'entity_page_edit_pageshow',
'page_arguments': ['taxonomy_form_term', 'taxonomy_term', 2],
'weight': 0,
'type': 'MENU_LOCAL_TASK',
'access_arguments': ['administer taxonomy'],
options: {reloadPage: true}
}
};
return items;
}
/**
* The taxonomy vocabulary form.
* @param {Object} form
* @param {Object} form_state
* @param {Object} vocabulary
* @return {Object}
*/
function taxonomy_form_vocabulary(form, form_state, vocabulary) {
try {
// Setup form defaults.
form.entity_type = 'taxonomy_vocabulary';
form.bundle = null;
form.action = 'taxonomy/vocabularies';
// Add the entity's core fields to the form.
drupalgap_entity_add_core_fields_to_form(
'taxonomy_vocabulary',
null,
form,
vocabulary
);
// Add submit to form.
form.elements.submit = {
'type': 'submit',
'value': t('Save')
};
// Add cancel button to form.
form.buttons['cancel'] = drupalgap_form_cancel_button();
// If we're editing a vocabulary add a delete button, if the user has
// access.
if (vocabulary && vocabulary.vid && user_access('administer taxonomy')) {
form.buttons['delete'] = drupalgap_entity_edit_form_delete_button(
'taxonomy_vocabulary',
vocabulary.vid
);
}
return form;
}
catch (error) { console.log('taxonomy_form_vocabulary - ' + error); }
}
/**
* The taxonomy vocabulary form submit handler.
* @param {Object} form
* @param {Object} form_state
*/
function taxonomy_form_vocabulary_submit(form, form_state) {
try {
var vocabulary = drupalgap_entity_build_from_form_state(form, form_state);
drupalgap_entity_form_submit(form, form_state, vocabulary);
}
catch (error) { console.log('taxonomy_form_vocabulary_submit - ' + error); }
}
/**
* The taxonomy term form.
* @param {Object} form
* @param {Object} form_state
* @param {Object} term
* @return {Object}
*/
function taxonomy_form_term(form, form_state, term) {
try {
// Setup form defaults.
form.entity_type = 'taxonomy_term';
form.bundle = null;
form.action = 'taxonomy/vocabularies';
// Add the entity's core fields to the form.
drupalgap_entity_add_core_fields_to_form('taxonomy_term', null, form, term);
// Add submit to form.
form.elements.submit = {
'type': 'submit',
'value': t('Save')
};
// Add cancel button to form.
form.buttons['cancel'] = drupalgap_form_cancel_button();
// If we are editing a term, add a delete button.
if (term && term.tid && user_access('administer taxonomy')) {
form.buttons['delete'] = drupalgap_entity_edit_form_delete_button(
'taxonomy_term',
term.tid
);
}
return form;
}
catch (error) { console.log('taxonomy_form_term - ' + error); }
}
/**
* The taxonomy term form submit handler.
* @param {Object} form
* @param {Object} form_state
*/
function taxonomy_form_term_submit(form, form_state) {
try {
var term = drupalgap_entity_build_from_form_state(form, form_state);
drupalgap_entity_form_submit(form, form_state, term);
}
catch (error) { console.log('taxonomy_form_term_submit - ' + error); }
}
/**
* Page callback for taxonomy/term/%.
* @param {Number} tid
* @return {Object}
*/
function taxonomy_term_page(tid) {
try {
if (tid) {
var content = {
container: _drupalgap_entity_page_container(
'taxonomy_term',
tid,
'view'
),
taxonomy_term_node_listing: {
theme: 'jqm_item_list',
items: [],
attributes: {
id: 'taxonomy_term_node_listing_items_' + tid
}
}
};
return content;
}
else { console.log('taxonomy_term_pageshow - No term id provided!'); }
}
catch (error) { console.log('taxonomy_term_pageshow - ' + error); }
}
/**
* jQM pageshow callback for taxonomy/term/%
* @param {Number} tid
*/
function taxonomy_term_pageshow(tid) {
try {
taxonomy_term_load(tid, {
success: function(term) {
var description = term.description ? term.description : '';
var content = {
'name': {
'markup': '
' + term.name + '
'
},
'description': {
'markup': '
' + description + '
'
}
};
_drupalgap_entity_page_container_inject(
'taxonomy_term',
term.tid,
'view',
content
);
taxonomy_term_selectNodes(term.tid, {
success: function(results) {
// Extract the nodes into items, then drop them in the list.
var items = [];
for (var index in results) {
if (!results.hasOwnProperty(index)) { continue; }
var node = results[index];
items.push(l(node.title, 'node/' + node.nid));
}
drupalgap_item_list_populate(
'#taxonomy_term_node_listing_items_' + term.tid,
items
);
}
});
}
});
}
catch (error) { console.log('taxonomy_term_pageshow - ' + error); }
}
/**
* The selectNodes resource from the Taxonomy Term service.
* @param {Number} tid The taxonomy term id.
* @param {Object} options
*/
function taxonomy_term_selectNodes(tid, options) {
try {
// @TODO - move this function to jDrupal.
options.method = 'POST';
options.path = 'taxonomy_term/selectNodes.json';
options.service = 'taxonomy_term';
options.resource = 'selectNodes';
options.data = JSON.stringify({ tid: tid});
Drupal.services.call(options);
}
catch (error) { console.log('taxonomy_term_selectNodes - ' + error); }
}
/**
* Page call back for taxonomy/vocabularies.
* @return {Object}
* @return {Object}
*/
function taxonomy_vocabularies_page() {
// Place an empty item list that will hold a list of users.
var content = {
'vocabulary_listing': {
'theme': 'jqm_item_list',
'title': t('Vocabularies'),
'items': [],
'attributes': {'id': 'vocabulary_listing_items'}
}
};
return content;
}
/**
* jQM pageshow call back for taxonomy/vocabularies.
*/
function taxonomy_vocabularies_pageshow() {
try {
taxonomy_vocabulary_index(null, {
success: function(vocabularies) {
// Extract the vocabs into items, then drop them in the list.
var items = [];
for (var index in vocabularies) {
if (!vocabularies.hasOwnProperty(index)) { continue; }
var vocabulary = vocabularies[index];
items.push(
l(vocabulary.name, 'taxonomy/vocabulary/' + vocabulary.vid)
);
}
drupalgap_item_list_populate('#vocabulary_listing_items', items);
}
});
}
catch (error) { console.log('taxonomy_vocabularies_pageshow - ' + error); }
}
/**
* Page callback for taxonomy/vocabulary/%
* @param {Number} vid
* @return {String|Object}
*/
function taxonomy_vocabulary_page(vid) {
try {
if (vid) {
var content = {
container: _drupalgap_entity_page_container(
'taxonomy_vocabulary',
vid,
'view'
),
taxonomy_term_listing: {
theme: 'jqm_item_list',
title: t('Terms'),
items: [],
attributes: {
id: 'taxonomy_term_listing_items_' + vid
}
}
};
return content;
}
else {
console.log('taxonomy_vocabulary_page - No vocabulary id provided!');
}
}
catch (error) { console.log('taxonomy_vocabulary_page - ' + error); }
}
/**
* jQM pageshow callback for taxonomy/vocabulary/%.
* @param {Number} vid
*/
function taxonomy_vocabulary_pageshow(vid) {
try {
taxonomy_vocabulary_load(vid, {
success: function(vocabulary) {
var content = {
'name': {
'markup': '
' + vocabulary.name + '
'
},
'description': {
'markup': '
' + vocabulary.description + '
'
}
};
_drupalgap_entity_page_container_inject(
'taxonomy_vocabulary',
vocabulary.vid,
'view',
content
);
var query = {
parameters: {
vid: vid
}
};
taxonomy_term_index(query, {
success: function(terms) {
if (terms.length != 0) {
// Extract the terms into items, then drop them in the list.
var items = [];
for (var index in terms) {
if (!terms.hasOwnProperty(index)) { continue; }
var term = terms[index];
items.push(l(term.name, 'taxonomy/term/' + term.tid));
}
drupalgap_item_list_populate(
'#taxonomy_term_listing_items_' + vid,
items
);
}
}
});
}
});
}
catch (error) { console.log('taxonomy_vocabulary_pageshow - ' + error); }
}
/**
* Given a vocabulary machine name this will return the vocabulary id or false.
* @param {String} name
* @return {Number|Boolean}
*/
function taxonomy_vocabulary_get_vid_from_name(name) {
try {
var vocabulary = taxonomy_vocabulary_machine_name_load(name);
if (vocabulary) { return vocabulary.vid; }
return false;
}
catch (error) {
console.log('taxonomy_vocabulary_get_vid_from_name - ' + error);
}
}
/**
* Given a vocabulary machine name this will return the JSON
* object for the vocabulary that is attached to the
* drupalgap.taxonomy_vocabularies object, or false if it doesn't exist.
* @param {String} name
* @return {Object|Boolean}
*/
function taxonomy_vocabulary_machine_name_load(name) {
try {
if (drupalgap.taxonomy_vocabularies &&
drupalgap.taxonomy_vocabularies[name]) {
return drupalgap.taxonomy_vocabularies[name];
}
return false;
}
catch (error) {
console.log('taxonomy_vocabulary_machine_name_load - ' + error);
}
}
/**
* Theme's the html for a taxonomy term reference field.
* @param {Object} variables
* @return {String}
*/
function theme_taxonomy_term_reference(variables) {
try {
var html = '';
// Make this a hidden field since the widget will just populate a value. If
// somone wants to skip the input element html generation (e.g. taxonomy
// term reference views exposed filter), let them do it, but by default
// we'll render the input for them.
var render_input_element = true;
if (typeof variables.render_input_element !== 'undefined') {
render_input_element = variables.render_input_element;
}
if (render_input_element) {
variables.attributes.type = 'hidden';
html += '';
}
// Is anyone flagging this as required? i.e. is it a views exposed filter?
// @TODO - the field system that assembles the elements onto a form, when it
// is a taxonomy term reference field, we need to pass along the required
// option so when we load the items into the select list, we know whether or
// not to include the "empty string" option. This is probably in field.js.
var required = false;
if (typeof variables.required !== 'undefined') {
required = variables.required;
}
// Is this widget exposed (aka views exposed filter)?
var exposed = false;
if (typeof variables.exposed !== 'undefined') {
exposed = variables.exposed;
}
// What vocabulary are we using?
var machine_name =
variables.field_info_field.settings.allowed_values[0].vocabulary;
var taxonomy_vocabulary = taxonomy_vocabulary_machine_name_load(
machine_name
);
// Prepare the variables for the widget and render it based on its type.
var widget_type = variables.field_info_instance.widget.type;
if (widget_type == 'options_select') { widget_type = 'select'; }
var widget_function = 'theme_' + widget_type;
var widget_id = variables.attributes.id + '-' + widget_type;
if (function_exists(widget_function)) {
// Grab the function in charge of themeing this widget.
var fn = window[widget_function];
// Build the variables for the widget.
var widget_variables = {
attributes: {
id: widget_id,
onchange: "_theme_taxonomy_term_reference_onchange(this, '" +
variables.attributes.id + "');"
}
};
// If the options were previously set aside for this widget, use them.
var options_available = false;
if (_taxonomy_term_reference_terms[variables.attributes.id]) {
options_available = true;
widget_variables.options =
_taxonomy_term_reference_terms[variables.attributes.id];
}
// Was their a value present to include as the default value for the
// widget, if so include it. If not, and this filter is not required, set
// the default value to an empty string so the widget renders the default
// option correctly. A views exposed filter uses 'All' instead of an
// empty string.
if (typeof variables.value !== 'undefined') {
widget_variables.value = variables.value;
}
else if (!required) {
if (exposed) { widget_variables.value = 'All'; }
else { widget_variables.value = ''; }
}
// Render the widget.
html += fn.call(null, widget_variables);
// If the options weren't available, attach a pageshow handler to the
// current page that will load the terms into the widget. Keep in mind,
// this inline pageshow handler only gets called once when the first view
// is loaded. That's why we later set aside the options so they can be
// used again without having to fire the pageshow event.
if (!options_available) {
var options = {
page_id: drupalgap_get_page_id(drupalgap_path_get()),
jqm_page_event: 'pageshow',
jqm_page_event_callback: '_theme_taxonomy_term_reference_load_items',
jqm_page_event_args: JSON.stringify({
taxonomy_vocabulary: taxonomy_vocabulary,
element_id: variables.attributes.id,
widget_id: widget_id,
required: required,
exposed: exposed
})
};
html += drupalgap_jqm_page_event_script_code(options);
}
}
else {
console.log(
'WARNING: theme_taxonomy_term_reference() - ' +
'unsupported widget type! (' + widget_type + ')'
);
}
return html;
}
catch (error) {
console.log('theme_taxonomy_term_reference - ' + error);
}
}
/**
* An internal function used to load the taxonomy terms from a term reference
* field and populate them into select list identified by options.widget_id.
* @param {Object} options
*/
function _theme_taxonomy_term_reference_load_items(options) {
try {
// Build the index query, then make the call to the server.
var query = {
parameters: {
vid: options.taxonomy_vocabulary.vid
},
options: {
orderby: {
weight: 'asc',
name: 'asc'
}
}
};
taxonomy_term_index(query, {
success: function(terms) {
if (terms.length == 0) { return; }
// As we iterate over the terms, we'll set them aside in a JSON
// object so they can be used later.
_taxonomy_term_reference_terms[options.element_id] = { };
// Grab the widget.
var widget = $('#' + options.widget_id);
// If it's not required, place an empty option on the widget and set
// it aside.
if (!options.required) {
var option = null;
if (options.exposed) {
option = '';
_taxonomy_term_reference_terms[options.element_id]['All'] =
'- Any -';
}
else {
option = '';
_taxonomy_term_reference_terms[options.element_id][''] =
'- None -';
}
$(widget).append(option);
}
// Place each term in the widget as an option, and set the option
// aside.
for (var index in terms) {
if (!terms.hasOwnProperty(index)) { continue; }
var term = terms[index];
var option = '';
$(widget).append(option);
_taxonomy_term_reference_terms[options.element_id][term.tid] =
term.name;
}
// Refresh the select list.
$(widget).selectmenu('refresh', true);
}
});
}
catch (error) {
console.log('_theme_taxonomy_term_reference_load_items - ' + error);
}
}
/**
* An internal function used by a taxonomy term reference field widget to
* detect changes on it and populate the hidden field that holds the tid in the
* form.
* @param {Object} input
* @param {String} id
*/
function _theme_taxonomy_term_reference_onchange(input, id) {
try {
$('#' + id).val($(input).val());
}
catch (error) {
console.log('_theme_taxonomy_term_reference_onchange - ' + error);
}
}
/**
* Implements hook_views_exposed_filter().
* @param {Object} form
* @param {Object} form_state
* @param {Object} element
* @param {Object} filter
* @param {Object} field
*/
function taxonomy_views_exposed_filter(
form, form_state, element, filter, field) {
try {
//dpm('taxonomy_views_exposed_filter');
//console.log(element);
//console.log(filter);
//console.log(field);
// @TODO this filter loses its value after one submission, aka the next
// submission will submit it as 'All' even though we have a term selected in
// the widget from the previous submission.
// Autocomplete.
if (filter.options.type == 'textfield') {
element.type = 'autocomplete';
element.remote = true;
element.custom = true;
element.handler = 'index';
element.entity_type = 'taxonomy_term';
if (typeof filter.options.vocabulary !== 'undefined') {
element.vid =
taxonomy_vocabulary_get_vid_from_name(filter.options.vocabulary);
}
element.value = 'name';
element.label = 'name';
element.filter = 'name';
}
// Dropdown.
else {
// Change the input to hidden, then iterate over each vocabulary and inject
// them into the widget. We'll just use a taxonomy term reference field and
// fake its instance.
element.type = 'hidden';
for (var index in field.settings.allowed_values) {
if (!field.settings.allowed_values.hasOwnProperty(index)) { continue; }
var object = field.settings.allowed_values[index];
// Build the variables for the widget.
var variables = {
required: element.required,
render_input_element: false,
attributes: {
id: element.options.attributes.id
},
field_info_field: field,
field_info_instance: {
widget: {
type: 'options_select'
}
},
exposed: true
};
// If we have a default value, send it along.
// @TODO add support for multiple values.
if (!empty(filter.value)) {
variables.value = parseInt(filter.value[0]);
variables.attributes.value = variables.value;
}
// Add the widget as a child to the form element, including a label if
// necessary (since the original label is lost because it was turned
// into a hidden element).
var child = '';
if (!empty(element.title)) {
child += theme('form_element_label', { element: element });
}
child += theme('taxonomy_term_reference', variables);
element.children.push({ markup: child });
}
}
}
catch (error) { console.log('taxonomy_views_exposed_filter - ' + error); }
}
/**
* The user login form.
* @param {Object} form
* @param {Object} form_state
* @return {Object}
*/
function user_login_form(form, form_state) {
try {
form.entity_type = 'user';
form.bundle = null;
form.elements.name = {
type: 'textfield',
title: t('Username'),
title_placeholder: true,
required: true,
attributes: {
autocapitalize: 'none'
}
};
form.elements.pass = {
type: 'password',
title: t('Password'),
title_placeholder: true,
required: true,
attributes: {
onkeypress: "drupalgap_form_onkeypress('" + form.id + "', event)"
}
};
form.elements.submit = {
type: 'submit',
value: t('Login')
};
if (user_register_access()) {
form.buttons['create_new_account'] = {
title: t('Create new account'),
attributes: {
onclick: "drupalgap_goto('user/register')"
}
};
}
form.buttons['forgot_password'] = {
title: t('Request new password'),
attributes: {
onclick: "drupalgap_goto('user/password')"
}
};
return form;
}
catch (error) { console.log('user_login_form - ' + error); }
}
/**
* The user login form submit handler.
* @param {Object} form
* @param {Object} form_state
*/
function user_login_form_submit(form, form_state) {
try {
user_login(form_state.values.name, form_state.values.pass, {
success: function(result) {
drupalgap_goto(
typeof form.action !== 'undefined' ?
form.action : drupalgap.settings.front,
{ reloadPage:true }
);
}
});
}
catch (error) { console.log('user_login_form_submit - ' + error); }
}
/**
* The user registration form.
* @param {Object} form
* @param {Object} form_state
* @return {Object}
*/
function user_register_form(form, form_state) {
try {
form.entity_type = 'user';
form.bundle = null;
var description = t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.');
form.elements.name = {
type: 'textfield',
title: t('Username'),
title_placeholder: true,
required: true,
description: description
};
form.elements.mail = {
type: 'email',
title: t('E-mail address'),
title_placeholder: true,
required: true
};
// If e-mail verification is not required, provide password fields and
// the confirm e-mail address field.
if (!drupalgap.site_settings.user_email_verification) {
form.elements.conf_mail = {
type: 'email',
title: t('Confirm e-mail address'),
title_placeholder: true,
required: true
};
form.elements.pass = {
type: 'password',
title: t('Password'),
title_placeholder: true,
required: true
};
form.elements.pass2 = {
type: 'password',
title: t('Confirm password'),
title_placeholder: true,
required: true
};
}
// @TODO - instead of a null bundle, it appears drupal uses the bundle 'user' instead.
drupalgap_field_info_instances_add_to_form('user', null, form, null);
// Add registration messages to form.
form.user_register = {
'user_mail_register_no_approval_required_body':
t('Registration complete!'),
'user_mail_register_pending_approval_required_body':
t('Registration complete, waiting for administrator approval.'),
'user_mail_register_email_verification_body':
t('Registration complete, check your e-mail inbox to verify the ' +
'account.')
};
// Set the auto login boolean. This only happens when the site's account
// settings require no e-mail verification. Others can stop this from
// happening via hook_form_alter().
form.auto_user_login = true;
// Add submit button.
form.elements.submit = {
'type': 'submit',
'value': t('Create new account')
};
return form;
}
catch (error) { console.log('user_register_form - ' + error); }
}
/**
* Define the form's validation function (optional).
* @param {Object} form
* @param {Object} form_state
*/
function user_register_form_validate(form, form_state) {
try {
// If e-mail verification is not required, make sure the passwords match.
if (!drupalgap.site_settings.user_email_verification &&
form_state.values.pass != form_state.values.pass2) {
drupalgap_form_set_error('pass', t('Passwords do not match!'));
}
// If there are two e-mail address fields on the form, make sure they match.
if (!empty(form_state.values.mail) && !empty(form_state.values.conf_mail) &&
form_state.values.mail != form_state.values.conf_mail
) { drupalgap_form_set_error('mail', t('E-mail addresses do not match!')); }
}
catch (error) {
console.log('user_register_form_validate - ' + error);
}
}
/**
* The user registration form submit handler.
* @param {Object} form
* @param {Object} form_state
*/
function user_register_form_submit(form, form_state) {
try {
var account = drupalgap_entity_build_from_form_state(form, form_state);
user_register(account, {
success: function(data) {
var config = form.user_register;
var options = {
title: t('Registered')
};
var destination = typeof form.action !== 'undefined' ?
form.action : drupalgap.settings.front;
// Check if e-mail verification is required or not..
if (!drupalgap.site_settings.user_email_verification) {
// E-mail verification not needed, if administrator approval is
// needed, notify the user, otherwise log them in.
if (drupalgap.site_settings.user_register == '2') {
drupalgap_alert(
config.user_mail_register_pending_approval_required_body,
options
);
drupalgap_goto(destination);
}
else {
drupalgap_alert(
config.user_mail_register_no_approval_required_body,
options
);
// If we're automatically logging in do it, otherwise just go to
// the front page.
if (form.auto_user_login) {
user_login(account.name, account.pass, {
success: function(result) {
drupalgap_goto('');
}
});
}
else { drupalgap_goto(destination); }
}
}
else {
// E-mail verification needed... notify the user.
drupalgap_alert(
config.user_mail_register_email_verification_body,
options
);
drupalgap_goto(destination);
}
},
error: function(xhr, status, message) {
// If there were any form errors, display them.
var msg = _drupalgap_form_submit_response_errors(form, form_state, xhr,
status, message);
if (msg) { drupalgap_alert(msg); }
}
});
}
catch (error) { console.log('user_register_form_submit - ' + error); }
}
/**
* The user profile form.
* @param {Object} form
* @param {Object} form_state
* @param {Object} account
* @return {Object}
*/
function user_profile_form(form, form_state, account) {
try {
// Setup form defaults.
form.entity_type = 'user';
form.bundle = null;
// Add the entity's core fields to the form.
drupalgap_entity_add_core_fields_to_form('user', null, form, account);
// Add the fields for accounts to the form.
drupalgap_field_info_instances_add_to_form('user', null, form, account);
// If the user can't change their user name, remove access to it.
if (!user_access('change own username')) {
form.elements['name'].access = false;
form.elements['name'].required = false;
}
// If profile pictures are disabled, remove the core field from the form.
if (drupalgap.site_settings.user_pictures == 0) {
delete form.elements.picture;
}
// Add password fields to the form. We show the current password field only
// if the user is editing their account. We show the password and confirm
// password field no matter what.
if (Drupal.user.uid == account.uid) {
form.elements.current_pass = {
'title': t('Current password'),
'type': 'password',
'description': t('Enter your current password to change the E-mail ' +
'address or Password.')
};
}
form.elements.pass_pass1 = {
'title': t('Password'),
'type': 'password'
};
form.elements.pass_pass2 = {
'title': t('Confirm password'),
'type': 'password',
'description': t('To change the current user password, enter the new ' +
'password in both fields.')
};
// Add submit to form.
form.elements.submit = {
'type': 'submit',
'value': t('Save')
};
// Add cancel button to form.
form.buttons['cancel'] = {
'title': t('Cancel'),
attributes: {
onclick: 'javascript:drupalgap_back();'
}
};
return form;
}
catch (error) { console.log('user_profile_form - ' + error); }
}
/**
* The user profile form validate handler.
* @param {Object} form
* @param {Object} form_state
*/
function user_profile_form_validate(form, form_state) {
try {
// If they entered their current password, and entered new passwords, make
// sure the new passwords match.
if (!empty(form_state.values['current_pass'])) {
if (
!empty(form_state.values['pass_pass1']) &&
!empty(form_state.values['pass_pass2']) &&
form_state.values['pass_pass1'] != form_state.values['pass_pass2']
) {
drupalgap_form_set_error('pass_pass1', t('Passwords do not match.'));
}
}
// If they didn't enter their current password and entered new passwords,
// tell them they need to enter their current password.
else if (
empty(form_state.values['current_pass']) &&
!empty(form_state.values['pass_pass1']) &&
!empty(form_state.values['pass_pass2'])
) {
drupalgap_form_set_error(
'current_pass',
t('You must enter your current password to change your password.')
);
}
}
catch (error) { console.log('user_profile_form_validate - ' + error); }
}
/**
* The user profile form submit handler.
* @param {Object} form
* @param {Object} form_state
*/
function user_profile_form_submit(form, form_state) {
try {
var account = drupalgap_entity_build_from_form_state(form, form_state);
// If they provided their current password, and their new password, prepare
// the account submission values.
if (
account.current_pass &&
!empty(account.pass_pass1) &&
!empty(account.pass_pass2)
) {
account.pass = account.pass_pass1;
delete account.pass_pass1;
delete account.pass_pass2;
}
drupalgap_entity_form_submit(form, form_state, account);
}
catch (error) { console.log('user_profile_form_submit - ' + error); }
}
/**
* The request new password form.
* @param {Object} form
* @param {Object} form_state
* @return {Object}
*/
function user_pass_form(form, form_state) {
form.elements['name'] = {
type: 'textfield',
title: t('Username or e-mail address'),
required: true,
attributes: {
onkeypress: "drupalgap_form_onkeypress('" + form.id + "', event)"
}
};
form.elements['submit'] = {
type: 'submit',
value: t('E-mail new password')
};
return form;
}
/**
* The request new password form submission handler.
* @param {Object} form
* @param {Object} form_state
*/
function user_pass_form_submit(form, form_state) {
user_request_new_password(form_state.values['name'], {
success: function(result) {
if (result[0]) {
var msg =
t('Further instructions have been sent to your e-mail address.');
drupalgap_set_message(msg);
}
else {
var msg =
t('There was a problem sending an e-mail to your address.');
drupalgap_set_message(msg, 'warning');
}
var path = 'user/login';
if (form.action) { path = form.action; }
if (_GET('destination')) { path = _GET('destination'); }
drupalgap_goto(path, { reloadPage: true });
}
});
}
/**
* Determine whether the user has a given privilege. Optionally pass in a user
* account JSON object for the second paramater to check that particular
* account.
* @param {String} string The permission, such as "administer nodes", being
* checked for.
* @return {Boolean}
*/
function user_access(string) {
try {
var account;
if (arguments[1]) { account = arguments[1]; }
else { account = Drupal.user; }
if (account.uid == 1) { return true; }
var access = false;
for (var index in account.permissions) {
if (!account.permissions.hasOwnProperty(index)) { continue; }
var object = account.permissions[index];
if (object.permission == string) {
access = true;
break;
}
}
return access;
}
catch (error) { console.log('user_access - ' + error); }
}
/**
* The access callback for the user/%/edit page.
* @param {Object} account
* @return {Boolean}
*/
function user_edit_access(account) {
try {
// If the current user is looking at their own account, or if they have the
// 'administer users' permission, then they are allowed to edit the account.
if (Drupal.user.uid == account.uid || user_access('administer users')) {
return true;
}
return false;
}
catch (error) { console.log('user_edit_access - ' + error); }
}
/**
* A page call back function to display a simple list of drupal users.
* @return {Object}
*/
function user_listing() {
var content = {};
content['user_list'] = {
theme: 'view',
format: 'ul',
path: 'drupalgap/views_datasource/drupalgap_users',
row_callback: 'user_listing_row',
empty_callback: 'user_listing_empty',
attributes: {
id: 'user_listing_' + user_password() /* make a random id */
}
};
return content;
}
/**
* The row callback for the simple user listing page.
* @param view
* @param row
* @returns {String}
*/
function user_listing_row(view, row) {
try {
return l(t(row.name), 'user/' + row.uid);
}
catch (error) { console.log('user_listing_row - ' + error); }
}
/**
* The empty callback for the simple user listing page.
* @param view
* @returns {String}
*/
function user_listing_empty(view) { return t('Sorry, no users were found.'); }
/**
* The user logout page callback.
* @return {String}
*/
function user_logout_callback() {
return '
' + t('Logging out') + '...
';
}
/**
* The user logout pageshow callback. This actually handles the call to the
* user logout service resource.
*/
function user_logout_pageshow() {
try {
user_logout({
success: function(data) {
drupalgap_remove_pages_from_dom();
drupalgap_goto(drupalgap.settings.front, { reloadPage:true });
}
});
}
catch (error) { console.log('user_logout_pagechange - ' + error); }
}
/**
* Implements hook_menu().
* @return {Object}
*/
function user_menu() {
var items = {
'user': {
'page_callback': 'user_page'
},
'user/login': {
'title': t('Login'),
'page_callback': 'drupalgap_get_form',
'page_arguments': ['user_login_form'],
options: {reloadPage: true}
},
'user/logout': {
'title': t('Logout'),
'page_callback': 'user_logout_callback',
'pageshow': 'user_logout_pageshow',
options: {reloadPage: true}
},
'user/register': {
'title': t('Register'),
'page_callback': 'drupalgap_get_form',
'page_arguments': ['user_register_form'],
'access_callback': 'user_register_access',
options: {reloadPage: true}
},
'user/%': {
title: t('My account'),
title_callback: 'user_view_title',
title_arguments: [1],
page_callback: 'user_view',
pageshow: 'user_view_pageshow',
page_arguments: [1]
},
'user/%/view': {
'title': t('View'),
'type': 'MENU_DEFAULT_LOCAL_TASK',
'weight': -10
},
'user/%/edit': {
'title': t('Edit'),
'page_callback': 'entity_page_edit',
'pageshow': 'entity_page_edit_pageshow',
'page_arguments': ['user_profile_form', 'user', 1],
'access_callback': 'user_edit_access',
'access_arguments': [1],
'weight': 0,
'type': 'MENU_LOCAL_TASK',
options: {reloadPage: true}
}
};
items['user-listing'] = {
title: t('Users'),
page_callback: 'user_listing',
access_arguments: ['access user profiles']
};
items['user/password'] = {
title: t('Request new password'),
page_callback: 'drupalgap_get_form',
page_arguments: ['user_pass_form']
};
return items;
}
/**
* Page callback for the user page.
* @return {String}
*/
function user_page() {
// NOTE, this page call back isn't actually used, because the 'user' path
// in DrupalGap is redirected to either 'user/login' or e.g.
// 'user/123'.
return 'user_page()';
}
/**
* Access callback for the user registration page.
* @return {Boolean}
*/
function user_register_access() {
try {
switch (drupalgap.site_settings.user_register.toString()) {
case '0': // admins only can register
return false;
break;
case '1': // visitors can register
case '2': // visitors can register, but admin approval is needed
return true;
break;
}
}
catch (error) { console.log('user_register_access - ' + error); }
}
/**
* Implements hook_services_postprocess().
* @param {Object} options
* @param {Object} result
*/
function user_services_postprocess(options, result) {
try {
// Don't process any other services.
if (options.service != 'user') { return; }
var resources = ['login', 'logout', 'register'];
// Only process login, logout and registration.
if (!in_array(options.resource, resources)) { return; }
// If there were any form errors, alert them to the user.
if (!result.responseText) { return; }
var response = JSON.parse(result.responseText);
if ($.isArray(response)) {
var msg = '';
for (var index in response) {
if (!response.hasOwnProperty(index)) { continue; }
var message = response[index];
msg += t(message) + '\n';
}
if (msg != '') { drupalgap_alert(msg); }
}
}
catch (error) { console.log('user_services_postprocess - ' + error); }
}
/**
* Implements hook_theme().
* @return {Object}
*/
function user_theme() {
return {
user_picture: {
template: 'user-picture'
},
user_profile: {
template: 'user-profile'
}
};
}
/**
* Page callback for user/%.
* @param {Number} uid
* @return {Object}
*/
function user_view(uid) {
try {
if (uid) {
var content = {
container: _drupalgap_entity_page_container('user', uid, 'view')
};
return content;
}
else { console.log('user_view - No user id provided!'); }
}
catch (error) { console.log('user_view - ' + error); }
}
/**
* jQM pageshow handler for node/% pages.
* @param {Number} uid
*/
function user_view_pageshow(uid) {
try {
user_load(uid, {
success: function(account) {
// Determine the incoming arguments, and set defaults if necessary.
var view_mode = 'full';
var langcode = null;
if (arguments[1]) { view_mode = arguments[1]; }
if (arguments[2]) { langcode = arguments[2]; }
if (!langcode) { langcode = language_default(); }
if (account) {
var build = {
'theme': 'user_profile',
'account': account,
'view_mode': view_mode,
'language': langcode,
'name': {'markup': account.name},
'created': {
markup:
'
' +
t('History') +
'
' +
'
' + t('Member since') + '
' +
(new Date(parseInt(account.created) * 1000)).toDateString() +
'
'
}
};
// Any content?
if (typeof account.content !== 'undefined') {
build.content = { markup: account.content };
}
// Any picture?
if (account.picture && account.picture.fid) {
build.picture = {
'theme': 'image',
'path': image_style_url(
drupalgap.site_settings.user_picture_style,
account.picture.uri
)
};
}
_drupalgap_entity_page_container_inject(
'user', account.uid, 'view', build
);
}
}
});
}
catch (error) { console.log('user_view_pageshow - ' + error); }
}
/**
* Title callback for the user profile view page.
* @param {Function} callback
* @param {Number} uid
*/
function user_view_title(callback, uid) {
try {
user_load(uid, {
success: function(account) {
callback.call(null, account.name);
}
});
}
catch (error) { console.log('user_view_title - ' + error); }
}
/**
* Given a user role (string), this determines if the current user has the role.
* Returns true if the user has the role, false otherwise. You may pass in a
* user account object to check against a certain account, instead of the
* current user.
* @param {String} role
* @return {Boolean}
*/
function drupalgap_user_has_role(role) {
try {
var has_role = false;
var account = null;
if (arguments[1]) { account = arguments[1]; }
else { account = Drupal.user; }
for (var rid in account.roles) {
if (!account.roles.hasOwnProperty(rid)) { continue; }
var value = account.roles[rid];
if (role == value) {
has_role = true;
break;
}
}
return has_role;
}
catch (error) { console.log('drupalgap_user_has_role - ' + error); }
}
// Holds onto views contexts on a per page basis.
var _views_embedded_views = {};
/**
* Gets an embedded view for the given page id.
* @param {String} page_id
* @returns {Object}
*/
function views_embedded_view_get(page_id) {
try {
if (!_views_embedded_views[page_id]) { return null; }
var property = arguments[1];
if (!property) { return _views_embedded_views[page_id]; }
return _views_embedded_views[page_id][property];
}
catch (error) { console.log('views_embedded_view_get - ' + error); }
}
/**
* Given a page id, property name and value, this will set the value for the
* embedded view.
* @param {String} page_id
* @param {String} property
* @param {*} value
*/
function views_embedded_view_set(page_id, property, value) {
try {
if (!_views_embedded_views[page_id]) {
_views_embedded_views[page_id] = {};
}
_views_embedded_views[page_id][property] = value;
}
catch (error) { console.log('views_embedded_view_set - ' + error); }
}
/**
* Given a page id, this will delete the embedded view for the page from
* memory. Returns true if successful, false otherwise.
* @param page_id
* @returns {boolean}
*/
function views_embedded_view_delete(page_id) {
try {
if (!_views_embedded_views[page_id]) { return false; }
var property = arguments[1];
if (!property) { delete _views_embedded_views[page_id]; }
else { delete _views_embedded_views[page_id][property]; }
return true;
}
catch (error) { console.log('views_embedded_view_delete - ' + error); }
}
/**
* Given a path to a Views Datasource (Views JSON) view, this will get the
* results and pass them along to the provided success callback.
* @param {String} path
* @param {Object} options
*/
function views_datasource_get_view_result(path, options) {
try {
// Since we do not use clean URLs, replace any potential question marks from
// the path with an ampersand so the path will not be invalid.
if (path.indexOf('?') != -1) {
var replacement = path.replace('?', '&');
path = replacement;
}
// If local storage caching is enabled, let's see if we can load the results
// from there. If we successfully loaded the result, make sure it didn't
// expire. If it did expire, remove it from local storage. If we don't have
// it in local storage, or local storage caching is disabled, call Drupal to
// get the results. After the results are fetched, save them to local
// storage with an expiration time, if necessary. We use the path of the
// view as the local storage key.
// If we are resetting, remove the item from localStorage.
if (options.reset) { window.localStorage.removeItem(path); }
else if (Drupal.settings.cache.views.enabled) {
var result = window.localStorage.getItem(path);
if (result) {
// Loaded from local storage, did it expire?
result = JSON.parse(result);
if (typeof result.expiration !== 'undefined' &&
result.expiration != 0 &&
time() > result.expiration
) {
// Expired, remove from local storage.
window.localStorage.removeItem(path);
}
else if (options.success) {
// Did not expire yet, use it.
options.success(result);
return;
}
}
}
Drupal.services.call({
endpoint: '',
service: 'views_datasource',
resource: '',
method: 'GET',
path: path,
success: function(result) {
try {
if (options.success) {
// Add the path to the result.
result.path = path;
// If any views caching is enabled, cache the results in local
// storage.
if (Drupal.settings.cache.views.enabled) {
var expiration =
time() + Drupal.settings.cache.views.expiration;
if (Drupal.settings.cache.views.expiration == 0) {
expiration = 0;
}
// Saving to local storage.
result.expiration = expiration;
window.localStorage.setItem(path, JSON.stringify(result));
}
options.success(result);
}
}
catch (error) {
console.log(
'views_datasource_get_view_result - success - ' + error
);
}
},
error: function(xhr, status, message) {
try {
if (options.error) { options.error(xhr, status, message); }
}
catch (error) {
console.log('views_datasource_get_view_result - error - ' + error);
}
}
});
}
catch (error) { console.log('views_datasource_get_view_result - ' + error); }
}
/**
* The exposed filter form builder.
* @param {Object} form
* @param {Object} form_state
* @param {Object} options
* @return {Object}
*/
function views_exposed_form(form, form_state, options) {
try {
//console.log(form);
//console.log(form_state);
//console.log(options);
// @TODO we tried to make the filters collapsible, but jQM doesn't seem to
// like collapsibles with form inputs in them... weird.
//var title = form.title ? form.title : 'Filter';
//form.prefix += '
' + title + '
';
// Attach the variables to the form so it can be used later.
form.variables = options.variables;
for (var views_field in options.filter) {
if (!options.filter.hasOwnProperty(views_field)) { continue; }
var filter = options.filter[views_field];
// Prep the element basics.
var element_id = null;
var element = null;
// @TODO - This ID is NOT unique enough, it will cause DOM collisions if
// the same exposed filter gets used twice...
// @TODO - we should be attaching the value right here, so filter
// implementors don't need to extract it on their own. Once this is
// implemented. Then update *_views_exposed_filter() to use this value
// instead.
element_id = filter.options.expose.identifier;
element = {
id: element_id,
type: filter.options.group_info.widget,
title: filter.options.expose.label,
required: filter.options.expose.required,
options: {
attributes: {
id: drupalgap_form_get_element_id(element_id, form.id)
}
},
views_field: views_field,
filter: filter,
children: []
};
// Attach the value to the element, if there is one.
// @TODO Add multi value support.
//if (!empty(filter.value)) { element.value = filter.value[0]; }
// Grab the field name and figure out which module is in charge of it.
var field_name = filter.definition.field_name;
if (field_name) {
// This is an entity field...
// Grab the field info, and determine the module that will handle it.
// Then see if hook_views_exposed_filter() has been implemented by
// that module. That module will be used to assemble the element. If
// we don't have a handler then just skip the filter.
var field = drupalgap_field_info_field(field_name);
var module = field.module;
var handler = module + '_views_exposed_filter';
if (!function_exists(handler)) {
dpm(
'WARNING: views_exposed_form() - ' + handler + '() must be ' +
'created to assemble the ' + field.type + ' filter used by ' +
field_name
);
continue;
}
// We have a handler, let's call it so the element can be assembled.
window[handler](form, form_state, element, filter, field);
}
else {
// This is NOT an entity field, so it is probably a core field (e.g.
// nid, status, etc). Let's assemble the element. In some cases we may
// just be able to forward it to a pre-existing handler.
// "Has taxonomy term" exposed filter.
if (filter.definition.handler == 'views_handler_filter_term_node_tid') {
element.type = 'autocomplete';
var autocomplete = {
remote: true,
custom: true,
handler: 'index',
entity_type: 'taxonomy_term',
value: 'name',
label: 'name',
filter: 'name'
};
if (filter.options.vocabulary != '') {
autocomplete.vid = taxonomy_vocabulary_get_vid_from_name(filter.options.vocabulary);
}
$.extend(element, autocomplete, true);
}
// User ID exposed filter.
else if (filter.definition.handler == 'views_handler_filter_user_name') {
element.type = 'autocomplete';
var autocomplete = {
remote: true,
custom: true,
handler: 'index',
entity_type: 'user',
value: 'name',
label: 'name',
filter: 'name'
};
$.extend(element, autocomplete, true);
}
else if (element.type == 'select') {
list_views_exposed_filter(form, form_state, element, filter, null);
}
else {
dpm(
'WARNING: views_exposed_form() - I do not know how to handle ' +
'the exposed filter for the "' + views_field + '" field'
);
console.log(filter);
continue;
}
}
// Finally attach the assembled element to the form.
if (element_id) { form.elements[element_id] = element; }
}
// Add the submit button.
form.elements['submit'] = {
type: 'submit',
value: options.exposed_data.submit
};
// Add the reset button, if necessary.
if (
options.exposed_data.reset &&
views_embedded_view_get(form.variables.page_id, 'exposed_filter_reset')
) {
form.buttons['reset'] = {
title: options.exposed_data.reset,
attributes: {
id: form.id + '-reset',
onclick: 'views_exposed_form_reset()'
}
};
}
//form.suffix += '
';
return form;
}
catch (error) { console.log('views_exposed_form - ' + error); }
}
/**
* The exposed filter submission handler.
* @param {Object} form
* @param {Object} form_state
*/
function views_exposed_form_submit(form, form_state) {
try {
var page_id = form.variables.page_id;
// Assemble the query string from the form state values.
var query = '';
for (var key in form_state.values) {
if (!form_state.values.hasOwnProperty(key)) { continue; }
var value = form_state.values[key];
if (empty(value)) { continue; }
query += key + '=' + encodeURIComponent(value) + '&';
}
if (!empty(query)) { query = query.substr(0, query.length - 1); }
// If there is a query set aside from previous requests, and it is equal to
// the submitted query, then stop the submission. Otherwise remove it from
// the path.
var _query = views_embedded_view_get(page_id, 'exposed_filter_query');
if (_query) {
if (_query == query) { return; }
if (form.variables.path.indexOf('&' + _query) != -1) {
form.variables.path =
form.variables.path.replace('&' + _query, '');
}
}
// Set aside a copy of the query string, so it can be removed from the path
// upon subsequent submissions of the form.
views_embedded_view_set(page_id, 'exposed_filter_query', query);
// Indicate that we have an exposed filter, so the reset button can easily
// be shown/hidden.
views_embedded_view_set(page_id, 'exposed_filter_reset', true);
// Update the path for the view, reset the pager, hold onto the variables,
// then theme the view.
form.variables.path += '&' + query;
form.variables.page = 0;
views_embedded_view_set(
page_id,
'exposed_filter_submit_variables',
form.variables
);
_theme_view(form.variables);
}
catch (error) { console.log('views_exposed_form_submit - ' + error); }
}
/**
* Handles clicks on the views exposed filter form's reset button.
*/
function views_exposed_form_reset() {
try {
var page_id = drupalgap_get_page_id();
// Reset the path to the view, the page, and the global vars, then re-theme
// the view.
var exposed_filter_submit_variables =
views_embedded_view_get(page_id, 'exposed_filter_submit_variables');
exposed_filter_submit_variables.path =
exposed_filter_submit_variables.path.replace(
'&' + views_embedded_view_get(page_id, 'exposed_filter_query'),
''
);
exposed_filter_submit_variables.page = 0;
views_embedded_view_set(
page_id,
'exposed_filter_submit_variables',
exposed_filter_submit_variables
);
views_embedded_view_set(page_id, 'exposed_filter_reset', false);
views_embedded_view_set(page_id, 'exposed_filter_query', null);
_theme_view(exposed_filter_submit_variables);
}
catch (error) { console.log('views_exposed_form_reset - ' + error); }
}
/**
* Themes a view.
* @param {Object} variables
* @return {String}
*/
function theme_view(variables) {
try {
// Create a random id attribute if one wasn't provided.
if (!variables.attributes.id) { variables.attributes.id = 'views-view--' + user_password(); }
// Since multiple pages can stack up in the DOM, warn the developer if they
// re-use a Views ID that is already in the DOM. If the ID hasn't been used
// yet, add it to the drupalgap.views.ids array.
if (in_array(variables.attributes.id, drupalgap.views.ids)) {
// Double check to make sure it is actually in the DOM. If it wasn't
// really in the DOM, remove the id from drupalgap.views.ids and continue
// onward.
if (!$('#' + variables.attributes.id)) {
// @see http://stackoverflow.com/a/3596141/763010
drupalgap.views.ids.splice(
$.inArray(variables.attributes.id, drupalgap.views.ids),
1
);
}
else {
dpm(
'WARNING: theme_view() - this id already exists in the DOM: #' +
variables.attributes.id +
' - the view will be rendered into the first container that is ' +
'located in the DOM - if you are re-using this same view, it is ' +
'recommended to append a unique identifier (e.g. an entity id) to ' +
'your views id, that way you can re-use the same view across ' +
'multiple pages.'
);
}
}
else { drupalgap.views.ids.push(variables.attributes.id); }
// Keep track of the page id for context.
var page_id = drupalgap_get_page_id();
variables.page_id = page_id;
// Since we'll by making an asynchronous call to load the view, we'll just
// return an empty div container, with a script snippet to load the view.
variables.attributes['class'] += 'view ';
var html =
'';
var options = {
page_id: page_id,
jqm_page_event: 'pageshow',
jqm_page_event_callback: '_theme_view',
jqm_page_event_args: JSON.stringify(variables)
};
return html += drupalgap_jqm_page_event_script_code(options, variables.attributes.id);
}
catch (error) { console.log('theme_view - ' + error); }
}
/**
* An internal function used to theme a view.
* @param {Object} variables
*/
function _theme_view(variables) {
try {
// Determine what page of the view we're on.
var page = 0;
if (variables.page) { page = variables.page; }
// Make a copy of variables and prepare the success callback for embedding
// the view.
var variables_copy = $.extend(
{},
{
success: function(html) {
try {
$('#' + variables.attributes.id).html(html);
}
catch (error) {
console.log('_theme_view - success - ' + error);
}
}
},
variables
);
// Finally, embed the view.
views_embed_view(variables.path + '&page=' + page, variables_copy);
}
catch (error) { console.log('_theme_view - ' + error); }
}
/**
* Returns the html string to options.success, used to embed a view.
* @param {String} path
* @param {Object} options
*/
function views_embed_view(path, options) {
try {
views_datasource_get_view_result(path, {
success: function(results) {
try {
views_embedded_view_set(options.page_id, 'results', results);
views_embedded_view_set(options.page_id, 'options', options);
if (!options.success) { return; }
options.results = results;
// Render the view if there are some results, or if there are no results and an
// empty_callback has been specified. Otherwise remove the empty div container for
// the view from the DOM.
if (results.view.count != 0 || results.view.count == 0 && options.empty_callback) {
options.success(theme('views_view', options));
}
else {
var elem = document.getElementById(options.attributes.id);
elem.parentElement.removeChild(elem);
}
}
catch (error) {
console.log('views_embed_view - success - ' + error);
}
},
error: function(xhr, status, message) {
try {
views_embedded_view_set(options.page_id, 'results', null);
if (options.error) { options.error(xhr, status, message); }
}
catch (error) {
console.log('views_embed_view - error - ' + error);
}
}
});
}
catch (error) { console.log('views_embed_view - ' + error); }
}
/**
* Theme's a view.
* @param {Object} variables
* @return {String}
*/
function theme_views_view(variables) {
try {
var html = '';
// Extract the results.
var results = views_embedded_view_get(variables.page_id, 'results');
if (!results) { return html; }
// Figure out the format.
if (!variables.format) { variables.format = 'unformatted_list'; }
// Extract the root and child object name.
var root = results.view.root;
var child = results.view.child;
// Is there a title to display?
if (variables.title) {
var title_attributes = variables.title_attributes ?
variables.title_attributes : null;
html += theme(
'header',
{ text: variables.title, attributes: title_attributes }
);
// Place spacers after the header for each format, except unformatted.
if (variables.format != 'unformatted') {
html += theme('views_spacer', null);
}
}
// Render the exposed filters if there are any, and the developer didn't explicitly exclude
// them via the Views Render Array.
var views_exposed_form_html = '';
if (typeof results.view.exposed_data !== 'undefined' &&
(typeof variables.exposed_filters === 'undefined' || variables.exposed_filters)
) {
views_exposed_form_html = drupalgap_get_form(
'views_exposed_form', {
exposed_data: results.view.exposed_data,
exposed_raw_input: results.view.exposed_raw_input,
filter: results.view.filter,
variables: variables
}
);
}
// Determine views container selector and place it in the global context.
var selector = '#' + variables.page_id + ' #' + variables.attributes.id;
views_embedded_view_set(variables.page_id, 'selector', selector);
// Are the results empty? If so, return the empty callback's html, if it
// exists. Often times, the empty callback will want to place html that
// needs to be enhanced by jQM, therefore we'll set a timeout to trigger
// the creation of the content area.
// @TODO putting views_litepager support here is a hack, we should be
// implementing views_litepager_views_view for theme_views_view() instead.
var views_litepager_present = module_exists('views_litepager');
if (
(results.view.count == 0 && !views_litepager_present) ||
(views_litepager_present && results.view.pages == null && results.view.count == 0)
) {
$(selector).hide();
setTimeout(function() {
$(selector).trigger('create').show('fast');
}, 100);
if (variables.empty_callback && function_exists(variables.empty_callback)) {
var empty_callback = window[variables.empty_callback];
return views_exposed_form_html + drupalgap_render(empty_callback(results.view, variables));
}
return html + views_exposed_form_html;
}
// The results are not empty...
// Append the exposed filter html.
html += views_exposed_form_html;
// Render all the rows.
var result_formats = drupalgap_views_get_result_formats(variables);
var rows = '' + result_formats.open + drupalgap_views_render_rows(
variables,
results,
root,
child,
result_formats.open_row,
result_formats.close_row
) + result_formats.close;
// If we have any pages, render the pager above or below the results
// according to the pager_pos setting.
var pager = '';
if (results.view.pages) { pager = theme('pager', variables); }
var pager_pos = 'top';
if (typeof variables.pager_pos !== 'undefined') {
pager_pos = variables.pager_pos;
}
// Append the rendered rows and the pager to the html string according to
// the pager position, unless the views infinite scroll module is enabled,
// then no pager at all.
// @TODO having this special case for views_infinite_scroll is a hack, we
// obviously need a hook or something around here...
if (
module_exists('views_infinite_scroll') &&
views_infinite_scroll_ok()
) { html += rows; }
else if (pager_pos == 'top') {
html += pager;
if (!empty(pager)) { html += theme('views_spacer', null); }
html += rows;
}
else if (pager_pos == 'bottom') {
html += rows;
if (!empty(pager)) { html += theme('views_spacer', null); }
html += pager;
}
else {
console.log('WARNING: theme_views_view - unsupported pager_pos (' +
pager_pos +
')');
}
// Since the views content is injected dynamically after the page is loaded,
// we need to have jQM refresh the page to add its styling.
$(selector).hide();
setTimeout(function() {
$(selector).trigger('create').show('fast');
}, 100);
return html;
}
catch (error) { console.log('theme_views_view - ' + error); }
}
/**
* Themes a spacer that can be placed between displayed components of the view.
* @param {Object} variables
* @return {String}
*/
function theme_views_spacer(variables) {
return '
';
}
/**
* Themes a pager.
* @param {Object} variables
* @return {String}
*/
function theme_pager(variables) {
try {
var html = '';
// Extract the view and pager data.
var view = variables.results.view;
var page = view.page;
var pages = view.pages;
var count = view.count;
var limit = view.limit;
var page = view.page;
// If we don't have any results, return.
// @TODO putting views_litepager support here is a hack, we should be
// implementing views_litepager_pager() for theme_pager() instead.
var views_litepager_present = module_exists('views_litepager');
if (
(count == 0 && !views_litepager_present) ||
(views_litepager_present && variables.results.view.pages == null)
) { return html; }
// Add the pager items to the list.
var items = [];
if (page != 0) { items.push(theme('pager_previous', variables)); }
if (
(page != pages - 1 && !views_litepager_present) ||
views_litepager_present
) { items.push(theme('pager_next', variables)); }
if (items.length > 0) {
// Make sure we have an id to use since we need to dynamically build the
// navbar container for the pager. If we don't have one, generate a random
// one.
var id = 'theme_pager_' + user_password();
var attrs = {
id: id,
'class': 'pager',
'data-role': 'navbar'
};
html += '
' + theme('item_list', {
items: items
}) + '
' +
'';
}
return html;
}
catch (error) { console.log('theme_pager - ' + error); }
}
/**
* Themes a pager link.
* @param {Object} variables
* @param {Object} link_vars
* @return {String}
*/
function theme_pager_link(variables, link_vars) {
try {
if (!link_vars.attributes) { link_vars.attributes = {}; }
link_vars.attributes.href = '#';
var attributes = drupalgap_attributes(link_vars.attributes);
return "' +
link_vars.text +
'';
}
catch (error) { console.log('theme_pager_link - ' + error); }
}
/**
* An internal function used to generate the onclick handler JS for a pager
* link.
* @param {Object} variables
* @return {String}
*/
function _theme_pager_link_onclick(variables) {
try {
// Make a copy of variables. While doing so remove any results from it
// because we don't want them in the onclick handler's html. The results
// will be available in the _views_embed_view_results variable if they are
// needed
var copy = $.extend({ }, { }, variables);
if (copy.results) { delete copy.results; }
var onclick = '_theme_pager_link_click(' + JSON.stringify(copy) + ')';
return onclick;
}
catch (error) { console.log('_theme_pager_link_onclick - ' + error); }
}
/**
* An internal function used to handle clicks on pager links.
* @param {Object} variables
*/
function _theme_pager_link_click(variables) {
try {
_theme_view(variables);
}
catch (error) { console.log('_theme_pager_link_click - ' + error); }
}
/**
* Themes a pager next link.
* @param {Object} variables
* @return {String}
*/
function theme_pager_next(variables) {
try {
var html;
variables.page = parseInt(variables.results.view.page) + 1;
var link_vars = {
text: '»',
attributes: {
'class': 'pager_next'
}
};
html = theme_pager_link(variables, link_vars);
return html;
}
catch (error) { console.log('theme_pager_next - ' + error); }
}
/**
* Themes a pager previous link.
* @param {Object} variables
* @return {String}
*/
function theme_pager_previous(variables) {
try {
var html;
variables.page = parseInt(variables.results.view.page) - 1;
var link_vars = {
text: '«',
attributes: {
'class': 'pager_previous'
}
};
html = theme_pager_link(variables, link_vars);
return html;
}
catch (error) { console.log('theme_pager_previous - ' + error); }
}
/**
* A helper function used to retrieve the various open and closing tags for
* views results, depending on their format.
* @param {Object} variables
* @return {Object}
*/
function drupalgap_views_get_result_formats(variables) {
try {
var result_formats = {};
// Depending on the format, let's render the container opening and closing,
// and then render the rows.
if (!variables.format) { variables.format = 'unformatted_list'; }
var open = '';
var close = '';
var open_row = '';
var close_row = '';
// Prepare the format's container attributes.
var format_attributes = {};
if (typeof variables.format_attributes !== 'undefined') {
format_attributes = $.extend(
true,
format_attributes,
variables.format_attributes
);
}
// Add a views-results class
if (typeof format_attributes['class'] === 'undefined') {
format_attributes['class'] = '';
}
format_attributes['class'] += ' views-results ';
switch (variables.format) {
case 'grid':
// Determine columns and jqm grid type.
var columns = jqm_grid_verify_columns(variables);
var grid = jqm_grid_get_type(columns);
// Prep the container class name.
if (!format_attributes.class) { format_attributes.class = ''; }
format_attributes.class += ' ui-grid-' + grid + ' ';
// Build the strings.
open = '
';
close = '
';
open_row = '
'; // This class will be replaced dynamically.
close_row = '
';
break;
case 'ul':
if (typeof format_attributes['data-role'] === 'undefined') {
format_attributes['data-role'] = 'listview';
}
open = '
';
close = '
';
open_row = '
';
close_row = '
';
break;
case 'ol':
if (typeof format_attributes['data-role'] === 'undefined') {
format_attributes['data-role'] = 'listview';
}
open = '';
close = '';
open_row = '
';
close_row = '
';
break;
case 'table':
case 'jqm_table':
if (variables.format == 'jqm_table') {
if (typeof format_attributes['data-role'] === 'undefined') {
format_attributes['data-role'] = 'table';
}
if (typeof format_attributes['data-mode'] === 'undefined') {
format_attributes['data-mode'] = 'reflow';
}
console.log(
'WARNING: theme_views_view() - jqm_table not supported, yet'
);
}
open = '
';
close = '
';
open_row = '
';
close_row = '
';
break;
case 'unformatted_list':
default:
if (typeof format_attributes['class'] === 'undefined') {
format_attributes['class'] = '';
}
format_attributes['class'] += ' views-rows';
open = '
';
close = '
';
open_row = '';
close_row = '';
break;
}
result_formats.open = open;
result_formats.close = close;
result_formats.open_row = open_row;
result_formats.close_row = close_row;
return result_formats;
}
catch (error) { console.log('drupalgap_views_get_result_formats - ' + error); }
}
/**
* A helper function used to render a views result's rows.
* @param {Object}
* @param {Object}
* @param {String}
* @param {String}
* @param {String}
* @param {String}
* @return {String}
*/
function drupalgap_views_render_rows(variables, results, root, child, open_row, close_row) {
try {
var html = '';
var totalRows = results[root].length;
for (var count in results[root]) {
if (!results[root].hasOwnProperty(count)) { continue; }
// Extract the row and mark its position.
var object = results[root][count];
var row = object[child];
row._position = parseInt(count);
// If a row_callback function exists, call it to render the row,
// otherwise use the default row render mechanism.
var row_content = '';
if (variables.row_callback && function_exists(variables.row_callback)) {
row_callback = window[variables.row_callback];
row_content = row_callback(results.view, row, variables);
}
else { row_content = JSON.stringify(row); }
// If we're rendering a grid, replace the class name for the current column. Otherwise
// just render the row as usual.
if (variables.format == 'grid') {
var className = jqm_grid_get_item_class(row._position, variables.columns);
var openRow = jqm_grid_set_item_row_class(open_row, className);
html += openRow + row_content + close_row;
}
else { html += open_row + row_content + close_row; }
}
return html;
}
catch (error) { console.log('drupalgap_views_render_rows - ' + error); }
}
/**
* @deprecated - Use views_datasource_get_view_result() instead.
*/
drupalgap.views_datasource = {
'options': { },
'call': function(options) {
try {
var msg = 'WARNING: drupalgap.views_datasource has been deprecated! ' +
'Use views_datasource_get_view_result() instead.';
console.log(msg);
views_datasource_get_view_result(options.path, options);
}
catch (error) { console.log('drupalgap.views_datasource - ' + error); }
}
};