<commit_before>/**
 * A widget that contains functions and function information for the Narrative.
 * When initialized, it uses a loading gif while waiting for functions to load
 * (unless functions were passed in on construction).
 *
 * Each function is presented in a list (for now - accordion may be coming soon)
 * and when clicked will fire a "function_clicked.narrative" event.
 *
 * @author Bill Riehl <wjriehl@lbl.gov>
 * @public
 */
(function( $, undefined ) {
    $.KBWidget({
        name: 'kbaseNarrativeFunctionPanel',
        parent: 'kbaseWidget',
        version: '0.0.1',
        options: {
            loadingImage: 'static/kbase/images/ajax-loader.gif',
            autopopulate: true,
        },

        /**
         * This private method is automatically called when the widget is initialized.
         *
         * Initialization steps:
         * 1. Put a loading spinner in its attached div
         * 2. Assume that the kernel is loaded before this is inited. //Check if the kernel is loaded - wait until it is.
         * 3. Make a kernel call to get the set of available services and functions.
         * 4. Shuffle the available functions into the right format.
         * 5. Display them on the screen, bind events.
         * @param {object} options
         * @private
         */
        init: function(options) {
            this._super(options);

            // DOM structure setup here.
            // After this, just need to update the function list

            /* There's a few bits here.
             * 1. It's all in a Bootstrap Panel scaffold.
             * 2. The panel-body section contains the core of the widget:
             *    a. loading panel (just a blank thing with a spinning gif)
             *    b. error panel
             *    c. actual function widget setup.
             *
             * So, initialize the scaffold, bind the three core pieces in the
             * panel-body, make sure the right one is being shown at the start,
             * and off we go.
             */

            // Make a function panel for everything to sit inside.
            this.$functionPanel = $('
')
                                  .addClass('kb-function-body');

            // The 'loading' panel should just have a spinning gif in it.
            this.$loadingPanel = $('
')
                                 .addClass('kb-data-loading')
                                 .append('')
                                 .append($('
')
                                         .attr('id', 'message'))
                                 .hide();

            // The error panel should be empty for now.
            this.$errorPanel = $('
')
                               .addClass('kb-error')
                               .hide();

            // The help element should be outside of the panel itself, so it can be manipulated separately.
            // It should hide itself when clicked.
            var self = this;
            this.$helpPanel = $('
')
                              .addClass('kb-function-help-popup alert alert-info')
                              .hide()
                              .click(function(event) { self.$helpPanel.hide(); });

            this.$elem.append($('
')
                              .addClass('panel panel-primary')
                              .append($('
')
                                      .addClass('panel-heading')
                                      .append($('
')
                                              .addClass('panel-title')
                                              .css({'text-align': 'center'})
                                              .append('Services')))
                              .append($('
')
                                      .addClass('panel-body kb-narr-panel-body')
                                      .append(this.$functionPanel)
                                      .append(this.$loadingPanel)
                                      .append(this.$errorPanel)));

            $('body').append(this.$helpPanel);
            this.refresh();

            if (this.options.autopopulate === true) {
                this.refresh();
            }

            return this;
        },

        /**
         * Refreshes the list of loaded functions.
         * This makes a kernel call, fetches the list of functions as an object,
         * pulls out necessary fields, and constructs the function list from them.
         * @private
         */
        refresh: function() {
            if (!IPython || !IPython.notebook || !IPython.notebook.kernel)
                return;

            this.showLoadingMessage("Loading available KBase Services...");

            // Command to load and fetch all functions from the kernel
            var fetchFunctionsCommand = 'import biokbase.narrative.common.service_root as root\n' +
                                        'print root.service.get_all_services(as_json_schema=True)\n';

            var callbacks = {
                'output' : $.proxy(function(msgType, content) {
                    this.parseKernelResponse(msgType, content);
                    this.showFunctionPanel();
                }, this),
                'execute_reply' : $.proxy(function(content) {
                    this.handleCallback("execute_reply", content);
                }, this),
                'clear_output' : $.proxy(function(content) {
                    this.handleCallback("clear_output", content);
                }, this),
                'set_next_input' : $.proxy(function(content) {
                    this.handleCallback("set_next_input", content);
                }, this),
                'input_request' : $.proxy(function(content) {
                    this.handleCallback("input_request", content);
                }, this),
            };

            var msgid = IPython.notebook.kernel.execute(fetchFunctionsCommand, callbacks, {silent: true});
        },

        handleCallback: function(call, content) {
            if (content.status === "error") {
                this.showError(content);
            }
            else {
                console.debug("kbaseNarrativeFunctionPanel." + call);
                console.debug(content);
            }
        },

        /**
         * Parses the text response from the kernel. If this response is not a "stream" type,
         * then an error is raised and execution stops.
         * @param {string} msgType - a string representing the type of message being propagated
         * @param {object} content - the content of the message
         * @private
         */
        parseKernelResponse: function(msgType, content) {
            // if it's not a datastream, display some kind of error, and return.
            // this.dbg("kbaseNarrativeFunctionPanel.parseKernelResponse");
            // this.dbg(content);

            if (msgType != 'stream') {
                this.showError('Sorry, an error occurred while loading the function list.');
                return;
            }
            var buffer = content.data;
            if (buffer.length > 0) {
                var serviceSet = JSON.parse(buffer);
                this.populateFunctionList(serviceSet);
            }
            this.$loadingPanel.hide();
        },

        /**
         * Populates the list of functions with clickable elements and hints/help popups.
         * @param {object} serviceSet - the object representing the set of loaded services
         * and their functions.
         * @private
         */
        populateFunctionList: function(serviceSet) {
            var serviceAccordion = [];

            for (var serviceName in serviceSet) {
                var $methodList = $('
');
                var service = serviceSet[serviceName];
                for (var i=0; i<service.methods.length; i++) {
                    var method = service.methods[i];
                    method['service'] = serviceName;
                    $methodList.append(this.buildFunction(method));
                }

                serviceAccordion.push({
                    'title' : serviceName,
                    'body' : $methodList
                });
            }

            this.$elem.find('.kb-function-body').kbaseAccordion( { elements : serviceAccordion } );
        },

        /**
         * Creates and returns a list item containing info about the given narrative function.
         * Clicking the function anywhere outside the help (?) button will trigger a
         * function_clicked.Narrative event. Clicking the help (?) button will trigger a
         * function_help.Narrative event.
         *
         * Both events have the relevant data passed along with them for use by the responding
         * element.
         * @param {object} method - the method object returned from the kernel.
         * @private
         */
        buildFunction: function(method) {
            var self = this;

            var $helpButton = $('')
                              .addClass('glyphicon glyphicon-question-sign kb-function-help')
                              .css({'margin-top': '-5px'})
                              .click(function(event) { event.preventDefault(); event.stopPropagation(); self.showHelpPopup(method, event); });

            var $newFunction = $('
')
                               .append(method.title)
                               .click(function(event) {self.trigger('function_clicked.Narrative', method); })
                               .append($helpButton);

            return $newFunction;
        },

        /**
         * Creates a new function field in the functions list.
         * This 'function' is represented as a DOM element. It has a name (styled as
         * a button, or something buttonish) and a help button.
         *
         * Clicking the function button triggers a function_clicked.Narrative event,
         * and clicking the help button triggers a function_help.Narrative event.
         *
         * Both of these events have the relevant data passed along with them for
         * population by the responding element.
         *
         * @param {object} method - the method object returned from the kernel.
         * @private
         */
        addFunction: function(method) {
            var self = this;
            var $funcButton = $('<button>')
                              .attr('type', 'button')
                              .addClass('btn btn-default')
                              .append(method.title)
                              .click(function(event) { self.trigger('function_clicked.Narrative', method); });

            var $helpButton = $('')
                              .addClass('glyphicon glyphicon-question-sign')
                              .css({'float': 'right',
                                    'cursor': 'pointer',
                                    'font-size': '14pt',
                                    'color': '#0064b6'})
                              .click(function(event) { self.showHelpPopup(method); });

            this.$functionList.append($('
')
                                        .append($funcButton)
                                        .append($helpButton)
                                     );
        },

        /**
         * Shows a popup panel with a description of the clicked method.
         * @param {object} method - the method containing a title and
         * description for populating the popup.
         * @private
         */
        showHelpPopup: function(method, event) {
            this.$helpPanel.css({'left':event.pageX, 'top':event.pageY})
            this.$helpPanel.empty();
            this.$helpPanel.append($('
').append(method.title + ' Help'))
                           .append(method.description)
                           .append($('
').append('Click to hide'));
            this.$helpPanel.show();
        },

        /**
         * Shows a loading spinner or message on top of the panel.
         * @private
         */
        showLoadingMessage: function(message) {
            this.$loadingPanel.find('#message').empty();
            if (message)
                this.$loadingPanel.find('#message').html(message);
            this.$functionPanel.hide();
            this.$errorPanel.hide();
            this.$loadingPanel.show();
        },

        /**
         * Shows the main function panel, hiding all others.
         * @private
         */
        showFunctionPanel: function() {
            this.$errorPanel.hide();
            this.$loadingPanel.hide();
            this.$functionPanel.show();
        },

        /**
         * Shows an error text message on top of the panel. All other pieces are hidden.
         * @param {string} error - the text of the error message
         * @private
         */
        showError: function(error) {
            var $errorHeader = $('
')
                               .addClass('alert alert-danger')
                               .append('Sorry, an error occurred while loading KBase functions.
Please contact the KBase team at help@kbase.us with the information below.');

            this.$errorPanel.empty();
            this.$errorPanel.append($errorHeader);

            // If it's a string, just dump the string.
            if (typeof error === 'string') {
                this.$errorPanel.append($('
').append(error));
            }

            // If it's an object, expect an error object as returned by the execute_reply callback from the IPython kernel.
            else if (typeof error === 'object') {
                var $details = $('
');
                $details.append($('
').append('Type: ' + error.ename))
                        .append($('
').append('Value: ' + error.evalue));

                var $tracebackDiv = $('
')
                                 .addClass('kb-function-error-traceback');
                for (var i=0; i<error.traceback.length; i++) {
                    $tracebackDiv.append(error.traceback[i] + "
");
                }

                var $tracebackPanel = $('
');
                var tracebackAccordion = [{'title' : 'Traceback', 'body' : $tracebackDiv}];

                this.$errorPanel.append($details)
                                .append($tracebackPanel);
                $tracebackPanel.kbaseAccordion({ elements : tracebackAccordion });
            }

            this.$functionPanel.hide();
            this.$loadingPanel.hide();
            this.$errorPanel.show();
        },
    });

})( jQuery );<commit_msg>Added little toggle to count the number of deployed services and functions.<commit_after>/**
 * A widget that contains functions and function information for the Narrative.
 * When initialized, it uses a loading gif while waiting for functions to load
 * (unless functions were passed in on construction).
 *
 * Each function is presented in a list (for now - accordion may be coming soon)
 * and when clicked will fire a "function_clicked.narrative" event.
 *
 * @author Bill Riehl <wjriehl@lbl.gov>
 * @public
 */
(function( $, undefined ) {
    $.KBWidget({
        name: 'kbaseNarrativeFunctionPanel',
        parent: 'kbaseWidget',
        version: '0.0.1',
        options: {
            loadingImage: 'static/kbase/images/ajax-loader.gif',
            autopopulate: true,
        },

        /**
         * This private method is automatically called when the widget is initialized.
         *
         * Initialization steps:
         * 1. Put a loading spinner in its attached div
         * 2. Assume that the kernel is loaded before this is inited. //Check if the kernel is loaded - wait until it is.
         * 3. Make a kernel call to get the set of available services and functions.
         * 4. Shuffle the available functions into the right format.
         * 5. Display them on the screen, bind events.
         * @param {object} options
         * @private
         */
        init: function(options) {
            this._super(options);

            // DOM structure setup here.
            // After this, just need to update the function list

            /* There's a few bits here.
             * 1. It's all in a Bootstrap Panel scaffold.
             * 2. The panel-body section contains the core of the widget:
             *    a. loading panel (just a blank thing with a spinning gif)
             *    b. error panel
             *    c. actual function widget setup.
             *
             * So, initialize the scaffold, bind the three core pieces in the
             * panel-body, make sure the right one is being shown at the start,
             * and off we go.
             */

            // Make a function panel for everything to sit inside.
            this.$functionPanel = $('
')
                                  .addClass('kb-function-body');

            // The 'loading' panel should just have a spinning gif in it.
            this.$loadingPanel = $('
')
                                 .addClass('kb-data-loading')
                                 .append('')
                                 .append($('
')
                                         .attr('id', 'message'))
                                 .hide();

            // The error panel should be empty for now.
            this.$errorPanel = $('
')
                               .addClass('kb-error')
                               .hide();

            // The help element should be outside of the panel itself, so it can be manipulated separately.
            // It should hide itself when clicked.
            var self = this;
            this.$helpPanel = $('
')
                              .addClass('kb-function-help-popup alert alert-info')
                              .hide()
                              .click(function(event) { self.$helpPanel.hide(); });

            this.$elem.append($('
')
                              .addClass('panel panel-primary')
                              .append($('
')
                                      .addClass('panel-heading')
                                      .append($('
')
                                              .addClass('panel-title')
                                              .css({'text-align': 'center'})
                                              .append('Services')))
                              .append($('
')
                                      .addClass('panel-body kb-narr-panel-body')
                                      .append(this.$functionPanel)
                                      .append(this.$loadingPanel)
                                      .append(this.$errorPanel)));

            $('body').append(this.$helpPanel);
            this.refresh();

            if (this.options.autopopulate === true) {
                this.refresh();
            }

            return this;
        },

        /**
         * Refreshes the list of loaded functions.
         * This makes a kernel call, fetches the list of functions as an object,
         * pulls out necessary fields, and constructs the function list from them.
         * @private
         */
        refresh: function() {
            if (!IPython || !IPython.notebook || !IPython.notebook.kernel)
                return;

            this.showLoadingMessage("Loading available KBase Services...");

            // Command to load and fetch all functions from the kernel
            var fetchFunctionsCommand = 'import biokbase.narrative.common.service_root as root\n' +
                                        'print root.service.get_all_services(as_json_schema=True)\n';

            var callbacks = {
                'output' : $.proxy(function(msgType, content) {
                    this.parseKernelResponse(msgType, content);
                    this.showFunctionPanel();
                }, this),
                'execute_reply' : $.proxy(function(content) {
                    this.handleCallback("execute_reply", content);
                }, this),
                'clear_output' : $.proxy(function(content) {
                    this.handleCallback("clear_output", content);
                }, this),
                'set_next_input' : $.proxy(function(content) {
                    this.handleCallback("set_next_input", content);
                }, this),
                'input_request' : $.proxy(function(content) {
                    this.handleCallback("input_request", content);
                }, this),
            };

            var msgid = IPython.notebook.kernel.execute(fetchFunctionsCommand, callbacks, {silent: true});
        },

        handleCallback: function(call, content) {
            if (content.status === "error") {
                this.showError(content);
            }
            else {
                console.debug("kbaseNarrativeFunctionPanel." + call);
                console.debug(content);
            }
        },

        /**
         * Parses the text response from the kernel. If this response is not a "stream" type,
         * then an error is raised and execution stops.
         * @param {string} msgType - a string representing the type of message being propagated
         * @param {object} content - the content of the message
         * @private
         */
        parseKernelResponse: function(msgType, content) {
            // if it's not a datastream, display some kind of error, and return.
            // this.dbg("kbaseNarrativeFunctionPanel.parseKernelResponse");
            // this.dbg(content);

            if (msgType != 'stream') {
                this.showError('Sorry, an error occurred while loading the function list.');
                return;
            }
            var buffer = content.data;
            if (buffer.length > 0) {
                var serviceSet = JSON.parse(buffer);
                this.populateFunctionList(serviceSet);
            }
            this.$loadingPanel.hide();
        },

        /**
         * Populates the list of functions with clickable elements and hints/help popups.
         * @param {object} serviceSet - the object representing the set of loaded services
         * and their functions.
         * @private
         */
        populateFunctionList: function(serviceSet) {
            var totalFunctions = 0;
            var totalServices = 0;

            var serviceAccordion = [];

            for (var serviceName in serviceSet) {
                totalServices++;

                var $methodList = $('
');
                var service = serviceSet[serviceName];
                for (var i=0; i<service.methods.length; i++) {
                    totalFunctions++;
                    var method = service.methods[i];
                    method['service'] = serviceName;
                    $methodList.append(this.buildFunction(method));
                }

                serviceAccordion.push({
                    'title' : serviceName,
                    'body' : $methodList
                });
            }

            // console.log("Total Services: " + totalServices);
            // console.log("Total Functions: " + totalFunctions);

            this.$elem.find('.kb-function-body').kbaseAccordion( { elements : serviceAccordion } );
        },

        /**
         * Creates and returns a list item containing info about the given narrative function.
         * Clicking the function anywhere outside the help (?) button will trigger a
         * function_clicked.Narrative event. Clicking the help (?) button will trigger a
         * function_help.Narrative event.
         *
         * Both events have the relevant data passed along with them for use by the responding
         * element.
         * @param {object} method - the method object returned from the kernel.
         * @private
         */
        buildFunction: function(method) {
            var self = this;

            var $helpButton = $('')
                              .addClass('glyphicon glyphicon-question-sign kb-function-help')
                              .css({'margin-top': '-5px'})
                              .click(function(event) { event.preventDefault(); event.stopPropagation(); self.showHelpPopup(method, event); });

            var $newFunction = $('
')
                               .append(method.title)
                               .click(function(event) {self.trigger('function_clicked.Narrative', method); })
                               .append($helpButton);

            return $newFunction;
        },

        /**
         * Creates a new function field in the functions list.
         * This 'function' is represented as a DOM element. It has a name (styled as
         * a button, or something buttonish) and a help button.
         *
         * Clicking the function button triggers a function_clicked.Narrative event,
         * and clicking the help button triggers a function_help.Narrative event.
         *
         * Both of these events have the relevant data passed along with them for
         * population by the responding element.
         *
         * @param {object} method - the method object returned from the kernel.
         * @private
         */
        addFunction: function(method) {
            var self = this;
            var $funcButton = $('<button>')
                              .attr('type', 'button')
                              .addClass('btn btn-default')
                              .append(method.title)
                              .click(function(event) { self.trigger('function_clicked.Narrative', method); });

            var $helpButton = $('')
                              .addClass('glyphicon glyphicon-question-sign')
                              .css({'float': 'right',
                                    'cursor': 'pointer',
                                    'font-size': '14pt',
                                    'color': '#0064b6'})
                              .click(function(event) { self.showHelpPopup(method); });

            this.$functionList.append($('
')
                                        .append($funcButton)
                                        .append($helpButton)
                                     );
        },

        /**
         * Shows a popup panel with a description of the clicked method.
         * @param {object} method - the method containing a title and
         * description for populating the popup.
         * @private
         */
        showHelpPopup: function(method, event) {
            this.$helpPanel.css({'left':event.pageX, 'top':event.pageY})
            this.$helpPanel.empty();
            this.$helpPanel.append($('
').append(method.title + ' Help'))
                           .append(method.description)
                           .append($('
').append('Click to hide'));
            this.$helpPanel.show();
        },

        /**
         * Shows a loading spinner or message on top of the panel.
         * @private
         */
        showLoadingMessage: function(message) {
            this.$loadingPanel.find('#message').empty();
            if (message)
                this.$loadingPanel.find('#message').html(message);
            this.$functionPanel.hide();
            this.$errorPanel.hide();
            this.$loadingPanel.show();
        },

        /**
         * Shows the main function panel, hiding all others.
         * @private
         */
        showFunctionPanel: function() {
            this.$errorPanel.hide();
            this.$loadingPanel.hide();
            this.$functionPanel.show();
        },

        /**
         * Shows an error text message on top of the panel. All other pieces are hidden.
         * @param {string} error - the text of the error message
         * @private
         */
        showError: function(error) {
            var $errorHeader = $('
')
                               .addClass('alert alert-danger')
                               .append('Sorry, an error occurred while loading KBase functions.
Please contact the KBase team at help@kbase.us with the information below.');

            this.$errorPanel.empty();
            this.$errorPanel.append($errorHeader);

            // If it's a string, just dump the string.
            if (typeof error === 'string') {
                this.$errorPanel.append($('
').append(error));
            }

            // If it's an object, expect an error object as returned by the execute_reply callback from the IPython kernel.
            else if (typeof error === 'object') {
                var $details = $('
');
                $details.append($('
').append('Type: ' + error.ename))
                        .append($('
').append('Value: ' + error.evalue));

                var $tracebackDiv = $('
')
                                 .addClass('kb-function-error-traceback');
                for (var i=0; i<error.traceback.length; i++) {
                    $tracebackDiv.append(error.traceback[i] + "
");
                }

                var $tracebackPanel = $('
');
                var tracebackAccordion = [{'title' : 'Traceback', 'body' : $tracebackDiv}];

                this.$errorPanel.append($details)
                                .append($tracebackPanel);
                $tracebackPanel.kbaseAccordion({ elements : tracebackAccordion });
            }

            this.$functionPanel.hide();
            this.$loadingPanel.hide();
            this.$errorPanel.show();
        },
    });

})( jQuery );<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name groongaAdminApp.controller:TableSearchController
 * @description
 * # TableSearchController
 * Controller of the groongaAdminApp
 */
angular.module('groongaAdminApp')
  .controller('TableSearchController', function ($scope, $routeParams, $location, $http) {
    var client = new GroongaClient($http);

    function computeCurrentPage(offset) {
      return Math.ceil((parseInt(offset) + 1) / $scope.nRecordsInPage);
    }

    function initialize() {
      $scope.table = $routeParams.table;
      $scope.style = 'table';
      $scope.response = {
        rawData: [],
        columns: [],
        records: [],
        drilldowns: [],
        elapsedTimeInMilliseconds: 0,
        nTotalRecords: 0
      };
      $scope.indexedColumns = [];
      $scope.allColumns = [];
      $scope.commandLine = '';
      $scope.message = '';
      $scope.currentPage = 1;
      $scope.nRecordsInPage = 10;
      $scope.maxNPages = 10;
      $scope.parameters = angular.copy($location.search());

      $scope.search = search;
      $scope.clear  = clear;
      $scope.toggleSort = toggleSort;
      $scope.selectDrilldown = selectDrilldown;
    }

    function packColumns(columns) {
      var names = columns.map(function(column) {
        return column.name;
      });
      return names.join(',');
    }

    function packSortColumns(columns) {
      var keys = columns.map(function(column) {
        if (column.sort === 'ascending') {
          return column.name;
        } else {
          return '-' + column.name;
        }
      });
      keys.join(',');
    }

    function search() {
      var parameters = angular.copy($scope.parameters);

      var matchColumns = $scope.indexedColumns.filter(function(column) {
        return column.inUse;
      });
      parameters.match_columns = packColumns(matchColumns);

      var outputColumns = $scope.allColumns.filter(function(column) {
        return column.output;
      });
      parameters.output_columns = packColumns(outputColumns);

      parameters.offset = ($scope.currentPage - 1) * $scope.nRecordsInPage;
      parameters.limit = $scope.nRecordsInPage;

      var sortColumns = $scope.response.columns.filter(function(column) {
        return column.sort;
      });
      parameters.sortby = packSortColumns(sortColumns);

      $location.search(parameters);
    }

    function clear() {
      $location.search({});
    }

    function setColumnSort(column, sort) {
      column.sort = sort;
      switch (column.sort) {
      case 'ascending':
        column.iconClass = 'glyphicon-sort-by-attributes';
        break;
      case 'descending':
        column.iconClass = 'glyphicon-sort-by-attributes-alt';
        break;
      default:
        column.iconClass = 'glyphicon-sort';
        break;
      }
    }

    function toggleSort(column) {
      var sort;
      switch (column.sort) {
      case 'ascending':
        sort = 'descending';
        break;
      case 'descending':
        sort = null;
        break;
      default:
        sort = 'ascending';
        break;
      }
      setColumnSort(column, sort);
      search();
    }

    function selectDrilldown(key, value) {
      var query = $scope.parameters.query || '';
      if (query.length > 0) {
        query += ' ';
      }
      $scope.parameters.query = query + key + ':' + value;

      var drilldowns = ($scope.parameters.drilldown || '').split(/\s*,\s*/);
      drilldowns = drilldowns.filter(function(drilldown) {
        return drilldown !== key;
      });
      $scope.parameters.drilldown = drilldowns.join(',');

      search();
    }

    function createColumnInfo(name) {
      var outputColumns = $scope.parameters.output_columns;
      var output = true;
      if (outputColumns) {
        output = outputColumns.indexOf(name) !== -1;
      }
      var drilldown = false;
      return {
        name: name,
        output: output,
        drilldown: drilldown
      };
    }

    function extractColumnsInfo(table, columns) {
      if (table.name === $scope.table) {
        columns.forEach(function(column) {
          if (column.isIndex) {
            return;
          }
          $scope.allColumns.push(createColumnInfo(column.name));
        });
      }

      columns.forEach(function(column) {
        if (!column.isIndex) {
          return;
        }
        if (column.range !== $scope.table) {
          return;
        }
        var matchColumns = $scope.parameters.match_columns;
        column.sources.forEach(function(source) {
          var localName;
          if (source.indexOf('.') === -1) {
            localName = '_key';
          } else {
            localName = source.split('.')[1];
          }
          var inUse = true;
          if (matchColumns) {
            inUse = matchColumns.indexOf(localName) !== -1;
          }
          $scope.indexedColumns.push({
            name: localName,
            inUse: inUse
          });
        });
      });
    }

    function extractTableInfo(table) {
      if (table.name === $scope.table) {
        $scope.allColumns.push(createColumnInfo('_id'));
      }

      client.execute('column_list', {table: table.name})
        .success(function(response) {
          extractColumnsInfo(table, response.columns());
        });
    }

    function fillOptions() {
      client.execute('table_list')
        .success(function(response) {
          response.tables().forEach(function(table) {
            extractTableInfo(table);
          });
        });
    }

    function select() {
      var parameters = {
        table: $scope.table
      };
      angular.forEach($scope.parameters, function(value, key) {
        if (key in parameters) {
          return;
        }
        parameters[key] = value;
      });
      var request = client.execute('select', parameters);
      request.success(function(response) {
        $scope.response.rawData = response.rawData();
        $scope.commandLine = request.commandLine();
        $scope.response.elapsedTimeInMilliseconds =
          response.elapsedTime() * 1000;
        if (!response.isSuccess()) {
          $scope.message =
            'Failed to call "select" command: ' + response.errorMessage();
          $scope.response.nTotalRecords = 0;
          return;
        }
        $scope.currentPage = computeCurrentPage(parameters.offset || 0);
        $scope.response.nTotalRecords = response.nTotalRecords();
        var sortKeys = ($scope.parameters.sortby || '').split(/\s*,\s*/);
        $scope.response.columns = response.columns().map(function(column) {
          var sort = null;
          if (sortKeys.indexOf(column.name) !== -1) {
            sort = 'ascending';
          } else if (sortKeys.indexOf('-' + column.name) !== -1) {
            sort = 'descending';
          }
          setColumnSort(column, sort);
          return column;
        });
        $scope.response.records = response.records().map(function(record) {
          return record.map(function(value, index) {
            return {
              value: value,
              column: $scope.response.columns[index]
            };
          });
        });
        $scope.response.drilldowns = response.drilldowns();
        ($scope.parameters.drilldown || '')
          .split(/\s*,\s*/)
          .filter(function(drilldown) {
            return drilldown.length > 0;
          })
          .forEach(function(drilldown, i) {
            $scope.response.drilldowns[i].key = drilldown;
          });
      });
    }

    initialize();
    fillOptions();
    select();
  });
<commit_msg>Support drilldown from UI<commit_after>'use strict';

/**
 * @ngdoc function
 * @name groongaAdminApp.controller:TableSearchController
 * @description
 * # TableSearchController
 * Controller of the groongaAdminApp
 */
angular.module('groongaAdminApp')
  .controller('TableSearchController', function ($scope, $routeParams, $location, $http) {
    var client = new GroongaClient($http);

    function computeCurrentPage(offset) {
      return Math.ceil((parseInt(offset) + 1) / $scope.nRecordsInPage);
    }

    function initialize() {
      $scope.table = $routeParams.table;
      $scope.style = 'table';
      $scope.response = {
        rawData: [],
        columns: [],
        records: [],
        drilldowns: [],
        elapsedTimeInMilliseconds: 0,
        nTotalRecords: 0
      };
      $scope.indexedColumns = [];
      $scope.allColumns = [];
      $scope.commandLine = '';
      $scope.message = '';
      $scope.currentPage = 1;
      $scope.nRecordsInPage = 10;
      $scope.maxNPages = 10;
      $scope.parameters = angular.copy($location.search());

      $scope.search = search;
      $scope.clear  = clear;
      $scope.toggleSort = toggleSort;
      $scope.selectDrilldown = selectDrilldown;
    }

    function packColumns(columns) {
      var names = columns.map(function(column) {
        return column.name;
      });
      return names.join(',');
    }

    function packSortColumns(columns) {
      var keys = columns.map(function(column) {
        if (column.sort === 'ascending') {
          return column.name;
        } else {
          return '-' + column.name;
        }
      });
      keys.join(',');
    }

    function search() {
      var parameters = angular.copy($scope.parameters);

      var matchColumns = $scope.indexedColumns.filter(function(column) {
        return column.inUse;
      });
      parameters.match_columns = packColumns(matchColumns);

      var outputColumns = $scope.allColumns.filter(function(column) {
        return column.output;
      });
      parameters.output_columns = packColumns(outputColumns);

      parameters.offset = ($scope.currentPage - 1) * $scope.nRecordsInPage;
      parameters.limit = $scope.nRecordsInPage;

      var sortColumns = $scope.response.columns.filter(function(column) {
        return column.sort;
      });
      parameters.sortby = packSortColumns(sortColumns);

      var drilldowns = $scope.allColumns.filter(function(column) {
        return column.drilldown;
      });
      parameters.drilldown = packColumns(drilldowns);

      parameters.drilldown_sortby = '-_nsubrecs';

      $location.search(parameters);
    }

    function clear() {
      $location.search({});
    }

    function setColumnSort(column, sort) {
      column.sort = sort;
      switch (column.sort) {
      case 'ascending':
        column.iconClass = 'glyphicon-sort-by-attributes';
        break;
      case 'descending':
        column.iconClass = 'glyphicon-sort-by-attributes-alt';
        break;
      default:
        column.iconClass = 'glyphicon-sort';
        break;
      }
    }

    function toggleSort(column) {
      var sort;
      switch (column.sort) {
      case 'ascending':
        sort = 'descending';
        break;
      case 'descending':
        sort = null;
        break;
      default:
        sort = 'ascending';
        break;
      }
      setColumnSort(column, sort);
      search();
    }

    function selectDrilldown(key, value) {
      var query = $scope.parameters.query || '';
      if (query.length > 0) {
        query += ' ';
      }
      $scope.parameters.query = query + key + ':' + value;

      var drilldowns = ($scope.parameters.drilldown || '').split(/\s*,\s*/);
      drilldowns = drilldowns.filter(function(drilldown) {
        return drilldown !== key;
      });
      $scope.parameters.drilldown = drilldowns.join(',');

      search();
    }

    function createColumnInfo(name) {
      var output = true;
      var outputColumns = $scope.parameters.output_columns;
      if (outputColumns) {
        outputColumns = outputColumns.split(/\s*,\s*/);
        output = outputColumns.indexOf(name) !== -1;
      }

      var drilldown = false;
      var drilldowns = $scope.parameters.drilldown;
      if (drilldowns) {
        drilldowns = drilldowns.split(/\s*,\s*/);
        drilldown = drilldowns.indexOf(name) !== -1;
      }

      return {
        name: name,
        output: output,
        drilldown: drilldown
      };
    }

    function extractColumnsInfo(table, columns) {
      if (table.name === $scope.table) {
        columns.forEach(function(column) {
          if (column.isIndex) {
            return;
          }
          $scope.allColumns.push(createColumnInfo(column.name));
        });
      }

      columns.forEach(function(column) {
        if (!column.isIndex) {
          return;
        }
        if (column.range !== $scope.table) {
          return;
        }
        var matchColumns = $scope.parameters.match_columns;
        column.sources.forEach(function(source) {
          var localName;
          if (source.indexOf('.') === -1) {
            localName = '_key';
          } else {
            localName = source.split('.')[1];
          }
          var inUse = true;
          if (matchColumns) {
            inUse = matchColumns.indexOf(localName) !== -1;
          }
          $scope.indexedColumns.push({
            name: localName,
            inUse: inUse
          });
        });
      });
    }

    function extractTableInfo(table) {
      if (table.name === $scope.table) {
        $scope.allColumns.push(createColumnInfo('_id'));
      }

      client.execute('column_list', {table: table.name})
        .success(function(response) {
          extractColumnsInfo(table, response.columns());
        });
    }

    function fillOptions() {
      client.execute('table_list')
        .success(function(response) {
          response.tables().forEach(function(table) {
            extractTableInfo(table);
          });
        });
    }

    function select() {
      var parameters = {
        table: $scope.table
      };
      angular.forEach($scope.parameters, function(value, key) {
        if (key in parameters) {
          return;
        }
        parameters[key] = value;
      });
      var request = client.execute('select', parameters);
      request.success(function(response) {
        $scope.response.rawData = response.rawData();
        $scope.commandLine = request.commandLine();
        $scope.response.elapsedTimeInMilliseconds =
          response.elapsedTime() * 1000;
        if (!response.isSuccess()) {
          $scope.message =
            'Failed to call "select" command: ' + response.errorMessage();
          $scope.response.nTotalRecords = 0;
          return;
        }
        $scope.currentPage = computeCurrentPage(parameters.offset || 0);
        $scope.response.nTotalRecords = response.nTotalRecords();
        var sortKeys = ($scope.parameters.sortby || '').split(/\s*,\s*/);
        $scope.response.columns = response.columns().map(function(column) {
          var sort = null;
          if (sortKeys.indexOf(column.name) !== -1) {
            sort = 'ascending';
          } else if (sortKeys.indexOf('-' + column.name) !== -1) {
            sort = 'descending';
          }
          setColumnSort(column, sort);
          return column;
        });
        $scope.response.records = response.records().map(function(record) {
          return record.map(function(value, index) {
            return {
              value: value,
              column: $scope.response.columns[index]
            };
          });
        });
        $scope.response.drilldowns = response.drilldowns();
        ($scope.parameters.drilldown || '')
          .split(/\s*,\s*/)
          .filter(function(drilldown) {
            return drilldown.length > 0;
          })
          .forEach(function(drilldown, i) {
            $scope.response.drilldowns[i].key = drilldown;
          });
      });
    }

    initialize();
    fillOptions();
    select();
  });
<|endoftext|>


<commit_before>/**
    Home activity (aka Search)
    //used to get apisearch results by term, lat, long,
**/
'use strict';
var $ = require('jquery');
var ko = require('knockout');
var Activity = require('../components/Activity');
var SignupVM = require('../viewmodels/Signup');
var snapPoints = require('../utils/snapPoints');
var SearchJobTitlesVM = require('../viewmodels/SearchJobTitlesVM');
var googleMapReady = require('../utils/googleMapReady');
require('geocomplete');

var A = Activity.extend(function LearnMoreProfessionalsActivity() {

    Activity.apply(this, arguments);
    this.navBar = Activity.createSectionNavBar(null);
    var navBar = this.navBar;
    navBar.additionalNavClasses('AppNav--home');
    this.accessLevel = null;
    this.viewModel = new ViewModel(this.app);
    this.viewModel.nav = this.app.navBarBinding;
    var $header = this.$header = $('.AppNav');

    this.registerHandler({
        target: this.$activity,
        event: 'scroll-fixed-header',
        handler: function(e, what) {
            if (what === 'after') {
                $header.addClass('is-fixed');
            }
            else {
                $header.removeClass('is-fixed');
            }
        }
    });

    // Redircect on success
    this.registerHandler({
        target: this.viewModel.signup,
        event: 'signedup',
        handler: function() {
            var url = '/addJobTitles' + this.viewModel.onboardingUrlParamsString();
            this.app.shell.go(url);
        }.bind(this)
    });

    this.registerHandler({
        target: this.$activity,
        event: 'scroll-search',
        handler: function(e, what) {
            if (what === 'after') {
                $header.addClass('is-search');
            }
            else {
                $header.removeClass('is-search');
            }
        }
    });
    // LOCATION AUTOCOMPLETE:
    // Load Google Maps API with Places and prepare the location autocomplete
    var $location = this.$activity.find('[name=location]');
    googleMapReady(function(/*UNCOMMENT FOR USE THE 'WITHOUT PLUGIN' CODE:*//*google*/) {
        var vm = this.viewModel.search();
        var options = {
            types: ['geocode'],
            bounds: null,
            componentRestrictions: {
                country: 'US'
            }
        };

        // WITH PLUGIN:
        $location.geocomplete(options);
        $location.on('geocode:result', function(e, place) {
            if (place && place.geometry) {
                // Save to viewmodel
                vm.lat(place.geometry.location.lat());
                vm.lng(place.geometry.location.lng());
                vm.city(place.formatted_address);
                console.log('LOCATION: ', place);
            }
        });

        // WITHOUT PLUGIN: Calling direclty Google Maps API, core feature of the plugin
        /*
        var autocomplete = new google.maps.places.Autocomplete(
            $location.get(0), options
        );

        google.maps.event.addListener(
            autocomplete,
            'place_changed',
            function() {
                var place = autocomplete.getPlace();
                if (place && place.geometry) {
                    // Save to viewmodel
                    vm.lat(place.geometry.location.lat());
                    vm.lng(place.geometry.location.lng());
                    console.log('LOCATION: ', place.geometry);
                }
            }
        );*/
    }.bind(this));
});

exports.init = A.init;

A.prototype._registerSnapPoints = function() {

    var $searchBox = this.$activity.find('[name=s]'),
        // Calculate the position where search box is completely hidden, and get 1 on the worse case -- bad value coerced to 0,
        // negative result because some lack of data (content hidden)
        searchPoint = Math.max(1, (
            // Top offset with the scrolling area plus current scrollTop to know the actual position inside the positioning context
            // (is an issue if the section is showed with scroll applied on the activity)
            $searchBox.offset().top + this.$activity.scrollTop() +
            // Add the box height but sustract the header height because that is fixed and overlaps
            $searchBox.outerHeight() - this.$header.outerHeight()
        ) |0);

    var pointsEvents = {
        // Just after start scrolling
        0: 'scroll-fixed-header'
    };
    pointsEvents[searchPoint] = 'scroll-search';

    snapPoints(this.$activity, pointsEvents);
};

A.prototype.show = function show(state) {
    Activity.prototype.show.call(this, state);

    if (!this._notFirstShow) {
        this._registerSnapPoints();
        this._notFirstShow = true;
    }
    this.viewModel.search().searchTerm('');
};

function ViewModel(app) {
    this.isServiceProfessional = ko.pureComputed(function() {
        var u = app.model.user();
        return u && u.isServiceProfessional();
    });
    this.isClient = ko.pureComputed(function() {
        var u = app.model.user();
        return u && u.isClient();
    });
    //Signup
    this.signup = new SignupVM(app);
    this.signup.profile('service-professional');

    this.onboardingUrlParamsString = ko.observable();
    // API entry-point for search component
    this.search = ko.observable(new SearchJobTitlesVM(app));
    this.search().onClickJobTitle = function(jobTitle, e) {
        // For anonymous users, we just let the link to scroll down to sign-up form (hash link must be in place)
        // For logged users, assist them to add the job title:
        if (!app.model.userProfile.data.isAnonymous()) {
            e.preventDefault();
            e.stopImmediatePropagation();

            var url = 'addJobTitles?s=' + encodeURIComponent(jobTitle.singularName()) + '&id=' + encodeURIComponent(jobTitle.jobTitleID());
            app.shell.go(url);
        }
        else {
            // After sign-up, the parameters must be provided to the onboarding
            this.onboardingUrlParamsString('?s=' + encodeURIComponent(jobTitle.singularName()) + '&id=' + encodeURIComponent(jobTitle.jobTitleID()));
        }
    }.bind(this);
    this.search().onClickNoJobTitle = function(jobTitleName, e) {
        // For anonymous users, we just let the link to scroll down to sign-up form (hash link must be in place)
        // For logged users, assist them to add the job title:
        if (!app.model.userProfile.data.isAnonymous()) {
            e.preventDefault();
            e.stopImmediatePropagation();
            // Go to addJobTitles
            var url = 'addJobTitles?s=' + encodeURIComponent(jobTitleName) + '&autoAddNew=true';
            app.shell.go(url);
        }
        else {
            // After sign-up, the parameters must be provided to the onboarding
            this.onboardingUrlParamsString('?s=' + encodeURIComponent(jobTitleName) + '&autoAddNew=true');
        }
    };
    this.search().jobTitleHref('#learnMoreProfessionals-signup');
    this.search().noJobTitleHref('#learnMoreProfessionals-signup');
}
<commit_msg>Fixed bug #1118<commit_after>/**
    Home activity (aka Search)
    //used to get apisearch results by term, lat, long,
**/
'use strict';
var $ = require('jquery');
var ko = require('knockout');
var Activity = require('../components/Activity');
var SignupVM = require('../viewmodels/Signup');
var snapPoints = require('../utils/snapPoints');
var SearchJobTitlesVM = require('../viewmodels/SearchJobTitlesVM');
var googleMapReady = require('../utils/googleMapReady');
require('geocomplete');

var A = Activity.extend(function LearnMoreProfessionalsActivity() {

    Activity.apply(this, arguments);
    this.navBar = Activity.createSectionNavBar(null);
    var navBar = this.navBar;
    navBar.additionalNavClasses('AppNav--home');
    this.accessLevel = null;
    this.viewModel = new ViewModel(this.app);
    this.viewModel.nav = this.app.navBarBinding;
    var $header = this.$header = $('.AppNav');

    this.registerHandler({
        target: this.$activity,
        event: 'scroll-fixed-header',
        handler: function(e, what) {
            if (what === 'after') {
                $header.addClass('is-fixed');
            }
            else {
                $header.removeClass('is-fixed');
            }
        }
    });

    // Redircect on success
    this.registerHandler({
        target: this.viewModel.signup,
        event: 'signedup',
        handler: function() {
            var url = '/addJobTitles' + (this.viewModel.onboardingUrlParamsString() || '');
            this.app.shell.go(url);
        }.bind(this)
    });

    this.registerHandler({
        target: this.$activity,
        event: 'scroll-search',
        handler: function(e, what) {
            if (what === 'after') {
                $header.addClass('is-search');
            }
            else {
                $header.removeClass('is-search');
            }
        }
    });
    // LOCATION AUTOCOMPLETE:
    // Load Google Maps API with Places and prepare the location autocomplete
    var $location = this.$activity.find('[name=location]');
    googleMapReady(function(/*UNCOMMENT FOR USE THE 'WITHOUT PLUGIN' CODE:*//*google*/) {
        var vm = this.viewModel.search();
        var options = {
            types: ['geocode'],
            bounds: null,
            componentRestrictions: {
                country: 'US'
            }
        };

        // WITH PLUGIN:
        $location.geocomplete(options);
        $location.on('geocode:result', function(e, place) {
            if (place && place.geometry) {
                // Save to viewmodel
                vm.lat(place.geometry.location.lat());
                vm.lng(place.geometry.location.lng());
                vm.city(place.formatted_address);
                console.log('LOCATION: ', place);
            }
        });

        // WITHOUT PLUGIN: Calling direclty Google Maps API, core feature of the plugin
        /*
        var autocomplete = new google.maps.places.Autocomplete(
            $location.get(0), options
        );

        google.maps.event.addListener(
            autocomplete,
            'place_changed',
            function() {
                var place = autocomplete.getPlace();
                if (place && place.geometry) {
                    // Save to viewmodel
                    vm.lat(place.geometry.location.lat());
                    vm.lng(place.geometry.location.lng());
                    console.log('LOCATION: ', place.geometry);
                }
            }
        );*/
    }.bind(this));
});

exports.init = A.init;

A.prototype._registerSnapPoints = function() {

    var $searchBox = this.$activity.find('[name=s]'),
        // Calculate the position where search box is completely hidden, and get 1 on the worse case -- bad value coerced to 0,
        // negative result because some lack of data (content hidden)
        searchPoint = Math.max(1, (
            // Top offset with the scrolling area plus current scrollTop to know the actual position inside the positioning context
            // (is an issue if the section is showed with scroll applied on the activity)
            $searchBox.offset().top + this.$activity.scrollTop() +
            // Add the box height but sustract the header height because that is fixed and overlaps
            $searchBox.outerHeight() - this.$header.outerHeight()
        ) |0);

    var pointsEvents = {
        // Just after start scrolling
        0: 'scroll-fixed-header'
    };
    pointsEvents[searchPoint] = 'scroll-search';

    snapPoints(this.$activity, pointsEvents);
};

A.prototype.show = function show(state) {
    Activity.prototype.show.call(this, state);

    if (!this._notFirstShow) {
        this._registerSnapPoints();
        this._notFirstShow = true;
    }
    this.viewModel.search().searchTerm('');
};

function ViewModel(app) {
    this.isServiceProfessional = ko.pureComputed(function() {
        var u = app.model.user();
        return u && u.isServiceProfessional();
    });
    this.isClient = ko.pureComputed(function() {
        var u = app.model.user();
        return u && u.isClient();
    });
    //Signup
    this.signup = new SignupVM(app);
    this.signup.profile('service-professional');

    this.onboardingUrlParamsString = ko.observable();
    // API entry-point for search component
    this.search = ko.observable(new SearchJobTitlesVM(app));
    this.search().onClickJobTitle = function(jobTitle, e) {
        // For anonymous users, we just let the link to scroll down to sign-up form (hash link must be in place)
        // For logged users, assist them to add the job title:
        if (!app.model.userProfile.data.isAnonymous()) {
            e.preventDefault();
            e.stopImmediatePropagation();

            var url = 'addJobTitles?s=' + encodeURIComponent(jobTitle.singularName()) + '&id=' + encodeURIComponent(jobTitle.jobTitleID());
            app.shell.go(url);
        }
        else {
            // After sign-up, the parameters must be provided to the onboarding
            this.onboardingUrlParamsString('?s=' + encodeURIComponent(jobTitle.singularName()) + '&id=' + encodeURIComponent(jobTitle.jobTitleID()));
        }
    }.bind(this);
    this.search().onClickNoJobTitle = function(jobTitleName, e) {
        // For anonymous users, we just let the link to scroll down to sign-up form (hash link must be in place)
        // For logged users, assist them to add the job title:
        if (!app.model.userProfile.data.isAnonymous()) {
            e.preventDefault();
            e.stopImmediatePropagation();
            // Go to addJobTitles
            var url = 'addJobTitles?s=' + encodeURIComponent(jobTitleName) + '&autoAddNew=true';
            app.shell.go(url);
        }
        else {
            // After sign-up, the parameters must be provided to the onboarding
            this.onboardingUrlParamsString('?s=' + encodeURIComponent(jobTitleName) + '&autoAddNew=true');
        }
    };
    this.search().jobTitleHref('#learnMoreProfessionals-signup');
    this.search().noJobTitleHref('#learnMoreProfessionals-signup');
}
<|endoftext|>


<commit_before>// BusinessProcess submission forms (step 3 of Part A) resolution

'use strict';

var memoize                     = require('memoizee/plain')
  , definePropertyGroupsProcess = require('../lib/property-groups-process')
  , defineInitial               = require('./base');

module.exports = memoize(function (db/* options */) {
	var BusinessProcess       = defineInitial(db, arguments[1])
	  , PropertyGroupsProcess = definePropertyGroupsProcess(db);

	BusinessProcess.prototype.defineProperties({
		submissionForms: { type: PropertyGroupsProcess, nested: true }
	});
	BusinessProcess.prototype.submissionForms.defineProperties({
		// Required confirmation from user, presented as last step before file submission
		isAffidavitSigned: { type: db.Boolean, required: true },
		progress: { value: function (_observe) {
			var superGetter = this.database.PropertyGroupsProcess.getDescriptor('progress')._value_
			  , total, valid, progress;
			superGetter = this.database.resolveGetterObservables(superGetter);
			progress = superGetter.call(this, _observe);
			total = this.weight - 1;
			if (!total && (progress < 1)) {
				++total;
				valid = progress;
			} else {
				valid = progress * total;
			}
			++total;
			if (this.isAffidavitSigned) ++valid;
			return valid / total;
		} },
		weight: { value: function (_observe) {
			var superGetter = this.database.PropertyGroupsProcess.getDescriptor('weight')._value_;
			superGetter = this.database.resolveGetterObservables(superGetter);
			return superGetter.call(this, _observe) + 1;
		} }
	});
	return BusinessProcess;
}, { normalizer: require('memoizee/normalizers/get-1')() });
<commit_msg>Fix getter resolution<commit_after>// BusinessProcess submission forms (step 3 of Part A) resolution

'use strict';

var memoize                     = require('memoizee/plain')
  , definePropertyGroupsProcess = require('../lib/property-groups-process')
  , defineInitial               = require('./base');

module.exports = memoize(function (db/* options */) {
	var BusinessProcess       = defineInitial(db, arguments[1])
	  , PropertyGroupsProcess = definePropertyGroupsProcess(db);

	BusinessProcess.prototype.defineProperties({
		submissionForms: { type: PropertyGroupsProcess, nested: true }
	});
	BusinessProcess.prototype.submissionForms.defineProperties({
		// Required confirmation from user, presented as last step before file submission
		isAffidavitSigned: { type: db.Boolean, required: true },
		progress: { value: function (_observe) {
			var superGetter, total, valid, progress;
			superGetter = this.database.PropertyGroupsProcess.prototype.getDescriptor('progress')._value_;
			superGetter = this.database.resolveGetterObservables(superGetter);
			progress = superGetter.call(this, _observe);
			total = this.weight - 1;
			if (!total && (progress < 1)) {
				++total;
				valid = progress;
			} else {
				valid = progress * total;
			}
			++total;
			if (this.isAffidavitSigned) ++valid;
			return valid / total;
		} },
		weight: { value: function (_observe) {
			var superGetter = this.database.PropertyGroupsProcess.getDescriptor('weight')._value_;
			superGetter = this.database.resolveGetterObservables(superGetter);
			return superGetter.call(this, _observe) + 1;
		} }
	});
	return BusinessProcess;
}, { normalizer: require('memoizee/normalizers/get-1')() });
<|endoftext|>


<commit_before>import createRenderer from '../../createRenderer'
import monolithic from '../monolithic'

const options = { enhancers: [monolithic()] }

describe('Monolithic enhancer', () => {
  it('should add a cache entry', () => {
    const rule = () => ({ color: 'red' })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(renderer.cache.hasOwnProperty(className)).toEqual(true)
  })

  it('should reuse classNames', () => {
    const rule = props => ({
      color: props.color,
      fontSize: '23px'
    })
    const renderer = createRenderer(options)

    const className1 = renderer.renderRule(rule, { color: 'red' })
    const className2 = renderer.renderRule(rule, { color: 'red' })

    expect(className1).toEqual(className2)
  })

  it('should return an empty string if the style is empty', () => {
    const rule = () => ({})
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(className).toEqual('')
  })

  it('should remove undefined values', () => {
    const rule = props => ({
      color: props.color,
      fontSize: '15px'
    })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(`.${className}{font-size:15px}`)
  })

  it('should allow nested props', () => {
    const rule = props => ({
      color: props.theme.color,
      fontSize: 15
    })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule, { theme: { color: 'red' } })

    expect(renderer.rules).toEqual(`.${className}{color:red;font-size:15}`)
  })

  it('should render pseudo classes', () => {
    const rule = () => ({
      color: 'red',
      ':hover': { color: 'blue' }
    })

    const renderer = createRenderer(options)
    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(
      `.${className}{color:red}.${className}:hover{color:blue}`
    )
  })

  it('should prefix classNames', () => {
    const rule = () => ({ color: 'red' })

    const renderer = createRenderer({ selectorPrefix: 'fela_' })
    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(`.${className}{color:red}`)
    expect(className).toContain('fela_')
  })

  it('should render attribute selectors', () => {
    const rule = () => ({
      color: 'red',
      '[bool=true]': { color: 'blue' }
    })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(
      `.${className}{color:red}.${className}[bool=true]{color:blue}`
    )
  })

  it('should render child selectors', () => {
    const rule = () => ({
      color: 'red',
      '>div': { color: 'blue' }
    })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(
      `.${className}{color:red}.${className}>div{color:blue}`
    )
  })

  it('should render any nested selector with the &-prefix', () => {
    const rule = () => ({
      color: 'red',
      '&~#foo': { color: 'blue' },
      '& .bar': { color: 'green' }
    })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(
      `.${className}{color:red}.${className}~#foo{color:blue}.${className} .bar{color:green}`
    )
  })

  it('should render media queries', () => {
    const rule = () => ({
      color: 'red',
      '@media (min-height:300px)': { color: 'blue' }
    })

    const renderer = createRenderer(options)
    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(`.${className}{color:red}`)
    expect(renderer.mediaRules['(min-height:300px)']).toEqual(
      `.${className}{color:blue}`
    )
  })

  it('should use custom className if defined', () => {
    const rule = () => ({
      className: 'custom',
      color: 'red'
    })

    const renderer = createRenderer(options)
    renderer.renderRule(rule)

    expect(renderer.rules).toEqual('.fela-custom{color:red}')
  })
})
<commit_msg>Add test for wrong reuse of classnames with monolithic<commit_after>import createRenderer from '../../createRenderer'
import monolithic from '../monolithic'

const options = { enhancers: [monolithic()] }

describe('Monolithic enhancer', () => {
  it('should add a cache entry', () => {
    const rule = () => ({ color: 'red' })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(renderer.cache.hasOwnProperty(className)).toEqual(true)
  })

  it('should reuse classNames', () => {
    const rule = props => ({
      color: props.color,
      fontSize: '23px'
    })
    const renderer = createRenderer(options)

    const className1 = renderer.renderRule(rule, { color: 'red' })
    const className2 = renderer.renderRule(rule, { color: 'red' })

    expect(className1).toEqual(className2)
  })

  it('should return an empty string if the style is empty', () => {
    const rule = () => ({})
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(className).toEqual('')
  })

  it('should remove undefined values', () => {
    const rule = props => ({
      color: props.color,
      fontSize: '15px'
    })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(`.${className}{font-size:15px}`)
  })

  it('should allow nested props', () => {
    const rule = props => ({
      color: props.theme.color,
      fontSize: 15
    })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule, { theme: { color: 'red' } })

    expect(renderer.rules).toEqual(`.${className}{color:red;font-size:15}`)
  })

  it('should render pseudo classes', () => {
    const rule = () => ({
      color: 'red',
      ':hover': { color: 'blue' }
    })

    const renderer = createRenderer(options)
    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(
      `.${className}{color:red}.${className}:hover{color:blue}`
    )
  })

  it('should prefix classNames', () => {
    const rule = () => ({ color: 'red' })

    const renderer = createRenderer({ selectorPrefix: 'fela_' })
    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(`.${className}{color:red}`)
    expect(className).toContain('fela_')
  })

  it('should render attribute selectors', () => {
    const rule = () => ({
      color: 'red',
      '[bool=true]': { color: 'blue' }
    })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(
      `.${className}{color:red}.${className}[bool=true]{color:blue}`
    )
  })

  it('should render child selectors', () => {
    const rule = () => ({
      color: 'red',
      '>div': { color: 'blue' }
    })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(
      `.${className}{color:red}.${className}>div{color:blue}`
    )
  })

  it('should render any nested selector with the &-prefix', () => {
    const rule = () => ({
      color: 'red',
      '&~#foo': { color: 'blue' },
      '& .bar': { color: 'green' }
    })
    const renderer = createRenderer(options)

    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(
      `.${className}{color:red}.${className}~#foo{color:blue}.${className} .bar{color:green}`
    )
  })

  it('should render media queries', () => {
    const rule = () => ({
      color: 'red',
      '@media (min-height:300px)': { color: 'blue' }
    })

    const renderer = createRenderer(options)
    const className = renderer.renderRule(rule)

    expect(renderer.rules).toEqual(`.${className}{color:red}`)
    expect(renderer.mediaRules['(min-height:300px)']).toEqual(
      `.${className}{color:blue}`
    )
  })

  it('should use custom className if defined', () => {
    const rule = () => ({
      className: 'custom',
      color: 'red'
    })

    const renderer = createRenderer(options)
    renderer.renderRule(rule)

    expect(renderer.rules).toEqual('.fela-custom{color:red}')
  })

  it('should create different classNames for different styles', () => {
    const rule1 = () => ({
      className: 'custom',
      color: 'red'
    })
    const rule2 = () => ({
      className: 'custom',
      color: 'green'
    })
    const renderer = createRenderer(options)
    const className1 = renderer.renderRule(rule1)
    const className2 = renderer.renderRule(rule2)

    expect(className1).not.toBe(className2)
  })
})
<|endoftext|>


<commit_before>'use strict';

angular.module('nin')
  .controller('BottomCtrl', function ($scope, $interval, socket, camera, commands) {

    var linesContainer = null;

    $scope.onBottomScroll = function(event) {
      linesContainer = event.target;
      $scope.bottomScrollOffset = event.target.scrollLeft;
    };

    $scope.musicLayerClick = function($event) {
      $scope.demo.jumpToFrame($event.offsetX);
    };

    $scope.layerLabelClick = function(layer) {
      $scope.$parent.$parent.inspectedLayer = $scope.inspectedLayer == layer
                                            ? null
                                            : layer;
      camera.startEdit(layer);
    };

    $scope.dragResizeLayer = function(event, ui, layer) {
      if (ui.position.left != layer.startFrame) {
        socket.sendEvent('set', {
          id: layer.position,
          field: 'startFrame',
          value: ui.position.left
        });
      } else {
        socket.sendEvent('set', {
          id: layer.position,
          field: 'endFrame',
          value: ui.position.left + ui.size.width
        });
      }
    };

    commands.on('setCuePoint', function() {
      if ($scope.loopStart == null) {
        $scope.loopStart = $scope.currentFrame;
      } else if ($scope.loopEnd == null) {
        if ($scope.loopStart > $scope.currentFrame) {
          $scope.loopEnd = $scope.loopStart;
          $scope.loopStart = $scope.currentFrame;
        } else {
          $scope.loopEnd = $scope.currentFrame;
        }
        $scope.loopActive = true;
      } else {
        $scope.loopStart = null;
        $scope.loopEnd = null;
        $scope.loopActive = false;
      }
    });

    $scope.$watch('currentFrame', function (nextFrame) {
      if ($scope.loopActive && nextFrame >= $scope.loopEnd) {
        $scope.demo.jumpToFrame($scope.loopStart);
      }
    });

    $interval(function(){
      $scope.hideMarker = false;
      if(!linesContainer) {
        return;
      }
      if(linesContainer.scrollLeft > $scope.currentFrame ||
        $scope.currentFrame >= linesContainer.scrollLeft + $(linesContainer).width()) {
        $scope.hideMarker = true;
      } else {
        $scope.hideMarker = false;
      }
    }, 1000 / 60);
  });
<commit_msg>Add offsetX shim for firefox et al compatibility<commit_after>'use strict';

angular.module('nin')
  .controller('BottomCtrl', function ($scope, $interval, socket, camera, commands) {

    var linesContainer = null;

    $scope.onBottomScroll = function(event) {
      linesContainer = event.target;
      $scope.bottomScrollOffset = event.target.scrollLeft;
    };

    $scope.musicLayerClick = function($event) {
      var target = $event.target || $event.srcElement,
        rect = target.getBoundingClientRect(),
        offsetX = $event.clientX - rect.left;

      $scope.demo.jumpToFrame(offsetX);
    };

    $scope.layerLabelClick = function(layer) {
      $scope.$parent.$parent.inspectedLayer = $scope.inspectedLayer == layer
                                            ? null
                                            : layer;
      camera.startEdit(layer);
    };

    $scope.dragResizeLayer = function(event, ui, layer) {
      if (ui.position.left != layer.startFrame) {
        socket.sendEvent('set', {
          id: layer.position,
          field: 'startFrame',
          value: ui.position.left
        });
      } else {
        socket.sendEvent('set', {
          id: layer.position,
          field: 'endFrame',
          value: ui.position.left + ui.size.width
        });
      }
    };

    commands.on('setCuePoint', function() {
      if ($scope.loopStart == null) {
        $scope.loopStart = $scope.currentFrame;
      } else if ($scope.loopEnd == null) {
        if ($scope.loopStart > $scope.currentFrame) {
          $scope.loopEnd = $scope.loopStart;
          $scope.loopStart = $scope.currentFrame;
        } else {
          $scope.loopEnd = $scope.currentFrame;
        }
        $scope.loopActive = true;
      } else {
        $scope.loopStart = null;
        $scope.loopEnd = null;
        $scope.loopActive = false;
      }
    });

    $scope.$watch('currentFrame', function (nextFrame) {
      if ($scope.loopActive && nextFrame >= $scope.loopEnd) {
        $scope.demo.jumpToFrame($scope.loopStart);
      }
    });

    $interval(function(){
      $scope.hideMarker = false;
      if(!linesContainer) {
        return;
      }
      if(linesContainer.scrollLeft > $scope.currentFrame ||
        $scope.currentFrame >= linesContainer.scrollLeft + $(linesContainer).width()) {
        $scope.hideMarker = true;
      } else {
        $scope.hideMarker = false;
      }
    }, 1000 / 60);
  });
<|endoftext|>