From 474b68b3e41dfc8bfd0f7448d8b161ad44e378ee Mon Sep 17 00:00:00 2001 From: Regis Houssin Date: Wed, 14 Nov 2012 11:29:22 +0100 Subject: [PATCH 01/36] Fix: strict mode mother --- htdocs/holiday/class/holiday.class.php | 35 +++++++++++----------- htdocs/holiday/common.inc.php | 10 +++---- htdocs/holiday/fiche.php | 41 +++++++++++++------------- htdocs/holiday/index.php | 7 +++-- 4 files changed, 47 insertions(+), 46 deletions(-) diff --git a/htdocs/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index 83c1a38ed0a..e16cbc29229 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 @@ -1182,12 +1183,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); 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..38911ca13da 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) { @@ -290,7 +290,6 @@ if ($action == 'confirm_send') $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 @@ -703,11 +702,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 $html->select_dolusers($valideur,"valideur",1,"",0,$valideurarray,''); + print $html->select_dolusers($validator->id, "valideur", 1, "", 0, $valideurarray); print ''; print ''; 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 '
'; From 8908f977b8b95d186767685b8edc5368faa160da Mon Sep 17 00:00:00 2001 From: Regis Houssin Date: Wed, 14 Nov 2012 11:31:08 +0100 Subject: [PATCH 02/36] Fix: converting line delimiters !!! --- htdocs/cashdesk/class/Facturation.class.php | 4 +- htdocs/categories/class/categorie.class.php | 2 +- htdocs/compta/bank/fiche.php | 12 +-- htdocs/compta/facture.php | 14 ++-- htdocs/core/class/CMailFile.class.php | 8 +- htdocs/core/lib/functions.lib.php | 84 +++++++++---------- htdocs/core/lib/price.lib.php | 30 +++---- htdocs/core/modules/action/rapport.pdf.php | 8 +- .../modules/facture/doc/pdf_crabe.modules.php | 4 +- .../modules/propale/doc/pdf_azur.modules.php | 10 +-- .../pdf/pdf_canelle.modules.php | 6 +- htdocs/externalsite/admin/externalsite.php | 2 +- htdocs/product/stock/class/entrepot.class.php | 2 +- 13 files changed, 93 insertions(+), 93 deletions(-) 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/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/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..86fb33f545c 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -2819,54 +2819,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; } diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index 714fdb9a578..dfca755bcac 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; 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/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/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/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); From 8f15dd4df28a244f2c39f78c668dac7f2e6450ad Mon Sep 17 00:00:00 2001 From: jfefe Date: Wed, 14 Nov 2012 12:42:04 +0100 Subject: [PATCH 03/36] Fix : correct invoice line description when calling by webservice --- htdocs/webservices/server_invoice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From 096699cf18fcc8db1f3a50ebbd8b5adc0a0218fd Mon Sep 17 00:00:00 2001 From: simnandez Date: Wed, 14 Nov 2012 15:17:26 +0100 Subject: [PATCH 04/36] Trad: Add missing ca_ES and es_ES translations --- htdocs/langs/ca_ES/exports.lang | 6 +++++- htdocs/langs/ca_ES/languages.lang | 3 ++- htdocs/langs/es_ES/exports.lang | 6 +++++- htdocs/langs/es_ES/languages.lang | 3 ++- 4 files changed, 14 insertions(+), 4 deletions(-) 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/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 From 768e719961d74a3b738bf32c7c41dece671c71d4 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 14 Nov 2012 18:32:52 +0100 Subject: [PATCH 05/36] Fix: firstname was missing --- htdocs/core/modules/mailings/framboise.modules.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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"; From 4c4eeaac2a1beb4e724086c88317c7c72edd95b6 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 14 Nov 2012 18:56:47 +0100 Subject: [PATCH 06/36] Fix: Test fails if value is 0 --- htdocs/core/lib/functions.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index aef25a6d679..d4c90c5495c 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -3565,7 +3565,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; From ce10c87e09fb335ac75753bf82a2d5a039bb5e18 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 14 Nov 2012 18:58:55 +0100 Subject: [PATCH 07/36] Fix: No message to show result Fix: Must not overwrite property of object --- htdocs/comm/mailing/fiche.php | 25 +++++++++++++++++++------ htdocs/langs/el_GR/mails.lang | 35 ----------------------------------- htdocs/langs/en_US/mails.lang | 3 ++- htdocs/langs/fr_FR/mails.lang | 1 + 4 files changed, 22 insertions(+), 42 deletions(-) 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/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/mails.lang b/htdocs/langs/en_US/mails.lang index f9c0d0f198f..97d6dbd92cd 100644 --- a/htdocs/langs/en_US/mails.lang +++ b/htdocs/langs/en_US/mails.lang @@ -79,6 +79,7 @@ YourMailUnsubcribeOK=The email %s is correctly unsubcribe from mailing l 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 +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/fr_FR/mails.lang b/htdocs/langs/fr_FR/mails.lang index 78c70886cd0..db8dc5d88eb 100644 --- a/htdocs/langs/fr_FR/mails.lang +++ b/htdocs/langs/fr_FR/mails.lang @@ -79,6 +79,7 @@ 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 +EMailSentToNRecipients=EMail envoyé à %s destinataires. # Libelle des modules de liste de destinataires mailing MailingModuleDescContactCompanies=Contacts de tiers (prospects, clients, fournisseurs...) From 28b4a03432df2c4ddca51c640ee853efe79cc9f4 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 14 Nov 2012 20:49:19 +0100 Subject: [PATCH 08/36] Fix: Correct vat --- htdocs/install/mysql/data/llx_c_tva.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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); From 7c78e1c0f66abe063a89e992bcb65f314fdd27fe Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 15 Nov 2012 14:52:19 +0100 Subject: [PATCH 09/36] Doxygen and normalize code. New: add more PHPUnit --- htdocs/adherents/class/adherent.class.php | 3 +- .../adherents/class/adherent_type.class.php | 3 +- .../adherents/class/adherentstats.class.php | 3 +- htdocs/adherents/class/cotisation.class.php | 3 +- htdocs/bookmarks/class/bookmark.class.php | 3 +- htdocs/contact/class/contact.class.php | 3 +- htdocs/holiday/class/holiday.class.php | 62 ++-- htdocs/holiday/fiche.php | 97 +++--- test/phpunit/HolidayTest.php | 319 ++++++++++++++++++ 9 files changed, 411 insertions(+), 85 deletions(-) create mode 100644 test/phpunit/HolidayTest.php 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/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/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/holiday/class/holiday.class.php b/htdocs/holiday/class/holiday.class.php index e16cbc29229..81390c9f0b0 100644 --- a/htdocs/holiday/class/holiday.class.php +++ b/htdocs/holiday/class/holiday.class.php @@ -89,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("; @@ -103,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.= ")"; @@ -183,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; @@ -226,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='') { @@ -321,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) { @@ -1595,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/fiche.php b/htdocs/holiday/fiche.php index 38911ca13da..f564a1893e6 100644 --- a/htdocs/holiday/fiche.php +++ b/htdocs/holiday/fiche.php @@ -574,6 +574,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') @@ -621,7 +624,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'); @@ -678,10 +680,10 @@ if (empty($id) || $action == 'add' || $action == 'request') print ''; // 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 ''; print ''; @@ -690,10 +692,10 @@ if (empty($id) || $action == 'add' || $action == 'request') 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 ''; @@ -706,7 +708,7 @@ if (empty($id) || $action == 'add' || $action == 'request') $valideurarray = $validator->listUsersForGroup(); print ''; - print $html->select_dolusers($validator->id, "valideur", 1, "", 0, $valideurarray); + print $form->select_dolusers($validator->id, "valideur", 1, "", 0, $valideurarray); print ''; print ''; print ''; @@ -798,44 +800,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 '
'; } @@ -849,8 +846,6 @@ else print ''."\n"; print ''."\n"; print ''."\n"; - - $html = new Form($db); } print ''; @@ -873,7 +868,7 @@ else print ''; print ''; print ''; print ''; } @@ -887,7 +882,7 @@ else print ''; print ''; print ''; print ''; } @@ -952,7 +947,7 @@ else $valideur = $validator->listUsersForGroup(); print ''; print ''; } @@ -982,44 +977,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/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; + } + +} +?> From 5f9cf28e4588a25967e1d5b0566392e09390f806 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Thu, 15 Nov 2012 16:26:05 +0100 Subject: [PATCH 10/36] New: Add option to choose if init of form is for a company third party or an individual third party. --- htdocs/societe/soc.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 From b7f38def61f0f1c5e829868aea75989e37865c3f Mon Sep 17 00:00:00 2001 From: simnandez Date: Fri, 16 Nov 2012 12:13:29 +0100 Subject: [PATCH 11/36] Fix: Local taxes. If buyer not subject to localtax no need make database access --- htdocs/core/lib/functions.lib.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 7f3587a99a5..fd069cb4667 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -2700,8 +2700,8 @@ 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 ($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; //if ($local == 0 && ! $thirdparty_seller->localtax1_assuj && ! $thirdparty_seller->localtax2_assuj) return array('localtax1'=>0,'localtax2'=>0); $code_country=$thirdparty_seller->country_code; From 2e8e1f759fb4a30f5fe387f2af072b59aa201c3d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 16 Nov 2012 13:25:49 +0100 Subject: [PATCH 12/36] New: Add warning when we try to load sql from an not existing dir --- htdocs/core/modules/DolibarrModules.class.php | 46 +++++++++---------- htdocs/core/modules/modGravatar.class.php | 2 +- htdocs/core/modules/modWorkflow.class.php | 2 +- 3 files changed, 24 insertions(+), 26 deletions(-) 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/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/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(''); } } ?> From 5515f1518770757b8973c288054d91f31a46c270 Mon Sep 17 00:00:00 2001 From: simnandez Date: Fri, 16 Nov 2012 16:23:51 +0100 Subject: [PATCH 13/36] Fix: Localtaxes rules for spain --- htdocs/core/lib/functions.lib.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index fd069cb4667..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 || ! $thirdparty_buyer->localtax1_assuj)) return 0; - if ($local == 2 && (! $thirdparty_seller->localtax2_assuj || ! $thirdparty_buyer->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; From 61ed6e10e1b3fe9917381094e8039a0eb990c72b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 16 Nov 2012 18:59:03 +0100 Subject: [PATCH 14/36] Uniformize code --- htdocs/core/lib/price.lib.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index dfca755bcac..4c06cbb2099 100644 --- a/htdocs/core/lib/price.lib.php +++ b/htdocs/core/lib/price.lib.php @@ -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; From 255bd565fbaf1351a9d250c5fc72e4caf31ab0e1 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sat, 17 Nov 2012 00:58:43 +0100 Subject: [PATCH 15/36] Removed warning --- htdocs/comm/mailing/cibles.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ''; From 19b7cc66d58d815e39ac4dad3b20a7a8f02d48f1 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sat, 17 Nov 2012 11:48:51 +0100 Subject: [PATCH 16/36] Qual: Remove hidden option so is not efficient and not used files. Qual: Uniformize code --- build/makepack-dolibarr.pl | 1 + htdocs/core/tpl/login.tpl.php | 1 - htdocs/core/tpl/passwordforgotten.tpl.php | 1 - htdocs/holiday/fiche.php | 161 +++++++++--------- htdocs/main.inc.php | 5 +- htdocs/paypal/lib/paypal.lib.php | 12 +- htdocs/theme/bureau2crea/tpl/login.tpl.php | 3 +- .../bureau2crea/tpl/passwordforgotten.tpl.php | 3 +- 8 files changed, 87 insertions(+), 100 deletions(-) diff --git a/build/makepack-dolibarr.pl b/build/makepack-dolibarr.pl index a18529821b6..ddf8feb32f4 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/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/holiday/fiche.php b/htdocs/holiday/fiche.php index f564a1893e6..43dba92b726 100644 --- a/htdocs/holiday/fiche.php +++ b/htdocs/holiday/fiche.php @@ -262,29 +262,27 @@ 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'); @@ -293,16 +291,18 @@ if ($action == 'confirm_send') $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)) { @@ -312,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; @@ -366,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); @@ -442,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); @@ -516,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); @@ -800,7 +793,7 @@ else { if ($action == 'delete' && $cp->statut == 1) { - if($user->rights->holiday->delete) + 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 '
'; @@ -808,21 +801,21 @@ else } // Si envoi en validation - if ($action == 'sendToValidate' && $cp->statut == 1 && $userID == $cp->fk_user) + 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) + 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) + 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=$form->form_confirm("fiche.php?id=".$_GET['id']."&action=confirm_refuse",$langs->trans("TitleRefuseCP"),"","confirm_refuse",$array_input,"",0); @@ -830,7 +823,7 @@ else } // Si annulation de la demande - if ($action == 'cancel' && $cp->statut == 2 && $userID == $cp->fk_validator) + 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 '
'; @@ -990,27 +983,27 @@ else } dol_fiche_end(); - + if (! $edit) { print '
'; // Boutons d'actions - if($user->rights->holiday->write && $_GET['action'] != 'edit' && $cp->statut == 1) + if($user->rights->holiday->write && $_GET['action'] != 'edit' && $cp->statut == 1) { print ''.$langs->trans("EditCP").''; } - if($user->rights->holiday->delete && $cp->statut == 1) + if($user->rights->holiday->delete && $cp->statut == 1) { print ''.$langs->trans("DeleteCP").''; } - if($user->id == $cp->fk_user && $cp->statut == 1) + 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) + if($userID == $cp->fk_validator && $cp->statut == 2) { print ''.$langs->trans("ActionValidCP").''; print ''.$langs->trans("ActionRefuseCP").''; 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/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/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; From f4f627f3be57d15770f425068e1679e4714a9fb1 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sat, 17 Nov 2012 11:51:20 +0100 Subject: [PATCH 17/36] Fix typo --- build/makepack-dolibarr.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/makepack-dolibarr.pl b/build/makepack-dolibarr.pl index ddf8feb32f4..eda983bbb88 100755 --- a/build/makepack-dolibarr.pl +++ b/build/makepack-dolibarr.pl @@ -337,7 +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`; + $ret=`rm -fr $BUILDROOT/$PROJECT/htdocs/includes/savant`; } # Build package for each target From 8c27a3202b2b9f98ded102f977e03d5a742191ad Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sat, 17 Nov 2012 11:56:30 +0100 Subject: [PATCH 18/36] Qual: Removed useless files. Speed gain was not significant, has problem with https and need a lot of work to maintain each time we update an external lib. --- htdocs/includes/jquery/js/jquery-latest.min.jgz | Bin 33382 -> 0 bytes .../jquery/js/jquery-ui-latest.custom.min.jgz | Bin 61904 -> 0 bytes .../extras/Bootstrap/js/DT_bootstrap.jgz | Bin 1296 -> 0 bytes .../extras/ColReorder/js/ColReorder.min.jgz | Bin 3267 -> 0 bytes .../datatables/extras/ColVis/js/ColVis.min.jgz | Bin 3065 -> 0 bytes .../extras/TableTools/js/TableTools.min.jgz | Bin 8658 -> 0 bytes .../datatables/js/jquery.dataTables.min.jgz | Bin 20946 -> 0 bytes .../plugins/jeditable/jquery.jeditable.min.jgz | Bin 2212 -> 0 bytes .../jquery/plugins/jstree/jquery.jstree.min.jgz | Bin 27707 -> 0 bytes .../plugins/layout/jquery.layout-latest.jgz | Bin 42730 -> 0 bytes .../plugins/mobile/jquery.mobile-latest.min.jgz | Bin 23784 -> 0 bytes .../plugins/tablednd/jquery.tablednd_0_5.jgz | Bin 4990 -> 0 bytes .../jquery/plugins/tiptip/jquery.tipTip.min.jgz | Bin 1726 -> 0 bytes 13 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 htdocs/includes/jquery/js/jquery-latest.min.jgz delete mode 100644 htdocs/includes/jquery/js/jquery-ui-latest.custom.min.jgz delete mode 100644 htdocs/includes/jquery/plugins/datatables/extras/Bootstrap/js/DT_bootstrap.jgz delete mode 100644 htdocs/includes/jquery/plugins/datatables/extras/ColReorder/js/ColReorder.min.jgz delete mode 100644 htdocs/includes/jquery/plugins/datatables/extras/ColVis/js/ColVis.min.jgz delete mode 100644 htdocs/includes/jquery/plugins/datatables/extras/TableTools/js/TableTools.min.jgz delete mode 100644 htdocs/includes/jquery/plugins/datatables/js/jquery.dataTables.min.jgz delete mode 100644 htdocs/includes/jquery/plugins/jeditable/jquery.jeditable.min.jgz delete mode 100644 htdocs/includes/jquery/plugins/jstree/jquery.jstree.min.jgz delete mode 100644 htdocs/includes/jquery/plugins/layout/jquery.layout-latest.jgz delete mode 100644 htdocs/includes/jquery/plugins/mobile/jquery.mobile-latest.min.jgz delete mode 100644 htdocs/includes/jquery/plugins/tablednd/jquery.tablednd_0_5.jgz delete mode 100755 htdocs/includes/jquery/plugins/tiptip/jquery.tipTip.min.jgz 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 7d052bcf861a4269098753c79830c0889ab638e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33382 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 S9rth0|Nj6TXoMsDYykkpzJikg 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 8bfca3a3e04ddd8f7b0f84ade9210cf37c611614..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61904 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 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 5790b3785af481be4fe6cf4cfb7532f6661d16fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3267 zcmV;!3_SB6iwFqv6N^s*14D0YQe|&)WMy(LZE0>UYI6X+8ryE$IQHFNq3FRCC#>pp z(TBC77HQf{vuK-a>`og5K|o2gtwa_z5?v>o`rr2)QWwgW-E?-a156{4=f-nClwbVT zvwpKq;)D;a^1ow|TyT*`Ja^}D>fROz@dwX~Sfm>0z#X`+aP?>*XCf~#kCKF?*1uRD z#VNO(<#Op3S^hBRQOw*>xQjc2IQaju$R6@|I+NDR!NK4;{BzJorxr^ROMw(t&I_J@ z=aCDjh&`Ugg;j_}9&&2}l3VaP$vKC$iCnTAuoo$aZ}C{p;1B#`{eJ$@`u@^N;*h5W zw?u9+B=vUm&T{@?Wg<=`u?qPB)Jhl-0k|x(EQxt!ErApeQj9edw>nIhI8j{4JPsBT zP~%idFcTSv2PUmp0y#Kdz^z5WCyRty0I6twJsp2I`!cqUK3`g2k1j5bK94Uw#asXc z|IRh7cwS1y@|>me!4ebe)BB6#4*-Ak_VnZF_)?LBW1gIjKffQ1tdp|~>&QAkx)`4x zfBATHVV!@uI6oV`cdZfUifE-9JOd~ca3d~RoD_J{7d>aPNJ9xe;7q8XF?p=st+~VC zP2|pZmRq5Jbxk9GKm(t-2~VeT=0%5r7wzwp@QPhU*VlgJ@eJwWapxKDseIE$0$vxs<63BnI29tS}V{MPmr zzjjl27(ZmZ-w)huQOuk*0OEyya1~yA=^{z?d_Q`_+!)lA%eNf3qp$%SjtrU0vW%zE zF=9I?tvgBGh)FhPLBikPOII9aF)TKL=GVF{&!f&lIi1pk-hr%40Ptd-Rwtn|FP=Ez zo*xnTJ9r)b_z}ML?+ALRCVw+YKZ+^PJD_&M$a*%(#rzrgWW?{$QPHiyc>)0uQw%M8 zKO`PczzY=F=*N#<^fbCs?4lZJs*h8!kB7zzz((A^^MXU}1z?fsRdDSG-mNoL3h+yr+kWHw83f>uQQT+Y8sLE?#izb#By77xRGUw(m>u zdN9_^og<<1Tx7&}_It>qe!ZqM@Z8_I#08*P`2?(-1-Cp@>nEvWQd20S4V2Li6f~46 zaCq$of5b%$0Yn}XzvYe^<`jKzEJ`4ZBjU|`GsVl z61jj-P^CA81Ek{*oJH=G%l8SN^HdgZAI5C@na#Ol%iK0ycEeFq2b~eZdPZ8_n2t|` zkmPY%KmYFxRxO_vsND_dIvUhAU;_8N-|wZ)+M1?#Ynnxes$M$J`G~VToWWkX@pJAI zH!hB#ggvyF-6)SH{r+vovLa2_chV1W6!El+FWL%PnKjOufZjuODa2NG5NHFQxAQ`| z*iB9E)zrEovF+7Lu`}W>(g0GYV~0@W?rcI^{)mx%Bqa-HAEBw>DbK+hH>rSwdN#H_ z6KJ0zd#?&>fC4A(_w`>0#0wrQurfg)o~DIlX~@?wdnKv?;ogYzWHF6XmDjY|`u(2( z0JAR{6h7{d)ymsa?Z~6Er_Ou)@n|&q@xv%8c!+uj+<_%fZ5{h7%Tvg(L!(i(=8HmN zKNY93j9C&FP0PluJN~c)FzZ>$m(T3hVe9M{_WMrgKdy+USQh?cB!}n>6iE(4I>B=d z2Q-58bPUaDz`{EXFc;9p4Ie=dx%@VKen8dJB|eGCq~Ll{KdM#OhR|-FRIb5Stj*QS ziZU@)+r{wedZopR#2f_6*;I2@Z7a{!O9=rm5*$@!SFA&YQ=Uj=HZWGc2qml;O+KrQ zSm@3N;fdEPdo+AAt>^nbaIhz+Yt<4r-ieM@cRxoY|WK`~0nLX%@0c0y@8Gcp>o&rc2G|8s+R)!&UlRB~C4-^| zMD?Syc6CMi~A-UuvgeDt$n;u&arAEuTbw{mofA7FE#d(*rWo1+y!jKcpm;=6h zos9eaJ!s{C;K0*kBZ}eekYcr>1$IC~)8x=~*el#xLu*7}N`|WQ*xRK{LzQ(|GiM(0 zZd_&6wdv=%g@gEmm6z|4fC)Mk(wZ|F&aAssEMZtx4L8n3VaeAT-Njjk(~MmzxtXFd zu-SST>$%W}wkg$ZQ%D&Mrh9$>9*h$-MhopahdFJkm?A6327mR9co(BE7fIq4-=GZR zUdPrYX?dXKf;@v}7IWEi)>;&HS51@X259u;;_68J+9QI;O>x({Yv^wRutL@3RC+MIh z)s~#P#tKJfLoUEV0GyMmUEV!HQBy=6wgKeQ-g-G0wi`7xfUY*)#0iP3n^x8SMBv~C zO(TDU;i9_EyhAV*(GgXdMfVtc2P<8+YoM*1Y0wqH;1TxtgN=Q=ZmHCD4rYrt)yfc) zfPN%y#&Vph97Mt!CcvLKoVdD4Yy4K7&DEV9(9YPDf9u%0EcY(kdu8`mAwK6|^oi_( zsR;m#6j>WY3*FRHb5(VVAiFJRHVw;j;guC})nyErsf|{*nEtNNEd69GjA7U)*u8C% z)XeJMwYs?*(2bok>u#dxI&wcWOKz$z&l(FURZ=O2gO#4N&_$*`Uj5zrL33+zt8PuE zb>3goLch=V_i1o7ZGxlDZ(RZ@NHw#y1le!xETAX<8b@;G5R{OEgo7rVst&A!$-1Hx z9GuOGTah*w-}w_F?UShbB#1P!+N86{oKoruRk$4 zb2G7Y>W3;OIs%QpB>QV{mmP4I07nPzD?)S_1-$c4@ty>1jKybxQt0Ca!1!lo}Izr=WpZW?iDr#Qbt zM~bG#e92lfE5=533DX&6W&E^Y+7V>l9<@Utb^{STxVa*#zY4Sg=q`v-d1Y5u;i&0` zt^*mXyV}3*RM@FtLjMn4A~n_4o`D-O#-h$i>Xw53a)U`XGDP_r-6>GL+ILRo)H1fzLq%d!KbRNBcG@DUnhJt&%~xzDaRiHHKzv+wV-h-}?P+TbFN~ zEeuelJG*TFFskq-0qu*OG%7b$_++?Cgx*A{toc}8+!RW7o37LSn&VgY{I7`k%PiU_ z@XBg!e&ye&);obzoVC7)n0N4aWnk=-SM0voY`1TTwp`_}T`+V*;jKWsdcq8?J{D~O zF2#Y&)%i(4f2;A$k2ogWAMY@t1^;ef>dbO$zhvkA$>2?YHjuRfnVPEx$++`PN;jqhdMAED~6XwA{z zbS?TRUmBmk%x`(L>pEKP$6#fHZpYg2aYcsV&fj@3Dfv9ZpEXHgd~LJj5{6gTI-RB> zho;Udcqr1yu5NfN^@)mxrKX1Vg-wh4#qbsopts{YU(Wwr#Gq;GT^+xcvJC#zX{wSh z-p1l@&(KCvLAWWGFYtyc5e!6H5$E>b3!XpFB{cJ5Nh1+1FkeIe@n6~t_CgOQ001c> BR{j70 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 517c50c55928c62a17a163688cd6ea514e53b6db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3065 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 HgC_t0Q)l{Z 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 a467a5f2008b23eb7dd2f2d2f63b23d9fa72ea39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8658 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 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 e7a91f87f757fb21c99dfd9591a0719b67c11b2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20946 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 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 8ba61f32afcbc31f54a7ab8b5a9df2ec6a2c2a26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27707 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%0Ak6anWetbE;|)DSQqL}ZfWmP(Qqdn< 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⁢- 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 GOacJQ->!H7 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 43f8746af6c43074049c03773311211ca7b55d49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23784 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
'; print ''; - print ''; + print ''; print ''; 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/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/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; From fe86b5d29ee4b4975c8629cae5c06c26bb676d2b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sat, 17 Nov 2012 20:25:44 +0100 Subject: [PATCH 21/36] Fix: 2 other fixes on option MAIN_JS_ON_PAYMENT --- htdocs/compta/ajaxpayment.php | 46 +++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/htdocs/compta/ajaxpayment.php b/htdocs/compta/ajaxpayment.php index d0b49b78762..12f0bd36978 100644 --- a/htdocs/compta/ajaxpayment.php +++ b/htdocs/compta/ajaxpayment.php @@ -31,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']; @@ -41,23 +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]); -} + +// 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('RemainingAmountPayment'); -if($currentInvId) // Here to breakdown +if ($currentInvId) // Here to breakdown { // Get the current amount (from form) and the corresponding remainToPay (from invoice) $currentAmount = $amounts['amount_'.$currentInvId]; @@ -95,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 From 40a370473913faca54f442e5f7d61df4fc90a6d6 Mon Sep 17 00:00:00 2001 From: jfefe Date: Sat, 17 Nov 2012 23:21:12 +0100 Subject: [PATCH 22/36] New webservice method to get list of usergroups --- htdocs/webservices/server_user.php | 118 +++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) 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. From 0f789f6ad37e3bca223560863ceb751f356536c9 Mon Sep 17 00:00:00 2001 From: jfefe Date: Sat, 17 Nov 2012 23:40:58 +0100 Subject: [PATCH 23/36] New webservice method to get list of product for a specific category --- .../webservices/server_productorservice.php | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/htdocs/webservices/server_productorservice.php b/htdocs/webservices/server_productorservice.php index 0bba1e80058..e6caadaed57 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"); @@ -255,6 +257,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 @@ -541,6 +557,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. From 3fa7afae101cb7a45a844f6fee35afe89cdab3b9 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 18 Nov 2012 11:52:04 +0100 Subject: [PATCH 24/36] Fix: jquery upgrade has broken background compatibility with plugins. Upgrade jquery core only if required. --- .../plugins/layout/jquery.layout-latest.js | 10270 +++++++++------- 1 file changed, 5918 insertions(+), 4352 deletions(-) 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 From a08735d5be77b56822585a62a488c235f73f68a4 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 18 Nov 2012 12:59:11 +0100 Subject: [PATCH 25/36] Add robots.txt to prevent indexing of dev installations --- robots.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 robots.txt 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 From f47ce7b220135b061adaf6c5c8e2243eea0260e3 Mon Sep 17 00:00:00 2001 From: jfefe Date: Sun, 18 Nov 2012 13:15:19 +0100 Subject: [PATCH 26/36] New webservice to fetch categories --- .../demo_wsclient_category.php-NORUN | 98 ++++++ htdocs/webservices/server_category.php | 301 ++++++++++++++++++ 2 files changed, 399 insertions(+) create mode 100755 htdocs/webservices/demo_wsclient_category.php-NORUN create mode 100755 htdocs/webservices/server_category.php 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/server_category.php b/htdocs/webservices/server_category.php new file mode 100755 index 00000000000..dbe9949db46 --- /dev/null +++ b/htdocs/webservices/server_category.php @@ -0,0 +1,301 @@ + + * 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' +); + + +// return category infos and children +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); + +?> From 324b11ebb9dea32a6f356aca1088eafce21c5d0a Mon Sep 17 00:00:00 2001 From: jfefe Date: Sun, 18 Nov 2012 13:20:27 +0100 Subject: [PATCH 27/36] Add comment --- htdocs/webservices/server_category.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/htdocs/webservices/server_category.php b/htdocs/webservices/server_category.php index dbe9949db46..b08aa90d763 100755 --- a/htdocs/webservices/server_category.php +++ b/htdocs/webservices/server_category.php @@ -196,7 +196,13 @@ $server->register( ); -// return category infos and children +/** + * 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; From 6512fd90a946612375745ae351d0ce55e8faaf24 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 18 Nov 2012 15:24:01 +0100 Subject: [PATCH 28/36] New: Can export customer shipments. --- ChangeLog | 2 +- htdocs/core/modules/modExpedition.class.php | 31 +++++++++++++++++++ htdocs/core/modules/modProjet.class.php | 2 ++ htdocs/exports/export.php | 6 +++- .../install/mysql/migration/3.2.0-3.3.0.sql | 2 ++ .../install/mysql/tables/llx_expedition.sql | 14 ++++----- htdocs/langs/en_US/exports.lang | 2 +- htdocs/langs/en_US/interventions.lang | 1 + htdocs/langs/en_US/projects.lang | 1 + htdocs/langs/en_US/sendings.lang | 1 + htdocs/langs/fr_FR/projects.lang | 1 + htdocs/langs/fr_FR/sendings.lang | 1 + 12 files changed, 54 insertions(+), 10 deletions(-) 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/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/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/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/install/mysql/migration/3.2.0-3.3.0.sql b/htdocs/install/mysql/migration/3.2.0-3.3.0.sql index f279437ec09..1a1cc16f3f3 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/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/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/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 From 46976bd009b48345515ee0b7d4d797129f1211bc Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 18 Nov 2012 16:21:38 +0100 Subject: [PATCH 29/36] Fix: Some fix in volume calculation --- htdocs/expedition/class/expedition.class.php | 15 +++-- htdocs/expedition/fiche.php | 71 ++++++++++---------- 2 files changed, 45 insertions(+), 41 deletions(-) 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..f26ed0e8efe 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 '
      '.$langs->trans('LastName').''; - 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 '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 Date: Sat, 17 Nov 2012 15:53:04 +0100 Subject: [PATCH 19/36] fix some text --- htdocs/langs/fr_FR/mails.lang | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/htdocs/langs/fr_FR/mails.lang b/htdocs/langs/fr_FR/mails.lang index 78c70886cd0..ed6d59226a2 100644 --- a/htdocs/langs/fr_FR/mails.lang +++ b/htdocs/langs/fr_FR/mails.lang @@ -75,17 +75,17 @@ 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. +YourMailUnsubcribeOK=L'adresse e-mail %s est bien désinscrite 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 +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 # 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 +105,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 From 5b02ff3a86d5819e2916d334dd172e31cc92fb3b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sat, 17 Nov 2012 19:55:19 +0100 Subject: [PATCH 20/36] Fix: 3 fixes on option MAIN_JS_ON_PAYMENT (css, bad path, removed forced var) --- htdocs/compta/ajaxpayment.php | 8 ++--- htdocs/compta/paiement.php | 47 ++++++++++++-------------- htdocs/master.inc.php | 2 +- htdocs/theme/auguria/style.css.php | 2 +- htdocs/theme/bureau2crea/style.css.php | 2 +- htdocs/theme/cameleo/style.css.php | 2 +- htdocs/theme/eldy/style.css.php | 4 +-- 7 files changed, 29 insertions(+), 38 deletions(-) diff --git a/htdocs/compta/ajaxpayment.php b/htdocs/compta/ajaxpayment.php index e9ceafdaf52..d0b49b78762 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'); @@ -45,10 +44,7 @@ $currentInvId = $_POST['imgClicked']; // from DOM elements : imgId (equals invo // 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 +$amountPayment = $amountPayment!='' ? ( is_numeric(price2num($amountPayment)) ? price2num($amountPayment) : '' ) : ''; // keep void if not a valid entry // Checkamounts foreach ($amounts as $key => $value) { @@ -60,7 +56,7 @@ foreach ($amounts as $key => $value) $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'); +$toJsonArray['label'] = $amountPayment == '' ? '' : $langs->transnoentities('RemainingAmountPayment'); if($currentInvId) // Here to breakdown { // Get the current amount (from form) and the corresponding remainToPay (from invoice) 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 '
      '.price(price2num($total_ttc - $totalrecu - $totalrecucreditnote - $totalrecudeposits,'MT')).' 
      '; @@ -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"; From eb7dc8934c090c0dfe749a812216488cd96e3bfb Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 18 Nov 2012 16:41:10 +0100 Subject: [PATCH 30/36] Fix: Do not loose label --- htdocs/compta/sociales/charges.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ''; // Date end period From f58d0636a1a5a9980edd96d98eeb26c935bd5f1d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 18 Nov 2012 16:55:00 +0100 Subject: [PATCH 31/36] Doxygen --- .../class/bonprelevement.class.php | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) 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"; From 1b74df932b1618143aa77b550f10f8b4d014f15d Mon Sep 17 00:00:00 2001 From: Grand Philippe Date: Sun, 18 Nov 2012 17:39:49 +0100 Subject: [PATCH 32/36] fix some translations --- htdocs/langs/fr_FR/withdrawals.lang | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) 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 From c2d4b4d2e96f7424210297d0c32eba8d7acc5117 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 18 Nov 2012 18:36:50 +0100 Subject: [PATCH 33/36] Fix: Edit of emailing setup was not possible --- htdocs/admin/mailing.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) 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 '
      '.$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->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!='')?' '.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!='')?' '.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!='')?' '.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)." '; - $formsocialcontrib->select_type_socialcontrib(isset($_POST["actioncode"])?$_POST["actioncode"]:'','actioncode',1); + $formsocialcontrib->select_type_socialcontrib(GETPOST("actioncode")?GETPOST("actioncode"):'','actioncode',1); 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 ''; + llxFooter(); $db->close(); From b9868bf0ce4e2776221609ad3acffa73720d7dbf Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sun, 18 Nov 2012 18:44:51 +0100 Subject: [PATCH 34/36] Fix: units --- htdocs/expedition/fiche.php | 8 ++++---- htdocs/langs/en_US/mails.lang | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/htdocs/expedition/fiche.php b/htdocs/expedition/fiche.php index f26ed0e8efe..3158c1ccf4b 100644 --- a/htdocs/expedition/fiche.php +++ b/htdocs/expedition/fiche.php @@ -1051,25 +1051,25 @@ else // Weight 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 ''; // Width 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 ''; // Height 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 ''; // Depth 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 ''; // Volume diff --git a/htdocs/langs/en_US/mails.lang b/htdocs/langs/en_US/mails.lang index 97d6dbd92cd..f143a7fa167 100644 --- a/htdocs/langs/en_US/mails.lang +++ b/htdocs/langs/en_US/mails.lang @@ -77,8 +77,8 @@ 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 From d9d01c80b0536cb89664263726e3d1bdc4809bf3 Mon Sep 17 00:00:00 2001 From: jfefe Date: Mon, 19 Nov 2012 00:35:01 +0100 Subject: [PATCH 35/36] Add information about price when call a product by webservice --- .../webservices/server_productorservice.php | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/htdocs/webservices/server_productorservice.php b/htdocs/webservices/server_productorservice.php index e6caadaed57..b256621fb79 100755 --- a/htdocs/webservices/server_productorservice.php +++ b/htdocs/webservices/server_productorservice.php @@ -114,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'), @@ -336,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, From 7157869371de49cb179602a05032cd636f0f8428 Mon Sep 17 00:00:00 2001 From: jfefe Date: Mon, 19 Nov 2012 12:14:51 +0100 Subject: [PATCH 36/36] New webservice for order with method to get, create and valid an order --- .../webservices/demo_wsclient_order.php-NORUN | 141 ++++ htdocs/webservices/server_order.php | 778 ++++++++++++++++++ 2 files changed, 919 insertions(+) create mode 100755 htdocs/webservices/demo_wsclient_order.php-NORUN create mode 100644 htdocs/webservices/server_order.php 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_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); + +?>