/**
* Created by gavorhes on 12/10/2015.
*/
import provide from '../util/provide';
import $ = require('jquery');
let nm = provide('collections');
export interface TipSliderConfig {
label: string;
yearOptions: Array<{column: string, label: string}>
}
export interface TipPresetConfig {
label: string;
presets: Array<{column: string, value: number}>
}
export interface ChangeResponse{
paramWeights: Object;
region: string;
mmVersion: string;
}
export interface ChangeCallback{
(chg: ChangeResponse): any
}
class TipPresets implements TipPresetConfig {
label: string;
presets: Array<{column: string, value: number}>;
domId: string;
constructor(conf: TipPresetConfig) {
this.label = conf.label;
this.presets = conf.presets;
this.domId = this.label.replace(/ /g, '').toLowerCase();
let theSum = 0;
for (let pr of this.presets){
theSum += pr.value;
}
if (theSum != 100){
throw 'preset sum does note equal 100';
}
}
}
class _Slider {
_min: number;
_max: number;
_locked: boolean;
atMin: boolean;
atMax: boolean;
_weightDefault: number;
_weight: number;
html: string;
domId: string;
name: string;
labelLow: JQuery;
labelHigh: JQuery;
labelVal: JQuery;
slider: JQuery;
selectionBox: JQuery;
chk: JQuery;
selectedParam: any;
selectedParamDefault: any;
_dropdownSelection: string;
/**
* Slider constructor
* @param sliderConfig - the configuration
*/
constructor(sliderConfig: TipSliderConfig) {
this._dropdownSelection = null;
this._weight = null;
this.name = sliderConfig.label;
this.domId = this.name.toLowerCase().replace(/ /g, '-');
this._locked = false;
this._min = 0.0;
this._max = 100;
this.labelLow = null;
this.labelHigh = null;
this.labelVal = null;
this.slider = null;
this.chk = null;
this.atMin = false;
this.atMax = false;
let sel = `<select class="${sliderConfig.yearOptions.length == 1 ? 'hidden-select' : 'show-select'}" id="${this.domId}_chg">`;
for (let i = 0; i < sliderConfig.yearOptions.length; i++) {
let itm = sliderConfig.yearOptions[i];
sel += `<option value="${itm.column}">${itm.label}</option>`;
}
sel += '</select>';
this.selectedParamDefault = this.selectedParam;
this.html = '<div class="slider-div">' +
`<label for="${this.domId}_chk" class="slider-label">${this.name}</label>` +
sel + `<br>` +
`<input id="${this.domId}_chk" type="checkbox" title="Lock/Unlock Slider">` +
`<label id="${this.domId}_low" class="low-high"></label>` +
`<input id="${this.domId}" type="range" value="50" min="0" max="100" step="0.1">` +
`<label id="${this.domId}_high" class="low-high"></label>` +
`<label id="${this.domId}_lbl" for="${this.domId}" class="percent-label"></label></div>`;
}
/**
* add html to dom
* @param {jQuery} $container - container element
*/
addToDom($container) {
$container.append(this.html);
this.labelLow = $(`#${this.domId}_low`);
this.labelHigh = $(`#${this.domId}_high`);
this.labelVal = $(`#${this.domId}_lbl`);
this.slider = $(`#${this.domId}`);
this.selectionBox = $(`#${this.domId}_chg`);
this.chk = $(`#${this.domId}_chk`);
}
/**
* increment the slider
* @param {number} delta change delta
* @returns {number} the remainder not able to be allocated to this slider
*/
increment(delta) {
let remainder = 0;
delta = Number(delta.toFixed(1));
this._weight += delta;
if (this._weight < this._min) {
remainder = this._min - this._weight;
this._weight = this._min;
this.atMin = true;
} else if (this._weight > this._max) {
remainder = this._max - this._weight;
this._weight = this._max;
this.atMax = true;
} else {
this.atMin = false;
this.atMax = false;
}
this.slider.val(this._weight.toFixed(1));
this.labelVal.html(this._weight.toFixed(1) + '%');
return remainder;
}
/**
* set the value and drop down
* @param {number} newVal the new value
* @param {string} selectedParam the selected parameter
*/
setValAndDropDown(newVal: number, selectedParam: string) {
this.min = 0;
this.max = 100;
this.weight = newVal;
this.slider.val(newVal.toFixed(1));
this.selectionBox.val(selectedParam);
this.selectedParam = selectedParam;
this.locked = true;
}
/**
*
* @returns {boolean} if locked
*/
get locked() {
return this._locked;
}
/**
*
* @param {boolean} val if locked
*/
set locked(val) {
this._locked = val;
this.slider.prop('disabled', this._locked);
this.selectionBox.prop('disabled', this._locked);
this.chk.prop('checked', !this._locked);
}
/**
*
* @returns {number} the minimum
*/
get min() {
return this._min;
}
/**
*
* @param {number} newVal new minimum
*/
set min(newVal) {
this._min = Number(newVal.toFixed(1));
if (this._min < 0) {
this._min = 0;
}
this.labelLow.html(this._min.toFixed(1));
this.slider.attr('min', this._min.toFixed(1));
this.atMin = this._weight == this._min;
}
/**
*
* @returns {number} the maximum
*/
get max() {
return this._max;
}
/**
*
* @param {number} newVal the maximum
*/
set max(newVal) {
this._max = Number(newVal.toFixed(1));
if (this._max > 100) {
this._max = 100.0;
}
this.labelHigh.html(this._max.toFixed(1));
this.slider.attr('max', this._max.toFixed(1));
this.atMax = this._weight == this._max;
}
/**
*
* @returns {number} the weight
*/
get weight() {
return this._weight;
}
/**
*
* @param {number} newVal the weight
*/
set weight(newVal) {
this._weight = Number(newVal.toFixed(1));
this.labelVal.html(this._weight.toFixed(1) + '%');
if (this._weight <= this._min) {
this.atMin = true;
this.atMax = false;
} else if (this._weight >= this._max) {
this.atMin = false;
this.atMax = true;
} else {
this.atMin = false;
this.atMax = false;
}
}
}
nm._Slider = _Slider;
/**
* class to keep track of the sliders
*/
export class TipSliders {
$container: JQuery;
reservedPercent: number;
lockedCount: number;
notLockedCount: number;
private _sliderList: Array<_Slider>;
private _sliderLookup: {[s: string]: _Slider};
private resetting: boolean;
private _changedCallback: ChangeCallback;
private _lockedList: Array<_Slider>;
private _inRangeList: Array<_Slider>;
private _atMinList: Array<_Slider>;
private _atMaxList: Array<_Slider>;
private _presetArray: Array<TipPresets>;
private _presetLookup: {[s: string]: TipPresets};
private _$presetSelector: JQuery;
private _$regionSelector: JQuery;
private _$versionSelector: JQuery;
/**
*
* @param sliderConfigs
* @param presetConfig
* @param divId
* @param presetSelector
* @param regionSelector
* @param versionSelector
* @param chgCallback
*/
constructor(sliderConfigs: Array<TipSliderConfig>, presetConfig: Array<TipPresetConfig>,
divId: string, presetSelector: JQuery, regionSelector: JQuery, versionSelector: JQuery,
chgCallback?: ChangeCallback) {
this.resetting = false;
this.reservedPercent = 0.0;
this.$container = $('#' + divId);
this.$container.addClass('slider-container');
this._changedCallback = typeof chgCallback == 'function' ? chgCallback : () => {};
this._$presetSelector = presetSelector;
this._$regionSelector = regionSelector;
this._$versionSelector = versionSelector;
this._sliderList = [];
this._sliderLookup = {};
for (let i = 0; i < sliderConfigs.length; i++) {
let sld = new _Slider(sliderConfigs[i]);
this._sliderList.push(sld);
this._sliderLookup[sld.domId] = sld;
sld.addToDom(this.$container);
}
this._presetArray = [];
this._presetLookup = {};
for (let i = 0; i < presetConfig.length; i++) {
let preset = new TipPresets(presetConfig[i]);
let idx = (i + 1).toFixed();
this._presetLookup[idx] = preset;
this._presetArray.push(preset);
this._$presetSelector.append(`<option value="${idx}">${preset.label}</option>`);
}
this._lockedList = [];
this._inRangeList = [];
this._atMinList = [];
this._atMaxList = [];
this.lockedCount = 10;
this.notLockedCount = 0;
this._splitSliderArray();
this._$presetSelector.change(() => {
this.setPresetValues();
this._runChangedCallback();
});
this._$regionSelector.change(() => {
this._runChangedCallback();
});
this._$versionSelector.change(() => {
this._runChangedCallback();
});
this._$presetSelector.trigger('change');
this._addEventListeners();
}
_runChangedCallback(){
this._changedCallback(this.paramWeightsRegionVersion);
}
get changedCallback(): ChangeCallback{
return this._changedCallback
}
set changedCallback(chg: ChangeCallback){
this._changedCallback = chg;
this._runChangedCallback();
}
setPresetValues() {
let idx = this._$presetSelector.val() || '1';
let thePreset = this._presetLookup[idx];
for (let i = 0; i < thePreset.presets.length; i++) {
let presetValues = thePreset.presets[i];
let theSlider = this._sliderList[i];
theSlider.locked = true;
theSlider.setValAndDropDown(presetValues.value, presetValues.column);
}
}
/**
* split array into subarrays holding the sliders
* @private
*/
_splitSliderArray() {
this._lockedList = [];
this._inRangeList = [];
this._atMinList = [];
this._atMaxList = [];
for (let i = 0; i < this._sliderList.length; i++) {
let sld = this._sliderList[i];
if (sld.locked) {
this._lockedList.push(sld);
} else if (sld.atMin) {
this._atMinList.push(sld);
} else if (sld.atMax) {
this._atMaxList.push(sld);
} else {
this._inRangeList.push(sld);
}
}
this.lockedCount = this._lockedList.length;
this.notLockedCount = this._sliderList.length - this.lockedCount;
}
/**
* handle remainder, recursive to take care of min max overshoots
* @param {number} remain the remainder
* @param {string} skipDomId - this dom id
* @private
*/
_handleRemainder(remain, skipDomId) {
remain = Number(remain.toFixed(1));
if (remain == 0) {
return;
}
this._splitSliderArray();
let canChangeArray = [];
for (let i = 0; i < this._inRangeList.length; i++) {
let sld = this._inRangeList[i];
if (sld.domId == skipDomId) {
continue;
}
canChangeArray.push(sld);
}
if (remain > 0) {
for (let i = 0; i < this._atMaxList.length; i++) {
let sld = this._atMaxList[i];
if (sld.domId == skipDomId) {
continue;
}
canChangeArray.push(sld);
}
} else {
for (let i = 0; i < this._atMinList.length; i++) {
let sld = this._atMinList[i];
if (sld.domId == skipDomId) {
continue;
}
canChangeArray.push(sld);
}
}
if (canChangeArray.length == 0) {
return;
}
let inc = -1 * Number((remain / canChangeArray.length).toFixed(1));
let newRemainder = 0;
for (let i = 0; i < canChangeArray.length; i++) {
newRemainder += canChangeArray[i].increment(inc);
}
this._handleRemainder(newRemainder, skipDomId);
}
/**
*
* @param {object} keyValList key and value list
*/
setValues(keyValList) {
this.resetting = true;
for (let k in keyValList) {
if (keyValList.hasOwnProperty(k)) {
this._sliderLookup[k].setValAndDropDown(keyValList[k][0], keyValList[k][1]);
}
}
this.resetting = false;
}
/**
* get the weight sum
* @returns {number} the weight sum
*/
getSum() {
let total = 0;
for (let i = 0; i < this._sliderList.length; i++) {
let sld = this._sliderList[i];
total += Number(sld.weight.toFixed(1));
}
return total;
}
/**
* get the parameter weights
* @returns {object} lookup with parameter weights
*/
getParams() {
let paramWeights = {};
for (let i = 0; i < this._sliderList.length; i++) {
let sld = this._sliderList[i];
paramWeights[sld.selectedParam] = Number(sld.weight.toFixed(1));
}
return paramWeights;
}
_addEventListeners() {
let ___this = this;
//change function
this.$container.find('input[type="range"]').change(function () {
if (___this.resetting) {
return;
}
let $this = $(this);
let domId = this['id'];
let sldr = ___this._sliderLookup[domId];
let newValue = parseFloat($this.val());
let oldValue = sldr.weight;
let diff = newValue - oldValue;
diff = Number(diff.toFixed(1));
sldr.weight = Number(newValue.toFixed(1));
___this._handleRemainder(diff, domId);
//cleanup, make sure the sum is still 100
let sum = Number(___this.getSum().toFixed(1));
if (sum > 100) {
loop1:
while (true) {
for (let i = 0; i < ___this._sliderList.length; i++) {
let sld = ___this._sliderList[i];
if (sld.domId == domId || sld.locked || sld.atMin) {
continue;
}
sld.increment(-0.1);
sum -= 0.1;
if (sum.toFixed(1) == '100.0') {
break loop1;
}
}
}
} else if (sum < 100) {
loop1:
while (true) {
for (let i = 0; i < ___this._sliderList.length; i++) {
let sld = ___this._sliderList[i];
if (sld.domId == domId || sld.locked || sld.atMax) {
continue;
}
sld.increment(0.1);
sum += 0.1;
if (sum.toFixed(1) == '100.0') {
break loop1;
}
}
}
}
___this._$presetSelector.val('0');
___this._runChangedCallback();
}
);
//update the selected parameter when the selection is changed
$('.show-select').change(function () {
if (___this.resetting) {
return;
}
___this._sliderLookup[this['id'].replace('_chg', '')].selectedParam = $(this).val();
___this._$presetSelector.val('0');
___this._runChangedCallback();
});
//lock the slider and update the reserved percent
this.$container.find('input[type="checkbox"]').change(function () {
let domEl = this;
___this._sliderLookup[domEl.id.replace('_chk', '')].locked = !domEl.checked;
___this.reservedPercent = 0.0;
___this.notLockedCount = 0;
let notLockedSliders = [];
for (let i = 0; i < ___this._sliderList.length; i++) {
let sld = ___this._sliderList[i];
if (sld.locked) {
___this.reservedPercent += sld.weight;
continue;
}
notLockedSliders.push(sld);
___this.notLockedCount++;
}
for (let i = 0; i < ___this._sliderList.length; i++) {
let sld = ___this._sliderList[i];
if (sld.locked) {
continue;
}
sld.max = 100 - ___this.reservedPercent;
}
if (notLockedSliders.length == 1) {
notLockedSliders[0].min = notLockedSliders[0].weight;
} else {
for (let i = 0; i < notLockedSliders.length; i++) {
notLockedSliders[i].min = 0;
}
}
});
}
get paramWeightsRegionVersion(): ChangeResponse{
return {paramWeights: this.getParams(),
region: this._$regionSelector.val() as string, mmVersion: this._$versionSelector.val() as string}
}
}
nm.Sliders = TipSliders;
export default TipSliders;
|