MINOR Moved LeftAndMain, SecurityAdmin, ModelAdmin and related functionality from 'cms' module to sapphire/admin

This commit is contained in:
Ingo Schommer 2011-03-24 21:14:20 +13:00
parent f1e1cbe09b
commit 0dfbec8d2d
92 changed files with 1 additions and 34948 deletions

View File

@ -6,8 +6,7 @@
* @package cms
*/
Director::addRules(50, array(
'processes//$Action/$ID/$Batch' => 'BatchProcess_Controller',
'admin/help//$Action/$ID' => 'CMSHelp',
'' => 'RootURLController',
'admin/bulkload//$Action/$ID/$OtherID' => 'BulkLoaderAdmin',
'admin/cms//$Action/$ID/$OtherID' => 'CMSMain',
'dev/buildcache/$Action' => 'RebuildStaticCacheTask',
@ -17,36 +16,6 @@ Director::addRules(1, array(
'$URLSegment//$Action/$ID/$OtherID' => 'ModelAsController',
));
CMSMenu::add_director_rules();
// Default CMS HTMLEditorConfig
HtmlEditorConfig::get('cms')->setOptions(array(
'friendly_name' => 'Default CMS',
'priority' => '50',
'mode' => 'none',
'body_class' => 'typography',
'document_base_url' => Director::absoluteBaseURL(),
'urlconverter_callback' => "nullConverter",
'setupcontent_callback' => "sapphiremce_setupcontent",
'cleanup_callback' => "sapphiremce_cleanup",
'use_native_selects' => true, // fancy selects are bug as of SS 2.3.0
'valid_elements' => "@[id|class|style|title],#a[id|rel|rev|dir|tabindex|accesskey|type|name|href|target|title|class],-strong/-b[class],-em/-i[class],-strike[class],-u[class],#p[id|dir|class|align|style],-ol[class],-ul[class],-li[class],br,img[id|dir|longdesc|usemap|class|src|border|alt=|title|width|height|align],-sub[class],-sup[class],-blockquote[dir|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|dir|id|style],-tr[id|dir|class|rowspan|width|height|align|valign|bgcolor|background|bordercolor|style],tbody[id|class|style],thead[id|class|style],tfoot[id|class|style],#td[id|dir|class|colspan|rowspan|width|height|align|valign|scope|style],-th[id|dir|class|colspan|rowspan|width|height|align|valign|scope|style],caption[id|dir|class],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align],address[class|align],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|dir|class|align|style],hr[class],dd[id|class|title|dir],dl[id|class|title|dir],dt[id|class|title|dir],@[id,style,class]",
'extended_valid_elements' => "img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name|usemap],iframe[src|name|width|height|align|frameborder|marginwidth|marginheight|scrolling],object[width|height|data|type],param[name|value],map[class|name|id],area[shape|coords|href|target|alt]"
));
HtmlEditorConfig::get('cms')->enablePlugins('media', 'fullscreen');
HtmlEditorConfig::get('cms')->enablePlugins(array('ssbuttons' => '../../../cms/javascript/tinymce_ssbuttons/editor_plugin_src.js'));
HtmlEditorConfig::get('cms')->insertButtonsBefore('formatselect', 'styleselect');
HtmlEditorConfig::get('cms')->insertButtonsBefore('advcode', 'ssimage', 'ssflash', 'sslink', 'unlink', 'anchor', 'separator' );
HtmlEditorConfig::get('cms')->insertButtonsAfter ('advcode', 'fullscreen', 'separator');
HtmlEditorConfig::get('cms')->removeButtons('tablecontrols');
HtmlEditorConfig::get('cms')->addButtonsToLine(3, 'tablecontrols');
// Register default side reports
SS_Report::register("SideReport", "SideReport_EmptyPages");
SS_Report::register("SideReport", "SideReport_RecentlyEdited");

View File

@ -1,325 +0,0 @@
<?php
/**
* The object manages the main CMS menu. See {@link LeftAndMain::init()} for example usage.
*
* The menu will be automatically populated with menu items for subclasses of {@link LeftAndMain}.
* That is, for each class in the CMS that creates an administration panel, a CMS menu item will be created.
* The default configuration will also include a 'help' link to the SilverStripe user documentation.
*
* @package cms
* @subpackage content
*/
class CMSMenu extends Object implements IteratorAggregate, i18nEntityProvider
{
/**
* An array of changes to be made to the menu items, in the order that the changes should be
* applied. Each item is a map in one of the two forms:
* - array('type' => 'add', 'item' => new CMSMenuItem(...) )
* - array('type' => 'remove', 'code' => 'codename' )
*/
protected static $menu_item_changes = array();
/**
* Set to true if clear_menu() is called, to indicate that the default menu shouldn't be
* included
*/
protected static $menu_is_cleared = false;
/**
* Generate CMS main menu items by collecting valid
* subclasses of {@link LeftAndMain}
*/
public static function populate_menu() {
self::$menu_is_cleared = false;
}
/**
* Add Director rules for all of the CMS controllers.
*/
public static function add_director_rules() {
array_map(array('self','add_director_rule_for_controller'), self::get_cms_classes());
}
/**
* Add a LeftAndMain controller to the CMS menu.
*
* @param string $controllerClass The class name of the controller
* @return The result of the operation
* @todo A director rule is added when a controller link is added, but it won't be removed
* when the item is removed. Functionality needed in {@link Director}.
*/
public static function add_controller($controllerClass) {
if($menuItem = self::menuitem_for_controller($controllerClass)) {
self::add_menu_item_obj($controllerClass, $menuItem);
}
}
/**
* Return a CMSMenuItem to add the given controller to the CMSMenu
*/
protected static function menuitem_for_controller($controllerClass) {
$urlBase = Object::get_static($controllerClass, 'url_base');
$urlSegment = Object::get_static($controllerClass, 'url_segment');
$menuPriority = Object::get_static($controllerClass, 'menu_priority');
// Don't add menu items defined the old way
if($urlSegment === null && $controllerClass != "CMSMain") return;
$link = Controller::join_links($urlBase, $urlSegment) . '/';
// doesn't work if called outside of a controller context (e.g. in _config.php)
// as the locale won't be detected properly. Use {@link LeftAndMain->MainMenu()} to update
// titles for existing menu entries
$defaultTitle = LeftAndMain::menu_title_for_class($controllerClass);
$menuTitle = _t("{$controllerClass}.MENUTITLE", $defaultTitle);
return new CMSMenuItem($menuTitle, $link, $controllerClass, $menuPriority);
}
/**
* Add the appropriate Director rules for the given controller.
*/
protected static function add_director_rule_for_controller($controllerClass) {
$urlBase = Object::get_static($controllerClass, 'url_base');
$urlSegment = Object::get_static($controllerClass, 'url_segment');
$urlRule = Object::get_static($controllerClass, 'url_rule');
$urlPriority = Object::get_static($controllerClass, 'url_priority');
if($urlSegment || $controllerClass == "CMSMain") {
$link = Controller::join_links($urlBase, $urlSegment) . '/';
// Make director rule
if($urlRule[0] == '/') $urlRule = substr($urlRule,1);
$rule = $link . '/' . $urlRule; // the / will combine with the / on the end of $link to make a //
Director::addRules($urlPriority, array(
$rule => $controllerClass
));
}
}
/**
* Add an arbitrary URL to the CMS menu.
*
* @param string $code A unique identifier (used to create a CSS ID and as it's key in {@link $menu_items}
* @param string $menuTitle The link's title in the CMS menu
* @param string $url The url of the link
* @param integer $priority The menu priority (sorting order) of the menu item. Higher priorities will be further left.
* @return boolean The result of the operation.
*/
public static function add_link($code, $menuTitle, $url, $priority = -1) {
return self::add_menu_item($code, $menuTitle, $url, null, $priority);
}
/**
* Add a navigation item to the main administration menu showing in the top bar.
*
* uses {@link CMSMenu::$menu_items}
*
* @param string $code Unique identifier for this menu item (e.g. used by {@link replace_menu_item()} and
* {@link remove_menu_item}. Also used as a CSS-class for icon customization.
* @param string $menuTitle Localized title showing in the menu bar
* @param string $url A relative URL that will be linked in the menu bar.
* @param string $controllerClass The controller class for this menu, used to check permisssions.
* If blank, it's assumed that this is public, and always shown to users who
* have the rights to access some other part of the admin area.
* @return boolean Success
*/
public static function add_menu_item($code, $menuTitle, $url, $controllerClass = null, $priority = -1) {
// If a class is defined, then force the use of that as a code. This helps prevent menu item duplication
if($controllerClass) $code = $controllerClass;
return self::replace_menu_item($code, $menuTitle, $url, $controllerClass, $priority);
}
/**
* Get a single menu item by its code value.
*
* @param string $code
* @return array
*/
public static function get_menu_item($code) {
$menuItems = self::get_menu_items();
return (isset($menuItems[$code])) ? $menuItems[$code] : false;
}
/**
* Get all menu entries.
*
* @return array
*/
public static function get_menu_items() {
$menuItems = array();
// Set up default menu items
if(!self::$menu_is_cleared) {
$cmsClasses = self::get_cms_classes();
foreach($cmsClasses as $cmsClass) {
$menuItem = self::menuitem_for_controller($cmsClass);
if($menuItem) $menuItems[$cmsClass] = $menuItem;
}
}
// Apply changes
foreach(self::$menu_item_changes as $change) {
switch($change['type']) {
case 'add':
$menuItems[$change['code']] = $change['item'];
break;
case 'remove':
unset($menuItems[$change['code']]);
break;
default:
user_error("Bad menu item change type {$change[type]}", E_USER_WARNING);
}
}
// Sort menu items according to priority
$menuPriority = array();
$i = 0;
foreach($menuItems as $key => $menuItem) {
$i++;
// This funny litle formula ensures that the first item added with the same priority will be left-most.
$menuPriority[$key] = $menuItem->priority*100 - $i;
}
array_multisort($menuPriority, SORT_DESC, $menuItems);
return $menuItems;
}
/**
* Get all menu items that the passed member can view.
* Defaults to {@link Member::currentUser()}.
*
* @param Member $member
* @return array
*/
public static function get_viewable_menu_items($member = null) {
if(!$member && $member !== FALSE) {
$member = Member::currentUser();
}
$viewableMenuItems = array();
$allMenuItems = self::get_menu_items();
if($allMenuItems) foreach($allMenuItems as $code => $menuItem) {
// exclude all items which have a controller to perform permission
// checks on
if($menuItem->controller) {
$controllerObj = singleton($menuItem->controller);
// Necessary for canView() to have request data available,
// e.g. to check permissions against LeftAndMain->currentPage()
$controllerObj->setRequest(Controller::curr()->getRequest());
if(!$controllerObj->canView($member)) continue;
}
$viewableMenuItems[$code] = $menuItem;
}
return $viewableMenuItems;
}
/**
* Removes an existing item from the menu.
*
* @param string $code Unique identifier for this menu item
*/
public static function remove_menu_item($code) {
self::$menu_item_changes[] = array('type' => 'remove', 'code' => $code);
}
/**
* Clears the entire menu
*/
public static function clear_menu() {
self::$menu_item_changes = array();
self::$menu_is_cleared = true;
}
/**
* Replace a navigation item to the main administration menu showing in the top bar.
*
* @param string $code Unique identifier for this menu item (e.g. used by {@link replace_menu_item()} and
* {@link remove_menu_item}. Also used as a CSS-class for icon customization.
* @param string $menuTitle Localized title showing in the menu bar
* @param string $url A relative URL that will be linked in the menu bar.
* Make sure to add a matching route via {@link Director::addRules()} to this url.
* @param string $controllerClass The controller class for this menu, used to check permisssions.
* If blank, it's assumed that this is public, and always shown to users who
* have the rights to access some other part of the admin area.
* @return boolean Success
*/
public static function replace_menu_item($code, $menuTitle, $url, $controllerClass = null, $priority = -1) {
self::$menu_item_changes[] = array(
'type' => 'add',
'code' => $code,
'item' => new CMSMenuItem($menuTitle, $url, $controllerClass, $priority),
);
}
/**
* Add a previously built menuitem object to the menu
*/
protected static function add_menu_item_obj($code, $cmsMenuItem) {
self::$menu_item_changes[] = array(
'type' => 'add',
'code' => $code,
'item' => $cmsMenuItem,
);
}
/**
* A utility funciton to retrieve subclasses of a given class that
* are instantiable (ie, not abstract) and have a valid menu title.
*
* @todo A variation of this function could probably be moved to {@link ClassInfo}
* @param string $root The root class to begin finding subclasses
* @param boolean $recursive Look for subclasses recursively?
* @return array Valid, unique subclasses
*/
public static function get_cms_classes($root = 'LeftAndMain', $recursive = true) {
$subClasses = array_values(ClassInfo::subclassesFor($root));
foreach($subClasses as $className) {
if($recursive && $className != $root) {
$subClasses = array_merge($subClasses, array_values(ClassInfo::subclassesFor($className)));
}
}
$subClasses = array_unique($subClasses);
foreach($subClasses as $key => $className) {
// Remove abstract classes and LeftAndMain
$classReflection = new ReflectionClass($className);
if(
!$classReflection->isInstantiable()
|| 'LeftAndMain' == $className
|| ClassInfo::classImplements($className, 'TestOnly')
) {
unset($subClasses[$key]);
}
}
return $subClasses;
}
/**
* IteratorAggregate Interface Method. Iterates over the menu items.
*/
function getIterator() {
return new ArrayIterator(self::get_menu_items());
}
/**
* Provide menu titles to the i18n entity provider
*/
function provideI18nEntities() {
$cmsClasses = self::get_cms_classes();
$entities = array();
foreach($cmsClasses as $cmsClass) {
$defaultTitle = LeftAndMain::menu_title_for_class($cmsClass);
$ownerModule = i18n::get_owner_module($cmsClass);
$entities["{$cmsClass}.MENUTITLE"] = array($defaultTitle, PR_HIGH, 'Menu title', $ownerModule);
}
return $entities;
}
}
?>

View File

@ -1,50 +0,0 @@
<?php
/**
* A simple CMS menu item
*
* @package cms
* @subpackage content
*/
class CMSMenuItem extends Object
{
/**
* The (translated) menu title
* @var string $title
*/
public $title;
/**
* Relative URL
* @var string $url
*/
public $url;
/**
* Parent controller class name
* @var string $controller
*/
public $controller;
/**
* Menu priority (sort order)
* @var integer $priority
*/
public $priority;
/**
* Create a new CMS Menu Item
* @param string $title
* @param string $url
* @param string $controller Controller class name
* @param integer $priority The sort priority of the item
*/
public function __construct($title, $url, $controller = null, $priority = -1) {
$this->title = $title;
$this->url = $url;
$this->controller = $controller;
$this->priority = $priority;
parent::__construct();
}
}
?>

View File

@ -1,90 +0,0 @@
<?php
/**
* Imports {@link Group} records by CSV upload, as defined in
* {@link GroupCsvBulkLoader}.
*
* @package cms
* @subpackage batchactions
*/
class GroupImportForm extends Form {
/**
* @var Group Optional group relation
*/
protected $group;
function __construct($controller, $name, $fields = null, $actions = null, $validator = null) {
if(!$fields) {
$helpHtml = _t(
'GroupImportForm.Help1',
'<p>Import one or more groups in <em>CSV</em> format (comma-separated values). <small><a href="#" class="toggle-advanced">Show advanced usage</a></small></p>'
);
$helpHtml .= _t(
'GroupImportForm.Help2',
'<div class="advanced">
<h4>Advanced usage</h4>
<ul>
<li>Allowed columns: <em>%s</em></li>
<li>Existing groups are matched by their unique <em>Code</em> value, and updated with any new values from the imported file</li>
<li>Group hierarchies can be created by using a <em>ParentCode</em> column.</li>
<li>Permission codes can be assigned by the <em>PermissionCode</em> column. Existing permission codes are not cleared.</li>
</ul>
</div>');
$importer = new GroupCsvBulkLoader();
$importSpec = $importer->getImportSpec();
$helpHtml = sprintf($helpHtml, implode(', ', array_keys($importSpec['fields'])));
$fields = new FieldSet(
new LiteralField('Help', $helpHtml),
$fileField = new FileField(
'CsvFile',
_t(
'SecurityAdmin_MemberImportForm.FileFieldLabel',
'CSV File <small>(Allowed extensions: *.csv)</small>'
)
)
);
$fileField->getValidator()->setAllowedExtensions(array('csv'));
}
if(!$actions) $actions = new FieldSet(
new FormAction('doImport', _t('SecurityAdmin_MemberImportForm.BtnImport', 'Import'))
);
if(!$validator) $validator = new RequiredFields('CsvFile');
parent::__construct($controller, $name, $fields, $actions, $validator);
$this->addExtraClass('import-form');
}
function doImport($data, $form) {
$loader = new GroupCsvBulkLoader();
// load file
$result = $loader->load($data['CsvFile']['tmp_name']);
// result message
$msgArr = array();
if($result->CreatedCount()) $msgArr[] = sprintf(
_t('GroupImportForm.ResultCreated', 'Created %d groups'),
$result->CreatedCount()
);
if($result->UpdatedCount()) $msgArr[] = sprintf(
_t('GroupImportForm.ResultUpdated', 'Updated %d groups'),
$result->UpdatedCount()
);
if($result->DeletedCount()) $msgArr[] = sprintf(
_t('GroupImportForm.ResultDeleted', 'Deleted %d groups'),
$result->DeletedCount()
);
$msg = ($msgArr) ? implode(',', $msgArr) : _t('MemberImportForm.ResultNone', 'No changes');
$this->sessionMessage($msg, 'good');
$this->redirectBack();
}
}
?>

View File

@ -1,1325 +0,0 @@
<?php
/**
* LeftAndMain is the parent class of all the two-pane views in the CMS.
* If you are wanting to add more areas to the CMS, you can do it by subclassing LeftAndMain.
*
* This is essentially an abstract class which should be subclassed.
* See {@link CMSMain} for a good example.
*
* @package cms
* @subpackage core
*/
class LeftAndMain extends Controller {
/**
* The 'base' url for CMS administration areas.
* Note that if this is changed, many javascript
* behaviours need to be updated with the correct url
*
* @var string $url_base
*/
static $url_base = "admin";
static $url_segment;
static $url_rule = '/$Action/$ID/$OtherID';
static $menu_title;
static $menu_priority = 0;
static $url_priority = 50;
/**
* @var string A subclass of {@link DataObject}.
* Determines what is managed in this interface,
* through {@link getEditForm()} and other logic.
*/
static $tree_class = null;
/**
* The url used for the link in the Help tab in the backend
* Value can be overwritten if required in _config.php
*/
static $help_link = 'http://userhelp.silverstripe.org';
static $allowed_actions = array(
'index',
'savetreenode',
'getitem',
'getsubtree',
'myprofile',
'printable',
'show',
'Member_ProfileForm',
'EditorToolbar',
'EditForm',
'RootForm',
'AddForm',
'batchactions',
'BatchActionsForm',
'Member_ProfileForm',
);
/**
* Register additional requirements through the {@link Requirements class}.
* Used mainly to work around the missing "lazy loading" functionality
* for getting css/javascript required after an ajax-call (e.g. loading the editform).
*
* @var array $extra_requirements
*/
protected static $extra_requirements = array(
'javascript' => array(),
'css' => array(),
'themedcss' => array(),
);
/**
* @param Member $member
* @return boolean
*/
function canView($member = null) {
if(!$member && $member !== FALSE) {
$member = Member::currentUser();
}
// cms menus only for logged-in members
if(!$member) return false;
// alternative decorated checks
if($this->hasMethod('alternateAccessCheck')) {
$alternateAllowed = $this->alternateAccessCheck();
if($alternateAllowed === FALSE) return false;
}
// Default security check for LeftAndMain sub-class permissions
if(!Permission::checkMember($member, "CMS_ACCESS_$this->class") &&
!Permission::checkMember($member, "CMS_ACCESS_LeftAndMain")) {
return false;
}
return true;
}
/**
* @uses LeftAndMainDecorator->init()
* @uses LeftAndMainDecorator->accessedCMS()
* @uses CMSMenu
*/
function init() {
parent::init();
SSViewer::setOption('rewriteHashlinks', false);
// set language
$member = Member::currentUser();
if(!empty($member->Locale)) i18n::set_locale($member->Locale);
if(!empty($member->DateFormat)) i18n::set_date_format($member->DateFormat);
if(!empty($member->TimeFormat)) i18n::set_time_format($member->TimeFormat);
// can't be done in cms/_config.php as locale is not set yet
CMSMenu::add_link(
'Help',
_t('LeftAndMain.HELP', 'Help', PR_HIGH, 'Menu title'),
self::$help_link
);
// set reading lang
if(class_exists('Translatable') && Object::has_extension('SiteTree', 'Translatable') && !$this->isAjax()) {
Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SiteTree')));
}
// Allow customisation of the access check by a decorator
// Also all the canView() check to execute Director::redirect()
if(!$this->canView() && !$this->response->isFinished()) {
// When access /admin/, we should try a redirect to another part of the admin rather than be locked out
$menu = $this->MainMenu();
foreach($menu as $candidate) {
if(
$candidate->Link &&
$candidate->Link != $this->Link()
&& $candidate->MenuItem->controller
&& singleton($candidate->MenuItem->controller)->canView()
) {
return Director::redirect($candidate->Link);
}
}
if(Member::currentUser()) {
Session::set("BackURL", null);
}
// if no alternate menu items have matched, return a permission error
$messageSet = array(
'default' => _t('LeftAndMain.PERMDEFAULT',"Please choose an authentication method and enter your credentials to access the CMS."),
'alreadyLoggedIn' => _t('LeftAndMain.PERMALREADY',"I'm sorry, but you can't access that part of the CMS. If you want to log in as someone else, do so below"),
'logInAgain' => _t('LeftAndMain.PERMAGAIN',"You have been logged out of the CMS. If you would like to log in again, enter a username and password below."),
);
return Security::permissionFailure($this, $messageSet);
}
// Don't continue if there's already been a redirection request.
if(Director::redirected_to()) return;
// Audit logging hook
if(empty($_REQUEST['executeForm']) && !$this->isAjax()) $this->extend('accessedCMS');
// Set the members html editor config
HtmlEditorConfig::set_active(Member::currentUser()->getHtmlEditorConfigForCMS());
// Set default values in the config if missing. These things can't be defined in the config
// file because insufficient information exists when that is being processed
$htmlEditorConfig = HtmlEditorConfig::get_active();
$htmlEditorConfig->setOption('language', i18n::get_tinymce_lang());
if(!$htmlEditorConfig->getOption('content_css')) {
$cssFiles = 'cms/css/editor.css';
// Use theme from the site config
if(($config = SiteConfig::current_site_config()) && $config->Theme) {
$theme = $config->Theme;
} elseif(SSViewer::current_theme()) {
$theme = SSViewer::current_theme();
} else {
$theme = false;
}
if($theme) $cssFiles .= ',' . THEMES_DIR . "/{$theme}/css/editor.css";
else if(project()) $cssFiles .= ',' . project() . '/css/editor.css';
$htmlEditorConfig->setOption('content_css', $cssFiles);
}
Requirements::css(CMS_DIR . '/css/typography.css');
Requirements::css(CMS_DIR . '/css/layout.css');
Requirements::css(CMS_DIR . '/css/cms_left.css');
Requirements::css(CMS_DIR . '/css/cms_right.css');
Requirements::css(SAPPHIRE_DIR . '/css/Form.css');
if(isset($_REQUEST['debug_firebug'])) {
// Firebug is a useful console for debugging javascript
// Its available as a Firefox extension or a javascript library
// for easy inclusion in other browsers (just append ?debug_firebug=1 to the URL)
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/firebug-lite/firebug.js');
}
Requirements::javascript(SAPPHIRE_DIR . '/javascript/prototypefix/intro.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/prototype/prototype.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/prototypefix/outro.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery/jquery.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery_improvements.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/jquery-ui.js'); //import all of jquery ui
Requirements::javascript(CMS_DIR . '/thirdparty/jquery-layout/jquery.layout.js');
Requirements::javascript(CMS_DIR . '/thirdparty/jquery-layout/jquery.layout.state.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/json-js/json2.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-metadata/jquery.metadata.js');
Requirements::javascript(CMS_DIR . '/javascript/jquery-fitheighttoparent/jquery.fitheighttoparent.js');
Requirements::javascript(CMS_DIR . '/javascript/ssui.core.js');
// @todo Load separately so the CSS files can be inlined
Requirements::css(SAPPHIRE_DIR . '/thirdparty/jquery-ui-themes/smoothness/jquery.ui.all.css');
// entwine
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
// Required for TreeTools panel above tree
Requirements::javascript(SAPPHIRE_DIR . '/javascript/TabSet.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/behaviour/behaviour.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-cookie/jquery.cookie.js');
Requirements::javascript(CMS_DIR . '/thirdparty/jquery-notice/jquery.notice.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery-ondemand/jquery.ondemand.js');
Requirements::javascript(CMS_DIR . '/javascript/jquery-changetracker/lib/jquery.changetracker.js');
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::add_i18n_javascript(CMS_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/scriptaculous/effects.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/scriptaculous/dragdrop.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/scriptaculous/controls.js');
Requirements::javascript(THIRDPARTY_DIR . '/jstree/jquery.jstree.js');
Requirements::css(THIRDPARTY_DIR . '/jstree/themes/apple/style.css');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.js');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.Tree.js');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.EditForm.js');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.AddForm.js');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.BatchActions.js');
// navigator
Requirements::css(CMS_DIR . '/css/SilverStripeNavigator.css');
Requirements::javascript(CMS_DIR . '/javascript/SilverStripeNavigator.js');
Requirements::themedCSS('typography');
foreach (self::$extra_requirements['javascript'] as $file) {
Requirements::javascript($file[0]);
}
foreach (self::$extra_requirements['css'] as $file) {
Requirements::css($file[0], $file[1]);
}
foreach (self::$extra_requirements['themedcss'] as $file) {
Requirements::themedCSS($file[0], $file[1]);
}
Requirements::css(CMS_DIR . '/css/unjquery.css');
// Javascript combined files
Requirements::combine_files(
'base.js',
array(
'sapphire/thirdparty/prototype/prototype.js',
'sapphire/thirdparty/behaviour/behaviour.js',
'sapphire/thirdparty/jquery/jquery.js',
'sapphire/thirdparty/jquery-livequery/jquery.livequery.js',
'sapphire/javascript/jquery-ondemand/jquery.ondemand.js',
'sapphire/thirdparty/jquery-ui/jquery-ui.js',
'sapphire/javascript/i18n.js',
)
);
Requirements::combine_files(
'leftandmain.js',
array(
'sapphire/thirdparty/scriptaculous/effects.js',
'sapphire/thirdparty/scriptaculous/dragdrop.js',
'sapphire/thirdparty/scriptaculous/controls.js',
'cms/javascript/LeftAndMain.js',
'sapphire/thirdparty/jstree/jquery.jstree.js',
'sapphire/javascript/TreeDropdownField.js',
'cms/javascript/ThumbnailStripField.js',
)
);
$dummy = null;
$this->extend('init', $dummy);
// The user's theme shouldn't affect the CMS, if, for example, they have replaced
// TableListField.ss or Form.ss.
SSViewer::set_theme(null);
}
/**
* If this is set to true, the "switchView" context in the
* template is shown, with links to the staging and publish site.
*
* @return boolean
*/
function ShowSwitchView() {
return false;
}
//------------------------------------------------------------------------------------------//
// Main controllers
/**
* You should implement a Link() function in your subclass of LeftAndMain,
* to point to the URL of that particular controller.
*
* @return string
*/
public function Link($action = null) {
// Handle missing url_segments
if(!$this->stat('url_segment', true))
self::$url_segment = $this->class;
return Controller::join_links(
$this->stat('url_base', true),
$this->stat('url_segment', true),
'/', // trailing slash needed if $action is null!
"$action"
);
}
/**
* Returns the menu title for the given LeftAndMain subclass.
* Implemented static so that we can get this value without instantiating an object.
* Menu title is *not* internationalised.
*/
static function menu_title_for_class($class) {
$title = eval("return $class::\$menu_title;");
if(!$title) $title = preg_replace('/Admin$/', '', $class);
return $title;
}
public function show($request) {
// TODO Necessary for TableListField URLs to work properly
if($request->param('ID')) $this->setCurrentPageID($request->param('ID'));
if($this->isAjax()) {
SSViewer::setOption('rewriteHashlinks', false);
$form = $this->getEditForm($request->param('ID'));
$content = $form->formHtmlContent();
} else {
// Rendering is handled by template, which will call EditForm() eventually
$content = $this->renderWith($this->getViewer('show'));
}
if($this->ShowSwitchView()) {
$content .= '<div id="AjaxSwitchView">' . $this->SwitchView() . '</div>';
}
return $content;
}
/**
* @deprecated 2.4 Please use show()
*/
public function getitem($request) {
$form = $this->getEditForm($request->getVar('ID'));
return $form->formHtmlContent();
}
//------------------------------------------------------------------------------------------//
// Main UI components
/**
* Returns the main menu of the CMS. This is also used by init()
* to work out which sections the user has access to.
*
* @return DataObjectSet
*/
public function MainMenu() {
// Don't accidentally return a menu if you're not logged in - it's used to determine access.
if(!Member::currentUser()) return new DataObjectSet();
// Encode into DO set
$menu = new DataObjectSet();
$menuItems = CMSMenu::get_viewable_menu_items();
if($menuItems) foreach($menuItems as $code => $menuItem) {
// alternate permission checks (in addition to LeftAndMain->canView())
if(
isset($menuItem->controller)
&& $this->hasMethod('alternateMenuDisplayCheck')
&& !$this->alternateMenuDisplayCheck($menuItem->controller)
) {
continue;
}
$linkingmode = "";
if(strpos($this->Link(), $menuItem->url) !== false) {
if($this->Link() == $menuItem->url) {
$linkingmode = "current";
// default menu is the one with a blank {@link url_segment}
} else if(singleton($menuItem->controller)->stat('url_segment') == '') {
if($this->Link() == $this->stat('url_base').'/') $linkingmode = "current";
} else {
$linkingmode = "current";
}
}
// already set in CMSMenu::populate_menu(), but from a static pre-controller
// context, so doesn't respect the current user locale in _t() calls - as a workaround,
// we simply call LeftAndMain::menu_title_for_class() again
// if we're dealing with a controller
if($menuItem->controller) {
$defaultTitle = LeftAndMain::menu_title_for_class($menuItem->controller);
$title = _t("{$menuItem->controller}.MENUTITLE", $defaultTitle);
} else {
$title = $menuItem->title;
}
$menu->push(new ArrayData(array(
"MenuItem" => $menuItem,
"Title" => Convert::raw2xml($title),
"Code" => $code,
"Link" => $menuItem->url,
"LinkingMode" => $linkingmode
)));
}
// if no current item is found, assume that first item is shown
//if(!isset($foundCurrent))
return $menu;
}
public function CMSTopMenu() {
return $this->renderWith(array('CMSTopMenu_alternative','CMSTopMenu'));
}
/**
* Return a list of appropriate templates for this class, with the given suffix
*/
protected function getTemplatesWithSuffix($suffix) {
$classes = array_reverse(ClassInfo::ancestry($this->class));
foreach($classes as $class) {
$templates[] = $class . $suffix;
if($class == 'LeftAndMain') break;
}
return $templates;
}
public function Left() {
return $this->renderWith($this->getTemplatesWithSuffix('_left'));
}
public function Right() {
return $this->renderWith($this->getTemplatesWithSuffix('_right'));
}
public function getRecord($id) {
$className = $this->stat('tree_class');
if($id instanceof $className) {
return $id;
} else if(is_numeric($id)) {
return DataObject::get_by_id($className, $id);
} else {
return false;
}
}
/**
* @return String HTML
*/
public function SiteTreeAsUL() {
return $this->getSiteTreeFor($this->stat('tree_class'));
}
/**
* Get a site tree HTML listing which displays the nodes under the given criteria.
*
* @param $className The class of the root object
* @param $rootID The ID of the root object. If this is null then a complete tree will be
* shown
* @param $childrenMethod The method to call to get the children of the tree. For example,
* Children, AllChildrenIncludingDeleted, or AllHistoricalChildren
* @return String Nested unordered list with links to each page
*/
function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, $filterFunction = null, $minNodeCount = 30) {
// Default childrenMethod and numChildrenMethod
if (!$childrenMethod) $childrenMethod = 'AllChildrenIncludingDeleted';
if (!$numChildrenMethod) $numChildrenMethod = 'numChildren';
// Get the tree root
$obj = $rootID ? $this->getRecord($rootID) : singleton($className);
// Mark the nodes of the tree to return
if ($filterFunction) $obj->setMarkingFilterFunction($filterFunction);
$obj->markPartialTree($minNodeCount, $this, $childrenMethod, $numChildrenMethod);
// Ensure current page is exposed
if($p = $this->currentPage()) $obj->markToExpose($p);
// NOTE: SiteTree/CMSMain coupling :-(
SiteTree::prepopuplate_permission_cache('CanEditType', $obj->markedNodeIDs(), 'SiteTree::can_edit_multiple');
// getChildrenAsUL is a flexible and complex way of traversing the tree
$titleEval = '
"<li id=\"record-$child->ID\" data-id=\"$child->ID\" class=\"" . $child->CMSTreeClasses($extraArg) . "\">" .
"<ins class=\"jstree-icon\">&nbsp;</ins>" .
"<a href=\"" . Controller::join_links(substr($extraArg->Link(),0,-1), "show", $child->ID) . "\" title=\"'
. _t('LeftAndMain.PAGETYPE','Page type: ')
. '".$child->class."\" ><ins class=\"jstree-icon\">&nbsp;</ins>" . ($child->TreeTitle) .
"</a>"
';
$html = $obj->getChildrenAsUL(
"",
$titleEval,
$this,
true,
$childrenMethod,
$numChildrenMethod,
$minNodeCount
);
// Wrap the root if needs be.
if(!$rootID) {
$rootLink = $this->Link('show') . '/root';
// This lets us override the tree title with an extension
if($this->hasMethod('getCMSTreeTitle') && $customTreeTitle = $this->getCMSTreeTitle()) {
$treeTitle = $customTreeTitle;
} else {
$siteConfig = SiteConfig::current_site_config();
$treeTitle = $siteConfig->Title;
}
$html = "<ul id=\"sitetree\" class=\"tree unformatted\"><li id=\"record-0\" data-id=\"0\"class=\"Root nodelete\"><a href=\"$rootLink\"><strong>$treeTitle</strong></a>"
. $html . "</li></ul>";
}
return $html;
}
/**
* Get a subtree underneath the request param 'ID'.
* If ID = 0, then get the whole tree.
*/
public function getsubtree($request) {
if($filterClass = $request->requestVar('FilterClass')) {
if(!is_subclass_of($filterClass, 'CMSSiteTreeFilter')) {
throw new Exception(sprintf('Invalid filter class passed: %s', $filterClass));
}
$filter = new $filterClass($request->requestVars());
} else {
$filter = null;
}
$html = $this->getSiteTreeFor(
$this->stat('tree_class'),
$request->getVar('ID'),
($filter) ? $filter->getChildrenMethod() : null,
null,
($filter) ? array($filter, 'isPageIncluded') : null,
$request->getVar('minNodeCount')
);
// Trim off the outer tag
$html = preg_replace('/^[\s\t\r\n]*<ul[^>]*>/','', $html);
$html = preg_replace('/<\/ul[^>]*>[\s\t\r\n]*$/','', $html);
return $html;
}
/**
* Save handler
*/
public function save($data, $form) {
$className = $this->stat('tree_class');
// Existing or new record?
$SQL_id = Convert::raw2sql($data['ID']);
if(substr($SQL_id,0,3) != 'new') {
$record = DataObject::get_by_id($className, $SQL_id);
if($record && !$record->canEdit()) return Security::permissionFailure($this);
} else {
if(!singleton($this->stat('tree_class'))->canCreate()) return Security::permissionFailure($this);
$record = $this->getNewItem($SQL_id, false);
}
// save form data into record
$form->saveInto($record, true);
$record->write();
$this->extend('onAfterSave', $record);
$this->response->addHeader('X-Status', _t('LeftAndMain.SAVEDUP'));
// write process might've changed the record, so we reload before returning
$form = $this->getEditForm($record->ID);
return $form->formHtmlContent();
}
/**
* Update the position and parent of a tree node.
* Only saves the node if changes were made.
*
* Required data:
* - 'ID': The moved node
* - 'ParentID': New parent relation of the moved node (0 for root)
* - 'SiblingIDs': Array of all sibling nodes to the moved node (incl. the node itself).
* In case of a 'ParentID' change, relates to the new siblings under the new parent.
*
* @return SS_HTTPResponse JSON string with a
*/
public function savetreenode($request) {
if (!Permission::check('SITETREE_REORGANISE') && !Permission::check('ADMIN')) {
$this->response->setStatusCode(
403,
_t('LeftAndMain.CANT_REORGANISE',"You do not have permission to rearange the site tree. Your change was not saved.")
);
return;
}
$className = $this->stat('tree_class');
$statusUpdates = array('modified'=>array());
$id = $request->requestVar('ID');
$parentID = $request->requestVar('ParentID');
$siblingIDs = $request->requestVar('SiblingIDs');
$statusUpdates = array('modified'=>array());
if(!is_numeric($id) || !is_numeric($parentID)) throw new InvalidArgumentException();
$node = DataObject::get_by_id($className, $id);
if($node && !$node->canEdit()) return Security::permissionFailure($this);
if(!$node) {
$this->response->setStatusCode(
500,
_t(
'LeftAndMain.PLEASESAVE',
"Please Save Page: This page could not be upated because it hasn't been saved yet."
)
);
return;
}
// Update hierarchy (only if ParentID changed)
if($node->ParentID != $parentID) {
$node->ParentID = (int)$parentID;
$node->write();
$statusUpdates['modified'][$node->ID] = array(
'TreeTitle'=>$node->TreeTitle
);
// Update all dependent pages
if($virtualPages = DataObject::get("VirtualPage", "\"CopyContentFromID\" = $node->ID")) {
foreach($virtualPages as $virtualPage) {
$statusUpdates['modified'][$virtualPage->ID] = array(
'TreeTitle' => $virtualPage->TreeTitle()
);
}
}
$this->response->addHeader('X-Status', _t('LeftAndMain.SAVED','saved'));
}
// Update sorting
if(is_array($siblingIDs)) {
$counter = 0;
foreach($siblingIDs as $id) {
if($id == $node->ID) {
$node->Sort = ++$counter;
$node->write();
$statusUpdates['modified'][$node->ID] = array(
'TreeTitle' => $node->TreeTitle
);
} else if(is_numeric($id)) {
// Nodes that weren't "actually moved" shouldn't be registered as
// having been edited; do a direct SQL update instead
++$counter;
DB::query(sprintf("UPDATE \"%s\" SET \"Sort\" = %d WHERE \"ID\" = '%d'", $className, $counter, $id));
}
}
$this->response->addHeader('X-Status', _t('LeftAndMain.SAVED','saved'));
}
return Convert::raw2json($statusUpdates);
}
public function CanOrganiseSitetree() {
return !Permission::check('SITETREE_REORGANISE') && !Permission::check('ADMIN') ? false : true;
}
/**
* Retrieves an edit form, either for display, or to process submitted data.
* Also used in the template rendered through {@link Right()} in the $EditForm placeholder.
*
* This is a "pseudo-abstract" methoed, usually connected to a {@link getEditForm()}
* method in an entwine subclass. This method can accept a record identifier,
* selected either in custom logic, or through {@link currentPageID()}.
* The form usually construct itself from {@link DataObject->getCMSFields()}
* for the specific managed subclass defined in {@link LeftAndMain::$tree_class}.
*
* @param HTTPRequest $request Optionally contains an identifier for the
* record to load into the form.
* @return Form Should return a form regardless wether a record has been found.
* Form might be readonly if the current user doesn't have the permission to edit
* the record.
*/
/**
* @return Form
*/
function EditForm($request = null) {
return $this->getEditForm();
}
public function getEditForm($id = null) {
if(!$id) $id = $this->currentPageID();
if(is_object($id)) {
$record = $id;
} else {
$record = ($id && $id != "root") ? $this->getRecord($id) : null;
if($record && !$record->canView()) return Security::permissionFailure($this);
}
if($record) {
$fields = $record->getCMSFields();
if ($fields == null) {
user_error(
"getCMSFields() returned null - it should return a FieldSet object.
Perhaps you forgot to put a return statement at the end of your method?",
E_USER_ERROR
);
}
// Add hidden fields which are required for saving the record
// and loading the UI state
if(!$fields->dataFieldByName('ClassName')) {
$fields->push(new HiddenField('ClassName'));
}
if(
Object::has_extension($this->stat('tree_class'), 'Hierarchy')
&& !$fields->dataFieldByName('ParentID')
) {
$fields->push(new HiddenField('ParentID'));
}
if($record->hasMethod('getAllCMSActions')) {
$actions = $record->getAllCMSActions();
} else {
$actions = $record->getCMSActions();
// add default actions if none are defined
if(!$actions || !$actions->Count()) {
if($record->canEdit()) {
$actions->push(new FormAction('save',_t('CMSMain.SAVE','Save')));
}
}
}
$form = new Form($this, "EditForm", $fields, $actions);
$form->loadDataFrom($record);
// Add a default or custom validator.
// @todo Currently the default Validator.js implementation
// adds javascript to the document body, meaning it won't
// be included properly if the associated fields are loaded
// through ajax. This means only serverside validation
// will kick in for pages+validation loaded through ajax.
// This will be solved by using less obtrusive javascript validation
// in the future, see http://open.silverstripe.com/ticket/2915 and
// http://open.silverstripe.com/ticket/3386
if($record->hasMethod('getCMSValidator')) {
$validator = $record->getCMSValidator();
// The clientside (mainly LeftAndMain*.js) rely on ajax responses
// which can be evaluated as javascript, hence we need
// to override any global changes to the validation handler.
$validator->setJavascriptValidationHandler('prototype');
$form->setValidator($validator);
} else {
$form->unsetValidator();
}
if(!$record->canEdit()) {
$readonlyFields = $form->Fields()->makeReadonly();
$form->setFields($readonlyFields);
}
} else {
$form = $this->RootForm();
}
return $form;
}
function RootForm() {
return $this->EmptyForm();
}
/**
* Returns a placeholder form, used by {@link getEditForm()} if no record is selected.
* Our javascript logic always requires a form to be present in the CMS interface.
*
* @return Form
*/
function EmptyForm() {
$form = new Form(
$this,
"EditForm",
new FieldSet(
new HeaderField(
'WelcomeHeader',
$this->getApplicationName()
),
new LiteralField(
'WelcomeText',
sprintf('<p id="WelcomeMessage">%s %s. %s</p>',
_t('LeftAndMain_right.ss.WELCOMETO','Welcome to'),
$this->getApplicationName(),
_t('CHOOSEPAGE','Please choose an item from the left.')
)
)
),
new FieldSet()
);
$form->unsetValidator();
return $form;
}
/**
* @return Form
*/
function AddForm() {
$class = $this->stat('tree_class');
$typeMap = array($class => singleton($class)->i18n_singular_name());
$typeField = new DropdownField('Type', false, $typeMap, $class);
$form = new Form(
$this,
'AddForm',
new FieldSet(
new HiddenField('ParentID'),
$typeField->performReadonlyTransformation()
),
new FieldSet(
new FormAction('doAdd', _t('AssetAdmin_left.ss.GO','Go'))
)
);
$form->addExtraClass('actionparams');
return $form;
}
/**
* Add a new group and return its details suitable for ajax.
*/
public function doAdd($data, $form) {
$class = $this->stat('tree_class');
// check create permissions
if(!singleton($class)->canCreate()) return Security::permissionFailure($this);
// check addchildren permissions
if(
singleton($class)->hasDatabaseField('Hierarchy')
&& isset($data['ParentID'])
&& is_numeric($data['ParentID'])
) {
$parentRecord = DataObject::get_by_id($class, $data['ParentID']);
if(
$parentRecord->hasMethod('canAddChildren')
&& !$parentRecord->canAddChildren()
) return Security::permissionFailure($this);
}
$record = Object::create($class);
$form->saveInto($record);
$record->write();
// Used in TinyMCE inline folder creation
if(isset($data['returnID'])) {
return $record->ID;
} else if($this->isAjax()) {
$form = $this->getEditForm($record->ID);
return $form->formHtmlContent();
} else {
return $this->redirect(Controller::join_links($this->Link('show'), $record->ID));
}
}
/**
* Batch Actions Handler
*/
function batchactions() {
return new CMSBatchActionHandler($this, 'batchactions', $this->stat('tree_class'));
}
/**
* @return Form
*/
function BatchActionsForm() {
$actions = $this->batchactions()->batchActionList();
$actionsMap = array();
foreach($actions as $action) $actionsMap[$action->Link] = $action->Title;
$form = new Form(
$this,
'BatchActionsForm',
new FieldSet(
new LiteralField(
'Intro',
sprintf('<p><small>%s</small></p>',
_t(
'CMSMain_left.ss.SELECTPAGESACTIONS',
'Select the pages that you want to change &amp; then click an action:'
)
)
),
new HiddenField('csvIDs'),
new DropdownField(
'Action',
false,
$actionsMap
)
),
new FieldSet(
// TODO i18n
new FormAction('submit', "Go")
)
);
$form->addExtraClass('actionparams');
$form->unsetValidator();
return $form;
}
public function myprofile() {
$form = $this->Member_ProfileForm();
return $this->customise(array(
'Form' => $form
))->renderWith('BlankPage');
}
public function Member_ProfileForm() {
return new Member_ProfileForm($this, 'Member_ProfileForm', Member::currentUser());
}
public function printable() {
$form = $this->getEditForm($this->currentPageID());
if(!$form) return false;
$form->transform(new PrintableTransformation());
$form->setActions(null);
Requirements::clear();
Requirements::css(CMS_DIR . '/css/LeftAndMain_printable.css');
return array(
"PrintForm" => $form
);
}
/**
* Identifier for the currently shown record,
* in most cases a database ID. Inspects the following
* sources (in this order):
* - GET/POST parameter named 'ID'
* - URL parameter named 'ID'
* - Session value namespaced by classname, e.g. "CMSMain.currentPage"
*
* @return int
*/
public function currentPageID() {
if($this->request->requestVar('ID')) {
return $this->request->requestVar('ID');
} elseif ($this->request->param('ID') && is_numeric($this->request->param('ID'))) {
return $this->request->param('ID');
} elseif(Session::get("{$this->class}.currentPage")) {
return Session::get("{$this->class}.currentPage");
} else {
return null;
}
}
/**
* Forces the current page to be set in session,
* which can be retrieved later through {@link currentPageID()}.
* Keep in mind that setting an ID through GET/POST or
* as a URL parameter will overrule this value.
*
* @param int $id
*/
public function setCurrentPageID($id) {
Session::set("{$this->class}.currentPage", $id);
}
/**
* Uses {@link getRecord()} and {@link currentPageID()}
* to get the currently selected record.
*
* @return DataObject
*/
public function currentPage() {
return $this->getRecord($this->currentPageID());
}
/**
* Compares a given record to the currently selected one (if any).
* Used for marking the current tree node.
*
* @return boolean
*/
public function isCurrentPage(DataObject $record) {
return ($record->ID == $this->currentPageID());
}
/**
* Get the staus of a certain page and version.
*
* This function is used for concurrent editing, and providing alerts
* when multiple users are editing a single page. It echoes a json
* encoded string to the UA.
*/
/**
* Return the CMS's HTML-editor toolbar
*/
public function EditorToolbar() {
return Object::create('HtmlEditorField_Toolbar', $this, "EditorToolbar");
}
/**
* Return the version number of this application.
* Uses the subversion path information in <mymodule>/silverstripe_version
* (automacially replaced $URL$ placeholder).
*
* @return string
*/
public function CMSVersion() {
$sapphireVersionFile = file_get_contents(BASE_PATH . '/sapphire/silverstripe_version');
$cmsVersionFile = file_get_contents(BASE_PATH . '/cms/silverstripe_version');
$sapphireVersion = $this->versionFromVersionFile($sapphireVersionFile);
$cmsVersion = $this->versionFromVersionFile($cmsVersionFile);
return "cms: $cmsVersion, sapphire: $sapphireVersion";
}
/**
* Return the version from the content of a silverstripe_version file
*/
public function versionFromVersionFile($fileContent) {
if(preg_match('/\/trunk\/silverstripe_version/', $fileContent)) {
return "trunk";
} else {
preg_match("/\/(?:branches|tags\/rc|tags\/beta|tags\/alpha|tags)\/([A-Za-z0-9._-]+)\/silverstripe_version/", $fileContent, $matches);
return ($matches) ? $matches[1] : null;
}
}
/**
* @return array
*/
function SwitchView() {
if($page = $this->currentPage()) {
$nav = SilverStripeNavigator::get_for_record($page);
return $nav['items'];
}
}
/**
* The application name. Customisable by calling
* LeftAndMain::setApplicationName() - the first parameter.
*
* @var String
*/
static $application_name = 'SilverStripe CMS';
/**
* The application logo text. Customisable by calling
* LeftAndMain::setApplicationName() - the second parameter.
*
* @var String
*/
static $application_logo_text = 'SilverStripe';
/**
* Set the application name, and the logo text.
*
* @param String $name The application name
* @param String $logoText The logo text
*/
static $application_link = "http://www.silverstripe.org/";
/**
* @param String $name
* @param String $logoText
* @param String $link (Optional)
*/
static function setApplicationName($name, $logoText = null, $link = null) {
self::$application_name = $name;
self::$application_logo_text = $logoText ? $logoText : $name;
if($link) self::$application_link = $link;
}
/**
* Get the application name.
* @return String
*/
function getApplicationName() {
return self::$application_name;
}
/**
* Get the application logo text.
* @return String
*/
function getApplicationLogoText() {
return self::$application_logo_text;
}
/**
* @return String
*/
function ApplicationLink() {
return self::$application_link;
}
/**
* Return the title of the current section, as shown on the main menu
*/
function SectionTitle() {
// Get menu - use obj() to cache it in the same place as the template engine
$menu = $this->obj('MainMenu');
foreach($menu as $menuItem) {
if($menuItem->LinkingMode == 'current') return $menuItem->Title;
}
}
/**
* The application logo path. Customisable by calling
* LeftAndMain::setLogo() - the first parameter.
*
* @var unknown_type
*/
static $application_logo = 'cms/images/mainmenu/logo.gif';
/**
* The application logo style. Customisable by calling
* LeftAndMain::setLogo() - the second parameter.
*
* @var String
*/
static $application_logo_style = '';
/**
* Set the CMS application logo.
*
* @param String $logo Relative path to the logo
* @param String $logoStyle Custom CSS styles for the logo
* e.g. "border: 1px solid red; padding: 5px;"
*/
static function setLogo($logo, $logoStyle) {
self::$application_logo = $logo;
self::$application_logo_style = $logoStyle;
self::$application_logo_text = '';
}
/**
* The height of the image should be around 164px to avoid the overlaping between the image and loading animation graphic.
* If the given image's height is significantly larger or smaller, adjust the loading animation's top offset in
* positionLoadingSpinner() in LeftAndMain.js
*/
protected static $loading_image = 'cms/images/logo.gif';
/**
* Set the image shown when the CMS is loading.
*/
static function set_loading_image($loadingImage) {
self::$loading_image = $loadingImage;
}
function LoadingImage() {
return self::$loading_image;
}
/**
* Combines an optional background image and additional CSS styles,
* set through {@link setLogo()}.
*
* @return String CSS attribute
*/
function LogoStyle() {
$attr = self::$application_logo_style;
if(self::$application_logo) $attr .= "background: url(" . self::$application_logo . ") no-repeat; ";
return $attr;
}
/**
* Return the base directory of the tiny_mce codebase
*/
function MceRoot() {
return MCE_ROOT;
}
/**
* Register the given javascript file as required in the CMS.
* Filenames should be relative to the base, eg, SAPPHIRE_DIR . '/javascript/loader.js'
*/
public static function require_javascript($file) {
self::$extra_requirements['javascript'][] = array($file);
}
/**
* Register the given stylesheet file as required.
*
* @param $file String Filenames should be relative to the base, eg, THIRDPARTY_DIR . '/tree/tree.css'
* @param $media String Comma-separated list of media-types (e.g. "screen,projector")
* @see http://www.w3.org/TR/REC-CSS2/media.html
*/
public static function require_css($file, $media = null) {
self::$extra_requirements['css'][] = array($file, $media);
}
/**
* Register the given "themeable stylesheet" as required.
* Themeable stylesheets have globally unique names, just like templates and PHP files.
* Because of this, they can be replaced by similarly named CSS files in the theme directory.
*
* @param $name String The identifier of the file. For example, css/MyFile.css would have the identifier "MyFile"
* @param $media String Comma-separated list of media-types (e.g. "screen,projector")
*/
static function require_themed_css($name, $media = null) {
self::$extra_requirements['themedcss'][] = array($name, $media);
}
}
/**
* @package cms
* @subpackage core
*/
class LeftAndMainMarkingFilter {
/**
* @var array Request params (unsanitized)
*/
protected $params = array();
/**
* @param array $params Request params (unsanitized)
*/
function __construct($params = null) {
$this->ids = array();
$this->expanded = array();
$parents = array();
$q = $this->getQuery($params);
$res = $q->execute();
if (!$res) return;
// And keep a record of parents we don't need to get parents
// of themselves, as well as IDs to mark
foreach($res as $row) {
if ($row['ParentID']) $parents[$row['ParentID']] = true;
$this->ids[$row['ID']] = true;
}
// We need to recurse up the tree,
// finding ParentIDs for each ID until we run out of parents
while (!empty($parents)) {
$res = DB::query('SELECT "ParentID", "ID" FROM "SiteTree" WHERE "ID" in ('.implode(',',array_keys($parents)).')');
$parents = array();
foreach($res as $row) {
if ($row['ParentID']) $parents[$row['ParentID']] = true;
$this->ids[$row['ID']] = true;
$this->expanded[$row['ID']] = true;
}
}
}
protected function getQuery($params) {
$where = array();
$SQL_params = Convert::raw2sql($params);
if(isset($SQL_params['ID'])) unset($SQL_params['ID']);
foreach($SQL_params as $name => $val) {
switch($name) {
default:
// Partial string match against a variety of fields
if(!empty($val) && singleton("SiteTree")->hasDatabaseField($name)) {
$where[] = "\"$name\" LIKE '%$val%'";
}
}
}
return new SQLQuery(
array("ParentID", "ID"),
'SiteTree',
$where
);
}
function mark($node) {
$id = $node->ID;
if(array_key_exists((int) $id, $this->expanded)) $node->markOpened();
return array_key_exists((int) $id, $this->ids) ? $this->ids[$id] : false;
}
}
?>

View File

@ -1,21 +0,0 @@
<?php
/**
* Plug-ins for additional functionality in your LeftAndMain classes.
*
* @package cms
* @subpackage core
*/
abstract class LeftAndMainDecorator extends Extension {
function init() {
}
function accessedCMS() {
}
function augmentNewSiteTreeItem(&$item) {
}
}
?>

View File

@ -1,108 +0,0 @@
<?php
/**
* Imports {@link Member} records by CSV upload, as defined in
* {@link MemberCsvBulkLoader}.
*
* @package cms
* @subpackage batchactions
*/
class MemberImportForm extends Form {
/**
* @var Group Optional group relation
*/
protected $group;
function __construct($controller, $name, $fields = null, $actions = null, $validator = null) {
if(!$fields) {
$helpHtml = _t(
'MemberImportForm.Help1',
'<p>Import members in <em>CSV format</em> (comma-separated values). <small><a href="#" class="toggle-advanced">Show advanced usage</a></small></p>'
);
$helpHtml .= _t(
'MemberImportForm.Help2',
'<div class="advanced">
<h4>Advanced usage</h4>
<ul>
<li>Allowed columns: <em>%s</em></li>
<li>Existing members are matched by their unique <em>Code</em> property, and updated with any new values from the imported file.</li>
<li>Groups can be assigned by the <em>Groups</em> column. Groups are identified by their <em>Code</em> property, multiple groups can be separated by comma. Existing group memberships are not cleared.</li>
</ul>
</div>');
$importer = new MemberCsvBulkLoader();
$importSpec = $importer->getImportSpec();
$helpHtml = sprintf($helpHtml, implode(', ', array_keys($importSpec['fields'])));
$fields = new FieldSet(
new LiteralField('Help', $helpHtml),
$fileField = new FileField(
'CsvFile',
_t(
'SecurityAdmin_MemberImportForm.FileFieldLabel',
'CSV File <small>(Allowed extensions: *.csv)</small>'
)
)
);
$fileField->getValidator()->setAllowedExtensions(array('csv'));
}
if(!$actions) $actions = new FieldSet(
new FormAction('doImport', _t('SecurityAdmin_MemberImportForm.BtnImport', 'Import'))
);
if(!$validator) $validator = new RequiredFields('CsvFile');
parent::__construct($controller, $name, $fields, $actions, $validator);
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(CMS_DIR . '/javascript/MemberImportForm.js');
$this->addExtraClass('import-form');
}
function doImport($data, $form) {
$loader = new MemberCsvBulkLoader();
// optionally set group relation
if($this->group) $loader->setGroups(array($this->group));
// load file
$result = $loader->load($data['CsvFile']['tmp_name']);
// result message
$msgArr = array();
if($result->CreatedCount()) $msgArr[] = sprintf(
_t('MemberImportForm.ResultCreated', 'Created %d members'),
$result->CreatedCount()
);
if($result->UpdatedCount()) $msgArr[] = sprintf(
_t('MemberImportForm.ResultUpdated', 'Updated %d members'),
$result->UpdatedCount()
);
if($result->DeletedCount()) $msgArr[] = sprintf(
_t('MemberImportForm.ResultDeleted', 'Deleted %d members'),
$result->DeletedCount()
);
$msg = ($msgArr) ? implode(',', $msgArr) : _t('MemberImportForm.ResultNone', 'No changes');
$this->sessionMessage($msg, 'good');
$this->controller->redirectBack();
}
/**
* @param $group Group
*/
function setGroup($group) {
$this->group = $group;
}
/**
* @return Group
*/
function getGroup($group) {
return $this->group;
}
}
?>

View File

@ -1,553 +0,0 @@
<?php
/**
* Enhances {ComplexTableField} with the ability to list groups and given members.
* It is based around groups, so it deletes Members from a Group rather than from the entire system.
*
* In contrast to the original implementation, the URL-parameters "ParentClass" and "ParentID" are used
* to specify "Group" (hardcoded) and the GroupID-relation.
*
* @todo write a better description about what this field does.
*
* Returns either:
* - provided members
* - members of a provided group
* - all members
* - members based on a search-query
*
* @package cms
* @subpackage security
*/
class MemberTableField extends ComplexTableField {
protected $members;
protected $hidePassword;
protected $detailFormValidator;
protected $group;
protected $template = 'MemberTableField';
public $popupClass = 'MemberTableField_Popup';
public $itemClass = 'MemberTableField_Item';
static $data_class = 'Member';
/**
* Set the page size for this table.
* @var int
*/
public static $page_size = 20;
protected $permissions = array(
"add",
"edit",
"show",
"delete",
'inlineadd'
//"export",
);
/**
* Constructor method for MemberTableField.
*
* @param Controller $controller Controller class which created this field
* @param string $name Name of the field (e.g. "Members")
* @param mixed $group Can be the ID of a Group instance, or a Group instance itself
* @param DataObjectSet $members Optional set of Members to set as the source items for this field
* @param boolean $hidePassword Hide the password field or not in the summary?
*/
function __construct($controller, $name, $group = null, $members = null, $hidePassword = true) {
$sourceClass = self::$data_class;
$SNG_member = singleton($sourceClass);
$fieldList = $SNG_member->summaryFields();
$memberDbFields = $SNG_member->db();
$csvFieldList = array();
foreach($memberDbFields as $field => $dbFieldType) {
$csvFieldList[$field] = $field;
}
if($group) {
if(is_object($group)) {
$this->group = $group;
} elseif(is_numeric($group)) {
$this->group = DataObject::get_by_id('Group', $group);
}
} else if(isset($_REQUEST['ctf'][$this->Name()]["ID"]) && is_numeric($_REQUEST['ctf'][$this->Name()]["ID"])) {
$this->group = DataObject::get_by_id('Group', $_REQUEST['ctf'][$this->Name()]["ID"]);
}
if(!$hidePassword) {
$fieldList["SetPassword"] = "Password";
}
$this->hidePassword = $hidePassword;
// @todo shouldn't this use $this->group? It's unclear exactly
// what group it should be customising the custom Member set with.
if($members && $group) {
$this->setCustomSourceItems($this->memberListWithGroupID($members, $group));
}
parent::__construct($controller, $name, $sourceClass, $fieldList);
$SQL_search = isset($_REQUEST['MemberSearch']) ? Convert::raw2sql($_REQUEST['MemberSearch']) : null;
if(!empty($_REQUEST['MemberSearch'])) {
$searchFilters = array();
foreach($SNG_member->searchableFields() as $fieldName => $fieldSpec) {
if(strpos($fieldName, '.') === false) $searchFilters[] = "\"$fieldName\" LIKE '%{$SQL_search}%'";
}
$this->sourceFilter[] = '(' . implode(' OR ', $searchFilters) . ')';
}
if($this->group) {
$groupIDs = array($this->group->ID);
if($this->group->AllChildren()) $groupIDs = array_merge($groupIDs, $this->group->AllChildren()->column('ID'));
$this->sourceFilter[] = sprintf(
'"Group_Members"."GroupID" IN (%s)',
implode(',', $groupIDs)
);
}
$this->sourceJoin = " INNER JOIN \"Group_Members\" ON \"MemberID\"=\"Member\".\"ID\"";
$this->setFieldListCsv($csvFieldList);
$this->setPageSize($this->stat('page_size'));
}
function FieldHolder() {
$ret = parent::FieldHolder();
Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/scriptaculous/controls.js");
Requirements::javascript(CMS_DIR . '/javascript/MemberTableField.js');
Requirements::javascript(CMS_DIR . "/javascript/MemberTableField_popup.js");
return $ret;
}
function sourceID() {
return ($this->group) ? $this->group->ID : 0;
}
function AddLink() {
return Controller::join_links($this->Link(), 'add');
}
function SearchForm() {
$groupID = (isset($this->group)) ? $this->group->ID : 0;
$query = isset($_GET['MemberSearch']) ? $_GET['MemberSearch'] : null;
$searchFields = new FieldGroup(
new TextField('MemberSearch', _t('MemberTableField.SEARCH', 'Search'), $query),
new HiddenField("ctf[ID]", '', $groupID),
new HiddenField('MemberFieldName', '', $this->name),
new HiddenField('MemberDontShowPassword', '', $this->hidePassword)
);
$actionFields = new LiteralField('MemberFilterButton','<input type="submit" class="action" name="MemberFilterButton" value="'._t('MemberTableField.FILTER', 'Filter').'" id="MemberFilterButton"/>');
$fieldContainer = new FieldGroup(
$searchFields,
$actionFields
);
return $fieldContainer->FieldHolder();
}
/**
* Add existing member to group rather than creating a new member
*/
function addtogroup() {
// Protect against CSRF on destructive action
$token = $this->getForm()->getSecurityToken();
if(!$token->checkRequest($this->controller->getRequest())) return $this->httpError(400);
$data = $_REQUEST;
$groupID = (isset($data['ctf']['ID'])) ? $data['ctf']['ID'] : null;
if(!is_numeric($groupID)) {
FormResponse::status_messsage(_t('MemberTableField.ADDINGFIELD', 'Adding failed'), 'bad');
return;
}
// Get existing record either by ID or unique identifier.
$identifierField = Member::get_unique_identifier_field();
$className = self::$data_class;
$record = null;
if(isset($data[$identifierField])) {
$record = DataObject::get_one(
$className,
sprintf('"%s" = \'%s\'', $identifierField, $data[$identifierField])
);
if($record && !$record->canEdit()) return $this->httpError('401');
}
// Fall back to creating a new record
if(!$record) $record = new $className();
// Update an existing record, or populate a new one.
// If values on an existing (autocompleted) record have been changed,
// they will overwrite current data. We need to unset 'ID'
// record as it points to the group rather than the member record, and would
// cause the member to be written to a potentially existing record.
unset($data['ID']);
$record->update($data);
// Validate record, mainly password restrictions.
// Note: Doesn't use Member_Validator
$valid = $record->validate();
if($valid->valid()) {
$record->write();
$record->Groups()->add($groupID);
$this->sourceItems();
// TODO add javascript to highlight added row (problem: might not show up due to sorting/filtering)
FormResponse::update_dom_id($this->id(), $this->renderWith($this->template), true);
FormResponse::status_message(
_t(
'MemberTableField.ADDEDTOGROUP','Added member to group'
),
'good'
);
} else {
$message = sprintf(
_t(
'MemberTableField.ERRORADDINGUSER',
'There was an error adding the user to the group: %s'
),
Convert::raw2xml($valid->starredList())
);
FormResponse::status_message($message, 'bad');
}
return FormResponse::respond();
}
/**
* Custom delete implementation:
* Remove member from group rather than from the database
*/
function delete() {
// Protect against CSRF on destructive action
$token = $this->getForm()->getSecurityToken();
// TODO Not sure how this is called, using $_REQUEST to be on the safe side
if(!$token->check($_REQUEST['SecurityID'])) return $this->httpError(400);
$groupID = Convert::raw2sql($_REQUEST['ctf']['ID']);
$memberID = Convert::raw2sql($_REQUEST['ctf']['childID']);
if(is_numeric($groupID) && is_numeric($memberID)) {
$member = DataObject::get_by_id('Member', $memberID);
$member->Groups()->remove($groupID);
} else {
user_error("MemberTableField::delete: Bad parameters: Group=$groupID, Member=$memberID", E_USER_ERROR);
}
return FormResponse::respond();
}
/**
* #################################
* Utility Functions
* #################################
*/
function getParentClass() {
return 'Group';
}
function getParentIdName($childClass, $parentClass) {
return 'GroupID';
}
/**
* #################################
* Custom Functions
* #################################
*/
/**
* Customise an existing DataObjectSet of Member
* objects with a GroupID.
*
* @param DataObjectSet $members Set of Member objects to customise
* @param Group $group Group object to customise with
* @return DataObjectSet Customised set of Member objects
*/
function memberListWithGroupID($members, $group) {
$newMembers = new DataObjectSet();
foreach($members as $member) {
$newMembers->push($member->customise(array('GroupID' => $group->ID)));
}
return $newMembers;
}
function setGroup($group) {
$this->group = $group;
}
/**
* @return Group
*/
function getGroup() {
return $this->group;
}
function setController($controller) {
$this->controller = $controller;
}
function GetControllerName() {
return $this->controller->class;
}
/**
* Add existing member to group by name (with JS-autocompletion)
*/
function AddRecordForm() {
$fields = new FieldSet();
foreach($this->FieldList() as $fieldName => $fieldTitle) {
// If we're adding the set password field, we want to hide the text from any peeping eyes
if($fieldName == 'SetPassword') {
$fields->push(new PasswordField($fieldName));
} else {
$fields->push(new TextField($fieldName));
}
}
if($this->group) {
$fields->push(new HiddenField('ctf[ID]', null, $this->group->ID));
}
$actions = new FieldSet(
new FormAction('addtogroup', _t('MemberTableField.ADD','Add'))
);
return new TabularStyle(
new NestedForm(
new Form(
$this,
'AddRecordForm',
$fields,
$actions
)
)
);
}
function AddForm() {
$form = parent::AddForm();
// Set default groups - also implemented in MemberTableField_Popup::__construct()
if($this->group) {
$groupsField = $form->Fields()->dataFieldByName('Groups');
// TODO Needs to be a string value (not int) because of TreeMultiselectField->getItems(),
// see http://open.silverstripe.org/ticket/5836
if($groupsField) $groupsField->setValue((string)$this->group->ID);
}
return $form;
}
/**
* Same behaviour as parent class, but adds the
* member to the passed GroupID.
*
* @return string
*/
function saveComplexTableField($data, $form, $params) {
$className = $this->sourceClass();
$childData = new $className();
// Needs to write before saveInto() to ensure the 'Groups' TreeMultiselectField saves
$childData->write();
try {
$form->saveInto($childData);
$childData->write();
} catch(ValidationException $e) {
$form->sessionMessage($e->getResult()->message(), 'bad');
return Director::redirectBack();
}
$closeLink = sprintf(
'<small><a href="' . $_SERVER['HTTP_REFERER'] . '" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>',
_t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
);
$message = sprintf(
_t('ComplexTableField.SUCCESSADD', 'Added %s %s %s'),
$childData->singular_name(),
'<a href="' . $this->Link() . '">' . htmlspecialchars($childData->Title, ENT_QUOTES, 'UTF-8') . '</a>',
$closeLink
);
$form->sessionMessage($message, 'good');
$this->controller->redirectBack();
}
/**
* Cached version for getting the appropraite members for this particular group.
*
* This includes getting inherited groups, such as groups under groups.
*/
function sourceItems() {
// Caching.
if($this->sourceItems) {
return $this->sourceItems;
}
// Setup limits
$limitClause = '';
if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
$limitClause = ($_REQUEST['ctf'][$this->Name()]['start']) . ", {$this->pageSize}";
} else {
$limitClause = "0, {$this->pageSize}";
}
// We use the group to get the members, as they already have the bulk of the look up functions
$start = isset($_REQUEST['ctf'][$this->Name()]['start']) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0;
$this->sourceItems = false;
if($this->group) {
$this->sourceItems = $this->group->Members(
$this->pageSize, // limit
$start, // offset
$this->sourceFilter,
$this->sourceSort
);
} else {
$this->sourceItems = DataObject::get(self::$data_class,
$this->sourceFilter,
$this->sourceSort,
null,
array('limit' => $this->pageSize, 'start' => $start)
);
}
// Because we are not used $this->upagedSourceItems any more, and the DataObjectSet is usually the source
// that a large member set runs out of memory. we disable it here.
//$this->unpagedSourceItems = $this->group->Members('', '', $this->sourceFilter, $this->sourceSort);
$this->totalCount = ($this->sourceItems) ? $this->sourceItems->TotalItems() : 0;
return $this->sourceItems;
}
function TotalCount() {
$this->sourceItems(); // Called for its side-effect of setting total count
return $this->totalCount;
}
/**
* Handles item requests
* MemberTableField needs its own item request class so that it can overload the delete method
*/
function handleItem($request) {
return new MemberTableField_ItemRequest($this, $request->param('ID'));
}
}
/**
* Popup window for {@link MemberTableField}.
* @package cms
* @subpackage security
*/
class MemberTableField_Popup extends ComplexTableField_Popup {
function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) {
$group = ($controller instanceof MemberTableField) ? $controller->getGroup() : $controller->getParent()->getGroup();
// Set default groups - also implemented in AddForm()
if($group) {
$groupsField = $fields->dataFieldByName('Groups');
if($groupsField) $groupsField->setValue($group->ID);
}
parent::__construct($controller, $name, $fields, $validator, $readonly, $dataObject);
}
function forTemplate() {
$ret = parent::forTemplate();
Requirements::css(CMS_DIR . '/css/SecurityAdmin.css');
Requirements::javascript(CMS_DIR . '/javascript/MemberTableField.js');
Requirements::javascript(CMS_DIR . '/javascript/MemberTableField_popup.js');
return $ret;
}
}
/**
* @package cms
* @subpackage security
*/
class MemberTableField_Item extends ComplexTableField_Item {
function Actions() {
$actions = parent::Actions();
foreach($actions as $action) {
if($action->Name == 'delete') {
if($this->parent->getGroup()) {
$action->TitleText = _t('MemberTableField.DeleteTitleText',
'Delete from this group',
PR_MEDIUM,
'Delete button hover text'
);
} else {
$action->TitleText = _t('MemberTableField.DeleteTitleTextDatabase',
'Delete from database and all groups',
PR_MEDIUM,
'Delete button hover text'
);
}
}
}
return $actions;
}
}
/**
* @package cms
* @subpackage security
*/
class MemberTableField_ItemRequest extends ComplexTableField_ItemRequest {
/**
* Deleting an item from a member table field should just remove that member from the group
*/
function delete($request) {
// Protect against CSRF on destructive action
$token = $this->ctf->getForm()->getSecurityToken();
if(!$token->checkRequest($request)) return $this->httpError('400');
if($this->ctf->Can('delete') !== true) {
return false;
}
// if a group limitation is set on the table, remove relation.
// otherwise remove the record from the database
if($this->ctf->getGroup()) {
$groupID = $this->ctf->sourceID();
$group = DataObject::get_by_id('Group', $groupID);
// Remove from group and all child groups
foreach($group->getAllChildren() as $subGroup) {
$this->dataObj()->Groups()->remove($subGroup);
}
$this->dataObj()->Groups()->remove($groupID);
} else {
$this->dataObj()->delete();
}
}
function getParent() {
return $this->ctf;
}
}
?>

View File

@ -1,1055 +0,0 @@
<?php
/**
* Generates a three-pane UI for editing model classes,
* with an automatically generated search panel, tabular results
* and edit forms.
* Relies on data such as {@link DataObject::$db} and {@DataObject::getCMSFields()}
* to scaffold interfaces "out of the box", while at the same time providing
* flexibility to customize the default output.
*
* Add a route (note - this doc is not currently in sync with the code, need to update)
* <code>
* Director::addRules(50, array('admin/mymodel/$Class/$Action/$ID' => 'MyModelAdmin'));
* </code>
*
* @todo saving logic (should mostly use Form->saveInto() and iterate over relations)
* @todo ajax form loading and saving
* @todo ajax result display
* @todo relation formfield scaffolding (one tab per relation) - relations don't have DBField sublclasses, we do
* we define the scaffold defaults. can be ComplexTableField instances for a start.
* @todo has_many/many_many relation autocomplete field (HasManyComplexTableField doesn't work well with larger datasets)
*
* Long term TODOs:
* @todo Hook into RESTful interface on DataObjects (yet to be developed)
* @todo Permission control via datamodel and Form class
*
* @uses SearchContext
*
* @package cms
* @subpackage core
*/
abstract class ModelAdmin extends LeftAndMain {
static $url_rule = '/$Action';
/**
* List of all managed {@link DataObject}s in this interface.
*
* Simple notation with class names only:
* <code>
* array('MyObjectClass','MyOtherObjectClass')
* </code>
*
* Extended notation with options (e.g. custom titles):
* <code>
* array(
* 'MyObjectClass' => array('title' => "Custom title")
* )
* </code>
*
* Available options:
* - 'title': Set custom titles for the tabs or dropdown names
* - 'collection_controller': Set a custom class to use as a collection controller for this model
* - 'record_controller': Set a custom class to use as a record controller for this model
*
* @var array|string
*/
public static $managed_models = null;
/**
* More actions are dynamically added in {@link defineMethods()} below.
*/
public static $allowed_actions = array(
'add',
'edit',
'delete',
'import',
'renderimportform',
'handleList',
'handleItem',
'ImportForm'
);
/**
* @param string $collection_controller_class Override for controller class
*/
public static $collection_controller_class = "ModelAdmin_CollectionController";
/**
* @param string $collection_controller_class Override for controller class
*/
public static $record_controller_class = "ModelAdmin_RecordController";
/**
* Forward control to the default action handler
*/
public static $url_handlers = array(
'$Action' => 'handleAction'
);
/**
* Model object currently in manipulation queue. Used for updating Link to point
* to the correct generic data object in generated URLs.
*
* @var string
*/
private $currentModel = false;
/**
* Change this variable if you don't want the Import from CSV form to appear.
* This variable can be a boolean or an array.
* If array, you can list className you want the form to appear on. i.e. array('myClassOne','myClasstwo')
*/
public $showImportForm = true;
/**
* List of all {@link DataObject}s which can be imported through
* a subclass of {@link BulkLoader} (mostly CSV data).
* By default {@link CsvBulkLoader} is used, assuming a standard mapping
* of column names to {@link DataObject} properties/relations.
*
* e.g. "BlogEntry" => "BlogEntryCsvBulkLoader"
*
* @var array
*/
public static $model_importers = null;
/**
* Amount of results showing on a single page.
*
* @var int
*/
public static $page_length = 30;
/**
* Class name of the form field used for the results list. Overloading this in subclasses
* can let you customise the results table field.
*/
protected $resultsTableClassName = 'TableListField';
/**
* Return {@link $this->resultsTableClassName}
*/
public function resultsTableClassName() {
return $this->resultsTableClassName;
}
/**
* Initialize the model admin interface. Sets up embedded jquery libraries and requisite plugins.
*
* @todo remove reliance on urlParams
*/
public function init() {
parent::init();
// security check for valid models
if(isset($this->urlParams['Action']) && !in_array($this->urlParams['Action'], $this->getManagedModels())) {
//user_error('ModelAdmin::init(): Invalid Model class', E_USER_ERROR);
}
Requirements::css(CMS_DIR . '/css/ModelAdmin.css'); // standard layout formatting for management UI
Requirements::css(CMS_DIR . '/css/silverstripe.tabs.css'); // follows the jQuery UI theme conventions
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery/jquery.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-livequery/jquery.livequery.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/jquery-ui.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery/jquery_improvements.js');
Requirements::javascript(CMS_DIR . '/javascript/ModelAdmin.js');
Requirements::javascript(CMS_DIR . '/javascript/ModelAdmin.History.js');
}
/**
* overwrite the static page_length of the admin panel,
* should be called in the project _config file.
*/
static function set_page_length($length){
self::$page_length = $length;
}
/**
* Return the static page_length of the admin, default as 30
*/
static function get_page_length(){
return self::$page_length;
}
/**
* Return the class name of the collection controller
*
* @param string $model model name to get the controller for
* @return string the collection controller class
*/
function getCollectionControllerClass($model) {
$models = $this->getManagedModels();
if(isset($models[$model]['collection_controller'])) {
$class = $models[$model]['collection_controller'];
} else {
$class = $this->stat('collection_controller_class');
}
return $class;
}
/**
* Return the class name of the record controller
*
* @param string $model model name to get the controller for
* @return string the record controller class
*/
function getRecordControllerClass($model) {
$models = $this->getManagedModels();
if(isset($models[$model]['record_controller'])) {
$class = $models[$model]['record_controller'];
} else {
$class = $this->stat('record_controller_class');
}
return $class;
}
/**
* Add mappings for generic form constructors to automatically delegate to a scaffolded form object.
*/
function defineMethods() {
parent::defineMethods();
foreach($this->getManagedModels() as $class => $options) {
if(is_numeric($class)) $class = $options;
$this->addWrapperMethod($class, 'bindModelController');
self::$allowed_actions[] = $class;
}
}
/**
* Base scaffolding method for returning a generic model instance.
*/
public function bindModelController($model, $request = null) {
$class = $this->getCollectionControllerClass($model);
return new $class($this, $model);
}
/**
* This method can be overloaded to specify the UI by which the search class is chosen.
*
* It can create a tab strip or a dropdown. The dropdown is useful when there are a large number of classes.
* By default, it will show a tabs for 1-3 classes, and a dropdown for 4 or more classes.
*
* @return String: 'tabs' or 'dropdown'
*/
public function SearchClassSelector() {
return sizeof($this->getManagedModels()) > 3 ? 'dropdown' : 'tabs';
}
/**
* Returns managed models' create, search, and import forms
* @uses SearchContext
* @uses SearchFilter
* @return DataObjectSet of forms
*/
protected function getModelForms() {
$models = $this->getManagedModels();
$forms = new DataObjectSet();
foreach($models as $class => $options) {
if(is_numeric($class)) $class = $options;
$forms->push(new ArrayData(array (
'Title' => (is_array($options) && isset($options['title'])) ? $options['title'] : singleton($class)->i18n_singular_name(),
'ClassName' => $class,
'Content' => $this->$class()->getModelSidebar()
)));
}
return $forms;
}
/**
* @return array
*/
function getManagedModels() {
$models = $this->stat('managed_models');
if(is_string($models)) {
$models = array($models);
}
if(!count($models)) {
user_error(
'ModelAdmin::getManagedModels():
You need to specify at least one DataObject subclass in public static $managed_models.
Make sure that this property is defined, and that its visibility is set to "public"',
E_USER_ERROR
);
}
return $models;
}
/**
* Returns all importers defined in {@link self::$model_importers}.
* If none are defined, we fall back to {@link self::managed_models}
* with a default {@link CsvBulkLoader} class. In this case the column names of the first row
* in the CSV file are assumed to have direct mappings to properties on the object.
*
* @return array
*/
function getModelImporters() {
$importers = $this->stat('model_importers');
// fallback to all defined models if not explicitly defined
if(is_null($importers)) {
$models = $this->getManagedModels();
foreach($models as $modelName => $options) {
if(is_numeric($modelName)) $modelName = $options;
$importers[$modelName] = 'CsvBulkLoader';
}
}
return $importers;
}
}
/**
* Handles a managed model class and provides default collection filtering behavior.
*
* @package cms
* @subpackage core
*/
class ModelAdmin_CollectionController extends Controller {
public $parentController;
protected $modelClass;
public $showImportForm = null;
static $url_handlers = array(
'$Action' => 'handleActionOrID'
);
function __construct($parent, $model) {
$this->parentController = $parent;
$this->modelClass = $model;
parent::__construct();
}
/**
* Appends the model class to the URL.
*
* @param string $action
* @return string
*/
function Link($action = null) {
return $this->parentController->Link(Controller::join_links($this->modelClass, $action));
}
/**
* Return the class name of the model being managed.
*
* @return unknown
*/
function getModelClass() {
return $this->modelClass;
}
/**
* Delegate to different control flow, depending on whether the
* URL parameter is a number (record id) or string (action).
*
* @param unknown_type $request
* @return unknown
*/
function handleActionOrID($request) {
if (is_numeric($request->param('Action'))) {
return $this->handleID($request);
} else {
return $this->handleAction($request);
}
}
/**
* Delegate to the RecordController if a valid numeric ID appears in the URL
* segment.
*
* @param SS_HTTPRequest $request
* @return RecordController
*/
public function handleID($request) {
$class = $this->parentController->getRecordControllerClass($this->getModelClass());
return new $class($this, $request);
}
// -----------------------------------------------------------------------------------------------------------------
/**
* Get a combination of the Search, Import and Create forms that can be inserted into a {@link ModelAdmin} sidebar.
*
* @return string
*/
public function getModelSidebar() {
return $this->renderWith('ModelSidebar');
}
/**
* Get a search form for a single {@link DataObject} subclass.
*
* @return Form
*/
public function SearchForm() {
$context = singleton($this->modelClass)->getDefaultSearchContext();
$fields = $context->getSearchFields();
$columnSelectionField = $this->ColumnSelectionField();
$fields->push($columnSelectionField);
$validator = new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$form = new Form($this, "SearchForm",
$fields,
new FieldSet(
new FormAction('search', _t('MemberTableField.SEARCH', 'Search')),
$clearAction = new ResetFormAction('clearsearch', _t('ModelAdmin.CLEAR_SEARCH','Clear Search'))
),
$validator
);
//$form->setFormAction(Controller::join_links($this->Link(), "search"));
$form->setFormMethod('get');
$form->setHTMLID("Form_SearchForm_" . $this->modelClass);
$form->disableSecurityToken();
$clearAction->useButtonTag = true;
$clearAction->addExtraClass('minorAction');
return $form;
}
/**
* Create a form that consists of one button
* that directs to a give model's Add form
*/
public function CreateForm() {
$modelName = $this->modelClass;
if($this->hasMethod('alternatePermissionCheck')) {
if(!$this->alternatePermissionCheck()) return false;
} else {
if(!singleton($modelName)->canCreate(Member::currentUser())) return false;
}
$buttonLabel = sprintf(_t('ModelAdmin.CREATEBUTTON', "Create '%s'", PR_MEDIUM, "Create a new instance from a model class"), singleton($modelName)->i18n_singular_name());
$form = new Form($this, "CreateForm",
new FieldSet(),
new FieldSet($createButton = new FormAction('add', $buttonLabel)),
$validator = new RequiredFields()
);
$createButton->dontEscape = true;
$validator->setJavascriptValidationHandler('none');
$form->setHTMLID("Form_CreateForm_" . $this->modelClass);
return $form;
}
/**
* Checks if a CSV import form should be generated by a className criteria or in general for ModelAdmin.
*/
function showImportForm() {
if($this->showImportForm === null) return $this->parentController->showImportForm;
else return $this->showImportForm;
}
/**
* Generate a CSV import form for a single {@link DataObject} subclass.
*
* @return Form
*/
public function ImportForm() {
$modelName = $this->modelClass;
// check if a import form should be generated
if(!$this->showImportForm() || (is_array($this->showImportForm()) && !in_array($modelName,$this->showImportForm()))) return false;
$importers = $this->parentController->getModelImporters();
if(!$importers || !isset($importers[$modelName])) return false;
if(!singleton($modelName)->canCreate(Member::currentUser())) return false;
$fields = new FieldSet(
new HiddenField('ClassName', _t('ModelAdmin.CLASSTYPE'), $modelName),
new FileField('_CsvFile', false)
);
// get HTML specification for each import (column names etc.)
$importerClass = $importers[$modelName];
$importer = new $importerClass($modelName);
$spec = $importer->getImportSpec();
$specFields = new DataObjectSet();
foreach($spec['fields'] as $name => $desc) {
$specFields->push(new ArrayData(array('Name' => $name, 'Description' => $desc)));
}
$specRelations = new DataObjectSet();
foreach($spec['relations'] as $name => $desc) {
$specRelations->push(new ArrayData(array('Name' => $name, 'Description' => $desc)));
}
$specHTML = $this->customise(array(
'ModelName' => Convert::raw2att($modelName),
'Fields' => $specFields,
'Relations' => $specRelations,
))->renderWith('ModelAdmin_ImportSpec');
$fields->push(new LiteralField("SpecFor{$modelName}", $specHTML));
$fields->push(new CheckboxField('EmptyBeforeImport', 'Clear Database before import', false));
$actions = new FieldSet(
new FormAction('import', _t('ModelAdmin.IMPORT', 'Import from CSV'))
);
$validator = new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$form = new Form(
$this,
"ImportForm",
$fields,
$actions,
$validator
);
$form->setHTMLID("Form_ImportForm_" . $this->modelClass);
return $form;
}
/**
* Imports the submitted CSV file based on specifications given in
* {@link self::model_importers}.
* Redirects back with a success/failure message.
*
* @todo Figure out ajax submission of files via jQuery.form plugin
*
* @param array $data
* @param Form $form
* @param SS_HTTPRequest $request
*/
function import($data, $form, $request) {
$modelName = $data['ClassName'];
if(!$this->showImportForm() || (is_array($this->showImportForm()) && !in_array($modelName,$this->showImportForm()))) return false;
$importers = $this->parentController->getModelImporters();
$importerClass = $importers[$modelName];
$loader = new $importerClass($data['ClassName']);
// File wasn't properly uploaded, show a reminder to the user
if(
empty($_FILES['_CsvFile']['tmp_name']) ||
file_get_contents($_FILES['_CsvFile']['tmp_name']) == ''
) {
$form->sessionMessage(_t('ModelAdmin.NOCSVFILE', 'Please browse for a CSV file to import'), 'good');
$this->redirectBack();
return false;
}
if (!empty($data['EmptyBeforeImport']) && $data['EmptyBeforeImport']) { //clear database before import
$loader->deleteExistingRecords = true;
}
$results = $loader->load($_FILES['_CsvFile']['tmp_name']);
$message = '';
if($results->CreatedCount()) $message .= sprintf(
_t('ModelAdmin.IMPORTEDRECORDS', "Imported %s records."),
$results->CreatedCount()
);
if($results->UpdatedCount()) $message .= sprintf(
_t('ModelAdmin.UPDATEDRECORDS', "Updated %s records."),
$results->UpdatedCount()
);
if($results->DeletedCount()) $message .= sprintf(
_t('ModelAdmin.DELETEDRECORDS', "Deleted %s records."),
$results->DeletedCount()
);
if(!$results->CreatedCount() && !$results->UpdatedCount()) $message .= _t('ModelAdmin.NOIMPORT', "Nothing to import");
$form->sessionMessage($message, 'good');
$this->redirectBack();
}
/**
* Return the columns available in the column selection field.
* Overload this to make other columns available
*/
public function columnsAvailable() {
return singleton($this->modelClass)->summaryFields();
}
/**
* Return the columns selected by default in the column selection field.
* Overload this to make other columns selected by default
*/
public function columnsSelectedByDefault() {
return array_keys(singleton($this->modelClass)->summaryFields());
}
/**
* Give the flexibilility to show variouse combination of columns in the search result table
*/
public function ColumnSelectionField() {
$model = singleton($this->modelClass);
$source = $this->columnsAvailable();
// select all fields by default
$value = $this->columnsSelectedByDefault();
// Reorder the source so that you read items down the column and then across
$columnisedSource = array();
$keys = array_keys($source);
$midPoint = ceil(sizeof($source)/2);
for($i=0;$i<$midPoint;$i++) {
$key1 = $keys[$i];
$columnisedSource[$key1] = $model->fieldLabel($source[$key1]);
// If there are an odd number of items, the last item will be unset
if(isset($keys[$i+$midPoint])) {
$key2 = $keys[$i+$midPoint];
$columnisedSource[$key2] = $model->fieldLabel($source[$key2]);
}
}
$checkboxes = new CheckboxSetField("ResultAssembly", false, $columnisedSource, $value);
$field = new CompositeField(
new LiteralField(
"ToggleResultAssemblyLink",
sprintf("<a class=\"form_frontend_function toggle_result_assembly\" href=\"#\">%s</a>",
_t('ModelAdmin.CHOOSE_COLUMNS', 'Select result columns...')
)
),
$checkboxesBlock = new CompositeField(
$checkboxes,
new LiteralField("ClearDiv", "<div class=\"clear\"></div>"),
new LiteralField(
"TickAllAssemblyLink",
sprintf(
"<a class=\"form_frontend_function tick_all_result_assembly\" href=\"#\">%s</a>",
_t('ModelAdmin.SELECTALL', 'select all')
)
),
new LiteralField(
"UntickAllAssemblyLink",
sprintf(
"<a class=\"form_frontend_function untick_all_result_assembly\" href=\"#\">%s</a>",
_t('ModelAdmin.SELECTNONE', 'select none')
)
)
)
);
$field->addExtraClass("ResultAssemblyBlock");
$checkboxesBlock->addExtraClass("hidden");
return $field;
}
/**
* Action to render a data object collection, using the model context to provide filters
* and paging.
*
* @return string
*/
function search($request, $form) {
// Get the results form to be rendered
$resultsForm = $this->ResultsForm(array_merge($form->getData(), $request));
// Before rendering, let's get the total number of results returned
$tableField = $resultsForm->Fields()->dataFieldByName($this->modelClass);
$tableField->addExtraClass('resultsTable');
$numResults = $tableField->TotalCount();
if($numResults) {
$msg = sprintf(
_t('ModelAdmin.FOUNDRESULTS',"Your search found %s matching items"),
$numResults
);
} else {
$msg = _t('ModelAdmin.NORESULTS',"Your search didn't return any matching items");
}
return new SS_HTTPResponse(
$resultsForm->formHtmlContent(),
200,
$msg
);
}
/**
* Gets the search query generated on the SearchContext from
* {@link DataObject::getDefaultSearchContext()},
* and the current GET parameters on the request.
*
* @return SQLQuery
*/
function getSearchQuery($searchCriteria) {
$context = singleton($this->modelClass)->getDefaultSearchContext();
return $context->getQuery($searchCriteria);
}
/**
* Returns all columns used for tabular search results display.
* Defaults to all fields specified in {@link DataObject->summaryFields()}.
*
* @param array $searchCriteria Limit fields by populating the 'ResultsAssembly' key
* @param boolean $selectedOnly Limit by 'ResultsAssempty
*/
function getResultColumns($searchCriteria, $selectedOnly = true) {
$model = singleton($this->modelClass);
$summaryFields = $this->columnsAvailable();
if($selectedOnly && isset($searchCriteria['ResultAssembly'])) {
$resultAssembly = $searchCriteria['ResultAssembly'];
if(!is_array($resultAssembly)) {
$explodedAssembly = split(' *, *', $resultAssembly);
$resultAssembly = array();
foreach($explodedAssembly as $item) $resultAssembly[$item] = true;
}
return array_intersect_key($summaryFields, $resultAssembly);
} else {
return $summaryFields;
}
}
/**
* Creates and returns the result table field for resultsForm.
* Uses {@link resultsTableClassName()} to initialise the formfield.
* Method is called from {@link ResultsForm}.
*
* @param array $searchCriteria passed through from ResultsForm
*
* @return TableListField
*/
function getResultsTable($searchCriteria) {
$summaryFields = $this->getResultColumns($searchCriteria);
$className = $this->parentController->resultsTableClassName();
$tf = new $className(
$this->modelClass,
$this->modelClass,
$summaryFields
);
$tf->setCustomQuery($this->getSearchQuery($searchCriteria));
$tf->setPageSize($this->parentController->stat('page_length'));
$tf->setShowPagination(true);
// @todo Remove records that can't be viewed by the current user
$tf->setPermissions(array_merge(array('view','export'), TableListField::permissions_for_object($this->modelClass)));
// csv export settings (select all columns regardless of user checkbox settings in 'ResultsAssembly')
$exportFields = $this->getResultColumns($searchCriteria, false);
$tf->setFieldListCsv($exportFields);
$url = '<a href=\"' . $this->Link() . '/$ID/edit\">$value</a>';
if(count($summaryFields)) {
$tf->setFieldFormatting(array_combine(
array_keys($summaryFields),
array_fill(0,count($summaryFields), $url)
));
}
return $tf;
}
/**
* Shows results from the "search" action in a TableListField.
*
* @uses getResultsTable()
*
* @return Form
*/
function ResultsForm($searchCriteria) {
if($searchCriteria instanceof SS_HTTPRequest) $searchCriteria = $searchCriteria->getVars();
$tf = $this->getResultsTable($searchCriteria);
// implemented as a form to enable further actions on the resultset
// (serverside sorting, export as CSV, etc)
$form = new Form(
$this,
'ResultsForm',
new FieldSet(
new TabSet('Root',
new Tab('SearchResults',
_t('ModelAdmin.SEARCHRESULTS','Search Results'),
$tf
)
)
),
new FieldSet()
);
// Include the search criteria on the results form URL, but not dodgy variables like those below
$filteredCriteria = $searchCriteria;
unset($filteredCriteria['ctf']);
unset($filteredCriteria['url']);
unset($filteredCriteria['action_search']);
$form->setFormAction($this->Link() . '/ResultsForm?' . http_build_query($filteredCriteria));
return $form;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Create a new model record.
*
* @param unknown_type $request
* @return unknown
*/
function add($request) {
return new SS_HTTPResponse(
$this->AddForm()->formHtmlContent(),
200,
sprintf(
_t('ModelAdmin.ADDFORM', "Fill out this form to add a %s to the database."),
$this->modelClass
)
);
}
/**
* Returns a form suitable for adding a new model, falling back on the default edit form.
*
* Caution: The add-form shows a DataObject's {@link DataObject->getCMSFields()} method on a record
* that doesn't exist in the database yet, hence has no ID. This means the {@link DataObject->getCMSFields()}
* implementation has to ensure that no fields are added which would rely on a
* record ID being present, e.g. {@link HasManyComplexTableField}.
*
* Example:
* <code>
* function getCMSFields() {
* $fields = parent::getCMSFields();
* if($this->exists()) {
* $ctf = new HasManyComplexTableField($this, 'MyRelations', 'MyRelation');
* $fields->addFieldToTab('Root.Main', $ctf);
* }
* return $fields;
* }
* </code>
*
* @return Form
*/
public function AddForm() {
$newRecord = new $this->modelClass();
if($newRecord->canCreate()){
if($newRecord->hasMethod('getCMSAddFormFields')) {
$fields = $newRecord->getCMSAddFormFields();
} else {
$fields = $newRecord->getCMSFields();
}
$validator = ($newRecord->hasMethod('getCMSValidator')) ? $newRecord->getCMSValidator() : null;
if(!$validator) $validator = new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$actions = new FieldSet (
new FormAction("doCreate", _t('ModelAdmin.ADDBUTTON', "Add"))
);
$form = new Form($this, "AddForm", $fields, $actions, $validator);
$form->loadDataFrom($newRecord);
return $form;
}
}
function doCreate($data, $form, $request) {
$className = $this->getModelClass();
$model = new $className();
// We write before saveInto, since this will let us save has-many and many-many relationships :-)
$model->write();
$form->saveInto($model);
$model->write();
if($this->isAjax()) {
$class = $this->parentController->getRecordControllerClass($this->getModelClass());
$recordController = new $class($this, $request, $model->ID);
return new SS_HTTPResponse(
$recordController->EditForm()->formHtmlContent(),
200,
sprintf(
_t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."),
$model->Title
)
);
} else {
Director::redirect(Controller::join_links($this->Link(), $model->ID , 'edit'));
}
}
}
/**
* Handles operations on a single record from a managed model.
*
* @package cms
* @subpackage core
* @todo change the parent controller varname to indicate the model scaffolding functionality in ModelAdmin
*/
class ModelAdmin_RecordController extends Controller {
protected $parentController;
protected $currentRecord;
static $allowed_actions = array('edit', 'view', 'EditForm', 'ViewForm');
function __construct($parentController, $request, $recordID = null) {
$this->parentController = $parentController;
$modelName = $parentController->getModelClass();
$recordID = ($recordID) ? $recordID : $request->param('Action');
$this->currentRecord = DataObject::get_by_id($modelName, $recordID);
parent::__construct();
}
/**
* Link fragment - appends the current record ID to the URL.
*/
public function Link($action = null) {
return $this->parentController->Link(Controller::join_links($this->currentRecord->ID, $action));
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Edit action - shows a form for editing this record
*/
function edit($request) {
if ($this->currentRecord) {
if($this->isAjax()) {
$this->response->setBody($this->EditForm()->formHtmlContent());
$this->response->setStatusCode(
200,
sprintf(
_t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."),
$this->currentRecord->Title
)
);
return $this->response;
} else {
// This is really quite ugly; to fix will require a change in the way that customise() works. :-(
return $this->parentController->parentController->customise(array(
'Right' => $this->parentController->parentController->customise(array(
'EditForm' => $this->EditForm()
))->renderWith(array("{$this->class}_right",'LeftAndMain_right'))
))->renderWith(array('ModelAdmin','LeftAndMain'));
}
} else {
return _t('ModelAdmin.ITEMNOTFOUND', "I can't find that item");
}
}
/**
* Returns a form for editing the attached model
*/
public function EditForm() {
$fields = $this->currentRecord->getCMSFields();
$fields->push(new HiddenField("ID"));
$validator = ($this->currentRecord->hasMethod('getCMSValidator')) ? $this->currentRecord->getCMSValidator() : new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$actions = $this->currentRecord->getCMSActions();
if($this->currentRecord->canEdit(Member::currentUser())){
if(!$actions->fieldByName('action_doSave') && !$actions->fieldByName('action_save')) {
$actions->push(new FormAction("doSave", _t('ModelAdmin.SAVE', "Save")));
}
}else{
$fields = $fields->makeReadonly();
}
if($this->currentRecord->canDelete(Member::currentUser())) {
if(!$actions->fieldByName('action_doDelete')) {
$actions->insertFirst($deleteAction = new FormAction('doDelete', _t('ModelAdmin.DELETE', 'Delete')));
}
$deleteAction->addExtraClass('delete');
}
$form = new Form($this, "EditForm", $fields, $actions, $validator);
$form->loadDataFrom($this->currentRecord);
return $form;
}
/**
* Postback action to save a record
*
* @param array $data
* @param Form $form
* @param SS_HTTPRequest $request
* @return mixed
*/
function doSave($data, $form, $request) {
$form->saveInto($this->currentRecord);
try {
$this->currentRecord->write();
} catch(ValidationException $e) {
$form->sessionMessage($e->getResult()->message(), 'bad');
}
// Behaviour switched on .
if($this->parentController->isAjax()) {
return $this->edit($request);
} else {
$this->parentController->redirectBack();
}
}
/**
* Delete the current record
*/
public function doDelete($data, $form, $request) {
if($this->currentRecord->canDelete(Member::currentUser())) {
$this->currentRecord->delete();
Director::redirect($this->parentController->Link('SearchForm?action=search'));
} else {
$this->parentController->redirectBack();
}
return;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Renders the record view template.
*
* @param SS_HTTPRequest $request
* @return mixed
*/
function view($request) {
if($this->currentRecord) {
$form = $this->ViewForm();
return $form->formHtmlContent();
} else {
return _t('ModelAdmin.ITEMNOTFOUND');
}
}
/**
* Returns a form for viewing the attached model
*
* @return Form
*/
public function ViewForm() {
$fields = $this->currentRecord->getCMSFields();
$form = new Form($this, "EditForm", $fields, new FieldSet());
$form->loadDataFrom($this->currentRecord);
$form->makeReadonly();
return $form;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
function index() {
Director::redirect(Controller::join_links($this->Link(), 'edit'));
}
function getCurrentRecord(){
return $this->currentRecord;
}
}
?>

View File

@ -1,372 +0,0 @@
<?php
/**
* Security section of the CMS
* @package cms
* @subpackage security
*/
class SecurityAdmin extends LeftAndMain implements PermissionProvider {
static $url_segment = 'security';
static $url_rule = '/$Action/$ID/$OtherID';
static $menu_title = 'Security';
static $tree_class = 'Group';
static $subitem_class = 'Member';
static $allowed_actions = array(
'autocomplete',
'removememberfromgroup',
'AddRecordForm',
'EditForm',
'MemberImportForm',
'memberimport',
'GroupImportForm',
'groupimport',
'RootForm'
);
/**
* @var Array
*/
static $hidden_permissions = array();
public function init() {
parent::init();
Requirements::javascript(CMS_DIR . '/javascript/SecurityAdmin.js');
CMSBatchActionHandler::register('delete', 'SecurityAdmin_DeleteBatchAction', 'Group');
}
function getEditForm($id = null) {
// TODO Duplicate record fetching (see parent implementation)
if(!$id) $id = $this->currentPageID();
$form = parent::getEditForm($id);
// TODO Duplicate record fetching (see parent implementation)
$record = $this->getRecord($id);
if($record && !$record->canView()) return Security::permissionFailure($this);
if($id && is_numeric($id)) {
$form = parent::getEditForm($id);
if(!$form) return false;
$fields = $form->Fields();
if($fields->hasTabSet() && $record->canEdit()) {
$fields->findOrMakeTab('Root.Import',_t('Group.IMPORTTABTITLE', 'Import'));
$fields->addFieldToTab('Root.Import',
new LiteralField(
'MemberImportFormIframe',
sprintf(
'<iframe src="%s" id="MemberImportFormIframe" width="100%%" height="400px" border="0"></iframe>',
$this->Link('memberimport')
)
)
);
if(Permission::check('APPLY_ROLES')) {
$fields->addFieldToTab(
'Root.Roles',
new LiteralField(
'RolesAddEditLink',
sprintf(
'<p class="add-role"><a href="%s">%s</a></p>',
$this->Link('show/root'),
// TODO This should include #Root_Roles to switch directly to the tab,
// but tabstrip.js doesn't display tabs when directly adressed through a URL pragma
_t('Group.RolesAddEditLink', 'Add/edit roles')
)
)
);
}
$form->Actions()->insertBefore(
$actionAddMember = new FormAction('addmember',_t('SecurityAdmin.ADDMEMBER','Add Member')),
'action_save'
);
$actionAddMember->setForm($form);
// Filter permissions
$permissionField = $form->Fields()->dataFieldByName('Permissions');
if($permissionField) $permissionField->setHiddenPermissions(self::$hidden_permissions);
}
$this->extend('updateEditForm', $form);
} else {
$form = $this->RootForm();
}
return $form;
}
/**
* @return FieldSet
*/
function RootForm() {
$memberList = new MemberTableField(
$this,
"Members"
);
// unset 'inlineadd' permission, we don't want inline addition
$memberList->setPermissions(array('edit', 'delete', 'add'));
$memberList->setRelationAutoSetting(false);
$fields = new FieldSet(
new TabSet(
'Root',
new Tab('Members', singleton('Member')->i18n_plural_name(),
$memberList,
new LiteralField('MembersCautionText',
sprintf('<p class="caution-remove"><strong>%s</strong></p>',
_t(
'SecurityAdmin.MemberListCaution',
'Caution: Removing members from this list will remove them from all groups and the database'
)
)
)
),
new Tab('Import', _t('SecurityAdmin.TABIMPORT', 'Import'),
new LiteralField(
'GroupImportFormIframe',
sprintf(
'<iframe src="%s" id="GroupImportFormIframe" width="100%%" height="400px" border="0"></iframe>',
$this->Link('groupimport')
)
)
)
),
// necessary for tree node selection in LeftAndMain.EditForm.js
new HiddenField('ID', false, 0)
);
// Add roles editing interface
if(Permission::check('APPLY_ROLES')) {
$rolesCTF = new ComplexTableField(
$this,
'Roles',
'PermissionRole'
);
$rolesCTF->setPermissions(array('add', 'edit', 'delete'));
$rolesTab = $fields->findOrMakeTab('Root.Roles', _t('SecurityAdmin.TABROLES', 'Roles'));
$rolesTab->push(new LiteralField(
'RolesDescription',
''
));
$rolesTab->push($rolesCTF);
}
$actions = new FieldSet(
new FormAction('addmember',_t('SecurityAdmin.ADDMEMBER','Add Member'))
);
$this->extend('updateRootFormFields', $fields, $actions);
$form = new Form(
$this,
'EditForm',
$fields,
$actions
);
return $form;
}
public function memberimport() {
Requirements::clear();
Requirements::css(SAPPHIRE_DIR . '/css/Form.css');
Requirements::css(CMS_DIR . '/css/typography.css');
Requirements::css(CMS_DIR . '/css/cms_right.css');
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
Requirements::css(CMS_DIR . '/css/MemberImportForm.css');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(CMS_DIR . '/javascript/MemberImportForm.js');
return $this->renderWith('BlankPage', array(
'Form' => $this->MemberImportForm()
));
}
/**
* @see SecurityAdmin_MemberImportForm
*
* @return Form
*/
public function MemberImportForm() {
$group = $this->currentPage();
$form = new MemberImportForm(
$this,
'MemberImportForm'
);
$form->setGroup($group);
return $form;
}
public function groupimport() {
Requirements::clear();
Requirements::css(SAPPHIRE_DIR . '/css/Form.css');
Requirements::css(CMS_DIR . '/css/typography.css');
Requirements::css(CMS_DIR . '/css/cms_right.css');
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
Requirements::css(CMS_DIR . '/css/MemberImportForm.css');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js');
Requirements::javascript(CMS_DIR . '/javascript/MemberImportForm.js');
return $this->renderWith('BlankPage', array(
'Form' => $this->GroupImportForm()
));
}
/**
* @see SecurityAdmin_MemberImportForm
*
* @return Form
*/
public function GroupImportForm() {
$form = new GroupImportForm(
$this,
'GroupImportForm'
);
return $form;
}
public function AddRecordForm() {
$m = Object::create('MemberTableField',
$this,
"Members",
$this->currentPageID()
);
return $m->AddRecordForm();
}
/**
* Ajax autocompletion
*/
public function autocomplete() {
$fieldName = $this->urlParams['ID'];
$fieldVal = $_REQUEST[$fieldName];
$result = '';
$uidField = Member::get_unique_identifier_field();
// Make sure we only autocomplete on keys that actually exist, and that we don't autocomplete on password
if(!singleton($this->stat('subitem_class'))->hasDatabaseField($fieldName) || $fieldName == 'Password') return;
$matches = DataObject::get($this->stat('subitem_class'),"\"$fieldName\" LIKE '" . Convert::raw2sql($fieldVal) . "%'");
if($matches) {
$result .= "<ul>";
foreach($matches as $match) {
// If the current user doesnt have permissions on the target user,
// he's not allowed to add it to a group either: Don't include it in the suggestions.
if(!$match->canView() || !$match->canEdit()) continue;
$data = array();
foreach($match->summaryFields() as $k => $v) {
$data[$k] = $match->$k;
}
$result .= sprintf(
'<li data-fields="%s">%s <span class="informal">(%s)</span></li>',
Convert::raw2att(Convert::raw2json($data)),
$match->$fieldName,
implode(',', array_values($data))
);
}
$result .= "</ul>";
return $result;
}
}
function getCMSTreeTitle() {
return _t('SecurityAdmin.SGROUPS', 'Security Groups');
}
public function EditedMember() {
if(Session::get('currentMember')) return DataObject::get_by_id('Member', (int) Session::get('currentMember'));
}
function providePermissions() {
return array(
'EDIT_PERMISSIONS' => array(
'name' => _t('SecurityAdmin.EDITPERMISSIONS', 'Manage permissions for groups'),
'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'),
'help' => _t('SecurityAdmin.EDITPERMISSIONS_HELP', 'Ability to edit Permissions and IP Addresses for a group. Requires the "Access to \'Security\' section" permission.'),
'sort' => 0
),
'APPLY_ROLES' => array(
'name' => _t('SecurityAdmin.APPLY_ROLES', 'Apply roles to groups'),
'category' => _t('Permissions.PERMISSIONS_CATEGORY', 'Roles and access permissions'),
'help' => _t('SecurityAdmin.APPLY_ROLES_HELP', 'Ability to edit the roles assigned to a group. Requires the "Access to \'Security\' section" permission.'),
'sort' => 0
)
);
}
/**
* The permissions represented in the $codes will not appearing in the form
* containing {@link PermissionCheckboxSetField} so as not to be checked / unchecked.
*
* @param $codes String|Array
*/
static function add_hidden_permission($codes){
if(is_string($codes)) $codes = array($codes);
self::$hidden_permissions = array_merge(self::$hidden_permissions, $codes);
}
/**
* @param $codes String|Array
*/
static function remove_hidden_permission($codes){
if(is_string($codes)) $codes = array($codes);
self::$hidden_permissions = array_diff(self::$hidden_permissions, $codes);
}
/**
* @return Array
*/
static function get_hidden_permissions(){
return self::$hidden_permissions;
}
/**
* Clear all permissions previously hidden with {@link add_hidden_permission}
*/
static function clear_hidden_permissions(){
self::$hidden_permissions = array();
}
}
/**
* Delete multiple {@link Group} records. Usually used through the {@link SecurityAdmin} interface.
*
* @package cms
* @subpackage batchactions
*/
class SecurityAdmin_DeleteBatchAction extends CMSBatchAction {
function getActionTitle() {
return _t('AssetAdmin_DeleteBatchAction.TITLE', 'Delete groups');
}
function run(DataObjectSet $records) {
$status = array(
'modified'=>array(),
'deleted'=>array()
);
foreach($records as $record) {
// TODO Provide better feedback if permission was denied
if(!$record->canDelete()) continue;
$id = $record->ID;
$record->delete();
$status['deleted'][$id] = array();
$record->destroy();
unset($record);
}
return Convert::raw2json($status);
}
}
?>

View File

@ -1,81 +0,0 @@
@import url("typography.css");
html,body {
padding: 0;
margin: 0;
border-style: none;
height: 100%;
overflow: hidden;
}
form {
margin: 0; padding: 0;
}
h2 {
margin: 0;
font-size: 1.4em;
}
/**
* Selection Groups
*/
.SelectionGroup {
padding: 0;
margin: 10px 0 0 0;
}
.SelectionGroup li {
list-style-type: none;
margin: 0 0 4px;
}
.SelectionGroup li label {
font-size: 11px;
}
.SelectionGroup li input.selector {
width: 20px;
margin-top: 0;
}
.SelectionGroup li div.field {
display: none;
}
.SelectionGroup li.selected div.field {
display: block;
margin-left: 30px;
margin-bottom: 1em;
margin-top: 4px;
}
.mainblock .SelectionGroup li.selected div.field {
margin-left: 27px;
margin-bottom: 4px;
}
.SelectionGroup li.selected label.selector {
font-weight: bold;
}
.Actions {
text-align: right;
margin: 0;
position: absolute;
right: 5px;
bottom: 5px;
}
.mainblock {
float: left;
border: 1px #CCC solid;
padding: 5px;
margin-right: 5px;
height: 140px;
position: relative;
}
.mainblock form fieldset {
border: none;
}
.mainblock form div.Actions input {
font-size: 11px;
}

View File

@ -1,36 +0,0 @@
* {
font-family: Helvetica, Verdana, Geneva, Arial, Helvetica, sans-serif;
}
h3 {
margin-top: 2em;
}
form .field {
margin-bottom: 1em;
}
form label.left {
float: left;
width: 15em;
font-weight: bold;
}
form div.field {
/*margin-left: 10em;*/
}
form .inlineformaction,
form .inlineformaction_readonly,
form .action,
form .action_readonly {
display: none !important;
}
.TableListField table.data th {
background: #eee;
}
.TableListField table.data {
width: 100%;
border: 1px #aaa solid;
}

View File

@ -1,25 +0,0 @@
* {
font-size:12px;
}
body {
margin: 0;
padding: 0;
}
form.import-form {
margin: 0;
}
form.import-form #CsvFile .middleColumn {
background: none !important;
}
form.import-form .advanced h4 {
margin-bottom: .5em;
}
form.import-form .Actions {
text-align: left;
border: 0;
}

View File

@ -1,173 +0,0 @@
.clear{
display: block;
clear: both;
}
body.ModelAdmin .ui-layout-west {
width: 280px;
}
body.ModelAdmin #Form_AddForm fieldset {
border: none;
}
body.ModelAdmin #Form_AddForm #ClassName {
margin: 0;
padding: auto;
width: auto;
}
body.ModelAdmin #Form_AddForm #ClassName label {
display: none;
}
body.ModelAdmin #SearchForm_holder,
body.ModelAdmin #AddForm_holder {
padding: 1em;
}
body.ModelAdmin #SearchForm_holder {
overflow: auto;
margin-bottom:1em;
}
body.ModelAdmin #ModelClassSelector {
border-bottom: 1px #AAA solid;
padding-bottom: 0.5em;
}
body.ModelAdmin #SearchForm_holder div.ResultAssemblyBlock{
margin-bottom: 1em;
}
body.ModelAdmin #SearchForm_holder form div.field.hidden{
display: none;
}
body.ModelAdmin #SearchForm_holder form div#ResultAssembly{
margin: 0;
display: block;
clear: both;
}
body.ModelAdmin #SearchForm_holder form div#ResultAssembly label{
width: auto;
clear: both;
margin: 0px;
}
body.ModelAdmin #SearchForm_holder form div#ResultAssembly ul.optionset li{
float: left;
width: 45%;
}
body.ModelAdmin .resultsTable {
overflow:auto;
}
body.ModelAdmin .resultsTable tbody td {
cursor: pointer;
}
body.ModelAdmin #right{
overflow:hidden !important;
}
body.ModelAdmin #right table.results {
margin:6px;
padding:0;
border-spacing:0 2px;
}
body.ModelAdmin #right table.results td {
font-size:12px;
font-weight:bold;
background-color:#fff;
padding:2px;
}
body.ModelAdmin #right table.results td.over {
background-color:#bbb;
cursor:pointer;
}
body.ModelAdmin #right table.results td.active {
background-color:#555;
color:#fff;
}
body.ModelAdmin #right .tab {
overflow: auto;
padding-bottom: 20px;
}
body.ModelAdmin #Form_ImportForm dt {
float: left;
width: 10em;
margin-right: 2em;
}
body.ModelAdmin #statusMessage {
position: fixed !important;
position: absolute;
left: auto !important;
bottom: 35px !important;
}
/* Form */
body.ModelAdmin .ui-layout-west form {
border-bottom: 2px solid #ddd;
padding-bottom: 10px;
}
body.ModelAdmin .ui-layout-west #Form_ImportForm {
border-bottom: none;
}
body.ModelAdmin .ui-layout-west form .Actions {
margin: 5px 0;
}
body.ModelAdmin #right .Actions {
position:absolute;
bottom:4px;
}
body.ModelAdmin #right .ajaxActions {
position:absolute;
bottom:4px;
height: 30px;
}
body.ModelAdmin .ui-layout-west .tab {
padding: 7px;
}
body.ModelAdmin .tab h3 {
margin: 10px 0 5px 0;
font-size: 12px;
}
form .message {
font-size: 10px;
margin: .3em 0;
padding: 0.3em;
}
body.ModelAdmin .historyNav {
background: #aaa;
padding: 0 10px;
overflow: auto;
}
body.ModelAdmin .historyNav a {
display: block;
color: white;
margin: 3px 0;
}
body.ModelAdmin .historyNav a.back {
float: left;
}
body.ModelAdmin .historyNav a.forward {
float: right;
}

View File

@ -1,14 +0,0 @@
#Form_EditForm_Permissions .Arg,
#Form_EditForm_Permissions .Enabled {
width: 6em;
}
#Form_EditForm_Permissions .Arg input {
text-align: right;
}
/* Ensure there's enough room in the tab for TreeMultiSelectField expansion */
#MemberTableField_Popup_DetailForm #Groups,
#MemberTableField_Popup_AddForm #Groups {
height: 250px;
}

View File

@ -1,414 +0,0 @@
#left form.actionparams {
background: #eee;
padding: 0 5px 5px 5px;
_padding-left: 5px;
float: left;
width: 100%;
}
#left form#search_options {
padding-bottom: 8px;
}
#left form.actionparams input,
#left form.actionparams select {
padding: 1px;
}
#left form.actionparams select,
#ReportSelector_holder select {
width: 75%;
}
/* reset for a fieldtype that shouldn't have this width setting */
#left form.actionparams .dropdownpopup select {
width: auto;
}
#PageType,
#ReportSelector_holder select {
float: left;
margin-right: 4px;
padding: 1px;
}
#left form.actionparms input.action {
width: 5%;
}
#Form_DeleteItemsForm p {
margin: 4px 8px 4px 0;
font-size: 11px;
color: #666;
}
form.oneline fieldset {
width: 80%;
float: left;
margin-right: .5em;
}
/**
* Selection Groups
*/
#left .SelectionGroup {
display: block;
}
#left .SelectionGroup li {
list-style-type: none;
clear: left;
border: 1px solid #ccc;
}
#left .SelectionGroup li input {
width: 20px;
float: left;
}
#left .SelectionGroup li div.field {
display: none;
font-size: 1em;
}
#left .SelectionGroup li label {
margin: 0 0 0 2px;
display: block;
height: 1.2em;
width: auto;
float: none;
}
#left .SelectionGroup li.selected div.field {
margin-left: 23px;
display: block;
margin-bottom: 1em;
}
#left div.field select {
width: 100%;
}
#left .SelectionGroup li.selected label.selector {
font-weight: bold;
}
#publication_key {
border-bottom: 1px solid #ccc;
background: #eee;
padding: 3px;
font-weight: bold;
}
#publication_key ins,
#publication_key del,
#publication_key span {
margin: 0 5px;
font-weight: normal;
}
#publication_key span.notinmenu {
color: #77A;
}
/* Created on stage, never published */
#publication_key ins {
color: orange;
text-decoration : none;
}
/* Deleted on stage */
#publication_key del {
color: red;
}
/* Deleted on stage & live (when show deleted pages is active) */
#publication_key del.deletedOnLive {
color: #700;
}
#publication_key span.modified {
color: green;
text-decoration : none;
}
#left {
background-color: #eee;
}
#treepanes .ui-accordion-content {
padding: 0;
}
.ui-layout-pane-west {
/* OVERRIDE 'default styles' */
padding: 0 !important;
overflow: hidden !important;
}
#treepanes h2 {
cursor: pointer;
}
#treepanes h2 img {
float:right;
margin-right: 2px;
}
#treepanes form .field {
margin: 0;
}
.listpane p {
display: block;
margin: 0px 4px 0px 4px;
padding: 3px 0px;
font-size: 10px;
}
.listpane ul {
margin: 0;
padding: 0;
}
.listpane ul li {
list-style-type: none;
font-size: 10px;
border-bottom: 1px #CCC dotted;
background-color: #FFF;
}
.listpane ul li.odd {
background-color: #EEE;
}
.listpane ul li .extra {
font-style: italic;
color: #666;
}
.listpane div.unitBody {
overflow: auto;
width: 100%;
clear: left;
}
.listpane li a {
display: block;
padding: 5px;
}
.listpane input.action {
padding: 0;
}
.listpane p.message {
padding: 5px;
}
#treepanes .pane_actions {
margin: 0px 4px 0px 4px;
}
#treepanes .pane_actions a {
margin: 4px;
padding: 3px;
background-color: #EEE;
border: 1px #CCC solid;
}
#treepanes .pane_actions a.current {
background-color: #CCC;
}
#treepanes table {
border-collapse: collapse;
width: 100%;
}
#treepanes table thead td {
background-color: #aaa;
color: #fff;
border-bottom: 2px #ccc solid;
padding: 0 3px;
}
#treepanes table tbody tr {
cursor: pointer;
}
#treepanes table tbody td {
font-size: 11px;
padding: 1px 3px;
}
#treepanes table tbody tr.even td {
background-color: #EEE;
}
#treepanes table tbody tr.over td {
background-color: #FFFFBB;
}
#treepanes table tbody tr.current td {
background-color: #BBBBFF;
}
#treepanes #Versions tbody tr.internal {
color: #777;
}
#treepanes #Versions tbody tr.published {
color: black;
}
.SideReport_RecentlyEdited li a:hover {
background-color: #FFFFBB;
}
/* This applies to both the LHS context menu and the TinyMCE context menu */
.contextMenu {
z-index: 1000;
background-color: white;
border: 1px #CCC solid;
}
ul.contextMenu {
padding: 0;
margin: 0;
}
ul.contextMenu li {
list-style-type: none;
margin: 0;
padding: 0;
}
ul.contextMenu li a {
display: block;
font-family: Tahoma, Verdana, Arial, Helvetica;
font-size: 11px;
padding: 5px 10px 5px 5px;
color: black;
text-decoration: none;
}
ul.contextMenu li a:hover {
background-color: #B6BDD2;
text-decoration: none;
}
/* Tree */
/* Added through jstree.js on DOM load, but we need it earlier in order to correctly display the uninitialized tree */
.jstree ul, .jstree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; }
.jstree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; }
.jstree-rtl li { margin-left:0; margin-right:18px; }
.jstree > ul > li { margin-left:0px; }
.jstree-rtl > ul > li { margin-right:0px; }
.jstree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; }
.jstree a { display:inline-block; line-height:16px; height:16px; color:black; white-space:nowrap; text-decoration:none; padding:1px 2px; margin:0; }
.jstree a:focus { outline: none; }
.jstree a > ins { height:16px; width:16px; }
.jstree a > .jstree-icon { margin-right:3px; }
.jstree-rtl a > .jstree-icon { margin-left:3px; margin-right:0; }
li.jstree-open > ul { display:block; }
li.jstree-closed > ul { display:none; }
ul.tree span.untranslated a:link,
ul.tree span.untranslated a:hover,
ul.tree span.untranslated a:visited {
color: #ccc
}
#sitetree_ul li.disabled a {
color: #aaa;
}
#Form_SearchTreeForm .field .middleColumn {
width: 60%;
float: left;
margin: 0;
}
#Form_SearchTreeForm .field label {
margin-left: 0;
width: 28%;
}
#Form_SearchTreeForm select.options {
clear: left;
}
/* Change detection CSS */
#right .compare ins, #right .compare ins * {
background-color: #99EE66;
text-decoration: none;
}
#right .compare del, #right .compare del *{
background-color: #FF9999;
text-decoration: line-through;
}
#sitetree a.contents {
font-style: italic;
}
#ShowDrafts {
float: none;
}
.noBottomBorder {
border-bottom: none;
}
.checkboxAboveTree {
border-top: 1px solid #cccccc;
padding: .5em;
overflow: hidden;
clear: left;
}
.checkboxAboveTree div {
clear: left;
}
.checkboxAboveTree input, #ShowChanged input, #ShowDrafts input {
float: left;
margin: 0 3px 0 0;
}
#versions_actions {
float: left;
width: 100%;
border-bottom:1px solid #CCC;
}
#versions_actions .versionChoice {
float: left;
display: block;
margin: 0 0 5px 0;
}
.versionChoice label {
display: block;
float: left;
margin-top: 3px;
_margin-top: 4px;
}
#versions_actions input, #Remember input {
float: left;
display: block;
margin: 0 3px 0 0;
}
#ReportSelector_holder {
background: #EEE;
margin: 0 !important;
padding: 3px 3px 7px;
border-bottom: 1px solid #CCC;
}
#versions_actions {
background: #EEE;
overflow: hidden;
margin: 0 !important;
padding: 4px 7px;
}
#Form_VersionsForm td.checkbox {
}
#Form_SideReportsForm select,
#Form_SideReportsForm .middlecolumn {
width: 80%;
}
/**
* i18n
*/
#LangSelector_holder {
float: none;
background-color: #EEEEEE;
border-bottom: 1px solid #CCCCCC;
border-top: 1px solid #CCCCCC;
padding: 3px 0 3px 7px;
}
#LangSelector_holder .Actions {
display: none;
}
#LangSelector_holder label.left {
margin-left: 0;
width: auto;
}

View File

@ -1,590 +0,0 @@
/**
* General
*/
#right p/*,
#right .optionset*/ {
margin: 7px 0;
}
#right .optionset li {
margin: 8px 0;
}
/**
* Forms
*/
.right form {
margin: 1em;
position: relative;
}
/* legend tag shows up annoying spacing in IE6/7 */
.right form legend {
display: none;
}
.right form .field {
border: none;
margin: 0;
}
.right form .text,
.right form .field span.readonly,
.right form .field span.readonly i,
.right form div.TableListField table i {
font-size: 12px;
}
.right form .field span.readonly {
margin: 0;
width: auto;
}
.right form #URL .fieldgroup * {
font-size: 12px;
font-weight: bold;
}
.right form .fieldgroupField {
display: inline;
}
.right form #URL .fieldgroup {
display:block;
height: 16px;
background-color:#F7F7F7;
border:1px dotted #CCCCCC;
padding:3px;
}
.right form #URL .originallang_holder {
display: inline;
border: none;
padding: 0;
}
.right form #URL .fieldgroup input {
border: 1px solid #A7A7A7;
padding: 2px;
margin-top: -3px;
}
.right form #URL .fieldgroup span.readonly {
display: inline;
border: none;
padding: 0;
}
.right form .parentTypeSelector {
margin-left: 0 !important;
}
.right form .parentTypeSelector label.left {
float: none !important;
margin-left: 0 !important;
width: auto !important;
}
.right form .dropdown,
.right form .countrydropdown {
font-size: 12px;
}
.right form .dropdown select,
.right form .countrydropdown select {
font-size: 12px;
height: 19px;
}
.right form .dropdown select option,
.right form .countrydropdown select option {
font-size: 12px;
}
.right form .field .middleColumn {
display: block;
background: #e9e9e9;
padding: 3px;
width: 97%;
}
.right form .field .middleColumn .middleColumn {
padding: 0;
}
.right form .field.image .middleColumn,
.right form .field.simpleimagefield_disabled .middleColumn {
padding-bottom:0;
}
.right form .htmleditorfield span.readonly *,
#right form .htmleditorfield span.readonly * {
margin: 0;
border: 0;
padding: 0;
}
.right form .checkbox input,
.right form .optionset input {
border: none;
}
.right form .field label {
font-size: 11px;
color: #666;
}
.right form .field label.left {
float: none;
display: block;
margin-left: 0;
margin-bottom: 3px;
/*font-size: 11px;*/
width: 96%;
}
.time .timeIcon,
.datetime .timeIcon {
margin-left: 2px;
}
.right form .date input.month,
.right form .date input.day,
.right form .date input.year {
width: auto;
}
.right form .datetime .clear {
clear: both;
}
.right form .datetime .date,
.right form .datetime .time {
float: left;
clear: none;
width: auto;
}
.right form .datetime .middleColumn .middleColumn {
width: auto;
}
.right form .datetime .date input,
.right form .datetime .time input {
width: auto;
}
.right form textarea,
.right form input.text,
.right form textarea.htmleditor {
border: 1px solid #a7a7a7;
padding: 3px;
width: 99%;
}
.right form textarea {
font-size: 12px;
}
.right form input.checkbox,
.right form .optionset input,
.right form .htmleditor select {
width: auto;
margin-right: 4px;
}
.right form #Root_Access input {
float: left;
}
.right form ul.optionset {
padding: 0;
}
.right form .optionset li {
list-style: none;
}
.right form .optionset li.cancel {
float: left;
}
.right form .optionset li.submit {
float: right;
}
.right form h1 {
font-size: 20px;
}
.right form h2 {
font-size: 18px;
clear: both;
}
.right form h3 {
font-size: 16px;
}
.right form h4 {
font-size: 14px;
}
.right form h5 {
font-size: 12px;
}
.right form .fieldgroup input,
.right form .fieldgroup select {
width: auto;
}
.right form fieldset {
height: 100%;
}
.right form .Actions {
text-align: right;
padding: 0.5em;
background: white;
border-top: 1px solid #aaa;
}
.right form .Actions input {
padding: .5em;
font-size: 11px;
}
.right form.loading {
margin-left: -1000em;
margin-right: 1000em;
}
#SiteTreeFilterDate {
width: 68px;
}
/**
* Tinymce
*/
.right form .mceEditor select.mceNativeListBox {
background: #fff;
padding-top: 2px;
width: 95px; /* Added width to stop sizes jumping and text wrapping */
font-size: 11px;
}
.right form .mceEditor select.mceNativeListBox option {
padding: 0 2px 0 2px;
font-size: 11px;
}
.right form .mceEditor .mceFirst {
background: #f4f4f4;
}
.right form .mceEditor .mceToolbar table {
background: transparent;
}
.right form .mceEditor .mceButton {
padding: 1px;
}
.right form .mceEditor select.mceNativeListBox {
background: #fff;
}
.right form .mceEditor .mceFirst {
background: #fbfbfb url(../images/textures/mce_editor.gif) repeat-x bottom;
}
.right form .mceEditor .mceToolbar table {
background: transparent;
}
.right form .mceEditor select.mceNativeListBox {
background: #fff;
}
/**
* Non-tabbed scrolling area that can be used in place of tabs in LeftAndMAin
*/
.right #ScrollPanel {
background: #fff;
clear: left;
overflow: auto;
border: 1px solid #aaa;
position: relative;
top: 0;
padding: 10px;
}
/**
* RHS Action Parameters boxes
*/
#right form.actionparams {
border: 1px solid #777;
background: #ccc;
position: absolute;
bottom: 32px;
right: 11px;
width: 300px;
z-index: 200;
padding: 10px;
}
#right form.actionparams div.field {
margin-left: 6em;
}
#right form.actionparams label.left {
width: 6em;
margin-left: -6em;
}
#right form.actionparams input, #right form.actionparams select, #right form.actionparams textarea {
width: 100%;
}
#right form.actionparams input.checkbox, #right form.actionparams .optionset input {
width: auto;
}
#right form.actionparams p.label {
margin: 10px 0 0 0;
}
#right form.actionparams .actions {
margin: 10px 0 0 0;
text-align: right;
}
#right form.actionparams .actions input {
width: auto;
}
#right form.actionparams .sendingText {
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 10% 0 0 0;
height: 60%;
background: #ccc;
font-size: 25px;
font-weight: bold;
color: #fff;
text-align: center;
}
#right form.actionparams div.TreeDropdownField {
width: 220px;
padding-bottom: 150px;
}
#right form.actionparams div.TreeDropdownField .items {
width: 195px;
}
#right form.actionparams div.TreeDropdownField div.tree_holder {
width: 214px;
}
/**
* Autocomplete
*/
.autocomplete {
margin-left: 1px;
background: white;
overflow: visible;
}
.autocomplete ul {
border: 1px solid #aaa;
background: #FFFFBB;
}
.autocomplete li {
list-style-type: none;
cursor: pointer;
font-size: 12px;
padding: 3px;
white-space: nowrap;
}
.autocomplete li.selected {
background: Highlight;
color: HighlightText;
}
.autocomplete .data {
display: none;
}
.autocomplete .informal {
font-style: italic;
}
/**
* Status Messages
*/
.notice-wrap {
position: absolute;
z-index: 500;
bottom: 4px;
left: 10px;
z-index: 9999;
width: auto;
}
* html .notice-wrap {
position: absolute;
}
.notice-item {
display: block;
position: relative;
width: auto;
background: #f4f3eb;
border: 1px solid #a9a67d;
padding: 2px 20px 2px 20px;
font-size: 14px;
font-weight: bold;
line-height: 1.5;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
}
.notice-item-close {
position: absolute;
font-family: Arial;
font-size: 12px;
font-weight: bold;
right: 6px;
top: 6px;
cursor: pointer;
}
.notice-wrap .good {
padding-left: 40px;
color: #060;
background: #E2F9E3 url(../images/alert-good.gif) 7px 50% no-repeat;
border: 1px solid #8dd38d;
}
.notice-wrap .bad {
padding-left: 40px;
color: #c80700;
background: #ffe9e9 url(../images/alert-bad.gif) 7px 50% no-repeat;
border: 1px solid #ff8e8e;
}
/**
* TableField Subclasses
*/
/* Filter box (for search/filter box above a table on Asset/MemberTableField) */
.right div.filterBox {
width: inherit;
margin: 5px 0;
padding: 5px;
background: #EEE;
border: 1px solid #BBB;
}
.right div.filterBox .middleColumn {
background: none !important;
}
.right form div.filterBox label {
display: block;
width: 5em;
float: left;
margin-left: 0;
line-height: 2.3;
font-weight: bold;
font-size: 12px;
}
.right div.filterBox div.field {
margin-left: 0 !important;
padding: 0;
font-size: 12px;
display: inline;
}
.right div.filterBox input,
.right div.filterBox select {
width: auto;
}
/**
* i18n
*/
.right form div.originallang_holder {
border: 1px dashed #aaa;
margin-bottom: 10px;
margin-left: 0 !important;
padding: 8px 0 6px 5px;
}
.originallang,
#Form_EditForm_URLSegment_original {
color: #666;
font-style: italic;
}
.right form .CompositeField .originallang label.left,
.right form div.originallang_holder label.left {
margin-left: 0 !important;
width: auto !important;
float: none !important;
}
.right form div.CompositeField .originallang .middleColumn {
clear: left;
}
.right form div.languageDropdown {
clear: right;
float: left;
padding-right: 4px;
}
.right form .createTranslationButton .middleColumn {
background: none;
}
#contentPanel h2 img {
float: right;
margin-right: 2px;
cursor: pointer;
}
.right form .field.confirmedpassword.nolabel .middleColumn {
background-color: #fff;
padding: 0px;
}
.right form .field.confirmedpassword.nolabel .middleColumn .middleColumn {
background-color: #e9e9e9;
padding: 3px;
}
.autocompleteoptions {
background-color: white;
}
/**
* Tabs
*/
.right form .ss-tabset {
/* Need this to stop tabs from disappearing sometimes */
position: relative;
}
.right form .ss-tabset .tab {
position: relative;
overflow-x: hidden;
}
/**
* "Insert Image" forms etc.
*/
#Form_EditorToolbarImageForm .field,
#Form_EditorToolbarFlashForm .field,
#Form_EditorToolbarLinkForm .field {
margin-left: 0;
}
/**
* jQuery UI Datepicker - necessary because of custom
* z-indexing performed in jQuery.layout
*/
#ui-datepicker-div {
z-index: 1002;
}
/**
* Upload Image (Image Toolbar)
*/
#Form_EditorToolbarImageForm .file,
#Form_EditorToolbarImageForm #UploadFormResponse {
display: none;
}
.showUploadField {
font-size: 11px;
padding: 1px 3px;
}
.showUploadField a {
display: none;
}
#UploadFormResponse {
padding: 6px 0;
font-size: 12px;
}
#UploadFormResponse.loading
.loading#UploadFormResponse {
background: url(../../cms/images/network-save.gif) no-repeat left center;
padding-left: 20px;
}

View File

@ -1,20 +0,0 @@
/**
* Default Editor Styles for the CMS
*/
.typography .mceItemFlash {
border: 1px dotted #f00;
}
body.mceContentBody {
min-height: 200px;
font-size: 62.5%;
}
body.mceContentBody a.ss-broken {
background-color: #FF7B71;
border: 1px red solid;
color: #fff;
padding: 1px;
text-decoration: underline;
}

View File

@ -1,713 +0,0 @@
* {
margin: 0;
padding: 0;
list-style: none;
font-family: Arial, Helvetica, sans-serif;
}
html {
overflow: hidden;
}
body {
background: #ccdef3;
height: 100%;
}
.ss-loading-screen, .ss-loading-screen .loading-logo {
width: 100%;
height: 100%;
overflow: hidden;
position: absolute;
background: #fff;
background: -moz-radial-gradient(50% 50% 180deg, circle cover, #FFFFFF, #EFEFEF, #C7C7C7 100%);
background: -webkit-gradient(radial, 50% 50%, 350, 50% 50%, 0, from(#E3E3E3), to(white));
z-index: 100000;
margin: 0;
padding: 0;
}
.ss-loading-screen .loading-logo {
background-repeat: no-repeat;
background-color: transparent;
background-position: 50% 50%;
}
.ss-loading-screen p {
width: 100%;
text-align: center;
position: absolute;
bottom: 80px;
}
.ss-loading-screen p span.notice {
display: inline-block;
font-size: 14px;
padding: 10px 20px;
color: #dc7f00;
border: none;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
-o-border-radius: 5px;
-moz-box-shadow: 1px 1px 15px rgba(0,0,0, 0.1);
-webkit-box-shadow: 1px 1px 15px rgba(0,0,0, 0.1);
-o-box-shadow: 1px 1px 15px rgba(0,0,0, 0.1);
box-shadow: 1px 1px 15px rgba(0,0,0, 0.1);
}
.ss-loading-screen .loading-animation {
display: none;
position: absolute;
left: 49%;
top: 75%;
}
/*
* Default Layout Theme
*
* Created for jquery.layout
*
* Copyright (c) 2008
* Fabrizio Balliano (http://www.fabrizioballiano.net)
* Kevin Dalman (http://allpro.net)
*
* Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
* and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
*
* Last Updated: 2009-03-04
* NOTE: For best code readability, view this with a fixed-space font and tabs equal to 4-chars
*/
/*
* PANES
*/
.ui-layout-pane { /* all 'panes' */
background: #FFF;
/*border: 1px solid #BBB;*/
overflow: auto;
}
/* Overflow is handled by tabsets inside the panel */
.ui-layout-center,
.ui-layout-east {
overflow: hidden !important;
}
.ui-layout-west {
width: 225px;
}
.ui-layout-north {
height: 35px;
}
.ui-layout-south {
height: 23px;
}
.ui-layout-east {
height: 250px;
}
/*
* RESIZER-BARS
*/
.ui-layout-resizer { /* all 'resizer-bars' */
background: #DDD;
border: 1px solid #BBB;
border-width: 0;
opacity: 1; /* on-hover, show the resizer-bar normally */
filter: alpha(opacity=100);
}
.ui-layout-resizer-open:hover , /* hover-color to 'resize' */
.ui-layout-resizer-dragging { /* resizer beging 'dragging' */
background: #EEE;
}
.ui-layout-resizer-dragging { /* CLONED resizer being dragged */
border-left: 1px solid #BBB;
border-right: 1px solid #BBB;
}
.ui-layout-resizer-drag { /* REAL resizer while resize in progress */
}
.ui-layout-resizer-closed:hover { /* hover-color to 'slide open' */
background: #EEE;
}
.ui-layout-resizer-sliding { /* resizer when pane was 'slid open' */
opacity: 0.1; /* show only a slight shadow */
filter: alpha(opacity=10);
}
.ui-layout-resizer-sliding:hover { /* sliding resizer - hover */
opacity: 1; /* on-hover, show the resizer-bar normally */
filter: alpha(opacity=100);
}
/* sliding resizer - add 'outside-border' to resizer on-hover */
.ui-layout-resizer-north-sliding:hover { border-bottom-width: 1px; }
.ui-layout-resizer-south-sliding:hover { border-top-width: 1px; }
.ui-layout-resizer-west-sliding:hover { border-right-width: 1px; }
.ui-layout-resizer-east-sliding:hover { border-left-width: 1px; }
/*
* TOGGLER-BUTTONS
*/
.ui-layout-toggler {
color: #666;
/*border: 1px solid #BBB;*/ /* match pane-border */
background-color: #999;
}
.ui-layout-toggler:hover {
background-color: #FC6;
}
.ui-layout-toggler-north ,
.ui-layout-toggler-south {
border-width: 0 1px;
}
.ui-layout-toggler-west ,
.ui-layout-toggler-east {
border-width: 1px 0;
}
/* hide the toggler-button when the pane is 'slid open' */
.ui-layout-resizer-sliding ui-layout-toggler {
display: none;
}
/*
* style the text we put INSIDE the east/west togglers
*/
.ui-layout-toggler .content {
font: 30px bold Verdana, Arial, Helvetica, sans-serif;
padding-bottom: 0.35ex; /* to 'vertically center' text inside text-span */
}
/**
* Actions/Buttons
*/
#TreeActions button.disabled {
color: #aaa;
}
.ajaxActions input.disabled,
input.disabled {
color: #666;
}
input.action.loading, input.action.loading:hover {
padding-left: 22px !important;
background: url(../images/network-save.gif) 3px 2px no-repeat;
}
input.delete:hover,
button.delete:hover,
.actionparams input.delete:hover {
background: #ce0000;
color: #fff;
}
input.loading {
padding-left: 16px;
background: #fff url(../images/network-save.gif) no-repeat center left;
}
input.hidden {
display: none;
}
/* Overrides - TODO Find a better place to put them */
form#Form_EditForm fieldset, form#Form_AddForm fieldset {
height: 100%;
border: none;
}
form#Form_EditForm #ScrollPanel fieldset, form#Form_AddForm #ScrollPanel fieldset {
height: auto;
}
body.stillLoading select {
display: none;
}
/** 3-PANEL LAYOUT **/
.ss-cms-top-menu {
overflow: hidden;
font-size: 14px;
height: 33px;
background: #474855 url(../images/mainmenu/top-bg.gif) top left repeat-x;
color: #fff;
}
/**
* Hidden left-hand panel
*/
#left.hidden form, #left.hidden .title {
display: none;
}
#left.hidden {
width: 18px;
display: block;
}
#left div.title, #right div.title {
border-top: 1px solid #77BBEE;
height: 22px !important;
background: #0075C9 url(../images/textures/obar.gif) repeat-x 0 0;
}
#left div.title div, #right div.title div {
font-size: 14px;
font-weight: bold;
color: #fff;
padding: 2px 0 0 4px;
background-position: 2px 2px;
background-repeat: no-repeat;
padding-left: 20px;
border-top: 1px solid #77BBEE;
}
#left h2,
#contentPanel h2 {
background-image:url(../images/textures/obar-18.gif);
height: 18px;
line-height: 18px;
color: #fff;
font-size: 12px;
padding-left: 3px;
}
/** TOP PANEL **/
.ss-cms-top-menu #MainMenu {
margin: 0 0 0 10px;
}
.ss-cms-top-menu #MainMenu li {
margin: 0 5px;
float: left;
height: 35px;
cursor: pointer;
}
.ss-cms-top-menu #MainMenu a {
display: block;
height: 33px;
float: left;
padding: 0 10px;
font-size: 14px;
letter-spacing: -0.1px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal;
line-height: 32px;
color: #fff;
text-decoration: none;
}
.ss-cms-top-menu #MainMenu a:hover {
text-decoration: none;
background: #6a7580 url(../images/mainmenu/hover.gif) repeat-x left top;
}
.ss-cms-top-menu #MainMenu .current {
background: #ccdef3 url(../images/mainmenu/current.gif) repeat-x left top;
border-bottom : none;
}
.ss-cms-top-menu #MainMenu .current a:hover {
background: #cddef3 url(../images/mainmenu/currentHover.gif) repeat-x left top;
}
.ss-cms-top-menu #MainMenu .current a:link,
.ss-cms-top-menu #MainMenu .current a:visited {
color : #2f383f;
}
.ss-cms-top-menu #Logo {
float: right;
}
.ss-cms-top-menu #Logo a {
margin: 0;
display: block;
font-size: 14px;
text-decoration: none;
height: 26px;
line-height: 22px;
position: absolute;
top: 5px;
right: 8px;
padding-right: 34px;
background: url(../images/mainmenu/logo.gif) right top no-repeat;
color: #fff;
}
html > body .ss-cms-top-menu #Logo {
padding-bottom: 0;
}
.ss-cms-bottom-bar {
width: 100%;
background: #4d4e5a url(../images/textures/footerBg.gif) repeat-x left top;
}
.ss-cms-bottom-bar .holder {
text-align: center;
font-size: 10px;
color: #fff;
padding: 0.5em;
}
.ss-cms-bottom-bar #logInStatus {
float: right;
}
.ss-cms-bottom-bar a {
color: #fff;
background: none;
}
.ss-cms-bottom-bar a:hover {
text-decoration: none;
color: #ccc;
}
.ss-cms-bottom-bar #logInStatus #LogoutLink {
background: url(../images/logout.gif) no-repeat right top;
padding-right: 20px;
}
.ss-cms-bottom-bar #switchView {
float: left;
}
.ss-cms-bottom-bar #switchView a, .ss-cms-bottom-bar #switchView span {
background: none;
padding-left: 8px;
padding-right: 8px;
display: inline-block;
border-left: 1px solid #AAA;
}
.ss-cms-bottom-bar #switchView span {
border-left: none;
}
.ss-cms-bottom-bar .bottomTabs a {
color: #fff;
}
.ss-cms-bottom-bar .bottomTabs div.blank {
display: block;
float: left;
cursor: pointer;
background-color: transparent;
padding-right: 4px;
font-weight: bold;
line-height: 13px;
font-size: 11px;
padding-left: 2px;
color: #fff;
}
.ss-cms-bottom-bar .bottomTabs .current {
color: #ccc;
}
.ss-cms-bottom-bar .bottomTabs a:hover {
color: #ccc;
text-decoration: none;
}
/** LEFT PANEL **/
#sitetree_and_tools {
overflow: auto;
}
#sitetree_holder #TreeTools {
background: #EEE;
}
#SearchControls {
margin-top:2px;
}
#SearchControls label {
display: none;
}
#left #TreeActions,
#left .checkboxAboveTree {
background: #EEE;
padding: 5px;
float: left;
width: 95%;
}
#left .checkboxAboveTree {
border-bottom: 1px solid #CCC;
}
#TreeTools label {
display: block;
padding-top: 2px;
_padding-top: 4px;
}
#TreeTools .ui-tabs .ui-tabs-nav li a {
padding-left: .5em;
padding-right: .5em;
}
#TreeTools #batchactionsforms {
padding: 0 5px 7px 5px;
}
#TreeTools select {
margin-left: 2px;
}
#TreeTools div p, #ShowChanged {
margin: 0 0 5px 0;
float: left;
width: 100%;
}
#TreeTools div.middleColumn {
margin: 0;
}
#TreeTools #action_publish_selected,
#TreeTools #action_publish_selected {
padding: 2px 1px;
float: left;
}
#checkboxActionIndicator {
float: right;
width: auto;
margin-top: 7px;
display: none;
}
div.spacer,
li.spacer {
float: none;
clear: both;
background-color: transparent;
border-style: none;
margin: -1px 0 0 0;
height: 1px;
font-size: 1px;
width: auto;
}
/** RIGHT PANEL **/
.mceToolbarExternal {
background-color: #eee;
display: block;
}
.mceToolbarExternal a {
text-decoration: none;
border: 0 !important;
}
.mceToolbarExternal a img {
border: none;
}
#right form#Form_EditorToolbarLinkForm,
#right form#Form_EditorToolbarImageForm,
#right form#Form_EditorToolbarFlashForm {
background: #eee;
border-bottom: 1px solid #ccc;
display: none;
margin: 1px 0 0 0;
padding: 5px;
/*
* HACK IE (all versions): container needs to be higher stacking order
* than any DOM-elemnt under it.
* @see http://www.aplus.co.yu/lab/z-pos/
*/
/*overflow: hidden;*/
z-index: 1001;
}
#right form#Form_EditorToolbarLinkForm ul.optionset {
height: 1em;
}
#right form#Form_EditorToolbarLinkForm ul.optionset li {
float: left;
}
.thumbnailstrip {
overflow: auto;
white-space: nowrap;
float: left;
border: 1px solid #aaa;
}
.thumbnailstrip li {
float: left;
margin-right: 4px;
}
/* Misc Styling */
iframe {
border: none;
}
/* Content Panel Design
* This is our new right hand pane for inserting images and links etc
*/
#contentPanel {
font-size: 12px;
-moz-border-radius-topleft: 4px;
-moz-border-radius-bottomleft: 4px;
border: 1px solid #AAA;
}
#contentPanel .content {
overflow: auto;
}
#contentPanel div,
#contentPanel p#TargetBlank {
margin-left: 0;
}
#contentPanel .thumbnailstrip {
border: none;
width: 190px;
height: 120px;
overflow-y: auto;
margin-right: 0 !important;
}
#contentPanel .ui-dialog {
width: auto;
}
#contentPanel .thumbnailstrip h2 {
font-size: 1.1em;
margin: 0;
background: none;
color: #555;
height: auto;
}
#contentPanel select {
width: 186px;
padding: 1px 0;
font-size: 12px;
}
#contentPanel option {
font-size: 12px;
}
#contentPanel .middleColumn {
background:#E9E9E9 none repeat scroll 0%;
display:block;
padding:3px;
}
#contentPanel input.text {
border:1px solid #A7A7A7;
padding:3px;
width: 179px;
}
#contentPanel #Dimensions div.middleColumn {
background: none;
}
#contentPanel form {
display: none;
}
/* Image height and width inputs. We need to position them in the right places */
#contentPanel input#Form_EditorToolbarImageForm_Width,
#contentPanel input#Form_EditorToolbarFlashForm_Width {
width: 30px;
}
#contentPanel input#Form_EditorToolbarImageForm_Height,
#contentPanel input#Form_EditorToolbarFlashForm_Height {
width: 30px;
}
#Form_EditorToolbarImageForm #FolderImages,
#Form_EditorToolbarFlashForm #Flash {
margin: 5px;
}
#Form_EditorToolbarImageForm fieldset label.left,
#Form_EditorToolbarFlashForm fieldset label.left,
#Form_EditorToolbarLinkForm fieldset label.left {
float:left;
margin-left:0px;
color: #666;
line-height: 1.2;
font-size: 11px;
width: 190px;
}
#Form_EditorToolbarImageForm fieldset div.middleColumn,
#Form_EditorToolbarFlashForm fieldset div.middleColumn,
#Form_EditorToolbarLinkForm fieldset div.middleColumn {
float: left;
}
#Form_EditorToolbarLinkForm fieldset li div.middleColumn,
#Form_EditorToolbarFlashForm fieldset li div.middleColumn,
#Form_EditorToolbarImageForm fieldset li div.middleColumn {
float: none;
}
#Form_EditorToolbarImageForm fieldset div.fieldgroupField,
#Form_EditorToolbarFlashForm fieldset div.fieldgroupField,
#Form_EditorToolbarLinkForm fieldset div.fieldgroupField {
display: inline;
}
#Form_EditorToolbarLinkForm .buttonRefresh {
display:block;
border:1px solid #F0F0EE;
width:20px; height:20px;
float: left;
}
#Form_EditorToolbarLinkForm a.buttonRefreshHover {
border: 1px solid #0A246A;
background-color:#B2BBD0;
}
#Form_EditorToolbarLinkForm a.
.defaultSkin .mceButtonDisabled .mceIcon {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)}
#Form_EditorToolbarLinkForm a.buttonRefresh span {
display: block;
width: 20px;
height: 20px;
background: transparent url(../../cms/images/arrow_refresh.gif) no-repeat;
}
#Form_EditorToolbarLinkForm select.hasRefreshButton {
width: 160px;
}
#Form_EditorToolbarImageForm_Width,
#Form_EditorToolbarImageForm_Height,
#Form_EditorToolbarFlashForm_Width,
#Form_EditorToolbarFlashForm_Height {
outline: 3px solid #E9E9E9;
}
#Form_EditorToolbarImageForm #FolderImages img,
#Form_EditorToolbarFlashForm #Flash img {
border: 2px solid #e4e3e3;
}
#contentPanel div.Actions {
margin: 5px;
text-align: right;
}
#contentPanel #FolderImages a.selectedImage img,
#contentPanel #Flash a.selectedFlash img {
border: 2px solid #222;
}
/* going to use this */
#BlankImage {
text-indent: -9000px;
}
/**
* Messages (see sapphire/css/Form.css)
*/
.message {
margin: 1em 0;
padding: 0.5em;
font-weight: bold;
border: 1px black solid;
background-color: #B9FFB9;
border-color: #00FF00;
}
.message.notice {
background-color: #FCFFDF;
border-color: #FF9300;
}
.message.warning {
background-color: #FFD2A6;
border-color: #FF9300;
}
.message.bad {
background-color: #FF8080;
border-color: #FF0000;
}

View File

@ -1,31 +0,0 @@
/**
* SilverStripe specific styles that map to elements from the jQuery UI theme.
*
* This is not a fully functional tab module, but the file naming convention reflects
* the jQuery theme format
*
* @todo move generic tab styles into this file
* @todo add to silverstripe specific branch of /sapphire/thirdparty/jquery-ui-themes
*/
/* Caution! Ensure accessibility in print and other media types... */
@media projection, screen { /* Use class for showing/hiding tab content, so that visibility can be better controlled in different media types... */
.ui-tabs-hide {
display: none;
}
}
/* Hide useless elements in print layouts... */
@media print {
.ui-tabs-nav {
display: none;
}
}
/**
* stuff for search forms
*/
.loading {
cursor:hourglass;
}

View File

@ -1,34 +0,0 @@
* {
font-family: Arial, Helvetica, sans-serif;
font-size: 10px;
}
p, ul, td, th {
font-size: 12px;
}
h1, h2 {
line-height: 2;
color: #333;
}
h1 {
font-size: 20px;
}
h2 {
font-size: 18px;
}
a {
font-size: inherit;
color: #0074C6;
text-decoration: underline;
}
a:hover {
text-decoration: none;
background: #ccc;
}
a img {
border-style: none;
}

View File

@ -1,134 +0,0 @@
/**
* Clean up border overkill
*/
.ui-widget-header {
background: none;
border: none;
border-bottom: 1px solid #AAAAAA;
}
#Root.ui-widget-content {
border: 1px solid #AAAAAA;
}
.ui-widget-content {
border: none;
}
.ui-accordion .ui-accordion-content-active {
border: 1px solid #AAA;
border-top: none;
}
.CMSMain .ui-tabs .ui-corner-all {
-moz-border-radius: 0;
-webkit-border-radius: 0;
}
/* tabs 'reset' */
.CMSMain .ui-tabs,
.CMSMain .ui-tabs .ui-tabs-panel {
padding: 0;
}
#Root {
-moz-border-radius-bottomleft: 0;
-moz-border-radius-bottomright: 0;
-webkit-border-bottom-left-radius: 0;
-webkit-border-bottom-right-radius: 0;
}
.CMSMain .ui-tabs .ui-tabs-nav {
padding: 0.5em 0.5em 0 0.5em;
}
.CMSMain .ui-tabs .tab {
padding: 1em;
}
.CMSMain .ui-tabs .tab .ss-tabset {
margin: -0.5em -1em -1em -1em;
}
.CMSMain .ui-tabs .ui-tabs-nav li.ui-tabs-selected {
border-top:2px solid orange;
padding-bottom:0;
}
.ui-layout-pane-center,
.ui-layout-west {
background: none;
}
#treepanes .ui-widget input {
font-size: 1.1em;
}
#treepanes .ui-tabs-panel {
padding: 0.5em;
border-bottom: #AAA 1px solid;
}
#treepanes .ui-tabs-panel.ui-corner-bottom {
-moz-border-radius: 0;
-webkit-border-radius: 0;
}
#treepanes .ui-accordion-header.ui-corner-top,
#treepanes .ui-accordion-header.ui-corner-bottom {
-moz-border-radius-topleft: 0;
-webkit-border-top-left-radius: 0;
}
#treepanes .ui-corner-bottom {
-moz-border-radius-bottomleft: 0;
-moz-border-radius-bottomright: 4px;
-webkit-border-bottom-left-radius: 0;
-webkit-border-bottom-right-radius: 4px;
}
#treepanes .ui-accordion-header a {
font-size: 120%;
}
#treepanes.ui-accordion .ui-accordion-header .ui-icon {
right: 0.5em !important;
left: none !important;
}
#treepanes #TreeFooter {
border-bottom: 1px solid #ccc;
-moz-border-radius-bottomright: 4px;
-webkit-border-bottom-right-radius: 4px;
}
#treepanes .tree-panel {
padding: 0.5em;
background: #EEE;
}
.ui-tabs a {
font-size: 120%;
}
.right form {
margin: 1px 0 0 0;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
}
h2#Form_EditForm_WelcomeHeader,
p#WelcomeMessage {
margin: 1em 0 0 25px;
}
.ui-layout-resizer {
background: #ccdef3;
}
.ui-layout-resizer-open:hover,
.ui-layout-resizer-dragging {
background: #ccdef3 none repeat scroll 0 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

View File

@ -1,169 +0,0 @@
/**
* File: AssetTableField.js
*/
(function($) {
$.entwine('ss', function($){
/**
* Class: .AssetTableField
*/
$('.AssetTableField').entwine({
// Constructor: onmatch
onmatch: function() {
var self = this;
// search button
this.find('input#FileFilterButton').click(function(e) {
var btn = $(this);
$(this).addClass('loading');
self.refresh(function() {btn.removeClass('loading');});
return false;
});
// clear button
this.find('input#FileFilterClearButton').click(function(e) {
self.find('input#FileSearch').val('');
self.find('input#FileFilterButton').click();
return false;
});
// search field
this.find('input#FileSearch').keypress(function(e) {
if(e.keyCode == $.ui.keyCode.ENTER) {
self.find('input#FileFilterButton').click();
}
});
this._super();
},
/**
* Function: refresh
*
* Parameters:
* (Function) callback
*/
refresh: function(callback) {
var self = this;
this.load(
this.attr('href'),
this.find(':input').serialize(),
function(response, status, xmlhttp) {
Behaviour.apply(self[0], true);
if(callback) callback.apply(arguments);
}
);
}
});
/**
* Class: .AssetTableField :checkbox
*
* Checkboxes used to batch delete files
*/
$('.AssetTableField :checkbox').entwine({
// Function: onchange
onchange: function() {
var container = this.parents('.AssetTableField');
var input = container.find('input#deletemarked');
if(container.find(':input[name=Files\[\]]:checked').length) {
input.removeAttr('disabled');
} else {
input.attr('disabled', 'disabled');
}
}
})
/**
* Class: .AssetTableField input#deletemarked
*
* Batch delete files marked by checkboxes in the table.
* Refreshes the form field afterwards via ajax.
*/
$('.AssetTableField input#deletemarked').entwine({
// Constructor: onmatch
onmatch: function() {
this.attr('disabled', 'disabled');
this._super();
},
/**
* Function: onclick
*
* Parameters:
* (Event) e
*/
onclick: function(e) {
if(!confirm(ss.i18n._t('AssetTableField.REALLYDELETE'))) return false;
var container = this.parents('.AssetTableField');
var self = this;
this.addClass('loading');
$.post(
container.attr('href') + '/deletemarked',
this.parents('form').serialize(),
function(data, status) {
self.removeClass('loading');
container.refresh();
}
);
return false;
}
});
});
}(jQuery));
// TODO Implementation in Behaviour instead of entwine is necessary to overload TableListField
var AssetTableField = Class.create();
AssetTableField.applyTo('#Form_EditForm_Files');
AssetTableField.prototype = {
initialize: function() {
var rules = {};
rules['#'+this.id+' table.data a.deletelink'] = {onclick: this.deleteRecord.bind(this)};
Behaviour.register('ComplexTableField_'+this.id,rules);
},
deleteRecord: function(e) {
var img = Event.element(e);
var link = Event.findElement(e,"a");
var row = Event.findElement(e,"tr");
var self = this;
var linkCount = row.getElementsByClassName('linkCount')[0];
if(linkCount) linkCount = linkCount.innerHTML;
var confirmMessage = ss.i18n._t('TABLEFIELD.DELETECONFIRMMESSAGE', 'Are you sure you want to delete this record?');
if(linkCount && linkCount > 0) confirmMessage += '\nThere are ' + linkCount + ' page(s) that use this file, please review the list of pages on the Links tab of the file before continuing.';
// TODO ajaxErrorHandler and loading-image are dependent on cms, but formfield is in sapphire
var confirmed = confirm(confirmMessage);
if(confirmed)
{
img.setAttribute("src",'cms/images/network-save.gif'); // TODO doesn't work
jQuery.ajax({
'url': link.getAttribute("href"),
'method': 'post',
'data': {'forceajax': 1, 'SecurityID': $('SecurityID') ? $('SecurityID').value : null},
'success': function(){
Effect.Fade(
row,
{
afterFinish: function(obj) {
// remove row from DOM
obj.element.parentNode.removeChild(obj.element);
// recalculate summary if needed (assumes that TableListField.js is present)
// TODO Proper inheritance
if(self._summarise) self._summarise();
// custom callback
if(self.callback_deleteRecord) self.callback_deleteRecord(e);
}
}
);
},
'error': this.ajaxErrorHandler
});
}
Event.stop(e);
}
};

View File

@ -1,163 +0,0 @@
/**
* File: LeftAndMain.AddForm.js
*/
(function($) {
$.entwine('ss', function($){
/**
* Class: #Form_AddForm
*
* Simple form with a page type dropdown
* which creates a new page through #Form_EditForm and adds a new tree node.
*
* Requires:
* ss.i18n
* #Form_EditForm
*/
$('#Form_AddForm').entwine({
/**
* Variable: Tree
* (DOMElement)
*/
Tree: null,
/**
* Variable: OrigOptions
* (Array) Map of <option> values to an object of "title" and "value"
*/
OrigOptions: null,
/**
* Variable: NewPages
* (Array) Internal counter to create unique page identifiers prior to ajax saving
*/
NewPages: [],
/**
* Constructor: onmatch
*/
onmatch: function() {
var self = this, typeDropdown = this.find(':input[name=PageType]');
Observable.applyTo(this[0]);
var tree = $('#sitetree_ul');
this.setTree(tree);
// Event bindings
$(tree).bind('select_node.jstree', function(e, data) {self.refresh(data.rslt.obj);});
typeDropdown.bind('change', function(e) {self.refresh();});
// TODO Bind on tree initialization to set dropdown for selected node
// Store original page type options (they might get filtered to "allowed_children") later on
// TODO Better DOM element serialization (jQuery 1.4?)
var opts = {};
typeDropdown.find('option').each(function(el) {
opts[$(this).val()] = {html:$(this).html(), value: $(this).val()};
});
this.setOrigOptions(opts);
this._super();
},
/**
* Function: onsubmit
*
* Parameters:
* (Event) e
*/
onsubmit: function(e) {
var newPages = this.getNewPages(), tree = this.getTree(), node = $(tree).jstree('get_selected');
var parentID = (node.length) ? node.data('id') : 0;
// TODO: Remove 'new-' code http://open.silverstripe.com/ticket/875
// if(parentID && parentID.substr(0,3) == 'new') {
// alert(ss.i18n._t('CMSMAIN.WARNINGSAVEPAGESBEFOREADDING'));
// }
if(node && node.hasClass("nochildren")) {
alert(ss.i18n._t('CMSMAIN.CANTADDCHILDREN') );
}
// Optionally initalize the new pages tracker
if(!newPages[parentID] ) newPages[parentID] = 1;
// default to first button
var button = this.find(':submit:first');
button.addClass('loading');
// collect data and submit the form
var data = this.serializeArray();
data.push({name:'Suffix',value:newPages[parentID]++});
data.push({name:button.attr('name'),value:button.val()});
// TODO Should be set by hiddenfield already
jQuery('#Form_EditForm').entwine('ss').loadForm(
this.attr('action'),
function() {
// Tree updates are triggered by Form_EditForm load events
button.removeClass('loading');
},
{type: 'POST', data: data}
);
this.setNewPages(newPages);
return false;
},
/**
* Function: refresh
*
* Parameters:
* (DOMElement) selectedNode
*/
refresh: function(selectedNode) {
// Note: Uses siteTreeHints global
var tree = this.getTree(),
selectedNode = selectedNode || $(tree).jstree('get_selected')
origOptions = this.getOrigOptions(),
dropdown = this.find('select[name=PageType]');
// Clear all existing <option> elements
// (IE doesn't allow setting display:none on these elements)
dropdown.find('option').remove();
// Find allowed children through preferences on node or globally
var allowed = [];
if(selectedNode) {
if(selectedNode.hints && selectedNode.hints.allowedChildren) {
allowed = selectedNode.hints.allowedChildren;
} else {
// Fallback to globals
allowed = (typeof siteTreeHints !== 'undefined') ? siteTreeHints['Root'].allowedChildren : [];
}
// Re-add all allowed <option> to the dropdown
for(i=0;i<allowed.length;i++) {
var optProps = origOptions[allowed[i]];
if(optProps) dropdown.append($('<option value="' + optProps.value + '">' + optProps.html + '</option>'));
}
} else {
// No tree node selected, reset to original elements
$.each(origOptions, function(i, optProps) {
if(optProps) dropdown.append($('<option value="' + optProps.value + '">' + optProps.html + '</option>'));
});
}
// TODO Re-select the currently selected element
// Disable dropdown if no elements are selectable
if(allowed) dropdown.removeAttr('disabled');
else dropdown.attr('disabled', 'disabled');
// Set default child (optional)
if(selectedNode.hints && selectedNode.hints.defaultChild) {
dropdown.val(selectedNode.hints.defaultChild);
}
// Set parent node (fallback to root)
this.find(':input[name=ParentID]').val(selectedNode ? selectedNode.data('id') : 0);
}
});
});
}(jQuery));

View File

@ -1,355 +0,0 @@
/**
* File: LeftAndMain.BatchActions.js
*/
(function($) {
$.entwine('ss', function($){
/**
* Class: #Form_BatchActionsForm
*
* Batch actions which take a bunch of selected pages,
* usually from the CMS tree implementation, and perform serverside
* callbacks on the whole set. We make the tree selectable when the jQuery.UI tab
* enclosing this form is opened.
*
* Events:
* register - Called before an action is added.
* unregister - Called before an action is removed.
*/
$('#Form_BatchActionsForm').entwine({
/**
* Variable: Tree
* (DOMElement)
*/
Tree: null,
/**
* Variable: Actions
* (Array) Stores all actions that can be performed on the collected IDs as
* function closures. This might trigger filtering of the selected IDs,
* a confirmation message, etc.
*/
Actions: [],
/**
* Constructor: onmatch
*/
onmatch: function() {
var self = this, tree = $('#sitetree_ul');
this.setTree(tree);
tree.bind('check_node.jstree', function(e, data) {
self.serializeFromTree();
});
// if tab which contains this form is shown, make the tree selectable
$('#TreeActions').bind('tabsselect', function(e, ui) {
// if we are selecting another tab, or the panel is visible (meaning about to be closed),
// disable tree selection and reset any values. Otherwise enable it.
if($(ui.panel).attr('id') != 'TreeActions-batchactions' || $(ui.panel).is(':visible')) {
// @TODO: this is unneccessarily fired also when switching between two other tabs
tree.removeClass('multiple');
} else {
tree.addClass('multiple');
self.serializeFromTree();
}
});
this._super();
},
/**
* Function: register
*
* Parameters:
*
* (String) type - ...
* (Function) callback - ...
*/
register: function(type, callback) {
this.trigger('register', {type: type, callback: callback});
var actions = this.getActions();
actions[type] = callback;
this.setActions(actions);
},
/**
* Function: unregister
*
* Remove an existing action.
*
* Parameters:
*
* {String} type
*/
unregister: function(type) {
this.trigger('unregister', {type: type});
var actions = this.getActions();
if(actions[type]) delete actions[type];
this.setActions(actions);
},
/**
* Function: _isActive
*
* Determines if we should allow and track tree selections.
*
* Todo:
* Too much coupling with tabset
*
* Returns:
* (boolean)
*/
_isActive: function() {
return $('#TreeActions-batchactions').is(':visible');
},
/**
* Function: refreshSelected
*
* Ajax callbacks determine which pages is selectable in a certain batch action.
*
* Parameters:
* {Object} rootNode
*/
refreshSelected : function(rootNode) {
var self = this, st = this.getTree(), ids = this.getIDs(), allIds = [];
// Default to refreshing the entire tree
if(rootNode == null) rootNode = st;
for(var idx in ids) {
$($(st).getNodeByID(idx)).addClass('selected').attr('selected', 'selected');
}
$(rootNode).find('li').each(function() {
allIds.push($(this).data('id'));
// Disable the nodes while the ajax request is being processed
$(this).addClass('treeloading').setEnabled(false);
});
// Post to the server to ask which pages can have this batch action applied
var applicablePagesURL = this.find(':input[name=Action]').val() + '/applicablepages/?csvIDs=' + allIds.join(',');
jQuery.getJSON(applicablePagesURL, function(applicableIDs) {
// Set a CSS class on each tree node indicating which can be batch-actioned and which can't
jQuery(rootNode).find('li').each(function() {
$(this).removeClass('treeloading');
var id = $(this).data('id');
if(id == 0 || $.inArray(id, applicableIDs) >= 0) {
$(this).setEnabled(true);
} else {
// De-select the node if it's non-applicable
$(this).removeClass('selected').setEnabled(false);
}
});
self.serializeFromTree();
});
},
/**
* Function: serializeFromTree
*
* Returns:
* (boolean)
*/
serializeFromTree: function() {
var tree = this.getTree(), ids = tree.getSelectedIDs();
// if no IDs are selected, stop here. This is an implict way for the
// callback to cancel the actions
if(!ids || !ids.length) return false;
// write IDs to the hidden field
this.setIDs(ids);
return true;
},
/**
* Function: setIDS
*
* Parameters:
* {Array} ids
*/
setIDs: function(ids) {
if(ids) this.find(':input[name=csvIDs]').val(ids.join(','));
},
/**
* Function: getIDS
*
* Returns:
* {Array}
*/
getIDs: function() {
return this.find(':input[name=csvIDs]').val().split(',');
},
/**
* Function: onsubmit
*
* Parameters:
* (Event) e
*/
onsubmit: function(e) {
var ids = this.getIDs();
var tree = this.getTree();
// if no nodes are selected, return with an error
if(!ids || !ids.length) {
alert(ss.i18n._t('CMSMAIN.SELECTONEPAGE'));
return false;
}
// apply callback, which might modify the IDs
var type = this.find(':input[name=Action]').val();
if(this.getActions()[type]) ids = this.getActions()[type].apply(this, [ids]);
// write (possibly modified) IDs back into to the hidden field
this.setIDs(ids);
// Reset failure states
tree.find('li').removeClass('failed');
var button = this.find(':submit:first');
button.addClass('loading');
jQuery.ajax({
// don't use original form url
url: type,
type: 'POST',
data: this.serializeArray(),
complete: function(xmlhttp, status) {
button.removeClass('loading');
// status message
var msg = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText;
statusMessage(msg, (status == 'success') ? 'good' : 'bad');
},
success: function(data, status) {
var id;
// TODO This should use a more common serialization in a new tree library
if(data.modified) {
for(id in data.modified) {
tree.jstree('set_title', tree.getNodeByID(id), data.modified[id]['TreeTitle']);
}
}
if(data.deleted) {
for(id in data.deleted) {
var node = tree.getNodeByID(id);
// TODO Remove node
// if(node && node.parentTreeNode) node.parentTreeNode.removeTreeNode(node);
}
}
if(data.error) {
for(id in data.error) {
var node = tree.getNodeByID(id);
$(node).addClass('failed');
}
}
// Deselect all nodes
tree.find('li').removeClass('selected');
// reset selection state
// TODO Should unselect all selected nodes as well
tree.removeClass('multiple');
// TODO Fix up to work properly with jstree - unclear if state setting is still required in new design
// // Check if current page still exists, and refresh it.
// // Otherwise remove the current form
// var selectedNode = tree.jstree('get_selected');
// if(selectedNode) {
// var selectedNodeId = selectedNode.getID();
// if(data.modified[selectedNodeId]) {
// // only if the current page was modified
// tree.jstree('select_node', selectedNode);
// } else if(data.deleted[selectedNodeId]) {
// jQuery('#Form_EditForm').entwine('ss').removeForm();
// }
// } else {
// jQuery('#Form_EditForm').entwine('ss').removeForm();
// }
// close panel
// TODO Coupling with tabs
// jQuery('#TreeActions').tabs('select', -1);
},
dataType: 'json'
});
return false;
}
});
});
/**
* Class: #Form_BatchActionsForm :select[name=Action]
*/
$('#Form_BatchActionsForm select[name=Action]').entwine({
/**
* Function: onchange
*
* Parameters:
* (Event) e
*/
onchange: function(e) {
$(e.target.form).entwine('ss').refreshSelected();
}
});
$(document).ready(function() {
/**
* Publish selected pages action
*/
$('#Form_BatchActionsForm').entwine('ss').register('admin/batchactions/publish', function(ids) {
var confirmed = confirm(
"You have " + ids.length + " pages selected.\n\n"
+ "Do your really want to publish?"
);
return (confirmed) ? ids : false;
});
/**
* Unpublish selected pages action
*/
$('#Form_BatchActionsForm').entwine('ss').register('admin/batchactions/unpublish', function(ids) {
var confirmed = confirm(
"You have " + ids.length + " pages selected.\n\n"
+ "Do your really want to unpublish?"
);
return (confirmed) ? ids : false;
});
/**
* Delete selected pages action
*/
$('#Form_BatchActionsForm').entwine('ss').register('admin/batchactions/delete', function(ids) {
var confirmed = confirm(
"You have " + ids.length + " pages selected.\n\n"
+ "Do your really want to delete?"
);
return (confirmed) ? ids : false;
});
/**
* Delete selected pages from live action
*/
$('#Form_BatchActionsForm').entwine('ss').register('admin/batchactions/deletefromlive', function(ids) {
var confirmed = confirm(
"You have " + ids.length + " pages selected.\n\n"
+ "Do your really want to delete these pages from live?"
);
return (confirmed) ? ids : false;
});
});
})(jQuery);

View File

@ -1,382 +0,0 @@
/**
* File: LeftAndMain.EditForm.js
*/
(function($) {
$.entwine('ss', function($){
/**
* Class: #Form_EditForm
*
* Base edit form, provides ajaxified saving
* and reloading itself through the ajax return values.
* Takes care of resizing tabsets within the layout container.
* @name ss.Form_EditForm
* @require jquery.changetracker
*
* Events:
* ajaxsubmit - Form is about to be submitted through ajax
* validate - Contains validation result
* removeform - A form is about to be removed from the DOM
* load - Form is about to be loaded through ajax
*/
$('#Form_EditForm').entwine(/** @lends ss.Form_EditForm */{
/**
* Variable: PlaceholderHtml
* (String_ HTML text to show when no form content is chosen.
* Will show inside the <form> tag.
*/
PlaceholderHtml: '',
/**
* Variable: ChangeTrackerOptions
* (Object)
*/
ChangeTrackerOptions: {},
/**
* Constructor: onmatch
*/
onmatch: function() {
var self = this;
this._setupChangeTracker();
// Can't bind this through jQuery
window.onbeforeunload = function(e) {return self._checkChangeTracker(false);};
this._super();
},
/**
* Function: _setupChangeTracker
*/
_setupChangeTracker: function() {
// Don't bind any events here, as we dont replace the
// full <form> tag by any ajax updates they won't automatically reapply
this.changetracker(this.getChangeTrackerOptions());
},
/**
* Function: _checkChangeTracker
*
* Checks the jquery.changetracker plugin status for this form.
* Usually bound to window.onbeforeunload.
*
* Parameters:
* {boolean} isUnloadEvent - ..
*
* Returns:
* (String) Either a string with a confirmation message, or the result of a confirm() dialog,
* based on the isUnloadEvent parameter.
*/
_checkChangeTracker: function(isUnloadEvent) {
var self = this;
// @todo TinyMCE coupling
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
// check for form changes
if(self.is('.changed')) {
// returned string will trigger a confirm() dialog,
// but only if the method is triggered by an event
if(isUnloadEvent) {
return confirm(ss.i18n._t('LeftAndMain.CONFIRMUNSAVED'));
} else {
return ss.i18n._t('LeftAndMain.CONFIRMUNSAVEDSHORT');
}
}
},
/**
* Function: onsubmit
*
* Suppress submission unless it is handled through ajaxSubmit().
*/
onsubmit: function(e) {
this.ajaxSubmit();
return false;
},
/**
* Function: ajaxSubmit
*
* Parameters:
* {DOMElement} button - The pressed button (optional)
* {Function} callback - Called in complete() handler of jQuery.ajax()
* {Object} ajaxOptions - Object literal to merge into $.ajax() call
* {boolean} loadResponse - Render response through _loadResponse() (Default: true)
*
* Returns:
* (boolean)
*/
ajaxSubmit: function(button, callback, ajaxOptions, loadResponse) {
var self = this;
// look for save button
if(!button) button = this.find('.Actions :submit[name=action_save]');
// default to first button if none given - simulates browser behaviour
if(!button) button = this.find('.Actions :submit:first');
this.trigger('ajaxsubmit', {button: button});
// set button to "submitting" state
$(button).addClass('loading');
// @todo TinyMCE coupling
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
// validate if required
if(!this.validate()) {
// TODO Automatically switch to the tab/position of the first error
statusMessage("Validation failed.", "bad");
$(button).removeClass('loading');
return false;
}
// save tab selections in order to reconstruct them later
var selectedTabs = [];
this.find('.ss-tabset').each(function(i, el) {
if($(el).attr('id')) selectedTabs.push({id:$(el).attr('id'), selected:$(el).tabs('option', 'selected')});
});
// get all data from the form
var formData = this.serializeArray();
// add button action
formData.push({name: $(button).attr('name'), value:'1'});
jQuery.ajax(jQuery.extend({
url: this.attr('action'),
data: formData,
type: 'POST',
complete: function(xmlhttp, status) {
$(button).removeClass('loading');
// TODO This should be using the plugin API
self.removeClass('changed');
if(callback) callback(xmlhttp, status);
// pass along original form data to enable old/new comparisons
if(loadResponse !== false) {
self._loadResponse(xmlhttp.responseText, status, xmlhttp, formData);
}
// re-select previously saved tabs
$.each(selectedTabs, function(i, selectedTab) {
self.find('#' + selectedTab.id).tabs('select', selectedTab.selected);
});
},
dataType: 'html'
}, ajaxOptions));
return false;
},
/**
* Function: validate
*
* Hook in (optional) validation routines.
* Currently clientside validation is not supported out of the box in the CMS.
*
* Todo:
* Placeholder implementation
*
* Returns:
* {boolean}
*/
validate: function() {
var isValid = true;
this.trigger('validate', {isValid: isValid});
return isValid;
},
/**
* Function: loadForm
*
* Parameters:
* (String) url - ..
* (Function) callback - (Optional) Called after the form content as been loaded
* (Object) ajaxOptions - Object literal merged into the jQuery.ajax() call (Optional)
*
* Returns:
* (XMLHTTPRequest)
*/
loadForm: function(url, callback, ajaxOptions) {
var self = this;
// Alert when unsaved changes are present
if(this._checkChangeTracker(true) == false) return false;
// hide existing form - shown again through _loadResponse()
this.addClass('loading');
this.trigger('load', {url: url});
this.cleanup();
return jQuery.ajax(jQuery.extend({
url: url,
complete: function(xmlhttp, status) {
// TODO This should be using the plugin API
self.removeClass('changed');
self._loadResponse(xmlhttp.responseText, status, xmlhttp);
self.removeClass('loading');
if(callback) callback.apply(self, arguments);
},
dataType: 'html'
}, ajaxOptions));
},
/**
* Function: removeForm
*
* Remove everying inside the <form> tag
* with a custom HTML fragment. Useful e.g. for deleting a page in the CMS.
* Checks for unsaved changes before removing the form
*
* Parameters:
* {String} placeholderHtml - Short note why the form has been removed, displayed in <p> tags.
* Falls back to the default RemoveText() option (Optional)
*/
removeForm: function(placeholderHtml) {
if(!placeholderHtml) placeholderHtml = this.getPlaceholderHtml();
// Alert when unsaved changes are present
if(this._checkChangeTracker(true) == false) return;
this.trigger('removeform');
this.html(placeholderHtml);
// TODO This should be using the plugin API
this.removeClass('changed');
},
/**
* Function: cleanup
*
* Remove all the currently active TinyMCE editors.
* Note: Everything that calls this externally has an inappropriate coupling to TinyMCE.
*/
cleanup: function() {
if((typeof tinymce != 'undefined') && tinymce.EditorManager) {
var id;
for(id in tinymce.EditorManager.editors) {
tinymce.EditorManager.editors[id].remove();
}
tinymce.EditorManager.editors = {};
}
},
/**
* Function: _loadResponse
*
* Parameters:
* {String} data - Either HTML for straight insertion, or eval'ed JavaScript.
* If passed as HTML, it is assumed that everying inside the <form> tag is replaced,
* but the old <form> tag itself stays intact.
* {String} status
* {XMLHTTPRequest} xmlhttp - ..
* {Array} origData - The original submitted data, useful to do comparisons of changed
* values in new form output, e.g. to detect a URLSegment being changed on the serverside.
* Array in jQuery serializeArray() notation.
*/
_loadResponse: function(data, status, xmlhttp, origData) {
if(status == 'success') {
this.cleanup();
var html = data;
// Rewrite # links
html = html.replace(/(<a[^>]+href *= *")#/g, '$1' + window.location.href.replace(/#.*$/,'') + '#');
// Rewrite iframe links (for IE)
html = html.replace(/(<iframe[^>]*src=")([^"]+)("[^>]*>)/g, '$1' + $('base').attr('href') + '$2$3');
// Prepare iframes for removal, otherwise we get loading bugs
this.find('iframe').each(function() {
this.contentWindow.location.href = 'about:blank';
$(this).remove();
});
// update form content
if(html) {
this.html(html);
} else {
this.removeForm();
}
// @todo Coupling to avoid FOUC (entwine applies to late)
this.find('.ss-tabset').tabs();
this._setupChangeTracker();
// Optionally get the form attributes from embedded fields, see Form->formHtmlContent()
for(var overrideAttr in {'action':true,'method':true,'enctype':true,'name':true}) {
var el = this.find(':input[name='+ '_form_' + overrideAttr + ']');
if(el) {
this.attr(overrideAttr, el.val());
el.remove();
}
}
Behaviour.apply(); // refreshes ComplexTableField
// focus input on first form element
this.find(':input:visible:first').focus();
this.trigger('loadnewpage', {data: data, origData: origData});
}
// set status message based on response
var _statusMessage = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText;
if(this.hasClass('validationerror')) {
// TODO validation shouldnt need a special case
statusMessage(ss.i18n._t('ModelAdmin.VALIDATIONERROR', 'Validation Error'), 'bad');
}
}
});
/**
* Class: #Form_EditForm .Actions :submit
*
* All buttons in the right CMS form go through here by default.
* We need this onclick overloading because we can't get to the
* clicked button from a form.onsubmit event.
*/
$('#Form_EditForm .Actions :submit').entwine({
/**
* Function: onclick
*/
onclick: function(e) {
jQuery('#Form_EditForm').entwine('ss').ajaxSubmit(this);
return false;
}
});
/**
* Class: #Form_EditForm textarea.htmleditor
*
* Add tinymce to HtmlEditorFields within the CMS.
*/
$('#Form_EditForm textarea.htmleditor').entwine({
/**
* Constructor: onmatch
*/
onmatch : function() {
tinyMCE.execCommand("mceAddControl", true, this.attr('id'));
this.isChanged = function() {
return tinyMCE.getInstanceById(this.attr('id')).isDirty();
};
this.resetChanged = function() {
var inst = tinyMCE.getInstanceById(this.attr('id'));
if (inst) inst.startContent = tinymce.trim(inst.getContent({format : 'raw', no_events : 1}));
};
}
});
});
}(jQuery));

View File

@ -1,302 +0,0 @@
/**
* File: LeftAndMain.Tree.js
*/
(function($) {
$.entwine('ss', function($){
$('#sitetree_ul').entwine({
onmatch: function() {
this._super();
/**
* @todo Icon and page type hover support
* @todo Sorting of sub nodes (originally placed in context menu)
* @todo Refresh after language <select> change (with Translatable enabled)
* @todo Automatic load of full subtree via ajax on node checkbox selection (minNodeCount = 0)
* to avoid doing partial selection with "hidden nodes" (unloaded markup)
* @todo Add siteTreeHints to field (as "data-hints" attribute with serialized JSON instead of javascript global variable)
* @todo Disallow drag'n'drop when not matching "allowedChildren" or "allowedParents" (see siteTreeHints)
* @todo Disallow drag'n'drop when node has "noChildren" set (see siteTreeHints)
* @todo Disallow moving of pages marked as deleted
* @todo Enforce sitetreeHints rules on page creation ("allowedChildren", "noChildren") -
* most likely by server response codes rather than clientside
* @todo "defaultChild" when creating a page (sitetreeHints)
* @todo Duplicate page (originally located in context menu)
* @todo Update tree node title information and modified state after reordering (response is a JSON array)
*
* Tasks most likely not required after moving to a standalone tree:
*
* @todo Context menu - to be replaced by a bezel UI
* @todo Refresh form for selected tree node if affected by reordering (new parent relationship)
* @todo Cancel current form load via ajax when new load is requested (synchronous loading)
* @todo When new edit form is loaded, automatically: Select matching node, set correct parent,
* update icon and title
*/
var self = this;
this
.jstree({
'core': {
'initially_open': ['record-0'],
'animation': 0,
'html_titles': true
},
'html_data': {
// 'ajax' will be set on 'loaded.jstree' event
},
'ui': {
"select_limit" : 1,
'initially_select': [this.find('.current').attr('id')]
},
"crrm": {
'move': {
// Check if a node is allowed to be moved.
// Caution: Runs on every drag over a new node
'check_move': function(data) {
var movedNode = $(data.o), newParent = $(data.np),
isMovedOntoContainer = data.ot.get_container()[0] == data.np[0];
var isAllowed = (
// Don't allow moving the root node
movedNode.data('id') != 0
// Only allow moving node inside the root container, not before/after it
&& (!isMovedOntoContainer || data.p == 'inside')
);
return isAllowed;
}
}
},
'dnd': {
"drop_target" : false,
"drag_target" : false
},
'themes': {
'theme': 'apple'
},
// 'plugins': ['html_data', 'ui', 'dnd', 'crrm', 'themeroller']
'plugins': [
'html_data', 'ui', 'dnd', 'crrm', 'themes',
'checkbox' // checkboxes are hidden unless .multiple is set
]
})
.bind('loaded.jstree', function(e, data) {
// Add ajax settings after init period to avoid unnecessary initial ajax load
// of existing tree in DOM - see load_node_html()
data.inst._set_settings({'html_data': {'ajax': {
'url': self.data('url-tree'),
'data': function(node) {
var params = self.data('searchparams') || [];
// Avoid duplication of parameters
params = $.grep(params, function(n, i) {return (n.name != 'ID' && n.name != 'value');});
params.push({name: 'ID', value: $(node).data("id") ? $(node).data("id") : 0});
params.push({name: 'ajax', value: 1});
return params;
}
}}});
// Only show checkboxes with .multiple class
data.inst.hide_checkboxes();
})
.bind('before.jstree', function(e, data) {
if(data.func == 'start_drag') {
// Only allow drag'n'drop if it has been specifically enabled, or the tree is in search mode
if(!$('input[id=sortitems]').is(':checked') || self.data('searchparams')) {
e.stopImmediatePropagation();
return false;
}
}
if($.inArray(data.func, ['check_node', 'uncheck_node'])) {
var node = $(data.args[0]).parents('li:first');
if(node.hasClass('disabled')) {
e.stopImmediatePropagation();
return false;
}
}
})
// TODO Move to EditForm logic
.bind('select_node.jstree', function(e, data) {
var node = data.rslt.obj, loadedNodeID = $('#Form_EditForm :input[name=ID]').val()
// Don't allow checking disabled nodes
if($(node).hasClass('disabled')) return false;
// Don't allow reloading of currently selected node,
// mainly to avoid doing an ajax request on initial page load
if($(node).data('id') == loadedNodeID) return;
var url = $(node).find('a:first').attr('href');
if(url && url != '#') {
var xmlhttp = $('#Form_EditForm').loadForm(
url,
function(response) {}
);
} else {
$('#Form_EditForm').removeForm();
}
})
.bind('move_node.jstree', function(e, data) {
var movedNode = data.rslt.o, newParentNode = data.rslt.np, oldParentNode = data.inst._get_parent(movedNode);
var siblingIDs = $.map($(movedNode).siblings().andSelf(), function(el) {
return $(el).data('id');
});
$.ajax({
'url': self.data('url-savetreenode'),
'data': {
ID: $(movedNode).data('id'),
ParentID: $(newParentNode).data('id') || 0,
SiblingIDs: siblingIDs
}
});
});
$('#Form_EditForm').bind('loadnewpage', function(e, data) {
self._onLoadNewPage(e, data);
});
},
/**
* Function:
* search
*
* Parameters:
* (Object) data Pass empty data to cancel search
* (Function) callback Success callback
*/
search: function(params, callback) {
if(params) this.data('searchparams', params);
else this.removeData('searchparams');
this.jstree('refresh', -1, callback);
},
/**
* Function: getNodeByID
*
* Parameters:
* (Int) id
*
* Returns
* DOMElement
*/
getNodeByID: function(id) {
return this.jstree('get_node', this.find('*[data-id='+id+']'));
},
/**
* Assumes to be triggered by a form element with the following input fields:
* ID, ParentID, TreeTitle (or Title), ClassName
*/
_onLoadNewPage: function(e, eventData) {
var self = this;
// finds a certain value in an array generated by jQuery.serializeArray()
var findInSerializedArray = function(arr, name) {
for(var i=0; i<arr.length; i++) {
if(arr[i].name == name) return arr[i].value;
};
return false;
};
var id = $(e.target.ID).val();
// check if a form with a valid ID exists
if(id) {
var parentID = $(e.target.ParentID).val(),
parentNode = this.find('li[data-id='+parentID+']');
node = this.find('li[data-id='+id+']'),
title = $((e.target.TreeTitle) ? e.target.TreeTitle : e.target.Title).val(),
className = $(e.target.ClassName).val();
// set title (either from TreeTitle or from Title fields)
// Treetitle has special HTML formatting to denote the status changes.
if(title) this.jstree('rename_node', node, title);
// TODO Fix node icon setting
// // update icon (only if it has changed)
// if(className) this.setNodeIcon(id, className);
// check if node exists, might have been created instead
if(!node.length) {
this.jstree(
'create_node',
parentNode,
'inside',
{data: '', attr: {'class': className, 'data-id': id}},
function() {
var newNode = self.find('li[data-id='+id+']');
// TODO Fix hardcoded link
// TODO Fix replacement of jstree-icon inside <a> tag
newNode.find('a:first').html(title).attr('href', 'admin/show/'+id);
self.jstree('deselect_node', parentNode);
self.jstree('select_node', newNode);
}
);
// set current tree element
this.jstree('select_node', node);
}
// TODO Fix node parent setting
// // set correct parent (only if it has changed)
// if(parentID) this.setNodeParentID(id, jQuery(e.target.ParentID).val());
// TODO Fix doubleup when replacing page form with root form, reloads the old form over the root
// set current tree element regardless of wether the item was new
// this.jstree('select_node', node);
} else {
if(typeof eventData.origData != 'undefined') {
var node = this.find('li[data-id='+eventData.origData.ID+']');
if(node && node.data('id') != 0) this.jstree('delete_node', node);
}
}
}
});
});
$('#sitetree_ul.multiple').entwine({
onmatch: function() {
this._super();
this.jstree('show_checkboxes');
},
onunmatch: function() {
this._super();
this.jstree('hide_checkboxes');
},
/**
* Function: getSelectedIDs
*
* Returns:
* (Array)
*/
getSelectedIDs: function() {
return $.map($(this).jstree('get_checked'), function(el, i) {return $(el).data('id');});
},
});
$('#sitetree_ul li').entwine({
/**
* Function: setEnabled
*
* Parameters:
* (bool)
*/
setEnabled: function(bool) {
this.toggleClass('disabled', !(bool));
},
/**
* Function: getID
*
* Returns:
* (Number)
*/
getID: function() {
return this.data('id');
}
});
}(jQuery));

View File

@ -1,457 +0,0 @@
/**
* File: LeftAndMain.js
*/
/**
* Variable: ss_MainLayout
* jquery.layout Global variable so layout state management can pick it up.
*/
var ss_MainLayout;
(function($) {
$.entwine('ss', function($){
/**
* Position the loading spinner animation below the ss logo
*/
var positionLoadingSpinner = function() {
var offset = 120; // offset from the ss logo
var spinner = $('.ss-loading-screen .loading-animation');
var top = ($(window).height() - spinner.height()) / 2;
spinner.css('top', top + offset);
spinner.show();
}
$(window).bind('resize', positionLoadingSpinner).trigger('resize');
// setup jquery.entwine
$.entwine.warningLevel = $.entwine.WARN_LEVEL_BESTPRACTISE;
// global ajax error handlers
$.ajaxSetup({
error: function(xmlhttp, status, error) {
var msg = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText;
statusMessage(msg, 'bad');
}
});
/**
* Class: .LeftAndMain
*
* Main LeftAndMain interface with some control panel and an edit form.
*
* Events:
* ajaxsubmit - ...
* validate - ...
* loadnewpage - ...
*/
$('.LeftAndMain').entwine({
/**
* Variable: MainLayout
* (Object) Reference to jQuery.layout element
*/
MainLayout: null,
/**
* Variable: PingIntervalSeconds
* (Number) Interval in which /Security/ping will be checked for a valid login session.
*/
PingIntervalSeconds: 5*60,
/**
* Constructor: onmatch
*/
onmatch: function() {
var self = this;
// Remove loading screen
$('.ss-loading-screen').hide();
$('body').removeClass('stillLoading');
$(window).unbind('resize', positionLoadingSpinner);
// Layout
ss_MainLayout = this._setupLayout();
this.setMainLayout(ss_MainLayout);
layoutState.options.keys = "west.size,west.isClosed";
$(window).unload(function(){ layoutState.save('ss_MainLayout');});
this._setupPinging();
// HACK Delay resizing to give jquery-ui tabs a change their dimensions
// through dynamically added css classes
$(window).resize(function () {
var timerID = "timerLeftAndMainResize";
if (window[timerID]) clearTimeout(window[timerID]);
window[timerID] = setTimeout(function() {
self._resizeChildren();
}, 200);
});
$(window).resize();
// If tab has no nested tabs, set overflow to auto
$(this).find('.tab').not(':has(.tab)').css('overflow', 'auto');
// @todo Doesn't resize properly if the response doesn't contain a tabset (see above)
$('#Form_EditForm').bind('loadnewpage', function() {
// HACK Delay resizing to give jquery-ui tabs a change their dimensions
// through dynamically added css classes
var timerID = "timerLeftAndMainResize";
if (window[timerID]) clearTimeout(window[timerID]);
window[timerID] = setTimeout(function() {
self._resizeChildren();
}, 200);
});
this._super();
},
/**
* Function: _setupLayout
*
* Initialize jQuery layout manager with the following panes:
* - east: Tree, Page Version History, Site Reports
* - center: Form
* - west: "Insert Image", "Insert Link", "Insert Flash" panes
* - north: CMS area menu bar
* - south: "Page view", "profile" and "logout" links
*/
_setupLayout: function() {
var self = this;
var widthEast = this.find('.ui-layout-east').width();
var widthWest = this.find('.ui-layout-west').width();
// layout containing the tree, CMS menu, the main form etc.
var savedLayoutSettings = layoutState.load('ss_MainLayout');
var layoutSettings = jQuery.extend({
defaults: {
// TODO Reactivate once we have localized values
togglerTip_open: '',
togglerTip_closed: '',
resizerTip: '',
sliderTip: '',
onresize: function() {self._resizeChildren();},
onopen: function() {self._resizeChildren();}
},
north: {
slidable: false,
resizable: false,
size: this.find('.ui-layout-north').height(),
togglerLength_open: 0
},
south: {
slidable: false,
resizable: false,
size: this.find('.ui-layout-south').height(),
togglerLength_open: 0
},
west: {
size: (widthWest) ? widthWest : undefined,
fxName: "none"
},
east: {
initClosed: true,
// multiple panels which are triggered through tinymce buttons,
// so a user shouldn't be able to toggle this panel manually
initHidden: true,
spacing_closed: 0,
fxName: "none"
},
center: {}
}, savedLayoutSettings);
var layout = $('body').layout(layoutSettings);
// Adjust tree accordion etc. in left panel to work correctly
// with jQuery.layout (see http://layout.jquery-dev.net/tips.html#Widget_Accordion)
this.find("#treepanes").accordion({
fillSpace: true,
animated: false
});
return layout;
},
/**
* Function: _setupPinging
*
* This function is called by prototype when it receives notification that the user was logged out.
* It uses /Security/ping for this purpose, which should return '1' if a valid user session exists.
* It redirects back to the login form if the URL is either unreachable, or returns '0'.
*/
_setupPinging: function() {
var onSessionLost = function(xmlhttp, status) {
if(xmlhttp.status > 400 || xmlhttp.responseText == 0) {
// TODO will pile up additional alerts when left unattended
if(window.open('Security/login')) {
alert("Please log in and then try again");
} else {
alert("Please enable pop-ups for this site");
}
}
};
// setup pinging for login expiry
setInterval(function() {
jQuery.ajax({
url: "Security/ping",
global: false,
complete: onSessionLost
});
}, this.getPingIntervalSeconds() * 1000);
},
/**
* Function: _resizeChildren
*
* Resize elements in center panel
* to fit the boundary box provided by the layout manager.
*
* Todo:
* Replace with automated less ugly parent/sibling traversal
*/
_resizeChildren: function() {
$("#treepanes", this).accordion("resize");
$('#sitetree_and_tools', this).fitHeightToParent();
$('#contentPanel form', this).fitHeightToParent();
$('#contentPanel form fieldset', this).fitHeightToParent();
$('#contentPanel form fieldset .content', this).fitHeightToParent();
$('#Form_EditForm').fitHeightToParent();
$('#Form_EditForm fieldset', this).fitHeightToParent();
// Order of resizing is important: Outer to inner
// TODO Only supports two levels of tabs at the moment
$('#Form_EditForm fieldset > .ss-tabset', this).fitHeightToParent();
$('#Form_EditForm fieldset > .ss-tabset > .tab', this).fitHeightToParent();
$('#Form_EditForm fieldset > .ss-tabset > .tab > .ss-tabset', this).fitHeightToParent();
$('#Form_EditForm fieldset > .ss-tabset > .tab > .ss-tabset > .tab', this).fitHeightToParent();
}
});
/**
* Class: .LeftAndMain :submit, .LeftAndMain button, .LeftAndMain :reset
*
* Make all buttons "hoverable" with jQuery theming.
* Also sets the clicked button on a form submission, making it available through
* a new 'clickedButton' property on the form DOM element.
*/
$('.LeftAndMain :submit, .LeftAndMain button, .LeftAndMain :reset').entwine({
/**
* Constructor: onmatch
*/
onmatch: function() {
this.addClass(
'ui-state-default ' +
'ui-corner-all'
)
.hover(
function() {
$(this).addClass('ui-state-hover');
},
function() {
$(this).removeClass('ui-state-hover');
}
)
.focus(function() {
$(this).addClass('ui-state-focus');
})
.blur(function() {
$(this).removeClass('ui-state-focus');
})
.click(function() {
var form = this.form;
// forms don't natively store the button they've been triggered with
form.clickedButton = this;
// Reset the clicked button shortly after the onsubmit handlers
// have fired on the form
setTimeout(function() {form.clickedButton = null;}, 10);
});
this._super();
}
});
/**
* Class: #TreeActions
*
* Container for tree actions like "create", "search", etc.
*/
$('#TreeActions').entwine({
/**
* Constructor: onmatch
*
* Setup "create", "search", "batch actions" layers above tree.
* All tab contents are closed by default.
*/
onmatch: function() {
this.tabs({
collapsible: true,
selected: parseInt(jQuery.cookie('ui-tabs-TreeActions'), 10) || null,
cookie: { expires: 30, path: '/', name: 'ui-tabs-TreeActions' }
});
}
});
/**
* Class: a#EditMemberProfile
*
* Link for editing the profile for a logged-in member through a modal dialog.
*/
$('a#EditMemberProfile').entwine({
/**
* Constructor: onmatch
*/
onmatch: function() {
var self = this;
this.bind('click', function(e) {return self._openPopup();});
$('body').append(
'<div id="ss-ui-dialog">'
+ '<iframe id="ss-ui-dialog-iframe" '
+ 'marginWidth="0" marginHeight="0" frameBorder="0" scrolling="auto">'
+ '</iframe>'
+ '</div>'
);
var cookieVal = (jQuery.cookie) ? JSON.parse(jQuery.cookie('ss-ui-dialog')) : false;
$("#ss-ui-dialog").dialog(jQuery.extend({
autoOpen: false,
bgiframe: true,
modal: true,
height: 300,
width: 500,
ghost: true,
resizeStop: function(e, ui) {
self._resize();
self._saveState();
},
dragStop: function(e, ui) {
self._saveState();
},
// TODO i18n
title: 'Edit Profile'
}, cookieVal)).css('overflow', 'hidden');
$('#ss-ui-dialog-iframe').bind('load', function(e) {self._resize();});
},
/**
* Function: _openPopup
*/
_openPopup: function(e) {
$('#ss-ui-dialog-iframe').attr('src', this.attr('href'));
$("#ss-ui-dialog").dialog('open');
return false;
},
/**
* Function: _resize
*/
_resize: function() {
var iframe = $('#ss-ui-dialog-iframe');
var container = $('#ss-ui-dialog');
iframe.attr('width',
container.innerWidth()
- parseFloat(container.css('paddingLeft'))
- parseFloat(container.css('paddingRight'))
);
iframe.attr('height',
container.innerHeight()
- parseFloat(container.css('paddingTop'))
- parseFloat(container.css('paddingBottom'))
);
this._saveState();
},
/**
* Function: _saveState
*/
_saveState: function() {
var container = $('#ss-ui-dialog');
// save size in cookie (optional)
if(jQuery.cookie && container.width() && container.height()) {
jQuery.cookie(
'ss-ui-dialog',
JSON.stringify({
width: parseInt(container.width(), 10),
height: parseInt(container.height(), 10),
position: [
parseInt(container.offset().top, 10),
parseInt(container.offset().left, 10)
]
}),
{ expires: 30, path: '/'}
);
}
}
});
/**
* Class: #switchView a
*
* Updates the different stage links which are generated through
* the SilverStripeNavigator class on the serverside each time a form record
* is reloaded.
*/
$('#switchView').entwine({
onmatch: function() {
this._super();
$('#Form_EditForm').bind('loadnewpage delete', function(e) {
var updatedSwitchView = $('#AjaxSwitchView');
if(updatedSwitchView.length) {
$('#SwitchView').html(updatedSwitchView.html());
updatedSwitchView.remove();
}
});
}
});
/**
* Class: #switchView a
*
* Links for viewing the currently loaded page
* in different modes: 'live', 'stage' or 'archived'.
*
* Requires:
* jquery.metadata
*/
$('#switchView a').entwine({
/**
* Function: onclick
*/
onclick: function(e) {
// Open in popup
window.open($(e.target).attr('href'));
return false;
}
});
});
}(jQuery));
// Backwards compatibility
var statusMessage = function(text, type) {
jQuery.noticeAdd({text: text, type: type});
};
var errorMessage = function(text) {
jQuery.noticeAdd({text: text, type: 'error'});
};
returnFalse = function() {
return false;
};
/**
* Find and enable TinyMCE on all htmleditor fields
* Pulled in from old tinymce.template.js
*/
function nullConverter(url) {
return url;
};

View File

@ -1,32 +0,0 @@
/**
* File: MemberImportForm.js
*/
(function($) {
$.entwine('ss', function($){
/**
* Class: .import-form .advanced
*/
$('.import-form .advanced').entwine({
onmatch: function() {
this._super();
this.hide();
}
});
/**
* Class: .import-form a.toggle-advanced
*/
$('.import-form a.toggle-advanced').entwine({
/**
* Function: onclick
*/
onclick: function(e) {
this.parents('form:eq(0)').find('.advanced').toggle();
return false;
}
});
});
}(jQuery));

View File

@ -1,357 +0,0 @@
/**
* File: MemberTableField.js
*/
(function($) {
$.entwine('ss', function($){
/**
* Class: #Permissions .checkbox[value=ADMIN]
*
* Automatically check and disable all checkboxes if ADMIN permissions are selected.
* As they're disabled, any changes won't be submitted (which is intended behaviour),
* checking all boxes is purely presentational.
*/
$('#Permissions .checkbox[value=ADMIN]').entwine({
onmatch: function() {
this.toggleCheckboxes();
this._super();
},
/**
* Function: onclick
*/
onclick: function(e) {
this.toggleCheckboxes();
},
/**
* Function: toggleCheckboxes
*/
toggleCheckboxes: function() {
var self = this, checkboxes = this.parents('.field:eq(0)').find('.checkbox').not(this);
if(this.is(':checked')) {
checkboxes.each(function() {
$(this).data('SecurityAdmin.oldChecked', $(this).attr('checked'));
$(this).data('SecurityAdmin.oldDisabled', $(this).attr('disabled'));
$(this).attr('disabled', 'disabled');
$(this).attr('checked', 'checked');
});
} else {
checkboxes.each(function() {
$(this).attr('checked', $(this).data('SecurityAdmin.oldChecked'));
$(this).attr('disabled', $(this).data('SecurityAdmin.oldDisabled'));
});
}
}
});
});
}(jQuery));
/**
* Modified 2006-10-05, Ingo Schommer
* This is more or less a copy of Member.js, with additions and changes
* to match the switch from Member.php to MemberTableField.php all over the UI.
* Eventually it will replace Member.js (please remove this message then).
*/
// no confirm message for removal from a group
if(typeof(ComplexTableField) != 'undefined') {
ComplexTableField.prototype.deleteConfirmMessage = null;
}
/**
* Class: AjaxMemberLookup
*
* Auto-lookup on ajax fields
*/
AjaxMemberLookup = {
initialise : function() {
var div = document.createElement('div');
div.id = this.id + '_ac';
div.className = 'autocomplete';
this.parentNode.appendChild(div);
if(this.id) {
new Ajax.Autocompleter(this.id, div.id, 'admin/security/autocomplete/' + this.name, {
afterUpdateElement : this.afterAutocomplete.bind(this)
});
}
},
afterAutocomplete : function(field, selectedItem) {
var items = jQuery(selectedItem).data('fields'), form = jQuery(selectedItem).parents('form:first');
for(name in items) {
jQuery(form).find('input[name='+name+']').val(items[name]);
}
}
}
/**
* Class: MemberTableField
*/
MemberTableField = Class.create();
MemberTableField.applyTo('#Form_EditForm div.MemberTableField');
MemberTableField.prototype = {
initialize: function() {
Behaviour.register({
'#Form_EditForm div.MemberFilter input' : {
onkeypress : this.prepareSearch.bind(this)
},
'#Form_EditForm div.MemberTableField table.data tr.addtogrouprow input' : {
onkeypress : this.prepareAddToGroup.bind(this)
},
'#Form_EditForm div.MemberTableField table.data tr.addtogrouprow #Form_AddRecordForm_action_addtogroup' : {
onclick : this.prepareAddToGroup.bind(this)
},
'#Form_EditForm div.MemberTableField table.data tr.addtogrouprow td.actions input' : {
initialise: function() {
data = this.parentNode.parentNode.getElementsByTagName('input');
var i,item,error = [];
for(i=0;item=data[i];i++) {
item.originalSerialized = Form.Element.serialize(item);
}
},
onclick : this.addToGroup.bind(this)
},
//'#Form_EditForm div.MemberTableField input' : AjaxMemberLookup,
'#Form_EditForm' : {
changeDetection_fieldsToIgnore : {
'ctf[start]' : true,
'ctf[ID]' : true,
'MemberOrderByField' : true,
'MemberOrderByOrder' : true,
'MemberGroup' : true,
'MemberFilterButton' : true,
'MemberFieldName' : true,
'MemberDontShowPassword' : true,
'MemberSearch' : true
}
}
});
},
// prevent submission of wrong form-button (MemberFilterButton)
prepareAddToGroup: function(e) {
// IE6 doesnt send an event-object with onkeypress
var event = (e) ? e : window.event;
var keyCode = (event.keyCode) ? event.keyCode : event.which;
if(keyCode == Event.KEY_RETURN) {
var el = Event.element(event);
this.addToGroup(event);
Event.stop(event);
return false;
}
},
// prevent submission of wrong form-button (MemberFilterButton)
prepareSearch: function(e) {
// IE6 doesnt send an event-object with onkeypress
var event = (e) ? e : window.event;
var keyCode = (event.keyCode) ? event.keyCode : event.which;
if(keyCode == Event.KEY_RETURN) {
var el = Event.element(event);
$('MemberFilterButton').onclick(event);
Event.stop(event);
return false;
}
},
addToGroup: function(e) {
// only submit parts of the form
var data = this.parentNode.parentNode.getElementsByTagName('input');
var i,item,error = [];
var form = Event.findElement(e,"form");
for(i=0;item=data[i];i++) {
if(item.name == 'Email' && !item.value) error[error.length] = "Email";
if(item.name == 'Password' && !item.value) error[error.length] = "Password";
}
if(error.length > 0) {
alert('Please enter a ' + error.join(' and a ') + ' to add a member.');
} else {
updateURL = "";
updateURL += Event.findElement(e,"form").action;
// we can't set "fieldName" as a HiddenField because there might be multiple ComplexTableFields in a single EditForm-container
updateURL += "?fieldName="+$('MemberFieldName').value;
updateURL += "&action_callfieldmethod&methodName=addtogroup";
ajaxSubmitFieldSet(updateURL, data);
}
return false;
}
/*
initialise : function() {
this.headerMap = [];
var i, item, headers = this.getElementsByTagName('thead')[0].getElementsByTagName('tr')[0].getElementsByTagName('td');
for(i=0;item=headers[i];i++) {
this.headerMap[i] = item.className;
}
},
setRecordDetails : function(id, details, groupID) {
var row = document.getElementById('member-' + id);
if(row) {
var i, item, cells = row.getElementsByTagName('td');
for(i=0;item=cells[i];i++) {
if(details[this.headerMap[i]]) {
item.innerHTML = details[this.headerMap[i]];
}
}
} else {
this.createRecord(id, details, groupID);
}
},
createRecord : function (id, details, groupId) {
var row = document.createElement('tr');
row.id = 'member-' + id;
var i, cell, cellField;
for(i=0;cellField=this.headerMap[i];i++) {
cell = document.createElement('td')
if(details[cellField]) {
cell.innerHTML = details[cellField];
}
row.appendChild(cell);
}
// Add the delete icon
if(typeof groupId == 'undefined')
var groupId = $('Form_EditForm').elements.ID.value;
cell = document.createElement('td')
cell.innerHTML = '<a class="deletelink" href="admin/security/removememberfromgroup/' + groupId + '/' + id + '"><img src="cms/images/delete.gif" alt="delete" /></a>';
cell.getElementsByTagName('0');
row.appendChild(cell);
var tbody = this.getElementsByTagName('tbody')[0];
var addRow = document.getElementsByClassName('addrow',tbody)[0];
if(addRow) tbody.insertBefore(row, addRow);
else tbody.appendChild(row);
Behaviour.apply(row, true);
},
clearAddForm : function() {
var tbody = this.getElementsByTagName('tbody')[0];
var addRow = document.getElementsByClassName('addrow',tbody)[0];
if(addRow) {
var i,field,fields = addRow.getElementsByTagName('input');
for(i=0;field=fields[i];i++) {
if(field.type != 'hidden' && field.type != 'submit') field.value = '';
}
}
},
removeMember : function(memberID) {
var record;
if(record = $('member-' + memberID)) {
record.parentNode.removeChild(record);
}
}
*/
}
/**
* Class: MemberFilterButton
*/
MemberFilterButton = Class.create();
MemberFilterButton.applyTo('#MemberFilterButton');
MemberFilterButton.prototype = {
initialize: function() {
this.inputFields = new Array();
var childNodes = this.parentNode.parentNode.getElementsByTagName('input');
for( var index = 0; index < childNodes.length; index++ ) {
if( childNodes[index].tagName ) {
childNodes[index].resetChanged = function() { return false; }
childNodes[index].isChanged = function() { return false; }
this.inputFields.push( childNodes[index] );
}
}
childNodes = this.parentNode.getElementsByTagName('select');
for( var index = 0; index < childNodes.length; index++ ) {
if( childNodes[index].tagName ) {
childNodes[index].resetChanged = function() { return false; }
childNodes[index].field_changed = function() { return false; }
this.inputFields.push( childNodes[index] );
}
}
},
isChanged: function() {
return false;
},
onclick: function(e) {
if(!$('ctf-ID') || !$('MemberFieldName')) {
return false;
}
try {
var form = Event.findElement(e,"form");
var fieldName = $('MemberFieldName').value;
var fieldID = form.id + '_' + fieldName;
var updateURL = form.action + '/field/' + fieldName + '?ajax=1';
for( var index = 0; index < this.inputFields.length; index++ ) {
if( this.inputFields[index].tagName ) {
updateURL += '&' + this.inputFields[index].name + '=' + encodeURIComponent( this.inputFields[index].value );
}
}
updateURL += ($('SecurityID') ? '&SecurityID=' + $('SecurityID').value : '');
new Ajax.Updater( fieldID, updateURL, {
onComplete: function() {
Behaviour.apply($(fieldID), true);
},
onFailure: function( response ) {
errorMessage('Could not filter results: ' + response.responseText );
}
});
} catch(er) {
errorMessage('Error searching');
}
return false;
}
}
// has to be external from initialize() because otherwise request will double on each reload - WTF
Behaviour.register({
'#Form_EditForm div.MemberTableField table.data input.text' : AjaxMemberLookup
});
/**
* Post the given fields to the given url
*/
function ajaxSubmitFieldSet(href, fieldSet, extraData) {
// Build data
var i,field,data = "ajax=1";
for(i=0;field=fieldSet[i];i++) {
data += '&' + Form.Element.serialize(field);
}
if(extraData){
data += '&'+extraData;
}
// Send request
jQuery.ajax({
'url': href,
'method' : 'post',
'data' : data,
'success' : function(response) {
//alert(response.responseText);
Ajax.Evaluator(response);
},
'error' : function(response) {
alert(response.responseText);
//errorMessage('Error: ', response);
}
});
}

View File

@ -1,21 +0,0 @@
/**
* File: MemberTableField_popup.js
*/
/**
* Class: MemberTableFieldPopupForm
*/
MemberTableFieldPopupForm = Class.extend("ComplexTableFieldPopupForm");
MemberTableFieldPopupForm.prototype = {
initialize: function() {
this.ComplexTableFieldPopupForm.initialize();
Behaviour.register('MemberTableFieldPopupForm',{
"div.MemberTableField_Popup .Actions input.action": {
onclick: this.submitForm.bind(this)
}
});
}
}
MemberTableFieldPopupForm.applyTo('div.MemberTableField_Popup .Actions');

View File

@ -1,178 +0,0 @@
/**
* File: ModelAdmin.History.js
*/
(function($) {
$.entwine('ss', function($){
/**
* Class: .ModelAdmin
*
* A simple ajax browser history implementation tailored towards
* navigating through search results and different forms loaded into
* the ModelAdmin right panels. The logic listens to search and form loading
* events, keeps track of the loaded URLs, and will display graphical back/forward
* buttons where appropriate. A search action will cause the history to be reset.
*
* Note: The logic does not replay save operations or hook into any form actions.
*
* Available Events:
* - historyAdd
* - historyStart
* - historyGoFoward
* - historyGoBack
*
* Todo:
* Switch tab state when re-displaying search forms
* Reload search parameters into forms
*/
$('.ModelAdmin').entwine({
/**
* Variable: History
*/
History: [],
/**
* Variable: Future
*/
Future: [],
onmatch: function() {
var self = this;
this._super();
// generate markup
this.find('#right').prepend(
'<div class="historyNav">'
+ '<a href="#" class="back">&lt; ' + ss.i18n._t('ModelAdmin.HISTORYBACK', 'back') + '</a>'
+ '<a href="#" class="forward">' + ss.i18n._t('ModelAdmin.HISTORYFORWARD', 'forward') + ' &gt;</a>'
+ '</div>'
).find('.back,.forward').hide();
this.find('.historyNav .back').live('click', function() {
self.goBack();
return false;
});
this.find('.historyNav .forward').live('click', function() {
self.goForward();
return false;
});
},
/**
* Function: redraw
*/
redraw: function() {
this.find('.historyNav .forward').toggle(Boolean(this.getFuture().length > 0));
this.find('.historyNav .back').toggle(Boolean(this.getHistory().length > 1));
},
/**
* Function: startHistory
*
* Parameters:
* (String) url - ...
* (Object) data - ...
*/
startHistory: function(url, data) {
this.trigger('historyStart', {url: url, data: data});
this.setHistory([]);
this.addHistory(url, data);
},
/**
* Add an item to the history, to be accessed by goBack and goForward
*/
addHistory: function(url, data) {
this.trigger('historyAdd', {url: url, data: data});
// Combine data into URL
if(data) {
if(url.indexOf('?') == -1) url += '?' + $.param(data);
else url += '&' + $.param(data);
}
// Add to history
this.getHistory().push(url);
// Reset future
this.setFuture([]);
this.redraw();
},
/**
* Function: goBack
*/
goBack: function() {
if(this.getHistory() && this.getHistory().length) {
if(this.getFuture() == null) this.setFuture([]);
var currentPage = this.getHistory().pop();
var previousPage = this.getHistory()[this.getHistory().length-1];
this.getFuture().push(currentPage);
this.trigger('historyGoBack', {url:previousPage});
// load new location
$('#Form_EditForm').loadForm(previousPage);
this.redraw();
}
},
/**
* Function: goForward
*/
goForward: function() {
if(this.getFuture() && this.getFuture().length) {
if(this.getFuture() == null) this.setFuture([]);
var nextPage = this.getFuture().pop();
this.getHistory().push(nextPage);
this.trigger('historyGoForward', {url:nextPage});
// load new location
$('#Form_EditForm').loadForm(nextPage);
this.redraw();
}
}
});
/**
* Class: #SearchForm_holder form
*
* A search action will cause the history to be reset.
*/
$('#SearchForm_holder form').entwine({
onmatch: function() {
var self = this;
this.bind('beforeSubmit', function(e) {
$('.ModelAdmin').startHistory(
self.attr('action'),
self.serializeArray()
);
});
this._super();
}
});
/**
* Class: form[name=Form_ResultsForm] tbody td a
*
* We have to apply this to the result table buttons instead of the
* more generic form loading.
*/
$('form[name=Form_ResultsForm] tbody td a').entwine({
onclick: function(e) {
$('.ModelAdmin').addHistory(this.attr('href'));
}
});
});
})(jQuery);

View File

@ -1,216 +0,0 @@
/**
* File: ModelAdmin.js
*/
/**
* Javascript handlers for generic model admin.
*
* Most of the work being done here is intercepting clicks on form submits,
* and managing the loading and sequencing of data between the different panels of
* the CMS interface.
*
* @todo add live query to manage application of events to DOM refreshes
* @todo alias the $ function instead of literal jQuery
*/
(function($) {
$.entwine('ss', function($){
//////////////////////////////////////////////////////////////////
// Search form
//////////////////////////////////////////////////////////////////
/**
* Class: #ModelClassSelector select
*
* If a dropdown is used to choose between the classes, it is handled by this code
*/
$('#ModelClassSelector select').entwine({
onmatch: function() {
// Initialise the form by calling this onchange event straight away
this.change();
this._super();
},
/**
* Function: onchange
*
* Set up an onchange function to show the applicable form and hide all others
*/
onchange: function(e) {
this.find('option').each(function() {
var $form = $('#'+this.val());
if(this.val() == this.val()) $form.show();
else $form.hide();
});
}
});
/**
* Class: #SearchForm_holder form
*
* Submits a search filter query and attaches event handlers
* to the response table, excluding the import form because
* file ($_FILES) submission doesn't work using AJAX
*
* Note: This is used for Form_CreateForm and all Form_SearchForm_* variations
*/
$('#SearchForm_holder form').entwine({
/**
* Function: onsubmit
*/
onsubmit: function(e) {
// Import forms are processed without ajax
if(this.attr('id').match(/^Form_ImportForm/)) return true;
$('#contentPanel').closeRightPanel();
this.trigger('beforeSubmit');
var btn = $(this[0].clickedButton);
btn.addClass('loading');
$('#Form_EditForm').loadForm(
this.attr('action'),
function() {
btn.removeClass('loading');
},
{data: this.serialize()}
);
return false;
}
});
/**
* Class: a.form_frontend_function.toggle_result_assembly
*
* Column selection in search form
*/
$('a.form_frontend_function.toggle_result_assembly').entwine({
/**
* Function: onclick
*/
onclick: function(e) {
var toggleElement = $(this).next();
toggleElement.toggle();
return false;
}
});
/**
* Class: a.form_frontend_function.tick_all_result_assembly
*/
$('a.form_frontend_function.tick_all_result_assembly').entwine({
/**
* Function: onclick
*/
onclick: function(e) {
var resultAssembly = $(this).prevAll('div#ResultAssembly').find('ul li input');
resultAssembly.attr('checked', 'checked');
return false;
}
});
$('a.form_frontend_function.untick_all_result_assembly').entwine({
onclick: function(e) {
var resultAssembly = $(this).prevAll('div#ResultAssembly').find('ul li input');
resultAssembly.removeAttr('checked');
return false;
}
});
/**
* Class: .resultsTable tbody td
*
* Table record handler for search result record
*/
$('.resultsTable tbody td').entwine({
/**
* Function: onclick
*/
onclick: function(e) {
var firstLink = this.find('a[href]');
if(!firstLink) return;
$('#Form_EditForm').loadForm(firstLink.attr('href'));
return false;
}
});
/**
* Class: #Form_ManagedModelsSelect
*
* Add object button
*/
$('#Form_ManagedModelsSelect').entwine({
/**
* Function: onsubmit
*/
onsubmit: function(e) {
className = $('select option:selected', this).val();
requestPath = this.attr('action').replace('ManagedModelsSelect', className + '/add');
var $button = $(':submit', this);
$('#Form_EditForm').loadForm(
requestPath,
function() {
$button.removeClass('loading');
$button = null;
}
);
return false;
}
});
/**
* Class: #Form_EditForm input[name=action_doDelete]
*
* RHS panel Delete button
*/
$('#Form_EditForm input[name=action_doDelete]').entwine({
// Function: onclick
onclick: function(e) {
if(!confirm(ss.i18n._t('ModelAdmin.REALLYDELETE', 'Really delete?'))) {
this.removeClass('loading');
return false;
}
}
});
/**
* Class: .importSpec
*
* Toggle import specifications
*/
$('.importSpec').entwine({
onmatch: function() {
this.hide();
this.find('a.detailsLink').click(function() {
$('#' + $(this).attr('href').replace(/.*#/,'')).toggle();
return false;
});
this._super();
}
});
$('#contentPanel').entwine({
/**
* Close TinyMCE image, link or flash panel.
* this function is called everytime a new search, back or add new DataObject are clicked
**/
closeRightPanel: function(){
if($('#contentPanel').is(':visible')) {
$('#contentPanel').hide();
$('#Form_EditorToolbarImageForm').hide();
$('#Form_EditorToolbarFlashForm').hide();
$('#Form_EditorToolbarLinkForm').hide();
}
}
});
});
})(jQuery);

View File

@ -1,73 +0,0 @@
/**
* File: SecurityAdmin.js
*/
(function($) {
var refreshAfterImport = function(e) {
// Check for a message <div>, an indication that the form has been submitted.
var existingFormMessage = $($(this).contents()).find('.message');
if(existingFormMessage && existingFormMessage.html()) {
// Refresh member listing
var memberTableField = $(window.parent.document).find('#Form_EditForm_Members').get(0);
if(memberTableField) memberTableField.refresh();
// Refresh tree
var tree = $(window.parent.document).find('#sitetree').get(0);
if(tree) tree.reload();
}
};
/**
* Refresh the member listing every time the import iframe is loaded,
* which is most likely a form submission.
*/
$(window).bind('load', function(e) {
$('#MemberImportFormIframe,#GroupImportFormIframe').entwine({
onmatch: function() {
this._super();
// TODO entwine can't seem to bind to iframe load events
$(this).bind('load', refreshAfterImport);
}
});
})
/**
* Delete selected folders through "batch actions" tab.
*/
$(document).ready(function() {
$('#Form_BatchActionsForm').entwine('ss').register(
// TODO Hardcoding of base URL
'admin/security/batchactions/delete',
function(ids) {
var confirmed = confirm(
ss.i18n.sprintf(
ss.i18n._t('SecurityAdmin.BATCHACTIONSDELETECONFIRM'),
ids.length
)
);
return (confirmed) ? ids : false;
}
);
});
$.entwine('ss', function($){
/**
* Class: #Form_EditForm .Actions #Form_EditForm_action_addmember
*/
$('#Form_EditForm .Actions #Form_EditForm_action_addmember').entwine({
// Function: onclick
onclick: function(e) {
// CAUTION: Assumes that a MemberTableField-instance is present as an editing form
var t = $('#Form_EditForm_Members');
t[0].openPopup(
null,
$('base').attr('href') + t.find('a.addlink').attr('href'),
t.find('table')[0]
);
return false;
}
});
});
}(jQuery));

View File

@ -1,57 +0,0 @@
/**
* Fits an element's height to its parent by substracting
* all (visible) siblings heights from the element.
* Caution: This will set overflow: hidden on the parent
*
* Copyright 2009 Ingo Schommer, SilverStripe Ltd.
* Licensed under MIT License: http://www.opensource.org/licenses/mit-license.php
*
* @todo Implement selectors to ignore certain elements
*
* @author Ingo Schommer, SilverStripe Ltd.
* @version 0.1
*/
jQuery.fn.extend({
fitHeightToParent: function() {
return jQuery(this).each(function() {
var $this = jQuery(this);
var boxmodel = ['marginTop','marginBottom','paddingTop','paddingBottom','borderBottomWidth','borderTopWidth'];
// don't bother if element or parent arent visible,
// we won't get height readings
if($this.is(':visible') && $this.parent().is(':visible')) {
// we set overflow = hidden so that large children don't muck things up in IE6 box model
var origParentOverflow = $this.parent().css('overflow');
$this.parent().css('overflow', 'hidden');
// get height from parent without any margins as a starting point,
// and reduce any top/bottom paddings
var height = $this.parent().innerHeight()
- parseFloat($this.parent().css('paddingTop'))
- parseFloat($this.parent().css('paddingBottom'));
// substract height of any siblings of the current element
// including their margins/paddings/borders
$this.siblings(':visible').filter(function() {
// remove all absolutely positioned elements
return (jQuery(this).css('position') != 'absolute');
}).each(function() {
height -= jQuery(this).outerHeight(true);
});
// remove margins/paddings/borders on inner element
jQuery.each(boxmodel, function(i, name) {
height -= parseFloat($this.css(name)) || 0;
});
// set new height
$this.height(height);
// Reset overflow
$this.parent().css('overflow', origParentOverflow);
}
});
}
});

View File

@ -1,11 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript" src="jquery.fitheighttoparent.js"></script>
</head>
<body>
test
</body>
</html>

View File

@ -1,150 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script type='text/javascript' src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
<script type="text/javascript" src="../jquery.fitheighttoparent.js"></script>
<link rel="stylesheet" href="http://dev.jquery.com/view/trunk/qunit/testsuite.css" type="text/css" media="screen" />
<style type="text/css">
.outer {background: #333;}
.inner {background: #f0f;}
.sibling {background: #bbb;}
#test1-parent {height: 200px; margin: 10px 0; padding: 15px 10px; border: 5px solid #000;}
#test1-sibling-before {height: 20px; margin: 3px 0; padding: 2px 0;}
#test1-sibling-after {height: 30px; margin: 3px 0; padding: 2px 0;}
#test2-parent {height: 200px; margin: 10px 0; padding: 15px 10px; }
#test2-sibling-absolute {height: 20px; position: absolute;}
#test3-parent {height: 200px; margin: 10px 0; padding: 15px 10px; }
#test3-sibling {height: 20px;}
#test3-sibling-hidden {height: 30px; display: none;}
#test4-parent {height: 200px; margin: 10px 0; padding: 15px 10px; }
#test4-sibling {height: 20px;}
#test4 {margin: 15px 0; padding: 10px 0;border: 5px solid #000;}
#test5-parent {height: 200px; margin: 10px 0; padding: 15px 10px; }
#test5-sibling {height: 20px;}
#test5 {margin: 15px 0; padding: 10px 0;border: 5px solid #000;}
#test6-parent {height: 200px; margin: 10px 0; padding: 15px 10px; position: relative; width: 100%;}
#test6-sibling {height: 20px;}
#test6 {overflow: auto; }
</style>
<script>
$(document).ready(function(){
test("with inline siblings, margins/paddings/borders on parent", function() {
equals(
jQuery('#test1').fitHeightToParent().height(),
130
);
});
test("with absolute siblings", function() {
equals(
jQuery('#test2').fitHeightToParent().height(),
200
);
});
test("with hidden siblings", function() {
equals(
jQuery('#test3').fitHeightToParent().height(),
180
);
});
test("with margins/paddings/borders on resized element", function() {
equals(
jQuery('#test4').fitHeightToParent().height(),
120
);
});
test("form with fieldset", function() {
equals(
jQuery('#test5').fitHeightToParent().height(),
120
);
});
test("overflow auto with long inner element", function() {
equals(
jQuery('#test6').fitHeightToParent().height(),
180
);
});
});
</script>
</head>
<body>
<script type="text/javascript" src="http://jqueryjs.googlecode.com/svn/trunk/qunit/testrunner.js"></script>
<h1>jquery.fitheighttoparent unit test</h1>
<h2 id="banner"></h2>
<h2 id="userAgent"></h2>
<ol id="tests"></ol>
<div id="main"></div>
<div id="test1-parent" class="outer">
<div id="test1-sibling-before" class="sibling"></div>
<div id="test1" class="inner">
<p>test 1</p>
</div>
<div id="test1-sibling-after" class="sibling"></div>
</div>
<div id="test2-parent" class="outer">
<div id="test2-sibling-absolute" class="sibling"></div>
<div id="test2" class="inner">
<p>test 2</p>
</div>
</div>
<div id="test3-parent" class="outer">
<div id="test3-sibling" class="sibling"></div>
<div id="test3" class="inner">
<p>test 3</p>
</div>
<div id="test3-sibling-hidden" class="sibling"></div>
</div>
<div id="test4-parent" class="outer">
<div id="test4-sibling" class="sibling"></div>
<div id="test4" class="inner">
<p>test 4</p>
</div>
</div>
<form action="#" method="POST" id="test5-parent" class="outer">
<fieldset id="test5" class="inner">
<p>test 5</p>
<input type="text">
</fieldset>
<div id="test5-sibling" class="sibling"></div>
</form>
<div id="test6-parent" class="outer">
<div id="test6-sibling" class="sibling"></div>
<div id="test6" class="inner">
<p>test 6</p>
<p>Suspendisse vestibulum dignissim quam. Integer vel augue. Phasellus nulla purus, interdum ac, venenatis non, varius rutrum, leo. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis a eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Fusce magna mi, porttitor quis, convallis eget, sodales ac, urna. Phasellus luctus venenatis magna. Vivamus eget lacus. Nunc tincidunt convallis tortor. Duis eros mi, dictum vel, fringilla sit amet, fermentum id, sem. Phasellus nunc enim, faucibus ut, laoreet in, consequat id, metus. Vivamus dignissim. Cras lobortis tempor velit. Phasellus nec diam ac nisl lacinia tristique. Nullam nec metus id mi dictum dignissim. Nullam quis wisi non sem lobortis condimentum. Phasellus pulvinar, nulla non aliquam eleifend, tortor wisi scelerisque felis, in sollicitudin arcu ante lacinia leo.</p>
<p>
Suspendisse vestibulum dignissim quam. Integer vel augue. Phasellus nulla purus, interdum ac, venenatis non, varius rutrum, leo. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis a eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Fusce magna mi, porttitor quis, convallis eget, sodales ac, urna. Phasellus luctus venenatis magna. Vivamus eget lacus. Nunc tincidunt convallis tortor. Duis eros mi, dictum vel, fringilla sit amet, fermentum id, sem. Phasellus nunc enim, faucibus ut, laoreet in, consequat id, metus. Vivamus dignissim. Cras lobortis tempor velit. Phasellus nec diam ac nisl lacinia tristique. Nullam nec metus id mi dictum dignissim. Nullam quis wisi non sem lobortis condimentum. Phasellus pulvinar, nulla non aliquam eleifend, tortor wisi scelerisque felis, in sollicitudin arcu ante lacinia leo.</p>
<p>
Suspendisse vestibulum dignissim quam. Integer vel augue. Phasellus nulla purus, interdum ac, venenatis non, varius rutrum, leo. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis a eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Fusce magna mi, porttitor quis, convallis eget, sodales ac, urna. Phasellus luctus venenatis magna. Vivamus eget lacus. Nunc tincidunt convallis tortor. Duis eros mi, dictum vel, fringilla sit amet, fermentum id, sem. Phasellus nunc enim, faucibus ut, laoreet in, consequat id, metus. Vivamus dignissim. Cras lobortis tempor velit. Phasellus nec diam ac nisl lacinia tristique. Nullam nec metus id mi dictum dignissim. Nullam quis wisi non sem lobortis condimentum. Phasellus pulvinar, nulla non aliquam eleifend, tortor wisi scelerisque felis, in sollicitudin arcu ante lacinia leo.</p>
</div>
</div>
</body>
</html>

View File

@ -1,93 +0,0 @@
(function($) {
$.widget("ssui.titlebar", {
_create: function() {
this.originalTitle = this.element.attr('title');
var self = this;
var options = this.options;
var title = options.title || this.originalTitle || '&nbsp;';
var titleId = $.ui.dialog.getTitleId(this.element);
this.element.parent().addClass('ui-dialog');
var uiDialogTitlebar = this.element.
addClass(
'ui-dialog-titlebar ' +
'ui-widget-header ' +
'ui-corner-all ' +
'ui-helper-clearfix'
);
// By default, the
if(options.closeButton) {
var uiDialogTitlebarClose = $('<a href="#"/>')
.addClass(
'ui-dialog-titlebar-close ' +
'ui-corner-all'
)
.attr('role', 'button')
.hover(
function() {
uiDialogTitlebarClose.addClass('ui-state-hover');
},
function() {
uiDialogTitlebarClose.removeClass('ui-state-hover');
}
)
.focus(function() {
uiDialogTitlebarClose.addClass('ui-state-focus');
})
.blur(function() {
uiDialogTitlebarClose.removeClass('ui-state-focus');
})
.mousedown(function(ev) {
ev.stopPropagation();
})
.appendTo(uiDialogTitlebar);
var uiDialogTitlebarCloseText = (this.uiDialogTitlebarCloseText = $('<span/>'))
.addClass(
'ui-icon ' +
'ui-icon-closethick'
)
.text(options.closeText)
.appendTo(uiDialogTitlebarClose);
}
var uiDialogTitle = $('<span/>')
.addClass('ui-dialog-title')
.attr('id', titleId)
.html(title)
.prependTo(uiDialogTitlebar);
uiDialogTitlebar.find("*").add(uiDialogTitlebar).disableSelection();
},
destroy: function() {
this.element
.unbind('.dialog')
.removeData('dialog')
.removeClass('ui-dialog-content ui-widget-content')
.hide().appendTo('body');
(this.originalTitle && this.element.attr('title', this.originalTitle));
}
});
$.extend($.ssui.titlebar, {
version: "0.0.1",
options: {
title: '',
closeButton: false,
closeText: 'close'
},
uuid: 0,
getTitleId: function($el) {
return 'ui-dialog-title-' + ($el.attr('id') || ++this.uuid);
}
});
}(jQuery));

View File

@ -1,10 +0,0 @@
<div id="Logo" style="$LogoStyle">
<% if ApplicationLogoText %>
<a href="$ApplicationLink" target="_blank">$ApplicationLogoText</a><br />
<% end_if %>
</div>
<ul id="MainMenu">
<% control MainMenu %>
<li class="$LinkingMode" id="Menu-$Code"><a href="$Link">$Title</a></li>
<% end_control %>
</ul>

View File

@ -1,35 +0,0 @@
<% control EditorToolbar %>
<div class="mceToolbarExternal" id="mce_editor_toolbar">
<table width="100%" border="0">
<tbody>
<tr>
<td>
<!--
<a title="Jump to tool buttons - Alt+Q, Jump to editor - Alt-Z, Jump to element path - Alt-X" accesskey="q" href="#">
</a>
-->
<% control Buttons %>
<% if Type = button %>
<a href="#$Command">
<img width="20" height="20" class="mceButtonNormal" title="$Title" alt="$Title" src="$Icon" id="mce_editor_$IDSegment" />
</a>
<% else_if Type = dropdown %>
<select name="$Command" class="mceSelectList" id="mce_editor_$IDSegment">$Options</select>
<% else_if Type = separator %>
<img width="1" height="15" class="mceSeparatorLine" src="{$MceRoot}themes/advanced/images/separator.gif" alt="|" />
<% else_if Type = break %>
<br />
<% end_if %>
<% end_control %>
<br />
<!--<a onfocus="tinyMCE.getInstanceById('mce_editor_0').getWin().focus();" accesskey="z" href="#">
</a>-->
</td>
</tr>
</tbody>
</table>
</div>
<% end_control %>

View File

@ -1,3 +0,0 @@
$EditForm
<div class="notice-wrap"></div>

View File

@ -1,62 +0,0 @@
<div id="$id" class="$CSSClasses $extraClass field" href="$CurrentLink">
<div class="MemberFilter filterBox">
$SearchForm
</div>
<div id="MemberList">
<% if Markable %>
<% include TableListField_SelectOptions %>
<% end_if %>
<% include TableListField_PageControls %>
<table class="data">
<thead>
<tr>
<% if Markable %><th width="18">&nbsp;</th><% end_if %>
<% control Headings %>
<th class="$Name">$Title</th>
<% end_control %>
<% if Can(show) %><th width="18">&nbsp;</th><% end_if %>
<% if Can(edit) %><th width="18">&nbsp;</th><% end_if %>
<% if Can(delete) %><th width="18">&nbsp;</th><% end_if %>
</tr>
</thead>
<tfoot>
<% if Can(inlineadd) %>
<tr class="addtogrouprow">
<% if Markable %><td width="18">&nbsp;</dh><% end_if %>
$AddRecordForm.CellFields
<td class="actions" colspan="3">$AddRecordForm.CellActions</td>
</tr>
<% end_if %>
<tr style="display: none;">
<% if Markable %><td width="18">&nbsp;</td><% end_if %>
<td colspan="$ItemCount">
<input type="hidden" id="{$id}_PopupHeight" value="$PopupHeight" disabled="disabled"/>
<input type="hidden" id="{$id}_PopupWidth" value="$PopupWidth" disabled="disabled"/>
<a class="popuplink addlink" href="$AddLink" alt="add"><img src="cms/images/add.gif" alt="add" /></a><a class="popuplink addlink" href="$AddLink" alt="add"><% _t('ADDNEW','Add new',50,'Followed by a member type') %> $Title</a>
</td>
<% if Can(show) %><td width="18">&nbsp;</td><% end_if %>
<% if Can(edit) %><td width="18">&nbsp;</td><% end_if %>
<% if Can(delete) %><td width="18">&nbsp;</td><% end_if %>
</tr>
</tfoot>
<tbody>
<% if Items %>
<% control Items %>
<% include TableListField_Item %>
<% end_control %>
<% else %>
<tr class="notfound">
<% if Markable %><th width="18">&nbsp;</th><% end_if %>
<td colspan="$Headings.Count"><i><% _t('NOITEMSFOUND', 'No items found') %></i></td>
<% control Actions %><td width="18">&nbsp;</td><% end_control %>
</tr>
<% end_if %>
</tbody>
</table>
<div class="utility">
<% control Utility %>
<span class="item"><a href="$Link" target="_blank">$Title</a></span>
<% end_control %>
</div>
</div>
</div>

View File

@ -1,21 +0,0 @@
<div class="importSpec" id="SpecFor{$ModelName}">
<a href="#SpecDetailsFor{$ModelName}" class="detailsLink"><% sprintf(_t('IMPORTSPECLINK', 'Show Specification for %s'),$ModelName) %></a>
<div class="details" id="SpecDetailsFor{$ModelName}">
<h4><% sprintf(_t('IMPORTSPECTITLE', 'Specification for %s'),$ModelName) %></h4>
<h5><% _t('IMPORTSPECFIELDS', 'Database columns') %></h5>
<% control Fields %>
<dl>
<dt><em>$Name</em></dt>
<dd>$Description</dd>
</dl>
<% end_control %>
<h5><% _t('IMPORTSPECRELATIONS', 'Relations') %></h5>
<% control Relations %>
<dl>
<dt><em>$Name</em></dt>
<dd>$Description</dd>
</dl>
<% end_control %>
</div>
</div>

View File

@ -1,5 +0,0 @@
<% if Results %>
$Form
<% else %>
<p><% sprintf(_t('ModelAdmin.NORESULTS', 'No results'), $ModelPluralName) %></p>
<% end_if %>

View File

@ -1,26 +0,0 @@
<div id="SearchForm_holder" class="leftbottom ss-tabset ui-layout-content">
<% if SearchClassSelector = tabs %>
<ul>
<% control ModelForms %>
<li class="$FirstLast"><a id="tab-ModelAdmin_$Title.HTMLATT" href="#{$Form.Name}_$ClassName">$Title</a></li>
<% end_control %>
</ul>
<% end_if %>
<% if SearchClassSelector = dropdown %>
<p id="ModelClassSelector">
Search for:
<select>
<% control ModelForms %>
<option value="{$Form.Name}_$ClassName">$Title</option>
<% end_control %>
</select>
</p>
<% end_if %>
<% control ModelForms %>
<div class="tab" id="{$Form.Name}_$ClassName">
$Content
</div>
<% end_control %>
</div>

View File

@ -1,45 +0,0 @@
<div id="treepanes">
<h3>
<a href="#"><% _t('SECGROUPS','Security Groups') %></a>
</h3>
<div>
<div id="TreeActions">
<ul>
<li>
<a href="#TreeActions-create" id="TreeActions-create-btn">
<% _t('CREATE','Create',PR_HIGH) %>
</a>
</li>
<li>
<a href="#TreeActions-batchactions" id="batchactions">
<% _t('BATCHACTIONS','Batch Actions',PR_HIGH) %>
</a>
</li>
</ul>
<div id="TreeActions-create">
$AddForm
</div>
<div id="TreeActions-batchactions">
$BatchActionsForm
</div>
</div>
<div class="checkboxAboveTree">
<input type="checkbox" id="sortitems" />
<label for="sortitems">
<% _t('ENABLEDRAGGING','Allow drag &amp; drop reordering', PR_HIGH) %>
</label>
</div>
<div id="sitetree_ul" data-url-tree="$Link(getsubtree)" data-url-savetreenode="$Link(savetreenode)" class="jstree jstree-apple">
$SiteTreeAsUL
</div>
</div>
</div>

View File

@ -1,60 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-language" content="$i18nLocale" />
<% base_tag %>
<title>$ApplicationName | $SectionTitle</title>
</head>
<body class="stillLoading $CSSClasses">
<div class="ss-loading-screen">
<div class="loading-logo" style="background-image: url($LoadingImage);">
<img class="loading-animation" src="cms/images/spinner.gif" alt="<% _t('LOADING','Loading...',PR_HIGH) %>" />
<noscript><p class="nojs-warning"><span class="message notice"><% _t('REQUIREJS','The CMS requires that you have JavaScript enabled.',PR_HIGH) %></span></p></noscript>
</div>
</div>
<div class="ui-layout-north ss-cms-top-menu">
$CMSTopMenu
</div>
<div class="ui-layout-west">
$Left
</div>
<div class="ui-layout-center right" id="right">
$Right
</div>
<div class="ui-layout-east" id="contentPanel">
<% control EditorToolbar %>
$ImageForm
$LinkForm
$FlashForm
<% end_control %>
</div>
<div class="ui-layout-south ss-cms-bottom-bar">
<div class="holder">
<div id="logInStatus">
<a href="$ApplicationLink" title="<% _t('SSWEB','Silverstripe Website') %>">$ApplicationName</a>
<% if CMSVersion %>-&nbsp;
<abbr style="border-style: none" title="<% _t('APPVERSIONTEXT1',"This is the") %> $ApplicationName <% _t('APPVERSIONTEXT2',"version that you are currently running, technically it's the CVS branch") %>">$CMSVersion</abbr>
<% end_if %>
&nbsp;&nbsp;
<% control CurrentMember %>
<% _t('LOGGEDINAS','Logged in as') %> <strong><% if FirstName && Surname %>$FirstName $Surname<% else_if FirstName %>$FirstName<% else %>$Email<% end_if %></strong> | <a href="{$AbsoluteBaseURL}admin/myprofile" id="EditMemberProfile"><% _t('EDITPROFILE','Profile') %></a> | <a href="Security/logout" id="LogoutLink"><% _t('LOGOUT','Log out') %></a>
<% end_control %>
</div>
<div id="switchView" class="bottomTabs">
<% if ShowSwitchView %>
<div class="blank"> <% _t('VIEWPAGEIN','Page view:') %> </div>
<span id="SwitchView">$SwitchView</span>
<% end_if %>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,13 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<% base_tag %>
<title>SilverStripe CMS - $Title</title>
</head>
<body class="$CSSClasses" onblur="window.close()">
$PrintForm
</body>
</html>

View File

@ -1,12 +0,0 @@
<% if CreateForm %>
<h3><% _t('ADDLISTING','Add') %></h3>
$CreateForm
<% end_if %>
<h3><% _t('SEARCHLISTINGS','Search') %></h3>
$SearchForm
<% if ImportForm %>
<h3><% _t('IMPORT_TAB_HEADER', 'Import') %></h3>
$ImportForm
<% end_if %>

View File

@ -1,79 +0,0 @@
<?php
/**
* @package cms
* @subpackage tests
*/
class CMSMenuTest extends SapphireTest implements TestOnly {
public function testBasicMenuHandling() {
// Clear existing menu
CMSMenu::clear_menu();
$menuItems = CMSMenu::get_menu_items();
$this->assertTrue((empty($menuItems)), 'Menu can be cleared');
// Add a controller to the menu and check it is as expected
CMSMenu::add_controller('CMSMain');
$menuItems = CMSMenu::get_menu_items();
$menuItem = $menuItems['CMSMain'];
$this->assertType('CMSMenuItem', $menuItem, 'Controller menu item is of class CMSMenuItem');
$this->assertEquals($menuItem->url, singleton('CMSMain')->Link(), 'Controller menu item has the correct link');
$this->assertEquals($menuItem->controller, 'CMSMain', 'Controller menu item has the correct controller class');
$this->assertEquals($menuItem->priority, singleton('CMSMain')->stat('menu_priority'), 'Controller menu item has the correct priority');
CMSMenu::clear_menu();
// Add a link to the menu
CMSMenu::add_link('LinkCode', 'link title', 'http://www.example.com');
$menuItems = CMSMenu::get_menu_items();
$menuItem = $menuItems['LinkCode'];
$this->assertType('CMSMenuItem', $menuItem, 'Link menu item is of class CMSMenuItem');
$this->assertEquals($menuItem->title, 'link title', 'Link menu item has the correct title');
$this->assertEquals($menuItem->url,'http://www.example.com', 'Link menu item has the correct link');
$this->assertNull($menuItem->controller, 'Link menu item has no controller class');
$this->assertEquals($menuItem->priority, -1, 'Link menu item has the correct priority');
CMSMenu::clear_menu();
}
public function testCmsClassDetection() {
// Get CMS classes and check that:
// 1.) CMSMain is included
// 2.) LeftAndMain & ModelAdmin are excluded
$cmsClasses = CMSMenu::get_cms_classes();
$this->assertContains('CMSMain', $cmsClasses, 'CMSMain included in valid CMS Classes');
$this->assertNotContains('LeftAndMain', $cmsClasses, 'LeftAndMain not included in valid CMS Classes');
$this->assertNotContains('ModelAdmin', $cmsClasses, 'LeftAndMain not included in valid CMS Classes');
}
public function testAdvancedMenuHandling() {
// Populate from CMS Classes, check for existance of CMSMain
CMSMenu::clear_menu();
CMSMenu::populate_menu();
$menuItem = CMSMenu::get_menu_item('CMSMain');
$this->assertType('CMSMenuItem', $menuItem, 'CMSMain menu item exists');
$this->assertEquals($menuItem->url, singleton('CMSMain')->Link(), 'Menu item has the correct link');
$this->assertEquals($menuItem->controller, 'CMSMain', 'Menu item has the correct controller class');
$this->assertEquals(
$menuItem->priority,
singleton('CMSMain')->stat('menu_priority'),
'Menu item has the correct priority'
);
// Check that menu order is correct by priority
// Note this will break if populate_menu includes normal links (ie, as not controller)
$menuItems = CMSMenu::get_menu_items();
$priority = 9999; // ok, *could* be set larger, but shouldn't need to be!
foreach($menuItems as $menuItem) {
$this->assertEquals(
$menuItem->priority,
singleton($menuItem->controller)->stat('menu_priority'),
"Menu item $menuItem->title has the correct priority"
);
$this->assertLessThanOrEqual($priority, $menuItem->priority, 'Menu item is of lower or equal priority');
}
}
}

View File

@ -1,176 +0,0 @@
<?php
/**
* @package cms
* @subpackage tests
*/
class LeftAndMainTest extends FunctionalTest {
static $fixture_file = 'cms/tests/CMSMainTest.yml';
function setUp() {
parent::setUp();
// @todo fix controller stack problems and re-activate
//$this->autoFollowRedirection = false;
CMSMenu::populate_menu();
}
public function testSaveTreeNodeSorting() {
$this->loginWithPermission('ADMIN');
$rootPages = DataObject::get('SiteTree', '"ParentID" = 0'); // implicitly sorted
$siblingIDs = $rootPages->column('ID');
$page1 = $rootPages->offsetGet(0);
$page2 = $rootPages->offsetGet(1);
$page3 = $rootPages->offsetGet(2);
// Move page2 before page1
$siblingIDs[0] = $page2->ID;
$siblingIDs[1] = $page1->ID;
$data = array(
'SiblingIDs' => $siblingIDs,
'ID' => $page2->ID,
'ParentID' => 0
);
$response = $this->post('admin/savetreenode', $data);
$this->assertEquals(200, $response->getStatusCode());
$page1 = DataObject::get_by_id('SiteTree', $page1->ID, false);
$page2 = DataObject::get_by_id('SiteTree', $page2->ID, false);
$page3 = DataObject::get_by_id('SiteTree', $page3->ID, false);
$this->assertEquals(2, $page1->Sort, 'Page1 is sorted after Page2');
$this->assertEquals(1, $page2->Sort, 'Page2 is sorted before Page1');
$this->assertEquals(3, $page3->Sort, 'Sort order for other pages is unaffected');
}
public function testSaveTreeNodeParentID() {
$this->loginWithPermission('ADMIN');
$page1 = $this->objFromFixture('Page', 'page1');
$page2 = $this->objFromFixture('Page', 'page2');
$page3 = $this->objFromFixture('Page', 'page3');
$page31 = $this->objFromFixture('Page', 'page31');
$page32 = $this->objFromFixture('Page', 'page32');
// Move page2 into page3, between page3.1 and page 3.2
$siblingIDs = array(
$page31->ID,
$page2->ID,
$page32->ID
);
$data = array(
'SiblingIDs' => $siblingIDs,
'ID' => $page2->ID,
'ParentID' => $page3->ID
);
$response = $this->post('admin/savetreenode', $data);
$this->assertEquals(200, $response->getStatusCode());
$page2 = DataObject::get_by_id('SiteTree', $page2->ID, false);
$page31 = DataObject::get_by_id('SiteTree', $page31->ID, false);
$page32 = DataObject::get_by_id('SiteTree', $page32->ID, false);
$this->assertEquals($page3->ID, $page2->ParentID, 'Moved page gets new parent');
$this->assertEquals(1, $page31->Sort, 'Children pages before insertaion are unaffected');
$this->assertEquals(2, $page2->Sort, 'Moved page is correctly sorted');
$this->assertEquals(3, $page32->Sort, 'Children pages after insertion are resorted');
}
/**
* Test that CMS versions can be interpreted appropriately
*/
public function testCMSVersion() {
$l = new LeftAndMain();
$this->assertEquals("2.4", $l->versionFromVersionFile(
'$URL: http://svn.silverstripe.com/open/modules/cms/branches/2.4/silverstripe_version $'));
$this->assertEquals("2.2.0", $l->versionFromVersionFile(
'$URL: http://svn.silverstripe.com/open/modules/cms/tags/2.2.0/silverstripe_version $'));
$this->assertEquals("trunk", $l->versionFromVersionFile(
'$URL: http://svn.silverstripe.com/open/modules/cms/trunk/silverstripe_version $'));
$this->assertEquals("2.4.0-alpha1", $l->versionFromVersionFile(
'$URL: http://svn.silverstripe.com/open/modules/cms/tags/alpha/2.4.0-alpha1/silverstripe_version $'));
$this->assertEquals("2.4.0-beta1", $l->versionFromVersionFile(
'$URL: http://svn.silverstripe.com/open/modules/cms/tags/beta/2.4.0-beta1/silverstripe_version $'));
$this->assertEquals("2.4.0-rc1", $l->versionFromVersionFile(
'$URL: http://svn.silverstripe.com/open/modules/cms/tags/rc/2.4.0-rc1/silverstripe_version $'));
}
/**
* Check that all subclasses of leftandmain can be accessed
*/
public function testLeftAndMainSubclasses() {
$adminuser = $this->objFromFixture('Member','admin');
$this->session()->inst_set('loggedInAs', $adminuser->ID);
$menuItems = singleton('CMSMain')->MainMenu();
foreach($menuItems as $menuItem) {
$link = $menuItem->Link;
// don't test external links
if(preg_match('/^https?:\/\//',$link)) continue;
$response = $this->get($link);
$this->assertType('SS_HTTPResponse', $response, "$link should return a response object");
$this->assertEquals(200, $response->getStatusCode(), "$link should return 200 status code");
// Check that a HTML page has been returned
$this->assertRegExp('/<html[^>]*>/i', $response->getBody(), "$link should contain <html> tag");
$this->assertRegExp('/<head[^>]*>/i', $response->getBody(), "$link should contain <head> tag");
$this->assertRegExp('/<body[^>]*>/i', $response->getBody(), "$link should contain <body> tag");
}
$this->session()->inst_set('loggedInAs', null);
}
function testCanView() {
$adminuser = $this->objFromFixture('Member', 'admin');
$assetsonlyuser = $this->objFromFixture('Member', 'assetsonlyuser');
$allcmssectionsuser = $this->objFromFixture('Member', 'allcmssectionsuser');
// anonymous user
$this->session()->inst_set('loggedInAs', null);
$menuItems = singleton('LeftAndMain')->MainMenu();
$this->assertEquals(
$menuItems->column('Code'),
array(),
'Without valid login, members cant access any menu entries'
);
// restricted cms user
$this->session()->inst_set('loggedInAs', $assetsonlyuser->ID);
$menuItems = singleton('LeftAndMain')->MainMenu();
$this->assertEquals(
$menuItems->column('Code'),
array('AssetAdmin','Help'),
'Groups with limited access can only access the interfaces they have permissions for'
);
// all cms sections user
$this->session()->inst_set('loggedInAs', $allcmssectionsuser->ID);
$menuItems = singleton('LeftAndMain')->MainMenu();
$requiredSections = array('CMSMain','AssetAdmin','SecurityAdmin','Help');
$this->assertEquals(
array_diff($requiredSections, $menuItems->column('Code')),
array(),
'Group with CMS_ACCESS_LeftAndMain permission can access all sections'
);
// admin
$this->session()->inst_set('loggedInAs', $adminuser->ID);
$menuItems = singleton('LeftAndMain')->MainMenu();
$this->assertContains(
'CMSMain',
$menuItems->column('Code'),
'Administrators can access CMS'
);
$this->assertContains(
'AssetAdmin',
$menuItems->column('Code'),
'Administrators can access Assets'
);
$this->session()->inst_set('loggedInAs', null);
}
}

View File

@ -1,139 +0,0 @@
<?php
/**
* @package cms
* @subpackage tests
*/
class MemberTableFieldTest extends FunctionalTest {
static $fixture_file = 'cms/tests/MemberTableFieldTest.yml';
function testLimitsToMembersInGroup() {
$member1 = $this->objFromFixture('Member', 'member1');
$member2 = $this->objFromFixture('Member', 'member2');
$member3 = $this->objFromFixture('Member', 'member3');
$group1 = $this->objFromFixture('Group', 'group1');
$tf = new MemberTableField(
$this,
"Members",
$group1
);
$members = $tf->sourceItems();
$this->assertContains($member1->ID, $members->column('ID'),
'Members in the associated group are listed'
);
$this->assertContains($member2->ID, $members->column('ID'),
'Members in children groups are listed as well'
);
$this->assertNotContains($member3->ID, $members->column('ID'),
'Members in other groups are filtered out'
);
}
function testShowsAllMembersWithoutGroupParameter() {
$member1 = $this->objFromFixture('Member', 'member1');
$member2 = $this->objFromFixture('Member', 'member2');
$member3 = $this->objFromFixture('Member', 'member3');
$group1 = $this->objFromFixture('Group', 'group1');
$tf = new MemberTableField(
$this,
"Members"
// no group assignment
);
$members = $tf->sourceItems();
$this->assertContains($member1->ID, $members->column('ID'),
'Members in the associated group are listed'
);
$this->assertContains($member2->ID, $members->column('ID'),
'Members in children groups are listed as well'
);
$this->assertContains($member3->ID, $members->column('ID'),
'Members in other groups are listed'
);
}
function testDeleteWithGroupOnlyDeletesRelation() {
$member1 = $this->objFromFixture('Member', 'member1');
$group1 = $this->objFromFixture('Group', 'group1');
$response = $this->get('MemberTableFieldTest_Controller');
$token = SecurityToken::inst();
$url = sprintf('MemberTableFieldTest_Controller/Form/field/Members/item/%d/delete/?usetestmanifest=1', $member1->ID);
$url = $token->addToUrl($url);
$response = $this->get($url);
$group1->flushCache();
$this->assertNotContains($member1->ID, $group1->Members()->column('ID'),
'Member relation to group is removed'
);
$this->assertType(
'DataObject',
DataObject::get_by_id('Member', $member1->ID),
'Member record still exists'
);
}
function testDeleteWithoutGroupDeletesFromDatabase() {
$member1 = $this->objFromFixture('Member', 'member1');
$member1ID = $member1->ID;
$group1 = $this->objFromFixture('Group', 'group1');
$response = $this->get('MemberTableFieldTest_Controller');
$token = SecurityToken::inst();
$url = sprintf('MemberTableFieldTest_Controller/FormNoGroup/field/Members/item/%d/delete/?usetestmanifest=1', $member1->ID);
$url = $token->addToUrl($url);
$response = $this->get($url);
$group1->flushCache();
$this->assertNotContains($member1->ID, $group1->Members()->column('ID'),
'Member relation to group is removed'
);
DataObject::flush_and_destroy_cache();
$this->assertFalse(
DataObject::get_by_id('Member', $member1ID),
'Member record is removed from database'
);
}
}
class MemberTableFieldTest_Controller extends Controller implements TestOnly {
protected $template = 'BlankPage';
function Link($action = null) {
return Controller::join_links('MemberTableFieldTest_Controller', $action);
}
function Form() {
$group1 = DataObject::get_one('Group', '"Code" = \'group1\'');
return new Form(
$this,
'FormNoGroup',
new FieldSet(new MemberTableField($this, "Members", $group1)),
new FieldSet(new FormAction('submit'))
);
}
function FormNoGroup() {
$tf = new MemberTableField(
$this,
"Members"
// no group
);
return new Form(
$this,
'FormNoGroup',
new FieldSet(new MemberTableField($this, "Members")),
new FieldSet(new FormAction('submit'))
);
}
}

View File

@ -1,31 +0,0 @@
Group:
admin:
Title: Administrators
Code: admin
group1:
Title: Group1
Code: group1
group1_child:
Title: Group1 Child
Parent: =>Group.group1
Code: group1_child
group2:
Title: Group2
Code: group2
Member:
admin:
Email: admin@example.com
Groups: =>Group.admin
member1:
Email: member1@test.com
Groups: =>Group.group1
member2:
Email: member2@test.com
Groups: =>Group.group1_child
member3:
Email: member3@test.com
Groups: =>Group.group2
Permission:
admin:
Code: ADMIN
GroupID: =>Group.admin

View File

@ -1,32 +0,0 @@
<?php
class ModelAdminTest extends FunctionalTest {
static $fixture_file = 'cms/tests/ModelAdminTest.yml';
protected $extraDataObjects = array(
'ModelAdminTest_Admin',
'ModelAdminTest_Contact',
);
function testModelAdminOpens() {
$this->autoFollowRedirection = false;
$this->logInAs('admin');
$this->assertTrue((bool)Permission::check("ADMIN"));
$this->assertEquals(200, $this->get('ModelAdminTest_Admin')->getStatusCode());
}
}
class ModelAdminTest_Admin extends ModelAdmin implements TestOnly {
static $url_segment = 'testadmin';
public static $managed_models = array(
'ModelAdminTest_Contact',
);
}
class ModelAdminTest_Contact extends DataObject implements TestOnly {
static $db = array(
"Name" => "Varchar",
"Phone" => "Varchar",
);
}

View File

@ -1,19 +0,0 @@
ModelAdminTest_Contact:
sam:
Name: Sam
Phone: 021 123 456
ingo:
Name: ingo
Phone: 04 987 6543
Member:
admin:
FirstName: admin
Group:
admin:
Title: Admin
Members: =>Member.admin
Permission:
admin:
Code: ADMIN
Group: =>Group.admin

View File

@ -1,86 +0,0 @@
<?php
/**
* @package cms
* @subpackage tests
*/
class SecurityAdminTest extends FunctionalTest {
static $fixture_file = 'cms/tests/CMSMainTest.yml';
function testGroupExport() {
$this->session()->inst_set('loggedInAs', $this->idFromFixture('Member', 'admin'));
/* First, open the applicable group */
$this->get('admin/security/show/' . $this->idFromFixture('Group','admin'));
$this->assertRegExp('/<input[^>]+id="Form_EditForm_Title"[^>]+value="Administrators"[^>]*>/',$this->content());
/* Then load the export page */
$this->get('admin/security/EditForm/field/Members/export');
$lines = preg_split('/\n/', $this->content());
$this->assertEquals(count($lines), 3, "Export with members has one content row");
$this->assertRegExp('/"","","admin@example.com"/', $lines[1], "Member values are correctly exported");
}
function testEmptyGroupExport() {
$this->session()->inst_set('loggedInAs', $this->idFromFixture('Member', 'admin'));
/* First, open the applicable group */
$this->get('admin/security/show/' . $this->idFromFixture('Group','empty'));
$this->assertRegExp('/<input[^>]+id="Form_EditForm_Title"[^>]+value="Empty Group"[^>]*>/',$this->content());
/* Then load the export page */
$this->get('admin/security/EditForm/field/Members/export');
$lines = preg_split('/\n/', $this->content());
$this->assertEquals(count($lines), 2, "Empty export only has header fields and an empty row");
$this->assertEquals($lines[1], '', "Empty export only has no content row");
}
function testAddHiddenPermission() {
SecurityAdmin::add_hidden_permission('CMS_ACCESS_ReportAdmin');
$this->assertContains('CMS_ACCESS_ReportAdmin', SecurityAdmin::get_hidden_permissions());
// reset to defaults
SecurityAdmin::clear_hidden_permissions();
}
function testRemoveHiddenPermission() {
SecurityAdmin::add_hidden_permission('CMS_ACCESS_ReportAdmin');
$this->assertContains('CMS_ACCESS_ReportAdmin', SecurityAdmin::get_hidden_permissions());
SecurityAdmin::remove_hidden_permission('CMS_ACCESS_ReportAdmin');
$this->assertNotContains('CMS_ACCESS_ReportAdmin', SecurityAdmin::get_hidden_permissions());
// reset to defaults
SecurityAdmin::clear_hidden_permissions();
}
function testClearHiddenPermission() {
SecurityAdmin::add_hidden_permission('CMS_ACCESS_ReportAdmin');
$this->assertContains('CMS_ACCESS_ReportAdmin', SecurityAdmin::get_hidden_permissions());
SecurityAdmin::clear_hidden_permissions('CMS_ACCESS_ReportAdmin');
$this->assertNotContains('CMS_ACCESS_ReportAdmin', SecurityAdmin::get_hidden_permissions());
}
function testPermissionFieldRespectsHiddenPermissions() {
$this->session()->inst_set('loggedInAs', $this->idFromFixture('Member', 'admin'));
$group = $this->objFromFixture('Group', 'admin');
SecurityAdmin::add_hidden_permission('CMS_ACCESS_ReportAdmin');
$response = $this->get('admin/security/show/' . $group->ID);
$this->assertContains(
'CMS_ACCESS_CMSMain',
$response->getBody()
);
$this->assertNotContains(
'CMS_ACCESS_ReportAdmin',
$response->getBody()
);
// reset to defaults
SecurityAdmin::clear_hidden_permissions();
}
}
?>

View File

@ -1 +0,0 @@
See http://layout.jquery-dev.net/downloads.html

View File

@ -1,60 +0,0 @@
1.2.0
* ADDED maskIframesOnResize option: true=ALL -OR- a selector string
* ADDED options to set different animations on open and close
* ADDED new callback events, ie: onshow, onhide
* ADDED start/end callbacks, eg: onopen_start, onopen_end, etc.
* ADDED ability to cancel events using callbacks, eg: onopen_start
* CHANGED Layout.config.fxDefaults to Layout.effects (internal use)
* FIXED missing semi-colon so minified version works in IE
1.1.3
* FIXED typo in cursor-hotkeys code
* ADDED scrollToBookmarkOnLoad options - enables use of URL hash:
o www.site.com/page.html#myBookmark
o AFTER layout is created, attempts to scroll to bookmark
o default = true - otherwise bookmarks are non-functional
1.1.2
* UPDATED paneSelector rules to handle FORMS and pane-nesting
o automatically looks for panes inside 'first form' in container
o if using an ID as paneSelector, pane can be 'deeply nested'
* ADDED auto-CSS for 'containers' other than BODY
o overflow: hidden - ensures no scrollbars on container
o position: relative - IF NOT: fixed, absolute or relative
o height: 100% - IF NOT specified or is 'auto'
* ADDED noAnimation param to open() and close() - not used internally
1.1.1
* CHANGED toggler element from a SPAN to a DIV
* CHANGED auto-generated custom-buttons classes for better consistency
o [buttonClass]-[pane]-[buttonType] ==> [buttonClass]-[buttonType]-[pane]
o ui-layout-button-west-open ==> ui-layout-button-open-west
o ui-layout-button-west-pin-up ==> ui-layout-button-pin-west-up
* CHANGED default for hideTogglerOnSlide to false
* CHANGED internal 'cDims' hash to alias for state.container
* CHANGED internal aliases: s = state[pane] and o = options[pane]
* UPDATED toggler-text to auto-show correct spans (content-open/closed)
* FIXED toggler-text - now centers text span correctly
* FIXED bug affecting IE6 when layout has no north or south pane
* ADDED new layout property 'state' - eg: myLayout.state.west.size
* REMOVED layout.containerDimensions property - USE: layout.state.container
* CHANGED data returned to callbacks - added pane-state as 3rd param
1.1.0
* RENAMED raisePaneZindexOnHover ==> showOverflowOnHover
* REMOVED "overflow: auto" from base-styles. Overflow must now be set by
CSS - unless applyDefaultStyles==true. No longer need "!important" to
set pane overflow in your stylesheet.
* CHANGED minSize default from 50 to 0 (still auto-limited to 'css size')
* FIXED bug in allowOverflow - now works with 'custom paneClass'
* EXPOSED two CSS utility methods
o myLayout.cssWidth( elem )
o myLayout.cssHeight( elem )
* NEW auto-resize for ALL layouts on windows.resize
* UPDATED auto-resizing of panes after a container-resize
* NEW flow-code to prevent simultaneous pane animations
* NEW options to add text inside toggler-buttons
* NEW options for hotkeys - standard (cursors) and user-defined
1.0
* Initial release

View File

@ -1,23 +0,0 @@
<HTML>
<HEAD>
<TITLE>Layout Example</TITLE>
<SCRIPT type="text/javascript" src="jquery.js"></SCRIPT>
<SCRIPT type="text/javascript" src="jquery.layout.js"></SCRIPT>
<SCRIPT type="text/javascript">
$(document).ready(function () {
$('body').layout({ applyDefaultStyles: true });
});
</SCRIPT>
</HEAD>
<BODY>
<DIV class="ui-layout-center">Center
<P><A href="http://layout.jquery-dev.net/demos.html">Go to the Demos page</A></P>
<P>* Pane-resizing is disabled because ui.draggable.js is not linked</P>
<P>* Pane-animation is disabled because ui.effects.js is not linked</P>
</DIV>
<DIV class="ui-layout-north">North</DIV>
<DIV class="ui-layout-south">South</DIV>
<DIV class="ui-layout-east">East</DIV>
<DIV class="ui-layout-west">West</DIV>
</BODY>
</HTML>

View File

@ -1,8176 +0,0 @@
/*!
* jQuery JavaScript Library v1.5
* http://jquery.com/
*
* Copyright 2011, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2011, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Mon Jan 31 08:31:29 2011 -0500
*/
(function( window, undefined ) {
// Use the correct document accordingly with window argument (sandbox)
var document = window.document;
var jQuery = (function() {
// Define a local copy of jQuery
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
},
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,
// Map over the $ in case of overwrite
_$ = window.$,
// A central reference to the root jQuery(document)
rootjQuery,
// A simple way to check for HTML strings or ID strings
// (both of which we optimize for)
quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,
// Check if a string has a non-whitespace character in it
rnotwhite = /\S/,
// Used for trimming whitespace
trimLeft = /^\s+/,
trimRight = /\s+$/,
// Check for digits
rdigit = /\d/,
// Match a standalone tag
rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
// JSON RegExp
rvalidchars = /^[\],:{}\s]*$/,
rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
// Useragent RegExp
rwebkit = /(webkit)[ \/]([\w.]+)/,
ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
rmsie = /(msie) ([\w.]+)/,
rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
// Keep a UserAgent string for use with jQuery.browser
userAgent = navigator.userAgent,
// For matching the engine and version of the browser
browserMatch,
// Has the ready events already been bound?
readyBound = false,
// The deferred used on DOM ready
readyList,
// Promise methods
promiseMethods = "then done fail isResolved isRejected promise".split( " " ),
// The ready event handler
DOMContentLoaded,
// Save a reference to some core methods
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
push = Array.prototype.push,
slice = Array.prototype.slice,
trim = String.prototype.trim,
indexOf = Array.prototype.indexOf,
// [[Class]] -> type pairs
class2type = {};
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function( selector, context, rootjQuery ) {
var match, elem, ret, doc;
// Handle $(""), $(null), or $(undefined)
if ( !selector ) {
return this;
}
// Handle $(DOMElement)
if ( selector.nodeType ) {
this.context = this[0] = selector;
this.length = 1;
return this;
}
// The body element only exists once, optimize finding it
if ( selector === "body" && !context && document.body ) {
this.context = document;
this[0] = document.body;
this.selector = "body";
this.length = 1;
return this;
}
// Handle HTML strings
if ( typeof selector === "string" ) {
// Are we dealing with HTML string or an ID?
match = quickExpr.exec( selector );
// Verify a match, and that no context was specified for #id
if ( match && (match[1] || !context) ) {
// HANDLE: $(html) -> $(array)
if ( match[1] ) {
context = context instanceof jQuery ? context[0] : context;
doc = (context ? context.ownerDocument || context : document);
// If a single string is passed in and it's a single tag
// just do a createElement and skip the rest
ret = rsingleTag.exec( selector );
if ( ret ) {
if ( jQuery.isPlainObject( context ) ) {
selector = [ document.createElement( ret[1] ) ];
jQuery.fn.attr.call( selector, context, true );
} else {
selector = [ doc.createElement( ret[1] ) ];
}
} else {
ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
}
return jQuery.merge( this, selector );
// HANDLE: $("#id")
} else {
elem = document.getElementById( match[2] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem.id !== match[2] ) {
return rootjQuery.find( selector );
}
// Otherwise, we inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;
}
this.context = document;
this.selector = selector;
return this;
}
// HANDLE: $(expr, $(...))
} else if ( !context || context.jquery ) {
return (context || rootjQuery).find( selector );
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}
// HANDLE: $(function)
// Shortcut for document ready
} else if ( jQuery.isFunction( selector ) ) {
return rootjQuery.ready( selector );
}
if (selector.selector !== undefined) {
this.selector = selector.selector;
this.context = selector.context;
}
return jQuery.makeArray( selector, this );
},
// Start with an empty selector
selector: "",
// The current version of jQuery being used
jquery: "1.5",
// The default length of a jQuery object is 0
length: 0,
// The number of elements contained in the matched element set
size: function() {
return this.length;
},
toArray: function() {
return slice.call( this, 0 );
},
// Get the Nth element in the matched element set OR
// Get the whole matched element set as a clean array
get: function( num ) {
return num == null ?
// Return a 'clean' array
this.toArray() :
// Return just the object
( num < 0 ? this[ this.length + num ] : this[ num ] );
},
// Take an array of elements and push it onto the stack
// (returning the new matched element set)
pushStack: function( elems, name, selector ) {
// Build a new jQuery matched element set
var ret = this.constructor();
if ( jQuery.isArray( elems ) ) {
push.apply( ret, elems );
} else {
jQuery.merge( ret, elems );
}
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
ret.context = this.context;
if ( name === "find" ) {
ret.selector = this.selector + (this.selector ? " " : "") + selector;
} else if ( name ) {
ret.selector = this.selector + "." + name + "(" + selector + ")";
}
// Return the newly-formed element set
return ret;
},
// Execute a callback for every element in the matched set.
// (You can seed the arguments with an array of args, but this is
// only used internally.)
each: function( callback, args ) {
return jQuery.each( this, callback, args );
},
ready: function( fn ) {
// Attach the listeners
jQuery.bindReady();
// Add the callback
readyList.done( fn );
return this;
},
eq: function( i ) {
return i === -1 ?
this.slice( i ) :
this.slice( i, +i + 1 );
},
first: function() {
return this.eq( 0 );
},
last: function() {
return this.eq( -1 );
},
slice: function() {
return this.pushStack( slice.apply( this, arguments ),
"slice", slice.call(arguments).join(",") );
},
map: function( callback ) {
return this.pushStack( jQuery.map(this, function( elem, i ) {
return callback.call( elem, i, elem );
}));
},
end: function() {
return this.prevObject || this.constructor(null);
},
// For internal use only.
// Behaves like an Array's method, not like a jQuery method.
push: push,
sort: [].sort,
splice: [].splice
};
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
jQuery.extend({
noConflict: function( deep ) {
window.$ = _$;
if ( deep ) {
window.jQuery = _jQuery;
}
return jQuery;
},
// Is the DOM ready to be used? Set to true once it occurs.
isReady: false,
// A counter to track how many items to wait for before
// the ready event fires. See #6781
readyWait: 1,
// Handle when the DOM is ready
ready: function( wait ) {
// A third-party is pushing the ready event forwards
if ( wait === true ) {
jQuery.readyWait--;
}
// Make sure that the DOM is not already loaded
if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) {
// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
if ( !document.body ) {
return setTimeout( jQuery.ready, 1 );
}
// Remember that the DOM is ready
jQuery.isReady = true;
// If a normal DOM Ready event fired, decrement, and wait if need be
if ( wait !== true && --jQuery.readyWait > 0 ) {
return;
}
// If there are functions bound, to execute
readyList.resolveWith( document, [ jQuery ] );
// Trigger any bound ready events
if ( jQuery.fn.trigger ) {
jQuery( document ).trigger( "ready" ).unbind( "ready" );
}
}
},
bindReady: function() {
if ( readyBound ) {
return;
}
readyBound = true;
// Catch cases where $(document).ready() is called after the
// browser event has already occurred.
if ( document.readyState === "complete" ) {
// Handle it asynchronously to allow scripts the opportunity to delay ready
return setTimeout( jQuery.ready, 1 );
}
// Mozilla, Opera and webkit nightlies currently support this event
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", jQuery.ready, false );
// If IE event model is used
} else if ( document.attachEvent ) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent("onreadystatechange", DOMContentLoaded);
// A fallback to window.onload, that will always work
window.attachEvent( "onload", jQuery.ready );
// If IE and not a frame
// continually check to see if the document is ready
var toplevel = false;
try {
toplevel = window.frameElement == null;
} catch(e) {}
if ( document.documentElement.doScroll && toplevel ) {
doScrollCheck();
}
}
},
// See test/unit/core.js for details concerning isFunction.
// Since version 1.3, DOM methods and functions like alert
// aren't supported. They return false on IE (#2968).
isFunction: function( obj ) {
return jQuery.type(obj) === "function";
},
isArray: Array.isArray || function( obj ) {
return jQuery.type(obj) === "array";
},
// A crude way of determining if an object is a window
isWindow: function( obj ) {
return obj && typeof obj === "object" && "setInterval" in obj;
},
isNaN: function( obj ) {
return obj == null || !rdigit.test( obj ) || isNaN( obj );
},
type: function( obj ) {
return obj == null ?
String( obj ) :
class2type[ toString.call(obj) ] || "object";
},
isPlainObject: function( obj ) {
// Must be an Object.
// Because of IE, we also have to check the presence of the constructor property.
// Make sure that DOM nodes and window objects don't pass through, as well
if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
return false;
}
// Not own constructor property must be Object
if ( obj.constructor &&
!hasOwn.call(obj, "constructor") &&
!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
var key;
for ( key in obj ) {}
return key === undefined || hasOwn.call( obj, key );
},
isEmptyObject: function( obj ) {
for ( var name in obj ) {
return false;
}
return true;
},
error: function( msg ) {
throw msg;
},
parseJSON: function( data ) {
if ( typeof data !== "string" || !data ) {
return null;
}
// Make sure leading/trailing whitespace is removed (IE can't handle it)
data = jQuery.trim( data );
// Make sure the incoming data is actual JSON
// Logic borrowed from http://json.org/json2.js
if ( rvalidchars.test(data.replace(rvalidescape, "@")
.replace(rvalidtokens, "]")
.replace(rvalidbraces, "")) ) {
// Try to use the native JSON parser first
return window.JSON && window.JSON.parse ?
window.JSON.parse( data ) :
(new Function("return " + data))();
} else {
jQuery.error( "Invalid JSON: " + data );
}
},
// Cross-browser xml parsing
// (xml & tmp used internally)
parseXML: function( data , xml , tmp ) {
if ( window.DOMParser ) { // Standard
tmp = new DOMParser();
xml = tmp.parseFromString( data , "text/xml" );
} else { // IE
xml = new ActiveXObject( "Microsoft.XMLDOM" );
xml.async = "false";
xml.loadXML( data );
}
tmp = xml.documentElement;
if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) {
jQuery.error( "Invalid XML: " + data );
}
return xml;
},
noop: function() {},
// Evalulates a script in a global context
globalEval: function( data ) {
if ( data && rnotwhite.test(data) ) {
// Inspired by code by Andrea Giammarchi
// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
var head = document.getElementsByTagName("head")[0] || document.documentElement,
script = document.createElement("script");
script.type = "text/javascript";
if ( jQuery.support.scriptEval() ) {
script.appendChild( document.createTextNode( data ) );
} else {
script.text = data;
}
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709).
head.insertBefore( script, head.firstChild );
head.removeChild( script );
}
},
nodeName: function( elem, name ) {
return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
},
// args is for internal usage only
each: function( object, callback, args ) {
var name, i = 0,
length = object.length,
isObj = length === undefined || jQuery.isFunction(object);
if ( args ) {
if ( isObj ) {
for ( name in object ) {
if ( callback.apply( object[ name ], args ) === false ) {
break;
}
}
} else {
for ( ; i < length; ) {
if ( callback.apply( object[ i++ ], args ) === false ) {
break;
}
}
}
// A special, fast, case for the most common use of each
} else {
if ( isObj ) {
for ( name in object ) {
if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
break;
}
}
} else {
for ( var value = object[0];
i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
}
}
return object;
},
// Use native String.trim function wherever possible
trim: trim ?
function( text ) {
return text == null ?
"" :
trim.call( text );
} :
// Otherwise use our own trimming functionality
function( text ) {
return text == null ?
"" :
text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
},
// results is for internal usage only
makeArray: function( array, results ) {
var ret = results || [];
if ( array != null ) {
// The window, strings (and functions) also have 'length'
// The extra typeof function check is to prevent crashes
// in Safari 2 (See: #3039)
// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
var type = jQuery.type(array);
if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
push.call( ret, array );
} else {
jQuery.merge( ret, array );
}
}
return ret;
},
inArray: function( elem, array ) {
if ( array.indexOf ) {
return array.indexOf( elem );
}
for ( var i = 0, length = array.length; i < length; i++ ) {
if ( array[ i ] === elem ) {
return i;
}
}
return -1;
},
merge: function( first, second ) {
var i = first.length,
j = 0;
if ( typeof second.length === "number" ) {
for ( var l = second.length; j < l; j++ ) {
first[ i++ ] = second[ j ];
}
} else {
while ( second[j] !== undefined ) {
first[ i++ ] = second[ j++ ];
}
}
first.length = i;
return first;
},
grep: function( elems, callback, inv ) {
var ret = [], retVal;
inv = !!inv;
// Go through the array, only saving the items
// that pass the validator function
for ( var i = 0, length = elems.length; i < length; i++ ) {
retVal = !!callback( elems[ i ], i );
if ( inv !== retVal ) {
ret.push( elems[ i ] );
}
}
return ret;
},
// arg is for internal usage only
map: function( elems, callback, arg ) {
var ret = [], value;
// Go through the array, translating each of the items to their
// new value (or values).
for ( var i = 0, length = elems.length; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret[ ret.length ] = value;
}
}
// Flatten any nested arrays
return ret.concat.apply( [], ret );
},
// A global GUID counter for objects
guid: 1,
proxy: function( fn, proxy, thisObject ) {
if ( arguments.length === 2 ) {
if ( typeof proxy === "string" ) {
thisObject = fn;
fn = thisObject[ proxy ];
proxy = undefined;
} else if ( proxy && !jQuery.isFunction( proxy ) ) {
thisObject = proxy;
proxy = undefined;
}
}
if ( !proxy && fn ) {
proxy = function() {
return fn.apply( thisObject || this, arguments );
};
}
// Set the guid of unique handler to the same of original handler, so it can be removed
if ( fn ) {
proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
}
// So proxy can be declared as an argument
return proxy;
},
// Mutifunctional method to get and set values to a collection
// The value/s can be optionally by executed if its a function
access: function( elems, key, value, exec, fn, pass ) {
var length = elems.length;
// Setting many attributes
if ( typeof key === "object" ) {
for ( var k in key ) {
jQuery.access( elems, k, key[k], exec, fn, value );
}
return elems;
}
// Setting one attribute
if ( value !== undefined ) {
// Optionally, function values get executed if exec is true
exec = !pass && exec && jQuery.isFunction(value);
for ( var i = 0; i < length; i++ ) {
fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
}
return elems;
}
// Getting an attribute
return length ? fn( elems[0], key ) : undefined;
},
now: function() {
return (new Date()).getTime();
},
// Create a simple deferred (one callbacks list)
_Deferred: function() {
var // callbacks list
callbacks = [],
// stored [ context , args ]
fired,
// to avoid firing when already doing so
firing,
// flag to know if the deferred has been cancelled
cancelled,
// the deferred itself
deferred = {
// done( f1, f2, ...)
done: function() {
if ( !cancelled ) {
var args = arguments,
i,
length,
elem,
type,
_fired;
if ( fired ) {
_fired = fired;
fired = 0;
}
for ( i = 0, length = args.length; i < length; i++ ) {
elem = args[ i ];
type = jQuery.type( elem );
if ( type === "array" ) {
deferred.done.apply( deferred, elem );
} else if ( type === "function" ) {
callbacks.push( elem );
}
}
if ( _fired ) {
deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
}
}
return this;
},
// resolve with given context and args
resolveWith: function( context, args ) {
if ( !cancelled && !fired && !firing ) {
firing = 1;
try {
while( callbacks[ 0 ] ) {
callbacks.shift().apply( context, args );
}
}
finally {
fired = [ context, args ];
firing = 0;
}
}
return this;
},
// resolve with this as context and given arguments
resolve: function() {
deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments );
return this;
},
// Has this deferred been resolved?
isResolved: function() {
return !!( firing || fired );
},
// Cancel
cancel: function() {
cancelled = 1;
callbacks = [];
return this;
}
};
return deferred;
},
// Full fledged deferred (two callbacks list)
Deferred: function( func ) {
var deferred = jQuery._Deferred(),
failDeferred = jQuery._Deferred(),
promise;
// Add errorDeferred methods, then and promise
jQuery.extend( deferred, {
then: function( doneCallbacks, failCallbacks ) {
deferred.done( doneCallbacks ).fail( failCallbacks );
return this;
},
fail: failDeferred.done,
rejectWith: failDeferred.resolveWith,
reject: failDeferred.resolve,
isRejected: failDeferred.isResolved,
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj , i /* internal */ ) {
if ( obj == null ) {
if ( promise ) {
return promise;
}
promise = obj = {};
}
i = promiseMethods.length;
while( i-- ) {
obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ];
}
return obj;
}
} );
// Make sure only one callback list will be used
deferred.then( failDeferred.cancel, deferred.cancel );
// Unexpose cancel
delete deferred.cancel;
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
return deferred;
},
// Deferred helper
when: function( object ) {
var args = arguments,
length = args.length,
deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ?
object :
jQuery.Deferred(),
promise = deferred.promise(),
resolveArray;
if ( length > 1 ) {
resolveArray = new Array( length );
jQuery.each( args, function( index, element ) {
jQuery.when( element ).then( function( value ) {
resolveArray[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value;
if( ! --length ) {
deferred.resolveWith( promise, resolveArray );
}
}, deferred.reject );
} );
} else if ( deferred !== object ) {
deferred.resolve( object );
}
return promise;
},
// Use of jQuery.browser is frowned upon.
// More details: http://docs.jquery.com/Utilities/jQuery.browser
uaMatch: function( ua ) {
ua = ua.toLowerCase();
var match = rwebkit.exec( ua ) ||
ropera.exec( ua ) ||
rmsie.exec( ua ) ||
ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
[];
return { browser: match[1] || "", version: match[2] || "0" };
},
sub: function() {
function jQuerySubclass( selector, context ) {
return new jQuerySubclass.fn.init( selector, context );
}
jQuery.extend( true, jQuerySubclass, this );
jQuerySubclass.superclass = this;
jQuerySubclass.fn = jQuerySubclass.prototype = this();
jQuerySubclass.fn.constructor = jQuerySubclass;
jQuerySubclass.subclass = this.subclass;
jQuerySubclass.fn.init = function init( selector, context ) {
if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) {
context = jQuerySubclass(context);
}
return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass );
};
jQuerySubclass.fn.init.prototype = jQuerySubclass.fn;
var rootjQuerySubclass = jQuerySubclass(document);
return jQuerySubclass;
},
browser: {}
});
// Create readyList deferred
readyList = jQuery._Deferred();
// Populate the class2type map
jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
});
browserMatch = jQuery.uaMatch( userAgent );
if ( browserMatch.browser ) {
jQuery.browser[ browserMatch.browser ] = true;
jQuery.browser.version = browserMatch.version;
}
// Deprecated, use jQuery.browser.webkit instead
if ( jQuery.browser.webkit ) {
jQuery.browser.safari = true;
}
if ( indexOf ) {
jQuery.inArray = function( elem, array ) {
return indexOf.call( array, elem );
};
}
// IE doesn't match non-breaking spaces with \s
if ( rnotwhite.test( "\xA0" ) ) {
trimLeft = /^[\s\xA0]+/;
trimRight = /[\s\xA0]+$/;
}
// All jQuery objects should point back to these
rootjQuery = jQuery(document);
// Cleanup functions for the document ready method
if ( document.addEventListener ) {
DOMContentLoaded = function() {
document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
jQuery.ready();
};
} else if ( document.attachEvent ) {
DOMContentLoaded = function() {
// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
if ( document.readyState === "complete" ) {
document.detachEvent( "onreadystatechange", DOMContentLoaded );
jQuery.ready();
}
};
}
// The DOM ready check for Internet Explorer
function doScrollCheck() {
if ( jQuery.isReady ) {
return;
}
try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
document.documentElement.doScroll("left");
} catch(e) {
setTimeout( doScrollCheck, 1 );
return;
}
// and execute any waiting functions
jQuery.ready();
}
// Expose jQuery to the global object
return (window.jQuery = window.$ = jQuery);
})();
(function() {
jQuery.support = {};
var div = document.createElement("div");
div.style.display = "none";
div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
var all = div.getElementsByTagName("*"),
a = div.getElementsByTagName("a")[0],
select = document.createElement("select"),
opt = select.appendChild( document.createElement("option") );
// Can't get basic test support
if ( !all || !all.length || !a ) {
return;
}
jQuery.support = {
// IE strips leading whitespace when .innerHTML is used
leadingWhitespace: div.firstChild.nodeType === 3,
// Make sure that tbody elements aren't automatically inserted
// IE will insert them into empty tables
tbody: !div.getElementsByTagName("tbody").length,
// Make sure that link elements get serialized correctly by innerHTML
// This requires a wrapper element in IE
htmlSerialize: !!div.getElementsByTagName("link").length,
// Get the style information from getAttribute
// (IE uses .cssText insted)
style: /red/.test( a.getAttribute("style") ),
// Make sure that URLs aren't manipulated
// (IE normalizes it by default)
hrefNormalized: a.getAttribute("href") === "/a",
// Make sure that element opacity exists
// (IE uses filter instead)
// Use a regex to work around a WebKit issue. See #5145
opacity: /^0.55$/.test( a.style.opacity ),
// Verify style float existence
// (IE uses styleFloat instead of cssFloat)
cssFloat: !!a.style.cssFloat,
// Make sure that if no value is specified for a checkbox
// that it defaults to "on".
// (WebKit defaults to "" instead)
checkOn: div.getElementsByTagName("input")[0].value === "on",
// Make sure that a selected-by-default option has a working selected property.
// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
optSelected: opt.selected,
// Will be defined later
deleteExpando: true,
optDisabled: false,
checkClone: false,
_scriptEval: null,
noCloneEvent: true,
boxModel: null,
inlineBlockNeedsLayout: false,
shrinkWrapBlocks: false,
reliableHiddenOffsets: true
};
// Make sure that the options inside disabled selects aren't marked as disabled
// (WebKit marks them as diabled)
select.disabled = true;
jQuery.support.optDisabled = !opt.disabled;
jQuery.support.scriptEval = function() {
if ( jQuery.support._scriptEval === null ) {
var root = document.documentElement,
script = document.createElement("script"),
id = "script" + jQuery.now();
script.type = "text/javascript";
try {
script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
} catch(e) {}
root.insertBefore( script, root.firstChild );
// Make sure that the execution of code works by injecting a script
// tag with appendChild/createTextNode
// (IE doesn't support this, fails, and uses .text instead)
if ( window[ id ] ) {
jQuery.support._scriptEval = true;
delete window[ id ];
} else {
jQuery.support._scriptEval = false;
}
root.removeChild( script );
// release memory in IE
root = script = id = null;
}
return jQuery.support._scriptEval;
};
// Test to see if it's possible to delete an expando from an element
// Fails in Internet Explorer
try {
delete div.test;
} catch(e) {
jQuery.support.deleteExpando = false;
}
if ( div.attachEvent && div.fireEvent ) {
div.attachEvent("onclick", function click() {
// Cloning a node shouldn't copy over any
// bound event handlers (IE does this)
jQuery.support.noCloneEvent = false;
div.detachEvent("onclick", click);
});
div.cloneNode(true).fireEvent("onclick");
}
div = document.createElement("div");
div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>";
var fragment = document.createDocumentFragment();
fragment.appendChild( div.firstChild );
// WebKit doesn't clone checked state correctly in fragments
jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked;
// Figure out if the W3C box model works as expected
// document.body must exist before we can do this
jQuery(function() {
var div = document.createElement("div"),
body = document.getElementsByTagName("body")[0];
// Frameset documents with no body should not run this code
if ( !body ) {
return;
}
div.style.width = div.style.paddingLeft = "1px";
body.appendChild( div );
jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
if ( "zoom" in div.style ) {
// Check if natively block-level elements act like inline-block
// elements when setting their display to 'inline' and giving
// them layout
// (IE < 8 does this)
div.style.display = "inline";
div.style.zoom = 1;
jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2;
// Check if elements with layout shrink-wrap their children
// (IE 6 does this)
div.style.display = "";
div.innerHTML = "<div style='width:4px;'></div>";
jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2;
}
div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
var tds = div.getElementsByTagName("td");
// Check if table cells still have offsetWidth/Height when they are set
// to display:none and there are still other visible table cells in a
// table row; if so, offsetWidth/Height are not reliable for use when
// determining if an element has been hidden directly using
// display:none (it is still safe to use offsets if a parent element is
// hidden; don safety goggles and see bug #4512 for more information).
// (only IE 8 fails this test)
jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0;
tds[0].style.display = "";
tds[1].style.display = "none";
// Check if empty table cells still have offsetWidth/Height
// (IE < 8 fail this test)
jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0;
div.innerHTML = "";
body.removeChild( div ).style.display = "none";
div = tds = null;
});
// Technique from Juriy Zaytsev
// http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
var eventSupported = function( eventName ) {
var el = document.createElement("div");
eventName = "on" + eventName;
// We only care about the case where non-standard event systems
// are used, namely in IE. Short-circuiting here helps us to
// avoid an eval call (in setAttribute) which can cause CSP
// to go haywire. See: https://developer.mozilla.org/en/Security/CSP
if ( !el.attachEvent ) {
return true;
}
var isSupported = (eventName in el);
if ( !isSupported ) {
el.setAttribute(eventName, "return;");
isSupported = typeof el[eventName] === "function";
}
el = null;
return isSupported;
};
jQuery.support.submitBubbles = eventSupported("submit");
jQuery.support.changeBubbles = eventSupported("change");
// release memory in IE
div = all = a = null;
})();
var rbrace = /^(?:\{.*\}|\[.*\])$/;
jQuery.extend({
cache: {},
// Please use with caution
uuid: 0,
// Unique for each copy of jQuery on the page
// Non-digits removed to match rinlinejQuery
expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
// The following elements throw uncatchable exceptions if you
// attempt to add expando properties to them.
noData: {
"embed": true,
// Ban all objects except for Flash (which handle expandos)
"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
"applet": true
},
hasData: function( elem ) {
elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
return !!elem && !jQuery.isEmptyObject(elem);
},
data: function( elem, name, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache,
// We have to handle DOM nodes and JS objects differently because IE6-7
// can't GC object references properly across the DOM-JS boundary
isNode = elem.nodeType,
// Only DOM nodes need the global jQuery cache; JS object data is
// attached directly to the object so GC can occur automatically
cache = isNode ? jQuery.cache : elem,
// Only defining an ID for JS objects if its cache already exists allows
// the code to shortcut on the same path as a DOM node with no cache
id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando;
// Avoid doing any more work than we need to when trying to get data on an
// object that has no data at all
if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) {
return;
}
if ( !id ) {
// Only DOM nodes need a new unique ID for each element since their data
// ends up in the global cache
if ( isNode ) {
elem[ jQuery.expando ] = id = ++jQuery.uuid;
} else {
id = jQuery.expando;
}
}
if ( !cache[ id ] ) {
cache[ id ] = {};
}
// An object can be passed to jQuery.data instead of a key/value pair; this gets
// shallow copied over onto the existing cache
if ( typeof name === "object" ) {
if ( pvt ) {
cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
} else {
cache[ id ] = jQuery.extend(cache[ id ], name);
}
}
thisCache = cache[ id ];
// Internal jQuery data is stored in a separate object inside the object's data
// cache in order to avoid key collisions between internal data and user-defined
// data
if ( pvt ) {
if ( !thisCache[ internalKey ] ) {
thisCache[ internalKey ] = {};
}
thisCache = thisCache[ internalKey ];
}
if ( data !== undefined ) {
thisCache[ name ] = data;
}
// TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
// not attempt to inspect the internal events object using jQuery.data, as this
// internal data object is undocumented and subject to change.
if ( name === "events" && !thisCache[name] ) {
return thisCache[ internalKey ] && thisCache[ internalKey ].events;
}
return getByName ? thisCache[ name ] : thisCache;
},
removeData: function( elem, name, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var internalKey = jQuery.expando, isNode = elem.nodeType,
// See jQuery.data for more information
cache = isNode ? jQuery.cache : elem,
// See jQuery.data for more information
id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
// If there is already no cache entry for this object, there is no
// purpose in continuing
if ( !cache[ id ] ) {
return;
}
if ( name ) {
var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
if ( thisCache ) {
delete thisCache[ name ];
// If there is no data left in the cache, we want to continue
// and let the cache object itself get destroyed
if ( !jQuery.isEmptyObject(thisCache) ) {
return;
}
}
}
// See jQuery.data for more information
if ( pvt ) {
delete cache[ id ][ internalKey ];
// Don't destroy the parent cache unless the internal data object
// had been the only thing left in it
if ( !jQuery.isEmptyObject(cache[ id ]) ) {
return;
}
}
var internalCache = cache[ id ][ internalKey ];
// Browsers that fail expando deletion also refuse to delete expandos on
// the window, but it will allow it on all other JS objects; other browsers
// don't care
if ( jQuery.support.deleteExpando || cache != window ) {
delete cache[ id ];
} else {
cache[ id ] = null;
}
// We destroyed the entire user cache at once because it's faster than
// iterating through each key, but we need to continue to persist internal
// data if it existed
if ( internalCache ) {
cache[ id ] = {};
cache[ id ][ internalKey ] = internalCache;
// Otherwise, we need to eliminate the expando on the node to avoid
// false lookups in the cache for entries that no longer exist
} else if ( isNode ) {
// IE does not allow us to delete expando properties from nodes,
// nor does it have a removeAttribute function on Document nodes;
// we must handle all of these cases
if ( jQuery.support.deleteExpando ) {
delete elem[ jQuery.expando ];
} else if ( elem.removeAttribute ) {
elem.removeAttribute( jQuery.expando );
} else {
elem[ jQuery.expando ] = null;
}
}
},
// For internal use only.
_data: function( elem, name, data ) {
return jQuery.data( elem, name, data, true );
},
// A method for determining if a DOM node can handle the data expando
acceptData: function( elem ) {
if ( elem.nodeName ) {
var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
if ( match ) {
return !(match === true || elem.getAttribute("classid") !== match);
}
}
return true;
}
});
jQuery.fn.extend({
data: function( key, value ) {
var data = null;
if ( typeof key === "undefined" ) {
if ( this.length ) {
data = jQuery.data( this[0] );
if ( this[0].nodeType === 1 ) {
var attr = this[0].attributes, name;
for ( var i = 0, l = attr.length; i < l; i++ ) {
name = attr[i].name;
if ( name.indexOf( "data-" ) === 0 ) {
name = name.substr( 5 );
dataAttr( this[0], name, data[ name ] );
}
}
}
}
return data;
} else if ( typeof key === "object" ) {
return this.each(function() {
jQuery.data( this, key );
});
}
var parts = key.split(".");
parts[1] = parts[1] ? "." + parts[1] : "";
if ( value === undefined ) {
data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
// Try to fetch any internally stored data first
if ( data === undefined && this.length ) {
data = jQuery.data( this[0], key );
data = dataAttr( this[0], key, data );
}
return data === undefined && parts[1] ?
this.data( parts[0] ) :
data;
} else {
return this.each(function() {
var $this = jQuery( this ),
args = [ parts[0], value ];
$this.triggerHandler( "setData" + parts[1] + "!", args );
jQuery.data( this, key, value );
$this.triggerHandler( "changeData" + parts[1] + "!", args );
});
}
},
removeData: function( key ) {
return this.each(function() {
jQuery.removeData( this, key );
});
}
});
function dataAttr( elem, key, data ) {
// If nothing was found internally, try to fetch any
// data from the HTML5 data-* attribute
if ( data === undefined && elem.nodeType === 1 ) {
data = elem.getAttribute( "data-" + key );
if ( typeof data === "string" ) {
try {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
!jQuery.isNaN( data ) ? parseFloat( data ) :
rbrace.test( data ) ? jQuery.parseJSON( data ) :
data;
} catch( e ) {}
// Make sure we set the data so it isn't changed later
jQuery.data( elem, key, data );
} else {
data = undefined;
}
}
return data;
}
jQuery.extend({
queue: function( elem, type, data ) {
if ( !elem ) {
return;
}
type = (type || "fx") + "queue";
var q = jQuery._data( elem, type );
// Speed up dequeue by getting out quickly if this is just a lookup
if ( !data ) {
return q || [];
}
if ( !q || jQuery.isArray(data) ) {
q = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
q.push( data );
}
return q;
},
dequeue: function( elem, type ) {
type = type || "fx";
var queue = jQuery.queue( elem, type ),
fn = queue.shift();
// If the fx queue is dequeued, always remove the progress sentinel
if ( fn === "inprogress" ) {
fn = queue.shift();
}
if ( fn ) {
// Add a progress sentinel to prevent the fx queue from being
// automatically dequeued
if ( type === "fx" ) {
queue.unshift("inprogress");
}
fn.call(elem, function() {
jQuery.dequeue(elem, type);
});
}
if ( !queue.length ) {
jQuery.removeData( elem, type + "queue", true );
}
}
});
jQuery.fn.extend({
queue: function( type, data ) {
if ( typeof type !== "string" ) {
data = type;
type = "fx";
}
if ( data === undefined ) {
return jQuery.queue( this[0], type );
}
return this.each(function( i ) {
var queue = jQuery.queue( this, type, data );
if ( type === "fx" && queue[0] !== "inprogress" ) {
jQuery.dequeue( this, type );
}
});
},
dequeue: function( type ) {
return this.each(function() {
jQuery.dequeue( this, type );
});
},
// Based off of the plugin by Clint Helfers, with permission.
// http://blindsignals.com/index.php/2009/07/jquery-delay/
delay: function( time, type ) {
time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
type = type || "fx";
return this.queue( type, function() {
var elem = this;
setTimeout(function() {
jQuery.dequeue( elem, type );
}, time );
});
},
clearQueue: function( type ) {
return this.queue( type || "fx", [] );
}
});
var rclass = /[\n\t\r]/g,
rspaces = /\s+/,
rreturn = /\r/g,
rspecialurl = /^(?:href|src|style)$/,
rtype = /^(?:button|input)$/i,
rfocusable = /^(?:button|input|object|select|textarea)$/i,
rclickable = /^a(?:rea)?$/i,
rradiocheck = /^(?:radio|checkbox)$/i;
jQuery.props = {
"for": "htmlFor",
"class": "className",
readonly: "readOnly",
maxlength: "maxLength",
cellspacing: "cellSpacing",
rowspan: "rowSpan",
colspan: "colSpan",
tabindex: "tabIndex",
usemap: "useMap",
frameborder: "frameBorder"
};
jQuery.fn.extend({
attr: function( name, value ) {
return jQuery.access( this, name, value, true, jQuery.attr );
},
removeAttr: function( name, fn ) {
return this.each(function(){
jQuery.attr( this, name, "" );
if ( this.nodeType === 1 ) {
this.removeAttribute( name );
}
});
},
addClass: function( value ) {
if ( jQuery.isFunction(value) ) {
return this.each(function(i) {
var self = jQuery(this);
self.addClass( value.call(this, i, self.attr("class")) );
});
}
if ( value && typeof value === "string" ) {
var classNames = (value || "").split( rspaces );
for ( var i = 0, l = this.length; i < l; i++ ) {
var elem = this[i];
if ( elem.nodeType === 1 ) {
if ( !elem.className ) {
elem.className = value;
} else {
var className = " " + elem.className + " ",
setClass = elem.className;
for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) {
setClass += " " + classNames[c];
}
}
elem.className = jQuery.trim( setClass );
}
}
}
}
return this;
},
removeClass: function( value ) {
if ( jQuery.isFunction(value) ) {
return this.each(function(i) {
var self = jQuery(this);
self.removeClass( value.call(this, i, self.attr("class")) );
});
}
if ( (value && typeof value === "string") || value === undefined ) {
var classNames = (value || "").split( rspaces );
for ( var i = 0, l = this.length; i < l; i++ ) {
var elem = this[i];
if ( elem.nodeType === 1 && elem.className ) {
if ( value ) {
var className = (" " + elem.className + " ").replace(rclass, " ");
for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
className = className.replace(" " + classNames[c] + " ", " ");
}
elem.className = jQuery.trim( className );
} else {
elem.className = "";
}
}
}
}
return this;
},
toggleClass: function( value, stateVal ) {
var type = typeof value,
isBool = typeof stateVal === "boolean";
if ( jQuery.isFunction( value ) ) {
return this.each(function(i) {
var self = jQuery(this);
self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal );
});
}
return this.each(function() {
if ( type === "string" ) {
// toggle individual class names
var className,
i = 0,
self = jQuery( this ),
state = stateVal,
classNames = value.split( rspaces );
while ( (className = classNames[ i++ ]) ) {
// check each className given, space seperated list
state = isBool ? state : !self.hasClass( className );
self[ state ? "addClass" : "removeClass" ]( className );
}
} else if ( type === "undefined" || type === "boolean" ) {
if ( this.className ) {
// store className if set
jQuery._data( this, "__className__", this.className );
}
// toggle whole className
this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
}
});
},
hasClass: function( selector ) {
var className = " " + selector + " ";
for ( var i = 0, l = this.length; i < l; i++ ) {
if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
return true;
}
}
return false;
},
val: function( value ) {
if ( !arguments.length ) {
var elem = this[0];
if ( elem ) {
if ( jQuery.nodeName( elem, "option" ) ) {
// attributes.value is undefined in Blackberry 4.7 but
// uses .value. See #6932
var val = elem.attributes.value;
return !val || val.specified ? elem.value : elem.text;
}
// We need to handle select boxes special
if ( jQuery.nodeName( elem, "select" ) ) {
var index = elem.selectedIndex,
values = [],
options = elem.options,
one = elem.type === "select-one";
// Nothing was selected
if ( index < 0 ) {
return null;
}
// Loop through all the selected options
for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
var option = options[ i ];
// Don't return options that are disabled or in a disabled optgroup
if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
(!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
// Get the specific value for the option
value = jQuery(option).val();
// We don't need an array for one selects
if ( one ) {
return value;
}
// Multi-Selects return an array
values.push( value );
}
}
return values;
}
// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) {
return elem.getAttribute("value") === null ? "on" : elem.value;
}
// Everything else, we just grab the value
return (elem.value || "").replace(rreturn, "");
}
return undefined;
}
var isFunction = jQuery.isFunction(value);
return this.each(function(i) {
var self = jQuery(this), val = value;
if ( this.nodeType !== 1 ) {
return;
}
if ( isFunction ) {
val = value.call(this, i, self.val());
}
// Treat null/undefined as ""; convert numbers to string
if ( val == null ) {
val = "";
} else if ( typeof val === "number" ) {
val += "";
} else if ( jQuery.isArray(val) ) {
val = jQuery.map(val, function (value) {
return value == null ? "" : value + "";
});
}
if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) {
this.checked = jQuery.inArray( self.val(), val ) >= 0;
} else if ( jQuery.nodeName( this, "select" ) ) {
var values = jQuery.makeArray(val);
jQuery( "option", this ).each(function() {
this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
});
if ( !values.length ) {
this.selectedIndex = -1;
}
} else {
this.value = val;
}
});
}
});
jQuery.extend({
attrFn: {
val: true,
css: true,
html: true,
text: true,
data: true,
width: true,
height: true,
offset: true
},
attr: function( elem, name, value, pass ) {
// don't get/set attributes on text, comment and attribute nodes
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) {
return undefined;
}
if ( pass && name in jQuery.attrFn ) {
return jQuery(elem)[name](value);
}
var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ),
// Whether we are setting (or getting)
set = value !== undefined;
// Try to normalize/fix the name
name = notxml && jQuery.props[ name ] || name;
// Only do all the following if this is a node (faster for style)
if ( elem.nodeType === 1 ) {
// These attributes require special treatment
var special = rspecialurl.test( name );
// Safari mis-reports the default selected property of an option
// Accessing the parent's selectedIndex property fixes it
if ( name === "selected" && !jQuery.support.optSelected ) {
var parent = elem.parentNode;
if ( parent ) {
parent.selectedIndex;
// Make sure that it also works with optgroups, see #5701
if ( parent.parentNode ) {
parent.parentNode.selectedIndex;
}
}
}
// If applicable, access the attribute via the DOM 0 way
// 'in' checks fail in Blackberry 4.7 #6931
if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) {
if ( set ) {
// We can't allow the type property to be changed (since it causes problems in IE)
if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) {
jQuery.error( "type property can't be changed" );
}
if ( value === null ) {
if ( elem.nodeType === 1 ) {
elem.removeAttribute( name );
}
} else {
elem[ name ] = value;
}
}
// browsers index elements by id/name on forms, give priority to attributes.
if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) {
return elem.getAttributeNode( name ).nodeValue;
}
// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
if ( name === "tabIndex" ) {
var attributeNode = elem.getAttributeNode( "tabIndex" );
return attributeNode && attributeNode.specified ?
attributeNode.value :
rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
0 :
undefined;
}
return elem[ name ];
}
if ( !jQuery.support.style && notxml && name === "style" ) {
if ( set ) {
elem.style.cssText = "" + value;
}
return elem.style.cssText;
}
if ( set ) {
// convert the value to a string (all browsers do this but IE) see #1070
elem.setAttribute( name, "" + value );
}
// Ensure that missing attributes return undefined
// Blackberry 4.7 returns "" from getAttribute #6938
if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) {
return undefined;
}
var attr = !jQuery.support.hrefNormalized && notxml && special ?
// Some attributes require a special call on IE
elem.getAttribute( name, 2 ) :
elem.getAttribute( name );
// Non-existent attributes return null, we normalize to undefined
return attr === null ? undefined : attr;
}
// Handle everything which isn't a DOM element node
if ( set ) {
elem[ name ] = value;
}
return elem[ name ];
}
});
var rnamespaces = /\.(.*)$/,
rformElems = /^(?:textarea|input|select)$/i,
rperiod = /\./g,
rspace = / /g,
rescape = /[^\w\s.|`]/g,
fcleanup = function( nm ) {
return nm.replace(rescape, "\\$&");
},
eventKey = "events";
/*
* A number of helper functions used for managing events.
* Many of the ideas behind this code originated from
* Dean Edwards' addEvent library.
*/
jQuery.event = {
// Bind an event to an element
// Original by Dean Edwards
add: function( elem, types, handler, data ) {
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
return;
}
// For whatever reason, IE has trouble passing the window object
// around, causing it to be cloned in the process
if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
elem = window;
}
if ( handler === false ) {
handler = returnFalse;
} else if ( !handler ) {
// Fixes bug #7229. Fix recommended by jdalton
return;
}
var handleObjIn, handleObj;
if ( handler.handler ) {
handleObjIn = handler;
handler = handleObjIn.handler;
}
// Make sure that the function being executed has a unique ID
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
// Init the element's event structure
var elemData = jQuery._data( elem );
// If no elemData is found then we must be trying to bind to one of the
// banned noData elements
if ( !elemData ) {
return;
}
var events = elemData[ eventKey ],
eventHandle = elemData.handle;
if ( typeof events === "function" ) {
// On plain objects events is a fn that holds the the data
// which prevents this data from being JSON serialized
// the function does not need to be called, it just contains the data
eventHandle = events.handle;
events = events.events;
} else if ( !events ) {
if ( !elem.nodeType ) {
// On plain objects, create a fn that acts as the holder
// of the values to avoid JSON serialization of event data
elemData[ eventKey ] = elemData = function(){};
}
elemData.events = events = {};
}
if ( !eventHandle ) {
elemData.handle = eventHandle = function() {
// Handle the second event of a trigger and when
// an event is called after a page has unloaded
return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
jQuery.event.handle.apply( eventHandle.elem, arguments ) :
undefined;
};
}
// Add elem as a property of the handle function
// This is to prevent a memory leak with non-native events in IE.
eventHandle.elem = elem;
// Handle multiple events separated by a space
// jQuery(...).bind("mouseover mouseout", fn);
types = types.split(" ");
var type, i = 0, namespaces;
while ( (type = types[ i++ ]) ) {
handleObj = handleObjIn ?
jQuery.extend({}, handleObjIn) :
{ handler: handler, data: data };
// Namespaced event handlers
if ( type.indexOf(".") > -1 ) {
namespaces = type.split(".");
type = namespaces.shift();
handleObj.namespace = namespaces.slice(0).sort().join(".");
} else {
namespaces = [];
handleObj.namespace = "";
}
handleObj.type = type;
if ( !handleObj.guid ) {
handleObj.guid = handler.guid;
}
// Get the current list of functions bound to this event
var handlers = events[ type ],
special = jQuery.event.special[ type ] || {};
// Init the event handler queue
if ( !handlers ) {
handlers = events[ type ] = [];
// Check for a special event handler
// Only use addEventListener/attachEvent if the special
// events handler returns false
if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
// Bind the global event handler to the element
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false );
} else if ( elem.attachEvent ) {
elem.attachEvent( "on" + type, eventHandle );
}
}
}
if ( special.add ) {
special.add.call( elem, handleObj );
if ( !handleObj.handler.guid ) {
handleObj.handler.guid = handler.guid;
}
}
// Add the function to the element's handler list
handlers.push( handleObj );
// Keep track of which events have been used, for global triggering
jQuery.event.global[ type ] = true;
}
// Nullify elem to prevent memory leaks in IE
elem = null;
},
global: {},
// Detach an event or set of events from an element
remove: function( elem, types, handler, pos ) {
// don't do events on text and comment nodes
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
return;
}
if ( handler === false ) {
handler = returnFalse;
}
var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
events = elemData && elemData[ eventKey ];
if ( !elemData || !events ) {
return;
}
if ( typeof events === "function" ) {
elemData = events;
events = events.events;
}
// types is actually an event object here
if ( types && types.type ) {
handler = types.handler;
types = types.type;
}
// Unbind all events for the element
if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
types = types || "";
for ( type in events ) {
jQuery.event.remove( elem, type + types );
}
return;
}
// Handle multiple events separated by a space
// jQuery(...).unbind("mouseover mouseout", fn);
types = types.split(" ");
while ( (type = types[ i++ ]) ) {
origType = type;
handleObj = null;
all = type.indexOf(".") < 0;
namespaces = [];
if ( !all ) {
// Namespaced event handlers
namespaces = type.split(".");
type = namespaces.shift();
namespace = new RegExp("(^|\\.)" +
jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
}
eventType = events[ type ];
if ( !eventType ) {
continue;
}
if ( !handler ) {
for ( j = 0; j < eventType.length; j++ ) {
handleObj = eventType[ j ];
if ( all || namespace.test( handleObj.namespace ) ) {
jQuery.event.remove( elem, origType, handleObj.handler, j );
eventType.splice( j--, 1 );
}
}
continue;
}
special = jQuery.event.special[ type ] || {};
for ( j = pos || 0; j < eventType.length; j++ ) {
handleObj = eventType[ j ];
if ( handler.guid === handleObj.guid ) {
// remove the given handler for the given type
if ( all || namespace.test( handleObj.namespace ) ) {
if ( pos == null ) {
eventType.splice( j--, 1 );
}
if ( special.remove ) {
special.remove.call( elem, handleObj );
}
}
if ( pos != null ) {
break;
}
}
}
// remove generic event handler if no more handlers exist
if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
jQuery.removeEvent( elem, type, elemData.handle );
}
ret = null;
delete events[ type ];
}
}
// Remove the expando if it's no longer used
if ( jQuery.isEmptyObject( events ) ) {
var handle = elemData.handle;
if ( handle ) {
handle.elem = null;
}
delete elemData.events;
delete elemData.handle;
if ( typeof elemData === "function" ) {
jQuery.removeData( elem, eventKey, true );
} else if ( jQuery.isEmptyObject( elemData ) ) {
jQuery.removeData( elem, undefined, true );
}
}
},
// bubbling is internal
trigger: function( event, data, elem /*, bubbling */ ) {
// Event object or event type
var type = event.type || event,
bubbling = arguments[3];
if ( !bubbling ) {
event = typeof event === "object" ?
// jQuery.Event object
event[ jQuery.expando ] ? event :
// Object literal
jQuery.extend( jQuery.Event(type), event ) :
// Just the event type (string)
jQuery.Event(type);
if ( type.indexOf("!") >= 0 ) {
event.type = type = type.slice(0, -1);
event.exclusive = true;
}
// Handle a global trigger
if ( !elem ) {
// Don't bubble custom events when global (to avoid too much overhead)
event.stopPropagation();
// Only trigger if we've ever bound an event for it
if ( jQuery.event.global[ type ] ) {
// XXX This code smells terrible. event.js should not be directly
// inspecting the data cache
jQuery.each( jQuery.cache, function() {
// internalKey variable is just used to make it easier to find
// and potentially change this stuff later; currently it just
// points to jQuery.expando
var internalKey = jQuery.expando,
internalCache = this[ internalKey ];
if ( internalCache && internalCache.events && internalCache.events[type] ) {
jQuery.event.trigger( event, data, internalCache.handle.elem );
}
});
}
}
// Handle triggering a single element
// don't do events on text and comment nodes
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
return undefined;
}
// Clean up in case it is reused
event.result = undefined;
event.target = elem;
// Clone the incoming data, if any
data = jQuery.makeArray( data );
data.unshift( event );
}
event.currentTarget = elem;
// Trigger the event, it is assumed that "handle" is a function
var handle = elem.nodeType ?
jQuery._data( elem, "handle" ) :
(jQuery._data( elem, eventKey ) || {}).handle;
if ( handle ) {
handle.apply( elem, data );
}
var parent = elem.parentNode || elem.ownerDocument;
// Trigger an inline bound script
try {
if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
event.result = false;
event.preventDefault();
}
}
// prevent IE from throwing an error for some elements with some event types, see #3533
} catch (inlineError) {}
if ( !event.isPropagationStopped() && parent ) {
jQuery.event.trigger( event, data, parent, true );
} else if ( !event.isDefaultPrevented() ) {
var old,
target = event.target,
targetType = type.replace( rnamespaces, "" ),
isClick = jQuery.nodeName( target, "a" ) && targetType === "click",
special = jQuery.event.special[ targetType ] || {};
if ( (!special._default || special._default.call( elem, event ) === false) &&
!isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
try {
if ( target[ targetType ] ) {
// Make sure that we don't accidentally re-trigger the onFOO events
old = target[ "on" + targetType ];
if ( old ) {
target[ "on" + targetType ] = null;
}
jQuery.event.triggered = true;
target[ targetType ]();
}
// prevent IE from throwing an error for some elements with some event types, see #3533
} catch (triggerError) {}
if ( old ) {
target[ "on" + targetType ] = old;
}
jQuery.event.triggered = false;
}
}
},
handle: function( event ) {
var all, handlers, namespaces, namespace_re, events,
namespace_sort = [],
args = jQuery.makeArray( arguments );
event = args[0] = jQuery.event.fix( event || window.event );
event.currentTarget = this;
// Namespaced event handlers
all = event.type.indexOf(".") < 0 && !event.exclusive;
if ( !all ) {
namespaces = event.type.split(".");
event.type = namespaces.shift();
namespace_sort = namespaces.slice(0).sort();
namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)");
}
event.namespace = event.namespace || namespace_sort.join(".");
events = jQuery._data(this, eventKey);
if ( typeof events === "function" ) {
events = events.events;
}
handlers = (events || {})[ event.type ];
if ( events && handlers ) {
// Clone the handlers to prevent manipulation
handlers = handlers.slice(0);
for ( var j = 0, l = handlers.length; j < l; j++ ) {
var handleObj = handlers[ j ];
// Filter the functions by class
if ( all || namespace_re.test( handleObj.namespace ) ) {
// Pass in a reference to the handler function itself
// So that we can later remove it
event.handler = handleObj.handler;
event.data = handleObj.data;
event.handleObj = handleObj;
var ret = handleObj.handler.apply( this, args );
if ( ret !== undefined ) {
event.result = ret;
if ( ret === false ) {
event.preventDefault();
event.stopPropagation();
}
}
if ( event.isImmediatePropagationStopped() ) {
break;
}
}
}
}
return event.result;
},
props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix: function( event ) {
if ( event[ jQuery.expando ] ) {
return event;
}
// store a copy of the original event object
// and "clone" to set read-only properties
var originalEvent = event;
event = jQuery.Event( originalEvent );
for ( var i = this.props.length, prop; i; ) {
prop = this.props[ --i ];
event[ prop ] = originalEvent[ prop ];
}
// Fix target property, if necessary
if ( !event.target ) {
// Fixes #1925 where srcElement might not be defined either
event.target = event.srcElement || document;
}
// check if target is a textnode (safari)
if ( event.target.nodeType === 3 ) {
event.target = event.target.parentNode;
}
// Add relatedTarget, if necessary
if ( !event.relatedTarget && event.fromElement ) {
event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
}
// Calculate pageX/Y if missing and clientX/Y available
if ( event.pageX == null && event.clientX != null ) {
var doc = document.documentElement,
body = document.body;
event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
}
// Add which for key events
if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
event.which = event.charCode != null ? event.charCode : event.keyCode;
}
// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
if ( !event.metaKey && event.ctrlKey ) {
event.metaKey = event.ctrlKey;
}
// Add which for click: 1 === left; 2 === middle; 3 === right
// Note: button is not normalized, so don't use it
if ( !event.which && event.button !== undefined ) {
event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
}
return event;
},
// Deprecated, use jQuery.guid instead
guid: 1E8,
// Deprecated, use jQuery.proxy instead
proxy: jQuery.proxy,
special: {
ready: {
// Make sure the ready event is setup
setup: jQuery.bindReady,
teardown: jQuery.noop
},
live: {
add: function( handleObj ) {
jQuery.event.add( this,
liveConvert( handleObj.origType, handleObj.selector ),
jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) );
},
remove: function( handleObj ) {
jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
}
},
beforeunload: {
setup: function( data, namespaces, eventHandle ) {
// We only want to do this special case on windows
if ( jQuery.isWindow( this ) ) {
this.onbeforeunload = eventHandle;
}
},
teardown: function( namespaces, eventHandle ) {
if ( this.onbeforeunload === eventHandle ) {
this.onbeforeunload = null;
}
}
}
}
};
jQuery.removeEvent = document.removeEventListener ?
function( elem, type, handle ) {
if ( elem.removeEventListener ) {
elem.removeEventListener( type, handle, false );
}
} :
function( elem, type, handle ) {
if ( elem.detachEvent ) {
elem.detachEvent( "on" + type, handle );
}
};
jQuery.Event = function( src ) {
// Allow instantiation without the 'new' keyword
if ( !this.preventDefault ) {
return new jQuery.Event( src );
}
// Event object
if ( src && src.type ) {
this.originalEvent = src;
this.type = src.type;
// Events bubbling up the document may have been marked as prevented
// by a handler lower down the tree; reflect the correct value.
this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
// Event type
} else {
this.type = src;
}
// timeStamp is buggy for some events on Firefox(#3843)
// So we won't rely on the native value
this.timeStamp = jQuery.now();
// Mark it as fixed
this[ jQuery.expando ] = true;
};
function returnFalse() {
return false;
}
function returnTrue() {
return true;
}
// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
preventDefault: function() {
this.isDefaultPrevented = returnTrue;
var e = this.originalEvent;
if ( !e ) {
return;
}
// if preventDefault exists run it on the original event
if ( e.preventDefault ) {
e.preventDefault();
// otherwise set the returnValue property of the original event to false (IE)
} else {
e.returnValue = false;
}
},
stopPropagation: function() {
this.isPropagationStopped = returnTrue;
var e = this.originalEvent;
if ( !e ) {
return;
}
// if stopPropagation exists run it on the original event
if ( e.stopPropagation ) {
e.stopPropagation();
}
// otherwise set the cancelBubble property of the original event to true (IE)
e.cancelBubble = true;
},
stopImmediatePropagation: function() {
this.isImmediatePropagationStopped = returnTrue;
this.stopPropagation();
},
isDefaultPrevented: returnFalse,
isPropagationStopped: returnFalse,
isImmediatePropagationStopped: returnFalse
};
// Checks if an event happened on an element within another element
// Used in jQuery.event.special.mouseenter and mouseleave handlers
var withinElement = function( event ) {
// Check if mouse(over|out) are still within the same parent element
var parent = event.relatedTarget;
// Firefox sometimes assigns relatedTarget a XUL element
// which we cannot access the parentNode property of
try {
// Traverse up the tree
while ( parent && parent !== this ) {
parent = parent.parentNode;
}
if ( parent !== this ) {
// set the correct event type
event.type = event.data;
// handle event if we actually just moused on to a non sub-element
jQuery.event.handle.apply( this, arguments );
}
// assuming we've left the element since we most likely mousedover a xul element
} catch(e) { }
},
// In case of event delegation, we only need to rename the event.type,
// liveHandler will take care of the rest.
delegate = function( event ) {
event.type = event.data;
jQuery.event.handle.apply( this, arguments );
};
// Create mouseenter and mouseleave events
jQuery.each({
mouseenter: "mouseover",
mouseleave: "mouseout"
}, function( orig, fix ) {
jQuery.event.special[ orig ] = {
setup: function( data ) {
jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
},
teardown: function( data ) {
jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
}
};
});
// submit delegation
if ( !jQuery.support.submitBubbles ) {
jQuery.event.special.submit = {
setup: function( data, namespaces ) {
if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) {
jQuery.event.add(this, "click.specialSubmit", function( e ) {
var elem = e.target,
type = elem.type;
if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
e.liveFired = undefined;
return trigger( "submit", this, arguments );
}
});
jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
var elem = e.target,
type = elem.type;
if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
e.liveFired = undefined;
return trigger( "submit", this, arguments );
}
});
} else {
return false;
}
},
teardown: function( namespaces ) {
jQuery.event.remove( this, ".specialSubmit" );
}
};
}
// change delegation, happens here so we have bind.
if ( !jQuery.support.changeBubbles ) {
var changeFilters,
getVal = function( elem ) {
var type = elem.type, val = elem.value;
if ( type === "radio" || type === "checkbox" ) {
val = elem.checked;
} else if ( type === "select-multiple" ) {
val = elem.selectedIndex > -1 ?
jQuery.map( elem.options, function( elem ) {
return elem.selected;
}).join("-") :
"";
} else if ( elem.nodeName.toLowerCase() === "select" ) {
val = elem.selectedIndex;
}
return val;
},
testChange = function testChange( e ) {
var elem = e.target, data, val;
if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) {
return;
}
data = jQuery._data( elem, "_change_data" );
val = getVal(elem);
// the current data will be also retrieved by beforeactivate
if ( e.type !== "focusout" || elem.type !== "radio" ) {
jQuery._data( elem, "_change_data", val );
}
if ( data === undefined || val === data ) {
return;
}
if ( data != null || val ) {
e.type = "change";
e.liveFired = undefined;
return jQuery.event.trigger( e, arguments[1], elem );
}
};
jQuery.event.special.change = {
filters: {
focusout: testChange,
beforedeactivate: testChange,
click: function( e ) {
var elem = e.target, type = elem.type;
if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
return testChange.call( this, e );
}
},
// Change has to be called before submit
// Keydown will be called before keypress, which is used in submit-event delegation
keydown: function( e ) {
var elem = e.target, type = elem.type;
if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
(e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
type === "select-multiple" ) {
return testChange.call( this, e );
}
},
// Beforeactivate happens also before the previous element is blurred
// with this event you can't trigger a change event, but you can store
// information
beforeactivate: function( e ) {
var elem = e.target;
jQuery._data( elem, "_change_data", getVal(elem) );
}
},
setup: function( data, namespaces ) {
if ( this.type === "file" ) {
return false;
}
for ( var type in changeFilters ) {
jQuery.event.add( this, type + ".specialChange", changeFilters[type] );
}
return rformElems.test( this.nodeName );
},
teardown: function( namespaces ) {
jQuery.event.remove( this, ".specialChange" );
return rformElems.test( this.nodeName );
}
};
changeFilters = jQuery.event.special.change.filters;
// Handle when the input is .focus()'d
changeFilters.focus = changeFilters.beforeactivate;
}
function trigger( type, elem, args ) {
args[0].type = type;
return jQuery.event.handle.apply( elem, args );
}
// Create "bubbling" focus and blur events
if ( document.addEventListener ) {
jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
jQuery.event.special[ fix ] = {
setup: function() {
this.addEventListener( orig, handler, true );
},
teardown: function() {
this.removeEventListener( orig, handler, true );
}
};
function handler( e ) {
e = jQuery.event.fix( e );
e.type = fix;
return jQuery.event.handle.call( this, e );
}
});
}
jQuery.each(["bind", "one"], function( i, name ) {
jQuery.fn[ name ] = function( type, data, fn ) {
// Handle object literals
if ( typeof type === "object" ) {
for ( var key in type ) {
this[ name ](key, data, type[key], fn);
}
return this;
}
if ( jQuery.isFunction( data ) || data === false ) {
fn = data;
data = undefined;
}
var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
jQuery( this ).unbind( event, handler );
return fn.apply( this, arguments );
}) : fn;
if ( type === "unload" && name !== "one" ) {
this.one( type, data, fn );
} else {
for ( var i = 0, l = this.length; i < l; i++ ) {
jQuery.event.add( this[i], type, handler, data );
}
}
return this;
};
});
jQuery.fn.extend({
unbind: function( type, fn ) {
// Handle object literals
if ( typeof type === "object" && !type.preventDefault ) {
for ( var key in type ) {
this.unbind(key, type[key]);
}
} else {
for ( var i = 0, l = this.length; i < l; i++ ) {
jQuery.event.remove( this[i], type, fn );
}
}
return this;
},
delegate: function( selector, types, data, fn ) {
return this.live( types, data, fn, selector );
},
undelegate: function( selector, types, fn ) {
if ( arguments.length === 0 ) {
return this.unbind( "live" );
} else {
return this.die( types, null, fn, selector );
}
},
trigger: function( type, data ) {
return this.each(function() {
jQuery.event.trigger( type, data, this );
});
},
triggerHandler: function( type, data ) {
if ( this[0] ) {
var event = jQuery.Event( type );
event.preventDefault();
event.stopPropagation();
jQuery.event.trigger( event, data, this[0] );
return event.result;
}
},
toggle: function( fn ) {
// Save reference to arguments for access in closure
var args = arguments,
i = 1;
// link all the functions, so any of them can unbind this click handler
while ( i < args.length ) {
jQuery.proxy( fn, args[ i++ ] );
}
return this.click( jQuery.proxy( fn, function( event ) {
// Figure out which function to execute
var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
// Make sure that clicks stop
event.preventDefault();
// and execute the function
return args[ lastToggle ].apply( this, arguments ) || false;
}));
},
hover: function( fnOver, fnOut ) {
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
}
});
var liveMap = {
focus: "focusin",
blur: "focusout",
mouseenter: "mouseover",
mouseleave: "mouseout"
};
jQuery.each(["live", "die"], function( i, name ) {
jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
var type, i = 0, match, namespaces, preType,
selector = origSelector || this.selector,
context = origSelector ? this : jQuery( this.context );
if ( typeof types === "object" && !types.preventDefault ) {
for ( var key in types ) {
context[ name ]( key, data, types[key], selector );
}
return this;
}
if ( jQuery.isFunction( data ) ) {
fn = data;
data = undefined;
}
types = (types || "").split(" ");
while ( (type = types[ i++ ]) != null ) {
match = rnamespaces.exec( type );
namespaces = "";
if ( match ) {
namespaces = match[0];
type = type.replace( rnamespaces, "" );
}
if ( type === "hover" ) {
types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
continue;
}
preType = type;
if ( type === "focus" || type === "blur" ) {
types.push( liveMap[ type ] + namespaces );
type = type + namespaces;
} else {
type = (liveMap[ type ] || type) + namespaces;
}
if ( name === "live" ) {
// bind live handler
for ( var j = 0, l = context.length; j < l; j++ ) {
jQuery.event.add( context[j], "live." + liveConvert( type, selector ),
{ data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
}
} else {
// unbind live handler
context.unbind( "live." + liveConvert( type, selector ), fn );
}
}
return this;
};
});
function liveHandler( event ) {
var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
elems = [],
selectors = [],
events = jQuery._data( this, eventKey );
if ( typeof events === "function" ) {
events = events.events;
}
// Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
return;
}
if ( event.namespace ) {
namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)");
}
event.liveFired = this;
var live = events.live.slice(0);
for ( j = 0; j < live.length; j++ ) {
handleObj = live[j];
if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
selectors.push( handleObj.selector );
} else {
live.splice( j--, 1 );
}
}
match = jQuery( event.target ).closest( selectors, event.currentTarget );
for ( i = 0, l = match.length; i < l; i++ ) {
close = match[i];
for ( j = 0; j < live.length; j++ ) {
handleObj = live[j];
if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) {
elem = close.elem;
related = null;
// Those two events require additional checking
if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
event.type = handleObj.preType;
related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
}
if ( !related || related !== elem ) {
elems.push({ elem: elem, handleObj: handleObj, level: close.level });
}
}
}
}
for ( i = 0, l = elems.length; i < l; i++ ) {
match = elems[i];
if ( maxLevel && match.level > maxLevel ) {
break;
}
event.currentTarget = match.elem;
event.data = match.handleObj.data;
event.handleObj = match.handleObj;
ret = match.handleObj.origHandler.apply( match.elem, arguments );
if ( ret === false || event.isPropagationStopped() ) {
maxLevel = match.level;
if ( ret === false ) {
stop = false;
}
if ( event.isImmediatePropagationStopped() ) {
break;
}
}
}
return stop;
}
function liveConvert( type, selector ) {
return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&");
}
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error").split(" "), function( i, name ) {
// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
if ( fn == null ) {
fn = data;
data = null;
}
return arguments.length > 0 ?
this.bind( name, data, fn ) :
this.trigger( name );
};
if ( jQuery.attrFn ) {
jQuery.attrFn[ name ] = true;
}
});
/*!
* Sizzle CSS Selector Engine
* Copyright 2011, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
* More information: http://sizzlejs.com/
*/
(function(){
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
done = 0,
toString = Object.prototype.toString,
hasDuplicate = false,
baseHasDuplicate = true;
// Here we check if the JavaScript engine is using some sort of
// optimization where it does not always call our comparision
// function. If that is the case, discard the hasDuplicate value.
// Thus far that includes Google Chrome.
[0, 0].sort(function() {
baseHasDuplicate = false;
return 0;
});
var Sizzle = function( selector, context, results, seed ) {
results = results || [];
context = context || document;
var origContext = context;
if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
return [];
}
if ( !selector || typeof selector !== "string" ) {
return results;
}
var m, set, checkSet, extra, ret, cur, pop, i,
prune = true,
contextXML = Sizzle.isXML( context ),
parts = [],
soFar = selector;
// Reset the position of the chunker regexp (start from head)
do {
chunker.exec( "" );
m = chunker.exec( soFar );
if ( m ) {
soFar = m[3];
parts.push( m[1] );
if ( m[2] ) {
extra = m[3];
break;
}
}
} while ( m );
if ( parts.length > 1 && origPOS.exec( selector ) ) {
if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
set = posProcess( parts[0] + parts[1], context );
} else {
set = Expr.relative[ parts[0] ] ?
[ context ] :
Sizzle( parts.shift(), context );
while ( parts.length ) {
selector = parts.shift();
if ( Expr.relative[ selector ] ) {
selector += parts.shift();
}
set = posProcess( selector, set );
}
}
} else {
// Take a shortcut and set the context if the root selector is an ID
// (but not if it'll be faster if the inner selector is an ID)
if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
ret = Sizzle.find( parts.shift(), context, contextXML );
context = ret.expr ?
Sizzle.filter( ret.expr, ret.set )[0] :
ret.set[0];
}
if ( context ) {
ret = seed ?
{ expr: parts.pop(), set: makeArray(seed) } :
Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
set = ret.expr ?
Sizzle.filter( ret.expr, ret.set ) :
ret.set;
if ( parts.length > 0 ) {
checkSet = makeArray( set );
} else {
prune = false;
}
while ( parts.length ) {
cur = parts.pop();
pop = cur;
if ( !Expr.relative[ cur ] ) {
cur = "";
} else {
pop = parts.pop();
}
if ( pop == null ) {
pop = context;
}
Expr.relative[ cur ]( checkSet, pop, contextXML );
}
} else {
checkSet = parts = [];
}
}
if ( !checkSet ) {
checkSet = set;
}
if ( !checkSet ) {
Sizzle.error( cur || selector );
}
if ( toString.call(checkSet) === "[object Array]" ) {
if ( !prune ) {
results.push.apply( results, checkSet );
} else if ( context && context.nodeType === 1 ) {
for ( i = 0; checkSet[i] != null; i++ ) {
if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
results.push( set[i] );
}
}
} else {
for ( i = 0; checkSet[i] != null; i++ ) {
if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
results.push( set[i] );
}
}
}
} else {
makeArray( checkSet, results );
}
if ( extra ) {
Sizzle( extra, origContext, results, seed );
Sizzle.uniqueSort( results );
}
return results;
};
Sizzle.uniqueSort = function( results ) {
if ( sortOrder ) {
hasDuplicate = baseHasDuplicate;
results.sort( sortOrder );
if ( hasDuplicate ) {
for ( var i = 1; i < results.length; i++ ) {
if ( results[i] === results[ i - 1 ] ) {
results.splice( i--, 1 );
}
}
}
}
return results;
};
Sizzle.matches = function( expr, set ) {
return Sizzle( expr, null, null, set );
};
Sizzle.matchesSelector = function( node, expr ) {
return Sizzle( expr, null, null, [node] ).length > 0;
};
Sizzle.find = function( expr, context, isXML ) {
var set;
if ( !expr ) {
return [];
}
for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
var match,
type = Expr.order[i];
if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
var left = match[1];
match.splice( 1, 1 );
if ( left.substr( left.length - 1 ) !== "\\" ) {
match[1] = (match[1] || "").replace(/\\/g, "");
set = Expr.find[ type ]( match, context, isXML );
if ( set != null ) {
expr = expr.replace( Expr.match[ type ], "" );
break;
}
}
}
}
if ( !set ) {
set = typeof context.getElementsByTagName !== "undefined" ?
context.getElementsByTagName( "*" ) :
[];
}
return { set: set, expr: expr };
};
Sizzle.filter = function( expr, set, inplace, not ) {
var match, anyFound,
old = expr,
result = [],
curLoop = set,
isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
while ( expr && set.length ) {
for ( var type in Expr.filter ) {
if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
var found, item,
filter = Expr.filter[ type ],
left = match[1];
anyFound = false;
match.splice(1,1);
if ( left.substr( left.length - 1 ) === "\\" ) {
continue;
}
if ( curLoop === result ) {
result = [];
}
if ( Expr.preFilter[ type ] ) {
match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
if ( !match ) {
anyFound = found = true;
} else if ( match === true ) {
continue;
}
}
if ( match ) {
for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
if ( item ) {
found = filter( item, match, i, curLoop );
var pass = not ^ !!found;
if ( inplace && found != null ) {
if ( pass ) {
anyFound = true;
} else {
curLoop[i] = false;
}
} else if ( pass ) {
result.push( item );
anyFound = true;
}
}
}
}
if ( found !== undefined ) {
if ( !inplace ) {
curLoop = result;
}
expr = expr.replace( Expr.match[ type ], "" );
if ( !anyFound ) {
return [];
}
break;
}
}
}
// Improper expression
if ( expr === old ) {
if ( anyFound == null ) {
Sizzle.error( expr );
} else {
break;
}
}
old = expr;
}
return curLoop;
};
Sizzle.error = function( msg ) {
throw "Syntax error, unrecognized expression: " + msg;
};
var Expr = Sizzle.selectors = {
order: [ "ID", "NAME", "TAG" ],
match: {
ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
},
leftMatch: {},
attrMap: {
"class": "className",
"for": "htmlFor"
},
attrHandle: {
href: function( elem ) {
return elem.getAttribute( "href" );
}
},
relative: {
"+": function(checkSet, part){
var isPartStr = typeof part === "string",
isTag = isPartStr && !/\W/.test( part ),
isPartStrNotTag = isPartStr && !isTag;
if ( isTag ) {
part = part.toLowerCase();
}
for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
if ( (elem = checkSet[i]) ) {
while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
elem || false :
elem === part;
}
}
if ( isPartStrNotTag ) {
Sizzle.filter( part, checkSet, true );
}
},
">": function( checkSet, part ) {
var elem,
isPartStr = typeof part === "string",
i = 0,
l = checkSet.length;
if ( isPartStr && !/\W/.test( part ) ) {
part = part.toLowerCase();
for ( ; i < l; i++ ) {
elem = checkSet[i];
if ( elem ) {
var parent = elem.parentNode;
checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
}
}
} else {
for ( ; i < l; i++ ) {
elem = checkSet[i];
if ( elem ) {
checkSet[i] = isPartStr ?
elem.parentNode :
elem.parentNode === part;
}
}
if ( isPartStr ) {
Sizzle.filter( part, checkSet, true );
}
}
},
"": function(checkSet, part, isXML){
var nodeCheck,
doneName = done++,
checkFn = dirCheck;
if ( typeof part === "string" && !/\W/.test(part) ) {
part = part.toLowerCase();
nodeCheck = part;
checkFn = dirNodeCheck;
}
checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
},
"~": function( checkSet, part, isXML ) {
var nodeCheck,
doneName = done++,
checkFn = dirCheck;
if ( typeof part === "string" && !/\W/.test( part ) ) {
part = part.toLowerCase();
nodeCheck = part;
checkFn = dirNodeCheck;
}
checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
}
},
find: {
ID: function( match, context, isXML ) {
if ( typeof context.getElementById !== "undefined" && !isXML ) {
var m = context.getElementById(match[1]);
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
return m && m.parentNode ? [m] : [];
}
},
NAME: function( match, context ) {
if ( typeof context.getElementsByName !== "undefined" ) {
var ret = [],
results = context.getElementsByName( match[1] );
for ( var i = 0, l = results.length; i < l; i++ ) {
if ( results[i].getAttribute("name") === match[1] ) {
ret.push( results[i] );
}
}
return ret.length === 0 ? null : ret;
}
},
TAG: function( match, context ) {
if ( typeof context.getElementsByTagName !== "undefined" ) {
return context.getElementsByTagName( match[1] );
}
}
},
preFilter: {
CLASS: function( match, curLoop, inplace, result, not, isXML ) {
match = " " + match[1].replace(/\\/g, "") + " ";
if ( isXML ) {
return match;
}
for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
if ( elem ) {
if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
if ( !inplace ) {
result.push( elem );
}
} else if ( inplace ) {
curLoop[i] = false;
}
}
}
return false;
},
ID: function( match ) {
return match[1].replace(/\\/g, "");
},
TAG: function( match, curLoop ) {
return match[1].toLowerCase();
},
CHILD: function( match ) {
if ( match[1] === "nth" ) {
if ( !match[2] ) {
Sizzle.error( match[0] );
}
match[2] = match[2].replace(/^\+|\s*/g, '');
// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
// calculate the numbers (first)n+(last) including if they are negative
match[2] = (test[1] + (test[2] || 1)) - 0;
match[3] = test[3] - 0;
}
else if ( match[2] ) {
Sizzle.error( match[0] );
}
// TODO: Move to normal caching system
match[0] = done++;
return match;
},
ATTR: function( match, curLoop, inplace, result, not, isXML ) {
var name = match[1] = match[1].replace(/\\/g, "");
if ( !isXML && Expr.attrMap[name] ) {
match[1] = Expr.attrMap[name];
}
// Handle if an un-quoted value was used
match[4] = ( match[4] || match[5] || "" ).replace(/\\/g, "");
if ( match[2] === "~=" ) {
match[4] = " " + match[4] + " ";
}
return match;
},
PSEUDO: function( match, curLoop, inplace, result, not ) {
if ( match[1] === "not" ) {
// If we're dealing with a complex expression, or a simple one
if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
match[3] = Sizzle(match[3], null, null, curLoop);
} else {
var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
if ( !inplace ) {
result.push.apply( result, ret );
}
return false;
}
} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
return true;
}
return match;
},
POS: function( match ) {
match.unshift( true );
return match;
}
},
filters: {
enabled: function( elem ) {
return elem.disabled === false && elem.type !== "hidden";
},
disabled: function( elem ) {
return elem.disabled === true;
},
checked: function( elem ) {
return elem.checked === true;
},
selected: function( elem ) {
// Accessing this property makes selected-by-default
// options in Safari work properly
elem.parentNode.selectedIndex;
return elem.selected === true;
},
parent: function( elem ) {
return !!elem.firstChild;
},
empty: function( elem ) {
return !elem.firstChild;
},
has: function( elem, i, match ) {
return !!Sizzle( match[3], elem ).length;
},
header: function( elem ) {
return (/h\d/i).test( elem.nodeName );
},
text: function( elem ) {
return "text" === elem.type;
},
radio: function( elem ) {
return "radio" === elem.type;
},
checkbox: function( elem ) {
return "checkbox" === elem.type;
},
file: function( elem ) {
return "file" === elem.type;
},
password: function( elem ) {
return "password" === elem.type;
},
submit: function( elem ) {
return "submit" === elem.type;
},
image: function( elem ) {
return "image" === elem.type;
},
reset: function( elem ) {
return "reset" === elem.type;
},
button: function( elem ) {
return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
},
input: function( elem ) {
return (/input|select|textarea|button/i).test( elem.nodeName );
}
},
setFilters: {
first: function( elem, i ) {
return i === 0;
},
last: function( elem, i, match, array ) {
return i === array.length - 1;
},
even: function( elem, i ) {
return i % 2 === 0;
},
odd: function( elem, i ) {
return i % 2 === 1;
},
lt: function( elem, i, match ) {
return i < match[3] - 0;
},
gt: function( elem, i, match ) {
return i > match[3] - 0;
},
nth: function( elem, i, match ) {
return match[3] - 0 === i;
},
eq: function( elem, i, match ) {
return match[3] - 0 === i;
}
},
filter: {
PSEUDO: function( elem, match, i, array ) {
var name = match[1],
filter = Expr.filters[ name ];
if ( filter ) {
return filter( elem, i, match, array );
} else if ( name === "contains" ) {
return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
} else if ( name === "not" ) {
var not = match[3];
for ( var j = 0, l = not.length; j < l; j++ ) {
if ( not[j] === elem ) {
return false;
}
}
return true;
} else {
Sizzle.error( name );
}
},
CHILD: function( elem, match ) {
var type = match[1],
node = elem;
switch ( type ) {
case "only":
case "first":
while ( (node = node.previousSibling) ) {
if ( node.nodeType === 1 ) {
return false;
}
}
if ( type === "first" ) {
return true;
}
node = elem;
case "last":
while ( (node = node.nextSibling) ) {
if ( node.nodeType === 1 ) {
return false;
}
}
return true;
case "nth":
var first = match[2],
last = match[3];
if ( first === 1 && last === 0 ) {
return true;
}
var doneName = match[0],
parent = elem.parentNode;
if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
var count = 0;
for ( node = parent.firstChild; node; node = node.nextSibling ) {
if ( node.nodeType === 1 ) {
node.nodeIndex = ++count;
}
}
parent.sizcache = doneName;
}
var diff = elem.nodeIndex - last;
if ( first === 0 ) {
return diff === 0;
} else {
return ( diff % first === 0 && diff / first >= 0 );
}
}
},
ID: function( elem, match ) {
return elem.nodeType === 1 && elem.getAttribute("id") === match;
},
TAG: function( elem, match ) {
return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
},
CLASS: function( elem, match ) {
return (" " + (elem.className || elem.getAttribute("class")) + " ")
.indexOf( match ) > -1;
},
ATTR: function( elem, match ) {
var name = match[1],
result = Expr.attrHandle[ name ] ?
Expr.attrHandle[ name ]( elem ) :
elem[ name ] != null ?
elem[ name ] :
elem.getAttribute( name ),
value = result + "",
type = match[2],
check = match[4];
return result == null ?
type === "!=" :
type === "=" ?
value === check :
type === "*=" ?
value.indexOf(check) >= 0 :
type === "~=" ?
(" " + value + " ").indexOf(check) >= 0 :
!check ?
value && result !== false :
type === "!=" ?
value !== check :
type === "^=" ?
value.indexOf(check) === 0 :
type === "$=" ?
value.substr(value.length - check.length) === check :
type === "|=" ?
value === check || value.substr(0, check.length + 1) === check + "-" :
false;
},
POS: function( elem, match, i, array ) {
var name = match[2],
filter = Expr.setFilters[ name ];
if ( filter ) {
return filter( elem, i, match, array );
}
}
}
};
var origPOS = Expr.match.POS,
fescape = function(all, num){
return "\\" + (num - 0 + 1);
};
for ( var type in Expr.match ) {
Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
}
var makeArray = function( array, results ) {
array = Array.prototype.slice.call( array, 0 );
if ( results ) {
results.push.apply( results, array );
return results;
}
return array;
};
// Perform a simple check to determine if the browser is capable of
// converting a NodeList to an array using builtin methods.
// Also verifies that the returned array holds DOM nodes
// (which is not the case in the Blackberry browser)
try {
Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
// Provide a fallback method if it does not work
} catch( e ) {
makeArray = function( array, results ) {
var i = 0,
ret = results || [];
if ( toString.call(array) === "[object Array]" ) {
Array.prototype.push.apply( ret, array );
} else {
if ( typeof array.length === "number" ) {
for ( var l = array.length; i < l; i++ ) {
ret.push( array[i] );
}
} else {
for ( ; array[i]; i++ ) {
ret.push( array[i] );
}
}
}
return ret;
};
}
var sortOrder, siblingCheck;
if ( document.documentElement.compareDocumentPosition ) {
sortOrder = function( a, b ) {
if ( a === b ) {
hasDuplicate = true;
return 0;
}
if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
return a.compareDocumentPosition ? -1 : 1;
}
return a.compareDocumentPosition(b) & 4 ? -1 : 1;
};
} else {
sortOrder = function( a, b ) {
var al, bl,
ap = [],
bp = [],
aup = a.parentNode,
bup = b.parentNode,
cur = aup;
// The nodes are identical, we can exit early
if ( a === b ) {
hasDuplicate = true;
return 0;
// If the nodes are siblings (or identical) we can do a quick check
} else if ( aup === bup ) {
return siblingCheck( a, b );
// If no parents were found then the nodes are disconnected
} else if ( !aup ) {
return -1;
} else if ( !bup ) {
return 1;
}
// Otherwise they're somewhere else in the tree so we need
// to build up a full list of the parentNodes for comparison
while ( cur ) {
ap.unshift( cur );
cur = cur.parentNode;
}
cur = bup;
while ( cur ) {
bp.unshift( cur );
cur = cur.parentNode;
}
al = ap.length;
bl = bp.length;
// Start walking down the tree looking for a discrepancy
for ( var i = 0; i < al && i < bl; i++ ) {
if ( ap[i] !== bp[i] ) {
return siblingCheck( ap[i], bp[i] );
}
}
// We ended someplace up the tree so do a sibling check
return i === al ?
siblingCheck( a, bp[i], -1 ) :
siblingCheck( ap[i], b, 1 );
};
siblingCheck = function( a, b, ret ) {
if ( a === b ) {
return ret;
}
var cur = a.nextSibling;
while ( cur ) {
if ( cur === b ) {
return -1;
}
cur = cur.nextSibling;
}
return 1;
};
}
// Utility function for retreiving the text value of an array of DOM nodes
Sizzle.getText = function( elems ) {
var ret = "", elem;
for ( var i = 0; elems[i]; i++ ) {
elem = elems[i];
// Get the text from text nodes and CDATA nodes
if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
ret += elem.nodeValue;
// Traverse everything else, except comment nodes
} else if ( elem.nodeType !== 8 ) {
ret += Sizzle.getText( elem.childNodes );
}
}
return ret;
};
// Check to see if the browser returns elements by name when
// querying by getElementById (and provide a workaround)
(function(){
// We're going to inject a fake input element with a specified name
var form = document.createElement("div"),
id = "script" + (new Date()).getTime(),
root = document.documentElement;
form.innerHTML = "<a name='" + id + "'/>";
// Inject it into the root element, check its status, and remove it quickly
root.insertBefore( form, root.firstChild );
// The workaround has to do additional checks after a getElementById
// Which slows things down for other browsers (hence the branching)
if ( document.getElementById( id ) ) {
Expr.find.ID = function( match, context, isXML ) {
if ( typeof context.getElementById !== "undefined" && !isXML ) {
var m = context.getElementById(match[1]);
return m ?
m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
[m] :
undefined :
[];
}
};
Expr.filter.ID = function( elem, match ) {
var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
return elem.nodeType === 1 && node && node.nodeValue === match;
};
}
root.removeChild( form );
// release memory in IE
root = form = null;
})();
(function(){
// Check to see if the browser returns only elements
// when doing getElementsByTagName("*")
// Create a fake element
var div = document.createElement("div");
div.appendChild( document.createComment("") );
// Make sure no comments are found
if ( div.getElementsByTagName("*").length > 0 ) {
Expr.find.TAG = function( match, context ) {
var results = context.getElementsByTagName( match[1] );
// Filter out possible comments
if ( match[1] === "*" ) {
var tmp = [];
for ( var i = 0; results[i]; i++ ) {
if ( results[i].nodeType === 1 ) {
tmp.push( results[i] );
}
}
results = tmp;
}
return results;
};
}
// Check to see if an attribute returns normalized href attributes
div.innerHTML = "<a href='#'></a>";
if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
div.firstChild.getAttribute("href") !== "#" ) {
Expr.attrHandle.href = function( elem ) {
return elem.getAttribute( "href", 2 );
};
}
// release memory in IE
div = null;
})();
if ( document.querySelectorAll ) {
(function(){
var oldSizzle = Sizzle,
div = document.createElement("div"),
id = "__sizzle__";
div.innerHTML = "<p class='TEST'></p>";
// Safari can't handle uppercase or unicode characters when
// in quirks mode.
if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
return;
}
Sizzle = function( query, context, extra, seed ) {
context = context || document;
// Only use querySelectorAll on non-XML documents
// (ID selectors don't work in non-HTML documents)
if ( !seed && !Sizzle.isXML(context) ) {
// See if we find a selector to speed up
var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
// Speed-up: Sizzle("TAG")
if ( match[1] ) {
return makeArray( context.getElementsByTagName( query ), extra );
// Speed-up: Sizzle(".CLASS")
} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
return makeArray( context.getElementsByClassName( match[2] ), extra );
}
}
if ( context.nodeType === 9 ) {
// Speed-up: Sizzle("body")
// The body element only exists once, optimize finding it
if ( query === "body" && context.body ) {
return makeArray( [ context.body ], extra );
// Speed-up: Sizzle("#ID")
} else if ( match && match[3] ) {
var elem = context.getElementById( match[3] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem.id === match[3] ) {
return makeArray( [ elem ], extra );
}
} else {
return makeArray( [], extra );
}
}
try {
return makeArray( context.querySelectorAll(query), extra );
} catch(qsaError) {}
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
var old = context.getAttribute( "id" ),
nid = old || id,
hasParent = context.parentNode,
relativeHierarchySelector = /^\s*[+~]/.test( query );
if ( !old ) {
context.setAttribute( "id", nid );
} else {
nid = nid.replace( /'/g, "\\$&" );
}
if ( relativeHierarchySelector && hasParent ) {
context = context.parentNode;
}
try {
if ( !relativeHierarchySelector || hasParent ) {
return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
}
} catch(pseudoError) {
} finally {
if ( !old ) {
context.removeAttribute( "id" );
}
}
}
}
return oldSizzle(query, context, extra, seed);
};
for ( var prop in oldSizzle ) {
Sizzle[ prop ] = oldSizzle[ prop ];
}
// release memory in IE
div = null;
})();
}
(function(){
var html = document.documentElement,
matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector,
pseudoWorks = false;
try {
// This should fail with an exception
// Gecko does not error, returns false instead
matches.call( document.documentElement, "[test!='']:sizzle" );
} catch( pseudoError ) {
pseudoWorks = true;
}
if ( matches ) {
Sizzle.matchesSelector = function( node, expr ) {
// Make sure that attribute selectors are quoted
expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
if ( !Sizzle.isXML( node ) ) {
try {
if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
return matches.call( node, expr );
}
} catch(e) {}
}
return Sizzle(expr, null, null, [node]).length > 0;
};
}
})();
(function(){
var div = document.createElement("div");
div.innerHTML = "<div class='test e'></div><div class='test'></div>";
// Opera can't find a second classname (in 9.6)
// Also, make sure that getElementsByClassName actually exists
if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
return;
}
// Safari caches class attributes, doesn't catch changes (in 3.2)
div.lastChild.className = "e";
if ( div.getElementsByClassName("e").length === 1 ) {
return;
}
Expr.order.splice(1, 0, "CLASS");
Expr.find.CLASS = function( match, context, isXML ) {
if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
return context.getElementsByClassName(match[1]);
}
};
// release memory in IE
div = null;
})();
function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
var elem = checkSet[i];
if ( elem ) {
var match = false;
elem = elem[dir];
while ( elem ) {
if ( elem.sizcache === doneName ) {
match = checkSet[elem.sizset];
break;
}
if ( elem.nodeType === 1 && !isXML ){
elem.sizcache = doneName;
elem.sizset = i;
}
if ( elem.nodeName.toLowerCase() === cur ) {
match = elem;
break;
}
elem = elem[dir];
}
checkSet[i] = match;
}
}
}
function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
var elem = checkSet[i];
if ( elem ) {
var match = false;
elem = elem[dir];
while ( elem ) {
if ( elem.sizcache === doneName ) {
match = checkSet[elem.sizset];
break;
}
if ( elem.nodeType === 1 ) {
if ( !isXML ) {
elem.sizcache = doneName;
elem.sizset = i;
}
if ( typeof cur !== "string" ) {
if ( elem === cur ) {
match = true;
break;
}
} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
match = elem;
break;
}
}
elem = elem[dir];
}
checkSet[i] = match;
}
}
}
if ( document.documentElement.contains ) {
Sizzle.contains = function( a, b ) {
return a !== b && (a.contains ? a.contains(b) : true);
};
} else if ( document.documentElement.compareDocumentPosition ) {
Sizzle.contains = function( a, b ) {
return !!(a.compareDocumentPosition(b) & 16);
};
} else {
Sizzle.contains = function() {
return false;
};
}
Sizzle.isXML = function( elem ) {
// documentElement is verified for cases where it doesn't yet exist
// (such as loading iframes in IE - #4833)
var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
return documentElement ? documentElement.nodeName !== "HTML" : false;
};
var posProcess = function( selector, context ) {
var match,
tmpSet = [],
later = "",
root = context.nodeType ? [context] : context;
// Position selectors must be done after the filter
// And so must :not(positional) so we move all PSEUDOs to the end
while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
later += match[0];
selector = selector.replace( Expr.match.PSEUDO, "" );
}
selector = Expr.relative[selector] ? selector + "*" : selector;
for ( var i = 0, l = root.length; i < l; i++ ) {
Sizzle( selector, root[i], tmpSet );
}
return Sizzle.filter( later, tmpSet );
};
// EXPOSE
jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.filters;
jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;
})();
var runtil = /Until$/,
rparentsprev = /^(?:parents|prevUntil|prevAll)/,
// Note: This RegExp should be improved, or likely pulled from Sizzle
rmultiselector = /,/,
isSimple = /^.[^:#\[\.,]*$/,
slice = Array.prototype.slice,
POS = jQuery.expr.match.POS,
// methods guaranteed to produce a unique set when starting from a unique set
guaranteedUnique = {
children: true,
contents: true,
next: true,
prev: true
};
jQuery.fn.extend({
find: function( selector ) {
var ret = this.pushStack( "", "find", selector ),
length = 0;
for ( var i = 0, l = this.length; i < l; i++ ) {
length = ret.length;
jQuery.find( selector, this[i], ret );
if ( i > 0 ) {
// Make sure that the results are unique
for ( var n = length; n < ret.length; n++ ) {
for ( var r = 0; r < length; r++ ) {
if ( ret[r] === ret[n] ) {
ret.splice(n--, 1);
break;
}
}
}
}
}
return ret;
},
has: function( target ) {
var targets = jQuery( target );
return this.filter(function() {
for ( var i = 0, l = targets.length; i < l; i++ ) {
if ( jQuery.contains( this, targets[i] ) ) {
return true;
}
}
});
},
not: function( selector ) {
return this.pushStack( winnow(this, selector, false), "not", selector);
},
filter: function( selector ) {
return this.pushStack( winnow(this, selector, true), "filter", selector );
},
is: function( selector ) {
return !!selector && jQuery.filter( selector, this ).length > 0;
},
closest: function( selectors, context ) {
var ret = [], i, l, cur = this[0];
if ( jQuery.isArray( selectors ) ) {
var match, selector,
matches = {},
level = 1;
if ( cur && selectors.length ) {
for ( i = 0, l = selectors.length; i < l; i++ ) {
selector = selectors[i];
if ( !matches[selector] ) {
matches[selector] = jQuery.expr.match.POS.test( selector ) ?
jQuery( selector, context || this.context ) :
selector;
}
}
while ( cur && cur.ownerDocument && cur !== context ) {
for ( selector in matches ) {
match = matches[selector];
if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) {
ret.push({ selector: selector, elem: cur, level: level });
}
}
cur = cur.parentNode;
level++;
}
}
return ret;
}
var pos = POS.test( selectors ) ?
jQuery( selectors, context || this.context ) : null;
for ( i = 0, l = this.length; i < l; i++ ) {
cur = this[i];
while ( cur ) {
if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
ret.push( cur );
break;
} else {
cur = cur.parentNode;
if ( !cur || !cur.ownerDocument || cur === context ) {
break;
}
}
}
}
ret = ret.length > 1 ? jQuery.unique(ret) : ret;
return this.pushStack( ret, "closest", selectors );
},
// Determine the position of an element within
// the matched set of elements
index: function( elem ) {
if ( !elem || typeof elem === "string" ) {
return jQuery.inArray( this[0],
// If it receives a string, the selector is used
// If it receives nothing, the siblings are used
elem ? jQuery( elem ) : this.parent().children() );
}
// Locate the position of the desired element
return jQuery.inArray(
// If it receives a jQuery object, the first element is used
elem.jquery ? elem[0] : elem, this );
},
add: function( selector, context ) {
var set = typeof selector === "string" ?
jQuery( selector, context ) :
jQuery.makeArray( selector ),
all = jQuery.merge( this.get(), set );
return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
all :
jQuery.unique( all ) );
},
andSelf: function() {
return this.add( this.prevObject );
}
});
// A painfully simple check to see if an element is disconnected
// from a document (should be improved, where feasible).
function isDisconnected( node ) {
return !node || !node.parentNode || node.parentNode.nodeType === 11;
}
jQuery.each({
parent: function( elem ) {
var parent = elem.parentNode;
return parent && parent.nodeType !== 11 ? parent : null;
},
parents: function( elem ) {
return jQuery.dir( elem, "parentNode" );
},
parentsUntil: function( elem, i, until ) {
return jQuery.dir( elem, "parentNode", until );
},
next: function( elem ) {
return jQuery.nth( elem, 2, "nextSibling" );
},
prev: function( elem ) {
return jQuery.nth( elem, 2, "previousSibling" );
},
nextAll: function( elem ) {
return jQuery.dir( elem, "nextSibling" );
},
prevAll: function( elem ) {
return jQuery.dir( elem, "previousSibling" );
},
nextUntil: function( elem, i, until ) {
return jQuery.dir( elem, "nextSibling", until );
},
prevUntil: function( elem, i, until ) {
return jQuery.dir( elem, "previousSibling", until );
},
siblings: function( elem ) {
return jQuery.sibling( elem.parentNode.firstChild, elem );
},
children: function( elem ) {
return jQuery.sibling( elem.firstChild );
},
contents: function( elem ) {
return jQuery.nodeName( elem, "iframe" ) ?
elem.contentDocument || elem.contentWindow.document :
jQuery.makeArray( elem.childNodes );
}
}, function( name, fn ) {
jQuery.fn[ name ] = function( until, selector ) {
var ret = jQuery.map( this, fn, until ),
// The variable 'args' was introduced in
// https://github.com/jquery/jquery/commit/52a0238
// to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
// http://code.google.com/p/v8/issues/detail?id=1050
args = slice.call(arguments);
if ( !runtil.test( name ) ) {
selector = until;
}
if ( selector && typeof selector === "string" ) {
ret = jQuery.filter( selector, ret );
}
ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
ret = ret.reverse();
}
return this.pushStack( ret, name, args.join(",") );
};
});
jQuery.extend({
filter: function( expr, elems, not ) {
if ( not ) {
expr = ":not(" + expr + ")";
}
return elems.length === 1 ?
jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
jQuery.find.matches(expr, elems);
},
dir: function( elem, dir, until ) {
var matched = [],
cur = elem[ dir ];
while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
if ( cur.nodeType === 1 ) {
matched.push( cur );
}
cur = cur[dir];
}
return matched;
},
nth: function( cur, result, dir, elem ) {
result = result || 1;
var num = 0;
for ( ; cur; cur = cur[dir] ) {
if ( cur.nodeType === 1 && ++num === result ) {
break;
}
}
return cur;
},
sibling: function( n, elem ) {
var r = [];
for ( ; n; n = n.nextSibling ) {
if ( n.nodeType === 1 && n !== elem ) {
r.push( n );
}
}
return r;
}
});
// Implement the identical functionality for filter and not
function winnow( elements, qualifier, keep ) {
if ( jQuery.isFunction( qualifier ) ) {
return jQuery.grep(elements, function( elem, i ) {
var retVal = !!qualifier.call( elem, i, elem );
return retVal === keep;
});
} else if ( qualifier.nodeType ) {
return jQuery.grep(elements, function( elem, i ) {
return (elem === qualifier) === keep;
});
} else if ( typeof qualifier === "string" ) {
var filtered = jQuery.grep(elements, function( elem ) {
return elem.nodeType === 1;
});
if ( isSimple.test( qualifier ) ) {
return jQuery.filter(qualifier, filtered, !keep);
} else {
qualifier = jQuery.filter( qualifier, filtered );
}
}
return jQuery.grep(elements, function( elem, i ) {
return (jQuery.inArray( elem, qualifier ) >= 0) === keep;
});
}
var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
rleadingWhitespace = /^\s+/,
rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
rtagName = /<([\w:]+)/,
rtbody = /<tbody/i,
rhtml = /<|&#?\w+;/,
rnocache = /<(?:script|object|embed|option|style)/i,
// checked="checked" or checked (html5)
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
wrapMap = {
option: [ 1, "<select multiple='multiple'>", "</select>" ],
legend: [ 1, "<fieldset>", "</fieldset>" ],
thead: [ 1, "<table>", "</table>" ],
tr: [ 2, "<table><tbody>", "</tbody></table>" ],
td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
area: [ 1, "<map>", "</map>" ],
_default: [ 0, "", "" ]
};
wrapMap.optgroup = wrapMap.option;
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;
// IE can't serialize <link> and <script> tags normally
if ( !jQuery.support.htmlSerialize ) {
wrapMap._default = [ 1, "div<div>", "</div>" ];
}
jQuery.fn.extend({
text: function( text ) {
if ( jQuery.isFunction(text) ) {
return this.each(function(i) {
var self = jQuery( this );
self.text( text.call(this, i, self.text()) );
});
}
if ( typeof text !== "object" && text !== undefined ) {
return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
}
return jQuery.text( this );
},
wrapAll: function( html ) {
if ( jQuery.isFunction( html ) ) {
return this.each(function(i) {
jQuery(this).wrapAll( html.call(this, i) );
});
}
if ( this[0] ) {
// The elements to wrap the target around
var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
if ( this[0].parentNode ) {
wrap.insertBefore( this[0] );
}
wrap.map(function() {
var elem = this;
while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
elem = elem.firstChild;
}
return elem;
}).append(this);
}
return this;
},
wrapInner: function( html ) {
if ( jQuery.isFunction( html ) ) {
return this.each(function(i) {
jQuery(this).wrapInner( html.call(this, i) );
});
}
return this.each(function() {
var self = jQuery( this ),
contents = self.contents();
if ( contents.length ) {
contents.wrapAll( html );
} else {
self.append( html );
}
});
},
wrap: function( html ) {
return this.each(function() {
jQuery( this ).wrapAll( html );
});
},
unwrap: function() {
return this.parent().each(function() {
if ( !jQuery.nodeName( this, "body" ) ) {
jQuery( this ).replaceWith( this.childNodes );
}
}).end();
},
append: function() {
return this.domManip(arguments, true, function( elem ) {
if ( this.nodeType === 1 ) {
this.appendChild( elem );
}
});
},
prepend: function() {
return this.domManip(arguments, true, function( elem ) {
if ( this.nodeType === 1 ) {
this.insertBefore( elem, this.firstChild );
}
});
},
before: function() {
if ( this[0] && this[0].parentNode ) {
return this.domManip(arguments, false, function( elem ) {
this.parentNode.insertBefore( elem, this );
});
} else if ( arguments.length ) {
var set = jQuery(arguments[0]);
set.push.apply( set, this.toArray() );
return this.pushStack( set, "before", arguments );
}
},
after: function() {
if ( this[0] && this[0].parentNode ) {
return this.domManip(arguments, false, function( elem ) {
this.parentNode.insertBefore( elem, this.nextSibling );
});
} else if ( arguments.length ) {
var set = this.pushStack( this, "after", arguments );
set.push.apply( set, jQuery(arguments[0]).toArray() );
return set;
}
},
// keepData is for internal use only--do not document
remove: function( selector, keepData ) {
for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
if ( !keepData && elem.nodeType === 1 ) {
jQuery.cleanData( elem.getElementsByTagName("*") );
jQuery.cleanData( [ elem ] );
}
if ( elem.parentNode ) {
elem.parentNode.removeChild( elem );
}
}
}
return this;
},
empty: function() {
for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
// Remove element nodes and prevent memory leaks
if ( elem.nodeType === 1 ) {
jQuery.cleanData( elem.getElementsByTagName("*") );
}
// Remove any remaining nodes
while ( elem.firstChild ) {
elem.removeChild( elem.firstChild );
}
}
return this;
},
clone: function( dataAndEvents, deepDataAndEvents ) {
dataAndEvents = dataAndEvents == null ? true : dataAndEvents;
deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
return this.map( function () {
return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
});
},
html: function( value ) {
if ( value === undefined ) {
return this[0] && this[0].nodeType === 1 ?
this[0].innerHTML.replace(rinlinejQuery, "") :
null;
// See if we can take a shortcut and just use innerHTML
} else if ( typeof value === "string" && !rnocache.test( value ) &&
(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
value = value.replace(rxhtmlTag, "<$1></$2>");
try {
for ( var i = 0, l = this.length; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
if ( this[i].nodeType === 1 ) {
jQuery.cleanData( this[i].getElementsByTagName("*") );
this[i].innerHTML = value;
}
}
// If using innerHTML throws an exception, use the fallback method
} catch(e) {
this.empty().append( value );
}
} else if ( jQuery.isFunction( value ) ) {
this.each(function(i){
var self = jQuery( this );
self.html( value.call(this, i, self.html()) );
});
} else {
this.empty().append( value );
}
return this;
},
replaceWith: function( value ) {
if ( this[0] && this[0].parentNode ) {
// Make sure that the elements are removed from the DOM before they are inserted
// this can help fix replacing a parent with child elements
if ( jQuery.isFunction( value ) ) {
return this.each(function(i) {
var self = jQuery(this), old = self.html();
self.replaceWith( value.call( this, i, old ) );
});
}
if ( typeof value !== "string" ) {
value = jQuery( value ).detach();
}
return this.each(function() {
var next = this.nextSibling,
parent = this.parentNode;
jQuery( this ).remove();
if ( next ) {
jQuery(next).before( value );
} else {
jQuery(parent).append( value );
}
});
} else {
return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value );
}
},
detach: function( selector ) {
return this.remove( selector, true );
},
domManip: function( args, table, callback ) {
var results, first, fragment, parent,
value = args[0],
scripts = [];
// We can't cloneNode fragments that contain checked, in WebKit
if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
return this.each(function() {
jQuery(this).domManip( args, table, callback, true );
});
}
if ( jQuery.isFunction(value) ) {
return this.each(function(i) {
var self = jQuery(this);
args[0] = value.call(this, i, table ? self.html() : undefined);
self.domManip( args, table, callback );
});
}
if ( this[0] ) {
parent = value && value.parentNode;
// If we're in a fragment, just use that instead of building a new one
if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
results = { fragment: parent };
} else {
results = jQuery.buildFragment( args, this, scripts );
}
fragment = results.fragment;
if ( fragment.childNodes.length === 1 ) {
first = fragment = fragment.firstChild;
} else {
first = fragment.firstChild;
}
if ( first ) {
table = table && jQuery.nodeName( first, "tr" );
for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
callback.call(
table ?
root(this[i], first) :
this[i],
// Make sure that we do not leak memory by inadvertently discarding
// the original fragment (which might have attached data) instead of
// using it; in addition, use the original fragment object for the last
// item instead of first because it can end up being emptied incorrectly
// in certain situations (Bug #8070).
// Fragments from the fragment cache must always be cloned and never used
// in place.
results.cacheable || (l > 1 && i < lastIndex) ?
jQuery.clone( fragment, true, true ) :
fragment
);
}
}
if ( scripts.length ) {
jQuery.each( scripts, evalScript );
}
}
return this;
}
});
function root( elem, cur ) {
return jQuery.nodeName(elem, "table") ?
(elem.getElementsByTagName("tbody")[0] ||
elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
elem;
}
function cloneCopyEvent( src, dest ) {
if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
return;
}
var internalKey = jQuery.expando,
oldData = jQuery.data( src ),
curData = jQuery.data( dest, oldData );
// Switch to use the internal data object, if it exists, for the next
// stage of data copying
if ( (oldData = oldData[ internalKey ]) ) {
var events = oldData.events;
curData = curData[ internalKey ] = jQuery.extend({}, oldData);
if ( events ) {
delete curData.handle;
curData.events = {};
for ( var type in events ) {
for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
jQuery.event.add( dest, type, events[ type ][ i ], events[ type ][ i ].data );
}
}
}
}
}
function cloneFixAttributes(src, dest) {
// We do not need to do anything for non-Elements
if ( dest.nodeType !== 1 ) {
return;
}
var nodeName = dest.nodeName.toLowerCase();
// clearAttributes removes the attributes, which we don't want,
// but also removes the attachEvent events, which we *do* want
dest.clearAttributes();
// mergeAttributes, in contrast, only merges back on the
// original attributes, not the events
dest.mergeAttributes(src);
// IE6-8 fail to clone children inside object elements that use
// the proprietary classid attribute value (rather than the type
// attribute) to identify the type of content to display
if ( nodeName === "object" ) {
dest.outerHTML = src.outerHTML;
} else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
// IE6-8 fails to persist the checked state of a cloned checkbox
// or radio button. Worse, IE6-7 fail to give the cloned element
// a checked appearance if the defaultChecked value isn't also set
if ( src.checked ) {
dest.defaultChecked = dest.checked = src.checked;
}
// IE6-7 get confused and end up setting the value of a cloned
// checkbox/radio button to an empty string instead of "on"
if ( dest.value !== src.value ) {
dest.value = src.value;
}
// IE6-8 fails to return the selected option to the default selected
// state when cloning options
} else if ( nodeName === "option" ) {
dest.selected = src.defaultSelected;
// IE6-8 fails to set the defaultValue to the correct value when
// cloning other types of input fields
} else if ( nodeName === "input" || nodeName === "textarea" ) {
dest.defaultValue = src.defaultValue;
}
// Event data gets referenced instead of copied if the expando
// gets copied too
dest.removeAttribute( jQuery.expando );
}
jQuery.buildFragment = function( args, nodes, scripts ) {
var fragment, cacheable, cacheresults,
doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
// Only cache "small" (1/2 KB) HTML strings that are associated with the main document
// Cloning options loses the selected state, so don't cache them
// IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
// Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
cacheable = true;
cacheresults = jQuery.fragments[ args[0] ];
if ( cacheresults ) {
if ( cacheresults !== 1 ) {
fragment = cacheresults;
}
}
}
if ( !fragment ) {
fragment = doc.createDocumentFragment();
jQuery.clean( args, doc, fragment, scripts );
}
if ( cacheable ) {
jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
}
return { fragment: fragment, cacheable: cacheable };
};
jQuery.fragments = {};
jQuery.each({
appendTo: "append",
prependTo: "prepend",
insertBefore: "before",
insertAfter: "after",
replaceAll: "replaceWith"
}, function( name, original ) {
jQuery.fn[ name ] = function( selector ) {
var ret = [],
insert = jQuery( selector ),
parent = this.length === 1 && this[0].parentNode;
if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
insert[ original ]( this[0] );
return this;
} else {
for ( var i = 0, l = insert.length; i < l; i++ ) {
var elems = (i > 0 ? this.clone(true) : this).get();
jQuery( insert[i] )[ original ]( elems );
ret = ret.concat( elems );
}
return this.pushStack( ret, name, insert.selector );
}
};
});
jQuery.extend({
clone: function( elem, dataAndEvents, deepDataAndEvents ) {
var clone = elem.cloneNode(true),
srcElements,
destElements,
i;
if ( !jQuery.support.noCloneEvent && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
// IE copies events bound via attachEvent when using cloneNode.
// Calling detachEvent on the clone will also remove the events
// from the original. In order to get around this, we use some
// proprietary methods to clear the events. Thanks to MooTools
// guys for this hotness.
// Using Sizzle here is crazy slow, so we use getElementsByTagName
// instead
srcElements = elem.getElementsByTagName("*");
destElements = clone.getElementsByTagName("*");
// Weird iteration because IE will replace the length property
// with an element if you are cloning the body and one of the
// elements on the page has a name or id of "length"
for ( i = 0; srcElements[i]; ++i ) {
cloneFixAttributes( srcElements[i], destElements[i] );
}
cloneFixAttributes( elem, clone );
}
// Copy the events from the original to the clone
if ( dataAndEvents ) {
cloneCopyEvent( elem, clone );
if ( deepDataAndEvents && "getElementsByTagName" in elem ) {
srcElements = elem.getElementsByTagName("*");
destElements = clone.getElementsByTagName("*");
if ( srcElements.length ) {
for ( i = 0; srcElements[i]; ++i ) {
cloneCopyEvent( srcElements[i], destElements[i] );
}
}
}
}
// Return the cloned set
return clone;
},
clean: function( elems, context, fragment, scripts ) {
context = context || document;
// !context.createElement fails in IE with an error but returns typeof 'object'
if ( typeof context.createElement === "undefined" ) {
context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
}
var ret = [];
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
if ( typeof elem === "number" ) {
elem += "";
}
if ( !elem ) {
continue;
}
// Convert html string into DOM nodes
if ( typeof elem === "string" && !rhtml.test( elem ) ) {
elem = context.createTextNode( elem );
} else if ( typeof elem === "string" ) {
// Fix "XHTML"-style tags in all browsers
elem = elem.replace(rxhtmlTag, "<$1></$2>");
// Trim whitespace, otherwise indexOf won't work as expected
var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
wrap = wrapMap[ tag ] || wrapMap._default,
depth = wrap[0],
div = context.createElement("div");
// Go to html and back, then peel off extra wrappers
div.innerHTML = wrap[1] + elem + wrap[2];
// Move to the right depth
while ( depth-- ) {
div = div.lastChild;
}
// Remove IE's autoinserted <tbody> from table fragments
if ( !jQuery.support.tbody ) {
// String was a <table>, *may* have spurious <tbody>
var hasBody = rtbody.test(elem),
tbody = tag === "table" && !hasBody ?
div.firstChild && div.firstChild.childNodes :
// String was a bare <thead> or <tfoot>
wrap[1] === "<table>" && !hasBody ?
div.childNodes :
[];
for ( var j = tbody.length - 1; j >= 0 ; --j ) {
if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
tbody[ j ].parentNode.removeChild( tbody[ j ] );
}
}
}
// IE completely kills leading whitespace when innerHTML is used
if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
}
elem = div.childNodes;
}
if ( elem.nodeType ) {
ret.push( elem );
} else {
ret = jQuery.merge( ret, elem );
}
}
if ( fragment ) {
for ( i = 0; ret[i]; i++ ) {
if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
} else {
if ( ret[i].nodeType === 1 ) {
ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
}
fragment.appendChild( ret[i] );
}
}
}
return ret;
},
cleanData: function( elems ) {
var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special,
deleteExpando = jQuery.support.deleteExpando;
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
continue;
}
id = elem[ jQuery.expando ];
if ( id ) {
data = cache[ id ] && cache[ id ][ internalKey ];
if ( data && data.events ) {
for ( var type in data.events ) {
if ( special[ type ] ) {
jQuery.event.remove( elem, type );
// This is a shortcut to avoid jQuery.event.remove's overhead
} else {
jQuery.removeEvent( elem, type, data.handle );
}
}
// Null the DOM reference to avoid IE6/7/8 leak (#7054)
if ( data.handle ) {
data.handle.elem = null;
}
}
if ( deleteExpando ) {
delete elem[ jQuery.expando ];
} else if ( elem.removeAttribute ) {
elem.removeAttribute( jQuery.expando );
}
delete cache[ id ];
}
}
}
});
function evalScript( i, elem ) {
if ( elem.src ) {
jQuery.ajax({
url: elem.src,
async: false,
dataType: "script"
});
} else {
jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
}
if ( elem.parentNode ) {
elem.parentNode.removeChild( elem );
}
}
var ralpha = /alpha\([^)]*\)/i,
ropacity = /opacity=([^)]*)/,
rdashAlpha = /-([a-z])/ig,
rupper = /([A-Z])/g,
rnumpx = /^-?\d+(?:px)?$/i,
rnum = /^-?\d/,
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
cssWidth = [ "Left", "Right" ],
cssHeight = [ "Top", "Bottom" ],
curCSS,
getComputedStyle,
currentStyle,
fcamelCase = function( all, letter ) {
return letter.toUpperCase();
};
jQuery.fn.css = function( name, value ) {
// Setting 'undefined' is a no-op
if ( arguments.length === 2 && value === undefined ) {
return this;
}
return jQuery.access( this, name, value, true, function( elem, name, value ) {
return value !== undefined ?
jQuery.style( elem, name, value ) :
jQuery.css( elem, name );
});
};
jQuery.extend({
// Add in style property hooks for overriding the default
// behavior of getting and setting a style property
cssHooks: {
opacity: {
get: function( elem, computed ) {
if ( computed ) {
// We should always get a number back from opacity
var ret = curCSS( elem, "opacity", "opacity" );
return ret === "" ? "1" : ret;
} else {
return elem.style.opacity;
}
}
}
},
// Exclude the following css properties to add px
cssNumber: {
"zIndex": true,
"fontWeight": true,
"opacity": true,
"zoom": true,
"lineHeight": true
},
// Add in properties whose names you wish to fix before
// setting or getting the value
cssProps: {
// normalize float css property
"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
},
// Get and set the style property on a DOM Node
style: function( elem, name, value, extra ) {
// Don't set styles on text and comment nodes
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
return;
}
// Make sure that we're working with the right name
var ret, origName = jQuery.camelCase( name ),
style = elem.style, hooks = jQuery.cssHooks[ origName ];
name = jQuery.cssProps[ origName ] || origName;
// Check if we're setting a value
if ( value !== undefined ) {
// Make sure that NaN and null values aren't set. See: #7116
if ( typeof value === "number" && isNaN( value ) || value == null ) {
return;
}
// If a number was passed in, add 'px' to the (except for certain CSS properties)
if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) {
value += "px";
}
// If a hook was provided, use that value, otherwise just set the specified value
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
// Fixes bug #5509
try {
style[ name ] = value;
} catch(e) {}
}
} else {
// If a hook was provided get the non-computed value from there
if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
return ret;
}
// Otherwise just get the value from the style object
return style[ name ];
}
},
css: function( elem, name, extra ) {
// Make sure that we're working with the right name
var ret, origName = jQuery.camelCase( name ),
hooks = jQuery.cssHooks[ origName ];
name = jQuery.cssProps[ origName ] || origName;
// If a hook was provided get the computed value from there
if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
return ret;
// Otherwise, if a way to get the computed value exists, use that
} else if ( curCSS ) {
return curCSS( elem, name, origName );
}
},
// A method for quickly swapping in/out CSS properties to get correct calculations
swap: function( elem, options, callback ) {
var old = {};
// Remember the old values, and insert the new ones
for ( var name in options ) {
old[ name ] = elem.style[ name ];
elem.style[ name ] = options[ name ];
}
callback.call( elem );
// Revert the old values
for ( name in options ) {
elem.style[ name ] = old[ name ];
}
},
camelCase: function( string ) {
return string.replace( rdashAlpha, fcamelCase );
}
});
// DEPRECATED, Use jQuery.css() instead
jQuery.curCSS = jQuery.css;
jQuery.each(["height", "width"], function( i, name ) {
jQuery.cssHooks[ name ] = {
get: function( elem, computed, extra ) {
var val;
if ( computed ) {
if ( elem.offsetWidth !== 0 ) {
val = getWH( elem, name, extra );
} else {
jQuery.swap( elem, cssShow, function() {
val = getWH( elem, name, extra );
});
}
if ( val <= 0 ) {
val = curCSS( elem, name, name );
if ( val === "0px" && currentStyle ) {
val = currentStyle( elem, name, name );
}
if ( val != null ) {
// Should return "auto" instead of 0, use 0 for
// temporary backwards-compat
return val === "" || val === "auto" ? "0px" : val;
}
}
if ( val < 0 || val == null ) {
val = elem.style[ name ];
// Should return "auto" instead of 0, use 0 for
// temporary backwards-compat
return val === "" || val === "auto" ? "0px" : val;
}
return typeof val === "string" ? val : val + "px";
}
},
set: function( elem, value ) {
if ( rnumpx.test( value ) ) {
// ignore negative width and height values #1599
value = parseFloat(value);
if ( value >= 0 ) {
return value + "px";
}
} else {
return value;
}
}
};
});
if ( !jQuery.support.opacity ) {
jQuery.cssHooks.opacity = {
get: function( elem, computed ) {
// IE uses filters for opacity
return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ?
(parseFloat(RegExp.$1) / 100) + "" :
computed ? "1" : "";
},
set: function( elem, value ) {
var style = elem.style;
// IE has trouble with opacity if it does not have layout
// Force it by setting the zoom level
style.zoom = 1;
// Set the alpha filter to set the opacity
var opacity = jQuery.isNaN(value) ?
"" :
"alpha(opacity=" + value * 100 + ")",
filter = style.filter || "";
style.filter = ralpha.test(filter) ?
filter.replace(ralpha, opacity) :
style.filter + ' ' + opacity;
}
};
}
if ( document.defaultView && document.defaultView.getComputedStyle ) {
getComputedStyle = function( elem, newName, name ) {
var ret, defaultView, computedStyle;
name = name.replace( rupper, "-$1" ).toLowerCase();
if ( !(defaultView = elem.ownerDocument.defaultView) ) {
return undefined;
}
if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
ret = computedStyle.getPropertyValue( name );
if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
ret = jQuery.style( elem, name );
}
}
return ret;
};
}
if ( document.documentElement.currentStyle ) {
currentStyle = function( elem, name ) {
var left,
ret = elem.currentStyle && elem.currentStyle[ name ],
rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ],
style = elem.style;
// From the awesome hack by Dean Edwards
// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
// If we're not dealing with a regular pixel number
// but a number that has a weird ending, we need to convert it to pixels
if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
// Remember the original values
left = style.left;
// Put in the new values to get a computed value out
if ( rsLeft ) {
elem.runtimeStyle.left = elem.currentStyle.left;
}
style.left = name === "fontSize" ? "1em" : (ret || 0);
ret = style.pixelLeft + "px";
// Revert the changed values
style.left = left;
if ( rsLeft ) {
elem.runtimeStyle.left = rsLeft;
}
}
return ret === "" ? "auto" : ret;
};
}
curCSS = getComputedStyle || currentStyle;
function getWH( elem, name, extra ) {
var which = name === "width" ? cssWidth : cssHeight,
val = name === "width" ? elem.offsetWidth : elem.offsetHeight;
if ( extra === "border" ) {
return val;
}
jQuery.each( which, function() {
if ( !extra ) {
val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0;
}
if ( extra === "margin" ) {
val += parseFloat(jQuery.css( elem, "margin" + this )) || 0;
} else {
val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0;
}
});
return val;
}
if ( jQuery.expr && jQuery.expr.filters ) {
jQuery.expr.filters.hidden = function( elem ) {
var width = elem.offsetWidth,
height = elem.offsetHeight;
return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none");
};
jQuery.expr.filters.visible = function( elem ) {
return !jQuery.expr.filters.hidden( elem );
};
}
var r20 = /%20/g,
rbracket = /\[\]$/,
rCRLF = /\r?\n/g,
rhash = /#.*$/,
rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL
rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
rnoContent = /^(?:GET|HEAD)$/,
rprotocol = /^\/\//,
rquery = /\?/,
rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
rselectTextarea = /^(?:select|textarea)/i,
rspacesAjax = /\s+/,
rts = /([?&])_=[^&]*/,
rurl = /^(\w+:)\/\/([^\/?#:]+)(?::(\d+))?/,
// Keep a copy of the old load method
_load = jQuery.fn.load,
/* Prefilters
* 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
* 2) These are called:
* - BEFORE asking for a transport
* - AFTER param serialization (s.data is a string if s.processData is true)
* 3) key is the dataType
* 4) the catchall symbol "*" can be used
* 5) execution will start with transport dataType and THEN continue down to "*" if needed
*/
prefilters = {},
/* Transports bindings
* 1) key is the dataType
* 2) the catchall symbol "*" can be used
* 3) selection will start with transport dataType and THEN go to "*" if needed
*/
transports = {};
// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {
// dataTypeExpression is optional and defaults to "*"
return function( dataTypeExpression, func ) {
if ( typeof dataTypeExpression !== "string" ) {
func = dataTypeExpression;
dataTypeExpression = "*";
}
if ( jQuery.isFunction( func ) ) {
var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
i = 0,
length = dataTypes.length,
dataType,
list,
placeBefore;
// For each dataType in the dataTypeExpression
for(; i < length; i++ ) {
dataType = dataTypes[ i ];
// We control if we're asked to add before
// any existing element
placeBefore = /^\+/.test( dataType );
if ( placeBefore ) {
dataType = dataType.substr( 1 ) || "*";
}
list = structure[ dataType ] = structure[ dataType ] || [];
// then we add to the structure accordingly
list[ placeBefore ? "unshift" : "push" ]( func );
}
}
};
}
//Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR,
dataType /* internal */, inspected /* internal */ ) {
dataType = dataType || options.dataTypes[ 0 ];
inspected = inspected || {};
inspected[ dataType ] = true;
var list = structure[ dataType ],
i = 0,
length = list ? list.length : 0,
executeOnly = ( structure === prefilters ),
selection;
for(; i < length && ( executeOnly || !selection ); i++ ) {
selection = list[ i ]( options, originalOptions, jXHR );
// If we got redirected to another dataType
// we try there if not done already
if ( typeof selection === "string" ) {
if ( inspected[ selection ] ) {
selection = undefined;
} else {
options.dataTypes.unshift( selection );
selection = inspectPrefiltersOrTransports(
structure, options, originalOptions, jXHR, selection, inspected );
}
}
}
// If we're only executing or nothing was selected
// we try the catchall dataType if not done already
if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
selection = inspectPrefiltersOrTransports(
structure, options, originalOptions, jXHR, "*", inspected );
}
// unnecessary when only executing (prefilters)
// but it'll be ignored by the caller in that case
return selection;
}
jQuery.fn.extend({
load: function( url, params, callback ) {
if ( typeof url !== "string" && _load ) {
return _load.apply( this, arguments );
// Don't do a request if no elements are being requested
} else if ( !this.length ) {
return this;
}
var off = url.indexOf( " " );
if ( off >= 0 ) {
var selector = url.slice( off, url.length );
url = url.slice( 0, off );
}
// Default to a GET request
var type = "GET";
// If the second parameter was provided
if ( params ) {
// If it's a function
if ( jQuery.isFunction( params ) ) {
// We assume that it's the callback
callback = params;
params = null;
// Otherwise, build a param string
} else if ( typeof params === "object" ) {
params = jQuery.param( params, jQuery.ajaxSettings.traditional );
type = "POST";
}
}
var self = this;
// Request the remote document
jQuery.ajax({
url: url,
type: type,
dataType: "html",
data: params,
// Complete callback (responseText is used internally)
complete: function( jXHR, status, responseText ) {
// Store the response as specified by the jXHR object
responseText = jXHR.responseText;
// If successful, inject the HTML into all the matched elements
if ( jXHR.isResolved() ) {
// #4825: Get the actual response in case
// a dataFilter is present in ajaxSettings
jXHR.done(function( r ) {
responseText = r;
});
// See if a selector was specified
self.html( selector ?
// Create a dummy div to hold the results
jQuery("<div>")
// inject the contents of the document in, removing the scripts
// to avoid any 'Permission Denied' errors in IE
.append(responseText.replace(rscript, ""))
// Locate the specified elements
.find(selector) :
// If not, just inject the full result
responseText );
}
if ( callback ) {
self.each( callback, [ responseText, status, jXHR ] );
}
}
});
return this;
},
serialize: function() {
return jQuery.param( this.serializeArray() );
},
serializeArray: function() {
return this.map(function(){
return this.elements ? jQuery.makeArray( this.elements ) : this;
})
.filter(function(){
return this.name && !this.disabled &&
( this.checked || rselectTextarea.test( this.nodeName ) ||
rinput.test( this.type ) );
})
.map(function( i, elem ){
var val = jQuery( this ).val();
return val == null ?
null :
jQuery.isArray( val ) ?
jQuery.map( val, function( val, i ){
return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
}) :
{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
}).get();
}
});
// Attach a bunch of functions for handling common AJAX events
jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
jQuery.fn[ o ] = function( f ){
return this.bind( o, f );
};
} );
jQuery.each( [ "get", "post" ], function( i, method ) {
jQuery[ method ] = function( url, data, callback, type ) {
// shift arguments if data argument was omitted
if ( jQuery.isFunction( data ) ) {
type = type || callback;
callback = data;
data = null;
}
return jQuery.ajax({
type: method,
url: url,
data: data,
success: callback,
dataType: type
});
};
} );
jQuery.extend({
getScript: function( url, callback ) {
return jQuery.get( url, null, callback, "script" );
},
getJSON: function( url, data, callback ) {
return jQuery.get( url, data, callback, "json" );
},
ajaxSetup: function( settings ) {
jQuery.extend( true, jQuery.ajaxSettings, settings );
if ( settings.context ) {
jQuery.ajaxSettings.context = settings.context;
}
},
ajaxSettings: {
url: location.href,
global: true,
type: "GET",
contentType: "application/x-www-form-urlencoded",
processData: true,
async: true,
/*
timeout: 0,
data: null,
dataType: null,
username: null,
password: null,
cache: null,
traditional: false,
headers: {},
crossDomain: null,
*/
accepts: {
xml: "application/xml, text/xml",
html: "text/html",
text: "text/plain",
json: "application/json, text/javascript",
"*": "*/*"
},
contents: {
xml: /xml/,
html: /html/,
json: /json/
},
responseFields: {
xml: "responseXML",
text: "responseText"
},
// List of data converters
// 1) key format is "source_type destination_type" (a single space in-between)
// 2) the catchall symbol "*" can be used for source_type
converters: {
// Convert anything to text
"* text": window.String,
// Text to html (true = no transformation)
"text html": true,
// Evaluate text as a json expression
"text json": jQuery.parseJSON,
// Parse text as xml
"text xml": jQuery.parseXML
}
},
ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
ajaxTransport: addToPrefiltersOrTransports( transports ),
// Main method
ajax: function( url, options ) {
// If options is not an object,
// we simulate pre-1.5 signature
if ( typeof options !== "object" ) {
options = url;
url = undefined;
}
// Force options to be an object
options = options || {};
var // Create the final options object
s = jQuery.extend( true, {}, jQuery.ajaxSettings, options ),
// Callbacks contexts
// We force the original context if it exists
// or take it from jQuery.ajaxSettings otherwise
// (plain objects used as context get extended)
callbackContext =
( s.context = ( "context" in options ? options : jQuery.ajaxSettings ).context ) || s,
globalEventContext = callbackContext === s ? jQuery.event : jQuery( callbackContext ),
// Deferreds
deferred = jQuery.Deferred(),
completeDeferred = jQuery._Deferred(),
// Status-dependent callbacks
statusCode = s.statusCode || {},
// Headers (they are sent all at once)
requestHeaders = {},
// Response headers
responseHeadersString,
responseHeaders,
// transport
transport,
// timeout handle
timeoutTimer,
// Cross-domain detection vars
loc = document.location,
protocol = loc.protocol || "http:",
parts,
// The jXHR state
state = 0,
// Loop variable
i,
// Fake xhr
jXHR = {
readyState: 0,
// Caches the header
setRequestHeader: function( name, value ) {
if ( state === 0 ) {
requestHeaders[ name.toLowerCase() ] = value;
}
return this;
},
// Raw string
getAllResponseHeaders: function() {
return state === 2 ? responseHeadersString : null;
},
// Builds headers hashtable if needed
getResponseHeader: function( key ) {
var match;
if ( state === 2 ) {
if ( !responseHeaders ) {
responseHeaders = {};
while( ( match = rheaders.exec( responseHeadersString ) ) ) {
responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
}
}
match = responseHeaders[ key.toLowerCase() ];
}
return match || null;
},
// Cancel the request
abort: function( statusText ) {
statusText = statusText || "abort";
if ( transport ) {
transport.abort( statusText );
}
done( 0, statusText );
return this;
}
};
// Callback for when everything is done
// It is defined here because jslint complains if it is declared
// at the end of the function (which would be more logical and readable)
function done( status, statusText, responses, headers) {
// Called once
if ( state === 2 ) {
return;
}
// State is "done" now
state = 2;
// Clear timeout if it exists
if ( timeoutTimer ) {
clearTimeout( timeoutTimer );
}
// Dereference transport for early garbage collection
// (no matter how long the jXHR object will be used)
transport = undefined;
// Cache response headers
responseHeadersString = headers || "";
// Set readyState
jXHR.readyState = status ? 4 : 0;
var isSuccess,
success,
error,
response = responses ? ajaxHandleResponses( s, jXHR, responses ) : undefined,
lastModified,
etag;
// If successful, handle type chaining
if ( status >= 200 && status < 300 || status === 304 ) {
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if ( s.ifModified ) {
if ( ( lastModified = jXHR.getResponseHeader( "Last-Modified" ) ) ) {
jQuery.lastModified[ s.url ] = lastModified;
}
if ( ( etag = jXHR.getResponseHeader( "Etag" ) ) ) {
jQuery.etag[ s.url ] = etag;
}
}
// If not modified
if ( status === 304 ) {
statusText = "notmodified";
isSuccess = true;
// If we have data
} else {
try {
success = ajaxConvert( s, response );
statusText = "success";
isSuccess = true;
} catch(e) {
// We have a parsererror
statusText = "parsererror";
error = e;
}
}
} else {
// We extract error from statusText
// then normalize statusText and status for non-aborts
error = statusText;
if( status ) {
statusText = "error";
if ( status < 0 ) {
status = 0;
}
}
}
// Set data for the fake xhr object
jXHR.status = status;
jXHR.statusText = statusText;
// Success/Error
if ( isSuccess ) {
deferred.resolveWith( callbackContext, [ success, statusText, jXHR ] );
} else {
deferred.rejectWith( callbackContext, [ jXHR, statusText, error ] );
}
// Status-dependent callbacks
jXHR.statusCode( statusCode );
statusCode = undefined;
if ( s.global ) {
globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
[ jXHR, s, isSuccess ? success : error ] );
}
// Complete
completeDeferred.resolveWith( callbackContext, [ jXHR, statusText ] );
if ( s.global ) {
globalEventContext.trigger( "ajaxComplete", [ jXHR, s] );
// Handle the global AJAX counter
if ( !( --jQuery.active ) ) {
jQuery.event.trigger( "ajaxStop" );
}
}
}
// Attach deferreds
deferred.promise( jXHR );
jXHR.success = jXHR.done;
jXHR.error = jXHR.fail;
jXHR.complete = completeDeferred.done;
// Status-dependent callbacks
jXHR.statusCode = function( map ) {
if ( map ) {
var tmp;
if ( state < 2 ) {
for( tmp in map ) {
statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
}
} else {
tmp = map[ jXHR.status ];
jXHR.then( tmp, tmp );
}
}
return this;
};
// Remove hash character (#7531: and string promotion)
// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
// We also use the url parameter if available
s.url = ( "" + ( url || s.url ) ).replace( rhash, "" ).replace( rprotocol, protocol + "//" );
// Extract dataTypes list
s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
// Determine if a cross-domain request is in order
if ( !s.crossDomain ) {
parts = rurl.exec( s.url.toLowerCase() );
s.crossDomain = !!( parts &&
( parts[ 1 ] != protocol || parts[ 2 ] != loc.hostname ||
( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
( loc.port || ( protocol === "http:" ? 80 : 443 ) ) )
);
}
// Convert data if not already a string
if ( s.data && s.processData && typeof s.data !== "string" ) {
s.data = jQuery.param( s.data, s.traditional );
}
// Apply prefilters
inspectPrefiltersOrTransports( prefilters, s, options, jXHR );
// Uppercase the type
s.type = s.type.toUpperCase();
// Determine if request has content
s.hasContent = !rnoContent.test( s.type );
// Watch for a new set of requests
if ( s.global && jQuery.active++ === 0 ) {
jQuery.event.trigger( "ajaxStart" );
}
// More options handling for requests with no content
if ( !s.hasContent ) {
// If data is available, append data to url
if ( s.data ) {
s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
}
// Add anti-cache in url if needed
if ( s.cache === false ) {
var ts = jQuery.now(),
// try replacing _= if it is there
ret = s.url.replace( rts, "$1_=" + ts );
// if nothing was replaced, add timestamp to the end
s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
}
}
// Set the correct header, if data is being sent
if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
requestHeaders[ "content-type" ] = s.contentType;
}
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
if ( s.ifModified ) {
if ( jQuery.lastModified[ s.url ] ) {
requestHeaders[ "if-modified-since" ] = jQuery.lastModified[ s.url ];
}
if ( jQuery.etag[ s.url ] ) {
requestHeaders[ "if-none-match" ] = jQuery.etag[ s.url ];
}
}
// Set the Accepts header for the server, depending on the dataType
requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
s.accepts[ "*" ];
// Check for headers option
for ( i in s.headers ) {
requestHeaders[ i.toLowerCase() ] = s.headers[ i ];
}
// Allow custom headers/mimetypes and early abort
if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jXHR, s ) === false || state === 2 ) ) {
// Abort if not done already
done( 0, "abort" );
// Return false
jXHR = false;
} else {
// Install callbacks on deferreds
for ( i in { success: 1, error: 1, complete: 1 } ) {
jXHR[ i ]( s[ i ] );
}
// Get transport
transport = inspectPrefiltersOrTransports( transports, s, options, jXHR );
// If no transport, we auto-abort
if ( !transport ) {
done( -1, "No Transport" );
} else {
// Set state as sending
state = jXHR.readyState = 1;
// Send global event
if ( s.global ) {
globalEventContext.trigger( "ajaxSend", [ jXHR, s ] );
}
// Timeout
if ( s.async && s.timeout > 0 ) {
timeoutTimer = setTimeout( function(){
jXHR.abort( "timeout" );
}, s.timeout );
}
try {
transport.send( requestHeaders, done );
} catch (e) {
// Propagate exception as error if not done
if ( status < 2 ) {
done( -1, e );
// Simply rethrow otherwise
} else {
jQuery.error( e );
}
}
}
}
return jXHR;
},
// Serialize an array of form elements or a set of
// key/values into a query string
param: function( a, traditional ) {
var s = [],
add = function( key, value ) {
// If value is a function, invoke it and return its value
value = jQuery.isFunction( value ) ? value() : value;
s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
};
// Set traditional to true for jQuery <= 1.3.2 behavior.
if ( traditional === undefined ) {
traditional = jQuery.ajaxSettings.traditional;
}
// If an array was passed in, assume that it is an array of form elements.
if ( jQuery.isArray( a ) || a.jquery ) {
// Serialize the form elements
jQuery.each( a, function() {
add( this.name, this.value );
} );
} else {
// If traditional, encode the "old" way (the way 1.3.2 or older
// did it), otherwise encode params recursively.
for ( var prefix in a ) {
buildParams( prefix, a[ prefix ], traditional, add );
}
}
// Return the resulting serialization
return s.join( "&" ).replace( r20, "+" );
}
});
function buildParams( prefix, obj, traditional, add ) {
if ( jQuery.isArray( obj ) && obj.length ) {
// Serialize array item.
jQuery.each( obj, function( i, v ) {
if ( traditional || rbracket.test( prefix ) ) {
// Treat each array item as a scalar.
add( prefix, v );
} else {
// If array item is non-scalar (array or object), encode its
// numeric index to resolve deserialization ambiguity issues.
// Note that rack (as of 1.0.0) can't currently deserialize
// nested arrays properly, and attempting to do so may cause
// a server error. Possible fixes are to modify rack's
// deserialization algorithm or to provide an option or flag
// to force array serialization to be shallow.
buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
}
});
} else if ( !traditional && obj != null && typeof obj === "object" ) {
// If we see an array here, it is empty and should be treated as an empty
// object
if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) {
add( prefix, "" );
// Serialize object item.
} else {
jQuery.each( obj, function( k, v ) {
buildParams( prefix + "[" + k + "]", v, traditional, add );
});
}
} else {
// Serialize scalar item.
add( prefix, obj );
}
}
// This is still on the jQuery object... for now
// Want to move this to jQuery.ajax some day
jQuery.extend({
// Counter for holding the number of active queries
active: 0,
// Last-Modified header cache for next request
lastModified: {},
etag: {}
});
/* Handles responses to an ajax request:
* - sets all responseXXX fields accordingly
* - finds the right dataType (mediates between content-type and expected dataType)
* - returns the corresponding response
*/
function ajaxHandleResponses( s, jXHR, responses ) {
var contents = s.contents,
dataTypes = s.dataTypes,
responseFields = s.responseFields,
ct,
type,
finalDataType,
firstDataType;
// Fill responseXXX fields
for( type in responseFields ) {
if ( type in responses ) {
jXHR[ responseFields[type] ] = responses[ type ];
}
}
// Remove auto dataType and get content-type in the process
while( dataTypes[ 0 ] === "*" ) {
dataTypes.shift();
if ( ct === undefined ) {
ct = jXHR.getResponseHeader( "content-type" );
}
}
// Check if we're dealing with a known content-type
if ( ct ) {
for ( type in contents ) {
if ( contents[ type ] && contents[ type ].test( ct ) ) {
dataTypes.unshift( type );
break;
}
}
}
// Check to see if we have a response for the expected dataType
if ( dataTypes[ 0 ] in responses ) {
finalDataType = dataTypes[ 0 ];
} else {
// Try convertible dataTypes
for ( type in responses ) {
if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
finalDataType = type;
break;
}
if ( !firstDataType ) {
firstDataType = type;
}
}
// Or just use first one
finalDataType = finalDataType || firstDataType;
}
// If we found a dataType
// We add the dataType to the list if needed
// and return the corresponding response
if ( finalDataType ) {
if ( finalDataType !== dataTypes[ 0 ] ) {
dataTypes.unshift( finalDataType );
}
return responses[ finalDataType ];
}
}
// Chain conversions given the request and the original response
function ajaxConvert( s, response ) {
// Apply the dataFilter if provided
if ( s.dataFilter ) {
response = s.dataFilter( response, s.dataType );
}
var dataTypes = s.dataTypes,
converters = s.converters,
i,
length = dataTypes.length,
tmp,
// Current and previous dataTypes
current = dataTypes[ 0 ],
prev,
// Conversion expression
conversion,
// Conversion function
conv,
// Conversion functions (transitive conversion)
conv1,
conv2;
// For each dataType in the chain
for( i = 1; i < length; i++ ) {
// Get the dataTypes
prev = current;
current = dataTypes[ i ];
// If current is auto dataType, update it to prev
if( current === "*" ) {
current = prev;
// If no auto and dataTypes are actually different
} else if ( prev !== "*" && prev !== current ) {
// Get the converter
conversion = prev + " " + current;
conv = converters[ conversion ] || converters[ "* " + current ];
// If there is no direct converter, search transitively
if ( !conv ) {
conv2 = undefined;
for( conv1 in converters ) {
tmp = conv1.split( " " );
if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
conv2 = converters[ tmp[1] + " " + current ];
if ( conv2 ) {
conv1 = converters[ conv1 ];
if ( conv1 === true ) {
conv = conv2;
} else if ( conv2 === true ) {
conv = conv1;
}
break;
}
}
}
}
// If we found no converter, dispatch an error
if ( !( conv || conv2 ) ) {
jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
}
// If found converter is not an equivalence
if ( conv !== true ) {
// Convert with 1 or 2 converters accordingly
response = conv ? conv( response ) : conv2( conv1(response) );
}
}
}
return response;
}
var jsc = jQuery.now(),
jsre = /(\=)\?(&|$)|()\?\?()/i;
// Default jsonp settings
jQuery.ajaxSetup({
jsonp: "callback",
jsonpCallback: function() {
return jQuery.expando + "_" + ( jsc++ );
}
});
// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, dataIsString /* internal */ ) {
dataIsString = ( typeof s.data === "string" );
if ( s.dataTypes[ 0 ] === "jsonp" ||
originalSettings.jsonpCallback ||
originalSettings.jsonp != null ||
s.jsonp !== false && ( jsre.test( s.url ) ||
dataIsString && jsre.test( s.data ) ) ) {
var responseContainer,
jsonpCallback = s.jsonpCallback =
jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
previous = window[ jsonpCallback ],
url = s.url,
data = s.data,
replace = "$1" + jsonpCallback + "$2";
if ( s.jsonp !== false ) {
url = url.replace( jsre, replace );
if ( s.url === url ) {
if ( dataIsString ) {
data = data.replace( jsre, replace );
}
if ( s.data === data ) {
// Add callback manually
url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
}
}
}
s.url = url;
s.data = data;
window[ jsonpCallback ] = function( response ) {
responseContainer = [ response ];
};
s.complete = [ function() {
// Set callback back to previous value
window[ jsonpCallback ] = previous;
// Call if it was a function and we have a response
if ( previous) {
if ( responseContainer && jQuery.isFunction( previous ) ) {
window[ jsonpCallback ] ( responseContainer[ 0 ] );
}
} else {
// else, more memory leak avoidance
try{
delete window[ jsonpCallback ];
} catch( e ) {}
}
}, s.complete ];
// Use data converter to retrieve json after script execution
s.converters["script json"] = function() {
if ( ! responseContainer ) {
jQuery.error( jsonpCallback + " was not called" );
}
return responseContainer[ 0 ];
};
// force json dataType
s.dataTypes[ 0 ] = "json";
// Delegate to script
return "script";
}
} );
// Install script dataType
jQuery.ajaxSetup({
accepts: {
script: "text/javascript, application/javascript"
},
contents: {
script: /javascript/
},
converters: {
"text script": function( text ) {
jQuery.globalEval( text );
return text;
}
}
});
// Handle cache's special case and global
jQuery.ajaxPrefilter( "script", function( s ) {
if ( s.cache === undefined ) {
s.cache = false;
}
if ( s.crossDomain ) {
s.type = "GET";
s.global = false;
}
} );
// Bind script tag hack transport
jQuery.ajaxTransport( "script", function(s) {
// This transport only deals with cross domain requests
if ( s.crossDomain ) {
var script,
head = document.getElementsByTagName( "head" )[ 0 ] || document.documentElement;
return {
send: function( _, callback ) {
script = document.createElement( "script" );
script.async = "async";
if ( s.scriptCharset ) {
script.charset = s.scriptCharset;
}
script.src = s.url;
// Attach handlers for all browsers
script.onload = script.onreadystatechange = function( _, isAbort ) {
if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) {
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
// Remove the script
if ( head && script.parentNode ) {
head.removeChild( script );
}
// Dereference the script
script = undefined;
// Callback if not abort
if ( !isAbort ) {
callback( 200, "success" );
}
}
};
// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709 and #4378).
head.insertBefore( script, head.firstChild );
},
abort: function() {
if ( script ) {
script.onload( 0, 1 );
}
}
};
}
} );
var // Next active xhr id
xhrId = jQuery.now(),
// active xhrs
xhrs = {},
// #5280: see below
xhrUnloadAbortInstalled,
// XHR used to determine supports properties
testXHR;
// Create the request object
// (This is still attached to ajaxSettings for backward compatibility)
jQuery.ajaxSettings.xhr = window.ActiveXObject ?
/* Microsoft failed to properly
* implement the XMLHttpRequest in IE7 (can't request local files),
* so we use the ActiveXObject when it is available
* Additionally XMLHttpRequest can be disabled in IE7/IE8 so
* we need a fallback.
*/
function() {
if ( window.location.protocol !== "file:" ) {
try {
return new window.XMLHttpRequest();
} catch( xhrError ) {}
}
try {
return new window.ActiveXObject("Microsoft.XMLHTTP");
} catch( activeError ) {}
} :
// For all other browsers, use the standard XMLHttpRequest object
function() {
return new window.XMLHttpRequest();
};
// Test if we can create an xhr object
try {
testXHR = jQuery.ajaxSettings.xhr();
} catch( xhrCreationException ) {}
//Does this browser support XHR requests?
jQuery.support.ajax = !!testXHR;
// Does this browser support crossDomain XHR requests
jQuery.support.cors = testXHR && ( "withCredentials" in testXHR );
// No need for the temporary xhr anymore
testXHR = undefined;
// Create transport if the browser can provide an xhr
if ( jQuery.support.ajax ) {
jQuery.ajaxTransport(function( s ) {
// Cross domain only allowed if supported through XMLHttpRequest
if ( !s.crossDomain || jQuery.support.cors ) {
var callback;
return {
send: function( headers, complete ) {
// #5280: we need to abort on unload or IE will keep connections alive
if ( !xhrUnloadAbortInstalled ) {
xhrUnloadAbortInstalled = 1;
jQuery(window).bind( "unload", function() {
// Abort all pending requests
jQuery.each( xhrs, function( _, xhr ) {
if ( xhr.onreadystatechange ) {
xhr.onreadystatechange( 1 );
}
} );
} );
}
// Get a new xhr
var xhr = s.xhr(),
handle;
// Open the socket
// Passing null username, generates a login popup on Opera (#2865)
if ( s.username ) {
xhr.open( s.type, s.url, s.async, s.username, s.password );
} else {
xhr.open( s.type, s.url, s.async );
}
// Requested-With header
// Not set for crossDomain requests with no content
// (see why at http://trac.dojotoolkit.org/ticket/9486)
// Won't change header if already provided
if ( !( s.crossDomain && !s.hasContent ) && !headers["x-requested-with"] ) {
headers[ "x-requested-with" ] = "XMLHttpRequest";
}
// Need an extra try/catch for cross domain requests in Firefox 3
try {
jQuery.each( headers, function( key, value ) {
xhr.setRequestHeader( key, value );
} );
} catch( _ ) {}
// Do send the request
// This may raise an exception which is actually
// handled in jQuery.ajax (so no try/catch here)
xhr.send( ( s.hasContent && s.data ) || null );
// Listener
callback = function( _, isAbort ) {
// Was never called and is aborted or complete
if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
// Only called once
callback = 0;
// Do not keep as active anymore
if ( handle ) {
xhr.onreadystatechange = jQuery.noop;
delete xhrs[ handle ];
}
// If it's an abort
if ( isAbort ) {
// Abort it manually if needed
if ( xhr.readyState !== 4 ) {
xhr.abort();
}
} else {
// Get info
var status = xhr.status,
statusText,
responseHeaders = xhr.getAllResponseHeaders(),
responses = {},
xml = xhr.responseXML;
// Construct response list
if ( xml && xml.documentElement /* #4958 */ ) {
responses.xml = xml;
}
responses.text = xhr.responseText;
// Firefox throws an exception when accessing
// statusText for faulty cross-domain requests
try {
statusText = xhr.statusText;
} catch( e ) {
// We normalize with Webkit giving an empty statusText
statusText = "";
}
// Filter status for non standard behaviours
status =
// Opera returns 0 when it should be 304
// Webkit returns 0 for failing cross-domain no matter the real status
status === 0 ?
(
// Webkit, Firefox: filter out faulty cross-domain requests
!s.crossDomain || statusText ?
(
// Opera: filter out real aborts #6060
responseHeaders ?
304 :
0
) :
// We assume 302 but could be anything cross-domain related
302
) :
(
// IE sometimes returns 1223 when it should be 204 (see #1450)
status == 1223 ?
204 :
status
);
// Call complete
complete( status, statusText, responses, responseHeaders );
}
}
};
// if we're in sync mode or it's in cache
// and has been retrieved directly (IE6 & IE7)
// we need to manually fire the callback
if ( !s.async || xhr.readyState === 4 ) {
callback();
} else {
// Add to list of active xhrs
handle = xhrId++;
xhrs[ handle ] = xhr;
xhr.onreadystatechange = callback;
}
},
abort: function() {
if ( callback ) {
callback(0,1);
}
}
};
}
});
}
var elemdisplay = {},
rfxtypes = /^(?:toggle|show|hide)$/,
rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
timerId,
fxAttrs = [
// height animations
[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
// width animations
[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
// opacity animations
[ "opacity" ]
];
jQuery.fn.extend({
show: function( speed, easing, callback ) {
var elem, display;
if ( speed || speed === 0 ) {
return this.animate( genFx("show", 3), speed, easing, callback);
} else {
for ( var i = 0, j = this.length; i < j; i++ ) {
elem = this[i];
display = elem.style.display;
// Reset the inline display of this element to learn if it is
// being hidden by cascaded rules or not
if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
display = elem.style.display = "";
}
// Set elements which have been overridden with display: none
// in a stylesheet to whatever the default browser style is
// for such an element
if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
}
}
// Set the display of most of the elements in a second loop
// to avoid the constant reflow
for ( i = 0; i < j; i++ ) {
elem = this[i];
display = elem.style.display;
if ( display === "" || display === "none" ) {
elem.style.display = jQuery._data(elem, "olddisplay") || "";
}
}
return this;
}
},
hide: function( speed, easing, callback ) {
if ( speed || speed === 0 ) {
return this.animate( genFx("hide", 3), speed, easing, callback);
} else {
for ( var i = 0, j = this.length; i < j; i++ ) {
var display = jQuery.css( this[i], "display" );
if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
jQuery._data( this[i], "olddisplay", display );
}
}
// Set the display of the elements in a second loop
// to avoid the constant reflow
for ( i = 0; i < j; i++ ) {
this[i].style.display = "none";
}
return this;
}
},
// Save the old toggle function
_toggle: jQuery.fn.toggle,
toggle: function( fn, fn2, callback ) {
var bool = typeof fn === "boolean";
if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
this._toggle.apply( this, arguments );
} else if ( fn == null || bool ) {
this.each(function() {
var state = bool ? fn : jQuery(this).is(":hidden");
jQuery(this)[ state ? "show" : "hide" ]();
});
} else {
this.animate(genFx("toggle", 3), fn, fn2, callback);
}
return this;
},
fadeTo: function( speed, to, easing, callback ) {
return this.filter(":hidden").css("opacity", 0).show().end()
.animate({opacity: to}, speed, easing, callback);
},
animate: function( prop, speed, easing, callback ) {
var optall = jQuery.speed(speed, easing, callback);
if ( jQuery.isEmptyObject( prop ) ) {
return this.each( optall.complete );
}
return this[ optall.queue === false ? "each" : "queue" ](function() {
// XXX 'this' does not always have a nodeName when running the
// test suite
var opt = jQuery.extend({}, optall), p,
isElement = this.nodeType === 1,
hidden = isElement && jQuery(this).is(":hidden"),
self = this;
for ( p in prop ) {
var name = jQuery.camelCase( p );
if ( p !== name ) {
prop[ name ] = prop[ p ];
delete prop[ p ];
p = name;
}
if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) {
return opt.complete.call(this);
}
if ( isElement && ( p === "height" || p === "width" ) ) {
// Make sure that nothing sneaks out
// Record all 3 overflow attributes because IE does not
// change the overflow attribute when overflowX and
// overflowY are set to the same value
opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
// Set display property to inline-block for height/width
// animations on inline elements that are having width/height
// animated
if ( jQuery.css( this, "display" ) === "inline" &&
jQuery.css( this, "float" ) === "none" ) {
if ( !jQuery.support.inlineBlockNeedsLayout ) {
this.style.display = "inline-block";
} else {
var display = defaultDisplay(this.nodeName);
// inline-level elements accept inline-block;
// block-level elements need to be inline with layout
if ( display === "inline" ) {
this.style.display = "inline-block";
} else {
this.style.display = "inline";
this.style.zoom = 1;
}
}
}
}
if ( jQuery.isArray( prop[p] ) ) {
// Create (if needed) and add to specialEasing
(opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1];
prop[p] = prop[p][0];
}
}
if ( opt.overflow != null ) {
this.style.overflow = "hidden";
}
opt.curAnim = jQuery.extend({}, prop);
jQuery.each( prop, function( name, val ) {
var e = new jQuery.fx( self, opt, name );
if ( rfxtypes.test(val) ) {
e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop );
} else {
var parts = rfxnum.exec(val),
start = e.cur() || 0;
if ( parts ) {
var end = parseFloat( parts[2] ),
unit = parts[3] || "px";
// We need to compute starting value
if ( unit !== "px" ) {
jQuery.style( self, name, (end || 1) + unit);
start = ((end || 1) / e.cur()) * start;
jQuery.style( self, name, start + unit);
}
// If a +=/-= token was provided, we're doing a relative animation
if ( parts[1] ) {
end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
}
e.custom( start, end, unit );
} else {
e.custom( start, val, "" );
}
}
});
// For JS strict compliance
return true;
});
},
stop: function( clearQueue, gotoEnd ) {
var timers = jQuery.timers;
if ( clearQueue ) {
this.queue([]);
}
this.each(function() {
// go in reverse order so anything added to the queue during the loop is ignored
for ( var i = timers.length - 1; i >= 0; i-- ) {
if ( timers[i].elem === this ) {
if (gotoEnd) {
// force the next step to be the last
timers[i](true);
}
timers.splice(i, 1);
}
}
});
// start the next in the queue if the last step wasn't forced
if ( !gotoEnd ) {
this.dequeue();
}
return this;
}
});
function genFx( type, num ) {
var obj = {};
jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
obj[ this ] = type;
});
return obj;
}
// Generate shortcuts for custom animations
jQuery.each({
slideDown: genFx("show", 1),
slideUp: genFx("hide", 1),
slideToggle: genFx("toggle", 1),
fadeIn: { opacity: "show" },
fadeOut: { opacity: "hide" },
fadeToggle: { opacity: "toggle" }
}, function( name, props ) {
jQuery.fn[ name ] = function( speed, easing, callback ) {
return this.animate( props, speed, easing, callback );
};
});
jQuery.extend({
speed: function( speed, easing, fn ) {
var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : {
complete: fn || !fn && easing ||
jQuery.isFunction( speed ) && speed,
duration: speed,
easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
};
opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;
// Queueing
opt.old = opt.complete;
opt.complete = function() {
if ( opt.queue !== false ) {
jQuery(this).dequeue();
}
if ( jQuery.isFunction( opt.old ) ) {
opt.old.call( this );
}
};
return opt;
},
easing: {
linear: function( p, n, firstNum, diff ) {
return firstNum + diff * p;
},
swing: function( p, n, firstNum, diff ) {
return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
}
},
timers: [],
fx: function( elem, options, prop ) {
this.options = options;
this.elem = elem;
this.prop = prop;
if ( !options.orig ) {
options.orig = {};
}
}
});
jQuery.fx.prototype = {
// Simple function for setting a style value
update: function() {
if ( this.options.step ) {
this.options.step.call( this.elem, this.now, this );
}
(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
},
// Get the current size
cur: function() {
if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
return this.elem[ this.prop ];
}
var r = parseFloat( jQuery.css( this.elem, this.prop ) );
return r || 0;
},
// Start an animation from one number to another
custom: function( from, to, unit ) {
var self = this,
fx = jQuery.fx;
this.startTime = jQuery.now();
this.start = from;
this.end = to;
this.unit = unit || this.unit || "px";
this.now = this.start;
this.pos = this.state = 0;
function t( gotoEnd ) {
return self.step(gotoEnd);
}
t.elem = this.elem;
if ( t() && jQuery.timers.push(t) && !timerId ) {
timerId = setInterval(fx.tick, fx.interval);
}
},
// Simple 'show' function
show: function() {
// Remember where we started, so that we can go back to it later
this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
this.options.show = true;
// Begin the animation
// Make sure that we start at a small width/height to avoid any
// flash of content
this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
// Start by showing the element
jQuery( this.elem ).show();
},
// Simple 'hide' function
hide: function() {
// Remember where we started, so that we can go back to it later
this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
this.options.hide = true;
// Begin the animation
this.custom(this.cur(), 0);
},
// Each step of an animation
step: function( gotoEnd ) {
var t = jQuery.now(), done = true;
if ( gotoEnd || t >= this.options.duration + this.startTime ) {
this.now = this.end;
this.pos = this.state = 1;
this.update();
this.options.curAnim[ this.prop ] = true;
for ( var i in this.options.curAnim ) {
if ( this.options.curAnim[i] !== true ) {
done = false;
}
}
if ( done ) {
// Reset the overflow
if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
var elem = this.elem,
options = this.options;
jQuery.each( [ "", "X", "Y" ], function (index, value) {
elem.style[ "overflow" + value ] = options.overflow[index];
} );
}
// Hide the element if the "hide" operation was done
if ( this.options.hide ) {
jQuery(this.elem).hide();
}
// Reset the properties, if the item has been hidden or shown
if ( this.options.hide || this.options.show ) {
for ( var p in this.options.curAnim ) {
jQuery.style( this.elem, p, this.options.orig[p] );
}
}
// Execute the complete function
this.options.complete.call( this.elem );
}
return false;
} else {
var n = t - this.startTime;
this.state = n / this.options.duration;
// Perform the easing function, defaults to swing
var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop];
var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear");
this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration);
this.now = this.start + ((this.end - this.start) * this.pos);
// Perform the next step of the animation
this.update();
}
return true;
}
};
jQuery.extend( jQuery.fx, {
tick: function() {
var timers = jQuery.timers;
for ( var i = 0; i < timers.length; i++ ) {
if ( !timers[i]() ) {
timers.splice(i--, 1);
}
}
if ( !timers.length ) {
jQuery.fx.stop();
}
},
interval: 13,
stop: function() {
clearInterval( timerId );
timerId = null;
},
speeds: {
slow: 600,
fast: 200,
// Default speed
_default: 400
},
step: {
opacity: function( fx ) {
jQuery.style( fx.elem, "opacity", fx.now );
},
_default: function( fx ) {
if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
} else {
fx.elem[ fx.prop ] = fx.now;
}
}
}
});
if ( jQuery.expr && jQuery.expr.filters ) {
jQuery.expr.filters.animated = function( elem ) {
return jQuery.grep(jQuery.timers, function( fn ) {
return elem === fn.elem;
}).length;
};
}
function defaultDisplay( nodeName ) {
if ( !elemdisplay[ nodeName ] ) {
var elem = jQuery("<" + nodeName + ">").appendTo("body"),
display = elem.css("display");
elem.remove();
if ( display === "none" || display === "" ) {
display = "block";
}
elemdisplay[ nodeName ] = display;
}
return elemdisplay[ nodeName ];
}
var rtable = /^t(?:able|d|h)$/i,
rroot = /^(?:body|html)$/i;
if ( "getBoundingClientRect" in document.documentElement ) {
jQuery.fn.offset = function( options ) {
var elem = this[0], box;
if ( options ) {
return this.each(function( i ) {
jQuery.offset.setOffset( this, options, i );
});
}
if ( !elem || !elem.ownerDocument ) {
return null;
}
if ( elem === elem.ownerDocument.body ) {
return jQuery.offset.bodyOffset( elem );
}
try {
box = elem.getBoundingClientRect();
} catch(e) {}
var doc = elem.ownerDocument,
docElem = doc.documentElement;
// Make sure we're not dealing with a disconnected DOM node
if ( !box || !jQuery.contains( docElem, elem ) ) {
return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
}
var body = doc.body,
win = getWindow(doc),
clientTop = docElem.clientTop || body.clientTop || 0,
clientLeft = docElem.clientLeft || body.clientLeft || 0,
scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ),
scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft),
top = box.top + scrollTop - clientTop,
left = box.left + scrollLeft - clientLeft;
return { top: top, left: left };
};
} else {
jQuery.fn.offset = function( options ) {
var elem = this[0];
if ( options ) {
return this.each(function( i ) {
jQuery.offset.setOffset( this, options, i );
});
}
if ( !elem || !elem.ownerDocument ) {
return null;
}
if ( elem === elem.ownerDocument.body ) {
return jQuery.offset.bodyOffset( elem );
}
jQuery.offset.initialize();
var computedStyle,
offsetParent = elem.offsetParent,
prevOffsetParent = elem,
doc = elem.ownerDocument,
docElem = doc.documentElement,
body = doc.body,
defaultView = doc.defaultView,
prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
top = elem.offsetTop,
left = elem.offsetLeft;
while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
break;
}
computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
top -= elem.scrollTop;
left -= elem.scrollLeft;
if ( elem === offsetParent ) {
top += elem.offsetTop;
left += elem.offsetLeft;
if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
top += parseFloat( computedStyle.borderTopWidth ) || 0;
left += parseFloat( computedStyle.borderLeftWidth ) || 0;
}
prevOffsetParent = offsetParent;
offsetParent = elem.offsetParent;
}
if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
top += parseFloat( computedStyle.borderTopWidth ) || 0;
left += parseFloat( computedStyle.borderLeftWidth ) || 0;
}
prevComputedStyle = computedStyle;
}
if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
top += body.offsetTop;
left += body.offsetLeft;
}
if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
top += Math.max( docElem.scrollTop, body.scrollTop );
left += Math.max( docElem.scrollLeft, body.scrollLeft );
}
return { top: top, left: left };
};
}
jQuery.offset = {
initialize: function() {
var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0,
html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } );
container.innerHTML = html;
body.insertBefore( container, body.firstChild );
innerDiv = container.firstChild;
checkDiv = innerDiv.firstChild;
td = innerDiv.nextSibling.firstChild.firstChild;
this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
checkDiv.style.position = "fixed";
checkDiv.style.top = "20px";
// safari subtracts parent border width here which is 5px
this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15);
checkDiv.style.position = checkDiv.style.top = "";
innerDiv.style.overflow = "hidden";
innerDiv.style.position = "relative";
this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
body.removeChild( container );
body = container = innerDiv = checkDiv = table = td = null;
jQuery.offset.initialize = jQuery.noop;
},
bodyOffset: function( body ) {
var top = body.offsetTop,
left = body.offsetLeft;
jQuery.offset.initialize();
if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) {
top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
}
return { top: top, left: left };
},
setOffset: function( elem, options, i ) {
var position = jQuery.css( elem, "position" );
// set position first, in-case top/left are set even on static elem
if ( position === "static" ) {
elem.style.position = "relative";
}
var curElem = jQuery( elem ),
curOffset = curElem.offset(),
curCSSTop = jQuery.css( elem, "top" ),
curCSSLeft = jQuery.css( elem, "left" ),
calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1),
props = {}, curPosition = {}, curTop, curLeft;
// need to be able to calculate position if either top or left is auto and position is absolute
if ( calculatePosition ) {
curPosition = curElem.position();
}
curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0;
curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0;
if ( jQuery.isFunction( options ) ) {
options = options.call( elem, i, curOffset );
}
if (options.top != null) {
props.top = (options.top - curOffset.top) + curTop;
}
if (options.left != null) {
props.left = (options.left - curOffset.left) + curLeft;
}
if ( "using" in options ) {
options.using.call( elem, props );
} else {
curElem.css( props );
}
}
};
jQuery.fn.extend({
position: function() {
if ( !this[0] ) {
return null;
}
var elem = this[0],
// Get *real* offsetParent
offsetParent = this.offsetParent(),
// Get correct offsets
offset = this.offset(),
parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
// Subtract element margins
// note: when an element has margin: auto the offsetLeft and marginLeft
// are the same in Safari causing offset.left to incorrectly be 0
offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
// Add offsetParent borders
parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
// Subtract the two offsets
return {
top: offset.top - parentOffset.top,
left: offset.left - parentOffset.left
};
},
offsetParent: function() {
return this.map(function() {
var offsetParent = this.offsetParent || document.body;
while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
offsetParent = offsetParent.offsetParent;
}
return offsetParent;
});
}
});
// Create scrollLeft and scrollTop methods
jQuery.each( ["Left", "Top"], function( i, name ) {
var method = "scroll" + name;
jQuery.fn[ method ] = function(val) {
var elem = this[0], win;
if ( !elem ) {
return null;
}
if ( val !== undefined ) {
// Set the scroll offset
return this.each(function() {
win = getWindow( this );
if ( win ) {
win.scrollTo(
!i ? val : jQuery(win).scrollLeft(),
i ? val : jQuery(win).scrollTop()
);
} else {
this[ method ] = val;
}
});
} else {
win = getWindow( elem );
// Return the scroll offset
return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
jQuery.support.boxModel && win.document.documentElement[ method ] ||
win.document.body[ method ] :
elem[ method ];
}
};
});
function getWindow( elem ) {
return jQuery.isWindow( elem ) ?
elem :
elem.nodeType === 9 ?
elem.defaultView || elem.parentWindow :
false;
}
// Create innerHeight, innerWidth, outerHeight and outerWidth methods
jQuery.each([ "Height", "Width" ], function( i, name ) {
var type = name.toLowerCase();
// innerHeight and innerWidth
jQuery.fn["inner" + name] = function() {
return this[0] ?
parseFloat( jQuery.css( this[0], type, "padding" ) ) :
null;
};
// outerHeight and outerWidth
jQuery.fn["outer" + name] = function( margin ) {
return this[0] ?
parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) :
null;
};
jQuery.fn[ type ] = function( size ) {
// Get window width or height
var elem = this[0];
if ( !elem ) {
return size == null ? null : this;
}
if ( jQuery.isFunction( size ) ) {
return this.each(function( i ) {
var self = jQuery( this );
self[ type ]( size.call( this, i, self[ type ]() ) );
});
}
if ( jQuery.isWindow( elem ) ) {
// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
// 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
var docElemProp = elem.document.documentElement[ "client" + name ];
return elem.document.compatMode === "CSS1Compat" && docElemProp ||
elem.document.body[ "client" + name ] || docElemProp;
// Get document width or height
} else if ( elem.nodeType === 9 ) {
// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
return Math.max(
elem.documentElement["client" + name],
elem.body["scroll" + name], elem.documentElement["scroll" + name],
elem.body["offset" + name], elem.documentElement["offset" + name]
);
// Get or set width or height on the element
} else if ( size === undefined ) {
var orig = jQuery.css( elem, type ),
ret = parseFloat( orig );
return jQuery.isNaN( ret ) ? orig : ret;
// Set the width or height on the element (default to pixels if value is unitless)
} else {
return this.css( type, typeof size === "string" ? size : size + "px" );
}
};
});
})(window);

View File

@ -1,4332 +0,0 @@
/**
* @preserve jquery.layout 1.3.0 - Release Candidate 29.14
* $Date: 2011-02-13 08:00:00 (Sun, 13 Feb 2011) $
* $Rev: 302914 $
*
* Copyright (c) 2010
* Fabrizio Balliano (http://www.fabrizioballiano.net)
* Kevin Dalman (http://allpro.net)
*
* Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
* and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
*
* Changelog: http://layout.jquery-dev.net/changelog.cfm#1.3.0.rc29.13
*
* Docs: http://layout.jquery-dev.net/documentation.html
* Tips: http://layout.jquery-dev.net/tips.html
* Help: http://groups.google.com/group/jquery-ui-layout
*/
// NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars
;(function ($) {
var $b = $.browser;
/*
* GENERIC $.layout METHODS - used by all layouts
*/
$.layout = {
// can update code here if $.browser is phased out
browser: {
mozilla: !!$b.mozilla
, webkit: !!$b.webkit || !!$b.safari // webkit = jQ 1.4
, msie: !!$b.msie
, isIE6: !!$b.msie && $b.version == 6
, boxModel: false // page must load first, so will be updated set by _create
//, version: $b.version - not used
}
/*
* USER UTILITIES
*/
// calculate and return the scrollbar width, as an integer
, scrollbarWidth: function () { return window.scrollbarWidth || $.layout.getScrollbarSize('width'); }
, scrollbarHeight: function () { return window.scrollbarHeight || $.layout.getScrollbarSize('height'); }
, getScrollbarSize: function (dim) {
var $c = $('<div style="position: absolute; top: -10000px; left: -10000px; width: 100px; height: 100px; overflow: scroll;"></div>').appendTo("body");
var d = { width: $c.width() - $c[0].clientWidth, height: $c.height() - $c[0].clientHeight };
$c.remove();
window.scrollbarWidth = d.width;
window.scrollbarHeight = d.height;
return dim.match(/^(width|height)$/i) ? d[dim] : d;
}
/**
* Returns hash container 'display' and 'visibility'
*
* @see $.swap() - swaps CSS, runs callback, resets CSS
*/
, showInvisibly: function ($E, force) {
if (!$E) return {};
if (!$E.jquery) $E = $($E);
var CSS = {
display: $E.css('display')
, visibility: $E.css('visibility')
};
if (force || CSS.display == "none") { // only if not *already hidden*
$E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so can be measured
return CSS;
}
else return {};
}
/**
* Returns data for setting size of an element (container or a pane).
*
* @see _create(), onWindowResize() for container, plus others for pane
* @return JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc
*/
, getElemDims: function ($E) {
var
d = {} // dimensions hash
, x = d.css = {} // CSS hash
, i = {} // TEMP insets
, b, p // TEMP border, padding
, off = $E.offset()
;
d.offsetLeft = off.left;
d.offsetTop = off.top;
$.each("Left,Right,Top,Bottom".split(","), function (idx, e) { // e = edge
b = x["border" + e] = $.layout.borderWidth($E, e);
p = x["padding"+ e] = $.layout.cssNum($E, "padding"+e);
i[e] = b + p; // total offset of content from outer side
d["inset"+ e] = p;
/* WRONG ???
// if BOX MODEL, then 'position' = PADDING (ignore borderWidth)
if ($E == $Container)
d["inset"+ e] = (browser.boxModel ? p : 0);
*/
});
d.offsetWidth = $E.innerWidth();
d.offsetHeight = $E.innerHeight();
d.outerWidth = $E.outerWidth();
d.outerHeight = $E.outerHeight();
d.innerWidth = d.outerWidth - i.Left - i.Right;
d.innerHeight = d.outerHeight - i.Top - i.Bottom;
// TESTING
x.width = $E.width();
x.height = $E.height();
return d;
}
, getElemCSS: function ($E, list) {
var
CSS = {}
, style = $E[0].style
, props = list.split(",")
, sides = "Top,Bottom,Left,Right".split(",")
, attrs = "Color,Style,Width".split(",")
, p, s, a, i, j, k
;
for (i=0; i < props.length; i++) {
p = props[i];
if (p.match(/(border|padding|margin)$/))
for (j=0; j < 4; j++) {
s = sides[j];
if (p == "border")
for (k=0; k < 3; k++) {
a = attrs[k];
CSS[p+s+a] = style[p+s+a];
}
else
CSS[p+s] = style[p+s];
}
else
CSS[p] = style[p];
};
return CSS
}
/**
* Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype
*
* @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
* @param {Array.<Object>} $E Must pass a jQuery object - first element is processed
* @param {number=} outerWidth/outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized
* @return {number} Returns the innerWidth/Height of the elem by subtracting padding and borders
*/
, cssWidth: function ($E, outerWidth) {
var
b = $.layout.borderWidth
, n = $.layout.cssNum
;
// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
if (outerWidth <= 0) return 0;
if (!$.layout.browser.boxModel) return outerWidth;
// strip border and padding from outerWidth to get CSS Width
var W = outerWidth
- b($E, "Left")
- b($E, "Right")
- n($E, "paddingLeft")
- n($E, "paddingRight")
;
return Math.max(0,W);
}
, cssHeight: function ($E, outerHeight) {
var
b = $.layout.borderWidth
, n = $.layout.cssNum
;
// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
if (outerHeight <= 0) return 0;
if (!$.layout.browser.boxModel) return outerHeight;
// strip border and padding from outerHeight to get CSS Height
var H = outerHeight
- b($E, "Top")
- b($E, "Bottom")
- n($E, "paddingTop")
- n($E, "paddingBottom")
;
return Math.max(0,H);
}
/**
* Returns the 'current CSS numeric value' for an element - returns 0 if property does not exist
*
* @see Called by many methods
* @param {Array.<Object>} $E Must pass a jQuery object - first element is processed
* @param {string} prop The name of the CSS property, eg: top, width, etc.
* @return {*} Usually is used to get an integer value for position (top, left) or size (height, width)
*/
, cssNum: function ($E, prop) {
if (!$E.jquery) $E = $($E);
var CSS = $.layout.showInvisibly($E);
var val = parseInt($.curCSS($E[0], prop, true), 10) || 0;
$E.css( CSS ); // RESET
return val;
}
, borderWidth: function (el, side) {
if (el.jquery) el = el[0];
var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left
return $.curCSS(el, b+"Style", true) == "none" ? 0 : (parseInt($.curCSS(el, b+"Width", true), 10) || 0);
}
/**
* SUBROUTINE for preventPrematureSlideClose option
*
* @param {Object} evt
* @param {Object=} el
*/
, isMouseOverElem: function (evt, el) {
var
$E = $(el || this)
, d = $E.offset()
, T = d.top
, L = d.left
, R = L + $E.outerWidth()
, B = T + $E.outerHeight()
, x = evt.pageX
, y = evt.pageY
;
// if X & Y are < 0, probably means is over an open SELECT
return ($.layout.browser.msie && x < 0 && y < 0) || ((x >= L && x <= R) && (y >= T && y <= B));
}
};
$.fn.layout = function (opts) {
/*
* ###########################
* WIDGET CONFIG & OPTIONS
* ###########################
*/
// LANGUAGE CUSTOMIZATION - will be *externally customizable* in next version
var lang = {
Pane: "Pane"
, Open: "Open" // eg: "Open Pane"
, Close: "Close"
, Resize: "Resize"
, Slide: "Slide Open"
, Pin: "Pin"
, Unpin: "Un-Pin"
, selector: "selector"
, msgNoRoom: "Not enough room to show this pane."
, errContainerMissing: "UI Layout Initialization Error\n\nThe specified layout-container does not exist."
, errCenterPaneMissing: "UI Layout Initialization Error\n\nThe center-pane element does not exist.\n\nThe center-pane is a required element."
, errContainerHeight: "UI Layout Initialization Warning\n\nThe layout-container \"CONTAINER\" has no height.\n\nTherefore the layout is 0-height and hence 'invisible'!"
, errButton: "Error Adding Button \n\nInvalid "
};
// DEFAULT OPTIONS - CHANGE IF DESIRED
var options = {
name: "" // Not required, but useful for buttons and used for the state-cookie
, containerClass: "ui-layout-container" // layout-container element
, scrollToBookmarkOnLoad: true // after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark)
, resizeWithWindow: true // bind thisLayout.resizeAll() to the window.resize event
, resizeWithWindowDelay: 200 // delay calling resizeAll because makes window resizing very jerky
, resizeWithWindowMaxDelay: 0 // 0 = none - force resize every XX ms while window is being resized
, onresizeall_start: null // CALLBACK when resizeAll() STARTS - NOT pane-specific
, onresizeall_end: null // CALLBACK when resizeAll() ENDS - NOT pane-specific
, onload_start: null // CALLBACK when Layout inits - after options initialized, but before elements
, onload_end: null // CALLBACK when Layout inits - after EVERYTHING has been initialized
, onunload_start: null // CALLBACK when Layout is destroyed OR onWindowUnload
, onunload_end: null // CALLBACK when Layout is destroyed OR onWindowUnload
, autoBindCustomButtons: false // search for buttons with ui-layout-button class and auto-bind them
, zIndex: null // the PANE zIndex - resizers and masks will be +1
// PANE SETTINGS
, defaults: { // default options for 'all panes' - will be overridden by 'per-pane settings'
applyDemoStyles: false // NOTE: renamed from applyDefaultStyles for clarity
, closable: true // pane can open & close
, resizable: true // when open, pane can be resized
, slidable: true // when closed, pane can 'slide open' over other panes - closes on mouse-out
, initClosed: false // true = init pane as 'closed'
, initHidden: false // true = init pane as 'hidden' - no resizer-bar/spacing
// SELECTORS
//, paneSelector: "" // MUST be pane-specific - jQuery selector for pane
, contentSelector: ".ui-layout-content" // INNER div/element to auto-size so only it scrolls, not the entire pane!
, contentIgnoreSelector: ".ui-layout-ignore" // element(s) to 'ignore' when measuring 'content'
, findNestedContent: false // true = $P.find(contentSelector), false = $P.children(contentSelector)
// GENERIC ROOT-CLASSES - for auto-generated classNames
, paneClass: "ui-layout-pane" // border-Pane - default: 'ui-layout-pane'
, resizerClass: "ui-layout-resizer" // Resizer Bar - default: 'ui-layout-resizer'
, togglerClass: "ui-layout-toggler" // Toggler Button - default: 'ui-layout-toggler'
, buttonClass: "ui-layout-button" // CUSTOM Buttons - default: 'ui-layout-button-toggle/-open/-close/-pin'
// ELEMENT SIZE & SPACING
//, size: 100 // MUST be pane-specific -initial size of pane
, minSize: 0 // when manually resizing a pane
, maxSize: 0 // ditto, 0 = no limit
, spacing_open: 6 // space between pane and adjacent panes - when pane is 'open'
, spacing_closed: 6 // ditto - when pane is 'closed'
, togglerLength_open: 50 // Length = WIDTH of toggler button on north/south sides - HEIGHT on east/west sides
, togglerLength_closed: 50 // 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden'
, togglerAlign_open: "center" // top/left, bottom/right, center, OR...
, togglerAlign_closed: "center" // 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right
, togglerTip_open: lang.Close // Toggler tool-tip (title)
, togglerTip_closed: lang.Open // ditto
, togglerContent_open: "" // text or HTML to put INSIDE the toggler
, togglerContent_closed: "" // ditto
// RESIZING OPTIONS
, resizerDblClickToggle: true //
, autoResize: true // IF size is 'auto' or a percentage, then recalc 'pixel size' whenever the layout resizes
, autoReopen: true // IF a pane was auto-closed due to noRoom, reopen it when there is room? False = leave it closed
, resizerDragOpacity: 1 // option for ui.draggable
//, resizerCursor: "" // MUST be pane-specific - cursor when over resizer-bar
, maskIframesOnResize: true // true = all iframes OR = iframe-selector(s) - adds masking-div during resizing/dragging
, resizeNestedLayout: true // true = trigger nested.resizeAll() when a 'pane' of this layout is the 'container' for another
, resizeWhileDragging: false // true = LIVE Resizing as resizer is dragged
, resizeContentWhileDragging: false // true = re-measure header/footer heights as resizer is dragged
// TIPS & MESSAGES - also see lang object
, noRoomToOpenTip: lang.msgNoRoom
, resizerTip: lang.Resize // Resizer tool-tip (title)
, sliderTip: lang.Slide // resizer-bar triggers 'sliding' when pane is closed
, sliderCursor: "pointer" // cursor when resizer-bar will trigger 'sliding'
, slideTrigger_open: "click" // click, dblclick, mouseenter
, slideTrigger_close: "mouseleave"// click, mouseleave
, slideDelay_open: 300 // applies only for mouseenter event - 0 = instant open
, slideDelay_close: 300 // applies only for mouseleave event (300ms is the minimum!)
, hideTogglerOnSlide: false // when pane is slid-open, should the toggler show?
, preventQuickSlideClose: !!($.browser.webkit || $.browser.safari) // Chrome triggers slideClosed as is opening
, preventPrematureSlideClose: false
// HOT-KEYS & MISC
, showOverflowOnHover: false // will bind allowOverflow() utility to pane.onMouseOver
, enableCursorHotkey: true // enabled 'cursor' hotkeys
//, customHotkey: "" // MUST be pane-specific - EITHER a charCode OR a character
, customHotkeyModifier: "SHIFT" // either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT'
// PANE ANIMATION
// NOTE: fxSss_open & fxSss_close options (eg: fxName_open) are auto-generated if not passed
, fxName: "slide" // ('none' or blank), slide, drop, scale
, fxSpeed: null // slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration
, fxSettings: {} // can be passed, eg: { easing: "easeOutBounce", duration: 1500 }
, fxOpacityFix: true // tries to fix opacity in IE to restore anti-aliasing after animation
// CALLBACKS
, triggerEventsOnLoad: false // true = trigger onopen OR onclose callbacks when layout initializes
, triggerEventsWhileDragging: true // true = trigger onresize callback REPEATEDLY if resizeWhileDragging==true
, onshow_start: null // CALLBACK when pane STARTS to Show - BEFORE onopen/onhide_start
, onshow_end: null // CALLBACK when pane ENDS being Shown - AFTER onopen/onhide_end
, onhide_start: null // CALLBACK when pane STARTS to Close - BEFORE onclose_start
, onhide_end: null // CALLBACK when pane ENDS being Closed - AFTER onclose_end
, onopen_start: null // CALLBACK when pane STARTS to Open
, onopen_end: null // CALLBACK when pane ENDS being Opened
, onclose_start: null // CALLBACK when pane STARTS to Close
, onclose_end: null // CALLBACK when pane ENDS being Closed
, onresize_start: null // CALLBACK when pane STARTS being Resized ***FOR ANY REASON***
, onresize_end: null // CALLBACK when pane ENDS being Resized ***FOR ANY REASON***
, onsizecontent_start: null // CALLBACK when sizing of content-element STARTS
, onsizecontent_end: null // CALLBACK when sizing of content-element ENDS
, onswap_start: null // CALLBACK when pane STARTS to Swap
, onswap_end: null // CALLBACK when pane ENDS being Swapped
, ondrag_start: null // CALLBACK when pane STARTS being ***MANUALLY*** Resized
, ondrag_end: null // CALLBACK when pane ENDS being ***MANUALLY*** Resized
}
, north: {
paneSelector: ".ui-layout-north"
, size: "auto" // eg: "auto", "30%", 200
, resizerCursor: "n-resize" // custom = url(myCursor.cur)
, customHotkey: "" // EITHER a charCode OR a character
}
, south: {
paneSelector: ".ui-layout-south"
, size: "auto"
, resizerCursor: "s-resize"
, customHotkey: ""
}
, east: {
paneSelector: ".ui-layout-east"
, size: 200
, resizerCursor: "e-resize"
, customHotkey: ""
}
, west: {
paneSelector: ".ui-layout-west"
, size: 200
, resizerCursor: "w-resize"
, customHotkey: ""
}
, center: {
paneSelector: ".ui-layout-center"
, minWidth: 0
, minHeight: 0
}
// STATE MANAGMENT
, useStateCookie: false // Enable cookie-based state-management - can fine-tune with cookie.autoLoad/autoSave
, cookie: {
name: "" // If not specified, will use Layout.name, else just "Layout"
, autoSave: true // Save a state cookie when page exits?
, autoLoad: true // Load the state cookie when Layout inits?
// Cookie Options
, domain: ""
, path: ""
, expires: "" // 'days' to keep cookie - leave blank for 'session cookie'
, secure: false
// List of options to save in the cookie - must be pane-specific
, keys: "north.size,south.size,east.size,west.size,"+
"north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+
"north.isHidden,south.isHidden,east.isHidden,west.isHidden"
}
};
// PREDEFINED EFFECTS / DEFAULTS
var effects = { // LIST *PREDEFINED EFFECTS* HERE, even if effect has no settings
slide: {
all: { duration: "fast" } // eg: duration: 1000, easing: "easeOutBounce"
, north: { direction: "up" }
, south: { direction: "down" }
, east: { direction: "right"}
, west: { direction: "left" }
}
, drop: {
all: { duration: "slow" } // eg: duration: 1000, easing: "easeOutQuint"
, north: { direction: "up" }
, south: { direction: "down" }
, east: { direction: "right"}
, west: { direction: "left" }
}
, scale: {
all: { duration: "fast" }
}
};
// DYNAMIC DATA - IS READ-ONLY EXTERNALLY!
var state = {
// generate unique ID to use for event.namespace so can unbind only events added by 'this layout'
id: "layout"+ new Date().getTime() // code uses alias: sID
, initialized: false
, container: {} // init all keys
, north: {}
, south: {}
, east: {}
, west: {}
, center: {}
, cookie: {} // State Managment data storage
};
// INTERNAL CONFIG DATA - DO NOT CHANGE THIS!
var _c = {
allPanes: "north,south,west,east,center"
, borderPanes: "north,south,west,east"
, altSide: {
north: "south"
, south: "north"
, east: "west"
, west: "east"
}
// CSS used in multiple places
, hidden: { visibility: "hidden" }
, visible: { visibility: "visible" }
// layout element settings
, zIndex: { // set z-index values here
pane_normal: 1 // normal z-index for panes
, resizer_normal: 2 // normal z-index for resizer-bars
, iframe_mask: 2 // overlay div used to mask pane(s) during resizing
, pane_sliding: 100 // applied to *BOTH* the pane and its resizer when a pane is 'slid open'
, pane_animate: 1000 // applied to the pane when being animated - not applied to the resizer
, resizer_drag: 10000 // applied to the CLONED resizer-bar when being 'dragged'
}
, resizers: {
cssReq: {
position: "absolute"
, padding: 0
, margin: 0
, fontSize: "1px"
, textAlign: "left" // to counter-act "center" alignment!
, overflow: "hidden" // prevent toggler-button from overflowing
// SEE c.zIndex.resizer_normal
}
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
background: "#DDD"
, border: "none"
}
}
, togglers: {
cssReq: {
position: "absolute"
, display: "block"
, padding: 0
, margin: 0
, overflow: "hidden"
, textAlign: "center"
, fontSize: "1px"
, cursor: "pointer"
, zIndex: 1
}
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
background: "#AAA"
}
}
, content: {
cssReq: {
position: "relative" /* contain floated or positioned elements */
}
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
overflow: "auto"
, padding: "10px"
}
, cssDemoPane: { // DEMO CSS - REMOVE scrolling from 'pane' when it has a content-div
overflow: "hidden"
, padding: 0
}
}
, panes: { // defaults for ALL panes - overridden by 'per-pane settings' below
cssReq: {
position: "absolute"
, margin: 0
// SEE c.zIndex.pane_normal
}
, cssDemo: { // DEMO CSS - applied if: options.PANE.applyDemoStyles=true
padding: "10px"
, background: "#FFF"
, border: "1px solid #BBB"
, overflow: "auto"
}
}
, north: {
side: "Top"
, sizeType: "Height"
, dir: "horz"
, cssReq: {
top: 0
, bottom: "auto"
, left: 0
, right: 0
, width: "auto"
// height: DYNAMIC
}
, pins: [] // array of 'pin buttons' to be auto-updated on open/close (classNames)
}
, south: {
side: "Bottom"
, sizeType: "Height"
, dir: "horz"
, cssReq: {
top: "auto"
, bottom: 0
, left: 0
, right: 0
, width: "auto"
// height: DYNAMIC
}
, pins: []
}
, east: {
side: "Right"
, sizeType: "Width"
, dir: "vert"
, cssReq: {
left: "auto"
, right: 0
, top: "auto" // DYNAMIC
, bottom: "auto" // DYNAMIC
, height: "auto"
// width: DYNAMIC
}
, pins: []
}
, west: {
side: "Left"
, sizeType: "Width"
, dir: "vert"
, cssReq: {
left: 0
, right: "auto"
, top: "auto" // DYNAMIC
, bottom: "auto" // DYNAMIC
, height: "auto"
// width: DYNAMIC
}
, pins: []
}
, center: {
dir: "center"
, cssReq: {
left: "auto" // DYNAMIC
, right: "auto" // DYNAMIC
, top: "auto" // DYNAMIC
, bottom: "auto" // DYNAMIC
, height: "auto"
, width: "auto"
}
}
};
/*
* ###########################
* INTERNAL HELPER FUNCTIONS
* ###########################
*/
/**
* Manages all internal timers
*/
var timer = {
data: {}
, set: function (s, fn, ms) { timer.clear(s); timer.data[s] = setTimeout(fn, ms); }
, clear: function (s) { var t=timer.data; if (t[s]) {clearTimeout(t[s]); delete t[s];} }
};
/**
* Returns true if passed param is EITHER a simple string OR a 'string object' - otherwise returns false
*/
var isStr = function (o) {
try { return typeof o == "string"
|| (typeof o == "object" && o.constructor.toString().match(/string/i) !== null); }
catch (e) { return false; }
};
/**
* Returns a simple string if passed EITHER a simple string OR a 'string object',
* else returns the original object
*/
var str = function (o) { // trim converts 'String object' to a simple string
return isStr(o) ? $.trim(o) : o == undefined || o == null ? "" : o;
};
/**
* min / max
*
* Aliases for Math methods to simplify coding
*/
var min = function (x,y) { return Math.min(x,y); };
var max = function (x,y) { return Math.max(x,y); };
/**
* Processes the options passed in and transforms them into the format used by layout()
* Missing keys are added, and converts the data if passed in 'flat-format' (no sub-keys)
* In flat-format, pane-specific-settings are prefixed like: north__optName (2-underscores)
* To update effects, options MUST use nested-keys format, with an effects key ???
*
* @see initOptions()
* @param {Object} d Data/options passed by user - may be a single level or nested levels
* @return {Object} Creates a data struture that perfectly matches 'options', ready to be imported
*/
var _transformData = function (d) {
var a, json = { cookie:{}, defaults:{fxSettings:{}}, north:{fxSettings:{}}, south:{fxSettings:{}}, east:{fxSettings:{}}, west:{fxSettings:{}}, center:{fxSettings:{}} };
d = d || {};
if (d.effects || d.cookie || d.defaults || d.north || d.south || d.west || d.east || d.center)
json = $.extend( true, json, d ); // already in json format - add to base keys
else
// convert 'flat' to 'nest-keys' format - also handles 'empty' user-options
$.each( d, function (key,val) {
a = key.split("__");
if (!a[1] || json[a[0]]) // check for invalid keys
json[ a[1] ? a[0] : "defaults" ][ a[1] ? a[1] : a[0] ] = val;
});
return json;
};
/**
* Set an INTERNAL callback to avoid simultaneous animation
* Runs only if needed and only if all callbacks are not 'already set'
* Called by open() and close() when isLayoutBusy=true
*
* @param {string} action Either 'open' or 'close'
* @param {string} pane A valid border-pane name, eg 'west'
* @param {boolean=} param Extra param for callback (optional)
*/
var _queue = function (action, pane, param) {
var tried = [];
// if isLayoutBusy, then some pane must be 'moving'
$.each(_c.borderPanes.split(","), function (i, p) {
if (_c[p].isMoving) {
bindCallback(p); // TRY to bind a callback
return false; // BREAK
}
});
// if pane does NOT have a callback, then add one, else follow the callback chain...
function bindCallback (p) {
var c = _c[p];
if (!c.doCallback) {
c.doCallback = true;
c.callback = action +","+ pane +","+ (param ? 1 : 0);
}
else { // try to 'chain' this callback
tried.push(p);
var cbPane = c.callback.split(",")[1]; // 2nd param of callback is 'pane'
// ensure callback target NOT 'itself' and NOT 'target pane' and NOT already tried (avoid loop)
if (cbPane != pane && !$.inArray(cbPane, tried) >= 0)
bindCallback(cbPane); // RECURSE
}
}
};
/**
* RUN the INTERNAL callback for this pane - if one exists
*
* @param {string} pane A valid border-pane name, eg 'west'
*/
var _dequeue = function (pane) {
var c = _c[pane];
// RESET flow-control flags
_c.isLayoutBusy = false;
delete c.isMoving;
if (!c.doCallback || !c.callback) return;
c.doCallback = false; // RESET logic flag
// EXECUTE the callback
var
cb = c.callback.split(",")
, param = (cb[2] > 0 ? true : false)
;
if (cb[0] == "open")
open( cb[1], param );
else if (cb[0] == "close")
close( cb[1], param );
if (!c.doCallback) c.callback = null; // RESET - unless callback above enabled it again!
};
/**
* Executes a Callback function after a trigger event, like resize, open or close
*
* @param {?string} pane This is passed only so we can pass the 'pane object' to the callback
* @param {(string|function())} v_fn Accepts a function name, OR a comma-delimited array: [0]=function name, [1]=argument
*/
var _execCallback = function (pane, v_fn) {
if (!v_fn) return;
var fn;
try {
if (typeof v_fn == "function")
fn = v_fn;
else if (!isStr(v_fn))
return;
else if (v_fn.match(/,/)) {
// function name cannot contain a comma, so must be a function name AND a 'name' parameter
var args = v_fn.split(",");
fn = eval(args[0]);
if (typeof fn=="function" && args.length > 1)
return fn(args[1]); // pass the argument parsed from 'list'
}
else // just the name of an external function?
fn = eval(v_fn);
if (typeof fn=="function") {
if (pane && $Ps[pane])
// pass data: pane-name, pane-element, pane-state (copy), pane-options, and layout-name
return fn( pane, $Ps[pane], $.extend({},state[pane]), options[pane], options.name );
else // must be a layout/container callback - pass suitable info
return fn( Instance, $.extend({},state), options, options.name );
}
}
catch (ex) {}
};
/**
* Returns hash container 'display' and 'visibility'
*
* @see $.swap() - swaps CSS, runs callback, resets CSS
* @param {!Object} $E
* @param {boolean=} force
*/
var _showInvisibly = function ($E, force) {
if (!$E) return {};
if (!$E.jquery) $E = $($E);
var CSS = {
display: $E.css('display')
, visibility: $E.css('visibility')
};
if (force || CSS.display == "none") { // only if not *already hidden*
$E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so can be measured
return CSS;
}
else return {};
};
/**
* cure iframe display issues in IE & other browsers
*/
var _fixIframe = function (pane) {
if (state.browser.mozilla) return; // skip FireFox - it auto-refreshes iframes onShow
var $P = $Ps[pane];
// if the 'pane' is an iframe, do it
if (state[pane].tagName == "IFRAME")
$P.css(_c.hidden).css(_c.visible);
else // ditto for any iframes INSIDE the pane
$P.find('IFRAME').css(_c.hidden).css(_c.visible);
};
/**
* Returns the 'current CSS numeric value' for a CSS property - 0 if property does not exist
*
* @see Called by many methods
* @param {Array.<Object>} $E Must pass a jQuery object - first element is processed
* @param {string} prop The name of the CSS property, eg: top, width, etc.
* @return {(string|number)} Usually used to get an integer value for position (top, left) or size (height, width)
*/
var _cssNum = function ($E, prop) {
if (!$E.jquery) $E = $($E);
var CSS = _showInvisibly($E);
var val = parseInt($.curCSS($E[0], prop, true), 10) || 0;
$E.css( CSS ); // RESET
return val;
};
/**
* @param {!Object} E Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object
* @param {string} side Which border (top, left, etc.) is resized
* @return {number} Returns the borderWidth
*/
var _borderWidth = function (E, side) {
if (E.jquery) E = E[0];
var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left
return $.curCSS(E, b+"Style", true) == "none" ? 0 : (parseInt($.curCSS(E, b+"Width", true), 10) || 0);
};
/**
* cssW / cssH / cssSize / cssMinDims
*
* Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype
*
* @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
* @param {(string|!Object)} el Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object
* @param {number=} outerWidth (optional) Can pass a width, allowing calculations BEFORE element is resized
* @return {number} Returns the innerWidth of el by subtracting padding and borders
*/
var cssW = function (el, outerWidth) {
var
str = isStr(el)
, $E = str ? $Ps[el] : $(el)
;
if (isNaN(outerWidth)) // not specified
outerWidth = str ? getPaneSize(el) : $E.outerWidth();
// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
if (outerWidth <= 0) return 0;
if (!state.browser.boxModel) return outerWidth;
// strip border and padding from outerWidth to get CSS Width
var W = outerWidth
- _borderWidth($E, "Left")
- _borderWidth($E, "Right")
- _cssNum($E, "paddingLeft")
- _cssNum($E, "paddingRight")
;
return max(0,W);
};
/**
* @param {(string|!Object)} el Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object
* @param {number=} outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized
* @return {number} Returns the innerHeight el by subtracting padding and borders
*/
var cssH = function (el, outerHeight) {
var
str = isStr(el)
, $E = str ? $Ps[el] : $(el)
;
if (isNaN(outerHeight)) // not specified
outerHeight = str ? getPaneSize(el) : $E.outerHeight();
// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
if (outerHeight <= 0) return 0;
if (!state.browser.boxModel) return outerHeight;
// strip border and padding from outerHeight to get CSS Height
var H = outerHeight
- _borderWidth($E, "Top")
- _borderWidth($E, "Bottom")
- _cssNum($E, "paddingTop")
- _cssNum($E, "paddingBottom")
;
return max(0,H);
};
/**
* @param {string} pane Can accept ONLY a 'pane' (east, west, etc)
* @param {number=} outerSize (optional) Can pass a width, allowing calculations BEFORE element is resized
* @return {number} Returns the innerHeight/Width of el by subtracting padding and borders
*/
var cssSize = function (pane, outerSize) {
if (_c[pane].dir=="horz") // pane = north or south
return cssH(pane, outerSize);
else // pane = east or west
return cssW(pane, outerSize);
};
/**
* @param {string} pane Can accept ONLY a 'pane' (east, west, etc)
* @return {Object} Returns hash of minWidth & minHeight
*/
var cssMinDims = function (pane) {
// minWidth/Height means CSS width/height = 1px
var
dir = _c[pane].dir
, d = {
minWidth: 1001 - cssW(pane, 1000)
, minHeight: 1001 - cssH(pane, 1000)
}
;
if (dir == "horz") d.minSize = d.minHeight;
if (dir == "vert") d.minSize = d.minWidth;
return d;
};
// TODO: see if these methods can be made more useful...
// TODO: *maybe* return cssW/H from these so caller can use this info
/**
* @param {(string|!Object)} el
* @param {number=} outerWidth
* @param {boolean=} autoHide
*/
var setOuterWidth = function (el, outerWidth, autoHide) {
var $E = el, w;
if (isStr(el)) $E = $Ps[el]; // west
else if (!el.jquery) $E = $(el);
w = cssW($E, outerWidth);
$E.css({ width: w });
if (w > 0) {
if (autoHide && $E.data('autoHidden') && $E.innerHeight() > 0) {
$E.show().data('autoHidden', false);
if (!state.browser.mozilla) // FireFox refreshes iframes - IE doesn't
// make hidden, then visible to 'refresh' display after animation
$E.css(_c.hidden).css(_c.visible);
}
}
else if (autoHide && !$E.data('autoHidden'))
$E.hide().data('autoHidden', true);
};
/**
* @param {(string|!Object)} el
* @param {number=} outerHeight
* @param {boolean=} autoHide
*/
var setOuterHeight = function (el, outerHeight, autoHide) {
var $E = el, h;
if (isStr(el)) $E = $Ps[el]; // west
else if (!el.jquery) $E = $(el);
h = cssH($E, outerHeight);
$E.css({ height: h, visibility: "visible" }); // may have been 'hidden' by sizeContent
if (h > 0 && $E.innerWidth() > 0) {
if (autoHide && $E.data('autoHidden')) {
$E.show().data('autoHidden', false);
if (!state.browser.mozilla) // FireFox refreshes iframes - IE doesn't
$E.css(_c.hidden).css(_c.visible);
}
}
else if (autoHide && !$E.data('autoHidden'))
$E.hide().data('autoHidden', true);
};
/**
* @param {(string|!Object)} el
* @param {number=} outerSize
* @param {boolean=} autoHide
*/
var setOuterSize = function (el, outerSize, autoHide) {
if (_c[pane].dir=="horz") // pane = north or south
setOuterHeight(el, outerSize, autoHide);
else // pane = east or west
setOuterWidth(el, outerSize, autoHide);
};
/**
* Converts any 'size' params to a pixel/integer size, if not already
* If 'auto' or a decimal/percentage is passed as 'size', a pixel-size is calculated
*
/**
* @param {string} pane
* @param {(string|number)=} size
* @param {string=} dir
* @return {number}
*/
var _parseSize = function (pane, size, dir) {
if (!dir) dir = _c[pane].dir;
if (isStr(size) && size.match(/%/))
size = parseInt(size, 10) / 100; // convert % to decimal
if (size === 0)
return 0;
else if (size >= 1)
return parseInt(size, 10);
else if (size > 0) { // percentage, eg: .25
var o = options, avail;
if (dir=="horz") // north or south or center.minHeight
avail = sC.innerHeight - ($Ps.north ? o.north.spacing_open : 0) - ($Ps.south ? o.south.spacing_open : 0);
else if (dir=="vert") // east or west or center.minWidth
avail = sC.innerWidth - ($Ps.west ? o.west.spacing_open : 0) - ($Ps.east ? o.east.spacing_open : 0);
return Math.floor(avail * size);
}
else if (pane=="center")
return 0;
else { // size < 0 || size=='auto' || size==Missing || size==Invalid
// auto-size the pane
var
$P = $Ps[pane]
, dim = (dir == "horz" ? "height" : "width")
, vis = _showInvisibly($P) // show pane invisibly if hidden
, s = $P.css(dim); // SAVE current size
;
$P.css(dim, "auto");
size = (dim == "height") ? $P.outerHeight() : $P.outerWidth(); // MEASURE
$P.css(dim, s).css(vis); // RESET size & visibility
return size;
}
};
/**
* Calculates current 'size' (outer-width or outer-height) of a border-pane - optionally with 'pane-spacing' added
*
* @param {(string|!Object)} pane
* @param {boolean=} inclSpace
* @return {number} Returns EITHER Width for east/west panes OR Height for north/south panes - adjusted for boxModel & browser
*/
var getPaneSize = function (pane, inclSpace) {
var
$P = $Ps[pane]
, o = options[pane]
, s = state[pane]
, oSp = (inclSpace ? o.spacing_open : 0)
, cSp = (inclSpace ? o.spacing_closed : 0)
;
if (!$P || s.isHidden)
return 0;
else if (s.isClosed || (s.isSliding && inclSpace))
return cSp;
else if (_c[pane].dir == "horz")
return $P.outerHeight() + oSp;
else // dir == "vert"
return $P.outerWidth() + oSp;
};
/**
* Calculate min/max pane dimensions and limits for resizing
*
* @param {string} pane
* @param {boolean=} slide
*/
var setSizeLimits = function (pane, slide) {
var
o = options[pane]
, s = state[pane]
, c = _c[pane]
, dir = c.dir
, side = c.side.toLowerCase()
, type = c.sizeType.toLowerCase()
, isSliding = (slide != undefined ? slide : s.isSliding) // only open() passes 'slide' param
, $P = $Ps[pane]
, paneSpacing = o.spacing_open
// measure the pane on the *opposite side* from this pane
, altPane = _c.altSide[pane]
, altS = state[altPane]
, $altP = $Ps[altPane]
, altPaneSize = (!$altP || altS.isVisible===false || altS.isSliding ? 0 : (dir=="horz" ? $altP.outerHeight() : $altP.outerWidth()))
, altPaneSpacing = ((!$altP || altS.isHidden ? 0 : options[altPane][ altS.isClosed !== false ? "spacing_closed" : "spacing_open" ]) || 0)
// limitSize prevents this pane from 'overlapping' opposite pane
, containerSize = (dir=="horz" ? sC.innerHeight : sC.innerWidth)
, minCenterDims = cssMinDims("center")
, minCenterSize = dir=="horz" ? max(options.center.minHeight, minCenterDims.minHeight) : max(options.center.minWidth, minCenterDims.minWidth)
// if pane is 'sliding', then ignore center and alt-pane sizes - because 'overlays' them
, limitSize = (containerSize - paneSpacing - (isSliding ? 0 : (_parseSize("center", minCenterSize, dir) + altPaneSize + altPaneSpacing)))
, minSize = s.minSize = max( _parseSize(pane, o.minSize), cssMinDims(pane).minSize )
, maxSize = s.maxSize = min( (o.maxSize ? _parseSize(pane, o.maxSize) : 100000), limitSize )
, r = s.resizerPosition = {} // used to set resizing limits
, top = sC.insetTop
, left = sC.insetLeft
, W = sC.innerWidth
, H = sC.innerHeight
, rW = o.spacing_open // subtract resizer-width to get top/left position for south/east
;
switch (pane) {
case "north": r.min = top + minSize;
r.max = top + maxSize;
break;
case "west": r.min = left + minSize;
r.max = left + maxSize;
break;
case "south": r.min = top + H - maxSize - rW;
r.max = top + H - minSize - rW;
break;
case "east": r.min = left + W - maxSize - rW;
r.max = left + W - minSize - rW;
break;
};
};
/**
* Returns data for setting the size/position of center pane. Also used to set Height for east/west panes
*
* @return JSON Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height
*/
var calcNewCenterPaneDims = function () {
var d = {
top: getPaneSize("north", true) // true = include 'spacing' value for pane
, bottom: getPaneSize("south", true)
, left: getPaneSize("west", true)
, right: getPaneSize("east", true)
, width: 0
, height: 0
};
// NOTE: sC = state.container
// calc center-pane's outer dimensions
d.width = sC.innerWidth - d.left - d.right; // outerWidth
d.height = sC.innerHeight - d.bottom - d.top; // outerHeight
// add the 'container border/padding' to get final positions relative to the container
d.top += sC.insetTop;
d.bottom += sC.insetBottom;
d.left += sC.insetLeft;
d.right += sC.insetRight;
return d;
};
/**
* Returns data for setting size of an element (container or a pane).
*
* @see _create(), onWindowResize() for container, plus others for pane
* @return JSON Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc
*/
var getElemDims = function ($E) {
var
d = {} // dimensions hash
, x = d.css = {} // CSS hash
, i = {} // TEMP insets
, b, p // TEMP border, padding
, off = $E.offset()
;
d.offsetLeft = off.left;
d.offsetTop = off.top;
$.each("Left,Right,Top,Bottom".split(","), function (idx, e) {
b = x["border" + e] = _borderWidth($E, e);
p = x["padding"+ e] = _cssNum($E, "padding"+e);
i[e] = b + p; // total offset of content from outer side
d["inset"+ e] = p;
/* WRONG ???
// if BOX MODEL, then 'position' = PADDING (ignore borderWidth)
if ($E == $Container)
d["inset"+ e] = (state.browser.boxModel ? p : 0);
*/
});
d.offsetWidth = $E.innerWidth(); // true=include Padding
d.offsetHeight = $E.innerHeight();
d.outerWidth = $E.outerWidth();
d.outerHeight = $E.outerHeight();
d.innerWidth = d.outerWidth - i.Left - i.Right;
d.innerHeight = d.outerHeight - i.Top - i.Bottom;
// TESTING
x.width = $E.width();
x.height = $E.height();
return d;
};
var getElemCSS = function ($E, list) {
var
CSS = {}
, style = $E[0].style
, props = list.split(",")
, sides = "Top,Bottom,Left,Right".split(",")
, attrs = "Color,Style,Width".split(",")
, p, s, a, i, j, k
;
for (i=0; i < props.length; i++) {
p = props[i];
if (p.match(/(border|padding|margin)$/))
for (j=0; j < 4; j++) {
s = sides[j];
if (p == "border")
for (k=0; k < 3; k++) {
a = attrs[k];
CSS[p+s+a] = style[p+s+a];
}
else
CSS[p+s] = style[p+s];
}
else
CSS[p] = style[p];
};
return CSS
};
/**
* @param {!Object} el
* @param {boolean=} allStates
*/
var getHoverClasses = function (el, allStates) {
var
$El = $(el)
, type = $El.data("layoutRole")
, pane = $El.data("layoutEdge")
, o = options[pane]
, root = o[type +"Class"]
, _pane = "-"+ pane // eg: "-west"
, _open = "-open"
, _closed = "-closed"
, _slide = "-sliding"
, _hover = "-hover " // NOTE the trailing space
, _state = $El.hasClass(root+_closed) ? _closed : _open
, _alt = _state == _closed ? _open : _closed
, classes = (root+_hover) + (root+_pane+_hover) + (root+_state+_hover) + (root+_pane+_state+_hover)
;
if (allStates) // when 'removing' classes, also remove alternate-state classes
classes += (root+_alt+_hover) + (root+_pane+_alt+_hover);
if (type=="resizer" && $El.hasClass(root+_slide))
classes += (root+_slide+_hover) + (root+_pane+_slide+_hover);
return $.trim(classes);
};
var addHover = function (evt, el) {
var $E = $(el || this);
if (evt && $E.data("layoutRole") == "toggler")
evt.stopPropagation(); // prevent triggering 'slide' on Resizer-bar
$E.addClass( getHoverClasses($E) );
};
var removeHover = function (evt, el) {
var $E = $(el || this);
$E.removeClass( getHoverClasses($E, true) );
};
var onResizerEnter = function (evt) {
$('body').disableSelection();
addHover(evt, this);
};
var onResizerLeave = function (evt, el) {
var
e = el || this // el is only passed when called by the timer
, pane = $(e).data("layoutEdge")
, name = pane +"ResizerLeave"
;
timer.clear(pane+"_openSlider"); // cancel slideOpen timer, if set
timer.clear(name); // cancel enableSelection timer - may re/set below
if (!el) { // 1st call - mouseleave event
removeHover(evt, this); // do this on initial call
// this method calls itself on a timer because it needs to allow
// enough time for dragging to kick-in and set the isResizing flag
// dragging has a 100ms delay set, so this delay must be higher
timer.set(name, function(){ onResizerLeave(evt, e); }, 200);
}
// if user is resizing, then dragStop will enableSelection() when done
else if (!state[pane].isResizing) // 2nd call - by timer
$('body').enableSelection();
};
/*
* ###########################
* INITIALIZATION METHODS
* ###########################
*/
/**
* Initialize the layout - called automatically whenever an instance of layout is created
*
* @see none - triggered onInit
* @return An object pointer to the instance created
*/
var _create = function () {
// initialize config/options
initOptions();
var o = options;
// onload will CANCEL resizing if returns false
if (false === _execCallback(null, o.onload_start)) return false;
// a center pane is required, so make sure it exists
if (!getPane('center').length) {
alert( lang.errCenterPaneMissing );
return null;
}
// update options with saved state, if option enabled
if (o.useStateCookie && o.cookie.autoLoad)
loadCookie(); // Update options from state-cookie
// set environment - can update code here if $.browser is phased out
state.browser = {
mozilla: $.browser.mozilla
, webkit: $.browser.webkit || $.browser.safari
, msie: $.browser.msie
, isIE6: $.browser.msie && $.browser.version == 6
, boxModel: $.support.boxModel
//, version: $.browser.version - not used
};
// initialize all layout elements
initContainer(); // set CSS as needed and init state.container dimensions
initPanes(); // size & position panes - calls initHandles() - which calls initResizable()
sizeContent(); // AFTER panes & handles have been initialized, size 'content' divs
if (o.scrollToBookmarkOnLoad) {
var l = self.location;
if (l.hash) l.replace( l.hash ); // scrollTo Bookmark
}
// bind hotkey function - keyDown - if required
initHotkeys();
// search for and bind custom-buttons
if (o.autoBindCustomButtons) initButtons();
// bind resizeAll() for 'this layout instance' to window.resize event
if (o.resizeWithWindow && !$Container.data("layoutRole")) // skip if 'nested' inside a pane
$(window).bind("resize."+ sID, windowResize);
// bind window.onunload
$(window).bind("unload."+ sID, unload);
state.initialized = true;
_execCallback(null, o.onload_end || o.onload);
};
var windowResize = function () {
var delay = Number(options.resizeWithWindowDelay) || 100; // there MUST be some delay!
if (delay > 0) {
// resizing uses a delay-loop because the resize event fires repeatly - except in FF, but delay anyway
timer.clear("winResize"); // if already running
timer.set("winResize", function(){ timer.clear("winResize"); timer.clear("winResizeRepeater"); resizeAll(); }, delay);
// ALSO set fixed-delay timer, if not already running
if (!timer.data["winResizeRepeater"]) setWindowResizeRepeater();
}
};
var setWindowResizeRepeater = function () {
var delay = Number(options.resizeWithWindowMaxDelay);
if (delay > 0)
timer.set("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay);
};
var unload = function () {
var o = options;
state.cookie = getState(); // save state in case onunload has custom state-management
_execCallback(null, o.onunload_start);
if (o.useStateCookie && o.cookie.autoSave) saveCookie();
_execCallback(null, o.onunload_end || o.onunload);
};
/**
* Validate and initialize container CSS and events
*
* @see _create()
*/
var initContainer = function () {
var
$C = $Container // alias
, tag = sC.tagName = $C.attr("tagName")
, fullPage= (tag == "BODY")
, props = "position,margin,padding,border"
, CSS = {}
;
sC.selector = $C.selector.split(".slice")[0];
sC.ref = tag +"/"+ sC.selector; // used in messages
$C .data("layout", Instance)
.data("layoutContainer", sID) // unique identifier for internal use
.addClass(options.containerClass)
;
// SAVE original container CSS for use in destroy()
if (!$C.data("layoutCSS")) {
// handle props like overflow different for BODY & HTML - has 'system default' values
if (fullPage) {
CSS = $.extend( getElemCSS($C, props), {
height: $C.css("height")
, overflow: $C.css("overflow")
, overflowX: $C.css("overflowX")
, overflowY: $C.css("overflowY")
});
// ALSO SAVE <HTML> CSS
var $H = $("html");
$H.data("layoutCSS", {
height: "auto" // FF would return a fixed px-size!
, overflow: $H.css("overflow")
, overflowX: $H.css("overflowX")
, overflowY: $H.css("overflowY")
});
}
else // handle props normally for non-body elements
CSS = getElemCSS($C, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY");
$C.data("layoutCSS", CSS);
}
try { // format html/body if this is a full page layout
if (fullPage) {
$("html").css({
height: "100%"
, overflow: "hidden"
, overflowX: "hidden"
, overflowY: "hidden"
});
$("body").css({
position: "relative"
, height: "100%"
, overflow: "hidden"
, overflowX: "hidden"
, overflowY: "hidden"
, margin: 0
, padding: 0 // TODO: test whether body-padding could be handled?
, border: "none" // a body-border creates problems because it cannot be measured!
});
}
else { // set required CSS for overflow and position
CSS = { overflow: "hidden" } // make sure container will not 'scroll'
var
p = $C.css("position")
, h = $C.css("height")
;
// if this is a NESTED layout, then container/outer-pane ALREADY has position and height
if (!$C.data("layoutRole")) {
if (!p || !p.match(/fixed|absolute|relative/))
CSS.position = "relative"; // container MUST have a 'position'
/*
if (!h || h=="auto")
CSS.height = "100%"; // container MUST have a 'height'
*/
}
$C.css( CSS );
if ($C.is(":visible") && $C.innerHeight() < 2)
alert( lang.errContainerHeight.replace(/CONTAINER/, sC.ref) );
}
} catch (ex) {}
// set current layout-container dimensions
$.extend(state.container, getElemDims( $C ));
};
/**
* Bind layout hotkeys - if options enabled
*
* @see _create() and addPane()
* @param {string=} panes The edge(s) to process, blank = all
*/
var initHotkeys = function (panes) {
if (!panes || panes == "all") panes = _c.borderPanes;
// bind keyDown to capture hotkeys, if option enabled for ANY pane
$.each(panes.split(","), function (i, pane) {
var o = options[pane];
if (o.enableCursorHotkey || o.customHotkey) {
$(document).bind("keydown."+ sID, keyDown); // only need to bind this ONCE
return false; // BREAK - binding was done
}
});
};
/**
* Build final OPTIONS data
*
* @see _create()
*/
var initOptions = function () {
// simplify logic by making sure passed 'opts' var has basic keys
opts = _transformData( opts );
// TODO: create a compatibility add-on for new UI widget that will transform old option syntax
var newOpts = {
applyDefaultStyles: "applyDemoStyles"
};
renameOpts(opts.defaults);
$.each(_c.allPanes.split(","), function (i, pane) {
renameOpts(opts[pane]);
});
// update default effects, if case user passed key
if (opts.effects) {
$.extend( effects, opts.effects );
delete opts.effects;
}
$.extend( options.cookie, opts.cookie );
// see if any 'global options' were specified
var globals = "name,containerClass,zIndex,scrollToBookmarkOnLoad,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay,"+
"onresizeall,onresizeall_start,onresizeall_end,onload,onload_start,onload_end,onunload,onunload_start,onunload_end,autoBindCustomButtons,useStateCookie";
$.each(globals.split(","), function (i, key) {
if (opts[key] !== undefined)
options[key] = opts[key];
else if (opts.defaults[key] !== undefined) {
options[key] = opts.defaults[key];
delete opts.defaults[key];
}
});
// remove any 'defaults' that MUST be set 'per-pane'
$.each("paneSelector,resizerCursor,customHotkey".split(","),
function (i, key) { delete opts.defaults[key]; } // is OK if key does not exist
);
// now update options.defaults
$.extend( true, options.defaults, opts.defaults );
// merge config for 'center-pane' - border-panes handled in the loop below
_c.center = $.extend( true, {}, _c.panes, _c.center );
// update config.zIndex values if zIndex option specified
var z = options.zIndex;
if (z === 0 || z > 0) {
_c.zIndex.pane_normal = z;
_c.zIndex.resizer_normal = z+1;
_c.zIndex.iframe_mask = z+1;
}
// merge options for 'center-pane' - border-panes handled in the loop below
$.extend( options.center, opts.center );
// Most 'default options' do not apply to 'center', so add only those that DO
var o_Center = $.extend( true, {}, options.defaults, opts.defaults, options.center ); // TEMP data
var optionsCenter = ("paneClass,contentSelector,applyDemoStyles,triggerEventsOnLoad,showOverflowOnHover,"
+ "onresize,onresize_start,onresize_end,resizeNestedLayout,resizeContentWhileDragging,"
+ "onsizecontent,onsizecontent_start,onsizecontent_end").split(",");
$.each(optionsCenter,
function (i, key) { options.center[key] = o_Center[key]; }
);
var o, defs = options.defaults;
// create a COMPLETE set of options for EACH border-pane
$.each(_c.borderPanes.split(","), function (i, pane) {
// apply 'pane-defaults' to CONFIG.[PANE]
_c[pane] = $.extend( true, {}, _c.panes, _c[pane] );
// apply 'pane-defaults' + user-options to OPTIONS.PANE
o = options[pane] = $.extend( true, {}, options.defaults, options[pane], opts.defaults, opts[pane] );
// make sure we have base-classes
if (!o.paneClass) o.paneClass = "ui-layout-pane";
if (!o.resizerClass) o.resizerClass = "ui-layout-resizer";
if (!o.togglerClass) o.togglerClass = "ui-layout-toggler";
// create FINAL fx options for each pane, ie: options.PANE.fxName/fxSpeed/fxSettings[_open|_close]
$.each(["_open","_close",""], function (i,n) {
var
sName = "fxName"+n
, sSpeed = "fxSpeed"+n
, sSettings = "fxSettings"+n
;
// recalculate fxName according to specificity rules
o[sName] =
opts[pane][sName] // opts.west.fxName_open
|| opts[pane].fxName // opts.west.fxName
|| opts.defaults[sName] // opts.defaults.fxName_open
|| opts.defaults.fxName // opts.defaults.fxName
|| o[sName] // options.west.fxName_open
|| o.fxName // options.west.fxName
|| defs[sName] // options.defaults.fxName_open
|| defs.fxName // options.defaults.fxName
|| "none"
;
// validate fxName to be sure is a valid effect
var fxName = o[sName];
if (fxName == "none" || !$.effects || !$.effects[fxName] || (!effects[fxName] && !o[sSettings] && !o.fxSettings))
fxName = o[sName] = "none"; // effect not loaded, OR undefined FX AND fxSettings not passed
// set vars for effects subkeys to simplify logic
var
fx = effects[fxName] || {} // effects.slide
, fx_all = fx.all || {} // effects.slide.all
, fx_pane = fx[pane] || {} // effects.slide.west
;
// RECREATE the fxSettings[_open|_close] keys using specificity rules
o[sSettings] = $.extend(
{}
, fx_all // effects.slide.all
, fx_pane // effects.slide.west
, defs.fxSettings || {} // options.defaults.fxSettings
, defs[sSettings] || {} // options.defaults.fxSettings_open
, o.fxSettings // options.west.fxSettings
, o[sSettings] // options.west.fxSettings_open
, opts.defaults.fxSettings // opts.defaults.fxSettings
, opts.defaults[sSettings] || {} // opts.defaults.fxSettings_open
, opts[pane].fxSettings // opts.west.fxSettings
, opts[pane][sSettings] || {} // opts.west.fxSettings_open
);
// recalculate fxSpeed according to specificity rules
o[sSpeed] =
opts[pane][sSpeed] // opts.west.fxSpeed_open
|| opts[pane].fxSpeed // opts.west.fxSpeed (pane-default)
|| opts.defaults[sSpeed] // opts.defaults.fxSpeed_open
|| opts.defaults.fxSpeed // opts.defaults.fxSpeed
|| o[sSpeed] // options.west.fxSpeed_open
|| o[sSettings].duration // options.west.fxSettings_open.duration
|| o.fxSpeed // options.west.fxSpeed
|| o.fxSettings.duration // options.west.fxSettings.duration
|| defs.fxSpeed // options.defaults.fxSpeed
|| defs.fxSettings.duration// options.defaults.fxSettings.duration
|| fx_pane.duration // effects.slide.west.duration
|| fx_all.duration // effects.slide.all.duration
|| "normal" // DEFAULT
;
});
});
function renameOpts (O) {
for (var key in newOpts) {
if (O[key] != undefined) {
O[newOpts[key]] = O[key];
delete O[key];
}
}
}
};
/**
* Initialize module objects, styling, size and position for all panes
*
* @see _create()
* @param {string} pane The pane to process
*/
var getPane = function (pane) {
var sel = options[pane].paneSelector
if (sel.substr(0,1)==="#") // ID selector
// NOTE: elements selected 'by ID' DO NOT have to be 'children'
return $Container.find(sel).eq(0);
else { // class or other selector
var $P = $Container.children(sel).eq(0);
// look for the pane nested inside a 'form' element
return $P.length ? $P : $Container.children("form:first").children(sel).eq(0);
}
};
var initPanes = function () {
// NOTE: do north & south FIRST so we can measure their height - do center LAST
$.each(_c.allPanes.split(","), function (idx, pane) {
addPane( pane );
});
// init the pane-handles NOW in case we have to hide or close the pane below
initHandles();
// now that all panes have been initialized and initially-sized,
// make sure there is really enough space available for each pane
$.each(_c.borderPanes.split(","), function (i, pane) {
if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN
setSizeLimits(pane);
makePaneFit(pane); // pane may be Closed, Hidden or Resized by makePaneFit()
}
});
// size center-pane AGAIN in case we 'closed' a border-pane in loop above
sizeMidPanes("center");
// trigger onResize callbacks for all panes with triggerEventsOnLoad = true
$.each(_c.allPanes.split(","), function (i, pane) {
var o = options[pane];
if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN
if (o.triggerEventsOnLoad)
_execCallback(pane, o.onresize_end || o.onresize);
resizeNestedLayout(pane);
}
});
if ($Container.innerHeight() < 2)
alert( lang.errContainerHeight.replace(/CONTAINER/, sC.ref) );
};
/**
* Remove a pane from the layout - subroutine of destroy()
*
* @see initPanes()
* @param {string} pane The pane to process
*/
var addPane = function (pane) {
var
o = options[pane]
, s = state[pane]
, c = _c[pane]
, fx = s.fx
, dir = c.dir
, spacing = o.spacing_open || 0
, isCenter = (pane == "center")
, CSS = {}
, $P = $Ps[pane]
, size, minSize, maxSize
;
// if pane-pointer already exists, remove the old one first
if ($P)
removePane( pane );
else
$Cs[pane] = false; // init
$P = $Ps[pane] = getPane(pane);
if (!$P.length) {
$Ps[pane] = false; // logic
return;
}
// SAVE original Pane CSS
if (!$P.data("layoutCSS")) {
var props = "position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border";
$P.data("layoutCSS", getElemCSS($P, props));
}
// add basic classes & attributes
$P
.data("parentLayout", Instance)
.data("layoutRole", "pane")
.data("layoutEdge", pane)
.css(c.cssReq).css("zIndex", _c.zIndex.pane_normal)
.css(o.applyDemoStyles ? c.cssDemo : {}) // demo styles
.addClass( o.paneClass +" "+ o.paneClass+"-"+pane ) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector'
.bind("mouseenter."+ sID, addHover )
.bind("mouseleave."+ sID, removeHover )
;
// see if this pane has a 'scrolling-content element'
initContent(pane, false); // false = do NOT sizeContent() - called later
if (!isCenter) {
// call _parseSize AFTER applying pane classes & styles - but before making visible (if hidden)
// if o.size is auto or not valid, then MEASURE the pane and use that as it's 'size'
size = s.size = _parseSize(pane, o.size);
minSize = _parseSize(pane,o.minSize) || 1;
maxSize = _parseSize(pane,o.maxSize) || 100000;
if (size > 0) size = max(min(size, maxSize), minSize);
// state for border-panes
s.isClosed = false; // true = pane is closed
s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes
s.isResizing= false; // true = pane is in process of being resized
s.isHidden = false; // true = pane is hidden - no spacing, resizer or toggler is visible!
}
// state for all panes
s.tagName = $P.attr("tagName");
s.edge = pane // useful if pane is (or about to be) 'swapped' - easy find out where it is (or is going)
s.noRoom = false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically
s.isVisible = true; // false = pane is invisible - closed OR hidden - simplify logic
// set css-position to account for container borders & padding
switch (pane) {
case "north": CSS.top = sC.insetTop;
CSS.left = sC.insetLeft;
CSS.right = sC.insetRight;
break;
case "south": CSS.bottom = sC.insetBottom;
CSS.left = sC.insetLeft;
CSS.right = sC.insetRight;
break;
case "west": CSS.left = sC.insetLeft; // top, bottom & height set by sizeMidPanes()
break;
case "east": CSS.right = sC.insetRight; // ditto
break;
case "center": // top, left, width & height set by sizeMidPanes()
}
if (dir == "horz") // north or south pane
CSS.height = max(1, cssH(pane, size));
else if (dir == "vert") // east or west pane
CSS.width = max(1, cssW(pane, size));
//else if (isCenter) {}
$P.css(CSS); // apply size -- top, bottom & height will be set by sizeMidPanes
if (dir != "horz") sizeMidPanes(pane, true); // true = skipCallback
// NOW make the pane visible - in case was initially hidden
if (!s.noRoom)
$P.css({ visibility: "visible", display: "block" });
// close or hide the pane if specified in settings
if (o.initClosed && o.closable)
close(pane, true, true); // true, true = force, noAnimation
else if (o.initHidden || o.initClosed)
hide(pane); // will be completely invisible - no resizer or spacing
// ELSE setAsOpen() - called later by initHandles()
// check option for auto-handling of pop-ups & drop-downs
if (o.showOverflowOnHover)
$P.hover( allowOverflow, resetOverflow );
// if adding a pane AFTER initialization, then...
if (state.initialized) {
initHandles( pane );
initHotkeys( pane );
resizeAll(); // will sizeContent if pane is visible
if (s.isVisible) { // pane is OPEN
if (o.triggerEventsOnLoad)
_execCallback(pane, o.onresize_end || o.onresize);
resizeNestedLayout(pane);
}
}
};
/**
* Initialize module objects, styling, size and position for all resize bars and toggler buttons
*
* @see _create()
* @param {string=} panes The edge(s) to process, blank = all
*/
var initHandles = function (panes) {
if (!panes || panes == "all") panes = _c.borderPanes;
// create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV
$.each(panes.split(","), function (i, pane) {
var $P = $Ps[pane];
$Rs[pane] = false; // INIT
$Ts[pane] = false;
if (!$P) return; // pane does not exist - skip
var
o = options[pane]
, s = state[pane]
, c = _c[pane]
, rClass = o.resizerClass
, tClass = o.togglerClass
, side = c.side.toLowerCase()
, spacing = (s.isVisible ? o.spacing_open : o.spacing_closed)
, _pane = "-"+ pane // used for classNames
, _state = (s.isVisible ? "-open" : "-closed") // used for classNames
// INIT RESIZER BAR
, $R = $Rs[pane] = $("<div></div>")
// INIT TOGGLER BUTTON
, $T = (o.closable ? $Ts[pane] = $("<div></div>") : false)
;
//if (s.isVisible && o.resizable) ... handled by initResizable
if (!s.isVisible && o.slidable)
$R.attr("title", o.sliderTip).css("cursor", o.sliderCursor);
$R
// if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer"
.attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-resizer" : ""))
.data("parentLayout", Instance)
.data("layoutRole", "resizer")
.data("layoutEdge", pane)
.css(_c.resizers.cssReq).css("zIndex", _c.zIndex.resizer_normal)
.css(o.applyDemoStyles ? _c.resizers.cssDemo : {}) // add demo styles
.addClass(rClass +" "+ rClass+_pane)
.appendTo($Container) // append DIV to container
;
if ($T) {
$T
// if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "#paneLeft-toggler"
.attr("id", (o.paneSelector.substr(0,1)=="#" ? o.paneSelector.substr(1) + "-toggler" : ""))
.data("parentLayout", Instance)
.data("layoutRole", "toggler")
.data("layoutEdge", pane)
.css(_c.togglers.cssReq) // add base/required styles
.css(o.applyDemoStyles ? _c.togglers.cssDemo : {}) // add demo styles
.addClass(tClass +" "+ tClass+_pane)
.appendTo($R) // append SPAN to resizer DIV
;
// ADD INNER-SPANS TO TOGGLER
if (o.togglerContent_open) // ui-layout-open
$("<span>"+ o.togglerContent_open +"</span>")
.data("layoutRole", "togglerContent")
.data("layoutEdge", pane)
.addClass("content content-open")
.css("display","none")
.appendTo( $T )
//.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-open instead!
;
if (o.togglerContent_closed) // ui-layout-closed
$("<span>"+ o.togglerContent_closed +"</span>")
.data("layoutRole", "togglerContent")
.data("layoutEdge", pane)
.addClass("content content-closed")
.css("display","none")
.appendTo( $T )
//.hover( addHover, removeHover ) // use ui-layout-toggler-west-hover .content-closed instead!
;
// ADD TOGGLER.click/.hover
enableClosable(pane);
}
// add Draggable events
initResizable(pane);
// ADD CLASSNAMES & SLIDE-BINDINGS - eg: class="resizer resizer-west resizer-open"
if (s.isVisible)
setAsOpen(pane); // onOpen will be called, but NOT onResize
else {
setAsClosed(pane); // onClose will be called
bindStartSlidingEvent(pane, true); // will enable events IF option is set
}
});
// SET ALL HANDLE DIMENSIONS
sizeHandles("all");
};
/**
* Initialize scrolling ui-layout-content div - if exists
*
* @see initPane() - or externally after an Ajax injection
* @param {string} pane The pane to process
* @param {boolean=} resize Size content after init, default = true
*/
var initContent = function (pane, resize) {
var
o = options[pane]
, sel = o.contentSelector
, $P = $Ps[pane]
, $C
;
if (sel) $C = $Cs[pane] = (o.findNestedContent)
? $P.find(sel).eq(0) // match 1-element only
: $P.children(sel).eq(0)
;
if ($C && $C.length) {
// SAVE original Pane CSS
if (!$C.data("layoutCSS"))
$C.data("layoutCSS", getElemCSS($C, "height"));
$C.css( _c.content.cssReq );
if (o.applyDemoStyles) {
$C.css( _c.content.cssDemo ); // add padding & overflow: auto to content-div
$P.css( _c.content.cssDemoPane ); // REMOVE padding/scrolling from pane
}
state[pane].content = {}; // init content state
if (resize !== false) sizeContent(pane);
// sizeContent() is called AFTER init of all elements
}
else
$Cs[pane] = false;
};
/**
* Searches for .ui-layout-button-xxx elements and auto-binds them as layout-buttons
*
* @see _create()
*/
var initButtons = function () {
var pre = "ui-layout-button-", name;
$.each("toggle,open,close,pin,toggle-slide,open-slide".split(","), function (i, action) {
$.each(_c.borderPanes.split(","), function (ii, pane) {
$("."+pre+action+"-"+pane).each(function(){
// if button was previously 'bound', data.layoutName was set, but is blank if layout has no 'name'
name = $(this).data("layoutName") || $(this).attr("layoutName");
if (name == undefined || name == options.name)
bindButton(this, action, pane);
});
});
});
};
/**
* Add resize-bars to all panes that specify it in options
* -dependancy: $.fn.resizable - will skip if not found
*
* @see _create()
* @param {string=} panes The edge(s) to process, blank = all
*/
var initResizable = function (panes) {
var
draggingAvailable = (typeof $.fn.draggable == "function")
, $Frames, side // set in start()
;
if (!panes || panes == "all") panes = _c.borderPanes;
$.each(panes.split(","), function (idx, pane) {
var
o = options[pane]
, s = state[pane]
, c = _c[pane]
, side = (c.dir=="horz" ? "top" : "left")
, r, live // set in start because may change
;
if (!draggingAvailable || !$Ps[pane] || !o.resizable) {
o.resizable = false;
return true; // skip to next
}
var
$P = $Ps[pane]
, $R = $Rs[pane]
, base = o.resizerClass
// 'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process
, resizerClass = base+"-drag" // resizer-drag
, resizerPaneClass = base+"-"+pane+"-drag" // resizer-north-drag
// 'helper' class is applied to the CLONED resizer-bar while it is being dragged
, helperClass = base+"-dragging" // resizer-dragging
, helperPaneClass = base+"-"+pane+"-dragging" // resizer-north-dragging
, helperLimitClass = base+"-dragging-limit" // resizer-drag
, helperPaneLimitClass = base+"-"+pane+"-dragging-limit" // resizer-north-drag
, helperClassesSet = false // logic var
;
if (!s.isClosed)
$R
.attr("title", o.resizerTip)
.css("cursor", o.resizerCursor) // n-resize, s-resize, etc
;
$R.bind("mouseenter."+ sID, onResizerEnter)
.bind("mouseleave."+ sID, onResizerLeave);
$R.draggable({
containment: $Container[0] // limit resizing to layout container
, axis: (c.dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis
, delay: 0
, distance: 1
// basic format for helper - style it using class: .ui-draggable-dragging
, helper: "clone"
, opacity: o.resizerDragOpacity
, addClasses: false // avoid ui-state-disabled class when disabled
//, iframeFix: o.draggableIframeFix // TODO: consider using when bug is fixed
, zIndex: _c.zIndex.resizer_drag
, start: function (e, ui) {
// REFRESH options & state pointers in case we used swapPanes
o = options[pane];
s = state[pane];
// re-read options
live = o.resizeWhileDragging;
// ondrag_start callback - will CANCEL hide if returns false
// TODO: dragging CANNOT be cancelled like this, so see if there is a way?
if (false === _execCallback(pane, o.ondrag_start)) return false;
_c.isLayoutBusy = true; // used by sizePane() logic during a liveResize
s.isResizing = true; // prevent pane from closing while resizing
timer.clear(pane+"_closeSlider"); // just in case already triggered
// SET RESIZER LIMITS - used in drag()
setSizeLimits(pane); // update pane/resizer state
r = s.resizerPosition;
$R.addClass( resizerClass +" "+ resizerPaneClass ); // add drag classes
helperClassesSet = false; // reset logic var - see drag()
// MASK PANES WITH IFRAMES OR OTHER TROUBLESOME ELEMENTS
$Frames = $(o.maskIframesOnResize === true ? "iframe" : o.maskIframesOnResize).filter(":visible");
var id, i=0; // ID incrementer - used when 'resizing' masks during dynamic resizing
$Frames.each(function() {
id = "ui-layout-mask-"+ (++i);
$(this).data("layoutMaskID", id); // tag iframe with corresponding maskID
$('<div id="'+ id +'" class="ui-layout-mask ui-layout-mask-'+ pane +'"/>')
.css({
background: "#fff"
, opacity: "0.001"
, zIndex: _c.zIndex.iframe_mask
, position: "absolute"
, width: this.offsetWidth+"px"
, height: this.offsetHeight+"px"
})
.css($(this).position()) // top & left -- changed from offset()
.appendTo(this.parentNode) // put mask-div INSIDE pane to avoid zIndex issues
;
});
// DISABLE TEXT SELECTION (probably already done by resizer.mouseOver)
$('body').disableSelection();
}
, drag: function (e, ui) {
if (!helperClassesSet) { // can only add classes after clone has been added to the DOM
//$(".ui-draggable-dragging")
ui.helper
.addClass( helperClass +" "+ helperPaneClass ) // add helper classes
.css({ right: "auto", bottom: "auto" }) // fix dir="rtl" issue
.children().css("visibility","hidden") // hide toggler inside dragged resizer-bar
;
helperClassesSet = true;
// draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane!
if (s.isSliding) $Ps[pane].css("zIndex", _c.zIndex.pane_sliding);
}
// CONTAIN RESIZER-BAR TO RESIZING LIMITS
var limit = 0;
if (ui.position[side] < r.min) {
ui.position[side] = r.min;
limit = -1;
}
else if (ui.position[side] > r.max) {
ui.position[side] = r.max;
limit = 1;
}
// ADD/REMOVE dragging-limit CLASS
if (limit) {
ui.helper.addClass( helperLimitClass +" "+ helperPaneLimitClass ); // at dragging-limit
window.defaultStatus = "Panel has reached its " +
((limit>0 && pane.match(/north|west/)) || (limit<0 && pane.match(/south|east/)) ? "maximum" : "minimum") +" size";
}
else {
ui.helper.removeClass( helperLimitClass +" "+ helperPaneLimitClass ); // not at dragging-limit
window.defaultStatus = "";
}
// DYNAMICALLY RESIZE PANES IF OPTION ENABLED
if (live) resizePanes(e, ui, pane);
}
, stop: function (e, ui) {
$('body').enableSelection(); // RE-ENABLE TEXT SELECTION
window.defaultStatus = ""; // clear 'resizing limit' message from statusbar
$R.removeClass( resizerClass +" "+ resizerPaneClass ); // remove drag classes from Resizer
s.isResizing = false;
_c.isLayoutBusy = false; // set BEFORE resizePanes so other logic can pick it up
resizePanes(e, ui, pane, true); // true = resizingDone
}
});
/**
* resizePanes
*
* Sub-routine called from stop() and optionally drag()
*
* @param {!Object} evt
* @param {!Object} ui
* @param {string} pane
* @param {boolean=} resizingDone
*/
var resizePanes = function (evt, ui, pane, resizingDone) {
var
dragPos = ui.position
, c = _c[pane]
, resizerPos, newSize
, i = 0 // ID incrementer
;
switch (pane) {
case "north": resizerPos = dragPos.top; break;
case "west": resizerPos = dragPos.left; break;
case "south": resizerPos = sC.offsetHeight - dragPos.top - o.spacing_open; break;
case "east": resizerPos = sC.offsetWidth - dragPos.left - o.spacing_open; break;
};
if (resizingDone) {
// Remove OR Resize MASK(S) created in drag.start
$("div.ui-layout-mask").each(function() { this.parentNode.removeChild(this); });
//$("div.ui-layout-mask").remove(); // TODO: Is this less efficient?
// ondrag_start callback - will CANCEL hide if returns false
if (false === _execCallback(pane, o.ondrag_end || o.ondrag)) return false;
}
else
$Frames.each(function() {
$("#"+ $(this).data("layoutMaskID")) // get corresponding mask by ID
.css($(this).position()) // update top & left
.css({ // update width & height
width: this.offsetWidth +"px"
, height: this.offsetHeight+"px"
})
;
});
// remove container margin from resizer position to get the pane size
newSize = resizerPos - sC["inset"+ c.side];
manualSizePane(pane, newSize);
}
});
};
/**
* Destroy this layout and reset all elements
*/
var destroy = function () {
// UNBIND layout events and remove global object
$(window).unbind("."+ sID);
$(document).unbind("."+ sID);
// loop all panes to remove layout classes, attributes and bindings
$.each(_c.allPanes.split(","), function (i, pane) {
removePane( pane, false, true ); // true = skipResize
});
// reset layout-container
var $C = $Container
.removeData("layout")
.removeData("layoutContainer")
.removeClass(options.containerClass)
;
// do NOT reset container CSS if is a 'pane' in an outer-layout - ie, THIS layout is 'nested'
if (!$C.data("layoutEdge") && $C.data("layoutCSS")) // RESET CSS
$C.css( $C.data("layoutCSS") ).removeData("layoutCSS");
// for full-page layouts, also reset the <HTML> CSS
if (sC.tagName == "BODY" && ($C = $("html")).data("layoutCSS")) // RESET <HTML> CSS
$C.css( $C.data("layoutCSS") ).removeData("layoutCSS");
// trigger state-management and onunload callback
unload();
};
/**
* Remove a pane from the layout - subroutine of destroy()
*
* @see destroy()
* @param {string} pane The pane to process
* @param {boolean=} remove Remove the DOM element? default = false
* @param {boolean=} skipResize Skip calling resizeAll()? default = false
*/
var removePane = function (pane, remove, skipResize) {
if (!$Ps[pane]) return; // NO SUCH PANE
var
$P = $Ps[pane]
, $C = $Cs[pane]
, $R = $Rs[pane]
, $T = $Ts[pane]
// create list of ALL pane-classes that need to be removed
, _open = "-open"
, _sliding= "-sliding"
, _closed = "-closed"
, root = options[pane].paneClass // default="ui-layout-pane"
, pRoot = root +"-"+ pane // eg: "ui-layout-pane-west"
, classes = [ root, root+_open, root+_closed, root+_sliding, // generic classes
pRoot, pRoot+_open, pRoot+_closed, pRoot+_sliding ] // pane-specific classes
;
$.merge(classes, getHoverClasses($P, true)); // ADD hover-classes
if (!$P || !$P.length) {
} // pane has already been deleted!
else if (remove && !$P.data("layoutContainer") && (!$C || !$C.length || !$C.data("layoutContainer")))
$P.remove();
else {
$P .removeClass( classes.join(" ") ) // remove ALL pane-classes
.removeData("layoutParent")
.removeData("layoutRole")
.removeData("layoutEdge")
.removeData("autoHidden") // in case set
.unbind("."+ sID) // remove ALL Layout events
// TODO: remove these extra unbind commands when jQuery is fixed
//.unbind("mouseenter"+ sID)
//.unbind("mouseleave"+ sID)
;
// do NOT reset CSS if this pane is STILL the container of a nested layout!
// the nested layout will reset its 'container' when/if it is destroyed
if (!$P.data("layoutContainer"))
$P.css( $P.data("layoutCSS") ).removeData("layoutCSS");
// DITTO for the Content elem
if ($C && $C.length && !$C.data("layoutContainer"))
$C.css( $C.data("layoutCSS") ).removeData("layoutCSS");
}
// REMOVE pane's resizer and toggler elements
if ($T && $T.length) $T.remove();
if ($R && $R.length) $R.remove();
// CLEAR all pointers and data
$Ps[pane] = $Cs[pane] = $Rs[pane] = $Ts[pane] = false;
// skip resize & state-clear when called from destroy()
if (!skipResize) {
resizeAll();
state[pane] = {};
}
};
/*
* ###########################
* ACTION METHODS
* ###########################
*/
/**
* Completely 'hides' a pane, including its spacing - as if it does not exist
* The pane is not actually 'removed' from the source, so can use 'show' to un-hide it
*
* @param {string} pane The pane being hidden, ie: north, south, east, or west
* @param {boolean=} noAnimation
*/
var hide = function (pane, noAnimation) {
var
o = options[pane]
, s = state[pane]
, $P = $Ps[pane]
, $R = $Rs[pane]
;
if (!$P || s.isHidden) return; // pane does not exist OR is already hidden
// onhide_start callback - will CANCEL hide if returns false
if (state.initialized && false === _execCallback(pane, o.onhide_start)) return;
s.isSliding = false; // just in case
// now hide the elements
if ($R) $R.hide(); // hide resizer-bar
if (!state.initialized || s.isClosed) {
s.isClosed = true; // to trigger open-animation on show()
s.isHidden = true;
s.isVisible = false;
$P.hide(); // no animation when loading page
sizeMidPanes(_c[pane].dir == "horz" ? "all" : "center");
if (state.initialized || o.triggerEventsOnLoad)
_execCallback(pane, o.onhide_end || o.onhide);
}
else {
s.isHiding = true; // used by onclose
close(pane, false, noAnimation); // adjust all panes to fit
}
};
/**
* Show a hidden pane - show as 'closed' by default unless openPane = true
*
* @param {string} pane The pane being opened, ie: north, south, east, or west
* @param {boolean=} openPane
* @param {boolean=} noAnimation
* @param {boolean=} noAlert
*/
var show = function (pane, openPane, noAnimation, noAlert) {
var
o = options[pane]
, s = state[pane]
, $P = $Ps[pane]
, $R = $Rs[pane]
;
if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden
// onshow_start callback - will CANCEL show if returns false
if (false === _execCallback(pane, o.onshow_start)) return;
s.isSliding = false; // just in case
s.isShowing = true; // used by onopen/onclose
//s.isHidden = false; - will be set by open/close - if not cancelled
// now show the elements
//if ($R) $R.show(); - will be shown by open/close
if (openPane === false)
close(pane, true); // true = force
else
open(pane, false, noAnimation, noAlert); // adjust all panes to fit
};
/**
* Toggles a pane open/closed by calling either open or close
*
* @param {string} pane The pane being toggled, ie: north, south, east, or west
* @param {boolean=} slide
*/
var toggle = function (pane, slide) {
if (!isStr(pane)) {
pane.stopImmediatePropagation(); // pane = event
pane = $(this).data("layoutEdge"); // bound to $R.dblclick
}
var s = state[str(pane)];
if (s.isHidden)
show(pane); // will call 'open' after unhiding it
else if (s.isClosed)
open(pane, !!slide);
else
close(pane);
};
/**
* Utility method used during init or other auto-processes
*
* @param {string} pane The pane being closed
* @param {boolean=} setHandles
*/
var _closePane = function (pane, setHandles) {
var
$P = $Ps[pane]
, s = state[pane]
;
$P.hide();
s.isClosed = true;
s.isVisible = false;
// UNUSED: if (setHandles) setAsClosed(pane, true); // true = force
};
/**
* Close the specified pane (animation optional), and resize all other panes as needed
*
* @param {string} pane The pane being closed, ie: north, south, east, or west
* @param {boolean=} force
* @param {boolean=} noAnimation
* @param {boolean=} skipCallback
*/
var close = function (pane, force, noAnimation, skipCallback) {
if (!state.initialized) {
_closePane(pane)
return;
}
var
$P = $Ps[pane]
, $R = $Rs[pane]
, $T = $Ts[pane]
, o = options[pane]
, s = state[pane]
, doFX = !noAnimation && !s.isClosed && (o.fxName_close != "none")
// transfer logic vars to temp vars
, isShowing = s.isShowing
, isHiding = s.isHiding
, wasSliding = s.isSliding
;
// now clear the logic vars
delete s.isShowing;
delete s.isHiding;
if (!$P || (!o.closable && !isShowing && !isHiding)) return; // invalid request // (!o.resizable && !o.closable) ???
else if (!force && s.isClosed && !isShowing) return; // already closed
if (_c.isLayoutBusy) { // layout is 'busy' - probably with an animation
_queue("close", pane, force); // set a callback for this action, if possible
return; // ABORT
}
// onclose_start callback - will CANCEL hide if returns false
// SKIP if just 'showing' a hidden pane as 'closed'
if (!isShowing && false === _execCallback(pane, o.onclose_start)) return;
// SET flow-control flags
_c[pane].isMoving = true;
_c.isLayoutBusy = true;
s.isClosed = true;
s.isVisible = false;
// update isHidden BEFORE sizing panes
if (isHiding) s.isHidden = true;
else if (isShowing) s.isHidden = false;
if (s.isSliding) // pane is being closed, so UNBIND trigger events
bindStopSlidingEvents(pane, false); // will set isSliding=false
else // resize panes adjacent to this one
sizeMidPanes(_c[pane].dir == "horz" ? "all" : "center", false); // false = NOT skipCallback
// if this pane has a resizer bar, move it NOW - before animation
setAsClosed(pane);
// CLOSE THE PANE
if (doFX) { // animate the close
lockPaneForFX(pane, true); // need to set left/top so animation will work
$P.hide( o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function () {
lockPaneForFX(pane, false); // undo
close_2();
});
}
else { // hide the pane without animation
$P.hide();
close_2();
};
// SUBROUTINE
function close_2 () {
if (s.isClosed) { // make sure pane was not 'reopened' before animation finished!
bindStartSlidingEvent(pane, true); // will enable if o.slidable = true
// if opposite-pane was autoClosed, see if it can be autoOpened now
var altPane = _c.altSide[pane];
if (state[ altPane ].noRoom) {
setSizeLimits( altPane );
makePaneFit( altPane );
}
if (!skipCallback && (state.initialized || o.triggerEventsOnLoad)) {
// onclose callback - UNLESS just 'showing' a hidden pane as 'closed'
if (!isShowing) _execCallback(pane, o.onclose_end || o.onclose);
// onhide OR onshow callback
if (isShowing) _execCallback(pane, o.onshow_end || o.onshow);
if (isHiding) _execCallback(pane, o.onhide_end || o.onhide);
}
}
// execute internal flow-control callback
_dequeue(pane);
}
};
/**
* @param {string} pane The pane just closed, ie: north, south, east, or west
*/
var setAsClosed = function (pane) {
var
$P = $Ps[pane]
, $R = $Rs[pane]
, $T = $Ts[pane]
, o = options[pane]
, s = state[pane]
, side = _c[pane].side.toLowerCase()
, inset = "inset"+ _c[pane].side
, rClass = o.resizerClass
, tClass = o.togglerClass
, _pane = "-"+ pane // used for classNames
, _open = "-open"
, _sliding= "-sliding"
, _closed = "-closed"
;
$R
.css(side, sC[inset]) // move the resizer
.removeClass( rClass+_open +" "+ rClass+_pane+_open )
.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
.addClass( rClass+_closed +" "+ rClass+_pane+_closed )
.unbind("dblclick."+ sID)
;
// DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvent?
if (o.resizable && typeof $.fn.draggable == "function")
$R
.draggable("disable")
.removeClass("ui-state-disabled") // do NOT apply disabled styling - not suitable here
.css("cursor", "default")
.attr("title","")
;
// if pane has a toggler button, adjust that too
if ($T) {
$T
.removeClass( tClass+_open +" "+ tClass+_pane+_open )
.addClass( tClass+_closed +" "+ tClass+_pane+_closed )
.attr("title", o.togglerTip_closed) // may be blank
;
// toggler-content - if exists
$T.children(".content-open").hide();
$T.children(".content-closed").css("display","block");
}
// sync any 'pin buttons'
syncPinBtns(pane, false);
if (state.initialized) {
// resize 'length' and position togglers for adjacent panes
sizeHandles("all");
}
};
/**
* Open the specified pane (animation optional), and resize all other panes as needed
*
* @param {string} pane The pane being opened, ie: north, south, east, or west
* @param {boolean=} slide
* @param {boolean=} noAnimation
* @param {boolean=} noAlert
*/
var open = function (pane, slide, noAnimation, noAlert) {
var
$P = $Ps[pane]
, $R = $Rs[pane]
, $T = $Ts[pane]
, o = options[pane]
, s = state[pane]
, doFX = !noAnimation && s.isClosed && (o.fxName_open != "none")
// transfer logic var to temp var
, isShowing = s.isShowing
;
// now clear the logic var
delete s.isShowing;
if (!$P || (!o.resizable && !o.closable && !isShowing)) return; // invalid request
else if (s.isVisible && !s.isSliding) return; // already open
// pane can ALSO be unhidden by just calling show(), so handle this scenario
if (s.isHidden && !isShowing) {
show(pane, true);
return;
}
if (_c.isLayoutBusy) { // layout is 'busy' - probably with an animation
_queue("open", pane, slide); // set a callback for this action, if possible
return; // ABORT
}
setSizeLimits(pane, slide); // update pane-state
// onopen_start callback - will CANCEL hide if returns false
if (false === _execCallback(pane, o.onopen_start)) return;
// make sure there is enough space available to open the pane
if (s.minSize > s.maxSize) { // INSUFFICIENT ROOM FOR PANE TO OPEN!
syncPinBtns(pane, false); // make sure pin-buttons are reset
if (!noAlert && o.noRoomToOpenTip) alert(o.noRoomToOpenTip);
return; // ABORT
}
// SET flow-control flags
_c[pane].isMoving = true;
_c.isLayoutBusy = true;
if (slide) // START Sliding - will set isSliding=true
bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane
else if (s.isSliding) // PIN PANE (stop sliding) - open pane 'normally' instead
bindStopSlidingEvents(pane, false); // UNBIND trigger events - will set isSliding=false
else if (o.slidable)
bindStartSlidingEvent(pane, false); // UNBIND trigger events
s.noRoom = false; // will be reset by makePaneFit if 'noRoom'
makePaneFit(pane);
s.isVisible = true;
s.isClosed = false;
// update isHidden BEFORE sizing panes - WHY??? Old?
if (isShowing) s.isHidden = false;
if (doFX) { // ANIMATE
lockPaneForFX(pane, true); // need to set left/top so animation will work
$P.show( o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() {
lockPaneForFX(pane, false); // undo
open_2(); // continue
});
}
else {// no animation
$P.show(); // just show pane and...
open_2(); // continue
};
// SUBROUTINE
function open_2 () {
if (s.isVisible) { // make sure pane was not closed or hidden before animation finished!
// cure iframe display issues
_fixIframe(pane);
// NOTE: if isSliding, then other panes are NOT 'resized'
if (!s.isSliding) // resize all panes adjacent to this one
sizeMidPanes(_c[pane].dir=="vert" ? "center" : "all", false); // false = NOT skipCallback
// set classes, position handles and execute callbacks...
setAsOpen(pane);
}
// internal flow-control callback
_dequeue(pane);
};
};
/**
* @param {string} pane The pane just opened, ie: north, south, east, or west
* @param {boolean=} skipCallback
*/
var setAsOpen = function (pane, skipCallback) {
var
$P = $Ps[pane]
, $R = $Rs[pane]
, $T = $Ts[pane]
, o = options[pane]
, s = state[pane]
, side = _c[pane].side.toLowerCase()
, inset = "inset"+ _c[pane].side
, rClass = o.resizerClass
, tClass = o.togglerClass
, _pane = "-"+ pane // used for classNames
, _open = "-open"
, _closed = "-closed"
, _sliding= "-sliding"
;
$R
.css(side, sC[inset] + getPaneSize(pane)) // move the resizer
.removeClass( rClass+_closed +" "+ rClass+_pane+_closed )
.addClass( rClass+_open +" "+ rClass+_pane+_open )
;
if (s.isSliding)
$R.addClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
else // in case 'was sliding'
$R.removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding )
if (o.resizerDblClickToggle)
$R.bind("dblclick", toggle );
removeHover( 0, $R ); // remove hover classes
if (o.resizable && typeof $.fn.draggable == "function")
$R
.draggable("enable")
.css("cursor", o.resizerCursor)
.attr("title", o.resizerTip)
;
else if (!s.isSliding)
$R.css("cursor", "default"); // n-resize, s-resize, etc
// if pane also has a toggler button, adjust that too
if ($T) {
$T
.removeClass( tClass+_closed +" "+ tClass+_pane+_closed )
.addClass( tClass+_open +" "+ tClass+_pane+_open )
.attr("title", o.togglerTip_open) // may be blank
;
removeHover( 0, $T ); // remove hover classes
// toggler-content - if exists
$T.children(".content-closed").hide();
$T.children(".content-open").css("display","block");
}
// sync any 'pin buttons'
syncPinBtns(pane, !s.isSliding);
// update pane-state dimensions - BEFORE resizing content
$.extend(s, getElemDims($P));
if (state.initialized) {
// resize resizer & toggler sizes for all panes
sizeHandles("all");
// resize content every time pane opens - to be sure
sizeContent(pane, true); // true = remeasure headers/footers, even if 'isLayoutBusy'
}
if (!skipCallback && (state.initialized || o.triggerEventsOnLoad) && $P.is(":visible")) {
// onopen callback
_execCallback(pane, o.onopen_end || o.onopen);
// onshow callback - TODO: should this be here?
if (s.isShowing) _execCallback(pane, o.onshow_end || o.onshow);
// ALSO call onresize because layout-size *may* have changed while pane was closed
if (state.initialized) {
_execCallback(pane, o.onresize_end || o.onresize);
resizeNestedLayout(pane);
}
}
};
/**
* slideOpen / slideClose / slideToggle
*
* Pass-though methods for sliding
*/
var slideOpen = function (evt_or_pane) {
var
evt = isStr(evt_or_pane) ? null : evt_or_pane
, pane = evt ? $(this).data("layoutEdge") : evt_or_pane
, s = state[pane]
, delay = options[pane].slideDelay_open
;
// prevent event from triggering on NEW resizer binding created below
if (evt) evt.stopImmediatePropagation();
if (s.isClosed && evt && evt.type == "mouseenter" && delay > 0)
// trigger = mouseenter - use a delay
timer.set(pane+"_openSlider", open_NOW, delay);
else
open_NOW(); // will unbind events if is already open
/**
* SUBROUTINE for timed open
*/
function open_NOW (evt) {
if (!s.isClosed) // skip if no longer closed!
bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane
else if (!_c[pane].isMoving)
open(pane, true); // true = slide - open() will handle binding
};
};
var slideClose = function (evt_or_pane) {
var
evt = isStr(evt_or_pane) ? null : evt_or_pane
, pane = evt ? $(this).data("layoutEdge") : evt_or_pane
, o = options[pane]
, s = state[pane]
, delay = _c[pane].isMoving ? 1000 : 300 // MINIMUM delay - option may override
;
if (s.isClosed || s.isResizing)
return; // skip if already closed OR in process of resizing
else if (o.slideTrigger_close == "click")
close_NOW(); // close immediately onClick
else if (o.preventQuickSlideClose && _c.isLayoutBusy)
return; // handle Chrome quick-close on slide-open
else if (o.preventPrematureSlideClose && evt && $.layout.isMouseOverElem(evt, $Ps[pane]))
return; // handle incorrect mouseleave trigger, like when over a SELECT-list in IE
else if (evt) // trigger = mouseleave - use a delay
// 1 sec delay if 'opening', else .3 sec
timer.set(pane+"_closeSlider", close_NOW, max(o.slideDelay_close, delay));
else // called programically
close_NOW();
/**
* SUBROUTINE for timed close
*/
function close_NOW () {
if (s.isClosed) // skip 'close' if already closed!
bindStopSlidingEvents(pane, false); // UNBIND trigger events - TODO: is this needed here?
else if (!_c[pane].isMoving)
close(pane); // close will handle unbinding
};
};
var slideToggle = function (pane) { toggle(pane, true); };
/**
* Must set left/top on East/South panes so animation will work properly
*
* @param {string} pane The pane to lock, 'east' or 'south' - any other is ignored!
* @param {boolean} doLock true = set left/top, false = remove
*/
var lockPaneForFX = function (pane, doLock) {
var $P = $Ps[pane];
if (doLock) {
$P.css({ zIndex: _c.zIndex.pane_animate }); // overlay all elements during animation
if (pane=="south")
$P.css({ top: sC.insetTop + sC.innerHeight - $P.outerHeight() });
else if (pane=="east")
$P.css({ left: sC.insetLeft + sC.innerWidth - $P.outerWidth() });
}
else { // animation DONE - RESET CSS
// TODO: see if this can be deleted. It causes a quick-close when sliding in Chrome
$P.css({ zIndex: (state[pane].isSliding ? _c.zIndex.pane_sliding : _c.zIndex.pane_normal) });
if (pane=="south")
$P.css({ top: "auto" });
else if (pane=="east")
$P.css({ left: "auto" });
// fix anti-aliasing in IE - only needed for animations that change opacity
var o = options[pane];
if (state.browser.msie && o.fxOpacityFix && o.fxName_open != "slide" && $P.css("filter") && $P.css("opacity") == 1)
$P[0].style.removeAttribute('filter');
}
};
/**
* Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger
*
* @see open(), close()
* @param {string} pane The pane to enable/disable, 'north', 'south', etc.
* @param {boolean} enable Enable or Disable sliding?
*/
var bindStartSlidingEvent = function (pane, enable) {
var
o = options[pane]
, $P = $Ps[pane]
, $R = $Rs[pane]
, trigger = o.slideTrigger_open.toLowerCase()
;
if (!$R || (enable && !o.slidable)) return;
// make sure we have a valid event
if (trigger.match(/mouseover/))
trigger = o.slideTrigger_open = "mouseenter";
else if (!trigger.match(/click|dblclick|mouseenter/))
trigger = o.slideTrigger_open = "click";
$R
// add or remove trigger event
[enable ? "bind" : "unbind"](trigger +'.'+ sID, slideOpen)
// set the appropriate cursor & title/tip
.css("cursor", enable ? o.sliderCursor : "default")
.attr("title", enable ? o.sliderTip : "")
;
};
/**
* Add or remove 'mouseleave' events to 'slide close' when pane is 'sliding' open or closed
* Also increases zIndex when pane is sliding open
* See bindStartSlidingEvent for code to control 'slide open'
*
* @see slideOpen(), slideClose()
* @param {string} pane The pane to process, 'north', 'south', etc.
* @param {boolean} enable Enable or Disable events?
*/
var bindStopSlidingEvents = function (pane, enable) {
var
o = options[pane]
, s = state[pane]
, z = _c.zIndex
, trigger = o.slideTrigger_close.toLowerCase()
, action = (enable ? "bind" : "unbind")
, $P = $Ps[pane]
, $R = $Rs[pane]
;
s.isSliding = enable; // logic
timer.clear(pane+"_closeSlider"); // just in case
// remove 'slideOpen' trigger event from resizer
// ALSO will raise the zIndex of the pane & resizer
if (enable) bindStartSlidingEvent(pane, false);
// RE/SET zIndex - increases when pane is sliding-open, resets to normal when not
$P.css("zIndex", enable ? z.pane_sliding : z.pane_normal);
$R.css("zIndex", enable ? z.pane_sliding : z.resizer_normal);
// make sure we have a valid event
if (!trigger.match(/click|mouseleave/))
trigger = o.slideTrigger_close = "mouseleave"; // also catches 'mouseout'
// add/remove slide triggers
$R[action](trigger, slideClose); // base event on resize
// need extra events for mouseleave
if (trigger == "mouseleave") {
// also close on pane.mouseleave
$P[action]("mouseleave."+ sID, slideClose);
// cancel timer when mouse moves between 'pane' and 'resizer'
$R[action]("mouseenter."+ sID, cancelMouseOut);
$P[action]("mouseenter."+ sID, cancelMouseOut);
}
if (!enable)
timer.clear(pane+"_closeSlider");
else if (trigger == "click" && !o.resizable) {
// IF pane is not resizable (which already has a cursor and tip)
// then set the a cursor & title/tip on resizer when sliding
$R.css("cursor", enable ? o.sliderCursor : "default");
$R.attr("title", enable ? o.togglerTip_open : ""); // use Toggler-tip, eg: "Close Pane"
}
// SUBROUTINE for mouseleave timer clearing
function cancelMouseOut (evt) {
timer.clear(pane+"_closeSlider");
evt.stopPropagation();
}
};
/**
* Hides/closes a pane if there is insufficient room - reverses this when there is room again
* MUST have already called setSizeLimits() before calling this method
*
* @param {string} pane The pane being resized
* @param {boolean=} isOpening Called from onOpen?
* @param {boolean=} skipCallback Should the onresize callback be run?
* @param {boolean=} force
*/
var makePaneFit = function (pane, isOpening, skipCallback, force) {
var
o = options[pane]
, s = state[pane]
, c = _c[pane]
, $P = $Ps[pane]
, $R = $Rs[pane]
, isSidePane = c.dir=="vert"
, hasRoom = false
;
// special handling for center & east/west panes
if (pane == "center" || (isSidePane && s.noVerticalRoom)) {
// see if there is enough room to display the pane
// ERROR: hasRoom = s.minHeight <= s.maxHeight && (isSidePane || s.minWidth <= s.maxWidth);
hasRoom = (s.maxHeight > 0);
if (hasRoom && s.noRoom) { // previously hidden due to noRoom, so show now
$P.show();
if ($R) $R.show();
s.isVisible = true;
s.noRoom = false;
if (isSidePane) s.noVerticalRoom = false;
_fixIframe(pane);
}
else if (!hasRoom && !s.noRoom) { // not currently hidden, so hide now
$P.hide();
if ($R) $R.hide();
s.isVisible = false;
s.noRoom = true;
}
}
// see if there is enough room to fit the border-pane
if (pane == "center") {
// ignore center in this block
}
else if (s.minSize <= s.maxSize) { // pane CAN fit
hasRoom = true;
if (s.size > s.maxSize) // pane is too big - shrink it
sizePane(pane, s.maxSize, skipCallback, force);
else if (s.size < s.minSize) // pane is too small - enlarge it
sizePane(pane, s.minSize, skipCallback, force);
else if ($R && $P.is(":visible")) {
// make sure resizer-bar is positioned correctly
// handles situation where nested layout was 'hidden' when initialized
var
side = c.side.toLowerCase()
, pos = s.size + sC["inset"+ c.side]
;
if (_cssNum($R, side) != pos) $R.css( side, pos );
}
// if was previously hidden due to noRoom, then RESET because NOW there is room
if (s.noRoom) {
// s.noRoom state will be set by open or show
if (s.wasOpen && o.closable) {
if (o.autoReopen)
open(pane, false, true, true); // true = noAnimation, true = noAlert
else // leave the pane closed, so just update state
s.noRoom = false;
}
else
show(pane, s.wasOpen, true, true); // true = noAnimation, true = noAlert
}
}
else { // !hasRoom - pane CANNOT fit
if (!s.noRoom) { // pane not set as noRoom yet, so hide or close it now...
s.noRoom = true; // update state
s.wasOpen = !s.isClosed && !s.isSliding;
if (s.isClosed){} // SKIP
else if (o.closable) // 'close' if possible
close(pane, true, true); // true = force, true = noAnimation
else // 'hide' pane if cannot just be closed
hide(pane, true); // true = noAnimation
}
}
};
/**
* sizePane / manualSizePane
* sizePane is called only by internal methods whenever a pane needs to be resized
* manualSizePane is an exposed flow-through method allowing extra code when pane is 'manually resized'
*
* @param {string} pane The pane being resized
* @param {number} size The *desired* new size for this pane - will be validated
* @param {boolean=} skipCallback Should the onresize callback be run?
*/
var manualSizePane = function (pane, size, skipCallback) {
// ANY call to sizePane will disabled autoResize
var
o = options[pane]
// if resizing callbacks have been delayed and resizing is now DONE, force resizing to complete...
, forceResize = o.resizeWhileDragging && !_c.isLayoutBusy // && !o.triggerEventsWhileDragging
;
o.autoResize = false;
// flow-through...
sizePane(pane, size, skipCallback, forceResize);
}
/**
* @param {string} pane The pane being resized
* @param {number} size The *desired* new size for this pane - will be validated
* @param {boolean=} skipCallback Should the onresize callback be run?
* @param {boolean=} force Force resizing even if does not seem necessary
*/
var sizePane = function (pane, size, skipCallback, force) {
var
o = options[pane]
, s = state[pane]
, $P = $Ps[pane]
, $R = $Rs[pane]
, side = _c[pane].side.toLowerCase()
, inset = "inset"+ _c[pane].side
, skipResizeWhileDragging = _c.isLayoutBusy && !o.triggerEventsWhileDragging
, oldSize
;
// calculate 'current' min/max sizes
setSizeLimits(pane); // update pane-state
oldSize = s.size;
size = _parseSize(pane, size); // handle percentages & auto
size = max(size, _parseSize(pane, o.minSize));
size = min(size, s.maxSize);
if (size < s.minSize) { // not enough room for pane!
makePaneFit(pane, false, skipCallback); // will hide or close pane
return;
}
// IF newSize is same as oldSize, then nothing to do - abort
if (!force && size == oldSize) return;
// onresize_start callback CANNOT cancel resizing because this would break the layout!
if (!skipCallback && state.initialized && s.isVisible)
_execCallback(pane, o.onresize_start);
// resize the pane, and make sure its visible
$P.css( _c[pane].sizeType.toLowerCase(), max(1, cssSize(pane, size)) );
// update pane-state dimensions
s.size = size;
$.extend(s, getElemDims($P));
// reposition the resizer-bar
if ($R && $P.is(":visible")) $R.css( side, size + sC[inset] );
sizeContent(pane);
if (!skipCallback && !skipResizeWhileDragging && state.initialized && s.isVisible) {
_execCallback(pane, o.onresize_end || o.onresize);
resizeNestedLayout(pane);
}
// resize all the adjacent panes, and adjust their toggler buttons
// when skipCallback passed, it means the controlling method will handle 'other panes'
if (!skipCallback) {
// also no callback if live-resize is in progress and NOT triggerEventsWhileDragging
if (!s.isSliding) sizeMidPanes(_c[pane].dir=="horz" ? "all" : "center", skipResizeWhileDragging, force);
sizeHandles("all");
}
// if opposite-pane was autoClosed, see if it can be autoOpened now
var altPane = _c.altSide[pane];
if (size < oldSize && state[ altPane ].noRoom) {
setSizeLimits( altPane );
makePaneFit( altPane, false, skipCallback );
}
};
/**
* @see initPanes(), sizePane(), resizeAll(), open(), close(), hide()
* @param {string} panes The pane(s) being resized, comma-delmited string
* @param {boolean=} skipCallback Should the onresize callback be run?
* @param {boolean=} force
*/
var sizeMidPanes = function (panes, skipCallback, force) {
if (!panes || panes == "all") panes = "east,west,center";
$.each(panes.split(","), function (i, pane) {
if (!$Ps[pane]) return; // NO PANE - skip
var
o = options[pane]
, s = state[pane]
, $P = $Ps[pane]
, $R = $Rs[pane]
, isCenter= (pane=="center")
, hasRoom = true
, CSS = {}
, d = calcNewCenterPaneDims()
;
// update pane-state dimensions
$.extend(s, getElemDims($P));
if (pane == "center") {
if (!force && s.isVisible && d.width == s.outerWidth && d.height == s.outerHeight)
return true; // SKIP - pane already the correct size
// set state for makePaneFit() logic
$.extend(s, cssMinDims(pane), {
maxWidth: d.width
, maxHeight: d.height
});
CSS = d;
// convert OUTER width/height to CSS width/height
CSS.width = cssW(pane, d.width);
CSS.height = cssH(pane, d.height);
hasRoom = CSS.width > 0 && CSS.height > 0;
// during layout init, try to shrink east/west panes to make room for center
if (!hasRoom && !state.initialized && o.minWidth > 0) {
var
reqPx = o.minWidth - s.outerWidth
, minE = options.east.minSize || 0
, minW = options.west.minSize || 0
, sizeE = state.east.size
, sizeW = state.west.size
, newE = sizeE
, newW = sizeW
;
if (reqPx > 0 && state.east.isVisible && sizeE > minE) {
newE = max( sizeE-minE, sizeE-reqPx );
reqPx -= sizeE-newE;
}
if (reqPx > 0 && state.west.isVisible && sizeW > minW) {
newW = max( sizeW-minW, sizeW-reqPx );
reqPx -= sizeW-newW;
}
// IF we found enough extra space, then resize the border panes as calculated
if (reqPx == 0) {
if (sizeE != minE)
sizePane('east', newE, true); // true = skipCallback - initPanes will handle when done
if (sizeW != minW)
sizePane('west', newW, true);
// now start over!
sizeMidPanes('center', skipCallback, force);
return; // abort this loop
}
}
}
else { // for east and west, set only the height, which is same as center height
// set state.min/maxWidth/Height for makePaneFit() logic
if (s.isVisible && !s.noVerticalRoom)
$.extend(s, getElemDims($P), cssMinDims(pane))
if (!force && !s.noVerticalRoom && d.height == s.outerHeight)
return true; // SKIP - pane already the correct size
CSS.top = d.top;
CSS.bottom = d.bottom;
CSS.height = cssH(pane, d.height);
s.maxHeight = max(0, CSS.height);
hasRoom = (s.maxHeight > 0);
if (!hasRoom) s.noVerticalRoom = true; // makePaneFit() logic
}
if (hasRoom) {
// resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized
if (!skipCallback && state.initialized)
_execCallback(pane, o.onresize_start);
$P.css(CSS); // apply the CSS to pane
if (s.noRoom && !s.isClosed && !s.isHidden)
makePaneFit(pane); // will re-open/show auto-closed/hidden pane
if (s.isVisible) {
$.extend(s, getElemDims($P)); // update pane dimensions
if (state.initialized) sizeContent(pane); // also resize the contents, if exists
}
}
else if (!s.noRoom && s.isVisible) // no room for pane
makePaneFit(pane); // will hide or close pane
if (!s.isVisible)
return true; // DONE - next pane
/*
* Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes
* Normally these panes have only 'left' & 'right' positions so pane auto-sizes
* ALSO required when pane is an IFRAME because will NOT default to 'full width'
*/
if (pane == "center") { // finished processing midPanes
var b = state.browser;
var fix = b.isIE6 || (b.msie && !b.boxModel);
if ($Ps.north && (fix || state.north.tagName=="IFRAME"))
$Ps.north.css("width", cssW($Ps.north, sC.innerWidth));
if ($Ps.south && (fix || state.south.tagName=="IFRAME"))
$Ps.south.css("width", cssW($Ps.south, sC.innerWidth));
}
// resizeAll passes skipCallback because it triggers callbacks after ALL panes are resized
if (!skipCallback && state.initialized) {
_execCallback(pane, o.onresize_end || o.onresize);
resizeNestedLayout(pane);
}
});
};
/**
* @see window.onresize(), callbacks or custom code
*/
var resizeAll = function () {
var
oldW = sC.innerWidth
, oldH = sC.innerHeight
;
$.extend( state.container, getElemDims( $Container ) ); // UPDATE container dimensions
if (!sC.outerHeight) return; // cannot size layout when 'container' is hidden or collapsed
// onresizeall_start will CANCEL resizing if returns false
// state.container has already been set, so user can access this info for calcuations
if (false === _execCallback(null, options.onresizeall_start)) return false;
var
// see if container is now 'smaller' than before
shrunkH = (sC.innerHeight < oldH)
, shrunkW = (sC.innerWidth < oldW)
, $P, o, s, dir
;
// NOTE special order for sizing: S-N-E-W
$.each(["south","north","east","west"], function (i, pane) {
if (!$Ps[pane]) return; // no pane - SKIP
s = state[pane];
o = options[pane];
dir = _c[pane].dir;
if (o.autoResize && s.size != o.size) // resize pane to original size set in options
sizePane(pane, o.size, true, true); // true=skipCallback, true=forceResize
else {
setSizeLimits(pane);
makePaneFit(pane, false, true, true); // true=skipCallback, true=forceResize
}
});
sizeMidPanes("all", true, true); // true=skipCallback, true=forceResize
sizeHandles("all"); // reposition the toggler elements
// trigger all individual pane callbacks AFTER layout has finished resizing
o = options; // reuse alias
$.each(_c.allPanes.split(","), function (i, pane) {
$P = $Ps[pane];
if (!$P) return; // SKIP
if (state[pane].isVisible) { // undefined for non-existent panes
_execCallback(pane, o[pane].onresize_end || o[pane].onresize); // callback - if exists
resizeNestedLayout(pane);
}
});
_execCallback(null, o.onresizeall_end || o.onresizeall); // onresizeall callback, if exists
};
/**
* Whenever a pane resizes or opens that has a nested layout, trigger resizeAll
*
* @param {string} pane The pane just resized or opened
*/
var resizeNestedLayout = function (pane) {
var
$P = $Ps[pane]
, $C = $Cs[pane]
, d = "layoutContainer"
;
if (options[pane].resizeNestedLayout) {
if ($P.data( d ))
$P.layout().resizeAll();
else if ($C && $C.data( d ))
$C.layout().resizeAll();
}
};
/**
* IF pane has a content-div, then resize all elements inside pane to fit pane-height
*
* @param {string=} panes The pane(s) being resized
* @param {boolean=} remeasure Should the content (header/footer) be remeasured?
*/
var sizeContent = function (panes, remeasure) {
if (!panes || panes == "all") panes = _c.allPanes;
$.each(panes.split(","), function (idx, pane) {
var
$P = $Ps[pane]
, $C = $Cs[pane]
, o = options[pane]
, s = state[pane]
, m = s.content // m = measurements
;
if (!$P || !$C || !$P.is(":visible")) return true; // NOT VISIBLE - skip
// onsizecontent_start will CANCEL resizing if returns false
if (false === _execCallback(null, o.onsizecontent_start)) return;
// skip re-measuring offsets if live-resizing
if (!_c.isLayoutBusy || m.top == undefined || remeasure || o.resizeContentWhileDragging) {
_measure();
// if any footers are below pane-bottom, they may not measure correctly,
// so allow pane overflow and re-measure
if (m.hiddenFooters > 0 && $P.css("overflow") == "hidden") {
$P.css("overflow", "visible");
_measure(); // remeasure while overflowing
$P.css("overflow", "hidden");
}
}
// NOTE: spaceAbove/Below *includes* the pane's paddingTop/Bottom, but not pane.borders
var newH = s.innerHeight - (m.spaceAbove - s.css.paddingTop) - (m.spaceBelow - s.css.paddingBottom);
if (!$C.is(":visible") || m.height != newH) {
// size the Content element to fit new pane-size - will autoHide if not enough room
setOuterHeight($C, newH, true); // true=autoHide
m.height = newH; // save new height
};
if (state.initialized) {
_execCallback(pane, o.onsizecontent_end || o.onsizecontent);
resizeNestedLayout(pane);
}
function _below ($E) {
return max(s.css.paddingBottom, (parseInt($E.css("marginBottom"), 10) || 0));
};
function _measure () {
var
ignore = options[pane].contentIgnoreSelector
, $Fs = $C.nextAll().not(ignore || ':lt(0)') // not :lt(0) = ALL
, $Fs_vis = $Fs.filter(':visible')
, $F = $Fs_vis.filter(':last')
;
m = {
top: $C[0].offsetTop
, height: $C.outerHeight()
, numFooters: $Fs.length
, hiddenFooters: $Fs.length - $Fs_vis.length
, spaceBelow: 0 // correct if no content footer ($E)
}
m.spaceAbove = m.top; // just for state - not used in calc
m.bottom = m.top + m.height;
if ($F.length)
//spaceBelow = (LastFooter.top + LastFooter.height) [footerBottom] - Content.bottom + max(LastFooter.marginBottom, pane.paddingBotom)
m.spaceBelow = ($F[0].offsetTop + $F.outerHeight()) - m.bottom + _below($F);
else // no footer - check marginBottom on Content element itself
m.spaceBelow = _below($C);
};
});
};
/**
* Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary
*
* @see initHandles(), open(), close(), resizeAll()
* @param {string=} panes The pane(s) being resized
*/
var sizeHandles = function (panes) {
if (!panes || panes == "all") panes = _c.borderPanes;
$.each(panes.split(","), function (i, pane) {
var
o = options[pane]
, s = state[pane]
, $P = $Ps[pane]
, $R = $Rs[pane]
, $T = $Ts[pane]
, $TC
;
if (!$P || !$R) return;
var
dir = _c[pane].dir
, _state = (s.isClosed ? "_closed" : "_open")
, spacing = o["spacing"+ _state]
, togAlign = o["togglerAlign"+ _state]
, togLen = o["togglerLength"+ _state]
, paneLen
, offset
, CSS = {}
;
if (spacing == 0) {
$R.hide();
return;
}
else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason
$R.show(); // in case was previously hidden
// Resizer Bar is ALWAYS same width/height of pane it is attached to
if (dir == "horz") { // north/south
paneLen = $P.outerWidth(); // s.outerWidth ||
s.resizerLength = paneLen;
$R.css({
width: max(1, cssW($R, paneLen)) // account for borders & padding
, height: max(0, cssH($R, spacing)) // ditto
, left: _cssNum($P, "left")
});
}
else { // east/west
paneLen = $P.outerHeight(); // s.outerHeight ||
s.resizerLength = paneLen;
$R.css({
height: max(1, cssH($R, paneLen)) // account for borders & padding
, width: max(0, cssW($R, spacing)) // ditto
, top: sC.insetTop + getPaneSize("north", true) // TODO: what if no North pane?
//, top: _cssNum($Ps["center"], "top")
});
}
// remove hover classes
removeHover( o, $R );
if ($T) {
if (togLen == 0 || (s.isSliding && o.hideTogglerOnSlide)) {
$T.hide(); // always HIDE the toggler when 'sliding'
return;
}
else
$T.show(); // in case was previously hidden
if (!(togLen > 0) || togLen == "100%" || togLen > paneLen) {
togLen = paneLen;
offset = 0;
}
else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed
if (isStr(togAlign)) {
switch (togAlign) {
case "top":
case "left": offset = 0;
break;
case "bottom":
case "right": offset = paneLen - togLen;
break;
case "middle":
case "center":
default: offset = Math.floor((paneLen - togLen) / 2); // 'default' catches typos
}
}
else { // togAlign = number
var x = parseInt(togAlign, 10); //
if (togAlign >= 0) offset = x;
else offset = paneLen - togLen + x; // NOTE: x is negative!
}
}
if (dir == "horz") { // north/south
var width = cssW($T, togLen);
$T.css({
width: max(0, width) // account for borders & padding
, height: max(1, cssH($T, spacing)) // ditto
, left: offset // TODO: VERIFY that toggler positions correctly for ALL values
, top: 0
});
// CENTER the toggler content SPAN
$T.children(".content").each(function(){
$TC = $(this);
$TC.css("marginLeft", Math.floor((width-$TC.outerWidth())/2)); // could be negative
});
}
else { // east/west
var height = cssH($T, togLen);
$T.css({
height: max(0, height) // account for borders & padding
, width: max(1, cssW($T, spacing)) // ditto
, top: offset // POSITION the toggler
, left: 0
});
// CENTER the toggler content SPAN
$T.children(".content").each(function(){
$TC = $(this);
$TC.css("marginTop", Math.floor((height-$TC.outerHeight())/2)); // could be negative
});
}
// remove ALL hover classes
removeHover( 0, $T );
}
// DONE measuring and sizing this resizer/toggler, so can be 'hidden' now
if (!state.initialized && (o.initHidden || s.noRoom)) {
$R.hide();
if ($T) $T.hide();
}
});
};
var enableClosable = function (pane) {
var $T = $Ts[pane], o = options[pane];
if (!$T) return;
o.closable = true;
$T .bind("click."+ sID, function(evt){ evt.stopPropagation(); toggle(pane); })
.bind("mouseenter."+ sID, addHover)
.bind("mouseleave."+ sID, removeHover)
.css("visibility", "visible")
.css("cursor", "pointer")
.attr("title", state[pane].isClosed ? o.togglerTip_closed : o.togglerTip_open) // may be blank
.show()
;
};
var disableClosable = function (pane, hide) {
var $T = $Ts[pane];
if (!$T) return;
options[pane].closable = false;
// is closable is disable, then pane MUST be open!
if (state[pane].isClosed) open(pane, false, true);
$T .unbind("."+ sID)
.css("visibility", hide ? "hidden" : "visible") // instead of hide(), which creates logic issues
.css("cursor", "default")
.attr("title", "")
;
};
var enableSlidable = function (pane) {
var $R = $Rs[pane], o = options[pane];
if (!$R || !$R.data('draggable')) return;
options[pane].slidable = true;
if (s.isClosed)
bindStartSlidingEvent(pane, true);
};
var disableSlidable = function (pane) {
var $R = $Rs[pane];
if (!$R) return;
options[pane].slidable = false;
if (state[pane].isSliding)
close(pane, false, true);
else {
bindStartSlidingEvent(pane, false);
$R .css("cursor", "default")
.attr("title", "")
;
removeHover(null, $R[0]); // in case currently hovered
}
};
var enableResizable = function (pane) {
var $R = $Rs[pane], o = options[pane];
if (!$R || !$R.data('draggable')) return;
o.resizable = true;
$R .draggable("enable")
.bind("mouseenter."+ sID, onResizerEnter)
.bind("mouseleave."+ sID, onResizerLeave)
;
if (!state[pane].isClosed)
$R .css("cursor", o.resizerCursor)
.attr("title", o.resizerTip)
;
};
var disableResizable = function (pane) {
var $R = $Rs[pane];
if (!$R || !$R.data('draggable')) return;
options[pane].resizable = false;
$R .draggable("disable")
.unbind("."+ sID)
.css("cursor", "default")
.attr("title", "")
;
removeHover(null, $R[0]); // in case currently hovered
};
/**
* Move a pane from source-side (eg, west) to target-side (eg, east)
* If pane exists on target-side, move that to source-side, ie, 'swap' the panes
*
* @param {string} pane1 The pane/edge being swapped
* @param {string} pane2 ditto
*/
var swapPanes = function (pane1, pane2) {
// change state.edge NOW so callbacks can know where pane is headed...
state[pane1].edge = pane2;
state[pane2].edge = pane1;
// run these even if NOT state.initialized
var cancelled = false;
if (false === _execCallback(pane1, options[pane1].onswap_start)) cancelled = true;
if (!cancelled && false === _execCallback(pane2, options[pane2].onswap_start)) cancelled = true;
if (cancelled) {
state[pane1].edge = pane1; // reset
state[pane2].edge = pane2;
return;
}
var
oPane1 = copy( pane1 )
, oPane2 = copy( pane2 )
, sizes = {}
;
sizes[pane1] = oPane1 ? oPane1.state.size : 0;
sizes[pane2] = oPane2 ? oPane2.state.size : 0;
// clear pointers & state
$Ps[pane1] = false;
$Ps[pane2] = false;
state[pane1] = {};
state[pane2] = {};
// ALWAYS remove the resizer & toggler elements
if ($Ts[pane1]) $Ts[pane1].remove();
if ($Ts[pane2]) $Ts[pane2].remove();
if ($Rs[pane1]) $Rs[pane1].remove();
if ($Rs[pane2]) $Rs[pane2].remove();
$Rs[pane1] = $Rs[pane2] = $Ts[pane1] = $Ts[pane2] = false;
// transfer element pointers and data to NEW Layout keys
move( oPane1, pane2 );
move( oPane2, pane1 );
// cleanup objects
oPane1 = oPane2 = sizes = null;
// make panes 'visible' again
if ($Ps[pane1]) $Ps[pane1].css(_c.visible);
if ($Ps[pane2]) $Ps[pane2].css(_c.visible);
// fix any size discrepancies caused by swap
resizeAll();
// run these even if NOT state.initialized
_execCallback(pane1, options[pane1].onswap_end || options[pane1].onswap);
_execCallback(pane2, options[pane2].onswap_end || options[pane2].onswap);
return;
function copy (n) { // n = pane
var
$P = $Ps[n]
, $C = $Cs[n]
;
return !$P ? false : {
pane: n
, P: $P ? $P[0] : false
, C: $C ? $C[0] : false
, state: $.extend({}, state[n])
, options: $.extend({}, options[n])
}
};
function move (oPane, pane) {
if (!oPane) return;
var
P = oPane.P
, C = oPane.C
, oldPane = oPane.pane
, c = _c[pane]
, side = c.side.toLowerCase()
, inset = "inset"+ c.side
// save pane-options that should be retained
, s = $.extend({}, state[pane])
, o = options[pane]
// RETAIN side-specific FX Settings - more below
, fx = { resizerCursor: o.resizerCursor }
, re, size, pos
;
$.each("fxName,fxSpeed,fxSettings".split(","), function (i, k) {
fx[k] = o[k];
fx[k +"_open"] = o[k +"_open"];
fx[k +"_close"] = o[k +"_close"];
});
// update object pointers and attributes
$Ps[pane] = $(P)
.data("layoutEdge", pane)
.css(_c.hidden)
.css(c.cssReq)
;
$Cs[pane] = C ? $(C) : false;
// set options and state
options[pane] = $.extend({}, oPane.options, fx);
state[pane] = $.extend({}, oPane.state);
// change classNames on the pane, eg: ui-layout-pane-east ==> ui-layout-pane-west
re = new RegExp(o.paneClass +"-"+ oldPane, "g");
P.className = P.className.replace(re, o.paneClass +"-"+ pane);
// ALWAYS regenerate the resizer & toggler elements
initHandles(pane); // create the required resizer & toggler
// if moving to different orientation, then keep 'target' pane size
if (c.dir != _c[oldPane].dir) {
size = sizes[pane] || 0;
setSizeLimits(pane); // update pane-state
size = max(size, state[pane].minSize);
// use manualSizePane to disable autoResize - not useful after panes are swapped
manualSizePane(pane, size, true); // true = skipCallback
}
else // move the resizer here
$Rs[pane].css(side, sC[inset] + (state[pane].isVisible ? getPaneSize(pane) : 0));
// ADD CLASSNAMES & SLIDE-BINDINGS
if (oPane.state.isVisible && !s.isVisible)
setAsOpen(pane, true); // true = skipCallback
else {
setAsClosed(pane);
bindStartSlidingEvent(pane, true); // will enable events IF option is set
}
// DESTROY the object
oPane = null;
};
};
/**
* Capture keys when enableCursorHotkey - toggle pane if hotkey pressed
*
* @see document.keydown()
*/
function keyDown (evt) {
if (!evt) return true;
var code = evt.keyCode;
if (code < 33) return true; // ignore special keys: ENTER, TAB, etc
var
PANE = {
38: "north" // Up Cursor - $.ui.keyCode.UP
, 40: "south" // Down Cursor - $.ui.keyCode.DOWN
, 37: "west" // Left Cursor - $.ui.keyCode.LEFT
, 39: "east" // Right Cursor - $.ui.keyCode.RIGHT
}
, ALT = evt.altKey // no worky!
, SHIFT = evt.shiftKey
, CTRL = evt.ctrlKey
, CURSOR = (CTRL && code >= 37 && code <= 40)
, o, k, m, pane
;
if (CURSOR && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey
pane = PANE[code];
else if (CTRL || SHIFT) // check to see if this matches a custom-hotkey
$.each(_c.borderPanes.split(","), function (i, p) { // loop each pane to check its hotkey
o = options[p];
k = o.customHotkey;
m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT"
if ((SHIFT && m=="SHIFT") || (CTRL && m=="CTRL") || (CTRL && SHIFT)) { // Modifier matches
if (k && code == (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches
pane = p;
return false; // BREAK
}
}
});
// validate pane
if (!pane || !$Ps[pane] || !options[pane].closable || state[pane].isHidden)
return true;
toggle(pane);
evt.stopPropagation();
evt.returnValue = false; // CANCEL key
return false;
};
/*
* ######################################
* UTILITY METHODS
* called externally or by initButtons
* ######################################
*/
/**
* Change/reset a pane's overflow setting & zIndex to allow popups/drop-downs to work
*
* @param {Object=} el (optional) Can also be 'bound' to a click, mouseOver, or other event
*/
function allowOverflow (el) {
if (this && this.tagName) el = this; // BOUND to element
var $P;
if (isStr(el))
$P = $Ps[el];
else if ($(el).data("layoutRole"))
$P = $(el);
else
$(el).parents().each(function(){
if ($(this).data("layoutRole")) {
$P = $(this);
return false; // BREAK
}
});
if (!$P || !$P.length) return; // INVALID
var
pane = $P.data("layoutEdge")
, s = state[pane]
;
// if pane is already raised, then reset it before doing it again!
// this would happen if allowOverflow is attached to BOTH the pane and an element
if (s.cssSaved)
resetOverflow(pane); // reset previous CSS before continuing
// if pane is raised by sliding or resizing, or it's closed, then abort
if (s.isSliding || s.isResizing || s.isClosed) {
s.cssSaved = false;
return;
}
var
newCSS = { zIndex: (_c.zIndex.pane_normal + 2) }
, curCSS = {}
, of = $P.css("overflow")
, ofX = $P.css("overflowX")
, ofY = $P.css("overflowY")
;
// determine which, if any, overflow settings need to be changed
if (of != "visible") {
curCSS.overflow = of;
newCSS.overflow = "visible";
}
if (ofX && !ofX.match(/visible|auto/)) {
curCSS.overflowX = ofX;
newCSS.overflowX = "visible";
}
if (ofY && !ofY.match(/visible|auto/)) {
curCSS.overflowY = ofX;
newCSS.overflowY = "visible";
}
// save the current overflow settings - even if blank!
s.cssSaved = curCSS;
// apply new CSS to raise zIndex and, if necessary, make overflow 'visible'
$P.css( newCSS );
// make sure the zIndex of all other panes is normal
$.each(_c.allPanes.split(","), function(i, p) {
if (p != pane) resetOverflow(p);
});
};
function resetOverflow (el) {
if (this && this.tagName) el = this; // BOUND to element
var $P;
if (isStr(el))
$P = $Ps[el];
else if ($(el).data("layoutRole"))
$P = $(el);
else
$(el).parents().each(function(){
if ($(this).data("layoutRole")) {
$P = $(this);
return false; // BREAK
}
});
if (!$P || !$P.length) return; // INVALID
var
pane = $P.data("layoutEdge")
, s = state[pane]
, CSS = s.cssSaved || {}
;
// reset the zIndex
if (!s.isSliding && !s.isResizing)
$P.css("zIndex", _c.zIndex.pane_normal);
// reset Overflow - if necessary
$P.css( CSS );
// clear var
s.cssSaved = false;
};
/**
* Helper function to validate params received by addButton utilities
*
* Two classes are added to the element, based on the buttonClass...
* The type of button is appended to create the 2nd className:
* - ui-layout-button-pin
* - ui-layout-pane-button-toggle
* - ui-layout-pane-button-open
* - ui-layout-pane-button-close
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
* @return {Array.<Object>} If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise returns null
*/
function getBtn (selector, pane, action) {
var $E = $(selector);
if (!$E.length) // element not found
alert(lang.errButton + lang.selector +": "+ selector);
else if (_c.borderPanes.indexOf(pane) == -1) // invalid 'pane' sepecified
alert(lang.errButton + lang.Pane.toLowerCase() +": "+ pane);
else { // VALID
var btn = options[pane].buttonClass +"-"+ action;
$E
.addClass( btn +" "+ btn +"-"+ pane )
.data("layoutName", options.name) // add layout identifier - even if blank!
;
return $E;
}
return null; // INVALID
};
/**
* NEW syntax for binding layout-buttons - will eventually replace addToggleBtn, addOpenBtn, etc.
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} action
* @param {string} pane
*/
function bindButton (selector, action, pane) {
switch (action.toLowerCase()) {
case "toggle": addToggleBtn(selector, pane); break;
case "open": addOpenBtn(selector, pane); break;
case "close": addCloseBtn(selector, pane); break;
case "pin": addPinBtn(selector, pane); break;
case "toggle-slide": addToggleBtn(selector, pane, true); break;
case "open-slide": addOpenBtn(selector, pane, true); break;
}
};
/**
* Add a custom Toggler button for a pane
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
* @param {boolean=} slide true = slide-open, false = pin-open
*/
function addToggleBtn (selector, pane, slide) {
var $E = getBtn(selector, pane, "toggle");
if ($E)
$E.click(function (evt) {
toggle(pane, !!slide);
evt.stopPropagation();
});
};
/**
* Add a custom Open button for a pane
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
* @param {boolean=} slide true = slide-open, false = pin-open
*/
function addOpenBtn (selector, pane, slide) {
var $E = getBtn(selector, pane, "open");
if ($E)
$E
.attr("title", lang.Open)
.click(function (evt) {
open(pane, !!slide);
evt.stopPropagation();
})
;
};
/**
* Add a custom Close button for a pane
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} pane Name of the pane the button is for: 'north', 'south', etc.
*/
function addCloseBtn (selector, pane) {
var $E = getBtn(selector, pane, "close");
if ($E)
$E
.attr("title", lang.Close)
.click(function (evt) {
close(pane);
evt.stopPropagation();
})
;
};
/**
* addPinBtn
*
* Add a custom Pin button for a pane
*
* Four classes are added to the element, based on the paneClass for the associated pane...
* Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin:
* - ui-layout-pane-pin
* - ui-layout-pane-west-pin
* - ui-layout-pane-pin-up
* - ui-layout-pane-west-pin-up
*
* @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button"
* @param {string} pane Name of the pane the pin is for: 'north', 'south', etc.
*/
function addPinBtn (selector, pane) {
var $E = getBtn(selector, pane, "pin");
if ($E) {
var s = state[pane];
$E.click(function (evt) {
setPinState($(this), pane, (s.isSliding || s.isClosed));
if (s.isSliding || s.isClosed) open( pane ); // change from sliding to open
else close( pane ); // slide-closed
evt.stopPropagation();
});
// add up/down pin attributes and classes
setPinState($E, pane, (!s.isClosed && !s.isSliding));
// add this pin to the pane data so we can 'sync it' automatically
// PANE.pins key is an array so we can store multiple pins for each pane
_c[pane].pins.push( selector ); // just save the selector string
}
};
/**
* INTERNAL function to sync 'pin buttons' when pane is opened or closed
* Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes
*
* @see open(), close()
* @param {string} pane These are the params returned to callbacks by layout()
* @param {boolean} doPin True means set the pin 'down', False means 'up'
*/
function syncPinBtns (pane, doPin) {
$.each(_c[pane].pins, function (i, selector) {
setPinState($(selector), pane, doPin);
});
};
/**
* Change the class of the pin button to make it look 'up' or 'down'
*
* @see addPinBtn(), syncPinBtns()
* @param {Array.<Object>} $Pin The pin-span element in a jQuery wrapper
* @param {string} pane These are the params returned to callbacks by layout()
* @param {boolean} doPin true = set the pin 'down', false = set it 'up'
*/
function setPinState ($Pin, pane, doPin) {
var updown = $Pin.attr("pin");
if (updown && doPin == (updown=="down")) return; // already in correct state
var
pin = options[pane].buttonClass +"-pin"
, side = pin +"-"+ pane
, UP = pin +"-up "+ side +"-up"
, DN = pin +"-down "+side +"-down"
;
$Pin
.attr("pin", doPin ? "down" : "up") // logic
.attr("title", doPin ? lang.Unpin : lang.Pin)
.removeClass( doPin ? UP : DN )
.addClass( doPin ? DN : UP )
;
};
/*
* LAYOUT STATE MANAGEMENT
*
* @example .layout({ cookie: { name: "myLayout", keys: "west.isClosed,east.isClosed" } })
* @example .layout({ cookie__name: "myLayout", cookie__keys: "west.isClosed,east.isClosed" })
* @example myLayout.getState( "west.isClosed,north.size,south.isHidden" );
* @example myLayout.saveCookie( "west.isClosed,north.size,south.isHidden", {expires: 7} );
* @example myLayout.deleteCookie();
* @example myLayout.loadCookie();
* @example var hSaved = myLayout.state.cookie;
*/
function isCookiesEnabled () {
// TODO: is the cookieEnabled property common enough to be useful???
return (navigator.cookieEnabled != 0);
};
/**
* Read & return data from the cookie - as JSON
*
* @param {Object=} opts
*/
function getCookie (opts) {
var
o = $.extend( {}, options.cookie, opts || {} )
, name = o.name || options.name || "Layout"
, c = document.cookie
, cs = c ? c.split(';') : []
, pair // loop var
;
for (var i=0, n=cs.length; i < n; i++) {
pair = $.trim(cs[i]).split('='); // name=value pair
if (pair[0] == name) // found the layout cookie
// convert cookie string back to a hash
return decodeJSON( decodeURIComponent(pair[1]) );
}
return "";
};
/**
* Get the current layout state and save it to a cookie
*
* @param {(string|Array)=} keys
* @param {Object=} opts
*/
function saveCookie (keys, opts) {
var
o = $.extend( {}, options.cookie, opts || {} )
, name = o.name || options.name || "Layout"
, params = ''
, date = ''
, clear = false
;
if (o.expires.toUTCString)
date = o.expires;
else if (typeof o.expires == 'number') {
date = new Date();
if (o.expires > 0)
date.setDate(date.getDate() + o.expires);
else {
date.setYear(1970);
clear = true;
}
}
if (date) params += ';expires='+ date.toUTCString();
if (o.path) params += ';path='+ o.path;
if (o.domain) params += ';domain='+ o.domain;
if (o.secure) params += ';secure';
if (clear) {
state.cookie = {}; // clear data
document.cookie = name +'='+ params; // expire the cookie
}
else {
state.cookie = getState(keys || o.keys); // read current panes-state
document.cookie = name +'='+ encodeURIComponent( encodeJSON(state.cookie) ) + params; // write cookie
}
return $.extend({}, state.cookie); // return COPY of state.cookie
};
/**
* Remove the state cookie
*/
function deleteCookie () {
saveCookie('', { expires: -1 });
};
/**
* Get data from the cookie and USE IT to loadState
*
* @param {Object=} opts
*/
function loadCookie (opts) {
var o = getCookie(opts); // READ the cookie
if (o) {
state.cookie = $.extend({}, o); // SET state.cookie
loadState(o); // LOAD the retrieved state
}
return o;
};
/**
* Update layout options from the cookie, if one exists
*
* @param {Object=} opts
* @param {boolean=} animate
*/
function loadState (opts, animate) {
$.extend( true, options, opts ); // update layout options
// if layout has already been initialized, then UPDATE layout state
if (state.initialized) {
var pane, o, v, a = !animate;
$.each(_c.allPanes.split(","), function (idx, pane) {
o = opts[ pane ];
if (typeof o != 'object') return; // no key, continue
v = o.initHidden;
if (v === true) hide(pane, a);
if (v === false) show(pane, false, a);
v = o.size;
if (v > 0) sizePane(pane, v);
v = o.initClosed;
if (v === true) close(pane, false, a);
if (v === false) open(pane, false, a );
});
}
};
/**
* Get the *current layout state* and return it as a hash
*
* @param {(string|Array)=} keys
*/
function getState (keys) {
var
data = {}
, alt = { isClosed: 'initClosed', isHidden: 'initHidden' }
, pair, pane, key, val
;
if (!keys) keys = options.cookie.keys; // if called by user
if ($.isArray(keys)) keys = keys.join(",");
// convert keys to an array and change delimiters from '__' to '.'
keys = keys.replace(/__/g, ".").split(',');
// loop keys and create a data hash
for (var i=0,n=keys.length; i < n; i++) {
pair = keys[i].split(".");
pane = pair[0];
key = pair[1];
if (_c.allPanes.indexOf(pane) < 0) continue; // bad pane!
val = state[ pane ][ key ];
if (val == undefined) continue;
if (key=="isClosed" && state[pane]["isSliding"])
val = true; // if sliding, then *really* isClosed
( data[pane] || (data[pane]={}) )[ alt[key] ? alt[key] : key ] = val;
}
return data;
};
/**
* Stringify a JSON hash so can save in a cookie or db-field
*/
function encodeJSON (JSON) {
return parse( JSON );
function parse (h) {
var D=[], i=0, k, v, t; // k = key, v = value
for (k in h) {
v = h[k];
t = typeof v;
if (t == 'string') // STRING - add quotes
v = '"'+ v +'"';
else if (t == 'object') // SUB-KEY - recurse into it
v = parse(v);
D[i++] = '"'+ k +'":'+ v;
}
return "{"+ D.join(",") +"}";
};
};
/**
* Convert stringified JSON back to a hash object
*/
function decodeJSON (str) {
try { return window["eval"]("("+ str +")") || {}; }
catch (e) { return {}; }
};
/*
* #####################
* CREATE/RETURN LAYOUT
* #####################
*/
// validate that container exists
var $Container = $(this).eq(0); // FIRST matching Container element
if (!$Container.length) {
//alert( lang.errContainerMissing );
return null;
};
// Users retreive Instance of a layout with: $Container.layout() OR $Container.data("layout")
// return the Instance-pointer if layout has already been initialized
if ($Container.data("layoutContainer") && $Container.data("layout"))
return $Container.data("layout"); // cached pointer
// init global vars
var
$Ps = {} // Panes x5 - set in initPanes()
, $Cs = {} // Content x5 - set in initPanes()
, $Rs = {} // Resizers x4 - set in initHandles()
, $Ts = {} // Togglers x4 - set in initHandles()
// aliases for code brevity
, sC = state.container // alias for easy access to 'container dimensions'
, sID = state.id // alias for unique layout ID/namespace - eg: "layout435"
;
// create Instance object to expose data & option Properties, and primary action Methods
var Instance = {
options: options // property - options hash
, state: state // property - dimensions hash
, container: $Container // property - object pointers for layout container
, panes: $Ps // property - object pointers for ALL Panes: panes.north, panes.center
, contents: $Cs // property - object pointers for ALL Content: content.north, content.center
, resizers: $Rs // property - object pointers for ALL Resizers, eg: resizers.north
, togglers: $Ts // property - object pointers for ALL Togglers, eg: togglers.north
, toggle: toggle // method - pass a 'pane' ("north", "west", etc)
, hide: hide // method - ditto
, show: show // method - ditto
, open: open // method - ditto
, close: close // method - ditto
, slideOpen: slideOpen // method - ditto
, slideClose: slideClose // method - ditto
, slideToggle: slideToggle // method - ditto
, initContent: initContent // method - ditto
, sizeContent: sizeContent // method - pass a 'pane'
, sizePane: manualSizePane // method - pass a 'pane' AND an 'outer-size' in pixels or percent, or 'auto'
, swapPanes: swapPanes // method - pass TWO 'panes' - will swap them
, resizeAll: resizeAll // method - no parameters
, destroy: destroy // method - no parameters
, addPane: addPane // method - pass a 'pane'
, removePane: removePane // method - pass a 'pane' to remove from layout, add 'true' to delete the pane-elem
, setSizeLimits: setSizeLimits // method - pass a 'pane' - update state min/max data
, bindButton: bindButton // utility - pass element selector, 'action' and 'pane' (E, "toggle", "west")
, addToggleBtn: addToggleBtn // utility - pass element selector and 'pane' (E, "west")
, addOpenBtn: addOpenBtn // utility - ditto
, addCloseBtn: addCloseBtn // utility - ditto
, addPinBtn: addPinBtn // utility - ditto
, allowOverflow: allowOverflow // utility - pass calling element (this)
, resetOverflow: resetOverflow // utility - ditto
, encodeJSON: encodeJSON // method - pass a JSON object
, decodeJSON: decodeJSON // method - pass a string of encoded JSON
, getState: getState // method - returns hash of current layout-state
, getCookie: getCookie // method - update options from cookie - returns hash of cookie data
, saveCookie: saveCookie // method - optionally pass keys-list and cookie-options (hash)
, deleteCookie: deleteCookie // method
, loadCookie: loadCookie // method - update options from cookie - returns hash of cookie data
, loadState: loadState // method - pass a hash of state to use to update options
, cssWidth: cssW // utility - pass element and target outerWidth
, cssHeight: cssH // utility - ditto
, enableClosable: enableClosable
, disableClosable: disableClosable
, enableSlidable: enableSlidable
, disableSlidable: disableSlidable
, enableResizable: enableResizable
, disableResizable: disableResizable
};
// create the border layout NOW
_create();
// return the Instance object
return Instance;
}
})( jQuery );

View File

@ -1,142 +0,0 @@
/*
jquery.layout 1.3.0 - Release Candidate 29.14
$Date: 2011-02-13 08:00:00 (Sun, 13 Feb 2011) $
$Rev: 302914 $
Copyright (c) 2010
Fabrizio Balliano (http://www.fabrizioballiano.net)
Kevin Dalman (http://allpro.net)
Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
Changelog: http://layout.jquery-dev.net/changelog.cfm#1.3.0.rc29.13
Docs: http://layout.jquery-dev.net/documentation.html
Tips: http://layout.jquery-dev.net/tips.html
Help: http://groups.google.com/group/jquery-ui-layout
*/
(function($){var $b=$.browser;$.layout={browser:{mozilla:!!$b.mozilla,webkit:!!$b.webkit||!!$b.safari,msie:!!$b.msie,isIE6:!!$b.msie&&$b.version==6,boxModel:false},scrollbarWidth:function(){return window.scrollbarWidth||$.layout.getScrollbarSize("width")},scrollbarHeight:function(){return window.scrollbarHeight||$.layout.getScrollbarSize("height")},getScrollbarSize:function(dim){var $c=$('<div style="position: absolute; top: -10000px; left: -10000px; width: 100px; height: 100px; overflow: scroll;"></div>').appendTo("body");
var d={width:$c.width()-$c[0].clientWidth,height:$c.height()-$c[0].clientHeight};$c.remove();window.scrollbarWidth=d.width;window.scrollbarHeight=d.height;return dim.match(/^(width|height)$/i)?d[dim]:d},showInvisibly:function($E,force){if(!$E)return{};if(!$E.jquery)$E=$($E);var CSS={display:$E.css("display"),visibility:$E.css("visibility")};if(force||CSS.display=="none"){$E.css({display:"block",visibility:"hidden"});return CSS}else return{}},getElemDims:function($E){var d={},x=d.css={},i={},b,p,off=
$E.offset();d.offsetLeft=off.left;d.offsetTop=off.top;$.each("Left,Right,Top,Bottom".split(","),function(idx,e){b=x["border"+e]=$.layout.borderWidth($E,e);p=x["padding"+e]=$.layout.cssNum($E,"padding"+e);i[e]=b+p;d["inset"+e]=p});d.offsetWidth=$E.innerWidth();d.offsetHeight=$E.innerHeight();d.outerWidth=$E.outerWidth();d.outerHeight=$E.outerHeight();d.innerWidth=d.outerWidth-i.Left-i.Right;d.innerHeight=d.outerHeight-i.Top-i.Bottom;x.width=$E.width();x.height=$E.height();return d},getElemCSS:function($E,
list){var CSS={},style=$E[0].style,props=list.split(","),sides="Top,Bottom,Left,Right".split(","),attrs="Color,Style,Width".split(","),p,s,a,i,j,k;for(i=0;i<props.length;i++){p=props[i];if(p.match(/(border|padding|margin)$/))for(j=0;j<4;j++){s=sides[j];if(p=="border")for(k=0;k<3;k++){a=attrs[k];CSS[p+s+a]=style[p+s+a]}else CSS[p+s]=style[p+s]}else CSS[p]=style[p]}return CSS},cssWidth:function($E,outerWidth){var b=$.layout.borderWidth,n=$.layout.cssNum;if(outerWidth<=0)return 0;if(!$.layout.browser.boxModel)return outerWidth;
var W=outerWidth-b($E,"Left")-b($E,"Right")-n($E,"paddingLeft")-n($E,"paddingRight");return Math.max(0,W)},cssHeight:function($E,outerHeight){var b=$.layout.borderWidth,n=$.layout.cssNum;if(outerHeight<=0)return 0;if(!$.layout.browser.boxModel)return outerHeight;var H=outerHeight-b($E,"Top")-b($E,"Bottom")-n($E,"paddingTop")-n($E,"paddingBottom");return Math.max(0,H)},cssNum:function($E,prop){if(!$E.jquery)$E=$($E);var CSS=$.layout.showInvisibly($E);var val=parseInt($.curCSS($E[0],prop,true),10)||
0;$E.css(CSS);return val},borderWidth:function(el,side){if(el.jquery)el=el[0];var b="border"+side.substr(0,1).toUpperCase()+side.substr(1);return $.curCSS(el,b+"Style",true)=="none"?0:parseInt($.curCSS(el,b+"Width",true),10)||0},isMouseOverElem:function(evt,el){var $E=$(el||this),d=$E.offset(),T=d.top,L=d.left,R=L+$E.outerWidth(),B=T+$E.outerHeight(),x=evt.pageX,y=evt.pageY;return $.layout.browser.msie&&x<0&&y<0||x>=L&&x<=R&&y>=T&&y<=B}};$.fn.layout=function(opts){var lang={Pane:"Pane",Open:"Open",
Close:"Close",Resize:"Resize",Slide:"Slide Open",Pin:"Pin",Unpin:"Un-Pin",selector:"selector",msgNoRoom:"Not enough room to show this pane.",errContainerMissing:"UI Layout Initialization Error\n\nThe specified layout-container does not exist.",errCenterPaneMissing:"UI Layout Initialization Error\n\nThe center-pane element does not exist.\n\nThe center-pane is a required element.",errContainerHeight:"UI Layout Initialization Warning\n\nThe layout-container \"CONTAINER\" has no height.\n\nTherefore the layout is 0-height and hence 'invisible'!",
errButton:"Error Adding Button \n\nInvalid "};var options={name:"",containerClass:"ui-layout-container",scrollToBookmarkOnLoad:true,resizeWithWindow:true,resizeWithWindowDelay:200,resizeWithWindowMaxDelay:0,onresizeall_start:null,onresizeall_end:null,onload_start:null,onload_end:null,onunload_start:null,onunload_end:null,autoBindCustomButtons:false,zIndex:null,defaults:{applyDemoStyles:false,closable:true,resizable:true,slidable:true,initClosed:false,initHidden:false,contentSelector:".ui-layout-content",
contentIgnoreSelector:".ui-layout-ignore",findNestedContent:false,paneClass:"ui-layout-pane",resizerClass:"ui-layout-resizer",togglerClass:"ui-layout-toggler",buttonClass:"ui-layout-button",minSize:0,maxSize:0,spacing_open:6,spacing_closed:6,togglerLength_open:50,togglerLength_closed:50,togglerAlign_open:"center",togglerAlign_closed:"center",togglerTip_open:lang.Close,togglerTip_closed:lang.Open,togglerContent_open:"",togglerContent_closed:"",resizerDblClickToggle:true,autoResize:true,autoReopen:true,
resizerDragOpacity:1,maskIframesOnResize:true,resizeNestedLayout:true,resizeWhileDragging:false,resizeContentWhileDragging:false,noRoomToOpenTip:lang.msgNoRoom,resizerTip:lang.Resize,sliderTip:lang.Slide,sliderCursor:"pointer",slideTrigger_open:"click",slideTrigger_close:"mouseleave",slideDelay_open:300,slideDelay_close:300,hideTogglerOnSlide:false,preventQuickSlideClose:!!($.browser.webkit||$.browser.safari),preventPrematureSlideClose:false,showOverflowOnHover:false,enableCursorHotkey:true,customHotkeyModifier:"SHIFT",
fxName:"slide",fxSpeed:null,fxSettings:{},fxOpacityFix:true,triggerEventsOnLoad:false,triggerEventsWhileDragging:true,onshow_start:null,onshow_end:null,onhide_start:null,onhide_end:null,onopen_start:null,onopen_end:null,onclose_start:null,onclose_end:null,onresize_start:null,onresize_end:null,onsizecontent_start:null,onsizecontent_end:null,onswap_start:null,onswap_end:null,ondrag_start:null,ondrag_end:null},north:{paneSelector:".ui-layout-north",size:"auto",resizerCursor:"n-resize",customHotkey:""},
south:{paneSelector:".ui-layout-south",size:"auto",resizerCursor:"s-resize",customHotkey:""},east:{paneSelector:".ui-layout-east",size:200,resizerCursor:"e-resize",customHotkey:""},west:{paneSelector:".ui-layout-west",size:200,resizerCursor:"w-resize",customHotkey:""},center:{paneSelector:".ui-layout-center",minWidth:0,minHeight:0},useStateCookie:false,cookie:{name:"",autoSave:true,autoLoad:true,domain:"",path:"",expires:"",secure:false,keys:"north.size,south.size,east.size,west.size,"+"north.isClosed,south.isClosed,east.isClosed,west.isClosed,"+
"north.isHidden,south.isHidden,east.isHidden,west.isHidden"}};var effects={slide:{all:{duration:"fast"},north:{direction:"up"},south:{direction:"down"},east:{direction:"right"},west:{direction:"left"}},drop:{all:{duration:"slow"},north:{direction:"up"},south:{direction:"down"},east:{direction:"right"},west:{direction:"left"}},scale:{all:{duration:"fast"}}};var state={id:"layout"+(new Date).getTime(),initialized:false,container:{},north:{},south:{},east:{},west:{},center:{},cookie:{}};var _c={allPanes:"north,south,west,east,center",
borderPanes:"north,south,west,east",altSide:{north:"south",south:"north",east:"west",west:"east"},hidden:{visibility:"hidden"},visible:{visibility:"visible"},zIndex:{pane_normal:1,resizer_normal:2,iframe_mask:2,pane_sliding:100,pane_animate:1E3,resizer_drag:1E4},resizers:{cssReq:{position:"absolute",padding:0,margin:0,fontSize:"1px",textAlign:"left",overflow:"hidden"},cssDemo:{background:"#DDD",border:"none"}},togglers:{cssReq:{position:"absolute",display:"block",padding:0,margin:0,overflow:"hidden",
textAlign:"center",fontSize:"1px",cursor:"pointer",zIndex:1},cssDemo:{background:"#AAA"}},content:{cssReq:{position:"relative"},cssDemo:{overflow:"auto",padding:"10px"},cssDemoPane:{overflow:"hidden",padding:0}},panes:{cssReq:{position:"absolute",margin:0},cssDemo:{padding:"10px",background:"#FFF",border:"1px solid #BBB",overflow:"auto"}},north:{side:"Top",sizeType:"Height",dir:"horz",cssReq:{top:0,bottom:"auto",left:0,right:0,width:"auto"},pins:[]},south:{side:"Bottom",sizeType:"Height",dir:"horz",
cssReq:{top:"auto",bottom:0,left:0,right:0,width:"auto"},pins:[]},east:{side:"Right",sizeType:"Width",dir:"vert",cssReq:{left:"auto",right:0,top:"auto",bottom:"auto",height:"auto"},pins:[]},west:{side:"Left",sizeType:"Width",dir:"vert",cssReq:{left:0,right:"auto",top:"auto",bottom:"auto",height:"auto"},pins:[]},center:{dir:"center",cssReq:{left:"auto",right:"auto",top:"auto",bottom:"auto",height:"auto",width:"auto"}}};var timer={data:{},set:function(s,fn,ms){timer.clear(s);timer.data[s]=setTimeout(fn,
ms)},clear:function(s){var t=timer.data;if(t[s]){clearTimeout(t[s]);delete t[s]}}};var isStr=function(o){try{return typeof o=="string"||typeof o=="object"&&o.constructor.toString().match(/string/i)!==null}catch(e){return false}};var str=function(o){return isStr(o)?$.trim(o):o==undefined||o==null?"":o};var min=function(x,y){return Math.min(x,y)};var max=function(x,y){return Math.max(x,y)};var _transformData=function(d){var a,json={cookie:{},defaults:{fxSettings:{}},north:{fxSettings:{}},south:{fxSettings:{}},
east:{fxSettings:{}},west:{fxSettings:{}},center:{fxSettings:{}}};d=d||{};if(d.effects||d.cookie||d.defaults||d.north||d.south||d.west||d.east||d.center)json=$.extend(true,json,d);else $.each(d,function(key,val){a=key.split("__");if(!a[1]||json[a[0]])json[a[1]?a[0]:"defaults"][a[1]?a[1]:a[0]]=val});return json};var _queue=function(action,pane,param){var tried=[];$.each(_c.borderPanes.split(","),function(i,p){if(_c[p].isMoving){bindCallback(p);return false}});function bindCallback(p){var c=_c[p];if(!c.doCallback){c.doCallback=
true;c.callback=action+","+pane+","+(param?1:0)}else{tried.push(p);var cbPane=c.callback.split(",")[1];if(cbPane!=pane&&!$.inArray(cbPane,tried)>=0)bindCallback(cbPane)}}};var _dequeue=function(pane){var c=_c[pane];_c.isLayoutBusy=false;delete c.isMoving;if(!c.doCallback||!c.callback)return;c.doCallback=false;var cb=c.callback.split(","),param=cb[2]>0?true:false;if(cb[0]=="open")open(cb[1],param);else if(cb[0]=="close")close(cb[1],param);if(!c.doCallback)c.callback=null};var _execCallback=function(pane,
v_fn){if(!v_fn)return;var fn;try{if(typeof v_fn=="function")fn=v_fn;else if(!isStr(v_fn))return;else if(v_fn.match(/,/)){var args=v_fn.split(",");fn=eval(args[0]);if(typeof fn=="function"&&args.length>1)return fn(args[1])}else fn=eval(v_fn);if(typeof fn=="function")if(pane&&$Ps[pane])return fn(pane,$Ps[pane],$.extend({},state[pane]),options[pane],options.name);else return fn(Instance,$.extend({},state),options,options.name)}catch(ex){}};var _showInvisibly=function($E,force){if(!$E)return{};if(!$E.jquery)$E=
$($E);var CSS={display:$E.css("display"),visibility:$E.css("visibility")};if(force||CSS.display=="none"){$E.css({display:"block",visibility:"hidden"});return CSS}else return{}};var _fixIframe=function(pane){if(state.browser.mozilla)return;var $P=$Ps[pane];if(state[pane].tagName=="IFRAME")$P.css(_c.hidden).css(_c.visible);else $P.find("IFRAME").css(_c.hidden).css(_c.visible)};var _cssNum=function($E,prop){if(!$E.jquery)$E=$($E);var CSS=_showInvisibly($E);var val=parseInt($.curCSS($E[0],prop,true),
10)||0;$E.css(CSS);return val};var _borderWidth=function(E,side){if(E.jquery)E=E[0];var b="border"+side.substr(0,1).toUpperCase()+side.substr(1);return $.curCSS(E,b+"Style",true)=="none"?0:parseInt($.curCSS(E,b+"Width",true),10)||0};var cssW=function(el,outerWidth){var str=isStr(el),$E=str?$Ps[el]:$(el);if(isNaN(outerWidth))outerWidth=str?getPaneSize(el):$E.outerWidth();if(outerWidth<=0)return 0;if(!state.browser.boxModel)return outerWidth;var W=outerWidth-_borderWidth($E,"Left")-_borderWidth($E,
"Right")-_cssNum($E,"paddingLeft")-_cssNum($E,"paddingRight");return max(0,W)};var cssH=function(el,outerHeight){var str=isStr(el),$E=str?$Ps[el]:$(el);if(isNaN(outerHeight))outerHeight=str?getPaneSize(el):$E.outerHeight();if(outerHeight<=0)return 0;if(!state.browser.boxModel)return outerHeight;var H=outerHeight-_borderWidth($E,"Top")-_borderWidth($E,"Bottom")-_cssNum($E,"paddingTop")-_cssNum($E,"paddingBottom");return max(0,H)};var cssSize=function(pane,outerSize){if(_c[pane].dir=="horz")return cssH(pane,
outerSize);else return cssW(pane,outerSize)};var cssMinDims=function(pane){var dir=_c[pane].dir,d={minWidth:1001-cssW(pane,1E3),minHeight:1001-cssH(pane,1E3)};if(dir=="horz")d.minSize=d.minHeight;if(dir=="vert")d.minSize=d.minWidth;return d};var setOuterWidth=function(el,outerWidth,autoHide){var $E=el,w;if(isStr(el))$E=$Ps[el];else if(!el.jquery)$E=$(el);w=cssW($E,outerWidth);$E.css({width:w});if(w>0){if(autoHide&&$E.data("autoHidden")&&$E.innerHeight()>0){$E.show().data("autoHidden",false);if(!state.browser.mozilla)$E.css(_c.hidden).css(_c.visible)}}else if(autoHide&&
!$E.data("autoHidden"))$E.hide().data("autoHidden",true)};var setOuterHeight=function(el,outerHeight,autoHide){var $E=el,h;if(isStr(el))$E=$Ps[el];else if(!el.jquery)$E=$(el);h=cssH($E,outerHeight);$E.css({height:h,visibility:"visible"});if(h>0&&$E.innerWidth()>0){if(autoHide&&$E.data("autoHidden")){$E.show().data("autoHidden",false);if(!state.browser.mozilla)$E.css(_c.hidden).css(_c.visible)}}else if(autoHide&&!$E.data("autoHidden"))$E.hide().data("autoHidden",true)};var setOuterSize=function(el,
outerSize,autoHide){if(_c[pane].dir=="horz")setOuterHeight(el,outerSize,autoHide);else setOuterWidth(el,outerSize,autoHide)};var _parseSize=function(pane,size,dir){if(!dir)dir=_c[pane].dir;if(isStr(size)&&size.match(/%/))size=parseInt(size,10)/100;if(size===0)return 0;else if(size>=1)return parseInt(size,10);else if(size>0){var o=options,avail;if(dir=="horz")avail=sC.innerHeight-($Ps.north?o.north.spacing_open:0)-($Ps.south?o.south.spacing_open:0);else if(dir=="vert")avail=sC.innerWidth-($Ps.west?
o.west.spacing_open:0)-($Ps.east?o.east.spacing_open:0);return Math.floor(avail*size)}else if(pane=="center")return 0;else{var $P=$Ps[pane],dim=dir=="horz"?"height":"width",vis=_showInvisibly($P),s=$P.css(dim);$P.css(dim,"auto");size=dim=="height"?$P.outerHeight():$P.outerWidth();$P.css(dim,s).css(vis);return size}};var getPaneSize=function(pane,inclSpace){var $P=$Ps[pane],o=options[pane],s=state[pane],oSp=inclSpace?o.spacing_open:0,cSp=inclSpace?o.spacing_closed:0;if(!$P||s.isHidden)return 0;else if(s.isClosed||
s.isSliding&&inclSpace)return cSp;else if(_c[pane].dir=="horz")return $P.outerHeight()+oSp;else return $P.outerWidth()+oSp};var setSizeLimits=function(pane,slide){var o=options[pane],s=state[pane],c=_c[pane],dir=c.dir,side=c.side.toLowerCase(),type=c.sizeType.toLowerCase(),isSliding=slide!=undefined?slide:s.isSliding,$P=$Ps[pane],paneSpacing=o.spacing_open,altPane=_c.altSide[pane],altS=state[altPane],$altP=$Ps[altPane],altPaneSize=!$altP||altS.isVisible===false||altS.isSliding?0:dir=="horz"?$altP.outerHeight():
$altP.outerWidth(),altPaneSpacing=(!$altP||altS.isHidden?0:options[altPane][altS.isClosed!==false?"spacing_closed":"spacing_open"])||0,containerSize=dir=="horz"?sC.innerHeight:sC.innerWidth,minCenterDims=cssMinDims("center"),minCenterSize=dir=="horz"?max(options.center.minHeight,minCenterDims.minHeight):max(options.center.minWidth,minCenterDims.minWidth),limitSize=containerSize-paneSpacing-(isSliding?0:_parseSize("center",minCenterSize,dir)+altPaneSize+altPaneSpacing),minSize=s.minSize=max(_parseSize(pane,
o.minSize),cssMinDims(pane).minSize),maxSize=s.maxSize=min(o.maxSize?_parseSize(pane,o.maxSize):1E5,limitSize),r=s.resizerPosition={},top=sC.insetTop,left=sC.insetLeft,W=sC.innerWidth,H=sC.innerHeight,rW=o.spacing_open;switch(pane){case "north":r.min=top+minSize;r.max=top+maxSize;break;case "west":r.min=left+minSize;r.max=left+maxSize;break;case "south":r.min=top+H-maxSize-rW;r.max=top+H-minSize-rW;break;case "east":r.min=left+W-maxSize-rW;r.max=left+W-minSize-rW;break}};var calcNewCenterPaneDims=
function(){var d={top:getPaneSize("north",true),bottom:getPaneSize("south",true),left:getPaneSize("west",true),right:getPaneSize("east",true),width:0,height:0};d.width=sC.innerWidth-d.left-d.right;d.height=sC.innerHeight-d.bottom-d.top;d.top+=sC.insetTop;d.bottom+=sC.insetBottom;d.left+=sC.insetLeft;d.right+=sC.insetRight;return d};var getElemDims=function($E){var d={},x=d.css={},i={},b,p,off=$E.offset();d.offsetLeft=off.left;d.offsetTop=off.top;$.each("Left,Right,Top,Bottom".split(","),function(idx,
e){b=x["border"+e]=_borderWidth($E,e);p=x["padding"+e]=_cssNum($E,"padding"+e);i[e]=b+p;d["inset"+e]=p});d.offsetWidth=$E.innerWidth();d.offsetHeight=$E.innerHeight();d.outerWidth=$E.outerWidth();d.outerHeight=$E.outerHeight();d.innerWidth=d.outerWidth-i.Left-i.Right;d.innerHeight=d.outerHeight-i.Top-i.Bottom;x.width=$E.width();x.height=$E.height();return d};var getElemCSS=function($E,list){var CSS={},style=$E[0].style,props=list.split(","),sides="Top,Bottom,Left,Right".split(","),attrs="Color,Style,Width".split(","),
p,s,a,i,j,k;for(i=0;i<props.length;i++){p=props[i];if(p.match(/(border|padding|margin)$/))for(j=0;j<4;j++){s=sides[j];if(p=="border")for(k=0;k<3;k++){a=attrs[k];CSS[p+s+a]=style[p+s+a]}else CSS[p+s]=style[p+s]}else CSS[p]=style[p]}return CSS};var getHoverClasses=function(el,allStates){var $El=$(el),type=$El.data("layoutRole"),pane=$El.data("layoutEdge"),o=options[pane],root=o[type+"Class"],_pane="-"+pane,_open="-open",_closed="-closed",_slide="-sliding",_hover="-hover ",_state=$El.hasClass(root+_closed)?
_closed:_open,_alt=_state==_closed?_open:_closed,classes=root+_hover+(root+_pane+_hover)+(root+_state+_hover)+(root+_pane+_state+_hover);if(allStates)classes+=root+_alt+_hover+(root+_pane+_alt+_hover);if(type=="resizer"&&$El.hasClass(root+_slide))classes+=root+_slide+_hover+(root+_pane+_slide+_hover);return $.trim(classes)};var addHover=function(evt,el){var $E=$(el||this);if(evt&&$E.data("layoutRole")=="toggler")evt.stopPropagation();$E.addClass(getHoverClasses($E))};var removeHover=function(evt,
el){var $E=$(el||this);$E.removeClass(getHoverClasses($E,true))};var onResizerEnter=function(evt){$("body").disableSelection();addHover(evt,this)};var onResizerLeave=function(evt,el){var e=el||this,pane=$(e).data("layoutEdge"),name=pane+"ResizerLeave";timer.clear(pane+"_openSlider");timer.clear(name);if(!el){removeHover(evt,this);timer.set(name,function(){onResizerLeave(evt,e)},200)}else if(!state[pane].isResizing)$("body").enableSelection()};var _create=function(){initOptions();var o=options;if(false===
_execCallback(null,o.onload_start))return false;if(!getPane("center").length){alert(lang.errCenterPaneMissing);return null}if(o.useStateCookie&&o.cookie.autoLoad)loadCookie();state.browser={mozilla:$.browser.mozilla,webkit:$.browser.webkit||$.browser.safari,msie:$.browser.msie,isIE6:$.browser.msie&&$.browser.version==6,boxModel:$.support.boxModel};initContainer();initPanes();sizeContent();if(o.scrollToBookmarkOnLoad){var l=self.location;if(l.hash)l.replace(l.hash)}initHotkeys();if(o.autoBindCustomButtons)initButtons();
if(o.resizeWithWindow&&!$Container.data("layoutRole"))$(window).bind("resize."+sID,windowResize);$(window).bind("unload."+sID,unload);state.initialized=true;_execCallback(null,o.onload_end||o.onload)};var windowResize=function(){var delay=Number(options.resizeWithWindowDelay)||100;if(delay>0){timer.clear("winResize");timer.set("winResize",function(){timer.clear("winResize");timer.clear("winResizeRepeater");resizeAll()},delay);if(!timer.data["winResizeRepeater"])setWindowResizeRepeater()}};var setWindowResizeRepeater=
function(){var delay=Number(options.resizeWithWindowMaxDelay);if(delay>0)timer.set("winResizeRepeater",function(){setWindowResizeRepeater();resizeAll()},delay)};var unload=function(){var o=options;state.cookie=getState();_execCallback(null,o.onunload_start);if(o.useStateCookie&&o.cookie.autoSave)saveCookie();_execCallback(null,o.onunload_end||o.onunload)};var initContainer=function(){var $C=$Container,tag=sC.tagName=$C.attr("tagName"),fullPage=tag=="BODY",props="position,margin,padding,border",CSS=
{};sC.selector=$C.selector.split(".slice")[0];sC.ref=tag+"/"+sC.selector;$C.data("layout",Instance).data("layoutContainer",sID).addClass(options.containerClass);if(!$C.data("layoutCSS")){if(fullPage){CSS=$.extend(getElemCSS($C,props),{height:$C.css("height"),overflow:$C.css("overflow"),overflowX:$C.css("overflowX"),overflowY:$C.css("overflowY")});var $H=$("html");$H.data("layoutCSS",{height:"auto",overflow:$H.css("overflow"),overflowX:$H.css("overflowX"),overflowY:$H.css("overflowY")})}else CSS=getElemCSS($C,
props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY");$C.data("layoutCSS",CSS)}try{if(fullPage){$("html").css({height:"100%",overflow:"hidden",overflowX:"hidden",overflowY:"hidden"});$("body").css({position:"relative",height:"100%",overflow:"hidden",overflowX:"hidden",overflowY:"hidden",margin:0,padding:0,border:"none"})}else{CSS={overflow:"hidden"};var p=$C.css("position"),h=$C.css("height");if(!$C.data("layoutRole"))if(!p||!p.match(/fixed|absolute|relative/))CSS.position="relative";
$C.css(CSS);if($C.is(":visible")&&$C.innerHeight()<2)alert(lang.errContainerHeight.replace(/CONTAINER/,sC.ref))}}catch(ex){}$.extend(state.container,getElemDims($C))};var initHotkeys=function(panes){if(!panes||panes=="all")panes=_c.borderPanes;$.each(panes.split(","),function(i,pane){var o=options[pane];if(o.enableCursorHotkey||o.customHotkey){$(document).bind("keydown."+sID,keyDown);return false}})};var initOptions=function(){opts=_transformData(opts);var newOpts={applyDefaultStyles:"applyDemoStyles"};
renameOpts(opts.defaults);$.each(_c.allPanes.split(","),function(i,pane){renameOpts(opts[pane])});if(opts.effects){$.extend(effects,opts.effects);delete opts.effects}$.extend(options.cookie,opts.cookie);var globals="name,containerClass,zIndex,scrollToBookmarkOnLoad,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay,"+"onresizeall,onresizeall_start,onresizeall_end,onload,onload_start,onload_end,onunload,onunload_start,onunload_end,autoBindCustomButtons,useStateCookie";$.each(globals.split(","),
function(i,key){if(opts[key]!==undefined)options[key]=opts[key];else if(opts.defaults[key]!==undefined){options[key]=opts.defaults[key];delete opts.defaults[key]}});$.each("paneSelector,resizerCursor,customHotkey".split(","),function(i,key){delete opts.defaults[key]});$.extend(true,options.defaults,opts.defaults);_c.center=$.extend(true,{},_c.panes,_c.center);var z=options.zIndex;if(z===0||z>0){_c.zIndex.pane_normal=z;_c.zIndex.resizer_normal=z+1;_c.zIndex.iframe_mask=z+1}$.extend(options.center,
opts.center);var o_Center=$.extend(true,{},options.defaults,opts.defaults,options.center);var optionsCenter=("paneClass,contentSelector,applyDemoStyles,triggerEventsOnLoad,showOverflowOnHover,"+"onresize,onresize_start,onresize_end,resizeNestedLayout,resizeContentWhileDragging,"+"onsizecontent,onsizecontent_start,onsizecontent_end").split(",");$.each(optionsCenter,function(i,key){options.center[key]=o_Center[key]});var o,defs=options.defaults;$.each(_c.borderPanes.split(","),function(i,pane){_c[pane]=
$.extend(true,{},_c.panes,_c[pane]);o=options[pane]=$.extend(true,{},options.defaults,options[pane],opts.defaults,opts[pane]);if(!o.paneClass)o.paneClass="ui-layout-pane";if(!o.resizerClass)o.resizerClass="ui-layout-resizer";if(!o.togglerClass)o.togglerClass="ui-layout-toggler";$.each(["_open","_close",""],function(i,n){var sName="fxName"+n,sSpeed="fxSpeed"+n,sSettings="fxSettings"+n;o[sName]=opts[pane][sName]||opts[pane].fxName||opts.defaults[sName]||opts.defaults.fxName||o[sName]||o.fxName||defs[sName]||
defs.fxName||"none";var fxName=o[sName];if(fxName=="none"||!$.effects||!$.effects[fxName]||!effects[fxName]&&!o[sSettings]&&!o.fxSettings)fxName=o[sName]="none";var fx=effects[fxName]||{},fx_all=fx.all||{},fx_pane=fx[pane]||{};o[sSettings]=$.extend({},fx_all,fx_pane,defs.fxSettings||{},defs[sSettings]||{},o.fxSettings,o[sSettings],opts.defaults.fxSettings,opts.defaults[sSettings]||{},opts[pane].fxSettings,opts[pane][sSettings]||{});o[sSpeed]=opts[pane][sSpeed]||opts[pane].fxSpeed||opts.defaults[sSpeed]||
opts.defaults.fxSpeed||o[sSpeed]||o[sSettings].duration||o.fxSpeed||o.fxSettings.duration||defs.fxSpeed||defs.fxSettings.duration||fx_pane.duration||fx_all.duration||"normal"})});function renameOpts(O){for(var key in newOpts)if(O[key]!=undefined){O[newOpts[key]]=O[key];delete O[key]}}};var getPane=function(pane){var sel=options[pane].paneSelector;if(sel.substr(0,1)==="#")return $Container.find(sel).eq(0);else{var $P=$Container.children(sel).eq(0);return $P.length?$P:$Container.children("form:first").children(sel).eq(0)}};
var initPanes=function(){$.each(_c.allPanes.split(","),function(idx,pane){addPane(pane)});initHandles();$.each(_c.borderPanes.split(","),function(i,pane){if($Ps[pane]&&state[pane].isVisible){setSizeLimits(pane);makePaneFit(pane)}});sizeMidPanes("center");$.each(_c.allPanes.split(","),function(i,pane){var o=options[pane];if($Ps[pane]&&state[pane].isVisible){if(o.triggerEventsOnLoad)_execCallback(pane,o.onresize_end||o.onresize);resizeNestedLayout(pane)}});if($Container.innerHeight()<2)alert(lang.errContainerHeight.replace(/CONTAINER/,
sC.ref))};var addPane=function(pane){var o=options[pane],s=state[pane],c=_c[pane],fx=s.fx,dir=c.dir,spacing=o.spacing_open||0,isCenter=pane=="center",CSS={},$P=$Ps[pane],size,minSize,maxSize;if($P)removePane(pane);else $Cs[pane]=false;$P=$Ps[pane]=getPane(pane);if(!$P.length){$Ps[pane]=false;return}if(!$P.data("layoutCSS")){var props="position,top,left,bottom,right,width,height,overflow,zIndex,display,backgroundColor,padding,margin,border";$P.data("layoutCSS",getElemCSS($P,props))}$P.data("parentLayout",
Instance).data("layoutRole","pane").data("layoutEdge",pane).css(c.cssReq).css("zIndex",_c.zIndex.pane_normal).css(o.applyDemoStyles?c.cssDemo:{}).addClass(o.paneClass+" "+o.paneClass+"-"+pane).bind("mouseenter."+sID,addHover).bind("mouseleave."+sID,removeHover);initContent(pane,false);if(!isCenter){size=s.size=_parseSize(pane,o.size);minSize=_parseSize(pane,o.minSize)||1;maxSize=_parseSize(pane,o.maxSize)||1E5;if(size>0)size=max(min(size,maxSize),minSize);s.isClosed=false;s.isSliding=false;s.isResizing=
false;s.isHidden=false}s.tagName=$P.attr("tagName");s.edge=pane;s.noRoom=false;s.isVisible=true;switch(pane){case "north":CSS.top=sC.insetTop;CSS.left=sC.insetLeft;CSS.right=sC.insetRight;break;case "south":CSS.bottom=sC.insetBottom;CSS.left=sC.insetLeft;CSS.right=sC.insetRight;break;case "west":CSS.left=sC.insetLeft;break;case "east":CSS.right=sC.insetRight;break;case "center":}if(dir=="horz")CSS.height=max(1,cssH(pane,size));else if(dir=="vert")CSS.width=max(1,cssW(pane,size));$P.css(CSS);if(dir!=
"horz")sizeMidPanes(pane,true);if(!s.noRoom)$P.css({visibility:"visible",display:"block"});if(o.initClosed&&o.closable)close(pane,true,true);else if(o.initHidden||o.initClosed)hide(pane);if(o.showOverflowOnHover)$P.hover(allowOverflow,resetOverflow);if(state.initialized){initHandles(pane);initHotkeys(pane);resizeAll();if(s.isVisible){if(o.triggerEventsOnLoad)_execCallback(pane,o.onresize_end||o.onresize);resizeNestedLayout(pane)}}};var initHandles=function(panes){if(!panes||panes=="all")panes=_c.borderPanes;
$.each(panes.split(","),function(i,pane){var $P=$Ps[pane];$Rs[pane]=false;$Ts[pane]=false;if(!$P)return;var o=options[pane],s=state[pane],c=_c[pane],rClass=o.resizerClass,tClass=o.togglerClass,side=c.side.toLowerCase(),spacing=s.isVisible?o.spacing_open:o.spacing_closed,_pane="-"+pane,_state=s.isVisible?"-open":"-closed",$R=$Rs[pane]=$("<div></div>"),$T=o.closable?$Ts[pane]=$("<div></div>"):false;if(!s.isVisible&&o.slidable)$R.attr("title",o.sliderTip).css("cursor",o.sliderCursor);$R.attr("id",o.paneSelector.substr(0,
1)=="#"?o.paneSelector.substr(1)+"-resizer":"").data("parentLayout",Instance).data("layoutRole","resizer").data("layoutEdge",pane).css(_c.resizers.cssReq).css("zIndex",_c.zIndex.resizer_normal).css(o.applyDemoStyles?_c.resizers.cssDemo:{}).addClass(rClass+" "+rClass+_pane).appendTo($Container);if($T){$T.attr("id",o.paneSelector.substr(0,1)=="#"?o.paneSelector.substr(1)+"-toggler":"").data("parentLayout",Instance).data("layoutRole","toggler").data("layoutEdge",pane).css(_c.togglers.cssReq).css(o.applyDemoStyles?
_c.togglers.cssDemo:{}).addClass(tClass+" "+tClass+_pane).appendTo($R);if(o.togglerContent_open)$("<span>"+o.togglerContent_open+"</span>").data("layoutRole","togglerContent").data("layoutEdge",pane).addClass("content content-open").css("display","none").appendTo($T);if(o.togglerContent_closed)$("<span>"+o.togglerContent_closed+"</span>").data("layoutRole","togglerContent").data("layoutEdge",pane).addClass("content content-closed").css("display","none").appendTo($T);enableClosable(pane)}initResizable(pane);
if(s.isVisible)setAsOpen(pane);else{setAsClosed(pane);bindStartSlidingEvent(pane,true)}});sizeHandles("all")};var initContent=function(pane,resize){var o=options[pane],sel=o.contentSelector,$P=$Ps[pane],$C;if(sel)$C=$Cs[pane]=o.findNestedContent?$P.find(sel).eq(0):$P.children(sel).eq(0);if($C&&$C.length){if(!$C.data("layoutCSS"))$C.data("layoutCSS",getElemCSS($C,"height"));$C.css(_c.content.cssReq);if(o.applyDemoStyles){$C.css(_c.content.cssDemo);$P.css(_c.content.cssDemoPane)}state[pane].content=
{};if(resize!==false)sizeContent(pane)}else $Cs[pane]=false};var initButtons=function(){var pre="ui-layout-button-",name;$.each("toggle,open,close,pin,toggle-slide,open-slide".split(","),function(i,action){$.each(_c.borderPanes.split(","),function(ii,pane){$("."+pre+action+"-"+pane).each(function(){name=$(this).data("layoutName")||$(this).attr("layoutName");if(name==undefined||name==options.name)bindButton(this,action,pane)})})})};var initResizable=function(panes){var draggingAvailable=typeof $.fn.draggable==
"function",$Frames,side;if(!panes||panes=="all")panes=_c.borderPanes;$.each(panes.split(","),function(idx,pane){var o=options[pane],s=state[pane],c=_c[pane],side=c.dir=="horz"?"top":"left",r,live;if(!draggingAvailable||!$Ps[pane]||!o.resizable){o.resizable=false;return true}var $P=$Ps[pane],$R=$Rs[pane],base=o.resizerClass,resizerClass=base+"-drag",resizerPaneClass=base+"-"+pane+"-drag",helperClass=base+"-dragging",helperPaneClass=base+"-"+pane+"-dragging",helperLimitClass=base+"-dragging-limit",
helperPaneLimitClass=base+"-"+pane+"-dragging-limit",helperClassesSet=false;if(!s.isClosed)$R.attr("title",o.resizerTip).css("cursor",o.resizerCursor);$R.bind("mouseenter."+sID,onResizerEnter).bind("mouseleave."+sID,onResizerLeave);$R.draggable({containment:$Container[0],axis:c.dir=="horz"?"y":"x",delay:0,distance:1,helper:"clone",opacity:o.resizerDragOpacity,addClasses:false,zIndex:_c.zIndex.resizer_drag,start:function(e,ui){o=options[pane];s=state[pane];live=o.resizeWhileDragging;if(false===_execCallback(pane,
o.ondrag_start))return false;_c.isLayoutBusy=true;s.isResizing=true;timer.clear(pane+"_closeSlider");setSizeLimits(pane);r=s.resizerPosition;$R.addClass(resizerClass+" "+resizerPaneClass);helperClassesSet=false;$Frames=$(o.maskIframesOnResize===true?"iframe":o.maskIframesOnResize).filter(":visible");var id,i=0;$Frames.each(function(){id="ui-layout-mask-"+ ++i;$(this).data("layoutMaskID",id);$('<div id="'+id+'" class="ui-layout-mask ui-layout-mask-'+pane+'"/>').css({background:"#fff",opacity:"0.001",
zIndex:_c.zIndex.iframe_mask,position:"absolute",width:this.offsetWidth+"px",height:this.offsetHeight+"px"}).css($(this).position()).appendTo(this.parentNode)});$("body").disableSelection()},drag:function(e,ui){if(!helperClassesSet){ui.helper.addClass(helperClass+" "+helperPaneClass).css({right:"auto",bottom:"auto"}).children().css("visibility","hidden");helperClassesSet=true;if(s.isSliding)$Ps[pane].css("zIndex",_c.zIndex.pane_sliding)}var limit=0;if(ui.position[side]<r.min){ui.position[side]=r.min;
limit=-1}else if(ui.position[side]>r.max){ui.position[side]=r.max;limit=1}if(limit){ui.helper.addClass(helperLimitClass+" "+helperPaneLimitClass);window.defaultStatus="Panel has reached its "+(limit>0&&pane.match(/north|west/)||limit<0&&pane.match(/south|east/)?"maximum":"minimum")+" size"}else{ui.helper.removeClass(helperLimitClass+" "+helperPaneLimitClass);window.defaultStatus=""}if(live)resizePanes(e,ui,pane)},stop:function(e,ui){$("body").enableSelection();window.defaultStatus="";$R.removeClass(resizerClass+
" "+resizerPaneClass);s.isResizing=false;_c.isLayoutBusy=false;resizePanes(e,ui,pane,true)}});var resizePanes=function(evt,ui,pane,resizingDone){var dragPos=ui.position,c=_c[pane],resizerPos,newSize,i=0;switch(pane){case "north":resizerPos=dragPos.top;break;case "west":resizerPos=dragPos.left;break;case "south":resizerPos=sC.offsetHeight-dragPos.top-o.spacing_open;break;case "east":resizerPos=sC.offsetWidth-dragPos.left-o.spacing_open;break}if(resizingDone){$("div.ui-layout-mask").each(function(){this.parentNode.removeChild(this)});
if(false===_execCallback(pane,o.ondrag_end||o.ondrag))return false}else $Frames.each(function(){$("#"+$(this).data("layoutMaskID")).css($(this).position()).css({width:this.offsetWidth+"px",height:this.offsetHeight+"px"})});newSize=resizerPos-sC["inset"+c.side];manualSizePane(pane,newSize)}})};var destroy=function(){$(window).unbind("."+sID);$(document).unbind("."+sID);$.each(_c.allPanes.split(","),function(i,pane){removePane(pane,false,true)});var $C=$Container.removeData("layout").removeData("layoutContainer").removeClass(options.containerClass);
if(!$C.data("layoutEdge")&&$C.data("layoutCSS"))$C.css($C.data("layoutCSS")).removeData("layoutCSS");if(sC.tagName=="BODY"&&($C=$("html")).data("layoutCSS"))$C.css($C.data("layoutCSS")).removeData("layoutCSS");unload()};var removePane=function(pane,remove,skipResize){if(!$Ps[pane])return;var $P=$Ps[pane],$C=$Cs[pane],$R=$Rs[pane],$T=$Ts[pane],_open="-open",_sliding="-sliding",_closed="-closed",root=options[pane].paneClass,pRoot=root+"-"+pane,classes=[root,root+_open,root+_closed,root+_sliding,pRoot,
pRoot+_open,pRoot+_closed,pRoot+_sliding];$.merge(classes,getHoverClasses($P,true));if(!$P||!$P.length);else if(remove&&!$P.data("layoutContainer")&&(!$C||!$C.length||!$C.data("layoutContainer")))$P.remove();else{$P.removeClass(classes.join(" ")).removeData("layoutParent").removeData("layoutRole").removeData("layoutEdge").removeData("autoHidden").unbind("."+sID);if(!$P.data("layoutContainer"))$P.css($P.data("layoutCSS")).removeData("layoutCSS");if($C&&$C.length&&!$C.data("layoutContainer"))$C.css($C.data("layoutCSS")).removeData("layoutCSS")}if($T&&
$T.length)$T.remove();if($R&&$R.length)$R.remove();$Ps[pane]=$Cs[pane]=$Rs[pane]=$Ts[pane]=false;if(!skipResize){resizeAll();state[pane]={}}};var hide=function(pane,noAnimation){var o=options[pane],s=state[pane],$P=$Ps[pane],$R=$Rs[pane];if(!$P||s.isHidden)return;if(state.initialized&&false===_execCallback(pane,o.onhide_start))return;s.isSliding=false;if($R)$R.hide();if(!state.initialized||s.isClosed){s.isClosed=true;s.isHidden=true;s.isVisible=false;$P.hide();sizeMidPanes(_c[pane].dir=="horz"?"all":
"center");if(state.initialized||o.triggerEventsOnLoad)_execCallback(pane,o.onhide_end||o.onhide)}else{s.isHiding=true;close(pane,false,noAnimation)}};var show=function(pane,openPane,noAnimation,noAlert){var o=options[pane],s=state[pane],$P=$Ps[pane],$R=$Rs[pane];if(!$P||!s.isHidden)return;if(false===_execCallback(pane,o.onshow_start))return;s.isSliding=false;s.isShowing=true;if(openPane===false)close(pane,true);else open(pane,false,noAnimation,noAlert)};var toggle=function(pane,slide){if(!isStr(pane)){pane.stopImmediatePropagation();
pane=$(this).data("layoutEdge")}var s=state[str(pane)];if(s.isHidden)show(pane);else if(s.isClosed)open(pane,!!slide);else close(pane)};var _closePane=function(pane,setHandles){var $P=$Ps[pane],s=state[pane];$P.hide();s.isClosed=true;s.isVisible=false};var close=function(pane,force,noAnimation,skipCallback){if(!state.initialized){_closePane(pane);return}var $P=$Ps[pane],$R=$Rs[pane],$T=$Ts[pane],o=options[pane],s=state[pane],doFX=!noAnimation&&!s.isClosed&&o.fxName_close!="none",isShowing=s.isShowing,
isHiding=s.isHiding,wasSliding=s.isSliding;delete s.isShowing;delete s.isHiding;if(!$P||!o.closable&&!isShowing&&!isHiding)return;else if(!force&&s.isClosed&&!isShowing)return;if(_c.isLayoutBusy){_queue("close",pane,force);return}if(!isShowing&&false===_execCallback(pane,o.onclose_start))return;_c[pane].isMoving=true;_c.isLayoutBusy=true;s.isClosed=true;s.isVisible=false;if(isHiding)s.isHidden=true;else if(isShowing)s.isHidden=false;if(s.isSliding)bindStopSlidingEvents(pane,false);else sizeMidPanes(_c[pane].dir==
"horz"?"all":"center",false);setAsClosed(pane);if(doFX){lockPaneForFX(pane,true);$P.hide(o.fxName_close,o.fxSettings_close,o.fxSpeed_close,function(){lockPaneForFX(pane,false);close_2()})}else{$P.hide();close_2()}function close_2(){if(s.isClosed){bindStartSlidingEvent(pane,true);var altPane=_c.altSide[pane];if(state[altPane].noRoom){setSizeLimits(altPane);makePaneFit(altPane)}if(!skipCallback&&(state.initialized||o.triggerEventsOnLoad)){if(!isShowing)_execCallback(pane,o.onclose_end||o.onclose);if(isShowing)_execCallback(pane,
o.onshow_end||o.onshow);if(isHiding)_execCallback(pane,o.onhide_end||o.onhide)}}_dequeue(pane)}};var setAsClosed=function(pane){var $P=$Ps[pane],$R=$Rs[pane],$T=$Ts[pane],o=options[pane],s=state[pane],side=_c[pane].side.toLowerCase(),inset="inset"+_c[pane].side,rClass=o.resizerClass,tClass=o.togglerClass,_pane="-"+pane,_open="-open",_sliding="-sliding",_closed="-closed";$R.css(side,sC[inset]).removeClass(rClass+_open+" "+rClass+_pane+_open).removeClass(rClass+_sliding+" "+rClass+_pane+_sliding).addClass(rClass+
_closed+" "+rClass+_pane+_closed).unbind("dblclick."+sID);if(o.resizable&&typeof $.fn.draggable=="function")$R.draggable("disable").removeClass("ui-state-disabled").css("cursor","default").attr("title","");if($T){$T.removeClass(tClass+_open+" "+tClass+_pane+_open).addClass(tClass+_closed+" "+tClass+_pane+_closed).attr("title",o.togglerTip_closed);$T.children(".content-open").hide();$T.children(".content-closed").css("display","block")}syncPinBtns(pane,false);if(state.initialized)sizeHandles("all")};
var open=function(pane,slide,noAnimation,noAlert){var $P=$Ps[pane],$R=$Rs[pane],$T=$Ts[pane],o=options[pane],s=state[pane],doFX=!noAnimation&&s.isClosed&&o.fxName_open!="none",isShowing=s.isShowing;delete s.isShowing;if(!$P||!o.resizable&&!o.closable&&!isShowing)return;else if(s.isVisible&&!s.isSliding)return;if(s.isHidden&&!isShowing){show(pane,true);return}if(_c.isLayoutBusy){_queue("open",pane,slide);return}setSizeLimits(pane,slide);if(false===_execCallback(pane,o.onopen_start))return;if(s.minSize>
s.maxSize){syncPinBtns(pane,false);if(!noAlert&&o.noRoomToOpenTip)alert(o.noRoomToOpenTip);return}_c[pane].isMoving=true;_c.isLayoutBusy=true;if(slide)bindStopSlidingEvents(pane,true);else if(s.isSliding)bindStopSlidingEvents(pane,false);else if(o.slidable)bindStartSlidingEvent(pane,false);s.noRoom=false;makePaneFit(pane);s.isVisible=true;s.isClosed=false;if(isShowing)s.isHidden=false;if(doFX){lockPaneForFX(pane,true);$P.show(o.fxName_open,o.fxSettings_open,o.fxSpeed_open,function(){lockPaneForFX(pane,
false);open_2()})}else{$P.show();open_2()}function open_2(){if(s.isVisible){_fixIframe(pane);if(!s.isSliding)sizeMidPanes(_c[pane].dir=="vert"?"center":"all",false);setAsOpen(pane)}_dequeue(pane)}};var setAsOpen=function(pane,skipCallback){var $P=$Ps[pane],$R=$Rs[pane],$T=$Ts[pane],o=options[pane],s=state[pane],side=_c[pane].side.toLowerCase(),inset="inset"+_c[pane].side,rClass=o.resizerClass,tClass=o.togglerClass,_pane="-"+pane,_open="-open",_closed="-closed",_sliding="-sliding";$R.css(side,sC[inset]+
getPaneSize(pane)).removeClass(rClass+_closed+" "+rClass+_pane+_closed).addClass(rClass+_open+" "+rClass+_pane+_open);if(s.isSliding)$R.addClass(rClass+_sliding+" "+rClass+_pane+_sliding);else $R.removeClass(rClass+_sliding+" "+rClass+_pane+_sliding);if(o.resizerDblClickToggle)$R.bind("dblclick",toggle);removeHover(0,$R);if(o.resizable&&typeof $.fn.draggable=="function")$R.draggable("enable").css("cursor",o.resizerCursor).attr("title",o.resizerTip);else if(!s.isSliding)$R.css("cursor","default");
if($T){$T.removeClass(tClass+_closed+" "+tClass+_pane+_closed).addClass(tClass+_open+" "+tClass+_pane+_open).attr("title",o.togglerTip_open);removeHover(0,$T);$T.children(".content-closed").hide();$T.children(".content-open").css("display","block")}syncPinBtns(pane,!s.isSliding);$.extend(s,getElemDims($P));if(state.initialized){sizeHandles("all");sizeContent(pane,true)}if(!skipCallback&&(state.initialized||o.triggerEventsOnLoad)&&$P.is(":visible")){_execCallback(pane,o.onopen_end||o.onopen);if(s.isShowing)_execCallback(pane,
o.onshow_end||o.onshow);if(state.initialized){_execCallback(pane,o.onresize_end||o.onresize);resizeNestedLayout(pane)}}};var slideOpen=function(evt_or_pane){var evt=isStr(evt_or_pane)?null:evt_or_pane,pane=evt?$(this).data("layoutEdge"):evt_or_pane,s=state[pane],delay=options[pane].slideDelay_open;if(evt)evt.stopImmediatePropagation();if(s.isClosed&&evt&&evt.type=="mouseenter"&&delay>0)timer.set(pane+"_openSlider",open_NOW,delay);else open_NOW();function open_NOW(evt){if(!s.isClosed)bindStopSlidingEvents(pane,
true);else if(!_c[pane].isMoving)open(pane,true)}};var slideClose=function(evt_or_pane){var evt=isStr(evt_or_pane)?null:evt_or_pane,pane=evt?$(this).data("layoutEdge"):evt_or_pane,o=options[pane],s=state[pane],delay=_c[pane].isMoving?1E3:300;if(s.isClosed||s.isResizing)return;else if(o.slideTrigger_close=="click")close_NOW();else if(o.preventQuickSlideClose&&_c.isLayoutBusy)return;else if(o.preventPrematureSlideClose&&evt&&$.layout.isMouseOverElem(evt,$Ps[pane]))return;else if(evt)timer.set(pane+
"_closeSlider",close_NOW,max(o.slideDelay_close,delay));else close_NOW();function close_NOW(){if(s.isClosed)bindStopSlidingEvents(pane,false);else if(!_c[pane].isMoving)close(pane)}};var slideToggle=function(pane){toggle(pane,true)};var lockPaneForFX=function(pane,doLock){var $P=$Ps[pane];if(doLock){$P.css({zIndex:_c.zIndex.pane_animate});if(pane=="south")$P.css({top:sC.insetTop+sC.innerHeight-$P.outerHeight()});else if(pane=="east")$P.css({left:sC.insetLeft+sC.innerWidth-$P.outerWidth()})}else{$P.css({zIndex:state[pane].isSliding?
_c.zIndex.pane_sliding:_c.zIndex.pane_normal});if(pane=="south")$P.css({top:"auto"});else if(pane=="east")$P.css({left:"auto"});var o=options[pane];if(state.browser.msie&&o.fxOpacityFix&&o.fxName_open!="slide"&&$P.css("filter")&&$P.css("opacity")==1)$P[0].style.removeAttribute("filter")}};var bindStartSlidingEvent=function(pane,enable){var o=options[pane],$P=$Ps[pane],$R=$Rs[pane],trigger=o.slideTrigger_open.toLowerCase();if(!$R||enable&&!o.slidable)return;if(trigger.match(/mouseover/))trigger=o.slideTrigger_open=
"mouseenter";else if(!trigger.match(/click|dblclick|mouseenter/))trigger=o.slideTrigger_open="click";$R[enable?"bind":"unbind"](trigger+"."+sID,slideOpen).css("cursor",enable?o.sliderCursor:"default").attr("title",enable?o.sliderTip:"")};var bindStopSlidingEvents=function(pane,enable){var o=options[pane],s=state[pane],z=_c.zIndex,trigger=o.slideTrigger_close.toLowerCase(),action=enable?"bind":"unbind",$P=$Ps[pane],$R=$Rs[pane];s.isSliding=enable;timer.clear(pane+"_closeSlider");if(enable)bindStartSlidingEvent(pane,
false);$P.css("zIndex",enable?z.pane_sliding:z.pane_normal);$R.css("zIndex",enable?z.pane_sliding:z.resizer_normal);if(!trigger.match(/click|mouseleave/))trigger=o.slideTrigger_close="mouseleave";$R[action](trigger,slideClose);if(trigger=="mouseleave"){$P[action]("mouseleave."+sID,slideClose);$R[action]("mouseenter."+sID,cancelMouseOut);$P[action]("mouseenter."+sID,cancelMouseOut)}if(!enable)timer.clear(pane+"_closeSlider");else if(trigger=="click"&&!o.resizable){$R.css("cursor",enable?o.sliderCursor:
"default");$R.attr("title",enable?o.togglerTip_open:"")}function cancelMouseOut(evt){timer.clear(pane+"_closeSlider");evt.stopPropagation()}};var makePaneFit=function(pane,isOpening,skipCallback,force){var o=options[pane],s=state[pane],c=_c[pane],$P=$Ps[pane],$R=$Rs[pane],isSidePane=c.dir=="vert",hasRoom=false;if(pane=="center"||isSidePane&&s.noVerticalRoom){hasRoom=s.maxHeight>0;if(hasRoom&&s.noRoom){$P.show();if($R)$R.show();s.isVisible=true;s.noRoom=false;if(isSidePane)s.noVerticalRoom=false;_fixIframe(pane)}else if(!hasRoom&&
!s.noRoom){$P.hide();if($R)$R.hide();s.isVisible=false;s.noRoom=true}}if(pane=="center");else if(s.minSize<=s.maxSize){hasRoom=true;if(s.size>s.maxSize)sizePane(pane,s.maxSize,skipCallback,force);else if(s.size<s.minSize)sizePane(pane,s.minSize,skipCallback,force);else if($R&&$P.is(":visible")){var side=c.side.toLowerCase(),pos=s.size+sC["inset"+c.side];if(_cssNum($R,side)!=pos)$R.css(side,pos)}if(s.noRoom)if(s.wasOpen&&o.closable)if(o.autoReopen)open(pane,false,true,true);else s.noRoom=false;else show(pane,
s.wasOpen,true,true)}else if(!s.noRoom){s.noRoom=true;s.wasOpen=!s.isClosed&&!s.isSliding;if(s.isClosed);else if(o.closable)close(pane,true,true);else hide(pane,true)}};var manualSizePane=function(pane,size,skipCallback){var o=options[pane],forceResize=o.resizeWhileDragging&&!_c.isLayoutBusy;o.autoResize=false;sizePane(pane,size,skipCallback,forceResize)};var sizePane=function(pane,size,skipCallback,force){var o=options[pane],s=state[pane],$P=$Ps[pane],$R=$Rs[pane],side=_c[pane].side.toLowerCase(),
inset="inset"+_c[pane].side,skipResizeWhileDragging=_c.isLayoutBusy&&!o.triggerEventsWhileDragging,oldSize;setSizeLimits(pane);oldSize=s.size;size=_parseSize(pane,size);size=max(size,_parseSize(pane,o.minSize));size=min(size,s.maxSize);if(size<s.minSize){makePaneFit(pane,false,skipCallback);return}if(!force&&size==oldSize)return;if(!skipCallback&&state.initialized&&s.isVisible)_execCallback(pane,o.onresize_start);$P.css(_c[pane].sizeType.toLowerCase(),max(1,cssSize(pane,size)));s.size=size;$.extend(s,
getElemDims($P));if($R&&$P.is(":visible"))$R.css(side,size+sC[inset]);sizeContent(pane);if(!skipCallback&&!skipResizeWhileDragging&&state.initialized&&s.isVisible){_execCallback(pane,o.onresize_end||o.onresize);resizeNestedLayout(pane)}if(!skipCallback){if(!s.isSliding)sizeMidPanes(_c[pane].dir=="horz"?"all":"center",skipResizeWhileDragging,force);sizeHandles("all")}var altPane=_c.altSide[pane];if(size<oldSize&&state[altPane].noRoom){setSizeLimits(altPane);makePaneFit(altPane,false,skipCallback)}};
var sizeMidPanes=function(panes,skipCallback,force){if(!panes||panes=="all")panes="east,west,center";$.each(panes.split(","),function(i,pane){if(!$Ps[pane])return;var o=options[pane],s=state[pane],$P=$Ps[pane],$R=$Rs[pane],isCenter=pane=="center",hasRoom=true,CSS={},d=calcNewCenterPaneDims();$.extend(s,getElemDims($P));if(pane=="center"){if(!force&&s.isVisible&&d.width==s.outerWidth&&d.height==s.outerHeight)return true;$.extend(s,cssMinDims(pane),{maxWidth:d.width,maxHeight:d.height});CSS=d;CSS.width=
cssW(pane,d.width);CSS.height=cssH(pane,d.height);hasRoom=CSS.width>0&&CSS.height>0;if(!hasRoom&&!state.initialized&&o.minWidth>0){var reqPx=o.minWidth-s.outerWidth,minE=options.east.minSize||0,minW=options.west.minSize||0,sizeE=state.east.size,sizeW=state.west.size,newE=sizeE,newW=sizeW;if(reqPx>0&&state.east.isVisible&&sizeE>minE){newE=max(sizeE-minE,sizeE-reqPx);reqPx-=sizeE-newE}if(reqPx>0&&state.west.isVisible&&sizeW>minW){newW=max(sizeW-minW,sizeW-reqPx);reqPx-=sizeW-newW}if(reqPx==0){if(sizeE!=
minE)sizePane("east",newE,true);if(sizeW!=minW)sizePane("west",newW,true);sizeMidPanes("center",skipCallback,force);return}}}else{if(s.isVisible&&!s.noVerticalRoom)$.extend(s,getElemDims($P),cssMinDims(pane));if(!force&&!s.noVerticalRoom&&d.height==s.outerHeight)return true;CSS.top=d.top;CSS.bottom=d.bottom;CSS.height=cssH(pane,d.height);s.maxHeight=max(0,CSS.height);hasRoom=s.maxHeight>0;if(!hasRoom)s.noVerticalRoom=true}if(hasRoom){if(!skipCallback&&state.initialized)_execCallback(pane,o.onresize_start);
$P.css(CSS);if(s.noRoom&&!s.isClosed&&!s.isHidden)makePaneFit(pane);if(s.isVisible){$.extend(s,getElemDims($P));if(state.initialized)sizeContent(pane)}}else if(!s.noRoom&&s.isVisible)makePaneFit(pane);if(!s.isVisible)return true;if(pane=="center"){var b=state.browser;var fix=b.isIE6||b.msie&&!b.boxModel;if($Ps.north&&(fix||state.north.tagName=="IFRAME"))$Ps.north.css("width",cssW($Ps.north,sC.innerWidth));if($Ps.south&&(fix||state.south.tagName=="IFRAME"))$Ps.south.css("width",cssW($Ps.south,sC.innerWidth))}if(!skipCallback&&
state.initialized){_execCallback(pane,o.onresize_end||o.onresize);resizeNestedLayout(pane)}})};var resizeAll=function(){var oldW=sC.innerWidth,oldH=sC.innerHeight;$.extend(state.container,getElemDims($Container));if(!sC.outerHeight)return;if(false===_execCallback(null,options.onresizeall_start))return false;var shrunkH=sC.innerHeight<oldH,shrunkW=sC.innerWidth<oldW,$P,o,s,dir;$.each(["south","north","east","west"],function(i,pane){if(!$Ps[pane])return;s=state[pane];o=options[pane];dir=_c[pane].dir;
if(o.autoResize&&s.size!=o.size)sizePane(pane,o.size,true,true);else{setSizeLimits(pane);makePaneFit(pane,false,true,true)}});sizeMidPanes("all",true,true);sizeHandles("all");o=options;$.each(_c.allPanes.split(","),function(i,pane){$P=$Ps[pane];if(!$P)return;if(state[pane].isVisible){_execCallback(pane,o[pane].onresize_end||o[pane].onresize);resizeNestedLayout(pane)}});_execCallback(null,o.onresizeall_end||o.onresizeall)};var resizeNestedLayout=function(pane){var $P=$Ps[pane],$C=$Cs[pane],d="layoutContainer";
if(options[pane].resizeNestedLayout)if($P.data(d))$P.layout().resizeAll();else if($C&&$C.data(d))$C.layout().resizeAll()};var sizeContent=function(panes,remeasure){if(!panes||panes=="all")panes=_c.allPanes;$.each(panes.split(","),function(idx,pane){var $P=$Ps[pane],$C=$Cs[pane],o=options[pane],s=state[pane],m=s.content;if(!$P||!$C||!$P.is(":visible"))return true;if(false===_execCallback(null,o.onsizecontent_start))return;if(!_c.isLayoutBusy||m.top==undefined||remeasure||o.resizeContentWhileDragging){_measure();
if(m.hiddenFooters>0&&$P.css("overflow")=="hidden"){$P.css("overflow","visible");_measure();$P.css("overflow","hidden")}}var newH=s.innerHeight-(m.spaceAbove-s.css.paddingTop)-(m.spaceBelow-s.css.paddingBottom);if(!$C.is(":visible")||m.height!=newH){setOuterHeight($C,newH,true);m.height=newH}if(state.initialized){_execCallback(pane,o.onsizecontent_end||o.onsizecontent);resizeNestedLayout(pane)}function _below($E){return max(s.css.paddingBottom,parseInt($E.css("marginBottom"),10)||0)}function _measure(){var ignore=
options[pane].contentIgnoreSelector,$Fs=$C.nextAll().not(ignore||":lt(0)"),$Fs_vis=$Fs.filter(":visible"),$F=$Fs_vis.filter(":last");m={top:$C[0].offsetTop,height:$C.outerHeight(),numFooters:$Fs.length,hiddenFooters:$Fs.length-$Fs_vis.length,spaceBelow:0};m.spaceAbove=m.top;m.bottom=m.top+m.height;if($F.length)m.spaceBelow=$F[0].offsetTop+$F.outerHeight()-m.bottom+_below($F);else m.spaceBelow=_below($C)}})};var sizeHandles=function(panes){if(!panes||panes=="all")panes=_c.borderPanes;$.each(panes.split(","),
function(i,pane){var o=options[pane],s=state[pane],$P=$Ps[pane],$R=$Rs[pane],$T=$Ts[pane],$TC;if(!$P||!$R)return;var dir=_c[pane].dir,_state=s.isClosed?"_closed":"_open",spacing=o["spacing"+_state],togAlign=o["togglerAlign"+_state],togLen=o["togglerLength"+_state],paneLen,offset,CSS={};if(spacing==0){$R.hide();return}else if(!s.noRoom&&!s.isHidden)$R.show();if(dir=="horz"){paneLen=$P.outerWidth();s.resizerLength=paneLen;$R.css({width:max(1,cssW($R,paneLen)),height:max(0,cssH($R,spacing)),left:_cssNum($P,
"left")})}else{paneLen=$P.outerHeight();s.resizerLength=paneLen;$R.css({height:max(1,cssH($R,paneLen)),width:max(0,cssW($R,spacing)),top:sC.insetTop+getPaneSize("north",true)})}removeHover(o,$R);if($T){if(togLen==0||s.isSliding&&o.hideTogglerOnSlide){$T.hide();return}else $T.show();if(!(togLen>0)||togLen=="100%"||togLen>paneLen){togLen=paneLen;offset=0}else if(isStr(togAlign))switch(togAlign){case "top":case "left":offset=0;break;case "bottom":case "right":offset=paneLen-togLen;break;case "middle":case "center":default:offset=
Math.floor((paneLen-togLen)/2)}else{var x=parseInt(togAlign,10);if(togAlign>=0)offset=x;else offset=paneLen-togLen+x}if(dir=="horz"){var width=cssW($T,togLen);$T.css({width:max(0,width),height:max(1,cssH($T,spacing)),left:offset,top:0});$T.children(".content").each(function(){$TC=$(this);$TC.css("marginLeft",Math.floor((width-$TC.outerWidth())/2))})}else{var height=cssH($T,togLen);$T.css({height:max(0,height),width:max(1,cssW($T,spacing)),top:offset,left:0});$T.children(".content").each(function(){$TC=
$(this);$TC.css("marginTop",Math.floor((height-$TC.outerHeight())/2))})}removeHover(0,$T)}if(!state.initialized&&(o.initHidden||s.noRoom)){$R.hide();if($T)$T.hide()}})};var enableClosable=function(pane){var $T=$Ts[pane],o=options[pane];if(!$T)return;o.closable=true;$T.bind("click."+sID,function(evt){evt.stopPropagation();toggle(pane)}).bind("mouseenter."+sID,addHover).bind("mouseleave."+sID,removeHover).css("visibility","visible").css("cursor","pointer").attr("title",state[pane].isClosed?o.togglerTip_closed:
o.togglerTip_open).show()};var disableClosable=function(pane,hide){var $T=$Ts[pane];if(!$T)return;options[pane].closable=false;if(state[pane].isClosed)open(pane,false,true);$T.unbind("."+sID).css("visibility",hide?"hidden":"visible").css("cursor","default").attr("title","")};var enableSlidable=function(pane){var $R=$Rs[pane],o=options[pane];if(!$R||!$R.data("draggable"))return;options[pane].slidable=true;if(s.isClosed)bindStartSlidingEvent(pane,true)};var disableSlidable=function(pane){var $R=$Rs[pane];
if(!$R)return;options[pane].slidable=false;if(state[pane].isSliding)close(pane,false,true);else{bindStartSlidingEvent(pane,false);$R.css("cursor","default").attr("title","");removeHover(null,$R[0])}};var enableResizable=function(pane){var $R=$Rs[pane],o=options[pane];if(!$R||!$R.data("draggable"))return;o.resizable=true;$R.draggable("enable").bind("mouseenter."+sID,onResizerEnter).bind("mouseleave."+sID,onResizerLeave);if(!state[pane].isClosed)$R.css("cursor",o.resizerCursor).attr("title",o.resizerTip)};
var disableResizable=function(pane){var $R=$Rs[pane];if(!$R||!$R.data("draggable"))return;options[pane].resizable=false;$R.draggable("disable").unbind("."+sID).css("cursor","default").attr("title","");removeHover(null,$R[0])};var swapPanes=function(pane1,pane2){state[pane1].edge=pane2;state[pane2].edge=pane1;var cancelled=false;if(false===_execCallback(pane1,options[pane1].onswap_start))cancelled=true;if(!cancelled&&false===_execCallback(pane2,options[pane2].onswap_start))cancelled=true;if(cancelled){state[pane1].edge=
pane1;state[pane2].edge=pane2;return}var oPane1=copy(pane1),oPane2=copy(pane2),sizes={};sizes[pane1]=oPane1?oPane1.state.size:0;sizes[pane2]=oPane2?oPane2.state.size:0;$Ps[pane1]=false;$Ps[pane2]=false;state[pane1]={};state[pane2]={};if($Ts[pane1])$Ts[pane1].remove();if($Ts[pane2])$Ts[pane2].remove();if($Rs[pane1])$Rs[pane1].remove();if($Rs[pane2])$Rs[pane2].remove();$Rs[pane1]=$Rs[pane2]=$Ts[pane1]=$Ts[pane2]=false;move(oPane1,pane2);move(oPane2,pane1);oPane1=oPane2=sizes=null;if($Ps[pane1])$Ps[pane1].css(_c.visible);
if($Ps[pane2])$Ps[pane2].css(_c.visible);resizeAll();_execCallback(pane1,options[pane1].onswap_end||options[pane1].onswap);_execCallback(pane2,options[pane2].onswap_end||options[pane2].onswap);return;function copy(n){var $P=$Ps[n],$C=$Cs[n];return!$P?false:{pane:n,P:$P?$P[0]:false,C:$C?$C[0]:false,state:$.extend({},state[n]),options:$.extend({},options[n])}}function move(oPane,pane){if(!oPane)return;var P=oPane.P,C=oPane.C,oldPane=oPane.pane,c=_c[pane],side=c.side.toLowerCase(),inset="inset"+c.side,
s=$.extend({},state[pane]),o=options[pane],fx={resizerCursor:o.resizerCursor},re,size,pos;$.each("fxName,fxSpeed,fxSettings".split(","),function(i,k){fx[k]=o[k];fx[k+"_open"]=o[k+"_open"];fx[k+"_close"]=o[k+"_close"]});$Ps[pane]=$(P).data("layoutEdge",pane).css(_c.hidden).css(c.cssReq);$Cs[pane]=C?$(C):false;options[pane]=$.extend({},oPane.options,fx);state[pane]=$.extend({},oPane.state);re=new RegExp(o.paneClass+"-"+oldPane,"g");P.className=P.className.replace(re,o.paneClass+"-"+pane);initHandles(pane);
if(c.dir!=_c[oldPane].dir){size=sizes[pane]||0;setSizeLimits(pane);size=max(size,state[pane].minSize);manualSizePane(pane,size,true)}else $Rs[pane].css(side,sC[inset]+(state[pane].isVisible?getPaneSize(pane):0));if(oPane.state.isVisible&&!s.isVisible)setAsOpen(pane,true);else{setAsClosed(pane);bindStartSlidingEvent(pane,true)}oPane=null}};function keyDown(evt){if(!evt)return true;var code=evt.keyCode;if(code<33)return true;var PANE={38:"north",40:"south",37:"west",39:"east"},ALT=evt.altKey,SHIFT=
evt.shiftKey,CTRL=evt.ctrlKey,CURSOR=CTRL&&code>=37&&code<=40,o,k,m,pane;if(CURSOR&&options[PANE[code]].enableCursorHotkey)pane=PANE[code];else if(CTRL||SHIFT)$.each(_c.borderPanes.split(","),function(i,p){o=options[p];k=o.customHotkey;m=o.customHotkeyModifier;if(SHIFT&&m=="SHIFT"||CTRL&&m=="CTRL"||CTRL&&SHIFT)if(k&&code==(isNaN(k)||k<=9?k.toUpperCase().charCodeAt(0):k)){pane=p;return false}});if(!pane||!$Ps[pane]||!options[pane].closable||state[pane].isHidden)return true;toggle(pane);evt.stopPropagation();
evt.returnValue=false;return false}function allowOverflow(el){if(this&&this.tagName)el=this;var $P;if(isStr(el))$P=$Ps[el];else if($(el).data("layoutRole"))$P=$(el);else $(el).parents().each(function(){if($(this).data("layoutRole")){$P=$(this);return false}});if(!$P||!$P.length)return;var pane=$P.data("layoutEdge"),s=state[pane];if(s.cssSaved)resetOverflow(pane);if(s.isSliding||s.isResizing||s.isClosed){s.cssSaved=false;return}var newCSS={zIndex:_c.zIndex.pane_normal+2},curCSS={},of=$P.css("overflow"),
ofX=$P.css("overflowX"),ofY=$P.css("overflowY");if(of!="visible"){curCSS.overflow=of;newCSS.overflow="visible"}if(ofX&&!ofX.match(/visible|auto/)){curCSS.overflowX=ofX;newCSS.overflowX="visible"}if(ofY&&!ofY.match(/visible|auto/)){curCSS.overflowY=ofX;newCSS.overflowY="visible"}s.cssSaved=curCSS;$P.css(newCSS);$.each(_c.allPanes.split(","),function(i,p){if(p!=pane)resetOverflow(p)})}function resetOverflow(el){if(this&&this.tagName)el=this;var $P;if(isStr(el))$P=$Ps[el];else if($(el).data("layoutRole"))$P=
$(el);else $(el).parents().each(function(){if($(this).data("layoutRole")){$P=$(this);return false}});if(!$P||!$P.length)return;var pane=$P.data("layoutEdge"),s=state[pane],CSS=s.cssSaved||{};if(!s.isSliding&&!s.isResizing)$P.css("zIndex",_c.zIndex.pane_normal);$P.css(CSS);s.cssSaved=false}function getBtn(selector,pane,action){var $E=$(selector);if(!$E.length)alert(lang.errButton+lang.selector+": "+selector);else if(_c.borderPanes.indexOf(pane)==-1)alert(lang.errButton+lang.Pane.toLowerCase()+": "+
pane);else{var btn=options[pane].buttonClass+"-"+action;$E.addClass(btn+" "+btn+"-"+pane).data("layoutName",options.name);return $E}return null}function bindButton(selector,action,pane){switch(action.toLowerCase()){case "toggle":addToggleBtn(selector,pane);break;case "open":addOpenBtn(selector,pane);break;case "close":addCloseBtn(selector,pane);break;case "pin":addPinBtn(selector,pane);break;case "toggle-slide":addToggleBtn(selector,pane,true);break;case "open-slide":addOpenBtn(selector,pane,true);
break}}function addToggleBtn(selector,pane,slide){var $E=getBtn(selector,pane,"toggle");if($E)$E.click(function(evt){toggle(pane,!!slide);evt.stopPropagation()})}function addOpenBtn(selector,pane,slide){var $E=getBtn(selector,pane,"open");if($E)$E.attr("title",lang.Open).click(function(evt){open(pane,!!slide);evt.stopPropagation()})}function addCloseBtn(selector,pane){var $E=getBtn(selector,pane,"close");if($E)$E.attr("title",lang.Close).click(function(evt){close(pane);evt.stopPropagation()})}function addPinBtn(selector,
pane){var $E=getBtn(selector,pane,"pin");if($E){var s=state[pane];$E.click(function(evt){setPinState($(this),pane,s.isSliding||s.isClosed);if(s.isSliding||s.isClosed)open(pane);else close(pane);evt.stopPropagation()});setPinState($E,pane,!s.isClosed&&!s.isSliding);_c[pane].pins.push(selector)}}function syncPinBtns(pane,doPin){$.each(_c[pane].pins,function(i,selector){setPinState($(selector),pane,doPin)})}function setPinState($Pin,pane,doPin){var updown=$Pin.attr("pin");if(updown&&doPin==(updown==
"down"))return;var pin=options[pane].buttonClass+"-pin",side=pin+"-"+pane,UP=pin+"-up "+side+"-up",DN=pin+"-down "+side+"-down";$Pin.attr("pin",doPin?"down":"up").attr("title",doPin?lang.Unpin:lang.Pin).removeClass(doPin?UP:DN).addClass(doPin?DN:UP)}function isCookiesEnabled(){return navigator.cookieEnabled!=0}function getCookie(opts){var o=$.extend({},options.cookie,opts||{}),name=o.name||options.name||"Layout",c=document.cookie,cs=c?c.split(";"):[],pair;for(var i=0,n=cs.length;i<n;i++){pair=$.trim(cs[i]).split("=");
if(pair[0]==name)return decodeJSON(decodeURIComponent(pair[1]))}return""}function saveCookie(keys,opts){var o=$.extend({},options.cookie,opts||{}),name=o.name||options.name||"Layout",params="",date="",clear=false;if(o.expires.toUTCString)date=o.expires;else if(typeof o.expires=="number"){date=new Date;if(o.expires>0)date.setDate(date.getDate()+o.expires);else{date.setYear(1970);clear=true}}if(date)params+=";expires="+date.toUTCString();if(o.path)params+=";path="+o.path;if(o.domain)params+=";domain="+
o.domain;if(o.secure)params+=";secure";if(clear){state.cookie={};document.cookie=name+"="+params}else{state.cookie=getState(keys||o.keys);document.cookie=name+"="+encodeURIComponent(encodeJSON(state.cookie))+params}return $.extend({},state.cookie)}function deleteCookie(){saveCookie("",{expires:-1})}function loadCookie(opts){var o=getCookie(opts);if(o){state.cookie=$.extend({},o);loadState(o)}return o}function loadState(opts,animate){$.extend(true,options,opts);if(state.initialized){var pane,o,v,a=
!animate;$.each(_c.allPanes.split(","),function(idx,pane){o=opts[pane];if(typeof o!="object")return;v=o.initHidden;if(v===true)hide(pane,a);if(v===false)show(pane,false,a);v=o.size;if(v>0)sizePane(pane,v);v=o.initClosed;if(v===true)close(pane,false,a);if(v===false)open(pane,false,a)})}}function getState(keys){var data={},alt={isClosed:"initClosed",isHidden:"initHidden"},pair,pane,key,val;if(!keys)keys=options.cookie.keys;if($.isArray(keys))keys=keys.join(",");keys=keys.replace(/__/g,".").split(",");
for(var i=0,n=keys.length;i<n;i++){pair=keys[i].split(".");pane=pair[0];key=pair[1];if(_c.allPanes.indexOf(pane)<0)continue;val=state[pane][key];if(val==undefined)continue;if(key=="isClosed"&&state[pane]["isSliding"])val=true;(data[pane]||(data[pane]={}))[alt[key]?alt[key]:key]=val}return data}function encodeJSON(JSON){return parse(JSON);function parse(h){var D=[],i=0,k,v,t;for(k in h){v=h[k];t=typeof v;if(t=="string")v='"'+v+'"';else if(t=="object")v=parse(v);D[i++]='"'+k+'":'+v}return"{"+D.join(",")+
"}"}}function decodeJSON(str){try{return window["eval"]("("+str+")")||{}}catch(e){return{}}}var $Container=$(this).eq(0);if(!$Container.length)return null;if($Container.data("layoutContainer")&&$Container.data("layout"))return $Container.data("layout");var $Ps={},$Cs={},$Rs={},$Ts={},sC=state.container,sID=state.id;var Instance={options:options,state:state,container:$Container,panes:$Ps,contents:$Cs,resizers:$Rs,togglers:$Ts,toggle:toggle,hide:hide,show:show,open:open,close:close,slideOpen:slideOpen,
slideClose:slideClose,slideToggle:slideToggle,initContent:initContent,sizeContent:sizeContent,sizePane:manualSizePane,swapPanes:swapPanes,resizeAll:resizeAll,destroy:destroy,addPane:addPane,removePane:removePane,setSizeLimits:setSizeLimits,bindButton:bindButton,addToggleBtn:addToggleBtn,addOpenBtn:addOpenBtn,addCloseBtn:addCloseBtn,addPinBtn:addPinBtn,allowOverflow:allowOverflow,resetOverflow:resetOverflow,encodeJSON:encodeJSON,decodeJSON:decodeJSON,getState:getState,getCookie:getCookie,saveCookie:saveCookie,
deleteCookie:deleteCookie,loadCookie:loadCookie,loadState:loadState,cssWidth:cssW,cssHeight:cssH,enableClosable:enableClosable,disableClosable:disableClosable,enableSlidable:enableSlidable,disableSlidable:disableSlidable,enableResizable:enableResizable,disableResizable:disableResizable};_create();return Instance}})(jQuery);

View File

@ -1,139 +0,0 @@
/*
* LAYOUT STATE MANAGEMENT
*
* @requires json2.js - http://www.json.org/json2.js
*
* @example layoutState.options = { layoutName: "myLayout", keys: "west.isClosed,east.isClosed" }
* @example layoutState.save( "myLayout", "west.isClosed,north.size,south.isHidden", {expires: 7} );
* @example layoutState.load( "myLayout" );
* @example layoutState.clear();
* @example var hash_SavedState = layoutState.load();
* @example var hash_SavedState = layoutState.data;
*/
var layoutState = {
options: {
layoutName: 'myLayout' // default name (optional)
// *** IMPORTANT *** specify your keys in same format as your layout options...
/* Sub-Key-Format State Options
, keys: 'north.size,south.size,east.size,west.size,' +
'north.isClosed,south.isClosed,east.isClosed,west.isClosed,' +
'north.isHidden,south.isHidden,east.isHidden,west.isHidden'
*/
// Flat-Format State Options
, keys: 'north__size,south__size,east__size,west__size,' +
'north__isClosed,south__isClosed,east__isClosed,west__isClosed,' +
'north__isHidden,south__isHidden,east__isHidden,west__isHidden'
// Cookie Options
, domain: ''
, path: ''
, expires: '' // 'days' to keep cookie - leave blank for 'session cookie'
, secure: false
}
, data: {}
, clear: function (layoutName) {
this.save( layoutName, 'dummyKey', { expires: -1 });
}
, save: function (layoutName, keys, opts) {
var
o = jQuery.extend( {}, this.options, opts||{} )
, layout = window[ layoutName || o.layoutName ]
;
if (!keys) keys = o.keys;
if (typeof keys == 'string') keys = keys.split(',');
if (!layout || !layout.state || !keys.length) return false;
var
isNum = typeof o.expires == 'number'
, date = new Date()
, params = ''
, clear = false
;
if (isNum || o.expires.toUTCString) {
if (isNum) {
if (o.expires <= 0) {
date.setYear(1970);
clear = true;
}
else
date.setTime(date.getTime() + (o.expires * 24 * 60 * 60 * 1000));
}
else
date = o.expires;
// use expires attribute, max-age is not supported by IE
params += ';expires='+ date.toUTCString();
}
if (o.path) params += ';path='+ o.path;
if (o.domain) params += ';domain='+ o.domain;
if (o.secure) params += ';secure';
if (clear) {
this.data = {}; // clear the data struct too
document.cookie = (layoutName || o.layoutName) +'='+ params;
}
else {
this.data = readState( layout, keys ); // read current panes-state
document.cookie = (layoutName || o.layoutName) +'='+ encodeURIComponent(JSON.stringify(this.data)) + params;
//alert( 'JSON.stringify(this.data) = '+ (layoutName || o.layoutName) +'='+ JSON.stringify( this.data ) );
}
return this.data;
// SUB-ROUTINE
function readState (layout, keys) {
var
state = layout.state // alias to the 'layout state'
, data = {}
, panes = 'north,south,east,west,center' // validation
, alt = { isClosed: 'initClosed', isHidden: 'initHidden' }
, delim = (keys[0].indexOf('__') > 0 ? '__' : '.')
, pair, pane, key, val
;
for (var i=0; i < keys.length; i++) {
pair = keys[i].split(delim);
pane = pair[0];
key = pair[1];
if (panes.indexOf(pane) < 0) continue; // bad pane!
if (key=='isClosed') // if 'temporarily open' (sliding), then isClosed=false, so...
val = state[ pane ][ key ] || state[ pane ][ 'isSliding' ];
else
val = state[ pane ][ key ];
if (val != undefined) {
if (delim=='.') { // sub-key format
if (!data[ pane ]) data[ pane ] = {};
data[ pane ][ alt[key] ? alt[key] : key ] = val;
}
else // delim = '__' - flat-format
data[ pane + delim + (alt[key] ? alt[key] : key) ] = val;
}
}
return data;
}
}
, load: function (layoutName) {
if (!layoutName) layoutName = this.options.layoutName;
if (!layoutName) return {};
var
data = {}
, c = document.cookie
, cs, pair, i // loop vars
;
if (c && c != '') {
cs = c.split(';');
for (i = 0; i < cs.length; i++) {
c = jQuery.trim(cs[i]);
pair = c.split('='); // name=value pair
if (pair[0] == layoutName) { // this is the layout cookie
data = JSON.parse(decodeURIComponent(pair[1]));
break; // DONE
}
}
}
return (this.data = data);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,150 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Nested Layouts</title>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="jquery.layout.js"></script>
<script type="text/javascript" src="jquery.ui.all.js"></script>
<script>
var outerLayout, middleLayout, innerLayout;
$(document).ready(function () {
outerLayout = $('body').layout({
center__paneSelector: ".outer-center"
, west__paneSelector: ".outer-west"
, east__paneSelector: ".outer-east"
, west__size: 125
, east__size: 125
, spacing_open: 8 // ALL panes
, spacing_closed: 12 // ALL panes
, north__spacing_open: 0
, south__spacing_open: 0
, center__onresize: "middleLayout.resizeAll"
});
middleLayout = $('div.outer-center').layout({
center__paneSelector: ".middle-center"
, west__paneSelector: ".middle-west"
, east__paneSelector: ".middle-east"
, west__size: 100
, east__size: 100
, spacing_open: 8 // ALL panes
, spacing_closed: 12 // ALL panes
, center__onresize: "innerLayout.resizeAll"
});
innerLayout = $('div.middle-center').layout({
center__paneSelector: ".inner-center"
, west__paneSelector: ".inner-west"
, east__paneSelector: ".inner-east"
, west__size: 75
, east__size: 75
, spacing_open: 8 // ALL panes
, spacing_closed: 8 // ALL panes
, west__spacing_closed: 12
, east__spacing_closed: 12
});
});
</script>
<style type="text/css">
.ui-layout-pane { /* all 'panes' */
padding: 10px;
background: #FFF;
border-top: 1px solid #BBB;
border-bottom: 1px solid #BBB;
}
.ui-layout-pane-north ,
.ui-layout-pane-south {
border: 1px solid #BBB;
}
.ui-layout-pane-west {
border-left: 1px solid #BBB;
}
.ui-layout-pane-east {
border-right: 1px solid #BBB;
}
.ui-layout-pane-center {
border-left: 0;
border-right: 0;
}
.inner-center {
border: 1px solid #BBB;
}
.outer-west ,
.outer-east {
background-color: #EEE;
}
.middle-west ,
.middle-east {
background-color: #F8F8F8;
}
.ui-layout-resizer { /* all 'resizer-bars' */
background: #DDD;
}
.ui-layout-resizer:hover { /* all 'resizer-bars' */
background: #FED;
}
.ui-layout-resizer-west {
border-left: 1px solid #BBB;
}
.ui-layout-resizer-east {
border-right: 1px solid #BBB;
}
.ui-layout-toggler { /* all 'toggler-buttons' */
background: #AAA;
}
.ui-layout-toggler:hover { /* all 'toggler-buttons' */
background: #FC3;
}
.outer-center ,
.middle-center {
/* center pane that are 'containers' for a nested layout */
padding: 0;
border: 0;
}
</style>
</head>
<body>
<div class="outer-center">
<div class="middle-center">
<div class="inner-center">Inner Center</div>
<div class="inner-west">Inner West</div>
<div class="inner-east">Inner East</div>
<div class="ui-layout-north">Inner North</div>
<div class="ui-layout-south">Inner South</div>
</div>
<div class="middle-west">Middle West</div>
<div class="middle-east">Middle East</div>
</div>
<div class="outer-west">Outer West</div>
<div class="outer-east">Outer East</div>
<div class="ui-layout-north">Outer North</div>
<div class="ui-layout-south">Outer South</div>
</body>
</html>

View File

@ -1,197 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Simple Layout Demo</title>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="jquery.ui.all.js"></script>
<script type="text/javascript" src="jquery.layout.js"></script>
<script type="text/javascript">
var myLayout; // a var is required because this page utilizes: myLayout.allowOverflow() method
$(document).ready(function () {
myLayout = $('body').layout({
// enable showOverflow on west-pane so popups will overlap north pane
west__showOverflowOnHover: true
//, west__fxSettings_open: { easing: "easeOutBounce", duration: 750 }
});
});
</script>
<style type="text/css">
/**
* Basic Layout Theme
*
* This theme uses the default layout class-names for all classes
* Add any 'custom class-names', from options: paneClass, resizerClass, togglerClass
*/
.ui-layout-pane { /* all 'panes' */
background: #FFF;
border: 1px solid #BBB;
padding: 10px;
overflow: auto;
}
.ui-layout-resizer { /* all 'resizer-bars' */
background: #DDD;
}
.ui-layout-toggler { /* all 'toggler-buttons' */
background: #AAA;
}
</style>
<style type="text/css">
/**
* ALL CSS below is only for cosmetic and demo purposes
* Nothing here affects the appearance of the layout
*/
body {
font-family: Arial, sans-serif;
font-size: 0.85em;
}
p {
margin: 1em 0;
}
/*
* Rules below are for simulated drop-down/pop-up lists
*/
ul {
/* rules common to BOTH inner and outer UL */
z-index: 100000;
margin: 1ex 0;
padding: 0;
list-style: none;
cursor: pointer;
border: 1px solid Black;
/* rules for outer UL only */
width: 15ex;
position: relative;
}
ul li {
background-color: #EEE;
padding: 0.15em 1em 0.3em 5px;
}
ul ul {
display: none;
position: absolute;
width: 100%;
left: -1px;
/* Pop-Up */
bottom: 0;
margin: 0;
margin-bottom: 1.55em;
}
.ui-layout-north ul ul {
/* Drop-Down */
bottom: auto;
margin: 0;
margin-top: 1.45em;
}
ul ul li { padding: 3px 1em 3px 5px; }
ul ul li:hover { background-color: #FF9; }
ul li:hover ul { display: block; background-color: #EEE; }
</style>
</head>
<body>
<!-- manually attach allowOverflow method to pane -->
<div class="ui-layout-north" onmouseover="myLayout.allowOverflow('north')" onmouseout="myLayout.resetOverflow(this)">
This is the north pane, closable, slidable and resizable
<ul>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
<li>five</li>
</ul>
Drop-Down <!-- put this below so IE and FF render the same! -->
</li>
</ul>
</div>
<!-- allowOverflow auto-attached by option: west__showOverflowOnHover = true -->
<div class="ui-layout-west">
This is the west pane, closable, slidable and resizable
<ul>
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
<li>five</li>
</ul>
Pop-Up <!-- put this below so IE and FF render the same! -->
</li>
</ul>
<p><button onclick="myLayout.close('west')">Close Me</button></p>
</div>
<div class="ui-layout-south">
This is the south pane, closable, slidable and resizable &nbsp;
<button onclick="myLayout.toggle('north')">Toggle North Pane</button>
</div>
<div class="ui-layout-east">
This is the east pane, closable, slidable and resizable
<!-- attach allowOverflow method to this specific element -->
<ul onmouseover="myLayout.allowOverflow(this)" onmouseout="myLayout.resetOverflow('east')">
<li>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
<li>five</li>
</ul>
Pop-Up <!-- put this below so IE and FF render the same! -->
</li>
</ul>
<p><button onclick="myLayout.close('east')">Close Me</button></p>
<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
</div>
<div class="ui-layout-center">
This center pane auto-sizes to fit the space <i>between</i> the 'border-panes'
<p>This layout was created with only <b>default options</b> - no customization</p>
<p>Only the <b>applyDefaultStyles</b> option was enabled for <i>basic</i> formatting</p>
<p>The Pop-Up and Drop-Down lists demonstrate the <b>allowOverflow()</b> utility</p>
<p>The Close and Toggle buttons are examples of <b>custom buttons</b></p>
<p><a href="http://layout.jquery-dev.net/demos.html">Go to the Demos page</a></p>
<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
</div>
</body>
</html>

View File

@ -1,8 +0,0 @@
---
format: 1
handler:
piston:remote-revision: 14
piston:uuid: c0f28f7c-efbc-11dd-aa46-671127399dce
lock: false
repository_url: http://jquery-notice.googlecode.com/svn/trunk/
repository_class: Piston::Svn::Repository

View File

@ -1,36 +0,0 @@
.notice-wrap {
position: fixed;
top: 20px;
right: 20px;
width: 250px;
z-index: 9999;
}
* html .notice-wrap {
position: absolute;
}
.notice-item {
height: 60px;
background: #333;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
color: #eee;
padding: 6px 6px 0 6px;
font-family: lucida Grande;
font-size: 11px;
border: 2px solid #999;
display: block;
position: relative;
margin: 0 0 12px 0;
}
.notice-item-close {
position: absolute;
font-family: Arial;
font-size: 12px;
font-weight: bold;
right: 6px;
top: 6px;
cursor: pointer;
}

View File

@ -1,73 +0,0 @@
/**
* jQuery.noticeAdd() and jQuery.noticeRemove()
* These functions create and remove growl-like notices
*
* Copyright (c) 2009 Tim Benniks
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author Tim Benniks <tim@timbenniks.com>
* @copyright 2009 timbenniks.com
* @version $Id: jquery.notice.js 1 2009-01-24 12:24:18Z timbenniks $
**/
(function(jQuery)
{
jQuery.extend({
noticeAdd: function(options)
{
var defaults = {
inEffect: {opacity: 'show'}, // in effect
inEffectDuration: 600, // in effect duration in miliseconds
stayTime: 3000, // time in miliseconds before the item has to disappear
text: '', // content of the item
stay: false, // should the notice item stay or not?
type: 'notice' // could also be error, succes
}
// declare varaibles
var options, noticeWrapAll, noticeItemOuter, noticeItemInner, noticeItemClose;
options = jQuery.extend({}, defaults, options);
noticeWrapAll = (!jQuery('.notice-wrap').length) ? jQuery('<div></div>').addClass('notice-wrap').appendTo('body') : jQuery('.notice-wrap');
noticeItemOuter = jQuery('<div></div>').addClass('notice-item-wrapper');
noticeItemInner = jQuery('<div></div>').hide().addClass('notice-item ' + options.type).appendTo(noticeWrapAll).html('<p>'+options.text+'</p>').animate(options.inEffect, options.inEffectDuration).wrap(noticeItemOuter);
noticeItemClose = jQuery('<div></div>').addClass('notice-item-close').prependTo(noticeItemInner).html('x').click(function() { jQuery.noticeRemove(noticeItemInner) });
if(!options.stay)
{
setTimeout(function()
{
jQuery.noticeRemove(noticeItemInner);
},
options.stayTime);
}
},
noticeRemove: function(obj)
{
obj.animate({opacity: '0'}, 600, function()
{
obj.parent().animate({height: '0px'}, 300, function()
{
obj.parent().remove();
});
});
}
});
})(jQuery);