diff --git a/plugins/CustomAlerts/API.php b/plugins/CustomAlerts/API.php new file mode 100755 index 00000000000..d8b8c1a012c --- /dev/null +++ b/plugins/CustomAlerts/API.php @@ -0,0 +1,386 @@ +getSitesIdWithAtLeastViewAccess(); + } + + $alerts = Db::fetchAll(("SELECT * FROM " + . Common::prefixTable('alert') + . " WHERE idalert IN ( + SELECT pas.idalert FROM " . Common::prefixTable('alert_site') + . " pas WHERE idsite IN (" . implode(",", $idSites) . ")) " + . "AND deleted = 0" + )); + + return $alerts; + } + + public function getTriggeredAlerts($period, $date, $login = false) + { + Piwik::checkUserIsSuperUserOrTheUser($login); + + $this->checkPeriod($period); + $piwikDate = Date::factory($date); + $date = Period::factory($period, $piwikDate); + + $db = Db::get(); + + $sql = "SELECT pa.idalert AS idalert, + pal.idsite AS idsite, + pa.name AS alert_name, + ps.name AS site_name, + login, + period, + report, + report_condition, + report_matched, + metric, + metric_condition, + metric_matched + FROM ". Common::prefixTable('alert_log') ." pal + JOIN ". Common::prefixTable('alert') ." pa + ON pal.idalert = pa.idalert + JOIN ". Common::prefixTable('site') ." ps + ON pal.idsite = ps.idsite + WHERE period = ? + AND ts_triggered BETWEEN ? AND ?"; + + if ($login !== false) { + $sql .= " AND login = \"" . $login . "\""; + } + + return $db->fetchAll($sql, array( + $period, + $date->getDateStart()->getDateStartUTC(), + $date->getDateEnd()->getDateEndUTC()) + ); + + } + + public function getAllAlerts($period) + { + Piwik::checkUserIsSuperUser(); + + $sql = "SELECT * FROM " + . Common::prefixTable('alert_site') . " alert, " + . Common::prefixTable('alert') . " alert_site " + . "WHERE alert.idalert = alert_site.idalert " + . "AND deleted = 0 "; + + if ($this->isValidPeriod($period)) { + $sql .= sprintf("AND period = '%s'", $period); + } else { + throw new Exception("Invalid period given."); + } + + return Db::fetchAll($sql); + } + + /** + * Creates an Alert for given website(s). + * + * @param string $name + * @param mixed $idSites + * @param string $period + * @param bool $email + * @param string $metric (nb_uniq_visits, sum_visit_length, ..) + * @param string $metricCondition + * @param float $metricValue + * @param string $report + * @param string $reportCondition + * @param string $reportValue + * @return int ID of new Alert + */ + public function addAlert($name, $idSites, $period, $email, $metric, $metricCondition, $metricValue, $report, $reportCondition = '', $reportValue = '') + { + if (!is_array($idSites)) { + $idSites = array($idSites); + } + + Piwik::checkUserHasViewAccess($idSites); + + $name = $this->checkName($name); + $this->checkPeriod($period); + + // save in db + $db = Db::get(); + $idAlert = Db::fetchOne("SELECT max(idalert) + 1 FROM " . Common::prefixTable('alert')); + if ($idAlert == false) { + $idAlert = 1; + } + + $newAlert = array( + 'idalert' => $idAlert, + 'name' => $name, + 'period' => $period, + 'login' => Piwik::getCurrentUserLogin(), + 'enable_mail' => (int) $email, + 'metric' => $metric, + 'metric_condition' => $metricCondition, + 'metric_matched' => (float) $metricValue, + 'report' => $report, + 'deleted' => 0, + ); + + if (!empty($reportCondition) && !empty($reportCondition)) { + $newAlert['report_condition'] = $reportCondition; + $newAlert['report_matched'] = $reportValue; + } + + // Do we have a valid alert for all given idSites? + foreach ($idSites as $idSite) { + if (!$this->isValidAlert($newAlert, $idSite)) { + throw new Exception(Piwik::translate('Alerts_ReportOrMetricIsInvalid')); + } + } + + $db->insert(Common::prefixTable('alert'), $newAlert); + foreach ($idSites as $idSite) { + $db->insert(Common::prefixTable('alert_site'), array( + 'idalert' => $idAlert, + 'idsite' => $idSite + )); + } + return $idAlert; + } + + /** + * Edits an Alert for given website(s). + * + * @param string $idalert ID of the Alert to edit. + * @param string $name Name of Alert + * @param mixed $idSites Single int or array of ints of idSites. + * @param string $period Period the alert is defined on. + * @param bool $email + * @param string $metric (nb_uniq_visits, sum_visit_length, ..) + * @param string $metricCondition + * @param float $metricValue + * @param string $report + * @param string $reportCondition + * @param string $reportValue + * @return boolean + */ + public function editAlert($idAlert, $name, $idSites, $period, $email, $metric, $metricCondition, $metricValue, $report, $reportCondition = '', $reportValue = '') + { + if (!is_array($idSites)) { + $idSites = array($idSites); + } + + Piwik::checkUserHasViewAccess($idSites); + + // Is the name in a valid format? + $name = $this->checkName($name); + + // Is the period valid? + $this->checkPeriod($period); + + // Save in DB + $db = Db::get(); + + $alert = array( + 'name' => $name, + 'period' => $period, + 'login' => 'admin', //Piwik::getCurrentUserLogin(), + 'enable_mail' => (boolean) $email, + 'metric' => $metric, + 'metric_condition' => $metricCondition, + 'metric_matched' => (float) $metricValue, + 'report' => $report, + 'deleted' => 0, + ); + + // + if (!empty($reportCondition) && !empty($reportCondition)) { + $alert['report_condition'] = $reportCondition; + $alert['report_matched'] = $reportValue; + } else { + $alert['report_condition'] = null; + $alert['report_matched'] = null; + } + + // Do we have a valid alert for all given idSites? + foreach ($idSites as $idSite) { + if (!$this->isValidAlert($alert, $idSites)) { + throw new Exception(Piwik::translate('CustomAlerts_ReportOrMetricIsInvalid')); + } + } + + $db->update(Common::prefixTable('alert'), $alert, "idalert = " . $idAlert); + + $db->query("DELETE FROM " . Common::prefixTable("alert_site") . " + WHERE idalert = ?", $idAlert); + + foreach ($idSites as $idSite) { + $db->insert(Common::prefixTable('alert_site'), array( + 'idalert' => $idAlert, + 'idsite' => $idSite + )); + } + return $idAlert; + } + + /** + * Delete alert by id. + * + * @param int $idAlert + */ + public function deleteAlert($idAlert) + { + $alert = $this->getAlert($idAlert); + + if (!$alert) { + throw new Exception(Piwik::translate('CustomAlerts_AlertDoesNotExist', $idAlert)); + } + + Piwik::checkUserIsSuperUserOrTheUser($alert['login']); + + $db = Db::get(); + $db->update( + Common::prefixTable('alert'), + array("deleted" => 1), + "idalert = " . $idAlert + ); + } + + /** + * Checks whether a report + metric exists for + * the given idSites and if the a dimension is + * given (requires report_condition, report_matched) + * + * @param array alert + * @param int $idSite + * @return boolean + */ + private function isValidAlert($alert, $idSite) + { + list($module, $action) = explode(".", $alert['report']); + + $report = MetadataApi::getInstance()->getMetadata($idSite, $module, $action); + + // If there is no report matching module + action for idSite it's not valid. + if(count($report) == 0) { + return false; + } + + // Merge all available metrics + $allMetrics = $report[0]['metrics']; + if (isset($report[0]['processedMetrics'])) { + $allMetrics = array_merge($allMetrics, $report[0]['processedMetrics']); + } + if (isset($report[0]['metricsGoal'])) { + $allMetrics = array_merge($allMetrics, $report[0]['metricsGoal']); + } + if (isset($report[0]['processedMetricsGoal'])) { + $allMetrics = array_merge($allMetrics, $report[0]['processedMetricsGoal']); + } + + if (!in_array($alert['metric'], array_keys($allMetrics))) { + return false; + } + + // If we have a dimension, we need to check if + // report_condition and report_matched is given. + if (isset($report[0]['dimension']) + && (!isset($alert['report_condition']) || !isset($alert['report_matched']))) { + return false; + } else { + return true; + } + + return false; + } + + private function checkName($name) + { + return urldecode($name); + } + + private function checkPeriod($period) + { + if (!$this->isValidPeriod($period)) { + throw new Exception(Piwik::translate('CustomAlerts_InvalidPeriod')); + } + } + + private function isValidPeriod($period) + { + return in_array($period, array('day', 'week', 'month', 'year')); + } + +} +?> \ No newline at end of file diff --git a/plugins/CustomAlerts/Controller.php b/plugins/CustomAlerts/Controller.php new file mode 100755 index 00000000000..666262ac43c --- /dev/null +++ b/plugins/CustomAlerts/Controller.php @@ -0,0 +1,118 @@ + 'matches_exactly', + 'CustomAlerts_DoesNotMatchExactly' => 'does_not_match_exactly', + 'CustomAlerts_MatchesRegularExpression' => 'matches_regex', + 'CustomAlerts_DoesNotMatchRegularExpression' => 'does_not_match_regex', + 'CustomAlerts_Contains' => 'contains', + 'CustomAlerts_DoesNotContain' => 'does_not_contain', + 'CustomAlerts_StartsWith' => 'starts_with', + 'CustomAlerts_DoesNotStartWith' => 'does_not_start_with', + 'CustomAlerts_EndsWith' => 'ends_with', + 'CustomAlerts_DoesNotEndWith' => 'does_not_end_with', + ); + private $alertMetricConditions = array( + 'CustomAlerts_IsLessThan' => 'less_than', + 'CustomAlerts_IsGreaterThan' => 'greater_than', + 'CustomAlerts_DecreasesMoreThan' => 'decrease_more_than', + 'CustomAlerts_IncreasesMoreThan' => 'increase_more_than', + 'CustomAlerts_PercentageDecreasesMoreThan' => 'percentage_decrease_more_than', + 'CustomAlerts_PercentageIncreasesMoreThan' => 'percentage_increase_more_than', + ); + + /** + * Shows all Alerts of the current selected idSite. + */ + public function index() + { + $view = new View('@CustomAlerts/index'); + $this->setGeneralVariablesView($view); + + $idSite = Common::getRequestVar('idSite'); + + $alertList = API::getInstance()->getAlerts(array($idSite)); + + $view->alertList = $alertList; + + return $view->render(); + } + + public function addNewAlert() + { + $view = new View('@CustomAlerts/addNewAlert'); + $this->setGeneralVariablesView($view); + + $sitesList = SitesManagerApi::getInstance()->getSitesWithAtLeastViewAccess(); + $view->sitesList = $sitesList; + + $availableReports = MetadataApi::getInstance()->getReportMetadata(); + + // ToDo need to collect metrics,processedMetrics,goalMetrics, goalProcessedMetric + + $view->alertGroups = array(); + $view->alerts = $availableReports; + $view->alertGroupConditions = $this->alertGroupConditions; + $view->alertMetricConditions = $this->alertMetricConditions; + + return $view->render(); + } + + public function editAlert() + { + $idAlert = Common::getRequestVar('idalert'); + + $view = new View('@CustomAlerts/editAlert'); + $this->setGeneralVariablesView($view); + + $alert = API::getInstance()->getAlert($idAlert); + $view->alert = $alert; + + $sitesList = SitesManagerApi::getInstance()->getSitesWithAtLeastViewAccess(); + $view->sitesList = $sitesList; + + // Fetch sites that the alert was defined on. + $sql = "SELECT idsite FROM ".Common::prefixTable('alert_site')." WHERE idalert = ?"; + $sites = Db::fetchAll($sql, $idAlert, \PDO::FETCH_COLUMN); + $idSites = array(); + foreach ($sites as $site) { + $idSites[] = $site['idsite']; + } + $view->sitesDefined = $idSites; + + $availableReports = MetadataApi::getInstance()->getReportMetadata(); + + $view->alerts = $availableReports; + $view->alertGroupConditions = $this->alertGroupConditions; + $view->alertMetricConditions = $this->alertMetricConditions; + + return $view->render(); + } +} +?> diff --git a/plugins/CustomAlerts/CustomAlerts.php b/plugins/CustomAlerts/CustomAlerts.php new file mode 100755 index 00000000000..4333de2a366 --- /dev/null +++ b/plugins/CustomAlerts/CustomAlerts.php @@ -0,0 +1,371 @@ + 'addTopMenu', + 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks', + 'AssetManager.getJavaScriptFiles' => 'getJsFiles', + 'AssetManager.getStylesheetFiles' => 'getCssFiles', + ); + } + + public function getJsFiles(&$jsFiles) + { + $jsFiles[] = "plugins/CustomAlerts/javascripts/ui.dropdownchecklist.js"; + } + + public function getCssFiles(&$cssFiles) + { + $cssFiles[] = "plugins/CustomAlerts/stylesheets/ui.dropdownchecklist.css"; + } + + public function install() + { + $tableAlert = "CREATE TABLE " . Common::prefixTable('alert') . " ( + `idalert` INT NOT NULL PRIMARY KEY , + `name` VARCHAR(100) NOT NULL , + `login` VARCHAR(100) NOT NULL , + `period` VARCHAR(5) NOT NULL , + `report` VARCHAR(150) NOT NULL , + `report_condition` VARCHAR(50) , + `report_matched` VARCHAR(255) , + `metric` VARCHAR(150) NOT NULL , + `metric_condition` VARCHAR(50) NOT NULL , + `metric_matched` FLOAT NOT NULL , + `enable_mail` BOOLEAN NOT NULL , + `deleted` BOOLEAN NOT NULL + ) DEFAULT CHARSET=utf8 ;"; + + $tableAlertSite = "CREATE TABLE " . Common::prefixTable('alert_site') . "( + `idalert` INT( 11 ) NOT NULL , + `idsite` INT( 11 ) NOT NULL , + PRIMARY KEY ( idalert, idsite ) + ) DEFAULT CHARSET=utf8 ;"; + + $tableAlertLog = "CREATE TABLE " . Common::prefixTable('alert_log') . " ( + `idalert` INT( 11 ) NOT NULL , + `idsite` INT( 11 ) NOT NULL , + `ts_triggered` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + KEY `ts_triggered` (`ts_triggered`) + )"; + + try { + Db::exec($tableAlert); + Db::exec($tableAlertLog); + Db::exec($tableAlertSite); + } catch (Exception $e) { + // mysql code error 1050:table already exists + // see bug #153 http://dev.piwik.org/trac/ticket/153 + if (!Db::get()->isErrNo($e, '1050')) { + throw $e; + } + } + } + + public function uninstall() + { + $tables = array('alert', 'alert_log', 'alert_site'); + foreach ($tables as $table) { + $sql = "DROP TABLE " . Common::prefixTable($table); + Db::exec($sql); + } + } + + public function addTopMenu() + { + MenuTop::addEntry("Alerts", array("module" => "CustomAlerts", "action" => "index"), true, 9); + } + + public function getScheduledTasks(&$tasks) + { + $tasks[] = new ScheduledTask( + __CLASS__, + 'processDailyAlerts', + null, + ScheduledTime::factory('daily') + ); + + $tasks[] = new ScheduledTask( + __CLASS__, + 'processWeeklyAlerts', + null, + ScheduledTime::factory('weekly') + ); + + $tasks[] = new ScheduledTask( + __CLASS__, + 'processMonthlyAlerts', + null, + ScheduledTime::factory('monthly') + ); + } + + public function processDailyAlerts() + { + $this->processAlerts('day'); + } + + public function processWeeklyAlerts() + { + $this->processAlerts('week'); + } + + public function processMonthlyAlerts() + { + $this->processAlerts('month'); + } + + public function processAlerts($period) + { + $alerts = API::getInstance()->getAllAlerts($period); + + foreach ($alerts as $alert) { + $report = $alert['report']; + $metric = $alert['metric']; + $idSite = $alert['idsite']; + $idAlert = $alert['idalert']; + + $params = array( + "method" => $report, + "format" => "original", + "idSite" => $idSite, + "period" => $period, + "date" => Date::today()->subPeriod(1, $period)->toString() + ); + + // Get the data for the API request + $request = new Piwik\API\Request($params); + $result = $request->process(); + + $metric_one = $this->getMetricFromTable($result, $alert['metric'], $alert['report_condition'], $alert['report_matched']); + + // Do we have data? Continue otherwise. + if (is_null($metric_one)) { + continue; + } + + // Can we already trigger the alert? + switch ($alert['metric_condition']) { + case 'greater_than': + if ($metric_one > floatval($alert['metric_matched'])) { + $this->triggerAlert($idAlert, $idSite); + } + continue; + break; + case 'less_than': + if ($metric_one < floatval($alert['metric_matched'])) { + $this->triggerAlert($idAlert, $idSite); + } + continue; + break; + default: + break; + } + + $params['date'] = Date::today()->subPeriod(2, $period)->toString(); + + // Get the data for the API request + $request = new Piwik\API\Request($params); + $result = $request->process(); + + $metric_two = $this->getMetricFromTable($result, $alert['metric'], $alert['report_condition'], $alert['report_matched']); + + switch ($alert['metric_condition']) { + case 'decrease_more_than': + if (($metric_two - $metric_one) > $alert['metric_matched']) + $this->triggerAlert($idAlert, $idSite); + break; + case 'increase_more_than': + if (($metric_one - $metric_two) > $alert['metric_matched']) + $this->triggerAlert($idAlert, $idSite); + break; + case 'percentage_decrease_more_than': + // ToDo + break; + case 'percentage_increase_more_than': + // ToDo + break; + } + } + + //$this->sendNewAlerts($period); + } + + private function triggerAlert($idAlert, $idSite) + { + $db = Db::get(); + $db->insert( + Common::prefixTable('alert_log'), + array( + 'idalert' => $idAlert, + 'idsite' => $idSite, + 'ts_triggered' => Date::now()->getDatetime() + ) + ); + } + + /** + * + * @param array $dataTable DataTable + * @param string $metric Metric to fetch from row. + * @param string $filterCond Condition to filter for. + * @param string $filterValue Value to find + */ + private function getMetricFromTable($dataTable, $metric, $filterCond = '', $filterValue = '') + { + // Do we have a condition? Then filter.. + if (!empty($filterValue)) { + + $value = $filterValue; + + $invert = false; + + // Some escaping? + switch ($filterCond) { + case 'matches_exactly': + $pattern = sprintf("^%s$", $value); + break; + case 'matches_regex': + $pattern = $value; + break; + case 'does_not_match_exactly': + $pattern = sprintf("^%s$", $value); + $invert = true; + case 'does_not_match_regex': + $pattern = sprintf("%s", $value); + $invert = true; + break; + case 'contains': + $pattern = $value; + break; + case 'does_not_contain': + $pattern = sprintf("[^%s]", $value); + $invert = true; + break; + case 'starts_with': + $pattern = sprintf("^%s", $value); + break; + case 'does_not_start_with': + $pattern = sprintf("^%s", $value); + $invert = true; + break; + case 'ends_with': + $pattern = sprintf("%s$", $value); + break; + case 'does_not_end_with': + $pattern = sprintf("%s$", $value); + $invert = true; + break; + } + + $dataTable->filter('Pattern', array('label', $pattern, $invert)); + } + + if ($dataTable->getRowsCount() > 1) { + $dataTable->filter('Truncate'); + } + + // ToDo + //$dataTable->filter('AddColumnsProcessedMetrics'); + + $dataRow = $dataTable->getFirstRow(); + + if ($dataRow) { + return $dataRow->getColumn($metric); + } else { + return null; + } + } + + /** + * Sends a list of the triggered alerts to + * $recipient. + * + * @param string $recipient Email address of recipient. + */ + private function sendNewAlerts($period) + { + $triggeredAlerts = API::getInstance()->getTriggeredAlerts($period, Date::today()); + + foreach($triggeredAlerts as $triggeredAlert) { + // collect $triggered[$login] = array(of Alerts) + } + + $mail = new Piwik\Mail(); + $mail->addTo($recipient); + $mail->setSubject('Piwik alert [' . Date::today() . ']'); + + $viewHtml = new Piwik\View('@CustomAlerts/alertHtmlMail'); + $viewHtml->assign('triggeredAlerts', $this->getTriggeredAlerts('html')); + $mail->setBodyHtml($viewHtml->render()); + + $viewText = new Piwik\View('@CustomAlerts/alertTextMail'); + $viewText->assign('triggeredAlerts', $this->getTriggeredAlerts('tsv')); + $viewText->setContentType('text/plain'); + $mail->setBodyText($viewText->render()); + + $mail->send(); + } + + /** + * Returns the Alerts that were triggered in $format. + * + * @param string $format Can be 'html', 'tsv' or empty for php array + */ + private function getTriggeredAlerts($format = null) + { + switch ($format) { + case 'html': + $view = new Piwik\View('@CustomAlerts/htmlTriggeredAlerts'); + $view->triggeredAlerts = $this->triggeredAlerts; + return $view->render(); + break; + case 'tsv': + $tsv = ''; + $showedTitle = false; + foreach ($this->triggeredAlerts as $alert) { + if (!$showedTitle) { + $showedTitle = true; + $tsv .= implode("\t", array_keys($alert)) . "\n"; + } + $tsv .= implode("\t", array_values($alert)) . "\n"; + } + return $tsv; + break; + default: + return $this->triggeredAlerts; + } + } + +} +?> diff --git a/plugins/CustomAlerts/images/dropdown.png b/plugins/CustomAlerts/images/dropdown.png new file mode 100755 index 00000000000..1e86c61e49a Binary files /dev/null and b/plugins/CustomAlerts/images/dropdown.png differ diff --git a/plugins/CustomAlerts/images/dropdown_hover.png b/plugins/CustomAlerts/images/dropdown_hover.png new file mode 100755 index 00000000000..f3bbf75aece Binary files /dev/null and b/plugins/CustomAlerts/images/dropdown_hover.png differ diff --git a/plugins/CustomAlerts/javascripts/ui.dropdownchecklist.js b/plugins/CustomAlerts/javascripts/ui.dropdownchecklist.js new file mode 100755 index 00000000000..98ee0a9cb78 --- /dev/null +++ b/plugins/CustomAlerts/javascripts/ui.dropdownchecklist.js @@ -0,0 +1,487 @@ +;(function($) { + /* + * ui.dropdownchecklist + * + * Copyright (c) 2008-2009 Adrian Tosca + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + */ + // The dropdown check list jQuery plugin transforms a regular select html element into a dropdown check list. + $.widget("ui.dropdownchecklist", { + // Creates the drop container that keeps the items and appends it to the document + _appendDropContainer: function() { + var wrapper = $("
"); + // the container is wrapped in a div + wrapper.addClass("ui-dropdownchecklist-dropcontainer-wrapper"); + // initially hidden + wrapper.css({ + position: 'absolute', + left: "-33000", + top: "-33000px", + width: '3000px', + height: '30000px' + }); + var container = $("
"); // the actual container + container.addClass("ui-dropdownchecklist-dropcontainer") + .css("overflow-y", "auto"); + wrapper.append(container); + $(document.body).append(wrapper); + //wrapper.insertAfter(this.sourceSelect); + // flag that tells if the drop container is shown or not + wrapper.drop = false; + return wrapper; + }, + _isDropDownKeyShortcut: function(e) { + return e.altKey && ($.ui.keyCode.DOWN == (e.keyCode || e.which));// Alt + Down Arrow + }, + _isDroDownCloseKey: function(e) { + return $.ui.keyCode.ESCAPE == (e.keyCode || e.which); + }, + _handleKeyboard: function(e) { + var self = this; + if (self._isDropDownKeyShortcut(e)) { + e.stopPropagation(); + self._toggleDropContainer(); + self.dropWrapper.find("input:first").focus(); + } else if (self.dropWrapper.drop && self._isDroDownCloseKey(e)) { + self._toggleDropContainer(); + } + }, + // Creates the control that will replace the source select and appends it to the document + // The control resembles a regular select with single selection + _appendControl: function() { + var self = this, sourceSelect = this.sourceSelect; + + // the controls is wrapped in a span with inline-block display + var wrapper = $(""); + wrapper.addClass("ui-dropdownchecklist-wrapper"); + wrapper.css({ + display: "inline-block", + cursor: "default" + }); + + // the actual control, can be styled to set the border and drop right image + var control = $(""); + control.addClass("ui-dropdownchecklist"); + control.css({ + display: "inline-block" + }); + control.attr("tabIndex", 0); + control.keyup(function(e) { + self._handleKeyboard(e) + }); + wrapper.append(control); + + // the text container keeps the control text that is build from the selected (checked) items + var textContainer = $(""); + textContainer.addClass("ui-dropdownchecklist-text") + textContainer.css({ + display: "inline-block", + overflow: "hidden" + }); + control.append(textContainer); + + // add the hover styles to the control + wrapper.hover(function() { + if (!self.disabled) { + control.toggleClass("ui-dropdownchecklist-hover") + } + }, function() { + if (!self.disabled) { + control.toggleClass("ui-dropdownchecklist-hover") + } + }); + + // clicking on the control toggles the drop container + wrapper.click(function(event) { + if (!self.disabled) { + event.stopPropagation(); + self._toggleDropContainer(); + } + }) + + wrapper.insertAfter(sourceSelect); + + $(window).resize(function() { + if (!self.disabled && self.dropWrapper.drop) { + self._toggleDropContainer(); + } + }) + + return wrapper; + }, + // Creates a drop item that coresponds to an option element in the source select + _createDropItem: function(index, value, text, checked, disabled, indent) { + var self = this; + // the item contains a div that contains a checkbox input and a span for text + // the div + var item = $("
"); + item.addClass("ui-dropdownchecklist-item"); + item.css({ + whiteSpace: "nowrap" + }); + var checkedString = checked ? ' checked="checked"' : ''; + var disabledString = disabled ? ' disabled="disabled"' : ''; + var idBase = (self.sourceSelect.attr("id") || "ddcl"); + var id = idBase + index; + var checkBox; + if (self.initialMultiple) { // the checkbox + checkBox = $(''); + } else { // the radiobutton + checkBox = $(''); + } + checkBox = checkBox.attr("index", index).val(value); + item.append(checkBox); + // the text + var label = $("