Add label form field
authorMatthias Schmidt <gravatronics@live.com>
Tue, 26 Mar 2019 17:17:16 +0000 (18:17 +0100)
committerMatthias Schmidt <gravatronics@live.com>
Tue, 26 Mar 2019 17:17:16 +0000 (18:17 +0100)
See #2509

com.woltlab.wcf/templates/__labelFormField.tpl [new file with mode: 0644]
syncTemplates.json
wcfsetup/install/files/acp/templates/__labelFormField.tpl [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Label.js [new file with mode: 0644]
wcfsetup/install/files/lib/system/form/builder/field/label/LabelFormField.class.php [new file with mode: 0644]

diff --git a/com.woltlab.wcf/templates/__labelFormField.tpl b/com.woltlab.wcf/templates/__labelFormField.tpl
new file mode 100644 (file)
index 0000000..071acec
--- /dev/null
@@ -0,0 +1,46 @@
+{include file='__formFieldHeader'}
+
+<ul class="labelList jsOnly" data-object-id="{@$field->getLabelGroup()->groupID}">
+       <li class="dropdown labelChooser" data-group-id="{@$field->getLabelGroup()->groupID}">
+               <div class="dropdownToggle" data-toggle="labelGroup{@$field->getLabelGroup()->groupID}">
+                       <span class="badge label">{lang}wcf.label.none{/lang}</span>
+               </div>
+               <div class="dropdownMenu">
+                       <ul class="scrollableDropdownMenu">
+                               {foreach from=$field->getLabelGroup() item=label}
+                                       <li data-label-id="{@$label->labelID}">
+                                               <span><span class="badge label{if $label->getClassNames()} {@$label->getClassNames()}{/if}">{lang}{$label->label}{/lang}</span></span>
+                                       </li>
+                               {/foreach}
+                       </ul>
+               </div>
+       </li>
+</ul>
+
+<noscript>
+       <select name="{@$field->getPrefixedId()}[{@$field->getLabelGroup()->groupID}]">
+               {foreach from=$field->getLabelGroup() item=label}
+                       <option value="{@$label->labelID}">{lang}{$label->label}{/lang}</option>
+               {/foreach}
+       </select>
+</noscript>
+
+{js application='wcf' file='WCF.Label' bundle='WCF.Combined'}
+<script data-relocate="true">
+       require(['Dom/Util', 'Language', 'WoltLabSuite/Core/Form/Builder/Field/Label'], function(DomUtil, Language, FormBuilderFieldLabel) {
+               Language.addObject({
+                       'wcf.label.none': '{lang}wcf.label.none{/lang}',
+                       'wcf.label.withoutSelection': '{lang}wcf.label.withoutSelection{/lang}'
+               });
+               
+               new FormBuilderFieldLabel(
+                       '{@$field->getPrefixedId()}',
+                       {if $field->getValue()}{@$field->getValue()}{else}null{/if},
+                       {
+                               forceSelection: {if $field->getLabelGroup()->forceSelection}true{else}false{/if}
+                       }
+               );
+       });
+</script>
+
+{include file='__formFieldFooter'}
index e82e4f6871588e8eeb38161a65a7e53829749be3..f213ae46552e22393bbfe735c71f01964129b5b9 100644 (file)
@@ -21,6 +21,7 @@
     "__formFieldHeader",
     "__iconFormField",
     "__itemListFormField",
+    "__labelFormField",
     "__mediaSetCategoryDialog",
     "__multilineTextFormField",
     "__multiPageCondition",
diff --git a/wcfsetup/install/files/acp/templates/__labelFormField.tpl b/wcfsetup/install/files/acp/templates/__labelFormField.tpl
new file mode 100644 (file)
index 0000000..071acec
--- /dev/null
@@ -0,0 +1,46 @@
+{include file='__formFieldHeader'}
+
+<ul class="labelList jsOnly" data-object-id="{@$field->getLabelGroup()->groupID}">
+       <li class="dropdown labelChooser" data-group-id="{@$field->getLabelGroup()->groupID}">
+               <div class="dropdownToggle" data-toggle="labelGroup{@$field->getLabelGroup()->groupID}">
+                       <span class="badge label">{lang}wcf.label.none{/lang}</span>
+               </div>
+               <div class="dropdownMenu">
+                       <ul class="scrollableDropdownMenu">
+                               {foreach from=$field->getLabelGroup() item=label}
+                                       <li data-label-id="{@$label->labelID}">
+                                               <span><span class="badge label{if $label->getClassNames()} {@$label->getClassNames()}{/if}">{lang}{$label->label}{/lang}</span></span>
+                                       </li>
+                               {/foreach}
+                       </ul>
+               </div>
+       </li>
+</ul>
+
+<noscript>
+       <select name="{@$field->getPrefixedId()}[{@$field->getLabelGroup()->groupID}]">
+               {foreach from=$field->getLabelGroup() item=label}
+                       <option value="{@$label->labelID}">{lang}{$label->label}{/lang}</option>
+               {/foreach}
+       </select>
+</noscript>
+
+{js application='wcf' file='WCF.Label' bundle='WCF.Combined'}
+<script data-relocate="true">
+       require(['Dom/Util', 'Language', 'WoltLabSuite/Core/Form/Builder/Field/Label'], function(DomUtil, Language, FormBuilderFieldLabel) {
+               Language.addObject({
+                       'wcf.label.none': '{lang}wcf.label.none{/lang}',
+                       'wcf.label.withoutSelection': '{lang}wcf.label.withoutSelection{/lang}'
+               });
+               
+               new FormBuilderFieldLabel(
+                       '{@$field->getPrefixedId()}',
+                       {if $field->getValue()}{@$field->getValue()}{else}null{/if},
+                       {
+                               forceSelection: {if $field->getLabelGroup()->forceSelection}true{else}false{/if}
+                       }
+               );
+       });
+</script>
+
+{include file='__formFieldFooter'}
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Label.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/Label.js
new file mode 100644 (file)
index 0000000..ef136f7
--- /dev/null
@@ -0,0 +1,152 @@
+/**
+ * Handles the JavaScript part of the label form field.
+ * 
+ * @author     Alexander Ebert, Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Form/Builder/Field/Label
+ * @since      5.2
+ */
+define(['Core', 'Dom/Util', 'Language', 'Ui/SimpleDropdown'], function(Core, DomUtil, Language, UiSimpleDropdown) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function FormBuilderFieldLabel(fielId, labelId, options) {
+               this.init(fielId, labelId, options);
+       };
+       FormBuilderFieldLabel.prototype = {
+               /**
+                * Initializes the label form field.
+                * 
+                * @param       {string}        fieldId         id of the relevant form builder field
+                * @param       {integer}       labelId         id of the currently selected label
+                * @param       {object}        options         additional label options
+                */
+               init: function(fieldId, labelId, options) {
+                       this._formFieldContainer = elById(fieldId + 'Container');
+                       this._labelChooser = elByClass('labelChooser', this._formFieldContainer)[0];
+                       this._options = Core.extend({
+                               forceSelection: false,
+                               showWithoutSelection: false
+                       }, options);
+                       
+                       this._input = elCreate('input');
+                       this._input.type = 'hidden';
+                       this._input.name = fieldId;
+                       this._input.value = ~~labelId;
+                       this._formFieldContainer.appendChild(this._input);
+                       
+                       var labelChooserId = DomUtil.identify(this._labelChooser);
+                       
+                       // init dropdown
+                       var dropdownMenu = UiSimpleDropdown.getDropdownMenu(labelChooserId);
+                       if (dropdownMenu === null) {
+                               UiSimpleDropdown.init(elByClass('dropdownToggle', this._labelChooser)[0]);
+                               dropdownMenu = UiSimpleDropdown.getDropdownMenu(labelChooserId);
+                       }
+                       
+                       var additionalOptionList = null;
+                       if (this._options.showWithoutSelection || !this._options.forceSelection) {
+                               additionalOptionList = elCreate('ul');
+                               dropdownMenu.appendChild(additionalOptionList);
+                               
+                               var dropdownDivider = elCreate('li');
+                               dropdownDivider.className = 'dropdownDivider';
+                               additionalOptionList.appendChild(dropdownDivider);
+                       }
+                       
+                       if (this._options.showWithoutSelection) {
+                               var listItem = elCreate('li');
+                               elData(listItem, 'label-id', -1);
+                               this._blockScroll(listItem);
+                               additionalOptionList.appendChild(listItem);
+                               
+                               var span = elCreate('span');
+                               listItem.appendChild(span);
+                               
+                               var label = elCreate('span');
+                               label.className = 'badge label';
+                               label.innerHTML = Language.get('wcf.label.withoutSelection');
+                               span.appendChild(label);
+                       }
+                       
+                       if (!this._options.forceSelection) {
+                               var listItem = elCreate('li');
+                               elData(listItem, 'label-id', 0);
+                               this._blockScroll(listItem);
+                               additionalOptionList.appendChild(listItem);
+                               
+                               var span = elCreate('span');
+                               listItem.appendChild(span);
+                               
+                               var label = elCreate('span');
+                               label.className = 'badge label';
+                               label.innerHTML = Language.get('wcf.label.none');
+                               span.appendChild(label);
+                       }
+                       
+                       elBySelAll('li:not(.dropdownDivider)', dropdownMenu, function(listItem) {
+                               listItem.addEventListener('click', this._click.bind(this));
+                               
+                               if (labelId) {
+                                       if (~~elData(listItem, 'label-id') === labelId) {
+                                               this._selectLabel(listItem);
+                                       }
+                               }
+                       }.bind(this));
+               },
+               
+               /**
+                * Blocks page scrolling for the given element.
+                * 
+                * @param       {HTMLElement}           element
+                */
+               _blockScroll: function(element) {
+                       element.addEventListener(
+                               'wheel',
+                               function(event) {
+                                       event.preventDefault();
+                               },
+                               {
+                                       passive: false
+                               }
+                       );
+               },
+               
+               /**
+                * Select a label after clicking on it.
+                * 
+                * @param       {Event}         event   click event in label selection dropdown
+                */
+               _click: function(event) {
+                       event.preventDefault();
+                       
+                       this._selectLabel(event.currentTarget, false);
+               },
+               
+               /**
+                * Selects the given label.
+                * 
+                * @param       {HTMLElement}   label
+                */
+               _selectLabel: function(label) {
+                       // save label
+                       var labelId = elData(label, 'label-id');
+                       if (!labelId) {
+                               labelId = 0;
+                       }
+                       
+                       // replace button with currently selected label
+                       var displayLabel = elBySel('span > span', label);
+                       var button = elBySel('.dropdownToggle > span', this._labelChooser);
+                       button.className = displayLabel.className;
+                       button.textContent = displayLabel.textContent;
+                       
+                       this._input.value = labelId;
+               }
+       };
+       
+       return FormBuilderFieldLabel;
+});
diff --git a/wcfsetup/install/files/lib/system/form/builder/field/label/LabelFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/label/LabelFormField.class.php
new file mode 100644 (file)
index 0000000..3707156
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+namespace wcf\system\form\builder\field\label;
+use wcf\data\IStorableObject;
+use wcf\data\label\group\ViewableLabelGroup;
+use wcf\data\label\Label;
+use wcf\system\form\builder\field\AbstractFormField;
+use wcf\system\form\builder\field\data\processor\CustomFormFieldDataProcessor;
+use wcf\system\form\builder\field\validation\FormFieldValidationError;
+use wcf\system\form\builder\IFormDocument;
+use wcf\system\form\builder\IObjectTypeFormNode;
+use wcf\system\form\builder\TObjectTypeFormNode;
+use wcf\system\label\LabelHandler;
+
+/**
+ * Implementation of a form field to select labels.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2019 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Form\Builder\Field
+ * @since      5.2
+ */
+class LabelFormField extends AbstractFormField implements IObjectTypeFormNode {
+       use TObjectTypeFormNode;
+       
+       /**
+        * label group whose labels can be selected via this form field
+        * @var ViewableLabelGroup
+        */
+       protected $labelGroup;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $templateName = '__labelFormField';
+       
+       /**
+        * loaded labels grouped by label object type and object id to avoid loading the same labels
+        * over and over again for the same object and different label groups
+        * @var Label[][]
+        */
+       protected static $loadedLabels = [];
+       
+       /**
+        * Returns the label group whose labels can be selected via this form field.
+        * 
+        * @return      ViewableLabelGroup              label group whose labels can be selected
+        * @throws      \BadMethodCallException         if no label has been set
+        */
+       public function getLabelGroup() {
+               if ($this->labelGroup === null) {
+                       throw new \BadMethodCallException("No label group has been set.");
+               }
+               
+               return $this->labelGroup;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObjectTypeDefinition() {
+               return 'com.woltlab.wcf.label.object';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function hasSaveValue() {
+               return false;
+       }
+       
+       /**
+        * Sets the label group whose labels can be selected via this form field and returns this
+        * form field.
+        * 
+        * If no form field label has been set, the title of the label group will be set as label.
+        * 
+        * @param       ViewableLabelGroup      $labelGroup     label group whose labels can be selected
+        * @return      static                                  this form field
+        */
+       public function labelGroup(ViewableLabelGroup $labelGroup) {
+               $this->labelGroup = $labelGroup;
+               
+               if ($this->label === null) {
+                       $this->label($this->getLabelGroup()->getTitle());
+               }
+               
+               return $this;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function loadValueFromObject(IStorableObject $object) {
+               $objectTypeID = $this->getObjectType()->objectTypeID;
+               $objectID = $object->getObjectID();
+               
+               if (!isset(static::$loadedLabels[$objectTypeID])) {
+                       static::$loadedLabels[$objectTypeID] = [];
+               }
+               if (!isset(static::$loadedLabels[$objectTypeID][$objectID])) {
+                       static::$loadedLabels[$objectTypeID][$objectID] = LabelHandler::getInstance()->getAssignedLabels(
+                               $objectTypeID,
+                               [$objectID]
+                       )[$objectID];
+               }
+               
+               $labelIDs = $this->getLabelGroup()->getLabelIDs();
+               /** @var Label $label */
+               foreach (static::$loadedLabels[$objectTypeID][$objectID] as $label) {
+                       if (in_array($label->labelID, $labelIDs)) {
+                               $this->value($label->labelID);
+                       }
+               }
+               
+               return $this;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function populate() {
+               parent::populate();
+               
+               $this->getDocument()->getDataHandler()->add(new CustomFormFieldDataProcessor('label', function(IFormDocument $document, array $parameters) {
+                       $value = $this->getValue();
+                       
+                       // `-1` and `0` are special values that are irrlevent for saving
+                       if ($value > 0) {
+                               if (!isset($parameters[$this->getObjectProperty()])) {
+                                       $parameters[$this->getObjectProperty()] = [];
+                               }
+                               
+                               $parameters[$this->getObjectProperty()][$this->getLabelGroup()->groupID] = $value;
+                       }
+                       
+                       return $parameters;
+               }));
+               
+               return $this;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readValue() {
+               if ($this->getDocument()->hasRequestData($this->getPrefixedId())) {
+                       $this->value = intval($this->getDocument()->getRequestData($this->getPrefixedId()));
+               }
+               
+               return $this;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               if ($this->isRequired()) {
+                       if ($this->value <= 0) {
+                               $this->addValidationError(new FormFieldValidationError('empty'));
+                       }
+               }
+               else if ($this->value > 0 && !in_array($this->value, $this->getLabelGroup()->getLabelIDs())) {
+                       $this->addValidationError(new FormFieldValidationError(
+                               'invalidValue',
+                               'wcf.global.form.error.noValidSelection'
+                       ));
+               }
+       }
+       
+       /**
+        * Returns label group fields based for the given label groups using the given object property.
+        * 
+        * The id of each form fields is `{$objectProperty}{$labelGroupID}`.
+        * 
+        * @param       string                  $objectType             `com.woltlab.wcf.label.object` object type
+        * @param       ViewableLabelGroup[]    $labelGroups            label groups for which label form fields are created
+        * @param       string                  $objectProperty         object property of form fields
+        * @return      static[]
+        */
+       public static function createFields($objectType, array $labelGroups, $objectProperty = 'labelIDs') {
+               $formFields = [];
+               foreach ($labelGroups as $labelGroup) {
+                       $formFields[] = static::create($objectProperty . $labelGroup->groupID)
+                                       ->objectProperty($objectProperty)
+                                       ->objectType($objectType)
+                                       ->required($labelGroup->forceSelection)
+                                       ->labelGroup($labelGroup);
+               }
+               
+               return $formFields;
+       }
+}