typeahead: (function() {
    var engine = new Bloodhound({
      local: [{value: 'red'}, {value: 'blue'}, {value: 'green'} , 
              {value: 'yellow'}, {value: 'violet'}, {value: 'brown'}, 
              {value: 'purple'}, {value: 'black'}, {value: 'white'}],
      datumTokenizer: function(d) {
        return Bloodhound.tokenizers.whitespace(d.value);
      },
      queryTokenizer: Bloodhound.tokenizers.whitespace
    });
    engine.initialize();

    return [null, { source: engine.ttAdapter() }];
  })(),
Пример #2
0
var QueryEditor = function () {
    
    var chartEditor = new ChartEditor();
    var dbInfo = new DbInfo();
    var aceSqlEditor = new AceSqlEditor("ace-editor");
    var dataGrid = new DataGrid();
    
    function runQuery () {
        $('#server-run-time').html('');
        $('#rowcount').html('');
        dataGrid.emptyDataGrid();
        var data = {
            queryText: aceSqlEditor.getSelectedOrAllText(),
            connectionId: $('#connection').val(),
            cacheKey: $('#cache-key').val(),
            queryName: getQueryName()
        };
        dataGrid.startRunningTimer();
        $.ajax({
            type: "POST",
            url: "/run-query",
            data: data
        }).done(function (data) {
            chartEditor.setData(data);
            // TODO - if vis tab is active, render chart
            dataGrid.stopRunningTimer();
            $('#server-run-time').html(data.serverMs/1000 + " sec.");
            if (data.success) {
                $('.hide-while-running').show();
                if (data.incomplete) {
                    $('.incomplete-notification').removeClass("hidden");
                } else {
                    $('.incomplete-notification').addClass("hidden");
                }
                dataGrid.renderGridData(data);
            } else {
                dataGrid.renderError(data.error);
            }
        }).fail(function () {
            dataGrid.stopRunningTimer();
            dataGrid.renderError("Something is broken :(");
        });
    }
    
    function getQueryName () {
        return $('#header-query-name').val();
    }
    
    function getQueryTags () {
        return $.map($('#tags').val().split(','), $.trim);
    }
    
    function saveQuery () {
        var $queryId = $('#query-id');
        var query = {
            name: getQueryName(),
            queryText: aceSqlEditor.getEditorText(),
            tags: getQueryTags(),
            connectionId: dbInfo.getConnectionId(),
            chartConfiguration: chartEditor.getChartConfiguration()
        };
        $('#btn-save-result').text('saving...').show();
        $.ajax({
            type: "POST",
            url: "/queries/" + $queryId.val(),
            data: query
        }).done(function (data) {
            if (data.success) {
                window.history.replaceState({}, "query " + data.query._id, "/queries/" + data.query._id);
                $queryId.val(data.query._id);
                $('#btn-save-result').removeClass('label-info').addClass('label-success').text('Success');
                setTimeout(function () {
                    $('#btn-save-result').fadeOut(400, function () {
                        $('#btn-save-result').removeClass('label-success').addClass('label-info').text('');
                    });
                }, 1000);
            } else {
                $('#btn-save-result').removeClass('label-info').addClass('label-danger').text('Failed');
            }
        }).fail(function () {
            alert('ajax fail');
        });
    }
    
    $('#btn-save').click(function (event) {
        event.preventDefault();
        event.stopPropagation();
        saveQuery();
    });
    
    $('#btn-run-query').click(function (event) {
        event.preventDefault();
        event.stopPropagation();
        runQuery();
    });
    
    /*  (re-)render the chart when the viz tab is pressed, 
        TODO: only do this if necessary
    ==============================================================================*/
    $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
        // if shown tab was the chart tab, rerender the chart
        // e.target is the activated tab
        if (e.target.getAttribute("href") == "#tab-content-visualize") {
            chartEditor.rerenderChart();
        } else if (e.target.getAttribute("href") == "#tab-content-sql") {
            dataGrid.resize();
        }
    });
    
    /*  get query again, because not all the data is in the HTML
        TODO: do most the workflow this way? 
    ==============================================================================*/
    var $queryId = $('#query-id');
    $.ajax({
        type: "GET",
        url: "/queries/" + $queryId.val() + "?format=json"
    }).done(function (data) {
        console.log(data);
        chartEditor.loadChartConfiguration(data.chartConfiguration);
    }).fail(function () {
        alert('Failed to get additional Query info');
    });
    
    /*  Tags Typeahead
    ==============================================================================*/
    var Bloodhound = require('Bloodhound');
    var bloodhoundTags = new Bloodhound({
      datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
      queryTokenizer: Bloodhound.tokenizers.whitespace,
      prefetch: {
        url: '/tags', // array of tagnames
        ttl: 0,
        filter: function(list) {
          return $.map(list, function(tag) {
            return { name: tag }; });
        }
      }
    });
    bloodhoundTags.initialize();
    $('#tags').tagsinput({
      typeaheadjs: {
        //name: 'tags',
        displayKey: 'name',
        valueKey: 'name',
        source: bloodhoundTags.ttAdapter()
      }
    });
    
    /*  Shortcuts
    ==============================================================================*/
    // keymaster doesn't fire on input/textarea events by default
    // since we are only using command/ctrl shortcuts, 
    // we want the event to fire all the time for any element
    keymaster.filter = function (event) {
        return true; 
    };
    keymaster('ctrl+s, command+s', function() { 
        saveQuery();
        return false;
    });
    keymaster('ctrl+r, command+r, ctrl+e, command+e', function() { 
        runQuery();
        return false;
    });
};
Пример #3
0
var create = exports.create = function(options) {
    var config = options.config,
        template = mustache.compile($(options.template).html()),
        $input = $(options.input),
        $hidden_input = $(options.hidden),
        $openButton = $(options.button),
        reverse = options.reverse,
        sorter = _.isArray(options.sortKeys) ? getSortFunction(options.sortKeys) : undefined,

        engine = new Bloodhound({
            name: options.name, // Used for caching
            prefetch: {
                url: options.url,
                ttl: 0 // TODO: Use high TTL and set thumbprint
            },
            limit: 3000,
            datumTokenizer: function(datum) {
                return datum.tokens;
            },
            queryTokenizer: Bloodhound.tokenizers.nonword,
            sorter: sorter
        }),

        enginePromise = engine.initialize(),

        setTypeaheadAfterDataLoaded = function($typeahead, key, query) {
            if (!key) {
                setTypeahead($typeahead, query);
            } else if (query && query.length !== 0) {
                engine.get('', function(datums) {
                    var data = _.filter(datums, function(datum) {
                            return datum[key] == query;
                        });

                    if (data.length > 0) {
                        setTypeahead($typeahead, data[0].value);
                    }
                });
            } else {
                setTypeahead($typeahead, '');
            }
        },

        setTypeahead = function($typeahead, val) {
            $typeahead.typeahead('val', val);
            $typeahead.typeahead('close');
        };


    $input.typeahead({
        minLength: options.minLength || 0
    }, {
        source: engine.ttAdapter(),
        templates: {
            suggestion: template
        },
    });

    var selectStream = $input.asEventStream('typeahead:selected typeahead:autocompleted',
                                            function(e, datum) { return datum; }),

        backspaceOrDeleteStream = $input.asEventStream('keyup')
                                        .filter(BU.keyCodeIs([8, 46])),

        editStream = selectStream.merge(backspaceOrDeleteStream.map(undefined)).skipDuplicates(),

        idStream = selectStream.map(".id")
                               .merge(backspaceOrDeleteStream.map(""));

    editStream.filter(BU.isDefined).onValue($input, "data", "datum");
    editStream.filter(BU.isUndefined).onValue($input, "removeData", "datum");

    if (options.forceMatch) {
        $input.on('blur', function() {
            if ($input.find('.tt-dropdown-menu').is(':visible')) return;

            if ($input.data('datum') === undefined) {
                setTypeahead($input, '');
            }
        });
    }
    // Set data-unmatched to the input value if the value was not
    // matched to a typeahead datum. Allows for external code to take
    // alternate action if there is no typeahead match.
    inputStream($input)
        .merge(idStream.map(""))
        .onValue($input, 'attr', 'data-unmatched');

    if (options.hidden) {
        idStream.onValue($hidden_input, "val");

        enginePromise.done(function() {
            // Specify a 'reverse' key to lookup data in reverse,
            // otherwise restore verbatim
            var value = $hidden_input.val();
            if (value) {
                setTypeaheadAfterDataLoaded($input, reverse, value);
            }
        });

        $hidden_input.on('restore', function(event, value) {
            enginePromise.done(function() {
                // If we're already loaded, this applies right away
                setTypeaheadAfterDataLoaded($input, reverse, value);
            });

            // If we're not, this will get used when loaded later
            $hidden_input.val(value || '');
        });

    }

    if (options.button) {
        var isOpen = false;
        $input.on('typeahead:opened', function() {
            isOpen = true;
        });
        $input.on('typeahead:closed', function() {
            isOpen = false;
            $openButton.removeClass('active');
        });
        $openButton.on('click', function(e) {
            e.preventDefault();
            if (isOpen) {
                $input.typeahead('close');
            } else {
                setTypeahead($input, '');
                $input.typeahead('open');

                // The open may fail if there is no data to show
                // If so don't add the active class, because then it can never
                // be removed
                if (isOpen) {
                    $openButton.addClass('active');
                }
            }
        });
    }
};