diff --git a/ChangeLog b/ChangeLog index 460e2dc73ae..877bd292c05 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,7 +14,7 @@ For users: configured. Show also total number of activated modules. - New: Can filter list of proposal, order or invoice on sales representative. - New: Add supplier ref on supplier orders. -- New: Can export supplier orders. +- New: Can export supplier orders and customers shipments. - New: First change to install external plugins from gui (experimental). - New: Monaco is like France for default vat calculation - New: Can list elements (invoices, orders or proposals) on a particular diff --git a/build/makepack-dolibarr.pl b/build/makepack-dolibarr.pl index a18529821b6..eda983bbb88 100755 --- a/build/makepack-dolibarr.pl +++ b/build/makepack-dolibarr.pl @@ -337,6 +337,7 @@ if ($nboftargetok) { $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tcpdf/fonts/freefont-20100919`; $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/tcpdf/fonts/utils`; $ret=`rm -f $BUILDROOT/$PROJECT/htdocs/includes/tcpdf/LICENSE.TXT`; + $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/savant`; } # Build package for each target diff --git a/htdocs/adherents/class/adherent.class.php b/htdocs/adherents/class/adherent.class.php index 1e8f6dadc18..9cbb86cf017 100644 --- a/htdocs/adherents/class/adherent.class.php +++ b/htdocs/adherents/class/adherent.class.php @@ -33,8 +33,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; /** - * \class Adherent - * \brief Class to manage members of a foundation + * Class to manage members of a foundation */ class Adherent extends CommonObject { diff --git a/htdocs/adherents/class/adherent_type.class.php b/htdocs/adherents/class/adherent_type.class.php index d6a0ed1ce91..2c05f71a5da 100644 --- a/htdocs/adherents/class/adherent_type.class.php +++ b/htdocs/adherents/class/adherent_type.class.php @@ -28,8 +28,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; /** - * \class AdherentType - * \brief Class to manage members type + * Class to manage members type */ class AdherentType extends CommonObject { diff --git a/htdocs/adherents/class/adherentstats.class.php b/htdocs/adherents/class/adherentstats.class.php index 52d73691cb8..c1a2537aa3a 100755 --- a/htdocs/adherents/class/adherentstats.class.php +++ b/htdocs/adherents/class/adherentstats.class.php @@ -28,8 +28,7 @@ include_once DOL_DOCUMENT_ROOT . '/adherents/class/cotisation.class.php'; /** - * \class AdherentStats - * \brief Classe permettant la gestion des stats des adherents + * Class to manage statistics of members */ class AdherentStats extends Stats { diff --git a/htdocs/adherents/class/cotisation.class.php b/htdocs/adherents/class/cotisation.class.php index f08b2257a7d..f333a6fbb98 100644 --- a/htdocs/adherents/class/cotisation.class.php +++ b/htdocs/adherents/class/cotisation.class.php @@ -26,8 +26,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; /** - * \class Cotisation - * \brief Class to manage subscriptions of foundation members + * Class to manage subscriptions of foundation members */ class Cotisation extends CommonObject { diff --git a/htdocs/admin/mailing.php b/htdocs/admin/mailing.php index 0732dbeb31c..0a34a4d4c47 100644 --- a/htdocs/admin/mailing.php +++ b/htdocs/admin/mailing.php @@ -40,7 +40,16 @@ $action = GETPOST('action','alpha'); * Actions */ -if ($action == 'setvalue' && $user->admin) +if ($action == 'setMAILING_EMAIL_UNSUBSCRIBE') +{ + $res=dolibarr_set_const($db, "MAILING_EMAIL_UNSUBSCRIBE",1,'chaine',0,'',$conf->entity); +} +if ($action == 'unsetMAILING_EMAIL_UNSUBSCRIBE') +{ + $res=dolibarr_del_const($db, "MAILING_EMAIL_UNSUBSCRIBE"); +} + +if ($action == 'setvalue') { $db->begin(); @@ -129,14 +138,14 @@ print ''; print $langs->trans("ActivateCheckRead").''; if (!empty($conf->global->MAILING_EMAIL_UNSUBSCRIBE)) { - print ''; + print ''; print img_picto($langs->trans("Enabled"),'switch_on'); print ''; $readonly=''; } else { - print ''; + print ''; print img_picto($langs->trans("Disabled"),'switch_off'); print ''; $readonly='disabled="disabled"'; @@ -149,11 +158,13 @@ print $langs->trans("ActivateCheckReadKey").''; print ''; print ''; -print ''; +print ''; print '
'; print '
'; +print ''; + llxFooter(); $db->close(); diff --git a/htdocs/bookmarks/class/bookmark.class.php b/htdocs/bookmarks/class/bookmark.class.php index 088d4751d2e..277e6318590 100644 --- a/htdocs/bookmarks/class/bookmark.class.php +++ b/htdocs/bookmarks/class/bookmark.class.php @@ -23,8 +23,7 @@ /** - * \class Bookmark - * \brief Class to manage bookmarks + * Class to manage bookmarks */ class Bookmark { diff --git a/htdocs/cashdesk/class/Facturation.class.php b/htdocs/cashdesk/class/Facturation.class.php index 9eda4232281..6c3f40d0acf 100644 --- a/htdocs/cashdesk/class/Facturation.class.php +++ b/htdocs/cashdesk/class/Facturation.class.php @@ -96,8 +96,8 @@ class Facturation $societe = new Societe($db); $societe->fetch($thirdpartyid); - $product = new Product($db); - $product->fetch($this->id); + $product = new Product($db); + $product->fetch($this->id); $sql = "SELECT taux"; $sql.= " FROM ".MAIN_DB_PREFIX."c_tva"; diff --git a/htdocs/categories/class/categorie.class.php b/htdocs/categories/class/categorie.class.php index 6c4d1ed0d23..596d5901fbe 100644 --- a/htdocs/categories/class/categorie.class.php +++ b/htdocs/categories/class/categorie.class.php @@ -86,7 +86,7 @@ class Categorie $resql = $this->db->query($sql); if ($resql) { - if ($this->db->num_rows($resql) > 0) + if ($this->db->num_rows($resql) > 0) { $res = $this->db->fetch_array($resql); diff --git a/htdocs/comm/mailing/cibles.php b/htdocs/comm/mailing/cibles.php index 2081bda8cbb..79a018546c5 100644 --- a/htdocs/comm/mailing/cibles.php +++ b/htdocs/comm/mailing/cibles.php @@ -136,7 +136,7 @@ if ($action == 'delete') $classname = "MailingTargets"; $obj = new $classname($db); $obj->update_nb($id); - + header("Location: ".$_SERVER['PHP_SELF']."?id=".$id); exit; } @@ -298,7 +298,7 @@ if ($object->fetch($id) >= 0) } print ''; - if (! $obj->picto) $obj->picto='generic'; + if (empty($obj->picto)) $obj->picto='generic'; print img_object($langs->trans("Module").': '.get_class($obj),$obj->picto).' '.$obj->getDesc(); print ''; diff --git a/htdocs/comm/mailing/fiche.php b/htdocs/comm/mailing/fiche.php index 8e3b5064a70..7618334aa86 100644 --- a/htdocs/comm/mailing/fiche.php +++ b/htdocs/comm/mailing/fiche.php @@ -337,16 +337,30 @@ if ($action == 'sendallconfirmed' && $confirm == 'yes') $i++; } } + else + { + setEventMessage($langs->transnoentitiesnoconv("NoMoreRecipientToSendTo")); + } // Loop finished, set global statut of mail if ($nbko > 0) { $statut=2; // Status 'sent partially' (because at least one error) + if ($nbok > 0) setEventMessage($langs->transnoentitiesnoconv("EMailSentToNRecipients",$nbok)); + else setEventMessage($langs->transnoentitiesnoconv("EMailSentToNRecipients",$nbok)); } else { - if ($nbok >= $num) $statut=3; // Send to everybody - else $statut=2; // Status 'sent partially' (because not send to everybody) + if ($nbok >= $num) + { + $statut=3; // Send to everybody + setEventMessage($langs->transnoentitiesnoconv("EMailSentToNRecipients",$nbok)); + } + else + { + $statut=2; // Status 'sent partially' (because not send to everybody) + setEventMessage($langs->transnoentitiesnoconv("EMailSentToNRecipients",$nbok)); + } } $sql="UPDATE ".MAIN_DB_PREFIX."mailing SET statut=".$statut." WHERE rowid=".$object->id; @@ -388,8 +402,8 @@ if ($action == 'send' && empty($_POST["cancel"])) if (preg_match('/[\s\t]*/i',$object->body)) $msgishtml=1; // Pratique les substitutions sur le sujet et message - $object->sujet=make_substitutions($object->sujet,$object->substitutionarrayfortest); - $object->body=make_substitutions($object->body,$object->substitutionarrayfortest); + $tmpsujet=make_substitutions($object->sujet,$object->substitutionarrayfortest); + $tmpbody=make_substitutions($object->body,$object->substitutionarrayfortest); $arr_file = array(); $arr_mime = array(); @@ -412,7 +426,7 @@ if ($action == 'send' && empty($_POST["cancel"])) } } - $mailfile = new CMailFile($object->sujet,$object->sendto,$object->email_from,$object->body, $arr_file,$arr_mime,$arr_name,'', '', 0, $msgishtml,$object->email_errorsto,$arr_css); + $mailfile = new CMailFile($tmpsujet,$object->sendto,$object->email_from,$tmpbody, $arr_file,$arr_mime,$arr_name,'', '', 0, $msgishtml,$object->email_errorsto,$arr_css); $result=$mailfile->sendfile(); if ($result) @@ -1148,7 +1162,6 @@ else } } - llxFooter(); $db->close(); ?> diff --git a/htdocs/compta/ajaxpayment.php b/htdocs/compta/ajaxpayment.php index e9ceafdaf52..12f0bd36978 100644 --- a/htdocs/compta/ajaxpayment.php +++ b/htdocs/compta/ajaxpayment.php @@ -18,7 +18,6 @@ /** * \file htdocs/compta/ajaxpayment.php * \brief File to return Ajax response on payment breakdown process - * \version ajaxpayment.php,v 1.0 */ //if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); @@ -32,9 +31,15 @@ if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); // If we don't nee //if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX','1'); //if (! defined("NOLOGIN")) define("NOLOGIN",'1'); // If this page is public (can be called outside logged session) -require '../main.inc.php'; +require '../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php'; -$langs->Load('compta'); +$langs->load('compta'); + + +/* + * View + */ //init var $amountPayment = $_POST['amountPayment']; @@ -42,26 +47,31 @@ $amounts = $_POST['amounts']; // from text inputs : invoice amount payment (c $remains = $_POST['remains']; // from dolibarr's object (no need to check) $currentInvId = $_POST['imgClicked']; // from DOM elements : imgId (equals invoice id) - // Getting the posted keys=>values, sanitize the ones who are from text inputs // from text inputs : total amount -$amountPayment = $amountPayment!='' ? ( is_numeric(price2num($amountPayment)) ? price2num($amountPayment) - : '' - ) - : ''; // keep void if not a valid entry -// Checkamounts -foreach ($amounts as $key => $value) -{ - $value = price2num($value); - if (!is_numeric($value)) unset($amounts[$key]); -} +$amountPayment = $amountPayment!='' ? ( is_numeric(price2num($amountPayment)) ? price2num($amountPayment) : '' ) : ''; // keep void if not a valid entry + +// Clean checkamounts +foreach ($amounts as $key => $value) +{ + $value = price2num($value); + $amounts[$key]=$value; + if (empty($value)) unset($amounts[$key]); +} +// Clean remains +foreach ($remains as $key => $value) +{ + $value = price2num($value); + $remains[$key]=$value; + if (empty($value)) unset($remains[$key]); +} // Treatment -$result = $amountPayment != '' ? $amountPayment - array_sum($amounts) : $amountPayment + array_sum($amounts); // Remaining amountPayment +$result = $amountPayment != '' ? ($amountPayment - array_sum($amounts)) : ($amountPayment + array_sum($amounts)); // Remaining amountPayment $toJsonArray = array(); $totalRemaining = price2num(array_sum($remains)); -$toJsonArray['label'] = $amountPayment == '' ? $langs->transnoentities('AmountToBeCharged') : $langs->transnoentities('RemainingAmountPayment'); -if($currentInvId) // Here to breakdown +$toJsonArray['label'] = $amountPayment == '' ? '' : $langs->transnoentities('RemainingAmountPayment'); +if ($currentInvId) // Here to breakdown { // Get the current amount (from form) and the corresponding remainToPay (from invoice) $currentAmount = $amounts['amount_'.$currentInvId]; @@ -99,9 +109,11 @@ if($currentInvId) // Here to breakdown } $toJsonArray['amount_'.$currentInvId] = price2num($currentAmount).""; // Param will exist only if an img has been clicked } -// Encode to JSON to return -$toJsonArray['makeRed'] = $totalRemaining < price2num($result) || price2num($result) < 0 ? true : false; -$toJsonArray['result'] = price2num($result); -echo json_encode($toJsonArray); // Printing the call's result +$toJsonArray['makeRed'] = ($totalRemaining < price2num($result) || price2num($result) < 0) ? true : false; +$toJsonArray['result'] = price($result); // Return value to user format +$toJsonArray['resultnum'] = price2num($result); // Return value to numeric format + +// Encode to JSON to return +echo dol_json_encode($toJsonArray); // Printing the call's result ?> \ No newline at end of file diff --git a/htdocs/compta/bank/fiche.php b/htdocs/compta/bank/fiche.php index 75aaff172c5..b1d503b8760 100644 --- a/htdocs/compta/bank/fiche.php +++ b/htdocs/compta/bank/fiche.php @@ -88,12 +88,12 @@ if ($_POST["action"] == 'add') $action='create'; // Force chargement page en mode creation $error++; } - if (empty($account->label)) - { - setEventMessage($langs->transnoentitiesnoconv("ErrorFieldRequired",$langs->transnoentitiesnoconv("LabelBankCashAccount")), 'errors'); - $action='create'; // Force chargement page en mode creation - $error++; - } + if (empty($account->label)) + { + setEventMessage($langs->transnoentitiesnoconv("ErrorFieldRequired",$langs->transnoentitiesnoconv("LabelBankCashAccount")), 'errors'); + $action='create'; // Force chargement page en mode creation + $error++; + } if (! $error) { diff --git a/htdocs/compta/facture.php b/htdocs/compta/facture.php index 73e8555a8a3..67194b610d5 100644 --- a/htdocs/compta/facture.php +++ b/htdocs/compta/facture.php @@ -280,14 +280,14 @@ else if ($action == 'setconditions' && $user->rights->facture->creer) $object->cond_reglement_code=0; // To clean property $object->cond_reglement_id=0; // To clean property $result=$object->setPaymentTerms(GETPOST('cond_reglement_id','int')); - if ($result < 0) dol_print_error($db,$object->error); + if ($result < 0) dol_print_error($db,$object->error); - $old_date_lim_reglement=$object->date_lim_reglement; - $new_date_lim_reglement=$object->calculate_date_lim_reglement(); - if ($new_date_lim_reglement > $old_date_lim_reglement) $object->date_lim_reglement=$new_date_lim_reglement; - if ($object->date_lim_reglement < $object->date) $object->date_lim_reglement=$object->date; - $result=$object->update($user); - if ($result < 0) dol_print_error($db,$object->error); + $old_date_lim_reglement=$object->date_lim_reglement; + $new_date_lim_reglement=$object->calculate_date_lim_reglement(); + if ($new_date_lim_reglement > $old_date_lim_reglement) $object->date_lim_reglement=$new_date_lim_reglement; + if ($object->date_lim_reglement < $object->date) $object->date_lim_reglement=$object->date; + $result=$object->update($user); + if ($result < 0) dol_print_error($db,$object->error); } else if ($action == 'setpaymentterm' && $user->rights->facture->creer) { diff --git a/htdocs/compta/paiement.php b/htdocs/compta/paiement.php index b1896db302a..04a4af7e09d 100644 --- a/htdocs/compta/paiement.php +++ b/htdocs/compta/paiement.php @@ -280,12 +280,9 @@ if ($action == 'create' || $action == 'confirm_paiement' || $action == 'add_paie $(\'.fieldrequireddyn\').removeClass(\'fieldrequired\'); $(\'#fieldchqemetteur\').val(\'\'); } - }'; - // For paiement auto-completion - if (! empty($conf->global->MAIN_JS_ON_PAYMENT)) - { - print "\n".' - function elemToJson(selector) + } + + function _elemToJson(selector) { var subJson = {}; $.map(selector.serializeArray(), function(n,i) @@ -300,14 +297,14 @@ if ($action == 'create' || $action == 'confirm_paiement' || $action == 'add_paie var form = $("#payment_form"); json["amountPayment"] = $("#amountpayment").attr("value"); - json["amounts"] = elemToJson(form.find("input[name*=\"amount_\"]")); - json["remains"] = elemToJson(form.find("input[name*=\"remain_\"]")); + json["amounts"] = _elemToJson(form.find("input[name*=\"amount_\"]")); + json["remains"] = _elemToJson(form.find("input[name*=\"remain_\"]")); if (imgId != null) { json["imgClicked"] = imgId; } - $.post("ajaxpayment.php", json, function(data) + $.post("'.DOL_URL_ROOT.'/compta/ajaxpayment.php", json, function(data) { json = $.parseJSON(data); @@ -317,9 +314,9 @@ if ($action == 'create' || $action == 'confirm_paiement' || $action == 'add_paie { if (key == "result") { if (json["makeRed"]) { - $("#"+key).css("color", "red"); + $("#"+key).addClass("error"); } else { - $("#"+key).removeAttr("style"); + $("#"+key).removeClass("error"); } json[key]=json["label"]+" "+json[key]; $("#"+key).text(json[key]); @@ -331,27 +328,27 @@ if ($action == 'create' || $action == 'confirm_paiement' || $action == 'add_paie } }); } - function callToBreakdown(imgSelector) { - var form = $("#payment_form"), imgId; - - imgId = imgSelector.attr("id"); - callForResult(imgId); - } - - $("#payment_form").find("img").click(function() { - callToBreakdown(jQuery(this)); - }); - $("#payment_form").find("input[name*=\"amount_\"]").change(function() { callForResult(); }); + $("#payment_form").find("input[name*=\"amount_\"]").keyup(function() { + callForResult(); + }); + '; + + if (! empty($conf->global->MAIN_JS_ON_PAYMENT)) + { + print ' $("#payment_form").find("img").click(function() { + callForResult(jQuery(this).attr("id")); + }); $("#amountpayment").change(function() { callForResult(); });'; } - print '}); - '."\n"; + + print ' });'."\n"; + print ' '."\n"; } print '
'; @@ -573,7 +570,7 @@ if ($action == 'create' || $action == 'confirm_paiement' || $action == 'add_paie if ($totalrecudeposits) print '+'.price($totalrecudeposits); print ''; print ''.price(price2num($total_ttc - $totalrecu - $totalrecucreditnote - $totalrecudeposits,'MT')).''; - print ''; + print ''; print ' '; print "\n"; } diff --git a/htdocs/compta/prelevement/class/bonprelevement.class.php b/htdocs/compta/prelevement/class/bonprelevement.class.php index 11c575f701d..39414b0960d 100644 --- a/htdocs/compta/prelevement/class/bonprelevement.class.php +++ b/htdocs/compta/prelevement/class/bonprelevement.class.php @@ -28,9 +28,6 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php'; require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php'; -// FIXME don't include external module class -if (! empty($conf->esaeb->enabled)) - dol_include_once('/esaeb/class/esaeb19.class.php'); /** @@ -481,7 +478,6 @@ class BonPrelevement extends CommonObject /* * End of procedure - * */ if ($error == 0) { @@ -928,7 +924,7 @@ class BonPrelevement extends CommonObject } /* - * Creation process + * Create withdrawal receipt */ if (!$error) { @@ -1181,12 +1177,15 @@ class BonPrelevement extends CommonObject $this->file = fopen($this->filename,"w"); + // TODO Move code for es and fr into an external module file with selection into setup of prelevement module + // Build file for Spain if ($mysoc->country_code=='ES') { - // TODO replace by a hook (external modules) if (! empty($conf->esaeb->enabled)) { + dol_include_once('/esaeb/class/esaeb19.class.php'); + //Head $esaeb19 = new AEB19DocWritter; $esaeb19->configuraPresentador($this->numero_national_emetteur,$conf->global->ESAEB_SUFIX_PRESENTADOR,$this->raison_sociale,$this->emetteur_code_banque,$this->emetteur_code_guichet); @@ -1239,7 +1238,7 @@ class BonPrelevement extends CommonObject fputs($this->file, $esaeb19->generaRemesa()); } else - { + { $this->total = 0; $sql = "SELECT pl.amount"; $sql.= " FROM"; @@ -1271,10 +1270,8 @@ class BonPrelevement extends CommonObject $langs->load('withdrawals'); fputs($this->file, $langs->trans('WithdrawalFileNotCapable')); } - } - - //Build file for France + // Build file for France elseif ($mysoc->country_code=='FR') { /* @@ -1316,7 +1313,7 @@ class BonPrelevement extends CommonObject } } else - { + { $result = -2; } @@ -1326,10 +1323,9 @@ class BonPrelevement extends CommonObject $this->EnregTotal($this->total); } - - //Build file for Other Countries with unknow format + // Build file for Other Countries with unknow format else - { + { $this->total = 0; $sql = "SELECT pl.amount"; $sql.= " FROM"; diff --git a/htdocs/compta/sociales/charges.php b/htdocs/compta/sociales/charges.php index 7e380204640..685a0eb05ca 100644 --- a/htdocs/compta/sociales/charges.php +++ b/htdocs/compta/sociales/charges.php @@ -214,11 +214,11 @@ if ($action == 'create') print ' '; // Label - print ''; + print ''; // Type print ''; - $formsocialcontrib->select_type_socialcontrib(isset($_POST["actioncode"])?$_POST["actioncode"]:'','actioncode',1); + $formsocialcontrib->select_type_socialcontrib(GETPOST("actioncode")?GETPOST("actioncode"):'','actioncode',1); print ''; // Date end period diff --git a/htdocs/contact/class/contact.class.php b/htdocs/contact/class/contact.class.php index 0f9758cd427..4e2d80e7bd1 100644 --- a/htdocs/contact/class/contact.class.php +++ b/htdocs/contact/class/contact.class.php @@ -29,8 +29,7 @@ require_once DOL_DOCUMENT_ROOT .'/core/class/commonobject.class.php'; /** - * \class Contact - * \brief Classe permettant la gestion des contacts + * Class to manage contact/addresses */ class Contact extends CommonObject { diff --git a/htdocs/core/class/CMailFile.class.php b/htdocs/core/class/CMailFile.class.php index 82c1e63abf4..3f97b88ad21 100644 --- a/htdocs/core/class/CMailFile.class.php +++ b/htdocs/core/class/CMailFile.class.php @@ -651,10 +651,10 @@ class CMailFile // Sender //$out.= "Sender: ".getValidAddress($this->addr_from,2)).$this->eol2; - $out.= "From: ".$this->getValidAddress($this->addr_from,3,1).$this->eol2; + $out.= "From: ".$this->getValidAddress($this->addr_from,3,1).$this->eol2; if (! empty($conf->global->MAIN_MAIL_SENDMAIL_FORCE_BA)) { - $out.= "To: ".$this->getValidAddress($this->addr_to,0,1).$this->eol2; + $out.= "To: ".$this->getValidAddress($this->addr_to,0,1).$this->eol2; } $out.= "Return-Path: ".$this->getValidAddress($this->addr_from,0,1).$this->eol2; if (isset($this->reply_to) && $this->reply_to) $out.= "Reply-To: ".$this->getValidAddress($this->reply_to,2).$this->eol2; @@ -747,8 +747,8 @@ class CMailFile // Make RFC821 Compliant, replace bare linefeeds $strContent = preg_replace("/(?global->MAIN_FIX_FOR_BUGGED_MTA)) - { + if (! empty($conf->global->MAIN_FIX_FOR_BUGGED_MTA)) + { $strContent = preg_replace("/\r\n/si", "\n", $strContent); } diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index aef25a6d679..94e1a191c57 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -2700,8 +2700,17 @@ function get_localtax($tva, $local, $thirdparty_buyer="", $thirdparty_seller="") dol_syslog("get_localtax tva=".$tva." local=".$local." thirdparty_buyer=".(is_object($thirdparty_buyer)?$thirdparty_buyer->id:'')." thirdparty_seller=".$thirdparty_seller->id); // Some test to guess with no need to make database access - if ($local == 1 && ! $thirdparty_seller->localtax1_assuj) return 0; - if ($local == 2 && ! $thirdparty_seller->localtax2_assuj) return 0; + if ($mysoc->country_code == 'ES') // For spain, localtaxes are qualified if both supplier and seller use local taxe + { + if ($local == 1 && (! $thirdparty_seller->localtax1_assuj || ! $thirdparty_buyer->localtax1_assuj)) return 0; + if ($local == 2 && (! $thirdparty_seller->localtax2_assuj || ! $thirdparty_buyer->localtax2_assuj)) return 0; + + } + else + { + if ($local == 1 && ! $thirdparty_seller->localtax1_assuj) return 0; + if ($local == 2 && ! $thirdparty_seller->localtax2_assuj) return 0; + } //if ($local == 0 && ! $thirdparty_seller->localtax1_assuj && ! $thirdparty_seller->localtax2_assuj) return array('localtax1'=>0,'localtax2'=>0); $code_country=$thirdparty_seller->country_code; @@ -2819,54 +2828,54 @@ function get_product_localtax_for_country($idprod, $local, $thirdparty_seller) require DOL_DOCUMENT_ROOT . '/product/class/product.class.php'; } - $ret=0; - $found=0; + $ret=0; + $found=0; - if ($idprod > 0) - { - // Load product - $product=new Product($db); - $result=$product->fetch($idprod); - - if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours - { + if ($idprod > 0) + { + // Load product + $product=new Product($db); + $result=$product->fetch($idprod); + + if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours + { /* Not defined yet, so we don't use this if ($local==1) $ret=$product->localtax1_tx; elseif ($local==2) $ret=$product->localtax2_tx; $found=1; - */ - } - else - { - // TODO Read default product vat according to countrycode and product - - - } - } - - if (! $found) - { - // If vat of product for the country not found or not defined, we return higher vat of country. - $sql = "SELECT taux as vat_rate, localtax1, localtax2"; - $sql.= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_pays as p"; - $sql.= " WHERE t.active=1 AND t.fk_pays = p.rowid AND p.code='".$thirdparty_seller->country_code."'"; - $sql.= " ORDER BY t.taux DESC, t.recuperableonly ASC"; - $sql.= $db->plimit(1); - - $resql=$db->query($sql); - if ($resql) - { - $obj=$db->fetch_object($resql); - if ($obj) - { - if ($local==1) $ret=$obj->localtax1; + */ + } + else + { + // TODO Read default product vat according to countrycode and product + + + } + } + + if (! $found) + { + // If vat of product for the country not found or not defined, we return higher vat of country. + $sql = "SELECT taux as vat_rate, localtax1, localtax2"; + $sql.= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_pays as p"; + $sql.= " WHERE t.active=1 AND t.fk_pays = p.rowid AND p.code='".$thirdparty_seller->country_code."'"; + $sql.= " ORDER BY t.taux DESC, t.recuperableonly ASC"; + $sql.= $db->plimit(1); + + $resql=$db->query($sql); + if ($resql) + { + $obj=$db->fetch_object($resql); + if ($obj) + { + if ($local==1) $ret=$obj->localtax1; elseif ($local==2) $ret=$obj->localtax2; - } - } - else dol_print_error($db); - } - - dol_syslog("get_product_localtax_for_country: ret=".$ret); + } + } + else dol_print_error($db); + } + + dol_syslog("get_product_localtax_for_country: ret=".$ret); return $ret; } @@ -3565,7 +3574,7 @@ function get_date_range($date_start,$date_end,$format = '',$outputlangs='') */ function setEventMessage($mesgs, $style='mesgs') { - if (! in_array($style,array('mesgs','warnings','errors'))) dol_print_error('','Bad parameter for setEventMessage'); + if (! in_array((string) $style, array('mesgs','warnings','errors'))) dol_print_error('','Bad parameter for setEventMessage'); if (! is_array($mesgs)) // If mesgs is a string { if ($mesgs) $_SESSION['dol_events'][$style][] = $mesgs; diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index 714fdb9a578..4c06cbb2099 100644 --- a/htdocs/core/lib/price.lib.php +++ b/htdocs/core/lib/price.lib.php @@ -57,30 +57,30 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt if (empty($seller) || ! is_object($seller)) $seller=$mysoc; // If seller is a customer, $seller is not provided, we use $mysoc $countryid=$seller->country_id; if ($uselocaltax1_rate < 0) $uselocaltax1_rate=$seller->localtax1_assuj; - if ($uselocaltax2_rate < 0) $uselocaltax2_rate=$seller->localtax2_assuj; + if ($uselocaltax2_rate < 0) $uselocaltax2_rate=$seller->localtax2_assuj; // Now we search localtaxes information ourself (rates and types). - $sql = "SELECT taux, localtax1, localtax2, localtax1_type, localtax2_type"; + $sql = "SELECT taux, localtax1, localtax2, localtax1_type, localtax2_type"; $sql.= " FROM ".MAIN_DB_PREFIX."c_tva as cv"; - //$sql.= ", ".MAIN_DB_PREFIX."c_pays as cc"; + //$sql.= ", ".MAIN_DB_PREFIX."c_pays as cc"; $sql.= " WHERE cv.taux = ".$txtva; //$sql.= " AND cv.fk_pays = cc.rowid and cc.code = '".$mysoc->country_code."'"; - $sql.= " AND cv.fk_pays = ".$countryid; - dol_syslog("search vat information sql=".$sql); - $resql = $db->query($sql); - if ($resql) - { + $sql.= " AND cv.fk_pays = ".$countryid; + dol_syslog("search vat information sql=".$sql); + $resql = $db->query($sql); + if ($resql) + { $obj = $db->fetch_object($resql); if ($obj) - { - $localtax1_rate=$obj->localtax1; - $localtax2_rate=$obj->localtax2; - $localtax1_type=$obj->localtax1_type; - $localtax2_type=$obj->localtax2_type; + { + $localtax1_rate=$obj->localtax1; + $localtax2_rate=$obj->localtax2; + $localtax1_type=$obj->localtax1_type; + $localtax2_type=$obj->localtax2_type; //var_dump($localtax1_rate.' '.$localtax2_rate.' '.$localtax1_type.' '.$localtax2_type);exit; - } + } } - else dol_print_error($db); + else dol_print_error($db); // initialize total (may be HT or TTC depending on price_base_type) $tot_sans_remise = $pu * $qty; @@ -177,8 +177,7 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt } // if there's some localtax without vat, we calculate localtaxes (we will add them at end) - $apply_tax = false; - + //If price is 'TTC' we need to have the totals without VAT for a correct calculation if ($price_base_type=='TTC') { @@ -186,7 +185,8 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt $tot_avec_remise= price2num($tot_avec_remise / (1 + ($txtva / 100)),'MU'); } - switch($localtax1_type) { + $apply_tax = false; + switch($localtax1_type) { case '1': // localtax on product or service $apply_tax = true; break; diff --git a/htdocs/core/modules/DolibarrModules.class.php b/htdocs/core/modules/DolibarrModules.class.php index fc3f02d3250..2b5d0b7a60c 100644 --- a/htdocs/core/modules/DolibarrModules.class.php +++ b/htdocs/core/modules/DolibarrModules.class.php @@ -415,6 +415,9 @@ abstract class DolibarrModules global $db,$conf; $error=0; + $dirfound=0; + + if (empty($reldir)) return 1; include_once DOL_DOCUMENT_ROOT .'/core/lib/admin.lib.php'; @@ -426,11 +429,13 @@ abstract class DolibarrModules $dir = $dirroot.$reldir; $ok = 0; - // Run llx_mytable.sql files $handle=@opendir($dir); // Dir may not exists if (is_resource($handle)) { - while (($file = readdir($handle))!==false) + $dirfound++; + + // Run llx_mytable.sql files + while (($file = readdir($handle))!==false) { if (preg_match('/\.sql$/i',$file) && ! preg_match('/\.key\.sql$/i',$file) && substr($file,0,4) == 'llx_' && substr($file,0,4) != 'data') { @@ -438,14 +443,11 @@ abstract class DolibarrModules if ($result <= 0) $error++; } } - closedir($handle); - } + + rewinddir($handle); - // Run llx_mytable.key.sql files (Must be done after llx_mytable.sql) - $handle=@opendir($dir); // Dir may not exist - if (is_resource($handle)) - { - while (($file = readdir($handle))!==false) + // Run llx_mytable.key.sql files (Must be done after llx_mytable.sql) + while (($file = readdir($handle))!==false) { if (preg_match('/\.key\.sql$/i',$file) && substr($file,0,4) == 'llx_' && substr($file,0,4) != 'data') { @@ -453,14 +455,11 @@ abstract class DolibarrModules if ($result <= 0) $error++; } } - closedir($handle); - } - // Run data_xxx.sql files (Must be done after llx_mytable.key.sql) - $handle=@opendir($dir); // Dir may not exist - if (is_resource($handle)) - { - while (($file = readdir($handle))!==false) + rewinddir($handle); + + // Run data_xxx.sql files (Must be done after llx_mytable.key.sql) + while (($file = readdir($handle))!==false) { if (preg_match('/\.sql$/i',$file) && ! preg_match('/\.key\.sql$/i',$file) && substr($file,0,4) == 'data') { @@ -468,14 +467,11 @@ abstract class DolibarrModules if ($result <= 0) $error++; } } - closedir($handle); - } - - // Run update_xxx.sql files - $handle=@opendir($dir); // Dir may not exist - if (is_resource($handle)) - { - while (($file = readdir($handle))!==false) + + rewinddir($handle); + + // Run update_xxx.sql files + while (($file = readdir($handle))!==false) { if (preg_match('/\.sql$/i',$file) && ! preg_match('/\.key\.sql$/i',$file) && substr($file,0,6) == 'update') { @@ -483,6 +479,7 @@ abstract class DolibarrModules if ($result <= 0) $error++; } } + closedir($handle); } @@ -493,6 +490,7 @@ abstract class DolibarrModules } } + if (! $dirfound) dol_syslog("A module ask to load sql files into ".$reldir." but this directory was not found.", LOG_WARNING); return $ok; } diff --git a/htdocs/core/modules/action/rapport.pdf.php b/htdocs/core/modules/action/rapport.pdf.php index bb0af3993e6..39f84608864 100644 --- a/htdocs/core/modules/action/rapport.pdf.php +++ b/htdocs/core/modules/action/rapport.pdf.php @@ -42,10 +42,10 @@ class CommActionRapport var $title; var $subject; - var $marge_gauche; - var $marge_droite; - var $marge_haute; - var $marge_basse; + var $marge_gauche; + var $marge_droite; + var $marge_haute; + var $marge_basse; /** diff --git a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php index 13e18567271..77d09e85691 100755 --- a/htdocs/core/modules/facture/doc/pdf_crabe.modules.php +++ b/htdocs/core/modules/facture/doc/pdf_crabe.modules.php @@ -500,8 +500,8 @@ class pdf_crabe extends ModelePDFFactures $tab3_top = $posy + 8; $tab3_width = 80; $tab3_height = 4; - if ($this->page_largeur < 210) // To work with US executive format - { + if ($this->page_largeur < 210) // To work with US executive format + { $tab3_posx -= 20; } diff --git a/htdocs/core/modules/mailings/framboise.modules.php b/htdocs/core/modules/mailings/framboise.modules.php index 76da4b23714..3d222b98eb7 100644 --- a/htdocs/core/modules/mailings/framboise.modules.php +++ b/htdocs/core/modules/mailings/framboise.modules.php @@ -61,9 +61,8 @@ class mailing_framboise extends MailingTargets $cibles = array(); - // CHANGE THIS // Select the members from category - $sql = "SELECT a.rowid as id, a.email as email, a.nom as name, null as fk_contact, null as firstname,"; + $sql = "SELECT a.rowid as id, a.email as email, a.nom as name, null as fk_contact, a.prenom as firstname,"; if ($_POST['filter']) $sql.= " c.label"; else $sql.=" null as label"; $sql.= " FROM ".MAIN_DB_PREFIX."adherent as a"; diff --git a/htdocs/core/modules/modExpedition.class.php b/htdocs/core/modules/modExpedition.class.php index 5fa44181566..a6f6d717c94 100644 --- a/htdocs/core/modules/modExpedition.class.php +++ b/htdocs/core/modules/modExpedition.class.php @@ -42,6 +42,8 @@ class modExpedition extends DolibarrModules */ function __construct($db) { + global $conf; + $this->db = $db; $this->numero = 80; @@ -71,6 +73,8 @@ class modExpedition extends DolibarrModules // Dependances $this->depends = array("modCommande"); $this->requiredby = array(); + $this->conflictwith = array(); + $this->langfiles = array('deliveries','sendings'); // Constantes $this->const = array(); @@ -141,6 +145,14 @@ class modExpedition extends DolibarrModules $this->rights[$r][4] = 'shipping_advance'; $this->rights[$r][5] = 'send'; + $r++; + $this->rights[$r][0] = 106; + $this->rights[$r][1] = 'Exporter les expeditions'; + $this->rights[$r][2] = 'r'; + $this->rights[$r][3] = 0; + $this->rights[$r][4] = 'shipment'; + $this->rights[$r][5] = 'export'; + $r++; $this->rights[$r][0] = 109; $this->rights[$r][1] = 'Supprimer les expeditions'; @@ -180,6 +192,25 @@ class modExpedition extends DolibarrModules $this->rights[$r][4] = 'livraison'; $this->rights[$r][5] = 'supprimer'; + // Exports + //-------- + $r=0; + + $r++; + $this->export_code[$r]=$this->rights_class.'_'.$r; + $this->export_label[$r]='Shipments'; // Translation key (used only if key ExportDataset_xxx_z not found) + $this->export_permission[$r]=array(array("expedition","shipment","export")); + $this->export_fields_array[$r]=array('s.rowid'=>"IdCompany",'s.nom'=>'CompanyName','s.address'=>'Address','s.cp'=>'Zip','s.ville'=>'Town','s.fk_pays'=>'Country','s.tel'=>'Phone','s.siren'=>'ProfId1','s.siret'=>'ProfId2','s.ape'=>'ProfId3','s.idprof4'=>'ProfId4','s.idprof5'=>'ProfId5','s.idprof6'=>'ProfId6','c.rowid'=>"Id",'c.ref'=>"Ref",'c.ref_customer'=>"RefCustomer",'c.fk_soc'=>"IdCompany",'c.date_creation'=>"DateCreation",'c.date_delivery'=>"DateSending",'c.tracking_number'=>"TrackingNumber",'c.height'=>"Height",'c.width'=>"Width",'c.size'=>"Depth",'c.size_units'=>'SizeUnits','c.weight'=>"Weight",'c.weight_units'=>"WeightUnits",'c.fk_statut'=>'Status','c.note'=>"Note",'ed.rowid'=>'LineId','cd.description'=>'Description','ed.qty'=>"Qty",'p.rowid'=>'ProductId','p.ref'=>'ProductRef','p.label'=>'ProductLabel'); + //$this->export_TypeFields_array[$r]=array('s.rowid'=>"List:societe:nom",'s.nom'=>'Text','s.address'=>'Text','s.cp'=>'Text','s.ville'=>'Text','s.libelle'=>'List:c_pays:libelle:rowid','s.tel'=>'Text','s.siren'=>'Text','s.siret'=>'Text','s.ape'=>'Text','s.idprof4'=>'Text','c.ref'=>"Text",'c.ref_client'=>"Text",'c.date_creation'=>"Date",'c.date_commande'=>"Date",'c.amount_ht'=>"Number",'c.remise_percent'=>"Number",'c.total_ht'=>"Number",'c.total_ttc'=>"Number",'c.facture'=>"Boolean",'c.fk_statut'=>'Status','c.note'=>"Text",'c.date_livraison'=>'Date','ed.qty'=>"Text"); + $this->export_TypeFields_array[$r]=array('s.nom'=>'Text','s.address'=>'Text','s.cp'=>'Text','s.ville'=>'Text','s.libelle'=>'List:c_pays:libelle:rowid','s.tel'=>'Text','s.siren'=>'Text','s.siret'=>'Text','s.ape'=>'Text','s.idprof4'=>'Text','c.ref'=>"Text",'c.ref_customer'=>"Text",'c.date_creation'=>"Date",'c.date_delivery'=>"Date",'c.tracking_number'=>"Number",'c.height'=>"Number",'c.width'=>"Number",'c.weight'=>"Number",'c.fk_statut'=>'Status','c.note'=>"Text",'ed.qty'=>"Number"); + $this->export_entities_array[$r]=array('s.rowid'=>"company",'s.nom'=>'company','s.address'=>'company','s.cp'=>'company','s.ville'=>'company','s.fk_pays'=>'company','s.tel'=>'company','s.siren'=>'company','s.ape'=>'company','s.siret'=>'company','s.idprof4'=>'company','s.idprof5'=>'company','s.idprof6'=>'company','c.rowid'=>"shipment",'c.ref'=>"shipment",'c.ref_customer'=>"shipment",'c.fk_soc'=>"shipment",'c.date_creation'=>"shipment",'c.date_delivery'=>"shipment",'c.tracking_number'=>'shipment','c.height'=>"shipment",'c.width'=>"shipment",'c.size'=>'shipment','c.size_units'=>'shipment','c.weight'=>"shipment",'c.weight_units'=>'shipment','c.fk_statut'=>"shipment",'c.note'=>"shipment",'ed.rowid'=>'shipment_line','cd.description'=>'shipment_line','ed.qty'=>"shipment_line",'p.rowid'=>'product','p.ref'=>'product','p.label'=>'product'); + $this->export_dependencies_array[$r]=array('shipment_line'=>'ed.rowid','product'=>'ed.rowid'); // To add unique key if we ask a field of a child to avoid the DISTINCT to discard them + + $this->export_sql_start[$r]='SELECT DISTINCT '; + $this->export_sql_end[$r] =' FROM ('.MAIN_DB_PREFIX.'expedition as c, '.MAIN_DB_PREFIX.'societe as s, '.MAIN_DB_PREFIX.'expeditiondet as ed, '.MAIN_DB_PREFIX.'commandedet as cd)'; + $this->export_sql_end[$r] .=' LEFT JOIN '.MAIN_DB_PREFIX.'product as p on (cd.fk_product = p.rowid)'; + $this->export_sql_end[$r] .=' WHERE c.fk_soc = s.rowid AND c.rowid = ed.fk_expedition AND ed.fk_origin_line = cd.rowid'; + $this->export_sql_end[$r] .=' AND c.entity = '.$conf->entity; } diff --git a/htdocs/core/modules/modGravatar.class.php b/htdocs/core/modules/modGravatar.class.php index 7379b817396..c41c196e2ed 100644 --- a/htdocs/core/modules/modGravatar.class.php +++ b/htdocs/core/modules/modGravatar.class.php @@ -220,7 +220,7 @@ class modGravatar extends DolibarrModules */ function load_tables() { - return $this->_load_tables('/mymodule/sql/'); + return $this->_load_tables(''); } } diff --git a/htdocs/core/modules/modProjet.class.php b/htdocs/core/modules/modProjet.class.php index e4c4fb03952..0942eb17b4d 100644 --- a/htdocs/core/modules/modProjet.class.php +++ b/htdocs/core/modules/modProjet.class.php @@ -65,6 +65,8 @@ class modProjet extends DolibarrModules // Dependancies $this->depends = array(); $this->requiredby = array(); + $this->conflictwith = array(); + $this->langfiles = array('projects'); // Constants $this->const = array(); diff --git a/htdocs/core/modules/modWorkflow.class.php b/htdocs/core/modules/modWorkflow.class.php index 9ba4bf27d67..967e4178fb7 100644 --- a/htdocs/core/modules/modWorkflow.class.php +++ b/htdocs/core/modules/modWorkflow.class.php @@ -173,7 +173,7 @@ class modWorkflow extends DolibarrModules */ function load_tables() { - return $this->_load_tables('/workflow/sql/'); + return $this->_load_tables(''); } } ?> diff --git a/htdocs/core/modules/propale/doc/pdf_azur.modules.php b/htdocs/core/modules/propale/doc/pdf_azur.modules.php index 05485e86f73..77d9d849d2a 100644 --- a/htdocs/core/modules/propale/doc/pdf_azur.modules.php +++ b/htdocs/core/modules/propale/doc/pdf_azur.modules.php @@ -283,13 +283,13 @@ class pdf_azur extends ModelePDFPropales $pdf->startTransaction(); pdf_writelinedesc($pdf,$object,$i,$outputlangs,$this->posxtva-$curX,3,$curX,$curY,$hideref,$hidedesc,0,$hookmanager); - $pageposafter=$pdf->getPage(); + $pageposafter=$pdf->getPage(); if ($pageposafter > $pageposbefore) // There is a pagebreak { $pdf->rollbackTransaction(true); $pageposafter=$pageposbefore; //print $pageposafter.'-'.$pageposbefore;exit; - $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. + $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it. pdf_writelinedesc($pdf,$object,$i,$outputlangs,$this->posxtva-$curX,4,$curX,$curY,$hideref,$hidedesc,0,$hookmanager); $pageposafter=$pdf->getPage(); $posyafter=$pdf->GetY(); @@ -298,15 +298,15 @@ class pdf_azur extends ModelePDFPropales if ($i == ($nblignes-1)) // No more lines, and no space left to show total, so we create a new page { $pdf->AddPage('','',true); - if (! empty($tplidx)) $pdf->useTemplate($tplidx); + if (! empty($tplidx)) $pdf->useTemplate($tplidx); if (empty($conf->global->MAIN_PDF_DONOTREPEAT_HEAD)) $this->_pagehead($pdf, $object, 0, $outputlangs, $hookmanager); $pdf->setPage($pagenb+1); } } else { - // We found a page break - $showpricebeforepagebreak=0; + // We found a page break + $showpricebeforepagebreak=0; } } else // No pagebreak diff --git a/htdocs/core/modules/supplier_invoice/pdf/pdf_canelle.modules.php b/htdocs/core/modules/supplier_invoice/pdf/pdf_canelle.modules.php index dece24da350..b0a2b33e2e7 100755 --- a/htdocs/core/modules/supplier_invoice/pdf/pdf_canelle.modules.php +++ b/htdocs/core/modules/supplier_invoice/pdf/pdf_canelle.modules.php @@ -739,9 +739,9 @@ class pdf_canelle extends ModelePDFSuppliersInvoices $tab3_top = $posy + 8; $tab3_width = 80; $tab3_height = 4; - if ($this->page_largeur < 210) // To work with US executive format - { - $tab3_posx -= 20; + if ($this->page_largeur < 210) // To work with US executive format + { + $tab3_posx -= 20; } $default_font_size = pdf_getPDFFontSize($outputlangs); diff --git a/htdocs/core/tpl/login.tpl.php b/htdocs/core/tpl/login.tpl.php index fe1525eb6fe..81d446281ae 100644 --- a/htdocs/core/tpl/login.tpl.php +++ b/htdocs/core/tpl/login.tpl.php @@ -54,7 +54,6 @@ if (isset($conf->modules_parts['css'])) } // JQuery. Must be before other includes $ext='.js'; -if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x01)) $ext='.jgz'; print ''."\n"; if (constant('JS_JQUERY')) print ''."\n"; else print ''."\n"; diff --git a/htdocs/core/tpl/passwordforgotten.tpl.php b/htdocs/core/tpl/passwordforgotten.tpl.php index 5234949629c..460637fedee 100644 --- a/htdocs/core/tpl/passwordforgotten.tpl.php +++ b/htdocs/core/tpl/passwordforgotten.tpl.php @@ -53,7 +53,6 @@ if (isset($conf->modules_parts['css'])) } // JQuery. Must be before other includes $ext='.js'; -if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x01)) $ext='.jgz'; print ''."\n"; if (constant('JS_JQUERY')) print ''."\n"; else print ''."\n"; diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 1aeede8b5ba..0b36281c825 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -709,12 +709,12 @@ class Expedition extends CommonObject $sql.= " fk_expedition_methode=".((isset($this->expedition_method_id) && $this->expedition_method_id > 0)?$this->expedition_method_id:"null").","; $sql.= " tracking_number=".(isset($this->tracking_number)?"'".$this->db->escape($this->tracking_number)."'":"null").","; $sql.= " fk_statut=".(isset($this->statut)?$this->statut:"null").","; - $sql.= " height=".(isset($this->trueHeight)?$this->trueHeight:"null").","; - $sql.= " width=".(isset($this->trueWidth)?$this->trueWidth:"null").","; + $sql.= " height=".(($this->trueHeight != '')?$this->trueHeight:"null").","; + $sql.= " width=".(($this->trueWidth != '')?$this->trueWidth:"null").","; $sql.= " size_units=".(isset($this->size_units)?$this->size_units:"null").","; - $sql.= " size=".(isset($this->trueDepth)?$this->trueDepth:"null").","; + $sql.= " size=".(($this->trueDepth != '')?$this->trueDepth:"null").","; $sql.= " weight_units=".(isset($this->weight_units)?$this->weight_units:"null").","; - $sql.= " weight=".(isset($this->trueWeight)?$this->trueWeight:"null").","; + $sql.= " weight=".(($this->trueWeight != '')?$this->trueWeight:"null").","; $sql.= " note=".(isset($this->note)?"'".$this->db->escape($this->note)."'":"null").","; $sql.= " model_pdf=".(isset($this->model_pdf)?"'".$this->db->escape($this->model_pdf)."'":"null").","; $sql.= " entity=".$conf->entity; @@ -858,7 +858,8 @@ class Expedition extends CommonObject $sql.= ", cd.total_ht, cd.total_localtax1, cd.total_localtax2, cd.total_ttc, cd.total_tva"; $sql.= ", cd.tva_tx, cd.localtax1_tx, cd.localtax2_tx, cd.price, cd.subprice"; $sql.= ", ed.qty as qty_shipped, ed.fk_origin_line, ed.fk_entrepot"; - $sql.= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.weight, p.weight_units, p.volume, p.volume_units"; + $sql.= ", p.ref as product_ref, p.label as product_label, p.fk_product_type"; + $sql.= ", p.weight, p.weight_units, p.length, p.length_units, p.surface, p.surface_units, p.volume, p.volume_units"; $sql.= " FROM (".MAIN_DB_PREFIX."expeditiondet as ed,"; $sql.= " ".MAIN_DB_PREFIX."commandedet as cd)"; $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product"; @@ -900,6 +901,10 @@ class Expedition extends CommonObject $line->qty_shipped = $obj->qty_shipped; $line->weight = $obj->weight; $line->weight_units = $obj->weight_units; + $line->length = $obj->length; + $line->length_units = $obj->length_units; + $line->surface = $obj->surface; + $line->surface_units = $obj->surface_units; $line->volume = $obj->volume; $line->volume_units = $obj->volume_units; diff --git a/htdocs/expedition/fiche.php b/htdocs/expedition/fiche.php index 363ec801e3a..3158c1ccf4b 100644 --- a/htdocs/expedition/fiche.php +++ b/htdocs/expedition/fiche.php @@ -285,10 +285,9 @@ else if ($action == 'settrackingnumber' || $action == 'settrackingurl' header("Location: fiche.php?id=".$shipping->id); exit; } - $mesg=$shipping->error; + setEventMessage($shipping->error,'errors'); } - $mesg='
'.$mesg.'
'; $action=""; } @@ -537,7 +536,7 @@ else if ($action == 'classifybilled') /* * View -*/ + */ llxHeader('',$langs->trans('Sending'),'Expedition'); @@ -904,7 +903,7 @@ else /* * Confirmation de la suppression - */ + */ if ($action == 'delete') { $ret=$form->form_confirm($_SERVER['PHP_SELF'].'?id='.$object->id,$langs->trans('DeleteSending'),$langs->trans("ConfirmDeleteSending",$object->ref),'confirm_delete','',0,1); @@ -913,7 +912,7 @@ else /* * Confirmation de la validation - */ + */ if ($action == 'valid') { $objectref = substr($object->ref, 1, 4); @@ -930,15 +929,15 @@ else } /* * Confirmation de l'annulation - */ + */ if ($action == 'annuler') { $ret=$form->form_confirm($_SERVER['PHP_SELF'].'?id='.$object->id,$langs->trans('CancelSending'),$langs->trans("ConfirmCancelSending",$object->ref),'confirm_cancel','',0,1); if ($ret == 'html') print '
'; } - // Calculate ture totalVeight and totalVolume for all products - // by adding weight and volume of each line. + // Calculate true totalWeight and totalVolume for all products + // by adding weight and volume of each product line. $totalWeight = ''; $totalVolume = ''; $weightUnit=0; @@ -949,6 +948,7 @@ else $volumeUnit=0; if (! empty($lines[$i]->weight_units)) $weightUnit = $lines[$i]->weight_units; if (! empty($lines[$i]->volume_units)) $volumeUnit = $lines[$i]->volume_units; + // TODO Use a function addvalueunits(val1,unit1,val2,unit2)=>(val,unit) if ($lines[$i]->weight_units < 50) { @@ -973,8 +973,6 @@ else $totalVolume += $lines[$i]->volume*$lines[$i]->qty_shipped; } } - $totalVolume=$totalVolume; - //print "totalVolume=".$totalVolume." volumeUnit=".$volumeUnit; print ''; @@ -1053,49 +1051,50 @@ else // Weight print ''; - // Volume Total - print ''; - print '\n"; - print ''; - // Width print ''; // Height print ''; // Depth print ''; + // Volume + print ''; + print '\n"; + print ''; + // Status print ''; print '\n"; diff --git a/htdocs/exports/export.php b/htdocs/exports/export.php index 386be209cb3..1a73d47dfc1 100644 --- a/htdocs/exports/export.php +++ b/htdocs/exports/export.php @@ -58,6 +58,8 @@ $entitytoicon = array( 'product' => 'product', 'warehouse' => 'stock', 'category' => 'category', + 'shipment' => 'sending', + 'shipment_line'=> 'sending' ); // Translation code @@ -86,7 +88,9 @@ $entitytolang = array( 'warehouse' => 'Warehouse', 'category' => 'Category', 'other' => 'Other', - 'trip' => 'TripsAndExpenses' + 'trip' => 'TripsAndExpenses', + 'shipment' => 'Shipments', + 'shipment_line'=> 'ShipmentLine' ); $array_selected=isset($_SESSION["export_selected_fields"])?$_SESSION["export_selected_fields"]:array(); diff --git a/htdocs/externalsite/admin/externalsite.php b/htdocs/externalsite/admin/externalsite.php index 830d191bc82..986a92e8968 100644 --- a/htdocs/externalsite/admin/externalsite.php +++ b/htdocs/externalsite/admin/externalsite.php @@ -79,7 +79,7 @@ $linkback=''.$langs->trans("BackToM print_fiche_titre($langs->trans("ExternalSiteSetup"),$linkback,'setup'); print $langs->trans("Module100Desc")."
\n"; -print '
'; +print '
'; print ''; print ''; diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index 83c1a38ed0a..81390c9f0b0 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -1,20 +1,21 @@ - * Copyright (C) 2011 Dimitri Mouillard -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* +/* Copyright (C) 2011 Dimitri Mouillard + * Copyright (C) 2012 Laurent Destailleur + * Copyright (C) 2012 Regis Houssin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . -*/ + */ /** * \file holiday.class.php @@ -88,6 +89,12 @@ class Holiday extends CommonObject global $conf, $langs; $error=0; + $now=dol_now(); + + // Check parameters + if (empty($this->fk_user) || ! is_numeric($this->fk_user) || $this->fk_user < 0) { $this->error="ErrorBadParameter"; return -1; } + if (empty($this->fk_validator) || ! is_numeric($this->fk_validator) || $this->fk_validator < 0) { $this->error="ErrorBadParameter"; return -1; } + // Insert request $sql = "INSERT INTO ".MAIN_DB_PREFIX."holiday("; @@ -102,22 +109,13 @@ class Holiday extends CommonObject $sql.= ") VALUES ("; // User - if(!empty($this->fk_user)) { - $sql.= "'".$this->fk_user."',"; - } else { - $error++; - } - $sql.= " NOW(),"; + $sql.= "'".$this->fk_user."',"; + $sql.= " '".$this->db->idate($now)."',"; $sql.= " '".addslashes($this->description)."',"; $sql.= " '".$this->db->idate($this->date_debut)."',"; $sql.= " '".$this->db->idate($this->date_fin)."',"; $sql.= " '1',"; - if(is_numeric($this->fk_validator)) { - $sql.= " '".$this->fk_validator."'"; - } - else { - $error++; - } + $sql.= " '".$this->fk_validator."'"; $sql.= ")"; @@ -182,7 +180,6 @@ class Holiday extends CommonObject $sql.= " cp.fk_user_cancel,"; $sql.= " cp.detail_refuse"; - $sql.= " FROM ".MAIN_DB_PREFIX."holiday as cp"; $sql.= " WHERE cp.rowid = ".$id; @@ -225,12 +222,12 @@ class Holiday extends CommonObject } /** - * Liste les congés payés pour un utilisateur + * List holidays for a particular user * - * @param int $user_id ID de l'utilisateur à lister - * @param string $order Filtrage par ordre - * @param string $filter Filtre de séléction - * @return int -1 si erreur, 1 si OK et 2 si pas de résultat + * @param int $user_id ID of user to list + * @param string $order Sort order + * @param string $filter SQL Filter + * @return int -1 if KO, 1 if OK, 2 if no result */ function fetchByUser($user_id,$order='',$filter='') { @@ -320,11 +317,11 @@ class Holiday extends CommonObject } /** - * Liste les congés payés de tout les utilisateurs + * List all holidays of all users * - * @param string $order Filtrage par ordre - * @param string $filter Filtre de séléction - * @return int -1 si erreur, 1 si OK et 2 si pas de résultat + * @param string $order Sort order + * @param string $filter SQL Filter + * @return int -1 if KO, 1 if OK, 2 if no result */ function fetchAll($order,$filter) { @@ -1182,12 +1179,12 @@ class Holiday extends CommonObject // On séléctionne les utilisateurs qui ne sont pas déjà dans le module $sql = "SELECT u.fk_user"; $sql.= " FROM ".MAIN_DB_PREFIX."holiday_users as u"; - $sql.= " WHERE u.fk_user NOT IN(".$listUsersDolibarr.")"; + $sql.= " WHERE u.fk_user NOT IN (".$listUsersDolibarr.")"; - $result = $this->db->query($sql); + $resql = $this->db->query($sql); // Si pas d'erreur SQL - if($result) { + if ($resql) { $i = 0; $num = $this->db->num_rows($resql); @@ -1594,5 +1591,27 @@ class Holiday extends CommonObject } } + /** + * Initialise an instance with random values. + * Used to build previews or test instances. + * id must be 0 if object instance is a specimen. + * + * @return void + */ + function initAsSpecimen() + { + global $user,$langs; + + // Initialise parameters + $this->id=0; + $this->specimen=1; + + $this->fk_user=1; + $this->description='SPECIMEN description'; + $this->date_debut=dol_now(); + $this->date_fin=dol_now()+(24*3600); + $this->fk_validator=1; + } + } ?> diff --git a/htdocs/holiday/common.inc.php b/htdocs/holiday/common.inc.php index d321d6d386e..21bd2d6e231 100644 --- a/htdocs/holiday/common.inc.php +++ b/htdocs/holiday/common.inc.php @@ -43,14 +43,14 @@ if (empty($conf->holiday->enabled)) } -$verifConf.= "SELECT value"; -$verifConf.= " FROM ".MAIN_DB_PREFIX."holiday_config"; -$verifConf.= " WHERE name = 'userGroup'"; +$sql = "SELECT value"; +$sql.= " FROM ".MAIN_DB_PREFIX."holiday_config"; +$sql.= " WHERE name = 'userGroup'"; -$result = $db->query($verifConf); +$result = $db->query($sql); $obj = $db->fetch_object($result); -if($obj->value == NULL) +if ($obj->value == NULL) { llxHeader('',$langs->trans('CPTitreMenu')); print '
'; diff --git a/htdocs/holiday/fiche.php b/htdocs/holiday/fiche.php index f528499cae0..43dba92b726 100644 --- a/htdocs/holiday/fiche.php +++ b/htdocs/holiday/fiche.php @@ -1,20 +1,21 @@ - * Copyright (C) 2011 Dimitri Mouillard -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* +/* Copyright (C) 2011 Dimitri Mouillard + * Copyright (C) 2012 Laurent Destailleur + * Copyright (C) 2012 Regis Houssin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . -*/ + */ /** * \file fiche.php @@ -33,14 +34,14 @@ require_once DOL_DOCUMENT_ROOT.'/holiday/common.inc.php'; // Get parameters $myparam = GETPOST("myparam"); -$action=GETPOST('action'); -$id=GETPOST('id'); +$action=GETPOST('action', 'alpha'); +$id=GETPOST('id', 'int'); // Protection if external user if ($user->societe_id > 0) accessforbidden(); $user_id = $user->id; - +$now=dol_now(); /******************************************************************* @@ -50,7 +51,6 @@ $user_id = $user->id; // Si création de la demande if ($action == 'create') { - // Si pas le droit de créer une demande if(!$user->rights->holiday->write) { @@ -262,48 +262,47 @@ if ($action == 'confirm_send') $verif = $cp->update($user->id); // Si pas d'erreur SQL on redirige vers la fiche de la demande - if($verif > 0) { - - // A + if ($verif > 0) + { + // To $destinataire = new User($db); $destinataire->fetch($cp->fk_validator); $emailTo = $destinataire->email; - // De + // From $expediteur = new User($db); $expediteur->fetch($cp->fk_user); $emailFrom = $expediteur->email; - // Sujet - if($conf->global->MAIN_APPLICATION_TITLE != NULL) { - $societeName = addslashes($conf->global->MAIN_APPLICATION_TITLE); - } else { - $societeName = addslashes($conf->global->MAIN_INFO_SOCIETE_NOM); - } + // Subject + $societeName = $conf->global->MAIN_INFO_SOCIETE_NOM; + if (! empty($conf->global->MAIN_APPLICATION_TITLE)) $societeName = $conf->global->MAIN_APPLICATION_TITLE; - $subject = stripslashes($societeName)." - Demande de congés payés à valider"; + $subject = $societeName." - ".$langs->transnoentitiesnoconv("HolidaysToValidate"); - // Contenu - $message = "Bonjour {$destinataire->prenom},\n\n"; + // Content + $message = $langs->transnoentitiesnoconv("Hello")." ".$destinataire->prenom.",\n"; + $message.= "\n"; $message.= "Veuillez trouver ci-dessous une demande de congés payés à valider.\n"; $delayForRequest = $cp->getConfCP('delayForRequest'); //$delayForRequest = $delayForRequest * (60*60*24); - $now=dol_now(); $nextMonth = dol_time_plus_duree($now, $delayForRequest, 'd'); // Si l'option pour avertir le valideur en cas de délai trop court - if($cp->getConfCP('AlertValidatorDelay')) { - if($cp->date_debut < $nextMonth) { + if($cp->getConfCP('AlertValidatorDelay')) + { + if($cp->date_debut < $nextMonth) + { $message.= "\n"; - $message.= "Cette demande de congés payés à été effectué dans un"; - $message.= " délai de moins de ".$cp->getConfCP('delayForRequest')." jours avant ceux-ci.\n"; + $message.= "Cette demande de congés payés à été effectué dans un délai de moins de ".$cp->getConfCP('delayForRequest')." jours avant ceux-ci.\n"; } } // Si l'option pour avertir le valideur en cas de solde inférieur à la demande - if($cp->getConfCP('AlertValidatorSolde')) { + if($cp->getConfCP('AlertValidatorSolde')) + { $nbopenedday=num_open_day($cp->date_debut,$cp->date_fin,0,1); if ($nbopenedday > $cp->getCPforUser($cp->fk_user)) { @@ -313,24 +312,26 @@ if ($action == 'confirm_send') } $message.= "\n"; - $message.= "- Demandeur : {$expediteur->prenom} {$expediteur->nom}\n"; - $message.= "- Période : du ".date('d/m/Y',strtotime($cp->date_debut))." au ".date('d/m/Y',strtotime($cp->date_fin))."\n"; - $message.= "- Lien : {$dolibarr_main_url_root}/holiday/fiche.php?id={$cp->rowid}\n\n"; - $message.= "Bien cordialement,\n".$societeName; + $message.= "- ".$langs->transnoentitiesnoconv("Name")." : ".$expediteur->prenom." ".$expediteur->nom."\n"; + $message.= "- ".$langs->transnoentitiesnoconv("Period")." : ".dol_print_date($cp->date_debut,'day')." ".$langs->transnoentitiesnoconv("To")." ".dol_print_date($cp->date_fin,'day')."\n"; + $message.= "- ".$langs->transnoentitiesnoconv("Link")." : ".$dolibarr_main_url_root."/holiday/fiche.php?id=".$cp->rowid."\n\n"; + $message.= "\n"; $mail = new CMailFile($subject,$emailTo,$emailFrom,$message); // Envoi du mail $result=$mail->sendfile(); - if(!$result) { + if (!$result) + { header('Location: fiche.php?id='.$_GET['id'].'&error=mail&error_content='.$mail->error); exit; } - header('Location: fiche.php?id='.$_GET['id']); exit; - } else { + } + else + { // Sinon on affiche le formulaire de demande avec le message d'erreur SQL header('Location: fiche.php?id='.$_GET['id'].'&error=SQL_Create&msg='.$cp->error); exit; @@ -367,37 +368,34 @@ if($action == 'confirm_valid') $newSolde = $soldeActuel - ($nbJour*$cp->getConfCP('nbHolidayDeducted')); // On ajoute la modification dans le LOG - $cp->addLogCP($userID,$cp->fk_user,'Event : Prise de congés payés',$newSolde); + $cp->addLogCP($userID,$cp->fk_user,'Event : '.$langs->transnoentitiesnoconv("Holiday"),$newSolde); // Mise à jour du solde $cp->updateSoldeCP($cp->fk_user,$newSolde); - // A + // To $destinataire = new User($db); $destinataire->fetch($cp->fk_user); $emailTo = $destinataire->email; - // De + // From $expediteur = new User($db); $expediteur->fetch($cp->fk_validator); $emailFrom = $expediteur->email; - // Sujet - if($conf->global->MAIN_APPLICATION_TITLE != NULL) { - $societeName = addslashes($conf->global->MAIN_APPLICATION_TITLE); - } else { - $societeName = addslashes($conf->global->MAIN_INFO_SOCIETE_NOM); - } + // Subject + $societeName = $conf->global->MAIN_INFO_SOCIETE_NOM; + if (! empty($conf->global->MAIN_APPLICATION_TITLE)) $societeName = $conf->global->MAIN_APPLICATION_TITLE; - $subject = stripslashes($societeName)." - Demande de congés payés validée"; - - // Contenu - $message = "Bonjour {$destinataire->prenom},\n\n"; - $message.= "Votre demande de congés payés du ".$cp->date_debut." au ".$cp->date_fin." vient d'être validée!\n"; - $message.= "- Valideur : {$expediteur->prenom} {$expediteur->nom}\n"; - $message.= "- Lien : {$dolibarr_main_url_root}/holiday/fiche.php?id={$cp->rowid}\n\n"; - $message.= "Bien cordialement,\n".$societeName; + $subject = $societeName." - ".$langs->transnoentitiesnoconv("HolidaysValidated"); + // Content + $message = $langs->transnoentitiesnoconv("Hello")." ".$destinataire->prenom.",\n"; + $message.= "\n"; + $message.= "Votre demande de congés payés du ".dol_print_date($cp->date_debut,'day')." au ".dol_print_date($cp->date_fin,'day')." vient d'être validée!\n"; + $message.= "- ".$langs->transnoentitiesnoconv("ValidatedBy")." : ".$expediteur->prenom." ".$expediteur->nom."\n"; + $message.= "- ".$langs->transnoentitiesnoconv("Link")." : ".$dolibarr_main_url_root."/holiday/fiche.php?id=".$cp->rowid."\n\n"; + $message.= "\n"; $mail = new CMailFile($subject,$emailTo,$emailFrom,$message); @@ -443,33 +441,30 @@ if ($action == 'confirm_refuse') // Si pas d'erreur SQL on redirige vers la fiche de la demande if($verif > 0) { - // A + // To $destinataire = new User($db); $destinataire->fetch($cp->fk_user); $emailTo = $destinataire->email; - // De + // From $expediteur = new User($db); $expediteur->fetch($cp->fk_validator); $emailFrom = $expediteur->email; - // Sujet - if($conf->global->MAIN_APPLICATION_TITLE != NULL) { - $societeName = addslashes($conf->global->MAIN_APPLICATION_TITLE); - } else { - $societeName = addslashes($conf->global->MAIN_INFO_SOCIETE_NOM); - } + // Subject + $societeName = $conf->global->MAIN_INFO_SOCIETE_NOM; + if (! empty($conf->global->MAIN_APPLICATION_TITLE)) $societeName = $conf->global->MAIN_APPLICATION_TITLE; - $subject = stripslashes($societeName)." - Demande de congés payés refusée"; + $subject = $societeName." - ".$langs->transnoentitiesnoconv("HolidaysRefused"); - // Contenu - $message = "Bonjour {$destinataire->prenom},\n\n"; - $message.= "Votre demande de congés payés ".$cp->date_debut." au ".$cp->date_fin." vient d'être refusée pour le motif suivant :\n"; + // Content + $message = $langs->transnoentitiesnoconv("Hello")." ".$destinataire->prenom.",\n"; + $message.= "\n"; + $message.= "Votre demande de congés payés ".dol_print_date($cp->date_debut,'day')." ".$langs->transnoentitiesnoconv("To")." ".dol_print_date($cp->date_fin,'day')." vient d'être refusée pour le motif suivant :\n"; $message.= $_POST['detail_refuse']."\n\n"; - $message.= "- Valideur : {$expediteur->prenom} {$expediteur->nom}\n"; - $message.= "- Lien : {$dolibarr_main_url_root}/holiday/fiche.php?id={$cp->rowid}\n\n"; - $message.= "Bien cordialement,\n".$societeName; - + $message.= "- ".$langs->transnoentitiesnoconv("ModifiedBy")." : ".$expediteur->prenom." ".$expediteur->nom."\n"; + $message.= "- ".$langs->transnoentitiesnoconv("Link")." : ".$dolibarr_main_url_root."/holiday/fiche.php?id=".$cp->rowid."\n\n"; + $message.= "\n"; $mail = new CMailFile($subject,$emailTo,$emailFrom,$message); @@ -517,32 +512,29 @@ if ($action == 'confirm_cancel' && $_GET['confirm'] == 'yes') // Si pas d'erreur SQL on redirige vers la fiche de la demande if($verif > 0) { - // A + // To $destinataire = new User($db); $destinataire->fetch($cp->fk_user); $emailTo = $destinataire->email; - // De + // From $expediteur = new User($db); $expediteur->fetch($cp->fk_validator); $emailFrom = $expediteur->email; - // Sujet - if($conf->global->MAIN_APPLICATION_TITLE != NULL) { - $societeName = addslashes($conf->global->MAIN_APPLICATION_TITLE); - } else { - $societeName = addslashes($conf->global->MAIN_INFO_SOCIETE_NOM); - } + // Subject + $societeName = $conf->global->MAIN_INFO_SOCIETE_NOM; + if (! empty($conf->global->MAIN_APPLICATION_TITLE)) $societeName = $conf->global->MAIN_APPLICATION_TITLE; - $subject = stripslashes($societeName)."- Demande de congés payés annulée"; - - // Contenu - $message = "Bonjour {$destinataire->prenom},\n\n"; - $message.= "Votre demande de congés payés ".$cp->date_debut." au ".$cp->date_fin." vient d'être annulée !\n"; - $message.= "- Valideur : {$expediteur->prenom} {$expediteur->nom}\n"; - $message.= "- Lien : {$dolibarr_main_url_root}/holiday/fiche.php?id={$cp->rowid}\n\n"; - $message.= "Bien cordialement,\n".$societeName; + $subject = $societeName." - ".$langs->transnoentitiesnoconv("HolidaysCanceled"); + // Content + $message = $langs->transnoentitiesnoconv("Hello")." ".$destinataire->prenom.",\n"; + $message.= "\n"; + $message.= "Votre demande de congés ".dol_print_date($cp->date_debut,'day')." ".$langs->transnoentitiesnoconv("To")." ".dol_print_date($cp->date_fin,'day')." va été annulée.\n"; + $message.= "- ".$langs->transnoentitiesnoconv("ModifiedBy")." : ".$expediteur->prenom." ".$expediteur->nom."\n"; + $message.= "- ".$langs->transnoentitiesnoconv("Link")." : ".$dolibarr_main_url_root."/holiday/fiche.php?id=".$cp->rowid."\n\n"; + $message.= "\n"; $mail = new CMailFile($subject,$emailTo,$emailFrom,$message); @@ -575,6 +567,9 @@ if ($action == 'confirm_cancel' && $_GET['confirm'] == 'yes') * View ****************************************************/ +$form = new Form($db); + + llxHeader(array(),$langs->trans('CPTitreMenu')); if (empty($id) || $action == 'add' || $action == 'request') @@ -622,7 +617,6 @@ if (empty($id) || $action == 'add' || $action == 'request') dol_htmloutput_mesg('',$errors,'error'); } - $html = new Form($db); $cp = new Holiday($db); $delayForRequest = $cp->getConfCP('delayForRequest'); @@ -679,10 +673,10 @@ if (empty($id) || $action == 'add' || $action == 'request') print '
'; print ''; @@ -691,10 +685,10 @@ if (empty($id) || $action == 'add' || $action == 'request') print ''; print ''; @@ -703,11 +697,11 @@ if (empty($id) || $action == 'add' || $action == 'request') // Liste des utiliseurs du groupes choisi dans la config $idGroupValid = $cp->getConfCP('userGroup'); - $validator = new UserGroup($db,$idGroupValid); + $validator = new UserGroup($db, $idGroupValid); $valideurarray = $validator->listUsersForGroup(); print ''; print ''; print ''; @@ -799,44 +793,39 @@ else { if ($action == 'delete' && $cp->statut == 1) { - if($user->rights->holiday->delete) { - $html = new Form($db); - - $ret=$html->form_confirm("fiche.php?id=".$_GET['id'],$langs->trans("TitleDeleteCP"),$langs->trans("ConfirmDeleteCP"),"confirm_delete", '', 0, 1); + if($user->rights->holiday->delete) + { + $ret=$form->form_confirm("fiche.php?id=".$_GET['id'],$langs->trans("TitleDeleteCP"),$langs->trans("ConfirmDeleteCP"),"confirm_delete", '', 0, 1); if ($ret == 'html') print '
'; } } // Si envoi en validation - if ($action == 'sendToValidate' && $cp->statut == 1 && $userID == $cp->fk_user) { - $html = new Form($db); - - $ret=$html->form_confirm("fiche.php?id=".$_GET['id'],$langs->trans("TitleToValidCP"),$langs->trans("ConfirmToValidCP"),"confirm_send", '', 0, 1); + if ($action == 'sendToValidate' && $cp->statut == 1 && $userID == $cp->fk_user) + { + $ret=$form->form_confirm("fiche.php?id=".$_GET['id'],$langs->trans("TitleToValidCP"),$langs->trans("ConfirmToValidCP"),"confirm_send", '', 0, 1); if ($ret == 'html') print '
'; } // Si validation de la demande - if ($action == 'valid' && $cp->statut == 2 && $userID == $cp->fk_validator) { - $html = new Form($db); - - $ret=$html->form_confirm("fiche.php?id=".$_GET['id'],$langs->trans("TitleValidCP"),$langs->trans("ConfirmValidCP"),"confirm_valid", '', 0, 1); + if ($action == 'valid' && $cp->statut == 2 && $userID == $cp->fk_validator) + { + $ret=$form->form_confirm("fiche.php?id=".$_GET['id'],$langs->trans("TitleValidCP"),$langs->trans("ConfirmValidCP"),"confirm_valid", '', 0, 1); if ($ret == 'html') print '
'; } // Si refus de la demande - if ($action == 'refuse' && $cp->statut == 2 && $userID == $cp->fk_validator) { - $html = new Form($db); - + if ($action == 'refuse' && $cp->statut == 2 && $userID == $cp->fk_validator) + { $array_input = array(array('type'=>"text",'label'=>"Entrez ci-dessous un motif de refus :",'name'=>"detail_refuse",'size'=>"50",'value'=>"")); - $ret=$html->form_confirm("fiche.php?id=".$_GET['id']."&action=confirm_refuse",$langs->trans("TitleRefuseCP"),"","confirm_refuse",$array_input,"",0); + $ret=$form->form_confirm("fiche.php?id=".$_GET['id']."&action=confirm_refuse",$langs->trans("TitleRefuseCP"),"","confirm_refuse",$array_input,"",0); if ($ret == 'html') print '
'; } // Si annulation de la demande - if ($action == 'cancel' && $cp->statut == 2 && $userID == $cp->fk_validator) { - $html = new Form($db); - - $ret=$html->form_confirm("fiche.php?id=".$_GET['id'],$langs->trans("TitleCancelCP"),$langs->trans("ConfirmCancelCP"),"confirm_cancel", '', 0, 1); + if ($action == 'cancel' && $cp->statut == 2 && $userID == $cp->fk_validator) + { + $ret=$form->form_confirm("fiche.php?id=".$_GET['id'],$langs->trans("TitleCancelCP"),$langs->trans("ConfirmCancelCP"),"confirm_cancel", '', 0, 1); if ($ret == 'html') print '
'; } @@ -850,8 +839,6 @@ else print ''."\n"; print ''."\n"; print ''."\n"; - - $html = new Form($db); } print '
'.$form->editfieldkey("Weight",'trueWeight',$object->trueWeight,$object,$user->rights->expedition->creer).''; print $form->editfieldval("Weight",'trueWeight',$object->trueWeight,$object,$user->rights->expedition->creer); - print $object->weight_units?measuring_units_string($object->weight_units,"weight"):''; + print ($object->trueWeight && $object->weight_units!='')?' '.measuring_units_string($object->weight_units,"weight"):''; print '
'.$langs->trans("Volume").''; - if (! empty($object->trueVolume)) // FIXME trueVolume not exist - { - // If sending volume defined - print $object->trueVolume.' '.measuring_units_string($object->volumeUnit,"volume"); - } - else - { - // If sending volume not defined we use sum of products - if ($totalVolume > 0) - { - print $totalVolume.' '; - if ($volumeUnit < 50) print measuring_units_string(0,"volume"); - else print measuring_units_string($volumeUnit,"volume"); - } - else print ' '; - } - print "
'.$form->editfieldkey("Width",'trueWidth',$object->trueWidth,$object,$user->rights->expedition->creer).''; print $form->editfieldval("Width",'trueWidth',$object->trueWidth,$object,$user->rights->expedition->creer); - print $object->trueWidth?measuring_units_string($object->width_units,"size"):''; + print ($object->trueWidth && $object->width_units!='')?' '.measuring_units_string($object->width_units,"size"):''; print '
'.$form->editfieldkey("Height",'trueHeight',$object->trueHeight,$object,$user->rights->expedition->creer).''; print $form->editfieldval("Height",'trueHeight',$object->trueHeight,$object,$user->rights->expedition->creer); - print $object->trueHeight?measuring_units_string($object->height_units,"size"):''; + print ($object->trueHeight && $object->height_units!='')?' '.measuring_units_string($object->height_units,"size"):''; print '
'.$form->editfieldkey("Depth",'trueDepth',$object->trueDepth,$object,$user->rights->expedition->creer).''; print $form->editfieldval("Depth",'trueDepth',$object->trueDepth,$object,$user->rights->expedition->creer); - print $object->trueDepth?measuring_units_string($object->depth_units,"size"):''; + print ($object->trueDepth && $object->depth_units!='')?' '.measuring_units_string($object->depth_units,"size"):''; print '
'; + print $langs->trans("Volume"); + print ''; + $calculatedVolume=0; + if ($object->trueWidth && $object->trueHeight && $object->trueDepth) $calculatedVolume=($object->trueWidth * $object->trueHeight * $object->trueDepth); + // If sending volume not defined we use sum of products + if ($calculatedVolume > 0) + { + print $calculatedVolume.' '; + if ($volumeUnit < 50) print measuring_units_string(0,"volume"); + else print measuring_units_string($volumeUnit,"volume"); + } + if ($totalVolume > 0) + { + if ($calculatedVolume) print ' ('.$langs->trans("SumOfProductVolumes").': '; + print $totalVolume; + if ($calculatedVolume) print ')'; + } + print "
'.$langs->trans("Status").''.$object->getLibStatut(4)."'; // Si la demande ne vient pas de l'agenda if(!isset($_GET['datep'])) { - $html->select_date(-1,'date_debut_'); + $form->select_date(-1,'date_debut_'); } else { $tmpdate = dol_mktime(0, 0, 0, GETPOST('datepmonth'), GETPOST('datepday'), GETPOST('datepyear')); - $html->select_date($tmpdate,'date_debut_'); + $form->select_date($tmpdate,'date_debut_'); } print '
'; // Si la demande ne vient pas de l'agenda if(!isset($_GET['datep'])) { - $html->select_date(-1,'date_fin_'); + $form->select_date(-1,'date_fin_'); } else { $tmpdate = dol_mktime(0, 0, 0, GETPOST('datefmonth'), GETPOST('datefday'), GETPOST('datefyear')); - $html->select_date($tmpdate,'date_fin_'); + $form->select_date($tmpdate,'date_fin_'); } print '
'; - print $html->select_dolusers($valideur,"valideur",1,"",0,$valideurarray,''); + print $form->select_dolusers($validator->id, "valideur", 1, "", 0, $valideurarray); print '
'; @@ -874,7 +861,7 @@ else print ''; print ''; print ''; print ''; } @@ -888,7 +875,7 @@ else print ''; print ''; print ''; print ''; } @@ -953,7 +940,7 @@ else $valideur = $validator->listUsersForGroup(); print ''; print ''; } @@ -983,44 +970,44 @@ else print ''; print '
'.$langs->trans('DateDebCP').''; - $html->select_date($cp->date_debut,'date_debut_'); + $form->select_date($cp->date_debut,'date_debut_'); print '
'.$langs->trans('DateFinCP').''; - $html->select_date($cp->date_fin,'date_fin_'); + $form->select_date($cp->date_fin,'date_fin_'); print '
'; - $html->select_users($cp->fk_validator,"valideur",1,"",0,$valideur,''); + $form->select_users($cp->fk_validator,"valideur",1,"",0,$valideur,''); print '
'; - dol_fiche_end(); - - print '
'."\n"; - - if ($edit) + if ($edit && $user->id == $cp->fk_user && $cp->statut == 1) { - print '
'; + print '
'; if($user->rights->holiday->write && $_GET['action'] == 'edit' && $cp->statut == 1) { print ''; } - print '
'; + print ''; print ''; } + dol_fiche_end(); + if (! $edit) { - print '
'; - print '
'."\n"; + print '
'; // Boutons d'actions - - if($user->rights->holiday->write && $_GET['action'] != 'edit' && $cp->statut == 1) { - print ''.$langs->trans("EditCP").''; + if($user->rights->holiday->write && $_GET['action'] != 'edit' && $cp->statut == 1) + { + print ''.$langs->trans("EditCP").''; } - if($user->rights->holiday->delete && $cp->statut == 1) { - print ''.$langs->trans("DeleteCP").''; + if($user->rights->holiday->delete && $cp->statut == 1) + { + print ''.$langs->trans("DeleteCP").''; } - if($user->id == $cp->fk_user && $cp->statut == 1) { - print ''.$langs->trans("SendToValidationCP").''; + if($user->id == $cp->fk_user && $cp->statut == 1) + { + print ''.$langs->trans("SendToValidationCP").''; } // Si le statut est en attente de validation et que le valideur est connecté - if($userID == $cp->fk_validator && $cp->statut == 2) { - print ''.$langs->trans("ActionValidCP").''; - print ''.$langs->trans("ActionRefuseCP").''; - print ''.$langs->trans("ActionCancelCP").''; + if($userID == $cp->fk_validator && $cp->statut == 2) + { + print ''.$langs->trans("ActionValidCP").''; + print ''.$langs->trans("ActionRefuseCP").''; + print ''.$langs->trans("ActionCancelCP").''; } print '
'; diff --git a/htdocs/holiday/index.php b/htdocs/holiday/index.php index 4a10e6712d0..1c27e2978bf 100644 --- a/htdocs/holiday/index.php +++ b/htdocs/holiday/index.php @@ -1,6 +1,6 @@ - * Copyright (C) 2011 Dimitri Mouillard +/* Copyright (C) 2011 Dimitri Mouillard + * Copyright (C) 2012 Laurent Destailleur * Copyright (C) 2012 Regis Houssin * * This program is free software; you can redistribute it and/or modify @@ -71,6 +71,7 @@ $search_statut = GETPOST('select_statut'); $max_year = 5; $min_year = 10; +$filter=''; llxHeader(array(),$langs->trans('CPTitreMenu')); @@ -185,7 +186,7 @@ if($holiday_payes == '-1') $var=true; $num = count($holiday->holiday); $html = new Form($db); $htmlother = new FormOther($db); -print_barre_liste($langs->trans("ListeCP"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, "", $num,$nbtotalofrecords); +print_barre_liste($langs->trans("ListeCP"), $page, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, "", $num); print '
'; diff --git a/htdocs/includes/jquery/js/jquery-latest.min.jgz b/htdocs/includes/jquery/js/jquery-latest.min.jgz deleted file mode 100644 index 7d052bcf861..00000000000 Binary files a/htdocs/includes/jquery/js/jquery-latest.min.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/js/jquery-ui-latest.custom.min.jgz b/htdocs/includes/jquery/js/jquery-ui-latest.custom.min.jgz deleted file mode 100644 index 8bfca3a3e04..00000000000 Binary files a/htdocs/includes/jquery/js/jquery-ui-latest.custom.min.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/datatables/extras/Bootstrap/js/DT_bootstrap.jgz b/htdocs/includes/jquery/plugins/datatables/extras/Bootstrap/js/DT_bootstrap.jgz deleted file mode 100644 index 17b30bd297b..00000000000 Binary files a/htdocs/includes/jquery/plugins/datatables/extras/Bootstrap/js/DT_bootstrap.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/datatables/extras/ColReorder/js/ColReorder.min.jgz b/htdocs/includes/jquery/plugins/datatables/extras/ColReorder/js/ColReorder.min.jgz deleted file mode 100644 index 5790b3785af..00000000000 Binary files a/htdocs/includes/jquery/plugins/datatables/extras/ColReorder/js/ColReorder.min.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/datatables/extras/ColVis/js/ColVis.min.jgz b/htdocs/includes/jquery/plugins/datatables/extras/ColVis/js/ColVis.min.jgz deleted file mode 100644 index 517c50c5592..00000000000 Binary files a/htdocs/includes/jquery/plugins/datatables/extras/ColVis/js/ColVis.min.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/datatables/extras/TableTools/js/TableTools.min.jgz b/htdocs/includes/jquery/plugins/datatables/extras/TableTools/js/TableTools.min.jgz deleted file mode 100644 index a467a5f2008..00000000000 Binary files a/htdocs/includes/jquery/plugins/datatables/extras/TableTools/js/TableTools.min.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/datatables/js/jquery.dataTables.min.jgz b/htdocs/includes/jquery/plugins/datatables/js/jquery.dataTables.min.jgz deleted file mode 100644 index e7a91f87f75..00000000000 Binary files a/htdocs/includes/jquery/plugins/datatables/js/jquery.dataTables.min.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/jeditable/jquery.jeditable.min.jgz b/htdocs/includes/jquery/plugins/jeditable/jquery.jeditable.min.jgz deleted file mode 100644 index c6f41e14005..00000000000 Binary files a/htdocs/includes/jquery/plugins/jeditable/jquery.jeditable.min.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/jstree/jquery.jstree.min.jgz b/htdocs/includes/jquery/plugins/jstree/jquery.jstree.min.jgz deleted file mode 100644 index 8ba61f32afc..00000000000 Binary files a/htdocs/includes/jquery/plugins/jstree/jquery.jstree.min.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.jgz b/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.jgz deleted file mode 100644 index a3c068acb0e..00000000000 Binary files a/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.js b/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.js index a25c9eb872c..270ddb1d08e 100644 --- a/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.js +++ b/htdocs/includes/jquery/plugins/layout/jquery.layout-latest.js @@ -1,4353 +1,5919 @@ -/** - * @preserve jquery.layout 1.3.0 - Release Candidate 29.15 - * $Date: 2011/07/06 11:40:21 $ - * $Rev: 302915 $ - * - * 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.15 - * - * 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 ($) { - -/* - * GENERIC $.layout METHODS - used by all layouts - */ -$.layout = { - - version: "1.3.rc29.15" -, revision: 0.032915 // 1.3.0 final = 1.0300 - major(n+).minor(nn)+patch(nn+) - - // LANGUAGE CUSTOMIZATION -, language: { - // Tips and messages for resizers, togglers, custom buttons, etc. - Open: "Open" // eg: "Open Pane" - , Close: "Close" - , Resize: "Resize" - , Slide: "Slide Open" - , Pin: "Pin" - , Unpin: "Un-Pin" - , noRoomToOpenTip: "Not enough room to show this pane." - // Developer error messages - , pane: "pane" // description of "layout pane element" - , selector: "selector" // description of "jQuery-selector" - , errButton: "Error Adding Button \n\nInvalid " - , 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'!" - } - - // can update code here if $.browser is phased out -, browser: { - mozilla: !!$.browser.mozilla - , webkit: !!$.browser.webkit || !!$.browser.safari // webkit = jQ 1.4 - , msie: !!$.browser.msie - , isIE6: !!$.browser.msie && $.browser.version == 6 - , boxModel: false // page must load first, so will be updated set by _create - //, version: $.browser.version - not used - } - - /* - * GENERIC UTILITY METHODS - */ - - // 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 = $('
').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 - */ -, getElementDimensions: function ($E) { - var - d = {} // dimensions hash - , x = d.css = {} // CSS hash - , i = {} // TEMP insets - , b, p // TEMP border, padding - , N = $.layout.cssNum - , 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; - }); - - d.offsetWidth = $E.innerWidth(); - d.offsetHeight = $E.innerHeight(); - d.outerWidth = $E.outerWidth(); - d.outerHeight = $E.outerHeight(); - d.innerWidth = Math.max(0, d.outerWidth - i.Left - i.Right); - d.innerHeight = Math.max(0, d.outerHeight - i.Top - i.Bottom); - - x.width = $E.width(); - x.height = $E.height(); - x.top = N($E,"top",true); - x.bottom = N($E,"bottom",true); - x.left = N($E,"left",true); - x.right = N($E,"right",true); - - //d.visible = $E.is(":visible");// && x.width > 0 && x.height > 0; - - return d; - } - -, getElementCSS: 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.} $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 a CSS property - 0 if property does not exist - * - * @see Called by many methods - * @param {Array.} $E Must pass a jQuery object - first element is processed - * @param {string} prop The name of the CSS property, eg: top, width, etc. - * @param {boolean=} allowAuto true = return 'auto' if that is value; false = return 0 - * @return {(string|number)} Usually used to get an integer value for position (top, left) or size (height, width) - */ -, cssNum: function ($E, prop, allowAuto) { - if (!$E.jquery) $E = $($E); - var CSS = $.layout.showInvisibly($E) - , p = $.curCSS($E[0], prop, true) - , v = allowAuto && p=="auto" ? p : (parseInt(p, 10) || 0); - $E.css( CSS ); // RESET - return v; - } - -, 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); - } - - - /** - * UTLITY for mouse tracking - FUTURE REFERENCE - * - * init: if (!window.mouse) { - * window.mouse = { x: 0, y: 0 }; - * $(document).mousemove( $.layout.trackMouse ); - * } - * - * @param {Object} evt - * -, trackMouse: function (evt) { - window.mouse = { x: evt.clientX, y: evt.clientY }; - } - */ - - /** - * 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 // evt.clientX ? - , y = evt.pageY // evt.clientY ? - ; - // 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 - * ########################### - */ - var - - // LANGUAGE - for tips & messages - lang = $.layout.language // internal alias - - // DEFAULT OPTIONS - CHANGE IF DESIRED -, 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 - , initPanes: true // false = DO NOT initialize the panes onLoad - will init later - , showErrorMessages: true // enables fatal error messages to warn developers of common errors - // 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.noRoomToOpenTip - , 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: $.layout.browser.webkit // Chrome triggers slideClosed as it 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 -, 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! -, 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! -, _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 - */ -, 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 - */ -, 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 - */ -, 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 - */ -, min = function (x,y) { return Math.min(x,y); } -, 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 - */ -, _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) - */ -, _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' - */ -, _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 - */ -, _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, pane-options, and layout-name - return fn( pane, $Ps[pane], state[pane], options[pane], options.name ); - else // must be a layout/container callback - pass suitable info - return fn( Instance, state, options, options.name ); - } - } - catch (ex) {} - } - - /** - * cure iframe display issues in IE & other browsers - */ -, _fixIframe = function (pane) { - if ($.layout.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); - } - - /** - * 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 - */ -, cssW = function (el, outerWidth) { - var str = isStr(el) - , $E = str ? $Ps[el] : $(el) - ; - if (!$E.length) return 0; - if (isNaN(outerWidth)) // not specified - outerWidth = str ? getPaneSize(el) : $E.outerWidth(); - return $.layout.cssWidth($E, outerWidth); - } - - /** - * @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 - */ -, cssH = function (el, outerHeight) { - var str = isStr(el) - , $E = str ? $Ps[el] : $(el) - ; - if (!$E.length) return 0; - if (isNaN(outerHeight)) // not specified - outerHeight = str ? getPaneSize(el) : $E.outerHeight(); - return $.layout.cssHeight($E, outerHeight); - } - - /** - * @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 - */ -, 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 - */ -, 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 - */ -, 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 (!$.layout.browser.mozilla) // FireFox refreshes iframes - IE does not - // 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 - */ -, 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 (!$.layout.browser.mozilla) // FireFox refreshes iframes - IE does not - $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 - */ -, 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} - */ -, _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 = $.layout.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 - */ -, 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 - */ -, setSizeLimits = function (pane, slide) { - if (!isInitialized()) return; - 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 - */ -, 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 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 - */ -, elDims = function ($E) { return $.layout.getElementDimensions($E); } - -, elCSS = function ($E, list) { return $.layout.getElementCSS($E, list); } - - - /** - * @param {!Object} el - * @param {boolean=} allStates - */ -, 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); - } -, 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) ); - } -, removeHover = function (evt, el) { - var $E = $(el || this); - $E.removeClass( getHoverClasses($E, true) ); - } - -, onResizerEnter = function (evt) { - $('body').disableSelection(); - addHover(evt, this); - } -, 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 mixed true = fully initialized | false = panes not initialized (yet) | 'cancel' = abort - */ -, _create = function () { - // initialize config/options - initOptions(); - var o = options; - - $.layout.browser.boxModel = $.support.boxModel; - - // update options with saved state, if option enabled - if (o.useStateCookie && o.cookie.autoLoad) - loadCookie(); // Update options from state-cookie - - // TEMP state so isInitialized returns true during init process - state.creatingLayout = true; - - // options & state have been initialized, so now run beforeLoad callback - // onload will CANCEL layout creation if it returns false - if (false === _execCallback(null, o.onload_start)) - return 'cancel'; - - // initialize the container element - _initContainer(); - - // bind hotkey function - keyDown - if required - initHotkeys(); - - // search for and bind custom-buttons - if (o.autoBindCustomButtons) initButtons(); - - // bind window.onunload - $(window).bind("unload."+ sID, unload); - - // if layout elements are hidden, then layout WILL NOT complete initialization! - // initLayoutElements will set initialized=true and run the onload callback IF successful - if (o.initPanes) _initLayoutElements(); - - delete state.creatingLayout; - - return state.initialized; - } - - /** - * Initialize the layout IF not already - * - * @see All methods in Instance run this test - * @return boolean true = layoutElements have been initialized | false = panes are not initialized (yet) - */ -, isInitialized = function () { - if (state.initialized || state.creatingLayout) return true; // already initialized - else return _initLayoutElements(); // try to init panes NOW - } - - /** - * Initialize the layout - called automatically whenever an instance of layout is created - * - * @see _create() & isInitialized - * @return An object pointer to the instance created - */ -, _initLayoutElements = function () { - // initialize config/options - var o = options; - - // CANNOT init panes inside a hidden container! - if (!$N.is(":visible")) - return false; - // a center pane is required, so make sure it exists - if (!getPane('center').length) { - if (o.showErrorMessages) alert( lang.errCenterPaneMissing ); - return false; - } - - // TEMP state so isInitialized returns true during init process - state.creatingLayout = true; - - // update Container dims - $.extend(sC, elDims( $N )); - - // initialize all layout elements - 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 resizeAll() for 'this layout instance' to window.resize event - if (o.resizeWithWindow && !$N.data("layoutRole")) // skip if 'nested' inside a pane - $(window).bind("resize."+ sID, windowResize); - - delete state.creatingLayout; - state.initialized = true; - - _execCallback(null, o.onload_end || o.onload); - - return true; // elements initialized successfully - } - - -, windowResize = function () { - var delay = Number(options.resizeWithWindowDelay); - if (delay < 10) delay = 100; // MUST have a delay! - // 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"); - var dims = elDims( $N ); - // only trigger resizeAll() if container has changed size - if (dims.innerWidth !== sC.innerWidth || dims.innerHeight !== sC.innerHeight) - resizeAll(); - }, delay); - // ALSO set fixed-delay timer, if not already running - if (!timer.data["winResizeRepeater"]) setWindowResizeRepeater(); - } - -, setWindowResizeRepeater = function () { - var delay = Number(options.resizeWithWindowMaxDelay); - if (delay > 0) - timer.set("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay); - } - -, 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() - */ -, _initContainer = function () { - var - tag = sC.tagName = $N[0].tagName - , o = options - , fullPage= (tag == "BODY") - , props = "overflow,position,margin,padding,border" - , CSS = {} - , hid = "hidden" // used A LOT! - , isVis = $N.is(":visible") - ; - // sC -> state.container - sC.selector = $N.selector.split(".slice")[0]; - sC.ref = tag +"/"+ sC.selector; // used in messages - - $N .data("layout", Instance) - .data("layoutContainer", sID) // unique identifier for internal use - .addClass(o.containerClass) - ; - - // SAVE original container CSS for use in destroy() - if (!$N.data("layoutCSS")) { - // handle props like overflow different for BODY & HTML - has 'system default' values - if (fullPage) { - CSS = $.extend( elCSS($N, props), { - height: $N.css("height") - , overflow: $N.css("overflow") - , overflowX: $N.css("overflowX") - , overflowY: $N.css("overflowY") - }); - // ALSO SAVE 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 = elCSS($N, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY"); - - $N.data("layoutCSS", CSS); - } - - try { // format html/body if this is a full page layout - if (fullPage) { - $("html").css({ - height: "100%" - , overflow: hid - , overflowX: hid - , overflowY: hid - }); - $("body").css({ - position: "relative" - , height: "100%" - , overflow: hid - , overflowX: hid - , overflowY: hid - , margin: 0 - , padding: 0 // TODO: test whether body-padding could be handled? - , border: "none" // a body-border creates problems because it cannot be measured! - }); - - // set current layout-container dimensions - $.extend(sC, elDims( $N )); - } - else { // set required CSS for overflow and position - // ENSURE container will not 'scroll' - CSS = { overflow: hid, overflowX: hid, overflowY: hid } - var - p = $N.css("position") - , h = $N.css("height") - ; - // if this is a NESTED layout, then container/outer-pane ALREADY has position and height - if (!$N.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' - */ - } - $N.css( CSS ); - - // set current layout-container dimensions - if (isVis) { - $.extend(sC, elDims( $N )); - if (o.showErrorMessages && sC.innerHeight < 2) - alert( lang.errContainerHeight.replace(/CONTAINER/, sC.ref) ); - } - } - } catch (ex) {} - } - - /** - * Bind layout hotkeys - if options enabled - * - * @see _create() and addPane() - * @param {string=} panes The edge(s) to process, blank = all - */ -, 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() - */ -, 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 - */ -, 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 $N.find(sel).eq(0); - else { // class or other selector - var $P = $N.children(sel).eq(0); - // look for the pane nested inside a 'form' element - return $P.length ? $P : $N.children("form:first").children(sel).eq(0); - } - } -, 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, true ); - }); - - // 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"); - - // Chrome fires callback BEFORE it completes resizing, so add a delay before handling children - setTimeout(function(){ - $.each(_c.allPanes.split(","), function (i, pane) { - var o = options[pane]; - if ($Ps[pane] && state[pane].isVisible) { // pane is OPEN - // trigger onResize callbacks for all panes with triggerEventsOnLoad = true - if (o.triggerEventsOnLoad) - _execCallback(pane, o.onresize_end || o.onresize); - resizeNestedLayout(pane); - } - }); - }, 50 ); // 50ms delay is enough - - if (options.showErrorMessages && $N.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 - */ -, addPane = function (pane, force) { - if (!force && !isInitialized()) return; - 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", elCSS($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 its '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 common to ALL panes - s.tagName = $P[0].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 - - // close or hide the pane if specified in settings - if (o.initClosed && o.closable && !o.initHidden) - 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 if (!s.noRoom) - // make the pane visible - in case was initially hidden - $P.css("display","block"); - // ELSE setAsOpen() - called later by initHandles() - - // RESET visibility now - pane will appear IF display:block - $P.css("visibility","visible"); - - // 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 - */ -, 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] = $("
") - // INIT TOGGLER BUTTON - , $T = (o.closable ? $Ts[pane] = $("
") : 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($N) // 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 - $(""+ o.togglerContent_open +"") - .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 - $(""+ o.togglerContent_closed +"") - .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 - */ -, initContent = function (pane, resize) { - if (!isInitialized()) return; - 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", elCSS($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() - */ -, 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 - */ -, 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: $N[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 - $('
') - .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 - */ -, 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 - $N .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 (!$N.data("layoutEdge") && $N.data("layoutCSS")) // RESET CSS - $N.css( $N.data("layoutCSS") ).removeData("layoutCSS"); - - // for full-page layouts, also reset the CSS - if (sC.tagName == "BODY" && ($N = $("html")).data("layoutCSS")) // RESET CSS - $N.css( $N.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 - */ -, removePane = function (pane, remove, skipResize) { - if (!isInitialized()) return; - 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 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 - */ -, hide = function (pane, noAnimation) { - if (!isInitialized()) return; - 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 - */ -, show = function (pane, openPane, noAnimation, noAlert) { - if (!isInitialized()) return; - 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 - */ -, toggle = function (pane, slide) { - if (!isInitialized()) return; - 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 - */ -, _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 - */ -, close = function (pane, force, noAnimation, skipCallback) { - if (!state.initialized && $Ps[pane]) { - _closePane(pane); // INIT pane as closed - return; - } - if (!isInitialized()) 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 - */ -, 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 - */ -, open = function (pane, slide, noAnimation, noAlert) { - if (!isInitialized()) return; - 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 - setSizeLimits(pane, slide); // update pane-state - 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 - */ -, 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, elDims($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 - */ -, slideOpen = function (evt_or_pane) { - if (!isInitialized()) return; - 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 - }; - } - -, slideClose = function (evt_or_pane) { - if (!isInitialized()) return; - 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 - }; - } - -, 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 - */ -, 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 ($.layout.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? - */ -, 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? - */ -, 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 - */ -, 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 ($.layout.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? - */ -, manualSizePane = function (pane, size, skipCallback) { - if (!isInitialized()) return; - // 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 - */ -, sizePane = function (pane, size, skipCallback, force) { - if (!isInitialized()) return; - var - o = options[pane] - , s = state[pane] - , $P = $Ps[pane] - , $R = $Rs[pane] - , side = _c[pane].side.toLowerCase() - , dimName = _c[pane].sizeType.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( dimName, max(1, cssSize(pane, size)) ); - -/* -var - edge = _c[pane].sizeType.toLowerCase() -, test = [{ - target: size - , attempt: size - , actual: edge=='width' ? $P.outerWidth() : $P.outerHeight() - }] -, lastTest = test[0] -, thisTest = {} -; -while (lastTest.actual != size) { - test.push( {} ); - thisTest = test[ test.length - 1 ]; - - if (lastTest.actual > size) - thisTest.attempt = Math.max(1, lastTest.attempt - (lastTest.actual - size)); - else // lastTest.actual < size - thisTest.attempt = Math.max(1, lastTest.attempt + (size - lastTest.actual)); - - $P.css( edge, cssSize(pane, thisTest.attempt) ); - - thisTest.actual = edge=='width' ? $P.outerWidth() : $P.outerHeight() - - // after 3 tries, is as close as its gonna get! - if (test.length == 3) break; - else lastTest = thisTest; -} -debugData( test, pane ); -*/ - - // update pane-state dimensions - s.size = size; - $.extend(s, elDims($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 - */ -, 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 = {} - , newCenter = calcNewCenterPaneDims() - ; - // update pane-state dimensions - $.extend(s, elDims($P)); - - if (pane == "center") { - if (!force && s.isVisible && newCenter.width == s.outerWidth && newCenter.height == s.outerHeight) - return true; // SKIP - pane already the correct size - // set state for makePaneFit() logic - $.extend(s, cssMinDims(pane), { - maxWidth: newCenter.width - , maxHeight: newCenter.height - }); - CSS = newCenter; - // convert OUTER width/height to CSS width/height - CSS.width = cssW(pane, CSS.width); - CSS.height = cssH(pane, CSS.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, elDims($P), cssMinDims(pane)) - if (!force && !s.noVerticalRoom && newCenter.height == s.outerHeight) - return true; // SKIP - pane already the correct size - // east/west have same top, bottom & height as center - CSS.top = newCenter.top; - CSS.bottom = newCenter.bottom; - CSS.height = cssH(pane, newCenter.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, elDims($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 = $.layout.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 - */ -, resizeAll = function () { - if (!state.initialized) { - _initLayoutElements(); - return; // no need to resize since we just initialized! - } - var oldW = sC.innerWidth - , oldH = sC.innerHeight - ; - // cannot size layout when 'container' is hidden or collapsed - if (!$N.is(":visible:") ) return; - $.extend( state.container, elDims( $N ) ); // UPDATE container dimensions - if (!sC.outerHeight) return; - - // 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 - */ -, 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? - */ -, sizeContent = function (panes, remeasure) { - if (!isInitialized()) return; - 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 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 - */ -, 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: $.layout.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: $.layout.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(); - } - }); - } - - -, enableClosable = function (pane) { - if (!isInitialized()) return; - 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() - ; - } - -, disableClosable = function (pane, hide) { - if (!isInitialized()) return; - 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", "") - ; - } - - -, enableSlidable = function (pane) { - if (!isInitialized()) return; - var $R = $Rs[pane], o = options[pane]; - if (!$R || !$R.data('draggable')) return; - options[pane].slidable = true; - if (s.isClosed) - bindStartSlidingEvent(pane, true); - } - -, disableSlidable = function (pane) { - if (!isInitialized()) return; - 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 - } - } - - -, enableResizable = function (pane) { - if (!isInitialized()) return; - 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) - ; - } - -, disableResizable = function (pane) { - if (!isInitialized()) return; - 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 - */ -, swapPanes = function (pane1, pane2) { - if (!isInitialized()) return; - // 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; - }; - } - - -; // END var DECLARATIONS - - /** - * 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 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 (!isInitialized()) return; - 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 its 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 (!isInitialized()) return; - 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.} If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise returns null - */ - function getBtn (selector, pane, action) { - var $E = $(selector) - , err = options.showErrorMessages; - if (!$E.length) { // element not found - if (err) alert(lang.errButton + lang.selector +": "+ selector); - } - else if (_c.borderPanes.indexOf(pane) == -1) // invalid 'pane' sepecified - if (err) alert(lang.errButton + lang.pane +": "+ 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.} $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) { - opts = _transformData(opts); - $.extend( true, options, opts ); // update layout options - // if layout has already been initialized, then UPDATE layout state - if (state.initialized) { - var pane, o, s, h, c, a = !animate; - $.each(_c.allPanes.split(","), function (idx, pane) { - o = opts[ pane ]; - if (typeof o != 'object') return; // no key, continue - s = o.size; - c = o.initClosed; - h = o.initHidden; - if (s > 0 || s=="auto") sizePane(pane, s); - if (h === true) hide(pane, a); - else if (c === false) open(pane, false, a ); - else if (c === true) close(pane, false, a); - else if (h === false) show(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 $N = $(this).eq(0); // FIRST matching Container element - if (!$N.length) { - if (options.showErrorMessages) - alert( lang.errContainerMissing ); - return null; - }; - - // Users retreive Instance of a layout with: $N.layout() OR $N.data("layout") - // return the Instance-pointer if layout has already been initialized - if ($N.data("layoutContainer") && $N.data("layout")) - return $N.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: $N // 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 - ditto - , 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 - , initPanes: isInitialized // 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 - if (_create() === 'cancel') // onload_start callback returned false to CANCEL layout creation - return null; - else // true OR false -- if layout-elements did NOT init (hidden or do not exist), can auto-init later - return Instance; // return the Instance object - -} +/** + * @preserve + * jquery.layout 1.3.0 - Release Candidate 30.74 + * $Date: 2012-10-28 08:00:00 (Sun, 28 Oct 2012) $ + * $Rev: 303007 $ + * + * Copyright (c) 2012 + * 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.rc30.74 + * + * 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 + */ + +/* JavaDoc Info: http://code.google.com/closure/compiler/docs/js-for-compiler.html + * {!Object} non-nullable type (never NULL) + * {?string} nullable type (sometimes NULL) - default for {Object} + * {number=} optional parameter + * {*} ALL types + */ + +// NOTE: For best readability, view with a fixed-width font and tabs equal to 4-chars + +;(function ($) { + +// alias Math methods - used a lot! +var min = Math.min +, max = Math.max +, round = Math.floor + +, isStr = function (v) { return $.type(v) === "string"; } + + /** + * @param {!Object} Instance + * @param {Array.} a_fn + */ +, runPluginCallbacks = function (Instance, a_fn) { + if ($.isArray(a_fn)) + for (var i=0, c=a_fn.length; i').appendTo("body"); + var d = { width: $c.css("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)$/) ? d[dim] : d; + } + + + /** + * Returns hash container 'display' and 'visibility' + * + * @see $.swap() - swaps CSS, runs callback, resets CSS + * @param {!Object} $E jQuery element + * @param {boolean=} [force=false] Run even if display != none + * @return {!Object} Returns current style props, if applicable + */ +, showInvisibly: function ($E, force) { + if ($E && $E.length && (force || $E.css("display") === "none")) { // only if not *already hidden* + var s = $E[0].style + // save ONLY the 'style' props because that is what we must restore + , CSS = { display: s.display || '', visibility: s.visibility || '' }; + // show element 'invisibly' so can be measured + $E.css({ display: "block", visibility: "hidden" }); + return CSS; + } + 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 + */ +, getElementDimensions: function ($E, inset) { + var + // dimensions hash - start with current data IF passed + d = { css: {}, inset: {} } + , x = d.css // CSS hash + , i = { bottom: 0 } // TEMP insets (bottom = complier hack) + , N = $.layout.cssNum + , off = $E.offset() + , b, p, ei // TEMP border, padding + ; + d.offsetLeft = off.left; + d.offsetTop = off.top; + + if (!inset) inset = {}; // simplify logic below + + $.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); + ei = e.toLowerCase(); + d.inset[ei] = inset[ei] >= 0 ? inset[ei] : p; // any missing insetX value = paddingX + i[ei] = d.inset[ei] + b; // total offset of content from outer side + }); + + x.width = $E.css("width"); + x.height = $E.height(); + x.top = N($E,"top",true); + x.bottom = N($E,"bottom",true); + x.left = N($E,"left",true); + x.right = N($E,"right",true); + + d.outerWidth = $E.outerWidth(); + d.outerHeight = $E.outerHeight(); + // calc the TRUE inner-dimensions, even in quirks-mode! + d.innerWidth = max(0, d.outerWidth - i.left - i.right); + d.innerHeight = max(0, d.outerHeight - i.top - i.bottom); + // layoutWidth/Height is used in calcs for manual resizing + // layoutW/H only differs from innerW/H when in quirks-mode - then is like outerW/H + d.layoutWidth = $E.innerWidth(); + d.layoutHeight = $E.innerHeight(); + + //if ($E.prop('tagName') === 'BODY') { debugData( d, $E.prop('tagName') ); } // DEBUG + + //d.visible = $E.is(":visible");// && x.width > 0 && x.height > 0; + + return d; + } + +, getElementStyles: 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 + } + + /** + * Return the innerWidth for the current browser/doctype + * + * @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles() + * @param {Array.} $E Must pass a jQuery object - first element is processed + * @param {number=} outerWidth (optional) Can pass a width, allowing calculations BEFORE element is resized + * @return {number} Returns the innerWidth of the elem by subtracting padding and borders + */ +, cssWidth: function ($E, outerWidth) { + // 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 b = $.layout.borderWidth + , n = $.layout.cssNum + , W = outerWidth + - b($E, "Left") + - b($E, "Right") + - n($E, "paddingLeft") + - n($E, "paddingRight"); + + return max(0,W); + } + + /** + * Return the innerHeight for the current browser/doctype + * + * @see initPanes(), sizeMidPanes(), initHandles(), sizeHandles() + * @param {Array.} $E Must pass a jQuery object - first element is processed + * @param {number=} outerHeight (optional) Can pass a width, allowing calculations BEFORE element is resized + * @return {number} Returns the innerHeight of the elem by subtracting padding and borders + */ +, cssHeight: function ($E, outerHeight) { + // 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 b = $.layout.borderWidth + , n = $.layout.cssNum + , H = outerHeight + - b($E, "Top") + - b($E, "Bottom") + - n($E, "paddingTop") + - n($E, "paddingBottom"); + + return max(0,H); + } + + /** + * Returns the 'current CSS numeric value' for a CSS property - 0 if property does not exist + * + * @see Called by many methods + * @param {Array.} $E Must pass a jQuery object - first element is processed + * @param {string} prop The name of the CSS property, eg: top, width, etc. + * @param {boolean=} [allowAuto=false] true = return 'auto' if that is value; false = return 0 + * @return {(string|number)} Usually used to get an integer value for position (top, left) or size (height, width) + */ +, cssNum: function ($E, prop, allowAuto) { + if (!$E.jquery) $E = $($E); + var CSS = $.layout.showInvisibly($E) + , p = $.css($E[0], prop, true) + , v = allowAuto && p=="auto" ? p : Math.round(parseFloat(p) || 0); + $E.css( CSS ); // RESET + return v; + } + +, borderWidth: function (el, side) { + if (el.jquery) el = el[0]; + var b = "border"+ side.substr(0,1).toUpperCase() + side.substr(1); // left => Left + return $.css(el, b+"Style", true) === "none" ? 0 : Math.round(parseFloat($.css(el, b+"Width", true)) || 0); + } + + /** + * Mouse-tracking utility - FUTURE REFERENCE + * + * init: if (!window.mouse) { + * window.mouse = { x: 0, y: 0 }; + * $(document).mousemove( $.layout.trackMouse ); + * } + * + * @param {Object} evt + * +, trackMouse: function (evt) { + window.mouse = { x: evt.clientX, y: evt.clientY }; + } + */ + + /** + * 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 // evt.clientX ? + , y = evt.pageY // evt.clientY ? + ; + // 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)); + } + + /** + * Message/Logging Utility + * + * @example $.layout.msg("My message"); // log text + * @example $.layout.msg("My message", true); // alert text + * @example $.layout.msg({ foo: "bar" }, "Title"); // log hash-data, with custom title + * @example $.layout.msg({ foo: "bar" }, true, "Title", { sort: false }); -OR- + * @example $.layout.msg({ foo: "bar" }, "Title", { sort: false, display: true }); // alert hash-data + * + * @param {(Object|string)} info String message OR Hash/Array + * @param {(Boolean|string|Object)=} [popup=false] True means alert-box - can be skipped + * @param {(Object|string)=} [debugTitle=""] Title for Hash data - can be skipped + * @param {Object=} [debugOpts] Extra options for debug output + */ +, msg: function (info, popup, debugTitle, debugOpts) { + if ($.isPlainObject(info) && window.debugData) { + if (typeof popup === "string") { + debugOpts = debugTitle; + debugTitle = popup; + } + else if (typeof debugTitle === "object") { + debugOpts = debugTitle; + debugTitle = null; + } + var t = debugTitle || "log( )" + , o = $.extend({ sort: false, returnHTML: false, display: false }, debugOpts); + if (popup === true || o.display) + debugData( info, t, o ); + else if (window.console) + console.log(debugData( info, t, o )); + } + else if (popup) + alert(info); + else if (window.console) + console.log(info); + else { + var id = "#layoutLogger" + , $l = $(id); + if (!$l.length) + $l = createLog(); + $l.children("ul").append('
  • '+ info.replace(/\/g,">") +'
  • '); + } + + function createLog () { + var pos = $.support.fixedPosition ? 'fixed' : 'absolute' + , $e = $('
    ' + + '
    ' + + 'XLayout console.log
    ' + + '
      ' + + '
      ' + ).appendTo("body"); + $e.css('left', $(window).width() - $e.outerWidth() - 5) + if ($.ui.draggable) $e.draggable({ handle: ':first-child' }); + return $e; + }; + } + +}; + +// DEFAULT OPTIONS +$.layout.defaults = { +/* + * LAYOUT & LAYOUT-CONTAINER OPTIONS + * - none of these options are applicable to individual panes + */ + name: "" // Not required, but useful for buttons and used for the state-cookie +, containerClass: "ui-layout-container" // layout-container element +, inset: null // custom container-inset values (override padding) +, 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 +, maskPanesEarly: false // true = create pane-masks on resizer.mouseDown instead of waiting for resizer.dragstart +, 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 +, initPanes: true // false = DO NOT initialize the panes onLoad - will init later +, showErrorMessages: true // enables fatal error messages to warn developers of common errors +, showDebugMessages: false // display console-and-alert debug msgs - IF this Layout version _has_ debugging code! +// Changing this zIndex value will cause other zIndex values to automatically change +, zIndex: null // the PANE zIndex - resizers and masks will be +1 +// DO NOT CHANGE the zIndex values below unless you clearly understand their relationships +, zIndexes: { // set _default_ z-index values here... + pane_normal: 0 // normal z-index for panes + , content_mask: 1 // applied to overlays used to mask content INSIDE panes during resizing + , resizer_normal: 2 // normal z-index for resizer-bars + , 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' + } +, errors: { + pane: "pane" // description of "layout pane element" - used only in error messages + , selector: "selector" // description of "jQuery-selector" - used only in error messages + , addButtonError: "Error Adding Button \n\nInvalid " + , containerMissing: "UI Layout Initialization Error\n\nThe specified layout-container does not exist." + , centerPaneMissing: "UI Layout Initialization Error\n\nThe center-pane element does not exist.\n\nThe center-pane is a required element." + , noContainerHeight: "UI Layout Initialization Warning\n\nThe layout-container \"CONTAINER\" has no height.\n\nTherefore the layout is 0-height and hence 'invisible'!" + , callbackError: "UI Layout Callback Error\n\nThe EVENT callback is not a valid function." + } +/* + * PANE DEFAULT SETTINGS + * - settings under the 'panes' key become the default settings for *all panes* + * - ALL pane-options can also be set specifically for each panes, which will override these 'default values' + */ +, panes: { // 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" // Layout Pane + , resizerClass: "ui-layout-resizer" // Resizer Bar + , togglerClass: "ui-layout-toggler" // Toggler Button + , buttonClass: "ui-layout-button" // CUSTOM Buttons - eg: '[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 + , 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 + , maskContents: false // true = add DIV-mask over-or-inside this pane so can 'drag' over IFRAMES + , maskObjects: false // true = add IFRAME-mask over-or-inside this pane to cover objects/applets - content-mask will overlay this mask + , maskZindex: null // will override zIndexes.content_mask if specified - not applicable to iframe-panes + , resizingGrid: false // grid size that the resizers will snap-to during resizing, eg: [20,20] + , livePaneResizing: false // true = LIVE Resizing as resizer is dragged + , liveContentResizing: false // true = re-measure header/footer heights as resizer is dragged + , liveResizingTolerance: 1 // how many px change before pane resizes, to control performance + // SLIDING OPTIONS + , 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: $.layout.browser.webkit // Chrome triggers slideClosed as it is opening + , preventPrematureSlideClose: false // handle incorrect mouseleave trigger, like when over a SELECT-list in IE + // PANE-SPECIFIC TIPS & MESSAGES + , tips: { + Open: "Open" // eg: "Open Pane" + , Close: "Close" + , Resize: "Resize" + , Slide: "Slide Open" + , Pin: "Pin" + , Unpin: "Un-Pin" + , noRoomToOpen: "Not enough room to show this panel." // alert if user tries to open a pane that cannot + , minSizeWarning: "Panel has reached its minimum size" // displays in browser statusbar + , maxSizeWarning: "Panel has reached its maximum size" // ditto + } + // 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 & fxSss_size options (eg: fxName_open) are auto-generated if not passed + , fxName: "slide" // ('none' or blank), slide, drop, scale -- only relevant to 'open' & 'close', NOT 'size' + , 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 + , animatePaneSizing: false // true = animate resizing after dragging resizer-bar OR sizePane() is called + /* NOTE: Action-specific FX options are auto-generated from the options above if not specifically set: + fxName_open: "slide" // 'Open' pane animation + fnName_close: "slide" // 'Close' pane animation + fxName_size: "slide" // 'Size' pane animation - when animatePaneSizing = true + fxSpeed_open: null + fxSpeed_close: null + fxSpeed_size: null + fxSettings_open: {} + fxSettings_close: {} + fxSettings_size: {} + */ + // CHILD/NESTED LAYOUTS + , children: null // Layout-options for nested/child layout - even {} is valid as options + , containerSelector: '' // if child is NOT 'directly nested', a selector to find it/them (can have more than one child layout!) + , initChildren: true // true = child layout will be created as soon as _this_ layout completes initialization + , destroyChildren: true // true = destroy child-layout if this pane is destroyed + , resizeChildren: true // true = trigger child-layout.resizeAll() when this pane is resized + // EVENT TRIGGERING + , triggerEventsOnLoad: false // true = trigger onopen OR onclose callbacks when layout initializes + , triggerEventsDuringLiveResize: true // true = trigger onresize callback REPEATEDLY if livePaneResizing==true + // PANE CALLBACKS + , 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 + } +/* + * PANE-SPECIFIC SETTINGS + * - options listed below MUST be specified per-pane - they CANNOT be set under 'panes' + * - all options under the 'panes' key can also be set specifically for any pane + * - most options under the 'panes' key apply only to 'border-panes' - NOT the the center-pane + */ +, north: { + paneSelector: ".ui-layout-north" + , size: "auto" // eg: "auto", "30%", .30, 200 + , resizerCursor: "n-resize" // custom = url(myCursor.cur) + , customHotkey: "" // EITHER a charCode (43) OR a character ("o") + } +, 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 + } +}; + +$.layout.optionsMap = { + // layout/global options - NOT pane-options + layout: ("name,instanceKey,stateManagement,effects,inset,zIndexes,errors," + + "zIndex,scrollToBookmarkOnLoad,showErrorMessages,maskPanesEarly," + + "outset,resizeWithWindow,resizeWithWindowDelay,resizeWithWindowMaxDelay," + + "onresizeall,onresizeall_start,onresizeall_end,onload,onunload").split(",") +// borderPanes: [ ALL options that are NOT specified as 'layout' ] + // default.panes options that apply to the center-pane (most options apply _only_ to border-panes) +, center: ("paneClass,contentSelector,contentIgnoreSelector,findNestedContent,applyDemoStyles,triggerEventsOnLoad," + + "showOverflowOnHover,maskContents,maskObjects,liveContentResizing," + + "containerSelector,children,initChildren,resizeChildren,destroyChildren," + + "onresize,onresize_start,onresize_end,onsizecontent,onsizecontent_start,onsizecontent_end").split(",") + // options that MUST be specifically set 'per-pane' - CANNOT set in the panes (defaults) key +, noDefault: ("paneSelector,resizerCursor,customHotkey").split(",") +}; + +/** + * Processes options passed in converts flat-format data into subkey (JSON) format + * In flat-format, subkeys are _currently_ separated with 2 underscores, like north__optName + * Plugins may also call this method so they can transform their own data + * + * @param {!Object} hash Data/options passed by user - may be a single level or nested levels + * @param {boolean=} [addKeys=false] Should the primary layout.options keys be added if they do not exist? + * @return {Object} Returns hash of minWidth & minHeight + */ +$.layout.transformData = function (hash, addKeys) { + var json = addKeys ? { panes: {}, center: {} } : {} // init return object + , branch, optKey, keys, key, val, i, c; + + if (typeof hash !== "object") return json; // no options passed + + // convert all 'flat-keys' to 'sub-key' format + for (optKey in hash) { + branch = json; + val = hash[ optKey ]; + keys = optKey.split("__"); // eg: west__size or north__fxSettings__duration + c = keys.length - 1; + // convert underscore-delimited to subkeys + for (i=0; i <= c; i++) { + key = keys[i]; + if (i === c) { // last key = value + if ($.isPlainObject( val )) + branch[key] = $.layout.transformData( val ); // RECURSE + else + branch[key] = val; + } + else { + if (!branch[key]) + branch[key] = {}; // create the subkey + // recurse to sub-key for next loop - if not done + branch = branch[key]; + } + } + } + return json; +}; + +// INTERNAL CONFIG DATA - DO NOT CHANGE THIS! +$.layout.backwardCompatibility = { + // data used by renameOldOptions() + map: { + // OLD Option Name: NEW Option Name + applyDefaultStyles: "applyDemoStyles" + // CHILD/NESTED LAYOUTS + , childOptions: "children" + , initChildLayout: "initChildren" + , destroyChildLayout: "destroyChildren" + , resizeChildLayout: "resizeChildren" + , resizeNestedLayout: "resizeChildren" + // MISC Options + , resizeWhileDragging: "livePaneResizing" + , resizeContentWhileDragging: "liveContentResizing" + , triggerEventsWhileDragging: "triggerEventsDuringLiveResize" + , maskIframesOnResize: "maskContents" + // STATE MANAGEMENT + , useStateCookie: "stateManagement.enabled" + , "cookie.autoLoad": "stateManagement.autoLoad" + , "cookie.autoSave": "stateManagement.autoSave" + , "cookie.keys": "stateManagement.stateKeys" + , "cookie.name": "stateManagement.cookie.name" + , "cookie.domain": "stateManagement.cookie.domain" + , "cookie.path": "stateManagement.cookie.path" + , "cookie.expires": "stateManagement.cookie.expires" + , "cookie.secure": "stateManagement.cookie.secure" + // OLD Language options + , noRoomToOpenTip: "tips.noRoomToOpen" + , togglerTip_open: "tips.Close" // open = Close + , togglerTip_closed: "tips.Open" // closed = Open + , resizerTip: "tips.Resize" + , sliderTip: "tips.Slide" + } + +/** +* @param {Object} opts +*/ +, renameOptions: function (opts) { + var map = $.layout.backwardCompatibility.map + , oldData, newData, value + ; + for (var itemPath in map) { + oldData = getBranch( itemPath ); + value = oldData.branch[ oldData.key ]; + if (value !== undefined) { + newData = getBranch( map[itemPath], true ); + newData.branch[ newData.key ] = value; + delete oldData.branch[ oldData.key ]; + } + } + + /** + * @param {string} path + * @param {boolean=} [create=false] Create path if does not exist + */ + function getBranch (path, create) { + var a = path.split(".") // split keys into array + , c = a.length - 1 + , D = { branch: opts, key: a[c] } // init branch at top & set key (last item) + , i = 0, k, undef; + for (; i 0) { + if (autoHide && $E.data('autoHidden') && $E.innerHeight() > 0) { + $E.show().data('autoHidden', false); + if (!browser.mozilla) // FireFox refreshes iframes - IE does not + // 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=false] + */ +, 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 (!browser.mozilla) // FireFox refreshes iframes - IE does not + $E.css(_c.hidden).css(_c.visible); + } + } + else if (autoHide && !$E.data('autoHidden')) + $E.hide().data('autoHidden', true); + } + + + /** + * 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} + */ +, _parseSize = function (pane, size, dir) { + if (!dir) dir = _c[pane].dir; + + if (isStr(size) && size.match(/%/)) + size = (size === '100%') ? -1 : parseInt(size, 10) / 100; // convert % to decimal + + if (size === 0) + return 0; + else if (size >= 1) + return parseInt(size, 10); + + var o = options, avail = 0; + 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); + + if (size === -1) // -1 == 100% + return avail; + else if (size > 0) // percentage, eg: .25 + return round(avail * size); + else if (pane=="center") + return 0; + else { // size < 0 || size=='auto' || size==Missing || size==Invalid + // auto-size the pane + var dim = (dir === "horz" ? "height" : "width") + , $P = $Ps[pane] + , $C = dim === 'height' ? $Cs[pane] : false + , vis = $.layout.showInvisibly($P) // show pane invisibly if hidden + , szP = $P.css(dim) // SAVE current pane size + , szC = $C ? $C.css(dim) : 0 // SAVE current content size + ; + $P.css(dim, "auto"); + if ($C) $C.css(dim, "auto"); + size = (dim === "height") ? $P.outerHeight() : $P.outerWidth(); // MEASURE + $P.css(dim, szP).css(vis); // RESET size & visibility + if ($C) $C.css(dim, szC); + 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=false] + * @return {number} Returns EITHER Width for east/west panes OR Height for north/south panes + */ +, 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=false] + */ +, setSizeLimits = function (pane, slide) { + if (!isInitialized()) return; + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , dir = c.dir + , 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.oppositeEdge[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.inset.top + , left = sC.inset.left + , 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 + */ +, 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 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.inset.top; + d.bottom += sC.inset.bottom; + d.left += sC.inset.left; + d.right += sC.inset.right; + + return d; + } + + + /** + * @param {!Object} el + * @param {boolean=} [allStates=false] + */ +, 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); + } +, 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) ); + } +, removeHover = function (evt, el) { + var $E = $(el || this); + $E.removeClass( getHoverClasses($E, true) ); + } + +, onResizerEnter = function (evt) { // ALSO called by toggler.mouseenter + var pane = $(this).data("layoutEdge") + , s = state[pane] + ; + // ignore closed-panes and mouse moving back & forth over resizer! + // also ignore if ANY pane is currently resizing + if ( s.isClosed || s.isResizing || state.paneResizing ) return; + + if ($.fn.disableSelection) + $("body").disableSelection(); + if (options.maskPanesEarly) + showMasks( pane, { resizing: true }); + } +, 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 + // 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 >100 + if (!el) // 1st call - mouseleave event + timer.set(name, function(){ onResizerLeave(evt, e); }, 200); + // if user is resizing, then dragStop will enableSelection(), so can skip it here + else if ( !state.paneResizing ) { // 2nd call - by timer + if ($.fn.enableSelection) + $("body").enableSelection(); + if (options.maskPanesEarly) + hideMasks(); + } + } + +/* + * ########################### + * INITIALIZATION METHODS + * ########################### + */ + + /** + * Initialize the layout - called automatically whenever an instance of layout is created + * + * @see none - triggered onInit + * @return mixed true = fully initialized | false = panes not initialized (yet) | 'cancel' = abort + */ +, _create = function () { + // initialize config/options + initOptions(); + var o = options + , s = state; + + // TEMP state so isInitialized returns true during init process + s.creatingLayout = true; + + // init plugins for this layout, if there are any (eg: stateManagement) + runPluginCallbacks( Instance, $.layout.onCreate ); + + // options & state have been initialized, so now run beforeLoad callback + // onload will CANCEL layout creation if it returns false + if (false === _runCallbacks("onload_start")) + return 'cancel'; + + // initialize the container element + _initContainer(); + + // bind hotkey function - keyDown - if required + initHotkeys(); + + // bind window.onunload + $(window).bind("unload."+ sID, unload); + + // init plugins for this layout, if there are any (eg: customButtons) + runPluginCallbacks( Instance, $.layout.onLoad ); + + // if layout elements are hidden, then layout WILL NOT complete initialization! + // initLayoutElements will set initialized=true and run the onload callback IF successful + if (o.initPanes) _initLayoutElements(); + + delete s.creatingLayout; + + return state.initialized; + } + + /** + * Initialize the layout IF not already + * + * @see All methods in Instance run this test + * @return boolean true = layoutElements have been initialized | false = panes are not initialized (yet) + */ +, isInitialized = function () { + if (state.initialized || state.creatingLayout) return true; // already initialized + else return _initLayoutElements(); // try to init panes NOW + } + + /** + * Initialize the layout - called automatically whenever an instance of layout is created + * + * @see _create() & isInitialized + * @param {boolean=} [retry=false] // indicates this is a 2nd try + * @return An object pointer to the instance created + */ +, _initLayoutElements = function (retry) { + // initialize config/options + var o = options; + // CANNOT init panes inside a hidden container! + if (!$N.is(":visible")) { + // handle Chrome bug where popup window 'has no height' + // if layout is BODY element, try again in 50ms + // SEE: http://layout.jquery-dev.net/samples/test_popup_window.html + if ( !retry && browser.webkit && $N[0].tagName === "BODY" ) + setTimeout(function(){ _initLayoutElements(true); }, 50); + return false; + } + + // a center pane is required, so make sure it exists + if (!getPane("center").length) { + return _log( o.errors.centerPaneMissing ); + } + + // TEMP state so isInitialized returns true during init process + state.creatingLayout = true; + + // update Container dims + $.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT include insetX values + + // initialize all layout elements + initPanes(); // size & position panes - calls initHandles() - which calls initResizable() + + if (o.scrollToBookmarkOnLoad) { + var l = self.location; + if (l.hash) l.replace( l.hash ); // scrollTo Bookmark + } + + // check to see if this layout 'nested' inside a pane + if (Instance.hasParentLayout) + o.resizeWithWindow = false; + // bind resizeAll() for 'this layout instance' to window.resize event + else if (o.resizeWithWindow) + $(window).bind("resize."+ sID, windowResize); + + delete state.creatingLayout; + state.initialized = true; + + // init plugins for this layout, if there are any + runPluginCallbacks( Instance, $.layout.onReady ); + + // now run the onload callback, if exists + _runCallbacks("onload_end"); + + return true; // elements initialized successfully + } + + /** + * Initialize nested layouts for a specific pane - can optionally pass layout-options + * + * @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west + * @param {Object=} [opts] Layout-options - if passed, will OVERRRIDE options[pane].children + * @return An object pointer to the layout instance created - or null + */ +, createChildren = function (evt_or_pane, opts) { + var pane = evtPane.call(this, evt_or_pane) + , $P = $Ps[pane] + ; + if (!$P) return; + var $C = $Cs[pane] + , s = state[pane] + , o = options[pane] + , sm = options.stateManagement || {} + , cos = opts ? (o.children = opts) : o.children + ; + if ( $.isPlainObject( cos ) ) + cos = [ cos ]; // convert a hash to a 1-elem array + else if (!cos || !$.isArray( cos )) + return; + + $.each( cos, function (idx, co) { + if ( !$.isPlainObject( co ) ) return; + + // determine which element is supposed to be the 'child container' + // if pane has a 'containerSelector' OR a 'content-div', use those instead of the pane + var $containers = co.containerSelector ? $P.find( co.containerSelector ) : ($C || $P); + + $containers.each(function(){ + var $cont = $(this) + , child = $cont.data("layout") // see if a child-layout ALREADY exists on this element + ; + // if no layout exists, but children are set, try to create the layout now + if (!child) { + // TODO: see about moving this to the stateManagement plugin, as a method + // set a unique child-instance key for this layout, if not already set + setInstanceKey({ container: $cont, options: co }, s ); + // If THIS layout has a hash in stateManagement.autoLoad, + // then see if it also contains state-data for this child-layout + // If so, copy the stateData to child.options.stateManagement.autoLoad + if ( sm.includeChildren && state.stateData[pane] ) { + // THIS layout's state was cached when its state was loaded + var paneChildren = state.stateData[pane].children || {} + , childState = paneChildren[ co.instanceKey ] + , co_sm = co.stateManagement || (co.stateManagement = { autoLoad: true }) + ; + // COPY the stateData into the autoLoad key + if ( co_sm.autoLoad === true && childState ) { + co_sm.autoSave = false; // disable autoSave because saving handled by parent-layout + co_sm.includeChildren = true; // cascade option - FOR NOW + co_sm.autoLoad = $.extend(true, {}, childState); // COPY the state-hash + } + } + + // create the layout + child = $cont.layout( co ); + + // if successful, update data + if (child) { + // add the child and update all layout-pointers + // MAY have already been done by child-layout calling parent.refreshChildren() + refreshChildren( pane, child ); + } + } + }); + }); + } + +, setInstanceKey = function (child, parentPaneState) { + // create a named key for use in state and instance branches + var $c = child.container + , o = child.options + , sm = o.stateManagement + , key = o.instanceKey || $c.data("layoutInstanceKey") + ; + if (!key) key = (sm && sm.cookie ? sm.cookie.name : '') || o.name; // look for a name/key + if (!key) key = "layout"+ (++parentPaneState.childIdx); // if no name/key found, generate one + else key = key.replace(/[^\w-]/gi, '_').replace(/_{2,}/g, '_'); // ensure is valid as a hash key + o.instanceKey = key; + $c.data("layoutInstanceKey", key); // useful if layout is destroyed and then recreated + return key; + } + + /** + * @param {string} pane The pane being opened, ie: north, south, east, or west + * @param {Object=} newChild New child-layout Instance to add to this pane + */ +, refreshChildren = function (pane, newChild) { + var $P = $Ps[pane] + , pC = children[pane] + , s = state[pane] + , o + ; + // check for destroy()ed layouts and update the child pointers & arrays + if ($.isPlainObject( pC )) { + $.each( pC, function (key, child) { + if (child.destroyed) delete pC[key] + }); + // if no more children, remove the children hash + if ($.isEmptyObject( pC )) + pC = children[pane] = null; // clear children hash + } + + // see if there is a directly-nested layout inside this pane + // if there is, then there can be only ONE child-layout, so check that... + if (!newChild && !pC) { + newChild = $P.data("layout"); + } + + // if a newChild instance was passed, add it to children[pane] + if (newChild) { + // update child.state + newChild.hasParentLayout = true; // set parent-flag in child + // instanceKey is a key-name used in both state and children + o = newChild.options; + // set a unique child-instance key for this layout, if not already set + setInstanceKey( newChild, s ); + // add pointer to pane.children hash + if (!pC) pC = children[pane] = {}; // create an empty children hash + pC[ o.instanceKey ] = newChild.container.data("layout"); // add childLayout instance + } + + // ALWAYS refresh the pane.children alias, even if null + Instance[pane].children = children[pane]; + + // if newChild was NOT passed - see if there is a child layout NOW + if (!newChild) { + createChildren(pane); // MAY create a child and re-call this method + } + } + +, windowResize = function () { + var o = options + , delay = Number(o.resizeWithWindowDelay); + if (delay < 10) delay = 100; // MUST have a delay! + // 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"); + var dims = elDims( $N, o.inset ); + // only trigger resizeAll() if container has changed size + if (dims.innerWidth !== sC.innerWidth || dims.innerHeight !== sC.innerHeight) + resizeAll(); + }, delay); + // ALSO set fixed-delay timer, if not already running + if (!timer.data["winResizeRepeater"]) setWindowResizeRepeater(); + } + +, setWindowResizeRepeater = function () { + var delay = Number(options.resizeWithWindowMaxDelay); + if (delay > 0) + timer.set("winResizeRepeater", function(){ setWindowResizeRepeater(); resizeAll(); }, delay); + } + +, unload = function () { + var o = options; + + _runCallbacks("onunload_start"); + + // trigger plugin callabacks for this layout (eg: stateManagement) + runPluginCallbacks( Instance, $.layout.onUnload ); + + _runCallbacks("onunload_end"); + } + + /** + * Validate and initialize container CSS and events + * + * @see _create() + */ +, _initContainer = function () { + var + N = $N[0] + , $H = $("html") + , tag = sC.tagName = N.tagName + , id = sC.id = N.id + , cls = sC.className = N.className + , o = options + , name = o.name + , props = "position,margin,padding,border" + , css = "layoutCSS" + , CSS = {} + , hid = "hidden" // used A LOT! + // see if this container is a 'pane' inside an outer-layout + , parent = $N.data("parentLayout") // parent-layout Instance + , pane = $N.data("layoutEdge") // pane-name in parent-layout + , isChild = parent && pane + , num = $.layout.cssNum + , $parent, n + ; + // sC = state.container + sC.selector = $N.selector.split(".slice")[0]; + sC.ref = (o.name ? o.name +' layout / ' : '') + tag + (id ? "#"+id : cls ? '.['+cls+']' : ''); // used in messages + sC.isBody = (tag === "BODY"); + + // try to find a parent-layout + if (!isChild && !sC.isBody) { + $parent = $N.closest("."+ $.layout.defaults.panes.paneClass); + parent = $parent.data("parentLayout"); + pane = $parent.data("layoutEdge"); + isChild = parent && pane; + } + + $N .data({ + layout: Instance + , layoutContainer: sID // FLAG to indicate this is a layout-container - contains unique internal ID + }) + .addClass(o.containerClass) + ; + var layoutMethods = { + destroy: '' + , initPanes: '' + , resizeAll: 'resizeAll' + , resize: 'resizeAll' + }; + // loop hash and bind all methods - include layoutID namespacing + for (name in layoutMethods) { + $N.bind("layout"+ name.toLowerCase() +"."+ sID, Instance[ layoutMethods[name] || name ]); + } + + // if this container is another layout's 'pane', then set child/parent pointers + if (isChild) { + // update parent flag + Instance.hasParentLayout = true; + // set pointers to THIS child-layout (Instance) in parent-layout + parent.refreshChildren( pane, Instance ); + } + + // SAVE original container CSS for use in destroy() + if (!$N.data(css)) { + // handle props like overflow different for BODY & HTML - has 'system default' values + if (sC.isBody) { + // SAVE CSS + $N.data(css, $.extend( styles($N, props), { + height: $N.css("height") + , overflow: $N.css("overflow") + , overflowX: $N.css("overflowX") + , overflowY: $N.css("overflowY") + })); + // ALSO SAVE CSS + $H.data(css, $.extend( styles($H, 'padding'), { + 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 + $N.data(css, styles($N, props+",top,bottom,left,right,width,height,overflow,overflowX,overflowY") ); + } + + try { + // common container CSS + CSS = { + overflow: hid + , overflowX: hid + , overflowY: hid + }; + $N.css( CSS ); + + if (o.inset && !$.isPlainObject(o.inset)) { + // can specify a single number for equal outset all-around + n = parseInt(o.inset, 10) || 0 + o.inset = { + top: n + , bottom: n + , left: n + , right: n + }; + } + + // format html & body if this is a full page layout + if (sC.isBody) { + // if HTML has padding, use this as an outer-spacing around BODY + if (!o.outset) { + // use padding from parent-elem (HTML) as outset + o.outset = { + top: num($H, "paddingTop") + , bottom: num($H, "paddingBottom") + , left: num($H, "paddingLeft") + , right: num($H, "paddingRight") + }; + } + else if (!$.isPlainObject(o.outset)) { + // can specify a single number for equal outset all-around + n = parseInt(o.outset, 10) || 0 + o.outset = { + top: n + , bottom: n + , left: n + , right: n + }; + } + // HTML + $H.css( CSS ).css({ + height: "100%" + , border: "none" // no border or padding allowed when using height = 100% + , padding: 0 // ditto + , margin: 0 + }); + // BODY + if (browser.isIE6) { + // IE6 CANNOT use the trick of setting absolute positioning on all 4 sides - must have 'height' + $N.css({ + width: "100%" + , height: "100%" + , border: "none" // no border or padding allowed when using height = 100% + , padding: 0 // ditto + , margin: 0 + , position: "relative" + }); + // convert body padding to an inset option - the border cannot be measured in IE6! + if (!o.inset) o.inset = elDims( $N ).inset; + } + else { // use absolute positioning for BODY to allow borders & padding without overflow + $N.css({ + width: "auto" + , height: "auto" + , margin: 0 + , position: "absolute" // allows for border and padding on BODY + }); + // apply edge-positioning created above + $N.css( o.outset ); + } + // set current layout-container dimensions + $.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT include insetX values + } + else { + // container MUST have 'position' + var p = $N.css("position"); + if (!p || !p.match(/(fixed|absolute|relative)/)) + $N.css("position","relative"); + + // set current layout-container dimensions + if ( $N.is(":visible") ) { + $.extend(sC, elDims( $N, o.inset )); // passing inset means DO NOT change insetX (padding) values + if (sC.innerHeight < 1) // container has no 'height' - warn developer + _log( o.errors.noContainerHeight.replace(/CONTAINER/, sC.ref) ); + } + } + + // if container has min-width/height, then enable scrollbar(s) + if ( num($N, "minWidth") ) $N.parent().css("overflowX","auto"); + if ( num($N, "minHeight") ) $N.parent().css("overflowY","auto"); + + } catch (ex) {} + } + + /** + * Bind layout hotkeys - if options enabled + * + * @see _create() and addPane() + * @param {string=} [panes=""] The edge(s) to process + */ +, initHotkeys = function (panes) { + panes = panes ? panes.split(",") : _c.borderPanes; + // bind keyDown to capture hotkeys, if option enabled for ANY pane + $.each(panes, 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() + */ +, initOptions = function () { + var data, d, pane, key, val, i, c, o; + + // reprocess user's layout-options to have correct options sub-key structure + opts = $.layout.transformData( opts, true ); // panes = default subkey + + // auto-rename old options for backward compatibility + opts = $.layout.backwardCompatibility.renameAllOptions( opts ); + + // if user-options has 'panes' key (pane-defaults), clean it... + if (!$.isEmptyObject(opts.panes)) { + // REMOVE any pane-defaults that MUST be set per-pane + data = $.layout.optionsMap.noDefault; + for (i=0, c=data.length; i 0) { + z.pane_normal = zo; + z.content_mask = max(zo+1, z.content_mask); // MIN = +1 + z.resizer_normal = max(zo+2, z.resizer_normal); // MIN = +2 + } + + // DELETE 'panes' key now that we are done - values were copied to EACH pane + delete options.panes; + + + function createFxOptions ( pane ) { + var o = options[pane] + , d = options.panes; + // ensure fxSettings key to avoid errors + if (!o.fxSettings) o.fxSettings = {}; + if (!d.fxSettings) d.fxSettings = {}; + + $.each(["_open","_close","_size"], function (i,n) { + var + sName = "fxName"+ n + , sSpeed = "fxSpeed"+ n + , sSettings = "fxSettings"+ n + // recalculate fxName according to specificity rules + , fxName = o[sName] = + o[sName] // options.west.fxName_open + || d[sName] // options.panes.fxName_open + || o.fxName // options.west.fxName + || d.fxName // options.panes.fxName + || "none" // MEANS $.layout.defaults.panes.fxName == "" || false || null || 0 + , fxExists = $.effects && ($.effects[fxName] || ($.effects.effect && $.effects.effect[fxName])) + ; + // validate fxName to ensure is valid effect - MUST have effect-config data in options.effects + if (fxName === "none" || !options.effects[fxName] || !fxExists) + fxName = o[sName] = "none"; // effect not loaded OR unrecognized fxName + + // set vars for effects subkeys to simplify logic + var fx = options.effects[fxName] || {} // effects.slide + , fx_all = fx.all || null // effects.slide.all + , fx_pane = fx[pane] || null // effects.slide.west + ; + // create fxSpeed[_open|_close|_size] + o[sSpeed] = + o[sSpeed] // options.west.fxSpeed_open + || d[sSpeed] // options.west.fxSpeed_open + || o.fxSpeed // options.west.fxSpeed + || d.fxSpeed // options.panes.fxSpeed + || null // DEFAULT - let fxSetting.duration control speed + ; + // create fxSettings[_open|_close|_size] + o[sSettings] = $.extend( + true + , {} + , fx_all // effects.slide.all + , fx_pane // effects.slide.west + , d.fxSettings // options.panes.fxSettings + , o.fxSettings // options.west.fxSettings + , d[sSettings] // options.panes.fxSettings_open + , o[sSettings] // options.west.fxSettings_open + ); + }); + + // DONE creating action-specific-settings for this pane, + // so DELETE generic options - are no longer meaningful + delete o.fxName; + delete o.fxSpeed; + delete o.fxSettings; + } + } + + /** + * Initialize module objects, styling, size and position for all panes + * + * @see _initElements() + * @param {string} pane The pane to process + */ +, 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 $N.find(sel).eq(0); + else { // class or other selector + var $P = $N.children(sel).eq(0); + // look for the pane nested inside a 'form' element + return $P.length ? $P : $N.children("form:first").children(sel).eq(0); + } + } + + /** + * @param {Object=} evt + */ +, initPanes = function (evt) { + // stopPropagation if called by trigger("layoutinitpanes") - use evtPane utility + evtPane(evt); + + // NOTE: do north & south FIRST so we can measure their height - do center LAST + $.each(_c.allPanes, function (idx, pane) { + addPane( pane, true ); + }); + + // 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, 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"); + + // Chrome/Webkit sometimes fires callbacks BEFORE it completes resizing! + // Before RC30.3, there was a 10ms delay here, but that caused layout + // to load asynchrously, which is BAD, so try skipping delay for now + + // process pane contents and callbacks, and init/resize child-layout if exists + $.each(_c.allPanes, function (idx, pane) { + afterInitPane(pane); + }); + } + + /** + * Add a pane to the layout - subroutine of initPanes() + * + * @see initPanes() + * @param {string} pane The pane to process + * @param {boolean=} [force=false] Size content after init + */ +, addPane = function (pane, force) { + if (!force && !isInitialized()) return; + var + o = options[pane] + , s = state[pane] + , c = _c[pane] + , dir = c.dir + , fx = s.fx + , spacing = o.spacing_open || 0 + , isCenter = (pane === "center") + , CSS = {} + , $P = $Ps[pane] + , size, minSize, maxSize, child + ; + // if pane-pointer already exists, remove the old one first + if ($P) + removePane( pane, false, true, false ); + 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", styles($P, props)); + } + + // create alias for pane data in Instance - initHandles will add more + Instance[pane] = { + name: pane + , pane: $Ps[pane] + , content: $Cs[pane] + , options: options[pane] + , state: state[pane] + , children: children[pane] + }; + + // add classes, attributes & events + $P .data({ + parentLayout: Instance // pointer to Layout Instance + , layoutPane: Instance[pane] // NEW pointer to pane-alias-object + , layoutEdge: pane + , layoutRole: "pane" + }) + .css(c.cssReq).css("zIndex", options.zIndexes.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 ) + ; + var paneMethods = { + hide: '' + , show: '' + , toggle: '' + , close: '' + , open: '' + , slideOpen: '' + , slideClose: '' + , slideToggle: '' + , size: 'sizePane' + , sizePane: 'sizePane' + , sizeContent: '' + , sizeHandles: '' + , enableClosable: '' + , disableClosable: '' + , enableSlideable: '' + , disableSlideable: '' + , enableResizable: '' + , disableResizable: '' + , swapPanes: 'swapPanes' + , swap: 'swapPanes' + , move: 'swapPanes' + , removePane: 'removePane' + , remove: 'removePane' + , createChildren: '' + , resizeChildren: '' + , resizeAll: 'resizeAll' + , resizeLayout: 'resizeAll' + } + , name; + // loop hash and bind all methods - include layoutID namespacing + for (name in paneMethods) { + $P.bind("layoutpane"+ name.toLowerCase() +"."+ sID, Instance[ paneMethods[name] || name ]); + } + + // 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 its '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); + s.autoResize = o.autoResize; // used with percentage sizes + + // 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! + + // array for 'pin buttons' whose classNames are auto-updated on pane-open/-close + if (!s.pins) s.pins = []; + } + // states common to ALL panes + s.tagName = $P[0].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 + + // init pane positioning + setPanePosition( pane ); + + // if pane is not visible, + if (dir === "horz") // north or south pane + CSS.height = cssH($P, size); + else if (dir === "vert") // east or west pane + CSS.width = cssW($P, 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 + + // if manually adding a pane AFTER layout initialization, then... + if (state.initialized) { + initHandles( pane ); + initHotkeys( pane ); + } + + // close or hide the pane if specified in settings + if (o.initClosed && o.closable && !o.initHidden) + 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 if (!s.noRoom) + // make the pane visible - in case was initially hidden + $P.css("display","block"); + // ELSE setAsOpen() - called later by initHandles() + + // RESET visibility now - pane will appear IF display:block + $P.css("visibility","visible"); + + // check option for auto-handling of pop-ups & drop-downs + if (o.showOverflowOnHover) + $P.hover( allowOverflow, resetOverflow ); + + // if manually adding a pane AFTER layout initialization, then... + if (state.initialized) { + afterInitPane( pane ); + } + } + +, afterInitPane = function (pane) { + var $P = $Ps[pane] + , s = state[pane] + , o = options[pane] + ; + if (!$P) return; + + // see if there is a directly-nested layout inside this pane + if ($P.data("layout")) + refreshChildren( pane, $P.data("layout") ); + + // process pane contents and callbacks, and init/resize child-layout if exists + if (s.isVisible) { // pane is OPEN + if (state.initialized) // this pane was added AFTER layout was created + resizeAll(); // will also sizeContent + else + sizeContent(pane); + + if (o.triggerEventsOnLoad) + _runCallbacks("onresize_end", pane); + else // automatic if onresize called, otherwise call it specifically + // resize child - IF inner-layout already exists (created before this layout) + resizeChildren(pane, true); // a previously existing childLayout + } + + // init childLayouts - even if pane is not visible + if (o.initChildren && o.children) + createChildren(pane); + } + + /** + * @param {string=} panes The pane(s) to process + */ +, setPanePosition = function (panes) { + panes = panes ? panes.split(",") : _c.borderPanes; + + // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV + $.each(panes, function (i, pane) { + var $P = $Ps[pane] + , $R = $Rs[pane] + , o = options[pane] + , s = state[pane] + , side = _c[pane].side + , CSS = {} + ; + if (!$P) return; // pane does not exist - skip + + // set css-position to account for container borders & padding + switch (pane) { + case "north": CSS.top = sC.inset.top; + CSS.left = sC.inset.left; + CSS.right = sC.inset.right; + break; + case "south": CSS.bottom = sC.inset.bottom; + CSS.left = sC.inset.left; + CSS.right = sC.inset.right; + break; + case "west": CSS.left = sC.inset.left; // top, bottom & height set by sizeMidPanes() + break; + case "east": CSS.right = sC.inset.right; // ditto + break; + case "center": // top, left, width & height set by sizeMidPanes() + } + // apply position + $P.css(CSS); + + // update resizer position + if ($R && s.isClosed) + $R.css(side, sC.inset[side]); + else if ($R && !s.isHidden) + $R.css(side, sC.inset[side] + getPaneSize(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 + */ +, initHandles = function (panes) { + panes = panes ? panes.split(",") : _c.borderPanes; + + // create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV + $.each(panes, 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] + , paneId = o.paneSelector.substr(0,1) === "#" ? o.paneSelector.substr(1) : "" + , rClass = o.resizerClass + , tClass = o.togglerClass + , spacing = (s.isVisible ? o.spacing_open : o.spacing_closed) + , _pane = "-"+ pane // used for classNames + , _state = (s.isVisible ? "-open" : "-closed") // used for classNames + , I = Instance[pane] + // INIT RESIZER BAR + , $R = I.resizer = $Rs[pane] = $("
      ") + // INIT TOGGLER BUTTON + , $T = I.toggler = (o.closable ? $Ts[pane] = $("
      ") : false) + ; + + //if (s.isVisible && o.resizable) ... handled by initResizable + if (!s.isVisible && o.slidable) + $R.attr("title", o.tips.Slide).css("cursor", o.sliderCursor); + + $R // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer" + .attr("id", paneId ? paneId +"-resizer" : "" ) + .data({ + parentLayout: Instance + , layoutPane: Instance[pane] // NEW pointer to pane-alias-object + , layoutEdge: pane + , layoutRole: "resizer" + }) + .css(_c.resizers.cssReq).css("zIndex", options.zIndexes.resizer_normal) + .css(o.applyDemoStyles ? _c.resizers.cssDemo : {}) // add demo styles + .addClass(rClass +" "+ rClass+_pane) + .hover(addHover, removeHover) // ALWAYS add hover-classes, even if resizing is not enabled - handle with CSS instead + .hover(onResizerEnter, onResizerLeave) // ALWAYS NEED resizer.mouseleave to balance toggler.mouseenter + .appendTo($N) // append DIV to container + ; + if (o.resizerDblClickToggle) + $R.bind("dblclick."+ sID, toggle ); + + if ($T) { + $T // if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "#paneLeft-toggler" + .attr("id", paneId ? paneId +"-toggler" : "" ) + .data({ + parentLayout: Instance + , layoutPane: Instance[pane] // NEW pointer to pane-alias-object + , layoutEdge: pane + , layoutRole: "toggler" + }) + .css(_c.togglers.cssReq) // add base/required styles + .css(o.applyDemoStyles ? _c.togglers.cssDemo : {}) // add demo styles + .addClass(tClass +" "+ tClass+_pane) + .hover(addHover, removeHover) // ALWAYS add hover-classes, even if toggling is not enabled - handle with CSS instead + .bind("mouseenter", onResizerEnter) // NEED toggler.mouseenter because mouseenter MAY NOT fire on resizer + .appendTo($R) // append SPAN to resizer DIV + ; + // ADD INNER-SPANS TO TOGGLER + if (o.togglerContent_open) // ui-layout-open + $(""+ o.togglerContent_open +"") + .data({ + layoutEdge: pane + , layoutRole: "togglerContent" + }) + .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 + $(""+ o.togglerContent_closed +"") + .data({ + layoutEdge: pane + , layoutRole: "togglerContent" + }) + .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 + bindStartSlidingEvents(pane, true); // will enable events IF option is set + } + + }); + + // SET ALL HANDLE DIMENSIONS + sizeHandles(); + } + + + /** + * 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=true] Size content after init + */ +, initContent = function (pane, resize) { + if (!isInitialized()) return; + var + o = options[pane] + , sel = o.contentSelector + , I = Instance[pane] + , $P = $Ps[pane] + , $C + ; + if (sel) $C = I.content = $Cs[pane] = (o.findNestedContent) + ? $P.find(sel).eq(0) // match 1-element only + : $P.children(sel).eq(0) + ; + if ($C && $C.length) { + $C.data("layoutRole", "content"); + // SAVE original Content CSS + if (!$C.data("layoutCSS")) + $C.data("layoutCSS", styles($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 + } + // ensure no vertical scrollbar on pane - will mess up measurements + if ($P.css("overflowX").match(/(scroll|auto)/)) { + $P.css("overflow", "hidden"); + } + state[pane].content = {}; // init content state + if (resize !== false) sizeContent(pane); + // sizeContent() is called AFTER init of all elements + } + else + I.content = $Cs[pane] = false; + } + + + /** + * 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 + */ +, initResizable = function (panes) { + var draggingAvailable = $.layout.plugins.draggable + , side // set in start() + ; + panes = panes ? panes.split(",") : _c.borderPanes; + + $.each(panes, function (idx, pane) { + var o = options[pane]; + if (!draggingAvailable || !$Ps[pane] || !o.resizable) { + o.resizable = false; + return true; // skip to next + } + + var s = state[pane] + , z = options.zIndexes + , c = _c[pane] + , side = c.dir=="horz" ? "top" : "left" + , $P = $Ps[pane] + , $R = $Rs[pane] + , base = o.resizerClass + , lastPos = 0 // used when live-resizing + , r, live // set in start because may change + // '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.tips.Resize) + .css("cursor", o.resizerCursor); // n-resize, s-resize, etc + + $R.draggable({ + containment: $N[0] // limit resizing to layout container + , axis: (c.dir=="horz" ? "y" : "x") // limit resizing to horz or vert axis + , delay: 0 + , distance: 1 + , grid: o.resizingGrid + // 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: z.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.livePaneResizing; + + // 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 === _runCallbacks("ondrag_start", pane)) return false; + + s.isResizing = true; // prevent pane from closing while resizing + state.paneResizing = pane; // easy to see if ANY pane is 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; + lastPos = ui.position[ side ] + + $R.addClass( resizerClass +" "+ resizerPaneClass ); // add drag classes + helperClassesSet = false; // reset logic var - see drag() + + // DISABLE TEXT SELECTION (probably already done by resizer.mouseOver) + $('body').disableSelection(); + + // MASK PANES CONTAINING IFRAMES, APPLETS OR OTHER TROUBLESOME ELEMENTS + showMasks( pane, { resizing: true }); + } + + , 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", z.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 = (limit>0 && pane.match(/(north|west)/)) || (limit<0 && pane.match(/(south|east)/)) ? o.tips.maxSizeWarning : o.tips.minSizeWarning; + } + else { + ui.helper.removeClass( helperLimitClass +" "+ helperPaneLimitClass ); // not at dragging-limit + window.defaultStatus = ""; + } + // DYNAMICALLY RESIZE PANES IF OPTION ENABLED + // won't trigger unless resizer has actually moved! + if (live && Math.abs(ui.position[side] - lastPos) >= o.liveResizingTolerance) { + lastPos = ui.position[side]; + 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; + state.paneResizing = false; // easy to see if ANY pane is resizing + resizePanes(e, ui, pane, true); // true = resizingDone + } + + }); + }); + + /** + * resizePanes + * + * Sub-routine called from stop() - and drag() if livePaneResizing + * + * @param {!Object} evt + * @param {!Object} ui + * @param {string} pane + * @param {boolean=} [resizingDone=false] + */ + var resizePanes = function (evt, ui, pane, resizingDone) { + var dragPos = ui.position + , c = _c[pane] + , o = options[pane] + , s = state[pane] + , resizerPos + ; + switch (pane) { + case "north": resizerPos = dragPos.top; break; + case "west": resizerPos = dragPos.left; break; + case "south": resizerPos = sC.layoutHeight - dragPos.top - o.spacing_open; break; + case "east": resizerPos = sC.layoutWidth - dragPos.left - o.spacing_open; break; + }; + // remove container margin from resizer position to get the pane size + var newSize = resizerPos - sC.inset[c.side]; + + // Disable OR Resize Mask(s) created in drag.start + if (!resizingDone) { + // ensure we meet liveResizingTolerance criteria + if (Math.abs(newSize - s.size) < o.liveResizingTolerance) + return; // SKIP resize this time + // resize the pane + manualSizePane(pane, newSize, false, true); // true = noAnimation + sizeMasks(); // resize all visible masks + } + else { // resizingDone + // ondrag_end callback + if (false !== _runCallbacks("ondrag_end", pane)) + manualSizePane(pane, newSize, false, true); // true = noAnimation + hideMasks(true); // true = force hiding all masks even if one is 'sliding' + if (s.isSliding) // RE-SHOW 'object-masks' so objects won't show through sliding pane + showMasks( pane, { resizing: true }); + } + }; + } + + /** + * sizeMask + * + * Needed to overlay a DIV over an IFRAME-pane because mask CANNOT be *inside* the pane + * Called when mask created, and during livePaneResizing + */ +, sizeMask = function () { + var $M = $(this) + , pane = $M.data("layoutMask") // eg: "west" + , s = state[pane] + ; + // only masks over an IFRAME-pane need manual resizing + if (s.tagName == "IFRAME" && s.isVisible) // no need to mask closed/hidden panes + $M.css({ + top: s.offsetTop + , left: s.offsetLeft + , width: s.outerWidth + , height: s.outerHeight + }); + /* ALT Method... + var $P = $Ps[pane]; + $M.css( $P.position() ).css({ width: $P[0].offsetWidth, height: $P[0].offsetHeight }); + */ + } +, sizeMasks = function () { + $Ms.each( sizeMask ); // resize all 'visible' masks + } + + /** + * @param {string} pane The pane being resized, animated or isSliding + * @param {Object=} [args] (optional) Options: which masks to apply, and to which panes + */ +, showMasks = function (pane, args) { + var c = _c[pane] + , panes = ["center"] + , z = options.zIndexes + , a = $.extend({ + objectsOnly: false + , animation: false + , resizing: true + , sliding: state[pane].isSliding + }, args ) + , o, s + ; + if (a.resizing) + panes.push( pane ); + if (a.sliding) + panes.push( _c.oppositeEdge[pane] ); // ADD the oppositeEdge-pane + + if (c.dir === "horz") { + panes.push("west"); + panes.push("east"); + } + + $.each(panes, function(i,p){ + s = state[p]; + o = options[p]; + if (s.isVisible && ( o.maskObjects || (!a.objectsOnly && o.maskContents) )) { + getMasks(p).each(function(){ + sizeMask.call(this); + this.style.zIndex = s.isSliding ? z.pane_sliding+1 : z.pane_normal+1 + this.style.display = "block"; + }); + } + }); + } + + /** + * @param {boolean=} force Hide masks even if a pane is sliding + */ +, hideMasks = function (force) { + // ensure no pane is resizing - could be a timing issue + if (force || !state.paneResizing) { + $Ms.hide(); // hide ALL masks + } + // if ANY pane is sliding, then DO NOT remove masks from panes with maskObjects enabled + else if (!force && !$.isEmptyObject( state.panesSliding )) { + var i = $Ms.length - 1 + , p, $M; + for (; i >= 0; i--) { + $M = $Ms.eq(i); + p = $M.data("layoutMask"); + if (!options[p].maskObjects) { + $M.hide(); + } + } + } + } + + /** + * @param {string} pane + */ +, getMasks = function (pane) { + var $Masks = $([]) + , $M, i = 0, c = $Ms.length + ; + for (; i CSS + if (sC.tagName === "BODY" && ($N = $("html")).data(css)) // RESET CSS + $N.css( $N.data(css) ).removeData(css); + + // trigger plugins for this layout, if there are any + runPluginCallbacks( Instance, $.layout.onDestroy ); + + // trigger state-management and onunload callback + unload(); + + // clear the Instance of everything except for container & options (so could recreate) + // RE-CREATE: myLayout = myLayout.container.layout( myLayout.options ); + for (var n in Instance) + if (!n.match(/^(container|options)$/)) delete Instance[ n ]; + // add a 'destroyed' flag to make it easy to check + Instance.destroyed = true; + + // if this is a child layout, CLEAR the child-pointer in the parent + /* for now the pointer REMAINS, but with only container, options and destroyed keys + if (parentPane) { + var layout = parentPane.pane.data("parentLayout") + , key = layout.options.instanceKey || 'error'; + // THIS SYNTAX MAY BE WRONG! + parentPane.children[key] = layout.children[ parentPane.name ].children[key] = null; + } + */ + + return Instance; // for coding convenience + } + + /** + * Remove a pane from the layout - subroutine of destroy() + * + * @see destroy() + * @param {(string|Object)} evt_or_pane The pane to process + * @param {boolean=} [remove=false] Remove the DOM element? + * @param {boolean=} [skipResize=false] Skip calling resizeAll()? + * @param {boolean=} [destroyChild=true] Destroy Child-layouts? If not passed, obeys options setting + */ +, removePane = function (evt_or_pane, remove, skipResize, destroyChild) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $P = $Ps[pane] + , $C = $Cs[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + ; + // NOTE: elements can still exist even after remove() + // so check for missing data(), which is cleared by removed() + if ($P && $.isEmptyObject( $P.data() )) $P = false; + if ($C && $.isEmptyObject( $C.data() )) $C = false; + if ($R && $.isEmptyObject( $R.data() )) $R = false; + if ($T && $.isEmptyObject( $T.data() )) $T = false; + + if ($P) $P.stop(true, true); + + var o = options[pane] + , s = state[pane] + , d = "layout" + , css = "layoutCSS" + , pC = children[pane] + , hasChildren = $.isPlainObject( pC ) && !$.isEmptyObject( pC ) + , destroy = destroyChild !== undefined ? destroyChild : o.destroyChildren + ; + // FIRST destroy the child-layout(s) + if (hasChildren && destroy) { + $.each( pC, function (key, child) { + if (!child.destroyed) + child.destroy(true);// tell child-layout to destroy ALL its child-layouts too + if (child.destroyed) // destroy was successful + delete pC[key]; + }); + // if no more children, remove the children hash + if ($.isEmptyObject( pC )) { + pC = children[pane] = null; // clear children hash + hasChildren = false; + } + } + + // Note: can't 'remove' a pane element with non-destroyed children + if ($P && remove && !hasChildren) + $P.remove(); // remove the pane-element and everything inside it + else if ($P && $P[0]) { + // create list of ALL pane-classes that need to be removed + var root = o.paneClass // default="ui-layout-pane" + , pRoot = root +"-"+ pane // eg: "ui-layout-pane-west" + , _open = "-open" + , _sliding= "-sliding" + , _closed = "-closed" + , 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 + // remove all Layout classes from pane-element + $P .removeClass( classes.join(" ") ) // remove ALL pane-classes + .removeData("parentLayout") + .removeData("layoutPane") + .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/content is STILL the container of a nested layout! + // the nested layout will reset its 'container' CSS when/if it is destroyed + if (hasChildren && $C) { + // a content-div may not have a specific width, so give it one to contain the Layout + $C.width( $C.width() ); + $.each( pC, function (key, child) { + child.resizeAll(); // resize the Layout + }); + } + else if ($C) + $C.css( $C.data(css) ).removeData(css).removeData("layoutRole"); + // remove pane AFTER content in case there was a nested layout + if (!$P.data(d)) + $P.css( $P.data(css) ).removeData(css); + } + + // REMOVE pane resizer and toggler elements + if ($T) $T.remove(); + if ($R) $R.remove(); + + // CLEAR all pointers and state data + Instance[pane] = $Ps[pane] = $Cs[pane] = $Rs[pane] = $Ts[pane] = false; + s = { removed: true }; + + if (!skipResize) + resizeAll(); + } + + +/* + * ########################### + * ACTION METHODS + * ########################### + */ + + /** + * @param {string} pane + */ +, _hidePane = function (pane) { + var $P = $Ps[pane] + , o = options[pane] + , s = $P[0].style + ; + if (o.useOffscreenClose) { + if (!$P.data(_c.offscreenReset)) + $P.data(_c.offscreenReset, { left: s.left, right: s.right }); + $P.css( _c.offscreenCSS ); + } + else + $P.hide().removeData(_c.offscreenReset); + } + + /** + * @param {string} pane + */ +, _showPane = function (pane) { + var $P = $Ps[pane] + , o = options[pane] + , off = _c.offscreenCSS + , old = $P.data(_c.offscreenReset) + , s = $P[0].style + ; + $P .show() // ALWAYS show, just in case + .removeData(_c.offscreenReset); + if (o.useOffscreenClose && old) { + if (s.left == off.left) + s.left = old.left; + if (s.right == off.right) + s.right = old.right; + } + } + + + /** + * 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|Object)} evt_or_pane The pane being hidden, ie: north, south, east, or west + * @param {boolean=} [noAnimation=false] + */ +, hide = function (evt_or_pane, noAnimation) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , 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 === _runCallbacks("onhide_start", pane)) return; + + s.isSliding = false; // just in case + delete state.panesSliding[pane]; + + // 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; + if (!state.initialized) + _hidePane(pane); // no animation when loading page + sizeMidPanes(_c[pane].dir === "horz" ? "" : "center"); + if (state.initialized || o.triggerEventsOnLoad) + _runCallbacks("onhide_end", pane); + } + 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|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west + * @param {boolean=} [openPane=false] + * @param {boolean=} [noAnimation=false] + * @param {boolean=} [noAlert=false] + */ +, show = function (evt_or_pane, openPane, noAnimation, noAlert) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , 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 === _runCallbacks("onshow_start", pane)) return; + + s.isShowing = true; // used by onopen/onclose + //s.isHidden = false; - will be set by open/close - if not cancelled + s.isSliding = false; // just in case + delete state.panesSliding[pane]; + + // 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|Object)} evt_or_pane The pane being toggled, ie: north, south, east, or west + * @param {boolean=} [slide=false] + */ +, toggle = function (evt_or_pane, slide) { + if (!isInitialized()) return; + var evt = evtObj(evt_or_pane) + , pane = evtPane.call(this, evt_or_pane) + , s = state[pane] + ; + if (evt) // called from to $R.dblclick OR triggerPaneEvent + evt.stopImmediatePropagation(); + 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=false] + */ +, _closePane = function (pane, setHandles) { + var + $P = $Ps[pane] + , s = state[pane] + ; + _hidePane(pane); + 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|Object)} evt_or_pane The pane being closed, ie: north, south, east, or west + * @param {boolean=} [force=false] + * @param {boolean=} [noAnimation=false] + * @param {boolean=} [skipCallback=false] + */ +, close = function (evt_or_pane, force, noAnimation, skipCallback) { + var pane = evtPane.call(this, evt_or_pane); + // if pane has been initialized, but NOT the complete layout, close pane instantly + if (!state.initialized && $Ps[pane]) { + _closePane(pane); // INIT pane as closed + return; + } + if (!isInitialized()) return; + + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , c = _c[pane] + , doFX, isShowing, isHiding, wasSliding; + + // QUEUE in case another action/animation is in progress + $N.queue(function( queueNext ){ + + if ( !$P + || (!o.closable && !s.isShowing && !s.isHiding) // invalid request // (!o.resizable && !o.closable) ??? + || (!force && s.isClosed && !s.isShowing) // already closed + ) return queueNext(); + + // onclose_start callback - will CANCEL hide if returns false + // SKIP if just 'showing' a hidden pane as 'closed' + var abort = !s.isShowing && false === _runCallbacks("onclose_start", pane); + + // transfer logic vars to temp vars + isShowing = s.isShowing; + isHiding = s.isHiding; + wasSliding = s.isSliding; + // now clear the logic vars (REQUIRED before aborting) + delete s.isShowing; + delete s.isHiding; + + if (abort) return queueNext(); + + doFX = !noAnimation && !s.isClosed && (o.fxName_close != "none"); + s.isMoving = 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" ? "" : "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 + if (s.isClosed) close_2(); + queueNext(); + }); + } + else { // hide the pane without animation + _hidePane(pane); + close_2(); + queueNext(); + }; + }); + + // SUBROUTINE + function close_2 () { + s.isMoving = false; + bindStartSlidingEvents(pane, true); // will enable if o.slidable = true + + // if opposite-pane was autoClosed, see if it can be autoOpened now + var altPane = _c.oppositeEdge[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) _runCallbacks("onclose_end", pane); + // onhide OR onshow callback + if (isShowing) _runCallbacks("onshow_end", pane); + if (isHiding) _runCallbacks("onhide_end", pane); + } + } + } + + /** + * @param {string} pane The pane just closed, ie: north, south, east, or west + */ +, setAsClosed = function (pane) { + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , side = _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[side]) // move the resizer + .removeClass( rClass+_open +" "+ rClass+_pane+_open ) + .removeClass( rClass+_sliding +" "+ rClass+_pane+_sliding ) + .addClass( rClass+_closed +" "+ rClass+_pane+_closed ) + ; + // DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvents? + if (o.resizable && $.layout.plugins.draggable) + $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.tips.Open) // 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(); + } + } + + /** + * Open the specified pane (animation optional), and resize all other panes as needed + * + * @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west + * @param {boolean=} [slide=false] + * @param {boolean=} [noAnimation=false] + * @param {boolean=} [noAlert=false] + */ +, open = function (evt_or_pane, slide, noAnimation, noAlert) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , c = _c[pane] + , doFX, isShowing + ; + // QUEUE in case another action/animation is in progress + $N.queue(function( queueNext ){ + + if ( !$P + || (!o.resizable && !o.closable && !s.isShowing) // invalid request + || (s.isVisible && !s.isSliding) // already open + ) return queueNext(); + + // pane can ALSO be unhidden by just calling show(), so handle this scenario + if (s.isHidden && !s.isShowing) { + queueNext(); // call before show() because it needs the queue free + show(pane, true); + return; + } + + if (s.autoResize && s.size != o.size) // resize pane to original size set in options + sizePane(pane, o.size, true, true, true); // true=skipCallback/noAnimation/forceResize + else + // make sure there is enough space available to open the pane + setSizeLimits(pane, slide); + + // onopen_start callback - will CANCEL open if returns false + var cbReturn = _runCallbacks("onopen_start", pane); + + if (cbReturn === "abort") + return queueNext(); + + // update pane-state again in case options were changed in onopen_start + if (cbReturn !== "NC") // NC = "No Callback" + setSizeLimits(pane, slide); + + if (s.minSize > s.maxSize) { // INSUFFICIENT ROOM FOR PANE TO OPEN! + syncPinBtns(pane, false); // make sure pin-buttons are reset + if (!noAlert && o.tips.noRoomToOpen) + alert(o.tips.noRoomToOpen); + return queueNext(); // ABORT + } + + 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) + bindStartSlidingEvents(pane, false); // UNBIND trigger events + + s.noRoom = false; // will be reset by makePaneFit if 'noRoom' + makePaneFit(pane); + + // transfer logic var to temp var + isShowing = s.isShowing; + // now clear the logic var + delete s.isShowing; + + doFX = !noAnimation && s.isClosed && (o.fxName_open != "none"); + s.isMoving = true; + s.isVisible = true; + s.isClosed = false; + // update isHidden BEFORE sizing panes - WHY??? Old? + if (isShowing) s.isHidden = false; + + if (doFX) { // ANIMATE + // mask adjacent panes with objects + 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 + if (s.isVisible) open_2(); // continue + queueNext(); + }); + } + else { // no animation + _showPane(pane);// just show pane and... + open_2(); // continue + queueNext(); + }; + }); + + // SUBROUTINE + function open_2 () { + s.isMoving = false; + + // 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" : "", false); // false = NOT skipCallback + } + + // set classes, position handles and execute callbacks... + setAsOpen(pane); + }; + + } + + /** + * @param {string} pane The pane just opened, ie: north, south, east, or west + * @param {boolean=} [skipCallback=false] + */ +, setAsOpen = function (pane, skipCallback) { + var + $P = $Ps[pane] + , $R = $Rs[pane] + , $T = $Ts[pane] + , o = options[pane] + , s = state[pane] + , side = _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[side] + 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 ) + + removeHover( 0, $R ); // remove hover classes + if (o.resizable && $.layout.plugins.draggable) + $R .draggable("enable") + .css("cursor", o.resizerCursor) + .attr("title", o.tips.Resize); + 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.tips.Close); // 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, elDims($P)); + + if (state.initialized) { + // resize resizer & toggler sizes for all panes + sizeHandles(); + // resize content every time pane opens - to be sure + sizeContent(pane, true); // true = remeasure headers/footers, even if 'pane.isMoving' + } + + if (!skipCallback && (state.initialized || o.triggerEventsOnLoad) && $P.is(":visible")) { + // onopen callback + _runCallbacks("onopen_end", pane); + // onshow callback - TODO: should this be here? + if (s.isShowing) _runCallbacks("onshow_end", pane); + + // ALSO call onresize because layout-size *may* have changed while pane was closed + if (state.initialized) + _runCallbacks("onresize_end", pane); + } + + // TODO: Somehow sizePane("north") is being called after this point??? + } + + + /** + * slideOpen / slideClose / slideToggle + * + * Pass-though methods for sliding + */ +, slideOpen = function (evt_or_pane) { + if (!isInitialized()) return; + var evt = evtObj(evt_or_pane) + , pane = evtPane.call(this, 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 () { + if (!s.isClosed) // skip if no longer closed! + bindStopSlidingEvents(pane, true); // BIND trigger events to close sliding-pane + else if (!s.isMoving) + open(pane, true); // true = slide - open() will handle binding + }; + } + +, slideClose = function (evt_or_pane) { + if (!isInitialized()) return; + var evt = evtObj(evt_or_pane) + , pane = evtPane.call(this, evt_or_pane) + , o = options[pane] + , s = state[pane] + , delay = s.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 && s.isMoving) + 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 (!s.isMoving) + close(pane); // close will handle unbinding + }; + } + + /** + * @param {(string|Object)} evt_or_pane The pane being opened, ie: north, south, east, or west + */ +, slideToggle = function (evt_or_pane) { + var pane = evtPane.call(this, evt_or_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 + */ +, lockPaneForFX = function (pane, doLock) { + var $P = $Ps[pane] + , s = state[pane] + , o = options[pane] + , z = options.zIndexes + ; + if (doLock) { + showMasks( pane, { animation: true, objectsOnly: true }); + $P.css({ zIndex: z.pane_animate }); // overlay all elements during animation + if (pane=="south") + $P.css({ top: sC.inset.top + sC.innerHeight - $P.outerHeight() }); + else if (pane=="east") + $P.css({ left: sC.inset.left + sC.innerWidth - $P.outerWidth() }); + } + else { // animation DONE - RESET CSS + hideMasks(); + $P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) }); + if (pane=="south") + $P.css({ top: "auto" }); + // if pane is positioned 'off-screen', then DO NOT screw with it! + else if (pane=="east" && !$P.css("left").match(/\-99999/)) + $P.css({ left: "auto" }); + // fix anti-aliasing in IE - only needed for animations that change opacity + if (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? + */ +, bindStartSlidingEvents = function (pane, enable) { + var o = options[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , evtName = o.slideTrigger_open.toLowerCase() + ; + if (!$R || (enable && !o.slidable)) return; + + // make sure we have a valid event + if (evtName.match(/mouseover/)) + evtName = o.slideTrigger_open = "mouseenter"; + else if (!evtName.match(/(click|dblclick|mouseenter)/)) + evtName = o.slideTrigger_open = "click"; + + // must remove double-click-toggle when using dblclick-slide + if (o.resizerDblClickToggle && evtName.match(/click/)) { + $R[enable ? "unbind" : "bind"]('dblclick.'+ sID, toggle) + } + + $R + // add or remove event + [enable ? "bind" : "unbind"](evtName +'.'+ sID, slideOpen) + // set the appropriate cursor & title/tip + .css("cursor", enable ? o.sliderCursor : "default") + .attr("title", enable ? o.tips.Slide : "") + ; + } + + /** + * Add or remove 'mouseleave' events to 'slide close' when pane is 'sliding' open or closed + * Also increases zIndex when pane is sliding open + * See bindStartSlidingEvents 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? + */ +, bindStopSlidingEvents = function (pane, enable) { + var o = options[pane] + , s = state[pane] + , c = _c[pane] + , z = options.zIndexes + , evtName = o.slideTrigger_close.toLowerCase() + , action = (enable ? "bind" : "unbind") + , $P = $Ps[pane] + , $R = $Rs[pane] + ; + timer.clear(pane+"_closeSlider"); // just in case + + if (enable) { + s.isSliding = true; + state.panesSliding[pane] = true; + // remove 'slideOpen' event from resizer + // ALSO will raise the zIndex of the pane & resizer + bindStartSlidingEvents(pane, false); + } + else { + s.isSliding = false; + delete state.panesSliding[pane]; + } + + // 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+2 : z.resizer_normal); // NOTE: mask = pane_sliding+1 + + // make sure we have a valid event + if (!evtName.match(/(click|mouseleave)/)) + evtName = o.slideTrigger_close = "mouseleave"; // also catches 'mouseout' + + // add/remove slide triggers + $R[action](evtName, slideClose); // base event on resize + // need extra events for mouseleave + if (evtName === "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 (evtName === "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.tips.Close : ""); // 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=false] Called from onOpen? + * @param {boolean=} [skipCallback=false] Should the onresize callback be run? + * @param {boolean=} [force=false] + */ +, 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 + _showPane(pane); + 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 + _hidePane(pane); + 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, true, force); // true = noAnimation + else if (s.size < s.minSize) // pane is too small - enlarge it + sizePane(pane, s.minSize, skipCallback, true, force); // true = noAnimation + // need s.isVisible because new pseudoClose method keeps pane visible, but off-screen + else if ($R && s.isVisible && $P.is(":visible")) { + // make sure resizer-bar is positioned correctly + // handles situation where nested layout was 'hidden' when initialized + var pos = s.size + sC.inset[c.side]; + if ($.layout.cssNum( $R, c.side ) != pos) $R.css( c.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|Object)} evt_or_pane The pane being resized + * @param {number} size The *desired* new size for this pane - will be validated + * @param {boolean=} [skipCallback=false] Should the onresize callback be run? + * @param {boolean=} [noAnimation=false] + * @param {boolean=} [force=false] Force resizing even if does not seem necessary + */ +, manualSizePane = function (evt_or_pane, size, skipCallback, noAnimation, force) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , o = options[pane] + , s = state[pane] + // if resizing callbacks have been delayed and resizing is now DONE, force resizing to complete... + , forceResize = force || (o.livePaneResizing && !s.isResizing) + ; + // ANY call to manualSizePane disables autoResize - ie, percentage sizing + s.autoResize = false; + // flow-through... + sizePane(pane, size, skipCallback, noAnimation, forceResize); // will animate resize if option enabled + } + + /** + * @param {(string|Object)} evt_or_pane The pane being resized + * @param {number} size The *desired* new size for this pane - will be validated + * @param {boolean=} [skipCallback=false] Should the onresize callback be run? + * @param {boolean=} [noAnimation=false] + * @param {boolean=} [force=false] Force resizing even if does not seem necessary + */ +, sizePane = function (evt_or_pane, size, skipCallback, noAnimation, force) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) // probably NEVER called from event? + , o = options[pane] + , s = state[pane] + , $P = $Ps[pane] + , $R = $Rs[pane] + , side = _c[pane].side + , dimName = _c[pane].sizeType.toLowerCase() + , skipResizeWhileDragging = s.isResizing && !o.triggerEventsDuringLiveResize + , doFX = noAnimation !== true && o.animatePaneSizing + , oldSize, newSize + ; + // QUEUE in case another action/animation is in progress + $N.queue(function( queueNext ){ + // 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! + queueNext(); // call before makePaneFit() because it needs the queue free + 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 queueNext(); + + s.newSize = size; + + // onresize_start callback CANNOT cancel resizing because this would break the layout! + if (!skipCallback && state.initialized && s.isVisible) + _runCallbacks("onresize_start", pane); + + // resize the pane, and make sure its visible + newSize = cssSize(pane, size); + + if (doFX && $P.is(":visible")) { // ANIMATE + var fx = $.layout.effects.size[pane] || $.layout.effects.size.all + , easing = o.fxSettings_size.easing || fx.easing + , z = options.zIndexes + , props = {}; + props[ dimName ] = newSize +'px'; + s.isMoving = true; + // overlay all elements during animation + $P.css({ zIndex: z.pane_animate }) + .show().animate( props, o.fxSpeed_size, easing, function(){ + // reset zIndex after animation + $P.css({ zIndex: (s.isSliding ? z.pane_sliding : z.pane_normal) }); + s.isMoving = false; + delete s.newSize; + sizePane_2(); // continue + queueNext(); + }); + } + else { // no animation + $P.css( dimName, newSize ); // resize pane + delete s.newSize; + // if pane is visible, then + if ($P.is(":visible")) + sizePane_2(); // continue + else { + // pane is NOT VISIBLE, so just update state data... + // when pane is *next opened*, it will have the new size + s.size = size; // update state.size + $.extend(s, elDims($P)); // update state dimensions + } + queueNext(); + }; + + }); + + // SUBROUTINE + function sizePane_2 () { + /* Panes are sometimes not sized precisely in some browsers!? + * This code will resize the pane up to 3 times to nudge the pane to the correct size + */ + var actual = dimName==='width' ? $P.outerWidth() : $P.outerHeight() + , tries = [{ + pane: pane + , count: 1 + , target: size + , actual: actual + , correct: (size === actual) + , attempt: size + , cssSize: newSize + }] + , lastTry = tries[0] + , thisTry = {} + , msg = 'Inaccurate size after resizing the '+ pane +'-pane.' + ; + while ( !lastTry.correct ) { + thisTry = { pane: pane, count: lastTry.count+1, target: size }; + + if (lastTry.actual > size) + thisTry.attempt = max(0, lastTry.attempt - (lastTry.actual - size)); + else // lastTry.actual < size + thisTry.attempt = max(0, lastTry.attempt + (size - lastTry.actual)); + + thisTry.cssSize = cssSize(pane, thisTry.attempt); + $P.css( dimName, thisTry.cssSize ); + + thisTry.actual = dimName=='width' ? $P.outerWidth() : $P.outerHeight(); + thisTry.correct = (size === thisTry.actual); + + // log attempts and alert the user of this *non-fatal error* (if showDebugMessages) + if ( tries.length === 1) { + _log(msg, false, true); + _log(lastTry, false, true); + } + _log(thisTry, false, true); + // after 4 tries, is as close as its gonna get! + if (tries.length > 3) break; + + tries.push( thisTry ); + lastTry = tries[ tries.length - 1 ]; + } + // END TESTING CODE + + // update pane-state dimensions + s.size = size; + $.extend(s, elDims($P)); + + if (s.isVisible && $P.is(":visible")) { + // reposition the resizer-bar + if ($R) $R.css( side, size + sC.inset[side] ); + // resize the content-div + sizeContent(pane); + } + + if (!skipCallback && !skipResizeWhileDragging && state.initialized && s.isVisible) + _runCallbacks("onresize_end", 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 triggerEventsDuringLiveResize + if (!s.isSliding) sizeMidPanes(_c[pane].dir=="horz" ? "" : "center", skipResizeWhileDragging, force); + sizeHandles(); + } + + // if opposite-pane was autoClosed, see if it can be autoOpened now + var altPane = _c.oppositeEdge[pane]; + if (size < oldSize && state[ altPane ].noRoom) { + setSizeLimits( altPane ); + makePaneFit( altPane, false, skipCallback ); + } + + // DEBUG - ALERT user/developer so they know there was a sizing problem + if (tries.length > 1) + _log(msg +'\nSee the Error Console for details.', true, true); + } + } + + /** + * @see initPanes(), sizePane(), resizeAll(), open(), close(), hide() + * @param {(Array.|string)} panes The pane(s) being resized, comma-delmited string + * @param {boolean=} [skipCallback=false] Should the onresize callback be run? + * @param {boolean=} [force=false] + */ +, sizeMidPanes = function (panes, skipCallback, force) { + panes = (panes ? panes : "east,west,center").split(","); + + $.each(panes, 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 = {} + // if pane is not visible, show it invisibly NOW rather than for *each call* in this script + , visCSS = $.layout.showInvisibly($P) + + , newCenter = calcNewCenterPaneDims() + ; + + // update pane-state dimensions + $.extend(s, elDims($P)); + + if (pane === "center") { + if (!force && s.isVisible && newCenter.width === s.outerWidth && newCenter.height === s.outerHeight) { + $P.css(visCSS); + return true; // SKIP - pane already the correct size + } + // set state for makePaneFit() logic + $.extend(s, cssMinDims(pane), { + maxWidth: newCenter.width + , maxHeight: newCenter.height + }); + CSS = newCenter; + s.newWidth = CSS.width; + s.newHeight = CSS.height; + // convert OUTER width/height to CSS width/height + CSS.width = cssW($P, CSS.width); + // NEW - allow pane to extend 'below' visible area rather than hide it + CSS.height = cssH($P, CSS.height); + hasRoom = CSS.width >= 0 && CSS.height >= 0; // height >= 0 = ALWAYS TRUE NOW + + // during layout init, try to shrink east/west panes to make room for center + if (!state.initialized && o.minWidth > newCenter.width) { + 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 && sizeE != minE) + sizePane('east', newE, true, true, force); // true = skipCallback/noAnimation - initPanes will handle when done + if (sizeW && sizeW != minW) + sizePane('west', newW, true, true, force); // true = skipCallback/noAnimation + // now start over! + sizeMidPanes('center', skipCallback, force); + $P.css(visCSS); + 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, elDims($P), cssMinDims(pane)) + if (!force && !s.noVerticalRoom && newCenter.height === s.outerHeight) { + $P.css(visCSS); + return true; // SKIP - pane already the correct size + } + // east/west have same top, bottom & height as center + CSS.top = newCenter.top; + CSS.bottom = newCenter.bottom; + s.newSize = newCenter.height + // NEW - allow pane to extend 'below' visible area rather than hide it + CSS.height = cssH($P, newCenter.height); + s.maxHeight = CSS.height; + hasRoom = (s.maxHeight >= 0); // ALWAYS TRUE NOW + 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) + _runCallbacks("onresize_start", pane); + + $P.css(CSS); // apply the CSS to pane + if (pane !== "center") + sizeHandles(pane); // also update resizer length + if (s.noRoom && !s.isClosed && !s.isHidden) + makePaneFit(pane); // will re-open/show auto-closed/hidden pane + if (s.isVisible) { + $.extend(s, elDims($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 + + // reset visibility, if necessary + $P.css(visCSS); + + delete s.newSize; + delete s.newWidth; + delete s.newHeight; + + 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' + * TODO: Can I use width:100% for a north/south iframe? + * TODO: Sounds like a job for $P.outerWidth( sC.innerWidth ) SETTER METHOD + */ + if (pane === "center") { // finished processing midPanes + var fix = browser.isIE6 || !browser.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) + _runCallbacks("onresize_end", pane); + }); + } + + + /** + * @see window.onresize(), callbacks or custom code + * @param {(Object|boolean)=} evt_or_refresh If 'true', then also reset pane-positioning + */ +, resizeAll = function (evt_or_refresh) { + var oldW = sC.innerWidth + , oldH = sC.innerHeight + ; + // stopPropagation if called by trigger("layoutdestroy") - use evtPane utility + evtPane(evt_or_refresh); + + // cannot size layout when 'container' is hidden or collapsed + if (!$N.is(":visible")) return; + + if (!state.initialized) { + _initLayoutElements(); + return; // no need to resize since we just initialized! + } + + if (evt_or_refresh === true && $.isPlainObject(options.outset)) { + // update container CSS in case outset option has changed + $N.css( options.outset ); + } + // UPDATE container dimensions + $.extend(sC, elDims( $N, options.inset )); + if (!sC.outerHeight) return; + + // if 'true' passed, refresh pane & handle positioning too + if (evt_or_refresh === true) { + setPanePosition(); + } + + // onresizeall_start will CANCEL resizing if returns false + // state.container has already been set, so user can access this info for calcuations + if (false === _runCallbacks("onresizeall_start")) return false; + + var // see if container is now 'smaller' than before + shrunkH = (sC.innerHeight < oldH) + , shrunkW = (sC.innerWidth < oldW) + , $P, o, s + ; + // NOTE special order for sizing: S-N-E-W + $.each(["south","north","east","west"], function (i, pane) { + if (!$Ps[pane]) return; // no pane - SKIP + o = options[pane]; + s = state[pane]; + if (s.autoResize && s.size != o.size) // resize pane to original size set in options + sizePane(pane, o.size, true, true, true); // true=skipCallback/noAnimation/forceResize + else { + setSizeLimits(pane); + makePaneFit(pane, false, true, true); // true=skipCallback/forceResize + } + }); + + sizeMidPanes("", true, true); // true=skipCallback/forceResize + sizeHandles(); // reposition the toggler elements + + // trigger all individual pane callbacks AFTER layout has finished resizing + $.each(_c.allPanes, function (i, pane) { + $P = $Ps[pane]; + if (!$P) return; // SKIP + if (state[pane].isVisible) // undefined for non-existent panes + _runCallbacks("onresize_end", pane); // callback - if exists + }); + + _runCallbacks("onresizeall_end"); + //_triggerLayoutEvent(pane, 'resizeall'); + } + + /** + * Whenever a pane resizes or opens that has a nested layout, trigger resizeAll + * + * @param {(string|Object)} evt_or_pane The pane just resized or opened + */ +, resizeChildren = function (evt_or_pane, skipRefresh) { + var pane = evtPane.call(this, evt_or_pane); + + if (!options[pane].resizeChildren) return; + + // ensure the pane-children are up-to-date + if (!skipRefresh) refreshChildren( pane ); + var pC = children[pane]; + if ($.isPlainObject( pC )) { + // resize one or more children + $.each( pC, function (key, child) { + child.resizeAll(); + }); + } + } + + /** + * IF pane has a content-div, then resize all elements inside pane to fit pane-height + * + * @param {(string|Object)} evt_or_panes The pane(s) being resized + * @param {boolean=} [remeasure=false] Should the content (header/footer) be remeasured? + */ +, sizeContent = function (evt_or_panes, remeasure) { + if (!isInitialized()) return; + + var panes = evtPane.call(this, evt_or_panes); + panes = panes ? panes.split(",") : _c.allPanes; + + $.each(panes, 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 + + // if content-element was REMOVED, update OR remove the pointer + if (!$C.length) { + initContent(pane, false); // false = do NOT sizeContent() - already there! + if (!$C) return; // no replacement element found - pointer have been removed + } + + // onsizecontent_start will CANCEL resizing if returns false + if (false === _runCallbacks("onsizecontent_start", pane)) return; + + // skip re-measuring offsets if live-resizing + if ((!s.isMoving && !s.isResizing) || o.liveContentResizing || remeasure || m.top == undefined) { + _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 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) + _runCallbacks("onsizecontent_end", 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(".ui-layout-mask").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|Object)=} evt_or_panes The pane(s) being resized + */ +, sizeHandles = function (evt_or_panes) { + var panes = evtPane.call(this, evt_or_panes) + panes = panes ? panes.split(",") : _c.borderPanes; + + $.each(panes, 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 + , left + , 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 || + paneLen = sC.innerWidth; // handle offscreen-panes + s.resizerLength = paneLen; + left = $.layout.cssNum($P, "left") + $R.css({ + width: cssW($R, paneLen) // account for borders & padding + , height: cssH($R, spacing) // ditto + , left: left > -9999 ? left : sC.inset.left // handle offscreen-panes + }); + } + else { // east/west + paneLen = $P.outerHeight(); // s.outerHeight || + s.resizerLength = paneLen; + $R.css({ + height: cssH($R, paneLen) // account for borders & padding + , width: cssW($R, spacing) // ditto + , top: sC.inset.top + getPaneSize("north", true) // TODO: what if no North pane? + //, top: $.layout.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 = round((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: width // account for borders & padding + , height: 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", round((width-$TC.outerWidth())/2)); // could be negative + }); + } + else { // east/west + var height = cssH($T, togLen); + $T.css({ + height: height // account for borders & padding + , width: 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", round((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(); + } + }); + } + + + /** + * @param {(string|Object)} evt_or_pane + */ +, enableClosable = function (evt_or_pane) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $T = $Ts[pane] + , o = options[pane] + ; + if (!$T) return; + o.closable = true; + $T .bind("click."+ sID, function(evt){ evt.stopPropagation(); toggle(pane); }) + .css("visibility", "visible") + .css("cursor", "pointer") + .attr("title", state[pane].isClosed ? o.tips.Open : o.tips.Close) // may be blank + .show(); + } + /** + * @param {(string|Object)} evt_or_pane + * @param {boolean=} [hide=false] + */ +, disableClosable = function (evt_or_pane, hide) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $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", ""); + } + + + /** + * @param {(string|Object)} evt_or_pane + */ +, enableSlidable = function (evt_or_pane) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $R = $Rs[pane] + ; + if (!$R || !$R.data('draggable')) return; + options[pane].slidable = true; + if (state[pane].isClosed) + bindStartSlidingEvents(pane, true); + } + /** + * @param {(string|Object)} evt_or_pane + */ +, disableSlidable = function (evt_or_pane) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $R = $Rs[pane] + ; + if (!$R) return; + options[pane].slidable = false; + if (state[pane].isSliding) + close(pane, false, true); + else { + bindStartSlidingEvents(pane, false); + $R .css("cursor", "default") + .attr("title", ""); + removeHover(null, $R[0]); // in case currently hovered + } + } + + + /** + * @param {(string|Object)} evt_or_pane + */ +, enableResizable = function (evt_or_pane) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $R = $Rs[pane] + , o = options[pane] + ; + if (!$R || !$R.data('draggable')) return; + o.resizable = true; + $R.draggable("enable"); + if (!state[pane].isClosed) + $R .css("cursor", o.resizerCursor) + .attr("title", o.tips.Resize); + } + /** + * @param {(string|Object)} evt_or_pane + */ +, disableResizable = function (evt_or_pane) { + if (!isInitialized()) return; + var pane = evtPane.call(this, evt_or_pane) + , $R = $Rs[pane] + ; + if (!$R || !$R.data('draggable')) return; + options[pane].resizable = false; + $R .draggable("disable") + .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|Object)} evt_or_pane1 The pane/edge being swapped + * @param {string} pane2 ditto + */ +, swapPanes = function (evt_or_pane1, pane2) { + if (!isInitialized()) return; + var pane1 = evtPane.call(this, evt_or_pane1); + // 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 + if (false === _runCallbacks("onswap_start", pane1) + || false === _runCallbacks("onswap_start", pane2) + ) { + 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 + _runCallbacks("onswap_end", pane1); + _runCallbacks("onswap_end", pane2); + + 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(true, {}, state[n]) + , options: $.extend(true, {}, options[n]) + } + }; + + function move (oPane, pane) { + if (!oPane) return; + var + P = oPane.P + , C = oPane.C + , oldPane = oPane.pane + , c = _c[pane] + // save pane-options that should be retained + , s = $.extend(true, {}, 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 +"_open"] = o[k +"_open"]; + fx[k +"_close"] = o[k +"_close"]; + fx[k +"_size"] = o[k +"_size"]; + }); + + // update object pointers and attributes + $Ps[pane] = $(P) + .data({ + layoutPane: Instance[pane] // NEW pointer to pane-alias-object + , layoutEdge: pane + }) + .css(_c.hidden) + .css(c.cssReq) + ; + $Cs[pane] = C ? $(C) : false; + + // set options and state + options[pane] = $.extend(true, {}, oPane.options, fx); + state[pane] = $.extend(true, {}, 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); // true/true = skipCallback/noAnimation + } + else // move the resizer here + $Rs[pane].css(c.side, sC.inset[c.side] + (state[pane].isVisible ? getPaneSize(pane) : 0)); + + + // ADD CLASSNAMES & SLIDE-BINDINGS + if (oPane.state.isVisible && !s.isVisible) + setAsOpen(pane, true); // true = skipCallback + else { + setAsClosed(pane); + bindStartSlidingEvents(pane, true); // will enable events IF option is set + } + + // DESTROY the object + oPane = null; + }; + } + + + /** + * INTERNAL method 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(), setAsOpen(), setAsClosed() + * @param {string} pane These are the params returned to callbacks by layout() + * @param {boolean} doPin True means set the pin 'down', False means 'up' + */ +, syncPinBtns = function (pane, doPin) { + if ($.layout.plugins.buttons) + $.each(state[pane].pins, function (i, selector) { + $.layout.buttons.setPinState(Instance, $(selector), pane, doPin); + }); + } + +; // END var DECLARATIONS + + /** + * 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, 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 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 (!isInitialized()) return; + 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 its closed, then abort + if (s.isSliding || s.isResizing || s.isClosed) { + s.cssSaved = false; + return; + } + + var + newCSS = { zIndex: (options.zIndexes.resizer_normal + 1) } + , 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, function(i, p) { + if (p != pane) resetOverflow(p); + }); + + }; + /** + * @param {Object=} [el] (optional) Can also be 'bound' to a click, mouseOver, or other event + */ + function resetOverflow (el) { + if (!isInitialized()) return; + 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", options.zIndexes.pane_normal); + + // reset Overflow - if necessary + $P.css( CSS ); + + // clear var + s.cssSaved = false; + }; + +/* + * ##################### + * CREATE/RETURN LAYOUT + * ##################### + */ + + // validate that container exists + var $N = $(this).eq(0); // FIRST matching Container element + if (!$N.length) { + return _log( options.errors.containerMissing ); + }; + + // Users retrieve Instance of a layout with: $N.layout() OR $N.data("layout") + // return the Instance-pointer if layout has already been initialized + if ($N.data("layoutContainer") && $N.data("layout")) + return $N.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() + , $Ms = $([]) // Masks - up to 2 masks per pane (IFRAME + DIV) + // 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 = { + // layout data + options: options // property - options hash + , state: state // property - dimensions hash + // object pointers + , container: $N // 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: contents.north, contents.center + , resizers: $Rs // property - object pointers for ALL Resizers, eg: resizers.north + , togglers: $Ts // property - object pointers for ALL Togglers, eg: togglers.north + // border-pane open/close + , hide: hide // method - ditto + , show: show // method - ditto + , toggle: toggle // method - pass a 'pane' ("north", "west", etc) + , open: open // method - ditto + , close: close // method - ditto + , slideOpen: slideOpen // method - ditto + , slideClose: slideClose // method - ditto + , slideToggle: slideToggle // method - ditto + // pane actions + , setSizeLimits: setSizeLimits // method - pass a 'pane' - update state min/max data + , _sizePane: sizePane // method -intended for user by plugins only! + , sizePane: manualSizePane // method - pass a 'pane' AND an 'outer-size' in pixels or percent, or 'auto' + , sizeContent: sizeContent // method - pass a 'pane' + , swapPanes: swapPanes // method - pass TWO 'panes' - will swap them + , showMasks: showMasks // method - pass a 'pane' OR list of panes - default = all panes with mask option set + , hideMasks: hideMasks // method - ditto' + // pane element methods + , initContent: initContent // method - ditto + , addPane: addPane // method - pass a 'pane' + , removePane: removePane // method - pass a 'pane' to remove from layout, add 'true' to delete the pane-elem + , createChildren: createChildren // method - pass a 'pane' and (optional) layout-options (OVERRIDES options[pane].children + , refreshChildren: refreshChildren // method - pass a 'pane' and a layout-instance + // special pane option setting + , enableClosable: enableClosable // method - pass a 'pane' + , disableClosable: disableClosable // method - ditto + , enableSlidable: enableSlidable // method - ditto + , disableSlidable: disableSlidable // method - ditto + , enableResizable: enableResizable // method - ditto + , disableResizable: disableResizable// method - ditto + // utility methods for panes + , allowOverflow: allowOverflow // utility - pass calling element (this) + , resetOverflow: resetOverflow // utility - ditto + // layout control + , destroy: destroy // method - no parameters + , initPanes: isInitialized // method - no parameters + , resizeAll: resizeAll // method - no parameters + // callback triggering + , runCallbacks: _runCallbacks // method - pass evtName & pane (if a pane-event), eg: trigger("onopen", "west") + // alias collections of options, state and children - created in addPane and extended elsewhere + , hasParentLayout: false // set by initContainer() + , children: children // pointers to child-layouts, eg: Instance.children.west.layoutName + , north: false // alias group: { name: pane, pane: $Ps[pane], options: options[pane], state: state[pane], children: children[pane] } + , south: false // ditto + , west: false // ditto + , east: false // ditto + , center: false // ditto + }; + + // create the border layout NOW + if (_create() === 'cancel') // onload_start callback returned false to CANCEL layout creation + return null; + else // true OR false -- if layout-elements did NOT init (hidden or do not exist), can auto-init later + return Instance; // return the Instance object + +} + + +/* OLD versions of jQuery only set $.support.boxModel after page is loaded + * so if this is IE, use support.boxModel to test for quirks-mode (ONLY IE changes boxModel). + */ +$(function(){ + var b = $.layout.browser; + if (b.msie) b.boxModel = $.support.boxModel; +}); + + +})( jQuery ); +// END Layout - keep internal vars internal! + + + +// START Plugins - shared wrapper, no global vars +(function ($) { + + +/** + * jquery.layout.state 1.0 + * $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $ + * + * Copyright (c) 2012 + * 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. + * + * @requires: UI Layout 1.3.0.rc30.1 or higher + * @requires: $.ui.cookie (above) + * + * @see: http://groups.google.com/group/jquery-ui-layout + */ +/* + * State-management options stored in options.stateManagement, which includes a .cookie hash + * Default options saves ALL KEYS for ALL PANES, ie: pane.size, pane.isClosed, pane.isHidden + * + * // STATE/COOKIE OPTIONS + * @example $(el).layout({ + stateManagement: { + enabled: true + , stateKeys: "east.size,west.size,east.isClosed,west.isClosed" + , cookie: { name: "appLayout", path: "/" } + } + }) + * @example $(el).layout({ stateManagement__enabled: true }) // enable auto-state-management using cookies + * @example $(el).layout({ stateManagement__cookie: { name: "appLayout", path: "/" } }) + * @example $(el).layout({ stateManagement__cookie__name: "appLayout", stateManagement__cookie__path: "/" }) + * + * // STATE/COOKIE METHODS + * @example myLayout.saveCookie( "west.isClosed,north.size,south.isHidden", {expires: 7} ); + * @example myLayout.loadCookie(); + * @example myLayout.deleteCookie(); + * @example var JSON = myLayout.readState(); // CURRENT Layout State + * @example var JSON = myLayout.readCookie(); // SAVED Layout State (from cookie) + * @example var JSON = myLayout.state.stateData; // LAST LOADED Layout State (cookie saved in layout.state hash) + * + * CUSTOM STATE-MANAGEMENT (eg, saved in a database) + * @example var JSON = myLayout.readState( "west.isClosed,north.size,south.isHidden" ); + * @example myLayout.loadState( JSON ); + */ + +/** + * UI COOKIE UTILITY + * + * A $.cookie OR $.ui.cookie namespace *should be standard*, but until then... + * This creates $.ui.cookie so Layout does not need the cookie.jquery.js plugin + * NOTE: This utility is REQUIRED by the layout.state plugin + * + * Cookie methods in Layout are created as part of State Management + */ +if (!$.ui) $.ui = {}; +$.ui.cookie = { + + // cookieEnabled is not in DOM specs, but DOES works in all browsers,including IE6 + acceptsCookies: !!navigator.cookieEnabled + +, read: function (name) { + var + 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 + return decodeURIComponent(pair[1]); + + } + return null; + } + +, write: function (name, val, cookieOpts) { + var + params = '' + , date = '' + , clear = false + , o = cookieOpts || {} + , x = o.expires + ; + if (x && x.toUTCString) + date = x; + else if (x === null || typeof x === 'number') { + date = new Date(); + if (x > 0) + date.setDate(date.getDate() + x); + else { + date.setFullYear(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'; + document.cookie = name +'='+ (clear ? "" : encodeURIComponent( val )) + params; // write or clear cookie + } + +, clear: function (name) { + $.ui.cookie.write(name, '', {expires: -1}); + } + +}; +// if cookie.jquery.js is not loaded, create an alias to replicate it +// this may be useful to other plugins or code dependent on that plugin +if (!$.cookie) $.cookie = function (k, v, o) { + var C = $.ui.cookie; + if (v === null) + C.clear(k); + else if (v === undefined) + return C.read(k); + else + C.write(k, v, o); +}; + + +// tell Layout that the state plugin is available +$.layout.plugins.stateManagement = true; + +// Add State-Management options to layout.defaults +$.layout.config.optionRootKeys.push("stateManagement"); +$.layout.defaults.stateManagement = { + enabled: false // true = enable state-management, even if not using cookies +, autoSave: true // Save a state-cookie when page exits? +, autoLoad: true // Load the state-cookie when Layout inits? +, animateLoad: true // animate panes when loading state into an active layout +, includeChildren: true // recurse into child layouts to include their state as well + // List state-data to save - must be pane-specific +, stateKeys: "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: { + name: "" // If not specified, will use Layout.name, else just "Layout" + , domain: "" // blank = current domain + , path: "" // blank = current page, '/' = entire website + , expires: "" // 'days' to keep cookie - leave blank for 'session cookie' + , secure: false + } +}; +// Set stateManagement as a layout-option, NOT a pane-option +$.layout.optionsMap.layout.push("stateManagement"); + +/* + * State Management methods + */ +$.layout.state = { + + /** + * Get the current layout state and save it to a cookie + * + * myLayout.saveCookie( keys, cookieOpts ) + * + * @param {Object} inst + * @param {(string|Array)=} keys + * @param {Object=} cookieOpts + */ + saveCookie: function (inst, keys, cookieOpts) { + var o = inst.options + , sm = o.stateManagement + , oC = $.extend(true, {}, sm.cookie, cookieOpts || null) + , data = inst.state.stateData = inst.readState( keys || sm.stateKeys ) // read current panes-state + ; + $.ui.cookie.write( oC.name || o.name || "Layout", $.layout.state.encodeJSON(data), oC ); + return $.extend(true, {}, data); // return COPY of state.stateData data + } + + /** + * Remove the state cookie + * + * @param {Object} inst + */ +, deleteCookie: function (inst) { + var o = inst.options; + $.ui.cookie.clear( o.stateManagement.cookie.name || o.name || "Layout" ); + } + + /** + * Read & return data from the cookie - as JSON + * + * @param {Object} inst + */ +, readCookie: function (inst) { + var o = inst.options; + var c = $.ui.cookie.read( o.stateManagement.cookie.name || o.name || "Layout" ); + // convert cookie string back to a hash and return it + return c ? $.layout.state.decodeJSON(c) : {}; + } + + /** + * Get data from the cookie and USE IT to loadState + * + * @param {Object} inst + */ +, loadCookie: function (inst) { + var c = $.layout.state.readCookie(inst); // READ the cookie + if (c) { + inst.state.stateData = $.extend(true, {}, c); // SET state.stateData + inst.loadState(c); // LOAD the retrieved state + } + return c; + } + + /** + * Update layout options from the cookie, if one exists + * + * @param {Object} inst + * @param {Object=} stateData + * @param {boolean=} animate + */ +, loadState: function (inst, data, opts) { + if (!$.isPlainObject( data ) || $.isEmptyObject( data )) return; + + // normalize data & cache in the state object + data = inst.state.stateData = $.layout.transformData( data ); // panes = default subkey + + // add missing/default state-restore options + var smo = inst.options.stateManagement; + opts = $.extend({ + animateLoad: false //smo.animateLoad + , includeChildren: smo.includeChildren + }, opts ); + + if (!inst.state.initialized) { + /* + * layout NOT initialized, so just update its options + */ + // MUST remove pane.children keys before applying to options + // use a copy so we don't remove keys from original data + var o = $.extend(true, {}, data); + //delete o.center; // center has no state-data - only children + $.each($.layout.config.allPanes, function (idx, pane) { + if (o[pane]) delete o[pane].children; + }); + // update CURRENT layout-options with saved state data + $.extend(true, inst.options, o); + } + else { + /* + * layout already initialized, so modify layout's configuration + */ + var noAnimate = !opts.animateLoad + , o, c, h, state, open + ; + $.each($.layout.config.borderPanes, function (idx, pane) { + o = data[ pane ]; + if (!$.isPlainObject( o )) return; // no key, skip pane + + s = o.size; + c = o.initClosed; + h = o.initHidden; + ar = o.autoResize + state = inst.state[pane]; + open = state.isVisible; + + // reset autoResize + if (ar) + state.autoResize = ar; + // resize BEFORE opening + if (!open) + inst._sizePane(pane, s, false, false, false); // false=skipCallback/noAnimation/forceResize + // open/close as necessary - DO NOT CHANGE THIS ORDER! + if (h === true) inst.hide(pane, noAnimate); + else if (c === true) inst.close(pane, false, noAnimate); + else if (c === false) inst.open (pane, false, noAnimate); + else if (h === false) inst.show (pane, false, noAnimate); + // resize AFTER any other actions + if (open) + inst._sizePane(pane, s, false, false, noAnimate); // animate resize if option passed + }); + + /* + * RECURSE INTO CHILD-LAYOUTS + */ + if (opts.includeChildren) { + var paneStateChildren, childState; + $.each(inst.children, function (pane, paneChildren) { + paneStateChildren = data[pane] ? data[pane].children : 0; + if (paneStateChildren && paneChildren) { + $.each(paneChildren, function (stateKey, child) { + childState = paneStateChildren[stateKey]; + if (child && childState) + child.loadState( childState ); + }); + } + }); + } + } + } + + /** + * Get the *current layout state* and return it as a hash + * + * @param {Object=} inst // Layout instance to get state for + * @param {object=} [opts] // State-Managements override options + */ +, readState: function (inst, opts) { + // backward compatility + if ($.type(opts) === 'string') opts = { keys: opts }; + if (!opts) opts = {}; + var sm = inst.options.stateManagement + , ic = opts.includeChildren + , recurse = ic !== undefined ? ic : sm.includeChildren + , keys = opts.stateKeys || sm.stateKeys + , alt = { isClosed: 'initClosed', isHidden: 'initHidden' } + , state = inst.state + , panes = $.layout.config.allPanes + , data = {} + , pair, pane, key, val + , ps, pC, child, array, count, branch + ; + 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 ($.inArray(pane, panes) < 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; + } + + // recurse into the child-layouts for each pane + if (recurse) { + $.each(panes, function (idx, pane) { + pC = inst.children[pane]; + ps = state.stateData[pane]; + if ($.isPlainObject( pC ) && !$.isEmptyObject( pC )) { + // ensure a key exists for this 'pane', eg: branch = data.center + branch = data[pane] || (data[pane] = {}); + if (!branch.children) branch.children = {}; + $.each( pC, function (key, child) { + // ONLY read state from an initialize layout + if ( child.state.initialized ) + branch.children[ key ] = $.layout.state.readState( child ); + // if we have PREVIOUS (onLoad) state for this child-layout, KEEP IT! + else if ( ps && ps.children && ps.children[ key ] ) { + branch.children[ key ] = $.extend(true, {}, ps.children[ key ] ); + } + }); + } + }); + } + + return data; + } + + /** + * Stringify a JSON hash so can save in a cookie or db-field + */ +, encodeJSON: function (JSON) { + return parse(JSON); + function parse (h) { + var D=[], i=0, k, v, t // k = key, v = value + , a = $.isArray(h) + ; + 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++] = (!a ? '"'+ k +'":' : '') + v; + } + return (a ? '[' : '{') + D.join(',') + (a ? ']' : '}'); + }; + } + + /** + * Convert stringified JSON back to a hash object + * @see $.parseJSON(), adding in jQuery 1.4.1 + */ +, decodeJSON: function (str) { + try { return $.parseJSON ? $.parseJSON(str) : window["eval"]("("+ str +")") || {}; } + catch (e) { return {}; } + } + + +, _create: function (inst) { + var _ = $.layout.state + , o = inst.options + , sm = o.stateManagement + ; + // ADD State-Management plugin methods to inst + $.extend( inst, { + // readCookie - update options from cookie - returns hash of cookie data + readCookie: function () { return _.readCookie(inst); } + // deleteCookie + , deleteCookie: function () { _.deleteCookie(inst); } + // saveCookie - optionally pass keys-list and cookie-options (hash) + , saveCookie: function (keys, cookieOpts) { return _.saveCookie(inst, keys, cookieOpts); } + // loadCookie - readCookie and use to loadState() - returns hash of cookie data + , loadCookie: function () { return _.loadCookie(inst); } + // loadState - pass a hash of state to use to update options + , loadState: function (stateData, opts) { _.loadState(inst, stateData, opts); } + // readState - returns hash of current layout-state + , readState: function (keys) { return _.readState(inst, keys); } + // add JSON utility methods too... + , encodeJSON: _.encodeJSON + , decodeJSON: _.decodeJSON + }); + + // init state.stateData key, even if plugin is initially disabled + inst.state.stateData = {}; + + // autoLoad MUST BE one of: data-array, data-hash, callback-function, or TRUE + if ( !sm.autoLoad ) return; + + // When state-data exists in the autoLoad key USE IT, + // even if stateManagement.enabled == false + if ($.isPlainObject( sm.autoLoad )) { + if (!$.isEmptyObject( sm.autoLoad )) { + inst.loadState( sm.autoLoad ); + } + } + else if ( sm.enabled ) { + // update the options from cookie or callback + // if options is a function, call it to get stateData + if ($.isFunction( sm.autoLoad )) { + var d = {}; + try { + d = sm.autoLoad( inst, inst.state, inst.options, inst.options.name || '' ); // try to get data from fn + } catch (e) {} + if (d && $.isPlainObject( d ) && !$.isEmptyObject( d )) + inst.loadState(d); + } + else // any other truthy value will trigger loadCookie + inst.loadCookie(); + } + } + +, _unload: function (inst) { + var sm = inst.options.stateManagement; + if (sm.enabled && sm.autoSave) { + // if options is a function, call it to save the stateData + if ($.isFunction( sm.autoSave )) { + try { + sm.autoSave( inst, inst.state, inst.options, inst.options.name || '' ); // try to get data from fn + } catch (e) {} + } + else // any truthy value will trigger saveCookie + inst.saveCookie(); + } + } + +}; + +// add state initialization method to Layout's onCreate array of functions +$.layout.onCreate.push( $.layout.state._create ); +$.layout.onUnload.push( $.layout.state._unload ); + + + + +/** + * jquery.layout.buttons 1.0 + * $Date: 2011-07-16 08:00:00 (Sat, 16 July 2011) $ + * + * Copyright (c) 2012 + * 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. + * + * @requires: UI Layout 1.3.0.rc30.1 or higher + * + * @see: http://groups.google.com/group/jquery-ui-layout + * + * Docs: [ to come ] + * Tips: [ to come ] + */ + +// tell Layout that the state plugin is available +$.layout.plugins.buttons = true; + +// Add buttons options to layout.defaults +$.layout.defaults.autoBindCustomButtons = false; +// Specify autoBindCustomButtons as a layout-option, NOT a pane-option +$.layout.optionsMap.layout.push("autoBindCustomButtons"); + +/* + * Button methods + */ +$.layout.buttons = { + + /** + * Searches for .ui-layout-button-xxx elements and auto-binds them as layout-buttons + * + * @see _create() + * + * @param {Object} inst Layout Instance object + */ + init: function (inst) { + var pre = "ui-layout-button-" + , layout = inst.options.name || "" + , name; + $.each("toggle,open,close,pin,toggle-slide,open-slide".split(","), function (i, action) { + $.each($.layout.config.borderPanes, 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 === layout) + inst.bindButton(this, action, pane); + }); + }); + }); + } + + /** + * 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 // action btnClass + * - ui-layout-button-pin-west // action btnClass + pane + * - ui-layout-button-toggle + * - ui-layout-button-open + * - ui-layout-button-close + * + * @param {Object} inst Layout Instance object + * @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.} If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise returns null + */ +, get: function (inst, selector, pane, action) { + var $E = $(selector) + , o = inst.options + , err = o.errors.addButtonError + ; + if (!$E.length) { // element not found + $.layout.msg(err +" "+ o.errors.selector +": "+ selector, true); + } + else if ($.inArray(pane, $.layout.config.borderPanes) < 0) { // invalid 'pane' sepecified + $.layout.msg(err +" "+ o.errors.pane +": "+ pane, true); + $E = $(""); // NO BUTTON + } + else { // VALID + var btn = o[pane].buttonClass +"-"+ action; + $E .addClass( btn +" "+ btn +"-"+ pane ) + .data("layoutName", o.name); // add layout identifier - even if blank! + } + return $E; + } + + + /** + * NEW syntax for binding layout-buttons - will eventually replace addToggle, addOpen, etc. + * + * @param {Object} inst Layout Instance object + * @param {(string|!Object)} selector jQuery selector (or element) for button, eg: ".ui-layout-north .toggle-button" + * @param {string} action + * @param {string} pane + */ +, bind: function (inst, selector, action, pane) { + var _ = $.layout.buttons; + switch (action.toLowerCase()) { + case "toggle": _.addToggle (inst, selector, pane); break; + case "open": _.addOpen (inst, selector, pane); break; + case "close": _.addClose (inst, selector, pane); break; + case "pin": _.addPin (inst, selector, pane); break; + case "toggle-slide": _.addToggle (inst, selector, pane, true); break; + case "open-slide": _.addOpen (inst, selector, pane, true); break; + } + return inst; + } + + /** + * Add a custom Toggler button for a pane + * + * @param {Object} inst Layout Instance object + * @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 + */ +, addToggle: function (inst, selector, pane, slide) { + $.layout.buttons.get(inst, selector, pane, "toggle") + .click(function(evt){ + inst.toggle(pane, !!slide); + evt.stopPropagation(); + }); + return inst; + } + + /** + * Add a custom Open button for a pane + * + * @param {Object} inst Layout Instance object + * @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 + */ +, addOpen: function (inst, selector, pane, slide) { + $.layout.buttons.get(inst, selector, pane, "open") + .attr("title", inst.options[pane].tips.Open) + .click(function (evt) { + inst.open(pane, !!slide); + evt.stopPropagation(); + }); + return inst; + } + + /** + * Add a custom Close button for a pane + * + * @param {Object} inst Layout Instance object + * @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. + */ +, addClose: function (inst, selector, pane) { + $.layout.buttons.get(inst, selector, pane, "close") + .attr("title", inst.options[pane].tips.Close) + .click(function (evt) { + inst.close(pane); + evt.stopPropagation(); + }); + return inst; + } + + /** + * 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 {Object} inst Layout Instance object + * @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. + */ +, addPin: function (inst, selector, pane) { + var _ = $.layout.buttons + , $E = _.get(inst, selector, pane, "pin"); + if ($E.length) { + var s = inst.state[pane]; + $E.click(function (evt) { + _.setPinState(inst, $(this), pane, (s.isSliding || s.isClosed)); + if (s.isSliding || s.isClosed) inst.open( pane ); // change from sliding to open + else inst.close( pane ); // slide-closed + evt.stopPropagation(); + }); + // add up/down pin attributes and classes + _.setPinState(inst, $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 + s.pins.push( selector ); // just save the selector string + } + return inst; + } + + /** + * Change the class of the pin button to make it look 'up' or 'down' + * + * @see addPin(), syncPins() + * + * @param {Object} inst Layout Instance object + * @param {Array.} $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' + */ +, setPinState: function (inst, $Pin, pane, doPin) { + var updown = $Pin.attr("pin"); + if (updown && doPin === (updown=="down")) return; // already in correct state + var + o = inst.options[pane] + , pin = o.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 ? o.tips.Unpin : o.tips.Pin) + .removeClass( doPin ? UP : DN ) + .addClass( doPin ? DN : UP ) + ; + } + + /** + * 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 {Object} inst Layout Instance object + * @param {string} pane These are the params returned to callbacks by layout() + * @param {boolean} doPin True means set the pin 'down', False means 'up' + */ +, syncPinBtns: function (inst, pane, doPin) { + // REAL METHOD IS _INSIDE_ LAYOUT - THIS IS HERE JUST FOR REFERENCE + $.each(inst.state[pane].pins, function (i, selector) { + $.layout.buttons.setPinState(inst, $(selector), pane, doPin); + }); + } + + +, _load: function (inst) { + var _ = $.layout.buttons; + // ADD Button methods to Layout Instance + // Note: sel = jQuery Selector string + $.extend( inst, { + bindButton: function (sel, action, pane) { return _.bind(inst, sel, action, pane); } + // DEPRECATED METHODS + , addToggleBtn: function (sel, pane, slide) { return _.addToggle(inst, sel, pane, slide); } + , addOpenBtn: function (sel, pane, slide) { return _.addOpen(inst, sel, pane, slide); } + , addCloseBtn: function (sel, pane) { return _.addClose(inst, sel, pane); } + , addPinBtn: function (sel, pane) { return _.addPin(inst, sel, pane); } + }); + + // init state array to hold pin-buttons + for (var i=0; i<4; i++) { + var pane = $.layout.config.borderPanes[i]; + inst.state[pane].pins = []; + } + + // auto-init buttons onLoad if option is enabled + if ( inst.options.autoBindCustomButtons ) + _.init(inst); + } + +, _unload: function (inst) { + // TODO: unbind all buttons??? + } + +}; + +// add initialization method to Layout's onLoad array of functions +$.layout.onLoad.push( $.layout.buttons._load ); +//$.layout.onUnload.push( $.layout.buttons._unload ); + + + +/** + * jquery.layout.browserZoom 1.0 + * $Date: 2011-12-29 08:00:00 (Thu, 29 Dec 2011) $ + * + * Copyright (c) 2012 + * 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. + * + * @requires: UI Layout 1.3.0.rc30.1 or higher + * + * @see: http://groups.google.com/group/jquery-ui-layout + * + * TODO: Extend logic to handle other problematic zooming in browsers + * TODO: Add hotkey/mousewheel bindings to _instantly_ respond to these zoom event + */ + +// tell Layout that the plugin is available +$.layout.plugins.browserZoom = true; + +$.layout.defaults.browserZoomCheckInterval = 1000; +$.layout.optionsMap.layout.push("browserZoomCheckInterval"); + +/* + * browserZoom methods + */ +$.layout.browserZoom = { + + _init: function (inst) { + // abort if browser does not need this check + if ($.layout.browserZoom.ratio() !== false) + $.layout.browserZoom._setTimer(inst); + } + +, _setTimer: function (inst) { + // abort if layout destroyed or browser does not need this check + if (inst.destroyed) return; + var o = inst.options + , s = inst.state + // don't need check if inst has parentLayout, but check occassionally in case parent destroyed! + // MINIMUM 100ms interval, for performance + , ms = inst.hasParentLayout ? 5000 : Math.max( o.browserZoomCheckInterval, 100 ) + ; + // set the timer + setTimeout(function(){ + if (inst.destroyed || !o.resizeWithWindow) return; + var d = $.layout.browserZoom.ratio(); + if (d !== s.browserZoom) { + s.browserZoom = d; + inst.resizeAll(); + } + // set a NEW timeout + $.layout.browserZoom._setTimer(inst); + } + , ms ); + } + +, ratio: function () { + var w = window + , s = screen + , d = document + , dE = d.documentElement || d.body + , b = $.layout.browser + , v = b.version + , r, sW, cW + ; + // we can ignore all browsers that fire window.resize event onZoom + if ((b.msie && v > 8) + || !b.msie + ) return false; // don't need to track zoom + + if (s.deviceXDPI && s.systemXDPI) // syntax compiler hack + return calc(s.deviceXDPI, s.systemXDPI); + // everything below is just for future reference! + if (b.webkit && (r = d.body.getBoundingClientRect)) + return calc((r.left - r.right), d.body.offsetWidth); + if (b.webkit && (sW = w.outerWidth)) + return calc(sW, w.innerWidth); + if ((sW = s.width) && (cW = dE.clientWidth)) + return calc(sW, cW); + return false; // no match, so cannot - or don't need to - track zoom + + function calc (x,y) { return (parseInt(x,10) / parseInt(y,10) * 100).toFixed(); } + } + +}; +// add initialization method to Layout's onLoad array of functions +$.layout.onReady.push( $.layout.browserZoom._init ); + + })( jQuery ); \ No newline at end of file diff --git a/htdocs/includes/jquery/plugins/mobile/jquery.mobile-latest.min.jgz b/htdocs/includes/jquery/plugins/mobile/jquery.mobile-latest.min.jgz deleted file mode 100644 index 43f8746af6c..00000000000 Binary files a/htdocs/includes/jquery/plugins/mobile/jquery.mobile-latest.min.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/tablednd/jquery.tablednd_0_5.jgz b/htdocs/includes/jquery/plugins/tablednd/jquery.tablednd_0_5.jgz deleted file mode 100644 index 21cc5480d30..00000000000 Binary files a/htdocs/includes/jquery/plugins/tablednd/jquery.tablednd_0_5.jgz and /dev/null differ diff --git a/htdocs/includes/jquery/plugins/tiptip/jquery.tipTip.min.jgz b/htdocs/includes/jquery/plugins/tiptip/jquery.tipTip.min.jgz deleted file mode 100755 index 89019f6c730..00000000000 Binary files a/htdocs/includes/jquery/plugins/tiptip/jquery.tipTip.min.jgz and /dev/null differ diff --git a/htdocs/install/mysql/data/llx_c_tva.sql b/htdocs/install/mysql/data/llx_c_tva.sql index b0b051a8dcf..db277680875 100644 --- a/htdocs/install/mysql/data/llx_c_tva.sql +++ b/htdocs/install/mysql/data/llx_c_tva.sql @@ -164,10 +164,10 @@ insert into llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) values (18 insert into llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) values (1844, 184, '0','0','VAT Rate 0', 1); -- PORTUGAL (id country=25) -insert into llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) values (251, 25, '20','0','VAT standard rate',1); -insert into llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) values (252, 25, '12','0','VAT reduced rate',1); +insert into llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) values (251, 25, '23','0','VAT standard rate',1); +insert into llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) values (252, 25, '13','0','VAT reduced rate',1); insert into llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) values (253, 25, '0','0','VAT Rate 0', 1); -insert into llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) values (254, 25, '5','0','VAT reduced rate',1); +insert into llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) values (254, 25, '6','0','VAT reduced rate',1); -- ROMANIA (id country=188) insert into llx_c_tva(rowid,fk_pays,taux,recuperableonly,note,active) values (1881,188, '24','0','VAT standard rate',1); diff --git a/htdocs/install/mysql/migration/3.2.0-3.3.0.sql b/htdocs/install/mysql/migration/3.2.0-3.3.0.sql index c8f7be2be94..984198e38a1 100755 --- a/htdocs/install/mysql/migration/3.2.0-3.3.0.sql +++ b/htdocs/install/mysql/migration/3.2.0-3.3.0.sql @@ -79,6 +79,8 @@ alter table llx_propaldet drop column pa_ht; alter table llx_propaldet drop column marge_tx; alter table llx_propaldet drop column marque_tx; +alter table llx_expedition add column height_unit integer after height; + ALTER TABLE llx_commande CHANGE COLUMN fk_demand_reason fk_input_reason integer NULL DEFAULT NULL; ALTER TABLE llx_propal CHANGE COLUMN fk_demand_reason fk_input_reason integer NULL DEFAULT NULL; ALTER TABLE llx_commande_fournisseur CHANGE COLUMN fk_methode_commande fk_input_method integer NULL DEFAULT 0; diff --git a/htdocs/install/mysql/tables/llx_expedition.sql b/htdocs/install/mysql/tables/llx_expedition.sql index 9296395b120..f40521d67fd 100644 --- a/htdocs/install/mysql/tables/llx_expedition.sql +++ b/htdocs/install/mysql/tables/llx_expedition.sql @@ -1,7 +1,7 @@ -- =================================================================== -- Copyright (C) 2003-2010 Rodolphe Quiedeville -- Copyright (C) 2008-2010 Regis Houssin --- Copyright (C) 2011 Laurent Destailleur +-- Copyright (C) 2011-2012 Laurent Destailleur -- Copyright (C) 2012 Juanjo Menent -- -- This program is free software; you can redistribute it and/or modify @@ -42,12 +42,12 @@ create table llx_expedition tracking_number varchar(50), fk_statut smallint DEFAULT 0, - height integer, - width integer, - size_units integer, - size integer, - weight_units integer, - weight integer, + height integer, -- height + width integer, -- with + size_units integer, -- unit of all sizes (height, width, depth) + size integer, -- depth + weight_units integer, -- unit of weight + weight integer, -- weight note text, model_pdf varchar(255) diff --git a/htdocs/langs/ca_ES/exports.lang b/htdocs/langs/ca_ES/exports.lang index 77d8e30c30b..04b588e68d0 100644 --- a/htdocs/langs/ca_ES/exports.lang +++ b/htdocs/langs/ca_ES/exports.lang @@ -123,4 +123,8 @@ SuppliersProducts=Productes de proveïdors BankCode=Codi banc DeskCode=Codi oficina BankAccountNumber=Número compte -BankAccountNumberKey=Dígit Control \ No newline at end of file +BankAccountNumberKey=Dígit Control +## filters +FilterableFields=Camps filtrables +FilteredFields=Campos filtrats +FilteredFieldsValues=Valors de filtres \ No newline at end of file diff --git a/htdocs/langs/ca_ES/languages.lang b/htdocs/langs/ca_ES/languages.lang index af9d3f7d92a..d9716305d73 100644 --- a/htdocs/langs/ca_ES/languages.lang +++ b/htdocs/langs/ca_ES/languages.lang @@ -42,4 +42,5 @@ Language_tr_TR=Turc Language_sl_SI=Eslovè Language_sv_SV=Suec Language_sv_SE=Suec -Language_zh_CN=Xinès \ No newline at end of file +Language_zh_CN=Xinès +Language_zh_TW=Xinès (Tradicional) \ No newline at end of file diff --git a/htdocs/langs/el_GR/mails.lang b/htdocs/langs/el_GR/mails.lang index 3bde9b407b2..c2dd5f02e21 100644 --- a/htdocs/langs/el_GR/mails.lang +++ b/htdocs/langs/el_GR/mails.lang @@ -76,42 +76,7 @@ MailingModuleDescDolibarrUsers=All Dolibarr users with emails MailingModuleDescFundationMembers=Foundation members with emails MailingModuleDescEmailsFromFile=EMails from a text file (email;name;surname;comments) MailingModuleDescContactsCategories=Στοιχεία με emails (ανά κατηγορία) -MailingModuleDescDolibarrContractsLinesExpired=Third parties with expired contract's lines -LineInFile=Line %s in file -RecipientSelectionModules=Defined requests for recipient's selection -MailSelectedRecipients=Selected recipients -MailingArea=EMailings area -LastMailings=Last %s emailings -TargetsStatistics=Targets statistics -NbOfCompaniesContacts=Unique contacts of companies -MailNoChangePossible=Recipients for validated emailing can't be changed -SearchAMailing=Search mailing -SendMailing=Send emailing -SendMail=Send email -SentBy=Sent by -MailingNeedCommand=For securities reason, sending an emailing should be performed from command line. Ask your administrator to launch the following command to send the emailing to all recipients: -MailingNeedCommand2=You can however send them online by adding parameter MAILING_LIMIT_SENDBYWEB with value of max number of emails you want to send by session. For this, go on Home - Setup - Other. -ConfirmSendingEmailing=Are you sure you want to send mailing ? -LimitSendingEmailing=On line sending of emailings are limited for security and timeout reasons to %s recipients by sending session. -TargetsReset=Clear list -ToClearAllRecipientsClickHere=Click here to clear the recipient list for this emailing -ToAddRecipientsChooseHere=Add recipients by choosing from the lists -NbOfEMailingsReceived=Mass emailings received -IdRecord=ID record -DeliveryReceipt=Delivery Receipt -YouCanUseCommaSeparatorForSeveralRecipients=You can use the comma separator to specify several recipients. - -# Module Notifications -Notifications=Notifications -NoNotificationsWillBeSent=No email notifications are planned for this event and company -ANotificationsWillBeSent=1 notification will be sent by email -SomeNotificationsWillBeSent=%s notifications will be sent by email -AddNewNotification=Activate a new email notification request -ListOfActiveNotifications=List all active email notification requests -ListOfNotificationsDone=List all email notifications sent - - // START - Lines generated via autotranslator.php tool (2011-06-26 15:35:22). // Reference language: en_US -> el_GR MailingModuleDescContactsByCompanyCategory=Επαφές των τρίτων (από τρίτη κατηγορία μέρη) diff --git a/htdocs/langs/en_US/exports.lang b/htdocs/langs/en_US/exports.lang index e29fb718c08..4ec1482ffb3 100644 --- a/htdocs/langs/en_US/exports.lang +++ b/htdocs/langs/en_US/exports.lang @@ -115,7 +115,7 @@ CSVFormatDesc=Comma Separated Value file format (.csv).
      This is a text Excel95FormatDesc=Excel file format (.xls)
      This is native Excel 95 format (BIFF5). Excel2007FormatDesc=Excel file format (.xlsx)
      This is native Excel 2007 format (SpreadsheetML). TsvFormatDesc=Tab Separated Value file format (.tsv)
      This is a text file format where fields are separated by a tabulator [tab]. -ExportFieldAutomaticallyAdded=Field %s was automatically added. It will avoid you to have similar lines to be treated as duplicate records (with this field added, all ligne will own its own id and will differ). +ExportFieldAutomaticallyAdded=Field %s was automatically added. It will avoid you to have similar lines to be treated as duplicate records (with this field added, all lines will own their own id and will differ). CsvOptions=Csv Options Separator=Separator Enclosure=Enclosure diff --git a/htdocs/langs/en_US/interventions.lang b/htdocs/langs/en_US/interventions.lang index 1b531deed38..117a3481bae 100644 --- a/htdocs/langs/en_US/interventions.lang +++ b/htdocs/langs/en_US/interventions.lang @@ -24,6 +24,7 @@ ConfirmDeleteInterventionLine=Are you sure you want to delete this intervention NameAndSignatureOfInternalContact=Name and signature of intervening : NameAndSignatureOfExternalContact=Name and signature of customer : DocumentModelStandard=Standard document model for interventions +InterventionCardsAndInterventionLines=Interventions and lines of interventions ClassifyBilled=Classify "Billed" StatusInterInvoiced=Billed RelatedInterventions=Related interventions diff --git a/htdocs/langs/en_US/mails.lang b/htdocs/langs/en_US/mails.lang index f9c0d0f198f..f143a7fa167 100644 --- a/htdocs/langs/en_US/mails.lang +++ b/htdocs/langs/en_US/mails.lang @@ -77,8 +77,9 @@ MailingStatusRead=Read CheckRead=Read Receipt YourMailUnsubcribeOK=The email %s is correctly unsubcribe from mailing list MailtoEMail=Hyper link to email -ActivateCheckRead=Allow to use the Read receipt tracker and the unsubcribe link -ActivateCheckReadKey=Key use to encrypt URL use for Read Receipt and unsubcribe function +ActivateCheckRead=Allow to use the "Read receipt" tracker and the "Unsubcribe" link +ActivateCheckReadKey=Key use to encrypt URL use for "Read Receipt" and "Unsubcribe" feature +EMailSentToNRecipients=EMail sent to %s recipients. # Libelle des modules de liste de destinataires mailing MailingModuleDescContactCompanies=Contacts of all third parties (customer, prospect, supplier, ...) @@ -105,7 +106,7 @@ SearchAMailing=Search mailing SendMailing=Send emailing SendMail=Send email SentBy=Sent by -MailingNeedCommand=For security reason, sending an emailing should be performed from command line. Ask your administrator to launch the following command to send the emailing to all recipients: +MailingNeedCommand=For security reason, sending an emailing is better when performed from command line. Ask your administrator to launch the following command to send the emailing to all recipients: MailingNeedCommand2=You can however send them online by adding parameter MAILING_LIMIT_SENDBYWEB with value of max number of emails you want to send by session. For this, go on Home - Setup - Other. ConfirmSendingEmailing=Are you sure you want to send emailing without command line and from web mode ? LimitSendingEmailing=On line sending of emailings are limited for security and timeout reasons to %s recipients by sending session. diff --git a/htdocs/langs/en_US/projects.lang b/htdocs/langs/en_US/projects.lang index 18844f91868..3851d6b9967 100644 --- a/htdocs/langs/en_US/projects.lang +++ b/htdocs/langs/en_US/projects.lang @@ -93,6 +93,7 @@ CloneFiles=Clone joined files ConfirmCloneProject=Are you sure to clone this project ? ProjectReportDate=Change task date according project start date ErrorShiftTaskDate=Impossible to shift task date according to new project start date +ProjectsAndTasksLines=Projects and tasks ##### Types de contacts ##### TypeContact_project_internal_PROJECTLEADER=Project leader TypeContact_project_external_PROJECTLEADER=Project leader diff --git a/htdocs/langs/en_US/sendings.lang b/htdocs/langs/en_US/sendings.lang index 90fc0c7c3e2..a4299fb94ab 100644 --- a/htdocs/langs/en_US/sendings.lang +++ b/htdocs/langs/en_US/sendings.lang @@ -58,6 +58,7 @@ ActionsOnShipping=Events on shipment LinkToTrackYourPackage=Link to track your package ShipmentCreationIsDoneFromOrder=For the moment, creation of a new shipment is done from the order card. RelatedShippings=Related shippings +ShipmentLine=Shipment line # Sending methods SendingMethodCATCH=Catch by customer diff --git a/htdocs/langs/es_ES/exports.lang b/htdocs/langs/es_ES/exports.lang index 667c2a645e1..5308c2fa9aa 100644 --- a/htdocs/langs/es_ES/exports.lang +++ b/htdocs/langs/es_ES/exports.lang @@ -123,4 +123,8 @@ SuppliersProducts=Productos de proveedores BankCode=Código banco DeskCode=Código oficina BankAccountNumber=Número cuenta -BankAccountNumberKey=Dígito Control \ No newline at end of file +BankAccountNumberKey=Dígito Control +## filters +FilterableFields=Campos filtrables +FilteredFields=Campos filtrados +FilteredFieldsValues=Valores de filtros \ No newline at end of file diff --git a/htdocs/langs/es_ES/languages.lang b/htdocs/langs/es_ES/languages.lang index 41128cb4c3f..bd9b00231ce 100644 --- a/htdocs/langs/es_ES/languages.lang +++ b/htdocs/langs/es_ES/languages.lang @@ -45,4 +45,5 @@ Language_tr_TR=Turco Language_sl_SI=Esloveno Language_sv_SV=Sueco Language_sv_SE=Sueco -Language_zh_CN=Chino \ No newline at end of file +Language_zh_CN=Chino +Language_zh_TW=Chino (Tradicional) \ No newline at end of file diff --git a/htdocs/langs/fr_FR/mails.lang b/htdocs/langs/fr_FR/mails.lang index 78c70886cd0..be08ccad7e8 100644 --- a/htdocs/langs/fr_FR/mails.lang +++ b/htdocs/langs/fr_FR/mails.lang @@ -75,17 +75,18 @@ DateSending=Date envoi SentTo=Envoyés à %s MailingStatusRead=Lu CheckRead=Accusé de lecture -YourMailUnsubcribeOK=L'adresse e-mail %s est bien désincrite de la liste. -MailtoEMail=Ecrire a e-mail (lien) -ActivateCheckRead=Permettre l'utilisation du tracker d'accusé de lecture et du lien de désincription -ActivateCheckReadKey=Clef de sécurité utilisée pour l'encryption des URL utilisées dans les fonctions d'accusé de lecture et de désincription +YourMailUnsubcribeOK=L'adresse e-mail %s est bien désinscrite de la liste. +MailtoEMail=Ecrire un e-mail (lien) +ActivateCheckRead=Permettre l'utilisation du tracker d'accusé de lecture et du lien de désinscription +ActivateCheckReadKey=Clef de sécurité permettant l'encryption des URL utilisées dans les fonctions d'accusé de lecture et de désinscription +EMailSentToNRecipients=EMail envoyé à %s destinataires. # Libelle des modules de liste de destinataires mailing MailingModuleDescContactCompanies=Contacts de tiers (prospects, clients, fournisseurs...) MailingModuleDescDolibarrUsers=Utilisateurs de Dolibarr MailingModuleDescFundationMembers=Adhérents MailingModuleDescEmailsFromFile=EMails issus d'un fichier texte (email;nom;prenom;autre) -MailingModuleDescEmailsFromUser=EMails saisi manuellement (email;nom;prenom;autre) +MailingModuleDescEmailsFromUser=EMails saisis manuellement (email;nom;prenom;autre) MailingModuleDescContactsCategories=Tiers (par catégorie) MailingModuleDescDolibarrContractsLinesExpired=Tiers avec lignes de contrats de services expirées MailingModuleDescContactsByCompanyCategory=Contacts de tiers (par catégorie de tiers) @@ -105,11 +106,11 @@ SendMailing=Envoi emailing SendMail=Envoi mail SentBy=Envoyé par MailingNeedCommand=Pour des raisons de sécurité, il est recommandé de faire les envois d'un mailing de masse depuis une ligne de commande. Demandez à votre administrateur de lancer la commande suivante pour envoyer le mailing à tous les destinataires : -MailingNeedCommand2=Vous pouvez toutefois quand même les envoyer par l'interface écrans en ajoutant le paramètre MAILING_LIMIT_SENDBYWEB avec la valeur du nombre max de mails envoyés par session d'envoi. Pour cela, aller dans Accueil - Configuration - Divers. +MailingNeedCommand2=Vous pouvez toutefois quand même les envoyer par l'interface écran en ajoutant le paramètre MAILING_LIMIT_SENDBYWEB avec la valeur du nombre max de mails envoyés par session d'envoi. Pour cela, aller dans Accueil - Configuration - Divers. ConfirmSendingEmailing=Confirmez-vous l'envoi de l'emailing depuis le mode web ? LimitSendingEmailing=L'envoi d'un emailing depuis les écrans est limité pour raisons de sécurité et de timeout à %s destinataires par session d'envoi. TargetsReset=Vider liste -ToClearAllRecipientsClickHere=Pour vider la liste des destinataires de cet emailing, cliquer le bouton +ToClearAllRecipientsClickHere=Pour vider la liste des destinataires de cet emailing, cliquez sur le bouton ToAddRecipientsChooseHere=Pour ajouter des destinataires, choisir dans les listes ci-dessous NbOfEMailingsReceived=EMailings de masse reçus IdRecord=ID enregistrement diff --git a/htdocs/langs/fr_FR/projects.lang b/htdocs/langs/fr_FR/projects.lang index 01c6b337e98..d73ade4b1a1 100644 --- a/htdocs/langs/fr_FR/projects.lang +++ b/htdocs/langs/fr_FR/projects.lang @@ -93,6 +93,7 @@ CloneFiles=Cloner les pièces jointes ConfirmCloneProject=Êtes-vous sûr de vouloir cloner ce projet ? ProjectReportDate=Reporter les dates des taches en fonction de la date de départ. ErrorShiftTaskDate=Une erreur c'est produite dans le report des dates des taches. +ProjectsAndTasksLines=Projets et taches ##### Types de contacts ##### TypeContact_project_internal_PROJECTLEADER=Chef de projet TypeContact_project_external_PROJECTLEADER=Chef de projet diff --git a/htdocs/langs/fr_FR/sendings.lang b/htdocs/langs/fr_FR/sendings.lang index 1b316ab6c70..a80b61649da 100644 --- a/htdocs/langs/fr_FR/sendings.lang +++ b/htdocs/langs/fr_FR/sendings.lang @@ -58,6 +58,7 @@ ActionsOnShipping=Événements sur l'expédition LinkToTrackYourPackage=Lien pour suivi de votre colis ShipmentCreationIsDoneFromOrder=Pour le moment, la création d'une nouvelle expédition se fait depuis la fiche commande. RelatedShippings=Expédition(s) associée(s) +ShipmentLine=Ligne d'expédition # Sending methods SendingMethodCATCH=Enlèvement par le client diff --git a/htdocs/langs/fr_FR/withdrawals.lang b/htdocs/langs/fr_FR/withdrawals.lang index b66f2bca344..39f62c0f5c1 100644 --- a/htdocs/langs/fr_FR/withdrawals.lang +++ b/htdocs/langs/fr_FR/withdrawals.lang @@ -40,8 +40,8 @@ TransData=Date Transmission TransMetod=Méthode Transmission Send=Envoyer Lines=Lignes -StandingOrderReject=Emmètre un rejet -InvoiceRefused=Facture rejeté +StandingOrderReject=Émettre un rejet +InvoiceRefused=Facture rejetée WithdrawalRefused=Rejet de prélèvement WithdrawalRefusedConfirm=Êtes-vous sûr de vouloir saisir un rejet de prélèvement pour la société RefusedData=Date du rejet @@ -70,25 +70,25 @@ CreateBanque=Seulement banque OrderWaiting=En attente de traitement NotifyTransmision=Transmission du bon NotifyEmision=Emission du bon -NotifyCredit=Credit du bon +NotifyCredit=Crédit du bon NumeroNationalEmetter= Numéro National Émetteur PleaseSelectCustomerBankBANToWithdraw=Saisissez les informations du compte bancaire client à prélever WithBankUsingRIB=Pour les comptes bancaires utilisant le RIB WithBankUsingBANBIC=Pour les comptes bancaires utilisant le code BAN/BIC/SWIFT BankToReceiveWithdraw=Compte bancaire recevant les prélèvements CreditDate=Crédité le -WithdrawalFileNotCapable=Impossible de generer fichier de bon de prelevements pour votre pays +WithdrawalFileNotCapable=Impossible de générer un fichier de bons de prélèvements pour votre pays ShowWithdraw=Voir prélèvement IfInvoiceNeedOnWithdrawPaymentWontBeClosed=Toutefois, si la facture a au moins un paiement par prélèvement non traité, elle ne le sera pas afin de permettre la gestion du prélèvement d'abord. -DoStandingOrdersBeforePayments=Cet onglet permet de faire une demande de pélèvement bancaire. Une fois réalisé, vous pourrez saisir le paiement sur la facture pour la clore. +DoStandingOrdersBeforePayments=Cet onglet permet de faire une demande de prélèvement bancaire. Une fois réalisé, vous pourrez saisir le paiement sur la facture pour la clore. ### Notifications -InfoCreditSubject=Credit prélèvement %s a la banque -InfoCreditMessage=Le bon de prélèvement %s a eté credité par la banque.
      Date credit : %s -InfoTransSubject=Transmission du prélèvement %s a la banque -InfoTransMessage=Le bon de prélèvement %s a eté transmis a la banque par %s %s.

      -InfoTransData=Montant: %s
      Methode: %s
      Date: %s -InfoFoot=Ceci est un message automatique envoye par Dolibarr +InfoCreditSubject=Crédit prélèvement %s à la banque +InfoCreditMessage=Le bon de prélèvement %s a été crédité par la banque.
      Date crédit : %s +InfoTransSubject=Transmission du prélèvement %s à la banque +InfoTransMessage=Le bon de prélèvement %s a été transmis à la banque par %s %s.

      +InfoTransData=Montant: %s
      Méthode: %s
      Date: %s +InfoFoot=Ceci est un message automatique envoyé par Dolibarr InfoRejectSubject=Prélèvement rejeté -InfoRejectMessage=Bonjour,

      Le prelevement de la facture %s pour le compte de la societé %s, d'un montant de %s a été rejeté par la banque.

      --
      %$ +InfoRejectMessage=Bonjour,

      Le prélèvement de la facture %s pour le compte de la société %s, d'un montant de %s a été rejeté par la banque.

      --
      %$ ModeWarning=Option mode réel non établi, nous allons arrêter après cette simulation \ No newline at end of file diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index bcd0ed5c77a..a0b28c7ff25 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -946,13 +946,10 @@ function top_htmlhead($head, $title='', $disablejs=0, $disablehead=0, $arrayofjs if (! $disablejs && ! empty($conf->use_javascript_ajax)) { $ext='.js'; - if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x01)) { - $ext='.jgz'; - } // mini='_mini', ext='.gz' // JQuery. Must be before other includes print ''."\n"; - if (constant('JS_JQUERY')) print ''."\n"; + if (constant('JS_JQUERY')) print ''."\n"; else print ''."\n"; if (constant('JS_JQUERY_UI')) print ''."\n"; else print ''."\n"; diff --git a/htdocs/master.inc.php b/htdocs/master.inc.php index 9d6897c6de1..732215c9149 100644 --- a/htdocs/master.inc.php +++ b/htdocs/master.inc.php @@ -212,6 +212,6 @@ if (! defined('NOREQUIRETRAN')) if (! defined('MAIN_LABEL_MENTION_NPR') ) define('MAIN_LABEL_MENTION_NPR','NPR'); // We force feature to help debug -$conf->global->MAIN_JS_ON_PAYMENT=0; // We set to zero to unifrmize way of working between customer and supplier payments +//$conf->global->MAIN_JS_ON_PAYMENT=0; ?> diff --git a/htdocs/paypal/lib/paypal.lib.php b/htdocs/paypal/lib/paypal.lib.php index 72843f6aeeb..0ad9962ac1e 100755 --- a/htdocs/paypal/lib/paypal.lib.php +++ b/htdocs/paypal/lib/paypal.lib.php @@ -36,6 +36,9 @@ function llxHeaderPaypal($title, $head = "") header("Content-type: text/html; charset=".$conf->file->character_set_client); + $appli='Dolibarr'; + if (!empty($conf->global->MAIN_APPLICATION_TITLE)) $appli=$conf->global->MAIN_APPLICATION_TITLE; + print ''; //print ''; print "\n"; @@ -43,7 +46,7 @@ function llxHeaderPaypal($title, $head = "") print "\n"; print ''."\n"; print ''."\n"; - print ''."\n"; + print ''."\n"; print "".$title."\n"; if ($head) print $head."\n"; if (! empty($conf->global->PAYPAL_CSS_URL)) print ''."\n"; @@ -63,9 +66,6 @@ function llxHeaderPaypal($title, $head = "") // Output standard javascript links $ext='.js'; - if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x01)) { - $ext='.jgz'; - } // mini='_mini', ext='.gz' // JQuery. Must be before other includes print ''."\n"; @@ -73,8 +73,8 @@ function llxHeaderPaypal($title, $head = "") // jQuery jnotify if (empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY)) { - print ''."\n"; - print ''."\n"; + print ''."\n"; + print ''."\n"; } } print "\n"; diff --git a/htdocs/product/stock/class/entrepot.class.php b/htdocs/product/stock/class/entrepot.class.php index 35fb8faec4e..09da96dabb2 100644 --- a/htdocs/product/stock/class/entrepot.class.php +++ b/htdocs/product/stock/class/entrepot.class.php @@ -265,7 +265,7 @@ class Entrepot extends CommonObject $result = $this->db->query($sql); if ($result) { - if ($this->db->num_rows($result) > 0) + if ($this->db->num_rows($result) > 0) { $obj=$this->db->fetch_object($result); diff --git a/htdocs/societe/soc.php b/htdocs/societe/soc.php index 48ed418a62d..a916a7377c3 100644 --- a/htdocs/societe/soc.php +++ b/htdocs/societe/soc.php @@ -512,6 +512,9 @@ else /* * Creation */ + $private=GETPOST("private","int"); + if (! empty($conf->global->MAIN_THIRPARTY_CREATION_INDIVIDUAL) && ! isset($_GET['private']) && ! isset($_POST['private'])) $private=1; + if (empty($private)) $private=0; // Load object modCodeTiers $module=(! empty($conf->global->SOCIETE_CODECLIENT_ADDON)?$conf->global->SOCIETE_CODECLIENT_ADDON:'mod_codeclient_leopard'); @@ -545,11 +548,10 @@ else if (GETPOST("type")=='c') { $object->client=1; } if (GETPOST("type")=='p') { $object->client=2; } if (! empty($conf->fournisseur->enabled) && (GETPOST("type")=='f' || GETPOST("type")=='')) { $object->fournisseur=1; } - if (GETPOST("private")==1) { $object->particulier=1; } $object->name = GETPOST('nom'); $object->firstname = GETPOST('prenom'); - $object->particulier = GETPOST('private', 'int'); + $object->particulier = $private; $object->prefix_comm = GETPOST('prefix_comm'); $object->client = GETPOST('client')?GETPOST('client'):$object->client; $object->code_client = GETPOST('code_client'); @@ -638,7 +640,7 @@ else print '$(document).ready(function () { id_te_private=8; id_ef15=1; - is_private='.(GETPOST("private")?GETPOST("private"):0).'; + is_private='.$private.'; if (is_private) { $(".individualline").show(); } else { @@ -667,10 +669,10 @@ else print "
      \n"; print $langs->trans("ThirdPartyType").':   '; - print ' '.$langs->trans("Company/Fundation"); print '     '; - print ' '.$langs->trans("Individual"); print ' ('.$langs->trans("ToCreateContactWithSameName").')'; print "
      \n"; @@ -693,7 +695,7 @@ else print ''; // Name, firstname - if ($object->particulier || GETPOST("private")) + if ($object->particulier || $private) { print 'global->SOCIETE_USEPREFIX)?' colspan="3"':'').'>'; if (! empty($conf->global->SOCIETE_USEPREFIX)) // Old not used prefix field diff --git a/htdocs/theme/auguria/style.css.php b/htdocs/theme/auguria/style.css.php index b981dc6d69b..13c466f7bfc 100644 --- a/htdocs/theme/auguria/style.css.php +++ b/htdocs/theme/auguria/style.css.php @@ -1441,7 +1441,7 @@ font-family: ; .ok { color: #114466; } .warning { color: #887711; } -.error { color: #550000; font-weight: bold; } +.error { color: #550000 !important; font-weight: bold; } td.highlights { background: #f9c5c6; } diff --git a/htdocs/theme/bureau2crea/style.css.php b/htdocs/theme/bureau2crea/style.css.php index e1c8f5a6abf..6bbaddc7f45 100644 --- a/htdocs/theme/bureau2crea/style.css.php +++ b/htdocs/theme/bureau2crea/style.css.php @@ -1595,7 +1595,7 @@ font-family: ; .ok { color: #114466; } .warning { color: #887711; } -.error { color: #550000; font-weight: bold; } +.error { color: #550000 !important; font-weight: bold; } td.highlights { background: #f9c5c6; } diff --git a/htdocs/theme/bureau2crea/tpl/login.tpl.php b/htdocs/theme/bureau2crea/tpl/login.tpl.php index 27a4ea87adf..0d4b34fa555 100644 --- a/htdocs/theme/bureau2crea/tpl/login.tpl.php +++ b/htdocs/theme/bureau2crea/tpl/login.tpl.php @@ -52,9 +52,8 @@ if (isset($conf->modules_parts['css'])) } // JQuery. Must be before other includes $ext='.js'; -if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x01)) $ext='.jgz'; print ''."\n"; -if (constant('JS_JQUERY')) print ''."\n"; +if (constant('JS_JQUERY')) print ''."\n"; else print ''."\n"; print ''."\n"; print ' diff --git a/htdocs/theme/bureau2crea/tpl/passwordforgotten.tpl.php b/htdocs/theme/bureau2crea/tpl/passwordforgotten.tpl.php index becc5fe16f3..6bfc38917e8 100644 --- a/htdocs/theme/bureau2crea/tpl/passwordforgotten.tpl.php +++ b/htdocs/theme/bureau2crea/tpl/passwordforgotten.tpl.php @@ -53,9 +53,8 @@ if (isset($conf->modules_parts['css'])) } // JQuery. Must be before other includes $ext='.js'; -if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x01)) $ext='.jgz'; print ''."\n"; -if (constant('JS_JQUERY')) print ''."\n"; +if (constant('JS_JQUERY')) print ''."\n"; else print ''."\n"; print ''."\n"; if (! empty($conf->global->MAIN_HTML_HEADER)) print $conf->global->MAIN_HTML_HEADER; diff --git a/htdocs/theme/cameleo/style.css.php b/htdocs/theme/cameleo/style.css.php index 085e5227251..70a5bd23ddb 100644 --- a/htdocs/theme/cameleo/style.css.php +++ b/htdocs/theme/cameleo/style.css.php @@ -1511,7 +1511,7 @@ font-family: ; */ .ok { color: #114466; } .warning { color: #887711; } -.error { color: #550000; font-weight: bold; } +.error { color: #550000 !important; font-weight: bold; } div.ok { color: #114466; diff --git a/htdocs/theme/eldy/style.css.php b/htdocs/theme/eldy/style.css.php index 5f0e563a1cf..3e77e3df23b 100644 --- a/htdocs/theme/eldy/style.css.php +++ b/htdocs/theme/eldy/style.css.php @@ -1762,9 +1762,7 @@ tr.fiche { */ .ok { color: #114466; } .warning { color: #887711; } -.error { color: #550000; font-weight: bold; } - -td.highlights { background: #f9c5c6; } +.error { color: #550000 !important; font-weight: bold; } div.ok { color: #114466; diff --git a/htdocs/webservices/demo_wsclient_category.php-NORUN b/htdocs/webservices/demo_wsclient_category.php-NORUN new file mode 100755 index 00000000000..a37cf33660a --- /dev/null +++ b/htdocs/webservices/demo_wsclient_category.php-NORUN @@ -0,0 +1,98 @@ + + * Copyright (C) 2012 JF FERRY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/** + * \file htdocs/webservices/demo_wsclient_category.php + * \brief Demo page to make a category call to Dolibarr WebServices "server_category" + */ + +// This is to make Dolibarr working with Plesk +set_include_path($_SERVER['DOCUMENT_ROOT'].'/htdocs'); + +require_once("../master.inc.php"); +require_once(NUSOAP_PATH.'/nusoap.php'); // Include SOAP + +$WS_DOL_URL = $dolibarr_main_url_root.'/webservices/server_category.php'; +$WS_METHOD = 'getCategory'; + +// Set the WebService URL +dol_syslog("Create nusoap_client for URL=".$WS_DOL_URL); +$soapclient = new nusoap_client($WS_DOL_URL); +if ($soapclient) +{ + $soapclient->soap_defencoding='UTF-8'; +} + +$soapclient2 = new nusoap_client($WS_DOL_URL); +if ($soapclient2) +{ + $soapclient2->soap_defencoding='UTF-8'; +} +// Call the WebService method and store its result in $result. +$authentication=array( + 'dolibarrkey'=>$conf->global->WEBSERVICES_KEY, + 'sourceapplication'=>'DEMO', + 'login'=>'admin', + 'password'=>'changeme', + 'entity'=>''); + +$parameters = array('authentication'=>$authentication,'id'=>1); +dol_syslog("Call method ".$WS_METHOD); +$result = $soapclient->call($WS_METHOD,$parameters); +if (! $result) +{ + var_dump($soapclient); + print '

      Erreur SOAP 1

      '.$soapclient->error_str; + exit; +} + + +/* + * View + */ + +header("Content-type: text/html; charset=utf8"); +print ''."\n"; +echo ''."\n"; +echo ''; +echo 'WebService Test: '.$WS_METHOD.''; +echo ''."\n"; + +echo ''."\n"; + +echo "

      Request 1:

      "; +echo '

      Function

      '; +echo $WS_METHOD; +echo '

      SOAP Message

      '; +echo '
      ' . htmlspecialchars($soapclient->request, ENT_QUOTES) . '
      '; + +echo '
      '; + +echo "

      Response:

      "; +echo '

      Result

      '; +echo '
      ';
      +print_r($result);
      +echo '
      '; +echo '

      SOAP Message

      '; +echo '
      ' . htmlspecialchars($soapclient->response, ENT_QUOTES) . '
      '; + + +echo ''."\n";; +echo ''."\n";; +?> diff --git a/htdocs/webservices/demo_wsclient_order.php-NORUN b/htdocs/webservices/demo_wsclient_order.php-NORUN new file mode 100755 index 00000000000..d5b51c12a6b --- /dev/null +++ b/htdocs/webservices/demo_wsclient_order.php-NORUN @@ -0,0 +1,141 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/webservices/demo_wsclient_order.php + * \brief Demo page to make a client call to Dolibarr WebServices "server_order" + */ + +// This is to make Dolibarr working with Plesk +set_include_path($_SERVER['DOCUMENT_ROOT'].'/htdocs'); + +require_once '../master.inc.php'; +require_once NUSOAP_PATH.'/nusoap.php'; // Include SOAP + +$WS_DOL_URL = DOL_MAIN_URL_ROOT.'/webservices/server_order.php'; +//$WS_DOL_URL = 'http://localhost:8080/'; // To test with Soapui mock. If not a page, should end with / +$WS_METHOD1 = 'getOrder'; +$WS_METHOD2 = 'getOrdersForThirdParty'; +$ns='http://www.dolibarr.org/ns/'; + + +// Set the WebService URL +dol_syslog("Create nusoap_client for URL=".$WS_DOL_URL); +$soapclient1 = new nusoap_client($WS_DOL_URL); +if ($soapclient1) +{ + $soapclient1->soap_defencoding='UTF-8'; + $soapclient1->decodeUTF8(false); +} +$soapclient2 = new nusoap_client($WS_DOL_URL); +if ($soapclient2) +{ + $soapclient2->soap_defencoding='UTF-8'; + $soapclient2->decodeUTF8(false); +} + +// Call the WebService method and store its result in $result. +$authentication=array( + 'dolibarrkey'=>$conf->global->WEBSERVICES_KEY, + 'sourceapplication'=>'DEMO', + 'login'=>'admin', + 'password'=>'changeme', + 'entity'=>''); + + +// Test url 1 +if ($WS_METHOD1) +{ + $parameters = array('authentication'=>$authentication,'id'=>1,'ref'=>''); + dol_syslog("Call method ".$WS_METHOD1); + $result1 = $soapclient1->call($WS_METHOD1,$parameters,$ns,''); + if (! $result1) + { + print $soapclient1->error_str; + print "
      \n\n"; + print $soapclient1->request; + print "
      \n\n"; + print $soapclient1->response; + exit; + } +} + +// Test url 2 +if ($WS_METHOD2) +{ + $parameters = array('authentication'=>$authentication,'idthirdparty'=>'4'); + dol_syslog("Call method ".$WS_METHOD2); + $result2 = $soapclient2->call($WS_METHOD2,$parameters,$ns,''); + if (! $result2) + { + print $soapclient2->error_str; + print "
      \n\n"; + print $soapclient2->request; + print "
      \n\n"; + print $soapclient2->response; + exit; + } +} + + +/* + * View + */ + +header("Content-type: text/html; charset=utf8"); +print ''."\n"; +echo ''."\n"; +echo ''; +echo 'WebService Test: '.$WS_METHOD1.''; +echo ''."\n"; + +echo ''."\n"; +echo 'NUSOAP_PATH='.NUSOAP_PATH.'
      '; + +echo "

      Request:

      "; +echo '

      Function

      '; +echo $WS_METHOD1; +echo '

      SOAP Message

      '; +echo '
      ' . htmlspecialchars($soapclient1->request, ENT_QUOTES) . '
      '; +echo '
      '; +echo "

      Response:

      "; +echo '

      Result

      '; +echo '
      ';
      +print_r($result1);
      +echo '
      '; +echo '

      SOAP Message

      '; +echo '
      ' . htmlspecialchars($soapclient1->response, ENT_QUOTES) . '
      '; + +print '
      '; + +echo "

      Request:

      "; +echo '

      Function

      '; +echo $WS_METHOD2; +echo '

      SOAP Message

      '; +echo '
      ' . htmlspecialchars($soapclient2->request, ENT_QUOTES) . '
      '; +echo '
      '; +echo "

      Response:

      "; +echo '

      Result

      '; +echo '
      ';
      +print_r($result2);
      +echo '
      '; +echo '

      SOAP Message

      '; +echo '
      ' . htmlspecialchars($soapclient2->response, ENT_QUOTES) . '
      '; + +echo ''."\n";; +echo ''."\n";; +?> diff --git a/htdocs/webservices/server_category.php b/htdocs/webservices/server_category.php new file mode 100755 index 00000000000..b08aa90d763 --- /dev/null +++ b/htdocs/webservices/server_category.php @@ -0,0 +1,307 @@ + + * Copyright (C) 2012 JF FERRY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/** + * \file htdocs/webservices/server_category.php + * \brief File that is entry point to call Dolibarr WebServices + */ + +// This is to make Dolibarr working with Plesk +set_include_path($_SERVER['DOCUMENT_ROOT'].'/htdocs'); + +require_once("../master.inc.php"); +require_once(NUSOAP_PATH.'/nusoap.php'); // Include SOAP +require_once DOL_DOCUMENT_ROOT.'/core/lib/ws.lib.php'; +require_once(DOL_DOCUMENT_ROOT."/categories/class/categorie.class.php"); + + +dol_syslog("Call Dolibarr webservices interfaces"); + +// Enable and test if module web services is enabled +if (empty($conf->global->MAIN_MODULE_WEBSERVICES)) +{ + $langs->load("admin"); + dol_syslog("Call Dolibarr webservices interfaces with module webservices disabled"); + print $langs->trans("WarningModuleNotActive",'WebServices').'.

      '; + print $langs->trans("ToActivateModule"); + exit; +} + +// Create the soap Object +$server = new nusoap_server(); +$server->soap_defencoding='UTF-8'; +$server->decode_utf8=false; +$ns='http://www.dolibarr.org/ns/'; +$server->configureWSDL('WebServicesDolibarrCategorie',$ns); +$server->wsdl->schemaTargetNamespace=$ns; + + +// Define WSDL content +$server->wsdl->addComplexType( + 'authentication', + 'complexType', + 'struct', + 'all', + '', + array( + 'dolibarrkey' => array('name'=>'dolibarrkey','type'=>'xsd:string'), + 'sourceapplication' => array('name'=>'sourceapplication','type'=>'xsd:string'), + 'login' => array('name'=>'login','type'=>'xsd:string'), + 'password' => array('name'=>'password','type'=>'xsd:string'), + 'entity' => array('name'=>'entity','type'=>'xsd:string'), + ) +); + +/* + * Une catégorie + */ +$server->wsdl->addComplexType( + 'categorie', + 'complexType', + 'struct', + 'all', + '', + array( + 'id' => array('name'=>'id','type'=>'xsd:string'), + 'id_mere' => array('name'=>'id_mere','type'=>'xsd:string'), + 'label' => array('name'=>'label','type'=>'xsd:string'), + 'description' => array('name'=>'description','type'=>'xsd:string'), + 'socid' => array('name'=>'socid','type'=>'xsd:string'), + 'type' => array('name'=>'type','type'=>'xsd:string'), + 'visible' => array('name'=>'visible','type'=>'xsd:string'), + 'dir'=> array('name'=>'dir','type'=>'xsd:string'), + 'photos' => array('name'=>'photos','type'=>'tns:PhotosArray'), + 'filles' => array('name'=>'filles','type'=>'tns:FillesArray') + ) +); + +/* + * Les catégories filles, sous tableau dez la catégorie + */ + $server->wsdl->addComplexType( + 'FillesArray', + 'complexType', + 'array', + '', + 'SOAP-ENC:Array', + array(), + array( + array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:categorie[]') + ), + 'tns:categorie' +); + +/* + * Tableau des catégories + +$server->wsdl->addComplexType( + 'categories', + 'complexType', + 'array', + '', + 'SOAP-ENC:Array', + array(), + array( + array('id'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:categorie[]') + ), + 'tns:categories' +); + */ + +/* + * Les photos de la catégorie (un tableau indéxé qui contient les images avec leur vignette) + */ + $server->wsdl->addComplexType( + 'PhotosArray', + 'complexType', + 'array', + '', + 'SOAP-ENC:Array', + array(), + array( + array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:image[]') + ), +'' +); + +/* + * Une photo ( nom image / nom_vignette ) + */ +$server->wsdl->addComplexType( + 'image', + 'complexType', + 'array', + '', + 'SOAP-ENC:Array', + array(), + array( + 'photo' => array('name'=>'photo','type'=>'xsd:string'), + 'photo_vignette' => array('name'=>'photo_vignette','type'=>'xsd:string'), + 'imgWidth' => array('name'=>'imgWidth','type'=>'xsd:string'), + 'imgHeight' => array('name'=>'imgHeight','type'=>'xsd:string') + ) +); + +/* + * Retour + */ +$server->wsdl->addComplexType( + 'result', + 'complexType', + 'struct', + 'all', + '', + array( + 'result_code' => array('name'=>'result_code','type'=>'xsd:string'), + 'result_label' => array('name'=>'result_label','type'=>'xsd:string'), + ) +); + +// 5 styles: RPC/encoded, RPC/literal, Document/encoded (not WS-I compliant), Document/literal, Document/literal wrapped +// Style merely dictates how to translate a WSDL binding to a SOAP message. Nothing more. You can use either style with any programming model. +// http://www.ibm.com/developerworks/webservices/library/ws-whichwsdl/ +$styledoc='rpc'; // rpc/document (document is an extend into SOAP 1.0 to support unstructured messages) +$styleuse='encoded'; // encoded/literal/literal wrapped +// Better choice is document/literal wrapped but literal wrapped not supported by nusoap. + + +// Register WSDL +$server->register( + 'getCategory', + // Entry values + array('authentication'=>'tns:authentication','id'=>'xsd:string'), + // Exit values + array('result'=>'tns:result','categorie'=>'tns:categorie'), + $ns, + $ns.'#getCategory', + $styledoc, + $styleuse, + 'WS to get category' +); + + +/** + * Get category infos and children + * + * @param array $authentication Array of authentication information + * @param int $id Id of object + * @return mixed + */ +function getCategory($authentication,$id) +{ + global $db,$conf,$langs; + + dol_syslog("Function: getCategory login=".$authentication['login']." id=".$id); + + if ($authentication['entity']) $conf->entity=$authentication['entity']; + + $objectresp=array(); + $errorcode='';$errorlabel=''; + $error=0; + $fuser=check_authentication($authentication,$error,$errorcode,$errorlabel); + + if (! $error && !$id) + { + $error++; + $errorcode='BAD_PARAMETERS'; $errorlabel="Parameter id must be provided."; + } + + if (! $error) + { + $fuser->getrights(); + + if ($fuser->rights->categorie->lire) + { + $categorie=new Categorie($db); + $result=$categorie->fetch($id); + if ($result > 0) + { + $dir = (!empty($conf->categorie->dir_output)?$conf->categorie->dir_output:$conf->service->dir_output); + $pdir = get_exdir($categorie->id,2) . $categorie->id ."/photos/"; + $dir = $dir . '/'. $pdir; + + $cat = array( + 'id' => $categorie->id, + 'id_mere' => $categorie->id_mere, + 'label' => $categorie->label, + 'description' => $categorie->description, + 'socid' => $categorie->socid, + //'visible'=>$categorie->visible, + 'type' => $categorie->type, + 'dir' => $pdir, + 'photos' => $categorie->liste_photos($dir,$nbmax=10) + ); + + $cats = $categorie->get_filles(); + if (sizeof ($cats) > 0) + { + + foreach($cats as $fille) + { + $dir = (!empty($conf->categorie->dir_output)?$conf->categorie->dir_output:$conf->service->dir_output); + $pdir = get_exdir($fille->id,2) . $fille->id ."/photos/"; + $dir = $dir . '/'. $pdir; + $cat['filles'][] = array( + 'id'=>$fille->id, + 'id_mere' => $categorie->id_mere, + 'label'=>$fille->label, + 'description'=>$fille->description, + 'socid'=>$fille->socid, + //'visible'=>$fille->visible, + 'type'=>$fille->type, + 'dir' => $pdir, + 'photos' => $fille->liste_photos($dir,$nbmax=10) + ); + + } + + } + + // Create + $objectresp = array( + 'result'=>array('result_code'=>'OK', 'result_label'=>''), + 'categorie'=> $cat + ); + } + else + { + $error++; + $errorcode='NOT_FOUND'; $errorlabel='Object not found for id='.$id; + } + } + else + { + $error++; + $errorcode='PERMISSION_DENIED'; $errorlabel='User does not have permission for this request'; + } + } + + if ($error) + { + $objectresp = array('result'=>array('result_code' => $errorcode, 'result_label' => $errorlabel)); + } + + return $objectresp; +} + + +// Return the results. +$server->service($HTTP_RAW_POST_DATA); + +?> diff --git a/htdocs/webservices/server_invoice.php b/htdocs/webservices/server_invoice.php index d34749b745d..9430d230ca5 100755 --- a/htdocs/webservices/server_invoice.php +++ b/htdocs/webservices/server_invoice.php @@ -294,7 +294,7 @@ function getInvoice($authentication,$id='',$ref='',$ref_ext='') $linesresp[]=array( 'id'=>$line->rowid, 'type'=>$line->product_type, - 'desc'=>dol_htmlcleanlastbr($line->description), + 'desc'=>dol_htmlcleanlastbr($line->desc), 'total_net'=>$line->total_ht, 'total_vat'=>$line->total_tva, 'total'=>$line->total_ttc, diff --git a/htdocs/webservices/server_order.php b/htdocs/webservices/server_order.php new file mode 100644 index 00000000000..951147c9bee --- /dev/null +++ b/htdocs/webservices/server_order.php @@ -0,0 +1,778 @@ + + * Copyright (C) 2012 JF FERRY +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +/** + * \file htdocs/webservices/server_order.php + * \brief File that is entry point to call Dolibarr WebServices + */ + + +// This is to make Dolibarr working with Plesk +set_include_path($_SERVER['DOCUMENT_ROOT'].'/htdocs'); + +require_once '../master.inc.php'; +require_once NUSOAP_PATH.'/nusoap.php'; // Include SOAP +require_once DOL_DOCUMENT_ROOT.'/core/lib/ws.lib.php'; + +require_once(DOL_DOCUMENT_ROOT."/commande/class/commande.class.php"); + + +dol_syslog("Call Dolibarr webservices interfaces"); + +// Enable and test if module web services is enabled +if (empty($conf->global->MAIN_MODULE_WEBSERVICES)) +{ + $langs->load("admin"); + dol_syslog("Call Dolibarr webservices interfaces with module webservices disabled"); + print $langs->trans("WarningModuleNotActive",'WebServices').'.

      '; + print $langs->trans("ToActivateModule"); + exit; +} + +// Create the soap Object +$server = new nusoap_server(); +$server->soap_defencoding='UTF-8'; +$server->decode_utf8=false; +$ns='http://www.dolibarr.org/ns/'; +$server->configureWSDL('WebServicesDolibarrOrder',$ns); +$server->wsdl->schemaTargetNamespace=$ns; + + +// Define WSDL content +$server->wsdl->addComplexType( + 'authentication', + 'complexType', + 'struct', + 'all', + '', + array( + 'dolibarrkey' => array('name'=>'dolibarrkey','type'=>'xsd:string'), + 'sourceapplication' => array('name'=>'sourceapplication','type'=>'xsd:string'), + 'login' => array('name'=>'login','type'=>'xsd:string'), + 'password' => array('name'=>'password','type'=>'xsd:string'), + 'entity' => array('name'=>'entity','type'=>'xsd:string'), + ) +); + +$server->wsdl->addComplexType( + 'line', + 'complexType', + 'struct', + 'all', + '', + array( + 'id' => array('name'=>'id','type'=>'xsd:string'), + 'fk_commande' => array('name'=>'fk_commande','type'=>'xsd:int'), + 'fk_parent_line' => array('name'=>'fk_parent_line','type'=>'xsd:int'), + 'desc' => array('name'=>'desc','type'=>'xsd:string'), + 'qty' => array('name'=>'qty','type'=>'xsd:int'), + 'price' => array('name'=>'price','type'=>'xsd:double'), + 'subprice' => array('name'=>'subprice','type'=>'xsd:double'), + 'tva_tx' => array('name'=>'tva_tx','type'=>'xsd:double'), + + 'remise' => array('name'=>'remise','type'=>'xsd:double'), + 'remise_percent' => array('name'=>'remise_percent','type'=>'xsd:double'), + + 'fk_product' => array('name'=>'fk_product','type'=>'xsd:int'), + 'product_type' => array('name'=>'product_type','type'=>'xsd:int'), + 'total_ht' => array('name'=>'total_ht','type'=>'xsd:double'), + 'total_tva' => array('name'=>'totaltva','type'=>'xsd:double'), + 'total_ttc' => array('name'=>'total_ttc','type'=>'xsd:double'), + + 'date_start' => array('name'=>'date_start','type'=>'xsd:string'), + 'date_end' => array('name'=>'date_end','type'=>'xsd:string'), + + // From product + 'product_ref' => array('name'=>'product_ref','type'=>'xsd:string'), + 'product_label' => array('name'=>'product_label','type'=>'xsd:string'), + 'product_desc' => array('name'=>'product_desc','type'=>'xsd:string') + ) +); + +$server->wsdl->addComplexType( + 'LinesArray', + 'complexType', + 'array', + '', + 'SOAP-ENC:Array', + array(), + array( + array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:line[]') + ), + 'tns:line' +); +$server->wsdl->addComplexType( + 'LinesArray2', + 'complexType', + 'array', + 'sequence', + '', + array( + 'line' => array( + 'name' => 'line', + 'type' => 'tns:line', + 'minOccurs' => '0', + 'maxOccurs' => 'unbounded' + ) + ) +); + + +$server->wsdl->addComplexType( + 'order', + 'complexType', + 'struct', + 'all', + '', + array( + 'id' => array('name'=>'id','type'=>'xsd:string'), + 'ref' => array('name'=>'ref','type'=>'xsd:string'), + 'ref_client' => array('name'=>'ref_client','type'=>'xsd:string'), + 'ref_ext' => array('name'=>'ref_ext','type'=>'xsd:string'), + 'ref_int' => array('name'=>'ref_int','type'=>'xsd:string'), + 'socid' => array('name'=>'socid','type'=>'xsd:int'), + 'statut' => array('name'=>'statut','type'=>'xsd:int'), + 'facturee' => array('name'=>'facturee','type'=>'xsd:string'), + 'total_ht' => array('name'=>'total_ht','type'=>'xsd:double'), + 'total_tva' => array('name'=>'total_tva','type'=>'xsd:double'), + 'total_localtax1' => array('name'=>'total_localtax1','type'=>'xsd:double'), + 'total_localtax2' => array('name'=>'total_localtax2','type'=>'xsd:double'), + 'total_ttc' => array('name'=>'total_ttc','type'=>'xsd:double'), + 'date' => array('name'=>'date','type'=>'xsd:date'), + 'date_commande' => array('name'=>'date_commande','type'=>'xsd:date'), + 'remise' => array('name'=>'remise','type'=>'xsd:string'), + 'remise_percent' => array('name'=>'remise_percent','type'=>'xsd:string'), + 'remise_absolue' => array('name'=>'remise_absolue','type'=>'xsd:string'), + 'source' => array('name'=>'source','type'=>'xsd:string'), + 'note' => array('name'=>'note','type'=>'xsd:string'), + 'note_public' => array('name'=>'note_public','type'=>'xsd:string'), + 'fk_project' => array('name'=>'fk_project','type'=>'xsd:string'), + + 'mode_reglement_id' => array('name'=>'mode_reglement_id','type'=>'xsd:string'), + 'mode_reglement_code' => array('name'=>'mode_reglement_code','type'=>'xsd:string'), + 'mode_reglement' => array('name'=>'mode_reglement','type'=>'xsd:string'), + 'cond_reglement_id' => array('name'=>'cond_reglement_id','type'=>'xsd:string'), + 'cond_reglement_code' => array('name'=>'cond_reglement_code','type'=>'xsd:string'), + 'cond_reglement' => array('name'=>'cond_reglement','type'=>'xsd:string'), + 'cond_reglement_doc' => array('name'=>'cond_reglement_doc','type'=>'xsd:string'), + + 'date_livraison' => array('name'=>'date_livraison','type'=>'xsd:date'), + 'fk_delivery_address' => array('name'=>'fk_delivery_address','type'=>'xsd:int'), + 'demand_reason_id' => array('name'=>'demand_reason_id','type'=>'xsd:string'), + + 'lines' => array('name'=>'lines','type'=>'tns:LinesArray') + ) +); + +$server->wsdl->addComplexType( + 'OrdersArray', + 'complexType', + 'array', + '', + 'SOAP-ENC:Array', + array(), + array( + array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:order[]') + ), + 'tns:order' +); + +$server->wsdl->addComplexType( + 'OrdersArray2', + 'complexType', + 'array', + 'sequence', + '', + array( + 'order' => array( + 'name' => 'invoice', + 'type' => 'tns:invoice', + 'minOccurs' => '0', + 'maxOccurs' => 'unbounded' + ) + ) +); + +$server->wsdl->addComplexType( + 'result', + 'complexType', + 'struct', + 'all', + '', + array( + 'result_code' => array('name'=>'result_code','type'=>'xsd:string'), + 'result_label' => array('name'=>'result_label','type'=>'xsd:string'), + ) +); + + + +// 5 styles: RPC/encoded, RPC/literal, Document/encoded (not WS-I compliant), Document/literal, Document/literal wrapped +// Style merely dictates how to translate a WSDL binding to a SOAP message. Nothing more. You can use either style with any programming model. +// http://www.ibm.com/developerworks/webservices/library/ws-whichwsdl/ +$styledoc='rpc'; // rpc/document (document is an extend into SOAP 1.0 to support unstructured messages) +$styleuse='encoded'; // encoded/literal/literal wrapped +// Better choice is document/literal wrapped but literal wrapped not supported by nusoap. + +// Register WSDL +$server->register('getOrder', +// Entry values + array('authentication'=>'tns:authentication','id'=>'xsd:string','ref'=>'xsd:string','ref_ext'=>'xsd:string'), + // Exit values + array('result'=>'tns:result','order'=>'tns:order'), + $ns, + $ns.'#getOrder', + $styledoc, + $styleuse, + 'WS to get a particular invoice' +); + +$server->register('getOrdersForThirdParty', +// Entry values + array('authentication'=>'tns:authentication','idthirdparty'=>'xsd:string'), + // Exit values + array('result'=>'tns:result','orders'=>'tns:OrdersArray2'), + $ns, + $ns.'#getOrdersForThirdParty', + $styledoc, + $styleuse, + 'WS to get all orders of a third party' +); + +$server->register('createOrder', +// Entry values + array('authentication'=>'tns:authentication','order'=>'tns:order'), + // Exit values + array('result'=>'tns:result','id'=>'xsd:string'), + $ns, + $ns.'#createOrder', + $styledoc, + $styleuse, + 'WS to create an order' +); + + +// Register WSDL +$server->register('validOrder', +// Entry values + array('authentication'=>'tns:authentication','id'=>'xsd:string'), + // Exit values + array('result'=>'tns:result'), + $ns, + $ns.'#validOrder', + $styledoc, + $styleuse, + 'WS to valid an order' +); + +/** + * Get order from id, ref or ref_ext. + * + * @param array $authentication Array of authentication information + * @param int $id Id + * @param string $ref Ref + * @param string $ref_ext Ref_ext + * @return array Array result + */ +function getOrder($authentication,$id='',$ref='',$ref_ext='') +{ + global $db,$conf,$langs; + + dol_syslog("Function: getOrder login=".$authentication['login']." id=".$id." ref=".$ref." ref_ext=".$ref_ext); + + if ($authentication['entity']) $conf->entity=$authentication['entity']; + + // Init and check authentication + $objectresp=array(); + $errorcode='';$errorlabel=''; + $error=0; + + $fuser=check_authentication($authentication,$error,$errorcode,$errorlabel); + + if ($fuser->societe_id) $socid=$fuser->societe_id; + + // Check parameters + if (! $error && (($id && $ref) || ($id && $ref_ext) || ($ref && $ref_ext))) + { + $error++; + $errorcode='BAD_PARAMETERS'; $errorlabel="Parameter id, ref and ref_ext can't be both provided. You must choose one or other but not both."; + } + + if (! $error) + { + $fuser->getrights(); + + if ($fuser->rights->commande->lire) + { + $order=new Commande($db); + $result=$order->fetch($id,$ref,$ref_ext); + if ($result > 0) + { + // Security for external user + if( $socid && ( $socid != $order->socid) ) + { + $error++; + $errorcode='PERMISSION_DENIED'; $errorlabel=$order->socid.'User does not have permission for this request'; + } + + if(!$error) + { + + $linesresp=array(); + $i=0; + foreach($order->lines as $line) + { + //var_dump($line); exit; + $linesresp[]=array( + 'id'=>$line->rowid, + 'fk_commande'=>$line->fk_commande, + 'fk_parent_line'=>$line->fk_parent_line, + 'desc'=>$line->desc, + 'qty'=>$line->qty, + 'price'=>$line->price, + 'subprice'=>$line->subprice, + 'tva_tx'=>$line->tva_tx, + 'remise'=>$line->remise, + 'remise_percent'=>$line->remise_percent, + 'fk_product'=>$line->fk_product, + 'product_type'=>$line->product_type, + 'total_ht'=>$line->total_ht, + 'total_tva'=>$line->total_tva, + 'total_ttc'=>$line->total_ttc, + 'date_start'=>$line->date_start, + 'date_end'=>$line->date_end, + 'product_ref'=>$line->product_ref, + 'product_label'=>$line->product_label, + 'product_desc'=>$line->product_desc + ); + $i++; + } + + // Create order + $objectresp = array( + 'result'=>array('result_code'=>'OK', 'result_label'=>''), + 'order'=>array( + 'id' => $order->id, + 'ref' => $order->ref, + 'ref_client' => $order->ref_client, + 'ref_ext' => $order->ref_ext, + 'ref_int' => $order->ref_int, + 'socid' => $order->socid, + 'statut' => $order->statut, + + 'total_ht' => $order->total_ht, + 'total_tva' => $order->total_tva, + 'total_localtax1' => $order->total_localtax1, + 'total_localtax2' => $order->total_localtax2, + 'total_ttc' => $order->total_ttc, + 'fk_project' => $order->fk_project, + + 'date' => $order->date?dol_print_date($order->date,'dayrfc'):'', + 'date_commande' => $order->date_commande?dol_print_date($order->date_commande,'dayrfc'):'', + + 'remise' => $order->remise, + 'remise_percent' => $order->remise_percent, + 'remise_absolue' => $order->remise_absolue, + + 'source' => $order->source, + 'facturee' => $order->facturee, + 'note' => $order->note, + 'note_public' => $order->note_public, + 'cond_reglement_id' => $order->cond_reglement_id, + 'cond_reglement' => $order->cond_reglement, + 'cond_reglement_doc' => $order->cond_reglement_doc, + 'cond_reglement_code' => $order->cond_reglement_code, + 'mode_reglement_id' => $order->mode_reglement_id, + 'mode_reglement' => $order->mode_reglement, + 'mode_reglement_code' => $order->mode_reglement_code, + + 'date_livraison' => $order->date_livraison, + 'fk_delivery_address' => $order->fk_delivery_address, + + 'demand_reason_id' => $order->demand_reason_id, + 'demand_reason_code' => $order->demand_reason_code, + + 'lines' => $linesresp + )); + } + } + else + { + $error++; + $errorcode='NOT_FOUND'; $errorlabel='Object not found for id='.$id.' nor ref='.$ref.' nor ref_ext='.$ref_ext; + } + } + else + { + $error++; + $errorcode='PERMISSION_DENIED'; $errorlabel='User does not have permission for this request'; + } + } + + if ($error) + { + $objectresp = array('result'=>array('result_code' => $errorcode, 'result_label' => $errorlabel)); + } + + return $objectresp; +} + + +/** + * Get list of orders for third party + * @param array $authentication Array of authentication information + * @param int $idthirdparty Id of thirdparty + * @return array Array result + */ +function getOrdersForThirdParty($authentication,$idthirdparty) +{ + global $db,$conf,$langs; + + dol_syslog("Function: getOrdersForThirdParty login=".$authentication['login']." idthirdparty=".$idthirdparty); + + if ($authentication['entity']) $conf->entity=$authentication['entity']; + + // Init and check authentication + $objectresp=array(); + $errorcode='';$errorlabel=''; + $error=0; + $fuser=check_authentication($authentication,$error,$errorcode,$errorlabel); + + if ($fuser->societe_id) $socid=$fuser->societe_id; + + // Check parameters + if (! $error && !$idthirdparty) + { + $error++; + $errorcode='BAD_PARAMETERS'; $errorlabel='Parameter id is not provided'; + } + + if (! $error) + { + $linesorders=array(); + + $sql.='SELECT c.rowid as orderid'; + $sql.=' FROM '.MAIN_DB_PREFIX.'commande as c'; + $sql.=" WHERE c.entity = ".$conf->entity; + if ($idthirdparty != 'all' ) $sql.=" AND c.fk_soc = ".$db->escape($idthirdparty); + + + $resql=$db->query($sql); + if ($resql) + { + $num=$db->num_rows($resql); + $i=0; + while ($i < $num) + { + // En attendant remplissage par boucle + $obj=$db->fetch_object($resql); + + $order=new Commande($db); + $order->fetch($obj->orderid); + + // Sécurité pour utilisateur externe + if( $socid && ( $socid != $order->socid) ) + { + $error++; + $errorcode='PERMISSION_DENIED'; $errorlabel=$order->socid.'User does not have permission for this request'; + } + + if(!$error) + { + + // Define lines of invoice + $linesresp=array(); + foreach($order->lines as $line) + { + $linesresp[]=array( + 'id'=>$line->rowid, + 'fk_commande'=>$line->fk_commande, + 'fk_parent_line'=>$line->fk_parent_line, + 'desc'=>$line->desc, + 'qty'=>$line->qty, + 'price'=>$line->price, + 'subprice'=>$line->subprice, + 'tva_tx'=>$line->tva_tx, + 'remise'=>$line->remise, + 'remise_percent'=>$line->remise_percent, + 'fk_product'=>$line->fk_product, + 'product_type'=>$line->product_type, + 'total_ht'=>$line->total_ht, + 'total_tva'=>$line->total_tva, + 'total_ttc'=>$line->total_ttc, + 'date_start'=>$line->date_start, + 'date_end'=>$line->date_end, + 'product_ref'=>$line->product_ref, + 'product_label'=>$line->product_label, + 'product_desc'=>$line->product_desc + ); + } + + // Now define invoice + $linesorders[]=array( + 'id' => $order->id, + 'ref' => $order->ref, + 'ref_client' => $order->ref_client, + 'ref_ext' => $order->ref_ext, + 'ref_int' => $order->ref_int, + 'socid' => $order->socid, + 'statut' => $order->statut, + + 'total_ht' => $order->total_ht, + 'total_tva' => $order->total_tva, + 'total_localtax1' => $order->total_localtax1, + 'total_localtax2' => $order->total_localtax2, + 'total_ttc' => $order->total_ttc, + 'fk_project' => $order->fk_project, + + 'date' => $order->date?dol_print_date($order->date,'dayrfc'):'', + 'date_commande' => $order->date_commande?dol_print_date($order->date_commande,'dayrfc'):'', + + 'remise' => $order->remise, + 'remise_percent' => $order->remise_percent, + 'remise_absolue' => $order->remise_absolue, + + 'source' => $order->source, + 'facturee' => $order->facturee, + 'note' => $order->note, + 'note_public' => $order->note_public, + 'cond_reglement_id' => $order->cond_reglement_id, + 'cond_reglement' => $order->cond_reglement, + 'cond_reglement_doc' => $order->cond_reglement_doc, + 'cond_reglement_code' => $order->cond_reglement_code, + 'mode_reglement_id' => $order->mode_reglement_id, + 'mode_reglement' => $order->mode_reglement, + 'mode_reglement_code' => $order->mode_reglement_code, + + 'date_livraison' => $order->date_livraison, + 'fk_delivery_address' => $order->fk_delivery_address, + + 'demand_reason_id' => $order->demand_reason_id, + 'demand_reason_code' => $order->demand_reason_code, + + 'lines' => $linesresp + ); + } + $i++; + } + + $objectresp=array( + 'result'=>array('result_code'=>'OK', 'result_label'=>''), + 'orders'=>$linesorders + + ); + } + else + { + $error++; + $errorcode=$db->lasterrno(); $errorlabel=$db->lasterror(); + } + } + + if ($error) + { + $objectresp = array('result'=>array('result_code' => $errorcode, 'result_label' => $errorlabel)); + } + + return $objectresp; +} + + +/** + * Create order + * @param array $authentication Array of authentication information + * @param array $order Order info + * @return int Id of new order + */ +function createOrder($authentication,$order) +{ + global $db,$conf,$langs; + + $now=dol_now(); + + dol_syslog("Function: createOrder login=".$authentication['login']." socid :".$order['socid'] ); + + // Init and check authentication + $objectresp=array(); + $errorcode='';$errorlabel=''; + $error=0; + if ($authentication['entity']) $conf->entity=$authentication['entity']; + $fuser=check_authentication($authentication,$error,$errorcode,$errorlabel); + + // Check parameters + + + if (! $error) + { + $newobject=new Commande($db); + $newobject->socid=$order['socid']; + $newobject->type=$order['type']; + $newobject->ref_ext=$order['ref_ext']; + $newobject->date=$order['date']; + $newobject->date_lim_reglement=$order['date_due']; + $newobject->note=$order['note']; + $newobject->note_public=$order['note_public']; + $newobject->statut=$order['statut']; + $newobject->facturee=$order['facturee']; + $newobject->fk_project=$order['project_id']; + $newobject->cond_reglement_id=$order['cond_reglement_id']; + $newobject->demand_reason_id=$order['demand_reason_id']; + $newobject->date_commande=$now; + + // Trick because nusoap does not store data with same structure if there is one or several lines + $arrayoflines=array(); + if (isset($order['lines']['line'][0])) $arrayoflines=$order['lines']['line']; + else $arrayoflines=$order['lines']; + + foreach($arrayoflines as $key => $line) + { + // $key can be 'line' or '0','1',... + $newline=new OrderLigne($db); + + $newline->type=$line['type']; + $newline->desc=$line['desc']; + $newline->fk_product=$line['fk_product']; + $newline->tva_tx=$line['vat_rate']; + $newline->qty=$line['qty']; + $newline->subprice=$line['unitprice']; + $newline->total_ht=$line['total_net']; + $newline->total_tva=$line['total_vat']; + $newline->total_ttc=$line['total']; + $newline->fk_product=$line['fk_product']; + $newobject->lines[]=$newline; + } + + + $db->begin(); + + $object_id=$newobject->create($fuser,0,0); + if ($object_id < 0) + { + $error++; + + } + + if (! $error) + { + $db->commit(); + $objectresp=array('result'=>array('result_code'=>'OK', 'result_label'=>''),'id'=>$object_id); + } + else + { + $db->rollback(); + $error++; + $errorcode='KO'; + $errorlabel=$newobject->error; + } + } + + if ($error) + { + $objectresp = array('result'=>array('result_code' => $errorcode, 'result_label' => $errorlabel)); + } + + return $objectresp; +} + + +/** + * Valid an order + * @param array $authentication Array of authentication information + * @param int $id Id of order to validate + * @return array Array result + */ +function validOrder($authentication,$id='') +{ + global $db,$conf,$langs; + + dol_syslog("Function: validOrder login=".$authentication['login']." id=".$id." ref=".$ref." ref_ext=".$ref_ext); + + // Init and check authentication + $objectresp=array(); + $errorcode='';$errorlabel=''; + $error=0; + if ($authentication['entity']) $conf->entity=$authentication['entity']; + $fuser=check_authentication($authentication,$error,$errorcode,$errorlabel); + + if (! $error) + { + $fuser->getrights(); + + if ($fuser->rights->commande->lire) + { + $order=new Commande($db); + $result=$order->fetch($id,$ref,$ref_ext); + + $order->fetch_thirdparty(); + $db->begin(); + if ($result > 0) + { + $result=$order->valid($fuser); + + if ($result >= 0) + { + // Define output language + $outputlangs = $langs; + commande_pdf_create($db, $order, $order->modelpdf, $outputlangs, 0, 0, 0); + + } + else + { + $db->rollback(); + $error++; + $errorcode='KO'; + $errorlabel=$newobject->error; + } + } + else + { + $db->rollback(); + $error++; + $errorcode='KO'; + $errorlabel=$newobject->error; + } + + } + else + { + $db->rollback(); + $error++; + $errorcode='KO'; + $errorlabel=$newobject->error; + } + + } + + if ($error) + { + $objectresp = array('result'=>array('result_code' => $errorcode, 'result_label' => $errorlabel)); + } + else + { + $db->commit(); + $objectresp= array('result'=>array('result_code'=>'OK', 'result_label'=>'')); + } + + return $objectresp; +} + + +// Return the results. +$server->service($HTTP_RAW_POST_DATA); + +?> diff --git a/htdocs/webservices/server_productorservice.php b/htdocs/webservices/server_productorservice.php index 0bba1e80058..b256621fb79 100755 --- a/htdocs/webservices/server_productorservice.php +++ b/htdocs/webservices/server_productorservice.php @@ -31,6 +31,8 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/functions.lib.php'; require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +require_once(DOL_DOCUMENT_ROOT."/categories/class/categorie.class.php"); + dol_syslog("Call Dolibarr webservices interfaces"); @@ -112,8 +114,17 @@ $server->wsdl->addComplexType( 'price_net' => array('name'=>'price_net','type'=>'xsd:string'), 'price' => array('name'=>'price','type'=>'xsd:string'), 'price_ttc' => array('name'=>'price_ttc','type'=>'xsd:string'), + 'price_min' => array('name'=>'price_min','type'=>'xsd:string'), + 'price_min_ttc' => array('name'=>'price_min_ttc','type'=>'xsd:string'), + 'price_base_type' => array('name'=>'price_base_type','type'=>'xsd:string'), + 'vat_rate' => array('name'=>'vat_rate','type'=>'xsd:string'), + 'tva_tx' => array('name'=>'tva_tx','type'=>'xsd:string'), + 'tva_npr' => array('name'=>'tva_npr','type'=>'xsd:string'), + 'localtax1_tx' => array('name'=>'localtax1_tx','type'=>'xsd:string'), + 'localtax2_tx' => array('name'=>'localtax2_tx','type'=>'xsd:string'), + 'stock_alert' => array('name'=>'stock_alert','type'=>'xsd:string'), 'stock_real' => array('name'=>'stock_real','type'=>'xsd:string'), 'stock_pmp' => array('name'=>'stock_pmp','type'=>'xsd:string'), @@ -255,6 +266,20 @@ $server->register( 'WS to get list of all products or services id and ref' ); +// Register WSDL +$server->register( + 'getProductsForCategory', + // Entry values + array('authentication'=>'tns:authentication','id'=>'xsd:string'), + // Exit values + array('result'=>'tns:result','products'=>'ProductsArray'), + $ns, + $ns.'#getProductsForCategory', + $styledoc, + $styleuse, + 'WS to get list of all products or services for a category' +); + /** * Get produt or service @@ -320,9 +345,21 @@ function getProductOrService($authentication,$id='',$ref='',$ref_ext='') 'country_code' => $product->country_code, 'custom_code' => $product->customcode, - 'price_net' => $product->price, - 'price' => ($product->price_ttc-$product->price), - 'vat_rate' => $product->tva_tx, + 'price_net' => $product->price, // todo : DEPRECATED ? + //'price' => ($product->price_ttc-$product->price), + 'price' => $product->price, + 'price_ttc' => $product->price_ttc, + 'price_min' => $product->price_min, + 'price_min_ttc' => $product->price_min_ttc, + 'price_base_type' => $product->price_base_type, + 'vat_rate' => $product->tva_tx, // todo : DEPRECATED ? + 'tva_tx' => $product->tva_tx, + //! French VAT NPR + 'tva_npr' => $product->tva_npr, + //! Spanish local taxes + 'localtax1_tx' => $product->localtax1_tx, + 'localtax2_tx' => $product->localtax2_tx, + 'price_ttc' => $product->price_ttc, 'price_base_type' => $product->price_base_type, @@ -541,6 +578,135 @@ function getListOfProductsOrServices($authentication,$filterproduct) } +// return category infos and children +function getProductsForCategory($authentication,$id) +{ + global $db,$conf,$langs; + + dol_syslog("Function: getProductsForCategory login=".$authentication['login']." id=".$id); + + if ($authentication['entity']) $conf->entity=$authentication['entity']; + + $objectresp=array(); + $errorcode='';$errorlabel=''; + $error=0; + + $fuser=check_authentication($authentication,$error,$errorcode,$errorlabel); + + + if (! $error && !$id) + { + $error++; + $errorcode='BAD_PARAMETERS'; $errorlabel="Parameter id must be provided."; + } + + + if (! $error) + { + $fuser->getrights(); + + if ($fuser->rights->produit->lire) + { + $categorie=new Categorie($db); + $result=$categorie->fetch($id); + if ($result > 0) + { + $table = "product"; + $field = "product"; + $sql = "SELECT fk_".$field." FROM ".MAIN_DB_PREFIX."categorie_".$table; + $sql .= " WHERE fk_categorie = ".$id; + $sql .= " ORDER BY fk_".$field." ASC" ; + + + dol_syslog("GetProductsForCategory::get_type sql=".$sql); + $res = $db->query($sql); + if ($res) + { + + while ($rec = $db->fetch_array ($res)) + { + $obj = new Product ($db); + $obj->fetch ($rec['fk_'.$field]); + if($obj->status > 0 ) { + + $dir = (!empty($conf->product->dir_output)?$conf->product->dir_output:$conf->service->dir_output); + $pdir = get_exdir($obj->id,2) . $obj->id ."/photos/"; + $dir = $dir . '/'. $pdir; + + $products[] = array( + + 'id' => $obj->id, + 'ref' => $obj->ref, + 'ref_ext' => $obj->ref_ext, + 'label' => $obj->label, + 'description' => $obj->description, + 'date_creation' => dol_print_date($obj->date_creation,'dayhourrfc'), + 'date_modification' => dol_print_date($obj->date_modification,'dayhourrfc'), + 'note' => $obj->note, + 'status_tosell' => $obj->status, + 'status_tobuy' => $obj->status_buy, + 'type' => $obj->type, + 'barcode' => $obj->barcode, + 'barcode_type' => $obj->barcode_type, + 'country_id' => $obj->country_id>0?$obj->country_id:'', + 'country_code' => $obj->country_code, + 'custom_code' => $obj->customcode, + + 'price_net' => $obj->price, + 'price' => ($obj->price_ttc-$obj->price), + 'vat_rate' => $obj->tva_tx, + 'price_ttc' => $obj->price_ttc, + 'price_base_type' => $obj->price_base_type, + + 'stock_real' => $obj->stock_reel, + 'stock_alert' => $obj->seuil_stock_alerte, + 'pmp' => $obj->pmp, + 'import_key' => $obj->import_key, + 'dir' => $pdir, + 'photos' => $obj->liste_photos($dir,$nbmax=10) + + + ); + } + + } + + // Retour + $objectresp = array( + 'result'=>array('result_code'=>'OK', 'result_label'=>''), + 'products'=> $products + ); + + } + else + { + $errorcode='NORECORDS_FOR_ASSOCIATION'; $errorlabel='No products associated'.$sql; + $objectresp = array('result'=>array('result_code' => $errorcode, 'result_label' => $errorlabel)); + dol_syslog("getProductsForCategory:: ".$c->error, LOG_DEBUG); + + } + } + else + { + $error++; + $errorcode='NOT_FOUND'; $errorlabel='Object not found for id='.$id; + } + } + else + { + $error++; + $errorcode='PERMISSION_DENIED'; $errorlabel='User does not have permission for this request'; + } + } + + if ($error) + { + $objectresp = array('result'=>array('result_code' => $errorcode, 'result_label' => $errorlabel)); + } + + return $objectresp; +} + // Return the results. diff --git a/htdocs/webservices/server_user.php b/htdocs/webservices/server_user.php index 4aee9cf42b0..e035b098b91 100644 --- a/htdocs/webservices/server_user.php +++ b/htdocs/webservices/server_user.php @@ -120,6 +120,34 @@ $server->wsdl->addComplexType( ) ); +// Define other specific objects +$server->wsdl->addComplexType( + 'group', + 'complexType', + 'struct', + 'all', + '', + array( + 'nom' => array('name'=>'nom','type'=>'xsd:string'), + 'id' => array('name'=>'id','type'=>'xsd:string'), + 'datec' => array('name'=>'datec','type'=>'xsd:string'), + 'nb' => array('name'=>'nb','type'=>'xsd:string') + ) +); + +$server->wsdl->addComplexType( + 'GroupsArray', + 'complexType', + 'array', + '', + 'SOAP-ENC:Array', + array(), + array( + array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'tns:group[]') + ), + 'tns:group' +); + // 5 styles: RPC/encoded, RPC/literal, Document/encoded (not WS-I compliant), Document/literal, Document/literal wrapped @@ -144,6 +172,19 @@ $server->register( 'WS to get user' ); +$server->register( + 'getListOfGroups', + // Entry values + array('authentication'=>'tns:authentication'), + // Exit values + array('result'=>'tns:result','groups'=>'tns:GroupsArray'), + $ns, + $ns.'#getListOfGroups', + $styledoc, + $styleuse, + 'WS to get list of groups' +); + @@ -246,6 +287,83 @@ function getUser($authentication,$id,$ref='',$ref_ext='') return $objectresp; } +/** + * getListOfGroups + * + * @param array $authentication Array of authentication information + * @return array Array result + */ +function getListOfGroups($authentication) +{ + global $db,$conf,$langs; + + $now=dol_now(); + + dol_syslog("Function: getListOfGroups login=".$authentication['login']); + + if ($authentication['entity']) $conf->entity=$authentication['entity']; + + // Init and check authentication + $objectresp=array(); + $arraygroups=array(); + $errorcode='';$errorlabel=''; + $error=0; + $fuser=check_authentication($authentication,$error,$errorcode,$errorlabel); + // Check parameters + + if (! $error) + { + $sql = "SELECT g.rowid, g.nom, g.entity, g.datec, COUNT(DISTINCT ugu.fk_user) as nb"; + $sql.= " FROM ".MAIN_DB_PREFIX."usergroup as g"; + $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."usergroup_user as ugu ON ugu.fk_usergroup = g.rowid"; + if (! empty($conf->multicompany->enabled) && $conf->entity == 1 && ($conf->multicompany->transverse_mode || ($user->admin && ! $user->entity))) + { + $sql.= " WHERE g.entity IS NOT NULL"; + } + else + { + $sql.= " WHERE g.entity IN (0,".$conf->entity.")"; + } + $sql.= " GROUP BY g.rowid, g.nom, g.entity, g.datec"; + $resql=$db->query($sql); + if ($resql) + { + $num=$db->num_rows($resql); + + $i=0; + while ($i < $num) + { + $obj=$db->fetch_object($resql); + $arraygroups[]=array('id'=>$obj->rowid,'nom'=>$obj->nom,'datec'=>$obj->datec,'nb'=>$obj->nb); + $i++; + } + } + else + { + $error++; + $errorcode=$db->lasterrno(); + $errorlabel=$db->lasterror(); + } + } + + if ($error) + { + $objectresp = array( + 'result'=>array('result_code' => $errorcode, 'result_label' => $errorlabel), + 'groups'=>$arraygroups + ); + } + else + { + $objectresp = array( + 'result'=>array('result_code' => 'OK', 'result_label' => ''), + 'groups'=>$arraygroups + ); + } + + return $objectresp; +} + // Return the results. diff --git a/robots.txt b/robots.txt new file mode 100644 index 00000000000..ee23dd24607 --- /dev/null +++ b/robots.txt @@ -0,0 +1,6 @@ +User-agent: * +Disallow: /build +Disallow: /dev +Disallow: /doc +Disallow: /scripts +Disallow: /test \ No newline at end of file diff --git a/test/phpunit/HolidayTest.php b/test/phpunit/HolidayTest.php new file mode 100644 index 00000000000..919b1a0162a --- /dev/null +++ b/test/phpunit/HolidayTest.php @@ -0,0 +1,319 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * or see http://www.gnu.org/ + */ + +/** + * \file test/phpunit/HolidayTest.php + * \ingroup test + * \brief PHPUnit test + * \remarks To run this script as CLI: phpunit filename.php + */ + +global $conf,$user,$langs,$db; +//define('TEST_DB_FORCE_TYPE','mysql'); // This is to force using mysql driver +require_once 'PHPUnit/Autoload.php'; +require_once dirname(__FILE__).'/../../htdocs/master.inc.php'; +require_once dirname(__FILE__).'/../../htdocs/holiday/class/holiday.class.php'; +$langs->load("dict"); + +if (empty($user->id)) +{ + print "Load permissions for admin user nb 1\n"; + $user->fetch(1); + $user->getrights(); +} + +$conf->global->MAIN_DISABLE_ALL_MAILS=1; + + +/** + * Class for PHPUnit tests + * + * @backupGlobals disabled + * @backupStaticAttributes enabled + * @remarks backupGlobals must be disabled to have db,conf,user and lang not erased. + */ +class HolidayTest extends PHPUnit_Framework_TestCase +{ + protected $savconf; + protected $savuser; + protected $savlangs; + protected $savdb; + + /** + * Constructor + * We save global variables into local variables + * + * @return HolidayTest + */ + function __construct() + { + //$this->sharedFixture + global $conf,$user,$langs,$db; + $this->savconf=$conf; + $this->savuser=$user; + $this->savlangs=$langs; + $this->savdb=$db; + + print __METHOD__." db->type=".$db->type." user->id=".$user->id; + //print " - db ".$db->db; + print "\n"; + } + + // Static methods + public static function setUpBeforeClass() + { + global $conf,$user,$langs,$db; + + $db->begin(); // This is to have all actions inside a transaction even if test launched without suite. + + print __METHOD__."\n"; + } + public static function tearDownAfterClass() + { + global $conf,$user,$langs,$db; + $db->rollback(); + + print __METHOD__."\n"; + } + + /** + * Init phpunit tests + * + * @return void + */ + protected function setUp() + { + global $conf,$user,$langs,$db; + $conf=$this->savconf; + $user=$this->savuser; + $langs=$this->savlangs; + $db=$this->savdb; + + print __METHOD__."\n"; + } + /** + * End phpunit tests + * + * @return void + */ + protected function tearDown() + { + print __METHOD__."\n"; + } + + /** + * testHolidayCreate + * + * @return int + */ + public function testHolidayCreate() + { + global $conf,$user,$langs,$db; + $conf=$this->savconf; + $user=$this->savuser; + $langs=$this->savlangs; + $db=$this->savdb; + + $localobject=new Holiday($this->savdb); + $localobject->initAsSpecimen(); + $result=$localobject->create($user); + + print __METHOD__." result=".$result."\n"; + $this->assertLessThan($result, 0); + + return $result; + } + + /** + * testHolidayFetch + * + * @param int $id Id of Holiday + * @return int + * @depends testHolidayCreate + * The depends says test is run only if previous is ok + */ + public function testHolidayFetch($id) + { + global $conf,$user,$langs,$db; + $conf=$this->savconf; + $user=$this->savuser; + $langs=$this->savlangs; + $db=$this->savdb; + + $localobject=new Holiday($this->savdb); + $result=$localobject->fetch($id); + + print __METHOD__." id=".$id." result=".$result."\n"; + $this->assertLessThan($result, 0); + + return $localobject; + } + + /** + * testHolidayUpdate + * + * @param Holiday $localobject Holiday + * @return int + * + * @depends testHolidayFetch + * The depends says test is run only if previous is ok + */ + public function testHolidayUpdate($localobject) + { + global $conf,$user,$langs,$db; + $conf=$this->savconf; + $user=$this->savuser; + $langs=$this->savlangs; + $db=$this->savdb; + + $localobject->oldcopy=dol_clone($localobject); + + $localobject->note='New note after update'; + //$localobject->note_public='New note public after update'; + $localobject->lastname='New name'; + $localobject->firstname='New firstname'; + $localobject->address='New address'; + $localobject->zip='New zip'; + $localobject->town='New town'; + $localobject->country_id=2; + //$localobject->status=0; + $localobject->phone_pro='New tel pro'; + $localobject->phone_perso='New tel perso'; + $localobject->phone_mobile='New tel mobile'; + $localobject->fax='New fax'; + $localobject->email='newemail@newemail.com'; + $localobject->jabberid='New im id'; + $localobject->default_lang='es_ES'; + $result=$localobject->update($localobject->id,$user); + print __METHOD__." id=".$localobject->id." result=".$result."\n"; + $this->assertLessThan($result, 0, 'Holiday::update error'); + $result=$localobject->update_note($localobject->note); + print __METHOD__." id=".$localobject->id." result=".$result."\n"; + $this->assertLessThan($result, 0, 'Holiday::update_note error'); + //$result=$localobject->update_note_public($localobject->note_public); + //print __METHOD__." id=".$localobject->id." result=".$result."\n"; + //$this->assertLessThan($result, 0); + + $newobject=new Holiday($this->savdb); + $result=$newobject->fetch($localobject->id); + print __METHOD__." id=".$localobject->id." result=".$result."\n"; + $this->assertLessThan($result, 0, 'Holiday::fetch error'); + + print __METHOD__." old=".$localobject->note." new=".$newobject->note."\n"; + $this->assertEquals($localobject->note, $newobject->note); + //print __METHOD__." old=".$localobject->note_public." new=".$newobject->note_public."\n"; + //$this->assertEquals($localobject->note_public, $newobject->note_public); + print __METHOD__." old=".$localobject->lastname." new=".$newobject->lastname."\n"; + $this->assertEquals($localobject->lastname, $newobject->lastname); + print __METHOD__." old=".$localobject->firstname." new=".$newobject->firstname."\n"; + $this->assertEquals($localobject->firstname, $newobject->firstname); + print __METHOD__." old=".$localobject->address." new=".$newobject->address."\n"; + $this->assertEquals($localobject->address, $newobject->address); + print __METHOD__." old=".$localobject->zip." new=".$newobject->zip."\n"; + $this->assertEquals($localobject->zip, $newobject->zip); + print __METHOD__." old=".$localobject->town." new=".$newobject->town."\n"; + $this->assertEquals($localobject->town, $newobject->town); + print __METHOD__." old=".$localobject->country_id." new=".$newobject->country_id."\n"; + $this->assertEquals($localobject->country_id, $newobject->country_id); + print __METHOD__." old=BE new=".$newobject->country_code."\n"; + $this->assertEquals('BE', $newobject->country_code); + //print __METHOD__." old=".$localobject->status." new=".$newobject->status."\n"; + //$this->assertEquals($localobject->status, $newobject->status); + print __METHOD__." old=".$localobject->phone_pro." new=".$newobject->phone_pro."\n"; + $this->assertEquals($localobject->phone_pro, $newobject->phone_pro); + print __METHOD__." old=".$localobject->phone_pro." new=".$newobject->phone_pro."\n"; + $this->assertEquals($localobject->phone_perso, $newobject->phone_perso); + print __METHOD__." old=".$localobject->phone_mobile." new=".$newobject->phone_mobile."\n"; + $this->assertEquals($localobject->phone_mobile, $newobject->phone_mobile); + print __METHOD__." old=".$localobject->fax." new=".$newobject->fax."\n"; + $this->assertEquals($localobject->fax, $newobject->fax); + print __METHOD__." old=".$localobject->email." new=".$newobject->email."\n"; + $this->assertEquals($localobject->email, $newobject->email); + print __METHOD__." old=".$localobject->jabberid." new=".$newobject->jabberid."\n"; + $this->assertEquals($localobject->jabberid, $newobject->jabberid); + print __METHOD__." old=".$localobject->default_lang." new=".$newobject->default_lang."\n"; + $this->assertEquals($localobject->default_lang, $newobject->default_lang); + + return $localobject; + } + + /** + * testHolidayOther + * + * @param Holiday $localobject Holiday + * @return void + * + * @depends testHolidayUpdate + * The depends says test is run only if previous is ok + */ + public function testHolidayOther($localobject) + { + global $conf,$user,$langs,$db; + $conf=$this->savconf; + $user=$this->savuser; + $langs=$this->savlangs; + $db=$this->savdb; + + //$localobject->fetch($localobject->id); + + /* + $result=$localobject->getNomUrl(1); + print __METHOD__." id=".$localobject->id." result=".$result."\n"; + $this->assertNotEquals($result, ''); + + $result=$localobject->getFullAddress(1); + print __METHOD__." id=".$localobject->id." result=".$result."\n"; + $this->assertContains("New address\nNew zip New town\nBelgium", $result); + + $localobject->info($localobject->id); + print __METHOD__." localobject->date_creation=".$localobject->date_creation."\n"; + $this->assertNotEquals($localobject->date_creation, ''); + */ + + return $localobject->id; + } + + /** + * testHolidayDelete + * + * @param int $id Id of Holiday + * @return void + * + * @depends testHolidayOther + * The depends says test is run only if previous is ok + */ + public function testHolidayDelete($id) + { + global $conf,$user,$langs,$db; + $conf=$this->savconf; + $user=$this->savuser; + $langs=$this->savlangs; + $db=$this->savdb; + + $localobject=new Holiday($this->savdb); + $result=$localobject->fetch($id); + + $result=$localobject->delete(0); + print __METHOD__." id=".$id." result=".$result."\n"; + $this->assertLessThan($result, 0); + + return $result; + } + +} +?>
      '.$langs->trans('LastName').'