/**
* Handles the data to create and edit a poll in a form created via form builder.
*
- * @author Alexander Ebert, Matthias Schmidt
- * @copyright 2001-2020 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Ui/Poll/Editor
- * @since 5.2
+ * @author Alexander Ebert, Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/Poll/Editor
*/
-define([
- 'Core',
- 'Dom/Util',
- 'EventHandler',
- 'EventKey',
- 'Language',
- 'WoltLabSuite/Core/Date/Picker',
- 'WoltLabSuite/Core/Ui/Sortable/List'
-], function (Core, DomUtil, EventHandler, EventKey, Language, DatePicker, UiSortableList) {
+define(["require", "exports", "tslib", "../../Core", "../../Language", "../Sortable/List", "../../Event/Handler", "../../Date/Picker"], function (require, exports, tslib_1, Core, Language, List_1, EventHandler, DatePicker) {
"use strict";
- function UiPollEditor(containerId, pollOptions, wysiwygId, options) {
- this.init(containerId, pollOptions, wysiwygId, options);
- }
- UiPollEditor.prototype = {
- /**
- * Initializes the poll editor.
- *
- * @param {string} containerId id of the poll options container
- * @param {object[]} pollOptions existing poll options
- * @param {string} wysiwygId id of the related wysiwyg editor
- * @param {object} options additional poll options
- */
- init: function (containerId, pollOptions, wysiwygId, options) {
- this._container = elById(containerId);
- if (this._container === null) {
+ Core = tslib_1.__importStar(Core);
+ Language = tslib_1.__importStar(Language);
+ List_1 = tslib_1.__importDefault(List_1);
+ EventHandler = tslib_1.__importStar(EventHandler);
+ DatePicker = tslib_1.__importStar(DatePicker);
+ class UiPollEditor {
+ constructor(containerId, pollOptions, wysiwygId, options) {
+ const container = document.getElementById(containerId);
+ if (container === null) {
throw new Error("Unknown poll editor container with id '" + containerId + "'.");
}
- this._wysiwygId = wysiwygId;
- if (wysiwygId !== '' && elById(wysiwygId) === null) {
+ this.container = container;
+ this.wysiwygId = wysiwygId;
+ if (wysiwygId !== "" && document.getElementById(wysiwygId) === null) {
throw new Error("Unknown wysiwyg field with id '" + wysiwygId + "'.");
}
- this.questionField = elById(this._wysiwygId + 'Poll_question');
- var optionLists = elByClass('sortableList', this._container);
- if (optionLists.length === 0) {
+ this.questionField = document.getElementById(this.wysiwygId + "Poll_question");
+ const optionList = this.container.querySelector(".sortableList");
+ if (optionList === null) {
throw new Error("Cannot find poll options list for container with id '" + containerId + "'.");
}
- this.optionList = optionLists[0];
- this.endTimeField = elById(this._wysiwygId + 'Poll_endTime');
- this.maxVotesField = elById(this._wysiwygId + 'Poll_maxVotes');
- this.isChangeableYesField = elById(this._wysiwygId + 'Poll_isChangeable');
- this.isChangeableNoField = elById(this._wysiwygId + 'Poll_isChangeable_no');
- this.isPublicYesField = elById(this._wysiwygId + 'Poll_isPublic');
- this.isPublicNoField = elById(this._wysiwygId + 'Poll_isPublic_no');
- this.resultsRequireVoteYesField = elById(this._wysiwygId + 'Poll_resultsRequireVote');
- this.resultsRequireVoteNoField = elById(this._wysiwygId + 'Poll_resultsRequireVote_no');
- this.sortByVotesYesField = elById(this._wysiwygId + 'Poll_sortByVotes');
- this.sortByVotesNoField = elById(this._wysiwygId + 'Poll_sortByVotes_no');
- this._optionCount = 0;
- this._options = Core.extend({
+ this.optionList = optionList;
+ this.endTimeField = document.getElementById(this.wysiwygId + "Poll_endTime");
+ this.maxVotesField = document.getElementById(this.wysiwygId + "Poll_maxVotes");
+ this.isChangeableYesField = document.getElementById(this.wysiwygId + "Poll_isChangeable");
+ this.isChangeableNoField = document.getElementById(this.wysiwygId + "Poll_isChangeable_no");
+ this.isPublicYesField = document.getElementById(this.wysiwygId + "Poll_isPublic");
+ this.isPublicNoField = document.getElementById(this.wysiwygId + "Poll_isPublic_no");
+ this.resultsRequireVoteYesField = document.getElementById(this.wysiwygId + "Poll_resultsRequireVote");
+ this.resultsRequireVoteNoField = document.getElementById(this.wysiwygId + "Poll_resultsRequireVote_no");
+ this.sortByVotesYesField = document.getElementById(this.wysiwygId + "Poll_sortByVotes");
+ this.sortByVotesNoField = document.getElementById(this.wysiwygId + "Poll_sortByVotes_no");
+ this.optionCount = 0;
+ this.options = Core.extend({
isAjax: false,
- maxOptions: 20
+ maxOptions: 20,
}, options);
- this._createOptionList(pollOptions || []);
- new UiSortableList({
+ this.createOptionList(pollOptions || []);
+ new List_1.default({
containerId: containerId,
options: {
- toleranceElement: '> div'
- }
+ toleranceElement: "> div",
+ },
});
- if (this._options.isAjax) {
- var events = ['handleError', 'reset', 'submit', 'validate'];
- for (var i = 0, length = events.length; i < length; i++) {
- var event = events[i];
- EventHandler.add('com.woltlab.wcf.redactor2', event + '_' + this._wysiwygId, this['_' + event].bind(this));
- }
+ if (this.options.isAjax) {
+ ["handleError", "reset", "submit", "validate"].forEach((event) => {
+ EventHandler.add("com.woltlab.wcf.redactor2", event + "_" + this.wysiwygId, (...args) => this[event](...args));
+ });
}
else {
- var form = this._container.closest('form');
+ const form = this.container.closest("form");
if (form === null) {
throw new Error("Cannot find form for container with id '" + containerId + "'.");
}
- form.addEventListener('submit', this._submit.bind(this));
- }
- },
- /**
- * Adds an option based on below the option for which the `Add Option` button has
- * been clicked.
- *
- * @param {Event} event icon click event
- */
- _addOption: function (event) {
- event.preventDefault();
- if (this._optionCount === this._options.maxOptions) {
- return false;
+ form.addEventListener("submit", (ev) => this.submit(ev));
}
- this._createOption(undefined, undefined, event.currentTarget.closest('li'));
- },
+ }
/**
- * Creates a new option based on the given data or an empty option if no option data
- * is given.
- *
- * @param {string} optionValue value of the option
- * @param {integer} optionId id of the option
- * @param {Element?} insertAfter optional element after which the new option is added
- * @private
+ * Creates a poll option with the given data or an empty poll option of no data is given.
*/
- _createOption: function (optionValue, optionId, insertAfter) {
- optionValue = optionValue || '';
- optionId = ~~optionId || 0;
- var listItem = elCreate('LI');
- listItem.className = 'sortableNode';
- elData(listItem, 'option-id', optionId);
+ createOption(optionValue, optionId, insertAfter) {
+ optionValue = optionValue || "";
+ optionId = optionId || "0";
+ const listItem = document.createElement("LI");
+ listItem.classList.add("sortableNode");
+ listItem.dataset.optionId = optionId;
if (insertAfter) {
- DomUtil.insertAfter(listItem, insertAfter);
+ insertAfter.insertAdjacentElement("afterend", listItem);
}
else {
this.optionList.appendChild(listItem);
}
- var pollOptionInput = elCreate('div');
- pollOptionInput.className = 'pollOptionInput';
+ const pollOptionInput = document.createElement("div");
+ pollOptionInput.classList.add("pollOptionInput");
listItem.appendChild(pollOptionInput);
- var sortHandle = elCreate('span');
- sortHandle.className = 'icon icon16 fa-arrows sortableNodeHandle';
+ const sortHandle = document.createElement("span");
+ sortHandle.classList.add("icon", "icon16", "fa-arrows", "sortableNodeHandle");
pollOptionInput.appendChild(sortHandle);
// buttons
- var addButton = elCreate('a');
- elAttr(addButton, 'role', 'button');
- elAttr(addButton, 'href', '#');
- addButton.className = 'icon icon16 fa-plus jsTooltip jsAddOption pointer';
- elAttr(addButton, 'title', Language.get('wcf.poll.button.addOption'));
- addButton.addEventListener('click', this._addOption.bind(this));
+ const addButton = document.createElement("a");
+ listItem.setAttribute("role", "button");
+ listItem.setAttribute("href", "#");
+ addButton.classList.add("icon", "icon16", "fa-plus", "jsTooltip", "jsAddOption", "pointer");
+ addButton.setAttribute("title", Language.get("wcf.poll.button.addOption"));
+ addButton.addEventListener("click", () => this.createOption());
pollOptionInput.appendChild(addButton);
- var deleteButton = elCreate('a');
- elAttr(deleteButton, 'role', 'button');
- elAttr(deleteButton, 'href', '#');
- deleteButton.className = 'icon icon16 fa-times jsTooltip jsDeleteOption pointer';
- elAttr(deleteButton, 'title', Language.get('wcf.poll.button.removeOption'));
- deleteButton.addEventListener('click', this._removeOption.bind(this));
+ const deleteButton = document.createElement("a");
+ deleteButton.setAttribute("role", "button");
+ deleteButton.setAttribute("href", "#");
+ deleteButton.classList.add("icon", "icon16", "fa-times", "jsTooltip", "jsDeleteOption", "pointer");
+ deleteButton.setAttribute("title", Language.get("wcf.poll.button.removeOption"));
+ deleteButton.addEventListener("click", (ev) => this.removeOption(ev));
pollOptionInput.appendChild(deleteButton);
// input field
- var optionInput = elCreate('input');
- elAttr(optionInput, 'type', 'text');
+ const optionInput = document.createElement("input");
+ optionInput.type = "text";
optionInput.value = optionValue;
- elAttr(optionInput, 'maxlength', 255);
- optionInput.addEventListener('keydown', this._optionInputKeyDown.bind(this));
- optionInput.addEventListener('click', function () {
+ optionInput.maxLength = 255;
+ optionInput.addEventListener("keydown", (ev) => this.optionInputKeyDown(ev));
+ optionInput.addEventListener("click", () => {
// work-around for some weird focus issue on iOS/Android
- if (document.activeElement !== this) {
- this.focus();
+ if (document.activeElement !== optionInput) {
+ optionInput.focus();
}
});
pollOptionInput.appendChild(optionInput);
if (insertAfter !== null) {
optionInput.focus();
}
- this._optionCount++;
- if (this._optionCount === this._options.maxOptions) {
- elBySelAll('span.jsAddOption', this.optionList, function (icon) {
- icon.classList.remove('pointer');
- icon.classList.add('disabled');
+ this.optionCount++;
+ if (this.optionCount === this.options.maxOptions) {
+ this.optionList.querySelectorAll(".jsAddOption").forEach((icon) => {
+ icon.classList.remove("pointer");
+ icon.classList.add("disabled");
});
}
- },
+ }
/**
- * Adds the given poll option to the option list.
- *
- * @param {object[]} pollOptions data of the added options
+ * Populates the option list with the current options.
*/
- _createOptionList: function (pollOptions) {
- for (var i = 0, length = pollOptions.length; i < length; i++) {
- var option = pollOptions[i];
- this._createOption(option.optionValue, option.optionID);
- }
- // add empty option field to add new options
- if (this._optionCount < this._options.maxOptions) {
- this._createOption();
+ createOptionList(pollOptions) {
+ pollOptions.forEach((option) => {
+ this.createOption(option.optionValue, option.optionID);
+ });
+ if (this.optionCount < this.options.maxOptions) {
+ this.createOption();
}
- },
+ }
/**
- * Handles errors when the data is saved via AJAX.
- *
- * @param {object} data request response data
+ * Handles validation errors returned by Ajax request.
*/
- _handleError: function (data) {
+ handleError(data) {
switch (data.returnValues.fieldName) {
- case this._wysiwygId + 'Poll_endTime':
- case this._wysiwygId + 'Poll_maxVotes':
- var fieldName = data.returnValues.fieldName.replace(this._wysiwygId + 'Poll_', '');
- var small = elCreate('small');
- small.className = 'innerError';
- small.innerHTML = Language.get('wcf.poll.' + fieldName + '.error.' + data.returnValues.errorType);
- var element = elById(data.returnValues.fieldName);
- var errorParent = element.closest('dd');
- DomUtil.prepend(small, element.nextSibling);
+ case this.wysiwygId + "Poll_endTime":
+ case this.wysiwygId + "Poll_maxVotes": {
+ const fieldName = data.returnValues.fieldName.replace(this.wysiwygId + "Poll_", "");
+ const small = document.createElement("small");
+ small.classList.add("innerError");
+ small.innerHTML = Language.get("wcf.poll." + fieldName + ".error." + data.returnValues.errorType);
+ const field = document.getElementById(data.returnValues.fieldName);
+ field.nextSibling.insertAdjacentElement("afterbegin", small);
data.cancel = true;
break;
+ }
}
- },
+ }
/**
- * Adds an empty poll option after the current option when clicking enter.
- *
- * @param {Event} event key event
+ * Adds another option field below the current option field after pressing Enter.
*/
- _optionInputKeyDown: function (event) {
- // ignore every key except for [Enter]
- if (!EventKey.Enter(event)) {
+ optionInputKeyDown(event) {
+ if (event.key !== "Enter") {
return;
}
- Core.triggerEvent(elByClass('jsAddOption', event.currentTarget.parentNode)[0], 'click');
+ const target = event.currentTarget;
+ const addOption = target.parentElement.querySelector(".jsAddOption");
+ Core.triggerEvent(addOption, "click");
event.preventDefault();
- },
+ }
/**
- * Removes a poll option after clicking on the `Remove Option` button.
- *
- * @param {Event} event click event
+ * Removes a poll option after clicking on its deletion button.
*/
- _removeOption: function (event) {
+ removeOption(event) {
event.preventDefault();
- elRemove(event.currentTarget.closest('li'));
- this._optionCount--;
- elBySelAll('span.jsAddOption', this.optionList, function (icon) {
- icon.classList.add('pointer');
- icon.classList.remove('disabled');
- });
- if (this.optionList.length === 0) {
- this._createOption();
+ const button = event.currentTarget;
+ button.closest("li").remove();
+ this.optionCount--;
+ if (this.optionList.childElementCount === 0) {
+ this.createOption();
}
- },
+ else {
+ this.optionList.querySelectorAll(".jsAddOption").forEach((icon) => {
+ icon.classList.add("pointer");
+ icon.classList.remove("disabled");
+ });
+ }
+ }
/**
- * Resets all poll-related form fields.
+ * Resets all poll fields.
*/
- _reset: function () {
- this.questionField.value = '';
- this._optionCount = 0;
- this.optionList.innerHtml = '';
- this._createOption();
+ reset() {
+ this.questionField.value = "";
+ this.optionCount = 0;
+ this.optionList.innerHTML = "";
+ this.createOption();
DatePicker.clear(this.endTimeField);
- this.maxVotesField.value = 1;
+ this.maxVotesField.value = "1";
this.isChangeableYesField.checked = false;
this.isChangeableNoField.checked = true;
this.isPublicYesField.checked = false;
this.resultsRequireVoteNoField.checked = true;
this.sortByVotesYesField.checked = false;
this.sortByVotesNoField.checked = true;
- EventHandler.fire('com.woltlab.wcf.poll.editor', 'reset', {
- pollEditor: this
+ EventHandler.fire("com.woltlab.wcf.poll.editor", "reset", {
+ pollEditor: this,
});
- },
+ }
/**
- * Is called if the form is submitted or before the AJAX request is sent.
- *
- * @param {Event?} event form submit event
+ * Handles the poll data if the form is submitted.
*/
- _submit: function (event) {
- if (this._options.isAjax) {
- event.poll = this.getData();
- EventHandler.fire('com.woltlab.wcf.poll.editor', 'submit', {
+ submit(event) {
+ if (this.options.isAjax) {
+ EventHandler.fire("com.woltlab.wcf.poll.editor", "submit", {
event: event,
- pollEditor: this
+ pollEditor: this,
});
}
else {
- var form = this._container.closest('form');
- var options = this.getOptions();
- for (var i = 0, length = options.length; i < length; i++) {
- var input = elCreate('input');
- elAttr(input, 'type', 'hidden');
- elAttr(input, 'name', this._wysiwygId + 'Poll_options[' + i + ']');
- input.value = options[i];
+ const form = this.container.closest("form");
+ this.getOptions().forEach((option, i) => {
+ const input = document.createElement("input");
+ input.type = "hidden";
+ input.name = `${this.wysiwygId} + 'Poll_options[${i}}]`;
+ input.value = option;
form.appendChild(input);
- }
+ });
}
- },
+ }
/**
- * Is called to validate the poll data.
- *
- * @param {object} data event data
+ * Validates the poll data.
*/
- _validate: function (data) {
- if (this.questionField.value.trim() === '') {
+ validate(data) {
+ if (this.questionField.value.trim() === "") {
return;
}
- var nonEmptyOptionCount = 0;
- for (var i = 0, length = this.optionList.children.length; i < length; i++) {
- var optionInput = elBySel('input[type=text]', this.optionList.children[i]);
- if (optionInput.value.trim() !== '') {
+ let nonEmptyOptionCount = 0;
+ Array.from(this.optionList.children).forEach((listItem) => {
+ const optionInput = listItem.querySelector("input[type=text]");
+ if (optionInput.value.trim() !== "") {
nonEmptyOptionCount++;
}
- }
+ });
if (nonEmptyOptionCount === 0) {
- data.api.throwError(this._container, Language.get('wcf.global.form.error.empty'));
+ data.api.throwError(this.container, Language.get("wcf.global.form.error.empty"));
data.valid = false;
}
else {
- var maxVotes = ~~this.maxVotesField.value;
+ const maxVotes = ~~this.maxVotesField.value;
if (maxVotes && maxVotes > nonEmptyOptionCount) {
- data.api.throwError(this.maxVotesField.parentNode, Language.get('wcf.poll.maxVotes.error.invalid'));
+ data.api.throwError(this.maxVotesField.parentElement, Language.get("wcf.poll.maxVotes.error.invalid"));
data.valid = false;
}
else {
- EventHandler.fire('com.woltlab.wcf.poll.editor', 'validate', {
+ EventHandler.fire("com.woltlab.wcf.poll.editor", "validate", {
data: data,
- pollEditor: this
+ pollEditor: this,
});
}
}
- },
+ }
/**
- * Returns all poll data.
- *
- * @return {object}
+ * Returns the data of the poll.
*/
- getData: function () {
- var data = {};
- data[this.questionField.id] = this.questionField.value;
- data[this._wysiwygId + 'Poll_options'] = this.getOptions();
- data[this.endTimeField.id] = this.endTimeField.value;
- data[this.maxVotesField.id] = this.maxVotesField.value;
- data[this.isChangeableYesField.id] = !!this.isChangeableYesField.checked;
- data[this.isPublicYesField.id] = !!this.isPublicYesField.checked;
- data[this.resultsRequireVoteYesField.id] = !!this.resultsRequireVoteYesField.checked;
- data[this.sortByVotesYesField.id] = !!this.sortByVotesYesField.checked;
- return data;
- },
+ getData() {
+ return {
+ [this.questionField.id]: this.questionField.value,
+ [this.wysiwygId + "Poll_options"]: this.getOptions(),
+ [this.endTimeField.id]: this.endTimeField.value,
+ [this.maxVotesField.id]: this.maxVotesField.value,
+ [this.isChangeableYesField.id]: !!this.isChangeableYesField.checked,
+ [this.isPublicYesField.id]: !!this.isPublicYesField.checked,
+ [this.resultsRequireVoteYesField.id]: !!this.resultsRequireVoteYesField.checked,
+ [this.sortByVotesYesField.id]: !!this.sortByVotesYesField.checked,
+ };
+ }
/**
- * Returns all entered poll options.
+ * Returns the selectable options in the poll.
*
- * @return {string[]}
+ * Format: `{optionID}_{option}` with `optionID = 0` if it is a new option.
*/
- getOptions: function () {
- var options = [];
- for (var i = 0, length = this.optionList.children.length; i < length; i++) {
- var listItem = this.optionList.children[i];
- var optionValue = elBySel('input[type=text]', listItem).value.trim();
- if (optionValue !== '') {
- options.push(elData(listItem, 'option-id') + '_' + optionValue);
+ getOptions() {
+ const options = [];
+ Array.from(this.optionList.children).forEach((listItem) => {
+ const optionValue = listItem.querySelector("input[type=text]").value.trim();
+ if (optionValue !== "") {
+ options.push(`${listItem.dataset.optionId}_${optionValue}`);
}
- }
+ });
return options;
}
- };
+ }
+ Core.enableLegacyInheritance(UiPollEditor);
return UiPollEditor;
});
+"use strict";
/**
* Handles the data to create and edit a poll in a form created via form builder.
- *
- * @author Alexander Ebert, Matthias Schmidt
- * @copyright 2001-2020 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Ui/Poll/Editor
- * @since 5.2
+ *
+ * @author Alexander Ebert, Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/Poll/Editor
*/
-define([
- 'Core',
- 'Dom/Util',
- 'EventHandler',
- 'EventKey',
- 'Language',
- 'WoltLabSuite/Core/Date/Picker',
- 'WoltLabSuite/Core/Ui/Sortable/List'
-], function(
- Core,
- DomUtil,
- EventHandler,
- EventKey,
- Language,
- DatePicker,
- UiSortableList
-) {
- "use strict";
-
- function UiPollEditor(containerId, pollOptions, wysiwygId, options) {
- this.init(containerId, pollOptions, wysiwygId, options);
- }
- UiPollEditor.prototype = {
- /**
- * Initializes the poll editor.
- *
- * @param {string} containerId id of the poll options container
- * @param {object[]} pollOptions existing poll options
- * @param {string} wysiwygId id of the related wysiwyg editor
- * @param {object} options additional poll options
- */
- init: function(containerId, pollOptions, wysiwygId, options) {
- this._container = elById(containerId);
- if (this._container === null) {
- throw new Error("Unknown poll editor container with id '" + containerId + "'.");
- }
-
- this._wysiwygId = wysiwygId;
- if (wysiwygId !== '' && elById(wysiwygId) === null) {
- throw new Error("Unknown wysiwyg field with id '" + wysiwygId + "'.");
- }
-
- this.questionField = elById(this._wysiwygId + 'Poll_question');
-
- var optionLists = elByClass('sortableList', this._container);
- if (optionLists.length === 0) {
- throw new Error("Cannot find poll options list for container with id '" + containerId + "'.");
- }
- this.optionList = optionLists[0];
-
- this.endTimeField = elById(this._wysiwygId + 'Poll_endTime');
- this.maxVotesField = elById(this._wysiwygId + 'Poll_maxVotes');
- this.isChangeableYesField = elById(this._wysiwygId + 'Poll_isChangeable');
- this.isChangeableNoField = elById(this._wysiwygId + 'Poll_isChangeable_no');
- this.isPublicYesField = elById(this._wysiwygId + 'Poll_isPublic');
- this.isPublicNoField = elById(this._wysiwygId + 'Poll_isPublic_no');
- this.resultsRequireVoteYesField = elById(this._wysiwygId + 'Poll_resultsRequireVote');
- this.resultsRequireVoteNoField = elById(this._wysiwygId + 'Poll_resultsRequireVote_no');
- this.sortByVotesYesField = elById(this._wysiwygId + 'Poll_sortByVotes');
- this.sortByVotesNoField = elById(this._wysiwygId + 'Poll_sortByVotes_no');
-
- this._optionCount = 0;
- this._options = Core.extend({
- isAjax: false,
- maxOptions: 20
- }, options);
-
- this._createOptionList(pollOptions || []);
-
- new UiSortableList({
- containerId: containerId,
- options: {
- toleranceElement: '> div'
- }
- });
-
- if (this._options.isAjax) {
- var events = ['handleError', 'reset', 'submit', 'validate'];
- for (var i = 0, length = events.length; i < length; i++) {
- var event = events[i];
-
- EventHandler.add(
- 'com.woltlab.wcf.redactor2',
- event + '_' + this._wysiwygId,
- this['_' + event].bind(this)
- );
- }
- }
- else {
- var form = this._container.closest('form');
- if (form === null) {
- throw new Error("Cannot find form for container with id '" + containerId + "'.");
- }
-
- form.addEventListener('submit', this._submit.bind(this));
- }
- },
-
- /**
- * Adds an option based on below the option for which the `Add Option` button has
- * been clicked.
- *
- * @param {Event} event icon click event
- */
- _addOption: function(event) {
- event.preventDefault();
-
- if (this._optionCount === this._options.maxOptions) {
- return false;
- }
-
- this._createOption(
- undefined,
- undefined,
- event.currentTarget.closest('li')
- );
- },
-
- /**
- * Creates a new option based on the given data or an empty option if no option data
- * is given.
- *
- * @param {string} optionValue value of the option
- * @param {integer} optionId id of the option
- * @param {Element?} insertAfter optional element after which the new option is added
- * @private
- */
- _createOption: function(optionValue, optionId, insertAfter) {
- optionValue = optionValue || '';
- optionId = ~~optionId || 0;
-
- var listItem = elCreate('LI');
- listItem.className = 'sortableNode';
- elData(listItem, 'option-id', optionId);
-
- if (insertAfter) {
- DomUtil.insertAfter(listItem, insertAfter);
- }
- else {
- this.optionList.appendChild(listItem);
- }
-
- var pollOptionInput = elCreate('div');
- pollOptionInput.className = 'pollOptionInput';
- listItem.appendChild(pollOptionInput);
-
- var sortHandle = elCreate('span');
- sortHandle.className = 'icon icon16 fa-arrows sortableNodeHandle';
- pollOptionInput.appendChild(sortHandle);
-
- // buttons
- var addButton = elCreate('a');
- elAttr(addButton, 'role', 'button');
- elAttr(addButton, 'href', '#');
- addButton.className = 'icon icon16 fa-plus jsTooltip jsAddOption pointer';
- elAttr(addButton, 'title', Language.get('wcf.poll.button.addOption'));
- addButton.addEventListener('click', this._addOption.bind(this));
- pollOptionInput.appendChild(addButton);
-
- var deleteButton = elCreate('a');
- elAttr(deleteButton, 'role', 'button');
- elAttr(deleteButton, 'href', '#');
- deleteButton.className = 'icon icon16 fa-times jsTooltip jsDeleteOption pointer';
- elAttr(deleteButton, 'title', Language.get('wcf.poll.button.removeOption'));
- deleteButton.addEventListener('click', this._removeOption.bind(this));
- pollOptionInput.appendChild(deleteButton);
-
- // input field
- var optionInput = elCreate('input');
- elAttr(optionInput, 'type', 'text');
- optionInput.value = optionValue;
- elAttr(optionInput, 'maxlength', 255);
- optionInput.addEventListener('keydown', this._optionInputKeyDown.bind(this));
- optionInput.addEventListener('click', function() {
- // work-around for some weird focus issue on iOS/Android
- if (document.activeElement !== this) {
- this.focus();
- }
- });
- pollOptionInput.appendChild(optionInput);
-
- if (insertAfter !== null) {
- optionInput.focus();
- }
-
- this._optionCount++;
- if (this._optionCount === this._options.maxOptions) {
- elBySelAll('span.jsAddOption', this.optionList, function(icon) {
- icon.classList.remove('pointer');
- icon.classList.add('disabled');
- });
- }
- },
-
- /**
- * Adds the given poll option to the option list.
- *
- * @param {object[]} pollOptions data of the added options
- */
- _createOptionList: function(pollOptions) {
- for (var i = 0, length = pollOptions.length; i < length; i++) {
- var option = pollOptions[i];
- this._createOption(option.optionValue, option.optionID);
- }
-
- // add empty option field to add new options
- if (this._optionCount < this._options.maxOptions) {
- this._createOption();
- }
- },
-
- /**
- * Handles errors when the data is saved via AJAX.
- *
- * @param {object} data request response data
- */
- _handleError: function (data) {
- switch (data.returnValues.fieldName) {
- case this._wysiwygId + 'Poll_endTime':
- case this._wysiwygId + 'Poll_maxVotes':
- var fieldName = data.returnValues.fieldName.replace(this._wysiwygId + 'Poll_', '');
-
- var small = elCreate('small');
- small.className = 'innerError';
- small.innerHTML = Language.get('wcf.poll.' + fieldName + '.error.' + data.returnValues.errorType);
-
- var element = elById(data.returnValues.fieldName);
- var errorParent = element.closest('dd');
-
- DomUtil.prepend(small, element.nextSibling);
-
- data.cancel = true;
- break;
- }
- },
-
- /**
- * Adds an empty poll option after the current option when clicking enter.
- *
- * @param {Event} event key event
- */
- _optionInputKeyDown: function(event) {
- // ignore every key except for [Enter]
- if (!EventKey.Enter(event)) {
- return;
- }
-
- Core.triggerEvent(elByClass('jsAddOption', event.currentTarget.parentNode)[0], 'click');
-
- event.preventDefault();
- },
-
- /**
- * Removes a poll option after clicking on the `Remove Option` button.
- *
- * @param {Event} event click event
- */
- _removeOption: function (event) {
- event.preventDefault();
-
- elRemove(event.currentTarget.closest('li'));
-
- this._optionCount--;
-
- elBySelAll('span.jsAddOption', this.optionList, function(icon) {
- icon.classList.add('pointer');
- icon.classList.remove('disabled');
- });
-
- if (this.optionList.length === 0) {
- this._createOption();
- }
- },
-
- /**
- * Resets all poll-related form fields.
- */
- _reset: function() {
- this.questionField.value = '';
-
- this._optionCount = 0;
- this.optionList.innerHtml = '';
- this._createOption();
-
- DatePicker.clear(this.endTimeField);
-
- this.maxVotesField.value = 1;
- this.isChangeableYesField.checked = false;
- this.isChangeableNoField.checked = true;
- this.isPublicYesField.checked = false;
- this.isPublicNoField.checked = true;
- this.resultsRequireVoteYesField.checked = false;
- this.resultsRequireVoteNoField.checked = true;
- this.sortByVotesYesField.checked = false;
- this.sortByVotesNoField.checked = true;
-
- EventHandler.fire(
- 'com.woltlab.wcf.poll.editor',
- 'reset',
- {
- pollEditor: this
- }
- );
- },
-
- /**
- * Is called if the form is submitted or before the AJAX request is sent.
- *
- * @param {Event?} event form submit event
- */
- _submit: function(event) {
- if (this._options.isAjax) {
- event.poll = this.getData();
-
- EventHandler.fire(
- 'com.woltlab.wcf.poll.editor',
- 'submit',
- {
- event: event,
- pollEditor: this
- }
- );
- }
- else {
- var form = this._container.closest('form');
-
- var options = this.getOptions();
- for (var i = 0, length = options.length; i < length; i++) {
- var input = elCreate('input');
- elAttr(input, 'type', 'hidden');
- elAttr(input, 'name', this._wysiwygId + 'Poll_options[' + i + ']');
- input.value = options[i];
- form.appendChild(input);
- }
- }
- },
-
- /**
- * Is called to validate the poll data.
- *
- * @param {object} data event data
- */
- _validate: function(data) {
- if (this.questionField.value.trim() === '') {
- return;
- }
-
- var nonEmptyOptionCount = 0;
- for (var i = 0, length = this.optionList.children.length; i < length; i++) {
- var optionInput = elBySel('input[type=text]', this.optionList.children[i]);
- if (optionInput.value.trim() !== '') {
- nonEmptyOptionCount++;
- }
- }
-
- if (nonEmptyOptionCount === 0) {
- data.api.throwError(this._container, Language.get('wcf.global.form.error.empty'));
- data.valid = false;
- }
- else {
- var maxVotes = ~~this.maxVotesField.value;
-
- if (maxVotes && maxVotes > nonEmptyOptionCount) {
- data.api.throwError(this.maxVotesField.parentNode, Language.get('wcf.poll.maxVotes.error.invalid'));
- data.valid = false;
- }
- else {
- EventHandler.fire(
- 'com.woltlab.wcf.poll.editor',
- 'validate',
- {
- data: data,
- pollEditor: this
- }
- );
- }
- }
- },
-
- /**
- * Returns all poll data.
- *
- * @return {object}
- */
- getData: function() {
- var data = {};
-
- data[this.questionField.id] = this.questionField.value;
- data[this._wysiwygId + 'Poll_options'] = this.getOptions();
- data[this.endTimeField.id] = this.endTimeField.value;
- data[this.maxVotesField.id] = this.maxVotesField.value;
- data[this.isChangeableYesField.id] = !!this.isChangeableYesField.checked;
- data[this.isPublicYesField.id] = !!this.isPublicYesField.checked;
- data[this.resultsRequireVoteYesField.id] = !!this.resultsRequireVoteYesField.checked;
- data[this.sortByVotesYesField.id] = !!this.sortByVotesYesField.checked;
-
- return data;
- },
-
- /**
- * Returns all entered poll options.
- *
- * @return {string[]}
- */
- getOptions: function() {
- var options = [];
- for (var i = 0, length = this.optionList.children.length; i < length; i++) {
- var listItem = this.optionList.children[i];
- var optionValue = elBySel('input[type=text]', listItem).value.trim();
-
- if (optionValue !== '') {
- options.push(elData(listItem, 'option-id') + '_' + optionValue);
- }
- }
-
- return options;
- }
- };
-
- return UiPollEditor;
-});
+var Core = require("../../Core");
+var Language = require("../../Language");
+var List_1 = require("../Sortable/List");
+var EventHandler = require("../../Event/Handler");
+var DatePicker = require("../../Date/Picker");
+var UiPollEditor = /** @class */ (function () {
+ function UiPollEditor(containerId, pollOptions, wysiwygId, options) {
+ var _this = this;
+ var container = document.getElementById(containerId);
+ if (container === null) {
+ throw new Error("Unknown poll editor container with id '" + containerId + "'.");
+ }
+ this.container = container;
+ this.wysiwygId = wysiwygId;
+ if (wysiwygId !== "" && document.getElementById(wysiwygId) === null) {
+ throw new Error("Unknown wysiwyg field with id '" + wysiwygId + "'.");
+ }
+ this.questionField = document.getElementById(this.wysiwygId + "Poll_question");
+ var optionList = this.container.querySelector(".sortableList");
+ if (optionList === null) {
+ throw new Error("Cannot find poll options list for container with id '" + containerId + "'.");
+ }
+ this.optionList = optionList;
+ this.endTimeField = document.getElementById(this.wysiwygId + "Poll_endTime");
+ this.maxVotesField = document.getElementById(this.wysiwygId + "Poll_maxVotes");
+ this.isChangeableYesField = document.getElementById(this.wysiwygId + "Poll_isChangeable");
+ this.isChangeableNoField = document.getElementById(this.wysiwygId + "Poll_isChangeable_no");
+ this.isPublicYesField = document.getElementById(this.wysiwygId + "Poll_isPublic");
+ this.isPublicNoField = document.getElementById(this.wysiwygId + "Poll_isPublic_no");
+ this.resultsRequireVoteYesField = document.getElementById(this.wysiwygId + "Poll_resultsRequireVote");
+ this.resultsRequireVoteNoField = document.getElementById(this.wysiwygId + "Poll_resultsRequireVote_no");
+ this.sortByVotesYesField = document.getElementById(this.wysiwygId + "Poll_sortByVotes");
+ this.sortByVotesNoField = document.getElementById(this.wysiwygId + "Poll_sortByVotes_no");
+ this.optionCount = 0;
+ this.options = Core.extend({
+ isAjax: false,
+ maxOptions: 20
+ }, options);
+ this.createOptionList(pollOptions || []);
+ new List_1["default"]({
+ containerId: containerId,
+ options: {
+ toleranceElement: "> div"
+ }
+ });
+ if (this.options.isAjax) {
+ ["handleError", "reset", "submit", "validate"].forEach(function (event) {
+ EventHandler.add("com.woltlab.wcf.redactor2", event + "_" + _this.wysiwygId, function () {
+ var args = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ args[_i] = arguments[_i];
+ }
+ return _this[event].apply(_this, args);
+ });
+ });
+ }
+ else {
+ var form = this.container.closest("form");
+ if (form === null) {
+ throw new Error("Cannot find form for container with id '" + containerId + "'.");
+ }
+ form.addEventListener("submit", function (ev) { return _this.submit(ev); });
+ }
+ }
+ /**
+ * Creates a poll option with the given data or an empty poll option of no data is given.
+ */
+ UiPollEditor.prototype.createOption = function (optionValue, optionId, insertAfter) {
+ var _this = this;
+ optionValue = optionValue || "";
+ optionId = optionId || "0";
+ var listItem = document.createElement("LI");
+ listItem.classList.add("sortableNode");
+ listItem.dataset.optionId = optionId;
+ if (insertAfter) {
+ insertAfter.insertAdjacentElement("afterend", listItem);
+ }
+ else {
+ this.optionList.appendChild(listItem);
+ }
+ var pollOptionInput = document.createElement("div");
+ pollOptionInput.classList.add("pollOptionInput");
+ listItem.appendChild(pollOptionInput);
+ var sortHandle = document.createElement("span");
+ sortHandle.classList.add("icon", "icon16", "fa-arrows", "sortableNodeHandle");
+ pollOptionInput.appendChild(sortHandle);
+ // buttons
+ var addButton = document.createElement("a");
+ listItem.setAttribute("role", "button");
+ listItem.setAttribute("href", "#");
+ addButton.classList.add("icon", "icon16", "fa-plus", "jsTooltip", "jsAddOption", "pointer");
+ addButton.setAttribute("title", Language.get("wcf.poll.button.addOption"));
+ addButton.addEventListener("click", function () { return _this.createOption(); });
+ pollOptionInput.appendChild(addButton);
+ var deleteButton = document.createElement("a");
+ deleteButton.setAttribute("role", "button");
+ deleteButton.setAttribute("href", "#");
+ deleteButton.classList.add("icon", "icon16", "fa-times", "jsTooltip", "jsDeleteOption", "pointer");
+ deleteButton.setAttribute("title", Language.get("wcf.poll.button.removeOption"));
+ deleteButton.addEventListener("click", function (ev) { return _this.removeOption(ev); });
+ pollOptionInput.appendChild(deleteButton);
+ // input field
+ var optionInput = document.createElement("input");
+ optionInput.type = "text";
+ optionInput.value = optionValue;
+ optionInput.maxLength = 255;
+ optionInput.addEventListener("keydown", function (ev) { return _this.optionInputKeyDown(ev); });
+ optionInput.addEventListener("click", function () {
+ // work-around for some weird focus issue on iOS/Android
+ if (document.activeElement !== optionInput) {
+ optionInput.focus();
+ }
+ });
+ pollOptionInput.appendChild(optionInput);
+ if (insertAfter !== null) {
+ optionInput.focus();
+ }
+ this.optionCount++;
+ if (this.optionCount === this.options.maxOptions) {
+ this.optionList.querySelectorAll(".jsAddOption").forEach(function (icon) {
+ icon.classList.remove("pointer");
+ icon.classList.add("disabled");
+ });
+ }
+ };
+ /**
+ * Populates the option list with the current options.
+ */
+ UiPollEditor.prototype.createOptionList = function (pollOptions) {
+ var _this = this;
+ pollOptions.forEach(function (option) {
+ _this.createOption(option.optionValue, option.optionID);
+ });
+ if (this.optionCount < this.options.maxOptions) {
+ this.createOption();
+ }
+ };
+ /**
+ * Handles validation errors returned by Ajax request.
+ */
+ UiPollEditor.prototype.handleError = function (data) {
+ switch (data.returnValues.fieldName) {
+ case this.wysiwygId + "Poll_endTime":
+ case this.wysiwygId + "Poll_maxVotes": {
+ var fieldName = data.returnValues.fieldName.replace(this.wysiwygId + "Poll_", "");
+ var small = document.createElement("small");
+ small.classList.add("innerError");
+ small.innerHTML = Language.get("wcf.poll." + fieldName + ".error." + data.returnValues.errorType);
+ var field = document.getElementById(data.returnValues.fieldName);
+ field.nextSibling.insertAdjacentElement("afterbegin", small);
+ data.cancel = true;
+ break;
+ }
+ }
+ };
+ /**
+ * Adds another option field below the current option field after pressing Enter.
+ */
+ UiPollEditor.prototype.optionInputKeyDown = function (event) {
+ if (event.key !== "Enter") {
+ return;
+ }
+ var target = event.currentTarget;
+ var addOption = target.parentElement.querySelector(".jsAddOption");
+ Core.triggerEvent(addOption, "click");
+ event.preventDefault();
+ };
+ /**
+ * Removes a poll option after clicking on its deletion button.
+ */
+ UiPollEditor.prototype.removeOption = function (event) {
+ event.preventDefault();
+ var button = event.currentTarget;
+ button.closest("li").remove();
+ this.optionCount--;
+ if (this.optionList.childElementCount === 0) {
+ this.createOption();
+ }
+ else {
+ this.optionList.querySelectorAll(".jsAddOption").forEach(function (icon) {
+ icon.classList.add("pointer");
+ icon.classList.remove("disabled");
+ });
+ }
+ };
+ /**
+ * Resets all poll fields.
+ */
+ UiPollEditor.prototype.reset = function () {
+ this.questionField.value = "";
+ this.optionCount = 0;
+ this.optionList.innerHTML = "";
+ this.createOption();
+ DatePicker.clear(this.endTimeField);
+ this.maxVotesField.value = "1";
+ this.isChangeableYesField.checked = false;
+ this.isChangeableNoField.checked = true;
+ this.isPublicYesField.checked = false;
+ this.isPublicNoField.checked = true;
+ this.resultsRequireVoteYesField.checked = false;
+ this.resultsRequireVoteNoField.checked = true;
+ this.sortByVotesYesField.checked = false;
+ this.sortByVotesNoField.checked = true;
+ EventHandler.fire("com.woltlab.wcf.poll.editor", "reset", {
+ pollEditor: this
+ });
+ };
+ /**
+ * Handles the poll data if the form is submitted.
+ */
+ UiPollEditor.prototype.submit = function (event) {
+ var _this = this;
+ if (this.options.isAjax) {
+ EventHandler.fire("com.woltlab.wcf.poll.editor", "submit", {
+ event: event,
+ pollEditor: this
+ });
+ }
+ else {
+ var form_1 = this.container.closest("form");
+ this.getOptions().forEach(function (option, i) {
+ var input = document.createElement("input");
+ input.type = "hidden";
+ input.name = _this.wysiwygId + " + 'Poll_options[" + i + "}]";
+ input.value = option;
+ form_1.appendChild(input);
+ });
+ }
+ };
+ /**
+ * Validates the poll data.
+ */
+ UiPollEditor.prototype.validate = function (data) {
+ if (this.questionField.value.trim() === "") {
+ return;
+ }
+ var nonEmptyOptionCount = 0;
+ Array.from(this.optionList.children).forEach(function (listItem) {
+ var optionInput = listItem.querySelector("input[type=text]");
+ if (optionInput.value.trim() !== "") {
+ nonEmptyOptionCount++;
+ }
+ });
+ if (nonEmptyOptionCount === 0) {
+ data.api.throwError(this.container, Language.get("wcf.global.form.error.empty"));
+ data.valid = false;
+ }
+ else {
+ var maxVotes = ~~this.maxVotesField.value;
+ if (maxVotes && maxVotes > nonEmptyOptionCount) {
+ data.api.throwError(this.maxVotesField.parentElement, Language.get("wcf.poll.maxVotes.error.invalid"));
+ data.valid = false;
+ }
+ else {
+ EventHandler.fire("com.woltlab.wcf.poll.editor", "validate", {
+ data: data,
+ pollEditor: this
+ });
+ }
+ }
+ };
+ /**
+ * Returns the data of the poll.
+ */
+ UiPollEditor.prototype.getData = function () {
+ var _a;
+ return _a = {},
+ _a[this.questionField.id] = this.questionField.value,
+ _a[this.wysiwygId + "Poll_options"] = this.getOptions(),
+ _a[this.endTimeField.id] = this.endTimeField.value,
+ _a[this.maxVotesField.id] = this.maxVotesField.value,
+ _a[this.isChangeableYesField.id] = !!this.isChangeableYesField.checked,
+ _a[this.isPublicYesField.id] = !!this.isPublicYesField.checked,
+ _a[this.resultsRequireVoteYesField.id] = !!this.resultsRequireVoteYesField.checked,
+ _a[this.sortByVotesYesField.id] = !!this.sortByVotesYesField.checked,
+ _a;
+ };
+ /**
+ * Returns the selectable options in the poll.
+ *
+ * Format: `{optionID}_{option}` with `optionID = 0` if it is a new option.
+ */
+ UiPollEditor.prototype.getOptions = function () {
+ var options = [];
+ Array.from(this.optionList.children).forEach(function (listItem) {
+ var optionValue = listItem.querySelector("input[type=text]").value.trim();
+ if (optionValue !== "") {
+ options.push(listItem.dataset.optionId + "_" + optionValue);
+ }
+ });
+ return options;
+ };
+ return UiPollEditor;
+}());
+Core.enableLegacyInheritance(UiPollEditor);
+module.exports = UiPollEditor;
--- /dev/null
+/**
+ * Handles the data to create and edit a poll in a form created via form builder.
+ *
+ * @author Alexander Ebert, Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Ui/Poll/Editor
+ */
+
+import * as Core from "../../Core";
+import * as Language from "../../Language";
+import UiSortableList from "../Sortable/List";
+import * as EventHandler from "../../Event/Handler";
+import * as DatePicker from "../../Date/Picker";
+import { DatabaseObjectActionResponse } from "../../Ajax/Data";
+
+interface UiPollEditorOptions {
+ isAjax: boolean;
+ maxOptions: number;
+}
+
+interface PollOption {
+ optionID: string;
+ optionValue: string;
+}
+
+interface AjaxReturnValue {
+ errorType: string;
+ fieldName: string;
+}
+
+interface AjaxResponse extends DatabaseObjectActionResponse {
+ returnValues: AjaxReturnValue;
+}
+
+interface ValidationApi {
+ throwError: (HTMLElement, string) => void;
+}
+
+interface ValidationData {
+ api: ValidationApi;
+ valid: boolean;
+}
+
+class UiPollEditor {
+ private readonly container: HTMLElement;
+ private readonly endTimeField: HTMLInputElement;
+ private readonly isChangeableNoField: HTMLInputElement;
+ private readonly isChangeableYesField: HTMLInputElement;
+ private readonly isPublicNoField: HTMLInputElement;
+ private readonly isPublicYesField: HTMLInputElement;
+ private readonly maxVotesField: HTMLInputElement;
+ private optionCount: number;
+ private readonly options: UiPollEditorOptions;
+ private readonly optionList: HTMLOListElement;
+ private readonly questionField: HTMLInputElement;
+ private readonly resultsRequireVoteNoField: HTMLInputElement;
+ private readonly resultsRequireVoteYesField: HTMLInputElement;
+ private readonly sortByVotesNoField: HTMLInputElement;
+ private readonly sortByVotesYesField: HTMLInputElement;
+ private readonly wysiwygId: string;
+
+ constructor(containerId: string, pollOptions: PollOption[], wysiwygId: string, options: UiPollEditorOptions) {
+ const container = document.getElementById(containerId);
+ if (container === null) {
+ throw new Error("Unknown poll editor container with id '" + containerId + "'.");
+ }
+ this.container = container;
+
+ this.wysiwygId = wysiwygId;
+ if (wysiwygId !== "" && document.getElementById(wysiwygId) === null) {
+ throw new Error("Unknown wysiwyg field with id '" + wysiwygId + "'.");
+ }
+
+ this.questionField = document.getElementById(this.wysiwygId + "Poll_question") as HTMLInputElement;
+
+ const optionList = this.container.querySelector(".sortableList");
+ if (optionList === null) {
+ throw new Error("Cannot find poll options list for container with id '" + containerId + "'.");
+ }
+ this.optionList = optionList as HTMLOListElement;
+
+ this.endTimeField = document.getElementById(this.wysiwygId + "Poll_endTime") as HTMLInputElement;
+ this.maxVotesField = document.getElementById(this.wysiwygId + "Poll_maxVotes") as HTMLInputElement;
+ this.isChangeableYesField = document.getElementById(this.wysiwygId + "Poll_isChangeable") as HTMLInputElement;
+ this.isChangeableNoField = document.getElementById(this.wysiwygId + "Poll_isChangeable_no") as HTMLInputElement;
+ this.isPublicYesField = document.getElementById(this.wysiwygId + "Poll_isPublic") as HTMLInputElement;
+ this.isPublicNoField = document.getElementById(this.wysiwygId + "Poll_isPublic_no") as HTMLInputElement;
+ this.resultsRequireVoteYesField = document.getElementById(
+ this.wysiwygId + "Poll_resultsRequireVote",
+ ) as HTMLInputElement;
+ this.resultsRequireVoteNoField = document.getElementById(
+ this.wysiwygId + "Poll_resultsRequireVote_no",
+ ) as HTMLInputElement;
+ this.sortByVotesYesField = document.getElementById(this.wysiwygId + "Poll_sortByVotes") as HTMLInputElement;
+ this.sortByVotesNoField = document.getElementById(this.wysiwygId + "Poll_sortByVotes_no") as HTMLInputElement;
+
+ this.optionCount = 0;
+
+ this.options = Core.extend(
+ {
+ isAjax: false,
+ maxOptions: 20,
+ },
+ options,
+ ) as UiPollEditorOptions;
+
+ this.createOptionList(pollOptions || []);
+
+ new UiSortableList({
+ containerId: containerId,
+ options: {
+ toleranceElement: "> div",
+ },
+ });
+
+ if (this.options.isAjax) {
+ ["handleError", "reset", "submit", "validate"].forEach((event) => {
+ EventHandler.add("com.woltlab.wcf.redactor2", event + "_" + this.wysiwygId, (...args: unknown[]) =>
+ this[event](...args),
+ );
+ });
+ } else {
+ const form = this.container.closest("form");
+ if (form === null) {
+ throw new Error("Cannot find form for container with id '" + containerId + "'.");
+ }
+
+ form.addEventListener("submit", (ev) => this.submit(ev));
+ }
+ }
+
+ /**
+ * Creates a poll option with the given data or an empty poll option of no data is given.
+ */
+ private createOption(optionValue?: string, optionId?: string, insertAfter?: HTMLElement): void {
+ optionValue = optionValue || "";
+ optionId = optionId || "0";
+
+ const listItem = document.createElement("LI") as HTMLLIElement;
+ listItem.classList.add("sortableNode");
+ listItem.dataset.optionId = optionId;
+
+ if (insertAfter) {
+ insertAfter.insertAdjacentElement("afterend", listItem);
+ } else {
+ this.optionList.appendChild(listItem);
+ }
+
+ const pollOptionInput = document.createElement("div");
+ pollOptionInput.classList.add("pollOptionInput");
+ listItem.appendChild(pollOptionInput);
+
+ const sortHandle = document.createElement("span");
+ sortHandle.classList.add("icon", "icon16", "fa-arrows", "sortableNodeHandle");
+ pollOptionInput.appendChild(sortHandle);
+
+ // buttons
+ const addButton = document.createElement("a");
+ listItem.setAttribute("role", "button");
+ listItem.setAttribute("href", "#");
+ addButton.classList.add("icon", "icon16", "fa-plus", "jsTooltip", "jsAddOption", "pointer");
+ addButton.setAttribute("title", Language.get("wcf.poll.button.addOption"));
+ addButton.addEventListener("click", () => this.createOption());
+ pollOptionInput.appendChild(addButton);
+
+ const deleteButton = document.createElement("a");
+ deleteButton.setAttribute("role", "button");
+ deleteButton.setAttribute("href", "#");
+ deleteButton.classList.add("icon", "icon16", "fa-times", "jsTooltip", "jsDeleteOption", "pointer");
+ deleteButton.setAttribute("title", Language.get("wcf.poll.button.removeOption"));
+ deleteButton.addEventListener("click", (ev) => this.removeOption(ev));
+ pollOptionInput.appendChild(deleteButton);
+
+ // input field
+ const optionInput = document.createElement("input");
+ optionInput.type = "text";
+ optionInput.value = optionValue;
+ optionInput.maxLength = 255;
+ optionInput.addEventListener("keydown", (ev) => this.optionInputKeyDown(ev));
+ optionInput.addEventListener("click", () => {
+ // work-around for some weird focus issue on iOS/Android
+ if (document.activeElement !== optionInput) {
+ optionInput.focus();
+ }
+ });
+ pollOptionInput.appendChild(optionInput);
+
+ if (insertAfter !== null) {
+ optionInput.focus();
+ }
+
+ this.optionCount++;
+ if (this.optionCount === this.options.maxOptions) {
+ this.optionList.querySelectorAll(".jsAddOption").forEach((icon: HTMLSpanElement) => {
+ icon.classList.remove("pointer");
+ icon.classList.add("disabled");
+ });
+ }
+ }
+
+ /**
+ * Populates the option list with the current options.
+ */
+ private createOptionList(pollOptions: PollOption[]): void {
+ pollOptions.forEach((option) => {
+ this.createOption(option.optionValue, option.optionID);
+ });
+
+ if (this.optionCount < this.options.maxOptions) {
+ this.createOption();
+ }
+ }
+
+ /**
+ * Handles validation errors returned by Ajax request.
+ */
+ private handleError(data: AjaxResponse): void {
+ switch (data.returnValues.fieldName) {
+ case this.wysiwygId + "Poll_endTime":
+ case this.wysiwygId + "Poll_maxVotes": {
+ const fieldName = data.returnValues.fieldName.replace(this.wysiwygId + "Poll_", "");
+
+ const small = document.createElement("small");
+ small.classList.add("innerError");
+ small.innerHTML = Language.get("wcf.poll." + fieldName + ".error." + data.returnValues.errorType);
+
+ const field = document.getElementById(data.returnValues.fieldName)!;
+ (field.nextSibling! as HTMLElement).insertAdjacentElement("afterbegin", small);
+
+ data.cancel = true;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Adds another option field below the current option field after pressing Enter.
+ */
+ private optionInputKeyDown(event: KeyboardEvent): void {
+ if (event.key !== "Enter") {
+ return;
+ }
+
+ const target = event.currentTarget as HTMLInputElement;
+ const addOption = target.parentElement!.querySelector(".jsAddOption") as HTMLSpanElement;
+ Core.triggerEvent(addOption, "click");
+
+ event.preventDefault();
+ }
+
+ /**
+ * Removes a poll option after clicking on its deletion button.
+ */
+ private removeOption(event: Event): void {
+ event.preventDefault();
+
+ const button = event.currentTarget as HTMLSpanElement;
+ button.closest("li")!.remove();
+
+ this.optionCount--;
+
+ if (this.optionList.childElementCount === 0) {
+ this.createOption();
+ } else {
+ this.optionList.querySelectorAll(".jsAddOption").forEach((icon) => {
+ icon.classList.add("pointer");
+ icon.classList.remove("disabled");
+ });
+ }
+ }
+
+ /**
+ * Resets all poll fields.
+ */
+ private reset(): void {
+ this.questionField.value = "";
+
+ this.optionCount = 0;
+ this.optionList.innerHTML = "";
+ this.createOption();
+
+ DatePicker.clear(this.endTimeField);
+
+ this.maxVotesField.value = "1";
+ this.isChangeableYesField.checked = false;
+ this.isChangeableNoField.checked = true;
+ this.isPublicYesField.checked = false;
+ this.isPublicNoField.checked = true;
+ this.resultsRequireVoteYesField.checked = false;
+ this.resultsRequireVoteNoField.checked = true;
+ this.sortByVotesYesField.checked = false;
+ this.sortByVotesNoField.checked = true;
+
+ EventHandler.fire("com.woltlab.wcf.poll.editor", "reset", {
+ pollEditor: this,
+ });
+ }
+
+ /**
+ * Handles the poll data if the form is submitted.
+ */
+ private submit(event: Event): void {
+ if (this.options.isAjax) {
+ EventHandler.fire("com.woltlab.wcf.poll.editor", "submit", {
+ event: event,
+ pollEditor: this,
+ });
+ } else {
+ const form = this.container.closest("form")!;
+
+ this.getOptions().forEach((option, i) => {
+ const input = document.createElement("input");
+ input.type = "hidden";
+ input.name = `${this.wysiwygId} + 'Poll_options[${i}}]`;
+ input.value = option;
+ form.appendChild(input);
+ });
+ }
+ }
+
+ /**
+ * Validates the poll data.
+ */
+ private validate(data: ValidationData): void {
+ if (this.questionField.value.trim() === "") {
+ return;
+ }
+
+ let nonEmptyOptionCount = 0;
+ Array.from(this.optionList.children).forEach((listItem: HTMLLIElement) => {
+ const optionInput = listItem.querySelector("input[type=text]") as HTMLInputElement;
+ if (optionInput.value.trim() !== "") {
+ nonEmptyOptionCount++;
+ }
+ });
+
+ if (nonEmptyOptionCount === 0) {
+ data.api.throwError(this.container, Language.get("wcf.global.form.error.empty"));
+ data.valid = false;
+ } else {
+ const maxVotes = ~~this.maxVotesField.value;
+
+ if (maxVotes && maxVotes > nonEmptyOptionCount) {
+ data.api.throwError(this.maxVotesField.parentElement, Language.get("wcf.poll.maxVotes.error.invalid"));
+ data.valid = false;
+ } else {
+ EventHandler.fire("com.woltlab.wcf.poll.editor", "validate", {
+ data: data,
+ pollEditor: this,
+ });
+ }
+ }
+ }
+
+ /**
+ * Returns the data of the poll.
+ */
+ public getData(): object {
+ return {
+ [this.questionField.id]: this.questionField.value,
+ [this.wysiwygId + "Poll_options"]: this.getOptions(),
+ [this.endTimeField.id]: this.endTimeField.value,
+ [this.maxVotesField.id]: this.maxVotesField.value,
+ [this.isChangeableYesField.id]: !!this.isChangeableYesField.checked,
+ [this.isPublicYesField.id]: !!this.isPublicYesField.checked,
+ [this.resultsRequireVoteYesField.id]: !!this.resultsRequireVoteYesField.checked,
+ [this.sortByVotesYesField.id]: !!this.sortByVotesYesField.checked,
+ };
+ }
+
+ /**
+ * Returns the selectable options in the poll.
+ *
+ * Format: `{optionID}_{option}` with `optionID = 0` if it is a new option.
+ */
+ public getOptions(): string[] {
+ const options: string[] = [];
+ Array.from(this.optionList.children).forEach((listItem: HTMLLIElement) => {
+ const optionValue = (listItem.querySelector("input[type=text]")! as HTMLInputElement).value.trim();
+
+ if (optionValue !== "") {
+ options.push(`${listItem.dataset.optionId!}_${optionValue}`);
+ }
+ });
+
+ return options;
+ }
+}
+
+Core.enableLegacyInheritance(UiPollEditor);
+
+export = UiPollEditor;