UNPKG

freeboard-leaflet-realtime

Version:
347 lines (310 loc) 9.45 kB
ValueEditor = function(theFreeboardModel) { var _veDatasourceRegex = new RegExp(".*datasources\\[\"([^\"]*)(\"\\])?(.*)$"); var dropdown = null; var selectedOptionIndex = 0; var _autocompleteOptions = []; var currentValue = null; var EXPECTED_TYPE = { ANY : "any", ARRAY : "array", OBJECT : "object", STRING : "string", NUMBER : "number", BOOLEAN : "boolean" }; function _isPotentialTypeMatch(value, expectsType) { if(_.isArray(value) || _.isObject(value)) { return true; } return _isTypeMatch(value, expectsType); } function _isTypeMatch(value, expectsType) { switch(expectsType) { case EXPECTED_TYPE.ANY: return true; case EXPECTED_TYPE.ARRAY: return _.isArray(value); case EXPECTED_TYPE.OBJECT: return _.isObject(value); case EXPECTED_TYPE.STRING: return _.isString(value); case EXPECTED_TYPE.NUMBER: return _.isNumber(value); case EXPECTED_TYPE.BOOLEAN: return _.isBoolean(value); } } function _checkCurrentValueType(element, expectsType) { $(element).parent().find(".validation-error").remove(); if(!_isTypeMatch(currentValue, expectsType)) { $(element).parent().append("<div class='validation-error'>" + "This field expects an expression that evaluates to type " + expectsType + ".</div>"); } } function _resizeValueEditor(element) { var lineBreakCount = ($(element).val().match(/\n/g) || []).length; var newHeight = Math.min(200, 20 * (lineBreakCount + 1)); $(element).css({height: newHeight + "px"}); } function _autocompleteFromDatasource(inputString, datasources, expectsType) { var match = _veDatasourceRegex.exec(inputString); var options = []; if(match) { // Editor value is: datasources["; List all datasources if(match[1] == "") { _.each(datasources, function(datasource) { options.push({value: datasource.name(), entity: undefined, precede_char: "", follow_char: "\"]"}); }); } // Editor value is a partial match for a datasource; list matching datasources else if(match[1] != "" && _.isUndefined(match[2])) { var replacementString = match[1]; _.each(datasources, function(datasource) { var dsName = datasource.name(); if(dsName != replacementString && dsName.indexOf(replacementString) == 0) { options.push({value: dsName, entity: undefined, precede_char: "", follow_char: "\"]"}); } }); } // Editor value matches a datasources; parse JSON in order to populate list else { // We already have a datasource selected; find it var datasource = _.find(datasources, function(datasource) { return (datasource.name() === match[1]); }); if(!_.isUndefined(datasource)) { var dataPath = "data"; var remainder = ""; // Parse the partial JSON selectors if(!_.isUndefined(match[2])) { // Strip any incomplete field values, and store the remainder var remainderIndex = match[3].lastIndexOf("]") + 1; dataPath = dataPath + match[3].substring(0, remainderIndex); remainder = match[3].substring(remainderIndex, match[3].length); remainder = remainder.replace(/^[\[\"]*/, ""); remainder = remainder.replace(/[\"\]]*$/, ""); } // Get the data for the last complete JSON field var dataValue = datasource.getDataRepresentation(dataPath); currentValue = dataValue; // For arrays, list out the indices if(_.isArray(dataValue)) { for(var index = 0; index < dataValue.length; index++) { if(index.toString().indexOf(remainder) == 0) { var value = dataValue[index]; if(_isPotentialTypeMatch(value, expectsType)) { options.push({value: index, entity: value, precede_char: "[", follow_char: "]", preview: value.toString()}); } } } } // For objects, list out the keys else if(_.isObject(dataValue)) { _.each(dataValue, function(value, name) { if(name.indexOf(remainder) == 0) { if(_isPotentialTypeMatch(value, expectsType)) { options.push({value: name, entity: value, precede_char: "[\"", follow_char: "\"]"}); } } }); } // For everything else, do nothing (no further selection possible) else { // no-op } } } } _autocompleteOptions = options; } function _renderAutocompleteDropdown(element, expectsType) { var inputString = $(element).val().substring(0, $(element).getCaretPosition()); // Weird issue where the textarea box was putting in ASCII (nbsp) for spaces. inputString = inputString.replace(String.fromCharCode(160), " "); _autocompleteFromDatasource(inputString, theFreeboardModel.datasources(), expectsType); if(_autocompleteOptions.length > 0) { if(!dropdown) { dropdown = $('<ul id="value-selector" class="value-dropdown"></ul>') .insertAfter(element) .width($(element).outerWidth() - 2) .css("left", $(element).position().left) .css("top", $(element).position().top + $(element).outerHeight() - 1); } dropdown.empty(); dropdown.scrollTop(0); var selected = true; selectedOptionIndex = 0; _.each(_autocompleteOptions, function(option, index) { var li = _renderAutocompleteDropdownOption(element, inputString, option, index); if(selected) { $(li).addClass("selected"); selected = false; } }); } else { _checkCurrentValueType(element, expectsType); $(element).next("ul#value-selector").remove(); dropdown = null; selectedOptionIndex = -1; } } function _renderAutocompleteDropdownOption(element, inputString, option, currentIndex) { var optionLabel = option.value; if(option.preview) { optionLabel = optionLabel + "<span class='preview'>" + option.preview + "</span>"; } var li = $('<li>' + optionLabel + '</li>').appendTo(dropdown) .mouseenter(function() { $(this).trigger("freeboard-select"); }) .mousedown(function(event) { $(this).trigger("freeboard-insertValue"); event.preventDefault(); }) .data("freeboard-optionIndex", currentIndex) .data("freeboard-optionValue", option.value) .bind("freeboard-insertValue", function() { var optionValue = option.value; optionValue = option.precede_char + optionValue + option.follow_char; var replacementIndex = inputString.lastIndexOf("]"); if(replacementIndex != -1) { $(element).replaceTextAt(replacementIndex+1, $(element).val().length, optionValue); } else { $(element).insertAtCaret(optionValue); } currentValue = option.entity; $(element).triggerHandler("mouseup"); }) .bind("freeboard-select", function() { $(this).parent().find("li.selected").removeClass("selected"); $(this).addClass("selected"); selectedOptionIndex = $(this).data("freeboard-optionIndex"); }); return li; } function createValueEditor(element, expectsType) { $(element).addClass("calculated-value-input") .bind("keyup mouseup freeboard-eval", function(event) { // Ignore arrow keys and enter keys if(dropdown && event.type == "keyup" && (event.keyCode == 38 || event.keyCode == 40 || event.keyCode == 13)) { event.preventDefault(); return; } _renderAutocompleteDropdown(element, expectsType); }) .focus(function() { $(element).css({"z-index" : 3001}); _resizeValueEditor(element); }) .focusout(function() { _checkCurrentValueType(element, expectsType); $(element).css({ "height": "", "z-index" : 3000 }); $(element).next("ul#value-selector").remove(); dropdown = null; selectedOptionIndex = -1; }) .bind("keydown", function(event) { if(dropdown) { if(event.keyCode == 38 || event.keyCode == 40) // Handle Arrow keys { event.preventDefault(); var optionItems = $(dropdown).find("li"); if(event.keyCode == 38) // Up Arrow { selectedOptionIndex--; } else if(event.keyCode == 40) // Down Arrow { selectedOptionIndex++; } if(selectedOptionIndex < 0) { selectedOptionIndex = optionItems.size() - 1; } else if(selectedOptionIndex >= optionItems.size()) { selectedOptionIndex = 0; } var optionElement = $(optionItems).eq(selectedOptionIndex); optionElement.trigger("freeboard-select"); $(dropdown).scrollTop($(optionElement).position().top); } else if(event.keyCode == 13) // Handle enter key { event.preventDefault(); if(selectedOptionIndex != -1) { $(dropdown).find("li").eq(selectedOptionIndex) .trigger("freeboard-insertValue"); } } } }); } // Public API return { createValueEditor : function(element, expectsType) { if(expectsType) { createValueEditor(element, expectsType); } else { createValueEditor(element, EXPECTED_TYPE.ANY); } }, EXPECTED_TYPE : EXPECTED_TYPE } }