Add duration field

This commit is contained in:
Giuseppe Criscione 2020-12-12 00:11:16 +01:00
parent c98bb5833f
commit 95f7cf6c97
11 changed files with 318 additions and 3 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,213 @@
import Utils from './utils';
export default function DurationInput(input, options) {
var defaults = {
unit: 'seconds',
display: ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'],
labels: {
years: ['year', 'years'],
months: ['month', 'months'],
weeks: ['week', 'weeks'],
days: ['day', 'days'],
hours: ['hour', 'hours'],
minutes: ['minute', 'minutes'],
seconds: ['second', 'seconds']
}
};
var TIME_INTERVALS = {
years: 60 * 60 * 24 * 365,
months: 60 * 60 * 24 * 30,
weeks: 60 * 60 * 24 * 7,
days: 60 * 60 * 24,
hours: 60 * 60,
minutes: 60,
seconds: 1
};
var field, hiddenInput;
var innerInputs = {};
var labels = {};
options = Utils.extendObject({}, defaults, options);
createField();
function secondsToIntervals(seconds) {
var intervals = {};
var t;
for (t in TIME_INTERVALS) {
if (Object.prototype.hasOwnProperty.call(TIME_INTERVALS, t) && options.display.indexOf(t) !== -1) {
intervals[t] = Math.floor(seconds / TIME_INTERVALS[t]);
seconds -= intervals[t] * TIME_INTERVALS[t];
}
}
return intervals;
}
function intervalsToSeconds(intervals) {
var seconds = 0;
var i;
for (i in intervals) {
if (Object.prototype.hasOwnProperty.call(intervals, i) && Object.prototype.hasOwnProperty.call(TIME_INTERVALS, i)) {
seconds += intervals[i] * TIME_INTERVALS[i];
}
}
return seconds;
}
function updateHiddenInput() {
var intervals = {};
var seconds = 0;
var step = 0;
var i = 0;
for (i in innerInputs) {
if (Object.prototype.hasOwnProperty.call(innerInputs, i)) {
intervals[i] = innerInputs[i].value;
}
}
seconds = intervalsToSeconds(intervals);
if (hiddenInput.step) {
step = hiddenInput.step * TIME_INTERVALS[options.unit];
seconds = Math.floor(seconds / step) * step;
}
if (hiddenInput.min) {
seconds = Math.max(seconds, hiddenInput.min);
}
if (hiddenInput.max) {
seconds = Math.min(seconds, hiddenInput.max);
}
hiddenInput.value = Math.round(seconds / TIME_INTERVALS[options.unit]);
}
function updateInnerInputs() {
var intervals = secondsToIntervals(hiddenInput.value * TIME_INTERVALS[options.unit]);
var i;
for (i in innerInputs) {
if (Object.prototype.hasOwnProperty.call(innerInputs, i)) {
innerInputs[i].value = intervals[i];
}
}
}
function updateInnerInputsLength() {
var i;
for (i in innerInputs) {
if (Object.prototype.hasOwnProperty.call(innerInputs, i)) {
innerInputs[i].style.width = Math.max(3, innerInputs[i].value.length + 2) + 'ch';
}
}
}
function updateLabels() {
var i;
for (i in innerInputs) {
if (Object.prototype.hasOwnProperty.call(innerInputs, i)) {
labels[i].innerHTML = options.labels[i][parseInt(innerInputs[i].value) === 1 ? 0 : 1];
}
}
}
function createInnerInputs(intervals, steps) {
var wrap, name, innerInput, label, i;
field = document.createElement('div');
field.className = 'duration-input';
for (i = 0; i < options.display.length; i++) {
name = options.display[i];
wrap = document.createElement('span');
wrap.className = 'duration-' + name;
innerInput = document.createElement('input');
innerInput.type = 'number';
innerInput.value = intervals[name] || 0;
innerInput.style.width = Math.max(3, innerInput.value.length + 2) + 'ch';
if (steps[name] > 1) {
innerInput.step = steps[name];
}
if (input.disabled) {
innerInput.disabled = true;
}
innerInputs[name] = innerInput;
innerInput.addEventListener('change', function () {
updateHiddenInput();
updateInnerInputs();
updateInnerInputsLength();
updateLabels();
});
innerInput.addEventListener('input', function () {
updateHiddenInput();
updateInnerInputsLength();
updateLabels();
});
innerInput.addEventListener('focus', function () {
field.classList.add('focused');
});
innerInput.addEventListener('blur', function () {
field.classList.remove('focused');
});
wrap.addEventListener('mousedown', function (event) {
var input = $('input', this);
if (input && event.target !== input) {
input.focus();
event.preventDefault();
}
});
label = document.createElement('label');
label.innerHTML = options.labels[name][parseInt(innerInput.value) === 1 ? 0 : 1];
labels[name] = label;
wrap.appendChild(innerInput);
wrap.appendChild(label);
field.appendChild(wrap);
}
field.addEventListener('mousedown', function (event) {
if (event.target === this) {
innerInput.focus();
event.preventDefault();
}
});
return field;
}
function createField() {
var field, valueSeconds, stepSeconds;
hiddenInput = document.createElement('input');
hiddenInput.className = 'duration-hidden-input';
hiddenInput.name = input.name;
hiddenInput.id = input.id;
hiddenInput.type = 'text';
hiddenInput.value = input.value;
hiddenInput.readOnly = true;
hiddenInput.hidden = true;
if (input.min) {
hiddenInput.min = input.min;
}
if (input.max) {
hiddenInput.max = input.max;
}
if (input.step) {
hiddenInput.step = input.step;
}
if (input.required) {
hiddenInput.required = true;
}
if (input.disabled) {
hiddenInput.disabled = true;
}
if (input.hasAttribute('data-display')) {
options.display = input.getAttribute('data-display').split(', ');
}
if (input.hasAttribute('data-unit')) {
options.unit = input.getAttribute('data-unit');
}
valueSeconds = input.value * TIME_INTERVALS[options.unit];
stepSeconds = input.step * TIME_INTERVALS[options.unit];
field = createInnerInputs(secondsToIntervals(valueSeconds || 0), secondsToIntervals(stepSeconds || 1));
input.parentNode.replaceChild(field, input);
field.appendChild(hiddenInput);
}
}

View File

@ -1,5 +1,6 @@
import ArrayInput from './arrayinput';
import DatePicker from './datepicker';
import DurationInput from './durationinput';
import Editor from './editor';
import FileInput from './fileinput';
import Form from './form';
@ -74,6 +75,10 @@ export default {
TagInput(element);
});
$$('input[data-field=duration]').forEach(function (element) {
DurationInput(element, Formwork.config.DurationInput);
});
$$('input[type=range]').forEach(function (element) {
RangeInput(element);
});

View File

@ -150,6 +150,55 @@
font-weight: 600;
}
.duration-input {
box-sizing: border-box;
margin-top: 0;
margin-bottom: $input-margin-bottom;
padding: $input-padding-v $input-padding-h / 2;
border: 1px solid $color-base-500;
border-radius: $border-radius;
font-size: $input-font-size;
line-height: $input-line-height;
@include user-select-none;
}
.duration-input.focused {
border-color: $color-accent-500;
}
.duration-input > span {
text-align: center;
}
.duration-input input {
margin: 0;
padding: 0;
width: 2rem;
border: 0;
background-color: $color-base-700;
vertical-align: baseline;
text-align: center;
-moz-appearance: textfield;
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
margin: 0;
-webkit-appearance: none;
}
}
.duration-input input:focus {
box-shadow: none;
}
.duration-input label {
margin: 0;
padding: 0 0.5rem;
}
.duration-hidden-input {
display: none;
}
.title-input {
font-size: $font-size-l;
}

View File

@ -76,6 +76,17 @@ abstract class AbstractController
'weekdays' => ['long' => $this->label('date.weekdays.long'), 'short' => $this->label('date.weekdays.short')],
'months' => ['long' => $this->label('date.months.long'), 'short' => $this->label('date.months.short')]
]
],
'DurationInput' => [
'labels' => [
'years' => $this->label('date.duration.years'),
'months' => $this->label('date.duration.months'),
'weeks' => $this->label('date.duration.weeks'),
'days' => $this->label('date.duration.days'),
'hours' => $this->label('date.duration.hours'),
'minutes' => $this->label('date.duration.minutes'),
'seconds' => $this->label('date.duration.seconds')
]
]
])
];

View File

@ -292,6 +292,14 @@ class Validator
return $value;
}
/**
* Validate "duration" fields
*/
public static function validateDuration($value, Field $field)
{
return static::validateNumber($value, $field);
}
/**
* Cast a value to its correct type
*/

View File

@ -15,6 +15,13 @@ dashboard.statistics: Statistics
dashboard.statistics.unique-visitors: Unique Visitors
dashboard.statistics.visits: Visits
dashboard.welcome: Welcome
date.duration.days: ['day', 'days']
date.duration.hours: ['hour', 'hours']
date.duration.minutes: ['minute', 'minutes']
date.duration.months: ['month', 'months']
date.duration.seconds: ['second', 'seconds']
date.duration.weeks: ['week', 'weeks']
date.duration.years: ['year', 'years']
date.months.long: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
date.months.short: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
date.today: Today

View File

@ -15,6 +15,13 @@ dashboard.statistics: Statistiche
dashboard.statistics.unique-visitors: Visitatori unici
dashboard.statistics.visits: Visite
dashboard.welcome: Benvenuto/a
date.duration.days: ['giorno', 'giorni']
date.duration.hours: ['ora', 'ore']
date.duration.minutes: ['minuto', 'minuti']
date.duration.months: ['mese', 'mesi']
date.duration.seconds: ['secondo', 'secondi']
date.duration.weeks: ['settimana', 'settimane']
date.duration.years: ['anno', 'anni']
date.months.long: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre']
date.months.short: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic']
date.today: Oggi

View File

@ -0,0 +1,15 @@
<?= $this->insert('fields.label') ?>
<input <?= $this->attr([
'type' => 'number',
'id' => $field->name(),
'name' => $field->formName(),
'min' => $field->get('min'),
'max' => $field->get('max'),
'step' => $field->get('step'),
'value' => $field->value(),
'required' => $field->isRequired(),
'disabled' => $field->isDisabled(),
'data-field' => 'duration',
'data-display' => $field->has('display') ? implode(', ', $field->get('display')) : null,
'data-unit' => $field->get('unit', 'seconds')
]) ?>>