<commit_before>"use strict";

/**
 * @ngdoc service
 * @name mdvorakDataRouter.$dataRouterProvider
 *
 * @description
 * Allows simple configuration of all parts of the data router in one place.
 */
module.provider('$dataRouter', function $dataRouterProvider($$dataRouterMatchMap, $dataRouterRegistryProvider, $dataRouterLoaderProvider, $dataApiProvider) {
    var provider = this;

    /**
     * Enables the router. May be used to disable the router event handling. Cannot be changed after config phase.
     * @type {boolean}
     */
    provider.$enabled = true;

    /**
     * Map of redirects. Do not modify directly, use redirect function.
     * @type {Object}
     */
    provider.$redirects = $$dataRouterMatchMap.create();

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterProvider
     * @name apiPrefix
     *
     * @description
     * Configures prefix for default view to resource mapping.
     *
     * This is just an alias for {@link mdvorakDataApi.$dataApiProvider#methods_prefix $dataApiProvider.prefix(prefix)}
     * method, see its documentation for details.
     *
     * @param {String} prefix Relative URL prefix, relative to base href.
     * @return {String} API URL prefix. It's always normalized absolute URL, includes base href.
     */
    provider.apiPrefix = function apiPrefix(prefix) {
        return $dataApiProvider.prefix(prefix);
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterRegistryProvider
     * @name when
     *
     * @description
     * Configures view for given content type.
     *
     * This is just an alias for
     * {@link mdvorakDataRouter.$dataRouterRegistryProvider#methods_when $dataRouterRegistryProvider.when(mediaType,config)}
     * method, see its documentation for details.
     *
     * @param {String|Function} mediaType Content type to match.
     * @param {Object} config Configuration object.
     */
    provider.when = function when(mediaType, config) {
        $dataRouterRegistryProvider.when(mediaType, config);
        return provider;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterRegistryProvider
     * @name error
     *
     * @description
     * Configures view for error page. Error page is displayed when resource or view template cannot be loaded or
     * any of the resolvables fails.
     *
     * This is just an alias for
     * {@link mdvorakDataRouter.$dataRouterRegistryProvider#methods_error $dataRouterRegistryProvider.error(config)}
     * method, see its documentation for details.
     *
     * @param {Object} config Configuration object, as in
     * {@link mdvorakDataRouter.$dataRouterRegistryProvider#methods_when when(config)}.
     */
    provider.error = function error(config) {
        $dataRouterRegistryProvider.error(angular.copy(config));
        return provider;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterRegistryProvider
     * @name redirect
     *
     * @description
     * Forces redirect from one location to another.
     *
     * @param {String} path View to force redirect on. Supports wildcards. Parameters are not supported
     * @param {String} redirectTo View path which should be redirected to.
     * @returns {Object} Returns the provider.
     */
    provider.redirect = function redirect(path, redirectTo) {
        if (redirectTo) {
            provider.$redirects.addMatcher(path, redirectTo);
        }

        return provider;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterProvider
     * @name global
     *
     * @description
     * Sets global configuration for all routes. It is then merged into each view configuration, with view taking precedence.
     *
     * This is just an alias for
     * {@link mdvorakDataRouter.$dataRouterLoaderProvider#methods_global $dataRouterLoaderProvider.global(config)},
     * see its documentation for details.
     *
     * @param {Object} config Configuration object. Currently only `"resolve"` key is supported.
     * @returns {Object} Reference to the provider.
     */
    provider.global = function global(config) {
        $dataRouterLoaderProvider.global(config);
        return provider;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterProvider
     * @name enabled
     *
     * @description
     * Enables  or disables the router. May be used to disable the router event handling.
     * Cannot be changed after config phase.
     *
     * This method is intended mostly for unit tests.
     *
     * Enabled by default.
     *
     * @param {boolean} enabled Set to `false` to disable the router.
     * @returns {Object} Returns provider.
     */
    provider.enabled = function enabledFn(enabled) {
        provider.$enabled = !!enabled;
        return provider;
    };

    /**
     * @ngdoc service
     * @name mdvorakDataRouter.$dataRouter
     *
     * @description
     * Centerpiece of the data router. It tracks the `$location` and loads the main view data.
     */
    this.$get = function $dataRouterFactory($log, $location, $rootScope, $q, $dataRouterRegistry, $dataRouterLoader, $dataApi, $$dataRouterEventSupport) {
        /**
         * @ngdoc event
         * @eventOf mdvorakDataRouter.$dataRouter
         * @name $routeChangeStart
         * @eventType broadcast on root scope
         *
         * @description
         * This event is broadcasted whenever main view is about to change. The change can be averted
         * by setting preventDefault on the vent.
         */

        /**
         * @ngdoc event
         * @eventOf mdvorakDataRouter.$dataRouter
         * @name $routeChangeSuccess
         * @eventType broadcast on root scope
         *
         * @description
         * This event is broadcasted whenever main view has changed and the data are loaded.
         * View is ready to be displayed.
         */

        /**
         * @ngdoc event
         * @eventOf mdvorakDataRouter.$dataRouter
         * @name $routeChangeError
         * @eventType broadcast on root scope
         *
         * @description
         * This event is broadcasted when the view or data failed to load and there is no error view configured
         * (or error view failed to load). There is not much you can do at this point, it probably means you have
         * configuration error in your application.
         */

        var $dataRouter = {
            /**
             * @ngdoc property
             * @propertyOf mdvorakDataRouter.$dataRouter
             * @name api
             * @returns {Object} Reference to the {@link mdvorakDataApi.$dataApi $dataApi} instance.
             *
             * @description
             * Its here to make your life easier.
             */
            api: $dataApi,

            /**
             * @ngdoc property
             * @propertyOf mdvorakDataRouter.$dataRouter
             * @name registry
             * @returns {Object} Reference to the {@link mdvorakDataRouter.$dataRouterRegistry $dataRouterRegistry} instance.
             *
             * @description
             * Its here to make your life easier.
             */
            registry: $dataRouterRegistry,

            /**
             * @ngdoc property
             * @propertyOf mdvorakDataRouter.$dataRouter
             * @name current
             * @returns {Object} Currently loaded response for the main view is available here.
             * See {@link locals.$dataResponse $dataResponse} for its signature.
             *
             * @description
             * Note that you should in most cases use {@link locals.$dataResponse $dataResponse} object in the
             * controller to work with the data instead of this reference.
             */
            current: undefined,

            /**
             * Reloads data at current location. If content type remains same, only data are refreshed,
             * and $routeUpdate event is invoked on $dataResponse object. If content type differs,
             * full view refresh is performed (that is, controller is destroyed and recreated).
             * <p>
             * If you refresh data, you must listen to the $routeUpdate event on $dataResponse object to be notified of the change.
             *
             * @param {boolean=} forceReload If true, page is always refreshed (controller recreated). Otherwise only
             *                               when needed.
             */
            $$reload: function reload(forceReload) {
                var path = $location.path() || '/';
                var redirectTo;
                var url;
                var next = $dataRouter.$$next = {};

                // Forced redirect
                if ((redirectTo = provider.$redirects.match(path))) {
                    $log.debug("Redirecting to " + redirectTo);
                    $location.path(redirectTo).replace();
                    return;
                }

                // Load resource
                url = $dataApi.mapViewToApi($location.path());

                // Load data and view
                $log.debug("Loading main view");
                $dataRouterLoader.prepareView(url, $dataRouter.current, forceReload)
                    .then(showView, routeChangeFailed);

                // Promise resolutions
                function showView(response) {
                    if ($dataRouter.$$next === next) {
                        // Remove marker
                        $dataRouter.$$next = undefined;

                        // Update view data
                        if (response.routeDataUpdate && $dataRouter.current) {
                            $log.debug("Replacing current data");

                            // Update current (preserve listeners)
                            $$dataRouterEventSupport.$$extend($dataRouter.current, response);

                            // Fire event on the response (only safe way for both main view and fragments)
                            $dataRouter.current.$broadcast('$routeUpdate', $dataRouter.current);
                        } else {
                            $log.debug("Setting view to " + response.mediaType);

                            // Add reload implementation
                            response.reload = $dataRouter.$$reload;

                            // Set current
                            $dataRouter.current = response;

                            // Emit event
                            $rootScope.$broadcast('$routeChangeSuccess', response);
                        }
                    }
                }

                function routeChangeFailed(response) {
                    // Error handler
                    if ($dataRouter.$$next === next) {
                        // Remove next, but don't update current
                        $dataRouter.$$next = undefined;

                        // Show error view
                        $log.error("Failed to load view or data and no error view defined", response);
                        $rootScope.$broadcast('$routeChangeError', response);
                    }
                }
            }
        };

        if (provider.$enabled) {
            // Broadcast $routeChangeStart and cancel location change if it is prevented
            $rootScope.$on('$locationChangeStart', function locationChangeStart($locationEvent) {
                if ($rootScope.$broadcast('$routeChangeStart').defaultPrevented) {
                    if ($locationEvent) {
                        $locationEvent.preventDefault();
                    }
                }
            });

            // Reload view on location change
            $rootScope.$on('$locationChangeSuccess', function locationChangeSuccess() {
                $dataRouter.$$reload(true);
            });
        }

        return $dataRouter;
    };
});
<commit_msg>fixes in ngdocs<commit_after>"use strict";

/**
 * @ngdoc service
 * @name mdvorakDataRouter.$dataRouterProvider
 *
 * @description
 * Allows simple configuration of all parts of the data router in one place.
 */
module.provider('$dataRouter', function $dataRouterProvider($$dataRouterMatchMap, $dataRouterRegistryProvider, $dataRouterLoaderProvider, $dataApiProvider) {
    var provider = this;

    /**
     * Enables the router. May be used to disable the router event handling. Cannot be changed after config phase.
     * @type {boolean}
     */
    provider.$enabled = true;

    /**
     * Map of redirects. Do not modify directly, use redirect function.
     * @type {Object}
     */
    provider.$redirects = $$dataRouterMatchMap.create();

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterProvider
     * @name apiPrefix
     *
     * @description
     * Configures prefix for default view to resource mapping.
     *
     * This is just an alias for {@link mdvorakDataApi.$dataApiProvider#methods_prefix $dataApiProvider.prefix(prefix)}
     * method, see its documentation for details.
     *
     * @param {String} prefix Relative URL prefix, relative to base href.
     * @return {String} API URL prefix. It's always normalized absolute URL, includes base href.
     */
    provider.apiPrefix = function apiPrefix(prefix) {
        return $dataApiProvider.prefix(prefix);
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterProvider
     * @name when
     *
     * @description
     * Configures view for given content type.
     *
     * This is just an alias for
     * {@link mdvorakDataRouter.$dataRouterRegistryProvider#methods_when $dataRouterRegistryProvider.when(mediaType,config)}
     * method, see its documentation for details.
     *
     * @param {String|Function} mediaType Content type to match.
     * @param {Object} config Configuration object.
     */
    provider.when = function when(mediaType, config) {
        $dataRouterRegistryProvider.when(mediaType, config);
        return provider;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterProvider
     * @name error
     *
     * @description
     * Configures view for error page. Error page is displayed when resource or view template cannot be loaded or
     * any of the resolvables fails.
     *
     * This is just an alias for
     * {@link mdvorakDataRouter.$dataRouterRegistryProvider#methods_error $dataRouterRegistryProvider.error(config)}
     * method, see its documentation for details.
     *
     * @param {Object} config Configuration object, as in
     * {@link mdvorakDataRouter.$dataRouterRegistryProvider#methods_when when(config)}.
     */
    provider.error = function error(config) {
        $dataRouterRegistryProvider.error(angular.copy(config));
        return provider;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterProvider
     * @name redirect
     *
     * @description
     * Forces redirect from one location to another.
     *
     * @param {String} path View to force redirect on. Supports wildcards. Parameters are not supported
     * @param {String} redirectTo View path which should be redirected to.
     * @returns {Object} Returns the provider.
     */
    provider.redirect = function redirect(path, redirectTo) {
        if (redirectTo) {
            provider.$redirects.addMatcher(path, redirectTo);
        }

        return provider;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterProvider
     * @name global
     *
     * @description
     * Sets global configuration for all routes. It is then merged into each view configuration, with view taking precedence.
     *
     * This is just an alias for
     * {@link mdvorakDataRouter.$dataRouterLoaderProvider#methods_global $dataRouterLoaderProvider.global(config)},
     * see its documentation for details.
     *
     * @param {Object} config Configuration object. Currently only `"resolve"` key is supported.
     * @returns {Object} Reference to the provider.
     */
    provider.global = function global(config) {
        $dataRouterLoaderProvider.global(config);
        return provider;
    };

    /**
     * @ngdoc method
     * @methodOf mdvorakDataRouter.$dataRouterProvider
     * @name enabled
     *
     * @description
     * Enables  or disables the router. May be used to disable the router event handling.
     * Cannot be changed after config phase.
     *
     * This method is intended mostly for unit tests.
     *
     * Enabled by default.
     *
     * @param {boolean} enabled Set to `false` to disable the router.
     * @returns {Object} Returns provider.
     */
    provider.enabled = function enabledFn(enabled) {
        provider.$enabled = !!enabled;
        return provider;
    };

    /**
     * @ngdoc service
     * @name mdvorakDataRouter.$dataRouter
     *
     * @description
     * Centerpiece of the data router. It tracks the `$location` and loads the main view data.
     */
    this.$get = function $dataRouterFactory($log, $location, $rootScope, $q, $dataRouterRegistry, $dataRouterLoader, $dataApi, $$dataRouterEventSupport) {
        /**
         * @ngdoc event
         * @eventOf mdvorakDataRouter.$dataRouter
         * @name $routeChangeStart
         * @eventType broadcast on root scope
         *
         * @description
         * This event is broadcasted whenever main view is about to change. The change can be averted
         * by setting preventDefault on the vent.
         */

        /**
         * @ngdoc event
         * @eventOf mdvorakDataRouter.$dataRouter
         * @name $routeChangeSuccess
         * @eventType broadcast on root scope
         *
         * @description
         * This event is broadcasted whenever main view has changed and the data are loaded.
         * View is ready to be displayed.
         */

        /**
         * @ngdoc event
         * @eventOf mdvorakDataRouter.$dataRouter
         * @name $routeChangeError
         * @eventType broadcast on root scope
         *
         * @description
         * This event is broadcasted when the view or data failed to load and there is no error view configured
         * (or error view failed to load). There is not much you can do at this point, it probably means you have
         * configuration error in your application.
         */

        var $dataRouter = {
            /**
             * @ngdoc property
             * @propertyOf mdvorakDataRouter.$dataRouter
             * @name api
             * @returns {Object} Reference to the {@link mdvorakDataApi.$dataApi $dataApi} instance.
             *
             * @description
             * Its here to make your life easier.
             */
            api: $dataApi,

            /**
             * @ngdoc property
             * @propertyOf mdvorakDataRouter.$dataRouter
             * @name registry
             * @returns {Object} Reference to the {@link mdvorakDataRouter.$dataRouterRegistry $dataRouterRegistry} instance.
             *
             * @description
             * Its here to make your life easier.
             */
            registry: $dataRouterRegistry,

            /**
             * @ngdoc property
             * @propertyOf mdvorakDataRouter.$dataRouter
             * @name current
             * @returns {Object} Currently loaded response for the main view is available here.
             * See {@link locals.$dataResponse $dataResponse} for its signature.
             *
             * @description
             * Note that you should in most cases use {@link locals.$dataResponse $dataResponse} object in the
             * controller to work with the data instead of this reference.
             */
            current: undefined,

            /**
             * Reloads data at current location. If content type remains same, only data are refreshed,
             * and $routeUpdate event is invoked on $dataResponse object. If content type differs,
             * full view refresh is performed (that is, controller is destroyed and recreated).
             * <p>
             * If you refresh data, you must listen to the $routeUpdate event on $dataResponse object to be notified of the change.
             *
             * @param {boolean=} forceReload If true, page is always refreshed (controller recreated). Otherwise only
             *                               when needed.
             */
            $$reload: function reload(forceReload) {
                var path = $location.path() || '/';
                var redirectTo;
                var url;
                var next = $dataRouter.$$next = {};

                // Forced redirect
                if ((redirectTo = provider.$redirects.match(path))) {
                    $log.debug("Redirecting to " + redirectTo);
                    $location.path(redirectTo).replace();
                    return;
                }

                // Load resource
                url = $dataApi.mapViewToApi($location.path());

                // Load data and view
                $log.debug("Loading main view");
                $dataRouterLoader.prepareView(url, $dataRouter.current, forceReload)
                    .then(showView, routeChangeFailed);

                // Promise resolutions
                function showView(response) {
                    if ($dataRouter.$$next === next) {
                        // Remove marker
                        $dataRouter.$$next = undefined;

                        // Update view data
                        if (response.routeDataUpdate && $dataRouter.current) {
                            $log.debug("Replacing current data");

                            // Update current (preserve listeners)
                            $$dataRouterEventSupport.$$extend($dataRouter.current, response);

                            // Fire event on the response (only safe way for both main view and fragments)
                            $dataRouter.current.$broadcast('$routeUpdate', $dataRouter.current);
                        } else {
                            $log.debug("Setting view to " + response.mediaType);

                            // Add reload implementation
                            response.reload = $dataRouter.$$reload;

                            // Set current
                            $dataRouter.current = response;

                            // Emit event
                            $rootScope.$broadcast('$routeChangeSuccess', response);
                        }
                    }
                }

                function routeChangeFailed(response) {
                    // Error handler
                    if ($dataRouter.$$next === next) {
                        // Remove next, but don't update current
                        $dataRouter.$$next = undefined;

                        // Show error view
                        $log.error("Failed to load view or data and no error view defined", response);
                        $rootScope.$broadcast('$routeChangeError', response);
                    }
                }
            }
        };

        if (provider.$enabled) {
            // Broadcast $routeChangeStart and cancel location change if it is prevented
            $rootScope.$on('$locationChangeStart', function locationChangeStart($locationEvent) {
                if ($rootScope.$broadcast('$routeChangeStart').defaultPrevented) {
                    if ($locationEvent) {
                        $locationEvent.preventDefault();
                    }
                }
            });

            // Reload view on location change
            $rootScope.$on('$locationChangeSuccess', function locationChangeSuccess() {
                $dataRouter.$$reload(true);
            });
        }

        return $dataRouter;
    };
});
<|endoftext|>


<commit_before>// Copyright 2015 Foursquare Labs, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import _ from 'lodash';
import * as $ from 'jquery';
import moment from 'moment';
import * as dateMath from 'app/core/utils/datemath';

/** @ngInject */
export function ClouderaManagerDatasource(instanceSettings, $q, backendSrv, templateSrv) {
  this.url = instanceSettings.url;
  if (this.url.endsWith('/')) {
    this.url = this.url.substr(0, this.url.length - 1);
  }
  this.basicAuth = instanceSettings.basicAuth;
  this.withCredentials = instanceSettings.withCredentials;
  this.name = instanceSettings.name;

  this.apiVersion = 4;
  if (instanceSettings.jsonData.cmAPIVersion === 'v6-10') {
    this.apiVersion = 6;
  } else if (instanceSettings.jsonData.cmAPIVersion === 'v11+') {
    this.apiVersion = 11;
  }

  // Helper to make API requests to Cloudera Manager. To avoid CORS issues, the requests may be proxied
  // through Grafana's backend via `backendSrv.datasourceRequest`.
  this._request = function(options) {
    options.url = this.url + options.url;
    options.method = options.method || 'GET';
    options.inspect = { 'type': 'cloudera_manager' };

    if (this.basicAuth) {
      options.withCredentials = true;
      options.headers = {
        "Authorization": this.basicAuth
      };
    }

    return backendSrv.datasourceRequest(options);
  };

  // Test the connection to Cloudera Manager by querying for the supported API version.
  this.testDatasource = function() {
    var options = {
      url: '/api/version',
      transformResponse: function(data) {
        return data;
      }
    };

    return this._request(options).then(function(response) {
      return {
        status: "success",
        message: "Data source is working. API version is '" + response.data + "'.",
        title: "Success"
      };
    });
  };

  // Query for metric targets within the specified time range.
  // Returns the promise of a result dictionary. See the convertResponse comment
  // for specifics of the result dictionary.
  this.query = function(queryOptions) {
    var self = this;

    var targetPromises = _(queryOptions.targets)
      .filter(function(target) { return target.target && !target.hide; })
      .map(function(target) {
        var url = '/api/v' + self.apiVersion + '/timeseries';
        var query = {
          query: templateSrv.replace(target.target, queryOptions.scopedVars),
          from: queryOptions.range.from.toJSON(),
          to: queryOptions.range.to.toJSON(),
        };

        if (self.apiVersion >= 6) {
          query.contentType = 'application/json';
        }

        var requestOptions;
        if (self.apiVersion >= 11) {
          // Use POST method on API versions 11 and higher
          // Parameters are passed via the body. This allows longer TSQUERY queries.
          requestOptions = {
            method: 'POST',
            url: url,
            data: query
          };
        } else {
          // Use GET method for API versions prior to 11
          // Parameters are passed via query string.
          requestOptions = {
            method: 'GET',
            url: url,
            params: query
          };
        }

        return self._request(requestOptions).then(_.bind(self.convertResponse, self));
      })
      .value();

    return $q.all(targetPromises).then(function(convertedResponses) {
      var result = {
        data: _.map(convertedResponses, function(convertedResponse) {
          return convertedResponse.data;
        })
      };
      result.data = _.flatten(result.data);
      return result;
    });
  };

  // Convert the metadata returned from Cloudera Manager into the timeseries name for Grafana.
  this._makeTimeseriesName = function(metadata) {
    if (metadata.alias) {
      return metadata.alias;
    } else if (metadata.metricName && metadata.entityName) {
      return metadata.metricName + ' (' + metadata.entityName  + ')';
    } else if (metadata.metricName) {
      return metadata.metricName;
    } else if (metadata.entityName) {
      return metadata.entityName;
    } else {
      return 'UNKNOWN NAME';
    }
  };

  // Convert the Cloudera Manager response to the format expected by Grafana.
  //
  // Grafana generally expects:
  // { data: [
  //   { target: 'metricName1',
  //     datapoints: [ [a1, ts-a1], [a2, ts-a2], [a3, ts-a3] ]
  //   },
  //   { target: 'metricName2',
  //     datapoints: [ [b1, ts-b1], [b2, ts-b2], [c3, ts-b3] ]
  //   },
  // ]}
  //
  // The CM API response has the general form:
  // items: {
  //   timeSeries: [
  //     {
  //       metadata: {
  //         metricName: "metricName1",
  //         entityName: "entityName1",
  //         ...
  //       },
  //       data: [
  //         {
  //           value: 45.1234,
  //           timestamp: "2015-10-02T12:58:24.009Z",
  //           ...
  //         }, {
  //           value: 98.7654,
  //           timestamp: "2015-10-02T12:59:24.009Z",
  //           ...
  //         }
  //         ... (more datapoints)
  //       ]
  //     }
  //     ... (more timeseries)
  //   ]
  // }
  this.convertResponse = function(response) {
    var self = this;

    if (!response || !response.data || !response.data.items) { return []; }

    var seriesList = [];
    _(response.data.items).forEach(function(item) {
      _.forEach(item.timeSeries, function(timeSeries) {
        seriesList.push({
          target: self._makeTimeseriesName(timeSeries.metadata),
          datapoints: _.map(timeSeries.data, function(point) {
            var ts = moment.utc(dateMath.parse(point.timestamp)).unix() * 1000;
            return [point.value, ts];
          })
        });
      });
    });

    return {data: seriesList};
  };
}
<commit_msg>add basic replacements to the alias handling<commit_after>// Copyright 2015 Foursquare Labs, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import _ from 'lodash';
import * as $ from 'jquery';
import moment from 'moment';
import * as dateMath from 'app/core/utils/datemath';

/** @ngInject */
export function ClouderaManagerDatasource(instanceSettings, $q, backendSrv, templateSrv) {
  this.url = instanceSettings.url;
  if (this.url.endsWith('/')) {
    this.url = this.url.substr(0, this.url.length - 1);
  }
  this.basicAuth = instanceSettings.basicAuth;
  this.withCredentials = instanceSettings.withCredentials;
  this.name = instanceSettings.name;

  this.apiVersion = 4;
  if (instanceSettings.jsonData.cmAPIVersion === 'v6-10') {
    this.apiVersion = 6;
  } else if (instanceSettings.jsonData.cmAPIVersion === 'v11+') {
    this.apiVersion = 11;
  }

  // Helper to make API requests to Cloudera Manager. To avoid CORS issues, the requests may be proxied
  // through Grafana's backend via `backendSrv.datasourceRequest`.
  this._request = function(options) {
    options.url = this.url + options.url;
    options.method = options.method || 'GET';
    options.inspect = { 'type': 'cloudera_manager' };

    if (this.basicAuth) {
      options.withCredentials = true;
      options.headers = {
        "Authorization": this.basicAuth
      };
    }

    return backendSrv.datasourceRequest(options);
  };

  // Test the connection to Cloudera Manager by querying for the supported API version.
  this.testDatasource = function() {
    var options = {
      url: '/api/version',
      transformResponse: function(data) {
        return data;
      }
    };

    return this._request(options).then(function(response) {
      return {
        status: "success",
        message: "Data source is working. API version is '" + response.data + "'.",
        title: "Success"
      };
    });
  };

  // Query for metric targets within the specified time range.
  // Returns the promise of a result dictionary. See the convertResponse comment
  // for specifics of the result dictionary.
  this.query = function(queryOptions) {
    var self = this;

    var targetPromises = _(queryOptions.targets)
      .filter(function(target) { return target.target && !target.hide; })
      .map(function(target) {
        var url = '/api/v' + self.apiVersion + '/timeseries';
        var query = {
          query: templateSrv.replace(target.target, queryOptions.scopedVars),
          from: queryOptions.range.from.toJSON(),
          to: queryOptions.range.to.toJSON(),
        };

        if (self.apiVersion >= 6) {
          query.contentType = 'application/json';
        }

        var requestOptions;
        if (self.apiVersion >= 11) {
          // Use POST method on API versions 11 and higher
          // Parameters are passed via the body. This allows longer TSQUERY queries.
          requestOptions = {
            method: 'POST',
            url: url,
            data: query
          };
        } else {
          // Use GET method for API versions prior to 11
          // Parameters are passed via query string.
          requestOptions = {
            method: 'GET',
            url: url,
            params: query
          };
        }

        return self._request(requestOptions).then(_.bind(self.convertResponse, self));
      })
      .value();

    return $q.all(targetPromises).then(function(convertedResponses) {
      var result = {
        data: _.map(convertedResponses, function(convertedResponse) {
          return convertedResponse.data;
        })
      };
      result.data = _.flatten(result.data);
      return result;
    });
  };

  // Convert the metadata returned from Cloudera Manager into the timeseries name for Grafana.
  this._makeTimeseriesName = function(metadata) {
    if (metadata.alias) {
      var alias = metadata.alias;
      if (metadata.metricName) {
        alias = alias.replace('%metricName%', metadata.metricName);
      }
      if (metadata.entityName) {
        alias = alias.replace('%entityName%', metadata.entityName);
      }
      return alias;
    } else if (metadata.metricName && metadata.entityName) {
      return metadata.metricName + ' (' + metadata.entityName  + ')';
    } else if (metadata.metricName) {
      return metadata.metricName;
    } else if (metadata.entityName) {
      return metadata.entityName;
    } else {
      return 'UNKNOWN NAME';
    }
  };

  // Convert the Cloudera Manager response to the format expected by Grafana.
  //
  // Grafana generally expects:
  // { data: [
  //   { target: 'metricName1',
  //     datapoints: [ [a1, ts-a1], [a2, ts-a2], [a3, ts-a3] ]
  //   },
  //   { target: 'metricName2',
  //     datapoints: [ [b1, ts-b1], [b2, ts-b2], [c3, ts-b3] ]
  //   },
  // ]}
  //
  // The CM API response has the general form:
  // items: {
  //   timeSeries: [
  //     {
  //       metadata: {
  //         metricName: "metricName1",
  //         entityName: "entityName1",
  //         ...
  //       },
  //       data: [
  //         {
  //           value: 45.1234,
  //           timestamp: "2015-10-02T12:58:24.009Z",
  //           ...
  //         }, {
  //           value: 98.7654,
  //           timestamp: "2015-10-02T12:59:24.009Z",
  //           ...
  //         }
  //         ... (more datapoints)
  //       ]
  //     }
  //     ... (more timeseries)
  //   ]
  // }
  this.convertResponse = function(response) {
    var self = this;

    if (!response || !response.data || !response.data.items) { return []; }

    var seriesList = [];
    _(response.data.items).forEach(function(item) {
      _.forEach(item.timeSeries, function(timeSeries) {
        seriesList.push({
          target: self._makeTimeseriesName(timeSeries.metadata),
          datapoints: _.map(timeSeries.data, function(point) {
            var ts = moment.utc(dateMath.parse(point.timestamp)).unix() * 1000;
            return [point.value, ts];
          })
        });
      });
    });

    return {data: seriesList};
  };
}
<|endoftext|>


<commit_before>"use strict";

/**
 * @param {Node} picker
 * @param {Object} options
 * @constructor
 */
var DatePicker = function( picker, options ) {
	var that = this;

	this.picker = picker;

	this.options = {
		outputFormat      : 'Y-m-d',
		days              : ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
		months            : ["January", "February", "March", "April", "May", "June",
			"July", "August", "September", "October", "November", "December"],
		next              : '▶',
		prev              : '◀',
		date              : new Date(),
		minDate           : false,
		maxDate           : false,
		onPick            : function() { /*....*/ },
		triggerChangeEvent: true,
		offsetX           : 0,
		offsetY           : 0
	};

	if( typeof options == "object" ) {
		for( var attr in options ) {
			if( options.hasOwnProperty(attr) ) {
				this.options[attr] = options[attr];
			}
		}
	}

	this.calendar = document.createElement('div');
	this.calendar.className = 'DatePicker';
	document.querySelector('body').appendChild(this.calendar);

	this.offset = 0;

	this.display = function() {
		var rect = that.picker.getBoundingClientRect();

		that.calendar.style.top = (rect.bottom + that.options.offsetX) + "px";
		that.calendar.style.left = (rect.left + that.options.offsetY) + "px";
		that.calendar.style.display = 'inline-block';
		this.calendar.style.visibility = 'inherit';
	};

	this.hide = function() {
		that.calendar.style.display = 'none';
	};

	var hideTimeout;
	this.picker.addEventListener('focus', function() {
		clearTimeout(hideTimeout);
		that.display();
	});

	this.picker.addEventListener('blur', function() {
		//that.calendar.style.visibility = 'hidden';
		hideTimeout = setTimeout(that.hide, 300);
	});

	this.render();
	that.hide();
};

DatePicker.prototype = {

	/**
	 * @param {Date} date
	 * @returns {number}
	 */
	getDaysInMonth: function( date ) {
		for( var i = 27; i <= 32; i++ ) {
			var workingDate = new Date(date.getFullYear(), date.getMonth(), i);
			if( workingDate.getMonth() != date.getMonth() ) {
				return i - 1;
			}
		}

		throw "Days in Month Unknown!";
	},

	/**
	 * @param {Date} date
	 */
	setPickerDate: function( date ) {
		this.picker.value = this.format(date, this.options.outputFormat);
	},

	/**
	 * @param {number} month
	 */
	setMonth: function( month ) {
		this.options.date.setMonth(month);
		this.render();
	},

	/**
	 * @param {number} year
	 */
	setYear: function( year ) {
		this.options.date.setYear(year);
		this.render();
	},

	getWorkingDate: function() {
		return (new Date(this.options.date.getFullYear(), this.options.date.getMonth(), 1))
	},

	render: function() {
		var that = this;
		this.calendar.innerHTML = '';

		var workingDate = this.getWorkingDate();
		var header = document.createElement('div');
		header.className = 'DatePicker-header';

		header.innerHTML = this.format(workingDate, 'F Y');

		this.calendar.appendChild(header);

		var next = document.createElement('span');

		next.className = 'DatePicker-next-month';
		next.innerHTML = this.options.next;

		next.addEventListener('click', function() {
			that.picker.focus();
			that.setMonth(that.options.date.getMonth() + 1);
		});

		var prev = document.createElement('span');

		prev.className = 'DatePicker-prev-month';
		prev.innerHTML = this.options.prev;

		prev.addEventListener('click', function() {
			that.picker.focus();
			that.setMonth(that.options.date.getMonth() - 1);
		});

		header.appendChild(next);
		header.appendChild(prev);

		var tbl = document.createElement('table');
		this.calendar.appendChild(tbl);

		var wday = workingDate.getDay() - this.offset;

		var tr = document.createElement('tr');
		tbl.appendChild(tr);

		var count = wday + 1;

		if( wday > 0 ) {
			var startTd = document.createElement('td');
			startTd.colSpan = wday;
			startTd.innerHTML = ' ';
			tr.appendChild(startTd);
		}

		for( var i = 1; i <= this.getDaysInMonth(this.options.date); i++ ) {
			var td = document.createElement('td');
			td.innerHTML = i;
			td.className = 'DatePicker-date';

			var dayDate = new Date(workingDate.getFullYear(), workingDate.getMonth(), i);

			if( (this.options.minDate === false || this.options.minDate <= dayDate) && (this.options.maxDate === false || this.options.maxDate >= dayDate) ) {
				td.addEventListener('click', function( date ) {
					return function() {
						that.setPickerDate(date);
						that.options.onPick.apply(that.picker);
						if( that.options.triggerChangeEvent ) {
							if( that.picker.dispatchEvent ) { // IE9+
								var evt = document.createEvent("HTMLEvents");
								evt.initEvent('change', true, true);
								return !that.picker.dispatchEvent(evt);
							}
						}
					}
				}(dayDate));
			} else {
				td.className += " DatePicker-date-disabled";
			}

			tr.appendChild(td);

			if( count > 6 ) {
				tr = document.createElement('tr');
				tbl.appendChild(tr);

				count = 0;
			}

			count++;
		}

		if( count != 1 ) {
			var endTd = document.createElement('td');
			endTd.colSpan = 8 - count;
			endTd.innerHTML = ' ';
			tr.appendChild(endTd);
		}

	},

	/**
	 * From: http://www.electricprism.com/aeron/calendar/
	 *
	 * License:
	 * MIT-style license.
	 *
	 * Copyright:
	 * Copyright (c) 2008 [Aeron Glemann](http://www.electricprism.com/aeron/)
	 *
	 * @param date
	 * @param format
	 * @returns {string}
	 */
	format: function( date, format ) {
		var str = '';
		if( date ) {
			var j = date.getDate(), w = date.getDay(),
				l = this.options.days[w],
				n = date.getMonth() + 1,
				f = this.options.months[n - 1],
				y = date.getFullYear() + '';
			for( var i = 0, len = format.length; i < len; i++ ) {
				var cha = format.charAt(i);
				switch( cha ) {
					case 'y': // xx - xx
						y = y.substr(2);
					case 'Y': // 19xx - 20xx
						str += y;
						break;
					case 'm': // 01 - 12
						if( n < 10 ) {
							n = '0' + n;
						}
					case 'n': // 1 - 12
						str += n;
						break;
					case 'M': // Jan - Dec
						f = f.substr(0, 3);
					case 'F': // January - December
						str += f;
						break;
					case 'd': // 01 - 31
						if( j < 10 ) {
							j = '0' + j;
						}
					case 'j': // 1 - 31
						str += j;
						break;
					case 'D': // Sun - Sat
						l = l.substr(0, 3);
					case 'l': // Sunday - Saturday
						str += l;
						break;
					case 'N': // 1 - 7
						w += 1;
					case 'w': // 0 - 6
						str += w;
						break;
					case 'S': // st, nd, rd or th (works well with j)
						if( j % 10 == 1 && j != '11' ) {
							str += 'st';
						} else if( j % 10 == 2 && j != '12' ) {
							str += 'nd';
						} else if( j % 10 == 3 && j != '13' ) {
							str += 'rd';
						} else {
							str += 'th';
						}
						break;
					default:
						str += cha;
				}
			}
		}
		return str;
	}
};

if( typeof define == "function" ) {
	define([], function() {
		return DatePicker;
	});
}
<commit_msg>Automatically go above input if off bottom of page<commit_after>"use strict";

/**
 * @param {Node} picker
 * @param {Object} options
 * @constructor
 */
var DatePicker = function( picker, options ) {
	var that = this;

	this.picker = picker;

	this.options = {
		outputFormat      : 'Y-m-d',
		days              : ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
		months            : ["January", "February", "March", "April", "May", "June",
			"July", "August", "September", "October", "November", "December"],
		next              : '▶',
		prev              : '◀',
		date              : new Date(),
		minDate           : false,
		maxDate           : false,
		onPick            : function() { /*....*/ },
		triggerChangeEvent: true,
		offsetX           : 0,
		offsetY           : 0
	};

	if( typeof options == "object" ) {
		for( var attr in options ) {
			if( options.hasOwnProperty(attr) ) {
				this.options[attr] = options[attr];
			}
		}
	}

	this.calendar = document.createElement('div');
	this.calendar.className = 'DatePicker';
	document.querySelector('body').appendChild(this.calendar);

	this.offset = 0;

	this.display = function() {
		var rect   = that.picker.getBoundingClientRect(),
			bottom = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);

		that.calendar.style.top = (rect.bottom + that.options.offsetX) + "px";
		that.calendar.style.left = (rect.left + that.options.offsetY) + "px";
		that.calendar.style.display = 'inline-block';
		this.calendar.style.visibility = 'inherit';

		var calrect = that.calendar.getBoundingClientRect();

		if( calrect.bottom > bottom ) {
			that.calendar.style.top = ((rect.top - calrect.height) + that.options.offsetX) + "px";
		}
	};

	this.hide = function() {
		that.calendar.style.display = 'none';
	};

	var hideTimeout;
	this.picker.addEventListener('focus', function() {
		clearTimeout(hideTimeout);
		that.display();
	});

	this.picker.addEventListener('blur', function() {
		//that.calendar.style.visibility = 'hidden';
		hideTimeout = setTimeout(that.hide, 300);
	});

	this.render();
	that.hide();
};

DatePicker.prototype = {

	/**
	 * @param {Date} date
	 * @returns {number}
	 */
	getDaysInMonth: function( date ) {
		for( var i = 27; i <= 32; i++ ) {
			var workingDate = new Date(date.getFullYear(), date.getMonth(), i);
			if( workingDate.getMonth() != date.getMonth() ) {
				return i - 1;
			}
		}

		throw "Days in Month Unknown!";
	},

	/**
	 * @param {Date} date
	 */
	setPickerDate: function( date ) {
		this.picker.value = this.format(date, this.options.outputFormat);
	},

	/**
	 * @param {number} month
	 */
	setMonth: function( month ) {
		this.options.date.setMonth(month);
		this.render();
	},

	/**
	 * @param {number} year
	 */
	setYear: function( year ) {
		this.options.date.setYear(year);
		this.render();
	},

	getWorkingDate: function() {
		return (new Date(this.options.date.getFullYear(), this.options.date.getMonth(), 1))
	},

	render: function() {
		var that = this;
		this.calendar.innerHTML = '';

		var workingDate = this.getWorkingDate();
		var header = document.createElement('div');
		header.className = 'DatePicker-header';

		header.innerHTML = this.format(workingDate, 'F Y');

		this.calendar.appendChild(header);

		var next = document.createElement('span');

		next.className = 'DatePicker-next-month';
		next.innerHTML = this.options.next;

		next.addEventListener('click', function() {
			that.picker.focus();
			that.setMonth(that.options.date.getMonth() + 1);
		});

		var prev = document.createElement('span');

		prev.className = 'DatePicker-prev-month';
		prev.innerHTML = this.options.prev;

		prev.addEventListener('click', function() {
			that.picker.focus();
			that.setMonth(that.options.date.getMonth() - 1);
		});

		header.appendChild(next);
		header.appendChild(prev);

		var tbl = document.createElement('table');
		this.calendar.appendChild(tbl);

		var wday = workingDate.getDay() - this.offset;

		var tr = document.createElement('tr');
		tbl.appendChild(tr);

		var count = wday + 1;

		if( wday > 0 ) {
			var startTd = document.createElement('td');
			startTd.colSpan = wday;
			startTd.innerHTML = ' ';
			tr.appendChild(startTd);
		}

		for( var i = 1; i <= this.getDaysInMonth(this.options.date); i++ ) {
			var td = document.createElement('td');
			td.innerHTML = i;
			td.className = 'DatePicker-date';

			var dayDate = new Date(workingDate.getFullYear(), workingDate.getMonth(), i);

			if( (this.options.minDate === false || this.options.minDate <= dayDate) && (this.options.maxDate === false || this.options.maxDate >= dayDate) ) {
				td.addEventListener('click', function( date ) {
					return function() {
						that.setPickerDate(date);
						that.options.onPick.apply(that.picker);
						if( that.options.triggerChangeEvent ) {
							if( that.picker.dispatchEvent ) { // IE9+
								var evt = document.createEvent("HTMLEvents");
								evt.initEvent('change', true, true);
								return !that.picker.dispatchEvent(evt);
							}
						}
					}
				}(dayDate));
			} else {
				td.className += " DatePicker-date-disabled";
			}

			tr.appendChild(td);

			if( count > 6 ) {
				tr = document.createElement('tr');
				tbl.appendChild(tr);

				count = 0;
			}

			count++;
		}

		if( count != 1 ) {
			var endTd = document.createElement('td');
			endTd.colSpan = 8 - count;
			endTd.innerHTML = ' ';
			tr.appendChild(endTd);
		}

	},

	/**
	 * From: http://www.electricprism.com/aeron/calendar/
	 *
	 * License:
	 * MIT-style license.
	 *
	 * Copyright:
	 * Copyright (c) 2008 [Aeron Glemann](http://www.electricprism.com/aeron/)
	 *
	 * @param date
	 * @param format
	 * @returns {string}
	 */
	format: function( date, format ) {
		var str = '';
		if( date ) {
			var j = date.getDate(), w = date.getDay(),
				l = this.options.days[w],
				n = date.getMonth() + 1,
				f = this.options.months[n - 1],
				y = date.getFullYear() + '';
			for( var i = 0, len = format.length; i < len; i++ ) {
				var cha = format.charAt(i);
				switch( cha ) {
					case 'y': // xx - xx
						y = y.substr(2);
					case 'Y': // 19xx - 20xx
						str += y;
						break;
					case 'm': // 01 - 12
						if( n < 10 ) {
							n = '0' + n;
						}
					case 'n': // 1 - 12
						str += n;
						break;
					case 'M': // Jan - Dec
						f = f.substr(0, 3);
					case 'F': // January - December
						str += f;
						break;
					case 'd': // 01 - 31
						if( j < 10 ) {
							j = '0' + j;
						}
					case 'j': // 1 - 31
						str += j;
						break;
					case 'D': // Sun - Sat
						l = l.substr(0, 3);
					case 'l': // Sunday - Saturday
						str += l;
						break;
					case 'N': // 1 - 7
						w += 1;
					case 'w': // 0 - 6
						str += w;
						break;
					case 'S': // st, nd, rd or th (works well with j)
						if( j % 10 == 1 && j != '11' ) {
							str += 'st';
						} else if( j % 10 == 2 && j != '12' ) {
							str += 'nd';
						} else if( j % 10 == 3 && j != '13' ) {
							str += 'rd';
						} else {
							str += 'th';
						}
						break;
					default:
						str += cha;
				}
			}
		}
		return str;
	}
};

if( typeof define == "function" ) {
	define([], function() {
		return DatePicker;
	});
}
<|endoftext|>


<commit_before>import { IS_NON_DIMENSIONAL } from '../constants';

export function diffProps(node, props, oldProps, isSvg) {
	// if (props!=null) {
	if (props) {
		for (let i in props) {
			if (i!=='children' && i!=='key' && (!oldProps || oldProps[i]!=props[i])) {
			// if (i!=='children' && i!=='key' && (oldProps==null || oldProps[i]!=props[i])) {
			// if (oldProps==null || oldProps[i]!=props[i]) {
				setProperty(node, i, props[i], oldProps[i], isSvg);
				// node[i] = props[i];
			}
		}
	}
	// if (oldProps!=null) {
	if (oldProps) {
		for (let i in oldProps) {
			if (i!=='children' && i!=='key' && (!props || !(i in props))) {
			// if (i!=='children' && i!=='key' && (props==null || !(i in props))) {
			// if (props==null || !(i in props)) {
				// delete node[i];
				setProperty(node, i, null, oldProps[i], isSvg);
			}
		}
	}
}

// const DIMENSION_PROPS = {};

// function isDimensionalProp(name) {
// 	return DIMENSION_PROPS[name] || (DIMENSION_PROPS[name] = (IS_NON_DIMENSIONAL.test(name) === false));
// }

// let isNonDimensional = IS_NON_DIMENSIONAL.test.bind(IS_NON_DIMENSIONAL);

// function setStyle(node, value, oldValue) {
// 	for (let i in oldValue) {
// 		if (value==null || !(i in value)) node.style[i] = '';
// 	}
// 	for (let i in value) {
// 		let v = value[i];
// 		if (oldValue==null || v!==oldValue[i]) {
// 			node.style[i] = typeof v==='number' && IS_NON_DIMENSIONAL.test(i)===false ? (v + 'px') : v;
// 		}
// 	}
// }

function setProperty(node, name, value, oldValue, isSvg) {
	// if (name==='children') return;

	// let name = iname==='class' ? 'className' : iname;
	if (name==='class' || name==='className') name = isSvg ? 'class' : 'className';
	// let isProperty = !isSvg && name in node;

	// let obj = node[name];

	// if (name==='children' || name==='key') {}
	// else
	if (name==='ref') {
		if (oldValue) oldValue(null);
		if (value) value(node);
	}
	else if (name==='style') {
		// setStyle(node, value, oldValue);

		/* Possible golfing activities for setting styles:
		 *   - we could just drop String style values. They're not supported in other VDOM libs.
		 *   - assigning to .style sets .style.cssText - TODO: benchmark this, might not be worth the bytes.
		 *   - assigning also casts to String, and ignores invalid values. This means assigning an Object clears all styles.
		 */
		// if (typeof value==='string') {
		// 	// node.style.cssText = value;
		// 	node.style = value;
		// }
		// else {
		// 	if (typeof oldValue==='string') {
		// 		// node.style.cssText = '';
		// 		node.style = '';
		// 	}

		let s = node.style;

		// remove values not in the new list
		for (let i in oldValue) {
			if (value==null || !(i in value)) s[i] = '';
		}
		for (let i in value) {
			let v = value[i];
			if (oldValue==null || v!==oldValue[i]) {
				s[i] = typeof v==='number' && IS_NON_DIMENSIONAL.test(i)===false ? (v + 'px') : v;
				// obj[i] = v;

				// if (typeof v==='number' && IS_NON_DIMENSIONAL.test(i)===false) v += 'px';
				// obj[i] = v;

				// node.style[i] = typeof v==='number' && isDimensionalProp(i) ? `${v}px` : v;
				// node.style[i] = v;

				// if (typeof v==='number' && (DIMENSION_PROPS[i] === true || (DIMENSION_PROPS[i] = IS_NON_DIMENSIONAL.test(i)===false))) {
				// 	v += 'px';
				// }
				// node.style[i] = v;
			}
		}

		// }
	}
	else if (name==='dangerouslySetInnerHTML') {
		if (value) node.innerHTML = value.__html || '';
		else node.textContent = '';
	}
	// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
	// else if (name.charCodeAt(0)===111 && name.charCodeAt(1)===110) {
	else if (name[0]==='o' && name[1]==='n') {
		let useCapture = name !== (name=name.replace(/Capture$/, ''));
		let listenerName = name.toLowerCase().substring(2);
		node.removeEventListener(listenerName, oldValue, useCapture);
		node.addEventListener(listenerName, value, useCapture);
	}
	// else if (isProperty===true) {
	// else if (name in node) {
	else if (name!=='list' && !isSvg && (name in node)) {
		node[name] = value==null ? '' : value;
		// if (value==null || value===false) node.removeAttribute(name);
	}
	// else if (name in node && (isSvg===false || obj!=null && obj.baseVal!=null && obj.baseVal.valueAsString!=null)) {
	// 	if (isSvg===true) {
	// 		// console.log(name, value, node[name].baseVal.unitType);
	// 		obj.baseVal.valueAsString = typeof value==='number' ? (value + 'px') : value;
	// 	}
	// 	else {
	// 		node[name] = value==null ? '' : value;
	// 	}
	// 	if (value==null || value===false) node.removeAttribute(name);
	// }
	else if (value==null || value===false) {
		node.removeAttribute(name);
	}
	else if (typeof value!=='function') {
		node.setAttribute(name, value);
	}
	// @TODO handle this implicitly in set?
	// else if (value==null) {
	// 	if (isProperty===true) {
	// 		delete node[name];
	// 	}
	// 	else {
	// 		node.removeAttribute(name);
	// 	}
	// }
	// else if (isProperty===true) {
	// 	node[name] = value;
	// }
	// else {
	// 	node.setAttribute(name, value);
	// }
}
<commit_msg>Simplify dangerouslySetInnerHTML check<commit_after>import { IS_NON_DIMENSIONAL } from '../constants';

export function diffProps(node, props, oldProps, isSvg) {
	// if (props!=null) {
	if (props) {
		for (let i in props) {
			if (i!=='children' && i!=='key' && (!oldProps || oldProps[i]!=props[i])) {
			// if (i!=='children' && i!=='key' && (oldProps==null || oldProps[i]!=props[i])) {
			// if (oldProps==null || oldProps[i]!=props[i]) {
				setProperty(node, i, props[i], oldProps[i], isSvg);
				// node[i] = props[i];
			}
		}
	}
	// if (oldProps!=null) {
	if (oldProps) {
		for (let i in oldProps) {
			if (i!=='children' && i!=='key' && (!props || !(i in props))) {
			// if (i!=='children' && i!=='key' && (props==null || !(i in props))) {
			// if (props==null || !(i in props)) {
				// delete node[i];
				setProperty(node, i, null, oldProps[i], isSvg);
			}
		}
	}
}

// const DIMENSION_PROPS = {};

// function isDimensionalProp(name) {
// 	return DIMENSION_PROPS[name] || (DIMENSION_PROPS[name] = (IS_NON_DIMENSIONAL.test(name) === false));
// }

// let isNonDimensional = IS_NON_DIMENSIONAL.test.bind(IS_NON_DIMENSIONAL);

// function setStyle(node, value, oldValue) {
// 	for (let i in oldValue) {
// 		if (value==null || !(i in value)) node.style[i] = '';
// 	}
// 	for (let i in value) {
// 		let v = value[i];
// 		if (oldValue==null || v!==oldValue[i]) {
// 			node.style[i] = typeof v==='number' && IS_NON_DIMENSIONAL.test(i)===false ? (v + 'px') : v;
// 		}
// 	}
// }

function setProperty(node, name, value, oldValue, isSvg) {
	// if (name==='children') return;

	// let name = iname==='class' ? 'className' : iname;
	if (name==='class' || name==='className') name = isSvg ? 'class' : 'className';
	// let isProperty = !isSvg && name in node;

	// let obj = node[name];

	// if (name==='children' || name==='key') {}
	// else
	if (name==='ref') {
		if (oldValue) oldValue(null);
		if (value) value(node);
	}
	else if (name==='style') {
		// setStyle(node, value, oldValue);

		/* Possible golfing activities for setting styles:
		 *   - we could just drop String style values. They're not supported in other VDOM libs.
		 *   - assigning to .style sets .style.cssText - TODO: benchmark this, might not be worth the bytes.
		 *   - assigning also casts to String, and ignores invalid values. This means assigning an Object clears all styles.
		 */
		// if (typeof value==='string') {
		// 	// node.style.cssText = value;
		// 	node.style = value;
		// }
		// else {
		// 	if (typeof oldValue==='string') {
		// 		// node.style.cssText = '';
		// 		node.style = '';
		// 	}

		let s = node.style;

		// remove values not in the new list
		for (let i in oldValue) {
			if (value==null || !(i in value)) s[i] = '';
		}
		for (let i in value) {
			let v = value[i];
			if (oldValue==null || v!==oldValue[i]) {
				s[i] = typeof v==='number' && IS_NON_DIMENSIONAL.test(i)===false ? (v + 'px') : v;
				// obj[i] = v;

				// if (typeof v==='number' && IS_NON_DIMENSIONAL.test(i)===false) v += 'px';
				// obj[i] = v;

				// node.style[i] = typeof v==='number' && isDimensionalProp(i) ? `${v}px` : v;
				// node.style[i] = v;

				// if (typeof v==='number' && (DIMENSION_PROPS[i] === true || (DIMENSION_PROPS[i] = IS_NON_DIMENSIONAL.test(i)===false))) {
				// 	v += 'px';
				// }
				// node.style[i] = v;
			}
		}

		// }
	}
	else if (name==='dangerouslySetInnerHTML') {
		node.innerHTML = value && value.__html || '';
	}
	// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
	// else if (name.charCodeAt(0)===111 && name.charCodeAt(1)===110) {
	else if (name[0]==='o' && name[1]==='n') {
		let useCapture = name !== (name=name.replace(/Capture$/, ''));
		let listenerName = name.toLowerCase().substring(2);
		node.removeEventListener(listenerName, oldValue, useCapture);
		node.addEventListener(listenerName, value, useCapture);
	}
	// else if (isProperty===true) {
	// else if (name in node) {
	else if (name!=='list' && !isSvg && (name in node)) {
		node[name] = value==null ? '' : value;
		// if (value==null || value===false) node.removeAttribute(name);
	}
	// else if (name in node && (isSvg===false || obj!=null && obj.baseVal!=null && obj.baseVal.valueAsString!=null)) {
	// 	if (isSvg===true) {
	// 		// console.log(name, value, node[name].baseVal.unitType);
	// 		obj.baseVal.valueAsString = typeof value==='number' ? (value + 'px') : value;
	// 	}
	// 	else {
	// 		node[name] = value==null ? '' : value;
	// 	}
	// 	if (value==null || value===false) node.removeAttribute(name);
	// }
	else if (value==null || value===false) {
		node.removeAttribute(name);
	}
	else if (typeof value!=='function') {
		node.setAttribute(name, value);
	}
	// @TODO handle this implicitly in set?
	// else if (value==null) {
	// 	if (isProperty===true) {
	// 		delete node[name];
	// 	}
	// 	else {
	// 		node.removeAttribute(name);
	// 	}
	// }
	// else if (isProperty===true) {
	// 	node[name] = value;
	// }
	// else {
	// 	node.setAttribute(name, value);
	// }
}
<|endoftext|>


<commit_before>import { IS_NON_DIMENSIONAL } from '../constants';
import options from '../options';

/**
 * Diff the old and new properties of a VNode and apply changes to the DOM node
 * @param {import('../internal').PreactElement} dom The DOM node to apply
 * changes to
 * @param {object} newProps The new props
 * @param {object} oldProps The old props
 * @param {boolean} isSvg Whether or not this node is an SVG node
 * @param {boolean} hydrate Whether or not we are in hydration mode
 */
export function diffProps(dom, newProps, oldProps, isSvg, hydrate) {
	let i;

	for (i in oldProps) {
		if (i !== 'children' && i !== 'key' && !(i in newProps)) {
			setProperty(dom, i, null, oldProps[i], isSvg);
		}
	}

	for (i in newProps) {
		if (
			(!hydrate || typeof newProps[i] == 'function') &&
			i !== 'children' &&
			i !== 'key' &&
			i !== 'value' &&
			i !== 'checked' &&
			oldProps[i] !== newProps[i]
		) {
			setProperty(dom, i, newProps[i], oldProps[i], isSvg);
		}
	}
}

function setStyle(style, key, value) {
	if (key[0] === '-') {
		style.setProperty(key, value);
	} else if (value == null) {
		style[key] = '';
	} else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {
		style[key] = value;
	} else {
		style[key] = value + 'px';
	}
}

/**
 * Set a property value on a DOM node
 * @param {import('../internal').PreactElement} dom The DOM node to modify
 * @param {string} name The name of the property to set
 * @param {*} value The value to set the property to
 * @param {*} oldValue The old value the property had
 * @param {boolean} isSvg Whether or not this DOM node is an SVG node or not
 */
export function setProperty(dom, name, value, oldValue, isSvg) {
	let useCapture, nameLower, proxy;

	if (isSvg && name == 'className') name = 'class';

	// if (isSvg) {
	// 	if (name === 'className') name = 'class';
	// } else if (name === 'class') name += 'Name';

	if (name === 'style') {
		if (typeof value == 'string') {
			dom.style.cssText = value;
		} else {
			if (typeof oldValue == 'string') {
				dom.style.cssText = oldValue = '';
			}

			if (oldValue) {
				for (name in oldValue) {
					if (!(value && name in value)) {
						setStyle(dom.style, name, '');
					}
				}
			}

			if (value) {
				for (name in value) {
					if (!oldValue || value[name] !== oldValue[name]) {
						setStyle(dom.style, name, value[name]);
					}
				}
			}
		}
	}
	// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
	else if (name[0] === 'o' && name[1] === 'n') {
		useCapture = name !== (name = name.replace(/Capture$/, ''));
		nameLower = name.toLowerCase();
		if (nameLower in dom) name = nameLower;
		name = name.slice(2);

		if (!dom._listeners) dom._listeners = {};
		dom._listeners[name + useCapture] = value;

		proxy = useCapture ? eventProxyCapture : eventProxy;
		if (value) {
			if (!oldValue) dom.addEventListener(name, proxy, useCapture);
		} else {
			dom.removeEventListener(name, proxy, useCapture);
		}
	} else if (
		name !== 'list' &&
		name !== 'tagName' &&
		// HTMLButtonElement.form and HTMLInputElement.form are read-only but can be set using
		// setAttribute
		name !== 'form' &&
		name !== 'type' &&
		name !== 'size' &&
		name !== 'download' &&
		name !== 'href' &&
		!isSvg &&
		name in dom
	) {
		dom[name] = value == null ? '' : value;
	} else if (typeof value != 'function' && name !== 'dangerouslySetInnerHTML') {
		if (name !== (name = name.replace(/xlink:?/, ''))) {
			if (value == null || value === false) {
				dom.removeAttributeNS(
					'http://www.w3.org/1999/xlink',
					name.toLowerCase()
				);
			} else {
				dom.setAttributeNS(
					'http://www.w3.org/1999/xlink',
					name.toLowerCase(),
					value
				);
			}
		} else if (
			value == null ||
			(value === false &&
				// ARIA-attributes have a different notion of boolean values.
				// The value `false` is different from the attribute not
				// existing on the DOM, so we can't remove it. For non-boolean
				// ARIA-attributes we could treat false as a removal, but the
				// amount of exceptions would cost us too many bytes. On top of
				// that other VDOM frameworks also always stringify `false`.
				!/^ar/.test(name))
		) {
			dom.removeAttribute(name);
		} else {
			dom.setAttribute(name, value);
		}
	}
}

/**
 * Proxy an event to hooked event handlers
 * @param {Event} e The event object from the browser
 * @private
 */
function eventProxy(e) {
	this._listeners[e.type + false](options.event ? options.event(e) : e);
}

function eventProxyCapture(e) {
	this._listeners[e.type + true](options.event ? options.event(e) : e);
}
<commit_msg>use loose equality for prop comparison<commit_after>import { IS_NON_DIMENSIONAL } from '../constants';
import options from '../options';

/**
 * Diff the old and new properties of a VNode and apply changes to the DOM node
 * @param {import('../internal').PreactElement} dom The DOM node to apply
 * changes to
 * @param {object} newProps The new props
 * @param {object} oldProps The old props
 * @param {boolean} isSvg Whether or not this node is an SVG node
 * @param {boolean} hydrate Whether or not we are in hydration mode
 */
export function diffProps(dom, newProps, oldProps, isSvg, hydrate) {
	let i;

	for (i in oldProps) {
		if (i !== 'children' && i !== 'key' && !(i in newProps)) {
			setProperty(dom, i, null, oldProps[i], isSvg);
		}
	}

	for (i in newProps) {
		if (
			(!hydrate || typeof newProps[i] == 'function') &&
			i !== 'children' &&
			i !== 'key' &&
			i !== 'value' &&
			i !== 'checked' &&
			oldProps[i] != newProps[i]
		) {
			setProperty(dom, i, newProps[i], oldProps[i], isSvg);
		}
	}
}

function setStyle(style, key, value) {
	if (key[0] === '-') {
		style.setProperty(key, value);
	} else if (value == null) {
		style[key] = '';
	} else if (typeof value != 'number' || IS_NON_DIMENSIONAL.test(key)) {
		style[key] = value;
	} else {
		style[key] = value + 'px';
	}
}

/**
 * Set a property value on a DOM node
 * @param {import('../internal').PreactElement} dom The DOM node to modify
 * @param {string} name The name of the property to set
 * @param {*} value The value to set the property to
 * @param {*} oldValue The old value the property had
 * @param {boolean} isSvg Whether or not this DOM node is an SVG node or not
 */
export function setProperty(dom, name, value, oldValue, isSvg) {
	let useCapture, nameLower, proxy;

	if (isSvg && name == 'className') name = 'class';

	// if (isSvg) {
	// 	if (name === 'className') name = 'class';
	// } else if (name === 'class') name += 'Name';

	if (name === 'style') {
		if (typeof value == 'string') {
			dom.style.cssText = value;
		} else {
			if (typeof oldValue == 'string') {
				dom.style.cssText = oldValue = '';
			}

			if (oldValue) {
				for (name in oldValue) {
					if (!(value && name in value)) {
						setStyle(dom.style, name, '');
					}
				}
			}

			if (value) {
				for (name in value) {
					if (!oldValue || value[name] !== oldValue[name]) {
						setStyle(dom.style, name, value[name]);
					}
				}
			}
		}
	}
	// Benchmark for comparison: https://esbench.com/bench/574c954bdb965b9a00965ac6
	else if (name[0] === 'o' && name[1] === 'n') {
		useCapture = name !== (name = name.replace(/Capture$/, ''));
		nameLower = name.toLowerCase();
		if (nameLower in dom) name = nameLower;
		name = name.slice(2);

		if (!dom._listeners) dom._listeners = {};
		dom._listeners[name + useCapture] = value;

		proxy = useCapture ? eventProxyCapture : eventProxy;
		if (value) {
			if (!oldValue) dom.addEventListener(name, proxy, useCapture);
		} else {
			dom.removeEventListener(name, proxy, useCapture);
		}
	} else if (
		name !== 'list' &&
		name !== 'tagName' &&
		// HTMLButtonElement.form and HTMLInputElement.form are read-only but can be set using
		// setAttribute
		name !== 'form' &&
		name !== 'type' &&
		name !== 'size' &&
		name !== 'download' &&
		name !== 'href' &&
		!isSvg &&
		name in dom
	) {
		dom[name] = value == null ? '' : value;
	} else if (typeof value != 'function' && name !== 'dangerouslySetInnerHTML') {
		if (name !== (name = name.replace(/xlink:?/, ''))) {
			if (value == null || value === false) {
				dom.removeAttributeNS(
					'http://www.w3.org/1999/xlink',
					name.toLowerCase()
				);
			} else {
				dom.setAttributeNS(
					'http://www.w3.org/1999/xlink',
					name.toLowerCase(),
					value
				);
			}
		} else if (
			value == null ||
			(value === false &&
				// ARIA-attributes have a different notion of boolean values.
				// The value `false` is different from the attribute not
				// existing on the DOM, so we can't remove it. For non-boolean
				// ARIA-attributes we could treat false as a removal, but the
				// amount of exceptions would cost us too many bytes. On top of
				// that other VDOM frameworks also always stringify `false`.
				!/^ar/.test(name))
		) {
			dom.removeAttribute(name);
		} else {
			dom.setAttribute(name, value);
		}
	}
}

/**
 * Proxy an event to hooked event handlers
 * @param {Event} e The event object from the browser
 * @private
 */
function eventProxy(e) {
	this._listeners[e.type + false](options.event ? options.event(e) : e);
}

function eventProxyCapture(e) {
	this._listeners[e.type + true](options.event ? options.event(e) : e);
}
<|endoftext|>


<commit_before>// mostly taken from http://www.codinginsight.com/angularjs-and-pickadate/

angular.module('angular-datepicker', [])
	.directive('pickADate', function () {
	    return {
	        restrict: "A",
	        scope: {
	            pickADate: '=',
	            minDate: '=',
	            maxDate: '='
	        },
	        link: function (scope, element, attrs) {
	            element.pickadate({
	                onSet: function (e) {
	                    if (scope.$$phase || scope.$root.$$phase) // we are coming from $watch or link setup
	                        return;
	                    var select = element.pickadate('picker').get('select'); // selected date
	                    scope.$apply(function () {
	                        if (e.hasOwnProperty('clear')) {
	                            scope.pickADate = null;
	                            return;
	                        }
	                        if (!scope.pickADate || typeof scope.pickADate === 'string')
	                            scope.pickADate = new Date(0);
	                        scope.pickADate.setYear(select.obj.getYear() + 1900); // hello Y2K...
	                        // It took me half a day to figure out that javascript Date object's getYear
	                        // function returns the years since 1900. Ironically setYear() accepts the actual year A.D.
	                        // So as I got the $#%^ 114 and set it, guess what, I was transported to ancient Rome 114 A.D.
	                        // That's it I'm done being a programmer, I'd rather go serve Emperor Trajan as a sex slave.
	                        scope.pickADate.setMonth(select.obj.getMonth());
	                        scope.pickADate.setDate(select.obj.getDate());
	                    });
	                },
	                onClose: function () {
						if (typeof cordova === 'undefined' || !cordova.plugins || !cordova.plugins.Keyboard) {
							return;
						}

						var keyboardShowCallback = function () {
							cordova.plugins.Keyboard.close();
							window.removeEventListener('native.keyboardshow', this);
						};

						window.addEventListener('native.keyboardshow', keyboardShowCallback);

						setTimeout(function () {
							window.removeEventListener('native.keyboardshow', keyboardShowCallback);
						}, 500);
	                },
					container: document.body
	            });

				setTimeout(function() {
					if (scope.pickADate) {
						element.pickadate('picker').set('select', scope.pickADate);
					}
				}, 1000);
	        }
	    };
	})
	.directive('pickATime', function () {
	    return {
	        restrict: "A",
	        scope: {
	            pickATime: '='
	        },
	        link: function (scope, element, attrs) {
	            element.pickatime({
	                onSet: function (e) {
	                    if (scope.$$phase || scope.$root.$$phase) // we are coming from $watch or link setup
	                        return;
	                    var select = element.pickatime('picker').get('select'); // selected date
	                    scope.$apply(function () {
	                        if (e.hasOwnProperty('clear')) {
	                            scope.pickATime = null;
	                            return;
	                        }
	                        if (!scope.pickATime || typeof scope.pickATime === 'string')
	                            scope.pickATime = new Date();

	                        scope.pickATime.setHours(select.hour);
	                        scope.pickATime.setMinutes(select.mins);
	                        scope.pickATime.setSeconds(0);
	                        scope.pickATime.setMilliseconds(0);
	                    });
	                },
	                onClose: function () {
						if (typeof cordova ==='undefined' || !cordova.plugins || !cordova.plugins.Keyboard) {
							return;
						}

						var keyboardShowCallback = function () {
							cordova.plugins.Keyboard.close();
							window.removeEventListener('native.keyboardshow', this);
						};

						window.addEventListener('native.keyboardshow', keyboardShowCallback);

						setTimeout(function () {
							window.removeEventListener('native.keyboardshow', keyboardShowCallback);
						}, 500);
	                },
					container: document.body
	            });

				setTimeout(function() {
					if (scope.pickATime) {
						element.pickatime('picker').set('select', scope.pickATime);
					}
				}, 1000);
	        }
	    };
	});	<commit_msg>Passthrough options attr on directive to pickADate<commit_after>// mostly taken from http://www.codinginsight.com/angularjs-and-pickadate/

angular.module('angular-datepicker', [])
	.directive('pickADate', function () {
	    return {
	        restrict: "A",
	        scope: {
	            pickADate: '=',
	            pickADateOptions: '='
	        },
	        link: function (scope, element, attrs) {
	            var options = scope.pickADateOptions || {};

	            element.pickadate(angular.extend(options, {
	                onSet: function (e) {
	                    if (scope.$$phase || scope.$root.$$phase) // we are coming from $watch or link setup
	                        return;
	                    var select = element.pickadate('picker').get('select'); // selected date
	                    scope.$apply(function () {
	                        if (e.hasOwnProperty('clear')) {
	                            scope.pickADate = null;
	                            return;
	                        }
	                        if (!scope.pickADate || typeof scope.pickADate === 'string')
	                            scope.pickADate = new Date(0);
	                        scope.pickADate.setYear(select.obj.getYear() + 1900); // hello Y2K...
	                        // It took me half a day to figure out that javascript Date object's getYear
	                        // function returns the years since 1900. Ironically setYear() accepts the actual year A.D.
	                        // So as I got the $#%^ 114 and set it, guess what, I was transported to ancient Rome 114 A.D.
	                        // That's it I'm done being a programmer, I'd rather go serve Emperor Trajan as a sex slave.
	                        scope.pickADate.setMonth(select.obj.getMonth());
	                        scope.pickADate.setDate(select.obj.getDate());
	                    });
	                },
	                onClose: function () {
						if (typeof cordova === 'undefined' || !cordova.plugins || !cordova.plugins.Keyboard) {
							return;
						}

						var keyboardShowCallback = function () {
							cordova.plugins.Keyboard.close();
							window.removeEventListener('native.keyboardshow', this);
						};

						window.addEventListener('native.keyboardshow', keyboardShowCallback);

						setTimeout(function () {
							window.removeEventListener('native.keyboardshow', keyboardShowCallback);
						}, 500);
	                },
					container: document.body
	            }));

				setTimeout(function() {
					if (scope.pickADate) {
						element.pickadate('picker').set('select', scope.pickADate);
					}
				}, 1000);
	        }
	    };
	})
	.directive('pickATime', function () {
	    return {
	        restrict: "A",
	        scope: {
	            pickATime: '=',
	            pickATimeOptions: '='
	        },
	        link: function (scope, element, attrs) {
	            var options = scope.pickADateOptions || {};

	            element.pickatime(angular.extend(options, {
	                onSet: function (e) {
	                    if (scope.$$phase || scope.$root.$$phase) // we are coming from $watch or link setup
	                        return;
	                    var select = element.pickatime('picker').get('select'); // selected date
	                    scope.$apply(function () {
	                        if (e.hasOwnProperty('clear')) {
	                            scope.pickATime = null;
	                            return;
	                        }
	                        if (!scope.pickATime || typeof scope.pickATime === 'string')
	                            scope.pickATime = new Date();

	                        scope.pickATime.setHours(select.hour);
	                        scope.pickATime.setMinutes(select.mins);
	                        scope.pickATime.setSeconds(0);
	                        scope.pickATime.setMilliseconds(0);
	                    });
	                },
	                onClose: function () {
						if (typeof cordova ==='undefined' || !cordova.plugins || !cordova.plugins.Keyboard) {
							return;
						}

						var keyboardShowCallback = function () {
							cordova.plugins.Keyboard.close();
							window.removeEventListener('native.keyboardshow', this);
						};

						window.addEventListener('native.keyboardshow', keyboardShowCallback);

						setTimeout(function () {
							window.removeEventListener('native.keyboardshow', keyboardShowCallback);
						}, 500);
	                },
					container: document.body
	            }));

				setTimeout(function() {
					if (scope.pickATime) {
						element.pickatime('picker').set('select', scope.pickATime);
					}
				}, 1000);
	        }
	    };
	});	<|endoftext|>


<commit_before>/**
 * @author Edouard Demotes-Mainard <https://github.com/EdouardDem>
 * @license http://opensource.org/licenses/BSD-2-Clause BSD 2-Clause License
 */

/**
 * Object djs for namespace
 */
window.djs = window.djs || {};

/**
 * This object manages window's resizing event.
 * It detects the end of the resizing events suite and trigger callbacks.
 * It also allows to manage callbacks order through the class djs.Callstack
 * This object is "chainable".
 *
 * @see https://github.com/EdouardDem/djs-resize
 * @requires djs.CallStack <https://github.com/EdouardDem/djs-call-stack>
 */
djs.resize = {

	/* ========================================================================
	 * 	PROPERTIES
	 * ====================================================================== */
	/**
	 * Delay used to detect the end of the resize.
	 * For example, if we define delay = 100, then if a resize starts and no resize's event is recorded for 100ms,
	 * we will consider the resizing is done and run the callbacks.
	 * To disables this feature, delay should be set to 0.
	 *
	 * @var {Integer}
	 */
	_delay: 0,

	/**
	 * CSS classes used to tag the body while processing
	 *
	 * @var {Object}
	 */
	classes: {
		resizing: 'resizing'
	},

	/**
	 * Names and order of the call stacks used by this object
	 *
	 * @var {Object}
	 */
	stacks: {
		core: 'core',
		main: 'main',
		last: 'last'
	},

	/**
	 * Namespace used to bind events
	 *
	 * @var {String}
	 */
	namespace: 'djs-resize',

	/**
	 * Flag used to determine if the object is initialized
	 *
	 * @var {Boolean}
	 */
	initialized: false,

	/**
	 * The jQuery window object
	 *
	 * @var {Object}
	 */
	$window: null,

	/**
	 * The jQuery body object
	 *
	 * @var {Object}
	 */
	$body: null,

	/**
	 * Object containing the instances of djs.Callstack
	 *
	 * @var {Object}
	 */
	_stacks: {},

	/**
	 * Shortcut to _stack.main
	 *
	 * @var {Object}
	 */
	stack: null,

	/**
	 * Object used to get inactivity delay
	 *
	 * @var {Object}
	 */
	timeout: null,

	/* ========================================================================
	 * 	INITIALIZATION
	 * ====================================================================== */
	/**
	 * Initialize the object
	 *
	 * @return {Object}
	 */
	init: function () {

		// Check if already initialized
		if (this.initialized) return this;

		// Objects
		this.$window = $(window);
		this.$body = $('body');

		// Initialize the call stacks
		$.each(this.stacks, function (i, e) {
			this._stacks[e] = new djs.CallStack();
		}.bind(this));

		// Short cut to the main stack
		this.stack = this._stacks.main;

		// Bind the resize event
		this.$window.bind('resize.' + this.namespace, function () {

			// Check if initialized
			if (this.initialized) {

				// If a delay is defined, we set a timeout to trigger call stacks
				if (this._delay > 0) {

					// Clear timeout of previous resize's event
					clearTimeout(this.timeout);

					// Set new timeout
					this.timeout = setTimeout(function () {

						// Call the refresh
						this.refresh();

					}.bind(this), this._delay);

				}

				// If no delay, just call the refresh function
				else {
					this.refresh();
				}

			}

		}.bind(this));

		// Set the flag to prevent another initialization
		this.initialized = true;

		// Return self
		return this;
	},

	/**
	 * Destroy the object (if initialized)
	 *
	 * @return {Object}
	 */
	destroy: function () {

		// Check if already initialized
		if (!this.initialized) return this;

		// Unbind the events
		this.$window.unbind('resize.' + this.namespace);

		// Reset the stacks
		this.stack = null;
		this._stacks = {};

		// Unset the flag
		this.initialized = false;

		// Return self
		return this;
	},
	/**
	 * Re-initialize the object
	 *
	 * @return {Object}
	 */
	reinit: function () {
		// Destroy and init
		return this.destroy().init();
	},

	/* ========================================================================
	 * 	BINDING
	 * ====================================================================== */
	/**
	 * Add a callback to a stack. Default, to the main stack
	 *
	 * @param {String} namespace
	 * @param {Function} callback
	 * @param {String} stackName (default : djs.resize.stacks.main)
	 * @return {Object}
	 */
	bind: function (namespace, callback, stackName) {

		// Default value for stackName
		if (stackName == null) stackName = this.stacks.main;

		// Push the callback to the stack
		this._stacks[stackName].add(namespace, callback);

		// Return self
		return this;
	},
	/**
	 * Remove a callback to a stack. Default, from the main stack
	 *
	 * @param {String} namespace
	 * @param {String} stackName (default : djs.resize.stacks.main)
	 * @return {Object}
	 */
	unbind: function (namespace, stackName) {

		// Default value for stackName
		if (stackName == null) stackName = this.stacks.main;

		//Delete callback from the stack
		this._stacks[stackName].delete(namespace);

		// Return self
		return this;
	},

	/* ========================================================================
	 * 	METHODS
	 * ====================================================================== */

	/**
	 * Runs all the stacks.
	 * Subroutine of the resize event.
	 * Call this function to force refresh
	 *
	 * @return {Object}
	 */
	refresh: function () {

		// Add flag to body (for CSS use)
		this.$body.addClass(this.classes.resizing);

		// Run all stacks
		$.each(this.stacks, function (i, e) {
			this._stacks[e].run();
		}.bind(this));

		// Remove flag from body
		this.$body.removeClass(this.classes.resizing);

		// Return self
		return this;
	},

	/**
	 * Define the delay used to detect the end of the resize.
	 *
	 * @param {Integer} delay
	 * @return {Object}
	 */
	delay: function (delay) {
		this._delay = delay;
		return this;
	}
};
<commit_msg>Property namespace<commit_after>/**
 * @author Edouard Demotes-Mainard <https://github.com/EdouardDem>
 * @license http://opensource.org/licenses/BSD-2-Clause BSD 2-Clause License
 */

/**
 * Object djs for namespace
 */
window.djs = window.djs || {};

/**
 * This object manages window's resizing event.
 * It detects the end of the resizing events suite and trigger callbacks.
 * It also allows to manage callbacks order through the class djs.Callstack
 * This object is "chainable".
 *
 * @see https://github.com/EdouardDem/djs-resize
 * @requires djs.CallStack <https://github.com/EdouardDem/djs-call-stack>
 */
djs.resize = {

	/* ========================================================================
	 * 	PROPERTIES
	 * ====================================================================== */
	/**
	 * Delay used to detect the end of the resize.
	 * For example, if we define delay = 100, then if a resize starts and no resize's event is recorded for 100ms,
	 * we will consider the resizing is done and run the callbacks.
	 * To disables this feature, delay should be set to 0.
	 *
	 * @var {Integer}
	 */
	_delay: 0,

	/**
	 * CSS classes used to tag the body while processing
	 *
	 * @var {Object}
	 */
	classes: {
		resizing: 'resizing'
	},

	/**
	 * Names and order of the call stacks used by this object
	 *
	 * @var {Object}
	 */
	stacks: {
		core: 'core',
		main: 'main',
		last: 'last'
	},

	/**
	 * Namespace used to bind events
	 *
	 * @var {String}
	 */
	_namespace: 'djs-resize',

	/**
	 * Flag used to determine if the object is initialized
	 *
	 * @var {Boolean}
	 */
	initialized: false,

	/**
	 * The jQuery window object
	 *
	 * @var {Object}
	 */
	$window: null,

	/**
	 * The jQuery body object
	 *
	 * @var {Object}
	 */
	$body: null,

	/**
	 * Object containing the instances of djs.Callstack
	 *
	 * @var {Object}
	 */
	_stacks: {},

	/**
	 * Shortcut to _stack.main
	 *
	 * @var {Object}
	 */
	stack: null,

	/**
	 * Object used to get inactivity delay
	 *
	 * @var {Object}
	 */
	timeout: null,

	/* ========================================================================
	 * 	INITIALIZATION
	 * ====================================================================== */
	/**
	 * Initialize the object
	 *
	 * @return {Object}
	 */
	init: function () {

		// Check if already initialized
		if (this.initialized) return this;

		// Objects
		this.$window = $(window);
		this.$body = $('body');

		// Initialize the call stacks
		$.each(this.stacks, function (i, e) {
			this._stacks[e] = new djs.CallStack();
		}.bind(this));

		// Short cut to the main stack
		this.stack = this._stacks.main;

		// Bind the resize event
		this.$window.bind('resize.' + this._namespace, function () {

			// Check if initialized
			if (this.initialized) {

				// If a delay is defined, we set a timeout to trigger call stacks
				if (this._delay > 0) {

					// Clear timeout of previous resize's event
					clearTimeout(this.timeout);

					// Set new timeout
					this.timeout = setTimeout(function () {

						// Call the refresh
						this.refresh();

					}.bind(this), this._delay);

				}

				// If no delay, just call the refresh function
				else {
					this.refresh();
				}

			}

		}.bind(this));

		// Set the flag to prevent another initialization
		this.initialized = true;

		// Return self
		return this;
	},

	/**
	 * Destroy the object (if initialized)
	 *
	 * @return {Object}
	 */
	destroy: function () {

		// Check if already initialized
		if (!this.initialized) return this;

		// Unbind the events
		this.$window.unbind('resize.' + this._namespace);

		// Reset the stacks
		this.stack = null;
		this._stacks = {};

		// Unset the flag
		this.initialized = false;

		// Return self
		return this;
	},
	/**
	 * Re-initialize the object
	 *
	 * @return {Object}
	 */
	reinit: function () {
		// Destroy and init
		return this.destroy().init();
	},

	/* ========================================================================
	 * 	BINDING
	 * ====================================================================== */
	/**
	 * Add a callback to a stack. Default, to the main stack
	 *
	 * @param {String} namespace
	 * @param {Function} callback
	 * @param {String} stackName (default : djs.resize.stacks.main)
	 * @return {Object}
	 */
	bind: function (namespace, callback, stackName) {

		// Default value for stackName
		if (stackName == null) stackName = this.stacks.main;

		// Push the callback to the stack
		this._stacks[stackName].add(namespace, callback);

		// Return self
		return this;
	},
	/**
	 * Remove a callback to a stack. Default, from the main stack
	 *
	 * @param {String} namespace
	 * @param {String} stackName (default : djs.resize.stacks.main)
	 * @return {Object}
	 */
	unbind: function (namespace, stackName) {

		// Default value for stackName
		if (stackName == null) stackName = this.stacks.main;

		//Delete callback from the stack
		this._stacks[stackName].delete(namespace);

		// Return self
		return this;
	},

	/* ========================================================================
	 * 	METHODS
	 * ====================================================================== */

	/**
	 * Runs all the stacks.
	 * Subroutine of the resize event.
	 * Call this function to force refresh
	 *
	 * @return {Object}
	 */
	refresh: function () {

		// Add flag to body (for CSS use)
		this.$body.addClass(this.classes.resizing);

		// Run all stacks
		$.each(this.stacks, function (i, e) {
			this._stacks[e].run();
		}.bind(this));

		// Remove flag from body
		this.$body.removeClass(this.classes.resizing);

		// Return self
		return this;
	},

	/**
	 * Define the delay used to detect the end of the resize.
	 *
	 * @param {Integer} delay
	 * @return {Object}
	 */
	delay: function (delay) {
		this._delay = delay;
		return this;
	}
};
<|endoftext|>