<commit_before>/*!
 * ${copyright}
 */
sap.ui.define([
	"sap/m/OverflowToolbarLayoutData",
	"sap/m/OverflowToolbarPriority",
	"sap/ui/Device",
	"sap/ui/core/theming/Parameters"
], function(
	OverflowToolbarLayoutData,
	OverflowToolbarPriority,
	Device,
	Parameters
) {
	"use strict";

	var oControl;

	/**
	 * Class taking care of the control responsive behaviour.
	 * @alias sap/f/shellBar/ResponsiveHandler
	 * @experimental Since 1.63. This class is experimental and provides only limited functionality. Also the API might be changed in future.
	 * @since 1.63
	 * @private
	 */
	var ResponsiveHandler = function (oContext) {
		oControl = oContext;

		// Get and calculate padding's
		this._iREMSize = parseInt(jQuery("body").css("font-size"));
		this._iChildControlMargin = parseInt(Parameters.get("_sap_f_ShellBar_ChildMargin"));
		this._iDoubleChildControlMargin = this._iChildControlMargin * 2;
		this._iCoPilotWidth = parseInt(Parameters.get("_sap_f_ShellBar_CoPilotWidth")) + this._iDoubleChildControlMargin;
		this._iHalfCoPilotWidth = this._iCoPilotWidth / 2;

		this._oDelegate = {
			onAfterRendering: this.onAfterRendering,
			onBeforeRendering: this.onBeforeRendering
		};

		// Attach Event Delegates
		oControl.addDelegate(this._oDelegate, false, this);

		// Init resize handler method
		this._fnResize = this._resize;

		// Attach events
		oControl._oOverflowToolbar.attachEvent("_controlWidthChanged", this._handleResize, this);
	};

	ResponsiveHandler.prototype.onAfterRendering = function () {
		this._oDomRef = oControl.getDomRef();

		if (oControl._oMegaMenu) {
			// Attach on internal button image load
			var oButton = oControl._oMegaMenu.getAggregation("_button");
			if (oButton && oButton._image) {
				oButton._image.attachEvent("load", this._updateMegaMenuWidth, this);
			}
		}

		this._initResize();
		this._handleResize();
	};

	ResponsiveHandler.prototype.onBeforeRendering = function () {
		if (oControl._oHomeIcon) {
			oControl._oHomeIcon.attachEvent("load", this._updateHomeIconWidth, this);
		}
	};

	ResponsiveHandler.prototype.exit = function () {
		if (oControl._oOverflowToolbar) {
			oControl._oOverflowToolbar.detachEvent("_controlWidthChanged", this._handleResize, this);
		}
		if (oControl._oHomeIcon) {
			oControl._oHomeIcon.detachEvent("load", this._updateHomeIconWidth, this);
		}
		oControl.removeDelegate(this._oDelegate);
	};

	ResponsiveHandler.prototype._initResize = function () {
		this._iStaticWidth = 0;

		this._iMBWidth = this.getTargetWidth(oControl._oMegaMenu) + 65/* Control padding and arrow */ + (2 * this._iChildControlMargin);
		this._iTitleWidth = this.getTargetWidth(oControl._oSecondTitle);

		if (oControl._oHomeIcon) {
			this._iStaticWidth += oControl._oHomeIcon.$().outerWidth(true);
		}

		if (oControl._oNavButton) {
			this._iStaticWidth += 36 + this._iDoubleChildControlMargin;
		}

		if (oControl._oMenuButton) {
			this._iStaticWidth += 36 + this._iDoubleChildControlMargin;
		}
	};

	ResponsiveHandler.prototype._updateHomeIconWidth = function () {
		this._initResize();
		this._fnResize();
	};

	ResponsiveHandler.prototype._updateMegaMenuWidth = function () {
		this._initResize();
		this._fnResize();
	};

	ResponsiveHandler.prototype._handleResize = function () {
		if (!this._oDomRef) {return;}

		var $Control = oControl.$(),
			iWidth = $Control.outerWidth(),
			oCurrentRange = Device.media.getCurrentRange("Std", iWidth),
			bPhoneRange;

		if (oCurrentRange) {
			bPhoneRange = oCurrentRange.name === "Phone";

			$Control.toggleClass("sapFShellBarSizeDesktop", oCurrentRange.name === "Desktop");
			$Control.toggleClass("sapFShellBarSizeTablet", oCurrentRange.name === "Tablet");
			$Control.toggleClass("sapFShellBarSizePhone", bPhoneRange);
		}

		if (this._iPreviousWidth === iWidth) {
			return;
		}
		this._iPreviousWidth = iWidth;

		// If none of the managed controls are available - no further adaptation is needed
		if (!oControl._oNavButton &&
			!oControl._oMenuButton &&
			!oControl._oHomeIcon &&
			!oControl._oMegaMenu &&
			!oControl._oSecondTitle &&
			!oControl._oCopilot) {
			return;
		}

		if (bPhoneRange && !this.bWasInPhoneRange) {
			this._fnResize = this._resizeOnPhone;
			this._transformToPhoneState();
			return;
		} else if (!bPhoneRange && this.bWasInPhoneRange) {
			this._fnResize = this._resize;
			this._transformToRegularState();
			return;
		}

		setTimeout(this._fnResize.bind(this), 0);
	};

	ResponsiveHandler.prototype._transformToPhoneState = function () {
		if (oControl._oSecondTitle) {
			oControl._oSecondTitle.setVisible(false);
		}

		if (oControl._oHomeIcon) {
			oControl._oHomeIcon.setVisible(false);
			if (oControl._oMegaMenu) {
				oControl._oMegaMenu.setWidth("auto").setText("").setIcon(oControl.getHomeIcon());
			}
		}

		// Cache controls layout data
		this._cacheControlsLayoutData();

		// Force all controls in the overflow
		oControl._aOverflowControls.forEach(function (oControl) {
			oControl.setLayoutData(new sap.m.OverflowToolbarLayoutData({
				priority: sap.m.OverflowToolbarPriority.AlwaysOverflow
			}));
		});

		this.bWasInPhoneRange = true;
		oControl.invalidate();
	};

	ResponsiveHandler.prototype._transformToRegularState = function () {
		if (oControl._oSecondTitle) {
			oControl._oSecondTitle.setVisible(true);
		}

		if (oControl._oHomeIcon) {
			oControl._oHomeIcon.setVisible(true);
			if (oControl._oMegaMenu) {
				oControl._oMegaMenu.setWidth("auto").setText(oControl._sTitle).setIcon("");
			}
		}

		// Restore controls layout data from cache
		this._restoreControlsLayoutData();

		this.bWasInPhoneRange = false;
		oControl.invalidate();
	};

	ResponsiveHandler.prototype._resizeOnPhone = function () {
		var iWidth,
			iAvailableWidth;

		if  (oControl._oCopilot) {
			iWidth = oControl.$().width() - this._iCoPilotWidth;
			iAvailableWidth = (iWidth / 2) - this._iStaticWidth;
		} else {
			iWidth = oControl.$().width();
			iAvailableWidth = iWidth - this._iStaticWidth - this._getWidthOfAllNonManagedControls();
		}

		if (!oControl._oHomeIcon && oControl._sTitle) {
			if (this._iMBWidth >= iAvailableWidth) {
				// Applied width should be without margins
				oControl._oMegaMenu.setWidth((iAvailableWidth - this._iDoubleChildControlMargin) + "px");
			} else {
				// Applied width should be without margins
				oControl._oMegaMenu.setWidth((this._iMBWidth - this._iDoubleChildControlMargin) + "px");
			}
		}

		if (oControl._oMegaMenu) {
			iAvailableWidth -= oControl._oMegaMenu.$().outerWidth(true);
		}

		if (iAvailableWidth < 0) {iAvailableWidth = 0;}
		oControl._oControlSpacer.setWidth(iAvailableWidth + "px");
	};

	ResponsiveHandler.prototype._resize = function () {
		var iWidth = oControl.$().width(),
			iAvailableWidth,
			iOTBControls;

		if (!oControl._oCopilot) {
			iOTBControls = this._getWidthOfAllNonManagedControls();
			iAvailableWidth = iWidth - iOTBControls - this._iStaticWidth - (8 * this._iREMSize);

			this._adaptManagedWidthControls(iAvailableWidth);
			return;
		}

		iAvailableWidth = (iWidth / 2) - this._iHalfCoPilotWidth - this._iStaticWidth;
		this._adaptManagedWidthControls(iAvailableWidth);
	};

	ResponsiveHandler.prototype._getWidthOfAllNonManagedControls = function () {
		var aControls = oControl._oOverflowToolbar.$().children(),
			iOTBControls = 0;

		aControls.filter(function (i, oDomRef) {
			var $Ctr = jQuery(oDomRef),
				oCtr = $Ctr.control(0);

			if (oCtr === oControl._oNavButton) {return false;}
			if (oCtr === oControl._oMenuButton) {return false;}
			if (oCtr === oControl._oHomeIcon) {return false;}
			if (oCtr === oControl._oMegaMenu) {return false;}
			if (oCtr === oControl._oSecondTitle) {return false;}
			if (oCtr === oControl._oControlSpacer) {return false;}
			if (oCtr === oControl._oToolbarSpacer) {return false;}

			iOTBControls += $Ctr.outerWidth(true);
			return true;
		});

		return iOTBControls;
	};

	ResponsiveHandler.prototype._adaptManagedWidthControls = function (iAvailableWidth) {
		var bHasTitle = oControl._sTitle,
			iMBWidth = bHasTitle ? this._iMBWidth : 36 + this._iDoubleChildControlMargin,
			iTitleWidth = this._iTitleWidth,
			iCollectiveWidth = iMBWidth + iTitleWidth,
			oSecondTitle = oControl._oSecondTitle,
			oMegaMenu = oControl._oMegaMenu,
			oControlSpacer = oControl._oControlSpacer,
			iSecondTitleWidth;

		if (!oMegaMenu) {
			iCollectiveWidth -= 36 + this._iDoubleChildControlMargin;
		}

		if (iCollectiveWidth < 0) {iCollectiveWidth = 0;}
		if (iMBWidth < 0) {iMBWidth = 0;}

		if (iMBWidth >= iAvailableWidth) {
			oControlSpacer && oControlSpacer.setWidth("0px");
			oSecondTitle && oSecondTitle.setWidth("0px");

			// Applied width should be without margins
			bHasTitle && oMegaMenu.setWidth((iAvailableWidth - this._iDoubleChildControlMargin) + "px");
			return;
		} else {
			// Applied width should be without margins
			bHasTitle && oMegaMenu.setWidth((iMBWidth - this._iDoubleChildControlMargin) + "px");
		}

		if (iAvailableWidth >= iMBWidth && iAvailableWidth <= iCollectiveWidth) {

			iSecondTitleWidth = iAvailableWidth - iMBWidth;
			if (iSecondTitleWidth < 0) {iSecondTitleWidth = 0;}

			if (iSecondTitleWidth > 32 /* min-width of the Second Title */) {
				oControlSpacer && oControlSpacer.setWidth("0px");
				oSecondTitle && oSecondTitle.setWidth(iSecondTitleWidth + "px");
			} else {
				oControlSpacer && oControlSpacer.setWidth(iSecondTitleWidth + "px");
				oSecondTitle && oSecondTitle.setWidth("0px");
			}
			return;
		} else {
			oSecondTitle && oSecondTitle.setWidth(iTitleWidth + "px");
		}

		if (iAvailableWidth > iCollectiveWidth) {
			oControlSpacer && oControlSpacer.setWidth((iAvailableWidth - iCollectiveWidth) + "px");
		}
	};

	ResponsiveHandler.prototype._cacheControlsLayoutData = function () {
		this._oCachedLayoutData = {};
		oControl._aOverflowControls.forEach(function (oCtr) {
			this._oCachedLayoutData[oCtr.getId()] = oCtr.getLayoutData();
		}.bind(this));
	};

	ResponsiveHandler.prototype._restoreControlsLayoutData = function () {
		oControl._aOverflowControls.forEach(function (oCtr) {
			oCtr.setLayoutData(this._oCachedLayoutData[oCtr.getId()]);
		}.bind(this));
	};

	ResponsiveHandler.prototype.getTargetWidth = function (oCtr, bBold) {
		if (!oCtr) {return 0;}

		var sText = oCtr.getText(),
			oDiv = document.createElement("div"),
			oText = document.createTextNode(sText),
			oStaticArea = sap.ui.getCore().getStaticAreaRef(),
			iWidth;

		// Create div and add to static area
		oDiv.appendChild(oText);
		oDiv.style.setProperty("white-space", "nowrap");
		oDiv.style.setProperty("display", "inline-block");
		oDiv.style.setProperty("font-size", "0.875rem");
		if (bBold) {
			oDiv.style.setProperty("font-weight", "bold");
		}
		oStaticArea.appendChild(oDiv);

		// Record width
		iWidth = oDiv.scrollWidth;

		// Remove child from static area
		oStaticArea.removeChild(oDiv);

		return iWidth;
	};

	return ResponsiveHandler;

});<commit_msg>[INTERNAL] sap.f.shellBar.ResponsiveHandler: JSDoc added and minor fixes<commit_after>/*!
 * ${copyright}
 */
sap.ui.define([
	"sap/m/OverflowToolbarLayoutData",
	"sap/m/OverflowToolbarPriority",
	"sap/ui/Device",
	"sap/ui/core/theming/Parameters"
], function (
	OverflowToolbarLayoutData,
	OverflowToolbarPriority,
	Device,
	Parameters
) {
	"use strict";

	var oControl;

	/**
	 * Class taking care of the control responsive behaviour.
	 * @alias sap/f/shellBar/ResponsiveHandler
	 * @experimental Since 1.63. This class is experimental and provides only limited functionality. Also the API might be changed in future.
	 * @since 1.63
	 * @private
	 * @property {object} oContext the context of the ShellBar control instance
	 */
	var ResponsiveHandler = function (oContext) {
		oControl = oContext;

		// Get and calculate padding's
		this._iREMSize = parseInt(jQuery("body").css("font-size"));
		this._iChildControlMargin = parseInt(Parameters.get("_sap_f_ShellBar_ChildMargin"));
		this._iDoubleChildControlMargin = this._iChildControlMargin * 2;
		this._iCoPilotWidth = parseInt(Parameters.get("_sap_f_ShellBar_CoPilotWidth")) + this._iDoubleChildControlMargin;
		this._iHalfCoPilotWidth = this._iCoPilotWidth / 2;

		// Delegate used to attach on ShellBar lifecycle events
		this._oDelegate = {
			onAfterRendering: this.onAfterRendering,
			onBeforeRendering: this.onBeforeRendering
		};

		// Attach Event Delegates
		oControl.addDelegate(this._oDelegate, false, this);

		// Init resize handler method
		this._fnResize = this._resize;

		// Attach events
		oControl._oOverflowToolbar.attachEvent("_controlWidthChanged", this._handleResize, this);
	};

	/**
	 * Lifecycle event handler for ShellBar onAfterRendering event
	 */
	ResponsiveHandler.prototype.onAfterRendering = function () {
		this._oDomRef = oControl.getDomRef(); // Cache DOM Reference

		if (oControl._oMegaMenu) {
			// Attach on internal button image load
			this._oButton = oControl._oMegaMenu.getAggregation("_button");
			if (this._oButton && this._oButton._image) {
				// We need to update all the measurements of the control when the image is loaded in the DOM as we can't
				// measure it before that
				this._oButton._image.attachEvent("load", this._updateMegaMenuWidth, this);
			}
		}

		this._initResize();
		this._handleResize();
	};

	/**
	 * Lifecycle event handler for ShellBar onBeforeRendering event
	 */
	ResponsiveHandler.prototype.onBeforeRendering = function () {
		if (oControl._oHomeIcon) {
			oControl._oHomeIcon.attachEvent("load", this._updateHomeIconWidth, this);
		}
	};

	/**
	 * Lifecycle event handler for cleaning after the control is not needed anymore
	 */
	ResponsiveHandler.prototype.exit = function () {
		if (oControl._oOverflowToolbar) {
			oControl._oOverflowToolbar.detachEvent("_controlWidthChanged", this._handleResize, this);
		}
		if (oControl._oHomeIcon) {
			oControl._oHomeIcon.detachEvent("load", this._updateHomeIconWidth, this);
		}
		if (this._oButton) {
			this._oButton.detachEvent("load", this._updateMegaMenuWidth, this);
		}
		oControl.removeDelegate(this._oDelegate);
	};

	/**
	 * Initialize the resize handler by caching some sizes which are changing only control property changes or image
	 * loads of child controls. We don't need to update these cached sizes on control width changes.
	 * @private
	 */
	ResponsiveHandler.prototype._initResize = function () {
		this._iStaticWidth = 0;

		this._iMBWidth = this.getTargetWidth(oControl._oMegaMenu) + 65/* Control padding and arrow */ + (2 * this._iChildControlMargin);
		this._iTitleWidth = this.getTargetWidth(oControl._oSecondTitle);

		if (oControl._oHomeIcon) {
			this._iStaticWidth += oControl._oHomeIcon.$().outerWidth(true);
		}

		if (oControl._oNavButton) {
			this._iStaticWidth += 36 + this._iDoubleChildControlMargin;
		}

		if (oControl._oMenuButton) {
			this._iStaticWidth += 36 + this._iDoubleChildControlMargin;
		}
	};

	/**
	 * Handler for the homeIcon image load event
	 * @private
	 */
	ResponsiveHandler.prototype._updateHomeIconWidth = function () {
		this._initResize();
		this._fnResize();
	};

	/**
	 * Handler for the MegaMenu image load event
	 * @private
	 */
	ResponsiveHandler.prototype._updateMegaMenuWidth = function () {
		this._initResize();
		this._fnResize();
	};

	/**
	 * Event handler for control resize. This is a pre-resize handler which is executed on every control width change.
	 * It determines the available size for the managed controls prior to adapting their widths and visibility according
	 * to UX rules. This handler is also responsible for the control padding's outside of the managed area and for
	 * control mode according to available width as the control behaves different on desktop and mobile sizes and
	 * needs to adapt to different responsive behavior for the managed controls.
	 * @private
	 */
	ResponsiveHandler.prototype._handleResize = function () {
		if (!this._oDomRef) {return;}

		var $Control = oControl.$(),
			iWidth = $Control.outerWidth(),
			oCurrentRange = Device.media.getCurrentRange("Std", iWidth),
			bPhoneRange;

		// Adapt control padding's outside the managed area
		if (oCurrentRange) {
			bPhoneRange = oCurrentRange.name === "Phone";

			$Control.toggleClass("sapFShellBarSizeDesktop", oCurrentRange.name === "Desktop");
			$Control.toggleClass("sapFShellBarSizeTablet", oCurrentRange.name === "Tablet");
			$Control.toggleClass("sapFShellBarSizePhone", bPhoneRange);
		}

		if (this._iPreviousWidth === iWidth) {
			return; // We have nothing to update
		}
		this._iPreviousWidth = iWidth;

		// If none of the managed controls are available - no further adaptation is needed
		if (!oControl._oNavButton &&
			!oControl._oMenuButton &&
			!oControl._oHomeIcon &&
			!oControl._oMegaMenu &&
			!oControl._oSecondTitle &&
			!oControl._oCopilot) {
			return;
		}

		// Note: We change the final resize handler depending on available width of the control. This is done
		// only once when we go from mobile to desktop size and back.
		if (bPhoneRange && !this.bWasInPhoneRange) {
			this._fnResize = this._resizeOnPhone;
			this._transformToPhoneState();
			return;
		} else if (!bPhoneRange && this.bWasInPhoneRange) {
			this._fnResize = this._resize;
			this._transformToRegularState();
			return;
		}

		// We call the final resize handler which will resize the managed controls according to the UX rules
		setTimeout(this._fnResize.bind(this), 0);
	};

	/**
	 * Apply's UX rules to the control for phone size
	 * @private
	 */
	ResponsiveHandler.prototype._transformToPhoneState = function () {
		// Second title should not be visible
		if (oControl._oSecondTitle) {
			oControl._oSecondTitle.setVisible(false);
		}

		// Home icon should not be visible
		if (oControl._oHomeIcon) {
			oControl._oHomeIcon.setVisible(false);
			// We should inject the homeIcon in the MegaMenu and remove the text
			if (oControl._oMegaMenu) {
				oControl._oMegaMenu.setWidth("auto").setText("").setIcon(oControl.getHomeIcon());
			}
		}

		// Cache controls layout data
		this._cacheControlsLayoutData();

		// Force all controls in the overflow
		oControl._aOverflowControls.forEach(function (oControl) {
			oControl.setLayoutData(new sap.m.OverflowToolbarLayoutData({
				priority: sap.m.OverflowToolbarPriority.AlwaysOverflow
			}));
		});

		this.bWasInPhoneRange = true;
		oControl.invalidate();
	};

	/**
	 * Apply's UX rules to the control for bigger than phone screen sizes
	 * @private
	 */
	ResponsiveHandler.prototype._transformToRegularState = function () {
		// Second title should be visible
		if (oControl._oSecondTitle) {
			oControl._oSecondTitle.setVisible(true);
		}

		// Home icon should be visible
		if (oControl._oHomeIcon) {
			oControl._oHomeIcon.setVisible(true);
			// If we have MegaMenu we should get back the Icon and restore it's text
			if (oControl._oMegaMenu) {
				oControl._oMegaMenu.setWidth("auto").setText(oControl._sTitle).setIcon("");
			}
		}

		// Restore controls layout data from cache
		this._restoreControlsLayoutData();

		this.bWasInPhoneRange = false;
		oControl.invalidate();
	};

	/**
	 * Responsive handler for phone screen sizes.
	 * Applies managed controls sizes according to UX rules.
	 * @private
	 */
	ResponsiveHandler.prototype._resizeOnPhone = function () {
		var iWidth,
			iAvailableWidth;

		if  (oControl._oCopilot) {
			iWidth = oControl.$().width() - this._iCoPilotWidth;
			iAvailableWidth = (iWidth / 2) - this._iStaticWidth;
		} else {
			iWidth = oControl.$().width();
			iAvailableWidth = iWidth - this._iStaticWidth - this._getWidthOfAllNonManagedControls();
		}

		if (!oControl._oHomeIcon && oControl._sTitle) {
			if (this._iMBWidth >= iAvailableWidth) {
				// Applied width should be without margins
				oControl._oMegaMenu.setWidth((iAvailableWidth - this._iDoubleChildControlMargin) + "px");
			} else {
				// Applied width should be without margins
				oControl._oMegaMenu.setWidth((this._iMBWidth - this._iDoubleChildControlMargin) + "px");
			}
		}

		if (oControl._oMegaMenu) {
			iAvailableWidth -= oControl._oMegaMenu.$().outerWidth(true);
		}

		if (iAvailableWidth < 0) {iAvailableWidth = 0;}
		oControl._oControlSpacer.setWidth(iAvailableWidth + "px");
	};

	/**
	 * Responsive handler for bigger than phone screen sizes.
	 * Calculates the available width for managed controls based on ShellBar state.
	 * @private
	 */
	ResponsiveHandler.prototype._resize = function () {
		var iWidth = oControl.$().width(),
			iAvailableWidth,
			iOTBControls;

		if (!oControl._oCopilot) {
			iOTBControls = this._getWidthOfAllNonManagedControls();
			iAvailableWidth = iWidth - iOTBControls - this._iStaticWidth - (8 * this._iREMSize);

			this._adaptManagedWidthControls(iAvailableWidth);
			return;
		}

		iAvailableWidth = (iWidth / 2) - this._iHalfCoPilotWidth - this._iStaticWidth;
		this._adaptManagedWidthControls(iAvailableWidth);
	};

	/**
	 * Utility method to measure all non-managed controls that are part of the OverflowToolbar but are not in the
	 * overflow part of the control.
	 * @returns {int} Sum of all visible control widths
	 * @private
	 */
	ResponsiveHandler.prototype._getWidthOfAllNonManagedControls = function () {
		var aControls = oControl._oOverflowToolbar.$().children(),
			iOTBControls = 0;

		aControls.filter(function (i, oDomRef) {
			var $Ctr = jQuery(oDomRef),
				oCtr = $Ctr.control(0);

			if (oCtr === oControl._oNavButton) {return false;}
			if (oCtr === oControl._oMenuButton) {return false;}
			if (oCtr === oControl._oHomeIcon) {return false;}
			if (oCtr === oControl._oMegaMenu) {return false;}
			if (oCtr === oControl._oSecondTitle) {return false;}
			if (oCtr === oControl._oControlSpacer) {return false;}
			if (oCtr === oControl._oToolbarSpacer) {return false;}

			iOTBControls += $Ctr.outerWidth(true);
			return true;
		});

		return iOTBControls;
	};

	/**
	 * Final resize handler which handles control widths of all managed controls according to UX rules and the available
	 * width for them. Keep in mind that this handler takes care for different scenarios based on availability of
	 * managed controls. This handler is taking care for bigger than phone screen sizes.
	 * @param {int} iAvailableWidth The available width for the managed controls
	 * @private
	 */
	ResponsiveHandler.prototype._adaptManagedWidthControls = function (iAvailableWidth) {
		var bHasTitle = oControl._sTitle,
			iMBWidth = bHasTitle ? this._iMBWidth : 36 + this._iDoubleChildControlMargin,
			iTitleWidth = this._iTitleWidth,
			iCollectiveWidth = iMBWidth + iTitleWidth,
			oSecondTitle = oControl._oSecondTitle,
			oMegaMenu = oControl._oMegaMenu,
			oControlSpacer = oControl._oControlSpacer,
			iSecondTitleWidth;

		if (!oMegaMenu) {
			iCollectiveWidth -= 36 + this._iDoubleChildControlMargin;
		}

		if (iCollectiveWidth < 0) {iCollectiveWidth = 0;}
		if (iMBWidth < 0) {iMBWidth = 0;}

		if (iMBWidth >= iAvailableWidth) {
			oControlSpacer && oControlSpacer.setWidth("0px");
			oSecondTitle && oSecondTitle.setWidth("0px");

			// Applied width should be without margins
			bHasTitle && oMegaMenu.setWidth((iAvailableWidth - this._iDoubleChildControlMargin) + "px");
			return;
		} else {
			// Applied width should be without margins
			bHasTitle && oMegaMenu.setWidth((iMBWidth - this._iDoubleChildControlMargin) + "px");
		}

		if (iAvailableWidth >= iMBWidth && iAvailableWidth <= iCollectiveWidth) {

			iSecondTitleWidth = iAvailableWidth - iMBWidth;
			if (iSecondTitleWidth < 0) {iSecondTitleWidth = 0;}

			if (iSecondTitleWidth > 32 /* min-width of the Second Title */) {
				oControlSpacer && oControlSpacer.setWidth("0px");
				oSecondTitle && oSecondTitle.setWidth(iSecondTitleWidth + "px");
			} else {
				oControlSpacer && oControlSpacer.setWidth(iSecondTitleWidth + "px");
				oSecondTitle && oSecondTitle.setWidth("0px");
			}
			return;
		} else {
			oSecondTitle && oSecondTitle.setWidth(iTitleWidth + "px");
		}

		if (iAvailableWidth > iCollectiveWidth) {
			oControlSpacer && oControlSpacer.setWidth((iAvailableWidth - iCollectiveWidth) + "px");
		}
	};

	/**
	 * Cache layout data of all the controls that should go into the overflow
	 * @private
	 */
	ResponsiveHandler.prototype._cacheControlsLayoutData = function () {
		this._oCachedLayoutData = {};
		oControl._aOverflowControls.forEach(function (oCtr) {
			this._oCachedLayoutData[oCtr.getId()] = oCtr.getLayoutData();
		}.bind(this));
	};

	/**
	 * Restore layout data for all the control that were in the overflow area
	 * @private
	 */
	ResponsiveHandler.prototype._restoreControlsLayoutData = function () {
		oControl._aOverflowControls.forEach(function (oCtr) {
			oCtr.setLayoutData(this._oCachedLayoutData[oCtr.getId()]);
		}.bind(this));
	};

	/**
	 * Retrieve the target width of a control text by rendering a div with a specific font size in the static area
	 * to measure the width of the text and then removing the not needed div.
	 * @param {sap.ui.core.Control} oCtr the control with "text" property
	 * @param {boolean} [bBold=false] should we measure bold text
	 * @returns {int} the width of the measured text
	 */
	ResponsiveHandler.prototype.getTargetWidth = function (oCtr, bBold) {
		if (!oCtr) {return 0;}

		var sText = oCtr.getText(),
			oDiv = document.createElement("div"),
			oText = document.createTextNode(sText),
			oStaticArea = sap.ui.getCore().getStaticAreaRef(),
			iWidth;

		// Create div and add to static area
		oDiv.appendChild(oText);
		oDiv.style.setProperty("white-space", "nowrap");
		oDiv.style.setProperty("display", "inline-block");
		oDiv.style.setProperty("font-size", "0.875rem");
		if (bBold) {
			oDiv.style.setProperty("font-weight", "bold");
		}
		oStaticArea.appendChild(oDiv);

		// Record width
		iWidth = oDiv.scrollWidth;

		// Remove child from static area
		oStaticArea.removeChild(oDiv);

		return iWidth;
	};

	return ResponsiveHandler;

});<|endoftext|>


<commit_before>// ==========================================================================
// Project:  SproutCore Runtime
// Copyright: ©2011 Strobe Inc. and contributors.
// License:   Licensed under MIT license (see license.js)
// ==========================================================================

var get = SC.get;
var set = SC.set;

var sigFigs = 100;

/**
  @class

  Base class for all gesture recognizers. Handles low-level touch and state
  management, and provides some utility methods and some required methods all
  gesture recognizers are expected to implement.

  Overview
  =========

  Gestures coalesce multiple touch events to a single higher-level gesture
  event. For example, a tap gesture recognizer takes information about a
  touchstart event, a few touchmove events, and a touchend event and uses
  some heuristics to decide whether or not that sequence of events qualifies
  as a tap event. If it does, then it will notify the view of the higher-level
  tap events.

  Gesture events follow the format:

    * [GESTURE_NAME]Start - Sent when a gesture has gathered enough information
        to begin tracking the gesture

    * [GESTURE_NAME]Change - Sent when a gesture has already started and has
        received touchmove events that cause its state to change

    * [GESTURE_NAME]End - Sent when a touchend event is received and the gesture
        recognizer decides that the gesture is finished.

    * [GESTURE_NAME]Cancel - Sent when a touchcancel event is received.

  There are two types of gesturess: Discrete and Continuous gestures. In contrast
  to continuous gestures, discrete gestures don't have any change events. Rather,
  the start and end events are the only one that gets sent.

  Usage
  =======

  While you wouldn't use SC.Gesture directly, all its subclasses have the same
  API. For example, to implement pinch on a view, you implement pinchChange and
  optionally pinchStart, pinchEnd and pinchCancel.

      var myView = SC.View.create({
        pinchStart: function(recognizer) {
          this.$().css('background','red');
        },

        pinchChange: function(recognizer) {
          var scale = recognizer.get('scale');
          this.$().css('-webkit-transform','scale3d('+scale+','+scale+',1)');
        },

        pinchEnd: function(recognizer) {
          this.$().css('background','blue');
        },

        pinchCancel: function(recognizer) {
          this.$().css('background','blue');
        }
      });

  pinchStart(), pinchEnd() and pinchCancel() will only get called once per
  gesture, but pinchChange() will get called repeatedly called every time
  one of the touches moves.

  Creating Custom Gesture Recognizers
  ======

  SC.Gesture also defines an API which its subclasses can implement to build
  custom gestures. The methods are:

    * **didBecomePossible** - Called when a gesture enters a possible state. This
        means the gesture recognizer has accepted enough touches to match
        the number of required touches. You would usually initialize your state
        in this callback.

    * **eventWasRejected** - Called if a view returns false from a gesture event.
        This callback allows you to reset internal state if the user rejects
        an event.

    * **shouldBegin** - Allows a gesture to block itself from entering a began state.
        This callback will continuously be called as touches move until it begins.

    * **shouldEnd** - Allows a gesture to block itself from entering an ended state.
        This callback gets called whenever a tracked touch gets a touchEnd event.

    * **didBegin** - Called when the gesture enters a began state. Called before the
       view receives the Start event.

    * **didChange** - Called when the gesture enters a began state, and when one of the
        touches moves. Called before the view receives the Change event.

    * **didEnd** - Called when the gesture enters an ended state. Called before the
       view receives the End event.

    * **didCancel** - Called when the gesture enters a cancelled state. Called before the
       view receives the Cancel event.

  In all the callbacks, you can use the `touches` protected property to access the
  touches hash. The touches hash is keyed on the identifiers of the touches, and the
  values are the jQuery.Event objects.

  You can also use the numberOfActiveTouches property to inspect how many touches
  are active, this is mostly useful in shouldBegin since every other callback can
  assume that there are as many active touches as specified in the
  numberOfRequiredTouches property.

  Discrete vs Continuous Gestures
  =======

  There are two main classes of gesture recognizers: Discrete and Continuous
  gestures. Discrete gestures do not get Change events sent, since they represent
  a single, instantaneous event, rather than a continuous motion. If you are
  implementing your own discrete gesture recognizer, you must set the
  isDiscreteGesture property to yes, and SC.Gesture will adapt its behavior.

  Discrete gestures use the shouldEnd callback to either accept or decline the gesture
  event. If it is delined, then the gesture will enter a Cancelled state and trigger
  the Cancel event on the view.

  @extends SC.Object
*/

SC.Gesture = SC.Object.extend(
  /** @scope SC.Gesture.prototype */{

  /**
    The current state of the gesture recognizer. This value can be any one
    of the states defined at the end of this file.

    @type Number
  */
  state: null,

  /**
    A string of the gesture recognizer's name. This value is set automatically
    but SC.Gestures when a gesture is registered.

    @type String
  */
  name: null,

  /**
    Specifies whether a gesture is discrete or continuous.

    @type Boolean
    @default false
  */
  gestureIsDiscrete: false,

  _numActiveTouches: 0,
  /**
    You can use the `touches` protected property to access the touches hash. The touches
    hash is keyed on the identifiers of the touches, and the values are the jQuery.Event
    objects.

    @private
    @type Hash
  */
  touches: null,

  /**
    You can also use the numberOfActiveTouches property to inspect how many touches
    are active, this is mostly useful in shouldBegin since every other callback can
    assume that there are as many active touches as specified in the
    numberOfRequiredTouches property.

  numberOfTouches: 2,
    @private
    @type Number
  */
  numberOfActiveTouches: 0,

  /**
    Used to specify the number of touches required for the gesture to enter a possible
    state

    @private
    @type Number
  */

  init: function() {
    this._super();
    this.touches = {};
  },

  //..............................................
  // Gesture Callbacks

  /** @private */
  didBecomePossible: function() { },

  /** @private */
  shouldBegin: function() {
    return true;
  },

  /** @private */
  didBegin: function() { },

  /** @private */
  didChange: function() { },

  /** @private */
  eventWasRejected: function() { },

  /** @private */
  shouldEnd: function() {
    return true;
  },

  /** @private */
  didEnd: function() { },

  /** @private */
  didCancel: function() { },

  //..............................................
  // Utilities

  /** @private */
  attemptGestureEventDelivery: function(evt, view, eventName) {
    if (this.notifyViewOfGestureEvent(view, eventName) === false) {
      this.eventWasRejected();
    } else {
      evt.preventDefault();
    }
  },

  /**
    Given two Touch objects, this method returns the distance between them.

    @return Number
  */
  distance: function(touches) {
    if (!(touches instanceof Array)) {
      touches = this._objectValues(touches);
    }

    if (touches.length !== 2) {
      throw new SC.Error('trying to get the distance between more than two points is not defined. Touches length: '+touches.length);
      return;
    }

    var first = touches[0];
    var second = touches[1];

    var x = first.pageX;
    var y = first.pageY;
    var x0 = second.pageX;
    var y0 = second.pageY;

    return Math.sqrt((x -= x0) * x + (y -= y0) * y);
  },

  /**
    Given two Touch objects, this method returns the midpoint between them.

    @return Number
  */
  centerPointForTouches: function(touches) {
    var touches = this._objectValues(touches),
        sumX = sumY = 0;

    for (var i=0, l=touches.length; i<l; i++) {
      var touch = touches[i];
      sumX += touch.pageX;
      sumY += touch.pageY;
    }

    var location = {
      x: sumX / touches.length,
      y: sumY / touches.length
    };

    return location;
  },

  /** @private */
  _objectValues: function(object) {
    var ret = [];

    for (var item in object ) {
      if (object.hasOwnProperty(item)) {
        ret.push(object[item]);
      }
    }

    return ret;
  },

  /**
    Allows the gesture to notify the view it's associated with of a gesture
    event.

    @private
  */
  notifyViewOfGestureEvent: function(view, eventName, data) {
    var handler = view[eventName];
    var result = true;

    if (SC.typeOf(handler) === 'function') {
      result = handler.call(view, this, data);
    }

    return result;
  },

  toString: function() {
    return SC.Gesture+'<'+SC.guidFor(this)+'>';
  },

  /** @private */
  _resetState: function() {
    this.touches = {};
    this.numberOfActiveTouches = 0;
  },

  //..............................................
  // Touch event handlers

  /** @private */
  touchStart: function(evt, view, manager) {
    var targetTouches = evt.originalEvent.targetTouches;
    var _touches = this.touches;
    var state = get(this, 'state');

    //Collect touches by their identifiers
    for (var i=0, l=targetTouches.length; i<l; i++) {
      var touch = targetTouches[i];

      if(_touches[touch.identifier] === undefined && this._numActiveTouches < get(this, 'numberOfTouches')) {
        _touches[touch.identifier] = touch;
        this._numActiveTouches++;
      }
    }

    if (this._numActiveTouches < get(this, 'numberOfTouches')) {
      set(this ,'state', SC.Gesture.WAITING_FOR_TOUCHES);

    } else {
      // Discrete gestures may skip the possible step if they're ready to begin
      if (get(this, 'gestureIsDiscrete') && this.shouldBegin()) {
        set(this, 'state', SC.Gesture.BEGAN);
        this.didBegin();
        this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Start');
      } else {
        set(this, 'state', SC.Gesture.POSSIBLE);
        this.didBecomePossible();
      }
    }

    manager.redispatchEventToView(view,'touchstart', evt);
  },

  /** @private */
  touchMove: function(evt, view, manager) {
    var state = get(this, 'state');

    if (state === SC.Gesture.WAITING_FOR_TOUCHES || state === SC.Gesture.ENDED || state === SC.Gesture.CANCELLED) {
      // Nothing to do here
      manager.redispatchEventToView(view,'touchmove', evt);
      return;
    }

    var changedTouches = evt.originalEvent.changedTouches;
    var _touches = this.touches;

    // Update touches hash
    for (var i=0, l=changedTouches.length; i<l; i++) {
      var touch = changedTouches[i];
      var identifier = changedTouches[i].identifier;

      if (_touches[identifier] !== undefined) {
          _touches[identifier] = touch;
       }
    }

    if (state === SC.Gesture.POSSIBLE) {
      if (this.shouldBegin()) {
        set(this, 'state', SC.Gesture.BEGAN);
        this.didBegin();

        // Give the gesture a chance to update its state so the view can get
        // updated information in the Start event
        this.didChange();

        this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Start');
      }

    // Discrete gestures don't fire changed events
    } else if ((state === SC.Gesture.BEGAN || state === SC.Gesture.CHANGED) && !get(this, 'gestureIsDiscrete')) {
      set(this, 'state', SC.Gesture.CHANGED);
      this.didChange();

      this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Change');

    } else {
      manager.redispatchEventToView(view,'touchmove', evt);
    }
  },

  /** @private */
  touchEnd: function(evt, view, manager) {
    // Discrete gestures need to cancel if they shouldn't end successfully
    if (get(this, 'gestureIsDiscrete')) {

      // Discrete gestures use shouldEnd to either accept or decline the gesture.
      if (this.state === SC.Gesture.BEGAN && this.shouldEnd()) {
        set(this, 'state', SC.Gesture.ENDED);
        this.didEnd();
        this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'End');
      } else {
        set(this, 'state', SC.Gesture.CANCELLED);
        this.didCancel();
        this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Cancel');
      }
    }
    else {
      if (this.state !== SC.Gesture.ENDED && this.shouldEnd()) {
        set(this, 'state', SC.Gesture.ENDED);
        this.didEnd();

        this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'End');
      }

      manager.redispatchEventToView(view,'touchend', evt);
    }

    this._resetState();
  },

  /** @private */
  touchCancel: function(evt, view, manager) {
    if (this.state !== SC.Gesture.CANCELLED) {
      this._resetState();
      set(this, 'state', SC.Gesture.CANCELLED);
      this.notifyViewOfGestureEvent(view,get(this, 'name')+'Cancel');
    } else {
      manager.redispatchEventToView(view,'touchcancel', evt);
    }
  },

});

SC.Gesture.WAITING_FOR_TOUCHES = 0;
SC.Gesture.POSSIBLE = 1;
SC.Gesture.BEGAN = 2;
SC.Gesture.CHANGED = 3;
SC.Gesture.ENDED = 4;
SC.Gesture.CANCELLED = 4;
<commit_msg>make private property protected<commit_after>// ==========================================================================
// Project:  SproutCore Runtime
// Copyright: ©2011 Strobe Inc. and contributors.
// License:   Licensed under MIT license (see license.js)
// ==========================================================================

var get = SC.get;
var set = SC.set;

var sigFigs = 100;

/**
  @class

  Base class for all gesture recognizers. Handles low-level touch and state
  management, and provides some utility methods and some required methods all
  gesture recognizers are expected to implement.

  Overview
  =========

  Gestures coalesce multiple touch events to a single higher-level gesture
  event. For example, a tap gesture recognizer takes information about a
  touchstart event, a few touchmove events, and a touchend event and uses
  some heuristics to decide whether or not that sequence of events qualifies
  as a tap event. If it does, then it will notify the view of the higher-level
  tap events.

  Gesture events follow the format:

    * [GESTURE_NAME]Start - Sent when a gesture has gathered enough information
        to begin tracking the gesture

    * [GESTURE_NAME]Change - Sent when a gesture has already started and has
        received touchmove events that cause its state to change

    * [GESTURE_NAME]End - Sent when a touchend event is received and the gesture
        recognizer decides that the gesture is finished.

    * [GESTURE_NAME]Cancel - Sent when a touchcancel event is received.

  There are two types of gesturess: Discrete and Continuous gestures. In contrast
  to continuous gestures, discrete gestures don't have any change events. Rather,
  the start and end events are the only one that gets sent.

  Usage
  =======

  While you wouldn't use SC.Gesture directly, all its subclasses have the same
  API. For example, to implement pinch on a view, you implement pinchChange and
  optionally pinchStart, pinchEnd and pinchCancel.

      var myView = SC.View.create({
        pinchStart: function(recognizer) {
          this.$().css('background','red');
        },

        pinchChange: function(recognizer) {
          var scale = recognizer.get('scale');
          this.$().css('-webkit-transform','scale3d('+scale+','+scale+',1)');
        },

        pinchEnd: function(recognizer) {
          this.$().css('background','blue');
        },

        pinchCancel: function(recognizer) {
          this.$().css('background','blue');
        }
      });

  pinchStart(), pinchEnd() and pinchCancel() will only get called once per
  gesture, but pinchChange() will get called repeatedly called every time
  one of the touches moves.

  Creating Custom Gesture Recognizers
  ======

  SC.Gesture also defines an API which its subclasses can implement to build
  custom gestures. The methods are:

    * **didBecomePossible** - Called when a gesture enters a possible state. This
        means the gesture recognizer has accepted enough touches to match
        the number of required touches. You would usually initialize your state
        in this callback.

    * **eventWasRejected** - Called if a view returns false from a gesture event.
        This callback allows you to reset internal state if the user rejects
        an event.

    * **shouldBegin** - Allows a gesture to block itself from entering a began state.
        This callback will continuously be called as touches move until it begins.

    * **shouldEnd** - Allows a gesture to block itself from entering an ended state.
        This callback gets called whenever a tracked touch gets a touchEnd event.

    * **didBegin** - Called when the gesture enters a began state. Called before the
       view receives the Start event.

    * **didChange** - Called when the gesture enters a began state, and when one of the
        touches moves. Called before the view receives the Change event.

    * **didEnd** - Called when the gesture enters an ended state. Called before the
       view receives the End event.

    * **didCancel** - Called when the gesture enters a cancelled state. Called before the
       view receives the Cancel event.

  In all the callbacks, you can use the `touches` protected property to access the
  touches hash. The touches hash is keyed on the identifiers of the touches, and the
  values are the jQuery.Event objects.

  You can also use the numberOfActiveTouches property to inspect how many touches
  are active, this is mostly useful in shouldBegin since every other callback can
  assume that there are as many active touches as specified in the
  numberOfRequiredTouches property.

  Discrete vs Continuous Gestures
  =======

  There are two main classes of gesture recognizers: Discrete and Continuous
  gestures. Discrete gestures do not get Change events sent, since they represent
  a single, instantaneous event, rather than a continuous motion. If you are
  implementing your own discrete gesture recognizer, you must set the
  isDiscreteGesture property to yes, and SC.Gesture will adapt its behavior.

  Discrete gestures use the shouldEnd callback to either accept or decline the gesture
  event. If it is delined, then the gesture will enter a Cancelled state and trigger
  the Cancel event on the view.

  @extends SC.Object
*/

SC.Gesture = SC.Object.extend(
  /** @scope SC.Gesture.prototype */{

  /**
    The current state of the gesture recognizer. This value can be any one
    of the states defined at the end of this file.

    @type Number
  */
  state: null,

  /**
    A string of the gesture recognizer's name. This value is set automatically
    but SC.Gestures when a gesture is registered.

    @type String
  */
  name: null,

  /**
    Specifies whether a gesture is discrete or continuous.

    @type Boolean
    @default false
  */
  gestureIsDiscrete: false,

  /**
    You can use the `touches` protected property to access the touches hash. The touches
    hash is keyed on the identifiers of the touches, and the values are the jQuery.Event
    objects.

    @private
    @type Hash
  */
  touches: null,

  /**
    You can also use the numberOfActiveTouches property to inspect how many touches
    are active, this is mostly useful in shouldBegin since every other callback can
    assume that there are as many active touches as specified in the
    numberOfRequiredTouches property.

    @private
    @type Number
  */
  numberOfActiveTouches: 0,

  /**
    Used to specify the number of touches required for the gesture to enter a possible
    state

    @private
    @type Number
  */
  numberOfRequiredTouches: 1,

  init: function() {
    this._super();
    this.touches = {};
  },

  //..............................................
  // Gesture Callbacks

  /** @private */
  didBecomePossible: function() { },

  /** @private */
  shouldBegin: function() {
    return true;
  },

  /** @private */
  didBegin: function() { },

  /** @private */
  didChange: function() { },

  /** @private */
  eventWasRejected: function() { },

  /** @private */
  shouldEnd: function() {
    return true;
  },

  /** @private */
  didEnd: function() { },

  /** @private */
  didCancel: function() { },

  //..............................................
  // Utilities

  /** @private */
  attemptGestureEventDelivery: function(evt, view, eventName) {
    if (this.notifyViewOfGestureEvent(view, eventName) === false) {
      this.eventWasRejected();
    } else {
      evt.preventDefault();
    }
  },

  /**
    Given two Touch objects, this method returns the distance between them.

    @return Number
  */
  distance: function(touches) {
    if (!(touches instanceof Array)) {
      touches = this._objectValues(touches);
    }

    if (touches.length !== 2) {
      throw new SC.Error('trying to get the distance between more than two points is not defined. Touches length: '+touches.length);
      return;
    }

    var first = touches[0];
    var second = touches[1];

    var x = first.pageX;
    var y = first.pageY;
    var x0 = second.pageX;
    var y0 = second.pageY;

    return Math.sqrt((x -= x0) * x + (y -= y0) * y);
  },

  /**
    Given two Touch objects, this method returns the midpoint between them.

    @return Number
  */
  centerPointForTouches: function(touches) {
    var touches = this._objectValues(touches),
        sumX = sumY = 0;

    for (var i=0, l=touches.length; i<l; i++) {
      var touch = touches[i];
      sumX += touch.pageX;
      sumY += touch.pageY;
    }

    var location = {
      x: sumX / touches.length,
      y: sumY / touches.length
    };

    return location;
  },

  /** @private */
  _objectValues: function(object) {
    var ret = [];

    for (var item in object ) {
      if (object.hasOwnProperty(item)) {
        ret.push(object[item]);
      }
    }

    return ret;
  },

  /**
    Allows the gesture to notify the view it's associated with of a gesture
    event.

    @private
  */
  notifyViewOfGestureEvent: function(view, eventName, data) {
    var handler = view[eventName];
    var result = true;

    if (SC.typeOf(handler) === 'function') {
      result = handler.call(view, this, data);
    }

    return result;
  },

  toString: function() {
    return SC.Gesture+'<'+SC.guidFor(this)+'>';
  },

  /** @private */
  _resetState: function() {
    this.touches = {};
    this.numberOfActiveTouches = 0;
  },

  //..............................................
  // Touch event handlers

  /** @private */
  touchStart: function(evt, view, manager) {
    var targetTouches = evt.originalEvent.targetTouches;
    var _touches = this.touches;
    var state = get(this, 'state');

    //Collect touches by their identifiers
    for (var i=0, l=targetTouches.length; i<l; i++) {
      var touch = targetTouches[i];

      if(_touches[touch.identifier] === undefined && this.numberOfActiveTouches < get(this, 'numberOfRequiredTouches')) {
        _touches[touch.identifier] = touch;
        this.numberOfActiveTouches++;
      }
    }

    if (this.numberOfActiveTouches < get(this, 'numberOfRequiredTouches')) {
      set(this ,'state', SC.Gesture.WAITING_FOR_TOUCHES);

    } else {
      // Discrete gestures may skip the possible step if they're ready to begin
      if (get(this, 'gestureIsDiscrete') && this.shouldBegin()) {
        set(this, 'state', SC.Gesture.BEGAN);
        this.didBegin();
        this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Start');
      } else {
        set(this, 'state', SC.Gesture.POSSIBLE);
        this.didBecomePossible();
      }
    }

    manager.redispatchEventToView(view,'touchstart', evt);
  },

  /** @private */
  touchMove: function(evt, view, manager) {
    var state = get(this, 'state');

    if (state === SC.Gesture.WAITING_FOR_TOUCHES || state === SC.Gesture.ENDED || state === SC.Gesture.CANCELLED) {
      // Nothing to do here
      manager.redispatchEventToView(view,'touchmove', evt);
      return;
    }

    var changedTouches = evt.originalEvent.changedTouches;
    var _touches = this.touches;

    // Update touches hash
    for (var i=0, l=changedTouches.length; i<l; i++) {
      var touch = changedTouches[i];
      var identifier = changedTouches[i].identifier;

      if (_touches[identifier] !== undefined) {
          _touches[identifier] = touch;
       }
    }

    if (state === SC.Gesture.POSSIBLE) {
      if (this.shouldBegin()) {
        set(this, 'state', SC.Gesture.BEGAN);
        this.didBegin();

        // Give the gesture a chance to update its state so the view can get
        // updated information in the Start event
        this.didChange();

        this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Start');
      }

    // Discrete gestures don't fire changed events
    } else if ((state === SC.Gesture.BEGAN || state === SC.Gesture.CHANGED) && !get(this, 'gestureIsDiscrete')) {
      set(this, 'state', SC.Gesture.CHANGED);
      this.didChange();

      this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Change');

    } else {
      manager.redispatchEventToView(view,'touchmove', evt);
    }
  },

  /** @private */
  touchEnd: function(evt, view, manager) {
    // Discrete gestures need to cancel if they shouldn't end successfully
    if (get(this, 'gestureIsDiscrete')) {

      // Discrete gestures use shouldEnd to either accept or decline the gesture.
      if (this.state === SC.Gesture.BEGAN && this.shouldEnd()) {
        set(this, 'state', SC.Gesture.ENDED);
        this.didEnd();
        this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'End');
      } else {
        set(this, 'state', SC.Gesture.CANCELLED);
        this.didCancel();
        this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'Cancel');
      }
    }
    else {
      if (this.state !== SC.Gesture.ENDED && this.shouldEnd()) {
        set(this, 'state', SC.Gesture.ENDED);
        this.didEnd();

        this.attemptGestureEventDelivery(evt, view, get(this, 'name')+'End');
      }

      manager.redispatchEventToView(view,'touchend', evt);
    }

    this._resetState();
  },

  /** @private */
  touchCancel: function(evt, view, manager) {
    if (this.state !== SC.Gesture.CANCELLED) {
      this._resetState();
      set(this, 'state', SC.Gesture.CANCELLED);
      this.notifyViewOfGestureEvent(view,get(this, 'name')+'Cancel');
    } else {
      manager.redispatchEventToView(view,'touchcancel', evt);
    }
  },

});

SC.Gesture.WAITING_FOR_TOUCHES = 0;
SC.Gesture.POSSIBLE = 1;
SC.Gesture.BEGAN = 2;
SC.Gesture.CHANGED = 3;
SC.Gesture.ENDED = 4;
SC.Gesture.CANCELLED = 4;
<|endoftext|>


<commit_before>'use strict'

var React = require('react')
var ReactBootstrap = require('react-bootstrap')
var _ = require('lodash')
var Glyphicon = ReactBootstrap.Glyphicon
var FormControl = ReactBootstrap.FormControl
var FormGroup = ReactBootstrap.FormGroup
var ControlLabel = ReactBootstrap.ControlLabel
var Button = ReactBootstrap.Button
var Panel = ReactBootstrap.Panel

var ObserveFormControl = React.createClass({
  propTypes: {
    measurement: React.PropTypes.object,
    syncMeasurement: React.PropTypes.func.isRequired,
    onObservationChange: React.PropTypes.func.isRequired,
    onActionChange: React.PropTypes.func.isRequired,
    attributeId: React.PropTypes.number,
    disabled: React.PropTypes.bool
  },
  getInitialState: function () {
    return {
      dirtyObservation: false,
      saveBtnDisabled: true,
      observations: '',
      action: ''
    }
  },
  isObserveDirty: function (props) {
    if (props.measurement) {
      if (props.measurement.observations === this.state.observations) {
        return false
      } else if (!props.measurement.observations && !this.state.observations) {
        return false
      } else {
        return true
      }
    } else {
      return !!this.state.observations
    }
  },
  isActionDirty: function (props) {
    if (props.measurement) {
      if (props.measurement.action === this.state.action) {
        return false
      } else if (!props.measurement.action && !this.state.action) {
        return false
      } else {
        return true
      }
    } else {
      return !!this.state.action
    }
  },
  isSaveDisabled: function (props, dirty) {
    return !(!!props.measurement === true && dirty === true)
  },
  componentWillMount: function () {
    this.handleObserveDebounced = _.debounce(function () {
      this.props.onObservationChange.apply(this, [this.state.observations])
    }, 750)
    this.handleActionDebounced = _.debounce(function () {
      this.props.onActionChange.apply(this, [this.state.action])
    }, 750)
    var saveDisabled = this.isSaveDisabled(this.props, false)
    this.setState({
      observations: (this.props.measurement) ? this.props.measurement.observations : '',
      action: (this.props.measurement) ? this.props.measurement.action : '',
      saveBtnDisabled: saveDisabled,
      dirtyObservation: false
    })
  },
  componentWillReceiveProps: function (nextProps) {
    var dirty = this.isObserveDirty(nextProps) || this.isActionDirty(nextProps)
    var saveDisabled = this.isSaveDisabled(nextProps, dirty)
    if (nextProps.attributeId && nextProps.attributeId !== this.props.attributeId) {
      this.setState({
        observations: (nextProps.measurement) ? nextProps.measurement.observations : '',
        action: (nextProps.measurement) ? nextProps.measurement.action : '',
        saveBtnDisabled: true,
        dirtyObservation: false
      })
    } else {
      this.setState({saveBtnDisabled: saveDisabled, dirtyObservation: dirty})
    }
  },
  onObservationChange: function (event) {
    this.setState({ observations: event.target.value })
    this.handleObserveDebounced()
  },
  onActionChange: function (event) {
    this.setState({ action: event.target.value })
    this.handleActionDebounced()
  },
  handleSave: function () {
    if (this.state.saveBtnDisabled === false) {
      var postData = {
        id: this.props.measurement.id,
        assessment: this.props.measurement.assessment,
        rating: this.props.measurement.rating,
        targetRating: this.props.measurement.targetRating,
        observations: this.state.observations,
        action: this.state.action
      }
      this.props.syncMeasurement(postData)
    }
  },
  render: function () {
    var syncStatus =
    if (this.state.dirtyObservation === true) {
      if (!this.props.measurement) {
        syncStatus = (

            <Glyphicon glyph='info-sign' />
            Select a rating below to save this comment and complete the form.

        )
      }
    } else {
      if (this.props.measurement &&
        (this.props.measurement.observations || this.props.measurement.action)) {
        syncStatus = (

            <Glyphicon glyph='saved' />

        )
      }
    }

    return (

        <FormGroup>
          <ControlLabel>Observations</ControlLabel>
          {this.props.disabled
            ? <Panel bsStyle='default'><FormControl.Static className='like-pre'>{this.state.observations}</FormControl.Static></Panel>
            : <FormControl componentClass='textarea' rows='3'
              placeholder='Discuss your current practices and capture some notes.'
              ref='observeInput'
              value={this.state.observations}
              onChange={this.onObservationChange}
            />}
        </FormGroup>
        <FormGroup>
          <ControlLabel>Actions</ControlLabel>
          {this.props.disabled
            ? <Panel bsStyle='default'><FormControl.Static className='like-pre'>{this.state.action}</FormControl.Static></Panel>
            : <FormControl componentClass='textarea' rows='3'
              placeholder='Record actions you can take to improve your current practices.'
              ref='actionInput'
              value={this.state.action}
              onChange={this.onActionChange}
            />}
        </FormGroup>
        {this.props.disabled
          ? undefined
          : <FormGroup>
            <Button ref='obsSaveBtn'
              disabled={this.state.saveBtnDisabled}
              bsStyle='primary'
              bsSize='xsmall'
              onClick={this.handleSave}>
              Save
            </Button>
            {syncStatus}
          </FormGroup>
        }

    )
  }
})

module.exports = ObserveFormControl
<commit_msg>make it clearer that the save button is disabled<commit_after>'use strict'

var React = require('react')
var ReactBootstrap = require('react-bootstrap')
var _ = require('lodash')
var Glyphicon = ReactBootstrap.Glyphicon
var FormControl = ReactBootstrap.FormControl
var FormGroup = ReactBootstrap.FormGroup
var ControlLabel = ReactBootstrap.ControlLabel
var Button = ReactBootstrap.Button
var Panel = ReactBootstrap.Panel

var ObserveFormControl = React.createClass({
  propTypes: {
    measurement: React.PropTypes.object,
    syncMeasurement: React.PropTypes.func.isRequired,
    onObservationChange: React.PropTypes.func.isRequired,
    onActionChange: React.PropTypes.func.isRequired,
    attributeId: React.PropTypes.number,
    disabled: React.PropTypes.bool
  },
  getInitialState: function () {
    return {
      dirtyObservation: false,
      saveBtnDisabled: true,
      observations: '',
      action: ''
    }
  },
  isObserveDirty: function (props) {
    if (props.measurement) {
      if (props.measurement.observations === this.state.observations) {
        return false
      } else if (!props.measurement.observations && !this.state.observations) {
        return false
      } else {
        return true
      }
    } else {
      return !!this.state.observations
    }
  },
  isActionDirty: function (props) {
    if (props.measurement) {
      if (props.measurement.action === this.state.action) {
        return false
      } else if (!props.measurement.action && !this.state.action) {
        return false
      } else {
        return true
      }
    } else {
      return !!this.state.action
    }
  },
  isSaveDisabled: function (props, dirty) {
    return !(!!props.measurement === true && dirty === true)
  },
  componentWillMount: function () {
    this.handleObserveDebounced = _.debounce(function () {
      this.props.onObservationChange.apply(this, [this.state.observations])
    }, 750)
    this.handleActionDebounced = _.debounce(function () {
      this.props.onActionChange.apply(this, [this.state.action])
    }, 750)
    var saveDisabled = this.isSaveDisabled(this.props, false)
    this.setState({
      observations: (this.props.measurement) ? this.props.measurement.observations : '',
      action: (this.props.measurement) ? this.props.measurement.action : '',
      saveBtnDisabled: saveDisabled,
      dirtyObservation: false
    })
  },
  componentWillReceiveProps: function (nextProps) {
    var dirty = this.isObserveDirty(nextProps) || this.isActionDirty(nextProps)
    var saveDisabled = this.isSaveDisabled(nextProps, dirty)
    if (nextProps.attributeId && nextProps.attributeId !== this.props.attributeId) {
      this.setState({
        observations: (nextProps.measurement) ? nextProps.measurement.observations : '',
        action: (nextProps.measurement) ? nextProps.measurement.action : '',
        saveBtnDisabled: true,
        dirtyObservation: false
      })
    } else {
      this.setState({saveBtnDisabled: saveDisabled, dirtyObservation: dirty})
    }
  },
  onObservationChange: function (event) {
    this.setState({ observations: event.target.value })
    this.handleObserveDebounced()
  },
  onActionChange: function (event) {
    this.setState({ action: event.target.value })
    this.handleActionDebounced()
  },
  handleSave: function () {
    if (this.state.saveBtnDisabled === false) {
      var postData = {
        id: this.props.measurement.id,
        assessment: this.props.measurement.assessment,
        rating: this.props.measurement.rating,
        targetRating: this.props.measurement.targetRating,
        observations: this.state.observations,
        action: this.state.action
      }
      this.props.syncMeasurement(postData)
    }
  },
  render: function () {
    var syncStatus =
    if (this.state.dirtyObservation === true) {
      if (!this.props.measurement) {
        syncStatus = (

            <Glyphicon glyph='info-sign' />
            Select a rating below to save this comment and complete the form.

        )
      }
    } else {
      if (this.props.measurement &&
        (this.props.measurement.observations || this.props.measurement.action)) {
        syncStatus = (

            <Glyphicon glyph='saved' />

        )
      }
    }

    return (

        <FormGroup>
          <ControlLabel>Observations</ControlLabel>
          {this.props.disabled
            ? <Panel bsStyle='default'><FormControl.Static className='like-pre'>{this.state.observations}</FormControl.Static></Panel>
            : <FormControl componentClass='textarea' rows='3'
              placeholder='Discuss your current practices and capture some notes.'
              ref='observeInput'
              value={this.state.observations}
              onChange={this.onObservationChange}
            />}
        </FormGroup>
        <FormGroup>
          <ControlLabel>Actions</ControlLabel>
          {this.props.disabled
            ? <Panel bsStyle='default'><FormControl.Static className='like-pre'>{this.state.action}</FormControl.Static></Panel>
            : <FormControl componentClass='textarea' rows='3'
              placeholder='Record actions you can take to improve your current practices.'
              ref='actionInput'
              value={this.state.action}
              onChange={this.onActionChange}
            />}
        </FormGroup>
        {this.props.disabled ||
          <FormGroup>
            <Button ref='obsSaveBtn'
              disabled={this.state.saveBtnDisabled}
              bsStyle={this.state.saveBtnDisabled ? 'default' : 'primary'}
              bsSize='xsmall'
              onClick={this.handleSave}>
              Save
            </Button>
            {syncStatus}
          </FormGroup>
        }

    )
  }
})

module.exports = ObserveFormControl
<|endoftext|>


<commit_before>var app = require('ampersand-app');
var _ = require('lodash');
var config = require('clientconfig');
var Router = require('./router');
var MainView = require('./views/main');
var Me = require('./models/me');
var People = require('./models/persons');
var domReady = require('domready');

// attach our app to `window` so we can
// easily access it from the console.
window.app = app;

// Extends our main app singleton
app.extend({
    me: new Me(),
    people: new People(),
    router: new Router(),
    // This is where it all starts
    init: function() {
        // Create and attach our main view
        this.mainView = new MainView({
            model: this.me,
            el: document.body
        });

        // this kicks off our backbutton tracking (browser history)
        // and will cause the first matching handler in the router
        // to fire.
        this.router.history.start({ pushState: true });
    },
    // This is a helper for navigating around the app.
    // this gets called by a global click handler that handles
    // all the <a> tags in the app.
    // it expects a url pathname for example: "/costello/settings"
    navigate: function(page) {
        var url = (page.charAt(0) === '/') ? page.slice(1) : page;
        this.router.history.navigate(url, {
            trigger: true
        });
    }
});

// run it on domReady
domReady(_.bind(app.init, app));
<commit_msg>Fixed app<commit_after>var app = require('ampersand-app');
var _ = require('lodash');
var config = require('clientconfig');
var Router = require('./router');
var MainView = require('./views/main');
var domReady = require('domready');

// attach our app to `window` so we can
// easily access it from the console.
window.app = app;

// Extends our main app singleton
app.extend({
    router: new Router(),
    // This is where it all starts
    init: function() {
        // Create and attach our main view
        this.mainView = new MainView({
            el: document.body
        });

        // this kicks off our backbutton tracking (browser history)
        // and will cause the first matching handler in the router
        // to fire.
        this.router.history.start({ pushState: true });
    },
    // This is a helper for navigating around the app.
    // this gets called by a global click handler that handles
    // all the <a> tags in the app.
    // it expects a url pathname for example: "/costello/settings"
    navigate: function(page) {
        var url = (page.charAt(0) === '/') ? page.slice(1) : page;
        this.router.history.navigate(url, {
            trigger: true
        });
    }
});

// run it on domReady
domReady(_.bind(app.init, app));
<|endoftext|>


<commit_before>// Use express module for RESTful API
var express = require('express');
var app = express();
var bodyParser = require("body-parser");

// Set port
app.set('port', process.env.PORT || 3000);

// Configure express to use body-parser as middle-ware.
app.use(bodyParser.json());   // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true }));   // for parsing application/x-www-form-urlencoded

// If i2c module is not available just ignore i2c
var include_i2c = true;
try {
  var i2c = require('i2c');
} catch(err) {
  include_i2c = false;
  console.log('Module i2c is not installed. Proceeding without ...');
}

if (include_i2c) {
  var address = 0x40;
  var wire = new i2c(address, {device: '/dev/i2c-1'}); // point to your i2c address, debug provides REPL interface
}

// Log all requests
app.use(function(req, res, next){
  console.log('[' + Date.now() + '] Request received');
  console.log('    url: ' + req.originalUrl);
  console.log('    params: ' + req.params);
  console.log('    body: ' + req.body);
  next();
});

// @GET
// returns { "message": "Hello and welcome to the Thumper Control RESTful API" }
app.get('/', function (req, res){
  res.setHeader('Content-Type', 'application/json');
  res.send(JSON.stringify({ message: "Hello and welcome to the Thumper Control RESTful API" }));
});

// @GET
// returns { "battery_voltage": "7.88" }
app.get('/batteryvoltage', function (req, res){
  res.setHeader('Content-Type', 'application/json');
  var voltage = (7 + (Math.random() * ((11 - 7) + 1))).toFixed(2);;
  res.send(JSON.stringify({ batteryvoltage: voltage }));
});

// @GET
// returns { "number_of_string": "2", "string_ids": [0,1], "status":"success" }
app.get('/neopixels/strings', function (req, res) {
  res.setHeader('Content-Type', 'application/json');

  if (include_i2c) {
    wire.readByte(function(err, nos) {
      if (err) {
        console.log('Could not read from i2c: ' + err);
        res.send(JSON.stringify({ number_of_string: "", string_ids: [], status: "failed" }));
      } else {
        var ids = new Array(nos);
        for (i = 0; i < nos; i++) { ids[i] = i; }
        res.send(JSON.stringify({ number_of_string: nos, string_ids: ids, status: "success" }));
      }
    });
  } else {
    res.send(JSON.stringify({ number_of_string: "2", string_ids: [0, 1], status: "success" }));
  }
});

// Custom 404 (needs to be last in line of routes)
app.use(function(req, res, next){
  res.type('text/plain');
  res.status(404);
  res.send('404 - Not Found');
});

// Custom 500
app.use(function(err, req, res, next){
  console.error(err.stack);
  res.type('text/plain');
  res.status(500);
  res.send('500 - Internal server error');
});

// Server process
app.listen(app.get('port'), function(){
  console.log('Express app started on http://localhost:' + app.get('port'));
  console.log('Press CTRL-c to kill');
});
<commit_msg>Add neopixel string get route for info on particular string<commit_after>// Use express module for RESTful API
var express = require('express');
var app = express();
var bodyParser = require("body-parser");

// Set port
app.set('port', process.env.PORT || 3000);

// Configure express to use body-parser as middle-ware.
app.use(bodyParser.json());   // for parsing application/json
app.use(bodyParser.urlencoded({ extended: true }));   // for parsing application/x-www-form-urlencoded

// If i2c module is not available just ignore i2c
var include_i2c = true;
try {
  var i2c = require('i2c');
} catch(err) {
  include_i2c = false;
  console.log('Module i2c is not installed. Proceeding without ...');
}

if (include_i2c) {
  var address = 0x40;
  var wire = new i2c(address, {device: '/dev/i2c-1'}); // point to your i2c address, debug provides REPL interface
}

// Log all requests
app.use(function(req, res, next){
  console.log('[' + Date.now() + '] Request received');
  console.log('    url: ' + req.originalUrl);
  console.log('    params: ' + req.params);
  console.log('    body: ' + req.body);
  next();
});

// @GET
// returns { "message": "Hello and welcome to the Thumper Control RESTful API" }
app.get('/', function (req, res){
  res.setHeader('Content-Type', 'application/json');
  res.send(JSON.stringify({ message: "Hello and welcome to the Thumper Control RESTful API" }));
});

// @GET
// returns { "battery_voltage": "7.88" }
app.get('/batteryvoltage', function (req, res){
  res.setHeader('Content-Type', 'application/json');
  var voltage = (7 + (Math.random() * ((11 - 7) + 1))).toFixed(2);;
  res.send(JSON.stringify({ batteryvoltage: voltage }));
});

// @GET
// returns { "number_of_string": "2", "string_ids": [0,1], "status":"success" }
app.get('/neopixels/strings', function (req, res) {
  res.setHeader('Content-Type', 'application/json');

  if (include_i2c) {
    wire.readByte(function(err, nos) {
      if (err) {
        console.log('Could not read from i2c: ' + err);
        res.send(JSON.stringify({ number_of_string: "", string_ids: [], status: "failed" }));
      } else {
        var ids = new Array(nos);
        for (i = 0; i < nos; i++) { ids[i] = i; }
        res.send(JSON.stringify({ number_of_string: nos, string_ids: ids, status: "success" }));
      }
    });
  } else {
    res.send(JSON.stringify({ number_of_string: "2", string_ids: [0, 1], status: "success" }));
  }
});

// @GET
// returns { "string_id": "1", "number_of_pixels": "8" }
app.get('/neopixels/strings/:id', function (req, res) {
  var id = req.params.id    // id is currently unused (i2c function not implemented yet)
  res.setHeader('Content-Type', 'application/json');
  res.send(JSON.stringify({ string_id: id, number_of_pixels: "8" }));
});

// Custom 404 (needs to be last in line of routes)
app.use(function(req, res, next){
  res.type('text/plain');
  res.status(404);
  res.send('404 - Not Found');
});

// Custom 500
app.use(function(err, req, res, next){
  console.error(err.stack);
  res.type('text/plain');
  res.status(500);
  res.send('500 - Internal server error');
});

// Server process
app.listen(app.get('port'), function(){
  console.log('Express app started on http://localhost:' + app.get('port'));
  console.log('Press CTRL-c to kill');
});
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Component.extend({

  setup: function () {
    this.$('.ui.dropdown').dropdown({
      apiSettings: {
        url: '/api/v1/area/?navn=~{query}',
        onResponse: function (response) {
          var documents = Ember.ArrayProxy.create({content: Ember.A(response.documents)});
          var results = documents.map(function (item, index, enumerable) {
            return {name: item.navn, value: item._id};
          });
          return {success: true, results: results};
        }
      },
      onChange: Ember.run.bind(this, this.onChange)
    });
  }.on('didInsertElement'),

  onChange: function (value, text, $choice) {
    this.sendAction('action', value);
    this.$('input[type="text"]').val('');
    this.$('div.text').text('');
  }
});
<commit_msg>Only get områder with status set to `Offentlig`<commit_after>import Ember from 'ember';

export default Ember.Component.extend({

  setup: function () {
    this.$('.ui.dropdown').dropdown({
      apiSettings: {
        url: '/api/v1/area/?navn=~{query}&status=Offentlig',
        onResponse: function (response) {
          var documents = Ember.ArrayProxy.create({content: Ember.A(response.documents)});
          var results = documents.map(function (item, index, enumerable) {
            return {name: item.navn, value: item._id};
          });
          return {success: true, results: results};
        }
      },
      onChange: Ember.run.bind(this, this.onChange)
    });
  }.on('didInsertElement'),

  onChange: function (value, text, $choice) {
    this.sendAction('action', value);
    this.$('input[type="text"]').val('');
    this.$('div.text').text('');
  }
});
<|endoftext|>


<commit_before>
// Configuration

export const MAX_DAILY_HEARTS_FROM_TABS = 150

// Strings passed from server-side. Do not change.

export const WIDGET_TYPE_BOOKMARKS = 'bookmarks'
export const WIDGET_TYPE_CLOCK = 'clock'
export const WIDGET_TYPE_NOTES = 'notes'
export const WIDGET_TYPE_SEARCH = 'search'
export const WIDGET_TYPE_TODOS = 'todos'

export const USER_BACKGROUND_OPTION_CUSTOM = 'custom'
export const USER_BACKGROUND_OPTION_COLOR = 'color'
export const USER_BACKGROUND_OPTION_PHOTO = 'photo'
export const USER_BACKGROUND_OPTION_DAILY = 'daily'

// localStorage keys. Do not change, or it may
// break storage for existing users.

export const STORAGE_KEY_USERNAME = 'tab.user.username'
export const STORAGE_BACKGROUND_OPTION = 'tab.user.background.option'
export const STORAGE_BACKGROUND_CUSTOM_IMAGE = 'tab.user.background.customImage'
export const STORAGE_BACKGROUND_COLOR = 'tab.user.background.color'
export const STORAGE_BACKGROUND_IMAGE_URL = 'tab.user.background.imageURL'
export const STORAGE_LOCATION_COUNTRY_ISO_CODE = 'tab.clientLocation.countryIsoCode'
export const STORAGE_LOCATION_IS_IN_EU = 'tab.clientLocation.isInEU'
export const STORAGE_LOCATION_QUERY_TIME = 'tab.clientLocation.queryTime'

// An ISO timestamp: the time of the first tab opened in the
// most recent day (UTC day) the user was active.
export const STORAGE_TABS_LAST_TAB_OPENED_DATE = 'tab.user.lastTabDay.date'

// The count of tabs opened in the most recent day the user was
// active. It could be today (ongoing) or a day in the past.
export const STORAGE_TABS_RECENT_DAY_COUNT = 'tab.user.lastTabDay.count'

export const STORAGE_REFERRAL_DATA_REFERRING_USER = 'tab.referralData.referringUser'
export const STORAGE_REFERRAL_DATA_REFERRING_CHANNEL = 'tab.referralData.referringChannel'

// Data privacy consent flags
export const STORAGE_NEW_CONSENT_DATA_EXISTS = 'tab.consentData.newConsentDataExists'
<commit_msg>Organize localStorage keys<commit_after>
/**
  Configuration
**/

export const MAX_DAILY_HEARTS_FROM_TABS = 150

/**
  Strings passed from server-side. Do not change.
**/

export const WIDGET_TYPE_BOOKMARKS = 'bookmarks'
export const WIDGET_TYPE_CLOCK = 'clock'
export const WIDGET_TYPE_NOTES = 'notes'
export const WIDGET_TYPE_SEARCH = 'search'
export const WIDGET_TYPE_TODOS = 'todos'

export const USER_BACKGROUND_OPTION_CUSTOM = 'custom'
export const USER_BACKGROUND_OPTION_COLOR = 'color'
export const USER_BACKGROUND_OPTION_PHOTO = 'photo'
export const USER_BACKGROUND_OPTION_DAILY = 'daily'

/**
  localStorage keys. Do not change, or it may
  break storage for existing users.
**/

// tab.user: user attributes persisted server-side
export const STORAGE_KEY_USERNAME = 'tab.user.username'
export const STORAGE_BACKGROUND_COLOR = 'tab.user.background.color'
export const STORAGE_BACKGROUND_CUSTOM_IMAGE = 'tab.user.background.customImage'
export const STORAGE_BACKGROUND_IMAGE_URL = 'tab.user.background.imageURL'
export const STORAGE_BACKGROUND_OPTION = 'tab.user.background.option'

// An ISO timestamp: the time of the first tab opened in the
// most recent day (UTC day) the user was active.
export const STORAGE_TABS_LAST_TAB_OPENED_DATE = 'tab.user.lastTabDay.date'

// The count of tabs opened in the most recent day the user was
// active. It could be today (ongoing) or a day in the past.
export const STORAGE_TABS_RECENT_DAY_COUNT = 'tab.user.lastTabDay.count'

// tab.clientLocation: general location info, stored client-side only
export const STORAGE_LOCATION_COUNTRY_ISO_CODE = 'tab.clientLocation.countryIsoCode'
export const STORAGE_LOCATION_IS_IN_EU = 'tab.clientLocation.isInEU'
export const STORAGE_LOCATION_QUERY_TIME = 'tab.clientLocation.queryTime'

// tab.referralData: referral data set on homepage
export const STORAGE_REFERRAL_DATA_REFERRING_USER = 'tab.referralData.referringUser'
export const STORAGE_REFERRAL_DATA_REFERRING_CHANNEL = 'tab.referralData.referringChannel'

// tab.consentData: values related to data privacy consent choices,
//   like GDPR
export const STORAGE_NEW_CONSENT_DATA_EXISTS = 'tab.consentData.newConsentDataExists'
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

/**
 * @namespace
 * @name sap.m.delegate.ValueState
 * @private
 */

sap.ui.define(['jquery.sap.global', 'sap/ui/Device', 'sap/ui/base/Object', 'sap/ui/core/ValueStateSupport', 'sap/ui/core/Popup'],
	function(jQuery, Device, BaseObject, ValueStateSupport, Popup) {
		"use strict";

		/**
		 * Creates a sap.m.delegate.ValueState delegate that can be attached to controls that require
		 * a value state message popup.
		 *
		 * @example
Example of usage:

		 *
		 *

		 *	MyControl.prototype.init = function() {
		 *		this._oValueStateMessage = new ValueStateMessage(this);
		 *	};
		 *
		 *  MyControl.prototype.onfocusin = function(oEvent) {
		 *  	this._oValueStateMessage.open();
		 *  };
		 *
		 *  MyControl.prototype.onfocusout = function(oEvent) {
		 *  	this._oValueStateMessage.close();
		 *  };
		 *
		 *	MyControl.prototype.exit = function() {
		 *
		 *		if (this._oValueStateMessage) {
		 *			this._oValueStateMessage.destroy();
		 *		}
		 *
		 *		this._oValueStateMessage = null;
		 *	};
		 *

		 *
		 * Preconditions:
		 * The given control must implement the following interface:
		 *
		 *
		 * .getValueState()
		 * .getValueStateText()
		 * .getFocusDomRef()
		 * .getDomRefForValueStateMessage()
		 *
		 *
		 * @extends sap.ui.base.Object
		 * @param {sap.ui.core.Control} oControl The control for which this value state message is the delegate
		 * @constructor
		 * @private
		 * @alias sap.m.delegate.ValueState
		 * @version 1.42
		 * @author SAP SE
		 */
		var ValueStateMessage = BaseObject.extend("sap.m.delegate.ValueState", /** @lends sap.m.delegate.ValueState.prototype */ {
			constructor: function(oControl) {
				BaseObject.apply(this, arguments);
				this._oControl = oControl;
				this._oPopup = null;
			}
		});

		/**
		 * Opens value state message popup.
		 *
		 * @protected
		 */
		ValueStateMessage.prototype.open = function() {
			var oControl = this._oControl,
				oPopup = this.getPopup(),
				oMessageDomRef = this.createDom(),
				mDock = Popup.Dock,
				$Control = jQuery(oControl.getDomRefForValueStateMessage());

			if (!oControl || !oPopup || !oMessageDomRef) {
				return;
			}

			oPopup.setContent(oMessageDomRef);
			oPopup.close(0);
			oPopup.open(
				this.getOpenDuration(),
				mDock.BeginTop,
				mDock.BeginBottom,
				oControl.getDomRefForValueStateMessage(),
				null,
				null,
				Device.system.phone ? true : Popup.CLOSE_ON_SCROLL
			);

			var $DomRef = jQuery(oMessageDomRef);

			// check whether popup is below or above the input
			if ($Control.offset().top < $DomRef.offset().top) {
				$DomRef.addClass("sapMValueStateMessageBottom");
			} else {
				$DomRef.addClass("sapMValueStateMessageTop");
			}

			$Control.addAriaDescribedBy(this.getId());
		};

		/**
		 * Closes value state message popup.
		 *
		 * @protected
		 */
		ValueStateMessage.prototype.close = function() {
			var oControl = this._oControl,
				oPopup = this.getPopup();

			if (oPopup) {
				oPopup.close(0);
			}

			if (oControl) {
				jQuery(oControl.getFocusDomRef()).removeAriaDescribedBy(this.getId());
			}
		};

		ValueStateMessage.prototype.getId = function() {
			var oControl = this._oControl;

			if (!oControl) {
				return "";
			}

			return (typeof oControl.getValueStateMessageId === "function") ? oControl.getValueStateMessageId() : oControl.getId() + "-message";
		};

		ValueStateMessage.prototype.getOpenDuration = function() {
			var oControl = this._oControl;

			if (!oControl) {
				return 0;
			}

			return (oControl.iOpenMessagePopupDuration === undefined) ? 0 : oControl.iOpenMessagePopupDuration;
		};

		/**
		 * Creates the value state message popup.
		 *
		 * @param {string} [sID] ID for the new message popup; generated automatically if no ID is given
		 * @returns {sap.ui.core.Popup} The popup instance object
		 */
		ValueStateMessage.prototype.createPopup = function(sID) {
			sID = sID || this.getId();

			if (this._oPopup) {
				return this._oPopup;
			}

			this._oPopup = new Popup(document.createElement("span"), false, false, false);
			this._oPopup.attachClosed(function() {
				jQuery.sap.byId(sID).remove();
			});

			return this._oPopup;
		};

		/**
		 * Gets the value state message popup, creating it if necessary by calling
		 * the createPopup() method.
		 *
		 * @returns {sap.ui.core.Popup} The popup instance object
		 */
		ValueStateMessage.prototype.getPopup = function() {

			if (!this._oControl) {
				return null;
			}

			return this.createPopup();
		};

		/**
		 * Creates the value state message HTML elements.
		 *
		 * @returns {object} The value state message root HTML element
		 */
		ValueStateMessage.prototype.createDom = function() {
			var oControl = this._oControl;

			if (!oControl) {
				return null;
			}

			var sState = oControl.getValueState(),
				sText = oControl.getValueStateText() || sap.ui.core.ValueStateSupport.getAdditionalText(oControl),
				sClass = "sapMValueStateMessage sapMValueStateMessage" + sState,
				oRB = sap.ui.getCore().getLibraryResourceBundle("sap.m");

			if (sState === sap.ui.core.ValueState.Success) {
				sClass = "sapUiInvisibleText";
				sText = "";
			}

			var sID = this.getId();
			var oMessageDomRef = document.createElement("div");
			oMessageDomRef.id = sID;
			oMessageDomRef.className = sClass;
			oMessageDomRef.setAttribute("role", "tooltip");
			oMessageDomRef.setAttribute("aria-live", "assertive");

			var oAccDomRef = document.createElement("span");
			oAccDomRef.id = sID + "hidden";
			oAccDomRef.className = "sapUiHidden";
			oAccDomRef.setAttribute("aria-hidden", "true");
			oAccDomRef.appendChild(document.createTextNode(oRB.getText("INPUTBASE_VALUE_STATE_" + sState.toUpperCase())));

			oMessageDomRef.appendChild(oAccDomRef);
			oMessageDomRef.appendChild(document.createTextNode(sText));
			return oMessageDomRef;
		};

		ValueStateMessage.prototype.destroy = function() {

			if (this._oPopup) {
				this._oPopup.destroy();
				this._oPopup = null;
			}

			this._oControl = null;
		};

		return ValueStateMessage;
	});<commit_msg>[FIX] sap.m.delegate.ValueState: always set aria-describedby attribute to the focusable element<commit_after>/*!
 * ${copyright}
 */

/**
 * @namespace
 * @name sap.m.delegate.ValueState
 * @private
 */

sap.ui.define(['jquery.sap.global', 'sap/ui/Device', 'sap/ui/base/Object', 'sap/ui/core/ValueStateSupport', 'sap/ui/core/Popup'],
	function(jQuery, Device, BaseObject, ValueStateSupport, Popup) {
		"use strict";

		/**
		 * Creates a sap.m.delegate.ValueState delegate that can be attached to controls that require
		 * a value state message popup.
		 *
		 * @example
Example of usage:

		 *
		 *

		 *	MyControl.prototype.init = function() {
		 *		this._oValueStateMessage = new ValueStateMessage(this);
		 *	};
		 *
		 *  MyControl.prototype.onfocusin = function(oEvent) {
		 *  	this._oValueStateMessage.open();
		 *  };
		 *
		 *  MyControl.prototype.onfocusout = function(oEvent) {
		 *  	this._oValueStateMessage.close();
		 *  };
		 *
		 *	MyControl.prototype.exit = function() {
		 *
		 *		if (this._oValueStateMessage) {
		 *			this._oValueStateMessage.destroy();
		 *		}
		 *
		 *		this._oValueStateMessage = null;
		 *	};
		 *

		 *
		 * Preconditions:
		 * The given control must implement the following interface:
		 *
		 *
		 * .getValueState()
		 * .getValueStateText()
		 * .getFocusDomRef()
		 * .getDomRefForValueStateMessage()
		 *
		 *
		 * @extends sap.ui.base.Object
		 * @param {sap.ui.core.Control} oControl The control for which this value state message is the delegate
		 * @constructor
		 * @private
		 * @alias sap.m.delegate.ValueState
		 * @version 1.42
		 * @author SAP SE
		 */
		var ValueStateMessage = BaseObject.extend("sap.m.delegate.ValueState", /** @lends sap.m.delegate.ValueState.prototype */ {
			constructor: function(oControl) {
				BaseObject.apply(this, arguments);
				this._oControl = oControl;
				this._oPopup = null;
			}
		});

		/**
		 * Opens value state message popup.
		 *
		 * @protected
		 */
		ValueStateMessage.prototype.open = function() {
			var oControl = this._oControl,
				oPopup = this.getPopup(),
				oMessageDomRef = this.createDom(),
				mDock = Popup.Dock,
				$Control = jQuery(oControl.getDomRefForValueStateMessage());

			if (!oControl || !oPopup || !oMessageDomRef) {
				return;
			}

			oPopup.setContent(oMessageDomRef);
			oPopup.close(0);
			oPopup.open(
				this.getOpenDuration(),
				mDock.BeginTop,
				mDock.BeginBottom,
				oControl.getDomRefForValueStateMessage(),
				null,
				null,
				Device.system.phone ? true : Popup.CLOSE_ON_SCROLL
			);

			var $DomRef = jQuery(oMessageDomRef);

			// check whether popup is below or above the input
			if ($Control.offset().top < $DomRef.offset().top) {
				$DomRef.addClass("sapMValueStateMessageBottom");
			} else {
				$DomRef.addClass("sapMValueStateMessageTop");
			}

			jQuery(oControl.getFocusDomRef()).addAriaDescribedBy(this.getId());
		};

		/**
		 * Closes value state message popup.
		 *
		 * @protected
		 */
		ValueStateMessage.prototype.close = function() {
			var oControl = this._oControl,
				oPopup = this.getPopup();

			if (oPopup) {
				oPopup.close(0);
			}

			if (oControl) {
				jQuery(oControl.getFocusDomRef()).removeAriaDescribedBy(this.getId());
			}
		};

		ValueStateMessage.prototype.getId = function() {
			var oControl = this._oControl;

			if (!oControl) {
				return "";
			}

			return (typeof oControl.getValueStateMessageId === "function") ? oControl.getValueStateMessageId() : oControl.getId() + "-message";
		};

		ValueStateMessage.prototype.getOpenDuration = function() {
			var oControl = this._oControl;

			if (!oControl) {
				return 0;
			}

			return (oControl.iOpenMessagePopupDuration === undefined) ? 0 : oControl.iOpenMessagePopupDuration;
		};

		/**
		 * Creates the value state message popup.
		 *
		 * @param {string} [sID] ID for the new message popup; generated automatically if no ID is given
		 * @returns {sap.ui.core.Popup} The popup instance object
		 */
		ValueStateMessage.prototype.createPopup = function(sID) {
			sID = sID || this.getId();

			if (this._oPopup) {
				return this._oPopup;
			}

			this._oPopup = new Popup(document.createElement("span"), false, false, false);
			this._oPopup.attachClosed(function() {
				jQuery.sap.byId(sID).remove();
			});

			return this._oPopup;
		};

		/**
		 * Gets the value state message popup, creating it if necessary by calling
		 * the createPopup() method.
		 *
		 * @returns {sap.ui.core.Popup} The popup instance object
		 */
		ValueStateMessage.prototype.getPopup = function() {

			if (!this._oControl) {
				return null;
			}

			return this.createPopup();
		};

		/**
		 * Creates the value state message HTML elements.
		 *
		 * @returns {object} The value state message root HTML element
		 */
		ValueStateMessage.prototype.createDom = function() {
			var oControl = this._oControl;

			if (!oControl) {
				return null;
			}

			var sState = oControl.getValueState(),
				sText = oControl.getValueStateText() || sap.ui.core.ValueStateSupport.getAdditionalText(oControl),
				sClass = "sapMValueStateMessage sapMValueStateMessage" + sState,
				oRB = sap.ui.getCore().getLibraryResourceBundle("sap.m");

			if (sState === sap.ui.core.ValueState.Success) {
				sClass = "sapUiInvisibleText";
				sText = "";
			}

			var sID = this.getId();
			var oMessageDomRef = document.createElement("div");
			oMessageDomRef.id = sID;
			oMessageDomRef.className = sClass;
			oMessageDomRef.setAttribute("role", "tooltip");
			oMessageDomRef.setAttribute("aria-live", "assertive");

			var oAccDomRef = document.createElement("span");
			oAccDomRef.id = sID + "hidden";
			oAccDomRef.className = "sapUiHidden";
			oAccDomRef.setAttribute("aria-hidden", "true");
			oAccDomRef.appendChild(document.createTextNode(oRB.getText("INPUTBASE_VALUE_STATE_" + sState.toUpperCase())));

			oMessageDomRef.appendChild(oAccDomRef);
			oMessageDomRef.appendChild(document.createTextNode(sText));
			return oMessageDomRef;
		};

		ValueStateMessage.prototype.destroy = function() {

			if (this._oPopup) {
				this._oPopup.destroy();
				this._oPopup = null;
			}

			this._oControl = null;
		};

		return ValueStateMessage;
	});<|endoftext|>