// 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 <body> 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 <head>. 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', drupalgap_back, 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]; }
$('<link/>', {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($('<li></li>', { 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 + '<td>' + row[j] + '</td>';
}
$('<tr>').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 '<script type="text/javascript">' +
'$("#' + options.page_id + '").on("' +
options.jqm_page_event + '", drupalgap_jqm_page_event_fire(' +
event_fire_args +
'));' +
'</script>';
}
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); }
}