enketo-core
Version:
Extensible Enketo form engine
221 lines (190 loc) • 6.1 kB
JavaScript
import $ from 'jquery';
import 'drag-drop-touch';
import sortable from 'html5sortable/dist/html5sortable.cjs';
import { t } from 'enketo/translator';
import Widget from '../../js/widget';
import support from '../../js/support';
import events from '../../js/event';
/**
* @augments Widget
*/
class RankWidget extends Widget {
/**
* @type {string}
*/
static get selector() {
return '.question input.rank';
}
/**
* @type {boolean}
*/
static get list() {
return true;
}
_init() {
const that = this;
const loadedValue = this.originalInputValue;
const startTextKey = support.touch
? 'rankwidget.tapstart'
: 'rankwidget.clickstart';
this.itemSelector = 'label:not(.itemset-template)';
this.list = $(this.element)
.next('.option-wrapper')
.addClass('widget rank-widget')[0];
$(this.list)
.toggleClass('rank-widget--empty', !loadedValue)
.append(this.resetButtonHtml)
.append(
`<div class="rank-widget__overlay"><span class="rank-widget__overlay__content" data-i18n="${startTextKey}">${
support.touch
? t('rankwidget.tapstart')
: t('rankwidget.clickstart')
}</span></div>`
)
.on('click', function () {
if (!that.element.disabled) {
this.classList.remove('rank-widget--empty');
that.originalInputValue = that.value;
that.element.dispatchEvent(events.FakeFocus());
}
});
this.list
.querySelector('.btn-reset')
.addEventListener('click', (evt) => {
this._reset();
evt.stopPropagation();
});
this.element.classList.add('hide');
this.value = loadedValue;
this.list.querySelectorAll(this.itemSelector).forEach((item) => {
const handle = document.createElement('span');
handle.textContent = '::';
handle.className = 'handle';
item.append(handle);
});
// Create the sortable drag-and-drop functionality
sortable(this.list, {
items: this.itemSelector,
handle: '.handle',
// hoverClass: 'rank-widget__item--hover',
containerSerializer(container) {
return {
value: [].slice
.call(
container.node.querySelectorAll(
`${that.itemSelector} input`
)
)
.map((input) => input.value)
.join(' '),
};
},
})[0].addEventListener('sortupdate', () => {
this.originalInputValue = this.value;
this.element.dispatchEvent(events.FakeFocus());
});
if (this.props.readonly) {
this.disable();
}
}
/**
* Resets widget
*/
_reset() {
this.originalInputValue = '';
}
/**
* @type {string}
*/
get value() {
const result = sortable(this.list, 'serialize');
return result[0].container.value;
}
set value(value) {
if (!value) {
this._reset();
} else {
const that = this;
const values = value.split(' ');
const items = [
...this.list.querySelectorAll(`${this.itemSelector} input`),
];
// Basic error check
if (values.length !== items.length) {
throw new Error(
'Could not load rank widget value. Number of items mismatch.'
);
}
// Don't even attempt to rectify a mismatch between the value and the available items.
items.sort((a, b) => {
const aIndex = values.indexOf(a.value);
const bIndex = values.indexOf(b.value);
if (aIndex === -1 || bIndex === -1) {
throw new Error(
'Could not load rank widget value. Mismatch in item values.'
);
}
return aIndex - bIndex;
});
items.forEach((item) => {
$(that.list)
.find('.btn-reset')
.before($(item.parentNode).detach());
});
}
}
/**
* Disables widget
*/
disable() {
$(this.element)
.prop('disabled', true)
.next('.widget')
.find('input, button')
.prop('disabled', true);
sortable(this.list, 'disable');
}
/**
* Enables widget
*/
enable() {
$(this.element)
.prop('disabled', false)
.next('.widget')
.find('input, button')
.prop('disabled', false);
sortable(this.list, 'enable');
}
/**
* Updates widget
*/
update() {
const { value } = this.element;
// re-initalize sortable because the options may have changed
sortable(this.list);
if (value) {
this.value = value;
this.originalInputValue = value;
} else {
this._reset();
}
}
// Since we're overriding the setter we also have to overwrite the getter
// https://stackoverflow.com/questions/28950760/override-a-setter-and-the-getter-must-also-be-overridden
/**
* @type {string}
*/
get originalInputValue() {
return super.originalInputValue;
}
/**
* This is the input that Enketo's engine listens on.
*
* @type {string}
*/
set originalInputValue(value) {
super.originalInputValue = value;
this.list.classList.toggle('rank-widget--empty', !value);
}
}
export default RankWidget;