WCF.Dropdown now supports two alignments, left and right (relative to the outer element bounding). If you're using the HTML of dropdowns but no the JS (e.g. custom handler), don't forget to use WCF.Dropdown.setAlignment()!
}, this));
}
});
+
+/**
+ * Provides the search dropdown for ACP
+ *
+ * @see WCF.Search.Base
+ */
+WCF.ACP.Search = WCF.Search.Base.extend({
+ /**
+ * @see WCF.Search.Base.init()
+ */
+ init: function() {
+ this._className = 'wcf\\data\\acp\\search\\provider\\ACPSearchProviderAction';
+ this._super('#search input[name=q]');
+ },
+
+ /**
+ * @see WCF.Search.Base._createListItem()
+ */
+ _createListItem: function(resultList) {
+ // add a divider between result lists
+ if (this._list.children('li').length > 0) {
+ $('<li class="dropdownDivider" />').appendTo(this._list);
+ }
+
+ // add caption
+ $('<li class="dropdownText">' + resultList.title + '</li>').appendTo(this._list);
+
+ // add menu items
+ for (var $i in resultList.items) {
+ var $item = resultList.items[$i];
+
+ $('<li><a href="' + $item.link + '">' + $item.title + '</a></li>').appendTo(this._list);
+ }
+ }
+});
WCF.Dropdown.init();
+ new WCF.ACP.Search();
+
{event name='javascriptInit'}
});
//]]>
else if ($containerID === $targetID) {
$dropdown.addClass('dropdownOpen');
this._notifyCallbacks($dropdown, 'open');
+
+ this.setAlignment($dropdown);
}
}
return false;
},
+ /**
+ * Sets alignment for dropdown.
+ *
+ * @param jQuery dropdown
+ * @param jQuery dropdownMenu
+ */
+ setAlignment: function(dropdown, dropdownMenu) {
+ if (dropdown) {
+ var $dropdownMenu = dropdown.children('.dropdownMenu:eq(0)');
+ }
+ else {
+ var $dropdownMenu = dropdownMenu;
+ }
+
+ // calculate if dropdown should be right-aligned if there is not enough space
+ var $dimensions = $dropdownMenu.getDimensions('outer');
+ var $offsets = $dropdownMenu.getOffsets('offset');
+ var $windowWidth = $(window).width();
+
+ if (($offsets.left + $dimensions.width) > $windowWidth) {
+ $dropdownMenu.css({
+ left: 'auto',
+ right: '0px'
+ }).addClass('dropdownArrowRight');
+ }
+ else {
+ $dropdownMenu.css({
+ left: '0px',
+ right: 'auto'
+ }).removeClass('dropdownArrowRight');
+ }
+ },
+
/**
* Closes all dropdowns.
*/
if (excludedSearchValues) {
this._excludedSearchValues = excludedSearchValues;
}
- this._searchInput = $(searchInput).keyup($.proxy(this._keyUp, this));
- this._searchInput.wrap('<span class="dropdown" />');
+
+ this._searchInput = $(searchInput);
+ if (!this._searchInput.length) {
+ console.debug("[WCF.Search.Base] Selector '" + searchInput + "' for search input is invalid, aborting.");
+ return;
+ }
+
+ this._searchInput.keyup($.proxy(this._keyUp, this)).wrap('<span class="dropdown" />');
this._list = $('<ul class="dropdownMenu" />').insertAfter(this._searchInput);
this._commaSeperated = (commaSeperated) ? true : false;
this._oldSearchString = [ ];
}
this._list.parent().addClass('dropdownOpen');
+ WCF.Dropdown.setAlignment(undefined, this._list);
WCF.CloseOverlayHandler.addCallback('WCF.Search.Base', $.proxy(function() { this._clearList(true); }, this));
},
<?php
namespace wcf\data\acp\search\provider;
use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\UserInputException;
+use wcf\system\search\acp\ACPSearchHandler;
+use wcf\util\StringUtil;
/**
* Executes ACP search provider-related actions.
* @subpackage data.acp.search.provider
* @category Community Framework
*/
-class ACPSearchProviderAction extends AbstractDatabaseObjectAction { }
+class ACPSearchProviderAction extends AbstractDatabaseObjectAction {
+ public function validateGetList() {
+ $this->parameters['data']['searchString'] = (isset($this->parameters['data']['searchString'])) ? StringUtil::trim($this->parameters['data']['searchString']) : '';
+ if (empty($this->parameters['data']['searchString'])) {
+ throw new UserInputException('searchString');
+ }
+ }
+
+ public function getList() {
+ $data = array();
+ $results = ACPSearchHandler::getInstance()->search($this->parameters['data']['searchString']);
+
+ foreach ($results as $resultList) {
+ $items = array();
+ foreach ($resultList as $item) {
+ $items[] = array(
+ 'link' => $item->getLink(),
+ 'title' => $item->getTitle()
+ );
+ }
+
+ $data[] = array(
+ 'items' => $items,
+ 'title' => $resultList->getTitle()
+ );
+ }
+
+ return $data;
+ }
+}
* @category Community Framework
*/
class ACPSearchHandler extends SingletonFactory {
+ /**
+ * list of application abbreviations
+ * @var array<string>
+ */
+ public $abbreviations = array();
+
/**
* list of acp search provider
* @var array<wcf\data\acp\search\provider\ACPSearchProvider>
foreach ($this->cache as $acpSearchProvider) {
$className = $acpSearchProvider->className;
- if (!ClassUtil::isInstanceOf($className, 'wcf\system\search\acp\IACPSearchProvider')) {
- throw new SystemException("Class '".$className."' does not implement the interface 'wcf\system\search\acp\IACPSearchProvider'");
+ if (!ClassUtil::isInstanceOf($className, 'wcf\system\search\acp\IACPSearchResultProvider')) {
+ throw new SystemException("Class '".$className."' does not implement the interface 'wcf\system\search\acp\IACPSearchResultProvider'");
}
$provider = new $className();
$results = $provider->search($query, $maxResultsPerProvider);
if (!empty($results)) {
- $resultList = new ACPSearchResultList();
+ $resultList = new ACPSearchResultList($acpSearchProvider->providerName);
foreach ($results as $result) {
$resultList->addResult($result);
}
}
}
+ // sort all result lists
+ foreach ($data as $resultList) {
+ $resultList->sort();
+ }
+
return $data;
}
+
+ /**
+ * Returns a list of application abbreviations.
+ *
+ * @param string $suffix
+ * @return array<string>
+ */
+ public function getAbbreviations($suffix = '') {
+ if (empty($this->abbreviations)) {
+ // append the 'WCF' pseudo application
+ $this->abbreviations[] = 'wcf';
+
+ // get running application
+ $this->abbreviations[] = ApplicationHandler::getInstance()->getAbbreviation(ApplicationHandler::getInstance()->getActiveApplication()->packageID);
+
+ // get dependent applications
+ foreach (ApplicationHandler::getInstance()->getDependentApplications() as $application) {
+ $this->abbreviations[] = ApplicationHandler::getInstance()->getAbbreviation($application->packageID);
+ }
+ }
+
+ if (!empty($suffix)) {
+ $abbreviations = array();
+ foreach ($this->abbreviations as $abbreviation) {
+ $abbreviations[] = $abbreviation . $suffix;
+ }
+
+ return $abbreviations;
+ }
+
+ return $this->abbreviations;
+ }
}
<?php
namespace wcf\system\search\acp;
+use wcf\system\WCF;
/**
* Represents a list of ACP search results.
*/
protected $index = 0;
+ /**
+ * result list title
+ * @var string
+ */
+ protected $title = '';
+
/**
* result list
* @var array<wcf\system\search\acp\ACPSearchResult>
*/
protected $results = array();
+ /**
+ * Creates a new ACPSearchResultList.
+ *
+ * @param string $title
+ */
+ public function __construct($title) {
+ $this->title = WCF::getLanguage()->get('wcf.acp.search.provider.'.$title);
+ }
+
/**
* Adds a result to the collection.
*
});
}
+ /**
+ * Returns the result list title.
+ *
+ * @return string
+ */
+ public function getTitle() {
+ return $this->title;
+ }
+
+ /**
+ * @see wcf\system\search\acp\ACPSearchResultList::getTitle()
+ */
+ public function __toString() {
+ return $this->title;
+ }
+
/**
* @see \Countable::count()
*/
public function count() {
- return count($this->objects);
+ return count($this->results);
}
/**
<?php
namespace wcf\system\search\acp;
-use wcf\system\application\ApplicationHandler;
use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\package\PackageDependencyHandler;
use wcf\system\request\LinkHandler;
// search by language item
$conditions = new PreparedStatementConditionBuilder();
$conditions->add("languageID = ?", array(WCF::getLanguage()->languageID));
- $conditions->add("languageItem LIKE ?", array('wcf.acp.option.%'));
$conditions->add("languageItemValue LIKE ?", array($query.'%'));
$conditions->add("packageID IN (?)", array(PackageDependencyHandler::getInstance()->getDependencies()));
- // get available abbrevations
- $packageIDs = array(ApplicationHandler::getInstance()->getActiveApplication()->packageID);
- foreach (ApplicationHandler::getInstance()->getDependentApplications() as $application) {
- $packageIDs[] = $application->packageID;
+ // filter by language item
+ $languageItemsConditions = '';
+ $languageItemsParameters = array();
+ foreach (ACPSearchHandler::getInstance()->getAbbreviations('.acp.menu.link.%') as $abbreviation) {
+ if (!empty($languageItemsConditions)) $languageItemsConditions .= " OR ";
+ $languageItemsConditions .= "languageItem LIKE ?";
+ $languageItemsParameters[] = $abbreviation;
}
-
- $searchConditions = array();
- $searchString = '';
- foreach ($packageIDs as $packageID) {
- if (!empty($searchString)) {
- $searchString .= " OR ";
- }
-
- $searchString .= "languageItem LIKE ?";
- $searchConditions[] = ApplicationHandler::getInstance()->getAbbrevation($packageID) . '.acp.menu.link.'.$query.'%';
- }
- $conditions->add($searchString, $searchConditions);
+ $conditions->add("(".$languageItemsConditions.")", $languageItemsParameters);
$sql = "SELECT languageItem, languageItemValue
FROM wcf".WCF_N."_language_item
}
if ($this->checkMenuItem($row)) {
- $results[] = new ACPSearchResult($languageItems[$row['menuItem']], $row['menuItemLink']);
+ $results[] = new ACPSearchResult($languageItems[$row['menuItem']], $row['menuItemLink'] . SID_ARG_1ST);
$count++;
}
}
<?php
namespace wcf\system\search\acp;
+use wcf\data\option\category\OptionCategoryList;
use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\SystemException;
use wcf\system\package\PackageDependencyHandler;
use wcf\system\request\LinkHandler;
use wcf\system\WCF;
* @category Community Framework
*/
class OptionACPSearchResultProvider implements IACPSearchResultProvider {
+ /**
+ * list of option categories
+ * @var array<wcf\data\option\category\OptionCategory>
+ */
+ protected $optionCategories = array();
+
+ /**
+ * list of level 1 or 2 categories
+ * @var array<wcf\data\option\category\OptionCategory>
+ */
+ protected $topCategories = array();
+
/**
* @see wcf\system\search\acp\IACPSearchResultProvider::search()
*/
// search by language item
$conditions = new PreparedStatementConditionBuilder();
$conditions->add("languageID = ?", array(WCF::getLanguage()->languageID));
- $conditions->add("languageItem LIKE ?", array('wcf.acp.option.'.$query.'%'));
+ $conditions->add("languageItemValue LIKE ?", array($query.'%'));
$conditions->add("packageID IN (?)", array(PackageDependencyHandler::getInstance()->getDependencies()));
+ // filter by language item
+ $languageItemsConditions = '';
+ $languageItemsParameters = array();
+ foreach (ACPSearchHandler::getInstance()->getAbbreviations('.acp.option.%') as $abbreviation) {
+ if (!empty($languageItemsConditions)) $languageItemsConditions .= " OR ";
+ $languageItemsConditions .= "languageItem LIKE ?";
+ $languageItemsParameters[] = $abbreviation;
+ }
+ $conditions->add("(".$languageItemsConditions.")", $languageItemsParameters);
+
$sql = "SELECT languageItem, languageItemValue
FROM wcf".WCF_N."_language_item
".$conditions."
$languageItems = array();
$optionNames = array();
while ($row = $statement->fetchArray()) {
- $optionName = str_replace('wcf.acp.option.', '', $row['languageItem']);
+ $optionName = preg_replace('~^([a-z]+)\.acp\.option\.~', '', $row['languageItem']);
$languageItems[$optionName] = $row['languageItemValue'];
$optionNames[] = $optionName;
}
$conditions = new PreparedStatementConditionBuilder();
- $conditions->add("option.optionName IN (?)", array($optionNames));
+ $conditions->add("optionName IN (?)", array($optionNames));
- $sql = "SELECT option.optionName, option.categoryName, option_category.categoryID
- FROM wcf".WCF_N."_option option
- LEFT JOIN wcf".WCF_N."_option_category option_category
- ON (option_category.categoryName = option.categoryName)
+ $sql = "SELECT optionName, categoryName
+ FROM wcf".WCF_N."_option
".$conditions;
$statement = WCF::getDB()->prepareStatement($sql);
$statement->execute($conditions->getParameters());
while ($row = $statement->fetchArray()) {
- $link = LinkHandler::getInstance()->getLink('Option', array('id' => $row['categoryID']), '#'.$row['categoryName']);
+ $link = LinkHandler::getInstance()->getLink('Option', array('id' => $this->getCategoryID($row['categoryName'])), 'optionName='.$row['optionName'].'#'.$this->getCategoryName($row['categoryName']));
$results[] = new ACPSearchResult($languageItems[$row['optionName']], $link);
}
return $results;
}
+
+ /**
+ * Returns the primary category id for given category name.
+ *
+ * @param string $categoryName
+ * @return integer
+ */
+ protected function getCategoryID($categoryName) {
+ // get option categories
+ $this->loadCategories();
+
+ if (!isset($this->optionCategories[$categoryName])) {
+ throw new SystemException("Option category '".$categoryName."' is invalid");
+ }
+
+ // use the category id of parent category
+ if ($this->optionCategories[$categoryName]->parentCategoryName != '') {
+ return $this->getCategoryID($this->optionCategories[$categoryName]->parentCategoryName);
+ }
+
+ return $this->optionCategories[$categoryName]->categoryID;
+ }
+
+ /**
+ * Returns a level 1 or 2 category name.
+ *
+ * @param string $categoryName
+ * @return string
+ */
+ protected function getCategoryName($categoryName) {
+ // get option categories
+ $this->loadCategories();
+
+ // load level 1
+ if (empty($this->topCategories)) {
+ foreach ($this->optionCategories as $category) {
+ if ($category->parentCategoryName == '') {
+ $this->topCategories[$category->categoryName] = $category;
+ }
+ }
+
+ // load level 2
+ $secondLevelCategories = array();
+ foreach ($this->optionCategories as $category) {
+ if ($category->parentCategoryName != '' && isset($this->topCategories[$category->parentCategoryName])) {
+ $secondLevelCategories[$category->categoryName] = $category;
+ }
+ }
+
+ $this->topCategories = array_merge($this->topCategories, $secondLevelCategories);
+ }
+
+ if (!isset($this->optionCategories[$categoryName])) {
+ throw new SystemException("Option category '".$categoryName."' is invalid");
+ }
+
+ if (isset($this->topCategories[$categoryName])) {
+ return $categoryName;
+ }
+
+ return $this->getCategoryName($this->optionCategories[$categoryName]->parentCategoryName);
+ }
+
+ /**
+ * Loads option categories.
+ */
+ protected function loadCategories() {
+ if (empty($this->optionCategories)) {
+ $categoryList = new OptionCategoryList();
+ $categoryList->sqlLimit = 0;
+ $categoryList->readObjects();
+
+ foreach ($categoryList as $category) {
+ $this->optionCategories[$category->categoryName] = $category;
+ }
+ }
+ }
}
z-index: 101;
}
+ &.dropdownArrowRight {
+ &:after {
+ left: auto;
+ right: 9px;
+ }
+
+ &:before {
+ left: auto;
+ right: 10px;
+ }
+ }
+
li {
display: block;