<commit_before>hqDefine("reports/js/filters/main", [
    'jquery',
    'knockout',
    'hqwebapp/js/main',
    'reports/js/standard_hq_report',
    'reports/js/filters/select2s',
    'reports/js/filters/phone_number',
    'reports/js/filters/button_group',
    'reports/js/filters/schedule_instance',
    'locations/js/location_drilldown',
    'reports/js/report_filter/advanced_forms_options',
    'reports/js/report_filter/drilldown_options',
    'reports/js/choice_list_utils',
    'select2-3.5.2-legacy/select2',
], function(
    $,
    ko,
    hqMain,
    standardHQReportModule,
    select2Filter,
    phoneNumberFilter,
    buttonGroup,
    scheduleInstanceFilter,
    locationDrilldown,
    advancedFormsOptions,
    drilldownOptions,
    choiceListUtils
) {
    var init = function() {
        // Datespans
        $('.report-filter-datespan').each(function() {
            var $filterRange = $(this);
            if ($filterRange.data("init")) {
                var separator = $filterRange.data('separator');
                var reportLabels = $filterRange.data('reportLabels');
                var standardHQReport = standardHQReportModule.getStandardHQReport();

                $filterRange.createDateRangePicker(
                    reportLabels, separator,
                    $filterRange.data('startDate'),
                    $filterRange.data('endDate')
                );
                $filterRange.on('change apply', function() {
                    var dates = $(this).val().split(separator);
                    $(standardHQReport.filterAccordion).trigger('hqreport.filter.datespan.startdate', dates[0]);
                    $('#report_filter_datespan_startdate').val(dates[0]);
                    $(standardHQReport.filterAccordion).trigger('hqreport.filter.datespan.enddate', dates[1]);
                    $('#report_filter_datespan_enddate').val(dates[1]);
                });
            }
        });

        // other Datespans *groan* TODO unify on this one
        $('.report-filter-datespan-filter').each(function(i, el) {
            var $el = $(el), data = $el.data();
            var $filterStart = $("#" + data.cssId + "-start");
            var $filterEnd = $("#" + data.cssId + "-end");

            $el.createBootstrap3DefaultDateRangePicker();
            $el.on('apply change', function () {
                var separator = $().getDateRangeSeparator();
                var dates = $el.val().split(separator);
                $filterStart.val(dates[0]);
                $filterEnd.val(dates[1]);
            });

            if (!$el.val() && $filterStart.val() && $filterEnd.val()) {
                var text = $filterStart.val() + $().getDateRangeSeparator() + $filterEnd.val();
                $el.val(text);
            } else if (!$el.val()) {
                $el.val(gettext("Show All Dates"));
            }
        });

        // Date selector
        var $dateSelector = $("#filter_date_selector");
        if ($dateSelector.length && $dateSelector.data("init")) {
            $('#filter_date_selector').daterangepicker(
                {
                    locale: {
                        format: 'YYYY-MM-DD',
                    },
                    singleDatePicker: true,
                }
            );
        }

        // Date ranges (used in accounting)
        $('.date-range').each(function() {
            $(this).datepicker({
                changeMonth: true,
                changeYear: true,
                dateFormat: 'yy-mm-dd',
            });
        });

        // Optional date ranges, optional month+year (used in accounting)
        $(".report-filter-optional").each(function() {
            $(this).koApplyBindings({
                showFilterName: ko.observable($(this).data("showFilterName")),
            });
        });

        // Selects
        $('.report-filter-single-option').each(function() {
            select2Filter.initSingle(this);
        });
        $('.report-filter-single-option-paginated').each(function() {
            select2Filter.initSinglePaginated(this);
        });
        $('.report-filter-multi-option').each(function() {
            select2Filter.initMulti(this);
        });

        // Submission type (Raw Forms, Errors, & Duplicates)
        $('.report-filter-button-group').each(function() {
            buttonGroup.link(this);
        });

        // Tags (to filter by CC version in global device logs soft asserts report)
        $('.report-filter-tags').each(function() {
            $(this).select2({tags: $(this).data("tags"), allowClear: true});
        });

        // Initialize any help bubbles
        $('.hq-help-template').each(function () {
            hqMain.transformHelpTemplate($(this), true);
        });

        $(".report-filter-message-type-configuration").each(function (i, el) {
            var $el = $(el),
                data = $el.data();
            var model = scheduleInstanceFilter.model(data.initialValue, data.conditionalAlertChoices);
            $el.koApplyBindings(model);

            $('[name=rule_id]').each(function(i, el) {
                $(el).select2({
                    allowClear: true,
                    placeholder: gettext("All"),
                });
            });
        });
        $(".report-filter-phone-number").each(function (i, el) {
            var $el = $(el),
                data = $el.data();
            var model = phoneNumberFilter.model(data.initialValue, data.groups);
            $el.koApplyBindings(model);
        });
        $('[name=selected_group]').each(function(i, el) {
            $(el).select2({
                allowClear: true,
                placeholder: gettext("Select a group"),
            });
        });
        $('.report-filter-location-async').each(function(i, el) {
            var $el = $(el), data = $el.data();
            var model = locationDrilldown.locationSelectViewModel({
                "hierarchy": data.hierarchy,
                "show_location_filter": data.makeOptional && !data.locId ? "n" : "y",
                "loc_url": data.locationUrl,
                "auto_drill": data.autoDrill,
                "max_drilldown_length": data.maxDrilldownLength,
            });
            $el.koApplyBindings(model);
            model.load(data.locs, data.locId);
        });
        $('.report-filter-drilldown-options').each(function (i, el) {
            var $el = $(el), data = $el.data();
            if ($el.parents('.report-filter-form-drilldown').length > 0) return;
            if (data.isEmpty) return;
            var model = drilldownOptions.drilldownOptionFilterControl({
                drilldown_map: data.drilldownMap,
                controls: data.controls,
                selected: data.selected,
                notifications: data.notifications,
            });
            $el.find('#' + data.cssId).koApplyBindings(model);
            model.init();
        });
        $('.report-filter-form-drilldown').each(function (i, el) {
            // This is copied from drilldown-options above because the order matters
            // http://manage.dimagi.com/default.asp?231773
            var $el = $(el), data = $el.data();
            if (!data.isEmpty) {
                var model = drilldownOptions.drilldownOptionFilterControl({
                    drilldown_map: data.drilldownMap,
                    controls: data.controls,
                    selected: data.selected,
                    notifications: data.notifications,
                });
                $el.find('#' + data.cssId).koApplyBindings(model);
                model.init();
            }

            if (data.unknownAvailable || data.displayAppType) {
                advancedFormsOptions.advancedFormsOptions(
                    $el.find('#' + data.cssId + '-advanced-options'),
                    {
                        show: data.showAdvanced,
                        is_unknown_shown: data.unknown.show,
                        selected_unknown_form: data.unknown.selected,
                        all_unknown_forms: data.unknown.options,
                        caption_text: data.unknown.defaultText,
                        css_id: data.cssId,
                        css_class: data.cssClass,
                    }
                );
            }
        });
        $('.report-filter-logtag').each(function(i, el) {
            var $el = $(el), data = $el.data();
            if (data.defaultOn) {
                $el.attr("name", "");
                $el.change(function () {
                    $el.attr("name", "logtag");
                });
            }
            if (data.errorOnly) {
                $("#device-log-errors-only-checkbox").change(function () {
                    var multiSelect = $el;
                    if ($el.prop('checked')) {
                        $el.attr("name", "errors_only");
                        multiSelect.attr("name", "");
                        multiSelect.addClass("hide");
                    } else {
                        $el.attr("name", "");
                        if (!data.defaultOn) {
                            multiSelect.attr("name", "logtag");
                        }
                        multiSelect.removeClass("hide");
                    }
                });
            }
        });

        $('.report-filter-dynamic-choice-list').each(function (i, el) {
            var $el = $(el), data = $el.data();
            var initialValues = _.map(data.initialValues, function(value) {
                    return choiceListUtils.formatValueForSelect2(value);
                }),
                // TODO: Ideally the separator would be defined in one place. Right now it is
                //       also defined corehq.apps.userreports.reports.filters.CHOICE_DELIMITER
                separator = "\u001F";

            $el.select2({
                minimumInputLength: 0,
                multiple: true,
                separator: separator,
                allowClear: true,
                // allowClear only respected if there is a non empty placeholder
                placeholder: " ",
                ajax: {
                    url: data.ajaxFilterUrl,
                    dataType: 'json',
                    quietMillis: 250,
                    data: choiceListUtils.getApiQueryParams,
                    results: choiceListUtils.formatPageForSelect2,
                    cache: true,
                },
            });
            $el.select2('data', initialValues);
        });
    };

    return {
        init: init,
    };
});
<commit_msg>use actual props<commit_after>hqDefine("reports/js/filters/main", [
    'jquery',
    'knockout',
    'hqwebapp/js/main',
    'reports/js/standard_hq_report',
    'reports/js/filters/select2s',
    'reports/js/filters/phone_number',
    'reports/js/filters/button_group',
    'reports/js/filters/schedule_instance',
    'locations/js/location_drilldown',
    'reports/js/report_filter/advanced_forms_options',
    'reports/js/report_filter/drilldown_options',
    'reports/js/choice_list_utils',
    'select2-3.5.2-legacy/select2',
], function(
    $,
    ko,
    hqMain,
    standardHQReportModule,
    select2Filter,
    phoneNumberFilter,
    buttonGroup,
    scheduleInstanceFilter,
    locationDrilldown,
    advancedFormsOptions,
    drilldownOptions,
    choiceListUtils
) {
    var init = function() {
        // Datespans
        $('.report-filter-datespan').each(function() {
            var $filterRange = $(this);
            if ($filterRange.data("init")) {
                var separator = $filterRange.data('separator');
                var reportLabels = $filterRange.data('reportLabels');
                var standardHQReport = standardHQReportModule.getStandardHQReport();

                $filterRange.createDateRangePicker(
                    reportLabels, separator,
                    $filterRange.data('startDate'),
                    $filterRange.data('endDate')
                );
                $filterRange.on('change apply', function() {
                    var dates = $(this).val().split(separator);
                    $(standardHQReport.filterAccordion).trigger('hqreport.filter.datespan.startdate', dates[0]);
                    $('#report_filter_datespan_startdate').val(dates[0]);
                    $(standardHQReport.filterAccordion).trigger('hqreport.filter.datespan.enddate', dates[1]);
                    $('#report_filter_datespan_enddate').val(dates[1]);
                });
            }
        });

        // other Datespans *groan* TODO unify on this one
        $('.report-filter-datespan-filter').each(function(i, el) {
            var $el = $(el), data = $el.data();
            var $filterStart = $("#" + data.cssId + "-start");
            var $filterEnd = $("#" + data.cssId + "-end");

            $el.createBootstrap3DefaultDateRangePicker();
            $el.on('apply change', function () {
                var separator = $().getDateRangeSeparator();
                var dates = $el.val().split(separator);
                $filterStart.val(dates[0]);
                $filterEnd.val(dates[1]);
            });

            if (!$el.val() && $filterStart.val() && $filterEnd.val()) {
                var text = $filterStart.val() + $().getDateRangeSeparator() + $filterEnd.val();
                $el.val(text);
            } else if (!$el.val()) {
                $el.val(gettext("Show All Dates"));
            }
        });

        // Date selector
        var $dateSelector = $("#filter_date_selector");
        if ($dateSelector.length && $dateSelector.data("init")) {
            $('#filter_date_selector').daterangepicker(
                {
                    locale: {
                        format: 'YYYY-MM-DD',
                    },
                    singleDatePicker: true,
                }
            );
        }

        // Date ranges (used in accounting)
        $('.date-range').each(function() {
            $(this).datepicker({
                changeMonth: true,
                changeYear: true,
                dateFormat: 'yy-mm-dd',
            });
        });

        // Optional date ranges, optional month+year (used in accounting)
        $(".report-filter-optional").each(function() {
            $(this).koApplyBindings({
                showFilterName: ko.observable($(this).data("showFilterName")),
            });
        });

        // Selects
        $('.report-filter-single-option').each(function() {
            select2Filter.initSingle(this);
        });
        $('.report-filter-single-option-paginated').each(function() {
            select2Filter.initSinglePaginated(this);
        });
        $('.report-filter-multi-option').each(function() {
            select2Filter.initMulti(this);
        });

        // Submission type (Raw Forms, Errors, & Duplicates)
        $('.report-filter-button-group').each(function() {
            buttonGroup.link(this);
        });

        // Tags (to filter by CC version in global device logs soft asserts report)
        $('.report-filter-tags').each(function() {
            $(this).select2({tags: $(this).data("tags"), allowClear: true});
        });

        // Initialize any help bubbles
        $('.hq-help-template').each(function () {
            hqMain.transformHelpTemplate($(this), true);
        });

        $(".report-filter-message-type-configuration").each(function (i, el) {
            var $el = $(el),
                data = $el.data();
            var model = scheduleInstanceFilter.model(data.initialValue, data.conditionalAlertChoices);
            $el.koApplyBindings(model);

            $('[name=rule_id]').each(function(i, el) {
                $(el).select2({
                    allowClear: true,
                    placeholder: gettext("All"),
                });
            });
        });
        $(".report-filter-phone-number").each(function (i, el) {
            var $el = $(el),
                data = $el.data();
            var model = phoneNumberFilter.model(data.initialValue, data.groups);
            $el.koApplyBindings(model);
        });
        $('[name=selected_group]').each(function(i, el) {
            $(el).select2({
                allowClear: true,
                placeholder: gettext("Select a group"),
            });
        });
        $('.report-filter-location-async').each(function(i, el) {
            var $el = $(el), data = $el.data();
            var model = locationDrilldown.locationSelectViewModel({
                "hierarchy": data.hierarchy,
                "show_location_filter": data.makeOptional && !data.locId ? "n" : "y",
                "loc_url": data.locationUrl,
                "auto_drill": data.autoDrill,
                "max_drilldown_length": data.maxDrilldownLength,
            });
            $el.koApplyBindings(model);
            model.load(data.locs, data.locId);
        });
        $('.report-filter-drilldown-options').each(function (i, el) {
            var $el = $(el), data = $el.data();
            if ($el.parents('.report-filter-form-drilldown').length > 0) return;
            if (data.isEmpty) return;
            var model = drilldownOptions.drilldownOptionFilterControl({
                drilldown_map: data.drilldownMap,
                controls: data.controls,
                selected: data.selected,
                notifications: data.notifications,
            });
            $el.find('#' + data.cssId).koApplyBindings(model);
            model.init();
        });
        $('.report-filter-form-drilldown').each(function (i, el) {
            // This is copied from drilldown-options above because the order matters
            // http://manage.dimagi.com/default.asp?231773
            var $el = $(el), data = $el.data();
            if (!data.isEmpty) {
                var model = drilldownOptions.drilldownOptionFilterControl({
                    drilldown_map: data.drilldownMap,
                    controls: data.controls,
                    selected: data.selected,
                    notifications: data.notifications,
                });
                $el.find('#' + data.cssId).koApplyBindings(model);
                model.init();
            }

            if (data.unknownAvailable || data.displayAppType) {
                advancedFormsOptions.advancedFormsOptions(
                    $el.find('#' + data.cssId + '-advanced-options'),
                    {
                        show: data.showAdvanced,
                        is_unknown_shown: data.isUnknownShown,
                        selected_unknown_form: data.selectedUnknownForm,
                        all_unknown_forms: data.allUnknownForms,
                        caption_text: data.captionText,
                        css_id: data.cssId,
                        css_class: data.cssClass,
                    }
                );
            }
        });
        $('.report-filter-logtag').each(function(i, el) {
            var $el = $(el), data = $el.data();
            if (data.defaultOn) {
                $el.attr("name", "");
                $el.change(function () {
                    $el.attr("name", "logtag");
                });
            }
            if (data.errorOnly) {
                $("#device-log-errors-only-checkbox").change(function () {
                    var multiSelect = $el;
                    if ($el.prop('checked')) {
                        $el.attr("name", "errors_only");
                        multiSelect.attr("name", "");
                        multiSelect.addClass("hide");
                    } else {
                        $el.attr("name", "");
                        if (!data.defaultOn) {
                            multiSelect.attr("name", "logtag");
                        }
                        multiSelect.removeClass("hide");
                    }
                });
            }
        });

        $('.report-filter-dynamic-choice-list').each(function (i, el) {
            var $el = $(el), data = $el.data();
            var initialValues = _.map(data.initialValues, function(value) {
                    return choiceListUtils.formatValueForSelect2(value);
                }),
                // TODO: Ideally the separator would be defined in one place. Right now it is
                //       also defined corehq.apps.userreports.reports.filters.CHOICE_DELIMITER
                separator = "\u001F";

            $el.select2({
                minimumInputLength: 0,
                multiple: true,
                separator: separator,
                allowClear: true,
                // allowClear only respected if there is a non empty placeholder
                placeholder: " ",
                ajax: {
                    url: data.ajaxFilterUrl,
                    dataType: 'json',
                    quietMillis: 250,
                    data: choiceListUtils.getApiQueryParams,
                    results: choiceListUtils.formatPageForSelect2,
                    cache: true,
                },
            });
            $el.select2('data', initialValues);
        });
    };

    return {
        init: init,
    };
});
<|endoftext|>


<commit_before>(function (factory) {
	if (typeof define === 'function' && define.amd) {
		// AMD. Register as an anonymous module.
		define(['jquery'], factory);
	} else {
		// Browser globals
		factory(window.jQuery);
	}
}(function ($) {
	'use strict';

	// Utilities

	function toNumber (num) {
		return isFinite(num = parseFloat(num)) ? num : 0;
	}

	function throttle (fn, context) {
		var timer = false;
		return function () {
			var c = context || this;
			var a = arguments;
			if (false === timer) {
				timer = window.requestAnimationFrame(function () {
					fn.apply(c, a);
					timer = false;
				});
			}
		};
	}

	// Cling

	var positionMultiplier = {
		left: 0,
		top: 0,
		right: 1,
		bottom: 1,
		center: 0.5
	};

	var positionRegExp = /(left|right|center|\d+%)\s*(top|bottom|center|\d+%)/;

	function normalizePosition (position) {
		var normalized = [0.5, 0.5];

		position = positionRegExp.exec(position);

		for (var i = 1; i <= 2; i++) {
			if (!position[i]) {
				continue;
			} else if (positionMultiplier.hasOwnProperty(position[i])) {
				normalized[i - 1] = positionMultiplier[position[i]];
			} else if ('%' == position[i].substr(-1, 1)) {
				normalized[i - 1] = toNumber(position[i]);
			}
		}

		return normalized;
	}

	var offsetRegExp = /(-?\d+)\s*(-?\d+)?/;

	function normalizeOffset (offset) {
		var normalized = [0, 0];

		offset = offsetRegExp.exec(offset);

		for (var i = 1; i <= 2; i++) {
			if (offset[i]) {
				normalized[i - 1] = toNumber(offset[i]);
			}
		}

		return normalized;
	}

	function getScrollParents (element) {
		return element.parents().filter(function () {
			var parent = $(this);
			if (1 == element.nodeType && /auto|scroll/.test(parent.css('overflow') + parent.css('overflow-x') + parent.css('overflow-y'))) {
				return true;
			}
		});
	}

	function Cling (element, target, options) {
		if (!(this instanceof Cling)) {
			return new Cling(element, target, options);
		}

		// Throttle update method
		this.update = throttle(this.update, this);

		// Setup options
		this.e = element;
		this.t = target;
		this.options(options);

		// Add position styles if not already `absolute` or `fixed`
		if (!/absolute|fixed/.test(element.css('position'))) {
			element.css({
				position: 'absolute',
				left: 0,
				top: 0
			});
		}

		// Listen to scroll or resize on window
		$(window).on('scroll resize touchmove', this.update);

		// Get any scrollable parents, and listen for it scrolling
		var scrollParents = this.s = getScrollParents(target);
		if (scrollParents.length) {
			scrollParents.on('scroll', this.update);
		}

		// Store instance
		element.data('cling', this);

		this.update();
	}

	Cling.prototype = {
		defaults: {
			from: '',
			to: '',
			offset: ''
		},

		options: function (options) {
			options = $.extend(this.defaults, options);
			options.from = normalizePosition(options.from);
			options.to = normalizePosition(options.to);
			options.offset = normalizeOffset(options.offset);
			this.o = options;
		},

		update: function () {
			var element = this.e;
			var target = this.t;
			var options = this.o;

			var elementBounding = element.get(0).getBoundingClientRect();
			var targetBounding = target.get(0).getBoundingClientRect();

			// Dat Math
			// 1. The target point
			// 2. Subtract the element point
			// 3. Add the difference between the target and element's current position
			// 4. Add our offset
			// 5. Add the current left/top style -- prevents layout thrashing
			var left = (target.outerWidth() * options.from[0]) -
				(element.outerWidth() * options.to[0]) +
				(targetBounding.left - elementBounding.left) +
				options.offset[0] +
				toNumber(element.css('left'));

			var top = (target.outerHeight() * options.from[1]) -
				(element.outerHeight() * options.to[1]) +
				(targetBounding.top - elementBounding.top) +
				options.offset[1] +
				toNumber(element.css('top'));

			// Set the newly calculated positions
			element.css({
				left: Math.round(left),
				top: Math.round(top)
			});
		},

		destroy: function () {
			var element = this.e;
			var scrollParents = this.s;

			// Remove any applied styles
			element.css({
				position: '',
				left: '',
				top: ''
			});

			// Remove events
			$(window).off('scroll resize touchmove', this.update);

			if (scrollParents.length) {
				scrollParents.off('scroll', this.update);
			}

			// Remove stored instance
			element.removeData('cling');
		}
	};

	// jQuery Plugin
	$.fn.extend({
		cling: function (target, options) {
			target = $(target);

			return this.each(function () {
				var element = $(this);
				var instance = element.data('cling');

				if (instance instanceof Cling) {
					if ($.isFunction(instance[target])) {
						instance[target](options);
					}
				} else {
					Cling(element, target, options);
				}
			});
		}
	});

	return Cling;
}));
<commit_msg>Making jshint happy.<commit_after>(function (factory) {
	if (typeof define === 'function' && define.amd) {
		// AMD. Register as an anonymous module.
		define(['jquery'], factory);
	} else {
		// Browser globals
		factory(window.jQuery);
	}
}(function ($) {
	'use strict';

	// Utilities

	function toNumber (num) {
		return isFinite(num = parseFloat(num)) ? num : 0;
	}

	function throttle (fn, context) {
		var timer = false;
		return function () {
			var c = context || this;
			var a = arguments;
			if (false === timer) {
				timer = window.requestAnimationFrame(function () {
					fn.apply(c, a);
					timer = false;
				});
			}
		};
	}

	// Cling

	var positionMultiplier = {
		left: 0,
		top: 0,
		right: 1,
		bottom: 1,
		center: 0.5
	};

	var positionRegExp = /(left|right|center|\d+%)\s*(top|bottom|center|\d+%)/;

	function normalizePosition (position) {
		var normalized = [0.5, 0.5];

		position = positionRegExp.exec(position);

		for (var i = 1; i <= 2; i++) {
			if (!position[i]) {
				continue;
			} else if (positionMultiplier.hasOwnProperty(position[i])) {
				normalized[i - 1] = positionMultiplier[position[i]];
			} else if ('%' == position[i].substr(-1, 1)) {
				normalized[i - 1] = toNumber(position[i]);
			}
		}

		return normalized;
	}

	var offsetRegExp = /(-?\d+)\s*(-?\d+)?/;

	function normalizeOffset (offset) {
		var normalized = [0, 0];

		offset = offsetRegExp.exec(offset);

		for (var i = 1; i <= 2; i++) {
			if (offset[i]) {
				normalized[i - 1] = toNumber(offset[i]);
			}
		}

		return normalized;
	}

	function getScrollParents (element) {
		return element.parents().filter(function () {
			var parent = $(this);
			if (1 == element.nodeType && /auto|scroll/.test(parent.css('overflow') + parent.css('overflow-x') + parent.css('overflow-y'))) {
				return true;
			}
		});
	}

	function Cling (element, target, options) {
		// Throttle update method
		this.update = throttle(this.update, this);

		// Setup options
		this.e = element;
		this.t = target;
		this.options(options);

		// Add position styles if not already `absolute` or `fixed`
		if (!/absolute|fixed/.test(element.css('position'))) {
			element.css({
				position: 'absolute',
				left: 0,
				top: 0
			});
		}

		// Listen to scroll or resize on window
		$(window).on('scroll resize touchmove', this.update);

		// Get any scrollable parents, and listen for it scrolling
		var scrollParents = this.s = getScrollParents(target);
		if (scrollParents.length) {
			scrollParents.on('scroll', this.update);
		}

		// Store instance
		element.data('cling', this);

		this.update();
	}

	Cling.prototype = {
		defaults: {
			from: '',
			to: '',
			offset: ''
		},

		options: function (options) {
			options = $.extend(this.defaults, options);
			options.from = normalizePosition(options.from);
			options.to = normalizePosition(options.to);
			options.offset = normalizeOffset(options.offset);
			this.o = options;
		},

		update: function () {
			var element = this.e;
			var target = this.t;
			var options = this.o;

			var elementBounding = element.get(0).getBoundingClientRect();
			var targetBounding = target.get(0).getBoundingClientRect();

			// Dat Math
			// 1. The target point
			// 2. Subtract the element point
			// 3. Add the difference between the target and element's current position
			// 4. Add our offset
			// 5. Add the current left/top style -- prevents layout thrashing
			var left = (target.outerWidth() * options.from[0]) -
				(element.outerWidth() * options.to[0]) +
				(targetBounding.left - elementBounding.left) +
				options.offset[0] +
				toNumber(element.css('left'));

			var top = (target.outerHeight() * options.from[1]) -
				(element.outerHeight() * options.to[1]) +
				(targetBounding.top - elementBounding.top) +
				options.offset[1] +
				toNumber(element.css('top'));

			// Set the newly calculated positions
			element.css({
				left: Math.round(left),
				top: Math.round(top)
			});
		},

		destroy: function () {
			var element = this.e;
			var scrollParents = this.s;

			// Remove any applied styles
			element.css({
				position: '',
				left: '',
				top: ''
			});

			// Remove events
			$(window).off('scroll resize touchmove', this.update);

			if (scrollParents.length) {
				scrollParents.off('scroll', this.update);
			}

			// Remove stored instance
			element.removeData('cling');
		}
	};

	// jQuery Plugin
	$.fn.extend({
		cling: function (target, options) {
			target = $(target);

			return this.each(function () {
				var element = $(this);
				var instance = element.data('cling');

				if (instance instanceof Cling) {
					if ($.isFunction(instance[target])) {
						instance[target](options);
					}
				} else {
					new Cling(element, target, options);
				}
			});
		}
	});

	return Cling;
}));
<|endoftext|>


<commit_before>/*jshint multistr:true, curly: false */
/*global jQuery:false, define: false */
/**
 * jRange - Awesome range control
 *
 * Written by
 * ----------
 * Nitin Hayaran (nitinhayaran@gmail.com)
 *
 * Licensed under the MIT (MIT-LICENSE.txt).
 *
 * @author Nitin Hayaran
 * @version 0.1-RELEASE
 *
 * Dependencies
 * ------------
 * jQuery (http://jquery.com)
 *
 **/
;
(function($, window, document, undefined) {
	'use strict';

	var jRange = function() {
		return this.init.apply(this, arguments);
	};
	jRange.prototype = {
		defaults: {
			onstatechange: function() {},
			isRange: false,
			showLabels: true,
			showScale: true,
			step: 1,
			format: '%s',
			theme: 'theme-green',
			width: 300,
			disable: false,
			snap: false
		},
		template: '
\

\

\

123456
\

456789
\

\

\

\

',
		init: function(node, options) {
			this.options       = $.extend({}, this.defaults, options);
			this.inputNode     = $(node);
			this.options.value = this.inputNode.val() || (this.options.isRange ? this.options.from + ',' + this.options.from : this.options.from);
			this.domNode       = $(this.template);
			this.domNode.addClass(this.options.theme);
			this.inputNode.after(this.domNode);
			this.domNode.on('change', this.onChange);
			this.pointers      = $('.pointer', this.domNode);
			this.lowPointer    = this.pointers.first();
			this.highPointer   = this.pointers.last();
			this.labels        = $('.pointer-label', this.domNode);
			this.lowLabel      = this.labels.first();
			this.highLabel     = this.labels.last();
			this.scale         = $('.scale', this.domNode);
			this.bar           = $('.selected-bar', this.domNode);
			this.clickableBar  = this.domNode.find('.clickable-dummy');
			this.interval      = this.options.to - this.options.from;
			this.render();
		},
		render: function() {
			// Check if inputNode is visible, and have some width, so that we can set slider width accordingly.
			if (this.inputNode.width() === 0 && !this.options.width) {
				console.log('jRange : no width found, returning');
				return;
			} else {
				this.options.width = this.options.width || this.inputNode.width();
				this.domNode.width(this.options.width);
				this.inputNode.hide();
			}

			if (this.isSingle()) {
				this.lowPointer.hide();
				this.lowLabel.hide();
			}
			if (!this.options.showLabels) {
				this.labels.hide();
			}
			this.attachEvents();
			if (this.options.showScale) {
				this.renderScale();
			}
			this.setValue(this.options.value);
		},
		isSingle: function() {
			if (typeof(this.options.value) === 'number') {
				return true;
			}
			return (this.options.value.indexOf(',') !== -1 || this.options.isRange) ?
				false : true;
		},
		attachEvents: function() {
			this.clickableBar.click($.proxy(this.barClicked, this));
			this.pointers.on('mousedown touchstart', $.proxy(this.onDragStart, this));
			this.pointers.bind('dragstart', function(event) {
				event.preventDefault();
			});
		},
		onDragStart: function(e) {
			if ( this.options.disable || (e.type === 'mousedown' && e.which !== 1)) {
				return;
			}
			e.stopPropagation();
			e.preventDefault();
			var pointer = $(e.target);
			this.pointers.removeClass('last-active');
			pointer.addClass('focused last-active');
			this[(pointer.hasClass('low') ? 'low' : 'high') + 'Label'].addClass('focused');
			$(document).on('mousemove.slider touchmove.slider', $.proxy(this.onDrag, this, pointer));
			$(document).on('mouseup.slider touchend.slider touchcancel.slider', $.proxy(this.onDragEnd, this));
		},
		onDrag: function(pointer, e) {
			e.stopPropagation();
			e.preventDefault();

			if (e.originalEvent.touches && e.originalEvent.touches.length) {
				e = e.originalEvent.touches[0];
			} else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
				e = e.originalEvent.changedTouches[0];
			}

			var position = e.clientX - this.domNode.offset().left;
			this.domNode.trigger('change', [this, pointer, position]);
		},
		onDragEnd: function(e) {
			this.pointers.removeClass('focused');
			this.labels.removeClass('focused');
			$(document).off('.slider');
		},
		barClicked: function(e) {
			if(this.options.disable) return;
			var x = e.pageX - this.clickableBar.offset().left;
			if (this.isSingle())
				this.setPosition(this.pointers.last(), x, true, true);
			else {
				var leftPos = parseInt(this.pointers.first().css('left'), 10),
					rightPos =  parseInt(this.pointers.last().css('left'), 10),
					width = this.pointers.first().width() / 2,
					pointer = this.pointers.first();
				if ( x > rightPos || Math.abs(leftPos - x + width) > Math.abs(rightPos - x + width) ){
					pointer = this.pointers.last();
				}
				this.setPosition(pointer, x, true, true);
			}
		},
		onChange: function(e, self, pointer, position) {
			var min, max;
			if (self.isSingle()) {
				min = 0;
				max = self.domNode.width();
			} else {
				min = pointer.hasClass('high') ? self.lowPointer.position().left + self.lowPointer.width() / 2 : 0;
				max = pointer.hasClass('low') ? self.highPointer.position().left + self.highPointer.width() / 2 : self.domNode.width();
			}
			var value = Math.min(Math.max(position, min), max);
			self.setPosition(pointer, value, true);
		},
		setPosition: function(pointer, position, isPx, animate) {
			var leftPos,
				lowPos = this.lowPointer.position().left,
				highPos = this.highPointer.position().left,
				circleWidth = this.highPointer.width() / 2;
			if (!isPx) {
				position = this.prcToPx(position);
			}
			if(this.options.snap){
				var expPos = this.correctPositionForSnap(position);
				if(expPos === -1){
					return;
				}else{
					position = expPos;
				}
			}
			if (pointer[0] === this.highPointer[0]) {
				highPos = Math.round(position - circleWidth);
			} else {
				lowPos = Math.round(position - circleWidth);
			}
			pointer[animate ? 'animate' : 'css']({
				'left': Math.round(position - circleWidth)
			});
			if (this.isSingle()) {
				leftPos = 0;
			} else {
				leftPos = lowPos + circleWidth;
			}
			this.bar[animate ? 'animate' : 'css']({
				'width': Math.round(highPos + circleWidth - leftPos),
				'left': leftPos
			});
			this.showPointerValue(pointer, position, animate);
			this.isReadonly();
		},
		correctPositionForSnap: function(position){
			var currentValue = this.positionToValue(position) - this.options.from;
			var diff = this.options.width / (this.interval / this.options.step),
				expectedPosition = currentValue * diff;
			if( position <= expectedPosition + diff / 2 && position >= expectedPosition - diff / 2){
				return expectedPosition;
			}else{
				return -1;
			}
		},
		// will be called from outside
		setValue: function(value) {
			var values = value.toString().split(',');
			values[0] = Math.max(Math.min(values[0], this.options.from), this.options.to) + '';
			if (values.length > 1){
				values[1] = Math.max(Math.min(values[1], this.options.from), this.options.to) + '';
			}
			this.options.value = value;
			var prc = this.valuesToPrc(values.length === 2 ? values : [0, values[0]]);
			if (this.isSingle()) {
				this.setPosition(this.highPointer, prc[1]);
			} else {
				this.setPosition(this.lowPointer, prc[0]);
				this.setPosition(this.highPointer, prc[1]);
			}
		},
		renderScale: function() {
			var s = this.options.scale || [this.options.from, this.options.to];
			var prc = Math.round((100 / (s.length - 1)) * 10) / 10;
			var str = '';
			for (var i = 0; i < s.length; i++) {
				str += '' + (s[i] != '|' ? '' + s[i] + '' : '') + '';
			}
			this.scale.html(str);

			$('ins', this.scale).each(function() {
				$(this).css({
					marginLeft: -$(this).outerWidth() / 2
				});
			});
		},
		getBarWidth: function() {
			var values = this.options.value.split(',');
			if (values.length > 1) {
				return parseInt(values[1], 10) - parseInt(values[0], 10);
			} else {
				return parseInt(values[0], 10);
			}
		},
		showPointerValue: function(pointer, position, animate) {
			var label = $('.pointer-label', this.domNode)[pointer.hasClass('low') ? 'first' : 'last']();
			var text;
			var value = this.positionToValue(position);
			if ($.isFunction(this.options.format)) {
				var type = this.isSingle() ? undefined : (pointer.hasClass('low') ? 'low' : 'high');
				text = this.options.format(value, type);
			} else {
				text = this.options.format.replace('%s', value);
			}

			var width = label.html(text).width(),
				left = position - width / 2;
			left = Math.min(Math.max(left, 0), this.options.width - width);
			label[animate ? 'animate' : 'css']({
				left: left
			});
			this.setInputValue(pointer, value);
		},
		valuesToPrc: function(values) {
			var lowPrc = ((values[0] - this.options.from) * 100 / this.interval),
				highPrc = ((values[1] - this.options.from) * 100 / this.interval);
			return [lowPrc, highPrc];
		},
		prcToPx: function(prc) {
			return (this.domNode.width() * prc) / 100;
		},
		positionToValue: function(pos) {
			var value = (pos / this.domNode.width()) * this.interval;
			value = value + this.options.from;
			return Math.round(value / this.options.step) * this.options.step;
		},
		setInputValue: function(pointer, v) {
			// if(!isChanged) return;
			if (this.isSingle()) {
				this.options.value = v.toString();
			} else {
				var values = this.options.value.split(',');
				if (pointer.hasClass('low')) {
					this.options.value = v + ',' + values[1];
				} else {
					this.options.value = values[0] + ',' + v;
				}
			}
			if (this.inputNode.val() !== this.options.value) {
				this.inputNode.val(this.options.value);
				this.options.onstatechange.call(this, this.options.value);
			}
		},
		getValue: function() {
			return this.options.value;
		},
		isReadonly: function(){
			this.domNode.toggleClass('slider-readonly', this.options.disable);
		},
		disable: function(){
			this.options.disable = true;
			this.isReadonly();
		},
		enable: function(){
			this.options.disable = false;
			this.isReadonly();
		},
		toggleDisable: function(){
			this.options.disable = !this.options.disable;
			this.isReadonly();
		}
	};

	/*$.jRange = function (node, options) {
		var jNode = $(node);
		if(!jNode.data('jrange')){
			jNode.data('jrange', new jRange(node, options));
		}
		return jNode.data('jrange');
	};

	$.fn.jRange = function (options) {
		return this.each(function(){
			$.jRange(this, options);
		});
	};*/

	var pluginName = 'jRange';
	// A really lightweight plugin wrapper around the constructor,
	// preventing against multiple instantiations
	$.fn[pluginName] = function(option) {
		var args = arguments,
			result;

		this.each(function() {
			var $this = $(this),
				data = $.data(this, 'plugin_' + pluginName),
				options = typeof option === 'object' && option;
			if (!data) {
				$this.data('plugin_' + pluginName, (data = new jRange(this, options)));
				$(window).resize(function() {
					data.setValue(data.getValue());
				}); // Update slider position when window is resized to keep it in sync with scale
			}
			// if first argument is a string, call silimarly named function
			// this gives flexibility to call functions of the plugin e.g.
			//   - $('.dial').plugin('destroy');
			//   - $('.dial').plugin('render', $('.new-child'));
			if (typeof option === 'string') {
				result = data[option].apply(data, Array.prototype.slice.call(args, 1));
			}
		});

		// To enable plugin returns values
		return result || this;
	};

})(jQuery, window, document);<commit_msg>fix(#18) snap-to-interval, needed a pctToPx conversion<commit_after>/*jshint multistr:true, curly: false */
/*global jQuery:false, define: false */
/**
 * jRange - Awesome range control
 *
 * Written by
 * ----------
 * Nitin Hayaran (nitinhayaran@gmail.com)
 *
 * Licensed under the MIT (MIT-LICENSE.txt).
 *
 * @author Nitin Hayaran
 * @version 0.1-RELEASE
 *
 * Dependencies
 * ------------
 * jQuery (http://jquery.com)
 *
 **/
;
(function($, window, document, undefined) {
	'use strict';

	var jRange = function() {
		return this.init.apply(this, arguments);
	};
	jRange.prototype = {
		defaults: {
			onstatechange: function() {},
			isRange: false,
			showLabels: true,
			showScale: true,
			step: 1,
			format: '%s',
			theme: 'theme-green',
			width: 300,
			disable: false,
			snap: false
		},
		template: '
\

\

\

123456
\

456789
\

\

\

\

',
		init: function(node, options) {
			this.options       = $.extend({}, this.defaults, options);
			this.inputNode     = $(node);
			this.options.value = this.inputNode.val() || (this.options.isRange ? this.options.from + ',' + this.options.from : this.options.from);
			this.domNode       = $(this.template);
			this.domNode.addClass(this.options.theme);
			this.inputNode.after(this.domNode);
			this.domNode.on('change', this.onChange);
			this.pointers      = $('.pointer', this.domNode);
			this.lowPointer    = this.pointers.first();
			this.highPointer   = this.pointers.last();
			this.labels        = $('.pointer-label', this.domNode);
			this.lowLabel      = this.labels.first();
			this.highLabel     = this.labels.last();
			this.scale         = $('.scale', this.domNode);
			this.bar           = $('.selected-bar', this.domNode);
			this.clickableBar  = this.domNode.find('.clickable-dummy');
			this.interval      = this.options.to - this.options.from;
			this.render();
		},
		render: function() {
			// Check if inputNode is visible, and have some width, so that we can set slider width accordingly.
			if (this.inputNode.width() === 0 && !this.options.width) {
				console.log('jRange : no width found, returning');
				return;
			} else {
				this.options.width = this.options.width || this.inputNode.width();
				this.domNode.width(this.options.width);
				this.inputNode.hide();
			}

			if (this.isSingle()) {
				this.lowPointer.hide();
				this.lowLabel.hide();
			}
			if (!this.options.showLabels) {
				this.labels.hide();
			}
			this.attachEvents();
			if (this.options.showScale) {
				this.renderScale();
			}
			this.setValue(this.options.value);
		},
		isSingle: function() {
			if (typeof(this.options.value) === 'number') {
				return true;
			}
			return (this.options.value.indexOf(',') !== -1 || this.options.isRange) ?
				false : true;
		},
		attachEvents: function() {
			this.clickableBar.click($.proxy(this.barClicked, this));
			this.pointers.on('mousedown touchstart', $.proxy(this.onDragStart, this));
			this.pointers.bind('dragstart', function(event) {
				event.preventDefault();
			});
		},
		onDragStart: function(e) {
			if ( this.options.disable || (e.type === 'mousedown' && e.which !== 1)) {
				return;
			}
			e.stopPropagation();
			e.preventDefault();
			var pointer = $(e.target);
			this.pointers.removeClass('last-active');
			pointer.addClass('focused last-active');
			this[(pointer.hasClass('low') ? 'low' : 'high') + 'Label'].addClass('focused');
			$(document).on('mousemove.slider touchmove.slider', $.proxy(this.onDrag, this, pointer));
			$(document).on('mouseup.slider touchend.slider touchcancel.slider', $.proxy(this.onDragEnd, this));
		},
		onDrag: function(pointer, e) {
			e.stopPropagation();
			e.preventDefault();

			if (e.originalEvent.touches && e.originalEvent.touches.length) {
				e = e.originalEvent.touches[0];
			} else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
				e = e.originalEvent.changedTouches[0];
			}

			var position = e.clientX - this.domNode.offset().left;
			this.domNode.trigger('change', [this, pointer, position]);
		},
		onDragEnd: function(e) {
			this.pointers.removeClass('focused');
			this.labels.removeClass('focused');
			$(document).off('.slider');
		},
		barClicked: function(e) {
			if(this.options.disable) return;
			var x = e.pageX - this.clickableBar.offset().left;
			if (this.isSingle())
				this.setPosition(this.pointers.last(), x, true, true);
			else {
				var leftPos = parseInt(this.pointers.first().css('left'), 10),
					rightPos =  parseInt(this.pointers.last().css('left'), 10),
					width = this.pointers.first().width() / 2,
					pointer = this.pointers.first();
				if ( x > rightPos || Math.abs(leftPos - x + width) > Math.abs(rightPos - x + width) ){
					pointer = this.pointers.last();
				}
				this.setPosition(pointer, x, true, true);
			}
		},
		onChange: function(e, self, pointer, position) {
			var min, max;
			if (self.isSingle()) {
				min = 0;
				max = self.domNode.width();
			} else {
				min = pointer.hasClass('high') ? self.lowPointer.position().left + self.lowPointer.width() / 2 : 0;
				max = pointer.hasClass('low') ? self.highPointer.position().left + self.highPointer.width() / 2 : self.domNode.width();
			}
			var value = Math.min(Math.max(position, min), max);
			self.setPosition(pointer, value, true);
		},
		setPosition: function(pointer, position, isPx, animate) {
			var leftPos,
				lowPos = this.lowPointer.position().left,
				highPos = this.highPointer.position().left,
				circleWidth = this.highPointer.width() / 2;
			if (!isPx) {
				position = this.prcToPx(position);
			}
			if(this.options.snap){
				var expPos = this.correctPositionForSnap(position);
				if(expPos === -1){
					return;
				}else{
					position = expPos;
				}
			}
			if (pointer[0] === this.highPointer[0]) {
				highPos = Math.round(position - circleWidth);
			} else {
				lowPos = Math.round(position - circleWidth);
			}
			pointer[animate ? 'animate' : 'css']({
				'left': Math.round(position - circleWidth)
			});
			if (this.isSingle()) {
				leftPos = 0;
			} else {
				leftPos = lowPos + circleWidth;
			}
			this.bar[animate ? 'animate' : 'css']({
				'width': Math.round(highPos + circleWidth - leftPos),
				'left': leftPos
			});
			this.showPointerValue(pointer, position, animate);
			this.isReadonly();
		},
		correctPositionForSnap: function(position){
			var currentValue = this.positionToValue(position) - this.options.from;
			var diff = this.options.width / (this.interval / this.options.step),
				expectedPosition = this.prcToPx(currentValue * diff);
			if( position <= expectedPosition + diff / 2 && position >= expectedPosition - diff / 2){
				return expectedPosition;
			}else{
				return -1;
			}
		},
		// will be called from outside
		setValue: function(value) {
			var values = value.toString().split(',');
			values[0] = Math.max(Math.min(values[0], this.options.from), this.options.to) + '';
			if (values.length > 1){
				values[1] = Math.max(Math.min(values[1], this.options.from), this.options.to) + '';
			}
			this.options.value = value;
			var prc = this.valuesToPrc(values.length === 2 ? values : [0, values[0]]);
			if (this.isSingle()) {
				this.setPosition(this.highPointer, prc[1]);
			} else {
				this.setPosition(this.lowPointer, prc[0]);
				this.setPosition(this.highPointer, prc[1]);
			}
		},
		renderScale: function() {
			var s = this.options.scale || [this.options.from, this.options.to];
			var prc = Math.round((100 / (s.length - 1)) * 10) / 10;
			var str = '';
			for (var i = 0; i < s.length; i++) {
				str += '' + (s[i] != '|' ? '' + s[i] + '' : '') + '';
			}
			this.scale.html(str);

			$('ins', this.scale).each(function() {
				$(this).css({
					marginLeft: -$(this).outerWidth() / 2
				});
			});
		},
		getBarWidth: function() {
			var values = this.options.value.split(',');
			if (values.length > 1) {
				return parseInt(values[1], 10) - parseInt(values[0], 10);
			} else {
				return parseInt(values[0], 10);
			}
		},
		showPointerValue: function(pointer, position, animate) {
			var label = $('.pointer-label', this.domNode)[pointer.hasClass('low') ? 'first' : 'last']();
			var text;
			var value = this.positionToValue(position);
			if ($.isFunction(this.options.format)) {
				var type = this.isSingle() ? undefined : (pointer.hasClass('low') ? 'low' : 'high');
				text = this.options.format(value, type);
			} else {
				text = this.options.format.replace('%s', value);
			}

			var width = label.html(text).width(),
				left = position - width / 2;
			left = Math.min(Math.max(left, 0), this.options.width - width);
			label[animate ? 'animate' : 'css']({
				left: left
			});
			this.setInputValue(pointer, value);
		},
		valuesToPrc: function(values) {
			var lowPrc = ((values[0] - this.options.from) * 100 / this.interval),
				highPrc = ((values[1] - this.options.from) * 100 / this.interval);
			return [lowPrc, highPrc];
		},
		prcToPx: function(prc) {
			return (this.domNode.width() * prc) / 100;
		},
		positionToValue: function(pos) {
			var value = (pos / this.domNode.width()) * this.interval;
			value = value + this.options.from;
			return Math.round(value / this.options.step) * this.options.step;
		},
		setInputValue: function(pointer, v) {
			// if(!isChanged) return;
			if (this.isSingle()) {
				this.options.value = v.toString();
			} else {
				var values = this.options.value.split(',');
				if (pointer.hasClass('low')) {
					this.options.value = v + ',' + values[1];
				} else {
					this.options.value = values[0] + ',' + v;
				}
			}
			if (this.inputNode.val() !== this.options.value) {
				this.inputNode.val(this.options.value);
				this.options.onstatechange.call(this, this.options.value);
			}
		},
		getValue: function() {
			return this.options.value;
		},
		isReadonly: function(){
			this.domNode.toggleClass('slider-readonly', this.options.disable);
		},
		disable: function(){
			this.options.disable = true;
			this.isReadonly();
		},
		enable: function(){
			this.options.disable = false;
			this.isReadonly();
		},
		toggleDisable: function(){
			this.options.disable = !this.options.disable;
			this.isReadonly();
		}
	};

	/*$.jRange = function (node, options) {
		var jNode = $(node);
		if(!jNode.data('jrange')){
			jNode.data('jrange', new jRange(node, options));
		}
		return jNode.data('jrange');
	};

	$.fn.jRange = function (options) {
		return this.each(function(){
			$.jRange(this, options);
		});
	};*/

	var pluginName = 'jRange';
	// A really lightweight plugin wrapper around the constructor,
	// preventing against multiple instantiations
	$.fn[pluginName] = function(option) {
		var args = arguments,
			result;

		this.each(function() {
			var $this = $(this),
				data = $.data(this, 'plugin_' + pluginName),
				options = typeof option === 'object' && option;
			if (!data) {
				$this.data('plugin_' + pluginName, (data = new jRange(this, options)));
				$(window).resize(function() {
					data.setValue(data.getValue());
				}); // Update slider position when window is resized to keep it in sync with scale
			}
			// if first argument is a string, call silimarly named function
			// this gives flexibility to call functions of the plugin e.g.
			//   - $('.dial').plugin('destroy');
			//   - $('.dial').plugin('render', $('.new-child'));
			if (typeof option === 'string') {
				result = data[option].apply(data, Array.prototype.slice.call(args, 1));
			}
		});

		// To enable plugin returns values
		return result || this;
	};

})(jQuery, window, document);<|endoftext|>


<commit_before>// MODEL

/*
    class for operating on indexed representations of objects

    For example, if we have a lot of objects with similar attributes, e.g.:
        [
            {start: 1, end: 2, strand: -1},
            {start: 5, end: 6, strand: 1},
            ...
        ]
    we can represent them more compactly (e.g., in JSON) something like this:
        class = ["start", "end", "strand"]
        [
            [1, 2, -1],
            [5, 6, 1],
            ...
        ]

    If we want to represent a few different kinds of objects in our big list,
    we can have multiple "class" arrays, and tag each object to identify
    which "class" array describes it.

    For example, if we have a lot of instances of a few types of objects,
    like this:
        [
            {start: 1, end: 2, strand: 1, id: 1},
            {start: 5, end: 6, strand: 1, id: 2},
            ...
            {start: 10, end: 20, chunk: 1},
            {start: 30, end: 40, chunk: 2},
            ...
        ]
    We could use the first array position to indicate the "class" for the
    object, like this:
        classes = [["start", "end", "strand", "id"], ["start", "end", "chunk"]]
        [
            [0, 1, 2, 1, 1],
            [0, 5, 6, 1, 2],
            ...
            [1, 10, 20, 1],
            [1, 30, 40, 1]
        ]
    Also, if we occasionally want to add an ad-hoc attribute, we could just
    stick an optional dictionary onto the end:
        classes = [["start", "end", "strand", "id"], ["start", "end", "chunk"]]
        [
            [0, 1, 2, 1, 1],
            [0, 5, 6, 1, 2, {foo: 1}]
        ]

    Given that individual objects are being represented by arrays, generic
    code needs some way to differentiate arrays that are meant to be objects
    from arrays that are actually meant to be arrays.
    So for each class, we include a dict with <attribute name>: true mappings
    for each attribute that is meant to be an array.

    Also, in cases where some attribute values are the same for all objects
    in a particular set, it may be convenient to define a "prototype"
    with default values for all objects in the set

    In the end, we get something like this:

        classes=[
            {'attributes': ['Start', 'End', 'Subfeatures'],
             'proto': {'Chrom': 'chr1'},
             'isArrayAttr': {Subfeatures: true}}
            ]

    That's what this class facilitates.
    """

*/
function ArrayRepr (classes) {
    this.classes = classes;
    this.fields = [];
    for (var cl = 0; cl < classes.length; cl++) {
        this.fields[cl] = {};
        for (var f = 0; f < classes[cl].attributes.length; f++) {
            this.fields[cl][classes[cl].attributes[f]] = f + 1;
        }
        if (classes[cl].proto === undefined)
            classes[cl].proto = {};
        if (classes[cl].isArrayAttr === undefined)
            classes[cl].isArrayAttr = {};
    }
}

ArrayRepr.prototype.attrIndices = function(attr) {
    return this.classes.map(
        function(x) {
            var i = x.attributes.indexOf(attr);
            return i >= 0 ? i + 1 : undefined;
        }
    );
};

ArrayRepr.prototype.get = function(obj, attr) {
    if (attr in this.fields[obj[0]]) {
        return obj[this.fields[obj[0]][attr]];
    } else {
        var adhocIndex = this.classes[obj[0]].attributes.length + 1;
        if ((adhocIndex >= obj.length) || (!(attr in obj[adhocIndex]))) {
            if (attr in this.classes[obj[0]].proto)
                return this.classes[obj[0]].proto[attr];
            return undefined;
        }
        return obj[adhocIndex][attr];
    }
};

ArrayRepr.prototype.set = function(obj, attr, val) {
    if (attr in this.fields[obj[0]]) {
        obj[this.fields[obj[0]][attr]] = val;
    } else {
        var adhocIndex = self.classes[obj[0]].length + 1;
        if (adhocIndex >= obj.length)
            obj[adhocIndex] = {};
        obj[adhocIndex][attr] = val;
    }
};

ArrayRepr.prototype.makeSetter = function(attr) {
    var self = this;
    return function(obj, val) { self.set(obj, attr, val); };
};

ArrayRepr.prototype.makeGetter = function(attr) {
    var self = this;
    return function(obj) { return self.get(obj, attr); };
};

ArrayRepr.prototype.makeFastSetter = function(attr) {
    // can be used only if attr is guaranteed to be in
    // the "classes" array for this object
    var indices = this.attrIndices(attr);
    return function(obj, val) {
        if (indices[obj[0]] !== undefined)
            obj[indices[obj[0]]] = val;
    };
};

ArrayRepr.prototype.makeFastGetter = function(attr) {
    // can be used only if attr is guaranteed to be in
    // the "classes" array for this object
    var indices = this.attrIndices(attr);
    return function(obj) {
        if (indices[obj[0]] !== undefined)
            return obj[indices[obj[0]]];
        else
            return undefined;
    };
};

ArrayRepr.prototype.construct = function(self, obj, klass) {
    var result = new Array(self.classes[klass].length);
    for (var attr in obj) {
        this.set(result, attr, obj[attr]);
    }
    return result;
};

ArrayRepr.prototype.accessors = function () {
    return this._accessors = this._accessors || this._makeAccessors();
};

// make an object like
//     { get: { attrname: func() {}, ... },
//       set: { attrname: func() {}. ... }
//     }
// prototype object that, when set as the prototype on a data
// array, provides nicely-named, fast accessors to access the
// attributes in that array
ArrayRepr.prototype._makeAccessors = function() {
    var that = this,
        accessors = {
            get: function(field) {
                try { return this.get[field].call(this); }
                catch (x) { return undefined; }
            },
            set: function(field,val) {
                try { return this.set[field].call(this,val); }
                catch (x) { return undefined; }
            }
        };

    // make a data structure as: { attr_name: [offset,offset,offset], }
    // that will be convenient for finding the location of the attr
    // for a given class like: indexForAttr{attrname}[classnum]
    var indices = {};
    dojo.forEach( this.classes, function(cdef,classnum) {
        dojo.forEach( cdef.attributes || [], function(attrname,offset) {
            attrname = attrname.toLowerCase();
            indices[attrname] = indices[attrname] || [];
            indices[attrname][classnum] = offset + 1;
        });
    });

    for( var attrname in indices ) {
        if( ! indices.hasOwnProperty(attrname) ) continue;

        // make a get accessor
        accessors.get[ attrname ] = (function() {
            var attr_indices = indices[attrname];
            return !attr_indices ? function() { return undefined; } : function() {
                return this[ attr_indices[ this[0] ] ];
            };
        })();

        // make a set accessor
        accessors.set[ attrname ] = (function() {
            var attr_indices =  indices[attrname];
            return !attr_indices ? function() { return undefined; } : function(v) {
                return ( this[ attr_indices[ this[0] ] ] = v );
            };
        })();
    }

    return accessors;
};

/*

Copyright (c) 2007-2010 The Evolutionary Software Foundation

Created by Mitchell Skinner <mitch_skinner@berkeley.edu>

This package and its accompanying libraries are free software; you can
redistribute it and/or modify it under the terms of the LGPL (either
version 2.1, or at your option, any later version) or the Artistic
License 2.0.  Refer to LICENSE for the full license text.

*/
<commit_msg>improve ArrayRepr inline doc<commit_after>// MODEL

/**
    @class
    @constructor

    @description

    Class for operating on indexed array representations of objects.

    For example, if we have a lot of objects with similar attrbutes, e.g.:

        [
            {start: 1, end: 2, strand: -1},
            {start: 5, end: 6, strand: 1},
            ...
        ]

    @description
    we can represent them more compactly (e.g., in JSON) something like this:

        class = ["start", "end", "strand"]
        [
            [1, 2, -1],
            [5, 6, 1],
            ...
        ]

    If we want to represent a few different kinds of objects in our big list,
    we can have multiple "class" arrays, and tag each object to identify
    which "class" array describes it.

    For example, if we have a lot of instances of a few types of objects,
    like this:

        [
            {start: 1, end: 2, strand: 1, id: 1},
            {start: 5, end: 6, strand: 1, id: 2},
            ...
            {start: 10, end: 20, chunk: 1},
            {start: 30, end: 40, chunk: 2},
            ...
        ]

    We could use the first array position to indicate the "class" for the
    object, like this:

        classes = [["start", "end", "strand", "id"], ["start", "end", "chunk"]]
        [
            [0, 1, 2, 1, 1],
            [0, 5, 6, 1, 2],
            ...
            [1, 10, 20, 1],
            [1, 30, 40, 1]
        ]

    Also, if we occasionally want to add an ad-hoc attribute, we could just
    stick an optional dictionary onto the end:

        classes = [["start", "end", "strand", "id"], ["start", "end", "chunk"]]
        [
            [0, 1, 2, 1, 1],
            [0, 5, 6, 1, 2, {foo: 1}]
        ]

    Given that individual objects are being represented by arrays, generic
    code needs some way to differentiate arrays that are meant to be objects
    from arrays that are actually meant to be arrays.
    So for each class, we include a dict with <attribute name>: true mappings
    for each attribute that is meant to be an array.

    Also, in cases where some attribute values are the same for all objects
    in a particular set, it may be convenient to define a "prototype"
    with default values for all objects in the set

    In the end, we get something like this:

        classes=[
            {'attributes': ['Start', 'End', 'Subfeatures'],
             'proto': {'Chrom': 'chr1'},
             'isArrayAttr': {Subfeatures: true}}
            ]

    That's what this class facilitates.
*/
function ArrayRepr (classes) {
    this.classes = classes;
    this.fields = [];
    for (var cl = 0; cl < classes.length; cl++) {
        this.fields[cl] = {};
        for (var f = 0; f < classes[cl].attributes.length; f++) {
            this.fields[cl][classes[cl].attributes[f]] = f + 1;
        }
        if (classes[cl].proto === undefined)
            classes[cl].proto = {};
        if (classes[cl].isArrayAttr === undefined)
            classes[cl].isArrayAttr = {};
    }
}

/**
 * @private
 */
ArrayRepr.prototype.attrIndices = function(attr) {
    return this.classes.map(
        function(x) {
            var i = x.attributes.indexOf(attr);
            return i >= 0 ? i + 1 : undefined;
        }
    );
};

ArrayRepr.prototype.get = function(obj, attr) {
    if (attr in this.fields[obj[0]]) {
        return obj[this.fields[obj[0]][attr]];
    } else {
        var adhocIndex = this.classes[obj[0]].attributes.length + 1;
        if ((adhocIndex >= obj.length) || (!(attr in obj[adhocIndex]))) {
            if (attr in this.classes[obj[0]].proto)
                return this.classes[obj[0]].proto[attr];
            return undefined;
        }
        return obj[adhocIndex][attr];
    }
};

ArrayRepr.prototype.set = function(obj, attr, val) {
    if (attr in this.fields[obj[0]]) {
        obj[this.fields[obj[0]][attr]] = val;
    } else {
        var adhocIndex = self.classes[obj[0]].length + 1;
        if (adhocIndex >= obj.length)
            obj[adhocIndex] = {};
        obj[adhocIndex][attr] = val;
    }
};

ArrayRepr.prototype.makeSetter = function(attr) {
    var self = this;
    return function(obj, val) { self.set(obj, attr, val); };
};

ArrayRepr.prototype.makeGetter = function(attr) {
    var self = this;
    return function(obj) { return self.get(obj, attr); };
};

ArrayRepr.prototype.makeFastSetter = function(attr) {
    // can be used only if attr is guaranteed to be in
    // the "classes" array for this object
    var indices = this.attrIndices(attr);
    return function(obj, val) {
        if (indices[obj[0]] !== undefined)
            obj[indices[obj[0]]] = val;
    };
};

ArrayRepr.prototype.makeFastGetter = function(attr) {
    // can be used only if attr is guaranteed to be in
    // the "classes" array for this object
    var indices = this.attrIndices(attr);
    return function(obj) {
        if (indices[obj[0]] !== undefined)
            return obj[indices[obj[0]]];
        else
            return undefined;
    };
};

ArrayRepr.prototype.construct = function(self, obj, klass) {
    var result = new Array(self.classes[klass].length);
    for (var attr in obj) {
        this.set(result, attr, obj[attr]);
    }
    return result;
};

/**

Returns fast pre-compiled getter and setter functions for use with
Arrays that use this representation.

When the returned get and set functions are
added as methods to an Array that contains data in this
representation, they provide fast access by name to the data.

@returns {Object} { get: function() {...}, set: function(val) {...} }

@example
var accessors = attrs.accessors();
var feature = get_feature_from_someplace();
feature.get = accessors.get;
// print out the feature start and end
console.log( feature.get('start') + ',' + feature.get('end') );

*/
ArrayRepr.prototype.accessors = function () {
    return this._accessors = this._accessors || this._makeAccessors();
};

/**
 * @private
 */
ArrayRepr.prototype._makeAccessors = function() {
    var that = this,
        accessors = {
            get: function(field) {
                try { return this.get[field].call(this); }
                catch (x) { return undefined; }
            },
            set: function(field,val) {
                try { return this.set[field].call(this,val); }
                catch (x) { return undefined; }
            }
        };

    // make a data structure as: { attr_name: [offset,offset,offset], }
    // that will be convenient for finding the location of the attr
    // for a given class like: indexForAttr{attrname}[classnum]
    var indices = {};
    dojo.forEach( this.classes, function(cdef,classnum) {
        dojo.forEach( cdef.attributes || [], function(attrname,offset) {
            attrname = attrname.toLowerCase();
            indices[attrname] = indices[attrname] || [];
            indices[attrname][classnum] = offset + 1;
        });
    });

    for( var attrname in indices ) {
        if( ! indices.hasOwnProperty(attrname) ) continue;

        // make a get accessor
        accessors.get[ attrname ] = (function() {
            var attr_indices = indices[attrname];
            return !attr_indices ? function() { return undefined; } : function() {
                return this[ attr_indices[ this[0] ] ];
            };
        })();

        // make a set accessor
        accessors.set[ attrname ] = (function() {
            var attr_indices =  indices[attrname];
            return !attr_indices ? function() { return undefined; } : function(v) {
                return ( this[ attr_indices[ this[0] ] ] = v );
            };
        })();
    }

    return accessors;
};

/*

Copyright (c) 2007-2010 The Evolutionary Software Foundation

Created by Mitchell Skinner <mitch_skinner@berkeley.edu>

This package and its accompanying libraries are free software; you can
redistribute it and/or modify it under the terms of the LGPL (either
version 2.1, or at your option, any later version) or the Artistic
License 2.0.  Refer to LICENSE for the full license text.

*/
<|endoftext|>


<commit_before>/*global module: false, require: false, URL: false, Blob: false */
'use strict';

var React = require('react');
var {Table, Pagination, DataMixin} = require('react-data-components-bd2k');
var {Button, Row, Col} = require('react-bootstrap');
var VariantSearch = require('./VariantSearch');
var SelectField = require('./SelectField');
var _ = require('underscore');
var cx = require('classnames');

var filterDisplay = v => v == null ? 'Any' : v;
var filterAny = v => v === 'Any' ? null : v;
var addAny = opts => ['Any', ...opts];

var pluralize = (n, s) => n === 1 ? s : s + 's';

var DataTable = React.createClass({
	mixins: [DataMixin],
	createDownload: function (ev) {
		// XXX This is a bit horrible. In order to build the tsv lazily (on
		// button click, instead of on every table update), we catch the
		// mousedown event and modify the href on the anchor element, behind
		// the back of react. I don't believe this will cause any problems, but
		// it's something to be aware of if react starts doing something
		// strange.  Also needs to be tested cross-browser. We should not offer
		// download on browsers that don't allow client-driven download.
		var data = this.state.data,
			keys = _.keys(data[0]),
			tsvRows = _.map(data, obj => _.map(keys, k => obj[k]).join('\t')).join('\n'), // use os-specific line endings?
			tsv = keys.join('\t') + '\n' + tsvRows;
		ev.target.href = URL.createObjectURL(new Blob([tsv], { type: 'text/tsv' }));
	},
	getInitialState: function () {
		return {filtersOpen: false};
	},
	toggleFilters: function () {
		this.setState({filtersOpen: !this.state.filtersOpen});
	},
	render: function () {
		var {filtersOpen, filterValues} = this.state,
			{columns, filterColumns, suggestions, className} = this.props,
			page = this.buildPage(),
			filterFormEls = _.map(filterColumns, ({name, prop, values}) =>
				<SelectField onChange={v => this.onFilter(prop, filterAny(v))}
					key={prop} label={`${name} is: `} value={filterDisplay(filterValues[prop])} options={addAny(values)}/>);

		return (

				<Row style={{marginBottom: '2px'}}>
					<Col sm={12}>
						<Button bsSize='xsmall' onClick={this.toggleFilters}>{(filtersOpen ? 'Hide' : 'Show' ) + ' Filters'}</Button>
						{filtersOpen &&
{filterFormEls}
}
					</Col>
				</Row>
				<Row style={{marginBottom: '2px'}}>
					<Col sm={5}>
						<VariantSearch
							id='variants-search'
							suggestions={suggestions}
							value={this.state.filterValues.visibleSearch}
							onChange={this.onFilter.bind(this, 'visibleSearch')}
						/>
					</Col>
					<Col sm={3} smOffset={4}>

							<SelectField
								label="Page size:"
								value={this.state.pageLength}
								options={this.props.pageLengthOptions}
								onChange={this.onPageLengthChange}
							/>

					</Col>
				</Row>
				<Row style={{marginBottom: '2px'}}>
					<Col sm={6}>

									{this.state.data.length} matching {pluralize(this.state.data.length, 'variant')}

								<Button download="variants.tsv" href="#" onMouseDown={this.createDownload}>Download</Button>

					</Col>
					<Col sm={6}>
						<Pagination
							className="pagination pull-right-sm"
							currentPage={page.currentPage}
							totalPages={page.totalPages}
							onChangePage={this.onChangePage} />
					</Col>
				</Row>
				<Row>
					<Col className="table-responsive" sm={12}>

					</Col>
				</Row>

		);
	}
});

module.exports = DataTable;
<commit_msg>Swap match count & search.<commit_after>/*global module: false, require: false, URL: false, Blob: false */
'use strict';

var React = require('react');
var {Table, Pagination, DataMixin} = require('react-data-components-bd2k');
var {Button, Row, Col} = require('react-bootstrap');
var VariantSearch = require('./VariantSearch');
var SelectField = require('./SelectField');
var _ = require('underscore');
var cx = require('classnames');

var filterDisplay = v => v == null ? 'Any' : v;
var filterAny = v => v === 'Any' ? null : v;
var addAny = opts => ['Any', ...opts];

var pluralize = (n, s) => n === 1 ? s : s + 's';

var DataTable = React.createClass({
	mixins: [DataMixin],
	createDownload: function (ev) {
		// XXX This is a bit horrible. In order to build the tsv lazily (on
		// button click, instead of on every table update), we catch the
		// mousedown event and modify the href on the anchor element, behind
		// the back of react. I don't believe this will cause any problems, but
		// it's something to be aware of if react starts doing something
		// strange.  Also needs to be tested cross-browser. We should not offer
		// download on browsers that don't allow client-driven download.
		var data = this.state.data,
			keys = _.keys(data[0]),
			tsvRows = _.map(data, obj => _.map(keys, k => obj[k]).join('\t')).join('\n'), // use os-specific line endings?
			tsv = keys.join('\t') + '\n' + tsvRows;
		ev.target.href = URL.createObjectURL(new Blob([tsv], { type: 'text/tsv' }));
	},
	getInitialState: function () {
		return {filtersOpen: false};
	},
	toggleFilters: function () {
		this.setState({filtersOpen: !this.state.filtersOpen});
	},
	render: function () {
		var {filtersOpen, filterValues} = this.state,
			{columns, filterColumns, suggestions, className} = this.props,
			page = this.buildPage(),
			filterFormEls = _.map(filterColumns, ({name, prop, values}) =>
				<SelectField onChange={v => this.onFilter(prop, filterAny(v))}
					key={prop} label={`${name} is: `} value={filterDisplay(filterValues[prop])} options={addAny(values)}/>);

		return (

				<Row style={{marginBottom: '2px'}}>
					<Col sm={12}>
						<Button bsSize='xsmall' onClick={this.toggleFilters}>{(filtersOpen ? 'Hide' : 'Show' ) + ' Filters'}</Button>
						{filtersOpen &&
{filterFormEls}
}
					</Col>
				</Row>
				<Row style={{marginBottom: '2px'}}>
					<Col sm={6}>

									{this.state.data.length} matching {pluralize(this.state.data.length, 'variant')}

								<Button download="variants.tsv" href="#" onMouseDown={this.createDownload}>Download</Button>

					</Col>
					<Col sm={3} smOffset={3}>

							<SelectField
								label="Page size:"
								value={this.state.pageLength}
								options={this.props.pageLengthOptions}
								onChange={this.onPageLengthChange}
							/>

					</Col>
				</Row>
				<Row style={{marginBottom: '2px'}}>
					<Col sm={5}>
						<VariantSearch
							id='variants-search'
							suggestions={suggestions}
							value={this.state.filterValues.visibleSearch}
							onChange={this.onFilter.bind(this, 'visibleSearch')}
						/>
					</Col>
					<Col sm={6} smOffset={1}>
						<Pagination
							className="pagination pull-right-sm"
							currentPage={page.currentPage}
							totalPages={page.totalPages}
							onChangePage={this.onChangePage} />
					</Col>
				</Row>
				<Row>
					<Col className="table-responsive" sm={12}>

					</Col>
				</Row>

		);
	}
});

module.exports = DataTable;
<|endoftext|>


<commit_before>/*global module: false, require: false, URL: false, Blob: false */
'use strict';

var React = require('react');
var {Table, Pagination, DataMixin} = require('react-data-components-bd2k');
var {Button, Row, Col} = require('react-bootstrap');
var VariantSearch = require('./VariantSearch');
var SelectField = require('./SelectField');
var _ = require('underscore');

var filterDisplay = v => v == null ? 'Any' : v;
var filterAny = v => v === 'Any' ? null : v;
var addAny = opts => ['Any', ...opts];

var DataTable = React.createClass({
	mixins: [DataMixin],
	createDownload: function (ev) {
		// XXX This is a bit horrible. In order to build the tsv lazily (on
		// button click, instead of on every tabe update), we catch the
		// mousedown event and modify the href on the anchor element, behind
		// the back of react. I don't believe this will cause any problems, but
		// it's something to be aware of if react starts doing something
		// strange.  Also needs to be tested cross-browser. We should not offer
		// download on browsers that don't allow client-driven download.
		var data = this.state.data,
			keys = _.keys(data[0]),
			tsvRows = _.map(data, obj => _.map(keys, k => obj[k]).join('\t')).join('\n'), // use os-specific line endings?
			tsv = keys.join('\t') + '\n' + tsvRows;
		ev.target.href = URL.createObjectURL(new Blob([tsv], { type: 'text/tsv' }));
	},
	getInitialState: function () {
		return {filtersOpen: false};
	},
	toggleFilters: function () {
		this.setState({filtersOpen: !this.state.filtersOpen});
	},
	render: function () {
		var {filtersOpen, filterValues} = this.state,
			{columns, filterColumns} = this.props,
			page = this.buildPage(),
			filterFormEls = _.map(filterColumns, ({name, prop, values}) =>
				<SelectField onChange={v => this.onFilter(prop, filterAny(v))}
					key={prop} label={`${name} is: `} value={filterDisplay(filterValues[prop])} options={addAny(values)}/>);

		return (

				<Row style={{marginBottom: '2px'}}>
					<Col md={12}>
						<Button bsSize='xsmall' onClick={this.toggleFilters}>{(filtersOpen ? 'Hide' : 'Show' ) + ' Filters'}</Button>
						{filtersOpen &&
{filterFormEls}
}
					</Col>
				</Row>
				<Row style={{marginBottom: '2px'}}>
					<Col md={5}>
						<VariantSearch
							value={this.state.filterValues.visibleSearch}
							onChange={this.onFilter.bind(this, 'visibleSearch')}
						/>
					</Col>
					<Col md={3} mdOffset={4}>

							<SelectField
								label="Page size:"
								value={this.state.pageLength}
								options={this.props.pageLengthOptions}
								onChange={this.onPageLengthChange}
							/>

					</Col>
				</Row>
				<Row style={{marginBottom: '2px'}}>
					<Col md={6}>

									{this.state.data.length} matching variants

								<Button download="variants.tsv" href="#" onMouseDown={this.createDownload}>Download</Button>

					</Col>
					<Col md={6}>
						<Pagination
							className="pagination pull-right"
							currentPage={page.currentPage}
							totalPages={page.totalPages}
							onChangePage={this.onChangePage}
						/>
					</Col>
				</Row>

		);
	}
});

module.exports = DataTable;
<commit_msg>Add table-hover.<commit_after>/*global module: false, require: false, URL: false, Blob: false */
'use strict';

var React = require('react');
var {Table, Pagination, DataMixin} = require('react-data-components-bd2k');
var {Button, Row, Col} = require('react-bootstrap');
var VariantSearch = require('./VariantSearch');
var SelectField = require('./SelectField');
var _ = require('underscore');

var filterDisplay = v => v == null ? 'Any' : v;
var filterAny = v => v === 'Any' ? null : v;
var addAny = opts => ['Any', ...opts];

var DataTable = React.createClass({
	mixins: [DataMixin],
	createDownload: function (ev) {
		// XXX This is a bit horrible. In order to build the tsv lazily (on
		// button click, instead of on every tabe update), we catch the
		// mousedown event and modify the href on the anchor element, behind
		// the back of react. I don't believe this will cause any problems, but
		// it's something to be aware of if react starts doing something
		// strange.  Also needs to be tested cross-browser. We should not offer
		// download on browsers that don't allow client-driven download.
		var data = this.state.data,
			keys = _.keys(data[0]),
			tsvRows = _.map(data, obj => _.map(keys, k => obj[k]).join('\t')).join('\n'), // use os-specific line endings?
			tsv = keys.join('\t') + '\n' + tsvRows;
		ev.target.href = URL.createObjectURL(new Blob([tsv], { type: 'text/tsv' }));
	},
	getInitialState: function () {
		return {filtersOpen: false};
	},
	toggleFilters: function () {
		this.setState({filtersOpen: !this.state.filtersOpen});
	},
	render: function () {
		var {filtersOpen, filterValues} = this.state,
			{columns, filterColumns} = this.props,
			page = this.buildPage(),
			filterFormEls = _.map(filterColumns, ({name, prop, values}) =>
				<SelectField onChange={v => this.onFilter(prop, filterAny(v))}
					key={prop} label={`${name} is: `} value={filterDisplay(filterValues[prop])} options={addAny(values)}/>);

		return (

				<Row style={{marginBottom: '2px'}}>
					<Col md={12}>
						<Button bsSize='xsmall' onClick={this.toggleFilters}>{(filtersOpen ? 'Hide' : 'Show' ) + ' Filters'}</Button>
						{filtersOpen &&
{filterFormEls}
}
					</Col>
				</Row>
				<Row style={{marginBottom: '2px'}}>
					<Col md={5}>
						<VariantSearch
							value={this.state.filterValues.visibleSearch}
							onChange={this.onFilter.bind(this, 'visibleSearch')}
						/>
					</Col>
					<Col md={3} mdOffset={4}>

							<SelectField
								label="Page size:"
								value={this.state.pageLength}
								options={this.props.pageLengthOptions}
								onChange={this.onPageLengthChange}
							/>

					</Col>
				</Row>
				<Row style={{marginBottom: '2px'}}>
					<Col md={6}>

									{this.state.data.length} matching variants

								<Button download="variants.tsv" href="#" onMouseDown={this.createDownload}>Download</Button>

					</Col>
					<Col md={6}>
						<Pagination
							className="pagination pull-right"
							currentPage={page.currentPage}
							totalPages={page.totalPages}
							onChangePage={this.onChangePage}
						/>
					</Col>
				</Row>

		);
	}
});

module.exports = DataTable;
<|endoftext|>


<commit_before>/** @jsx React.DOM */
define([
    'underscore', 'react',
    './AutoControl',
    './ControlCommon',
    './ImmutableOptimizations'
], function (_, React, AutoControl, ControlCommon, ImmutableOptimizations) {
    'use strict';

    function determineFieldClass(children) {
        if (_.isArray(children)) {
            children = children[0];
        }

        if (children && _.isUndefined(children.type.fieldClass)) {
            // Support a textnode child, which won't have a fieldinfo
            if (children.props && children.props.fieldInfo) {
                return AutoControl.fieldClassForField(children.props.fieldInfo);
            }
            //console.warn('Unknown fieldClass for child component', children);

            return 'formFieldInput';
        }

        return children && children.type.fieldClass();
    }

    var FormField = React.createClass({
        mixins: [ImmutableOptimizations(['onStickyChange'])],

        getDefaultProps: function () {
            return {
                fieldInfo: {},
                layout: 'formField',
                noControl: false,
                isValid: [true, ''],
                lockable: false,
                locked: false,
                onStickyChange: function (isLocked) { /* set or clear a sticky */},
                width: '100%',
                marginLeft: '0',
                noLabel: false
            };
        },

        /* jshint ignore:start */
        render: function () {
            var fieldInfo = _.defaults(this.props.fieldInfo, {
                readOnly: false,
                disabled: false,
                label: '',
                helpText: ''
            });

            var hasInfoTooltip = !!fieldInfo.helpText;
            var hasErrorTooltip = (!this.props.isValid[0] && (this.props.isValid[1] || '').length > 0);

            var classes = _.compact([
                this.props.layout,
                determineFieldClass(this.props.children),
                ControlCommon.quadState(fieldInfo.disabled, fieldInfo.readOnly, this.props.isValid, this.props.noControl),
                hasInfoTooltip ? 'hasTooltip' : null,
                hasErrorTooltip ? 'hasErrorTooltip' : null,
                this.props.lockable ? 'lockable' : null
            ]);

            var lockedClasses = _.compact(['fieldLock', this.props.locked ? 'fieldLockOn' : null]);
            var lockDiv = this.props.lockable ? (
) : null;

            var styles = {
               'width': this.props.width,
               'margin-left': this.props.marginLeft
            };

            var statusIcon = (hasInfoTooltip ? () : null);

            // If there is no label and no icon, we must render   so the fields line up right.

            var label = ((fieldInfo.label || '').length === 0 && statusIcon === null
                ? ({'\u00A0'}) // unicode   to work around JSX bug:  https://groups.google.com/forum/?fromgroups=#!topic/reactjs/7FmlIyJBofA
                : ({fieldInfo.label}{statusIcon}));

            return (

                    {this.props.noLabel ? null : label}

                        {this.props.children}

                    {lockDiv}

            );
        },
        /* jshint ignore:end */

        componentWillReceiveProps: function (newProps) {
            var wasInvalid = !this.props.isValid[0];

            // If the field has become valid, hide the error tooltip.
            if (wasInvalid && newProps.isValid[0]) {
                ControlCommon.hideErrorTooltip();
            }
        },

        componentDidUpdate: function (prevProps) {
            var wasInvalid = prevProps.isValid[0] === false,
                isStillInvalid = this.props.isValid[0] === false,
                validationMessageChanged = prevProps.isValid[1] !== this.props.isValid[1];

            if (wasInvalid && isStillInvalid && validationMessageChanged) {
                ControlCommon.refreshErrorTooltip();
            }
        },

        toggleLock: function () {
            var isLocked = !this.props.locked;
            this.props.onStickyChange(isLocked);
            this.setState({ locked: isLocked });
        }
    });

    return FormField;
});
<commit_msg>Remove ImmutableOptimizations from FormField<commit_after>/** @jsx React.DOM */
define([
    'underscore', 'react',
    './AutoControl',
    './ControlCommon'
], function (_, React, AutoControl, ControlCommon) {
    'use strict';

    function determineFieldClass(children) {
        if (_.isArray(children)) {
            children = children[0];
        }

        if (children && _.isUndefined(children.type.fieldClass)) {
            // Support a textnode child, which won't have a fieldinfo
            if (children.props && children.props.fieldInfo) {
                return AutoControl.fieldClassForField(children.props.fieldInfo);
            }
            //console.warn('Unknown fieldClass for child component', children);

            return 'formFieldInput';
        }

        return children && children.type.fieldClass();
    }

    var FormField = React.createClass({
        getDefaultProps: function () {
            return {
                fieldInfo: {},
                layout: 'formField',
                noControl: false,
                isValid: [true, ''],
                lockable: false,
                locked: false,
                onStickyChange: function (isLocked) { /* set or clear a sticky */},
                width: '100%',
                marginLeft: '0',
                noLabel: false
            };
        },

        /* jshint ignore:start */
        render: function () {
            var fieldInfo = _.defaults(this.props.fieldInfo, {
                readOnly: false,
                disabled: false,
                label: '',
                helpText: ''
            });

            var hasInfoTooltip = !!fieldInfo.helpText;
            var hasErrorTooltip = (!this.props.isValid[0] && (this.props.isValid[1] || '').length > 0);

            var classes = _.compact([
                this.props.layout,
                determineFieldClass(this.props.children),
                ControlCommon.quadState(fieldInfo.disabled, fieldInfo.readOnly, this.props.isValid, this.props.noControl),
                hasInfoTooltip ? 'hasTooltip' : null,
                hasErrorTooltip ? 'hasErrorTooltip' : null,
                this.props.lockable ? 'lockable' : null
            ]);

            var lockedClasses = _.compact(['fieldLock', this.props.locked ? 'fieldLockOn' : null]);
            var lockDiv = this.props.lockable ? (
) : null;

            var styles = {
               'width': this.props.width,
               'margin-left': this.props.marginLeft
            };

            var statusIcon = (hasInfoTooltip ? () : null);

            // If there is no label and no icon, we must render   so the fields line up right.

            var label = ((fieldInfo.label || '').length === 0 && statusIcon === null
                ? ({'\u00A0'}) // unicode   to work around JSX bug:  https://groups.google.com/forum/?fromgroups=#!topic/reactjs/7FmlIyJBofA
                : ({fieldInfo.label}{statusIcon}));

            return (

                    {this.props.noLabel ? null : label}

                        {this.props.children}

                    {lockDiv}

            );
        },
        /* jshint ignore:end */

        componentWillReceiveProps: function (newProps) {
            var wasInvalid = !this.props.isValid[0];

            // If the field has become valid, hide the error tooltip.
            if (wasInvalid && newProps.isValid[0]) {
                ControlCommon.hideErrorTooltip();
            }
        },

        componentDidUpdate: function (prevProps) {
            var wasInvalid = prevProps.isValid[0] === false,
                isStillInvalid = this.props.isValid[0] === false,
                validationMessageChanged = prevProps.isValid[1] !== this.props.isValid[1];

            if (wasInvalid && isStillInvalid && validationMessageChanged) {
                ControlCommon.refreshErrorTooltip();
            }
        },

        toggleLock: function () {
            var isLocked = !this.props.locked;
            this.props.onStickyChange(isLocked);
            this.setState({ locked: isLocked });
        }
    });

    return FormField;
});
<|endoftext|>