воскресенье, 20 марта 2011 г.

Sencha Touch: Ext.form.Slider с полем для ручного ввода значения

В одном из моих проектов я столкнулся с задачей создания компонента слайдера с возможностью ручного ввода значения поля, т. е. выглядеть он должен был так:
Причём, при нажатии на значение поля (где написано "8.50 %") должна выдвигаться клавиатура для ввода значения поля вручную, а при перетаскивании ползунка значение поля должно обновляться.

Итак, первым делом я изучил внутренности компонента Ext.form.Slider и выяснил, что внешний вид задаётся свойством renderTpl.
Вот так выглядит renderTpl в оригинале:

renderTpl: [
    '<tpl if="label">',
        '<div class="x-form-label"><span>{label}</span></div>',
    '</tpl>',
    '<tpl if="fieldEl">',
        '<div id="{inputId}" name="{name}" class="{fieldCls}"',
        '<tpl if="tabIndex">tabIndex="{tabIndex}"</tpl>',
        '<tpl if="style">style="{style}" </tpl>',
    '/></tpl>'
]

В моём компоненте, помимо дополнительного поля для ручного ввода, будет присутствовать постфикс, т. е. один символ или набор символов, которые будут добавляться после поля со значением (на скриншоте - это "%"), поэтому я изменил шаблон компонента следующим образом:

renderTpl: [
    '<tpl if="label">',
        '<div class="x-form-label"><span>{label}</span></div>',
        '<div class="dnt_form_slider_value">' +
          '<input type="text" id="dnt-form-slider-valuefield-{name}"
              class="dnt_form_slider_value_input" value="{value}" />' +
          '{postfix}' +
        '</div>',
    '</tpl>',
    '<tpl if="fieldEl">',
        '<div id="{inputId}" name="{name}" class="{fieldCls}"',
        '<tpl if="tabIndex">tabIndex="{tabIndex}"</tpl>',
        '<tpl if="style">style="{style}" </tpl>',
    '/></tpl>'
]

CSS-классы, прописанные в этом шаблоне, заданы следующим образом:

.dnt_form_slider_title {
    background-color: transparent;
    display: inline;
}
.dnt_form_slider_value {
    position: absolute;
    right: 26px;
    display: inline;
}
.dnt_form_slider_value_input {
    display: inline !important;
    min-height: 20px !important;
    padding: 0px !important;
    text-align: right;
    width: 60px !important;
}

Теперь компонент будет выглядеть должным образом, но значения value и postfix не будут применяться нашему полю, поэтому переходим к функциональной части.

Итак, value и postfix изначально не будут подставляться в шаблон, даже если мы передадим их в конфигурационные свойства при объявлении нашего кастомизированного слайдера. Изучив исходный код Ext.form.Field, от которого унаследован Ext.form.Slider, я выяснил, что список полей, которые будут заполнять шаблон, определяется функцией initRenderData, поэтому для нашего слайдера я перегрузил эту функцию следующим образом:

initRenderData: function() {
    //вызываю код, который был прописан в оригинальном Ext.form.Field
    var renderData = Dnt.form.Slider.superclass.initRenderData.call(this);
    //добавляю новые поля, которые будут подставляться в шаблон
    Ext.applyIf(renderData, {
        value       : this.getValue(),
        postfix     : this.postfix
    });
    return renderData;
}

Теперь в шаблон будут вписаны значения, указанные в конфигурационных свойствах компонента. Осталось сделать так, чтобы эти значения менялись при перемещении ползунка слайдера.
Чтобы при перемещении ползунка новое значение применялась только к одному компоненту, а не ко всем слайдерам, добавленным на страницу, в шаблоне я задал полю input атрибут id, формирующийся из префикса dnt-form-slider-valuefield- и значения конфигурационного свойства name, которое должно быть объявлено.
Для динамического изменения значения поля необходимо подписаться к событию 'change' нашего слайдера и в качестве обработчика назначить следующую функцию:

onChangeValue: function(slider, thumb, newValue, oldValue) {
    //получаю ссылку на input-элемент компонента
    var valueField = Ext.get('dnt-form-slider-valuefield-' + this.name);
    /*
      устанавливаю новое значение, обрезав количество 
      символов после запятой до значения конфигурационного
      свойства digitalAfterPoint (у меня по умолчанию 2)
    */
    valueField.dom.value = newValue.toFixed(this.digitalsAfterPoint);
}

Итак, теперь значение поля меняется, но меняется оно только тогда, когда мы отпускаем ползунок. Для того, чтобы значение поля менялось постоянно при перетаскивании слайдера, нам необходимо функцию onChangeValue подписать и на событие 'drag'.

Осталось совсем немного: надо сделать так, чтобы при ручном изменении значения поля слайдер передвигался на нужную позицию.
Для этого подписываемся на событие 'render' нашего компонента и ставим обработчиком функцию onRenderSlider - в этот момент наш компонент прорендерен, поэтому мы можем подписаться на события, выбрасываемые нашим input-элементом, отображающим текущее значение поля.

onRenderSlider: function() {
    //получаю ссылку на input-элемент компонента
    var valueField = Ext.get('dnt-form-slider-valuefield-' + this.name);
    //подписываюсь на изменения значения input-элемента
    valueField.on('change', function(event, el) {
        //формирую из введённых данных новое значение поля
        var newValue = parseFloat(el.value) || 0;
        //форматирую значение и передвигаю слайдер
        this.setValue(newValue.toFixed(this.digitalsAfterPoint));
    }, this);
}

Всё, наш новый слайдер с полем для ручного ввода значения полностью готов, причём никаких дополнительных защит "от дурака" писать не надо!

Добавить компонент можно следующим образом:

new Dnt.form.Slider({
    name              : 'interestRate',
    postfix           : '%',
    digitalsAfterPoint: 2,
    minValue          : 1,
    maxValue          : 15,
    increment         : 0.05,
    label             : 'Interest Rate'
})

В дополнение прилагаю исходный код компонента и файл со стилями.

Тестовый проект с подключенным и работающим слайдером можно скачать отсюда.

Комментариев нет:

Отправить комментарий