diff --git a/docs/openlayers.api.php b/docs/openlayers.api.php index 7951b81..dcb75f3 100644 --- a/docs/openlayers.api.php +++ b/docs/openlayers.api.php @@ -282,3 +282,33 @@ function hook_openlayers_presets() { ); return array('default' => $default); } + +/** + * CTools Registration Hook (Style Plugins) + * + * IMPORTANT: + * + * In order to support style plugins, the first step is to + * tell CTools where to find the plugin. + * + * This function is just an example implementation of + * hook_ctools_plugin_directory() and should be alter according to + * your module's name. + * + * For an example, please see the openlayers_test.module + * + * @param $module + * Name of a module that supports CTools exportables. + * @param $plugin + * Name of the kind of plugin supported. + * @return + * If $module is 'openlayers', and $api is a type of exportable that + * your module provides, and you are using Openlayers 2.x, then + * return the directory relative to a module to look for this + * particular plugin. + */ +function openlayers_ctools_plugin_directory($module, $plugin) { + if ($module == 'openlayers' && $plugin == 'style_plugin') { + return 'plugins/style_plugin'; + } +} \ No newline at end of file diff --git a/includes/openlayers.render.inc b/includes/openlayers.render.inc index cace450..24dcc14 100644 --- a/includes/openlayers.render.inc +++ b/includes/openlayers.render.inc @@ -72,10 +72,41 @@ function _openlayers_behaviors_render($behaviors = array(), &$map = array()) { */ function _openlayers_styles_process($styles = array(), $layer_styles = array(), &$map = array()) { + ctools_include('plugins'); // Get styles info array $styles_info = openlayers_styles(); + // Process with handler if available. + $used_plugins = array(); + foreach ($styles_info as $i => $style) { + // Check for property plugins. + foreach ($style->data as $prop => $propval) { + if ( is_array($propval) ) { + $plugname = $propval['plugin']; + if ( ! empty($plugname) ) { + // the above should never happen, except + // for cases in which the old style plugin + // system was used (for prop == 'plugins') + $used_plugins[$plugname] = TRUE; + } + } + } + } + + if ( ! empty($used_plugins) ) { + $style_plugins = openlayers_style_plugins(); + foreach (array_keys($used_plugins) as $plugname) { + $plug = $style_plugins[$plugname]; + $plugin_class = ctools_plugin_get_class($plug, 'style_plugin'); + if (isset($plugin_class)) { + $plugin = new $plugin_class; + $plugin->render(); + } + } + } + + // Go through styles $processed = array(); foreach ($styles as $k => $style) { diff --git a/js/openlayers.js b/js/openlayers.js index e8d1589..f317b9e 100644 --- a/js/openlayers.js +++ b/js/openlayers.js @@ -283,16 +283,71 @@ Drupal.openlayers = { }, 'getStyleMap': function(map, layername) { if (map.styles) { + var stylesAdded = {}; + // Grab and map base styles. for (var style in map.styles) { stylesAdded[style] = new OpenLayers.Style(map.styles[style]); } + // Implement layer-specific styles. if (map.layer_styles !== undefined && map.layer_styles[layername]) { - var style = map.layer_styles[layername]; - stylesAdded['default'] = new OpenLayers.Style(map.styles[style]); + + var style_name = map.layer_styles[layername]; + var style = map.styles[style_name]; // TODO: skip if undefined + + // Build context object and callback values + var newContext = {} + for (var propname in style) { + if (typeof style[propname] == 'object') { + var plugin_name = style[propname]['plugin']; + var plugin_options = style[propname]['conf']; + var plugin_class = Drupal.openlayers.style_plugin[plugin_name]; + // Check for existance of plugin_context_class here + if ( typeof plugin_class === 'function' ) { + var plugin_context = new plugin_class(plugin_options); + + // Add plugin context functions to global context + for (var key in plugin_context) { + var newkey = plugin_name + '_' + propname + '_' + key; + var val = plugin_context[key]; + if ( typeof val === 'function' ) { + newContext[newkey] = OpenLayers.Function.bind(val, + plugin_context); + } + } + } + style[propname] = '${' + newkey + '}'; + } + } + + // Define parameters from plugin, if available + var plugins = map.styles[style_name].plugins; + for (var plugin_name in style.plugins) + { + var plugin_options = style.plugins[plugin_name]; + var plugin_context_class = Drupal.openlayers.style_plugin[plugin_name]; + // Check for existance of plugin_context_class here + if ( typeof plugin_context_class === 'function' ) { + var plugin_context = new plugin_context_class(plugin_options); + + // Add plugin context functions to global context + for (var key in plugin_context) { + var newkey = plugin_name + '_' + key; + var val = plugin_context[key]; + if ( typeof val === 'function' ) { + newContext[newkey] = OpenLayers.Function.bind(val, plugin_context); // plugin_method_scope); + } + } + } + } + + // Put together style_name + stylesAdded['default'] = stylesAdded['select'] = new OpenLayers.Style(style, + { context: newContext } ); } + return new OpenLayers.StyleMap(stylesAdded); } // Default styles @@ -323,3 +378,4 @@ Drupal.openlayers = { }; Drupal.openlayers.layer = {}; +Drupal.openlayers.style_plugin = {}; diff --git a/modules/openlayers_ui/includes/openlayers_ui.styles.inc b/modules/openlayers_ui/includes/openlayers_ui.styles.inc index b0b749e..3c72b06 100644 --- a/modules/openlayers_ui/includes/openlayers_ui.styles.inc +++ b/modules/openlayers_ui/includes/openlayers_ui.styles.inc @@ -12,9 +12,7 @@ /** * Styles add/edit form. */ -function openlayers_ui_styles_form(&$form_state, $style = NULL, $edit = FALSE) { - $form = array(); - +function openlayers_ui_styles_get_properties() { // Available styling properies. Defaults and descriptions are taken // from OpenLayers. // @see http://docs.openlayers.org/library/feature_styling.html @@ -202,17 +200,34 @@ function openlayers_ui_styles_form(&$form_state, $style = NULL, $edit = FALSE) { 'desc' => t('Label font weight.'), ), ); + + return $properties; +} + +/** + * Styles add/edit form. + */ +function openlayers_ui_styles_form(&$form_state, $style = NULL, $edit = FALSE) { + $form = array(); + + $properties = openlayers_ui_styles_get_properties(); // Pass style data along $form['style_data'] = array( '#type' => 'value', - '#value' => $properties, + '#value' => array( + 'definitions' => $properties, + 'defaults' => $style->data + ) ); // Style object basics $form['info'] = array( '#type' => 'fieldset', '#tree' => FALSE, + '#title' => t('Basic Information'), + '#description' => t('The basic information for the style, used to refer to and describe the style.'), + '#collapsible' => TRUE, ); $form['info']['name'] = array( '#title' => t('Name'), @@ -233,21 +248,89 @@ function openlayers_ui_styles_form(&$form_state, $style = NULL, $edit = FALSE) { ); // OpenLayers style properties - $form['data'] = array('#type' => 'fieldset', '#tree' => TRUE); - + $form['data'] = array( + '#type' => 'fieldset', + '#tree' => TRUE, + '#title' => t('Style Properties and Plugins'), + '#description' => t('Style properties are properties as + defined by the OpenLayers library. Plugins are dynamically + process the layer at render time; plugins may override the + values that you have set for style properies.'), + '#collapsible' => TRUE, + ); foreach ($properties as $key => $prop) { + $form['data'][$key] = array( - '#type' => !isset($prop['options']) ? 'textfield' : 'select', + '#type' => 'fieldset', + '#tree' => TRUE, '#title' => $key, '#description' => $prop['desc'], - '#default_value' => isset($style->data[$key]) ? - $style->data[$key] : $prop['default'], + '#collapsible' => TRUE, + '#collapsed' => TRUE, ); - - // Add options if needed - if (isset($prop['options']) && is_array($prop['options'])) { - $form['data'][$key]['#options'] = $prop['options']; + + $def_value = $prop['default']; + $def_use_plugin = ''; + + if ( isset($style->data[$key]) ) { + if ( is_array($style->data[$key]) ) { + $def_use_plugin = $style->data[$key]['plugin']; + } else { + $def_value = $style->data[$key]; + } } + + // Add plugin options, if any + $handling_plugins = openlayers_ui_get_style_plugins_for_property($key); + + if ( ! empty($handling_plugins) ) { + $plugin_options = array('' => 'NO'); + foreach ($handling_plugins as $plugname => $plug) { + $plugin_options[$plugname] = $plug['title']; + } + + $form['data'][$key]['uses_plugin'] = array( + '#title' => t('Use plugin'), + '#type' => 'select', + '#options' => $plugin_options, + '#default_value' => $def_use_plugin, + '#ahah' => array( + 'path' => 'openlayers/ahah/style_plugin/' . $key, + 'wrapper' => $key . '-style-plugin', + 'method' => 'replace', + 'effect' => 'fade', // 'fade', 'none', 'slide' + ) + ); + } + + // Hackish... but is that new for HTML ? ... + $form['data'][$key]['plugin_conf_start'] = array( + '#value' => '
"; + var_dump($propname); + $output = ob_get_clean(); + drupal_json(array('status' => TRUE, 'data' => $output)); + return; +*/ + + // Get cached form + $form_state = array('storage' => NULL, 'submitted' => FALSE); + $form_build_id = $_POST['form_build_id']; + $form_cached = form_get_cache($form_build_id, $form_state); + + // Get style_data from cached form, and defaults + $style_data = $form_cached['style_data']['#value']; + $defaults = $style_data['defaults']; + + + + if ( ! $plugname ) { + + // Find default value + $properties = $style_data['definitions']; + $prop = $properties[$propname]; + $def_value = $prop['default']; + if ( isset($defaults[$propname]) && ! is_array($defaults[$propname]) ) { + $def_value = $defaults[$propname]; + } + + $form = array( + '#type' => !isset($prop['options']) ? 'textfield' : 'select', + '#default_value' => $def_value + ); + + // Add options if needed + if (isset($prop['options']) && is_array($prop['options'])) { + $form['value']['#options'] = $prop['options']; + } + + $form['#parents'] = array('data', $propname, 'value'); + + } else { + + // Find default value + $defs = array(); + if ( isset($defaults[$propname]) && is_array($defaults[$propname]) + && $defaults[$propname]['plugin'] == $plugname ) { + $defs = $defaults[$propname]['conf']; + } + +/* + ob_start(); + echo ""; + var_dump($defs); + $output = ob_get_clean(); + drupal_json(array('status' => TRUE, 'data' => $output)); + return; +*/ + + $form = openlayers_ui_get_style_plugin_form($plugname, $defs); + $form['#parents'] = array('data', $propname, 'plugin'); + + } + + + $form_state = array(); // TODO: what to do with this ? + $plugform_built = form_builder('style_plugin_form', $form, $form_state); + + $output = drupal_render($plugform_built); + +/* + ob_start(); + echo ""; + var_dump($plugform_built); + $output = ob_get_clean(); + drupal_json(array('status' => TRUE, 'data' => $output)); + return; +*/ + + // Final rendering callback. + drupal_json(array('status' => TRUE, 'data' => $output)); + +} diff --git a/modules/openlayers_ui/openlayers_ui.module b/modules/openlayers_ui/openlayers_ui.module index fe77d5d..f3c3f20 100644 --- a/modules/openlayers_ui/openlayers_ui.module +++ b/modules/openlayers_ui/openlayers_ui.module @@ -324,6 +324,14 @@ function openlayers_ui_menu() { 'file' => 'includes/openlayers_ui.presets.inc', 'type' => MENU_CALLBACK, ); + $items['openlayers/ahah/style_plugin/%'] = array( + 'title' => 'OpenLayers Style Plugin AHAH', + 'page callback' => 'openlayers_ui_style_plugin_ahah', + 'page arguments' => array(3), // 3rd url item (the '%') is first arg + 'access callback' => TRUE, + 'file' => 'includes/openlayers_ui.styles.inc', + 'type' => MENU_CALLBACK, + ); return $items; } diff --git a/openlayers.module b/openlayers.module index c730349..cc39779 100644 --- a/openlayers.module +++ b/openlayers.module @@ -447,6 +447,21 @@ function openlayers_behaviors($reset = FALSE) { } /** + * Get all style plugins. + * + * @ingroup openlayers_api + * + * @param $reset + * Boolean whether to reset cache or not. + * @return + * Array of style handler info. + */ +function openlayers_style_plugins($reset = FALSE) { + ctools_include('plugins'); + return ctools_get_plugins('openlayers', 'style_plugin'); +} + +/** * Get all openlayers styles. * * @ingroup openlayers_api @@ -904,6 +919,88 @@ class openlayers_layer_type { } /** + * Base class for style plugins + * + * We define base classes in the core module. + * All other parent classes can be autoloaded through ctools. + */ +class openlayers_style_plugin { + + /** + * Return true if this plugin can handle + * the given property + */ + function can_handle_property($propname) { + return array_key_exists($propname, $this->get_context_properties()); + } + + /** + * Get an array of style property callbacks + * + * @return + * Array of=> + */ + function get_context_properties() { + return array(); + } + + /** + * Initial default options. + * + * @return + * Array of default options. + */ + function options_init() { + return array(); + } + + /** + * Options form. + * + * @param $defaults + * Array of default values for the form. + * @return + * Array of Drupal form elements. + */ + function options_form($defaults = array()) { + return array(); + } + + /** + * Render the style. + */ + function render() { + // Render style. + } +} + +/** + * Implementation of hook_ctools_plugin_directory + */ +function openlayers_ctools_plugin_directory($module, $plugin) { + // The format of plugin includes should be the + // following: + // modulename_plugin_name.inc + // + // For example: + // openlayers_style_plugin_name.inc + + // If this module needed to supply style plugins. + /* + if ($module == 'openlayers' && $plugin == 'style_plugin') { + return 'plugins/style_plugin'; + } + */ + + // This should change to the following when converted: + /* + if ($module == 'openlayers') { + return 'plugins/' . $plugin; + } + */ +} + +/** * Implementation of hook_ctools_plugin */ function openlayers_ctools_plugin_behaviors() { diff --git a/tests/openlayers_test.module b/tests/openlayers_test.module index 9e0d826..67362cf 100644 --- a/tests/openlayers_test.module +++ b/tests/openlayers_test.module @@ -30,6 +30,16 @@ function openlayers_test_menu() { } /** + * Implementation of hook_ctools_plugin_directory + */ +function openlayers_test_ctools_plugin_directory($module, $plugin) { + if ($module == 'openlayers' && $plugin == 'style_plugin') { + return 'plugins/style_plugin'; + } +} + + +/** * Implementation of hook_ctools_plugin_api(). */ function openlayers_test_ctools_plugin_api($module, $api) { @@ -38,6 +48,9 @@ function openlayers_test_ctools_plugin_api($module, $api) { switch ($api) { case 'openlayers_presets': return array('version' => 1); + + case 'openlayers_styles': + return array('version' => 1); } } @@ -455,4 +468,4 @@ function openlayers_test_openlayers_map_alter(&$map) { drupal_set_message(t('OpenLayers map alter hook fired.')); $performed = TRUE; } -} \ No newline at end of file +} diff --git a/tests/plugins/style_plugin/openlayers_test_rnd_factor.inc b/tests/plugins/style_plugin/openlayers_test_rnd_factor.inc new file mode 100644 index 0000000..9112f72 --- /dev/null +++ b/tests/plugins/style_plugin/openlayers_test_rnd_factor.inc @@ -0,0 +1,61 @@ + t('TEST: random factor'), + 'description' => t('Example style plugin for context styling. ' + . 'Provides a random 0..1 factors.'), + 'style_plugin' => array( + 'class' => 'openlayers_test_rnd_factor', + 'parent' => 'openlayers_style_plugin', + ), +); + +/** + * Style Plugin for testing purposes. + */ +class openlayers_test_rnd_factor extends + openlayers_style_plugin { + /** + * Provide initial values for options. + */ + function options_init() { + return array( + ); + } + + /** + * Options form. + */ + function options_form($defaults = array()) { + $form = array(); + return $form; + } + + /** + * Get an array of style property callbacks + */ + function get_context_properties() { + return array( + 'fillOpacity' => 'getFactor', + 'strokeOpacity' => 'getFactor', + 'graphicOpacity' => 'getFactor', + ); + } + + + /** + * Render function + */ + function render() { + // Add JS + drupal_add_js(drupal_get_path('module', 'openlayers_test') . + '/plugins/style_plugin/openlayers_test_rnd_factor.js'); + } +} diff --git a/tests/plugins/style_plugin/openlayers_test_rnd_factor.js b/tests/plugins/style_plugin/openlayers_test_rnd_factor.js new file mode 100644 index 0000000..823de59 --- /dev/null +++ b/tests/plugins/style_plugin/openlayers_test_rnd_factor.js @@ -0,0 +1,26 @@ +// $Id$ + +/** + * @file + * File to hold custom context styling + */ + +/** + * Style plugin context class + */ +Drupal.openlayers.style_plugin.openlayers_test_rnd_factor = function (params) { + this.params = params; +}; + +/** + * Style plugin context class methods + */ +Drupal.openlayers.style_plugin.openlayers_test_rnd_factor.prototype = { + + // Fill opacity context. Sets random fill opacity. + 'getFactor' : function(feature) { + // Random factor + return Math.random(); + } + +}; diff --git a/tests/plugins/style_plugin/openlayers_test_rnd_int.inc b/tests/plugins/style_plugin/openlayers_test_rnd_int.inc new file mode 100644 index 0000000..c72fd90 --- /dev/null +++ b/tests/plugins/style_plugin/openlayers_test_rnd_int.inc @@ -0,0 +1,88 @@ + t('TEST: random integer'), + 'description' => t('Example style plugin for context styling. ' + . 'Provides random integers.'), + 'style_plugin' => array( + 'class' => 'openlayers_test_rnd_int', + 'parent' => 'openlayers_style_plugin', + ), +); + +/** + * Style Plugin for testing purposes. + */ +class openlayers_test_rnd_int extends + openlayers_style_plugin { + /** + * Provide initial values for options. + */ + function options_init() { + return array( + 'low' => 1, + 'high' => 10, + ); + } + + /** + * Options form. + */ + function options_form($defaults = array()) { + $form = array(); + + // Allow use to pick the highest and lowest for random + // point radius + $form['low'] = array( + '#type' => 'textfield', + '#title' => t('Lowest value'), + '#description' => t('Lowest value for the random integer.'), + '#default_value' => isset($defaults['low']) ? + $defaults['low'] : 2, + ); + $form['high'] = array( + '#type' => 'textfield', + '#title' => t('Highest value'), + '#description' => t('Highest value for the random integer.'), + '#default_value' => isset($defaults['high']) ? + $defaults['high'] : 10, + ); + + return $form; + } + + /** + * Get an array of style property callbacks + */ + function get_context_properties() { + return array( + 'pointRadius' => 'getInt', + 'strokeWidth' => 'getInt', + 'graphicWidth' => 'getInt', + 'graphicHeight' => 'getInt', + 'graphicXOffset' => 'getInt', + 'graphicYOffset' => 'getInt', + 'rotation' => 'getInt', + 'labelXOffset' => 'getInt', + 'labelYOffset' => 'getInt', + 'fontSize' => 'getInt', + ); + } + + + /** + * Render function + */ + function render() { + // Add JS + drupal_add_js(drupal_get_path('module', 'openlayers_test') . + '/plugins/style_plugin/openlayers_test_rnd_int.js'); + } +} diff --git a/tests/plugins/style_plugin/openlayers_test_rnd_int.js b/tests/plugins/style_plugin/openlayers_test_rnd_int.js new file mode 100644 index 0000000..b575d70 --- /dev/null +++ b/tests/plugins/style_plugin/openlayers_test_rnd_int.js @@ -0,0 +1,37 @@ +// $Id$ + +/** + * @file + * File to hold custom context styling + */ + +/** + * Style plugin context class + */ +Drupal.openlayers.style_plugin.openlayers_test_rnd_int = function (params) { + this.params = params; + this.params.high = parseInt(this.params.high); + this.params.low = parseInt(this.params.low); +}; + +/** + * Style plugin context class methods + */ +Drupal.openlayers.style_plugin.openlayers_test_rnd_int.prototype = { + + // Private methods (not copied to final style context object) + 'prv' : { + 'random' : function(low, high) { + return Math.floor(Math.random() * (high-low+1)) + low; + } + }, + + // Point radius context. Given paramters, gets a random + // pointRadius. + 'getInt' : function(feature) { + var high = this.params.high; + var low = this.params.low; + var ret = this.prv.random(low, high); + return ret; + } +};