<commit_before>/**
 * The PlaceService class manages places in the application.
 *
 * A place is just the current state of the application which can be
 * represented as an Object or a URL. For example, the place associated with:
 *
 *   http://localhost:5000/map/6/2/17/ALL/terrain/loss
 *
 * Can also be represented like this:
 *
 *  zoom - 6
 *  lat - 2
 *  lng - 17
 *  iso - ALL
 *  maptype - terrain
 *  baselayers - loss
 *
 * The PlaceService class handles the following use cases:
 *
 * 1) New route updates views
 *
 *   The Router receives a new URL and all application views need to be updated
 *   with the state encoded in the URL.
 *
 *   Here the router publishes the "Place/update" event passing in the route
 *   name and route parameters. The PlaceService handles the event by
 *   standardizing the route parameters and publishing them in a "Place/go"
 *   event. Any presenters listening to the event receives the updated
 *   application state and can update their views.
 *
 * 2) Updated view updates URL
 *
 *   A View state changes (e.g., a new map zoom) and the URL needs to be
 *   updated, not only with its new state, but from the state of all views in
 *   the application that provide state for URLs.
 *
 *   Here presenters publishe the "Place/register" event passing in a
 *   reference to themselves. The PlaceService subscribes to the
 *   "Place/register" event so that it can keep references to all presenters
 *   that provide state. Then the view publishes the "Place/update" event
 *   passing in a "go" parameter. If "go" is false, the PlaceService will
 *   update the URL. Otherwise it will publish the "Place/go" event which will
 *   notify all subscribed presenters.
 *
 * @return {PlaceService} The PlaceService class
 */
define([
  'underscore',
  'mps',
  'uri',
  'moment',
  'map/presenters/PresenterClass',
  'map/services/LayerSpecService'
], function (_, mps, UriTemplate, moment, PresenterClass, layerSpecService) {

  'use strict';

  var urlDefaultsParams = {
    baselayers: 'loss,forestgain',
    zoom: 3,
    lat: 15,
    lng: 27,
    maptype: 'grayscale',
    iso: 'ALL'
  };

  var PlaceService = PresenterClass.extend({

    _uriTemplate: '{name}{/zoom}{/lat}{/lng}{/iso}{/maptype}{/baselayers}{/sublayers}{?geojson,wdpaid,begin,end,threshold,dont_analyze}',

    /**
     * Create new PlaceService with supplied Backbone.Router.
     *
     * @param  {Backbond.Router} router Instance of Backbone.Router
     */
    init: function(router) {
      this.router = router;
      this._presenters = [];
      this._name = null;
      this._presenters.push(layerSpecService); // this makes the test fail
      this._super();
    },

    /**
     * Subscribe to application events.
     */
    _subscriptions: [{
      'Place/register': function(presenter) {
        this._presenters = _.union(this._presenters, [presenter]);
      }
    }, {
      'Place/update': function() {
        this._updatePlace();
      }
    }],

    /**
     * Init by the router to set the name
     * and publish the first place.
     *
     * @param  {String} name   Place name
     * @param  {Object} params Url params
     */
    initPlace: function(name, params) {
      this._name = name;
      this._newPlace(params);
    },

    /**
     * Silently updates the url from the presenter params.
     */
    _updatePlace: function() {
      var route, params;
      params = this._destandardizeParams(
        this._getPresenterParams(this._presenters));

      route = this._getRoute(params);
      this.router.navigateTo(route, {silent: true});
    },

    /**
     * Handles a new place.
     *
     * @param  {Object}  params The place parameters
     */
    _newPlace: function(params) {
      var where, place = {};

      place.params = this._standardizeParams(params);

      where = _.union(place.params.baselayers,
        place.params.sublayers);

      layerSpecService.toggle(
        where,
        _.bind(function(layerSpec) {
          place.layerSpec = layerSpec;
          mps.publish('Place/go', [place]);
        }, this)
      );
    },

    /**
     * Return route URL for supplied route name and route params.
     *
     * @param  {Object} params The route params
     * @return {string} The route URL
     */
    _getRoute: function(param) {
      var url = new UriTemplate(this._uriTemplate).fillFromObject(param);
      return decodeURIComponent(url);
    },

    /**
     * Return standardized representation of supplied params object.
     *
     * @param  {Object} params The params to standardize
     * @return {Object} The standardized params.
     */
    _standardizeParams: function(params) {
      var p = _.extendNonNull({}, urlDefaultsParams, params);
      p.name = this._name;

      p.baselayers = _.map(p.baselayers.split(','), function(slug) {
        return {slug: slug};
      });

      p.sublayers = p.sublayers ? _.map(p.sublayers.split(','), function(id) {
        return {id: _.toNumber(id)};
      }) : [];

      p.zoom = _.toNumber(p.zoom);
      p.lat = _.toNumber(p.lat);
      p.lng = _.toNumber(p.lng);
      p.iso = _.object(['country', 'region'], p.iso.split('-'));
      p.begin = p.begin ? moment(p.begin) : null;
      p.end = p.end ? moment(p.end) : null;
      p.geojson = p.geojson ? JSON.parse(decodeURIComponent(p.geojson)) : null;
      p.wdpaid = p.wdpaid ? _.toNumber(p.wdpaid) : null;
      p.threshold = p.threshold ? _.toNumber(p.threshold) : null;
      p.subscribe_alerts = (p.subscribe_alerts === 'subscribe') ? true : null;
      p.referral = p.referral;
      return p;
    },

    /**
     * Return formated URL representation of supplied params object based on
     * a route name.
     *
     * @param  {Object} params Place to standardize
     * @return {Object} Params ready for URL
     */
    _destandardizeParams: function(params) {
      var p = _.extendNonNull({}, urlDefaultsParams, params);
      var baselayers = _.pluck(p.baselayers, 'slug');
      p.name = this._name;
      p.baselayers = (baselayers.length > 0) ? baselayers : 'none';
      p.sublayers = p.sublayers ? p.sublayers.join(',') : null;
      p.zoom = String(p.zoom);
      p.lat = p.lat.toFixed(2);
      p.lng = p.lng.toFixed(2);
      p.iso = _.compact(_.values(p.iso)).join('-') || 'ALL';
      p.begin = p.begin ? p.begin.hour(12).utc().format('YYYY-MM-DD') : null;
      p.end = p.end ? p.end.hour(12).utc().format('YYYY-MM-DD') : null;
      p.geojson = p.geojson ? encodeURIComponent(p.geojson) : null;
      p.wdpaid = p.wdpaid ? String(p.wdpaid) : null;
      p.threshold = p.threshold ? String(p.threshold) : null;
      return p;
    },

    /**
     * Return param object representing state from all registered presenters
     * that implement getPlaceParams().
     *
     * @param  {Array} presenters The registered presenters
     * @return {Object} Params representing state from all presenters
     */
    _getPresenterParams: function(presenters) {
      var p = {};

      _.each(presenters, function(presenter) {
        _.extend(p, presenter.getPlaceParams());
      }, this);

      return p;
    }

  });

  return PlaceService;
});
<commit_msg>Placeservice hour fix<commit_after>/**
 * The PlaceService class manages places in the application.
 *
 * A place is just the current state of the application which can be
 * represented as an Object or a URL. For example, the place associated with:
 *
 *   http://localhost:5000/map/6/2/17/ALL/terrain/loss
 *
 * Can also be represented like this:
 *
 *  zoom - 6
 *  lat - 2
 *  lng - 17
 *  iso - ALL
 *  maptype - terrain
 *  baselayers - loss
 *
 * The PlaceService class handles the following use cases:
 *
 * 1) New route updates views
 *
 *   The Router receives a new URL and all application views need to be updated
 *   with the state encoded in the URL.
 *
 *   Here the router publishes the "Place/update" event passing in the route
 *   name and route parameters. The PlaceService handles the event by
 *   standardizing the route parameters and publishing them in a "Place/go"
 *   event. Any presenters listening to the event receives the updated
 *   application state and can update their views.
 *
 * 2) Updated view updates URL
 *
 *   A View state changes (e.g., a new map zoom) and the URL needs to be
 *   updated, not only with its new state, but from the state of all views in
 *   the application that provide state for URLs.
 *
 *   Here presenters publishe the "Place/register" event passing in a
 *   reference to themselves. The PlaceService subscribes to the
 *   "Place/register" event so that it can keep references to all presenters
 *   that provide state. Then the view publishes the "Place/update" event
 *   passing in a "go" parameter. If "go" is false, the PlaceService will
 *   update the URL. Otherwise it will publish the "Place/go" event which will
 *   notify all subscribed presenters.
 *
 * @return {PlaceService} The PlaceService class
 */
define([
  'underscore',
  'mps',
  'uri',
  'moment',
  'map/presenters/PresenterClass',
  'map/services/LayerSpecService'
], function (_, mps, UriTemplate, moment, PresenterClass, layerSpecService) {

  'use strict';

  var urlDefaultsParams = {
    baselayers: 'loss,forestgain',
    zoom: 3,
    lat: 15,
    lng: 27,
    maptype: 'grayscale',
    iso: 'ALL'
  };

  var PlaceService = PresenterClass.extend({

    _uriTemplate: '{name}{/zoom}{/lat}{/lng}{/iso}{/maptype}{/baselayers}{/sublayers}{?geojson,wdpaid,begin,end,threshold,dont_analyze}',

    /**
     * Create new PlaceService with supplied Backbone.Router.
     *
     * @param  {Backbond.Router} router Instance of Backbone.Router
     */
    init: function(router) {
      this.router = router;
      this._presenters = [];
      this._name = null;
      this._presenters.push(layerSpecService); // this makes the test fail
      this._super();
    },

    /**
     * Subscribe to application events.
     */
    _subscriptions: [{
      'Place/register': function(presenter) {
        this._presenters = _.union(this._presenters, [presenter]);
      }
    }, {
      'Place/update': function() {
        this._updatePlace();
      }
    }],

    /**
     * Init by the router to set the name
     * and publish the first place.
     *
     * @param  {String} name   Place name
     * @param  {Object} params Url params
     */
    initPlace: function(name, params) {
      this._name = name;
      this._newPlace(params);
    },

    /**
     * Silently updates the url from the presenter params.
     */
    _updatePlace: function() {
      var route, params;
      params = this._destandardizeParams(
        this._getPresenterParams(this._presenters));

      route = this._getRoute(params);
      this.router.navigateTo(route, {silent: true});
    },

    /**
     * Handles a new place.
     *
     * @param  {Object}  params The place parameters
     */
    _newPlace: function(params) {
      var where, place = {};

      place.params = this._standardizeParams(params);

      where = _.union(place.params.baselayers,
        place.params.sublayers);

      layerSpecService.toggle(
        where,
        _.bind(function(layerSpec) {
          place.layerSpec = layerSpec;
          mps.publish('Place/go', [place]);
        }, this)
      );
    },

    /**
     * Return route URL for supplied route name and route params.
     *
     * @param  {Object} params The route params
     * @return {string} The route URL
     */
    _getRoute: function(param) {
      var url = new UriTemplate(this._uriTemplate).fillFromObject(param);
      return decodeURIComponent(url);
    },

    /**
     * Return standardized representation of supplied params object.
     *
     * @param  {Object} params The params to standardize
     * @return {Object} The standardized params.
     */
    _standardizeParams: function(params) {
      var p = _.extendNonNull({}, urlDefaultsParams, params);
      p.name = this._name;

      p.baselayers = _.map(p.baselayers.split(','), function(slug) {
        return {slug: slug};
      });

      p.sublayers = p.sublayers ? _.map(p.sublayers.split(','), function(id) {
        return {id: _.toNumber(id)};
      }) : [];

      p.zoom = _.toNumber(p.zoom);
      p.lat = _.toNumber(p.lat);
      p.lng = _.toNumber(p.lng);
      p.iso = _.object(['country', 'region'], p.iso.split('-'));
      p.begin = p.begin ? moment(p.begin) : null;
      p.end = p.end ? moment(p.end) : null;
      p.geojson = p.geojson ? JSON.parse(decodeURIComponent(p.geojson)) : null;
      p.wdpaid = p.wdpaid ? _.toNumber(p.wdpaid) : null;
      p.threshold = p.threshold ? _.toNumber(p.threshold) : null;
      p.subscribe_alerts = (p.subscribe_alerts === 'subscribe') ? true : null;
      p.referral = p.referral;
      return p;
    },

    /**
     * Return formated URL representation of supplied params object based on
     * a route name.
     *
     * @param  {Object} params Place to standardize
     * @return {Object} Params ready for URL
     */
    _destandardizeParams: function(params) {
      var p = _.extendNonNull({}, urlDefaultsParams, params);
      var baselayers = _.pluck(p.baselayers, 'slug');
      p.name = this._name;
      p.baselayers = (baselayers.length > 0) ? baselayers : 'none';
      p.sublayers = p.sublayers ? p.sublayers.join(',') : null;
      p.zoom = String(p.zoom);
      p.lat = p.lat.toFixed(2);
      p.lng = p.lng.toFixed(2);
      p.iso = _.compact(_.values(p.iso)).join('-') || 'ALL';
      p.begin = p.begin ? p.begin.hour(1).utc().format('YYYY-MM-DD') : null;
      p.end = p.end ? p.end.hour(1).utc().format('YYYY-MM-DD') : null;
      p.geojson = p.geojson ? encodeURIComponent(p.geojson) : null;
      p.wdpaid = p.wdpaid ? String(p.wdpaid) : null;
      p.threshold = p.threshold ? String(p.threshold) : null;
      return p;
    },

    /**
     * Return param object representing state from all registered presenters
     * that implement getPlaceParams().
     *
     * @param  {Array} presenters The registered presenters
     * @return {Object} Params representing state from all presenters
     */
    _getPresenterParams: function(presenters) {
      var p = {};

      _.each(presenters, function(presenter) {
        _.extend(p, presenter.getPlaceParams());
      }, this);

      return p;
    }

  });

  return PlaceService;
});
<|endoftext|>


<commit_before>function prettyDate(date, mini) {
	if (!date) return '';

	if (typeof (date) == "string")
		date = new Date((date || "").replace(/-/g, "/").replace(/[TZ]/g, " ").replace(/\.[0-9]*/, ""));

	let diff = (((new Date()).getTime() - date.getTime()) / 1000);
	let day_diff = Math.floor(diff / 86400);

	if (isNaN(day_diff) || day_diff < 0) return '';

	if (mini) {
		// Return short format of time difference
		if (day_diff == 0) {
			if (diff < 60) {
				return __("Now");
			} else if (diff < 3600) {
				return __("{0} m", [Math.floor(diff / 60)]);
			} else if (diff < 86400) {
				return __("{0} h", [Math.floor(diff / 3600)]);
			}
		} else {
			if (day_diff < 7) {
				return __("{0} D", [day_diff]);
			} else if (day_diff < 31) {
				return __("{0} W", [Math.ceil(day_diff / 7)]);
			} else if (day_diff < 365) {
				return __("{0} M", [Math.ceil(day_diff / 30)]);
			} else {
				return __("{0} Y", [Math.ceil(day_diff / 365)]);
			}
		}
	} else {
		// Return long format of time difference
		if (day_diff == 0) {
			if (diff < 60) {
				return __("Just now");
			} else if (diff < 120) {
				return __("1 minute ago");
			} else if (diff < 3600) {
				return __("{0} minutes ago", [Math.floor(diff / 60)]);
			} else if (diff < 7200) {
				return __("1 hour ago");
			} else if (diff < 86400) {
				return __("{0} hours ago", [Math.floor(diff / 3600)]);
			}
		} else {
			if (day_diff == 1) {
				return __("Yesterday");
			} else if (day_diff < 7) {
				return __("{0} days ago", [day_diff]);
			} else if (day_diff < 14) {
				return __("1 week ago");
			} else if (day_diff < 31) {
				return __("{0} weeks ago", [Math.ceil(day_diff / 7)]);
			} else if (day_diff < 62) {
				return __("1 month ago");
			} else if (day_diff < 365) {
				return __("{0} months ago", [Math.ceil(day_diff / 30)]);
			} else if (day_diff < 730) {
				return __("1 year ago");
			} else {
				return __("{0} years ago", [Math.ceil(day_diff / 365)]);
			}
		}
	}
}

frappe.provide("frappe.datetime");
window.comment_when = function(datetime, mini) {
	var timestamp = frappe.datetime.str_to_user ?
		frappe.datetime.str_to_user(datetime) : datetime;
	return ''
		+ prettyDate(datetime, mini) + '';
};
frappe.datetime.comment_when = comment_when;

frappe.datetime.refresh_when = function() {
	if (jQuery) {
		$(".frappe-timestamp").each(function() {
			$(this).html(prettyDate($(this).attr("data-timestamp"), $(this).hasClass("mini")));
		});
	}
};

setInterval(function() {
	frappe.datetime.refresh_when();
}, 60000); // refresh every minute
<commit_msg>chore: use lowercase for time strings<commit_after>function prettyDate(date, mini) {
	if (!date) return '';

	if (typeof (date) == "string")
		date = new Date((date || "").replace(/-/g, "/").replace(/[TZ]/g, " ").replace(/\.[0-9]*/, ""));

	let diff = (((new Date()).getTime() - date.getTime()) / 1000);
	let day_diff = Math.floor(diff / 86400);

	if (isNaN(day_diff) || day_diff < 0) return '';

	if (mini) {
		// Return short format of time difference
		if (day_diff == 0) {
			if (diff < 60) {
				return __("now");
			} else if (diff < 3600) {
				return __("{0} m", [Math.floor(diff / 60)]);
			} else if (diff < 86400) {
				return __("{0} h", [Math.floor(diff / 3600)]);
			}
		} else {
			if (day_diff < 7) {
				return __("{0} D", [day_diff]);
			} else if (day_diff < 31) {
				return __("{0} W", [Math.ceil(day_diff / 7)]);
			} else if (day_diff < 365) {
				return __("{0} M", [Math.ceil(day_diff / 30)]);
			} else {
				return __("{0} Y", [Math.ceil(day_diff / 365)]);
			}
		}
	} else {
		// Return long format of time difference
		if (day_diff == 0) {
			if (diff < 60) {
				return __("Just now");
			} else if (diff < 120) {
				return __("1 minute ago");
			} else if (diff < 3600) {
				return __("{0} minutes ago", [Math.floor(diff / 60)]);
			} else if (diff < 7200) {
				return __("1 hour ago");
			} else if (diff < 86400) {
				return __("{0} hours ago", [Math.floor(diff / 3600)]);
			}
		} else {
			if (day_diff == 1) {
				return __("Yesterday");
			} else if (day_diff < 7) {
				return __("{0} days ago", [day_diff]);
			} else if (day_diff < 14) {
				return __("1 week ago");
			} else if (day_diff < 31) {
				return __("{0} weeks ago", [Math.ceil(day_diff / 7)]);
			} else if (day_diff < 62) {
				return __("1 month ago");
			} else if (day_diff < 365) {
				return __("{0} months ago", [Math.ceil(day_diff / 30)]);
			} else if (day_diff < 730) {
				return __("1 year ago");
			} else {
				return __("{0} years ago", [Math.ceil(day_diff / 365)]);
			}
		}
	}
}

frappe.provide("frappe.datetime");
window.comment_when = function(datetime, mini) {
	var timestamp = frappe.datetime.str_to_user ?
		frappe.datetime.str_to_user(datetime) : datetime;
	return ''
		+ prettyDate(datetime, mini) + '';
};
frappe.datetime.comment_when = comment_when;

frappe.datetime.refresh_when = function() {
	if (jQuery) {
		$(".frappe-timestamp").each(function() {
			$(this).html(prettyDate($(this).attr("data-timestamp"), $(this).hasClass("mini")));
		});
	}
};

setInterval(function() {
	frappe.datetime.refresh_when();
}, 60000); // refresh every minute
<|endoftext|>


<commit_before>/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

<commit_msg>Deleted empty JS file<commit_after><|endoftext|>


<commit_before>
xross = {

    _handlers_registry: {},
    _default_handler: 'ajax',
    data_items_prefix: 'x',
    debug: false,

    automate: function(xross_class, handler_name) {
        if (xross_class === undefined) {
            xross_class = 'xross'
        }
        $(function(){
            $.each($('.' + xross_class), function(idx, obj) {
                xross.describe('#' + $(obj).attr('id'), handler_name);
            });
        });
    },

    bootstrap: function() {
        this.register_handler('ajax', this.handlers.ajax);
    },

    register_handler: function(alias, func, default_params) {
        if (typeof func === 'object') {
            default_params = func.params;
            func = func.func;
        }

        if (default_params===undefined) {
            default_params = {};
        }

        this.utils.log(function(){ return 'Registering `' + alias + '` handler.' });

        this._handlers_registry[alias] = {
            func: func,
            params: default_params
        };
    },

    describe: function(el, handler_name, params) {

        if (typeof handler_name !== 'string') {
            params = handler_name;
            handler_name = this._default_handler;
        }

        if (params===undefined) {
            params = {}
        }

        var handler = this._handlers_registry[handler_name];
        if (handler===undefined) {
            if (handler_name===this._default_handler) {
                xross.bootstrap();
                handler = this._handlers_registry[handler_name];
            } else {
                throw { message: 'You are trying to use an unregistered handler:  `' + handler + '`.' };
            }
        }

        $(function(){
            if (!(el instanceof Array)) {
                el = [el]
            }
            $.each(el, function(idx, obj) {
                xross.utils.log(function(){ return 'Describing `' + obj + '` using `' + handler_name + '` handler.' });
                handler.func(obj, $.extend({}, handler.params, params));
            });
        });
    },

    utils: {

        evaluate: function(input, arg) {
            if (input instanceof Function) {
                return input(arg);
            }
            return input;
        },

        log: function(message) {
            if (xross.debug) {
                console.log('xross: ' + message());
            }
        },

        get_function: function(name, context) {
            var path_chunks = name.split('.'),
                func = path_chunks.pop();

            for (var i = 0; i < path_chunks.length; i++) {
                context = context[path_chunks[i]];
                if (context === undefined) {
                    xross.utils.log(function(){ return 'Unable to find `' + name + '` function.' });
                    return undefined;
                }
            }

            return context[func];
        },

        get_element_data: function(el) {
            var data_filtered = {},
                prefix = xross.data_items_prefix;

            $.each(el.data(), function(name, val){
                var t = typeof val;
                if (t == 'string' || t == 'number' || t == 'boolean') {  // Only simple types are supported, no object serialization.
                    if (name.slice(0, prefix.length) == prefix) {
                        name = name.slice(prefix.length);
                    }
                    data_filtered[name] = val;
                }
            });
            return data_filtered;
        }

    },

    handlers: {
        ajax: {
            func: function(el_selector, params) {
                var event_target = el_selector,
                    $el = $(el_selector),
                    operation_id = $el.attr('id');

                if (params.op) {
                    operation_id = params.op;
                }

                // Populate params with those from element's data attributes.
                params = $.extend({}, params, xross.utils.get_element_data($el));

                if (params.event==='auto') {
                    // Trying to automatically deduce event from element type.
                    params.event = 'ready';
                    if ($el.length) {
                        var tag_name = $el.prop('tagName').toLowerCase(),
                            event_mapping = {
                                button: 'click',
                                a: 'click'
                            },
                            proposed_event = event_mapping[tag_name];

                        if (proposed_event!==undefined) {
                            params.event = proposed_event;
                        }
                    }
                }

                if (params.event==='ready') {
                    if (!$el.length) {
                        // no sense in binding ready to a non existing element
                        xross.utils.log(function(){ return 'Skipping binding `ready` for not found `' + el_selector + '` element.' });
                        return;
                    }
                    // ready is used only for document object, so we force a new event target
                    event_target = {};
                }

                if (typeof params.target==='string') {
                    if (params.target==='this') {
                        // `this` alias into an actual element
                        params.target = el_selector;
                    } else {
                        // Trying to consider this target to be an element id.
                        var target_id = '#'+params.target;
                        params.target = function() { return $(target_id); };
                    }
                }

                if (typeof params.after==='string') {
                    params.after = xross.utils.get_function(params.after, window);
                }

                if (typeof params.success==='string') {
                    var func_name = params.success,
                        func = xross.utils.get_function(func_name, window);  // todo set this last

                    if (func === undefined) {
                        func = xross.utils.get_function(func_name, {
                            fill: function(target, data) { target.html(data); },
                            replace: function(target, data) { target.replaceWith(data); },
                            append: function(target, data) { target.append(data); },
                            prepend: function(target, data) { target.prepend(data); }
                        })
                    }

                    params.success = function(data, status, xhr) {
                        xross.utils.log(function(){ return 'Running `' + func_name + '` success function for `' + el_selector + '` element.' });
                        func($(xross.utils.evaluate(params.target, $el)), data);
                    };
                }

                if (typeof params.error==='string') {
                    var err_func_name = params.error,
                        err_func = xross.utils.get_function(err_func_name, window);  // todo set this last

                    if (err_func === undefined) {
                        err_func = xross.utils.get_function(err_func_name, {
                            log: function(xhr, status, error) {
                                xross.utils.log(function(){ return 'Request failed `' + error + '`: `' + xhr.responseText + '`.' });
                            }
                        })
                    }

                    params.error = function(xhr, status, error) {
                        xross.utils.log(function(){ return 'Running `' + err_func_name + '` error function for `' + el_selector + '` element.' });
                        err_func(xhr, status, error);
                    };
                }

                xross.utils.log(function(){ return 'Binding `' + params.event + '` for `' + el_selector + '`.' });

                $(document).on(params.event, event_target, function(e) {
                    var data = $.extend({}, { op: operation_id }, xross.utils.get_element_data($(el_selector))),
                        form = null;
                    xross.utils.log(function(){ return 'Triggering `' + params.event + '` for `' + el_selector + '` with `' + $.param(data) + '`.' });
                    if (params.form) {
                        if (typeof params.form==='string') {
                            form = $('#' + params.form);
                        } else {
                            form = params.form;
                        }
                        if (form.length) {
                            data = $.param(data) + '&' + form.serialize();  // Join form data with basic data.
                        }
                    }
                    $.ajax({
                        type: params.method,
                        data: data,
                        success: function(data, status, xhr) {
                            params.success(data, status, xhr);
                            if (params.after) {
                                params.after(xhr, status);
                            }
                        },
                        error: function(xhr, status, error) {
                            params.error(xhr, status, error);
                            if (params.after) {
                                params.after(xhr, status);
                            }
                        },
                        cache: false,
                        dataType: 'html'
                    });
                    e.preventDefault();
                });

            },
            params: {
                method: 'GET',
                event: 'auto',
                target: 'this',
                success: 'fill',
                error: 'log',
                after: null,
                form: null,
                op: null
            }
        }
    }

};
<commit_msg>Optimized `error` and `success` functions search.<commit_after>
xross = {

    _handlers_registry: {},
    _default_handler: 'ajax',
    data_items_prefix: 'x',
    debug: false,

    automate: function(xross_class, handler_name) {
        if (xross_class === undefined) {
            xross_class = 'xross'
        }
        $(function(){
            $.each($('.' + xross_class), function(idx, obj) {
                xross.describe('#' + $(obj).attr('id'), handler_name);
            });
        });
    },

    bootstrap: function() {
        this.register_handler('ajax', this.handlers.ajax);
    },

    register_handler: function(alias, func, default_params) {
        if (typeof func === 'object') {
            default_params = func.params;
            func = func.func;
        }

        if (default_params===undefined) {
            default_params = {};
        }

        this.utils.log(function(){ return 'Registering `' + alias + '` handler.' });

        this._handlers_registry[alias] = {
            func: func,
            params: default_params
        };
    },

    describe: function(el, handler_name, params) {

        if (typeof handler_name !== 'string') {
            params = handler_name;
            handler_name = this._default_handler;
        }

        if (params===undefined) {
            params = {}
        }

        var handler = this._handlers_registry[handler_name];
        if (handler===undefined) {
            if (handler_name===this._default_handler) {
                xross.bootstrap();
                handler = this._handlers_registry[handler_name];
            } else {
                throw { message: 'You are trying to use an unregistered handler:  `' + handler + '`.' };
            }
        }

        $(function(){
            if (!(el instanceof Array)) {
                el = [el]
            }
            $.each(el, function(idx, obj) {
                xross.utils.log(function(){ return 'Describing `' + obj + '` using `' + handler_name + '` handler.' });
                handler.func(obj, $.extend({}, handler.params, params));
            });
        });
    },

    utils: {

        evaluate: function(input, arg) {
            if (input instanceof Function) {
                return input(arg);
            }
            return input;
        },

        log: function(message) {
            if (xross.debug) {
                console.log('xross: ' + message());
            }
        },

        get_function: function(name, context) {
            var path_chunks = name.split('.'),
                func = path_chunks.pop();

            for (var i = 0; i < path_chunks.length; i++) {
                context = context[path_chunks[i]];
                if (context === undefined) {
                    xross.utils.log(function(){ return 'Unable to find `' + name + '` function.' });
                    return undefined;
                }
            }

            return context[func];
        },

        get_element_data: function(el) {
            var data_filtered = {},
                prefix = xross.data_items_prefix;

            $.each(el.data(), function(name, val){
                var t = typeof val;
                if (t == 'string' || t == 'number' || t == 'boolean') {  // Only simple types are supported, no object serialization.
                    if (name.slice(0, prefix.length) == prefix) {
                        name = name.slice(prefix.length);
                    }
                    data_filtered[name] = val;
                }
            });
            return data_filtered;
        }

    },

    handlers: {
        ajax: {
            func: function(el_selector, params) {
                var event_target = el_selector,
                    $el = $(el_selector),
                    operation_id = $el.attr('id');

                if (params.op) {
                    operation_id = params.op;
                }

                // Populate params with those from element's data attributes.
                params = $.extend({}, params, xross.utils.get_element_data($el));

                if (params.event==='auto') {
                    // Trying to automatically deduce event from element type.
                    params.event = 'ready';
                    if ($el.length) {
                        var tag_name = $el.prop('tagName').toLowerCase(),
                            event_mapping = {
                                button: 'click',
                                a: 'click'
                            },
                            proposed_event = event_mapping[tag_name];

                        if (proposed_event!==undefined) {
                            params.event = proposed_event;
                        }
                    }
                }

                if (params.event==='ready') {
                    if (!$el.length) {
                        // no sense in binding ready to a non existing element
                        xross.utils.log(function(){ return 'Skipping binding `ready` for not found `' + el_selector + '` element.' });
                        return;
                    }
                    // ready is used only for document object, so we force a new event target
                    event_target = {};
                }

                if (typeof params.target==='string') {
                    if (params.target==='this') {
                        // `this` alias into an actual element
                        params.target = el_selector;
                    } else {
                        // Trying to consider this target to be an element id.
                        var target_id = '#'+params.target;
                        params.target = function() { return $(target_id); };
                    }
                }

                if (typeof params.after==='string') {
                    params.after = xross.utils.get_function(params.after, window);
                }

                if (typeof params.success==='string') {
                    var func_name = params.success,
                        func = xross.utils.get_function(func_name, {
                            fill: function(target, data) { target.html(data); },
                            replace: function(target, data) { target.replaceWith(data); },
                            append: function(target, data) { target.append(data); },
                            prepend: function(target, data) { target.prepend(data); }
                        });

                    if (func===undefined) {
                        func = xross.utils.get_function(func_name, window);
                    }

                    params.success = function(data, status, xhr) {
                        xross.utils.log(function(){ return 'Running `' + func_name + '` success function for `' + el_selector + '` element.' });
                        func($(xross.utils.evaluate(params.target, $el)), data);
                    };
                }

                if (typeof params.error==='string') {
                    var err_func_name = params.error,
                        err_func = xross.utils.get_function(err_func_name, {
                            log: function(xhr, status, error) {
                                xross.utils.log(function(){ return 'Request failed `' + error + '`: `' + xhr.responseText + '`.' });
                            }
                        });

                    if (err_func===undefined) {
                        err_func = xross.utils.get_function(err_func_name, window);
                    }

                    params.error = function(xhr, status, error) {
                        xross.utils.log(function(){ return 'Running `' + err_func_name + '` error function for `' + el_selector + '` element.' });
                        err_func(xhr, status, error);
                    };
                }

                xross.utils.log(function(){ return 'Binding `' + params.event + '` for `' + el_selector + '`.' });

                $(document).on(params.event, event_target, function(e) {
                    var data = $.extend({}, { op: operation_id }, xross.utils.get_element_data($(el_selector))),
                        form = null;
                    xross.utils.log(function(){ return 'Triggering `' + params.event + '` for `' + el_selector + '` with `' + $.param(data) + '`.' });
                    if (params.form) {
                        if (typeof params.form==='string') {
                            form = $('#' + params.form);
                        } else {
                            form = params.form;
                        }
                        if (form.length) {
                            data = $.param(data) + '&' + form.serialize();  // Join form data with basic data.
                        }
                    }
                    $.ajax({
                        type: params.method,
                        data: data,
                        success: function(data, status, xhr) {
                            params.success(data, status, xhr);
                            if (params.after) {
                                params.after(xhr, status);
                            }
                        },
                        error: function(xhr, status, error) {
                            params.error(xhr, status, error);
                            if (params.after) {
                                params.after(xhr, status);
                            }
                        },
                        cache: false,
                        dataType: 'html'
                    });
                    e.preventDefault();
                });

            },
            params: {
                method: 'GET',
                event: 'auto',
                target: 'this',
                success: 'fill',
                error: 'log',
                after: null,
                form: null,
                op: null
            }
        }
    }

};
<|endoftext|>


<commit_before>'use strict';

const assert = require('assert');

const objFactory = require('app/factories/db-object');

const testSubject = 'app/factories/repository';

const testDb = require('../../../modules/test-db'),
      qAssert = require('../../../modules/q-assert');

const repoFactory = require(testSubject);

describe(testSubject, () => {

  const repo = repoFactory('test');

  before((done) => {
    testDb.setup(done);
  });

  after((done) => {
    testDb.destroy(done);
  });

  describe('save', () => {

    it('should throw an error if object has no property _id', (done) => {
      repo.save({foo: 'bar'})
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'id undefined');
        }))
        .nodeify(done);
    });

    it('should save an object to the database and return promised and populated object', (done) => {
      repo.save(objFactory({foo: 'bar', _id: 'foo'}))
        .then((obj) => qAssert(() => {
          assert.deepEqual(Object.keys(obj), ['foo', '_id', 'toString', 'toJSON', '_rev']);
        }))
        .nodeify(done);
    });

    it('should pass-through db errors', (done) => {
      repo.save(objFactory({foo: 'bar', _id: 'foo', _rev: 'foo'}))
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'bad_request: Invalid rev format');
        }))
        .nodeify(done);
    });

  });

  describe('get', () => {

    it('should return a single object from the db', (done) => {
      repo.get('foo')
        .then((obj) => qAssert(() => {
          assert.deepEqual(Object.keys(obj), ['foo', '_id', '_rev']);
        }))
        .nodeify(done);
    });

    it('should error if object id was not found', (done) => {
      repo.get('bar')
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'unknown id');
        }))
        .nodeify(done);
    });

  });

  describe('view', () => {

    it('should return an array of objects from a db view', (done) => {
      repo.view('byId')
        .then((results) => qAssert(() => {
          assert.equal(Array.isArray(results), true);
          assert.equal(results.length === 1, true);
        }))
        .nodeify(done);
    });

    it('should pass-through db errors', (done) => {
      repo.view('byFoo')
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'not_found: missing_named_view');
        }))
        .nodeify(done);
    });

  });

  describe('one', () => {

    it('should return a single object from a db view', (done) => {
      repo.one('byId', 'foo')
        .then((obj) => qAssert(() => {
          assert.deepEqual(Object.keys(obj), ['_id', '_rev', 'foo']);
        }))
        .nodeify(done);
    });

    it('should error if object key was not found', (done) => {
      repo.one('byId', 'bar')
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'unknown key');
        }))
        .nodeify(done);
    });

  });

  describe('keyNotTaken', () => {

    it('should test if a key is already present in the view and return the promised key', (done) => {
      repo.keyNotTaken('byId', 'bar')
        .then((obj) => qAssert(() => {
          assert.equal(obj, 'bar');
        }))
        .nodeify(done);
    });

    it('should error if key is present', (done) => {
      repo.keyNotTaken('byId', 'foo')
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'key taken');
        }))
        .nodeify(done);
    });

    it('should accept and excludeId to exlcude an object from test', (done) => {
      repo.keyNotTaken('byId', 'foo', 'foo')
        .then((obj) => qAssert(() => {
          assert.equal(obj, 'foo');
        }))
        .nodeify(done);
    });

    it('should pass-through db errors', (done) => {
      repo.keyNotTaken('byFoo', 'foo')
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'not_found: missing_named_view');
        }))
        .nodeify(done);
    });

  });

  describe('remove', () => {

    it('should remove an object from the database', (done) => {
      repo.get('foo')
        .then((obj) => repo.remove(obj))
        .nodeify(done);
    });

    it('should pass-through db errors', (done) => {
      repo.remove({_id: 'bar'})
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'not_found: missing');
        }))
        .nodeify(done);
    });

  });

  describe('clear', () => {

    it('should remove all objects from the database', (done) => {
      repo.save(objFactory({a: 1}))
        .then(() => repo.save(objFactory({a: 2})))
        .then(() => repo.view('byId'))
        .then((results) => qAssert(() => {
          assert.equal(results.length, 2);
        }))
        .then(() => repo.clear())
        .then(() => repo.view('byId'))
        .then((results) => qAssert(() => {
          assert.equal(results.length, 0);
        }))
        .nodeify(done);
    });

  });

});
<commit_msg>update repositoryFactory tests<commit_after>'use strict';

const assert = require('assert');

const objFactory = require('app/factories/db-object');

const testSubject = 'app/factories/repository';

const testDb = require('../../../modules/test-db'),
      qAssert = require('../../../modules/q-assert');

const repoFactory = require(testSubject);

describe(testSubject, () => {

  const repo = repoFactory('test');

  before((done) => {
    testDb.setup(done);
  });

  after((done) => {
    testDb.destroy(done);
  });

  describe('save', () => {

    it('should throw an error if object has no property _id', (done) => {
      repo.save({foo: 'bar'})
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'id undefined');
        }))
        .nodeify(done);
    });

    it('should save an object to the database and return promised and populated object', (done) => {
      repo.save(objFactory({foo: 'bar', _id: 'foo'}))
        .then((obj) => qAssert(() => {
          assert.deepEqual(Object.keys(obj), ['foo', '_id', 'toString', 'toJSON', '_rev']);
        }))
        .nodeify(done);
    });

    it('should pass-through db errors', (done) => {
      repo.save(objFactory({foo: 'bar', _id: 'foo', _rev: 'foo'}))
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'bad_request: Invalid rev format');
        }))
        .nodeify(done);
    });

  });

  describe('get', () => {

    it('should return a single object from the db', (done) => {
      repo.get('foo')
        .then((obj) => qAssert(() => {
          assert.deepEqual(Object.keys(obj), ['foo', '_id', '_rev']);
        }))
        .nodeify(done);
    });

    it('should error if object id was not found', (done) => {
      repo.get('bar')
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'unknown id');
        }))
        .nodeify(done);
    });

  });

  describe('view', () => {

    it('should return an array of objects from a db view', (done) => {
      repo.view('byId')
        .then((results) => qAssert(() => {
          assert.equal(Array.isArray(results), true);
          assert.equal(results.length === 1, true);
        }))
        .nodeify(done);
    });

    it('should return an empty array view returns no objects', (done) => {
      repo.get('foo')
        .then((obj) => repo.remove(obj))
        .then((obj) => repo.view('byId'))
        .then((results) => qAssert(() => {
          assert.equal(Array.isArray(results), true);
          assert.equal(results.length === 0, true);
        }))
        .nodeify(done);
    });

    it('should pass-through db errors', (done) => {
      repo.view('byFoo')
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'not_found: missing_named_view');
        }))
        .nodeify(done);
    });

  });

  describe('one', () => {

    it('should return a single object from a db view', (done) => {
      repo.save(objFactory({foo: 'bar', _id: 'foo'}))
        .then((obj) => repo.one('byId', 'foo'))
        .then((obj) => qAssert(() => {
          assert.deepEqual(Object.keys(obj), ['_id', '_rev', 'foo']);
        }))
        .nodeify(done);
    });

    it('should error if object key was not found', (done) => {
      repo.one('byId', 'bar')
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'unknown key');
        }))
        .nodeify(done);
    });

  });

  describe('keyNotTaken', () => {

    it('should test if a key is already present in the view and return the promised key', (done) => {
      repo.keyNotTaken('byId', 'bar')
        .then((obj) => qAssert(() => {
          assert.equal(obj, 'bar');
        }))
        .nodeify(done);
    });

    it('should error if key is present', (done) => {
      repo.keyNotTaken('byId', 'foo')
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'key taken');
        }))
        .nodeify(done);
    });

    it('should accept and excludeId to exclude an object from test', (done) => {
      repo.keyNotTaken('byId', 'foo', 'foo')
        .then((obj) => qAssert(() => {
          assert.equal(obj, 'foo');
        }))
        .nodeify(done);
    });

    it('should pass-through db errors', (done) => {
      repo.keyNotTaken('byFoo', 'foo')
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'not_found: missing_named_view');
        }))
        .nodeify(done);
    });

  });

  describe('remove', () => {

    it('should remove an object from the database', (done) => {
      repo.get('foo')
        .then((obj) => repo.remove(obj))
        .nodeify(done);
    });

    it('should pass-through db errors', (done) => {
      repo.remove({_id: 'bar'})
        .then((obj) => obj, (err) => qAssert(() => {
          assert.equal(err.message, 'not_found: missing');
        }))
        .nodeify(done);
    });

  });

  describe('clear', () => {

    it('should remove all objects from the database', (done) => {
      repo.save(objFactory({a: 1}))
        .then(() => repo.save(objFactory({a: 2})))
        .then(() => repo.view('byId'))
        .then((results) => qAssert(() => {
          assert.equal(results.length, 2);
        }))
        .then(() => repo.clear())
        .then(() => repo.view('byId'))
        .then((results) => qAssert(() => {
          assert.equal(results.length, 0);
        }))
        .nodeify(done);
    });

  });

});
<|endoftext|>


<commit_before>/**
 * Easiest way to sort your tables
 *
 * For further information go to <https://github.com/dan-lee/jquery-easyTableSort>
 *
 * @author Daniel Lehr <daniellehr@gmx.de>
 * @internal-coding = utf-8
 * @internal UTF-Chars: ÄÖÜäöüß∆
 * created on 13.11.12 8:38 PM.
 */

(function($) {
  $.fn.simpleTableSort = function(options) {

    var defaultOptions = {
      defaultOrder: 'asc',
      sortMethods: {},
      excludeSortColumns: [],
      onBeforeSort: function () {},
      onAfterSort: function () {}
    };
    options = $.extend(defaultOptions, options || {});

    var table = $(this);
    var rows  = table.find('tbody').children();
    var cols  = table.find('thead').find('th');

    cols.on('click', sort);

    // http://stackoverflow.com/a/1353711/612202
    function isDate(d) {
      if (Object.prototype.toString.call(d) === '[object Date]') {
        return !isNaN(d.getTime());
      } else {
        return false;
      }
    }

    var defaultSortMethods = {
      numeric: function(a, b) {
        return (parseInt(a) - parseInt(b));
      },
      float: function(a, b) {
        return (parseFloat(a) - parseFloat(b));
      },
      alphabetical: function(a, b) {
        return (a.toLowerCase() > b.toLowerCase()) ? 1 : -1;
      },
      date: function(a, b) {
        a = new Date(a);
        b = new Date(b);

        if (isDate(a) && isDate(b)) {
          return (a > b) ? 1 : -1;
        }
        return 0;
      }
    };
    $.extend(options.sortMethods, defaultSortMethods);

    // default ordering
    var orderBy = options.defaultOrder;

    var orderStates = [];

    function toggleOrder(el) {
      if (!el.hasClass('sort-asc') && !el.hasClass('sort-desc')) {
        el.addClass('sort-' + orderBy);
      } else if (el.hasClass('sort-asc')) {
        el
          .addClass('sort-desc')
          .removeClass('sort-asc');

        orderBy = 'desc';
        console.log('order set to desc');
      } else if (el.hasClass('sort-desc')) {
        el
          .addClass('sort-asc')
          .removeClass('sort-desc');

        orderBy = 'asc';
      }
    }

    function isExcluded(current, len) {
      // is the clicked column an excluded one? if so: abort
      var abort = false;
      $.each(options.excludeSortColumns, function(i, val) {
        val += val < 0 ? len : -1;

        if (current === val) {
          abort = true;
        }
      });
      return abort;
    }

    function sort() {
      var el = $(this);
      var columns = el.parent().children();
      var columnLength = columns.length;
      var columnIndex = columns.index(el);

      var cn = this.className;
      // check if there's a class starting with 'sort-'
      if (/\bsort-/.test(cn)) {
        var method = cn.match(/sort-([^\s]+)/)[1];

        if (options.sortMethods.hasOwnProperty(method)) {
          options.onBeforeSort.call(el, columnIndex+1);

          // is the clicked column an excluded one? if so: abort
          if (isExcluded(columnIndex, columnLength)) {
            return;
          }

          // change order of the rows
          toggleOrder(el);

          rows.sort(function(a, b) {
            a = $(a).find('td').eq(columnIndex).text();
            b = $(b).find('td').eq(columnIndex).text();

            if (!a || !b) {
              return;
            }

            console.log(orderBy);
            return options.sortMethods[method](a, b) * (orderBy === 'asc' ? 1 : -1);
          });

          // re-render the new sorted table
          render();

          // call after sort hook
          options.onAfterSort.call(el, columnIndex+1);
        } else console.error('No suitable sort method found.');
      }
    }

    function render() {
      $.each(rows, function(i, val) {
        table.append(val);
      });
    }
  };
})(jQuery);
<commit_msg>made columns sort independent from each other. toggle sort modes is now generic and not depending on static values<commit_after>/**
 * Easiest way to sort your tables
 *
 * For further information go to <https://github.com/dan-lee/jquery-easyTableSort>
 *
 * @author Daniel Lehr <daniellehr@gmx.de>
 * @internal-coding = utf-8
 * @internal UTF-Chars: ÄÖÜäöüß∆
 * created on 13.11.12 8:38 PM.
 */

(function($) {
  $.fn.simpleTableSort = function(options) {
    var table = $(this);
    var rows = table.find('tbody').children();
    var headCols = table.find('thead').find('th');

    var sortOrder = new Array(headCols.length);
    var sortModes = ['asc', 'desc'];

    var defaultOptions = {
      prefix: 'sort-',
      defaultOrder: sortModes[0],
      sortMethods: {},
      excludeSortColumns: [],
      onBeforeSort: function () {},
      onAfterSort: function () {}
    };
    options = $.extend(defaultOptions, options || {});

    headCols.on('click', sort);

    // http://stackoverflow.com/a/1353711/612202
    function isDate(d) {
      if (Object.prototype.toString.call(d) === '[object Date]') {
        return !isNaN(d.getTime());
      } else {
        return false;
      }
    }

    var defaultSortMethods = {
      numeric: function(a, b) {
        return (parseInt(a) - parseInt(b));
      },
      float: function(a, b) {
        return (parseFloat(a) - parseFloat(b));
      },
      alphabetical: function(a, b) {
        return (a.toLowerCase() > b.toLowerCase()) ? 1 : -1;
      },
      date: function(a, b) {
        a = new Date(a);
        b = new Date(b);

        if (isDate(a) && isDate(b)) {
          return (a > b) ? 1 : -1;
        }
        return 0;
      }
    };
    $.extend(options.sortMethods, defaultSortMethods);

    if (!Array.prototype.getIndexByValue) {
      Array.prototype.getIndexByValue = function(value) {
        var result = false;
        $.each(this, function(index, item) {
          if (value === item) {
            result = index;
            return false;
          }
        });
        return result;
      };
    }

    function toggleOrder(col) {
      var index = col.index;
      var currentOrder = sortOrder[index];
      var newKey, oldKey;

      if (typeof currentOrder === 'undefined') {
        newKey = sortModes.getIndexByValue(options.defaultOrder);
        sortOrder[index] = sortModes[newKey];
        col.element.addClass(options.prefix + sortModes[newKey]);
      } else {
        oldKey = sortModes.getIndexByValue(currentOrder);
        // little trick for toggling of two values:
        // 1. cast to bool, 2. negate the value, 3. cast back to int
        newKey = +!oldKey;
        sortOrder[index] = sortModes[newKey];

        col.element.removeClass(options.prefix + sortModes[oldKey]).addClass(options.prefix + sortModes[newKey]);
      }
    }

    function isExcluded(current) {
      var len = headCols.length;

      // is the clicked column an excluded one? if so: abort
      var abort = false;
      $.each(options.excludeSortColumns, function(i, val) {
        val += val < 0 ? len : -1;

        if (current === val) {
          abort = true;
        }
      });
      return abort;
    }

    function sort() {
      var element = $(this);
      var columnLength = headCols.length;
      var columnIndex = headCols.index(element);

      var cn = this.className;
      // check if there's a class starting with the given prefix
      if (new RegExp('\\b' + options.prefix).test(cn)) {
        var method = cn.match(new RegExp(options.prefix + '([^\\s]+)'))[1];

        if (options.sortMethods.hasOwnProperty(method)) {
          options.onBeforeSort.call(element, columnIndex+1);

          // is the clicked column an excluded one? if so: abort
          if (isExcluded(columnIndex, columnLength)) {
            return;
          }

          // change order of the rows
          toggleOrder({
            element: element,
            index: columnIndex
          });

          rows.sort(function(a, b) {
            a = $(a).find('td').eq(columnIndex).text();
            b = $(b).find('td').eq(columnIndex).text();

            if (!a || !b) {
              return;
            }

            return options.sortMethods[method](a, b) * (sortOrder[columnIndex] === sortModes[0] ? 1 : -1);
          });

          // re-render the new sorted table
          render();

          // call after sort hook
          options.onAfterSort.call(element, columnIndex+1);
        } else console.error('No suitable sort method found.');
      }
    }

    function render() {
      $.each(rows, function(i, val) {
        table.append(val);
      });
    }
  };
})(jQuery);
<|endoftext|>


<commit_before>/**
 * Handles single pointer dragging
 */

import {
  equalizePoints, roundPoint, clamp
} from '../util/util.js';

const PAN_END_FRICTION = 0.35;
const VERTICAL_DRAG_FRICTION = 0.6;

// 1 corresponds to the third of viewport height
const MIN_RATIO_TO_CLOSE = 0.4;

// Minimum speed required to navigate
// to next or previous slide
const MIN_NEXT_SLIDE_SPEED = 0.5;

function project(initialVelocity, decelerationRate) {
  return initialVelocity * decelerationRate / (1 - decelerationRate);
}

class DragHandler {
  constructor(gestures) {
    this.gestures = gestures;
    this.pswp = gestures.pswp;
    this.startPan = {};
  }

  start() {
    equalizePoints(this.startPan, this.pswp.currSlide.pan);
    this.pswp.animations.stopAll();
  }

  change() {
    const { p1, prevP1, dragAxis, pswp } = this.gestures;
    const { currSlide } = pswp;

    if (dragAxis === 'y'
        && pswp.options.closeOnVerticalDrag
        && currSlide.currZoomLevel <= currSlide.zoomLevels.vFill
        && !this.gestures.isMultitouch) {
      // Handle vertical drag to close
      const panY = currSlide.pan.y + (p1.y - prevP1.y);
      if (!pswp.dispatch('verticalDrag', { panY }).defaultPrevented) {
        this._setPanWithFriction('y', panY, VERTICAL_DRAG_FRICTION);
        const bgOpacity = 1 - Math.abs(this._getVerticalDragRatio(currSlide.pan.y));
        pswp.applyBgOpacity(bgOpacity);
        currSlide.applyCurrentZoomPan();
      }
    } else {
      const mainScrollChanged = this._panOrMoveMainScroll('x');
      if (!mainScrollChanged) {
        this._panOrMoveMainScroll('y');

        roundPoint(currSlide.pan);
        currSlide.applyCurrentZoomPan();
      }
    }
  }

  end() {
    const { pswp, velocity } = this.gestures;
    const { mainScroll } = pswp;
    let indexDiff = 0;

    pswp.animations.stopAll();

    // Handle main scroll if it's shifted
    if (mainScroll.isShifted()) {
      // Position of the main scroll relative to the viewport
      const mainScrollShiftDiff = mainScroll.x - mainScroll.getCurrSlideX();

      // Ratio between 0 and 1:
      // 0 - slide is not visible at all,
      // 0.5 - half of the slide is vicible
      // 1 - slide is fully visible
      const currentSlideVisibilityRatio = (mainScrollShiftDiff / pswp.viewportSize.x);

      // Go next slide.
      //
      // - if velocity and its direction is matched
      //   and we see at least tiny part of the next slide
      //
      // - or if we see less than 50% of the current slide
      //   and velocity is close to 0
      //
      if ((velocity.x < -MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio < 0)
          || (velocity.x < 0.1 && currentSlideVisibilityRatio < -0.5)) {
        // Go to next slide
        indexDiff = 1;
        velocity.x = Math.min(velocity.x, 0);
      } else if ((velocity.x > MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio > 0)
          || (velocity.x > -0.1 && currentSlideVisibilityRatio > 0.5)) {
        // Go to prev slide
        indexDiff = -1;
        velocity.x = Math.max(velocity.x, 0);
      }

      mainScroll.moveIndexBy(indexDiff, true, velocity.x);
    }

    // Restore zoom level
    if ((pswp.currSlide.currZoomLevel > pswp.currSlide.zoomLevels.max
        && this.pswp.options.limitMaxZoom)
        || this.gestures.isMultitouch) {
      this.gestures.zoomLevels.correctZoomPan(true);
    } else {
      // we run two animations instead of one,
      // as each axis has own pan boundaries and thus different spring function
      // (correctZoomPan does not have this functionality,
      //  it animates all properties with single timing function)
      this._finishPanGestureForAxis('x');
      this._finishPanGestureForAxis('y');
    }
  }

  _finishPanGestureForAxis(axis) {
    const { pswp } = this;
    const { currSlide } = pswp;
    const { velocity } = this.gestures;
    const { pan, bounds } = currSlide;
    const panPos = pan[axis];
    const restoreBgOpacity = (pswp.bgOpacity < 1 && axis === 'y');

    // 0.995 means - scroll view loses 0.5% of its velocity per millisecond
    // Inceasing this number will reduce travel distance
    const decelerationRate = 0.995; // 0.99

    // Pan position if there is no bounds
    const projectedPosition = panPos + project(velocity[axis], decelerationRate);

    if (restoreBgOpacity) {
      const vDragRatio = this._getVerticalDragRatio(panPos);
      const projectedVDragRatio = this._getVerticalDragRatio(projectedPosition);

      // If we are above and moving upwards,
      // or if we are below and moving downwards
      if ((vDragRatio < 0 && projectedVDragRatio < -MIN_RATIO_TO_CLOSE)
          || (vDragRatio > 0 && projectedVDragRatio > MIN_RATIO_TO_CLOSE)) {
        pswp.close();
        return;
      }
    }

    // Pan position with corrected bounds
    const correctedPanPosition = bounds.correctPan(axis, projectedPosition);

    // Exit if pan position should not be changed
    // or if speed it too low
    if (panPos === correctedPanPosition) {
      return;
    }

    // Overshoot if the final position is out of pan bounds
    const dampingRatio = (correctedPanPosition === projectedPosition) ? 1 : 0.82;

    const initialBgOpacity = pswp.bgOpacity;
    const totalPanDist = correctedPanPosition - panPos;

    pswp.animations.startSpring({
      name: 'panGesture' + axis,
      isPan: true,
      start: panPos,
      end: correctedPanPosition,
      velocity: velocity[axis],
      dampingRatio,
      onUpdate: (pos) => {
        // Animate opacity of background relative to Y pan position of an image
        if (restoreBgOpacity && pswp.bgOpacity < 1) {
          // 0 - start of animation, 1 - end of animation
          const animationProgressRatio = 1 - (correctedPanPosition - pos) / totalPanDist;

          // We clamp opacity to keep it between 0 and 1.
          // As progress ratio can be larger than 1 due to overshoot,
          // and we do not want to bounce opacity.
          pswp.applyBgOpacity(clamp(
            initialBgOpacity + (1 - initialBgOpacity) * animationProgressRatio,
            0,
            1
          ));
        }

        pan[axis] = pos;
        currSlide.applyCurrentZoomPan();
      },
    });
  }

  /**
   * Update position of the main scroll,
   * or/and update pan position of the current slide.
   *
   * Should return true if it changes (or can change) main scroll.
   *
   * @param {String} axis
   */
  _panOrMoveMainScroll(axis) {
    const { p1, pswp, dragAxis, prevP1, isMultitouch } = this.gestures;
    const { currSlide, mainScroll } = pswp;
    const delta = (p1[axis] - prevP1[axis]);
    const newMainScrollX = mainScroll.x + delta;

    if (!delta) {
      return;
    }

    // Always move main scroll if image can not be panned
    if (axis === 'x' && !currSlide.isPannable() && !isMultitouch) {
      mainScroll.moveTo(newMainScrollX, true);
      return true; // changed main scroll
    }

    const { bounds } = currSlide;
    const newPan = currSlide.pan[axis] + delta;

    if (pswp.options.allowPanToNext
        && dragAxis === 'x'
        && axis === 'x'
        && !isMultitouch) {
      const currSlideMainScrollX = mainScroll.getCurrSlideX();

      // Position of the main scroll relative to the viewport
      const mainScrollShiftDiff = mainScroll.x - currSlideMainScrollX;

      const isLeftToRight = delta > 0;
      const isRightToLeft = !isLeftToRight;

      if (newPan > bounds.min[axis] && isLeftToRight) {
        // Panning from left to right, beyond the left edge

        // Wether the image was at minimum pan position (or less)
        // when this drag gesture started.
        // Minimum pan position refers to the left edge of the image.
        const wasAtMinPanPosition = (bounds.min[axis] <= this.startPan[axis]);

        if (wasAtMinPanPosition) {
          mainScroll.moveTo(newMainScrollX, true);
          return true;
        } else {
          this._setPanWithFriction(axis, newPan);
          //currSlide.pan[axis] = newPan;
        }
      } else if (newPan < bounds.max[axis] && isRightToLeft) {
        // Paning from right to left, beyond the right edge

        // Maximum pan position refers to the right edge of the image.
        const wasAtMaxPanPosition = (this.startPan[axis] <= bounds.max[axis]);

        if (wasAtMaxPanPosition) {
          mainScroll.moveTo(newMainScrollX, true);
          return true;
        } else {
          this._setPanWithFriction(axis, newPan);
          //currSlide.pan[axis] = newPan;
        }
      } else {
        // If main scroll is shifted
        if (mainScrollShiftDiff !== 0) {
          // If main scroll is shifted right
          if (mainScrollShiftDiff > 0 /*&& isRightToLeft*/) {
            mainScroll.moveTo(Math.max(newMainScrollX, currSlideMainScrollX), true);
            return true;
          } else if (mainScrollShiftDiff < 0 /*&& isLeftToRight*/) {
            // Main scroll is shifted left (Position is less than 0 comparing to the viewport 0)
            mainScroll.moveTo(Math.min(newMainScrollX, currSlideMainScrollX), true);
            return true;
          }
        } else {
          // We are within pan bounds, so just pan
          this._setPanWithFriction(axis, newPan);
        }
      }
    } else {
      if (axis === 'y') {
        // Do not pan vertically if main scroll is shifted o
        if (!mainScroll.isShifted() && bounds.min.y !== bounds.max.y) {
          this._setPanWithFriction(axis, newPan);
        }
      } else {
        this._setPanWithFriction(axis, newPan);
      }
    }
  }
  //
  // If we move above - the ratio is negative
  // If we move below the ratio is positive

  /**
   * Relation between pan Y position and third of viewport height.
   *
   * When we are at initial position (center bounds) - the ratio is 0,
   * if position is shifted upwards - the ratio is negative,
   * if position is shifted downwards - the ratio is positive.
   *
   * @param {Number} panY The current pan Y position.
   */
  _getVerticalDragRatio(panY) {
    return (panY - this.pswp.currSlide.bounds.center.y)
            / (this.pswp.viewportSize.y / 3);
  }

  /**
   * Set pan position of the current slide.
   * Apply friction if the position is beyond the pan bounds,
   * or if custom friction is defined.
   *
   * @param {String} axis
   * @param {Number} potentialPan
   * @param {Number|null} customFriction (0.1 - 1)
   */
  _setPanWithFriction(axis, potentialPan, customFriction) {
    const { pan, bounds } = this.pswp.currSlide;
    const correctedPan = bounds.correctPan(axis, potentialPan);
    // If we are out of pan bounds
    if (correctedPan !== potentialPan || customFriction) {
      const delta = Math.round(potentialPan - pan[axis]);
      pan[axis] += delta * (customFriction || PAN_END_FRICTION);
    } else {
      pan[axis] = potentialPan;
    }
  }
}

export default DragHandler;
<commit_msg>round pan position for animation after drag<commit_after>/**
 * Handles single pointer dragging
 */

import {
  equalizePoints, roundPoint, clamp
} from '../util/util.js';

const PAN_END_FRICTION = 0.35;
const VERTICAL_DRAG_FRICTION = 0.6;

// 1 corresponds to the third of viewport height
const MIN_RATIO_TO_CLOSE = 0.4;

// Minimum speed required to navigate
// to next or previous slide
const MIN_NEXT_SLIDE_SPEED = 0.5;

function project(initialVelocity, decelerationRate) {
  return initialVelocity * decelerationRate / (1 - decelerationRate);
}

class DragHandler {
  constructor(gestures) {
    this.gestures = gestures;
    this.pswp = gestures.pswp;
    this.startPan = {};
  }

  start() {
    equalizePoints(this.startPan, this.pswp.currSlide.pan);
    this.pswp.animations.stopAll();
  }

  change() {
    const { p1, prevP1, dragAxis, pswp } = this.gestures;
    const { currSlide } = pswp;

    if (dragAxis === 'y'
        && pswp.options.closeOnVerticalDrag
        && currSlide.currZoomLevel <= currSlide.zoomLevels.vFill
        && !this.gestures.isMultitouch) {
      // Handle vertical drag to close
      const panY = currSlide.pan.y + (p1.y - prevP1.y);
      if (!pswp.dispatch('verticalDrag', { panY }).defaultPrevented) {
        this._setPanWithFriction('y', panY, VERTICAL_DRAG_FRICTION);
        const bgOpacity = 1 - Math.abs(this._getVerticalDragRatio(currSlide.pan.y));
        pswp.applyBgOpacity(bgOpacity);
        currSlide.applyCurrentZoomPan();
      }
    } else {
      const mainScrollChanged = this._panOrMoveMainScroll('x');
      if (!mainScrollChanged) {
        this._panOrMoveMainScroll('y');

        roundPoint(currSlide.pan);
        currSlide.applyCurrentZoomPan();
      }
    }
  }

  end() {
    const { pswp, velocity } = this.gestures;
    const { mainScroll } = pswp;
    let indexDiff = 0;

    pswp.animations.stopAll();

    // Handle main scroll if it's shifted
    if (mainScroll.isShifted()) {
      // Position of the main scroll relative to the viewport
      const mainScrollShiftDiff = mainScroll.x - mainScroll.getCurrSlideX();

      // Ratio between 0 and 1:
      // 0 - slide is not visible at all,
      // 0.5 - half of the slide is vicible
      // 1 - slide is fully visible
      const currentSlideVisibilityRatio = (mainScrollShiftDiff / pswp.viewportSize.x);

      // Go next slide.
      //
      // - if velocity and its direction is matched
      //   and we see at least tiny part of the next slide
      //
      // - or if we see less than 50% of the current slide
      //   and velocity is close to 0
      //
      if ((velocity.x < -MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio < 0)
          || (velocity.x < 0.1 && currentSlideVisibilityRatio < -0.5)) {
        // Go to next slide
        indexDiff = 1;
        velocity.x = Math.min(velocity.x, 0);
      } else if ((velocity.x > MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio > 0)
          || (velocity.x > -0.1 && currentSlideVisibilityRatio > 0.5)) {
        // Go to prev slide
        indexDiff = -1;
        velocity.x = Math.max(velocity.x, 0);
      }

      mainScroll.moveIndexBy(indexDiff, true, velocity.x);
    }

    // Restore zoom level
    if ((pswp.currSlide.currZoomLevel > pswp.currSlide.zoomLevels.max
        && this.pswp.options.limitMaxZoom)
        || this.gestures.isMultitouch) {
      this.gestures.zoomLevels.correctZoomPan(true);
    } else {
      // we run two animations instead of one,
      // as each axis has own pan boundaries and thus different spring function
      // (correctZoomPan does not have this functionality,
      //  it animates all properties with single timing function)
      this._finishPanGestureForAxis('x');
      this._finishPanGestureForAxis('y');
    }
  }

  _finishPanGestureForAxis(axis) {
    const { pswp } = this;
    const { currSlide } = pswp;
    const { velocity } = this.gestures;
    const { pan, bounds } = currSlide;
    const panPos = pan[axis];
    const restoreBgOpacity = (pswp.bgOpacity < 1 && axis === 'y');

    // 0.995 means - scroll view loses 0.5% of its velocity per millisecond
    // Inceasing this number will reduce travel distance
    const decelerationRate = 0.995; // 0.99

    // Pan position if there is no bounds
    const projectedPosition = panPos + project(velocity[axis], decelerationRate);

    if (restoreBgOpacity) {
      const vDragRatio = this._getVerticalDragRatio(panPos);
      const projectedVDragRatio = this._getVerticalDragRatio(projectedPosition);

      // If we are above and moving upwards,
      // or if we are below and moving downwards
      if ((vDragRatio < 0 && projectedVDragRatio < -MIN_RATIO_TO_CLOSE)
          || (vDragRatio > 0 && projectedVDragRatio > MIN_RATIO_TO_CLOSE)) {
        pswp.close();
        return;
      }
    }

    // Pan position with corrected bounds
    const correctedPanPosition = bounds.correctPan(axis, projectedPosition);

    // Exit if pan position should not be changed
    // or if speed it too low
    if (panPos === correctedPanPosition) {
      return;
    }

    // Overshoot if the final position is out of pan bounds
    const dampingRatio = (correctedPanPosition === projectedPosition) ? 1 : 0.82;

    const initialBgOpacity = pswp.bgOpacity;
    const totalPanDist = correctedPanPosition - panPos;

    pswp.animations.startSpring({
      name: 'panGesture' + axis,
      isPan: true,
      start: panPos,
      end: correctedPanPosition,
      velocity: velocity[axis],
      dampingRatio,
      onUpdate: (pos) => {
        // Animate opacity of background relative to Y pan position of an image
        if (restoreBgOpacity && pswp.bgOpacity < 1) {
          // 0 - start of animation, 1 - end of animation
          const animationProgressRatio = 1 - (correctedPanPosition - pos) / totalPanDist;

          // We clamp opacity to keep it between 0 and 1.
          // As progress ratio can be larger than 1 due to overshoot,
          // and we do not want to bounce opacity.
          pswp.applyBgOpacity(clamp(
            initialBgOpacity + (1 - initialBgOpacity) * animationProgressRatio,
            0,
            1
          ));
        }

        pan[axis] = Math.floor(pos);
        currSlide.applyCurrentZoomPan();
      },
    });
  }

  /**
   * Update position of the main scroll,
   * or/and update pan position of the current slide.
   *
   * Should return true if it changes (or can change) main scroll.
   *
   * @param {String} axis
   */
  _panOrMoveMainScroll(axis) {
    const { p1, pswp, dragAxis, prevP1, isMultitouch } = this.gestures;
    const { currSlide, mainScroll } = pswp;
    const delta = (p1[axis] - prevP1[axis]);
    const newMainScrollX = mainScroll.x + delta;

    if (!delta) {
      return;
    }

    // Always move main scroll if image can not be panned
    if (axis === 'x' && !currSlide.isPannable() && !isMultitouch) {
      mainScroll.moveTo(newMainScrollX, true);
      return true; // changed main scroll
    }

    const { bounds } = currSlide;
    const newPan = currSlide.pan[axis] + delta;

    if (pswp.options.allowPanToNext
        && dragAxis === 'x'
        && axis === 'x'
        && !isMultitouch) {
      const currSlideMainScrollX = mainScroll.getCurrSlideX();

      // Position of the main scroll relative to the viewport
      const mainScrollShiftDiff = mainScroll.x - currSlideMainScrollX;

      const isLeftToRight = delta > 0;
      const isRightToLeft = !isLeftToRight;

      if (newPan > bounds.min[axis] && isLeftToRight) {
        // Panning from left to right, beyond the left edge

        // Wether the image was at minimum pan position (or less)
        // when this drag gesture started.
        // Minimum pan position refers to the left edge of the image.
        const wasAtMinPanPosition = (bounds.min[axis] <= this.startPan[axis]);

        if (wasAtMinPanPosition) {
          mainScroll.moveTo(newMainScrollX, true);
          return true;
        } else {
          this._setPanWithFriction(axis, newPan);
          //currSlide.pan[axis] = newPan;
        }
      } else if (newPan < bounds.max[axis] && isRightToLeft) {
        // Paning from right to left, beyond the right edge

        // Maximum pan position refers to the right edge of the image.
        const wasAtMaxPanPosition = (this.startPan[axis] <= bounds.max[axis]);

        if (wasAtMaxPanPosition) {
          mainScroll.moveTo(newMainScrollX, true);
          return true;
        } else {
          this._setPanWithFriction(axis, newPan);
          //currSlide.pan[axis] = newPan;
        }
      } else {
        // If main scroll is shifted
        if (mainScrollShiftDiff !== 0) {
          // If main scroll is shifted right
          if (mainScrollShiftDiff > 0 /*&& isRightToLeft*/) {
            mainScroll.moveTo(Math.max(newMainScrollX, currSlideMainScrollX), true);
            return true;
          } else if (mainScrollShiftDiff < 0 /*&& isLeftToRight*/) {
            // Main scroll is shifted left (Position is less than 0 comparing to the viewport 0)
            mainScroll.moveTo(Math.min(newMainScrollX, currSlideMainScrollX), true);
            return true;
          }
        } else {
          // We are within pan bounds, so just pan
          this._setPanWithFriction(axis, newPan);
        }
      }
    } else {
      if (axis === 'y') {
        // Do not pan vertically if main scroll is shifted o
        if (!mainScroll.isShifted() && bounds.min.y !== bounds.max.y) {
          this._setPanWithFriction(axis, newPan);
        }
      } else {
        this._setPanWithFriction(axis, newPan);
      }
    }
  }
  //
  // If we move above - the ratio is negative
  // If we move below the ratio is positive

  /**
   * Relation between pan Y position and third of viewport height.
   *
   * When we are at initial position (center bounds) - the ratio is 0,
   * if position is shifted upwards - the ratio is negative,
   * if position is shifted downwards - the ratio is positive.
   *
   * @param {Number} panY The current pan Y position.
   */
  _getVerticalDragRatio(panY) {
    return (panY - this.pswp.currSlide.bounds.center.y)
            / (this.pswp.viewportSize.y / 3);
  }

  /**
   * Set pan position of the current slide.
   * Apply friction if the position is beyond the pan bounds,
   * or if custom friction is defined.
   *
   * @param {String} axis
   * @param {Number} potentialPan
   * @param {Number|null} customFriction (0.1 - 1)
   */
  _setPanWithFriction(axis, potentialPan, customFriction) {
    const { pan, bounds } = this.pswp.currSlide;
    const correctedPan = bounds.correctPan(axis, potentialPan);
    // If we are out of pan bounds
    if (correctedPan !== potentialPan || customFriction) {
      const delta = Math.round(potentialPan - pan[axis]);
      pan[axis] += delta * (customFriction || PAN_END_FRICTION);
    } else {
      pan[axis] = potentialPan;
    }
  }
}

export default DragHandler;
<|endoftext|>


<commit_before>/*
 * Utilities: A classic collection of JavaScript utilities
 * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
 *
 * 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.
 *
*/

var async = {};

/*
AsyncChain -- performs a list of asynchronous calls in a desired order.
Optional "last" method can be set to run after all the items in the
chain have completed.

  // Example usage
  var asyncChain = new async.AsyncChain([
    {
      func: app.trainToBangkok,
      args: [geddy, neil, alex],
      callback: null, // No callback for this action
    },
    {
      func: fs.readdir,
      args: [geddy.config.dirname + '/thailand/express'],
      callback: function (err, result) {
        if (err) {
          // Bail out completely
          arguments.callee.chain.abort();
        }
        else if (result.theBest) {
          // Don't run the next item in the chain; go directly
          // to the 'last' method.
          arguments.callee.chain.shortCircuit();
        }
        else {
          // Otherwise do some other stuff and
          // then go to the next link
        }
      }
    },
    {
      func: child_process.exec,
      args: ['ls ./'],
      callback: this.hitTheStops
    }
  ]);

  // Function to exec after all the links in the chain finish
  asyncChain.last = function () { // Do some final stuff };

  // Start the async-chain
  asyncChain.run();

*/
async.execNonBlocking = function (func) {
  if (typeof process != 'undefined' && typeof process.nextTick == 'function') {
    process.nextTick(func);
  }
  else {
    setTimeout(func, 0);
  }
};

async.AsyncBase = new (function () {

  this.init = function (chain) {
    var item;
    this.chain = [];
    this.currentItem = null;
    this.shortCircuited = false;
    this.shortCircuitedArgs = undefined;
    this.aborted = false;

    for (var i = 0; i < chain.length; i++) {
      item = chain[i];
      this.chain.push(new async.AsyncCall(
          item.func, item.args, item.callback, item.context));
    }
  };

  this.runItem = function (item) {
    // Reference to the current item in the chain -- used
    // to look up the callback to execute with execCallback
    this.currentItem = item;
    // Scopage
    var _this = this;
    // Pass the arguments passed to the current async call
    // to the callback executor, execute it in the correct scope
    var executor = function () {
      _this.execCallback.apply(_this, arguments);
    };
    // Append the callback executor to the end of the arguments
    // Node helpfully always has the callback func last
    var args = item.args.concat(executor);
    var func = item.func;
    // Run the async call
    func.apply(item.context, args);
  };

  this.next = function () {
    if (this.chain.length) {
      this.runItem(this.chain.shift());
    }
    else {
      this.last();
    }
  };

  this.execCallback = function () {
    // Look up the callback, if any, specified for this async call
    var callback = this.currentItem.callback;
    // If there's a callback, do it
    if (callback && typeof callback == 'function') {
      // Allow access to the chain from inside the callback by setting
      // callback.chain = this, and then using arguments.callee.chain
      callback.chain = this;
      callback.apply(this.currentItem.context, arguments);
    }

    this.currentItem.finished = true;

    // If one of the async callbacks called chain.shortCircuit,
    // skip to the 'last' function for the chain
    if (this.shortCircuited) {
      this.last.apply(null, this.shortCircuitedArgs);
    }
    // If one of the async callbacks called chain.abort,
    // bail completely out
    else if (this.aborted) {
      return;
    }
    // Otherwise run the next item, if any, in the chain
    // Let's try not to block if we don't have to
    else {
      // Scopage
      var _this = this;
      async.execNonBlocking(function () { _this.next.call(_this); });
    }
  }

  // Short-circuit the chain, jump straight to the 'last' function
  this.shortCircuit = function () {
    this.shortCircuitedArgs = arguments;
    this.shortCircuited = true;
  }

  // Stop execution of the chain, bail completely out
  this.abort = function () {
    this.aborted = true;
  }

  // Kick off the chain by grabbing the first item and running it
  this.run = this.next;

  // Function to run when the chain is done -- default is a no-op
  this.last = function () {};

})();

async.AsyncChain = function (chain) {
  this.init(chain);
};

async.AsyncChain.prototype = async.AsyncBase;

async.AsyncGroup = function (group) {
  var item;
  var callback;
  var args;

  this.group = [];
  this.outstandingCount = 0;

  for (var i = 0; i < group.length; i++) {
    item = group[i];
    this.group.push(new async.AsyncCall(
        item.func, item.args, item.callback, item.context));
    this.outstandingCount++;
  }

};

/*
Simpler way to group async calls -- doesn't ensure completion order,
but still has a "last" method called when the entire group of calls
have completed.
*/
async.AsyncGroup.prototype = new function () {
  this.run = function () {
    var _this = this
      , group = this.group
      , item
      , createItem = function (item, args) {
          return function () {
            item.func.apply(item.context, args);
          };
        }
      , createCallback = function (item) {
          return function () {
            if (item.callback) {
              item.callback.apply(null, arguments);
            }
            _this.finish.call(_this);
          }
        };

    for (var i = 0; i < group.length; i++) {
      item = group[i];
      callback = createCallback(item);
      args = item.args.concat(callback);
      // Run the async call
      async.execNonBlocking(createItem(item, args));
    }
  };

  this.finish = function () {
    this.outstandingCount--;
    if (!this.outstandingCount) {
      this.last();
    };
  };

  this.last = function () {};

};

var _createSimpleAsyncCall = function (func, context) {
  return {
    func: func
  , args: []
  , callback: function () {}
  , context: context
  };
};

async.SimpleAsyncChain = function (funcs, context) {
  chain = [];
  for (var i = 0, ii = funcs.length; i < ii; i++) {
    chain.push(_createSimpleAsyncCall(funcs[i], context));
  }
  this.init(chain);
};

async.SimpleAsyncChain.prototype = async.AsyncBase;

async.AsyncCall = function (func, args, callback, context) {
  this.func = func;
  this.args = args;
  this.callback = callback || null;
  this.context = context || null;
};

async.Initializer = function (steps, callback) {
  var self = this;
  this.steps = {};
  this.callback = callback;
  // Create an object-literal of the steps to tick off
  steps.forEach(function (step) {
    self.steps[step] = false;
  });
};

async.Initializer.prototype = new (function () {
  this.complete = function (step) {
    var steps = this.steps;
    // Tick this step off
    steps[step] = true;
    // Iterate the steps -- if any are not done, bail out
    for (var p in steps) {
      if (!steps[p]) {
        return false;
      }
    }
    // If all steps are done, run the callback
    this.callback();
  };
})();

module.exports = async;

<commit_msg>added async.asyncChain.addItem() with alias push()<commit_after>/*
 * Utilities: A classic collection of JavaScript utilities
 * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
 *
 * 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.
 *
*/

var async = {};

/*
AsyncChain -- performs a list of asynchronous calls in a desired order.
Optional "last" method can be set to run after all the items in the
chain have completed.

  // Example usage
  var asyncChain = new async.AsyncChain([
    {
      func: app.trainToBangkok,
      args: [geddy, neil, alex],
      callback: null, // No callback for this action
    },
    {
      func: fs.readdir,
      args: [geddy.config.dirname + '/thailand/express'],
      callback: function (err, result) {
        if (err) {
          // Bail out completely
          arguments.callee.chain.abort();
        }
        else if (result.theBest) {
          // Don't run the next item in the chain; go directly
          // to the 'last' method.
          arguments.callee.chain.shortCircuit();
        }
        else {
          // Otherwise do some other stuff and
          // then go to the next link
        }
      }
    },
    {
      func: child_process.exec,
      args: ['ls ./'],
      callback: this.hitTheStops
    }
  ]);

  // Function to exec after all the links in the chain finish
  asyncChain.last = function () { // Do some final stuff };

  // Start the async-chain
  asyncChain.run();

*/
async.execNonBlocking = function (func) {
  if (typeof process != 'undefined' && typeof process.nextTick == 'function') {
    process.nextTick(func);
  }
  else {
    setTimeout(func, 0);
  }
};

async.AsyncBase = new (function () {

  this.init = function (chain) {
    var item;
    this.chain = [];
    this.currentItem = null;
    this.shortCircuited = false;
    this.shortCircuitedArgs = undefined;
    this.aborted = false;

    for (var i = 0; i < chain.length; i++) {
      item = chain[i];
      this.addItem(item);
    }
  };

  this.addItem = function(item) {
    this.chain.push(new async.AsyncCall(
      item.func, item.args || [], item.callback, item.context));
  };

  // alias
  this.push = this.addItem;

  this.runItem = function (item) {
    // Reference to the current item in the chain -- used
    // to look up the callback to execute with execCallback
    this.currentItem = item;
    // Scopage
    var _this = this;
    // Pass the arguments passed to the current async call
    // to the callback executor, execute it in the correct scope
    var executor = function () {
      _this.execCallback.apply(_this, arguments);
    };
    // Append the callback executor to the end of the arguments
    // Node helpfully always has the callback func last
    var args = item.args.concat(executor);
    var func = item.func;
    // Run the async call
    func.apply(item.context, args);
  };

  this.next = function () {
    if (this.chain.length) {
      this.runItem(this.chain.shift());
    }
    else {
      this.last();
    }
  };

  this.execCallback = function () {
    // Look up the callback, if any, specified for this async call
    var callback = this.currentItem.callback;
    // If there's a callback, do it
    if (callback && typeof callback == 'function') {
      // Allow access to the chain from inside the callback by setting
      // callback.chain = this, and then using arguments.callee.chain
      callback.chain = this;
      callback.apply(this.currentItem.context, arguments);
    }

    this.currentItem.finished = true;

    // If one of the async callbacks called chain.shortCircuit,
    // skip to the 'last' function for the chain
    if (this.shortCircuited) {
      this.last.apply(null, this.shortCircuitedArgs);
    }
    // If one of the async callbacks called chain.abort,
    // bail completely out
    else if (this.aborted) {
      return;
    }
    // Otherwise run the next item, if any, in the chain
    // Let's try not to block if we don't have to
    else {
      // Scopage
      var _this = this;
      async.execNonBlocking(function () { _this.next.call(_this); });
    }
  }

  // Short-circuit the chain, jump straight to the 'last' function
  this.shortCircuit = function () {
    this.shortCircuitedArgs = arguments;
    this.shortCircuited = true;
  }

  // Stop execution of the chain, bail completely out
  this.abort = function () {
    this.aborted = true;
  }

  // Kick off the chain by grabbing the first item and running it
  this.run = this.next;

  // Function to run when the chain is done -- default is a no-op
  this.last = function () {};

})();

async.AsyncChain = function (chain) {
  this.init(chain);
};

async.AsyncChain.prototype = async.AsyncBase;

async.AsyncGroup = function (group) {
  var item;
  var callback;
  var args;

  this.group = [];
  this.outstandingCount = 0;

  for (var i = 0; i < group.length; i++) {
    item = group[i];
    this.group.push(new async.AsyncCall(
        item.func, item.args, item.callback, item.context));
    this.outstandingCount++;
  }

};

/*
Simpler way to group async calls -- doesn't ensure completion order,
but still has a "last" method called when the entire group of calls
have completed.
*/
async.AsyncGroup.prototype = new function () {
  this.run = function () {
    var _this = this
      , group = this.group
      , item
      , createItem = function (item, args) {
          return function () {
            item.func.apply(item.context, args);
          };
        }
      , createCallback = function (item) {
          return function () {
            if (item.callback) {
              item.callback.apply(null, arguments);
            }
            _this.finish.call(_this);
          }
        };

    for (var i = 0; i < group.length; i++) {
      item = group[i];
      callback = createCallback(item);
      args = item.args.concat(callback);
      // Run the async call
      async.execNonBlocking(createItem(item, args));
    }
  };

  this.finish = function () {
    this.outstandingCount--;
    if (!this.outstandingCount) {
      this.last();
    };
  };

  this.last = function () {};

};

var _createSimpleAsyncCall = function (func, context) {
  return {
    func: func
  , args: []
  , callback: function () {}
  , context: context
  };
};

async.SimpleAsyncChain = function (funcs, context) {
  chain = [];
  for (var i = 0, ii = funcs.length; i < ii; i++) {
    chain.push(_createSimpleAsyncCall(funcs[i], context));
  }
  this.init(chain);
};

async.SimpleAsyncChain.prototype = async.AsyncBase;

async.AsyncCall = function (func, args, callback, context) {
  this.func = func;
  this.args = args;
  this.callback = callback || null;
  this.context = context || null;
};

async.Initializer = function (steps, callback) {
  var self = this;
  this.steps = {};
  this.callback = callback;
  // Create an object-literal of the steps to tick off
  steps.forEach(function (step) {
    self.steps[step] = false;
  });
};

async.Initializer.prototype = new (function () {
  this.complete = function (step) {
    var steps = this.steps;
    // Tick this step off
    steps[step] = true;
    // Iterate the steps -- if any are not done, bail out
    for (var p in steps) {
      if (!steps[p]) {
        return false;
      }
    }
    // If all steps are done, run the callback
    this.callback();
  };
})();

module.exports = async;

<|endoftext|>


<commit_before>const gulp = require('gulp');
const sass = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
const autoprefixer = require('gulp-autoprefixer');
const plumber = require('gulp-plumber');
const writefile = require('./bolg/helpers').writefile;
const browserSync = require('browser-sync');
const fs = require('fs');

function sync(done) {
  return browserSync.init({
    proxy: 'localhost:3000',
    open: true,
    logFileChanges: false,
  }, done);
}

function compileSass() {
  return gulp.src('src/styles/post-index.scss')
    .pipe(plumber())
    .pipe(sourcemaps.init())
    .pipe(sass({
      includePaths: ['node_modules'],
    }))
    .pipe(autoprefixer({
      browsers: ['last 2 versions'],
    }))
    .pipe(sourcemaps.write('/', {
      sourceMappingURLPrefix: '/css',
    }))
    .pipe(gulp.dest('public/css'))
    .pipe(browserSync.stream({ match: '**/*.css' }))
    .on('end', () => {
      const manifest = JSON.parse(fs.readFileSync('./public/config/webpack.manifest.json', 'utf-8'));
      manifest['/bolg.css'] = '/css/post-index.css';
      writefile('public/config/webpack.manifest.json', JSON.stringify(manifest));
    });
}

const watch = (done) => {
  gulp.watch('src/styles/**/*.scss', compileSass);
  sync(done);
};

gulp.task('default', watch);
gulp.task('sass', compileSass);
gulp.task('watch', watch);
gulp.task('server', sync);
<commit_msg>not used<commit_after><|endoftext|>