mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-07 16:44:20 +01:00
remove jQueryUI DateTime picker and splitter and use them also for eTemplates marked as legacy
This commit is contained in:
parent
97418862f4
commit
ef7c175814
@ -13,8 +13,8 @@
|
||||
use EGroupware\Api;
|
||||
|
||||
// add et2- prefix to following widgets/tags
|
||||
const ADD_ET2_PREFIX_REGEXP = '#<((/?)([vh]?box|textbox|textarea|button|colorpicker|description|image|url(-email|-phone|-fax)?))(/?|\s[^>]*)>#m';
|
||||
const ADD_ET2_PREFIX_LAST_GROUP = 5;
|
||||
const ADD_ET2_PREFIX_REGEXP = '#<((/?)([vh]?box|date(-time[^\s]*|-duration)?|textbox|textarea|button|colorpicker|description|image|url(-email|-phone|-fax)?))(/?|\s[^>]*)>#m';
|
||||
const ADD_ET2_PREFIX_LAST_GROUP = 6;
|
||||
|
||||
// switch evtl. set output-compression off, as we cant calculate a Content-Length header with transparent compression
|
||||
ini_set('zlib.output_compression', 0);
|
||||
@ -52,201 +52,198 @@ function send_template()
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
/* disable caching for now, as you need to delete the cache, once you change ADD_ET2_PREFIX_REGEXP
|
||||
$cache = $GLOBALS['egw_info']['server']['temp_dir'].'/egw_cache/eT2-Cache-'.$GLOBALS['egw_info']['server']['install_id'].$_SERVER['PATH_INFO'];
|
||||
if (file_exists($cache) && filemtime($cache) > filemtime($path) &&
|
||||
$cache = $GLOBALS['egw_info']['server']['temp_dir'].'/egw_cache/eT2-Cache-'.
|
||||
$GLOBALS['egw_info']['server']['install_id'].'-'.str_replace('/', '-', $_SERVER['PATH_INFO']);
|
||||
if (file_exists($cache) && filemtime($cache) > max(filemtime($path), filemtime(__FILE__)) &&
|
||||
($str = file_get_contents($cache)) !== false)
|
||||
{
|
||||
$cache_read = microtime(true);
|
||||
}
|
||||
else*/
|
||||
if(($str = file_get_contents($path)) !== false &&
|
||||
// eTemplate marked as legacy, return unchanged template without web-components / et2-prefix
|
||||
!preg_match('/<overlay[^>]* legacy="true"/', $str))
|
||||
elseif(($str = file_get_contents($path)) !== false)
|
||||
{
|
||||
// fix <menulist...><menupopup type="select-*"/></menulist> --> <select type="select-*" .../>
|
||||
$str = preg_replace('#<menulist([^>]*)>[\r\n\s]*<menupopup([^>]+>)[\r\n\s]*</menulist>#', '<select$1$2', $str);
|
||||
|
||||
// fix legacy options, so new client-side has not to deal with them
|
||||
$str = preg_replace_callback('#<([^- />]+)(-[^ ]+)?[^>]* (options="([^"]+)")[ />]#', static function($matches)
|
||||
{
|
||||
// take care of (static) type attribute, if used
|
||||
if (preg_match('/ type="([a-z-]+)"/', $matches[0], $type))
|
||||
{
|
||||
str_replace('<'.$matches[1].$matches[2], '<'.$type[1], $matches[0]);
|
||||
str_replace($type[0], '', $matches[0]);
|
||||
list($matches[1], $matches[2]) = explode('-', $type[1], 2);
|
||||
}
|
||||
static $legacy_options = array( // use "ignore" to ignore further comma-sep. values, otherwise they are all in last attribute
|
||||
'select' => 'empty_label,ignore',
|
||||
'select-account' => 'empty_label,account_type,ignore',
|
||||
'box' => ',cellpadding,cellspacing,keep',
|
||||
'hbox' => 'cellpadding,cellspacing,keep',
|
||||
'vbox' => 'cellpadding,cellspacing,keep',
|
||||
'groupbox' => 'cellpadding,cellspacing,keep',
|
||||
'checkbox' => 'selected_value,unselected_value,ro_true,ro_false',
|
||||
'radio' => 'set_value,ro_true,ro_false',
|
||||
'customfields' => 'sub-type,use-private,field-names',
|
||||
'date' => 'data_format,ignore', // Legacy option "mode" was never implemented in et2
|
||||
'description' => 'bold-italic,link,activate_links,label_for,link_target,link_popup_size,link_title',
|
||||
'button' => 'image,ro_image',
|
||||
'buttononly' => 'image,ro_image',
|
||||
);
|
||||
// prefer more specific type-subtype over just type
|
||||
$names = $legacy_options[$matches[1].$matches[2]] ?? $legacy_options[$matches[1]] ?? null;
|
||||
if (isset($names))
|
||||
{
|
||||
$names = explode(',', $names);
|
||||
$values = Api\Etemplate\Widget::csv_split($matches[4], count($names));
|
||||
if (count($values) < count($names))
|
||||
{
|
||||
$values = array_merge($values, array_fill(count($values), count($names)-count($values), ''));
|
||||
}
|
||||
$attrs = array_diff(array_combine($names, $values), ['', null]);
|
||||
unset($attrs['ignore']);
|
||||
// fix select options can be either multiple or empty_label
|
||||
if ($matches[1] === 'select' && !empty($attrs['empty_label']) && (int)$attrs['empty_label'] > 0)
|
||||
{
|
||||
$attrs['multiple'] = (int)$attrs['empty_label'];
|
||||
unset($matches['empty_label']);
|
||||
}
|
||||
$options = '';
|
||||
foreach($attrs as $attr => $value)
|
||||
{
|
||||
$options .= $attr.'="'.$value.'" ';
|
||||
}
|
||||
return str_replace($matches[3], $options, $matches[0]);
|
||||
}
|
||||
return $matches[0];
|
||||
}, $str);
|
||||
|
||||
// fix deprecated attributes: needed, blur, ...
|
||||
static $deprecated = [
|
||||
'needed' => 'required',
|
||||
'blur' => 'placeholder',
|
||||
];
|
||||
$str = preg_replace_callback('#<[^ ]+[^>]* ('.implode('|', array_keys($deprecated)).')="([^"]+)"[ />]#',
|
||||
static function($matches) use ($deprecated)
|
||||
{
|
||||
return str_replace($matches[1].'="', $deprecated[$matches[1]].'="', $matches[0]);
|
||||
}, $str);
|
||||
|
||||
// fix <textbox multiline="true" .../> --> <textarea .../> (et2-prefix and self-closing is handled below)
|
||||
$str = preg_replace('#<textbox(.*?)\smultiline="true"(.*?)/>#u', '<textarea$1$2/>', $str);
|
||||
|
||||
// fix <(textbox|int(eger)?|float) precision="int(eger)?|float" .../> --> <et2-number precision=...></et2-number>
|
||||
$str = preg_replace_callback('#<(textbox|int(eger)?|float|number).*?\s(type="(int(eger)?|float)")?.*?/>#u',
|
||||
static function($matches)
|
||||
{
|
||||
if ($matches[1] === 'textbox' && !in_array($matches[4], ['float', 'int', 'integer'], true))
|
||||
{
|
||||
return $matches[0]; // regular textbox --> nothing to do
|
||||
}
|
||||
$type = $matches[1] === 'float' || $matches[4] === 'float' ? 'float' : 'int';
|
||||
$tag = str_replace('<'.$matches[1], '<et2-number', substr($matches[0], 0, -2));
|
||||
if (!empty($matches[3])) $tag = str_replace($matches[3], '', $tag);
|
||||
if ($type !== 'float') $tag .= ' precision="0"';
|
||||
return $tag.'></et2-number>';
|
||||
}, $str);
|
||||
|
||||
// fix <button(only)?.../> --> <et2-button(-image)? noSubmit="true".../>
|
||||
$str = preg_replace_callback('#<button(only)?\s(.*?)/>#u', function($matches) use ($name)
|
||||
{
|
||||
$tag = 'et2-button';
|
||||
preg_match_all('/(^| )([a-z0-9_-]+)="([^"]+)"/', $matches[2], $attrs, PREG_PATTERN_ORDER);
|
||||
$attrs = array_combine($attrs[2], $attrs[3]);
|
||||
// replace buttononly tag with noSubmit="true" attribute
|
||||
if(!empty($matches[1]))
|
||||
{
|
||||
$attrs['noSubmit'] = 'true';
|
||||
}
|
||||
// novalidation --> noValidation
|
||||
if(!empty($attrs['novalidation']) && in_array($attrs['novalidation'], ['true', '1'], true))
|
||||
{
|
||||
unset($attrs['novalidation']);
|
||||
$attrs['noValidation'] = 'true';
|
||||
}
|
||||
// replace not set background_image attribute with et2-image tag, if not in NM / lists
|
||||
if(!empty($attrs['image']) && (empty($attrs['background_image']) || $attrs['background_image'] === 'false') &&
|
||||
!preg_match('/^(index|list)/', $name))
|
||||
{
|
||||
$tag = 'et2-image';
|
||||
$attrs['src'] = $attrs['image'];
|
||||
unset($attrs['image']);
|
||||
// Was expected to submit. Images don't have noValidation, so add directly
|
||||
if(!array_key_exists('onclick', $attrs) && empty($attrs['noSubmit']))
|
||||
{
|
||||
$attrs['onclick'] = 'this.getInstanceManager().submit(this, undefined, ' . $attrs['noValidation'] . ')';
|
||||
}
|
||||
}
|
||||
unset($attrs['background_image']);
|
||||
return "<$tag ".implode(' ', array_map(function($name, $value)
|
||||
{
|
||||
return $name.'="'.$value.'"';
|
||||
}, array_keys($attrs), $attrs)).'></'.$tag.'>';
|
||||
}, $str);
|
||||
|
||||
$str = preg_replace_callback(ADD_ET2_PREFIX_REGEXP, static function (array $matches)
|
||||
{
|
||||
return '<' . $matches[2] . 'et2-' . $matches[3] .
|
||||
// web-components must not be self-closing (no "<et2-button .../>", but "<et2-button ...></et2-button>")
|
||||
(substr($matches[ADD_ET2_PREFIX_LAST_GROUP], -1) === '/' ? substr($matches[ADD_ET2_PREFIX_LAST_GROUP], 0, -1) .
|
||||
'></et2-' . $matches[3] : $matches[ADD_ET2_PREFIX_LAST_GROUP]) . '>';
|
||||
}, $str);
|
||||
|
||||
// handling of date and partially implemented select widget (no search or tags attribute), incl. removing of type attribute
|
||||
$str = preg_replace_callback('#<(select|date)(-[^ ]+)? ([^>]+)/>#', static function (array $matches)
|
||||
{
|
||||
preg_match_all('/(^| )([a-z0-9_-]+)="([^"]+)"/', $matches[3], $attrs, PREG_PATTERN_ORDER);
|
||||
$attrs = array_combine($attrs[2], $attrs[3]);
|
||||
|
||||
// add et2-prefix for <date-* and <select-* without search or tags attribute
|
||||
if ($matches[1] === 'date' || $matches[1] === 'select' && !isset($attrs['search']) && !isset($attrs['tags']))
|
||||
{
|
||||
// type attribute need to go in widget type <select type="select-account" --> <et2-select-account
|
||||
if(empty($matches[2]) && isset($attrs['type']))
|
||||
{
|
||||
$matches[1] = $attrs['type'];
|
||||
$matches[3] = str_replace('type="' . $attrs['type'] . '"', '', $matches[3]);
|
||||
}
|
||||
return '<et2-' . $matches[1] . $matches[2] . ' ' . $matches[3] . '></et2-' . $matches[1] . $matches[2] . '>';
|
||||
}
|
||||
return $matches[0];
|
||||
}, $str);
|
||||
|
||||
// Change splitter dockside -> primary + vertical
|
||||
$str = preg_replace_callback('#<split([^>]*?)>(.*)</split>#su', function ($matches) use ($name)
|
||||
{
|
||||
$str = preg_replace_callback('#<split([^>]*?)>(.*)</split>#su', static function ($matches) use ($name) {
|
||||
$tag = 'et2-split';
|
||||
preg_match_all('/(^| )([a-z0-9_-]+)="([^"]+)"/', $matches[1], $attrs, PREG_PATTERN_ORDER);
|
||||
$attrs = array_combine($attrs[2], $attrs[3]);
|
||||
|
||||
$attrs['vertical'] = $attrs['orientation'] == 'h' ? "true" : "false";
|
||||
if(str_contains($attrs['dock_side'], 'top') || str_contains($attrs['dock_side'], 'left'))
|
||||
$attrs['vertical'] = $attrs['orientation'] === 'h' ? "true" : "false";
|
||||
if (str_contains($attrs['dock_side'], 'top') || str_contains($attrs['dock_side'], 'left'))
|
||||
{
|
||||
$attrs['primary'] = "end";
|
||||
}
|
||||
elseif(str_contains($attrs['dock_side'], 'bottom') || str_contains($attrs['dock_side'], 'right'))
|
||||
elseif (str_contains($attrs['dock_side'], 'bottom') || str_contains($attrs['dock_side'], 'right'))
|
||||
{
|
||||
$attrs['primary'] = "start";
|
||||
}
|
||||
unset($attrs['dock_side']);
|
||||
|
||||
return "<$tag " . implode(' ', array_map(function ($name, $value)
|
||||
{
|
||||
return $name . '="' . $value . '"';
|
||||
}, array_keys($attrs), $attrs)
|
||||
return "<$tag " . implode(' ', array_map(function ($name, $value) {
|
||||
return $name . '="' . $value . '"';
|
||||
}, array_keys($attrs), $attrs)
|
||||
) . '>' . $matches[2] . "</$tag>";
|
||||
}, $str);
|
||||
}, $str);
|
||||
|
||||
// ^^^^^^^^^^^^^^^^ above widgets get transformed independent of legacy="true" set in overlay ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
// eTemplate marked as legacy --> replace only some widgets (eg. requiring jQueryUI) with web-components
|
||||
if (preg_match('/<overlay[^>]* legacy="true"/', $str))
|
||||
{
|
||||
$str = preg_replace('#<date(-time[^\s]*|-duration)?\s([^>]+)/>#', '<et2-date$1 $2></et2-date$1>', $str);
|
||||
}
|
||||
else
|
||||
{
|
||||
// fix legacy options, so new client-side has not to deal with them
|
||||
$str = preg_replace_callback('#<([^- />]+)(-[^ ]+)?[^>]* (options="([^"]+)")[ />]#', static function ($matches) {
|
||||
// take care of (static) type attribute, if used
|
||||
if (preg_match('/ type="([a-z-]+)"/', $matches[0], $type))
|
||||
{
|
||||
str_replace('<' . $matches[1] . $matches[2], '<' . $type[1], $matches[0]);
|
||||
str_replace($type[0], '', $matches[0]);
|
||||
list($matches[1], $matches[2]) = explode('-', $type[1], 2);
|
||||
}
|
||||
static $legacy_options = array( // use "ignore" to ignore further comma-sep. values, otherwise they are all in last attribute
|
||||
'select' => 'empty_label,ignore',
|
||||
'select-account' => 'empty_label,account_type,ignore',
|
||||
'box' => ',cellpadding,cellspacing,keep',
|
||||
'hbox' => 'cellpadding,cellspacing,keep',
|
||||
'vbox' => 'cellpadding,cellspacing,keep',
|
||||
'groupbox' => 'cellpadding,cellspacing,keep',
|
||||
'checkbox' => 'selected_value,unselected_value,ro_true,ro_false',
|
||||
'radio' => 'set_value,ro_true,ro_false',
|
||||
'customfields' => 'sub-type,use-private,field-names',
|
||||
'date' => 'data_format,ignore', // Legacy option "mode" was never implemented in et2
|
||||
'description' => 'bold-italic,link,activate_links,label_for,link_target,link_popup_size,link_title',
|
||||
'button' => 'image,ro_image',
|
||||
'buttononly' => 'image,ro_image',
|
||||
);
|
||||
// prefer more specific type-subtype over just type
|
||||
$names = $legacy_options[$matches[1] . $matches[2]] ?? $legacy_options[$matches[1]] ?? null;
|
||||
if (isset($names))
|
||||
{
|
||||
$names = explode(',', $names);
|
||||
$values = Api\Etemplate\Widget::csv_split($matches[4], count($names));
|
||||
if (count($values) < count($names))
|
||||
{
|
||||
$values = array_merge($values, array_fill(count($values), count($names) - count($values), ''));
|
||||
}
|
||||
$attrs = array_diff(array_combine($names, $values), ['', null]);
|
||||
unset($attrs['ignore']);
|
||||
// fix select options can be either multiple or empty_label
|
||||
if ($matches[1] === 'select' && !empty($attrs['empty_label']) && (int)$attrs['empty_label'] > 0)
|
||||
{
|
||||
$attrs['multiple'] = (int)$attrs['empty_label'];
|
||||
unset($matches['empty_label']);
|
||||
}
|
||||
$options = '';
|
||||
foreach ($attrs as $attr => $value)
|
||||
{
|
||||
$options .= $attr . '="' . $value . '" ';
|
||||
}
|
||||
return str_replace($matches[3], $options, $matches[0]);
|
||||
}
|
||||
return $matches[0];
|
||||
}, $str);
|
||||
|
||||
// fix deprecated attributes: needed, blur, ...
|
||||
static $deprecated = [
|
||||
'needed' => 'required',
|
||||
'blur' => 'placeholder',
|
||||
];
|
||||
$str = preg_replace_callback('#<[^ ]+[^>]* (' . implode('|', array_keys($deprecated)) . ')="([^"]+)"[ />]#',
|
||||
static function ($matches) use ($deprecated) {
|
||||
return str_replace($matches[1] . '="', $deprecated[$matches[1]] . '="', $matches[0]);
|
||||
}, $str);
|
||||
|
||||
// fix <textbox multiline="true" .../> --> <textarea .../> (et2-prefix and self-closing is handled below)
|
||||
$str = preg_replace('#<textbox(.*?)\smultiline="true"(.*?)/>#u', '<textarea$1$2/>', $str);
|
||||
|
||||
// fix <(textbox|int(eger)?|float) precision="int(eger)?|float" .../> --> <et2-number precision=...></et2-number>
|
||||
$str = preg_replace_callback('#<(textbox|int(eger)?|float|number).*?\s(type="(int(eger)?|float)")?.*?/>#u',
|
||||
static function ($matches) {
|
||||
if ($matches[1] === 'textbox' && !in_array($matches[4], ['float', 'int', 'integer'], true))
|
||||
{
|
||||
return $matches[0]; // regular textbox --> nothing to do
|
||||
}
|
||||
$type = $matches[1] === 'float' || $matches[4] === 'float' ? 'float' : 'int';
|
||||
$tag = str_replace('<' . $matches[1], '<et2-number', substr($matches[0], 0, -2));
|
||||
if (!empty($matches[3])) $tag = str_replace($matches[3], '', $tag);
|
||||
if ($type !== 'float') $tag .= ' precision="0"';
|
||||
return $tag . '></et2-number>';
|
||||
}, $str);
|
||||
|
||||
// fix <button(only)?.../> --> <et2-button(-image)? noSubmit="true".../>
|
||||
$str = preg_replace_callback('#<button(only)?\s(.*?)/>#u', function ($matches) use ($name) {
|
||||
$tag = 'et2-button';
|
||||
preg_match_all('/(^| )([a-z0-9_-]+)="([^"]+)"/', $matches[2], $attrs, PREG_PATTERN_ORDER);
|
||||
$attrs = array_combine($attrs[2], $attrs[3]);
|
||||
// replace buttononly tag with noSubmit="true" attribute
|
||||
if (!empty($matches[1]))
|
||||
{
|
||||
$attrs['noSubmit'] = 'true';
|
||||
}
|
||||
// novalidation --> noValidation
|
||||
if (!empty($attrs['novalidation']) && in_array($attrs['novalidation'], ['true', '1'], true))
|
||||
{
|
||||
unset($attrs['novalidation']);
|
||||
$attrs['noValidation'] = 'true';
|
||||
}
|
||||
// replace not set background_image attribute with et2-image tag, if not in NM / lists
|
||||
if (!empty($attrs['image']) && (empty($attrs['background_image']) || $attrs['background_image'] === 'false') &&
|
||||
!preg_match('/^(index|list)/', $name))
|
||||
{
|
||||
$tag = 'et2-image';
|
||||
$attrs['src'] = $attrs['image'];
|
||||
unset($attrs['image']);
|
||||
// Was expected to submit. Images don't have noValidation, so add directly
|
||||
if (!array_key_exists('onclick', $attrs) && empty($attrs['noSubmit']))
|
||||
{
|
||||
$attrs['onclick'] = 'this.getInstanceManager().submit(this, undefined, ' . $attrs['noValidation'] . ')';
|
||||
}
|
||||
}
|
||||
unset($attrs['background_image']);
|
||||
return "<$tag " . implode(' ', array_map(function ($name, $value) {
|
||||
return $name . '="' . $value . '"';
|
||||
}, array_keys($attrs), $attrs)) . '></' . $tag . '>';
|
||||
}, $str);
|
||||
|
||||
$str = preg_replace_callback(ADD_ET2_PREFIX_REGEXP, static function (array $matches) {
|
||||
return '<' . $matches[2] . 'et2-' . $matches[3] .
|
||||
// web-components must not be self-closing (no "<et2-button .../>", but "<et2-button ...></et2-button>")
|
||||
(substr($matches[ADD_ET2_PREFIX_LAST_GROUP], -1) === '/' ? substr($matches[ADD_ET2_PREFIX_LAST_GROUP], 0, -1) .
|
||||
'></et2-' . $matches[3] : $matches[ADD_ET2_PREFIX_LAST_GROUP]) . '>';
|
||||
}, $str);
|
||||
|
||||
// handling of date and partially implemented select widget (no search or tags attribute), incl. removing of type attribute
|
||||
$str = preg_replace_callback('#<(select)(-[^ ]+)? ([^>]+)/>#', static function (array $matches) {
|
||||
preg_match_all('/(^| )([a-z0-9_-]+)="([^"]+)"/', $matches[3], $attrs, PREG_PATTERN_ORDER);
|
||||
$attrs = array_combine($attrs[2], $attrs[3]);
|
||||
|
||||
// add et2-prefix for <date-* and <select-* without search or tags attribute
|
||||
if (!isset($attrs['search']) && !isset($attrs['tags']))
|
||||
{
|
||||
// type attribute need to go in widget type <select type="select-account" --> <et2-select-account
|
||||
if (empty($matches[2]) && isset($attrs['type']))
|
||||
{
|
||||
$matches[1] = $attrs['type'];
|
||||
$matches[3] = str_replace('type="' . $attrs['type'] . '"', '', $matches[3]);
|
||||
}
|
||||
return '<et2-' . $matches[1] . $matches[2] . ' ' . $matches[3] . '></et2-' . $matches[1] . $matches[2] . '>';
|
||||
}
|
||||
return $matches[0];
|
||||
}, $str);
|
||||
}
|
||||
$processing = microtime(true);
|
||||
|
||||
if(isset($cache) && (file_exists($cache_dir = dirname($cache)) || mkdir($cache_dir, 0755, true)))
|
||||
if (isset($cache) && (file_exists($cache_dir = dirname($cache)) || mkdir($cache_dir, 0755, true) || is_dir($cache_dir)))
|
||||
{
|
||||
file_put_contents($cache, $str);
|
||||
}
|
||||
}
|
||||
// stop here for not existing file path-traversal for both file and cache here
|
||||
// stop here for not existing file or path-traversal for both file and cache here
|
||||
if(empty($str) || strpos($path, '..') !== false)
|
||||
{
|
||||
http_response_code(404);
|
||||
|
@ -1249,8 +1249,18 @@ export const Et2Widget = dedupeMixin(Et2WidgetMixin);
|
||||
* @param parent Parent widget
|
||||
*/
|
||||
// @ts-ignore Et2Widget is I guess not the right type
|
||||
export function loadWebComponent(_nodeName : string, _template_node, parent : Et2Widget | et2_widget) : HTMLElement
|
||||
export function loadWebComponent(_nodeName : string, _template_node : Element|{[index: string]: any}, parent : Et2Widget | et2_widget) : HTMLElement
|
||||
{
|
||||
// support attributes object instead of an Element
|
||||
if (typeof _template_node.getAttribute === 'undefined')
|
||||
{
|
||||
const _names = Object.keys(_template_node);
|
||||
_template_node.getAttributeNames = () => _names;
|
||||
_template_node.getAttribute = attr => _template_node[attr];
|
||||
_template_node.querySelectorAll = () => [];
|
||||
(<any>_template_node).nodeName = _nodeName;
|
||||
(<any>_template_node).childNodes = [];
|
||||
}
|
||||
// Try to find the class for the given node
|
||||
let widget_class = window.customElements.get(_nodeName);
|
||||
if(!widget_class)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,463 +0,0 @@
|
||||
/**
|
||||
* EGroupware eTemplate2 - Split panel
|
||||
*
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package etemplate
|
||||
* @subpackage api
|
||||
* @link https://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
* @copyright Nathan Gray 2013
|
||||
*/
|
||||
|
||||
/*egw:uses
|
||||
/vendor/bower-asset/jquery/dist/jquery.js;
|
||||
jquery.splitter;
|
||||
et2_core_baseWidget;
|
||||
*/
|
||||
|
||||
import {et2_DOMWidget} from "./et2_core_DOMWidget";
|
||||
import {et2_register_widget, WidgetConfig} from "./et2_core_widget";
|
||||
import {ClassWithAttributes} from "./et2_core_inheritance";
|
||||
import {et2_IPrint, et2_IResizeable} from "./et2_core_interfaces";
|
||||
import {et2_no_init} from "./et2_core_common";
|
||||
import {et2_dynheight} from "./et2_widget_dynheight";
|
||||
import "../jquery/splitter.js";
|
||||
|
||||
/**
|
||||
* A container widget that accepts 2 children, and puts a resize bar between them.
|
||||
*
|
||||
* This is the etemplate2 implementation of the traditional split pane / split panel.
|
||||
* The split can be horizontal or vertical, and can be limited in size. You can also
|
||||
* turn on double-click docking to minimize one of the children.
|
||||
*
|
||||
* @see http://methvin.com/splitter/ Uses Splitter
|
||||
* @augments et2_DOMWidget
|
||||
*/
|
||||
export class et2_split extends et2_DOMWidget implements et2_IResizeable, et2_IPrint
|
||||
{
|
||||
static readonly _attributes : any = {
|
||||
"orientation": {
|
||||
"name": "Orientation",
|
||||
"description": "Horizontal or vertical (v or h)",
|
||||
"default": "v",
|
||||
"type": "string"
|
||||
},
|
||||
"outline": {
|
||||
"name": "Outline",
|
||||
"description": "Use a 'ghosted' copy of the splitbar and does not resize the panes until the mouse button is released. Reduces flickering or unwanted re-layout during resize",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"dock_side": {
|
||||
"name": "Dock",
|
||||
"description": "Allow the user to 'Dock' the splitbar to one side of the splitter, essentially hiding one pane and using the entire splitter area for the other pane. One of leftDock, rightDock, topDock, bottomDock.",
|
||||
"default": et2_no_init,
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"default": "100%"
|
||||
},
|
||||
// Not needed
|
||||
"overflow": {"ignore": true},
|
||||
"no_lang": {"ignore": true},
|
||||
"rows": {"ignore": true},
|
||||
"cols": {"ignore": true}
|
||||
};
|
||||
|
||||
public static readonly DOCK_TOLERANCE : number = 15;
|
||||
|
||||
div : JQuery = null;
|
||||
dynheight : et2_dynheight = null;
|
||||
left : JQuery;
|
||||
right : JQuery;
|
||||
loading : JQueryDeferred<void>;
|
||||
stop_resize : boolean;
|
||||
orientation : "v" | "h";
|
||||
dock_side : "leftDock" | "rightDock" | "topDock" | "bottomDock";
|
||||
prefSize : number;
|
||||
outline : boolean;
|
||||
|
||||
constructor(_parent, _attrs? : WidgetConfig, _child? : object)
|
||||
{
|
||||
// Call the inherited constructor
|
||||
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_split._attributes, _child || {}));
|
||||
|
||||
this.div = jQuery(document.createElement("div"))
|
||||
.addClass('et2_split');
|
||||
|
||||
// Create the dynheight component which dynamically scales the inner
|
||||
// container.
|
||||
this.dynheight = new et2_dynheight(
|
||||
(<et2_DOMWidget><unknown>this.getParent()).getDOMNode() || this.getInstanceManager().DOMContainer,
|
||||
this.div, 100
|
||||
);
|
||||
|
||||
// Add something so we can see it - will be replaced if there's children
|
||||
this.left = jQuery("<div>Top / Left</div>").appendTo(this.div);
|
||||
this.right = jQuery("<div>Bottom / Right</div>").appendTo(this.div);
|
||||
|
||||
// Flag to temporarily ignore resizing
|
||||
this.stop_resize = false;
|
||||
}
|
||||
|
||||
destroy()
|
||||
{
|
||||
// Stop listening
|
||||
this.left.next().off("mouseup");
|
||||
|
||||
// Destroy splitter, restore children
|
||||
this.div.trigger("destroy");
|
||||
|
||||
// Destroy dynamic full-height
|
||||
this.dynheight.destroy();
|
||||
|
||||
super.destroy();
|
||||
|
||||
// Remove placeholder children
|
||||
if(this._children.length == 0)
|
||||
{
|
||||
this.div.empty();
|
||||
}
|
||||
this.div.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tap in here to check if we have real children, because all children should be created
|
||||
* by this point. If there are, replace the placeholders.
|
||||
*/
|
||||
loadFromXML(_node)
|
||||
{
|
||||
super.loadFromXML(_node);
|
||||
if(this._children.length > 0)
|
||||
{
|
||||
if(this._children[0])
|
||||
{
|
||||
this.left.detach();
|
||||
this.left = jQuery(this._children[0].getDOMNode(this._children[0]))
|
||||
.appendTo(this.div);
|
||||
}
|
||||
if(this._children[1])
|
||||
{
|
||||
this.right.detach();
|
||||
this.right = jQuery(this._children[1].getDOMNode(this._children[1]))
|
||||
.appendTo(this.div);
|
||||
}
|
||||
}
|
||||
|
||||
// Nextmatches (and possibly other "full height" widgets) need to be adjusted
|
||||
// Trigger the dynamic height thing to re-initialize
|
||||
for(var i = 0; i < this._children.length; i++)
|
||||
{
|
||||
if(this._children[i].dynheight)
|
||||
{
|
||||
this._children[i].dynheight.outerNode = (i == 0 ? this.left : this.right);
|
||||
this._children[i].dynheight.initialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doLoadingFinished()
|
||||
{
|
||||
super.doLoadingFinished();
|
||||
|
||||
// Not done yet, but widget will let you know
|
||||
let p = new Promise((resolve) =>
|
||||
{
|
||||
// Use a timeout to give the children a chance to finish
|
||||
window.setTimeout(() => this._init_splitter(resolve), 1);
|
||||
});
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the splitter UI
|
||||
* Internal.
|
||||
*/
|
||||
private _init_splitter(resolve? : Function)
|
||||
{
|
||||
if(!this.isAttached()) return;
|
||||
|
||||
// Avoid trying to do anything while hidden - it ruins all the calculations
|
||||
// Try again in resize()
|
||||
if(!this.div.is(':visible')) return;
|
||||
|
||||
let options = {
|
||||
type: this.orientation,
|
||||
dock: this.dock_side,
|
||||
splitterClass: "et2_split",
|
||||
outline: true,
|
||||
eventNamespace: '.et2_split.'+this.id,
|
||||
|
||||
// Default sizes, in case the preference doesn't work
|
||||
// Splitter would normally just go to 50%, but our deferred loading
|
||||
// ruins sizing for it
|
||||
sizeTop: this.dynheight.outerNode.height() / 2,
|
||||
sizeLeft: this.dynheight.outerNode.width() / 2
|
||||
};
|
||||
|
||||
let widget = this;
|
||||
|
||||
//Convert pixel size to percent
|
||||
let pix2per = function (_size)
|
||||
{
|
||||
let per;
|
||||
if (widget.orientation == "v")
|
||||
{
|
||||
per = _size * 100 / widget.dynheight.outerNode.width();
|
||||
}
|
||||
else
|
||||
{
|
||||
per = _size * 100 / widget.dynheight.outerNode.height();
|
||||
}
|
||||
return per.toFixed(2) + "%";
|
||||
};
|
||||
|
||||
// Check for position preference, load it in
|
||||
if(this.id)
|
||||
{
|
||||
let pref = this.egw().preference('splitter-size-' + this.id, this.egw().getAppName());
|
||||
if(pref)
|
||||
{
|
||||
// Change from percent back to numeric
|
||||
if(typeof pref.sizeLeft !== "undefined")
|
||||
{
|
||||
pref.sizeLeft = ((parseFloat(pref.sizeLeft) / 100) * widget.dynheight.outerNode.width());
|
||||
}
|
||||
if(typeof pref.sizeTop !== "undefined")
|
||||
{
|
||||
pref.sizeTop = ((parseFloat(pref.sizeTop) / 100) * widget.dynheight.outerNode.height());
|
||||
}
|
||||
options = jQuery.extend(options, pref);
|
||||
this.prefSize = pref[this.orientation == "v" ? 'sizeLeft' : 'sizeTop'];
|
||||
}
|
||||
// If there is no preference yet, set it to half size
|
||||
// Otherwise the right pane gets the fullsize
|
||||
if (typeof this.prefSize == 'undefined' || !this.prefSize)
|
||||
{
|
||||
this.prefSize = this.orientation == "v" ? options.sizeLeft: options.sizeTop;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid double init
|
||||
if(this.div.hasClass(options.splitterClass))
|
||||
{
|
||||
this.div.trigger("destroy");
|
||||
}
|
||||
|
||||
// Initialize splitter
|
||||
this.div.splitter(options);
|
||||
|
||||
this.div.trigger('resize',[options.sizeTop || options.sizeLeft || 0]);
|
||||
|
||||
// Start docked?
|
||||
if(options.dock)
|
||||
{
|
||||
if(options.dock == "bottomDock" && Math.abs(options.sizeTop - this.div.height()) < et2_split.DOCK_TOLERANCE ||
|
||||
options.dock == "topDock" && options.sizeTop == 0 ||
|
||||
!this.div.is(':visible') // Starting docked if hidden simplifies life when resizing
|
||||
)
|
||||
{
|
||||
this.dock();
|
||||
}
|
||||
}
|
||||
|
||||
// Add icon to splitter bar
|
||||
let icon = "ui-icon-grip-"
|
||||
+ (this.dock_side ? "solid" : "dotted") + "-"
|
||||
+ (this.orientation == "h" ? "horizontal" : "vertical");
|
||||
|
||||
jQuery(document.createElement("div"))
|
||||
.addClass("ui-icon")
|
||||
.addClass(icon)
|
||||
.appendTo(this.left.next());
|
||||
|
||||
// Save preference when size changed
|
||||
if(this.id && this.egw().getAppName())
|
||||
{
|
||||
let self = this;
|
||||
this.left.on("resize"+options.eventNamespace, function(e) {
|
||||
|
||||
// Force immediate layout, so proper layout & sizes are available
|
||||
// for resize(). Chrome defers layout, so current DOM node sizes
|
||||
// are not available to widgets if they ask.
|
||||
var display = this.style.display;
|
||||
this.style.display = 'none';
|
||||
this.offsetHeight;
|
||||
this.style.display = display;
|
||||
|
||||
if(e.namespace == options.eventNamespace.substr(1) && !self.isDocked())
|
||||
{
|
||||
// Store current position in preferences
|
||||
var size = self.orientation == "v" ? {sizeLeft: self.left.width()} : {sizeTop: self.left.height()};
|
||||
self.prefSize = size[self.orientation == "v" ?'sizeLeft' : 'sizeTop'];
|
||||
var prefInPercent = self.orientation == "v" ?{sizeLeft:pix2per(size.sizeLeft)}:{sizeTop:pix2per(size.sizeTop)};
|
||||
if(parseInt(self.orientation == 'v' ? prefInPercent.sizeLeft : prefInPercent.sizeTop) < 100)
|
||||
{
|
||||
self.egw().set_preference(self.egw().getAppName(), 'splitter-size-' + self.id, prefInPercent);
|
||||
}
|
||||
}
|
||||
|
||||
// Ok, update children
|
||||
self.iterateOver(function(widget) {
|
||||
// Extra resize would cause stalling chrome
|
||||
// as resize might conflict with bottom download bar
|
||||
// in chrome which does a window resize, so better to not
|
||||
// trigger second resize and leave that to an application
|
||||
// if it is necessary.
|
||||
|
||||
// Above forcing is not enough for Firefox, defer
|
||||
window.setTimeout(jQuery.proxy(function() {this.resize();}, widget),200);
|
||||
},self,et2_IResizeable);
|
||||
});
|
||||
}
|
||||
|
||||
if (resolve) resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the et2_IResizable interface to resize
|
||||
*/
|
||||
resize()
|
||||
{
|
||||
// Avoid doing anything while hidden - check here, and init if needed
|
||||
if(this.div.children().length <= 2)
|
||||
{
|
||||
this._init_splitter();
|
||||
}
|
||||
if(this.dynheight && !this.stop_resize )
|
||||
{
|
||||
let old = {w: this.div.width(), h: this.div.height()};
|
||||
this.dynheight.update(function(w,h) {
|
||||
if(this.orientation == "v")
|
||||
{
|
||||
this.left.height(h);
|
||||
this.right.height(h);
|
||||
if(this.left.width() + this.right.width() + this.div.find('.splitter-bar').outerWidth() < this.div.width() ||
|
||||
this.left.width() + this.right.width()- this.div.find('.splitter-bar').outerWidth() > this.div.width())
|
||||
this.div.trigger('resize.et2_split.'+this.id, this.prefSize);
|
||||
}
|
||||
if(this.orientation == "h")
|
||||
{
|
||||
this.left.width(w);
|
||||
this.right.width(w);
|
||||
if(this.isDocked()) {
|
||||
if(this.dock_side == "topDock")
|
||||
{
|
||||
this.right.height(h);
|
||||
this.left.height(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.left.height(h);
|
||||
this.right.height(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(w != old.w || h != old.h)
|
||||
{
|
||||
this.div.trigger('resize.et2_split.'+this.id, this.prefSize);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
getDOMNode()
|
||||
{
|
||||
return this.div[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set splitter orientation
|
||||
*
|
||||
* @param orient String "v" or "h"
|
||||
*/
|
||||
set_orientation(orient)
|
||||
{
|
||||
this.orientation = orient;
|
||||
|
||||
this._init_splitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the side for docking
|
||||
*
|
||||
* @param dock String One of leftDock, rightDock, topDock, bottomDock
|
||||
*/
|
||||
set_dock_side(dock)
|
||||
{
|
||||
this.dock_side = dock;
|
||||
|
||||
this._init_splitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on or off resizing while dragging
|
||||
*
|
||||
* @param outline boolean
|
||||
*/
|
||||
set_outline(outline)
|
||||
{
|
||||
this.outline = outline;
|
||||
this._init_splitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the splitter has a dock direction set, dock it.
|
||||
* Docking requires the dock attribute to be set.
|
||||
*/
|
||||
dock()
|
||||
{
|
||||
if(this.isDocked()) return;
|
||||
this.div.trigger("dock");
|
||||
}
|
||||
|
||||
/**
|
||||
* If the splitter is docked, restore it to previous size
|
||||
* Docking requires the dock attribute to be set.
|
||||
*/
|
||||
undock()
|
||||
{
|
||||
this.div.trigger("undock");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the splitter is docked
|
||||
* @return boolean
|
||||
*/
|
||||
isDocked()
|
||||
{
|
||||
var bar = jQuery('.splitter-bar',this.div);
|
||||
return bar.hasClass('splitter-bar-horizontal-docked') || bar.hasClass('splitter-bar-vertical-docked');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the splitter's docked state.
|
||||
* Docking requires the dock attribute to be set.
|
||||
*/
|
||||
toggleDock()
|
||||
{
|
||||
this.div.trigger("toggleDock");
|
||||
}
|
||||
|
||||
// Printing
|
||||
/**
|
||||
* Prepare for printing by stopping all the fuss
|
||||
*
|
||||
*/
|
||||
beforePrint()
|
||||
{
|
||||
// Resizing causes preference changes & relayouts. Don't do it.
|
||||
this.stop_resize = true;
|
||||
|
||||
// Add the class, if needed
|
||||
this.div.addClass('print');
|
||||
|
||||
// Don't return anything, just work normally
|
||||
}
|
||||
afterPrint()
|
||||
{
|
||||
this.div.removeClass('print');
|
||||
this.stop_resize = false;
|
||||
}
|
||||
}
|
||||
et2_register_widget(et2_split, ["split"]);
|
@ -65,7 +65,6 @@ import './et2_widget_grid';
|
||||
import './et2_widget_box';
|
||||
import './et2_widget_hbox';
|
||||
import './et2_widget_groupbox';
|
||||
import './et2_widget_split';
|
||||
import './et2_widget_button';
|
||||
import './et2_widget_color';
|
||||
import './et2_widget_description';
|
||||
|
@ -1,30 +0,0 @@
|
||||
.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
|
||||
.ui-timepicker-div dl { text-align: left; }
|
||||
.ui-timepicker-div dl dt { float: left; clear:left; padding: 0 0 0 5px; }
|
||||
.ui-timepicker-div dl dd { margin: 0 10px 10px 40%; }
|
||||
.ui-timepicker-div td { font-size: 90%; }
|
||||
.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
|
||||
.ui-timepicker-div .ui_tpicker_unit_hide{ display: none; }
|
||||
|
||||
.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input { background: none; color: inherit; border: none; outline: none; border-bottom: solid 1px #555; width: 95%; }
|
||||
.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input:focus { border-bottom-color: #aaa; }
|
||||
|
||||
.ui-timepicker-rtl{ direction: rtl; }
|
||||
.ui-timepicker-rtl dl { text-align: right; padding: 0 5px 0 0; }
|
||||
.ui-timepicker-rtl dl dt{ float: right; clear: right; }
|
||||
.ui-timepicker-rtl dl dd { margin: 0 40% 10px 10px; }
|
||||
|
||||
/* Shortened version style */
|
||||
.ui-timepicker-div.ui-timepicker-oneLine { padding-right: 2px; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time,
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dt { display: none; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time_label { display: block; padding-top: 2px; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl { text-align: right; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd,
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd > div { display:inline-block; margin:0; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_minute:before,
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_second:before { content:':'; display:inline-block; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_millisec:before,
|
||||
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_microsec:before { content:'.'; display:inline-block; }
|
||||
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide,
|
||||
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide:before{ display: none; }
|
2263
api/js/jquery/jquery-ui-timepicker-addon.js
vendored
2263
api/js/jquery/jquery-ui-timepicker-addon.js
vendored
File diff suppressed because it is too large
Load Diff
338
api/js/jquery/splitter.js
vendored
338
api/js/jquery/splitter.js
vendored
@ -1,338 +0,0 @@
|
||||
/*
|
||||
* jQuery.splitter.js - two-pane splitter window plugin
|
||||
*
|
||||
* version 1.6 (2010/01/03)
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
/**
|
||||
* The splitter() plugin implements a two-pane resizable splitter window.
|
||||
* The selected elements in the jQuery object are converted to a splitter;
|
||||
* each selected element should have two child elements, used for the panes
|
||||
* of the splitter. The plugin adds a third child element for the splitbar.
|
||||
*
|
||||
* For more details see: http://methvin.com/jquery/splitter/
|
||||
*
|
||||
*
|
||||
* @example $('#MySplitter').splitter();
|
||||
* @desc Create a vertical splitter with default settings
|
||||
*
|
||||
* @example $('#MySplitter').splitter({type: 'h', accessKey: 'M'});
|
||||
* @desc Create a horizontal splitter resizable via Alt+Shift+M
|
||||
*
|
||||
* @name splitter
|
||||
* @type jQuery
|
||||
* @param Object options Options for the splitter (not required)
|
||||
* @cat Plugins/Splitter
|
||||
* @return jQuery
|
||||
* @author Dave Methvin (dave.methvin@gmail.com)
|
||||
*/
|
||||
;(function($){
|
||||
|
||||
var splitterCounter = 0;
|
||||
|
||||
$.fn.splitter = function(args){
|
||||
args = args || {};
|
||||
return this.each(function() {
|
||||
if ( $(this).is(".splitter") ) // already a splitter
|
||||
return;
|
||||
var zombie; // left-behind splitbar for outline resizes
|
||||
function setBarState(state) {
|
||||
bar.removeClass(opts.barStateClasses).addClass(state);
|
||||
}
|
||||
function startSplitMouse(evt) {
|
||||
if ( evt.which != 1 )
|
||||
return; // left button only
|
||||
bar.removeClass(opts.barHoverClass);
|
||||
if ( opts.outline ) {
|
||||
zombie = zombie || bar.clone(false).insertAfter(A);
|
||||
bar.removeClass(opts.barDockedClass);
|
||||
}
|
||||
setBarState(opts.barActiveClass)
|
||||
// Safari selects A/B text on a move; iframes capture mouse events so hide them
|
||||
panes.css("-webkit-user-select", "none").find("iframe").andSelf().filter("iframe").addClass(opts.iframeClass);
|
||||
A._posSplit = A[0][opts.pxSplit] - evt[opts.eventPos];
|
||||
$(document)
|
||||
.bind("mousemove"+opts.eventNamespace, doSplitMouse)
|
||||
.bind("mouseup"+opts.eventNamespace, endSplitMouse);
|
||||
}
|
||||
function doSplitMouse(evt) {
|
||||
var pos = A._posSplit+evt[opts.eventPos],
|
||||
range = Math.max(0, Math.min(pos, splitter._DA - bar._DA)),
|
||||
limit = Math.max(A._min, splitter._DA - B._max,
|
||||
Math.min(pos, A._max, splitter._DA - bar._DA - B._min));
|
||||
if ( opts.outline ) {
|
||||
// Let docking splitbar be dragged to the dock position, even if min width applies
|
||||
if ( (opts.dockPane == A && pos < Math.max(A._min, bar._DA)) ||
|
||||
(opts.dockPane == B && pos > Math.min(pos, A._max, splitter._DA - bar._DA - B._min)) ) {
|
||||
bar.addClass(opts.barDockedClass).css(opts.origin, range);
|
||||
}
|
||||
else {
|
||||
bar.removeClass(opts.barDockedClass).css(opts.origin, limit);
|
||||
}
|
||||
bar._DA = bar[0][opts.pxSplit];
|
||||
} else
|
||||
resplit(pos);
|
||||
setBarState(pos == limit? opts.barActiveClass : opts.barLimitClass);
|
||||
}
|
||||
function endSplitMouse(evt) {
|
||||
setBarState(opts.barNormalClass);
|
||||
bar.addClass(opts.barHoverClass);
|
||||
var pos = A._posSplit+evt[opts.eventPos];
|
||||
if ( opts.outline ) {
|
||||
zombie.remove(); zombie = null;
|
||||
resplit(pos);
|
||||
}
|
||||
panes.css("-webkit-user-select", "text").find("iframe").andSelf().filter("iframe").removeClass(opts.iframeClass);
|
||||
$(document)
|
||||
.unbind("mousemove"+opts.eventNamespace+" mouseup"+opts.eventNamespace);
|
||||
}
|
||||
function resplit(pos) {
|
||||
bar._DA = bar[0][opts.pxSplit]; // bar size may change during dock
|
||||
// Constrain new splitbar position to fit pane size and docking limits
|
||||
if ( (opts.dockPane == A && pos < Math.max(A._min, bar._DA)) ||
|
||||
(opts.dockPane == B && pos > Math.min(pos, A._max, splitter._DA - bar._DA - B._min)) ) {
|
||||
bar.addClass(opts.barDockedClass);
|
||||
bar._DA = bar[0][opts.pxSplit];
|
||||
pos = opts.dockPane == A? 0 : splitter._DA - bar._DA;
|
||||
if ( bar._pos == null )
|
||||
bar._pos = A[0][opts.pxSplit];
|
||||
}
|
||||
else {
|
||||
bar.removeClass(opts.barDockedClass);
|
||||
bar._DA = bar[0][opts.pxSplit];
|
||||
bar._pos = null;
|
||||
pos = Math.max(A._min, splitter._DA - B._max,
|
||||
Math.min(pos, A._max, splitter._DA - bar._DA - B._min));
|
||||
}
|
||||
// Resize/position the two panes
|
||||
bar.css(opts.origin, pos).css(opts.fixed, splitter._DF);
|
||||
A.css(opts.origin, 0).css(opts.split, pos).css(opts.fixed, splitter._DF);
|
||||
B.css(opts.origin, pos+bar._DA)
|
||||
.css(opts.split, splitter._DA-bar._DA-pos).css(opts.fixed, splitter._DF);
|
||||
panes.trigger("resize"+opts.eventNamespace);
|
||||
}
|
||||
function dimSum(jq, dims) {
|
||||
// Opera returns -1 for missing min/max width, turn into 0
|
||||
var sum = 0;
|
||||
for ( var i=1; i < arguments.length; i++ )
|
||||
sum += Math.max(parseInt(jq.css(arguments[i]),10) || 0, 0);
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Determine settings based on incoming opts, element classes, and defaults
|
||||
var vh = (args.splitHorizontal? 'h' : args.splitVertical? 'v' : args.type) || 'v';
|
||||
var opts = $.extend({
|
||||
// Defaults here allow easy use with ThemeRoller
|
||||
splitterClass: "splitter ui-widget ui-widget-content",
|
||||
paneClass: "splitter-pane",
|
||||
barClass: "splitter-bar",
|
||||
barNormalClass: "ui-state-default", // splitbar normal
|
||||
barHoverClass: "ui-state-hover", // splitbar mouse hover
|
||||
barActiveClass: "ui-state-highlight", // splitbar being moved
|
||||
barLimitClass: "ui-state-error", // splitbar at limit
|
||||
iframeClass: "splitter-iframe-hide", // hide iframes during split
|
||||
eventNamespace: ".splitter"+(++splitterCounter),
|
||||
pxPerKey: 8, // splitter px moved per keypress
|
||||
tabIndex: 0, // tab order indicator
|
||||
accessKey: '' // accessKey for splitbar
|
||||
},{
|
||||
// user can override
|
||||
v: { // Vertical splitters:
|
||||
keyLeft: 39, keyRight: 37, cursor: "e-resize",
|
||||
barStateClass: "splitter-bar-vertical",
|
||||
barDockedClass: "splitter-bar-vertical-docked"
|
||||
},
|
||||
h: { // Horizontal splitters:
|
||||
keyTop: 40, keyBottom: 38, cursor: "n-resize",
|
||||
barStateClass: "splitter-bar-horizontal",
|
||||
barDockedClass: "splitter-bar-horizontal-docked"
|
||||
}
|
||||
}[vh], args, {
|
||||
// user cannot override
|
||||
v: { // Vertical splitters:
|
||||
type: 'v', eventPos: "pageX", origin: "left",
|
||||
split: "width", pxSplit: "offsetWidth", side1: "Left", side2: "Right",
|
||||
fixed: "height", pxFixed: "offsetHeight", side3: "Top", side4: "Bottom"
|
||||
},
|
||||
h: { // Horizontal splitters:
|
||||
type: 'h', eventPos: "pageY", origin: "top",
|
||||
split: "height", pxSplit: "offsetHeight", side1: "Top", side2: "Bottom",
|
||||
fixed: "width", pxFixed: "offsetWidth", side3: "Left", side4: "Right"
|
||||
}
|
||||
}[vh]);
|
||||
opts.barStateClasses = [opts.barNormalClass, opts.barHoverClass, opts.barActiveClass, opts.barLimitClass].join(' ');
|
||||
|
||||
// Create jQuery object closures for splitter and both panes
|
||||
var splitter = $(this).css({position: "relative"}).addClass(opts.splitterClass);
|
||||
var panes = $(">*", splitter[0]).addClass(opts.paneClass).css({
|
||||
position: "absolute", // positioned inside splitter container
|
||||
"z-index": "1", // splitbar is positioned above
|
||||
"-moz-outline-style": "none" // don't show dotted outline
|
||||
});
|
||||
var A = $(panes[0]), B = $(panes[1]); // A = left/top, B = right/bottom
|
||||
opts.dockPane = opts.dock && (/right|bottom/.test(opts.dock)? B:A);
|
||||
|
||||
// Focuser element, provides keyboard support; title is shown by Opera accessKeys
|
||||
var focuser = $('<a href="javascript:void(0)"></a>')
|
||||
.attr({accessKey: opts.accessKey, tabIndex: opts.tabIndex, title: opts.splitbarClass})
|
||||
.bind(("focus")+opts.eventNamespace,
|
||||
function(){ this.focus(); bar.addClass(opts.barActiveClass) })
|
||||
.bind("keydown"+opts.eventNamespace, function(e){
|
||||
var key = e.which || e.keyCode;
|
||||
var dir = key==opts["key"+opts.side1]? 1 : key==opts["key"+opts.side2]? -1 : 0;
|
||||
if ( dir )
|
||||
resplit(A[0][opts.pxSplit]+dir*opts.pxPerKey, false);
|
||||
})
|
||||
.bind("blur"+opts.eventNamespace,
|
||||
function(){ bar.removeClass(opts.barActiveClass) });
|
||||
|
||||
// Splitbar element
|
||||
var bar = $('<div></div>')
|
||||
.insertAfter(A).addClass(opts.barClass).addClass(opts.barStateClass)
|
||||
.append(focuser).attr({unselectable: "on"})
|
||||
.css({position: "absolute", "user-select": "none", "-webkit-user-select": "none",
|
||||
"-khtml-user-select": "none", "-moz-user-select": "none", "z-index": "100"})
|
||||
.bind("mousedown"+opts.eventNamespace, startSplitMouse)
|
||||
.bind("mouseover"+opts.eventNamespace, function(){
|
||||
$(this).addClass(opts.barHoverClass);
|
||||
})
|
||||
.bind("mouseout"+opts.eventNamespace, function(){
|
||||
$(this).removeClass(opts.barHoverClass);
|
||||
});
|
||||
// Use our cursor unless the style specifies a non-default cursor
|
||||
if ( /^(auto|default|)$/.test(bar.css("cursor")) )
|
||||
bar.css("cursor", opts.cursor);
|
||||
|
||||
// Cache several dimensions for speed, rather than re-querying constantly
|
||||
// These are saved on the A/B/bar/splitter jQuery vars, which are themselves cached
|
||||
// DA=dimension adjustable direction, PBF=padding/border fixed, PBA=padding/border adjustable
|
||||
bar._DA = bar[0][opts.pxSplit];
|
||||
splitter._PBF = dimSum(splitter, "border"+opts.side3+"Width", "border"+opts.side4+"Width");
|
||||
splitter._PBA = dimSum(splitter, "border"+opts.side1+"Width", "border"+opts.side2+"Width");
|
||||
A._pane = opts.side1;
|
||||
B._pane = opts.side2;
|
||||
$.each([A,B], function(){
|
||||
this._splitter_style = this.style;
|
||||
this._min = opts["min"+this._pane] || dimSum(this, "min-"+opts.split);
|
||||
this._max = opts["max"+this._pane] || dimSum(this, "max-"+opts.split) || 9999;
|
||||
this._init = opts["size"+this._pane]===true ?
|
||||
parseInt($.curCSS(this[0],opts.split),10) : opts["size"+this._pane];
|
||||
});
|
||||
|
||||
// Determine initial position, get from cookie if specified
|
||||
var initPos = A._init;
|
||||
if ( !isNaN(B._init) ) // recalc initial B size as an offset from the top or left side
|
||||
initPos = splitter[0][opts.pxSplit] - splitter._PBA - B._init - bar._DA;
|
||||
if ( opts.cookie ) {
|
||||
if ( !$.cookie )
|
||||
alert('jQuery.splitter(): jQuery cookie plugin required');
|
||||
initPos = parseInt($.cookie(opts.cookie),10);
|
||||
$(window).bind("unload"+opts.eventNamespace, function(){
|
||||
var state = String(bar.css(opts.origin)); // current location of splitbar
|
||||
$.cookie(opts.cookie, state, {expires: opts.cookieExpires || 365,
|
||||
path: opts.cookiePath || document.location.pathname});
|
||||
});
|
||||
}
|
||||
if ( isNaN(initPos) ) // King Solomon's algorithm
|
||||
initPos = Math.round((splitter[0][opts.pxSplit] - splitter._PBA - bar._DA)/2);
|
||||
|
||||
// Resize event propagation and splitter sizing
|
||||
if ( opts.anchorToWindow )
|
||||
opts.resizeTo = window;
|
||||
if ( opts.resizeTo ) {
|
||||
splitter._hadjust = dimSum(splitter, "borderTopWidth", "borderBottomWidth", "marginBottom");
|
||||
splitter._hmin = Math.max(dimSum(splitter, "minHeight"), 20);
|
||||
$(window).bind("resize"+opts.eventNamespace, function(){
|
||||
var top = splitter.offset().top;
|
||||
var eh = $(opts.resizeTo).height();
|
||||
splitter.css("height", Math.max(eh-top-splitter._hadjust, splitter._hmin)+"px");
|
||||
splitter.trigger("resize");
|
||||
}).trigger("resize"+opts.eventNamespace);
|
||||
}
|
||||
else if ( opts.resizeToWidth ) {
|
||||
$(window).bind("resize"+opts.eventNamespace, function(){
|
||||
splitter.trigger("resize");
|
||||
});
|
||||
}
|
||||
|
||||
// Docking support
|
||||
if ( opts.dock ) {
|
||||
splitter
|
||||
.bind("toggleDock"+opts.eventNamespace, function() {
|
||||
var pw = opts.dockPane[0][opts.pxSplit];
|
||||
splitter.trigger(pw?"dock":"undock");
|
||||
})
|
||||
.bind("dock"+opts.eventNamespace, function(){
|
||||
var pw = A[0][opts.pxSplit];
|
||||
if ( !pw ) return;
|
||||
// Don't try to dock twice
|
||||
if (bar.hasClass("splitter-bar-horizontal-docked") || bar.hasClass("splitter-bar-vertical-docked"))
|
||||
return;
|
||||
bar._pos = pw;
|
||||
var x={};
|
||||
x[opts.origin] = opts.dockPane==A? 0 :
|
||||
splitter[0][opts.pxSplit] - splitter._PBA - bar[0][opts.pxSplit];
|
||||
bar.animate(x, opts.dockSpeed||1, opts.dockEasing, function(){
|
||||
bar.addClass(opts.barDockedClass);
|
||||
resplit(x[opts.origin]+1);
|
||||
});
|
||||
})
|
||||
.bind("undock"+opts.eventNamespace, function(){
|
||||
var pw = opts.dockPane[0][opts.pxSplit];
|
||||
if ( pw ) return;
|
||||
// 20px away is too close, reset to 50%
|
||||
if(bar._pos == null || Math.abs(bar._pos - splitter._DA - bar._DA) < 20) bar._pos = splitter._DA / 2;
|
||||
var x={}; x[opts.origin]=bar._pos+"px";
|
||||
bar.removeClass(opts.barDockedClass)
|
||||
.animate(x, opts.undockSpeed||opts.dockSpeed||1, opts.undockEasing||opts.dockEasing, function(){
|
||||
resplit(bar._pos);
|
||||
bar._pos = null;
|
||||
});
|
||||
});
|
||||
if ( opts.dockKey )
|
||||
$('<a title="'+opts.splitbarClass+' toggle dock" href="javascript:void(0)"></a>')
|
||||
.attr({accessKey: opts.dockKey, tabIndex: -1}).appendTo(bar)
|
||||
.bind("focus", function(){
|
||||
splitter.trigger("toggleDock"); this.blur();
|
||||
});
|
||||
bar.bind("dblclick", function(){ splitter.trigger("toggleDock"); })
|
||||
}
|
||||
|
||||
|
||||
// Resize event handler; triggered immediately to set initial position
|
||||
splitter
|
||||
.bind("destroy"+opts.eventNamespace, function(){
|
||||
$([window, document]).unbind(opts.eventNamespace);
|
||||
bar.unbind().remove();
|
||||
panes.removeClass(opts.paneClass);
|
||||
splitter
|
||||
.removeClass(opts.splitterClass)
|
||||
.add(panes)
|
||||
.unbind(opts.eventNamespace)
|
||||
.attr("style", function(el){
|
||||
return this._splitter_style||""; //TODO: save style
|
||||
});
|
||||
splitter = bar = focuser = panes = A = B = opts = args = null;
|
||||
})
|
||||
.bind("resize"+opts.eventNamespace, function(e, size){
|
||||
// Custom events bubble in jQuery 1.3; avoid recursion
|
||||
if ( e.target != this ) return;
|
||||
// Determine new width/height of splitter container
|
||||
splitter._DF = splitter[0][opts.pxFixed] - splitter._PBF;
|
||||
splitter._DA = splitter[0][opts.pxSplit] - splitter._PBA;
|
||||
// Bail if splitter isn't visible or content isn't there yet
|
||||
if ( splitter._DF <= 0 || splitter._DA <= 0 ) return;
|
||||
// Re-divvy the adjustable dimension; maintain size of the preferred pane
|
||||
resplit(!isNaN(size)? size : (!(opts.sizeRight||opts.sizeBottom)? A[0][opts.pxSplit] :
|
||||
splitter._DA-B[0][opts.pxSplit]-bar._DA));
|
||||
setBarState(opts.barNormalClass);
|
||||
})
|
||||
.trigger("resize" , [initPos]);
|
||||
});
|
||||
};
|
||||
|
||||
})(jQuery);
|
@ -35,7 +35,7 @@ class ContentSecurityPolicy
|
||||
* @var array
|
||||
*/
|
||||
private static $sources = array( // our dhtmlxcommon version (not the current) uses eval,
|
||||
'script-src' => array("'unsafe-eval'"), // sidebox javascript links, et2_widget_date / jQueryUI datepicker, maybe more
|
||||
'script-src' => array("'unsafe-eval'"), // sidebox javascript links, maybe more
|
||||
'style-src' => array("'unsafe-inline'"), // eTemplate styles and custom framework colors
|
||||
'connect-src' => null, // NOT array(), to call the hook
|
||||
'frame-src' => null, // NOT array(), to call the hook
|
||||
@ -219,4 +219,4 @@ class ContentSecurityPolicy
|
||||
header("Content-Security-Policy: $csp");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user