<commit_before>(function ( mw, $ ) {
	"use strict";
	mw.dualScreen = mw.dualScreen || {};

	mw.dualScreen.dualScreenControlBar = mw.KBaseComponent.extend({
		defaultConfig: {
			'templatePath': 'dualScreenControlBar',
			'menuFadeout': 5000
		},
		"controlBarComponents": {
			sideBySide: {
				id: 'sideBySide',
				title: gM("ks-DUAL-SCREEN-SBS"),
				event: "SbS"
			},
			singleView: {
				id: 'singleView',
				title: gM("ks-DUAL-SCREEN-HIDE"),
				event: "hide"
			},
			pip: {
				id: 'pip',
				title: gM("ks-DUAL-SCREEN-PIP"),
				event: "PiP"
			},
			switchView: {
				id: 'switchView',
				title: gM("ks-DUAL-SCREEN-SWITCH"),
				event: "switchView"
			},
            contentSelection: {
                id: 'contentSelection',
                title: 'Content Selection',
                event: "contentSelection"
            }
		},
		disabled: false,

		nativeAppTooltip: "Switching content
on current view
is not yet
supported.

Try single view",
        streams: [],

		setup: function() {
			this.postFix = "." + this.pluginName;
			this.addBindings();

            // hide content selection by default
            this.getComponent().find('.displayControlGroup-contentSelection').hide();
		},

        setStreams: function(streams){
            this.streams = streams;
            this.renderStreams();
        },

        renderStreams: function () {
            var streamsContainer = this.getComponent().find('.ds-streams').empty();
            var contentSelectionGroup = this.getComponent().find('.displayControlGroup-contentSelection');
            if (this.streams && this.streams.length) {
                streamsContainer
                    .append($.map(this.streams, function (stream, index) {
                        return $('
', {
                            'class': 'ds-stream',
                            'data-stream-index': index
                        }).append($('', {
                            src: stream.thumbnailUrl,
                            'class': 'ds-stream__thumb'
                        })).draggable({
                            revert: true,
                            helper: 'clone',
                            containment: '.videoHolder',
                            start: function (event, ui) {
                                $(ui.helper).addClass('ds-stream--helper');
                                $(this).addClass('ds-stream--dragging').data('wasDropped', false);
                                streamsContainer.addClass('ds-streams--dragging');
                            },
                            stop: function () {
                                $(this).removeClass('ds-stream--dragging');
                                streamsContainer.removeClass('ds-streams--dragging');
                            }
                        }).data('stream', stream);
                    }))
                    .droppable({
                        greedy: true,
                        accept: '.ds-stream',
                        hoverClass: 'ui-state-hover',
                        drop: function (e, ui) {
                            ui.draggable.data('wasDropped', true);
                        }
                    });

                contentSelectionGroup.show();
            } else {
                contentSelectionGroup.hide();
            }
        },

		getComponent: function ( ) {
			if ( !this.$controlBar ) {
				var rawHTML = window.kalturaIframePackageData.templates[ this.getConfig("templatePath")];
				var transformedHTML = mw.util.tmpl( rawHTML );
				transformedHTML = transformedHTML({buttons: this.controlBarComponents});
				this.$controlBar = $( '
' )
					.addClass( 'controlBar componentOff dualScreen' + this.getCssClass() )
					.append(transformedHTML);
				//If top bar exist then position controlBar under it
				var noControls = this.embedPlayer.isMobileSkin() ? 2 : 0;
				if (this.embedPlayer.getTopBarContainer().length && this.embedPlayer.getTopBarContainer().children().length !== noControls) {
					var height = this.embedPlayer.getTopBarContainer().height();
					this.$controlBar.css("top", height + "px");
				}
				if (mw.isNativeApp()){
					this.$controlBar.find("#" + this.controlBarComponents.sideBySide.id ).remove();
				}
			}
			return this.$controlBar;
		},
		getControlBarDropShadow: function() {
			var _this = this;
			if (!this.$controlBarDropShadow) {
				this.$controlBarDropShadow = $("

")
					.addClass('componentOff')
					.on("click mouseover mousemove mouseout touchstart touchend", function (e) {
						_this.embedPlayer.triggerHelper(e);
				});
			}
			return this.$controlBarDropShadow;
		},
		addBindings: function () {
			//Set control bar visiblity handlers
			var _this = this;
			//TODO:hook these events to layoutbuilder events
			this.embedPlayer.getInterface()
				.on( 'mousemove' + this.postFix +' touchstart' + this.postFix, function(){
					_this.show();
				})
				.on( 'mouseleave' + this.postFix, function(){
					if (!mw.isMobileDevice()){
						_this.hide();
					}
				});

			//add drop shadow containers for control bar
			this.embedPlayer.getVideoHolder().prepend(this.getControlBarDropShadow());

			//Cache buttons
			var buttons = _this.getComponent().find( "span" );
            var switchBtn = buttons.filter('[data-type="switch"]');

			//Attach control bar action handlers
			_this.getComponent()
				.on( 'click' + this.postFix + ' touchstart' + this.postFix, '.displayControlGroup > .controlBarBtn', function () {
                    var $this = $(this);
					var group = $this.parent('.displayControlGroup');
					var isCollapsible = $this.hasClass('ds-collapsible-handle') && group.hasClass('ds-collapsible');
					var wasOpen = group.hasClass('ds-open');
					var btn = _this.controlBarComponents[this.id];

					if (wasOpen || !isCollapsible) {
						$('.displayControlGroup').removeClass('ds-blur ds-open');
					} else {
						$('.displayControlGroup').removeClass('ds-open').addClass('ds-blur');
						group.removeClass('ds-blur').addClass('ds-open');
					}

                    if (!isCollapsible) {
    					_this.changeButtonsStyles(this.id, true); // pass clicked indicator in order to open subMenu if needed

    					if (btn && btn.event){
    						_this.embedPlayer.triggerHelper("dualScreenStateChange", {action : btn.event, invoker : 'dualScreenControlBar'});
    					}
                    }

					return false;
				} );

			if (mw.isNativeApp()){
				switchBtn.addClass("disabled" ).attr("title", _this.nativeAppTooltip );
			}

            //Set tooltips (each row separately)
            //buttons.attr('data-show-tooltip', true);
            //this.embedPlayer.layoutBuilder.setupTooltip(switchBtn, "arrowTop");
            //this.embedPlayer.layoutBuilder.setupTooltip(stateButtons, "arrowTop");
            //this.embedPlayer.layoutBuilder.setupTooltip(contentBtn, "arrowTop");

			// listen to state change and update buttons style accordingly
			_this.bind( 'dualScreenStateChange', function(e, state){

				var eventToCompare;
				var invoker;
				if (typeof state === 'object')
				{
					eventToCompare = state.action;
					invoker = state.invoker;
				}else {
					eventToCompare = state;
				}

				if (invoker === 'dualScreenControlBar')
				{
					// the state change was invoked by this component so no need to handle that notification
					return;
				}

				for(var prop in _this.controlBarComponents)
				{
					var item = _this.controlBarComponents[prop];
					if (item.event === eventToCompare)
					{
						_this.changeButtonsStyles(prop);
					}
				}

			});

            _this.bind('displayDropped', function () {
                $('.displayControlGroup').removeClass('ds-blur ds-open');
            });
		},
		/**
		 * Changes the style of the buttons according to the selected view mode.
		 * This affect they layout only and doesn't change the player state.
		 * @param activeButtonId
         */
		changeButtonsStyles : function(activeButtonId, clicked)
		{
			var _this = this;
			var buttons = _this.getComponent().find(".controlBarBtn");
			var switchBtn = buttons.filter('[data-type="switch"]');
            var stateButtons = buttons.filter('[data-type="state"]');

            var obj = $(_this.getComponent().find('#' + activeButtonId)[0]);

            //Change state button disabled state
            if (obj.data("type") === "state" && clicked ) {
                //show state buttons if selected state was clicked
                stateButtons.removeClass('stateSelected ds-collapsible-handle').addClass('ds-collapsible-content');
                obj.addClass('stateSelected ds-collapsible-handle').removeClass('ds-collapsible-content');
            }

            if (mw.isNativeApp()){
                if (this.id === _this.controlBarComponents.pip.id){
                    switchBtn
                        .addClass("disabled")
                        .tooltip( "option", "content", _this.nativeAppTooltip);
                } else if(this.id === _this.controlBarComponents.singleView.id){
                    switchBtn.tooltip( "option", "content", _this.controlBarComponents.switchView.title);
                }
            }
		},
		disable: function () {
			clearTimeout(this.getComponent().handleTouchTimeoutId);
			this.disabled = true;
		},
		enable: function () {
			this.disabled = false;
		},
		hide: function ( ) {
			if ( !this.disabled ) {
				this.embedPlayer.triggerHelper( 'clearTooltip' );
				if ( this.isVisible ) {
					this.getComponent().addClass( 'componentOff componentAnimation' ).removeClass( 'componentOn' );
					this.embedPlayer.getVideoHolder().find( ".controlBarShadow" ).addClass( 'componentOff componentAnimation' ).removeClass( 'componentOn' );
					this.isVisible = false;
				}
			}
		},
		show: function ( ) {
			if ( !this.disabled) {
				if ( !this.isVisible ) {
                    //show only vertical main buttons (ignore sub-menus)
                    //this.getComponent().find("span").filter(".dualScreen-switchView, .stateSelected, .dualScreen-contentSelection").removeClass( 'componentAnimation' ).addClass( 'componentOn' ).removeClass( 'componentOff' );

                    this.getComponent().removeClass( 'componentAnimation' ).addClass( 'componentOn' ).removeClass( 'componentOff' );
					this.isVisible = true;
					this.embedPlayer.getVideoHolder().find( ".controlBarShadow" ).removeClass( 'componentAnimation' ).addClass( 'componentOn' ).removeClass( 'componentOff' );
				}

				var _this = this;
				if ( this.getComponent().handleTouchTimeoutId ) {
					clearTimeout( this.getComponent().handleTouchTimeoutId );
				}
				this.getComponent().handleTouchTimeoutId = setTimeout( function () {
					_this.hide();
				}, this.getConfig("menuFadeout"));
			}
		},
		set: function(id){
			if (id) {
				var component = this.getComponent();
				var buttons = $("span[data-type=state]", component);
				buttons.not("#" + id).removeClass("disabled");
				buttons.filter("#" + id).addClass("disabled");
				if (mw.isNativeApp()) {
					var switchBtn = $('span[data-type="switch"]', component);
					switchBtn
						.addClass("disabled")
						.tooltip("option", "content", this.nativeAppTooltip);
				}
			}
		},
		destroy: function() {
			this.embedPlayer.unbindHelper(this.postFix);
			this.getComponent().off(this.postFix);
			this.embedPlayer.getInterface().off(this.postFix);
			this.getComponent().remove();
			this.getControlBarDropShadow().remove();
			this.$controlBar = null;
			this.$controlBarDropShadow = null;
		}
	});
})( window.mw, window.jQuery );
<commit_msg>fix state buttons are not updated according to defaultDualScreenViewId<commit_after>(function ( mw, $ ) {
	"use strict";
	mw.dualScreen = mw.dualScreen || {};

	mw.dualScreen.dualScreenControlBar = mw.KBaseComponent.extend({
		defaultConfig: {
			'templatePath': 'dualScreenControlBar',
			'menuFadeout': 5000
		},
		"controlBarComponents": {
			sideBySide: {
				id: 'sideBySide',
				title: gM("ks-DUAL-SCREEN-SBS"),
				event: "SbS"
			},
			singleView: {
				id: 'singleView',
				title: gM("ks-DUAL-SCREEN-HIDE"),
				event: "hide"
			},
			pip: {
				id: 'pip',
				title: gM("ks-DUAL-SCREEN-PIP"),
				event: "PiP"
			},
			switchView: {
				id: 'switchView',
				title: gM("ks-DUAL-SCREEN-SWITCH"),
				event: "switchView"
			},
            contentSelection: {
                id: 'contentSelection',
                title: 'Content Selection',
                event: "contentSelection"
            }
		},
		disabled: false,

		nativeAppTooltip: "Switching content
on current view
is not yet
supported.

Try single view",
        streams: [],

		setup: function() {
			this.postFix = "." + this.pluginName;
			this.addBindings();

            // hide content selection by default
            this.getComponent().find('.displayControlGroup-contentSelection').hide();
		},

        setStreams: function(streams){
            this.streams = streams;
            this.renderStreams();
        },

        renderStreams: function () {
            var streamsContainer = this.getComponent().find('.ds-streams').empty();
            var contentSelectionGroup = this.getComponent().find('.displayControlGroup-contentSelection');
            if (this.streams && this.streams.length) {
                streamsContainer
                    .append($.map(this.streams, function (stream, index) {
                        return $('
', {
                            'class': 'ds-stream',
                            'data-stream-index': index
                        }).append($('', {
                            src: stream.thumbnailUrl,
                            'class': 'ds-stream__thumb'
                        })).draggable({
                            revert: true,
                            helper: 'clone',
                            containment: '.videoHolder',
                            start: function (event, ui) {
                                $(ui.helper).addClass('ds-stream--helper');
                                $(this).addClass('ds-stream--dragging').data('wasDropped', false);
                                streamsContainer.addClass('ds-streams--dragging');
                            },
                            stop: function () {
                                $(this).removeClass('ds-stream--dragging');
                                streamsContainer.removeClass('ds-streams--dragging');
                            }
                        }).data('stream', stream);
                    }))
                    .droppable({
                        greedy: true,
                        accept: '.ds-stream',
                        hoverClass: 'ui-state-hover',
                        drop: function (e, ui) {
                            ui.draggable.data('wasDropped', true);
                        }
                    });

                contentSelectionGroup.show();
            } else {
                contentSelectionGroup.hide();
            }
        },

		getComponent: function ( ) {
			if ( !this.$controlBar ) {
				var rawHTML = window.kalturaIframePackageData.templates[ this.getConfig("templatePath")];
				var transformedHTML = mw.util.tmpl( rawHTML );
				transformedHTML = transformedHTML({buttons: this.controlBarComponents});
				this.$controlBar = $( '
' )
					.addClass( 'controlBar componentOff dualScreen' + this.getCssClass() )
					.append(transformedHTML);
				//If top bar exist then position controlBar under it
				var noControls = this.embedPlayer.isMobileSkin() ? 2 : 0;
				if (this.embedPlayer.getTopBarContainer().length && this.embedPlayer.getTopBarContainer().children().length !== noControls) {
					var height = this.embedPlayer.getTopBarContainer().height();
					this.$controlBar.css("top", height + "px");
				}
				if (mw.isNativeApp()){
					this.$controlBar.find("#" + this.controlBarComponents.sideBySide.id ).remove();
				}
			}
			return this.$controlBar;
		},
		getControlBarDropShadow: function() {
			var _this = this;
			if (!this.$controlBarDropShadow) {
				this.$controlBarDropShadow = $("

")
					.addClass('componentOff')
					.on("click mouseover mousemove mouseout touchstart touchend", function (e) {
						_this.embedPlayer.triggerHelper(e);
				});
			}
			return this.$controlBarDropShadow;
		},
		addBindings: function () {
			//Set control bar visiblity handlers
			var _this = this;
			//TODO:hook these events to layoutbuilder events
			this.embedPlayer.getInterface()
				.on( 'mousemove' + this.postFix +' touchstart' + this.postFix, function(){
					_this.show();
				})
				.on( 'mouseleave' + this.postFix, function(){
					if (!mw.isMobileDevice()){
						_this.hide();
					}
				});

			//add drop shadow containers for control bar
			this.embedPlayer.getVideoHolder().prepend(this.getControlBarDropShadow());

			//Cache buttons
			var buttons = _this.getComponent().find( "span" );
            var switchBtn = buttons.filter('[data-type="switch"]');

			//Attach control bar action handlers
			_this.getComponent()
				.on( 'click' + this.postFix + ' touchstart' + this.postFix, '.displayControlGroup > .controlBarBtn', function () {
                    var $this = $(this);
					var group = $this.parent('.displayControlGroup');
					var isCollapsible = $this.hasClass('ds-collapsible-handle') && group.hasClass('ds-collapsible');
					var wasOpen = group.hasClass('ds-open');
					var btn = _this.controlBarComponents[this.id];

					if (wasOpen || !isCollapsible) {
						$('.displayControlGroup').removeClass('ds-blur ds-open');
					} else {
						$('.displayControlGroup').removeClass('ds-open').addClass('ds-blur');
						group.removeClass('ds-blur').addClass('ds-open');
					}

                    if (!isCollapsible) {
    					_this.changeButtonsStyles(this.id, true); // pass clicked indicator in order to open subMenu if needed

    					if (btn && btn.event){
    						_this.embedPlayer.triggerHelper("dualScreenStateChange", {action : btn.event, invoker : 'dualScreenControlBar'});
    					}
                    }

					return false;
				} );

			if (mw.isNativeApp()){
				switchBtn.addClass("disabled" ).attr("title", _this.nativeAppTooltip );
			}

            //Set tooltips (each row separately)
            //buttons.attr('data-show-tooltip', true);
            //this.embedPlayer.layoutBuilder.setupTooltip(switchBtn, "arrowTop");
            //this.embedPlayer.layoutBuilder.setupTooltip(stateButtons, "arrowTop");
            //this.embedPlayer.layoutBuilder.setupTooltip(contentBtn, "arrowTop");

			// listen to state change and update buttons style accordingly
			_this.bind( 'dualScreenStateChange', function(e, state){

				var eventToCompare;
				var invoker;
				if (typeof state === 'object')
				{
					eventToCompare = state.action;
					invoker = state.invoker;
				}else {
					eventToCompare = state;
				}

				if (invoker === 'dualScreenControlBar')
				{
					// the state change was invoked by this component so no need to handle that notification
					return;
				}

				for(var prop in _this.controlBarComponents)
				{
					var item = _this.controlBarComponents[prop];
					if (item.event === eventToCompare)
					{
						_this.changeButtonsStyles(prop);
					}
				}

			});

            _this.bind('displayDropped', function () {
                $('.displayControlGroup').removeClass('ds-blur ds-open');
            });
		},
		/**
		 * Changes the style of the buttons according to the selected view mode.
		 * This affect they layout only and doesn't change the player state.
		 * @param activeButtonId
         */
		changeButtonsStyles : function(activeButtonId, clicked)
		{
			var _this = this;
			var buttons = _this.getComponent().find(".controlBarBtn");
			var switchBtn = buttons.filter('[data-type="switch"]');
            var stateButtons = buttons.filter('[data-type="state"]');

            var obj = $(_this.getComponent().find('#' + activeButtonId)[0]);

            //Change state button disabled state
            if (obj.data("type") === "state") {
                //show state buttons if selected state was clicked
                stateButtons.removeClass('stateSelected ds-collapsible-handle').addClass('ds-collapsible-content');
                obj.addClass('stateSelected ds-collapsible-handle').removeClass('ds-collapsible-content');
            }

            if (mw.isNativeApp()){
                if (this.id === _this.controlBarComponents.pip.id){
                    switchBtn
                        .addClass("disabled")
                        .tooltip( "option", "content", _this.nativeAppTooltip);
                } else if(this.id === _this.controlBarComponents.singleView.id){
                    switchBtn.tooltip( "option", "content", _this.controlBarComponents.switchView.title);
                }
            }
		},
		disable: function () {
			clearTimeout(this.getComponent().handleTouchTimeoutId);
			this.disabled = true;
		},
		enable: function () {
			this.disabled = false;
		},
		hide: function ( ) {
			if ( !this.disabled ) {
				this.embedPlayer.triggerHelper( 'clearTooltip' );
				if ( this.isVisible ) {
					this.getComponent().addClass( 'componentOff componentAnimation' ).removeClass( 'componentOn' );
					this.embedPlayer.getVideoHolder().find( ".controlBarShadow" ).addClass( 'componentOff componentAnimation' ).removeClass( 'componentOn' );
					this.isVisible = false;
				}
			}
		},
		show: function ( ) {
			if ( !this.disabled) {
				if ( !this.isVisible ) {
                    //show only vertical main buttons (ignore sub-menus)
                    //this.getComponent().find("span").filter(".dualScreen-switchView, .stateSelected, .dualScreen-contentSelection").removeClass( 'componentAnimation' ).addClass( 'componentOn' ).removeClass( 'componentOff' );

                    this.getComponent().removeClass( 'componentAnimation' ).addClass( 'componentOn' ).removeClass( 'componentOff' );
					this.isVisible = true;
					this.embedPlayer.getVideoHolder().find( ".controlBarShadow" ).removeClass( 'componentAnimation' ).addClass( 'componentOn' ).removeClass( 'componentOff' );
				}

				var _this = this;
				if ( this.getComponent().handleTouchTimeoutId ) {
					clearTimeout( this.getComponent().handleTouchTimeoutId );
				}
				this.getComponent().handleTouchTimeoutId = setTimeout( function () {
					_this.hide();
				}, this.getConfig("menuFadeout"));
			}
		},
		set: function(id){
			if (id) {
				var component = this.getComponent();
				var buttons = $("span[data-type=state]", component);
				buttons.not("#" + id).removeClass("disabled");
				buttons.filter("#" + id).addClass("disabled");
				if (mw.isNativeApp()) {
					var switchBtn = $('span[data-type="switch"]', component);
					switchBtn
						.addClass("disabled")
						.tooltip("option", "content", this.nativeAppTooltip);
				}
			}
		},
		destroy: function() {
			this.embedPlayer.unbindHelper(this.postFix);
			this.getComponent().off(this.postFix);
			this.embedPlayer.getInterface().off(this.postFix);
			this.getComponent().remove();
			this.getControlBarDropShadow().remove();
			this.$controlBar = null;
			this.$controlBarDropShadow = null;
		}
	});
})( window.mw, window.jQuery );
<|endoftext|>


<commit_before>/* Magic Mirror
 * Module: Weather
 *
 * By Malcolm Oakes https://github.com/maloakes
 * Existing Met Office provider edited for new MetOffice Data Hub by CreepinJesus http://github.com/XBCreepinJesus
 * MIT Licensed.
 *
 * This class is a provider for UK Met Office Data Hub (the replacement for their Data Point services).
 * For more information on Data Hub, see https://www.metoffice.gov.uk/services/data/datapoint/notifications/weather-datahub
 * Data available:
 * 		Hourly data for next 2 days ("hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-hourly.pdf
 * 		3-hourly data for the next 7 days ("3hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-3-hourly.pdf
 * 		Daily data for the next 7 days ("daily") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-daily.pdf
 */

/* NOTES
 * This provider requires longitude/latitude coordinates, rather than a location ID (as with the previous Met Office provider)
 * Provide the following in your config.js file:
 * 		weatherProvider: "ukmetofficedatahub",
 * 		apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
 * 		apiKey: "[YOUR API KEY]",
 * 		apiSecret: "[YOUR API SECRET]]",
 * 		lat: [LATITUDE (DECIMAL)],
 * 		long: [LONGITUDE (DECIMAL)],
 *		windUnits: "mps" | "kph" | "mph" (default)
 *		tempUnits: "imperial" | "metric" (default)
 *
 * At time of writing, free accounts are limited to 360 requests a day per service (hourly, 3hourly, daily); take this in mind when
 * setting your update intervals. For reference, 360 requests per day is once every 4 minutes.
 *
 * Pay attention to the units of the supplied data from the Met Office - it is given in SI/metric units where applicable:
 * 	- Temperatures are in degrees Celsius (°C)
 * 	- Wind speeds are in metres per second (m/s)
 * 	- Wind direction given in degrees (°)
 * 	- Pressures are in Pascals (Pa)
 * 	- Distances are in metres (m)
 * 	- Probabilities and humidity are given as percentages (%)
 * 	- Precipitation is measured in millimetres (mm) with rates per hour (mm/h)
 *
 * See the PDFs linked above for more information on the data their corresponding units.
 */

WeatherProvider.register("ukmetofficedatahub", {
	// Set the name of the provider.
	providerName: "UK Met Office (DataHub)",

	// Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
	getUrl(forecastType) {
		let queryStrings = "?";
		queryStrings += "latitude=" + this.config.lat;
		queryStrings += "&longitude=" + this.config.long;
		if (this.config.appendLocationNameToHeader) {
			queryStrings += "&includeLocationName=" + true;
		}

		// Return URL, making sure there is a trailing "/" in the base URL.
		return this.config.apiBase + (this.config.apiBase.endsWith("/") ? "" : "/") + forecastType + queryStrings;
	},

	// Build the list of headers for the request
	// For DataHub requests, the API key/secret are sent in the headers rather than as query strings.
	// Headers defined according to Data Hub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
	getHeaders() {
		let headers = {
			accept: "application/json",
			"x-ibm-client-id": this.config.apiKey,
			"x-ibm-client-secret": this.config.apiSecret,
		};

		return headers;
	},

	// Fetch data using supplied URL and request headers
	async fetchWeather(url, headers) {
		const response = await fetch(url, { headers: headers });

		// Return JSON data
		return response.json();
	},

	// Fetch hourly forecast data (to use for current weather)
	fetchCurrentWeather() {
		this.fetchWeather(this.getUrl("hourly"), this.getHeaders())
			.then((data) => {
				// Check data is useable
				if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length == 0) {
					// Did not receive usable new data.
					// Maybe this needs a better check?
					Log.error("Possibly bad current/hourly data?");
					Log.info(data);
					return;
				}

				// Set location name
				this.setFetchedLocation(`${data.features[0].properties.location.name}`);

				// Generate current weather data
				const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
				this.setCurrentWeather(currentWeather);
			})

			// Catch any error(s)
			.catch((error) => Log.error("Could not load data: " + error.message))

			// Let the module know there're new data available
			.finally(() => this.updateAvailable());
	},

	// Create a WeatherObject using current weather data (data for the current hour)
	generateWeatherObjectFromCurrentWeather(currentWeatherData) {
		const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);

		// Extract the actual forecasts
		let forecastDataHours = currentWeatherData.features[0].properties.timeSeries;

		// Define now
		let nowUtc = moment.utc();

		// Find hour that contains the current time
		for (hour in forecastDataHours) {
			let forecastTime = moment.utc(forecastDataHours[hour].time);
			if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) {
				currentWeather.date = forecastTime;
				currentWeather.windSpeed = this.convertWindSpeed(forecastDataHours[hour].windSpeed10m);
				currentWeather.windDirection = forecastDataHours[hour].windDirectionFrom10m;
				currentWeather.temperature = this.convertTemp(forecastDataHours[hour].screenTemperature);
				currentWeather.minTemperature = this.convertTemp(forecastDataHours[hour].minScreenAirTemp);
				currentWeather.maxTemperature = this.convertTemp(forecastDataHours[hour].maxScreenAirTemp);
				currentWeather.weatherType = this.convertWeatherType(forecastDataHours[hour].significantWeatherCode);
				currentWeather.humidity = forecastDataHours[hour].screenRelativeHumidity;
				currentWeather.rain = forecastDataHours[hour].totalPrecipAmount;
				currentWeather.snow = forecastDataHours[hour].totalSnowAmount;
				currentWeather.precipitation = forecastDataHours[hour].probOfPrecipitation;
				currentWeather.feelsLikeTemp = this.convertTemp(forecastDataHours[hour].feelsLikeTemperature);

				// Pass on full details so they can be used in custom templates
				// Note the units of the supplied data when using this (see top of file)
				currentWeather.rawData = forecastDataHours[hour];
			}
		}

		// Determine the sunrise/sunset times - (still) not supplied in UK Met Office data
		// Passes {longitude, latitude, height} to calcAstroData
		// Could just pass lat/long from this.config, but returned data from MO also contains elevation
		let times = this.calcAstroData(currentWeatherData.features[0].geometry.coordinates);
		currentWeather.sunrise = times[0];
		currentWeather.sunset = times[1];

		return currentWeather;
	},

	// Fetch daily forecast data
	fetchWeatherForecast() {
		this.fetchWeather(this.getUrl("daily"), this.getHeaders())
			.then((data) => {
				// Check data is useable
				if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length == 0) {
					// Did not receive usable new data.
					// Maybe this needs a better check?
					Log.error("Possibly bad forecast data?");
					Log.info(data);
					return;
				}

				// Set location name
				this.setFetchedLocation(`${data.features[0].properties.location.name}`);

				// Generate the forecast data
				const forecast = this.generateWeatherObjectsFromForecast(data);
				this.setWeatherForecast(forecast);
			})

			// Catch any error(s)
			.catch((error) => Log.error("Could not load data: " + error.message))

			// Let the module know there're new data available
			.finally(() => this.updateAvailable());
	},

	// Create a WeatherObject for each day using daily forecast data
	generateWeatherObjectsFromForecast(forecasts) {
		const dailyForecasts = [];

		// Extract the actual forecasts
		let forecastDataDays = forecasts.features[0].properties.timeSeries;

		// Define today
		let today = moment.utc().startOf("date");

		// Go through each day in the forecasts
		for (day in forecastDataDays) {
			const forecastWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);

			// Get date of forecast
			let forecastDate = moment.utc(forecastDataDays[day].time);

			// Check if forecast is for today or in the future (i.e., ignore yesterday's forecast)
			if (forecastDate.isSameOrAfter(today)) {
				forecastWeather.date = forecastDate;
				forecastWeather.minTemperature = this.convertTemp(forecastDataDays[day].nightMinScreenTemperature);
				forecastWeather.maxTemperature = this.convertTemp(forecastDataDays[day].dayMaxScreenTemperature);

				// Using daytime forecast values
				forecastWeather.windSpeed = this.convertWindSpeed(forecastDataDays[day].midday10MWindSpeed);
				forecastWeather.windDirection = forecastDataDays[day].midday10MWindDirection;
				forecastWeather.weatherType = this.convertWeatherType(forecastDataDays[day].daySignificantWeatherCode);
				forecastWeather.precipitation = forecastDataDays[day].dayProbabilityOfPrecipitation;
				forecastWeather.temperature = forecastDataDays[day].dayMaxScreenTemperature;
				forecastWeather.humidity = forecastDataDays[day].middayRelativeHumidity;
				forecastWeather.rain = forecastDataDays[day].dayProbabilityOfRain;
				forecastWeather.snow = forecastDataDays[day].dayProbabilityOfSnow;
				forecastWeather.feelsLikeTemp = this.convertTemp(forecastDataDays[day].dayMaxFeelsLikeTemp);

				// Pass on full details so they can be used in custom templates
				// Note the units of the supplied data when using this (see top of file)

				forecastWeather.rawData = forecastDataDays[day];

				dailyForecasts.push(forecastWeather);
			}
		}

		return dailyForecasts;
	},

	// Set the fetched location name.
	setFetchedLocation: function (name) {
		this.fetchedLocationName = name;
	},

	// Calculate sunrise/sunset times
	calcAstroData(location) {
		const sunTimes = [];

		// Careful to pass values to SunCalc in correct order (latitude, longitude, elevation)
		let times = SunCalc.getTimes(new Date(), location[1], location[0], location[2]);
		sunTimes.push(moment(times.sunrise, "X"));
		sunTimes.push(moment(times.sunset, "X"));

		return sunTimes;
	},

	// Convert temperatures to Fahrenheit (from degrees C), if required
	convertTemp(tempInC) {
		return this.config.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
	},

	// Convert wind speed from metres per second
	// To keep the supplied metres per second units, use "mps"
	// To use kilometres per hour, use "kph"
	// Else assumed imperial and the value is returned in miles per hour (a Met Office user is likely to be UK-based)
	convertWindSpeed(windInMpS) {
		if (this.config.windUnits == "mps") {
			return windInMpS;
		}

		if (this.config.windUnits == "kph" || this.config.windUnits == "metric") {
			return windInMpS * 3.6;
		}

		return windInMpS * 2.23694;
	},

	// Match the Met Office "significant weather code" to a weathericons.css icon
	// Use: https://metoffice.apiconnect.ibmcloud.com/metoffice/production/node/264
	// and: https://erikflowers.github.io/weather-icons/
	convertWeatherType(weatherType) {
		const weatherTypes = {
			0: "night-clear",
			1: "day-sunny",
			2: "night-alt-cloudy",
			3: "day-cloudy",
			5: "fog",
			6: "fog",
			7: "cloudy",
			8: "cloud",
			9: "night-sprinkle",
			10: "day-sprinkle",
			11: "raindrops",
			12: "sprinkle",
			13: "night-alt-showers",
			14: "day-showers",
			15: "rain",
			16: "night-alt-sleet",
			17: "day-sleet",
			18: "sleet",
			19: "night-alt-hail",
			20: "day-hail",
			21: "hail",
			22: "night-alt-snow",
			23: "day-snow",
			24: "snow",
			25: "night-alt-snow",
			26: "day-snow",
			27: "snow",
			28: "night-alt-thunderstorm",
			29: "day-thunderstorm",
			30: "thunderstorm",
		};

		return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
	},
});
<commit_msg>Update metofficedatahub.js<commit_after>/* Magic Mirror
 * Module: Weather
 *
 * By Malcolm Oakes https://github.com/maloakes
 * Existing Met Office provider edited for new MetOffice Data Hub by CreepinJesus http://github.com/XBCreepinJesus
 * MIT Licensed.
 *
 * This class is a provider for UK Met Office Data Hub (the replacement for their Data Point services).
 * For more information on Data Hub, see https://www.metoffice.gov.uk/services/data/datapoint/notifications/weather-datahub
 * Data available:
 * 		Hourly data for next 2 days ("hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-hourly.pdf
 * 		3-hourly data for the next 7 days ("3hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-3-hourly.pdf
 * 		Daily data for the next 7 days ("daily") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-daily.pdf
 */

/* NOTES
 * This provider requires longitude/latitude coordinates, rather than a location ID (as with the previous Met Office provider)
 * Provide the following in your config.js file:
 * 		weatherProvider: "ukmetofficedatahub",
 * 		apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
 * 		apiKey: "[YOUR API KEY]",
 * 		apiSecret: "[YOUR API SECRET]]",
 * 		lat: [LATITUDE (DECIMAL)],
 * 		lon: [LONGITUDE (DECIMAL)],
 *		windUnits: "mps" | "kph" | "mph" (default)
 *		tempUnits: "imperial" | "metric" (default)
 *
 * At time of writing, free accounts are limited to 360 requests a day per service (hourly, 3hourly, daily); take this in mind when
 * setting your update intervals. For reference, 360 requests per day is once every 4 minutes.
 *
 * Pay attention to the units of the supplied data from the Met Office - it is given in SI/metric units where applicable:
 * 	- Temperatures are in degrees Celsius (°C)
 * 	- Wind speeds are in metres per second (m/s)
 * 	- Wind direction given in degrees (°)
 * 	- Pressures are in Pascals (Pa)
 * 	- Distances are in metres (m)
 * 	- Probabilities and humidity are given as percentages (%)
 * 	- Precipitation is measured in millimetres (mm) with rates per hour (mm/h)
 *
 * See the PDFs linked above for more information on the data their corresponding units.
 */

WeatherProvider.register("ukmetofficedatahub", {
	// Set the name of the provider.
	providerName: "UK Met Office (DataHub)",

	// Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
	getUrl(forecastType) {
		let queryStrings = "?";
		queryStrings += "latitude=" + this.config.lat;
		queryStrings += "&longitude=" + this.config.lon;
		if (this.config.appendLocationNameToHeader) {
			queryStrings += "&includeLocationName=" + true;
		}

		// Return URL, making sure there is a trailing "/" in the base URL.
		return this.config.apiBase + (this.config.apiBase.endsWith("/") ? "" : "/") + forecastType + queryStrings;
	},

	// Build the list of headers for the request
	// For DataHub requests, the API key/secret are sent in the headers rather than as query strings.
	// Headers defined according to Data Hub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
	getHeaders() {
		let headers = {
			accept: "application/json",
			"x-ibm-client-id": this.config.apiKey,
			"x-ibm-client-secret": this.config.apiSecret
		};

		return headers;
	},

	// Fetch data using supplied URL and request headers
	async fetchWeather(url, headers) {
		const response = await fetch(url, { headers: headers });

		// Return JSON data
		return response.json();
	},

	// Fetch hourly forecast data (to use for current weather)
	fetchCurrentWeather() {
		this.fetchWeather(this.getUrl("hourly"), this.getHeaders())
			.then((data) => {
				// Check data is useable
				if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length == 0) {
					// Did not receive usable new data.
					// Maybe this needs a better check?
					Log.error("Possibly bad current/hourly data?");
					Log.info(data);
					return;
				}

				// Set location name
				this.setFetchedLocation(`${data.features[0].properties.location.name}`);

				// Generate current weather data
				const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
				this.setCurrentWeather(currentWeather);
			})

			// Catch any error(s)
			.catch((error) => Log.error("Could not load data: " + error.message))

			// Let the module know there're new data available
			.finally(() => this.updateAvailable());
	},

	// Create a WeatherObject using current weather data (data for the current hour)
	generateWeatherObjectFromCurrentWeather(currentWeatherData) {
		const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);

		// Extract the actual forecasts
		let forecastDataHours = currentWeatherData.features[0].properties.timeSeries;

		// Define now
		let nowUtc = moment.utc();

		// Find hour that contains the current time
		for (hour in forecastDataHours) {
			let forecastTime = moment.utc(forecastDataHours[hour].time);
			if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) {
				currentWeather.date = forecastTime;
				currentWeather.windSpeed = this.convertWindSpeed(forecastDataHours[hour].windSpeed10m);
				currentWeather.windDirection = forecastDataHours[hour].windDirectionFrom10m;
				currentWeather.temperature = this.convertTemp(forecastDataHours[hour].screenTemperature);
				currentWeather.minTemperature = this.convertTemp(forecastDataHours[hour].minScreenAirTemp);
				currentWeather.maxTemperature = this.convertTemp(forecastDataHours[hour].maxScreenAirTemp);
				currentWeather.weatherType = this.convertWeatherType(forecastDataHours[hour].significantWeatherCode);
				currentWeather.humidity = forecastDataHours[hour].screenRelativeHumidity;
				currentWeather.rain = forecastDataHours[hour].totalPrecipAmount;
				currentWeather.snow = forecastDataHours[hour].totalSnowAmount;
				currentWeather.precipitation = forecastDataHours[hour].probOfPrecipitation;
				currentWeather.feelsLikeTemp = this.convertTemp(forecastDataHours[hour].feelsLikeTemperature);

				// Pass on full details so they can be used in custom templates
				// Note the units of the supplied data when using this (see top of file)
				currentWeather.rawData = forecastDataHours[hour];
			}
		}

		// Determine the sunrise/sunset times - (still) not supplied in UK Met Office data
		// Passes {longitude, latitude, height} to calcAstroData
		// Could just pass lat/long from this.config, but returned data from MO also contains elevation
		let times = this.calcAstroData(currentWeatherData.features[0].geometry.coordinates);
		currentWeather.sunrise = times[0];
		currentWeather.sunset = times[1];

		return currentWeather;
	},

	// Fetch daily forecast data
	fetchWeatherForecast() {
		this.fetchWeather(this.getUrl("daily"), this.getHeaders())
			.then((data) => {
				// Check data is useable
				if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length == 0) {
					// Did not receive usable new data.
					// Maybe this needs a better check?
					Log.error("Possibly bad forecast data?");
					Log.info(data);
					return;
				}

				// Set location name
				this.setFetchedLocation(`${data.features[0].properties.location.name}`);

				// Generate the forecast data
				const forecast = this.generateWeatherObjectsFromForecast(data);
				this.setWeatherForecast(forecast);
			})

			// Catch any error(s)
			.catch((error) => Log.error("Could not load data: " + error.message))

			// Let the module know there're new data available
			.finally(() => this.updateAvailable());
	},

	// Create a WeatherObject for each day using daily forecast data
	generateWeatherObjectsFromForecast(forecasts) {
		const dailyForecasts = [];

		// Extract the actual forecasts
		let forecastDataDays = forecasts.features[0].properties.timeSeries;

		// Define today
		let today = moment.utc().startOf("date");

		// Go through each day in the forecasts
		for (day in forecastDataDays) {
			const forecastWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);

			// Get date of forecast
			let forecastDate = moment.utc(forecastDataDays[day].time);

			// Check if forecast is for today or in the future (i.e., ignore yesterday's forecast)
			if (forecastDate.isSameOrAfter(today)) {
				forecastWeather.date = forecastDate;
				forecastWeather.minTemperature = this.convertTemp(forecastDataDays[day].nightMinScreenTemperature);
				forecastWeather.maxTemperature = this.convertTemp(forecastDataDays[day].dayMaxScreenTemperature);

				// Using daytime forecast values
				forecastWeather.windSpeed = this.convertWindSpeed(forecastDataDays[day].midday10MWindSpeed);
				forecastWeather.windDirection = forecastDataDays[day].midday10MWindDirection;
				forecastWeather.weatherType = this.convertWeatherType(forecastDataDays[day].daySignificantWeatherCode);
				forecastWeather.precipitation = forecastDataDays[day].dayProbabilityOfPrecipitation;
				forecastWeather.temperature = forecastDataDays[day].dayMaxScreenTemperature;
				forecastWeather.humidity = forecastDataDays[day].middayRelativeHumidity;
				forecastWeather.rain = forecastDataDays[day].dayProbabilityOfRain;
				forecastWeather.snow = forecastDataDays[day].dayProbabilityOfSnow;
				forecastWeather.feelsLikeTemp = this.convertTemp(forecastDataDays[day].dayMaxFeelsLikeTemp);

				// Pass on full details so they can be used in custom templates
				// Note the units of the supplied data when using this (see top of file)

				forecastWeather.rawData = forecastDataDays[day];

				dailyForecasts.push(forecastWeather);
			}
		}

		return dailyForecasts;
	},

	// Set the fetched location name.
	setFetchedLocation: function (name) {
		this.fetchedLocationName = name;
	},

	// Calculate sunrise/sunset times
	calcAstroData(location) {
		const sunTimes = [];

		// Careful to pass values to SunCalc in correct order (latitude, longitude, elevation)
		let times = SunCalc.getTimes(new Date(), location[1], location[0], location[2]);
		sunTimes.push(moment(times.sunrise, "X"));
		sunTimes.push(moment(times.sunset, "X"));

		return sunTimes;
	},

	// Convert temperatures to Fahrenheit (from degrees C), if required
	convertTemp(tempInC) {
		return this.config.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
	},

	// Convert wind speed from metres per second
	// To keep the supplied metres per second units, use "mps"
	// To use kilometres per hour, use "kph"
	// Else assumed imperial and the value is returned in miles per hour (a Met Office user is likely to be UK-based)
	convertWindSpeed(windInMpS) {
		if (this.config.windUnits == "mps") {
			return windInMpS;
		}

		if (this.config.windUnits == "kph" || this.config.windUnits == "metric") {
			return windInMpS * 3.6;
		}

		return windInMpS * 2.23694;
	},

	// Match the Met Office "significant weather code" to a weathericons.css icon
	// Use: https://metoffice.apiconnect.ibmcloud.com/metoffice/production/node/264
	// and: https://erikflowers.github.io/weather-icons/
	convertWeatherType(weatherType) {
		const weatherTypes = {
			0: "night-clear",
			1: "day-sunny",
			2: "night-alt-cloudy",
			3: "day-cloudy",
			5: "fog",
			6: "fog",
			7: "cloudy",
			8: "cloud",
			9: "night-sprinkle",
			10: "day-sprinkle",
			11: "raindrops",
			12: "sprinkle",
			13: "night-alt-showers",
			14: "day-showers",
			15: "rain",
			16: "night-alt-sleet",
			17: "day-sleet",
			18: "sleet",
			19: "night-alt-hail",
			20: "day-hail",
			21: "hail",
			22: "night-alt-snow",
			23: "day-snow",
			24: "snow",
			25: "night-alt-snow",
			26: "day-snow",
			27: "snow",
			28: "night-alt-thunderstorm",
			29: "day-thunderstorm",
			30: "thunderstorm"
		};

		return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
	}
});
<|endoftext|>


<commit_before>import React from 'react';
import { findDOMNode } from 'react-dom';
import moment from 'moment';
import {
	Button,
	Col,
	Form,
	FormField,
	FormInput,
	ResponsiveText,
	Row,
	Spinner,
} from 'elemental';

import AlertMessages from '../../../shared/AlertMessages';
import ConfirmationDialog from './../../../shared/ConfirmationDialog';
import Fields from '../../../../utils/fields';

import FormHeading from './FormHeading';
import AltText from './AltText';
import FooterBar from './FooterBar';
import InvalidFieldType from '../../../shared/InvalidFieldType';

import { deleteItem } from '../../List/actions';

function upCase (str) {
	return str.slice(0, 1).toUpperCase() + str.substr(1).toLowerCase();
};

function getNameFromData (data) {
	if (typeof data === 'object') {
		if (typeof data.first === 'string' && typeof data.last === 'string') {
			return data.first + ' ' + data.last;
		} else if (data.id) {
			return data.id;
		}
	}
	return data;
}

var EditForm = React.createClass({
	displayName: 'EditForm',
	propTypes: {
		data: React.PropTypes.object,
		list: React.PropTypes.object,
	},
	getInitialState () {
		return {
			values: Object.assign({}, this.props.data.fields),
			confirmationDialog: null,
			loading: false,
		};
	},
	getFieldProps (field) {
		const props = Object.assign({}, field);
		const alerts = this.state.alerts;
		// Display validation errors inline
		if (alerts && alerts.error && alerts.error.error === 'validation errors') {
			if (alerts.error.detail[field.path]) {
				// NOTE: This won't work yet, as ElementalUI doesn't allow
				// passed in isValid, only invalidates via internal state.
				// PR to fix that: https://github.com/elementalui/elemental/pull/149
				props.isValid = false;
			}
		}
		props.value = this.state.values[field.path];
		props.values = this.state.values;
		props.onChange = this.handleChange;
		props.mode = 'edit';
		return props;
	},
	handleChange (event) {
		const values = Object.assign({}, this.state.values);

		values[event.path] = event.value;
		this.setState({ values });
	},
	confirmReset (event) {
		const confirmationDialog = (
			<ConfirmationDialog
				isOpen
				body={`Reset your changes to <strong>${this.props.data.name}</strong>?`}
				confirmationLabel="Reset"
				onCancel={this.removeConfirmationDialog}
				onConfirmation={this.handleReset}
			/>
		);
		event.preventDefault();
		this.setState({ confirmationDialog });
	},
	handleReset () {
		window.location.reload();
	},
	confirmDelete () {
		const confirmationDialog = (
			<ConfirmationDialog
				isOpen
				body={`Are you sure you want to delete <strong>${this.props.data.name}?</strong><br /><br />This cannot be undone.`}
				confirmationLabel="Delete"
				onCancel={this.removeConfirmationDialog}
				onConfirmation={this.handleDelete}
			/>
		);
		this.setState({ confirmationDialog });
	},
	handleDelete () {
		const { data, list } = this.props;
		this.props.dispatch(deleteItem(data.id, this.props.router));
	},
	handleKeyFocus () {
		const input = findDOMNode(this.refs.keyOrIdInput);
		input.select();
	},
	removeConfirmationDialog () {
		this.setState({
			confirmationDialog: null,
		});
	},
	updateItem () {
		const { data, list } = this.props;
		const editForm = this.refs.editForm.getDOMNode();
		const formData = new FormData(editForm);

		// Clear alerts
		this.setState({
			alerts: null,
			loading: true,
		});

		list.updateItem(data.id, formData, (err, data) => {
			// TODO: implement smooth scolling
			scrollTo(0, 0); // Scroll to top
			if (err) {
				this.setState({
					alerts: {
						error: err,
					},
					loading: false,
				});
			} else {
				// Success, display success flash messages, replace values
				// TODO: Update key value
				this.setState({
					alerts: {
						success: {
							success: 'update success',
						},
					},
					values: data.fields,
					loading: false,
				});
			}
		});
	},
	renderKeyOrId () {
		var className = 'EditForm__key-or-id';
		var list = this.props.list;

		if (list.nameField && list.autokey && this.props.data[list.autokey.path]) {
			return (

					<AltText
						normal={`${upCase(list.autokey.path)}: `}
						modified="ID:"
						component="span"
						title="Press <alt> to reveal the ID"
						className="EditForm__key-or-id__label" />
					<AltText
						normal={<input ref="keyOrIdInput" onFocus={this.handleKeyFocus} value={this.props.data[list.autokey.path]} className="EditForm__key-or-id__input" readOnly />}
						modified={<input ref="keyOrIdInput" onFocus={this.handleKeyFocus} value={this.props.data.id} className="EditForm__key-or-id__input" readOnly />}
						component="span"
						title="Press <alt> to reveal the ID"
						className="EditForm__key-or-id__field" />

			);
		} else if (list.autokey && this.props.data[list.autokey.path]) {
			return (

					{list.autokey.path}:

						<input ref="keyOrIdInput" onFocus={this.handleKeyFocus} value={this.props.data[list.autokey.path]} className="EditForm__key-or-id__input" readOnly />

			);
		} else if (list.nameField) {
			return (

					ID:

						<input ref="keyOrIdInput" onFocus={this.handleKeyFocus} value={this.props.data.id} className="EditForm__key-or-id__input" readOnly />

			);
		}
	},
	renderNameField () {
		var nameField = this.props.list.nameField;
		var nameIsEditable = this.props.list.nameIsEditable;
		var wrapNameField = field => (

				{field}

		);
		if (nameIsEditable) {
			var nameFieldProps = this.getFieldProps(nameField);
			nameFieldProps.label = null;
			nameFieldProps.size = 'full';
			nameFieldProps.inputProps = {
				className: 'item-name-field',
				placeholder: nameField.label,
				size: 'lg',
			};
			return wrapNameField(
				React.createElement(Fields[nameField.type], nameFieldProps)
			);
		} else {
			return wrapNameField(

{this.props.data.name || '(no name)'}

			);
		}
	},
	renderFormElements () {
		var headings = 0;

		return this.props.list.uiElements.map((el) => {
			if (el.type === 'heading') {
				headings++;
				el.options.values = this.state.values;
				el.key = 'h-' + headings;
				return React.createElement(FormHeading, el);
			}

			if (el.type === 'field') {
				var field = this.props.list.fields[el.field];
				var props = this.getFieldProps(field);
				if (typeof Fields[field.type] !== 'function') {
					return React.createElement(InvalidFieldType, { type: field.type, path: field.path, key: field.path });
				}
				if (props.dependsOn) {
					props.currentDependencies = {};
					Object.keys(props.dependsOn).forEach(dep => {
						props.currentDependencies[dep] = this.state.values[dep];
					});
				}
				props.key = field.path;
				return React.createElement(Fields[field.type], props);
			}
		}, this);
	},
	renderFooterBar () {
		var buttons = [
			<Button
				key="save"
				type="primary"
				disabled={this.state.loading}
				onClick={() => this.updateItem()}
			>
				{this.state.loading ? (

						<Spinner type="inverted" />
						 Saving

				) : (
					'Save'
				)}
			</Button>,
		];
		buttons.push(
			<Button key="reset" onClick={this.confirmReset} type="link-cancel">
				<ResponsiveText hiddenXS="reset changes" visibleXS="reset" />
			</Button>
		);
		if (!this.props.list.nodelete) {
			buttons.push(
				<Button key="del" onClick={this.confirmDelete} type="link-delete" className="u-float-right">
					<ResponsiveText hiddenXS={`delete ${this.props.list.singular.toLowerCase()}`} visibleXS="delete" />
				</Button>
			);
		}
		return (
			<FooterBar className="EditForm__footer">
				{buttons}
			</FooterBar>
		);
	},
	renderTrackingMeta () {
		if (!this.props.list.tracking) return null;

		var elements = [];
		var data = {};

		if (this.props.list.tracking.createdAt) {
			data.createdAt = this.props.data.fields[this.props.list.tracking.createdAt];
			if (data.createdAt) {
				elements.push(
					<FormField key="createdAt" label="Created on">
						<FormInput noedit title={moment(data.createdAt).format('DD/MM/YYYY h:mm:ssa')}>{moment(data.createdAt).format('Do MMM YYYY')}</FormInput>
					</FormField>
				);
			}
		}

		if (this.props.list.tracking.createdBy) {
			data.createdBy = this.props.data.fields[this.props.list.tracking.createdBy];
			if (data.createdBy && data.createdBy.name) {
				let createdByName = getNameFromData(data.createdBy.name);
				if (createdByName) {
					elements.push(
						<FormField key="createdBy" label="Created by">
							<FormInput noedit>{data.createdBy.name.first} {data.createdBy.name.last}</FormInput>
						</FormField>
					);
				}
			}
		}

		if (this.props.list.tracking.updatedAt) {
			data.updatedAt = this.props.data.fields[this.props.list.tracking.updatedAt];
			if (data.updatedAt && (!data.createdAt || data.createdAt !== data.updatedAt)) {
				elements.push(
					<FormField key="updatedAt" label="Updated on">
						<FormInput noedit title={moment(data.updatedAt).format('DD/MM/YYYY h:mm:ssa')}>{moment(data.updatedAt).format('Do MMM YYYY')}</FormInput>
					</FormField>
				);
			}
		}

		if (this.props.list.tracking.updatedBy) {
			data.updatedBy = this.props.data.fields[this.props.list.tracking.updatedBy];
			if (data.updatedBy && data.updatedBy.name) {
				let updatedByName = getNameFromData(data.updatedBy.name);
				if (updatedByName) {
					elements.push(
						<FormField key="updatedBy" label="Updated by">
							<FormInput noedit>{data.updatedBy.name.first} {data.updatedBy.name.last}</FormInput>
						</FormField>
					);
				}
			}
		}

		return Object.keys(elements).length ? (

Meta

				{elements}

		) : null;
	},
	render () {
		return (
			<form ref="editForm" method="post" encType="multipart/form-data" className="EditForm-container">
				<Row>
					<Col lg="3/4">
						{(this.state.alerts) ? <AlertMessages alerts={this.state.alerts} /> : null}
						<Form type="horizontal" className="EditForm" component="div">
							<input type="hidden" name="action" value="updateItem" />
							<input type="hidden" name={Keystone.csrf.key} value={Keystone.csrf.value} />
							{this.renderNameField()}
							{this.renderKeyOrId()}
							{this.renderFormElements()}
							{this.renderTrackingMeta()}
						</Form>
					</Col>
					<Col lg="1/4"></Col>
				</Row>
				{this.renderFooterBar()}
				{this.state.confirmationDialog}
			</form>
		);
	},
});

module.exports = EditForm;
<commit_msg>Reset item screen from without page reload<commit_after>import React from 'react';
import { findDOMNode } from 'react-dom';
import moment from 'moment';
import {
	Button,
	Col,
	Form,
	FormField,
	FormInput,
	ResponsiveText,
	Row,
	Spinner,
} from 'elemental';

import AlertMessages from '../../../shared/AlertMessages';
import ConfirmationDialog from './../../../shared/ConfirmationDialog';
import Fields from '../../../../utils/fields';

import FormHeading from './FormHeading';
import AltText from './AltText';
import FooterBar from './FooterBar';
import InvalidFieldType from '../../../shared/InvalidFieldType';

import { deleteItem } from '../../List/actions';

function upCase (str) {
	return str.slice(0, 1).toUpperCase() + str.substr(1).toLowerCase();
};

function getNameFromData (data) {
	if (typeof data === 'object') {
		if (typeof data.first === 'string' && typeof data.last === 'string') {
			return data.first + ' ' + data.last;
		} else if (data.id) {
			return data.id;
		}
	}
	return data;
}

var EditForm = React.createClass({
	displayName: 'EditForm',
	propTypes: {
		data: React.PropTypes.object,
		list: React.PropTypes.object,
	},
	getInitialState () {
		return {
			values: Object.assign({}, this.props.data.fields),
			confirmationDialog: null,
			loading: false,
		};
	},
	getFieldProps (field) {
		const props = Object.assign({}, field);
		const alerts = this.state.alerts;
		// Display validation errors inline
		if (alerts && alerts.error && alerts.error.error === 'validation errors') {
			if (alerts.error.detail[field.path]) {
				// NOTE: This won't work yet, as ElementalUI doesn't allow
				// passed in isValid, only invalidates via internal state.
				// PR to fix that: https://github.com/elementalui/elemental/pull/149
				props.isValid = false;
			}
		}
		props.value = this.state.values[field.path];
		props.values = this.state.values;
		props.onChange = this.handleChange;
		props.mode = 'edit';
		return props;
	},
	handleChange (event) {
		const values = Object.assign({}, this.state.values);

		values[event.path] = event.value;
		this.setState({ values });
	},
	confirmReset (event) {
		const confirmationDialog = (
			<ConfirmationDialog
				isOpen
				body={`Reset your changes to <strong>${this.props.data.name}</strong>?`}
				confirmationLabel="Reset"
				onCancel={this.removeConfirmationDialog}
				onConfirmation={this.handleReset}
			/>
		);
		event.preventDefault();
		this.setState({ confirmationDialog });
	},
	handleReset () {
		this.setState({
			values: Object.assign({}, this.props.data.fields),
			confirmationDialog: null,
		});
	},
	confirmDelete () {
		const confirmationDialog = (
			<ConfirmationDialog
				isOpen
				body={`Are you sure you want to delete <strong>${this.props.data.name}?</strong><br /><br />This cannot be undone.`}
				confirmationLabel="Delete"
				onCancel={this.removeConfirmationDialog}
				onConfirmation={this.handleDelete}
			/>
		);
		this.setState({ confirmationDialog });
	},
	handleDelete () {
		const { data, list } = this.props;
		this.props.dispatch(deleteItem(data.id, this.props.router));
	},
	handleKeyFocus () {
		const input = findDOMNode(this.refs.keyOrIdInput);
		input.select();
	},
	removeConfirmationDialog () {
		this.setState({
			confirmationDialog: null,
		});
	},
	updateItem () {
		const { data, list } = this.props;
		const editForm = this.refs.editForm.getDOMNode();
		const formData = new FormData(editForm);

		// Clear alerts
		this.setState({
			alerts: null,
			loading: true,
		});

		list.updateItem(data.id, formData, (err, data) => {
			// TODO: implement smooth scolling
			scrollTo(0, 0); // Scroll to top
			if (err) {
				this.setState({
					alerts: {
						error: err,
					},
					loading: false,
				});
			} else {
				// Success, display success flash messages, replace values
				// TODO: Update key value
				this.setState({
					alerts: {
						success: {
							success: 'update success',
						},
					},
					values: data.fields,
					loading: false,
				});
			}
		});
	},
	renderKeyOrId () {
		var className = 'EditForm__key-or-id';
		var list = this.props.list;

		if (list.nameField && list.autokey && this.props.data[list.autokey.path]) {
			return (

					<AltText
						normal={`${upCase(list.autokey.path)}: `}
						modified="ID:"
						component="span"
						title="Press <alt> to reveal the ID"
						className="EditForm__key-or-id__label" />
					<AltText
						normal={<input ref="keyOrIdInput" onFocus={this.handleKeyFocus} value={this.props.data[list.autokey.path]} className="EditForm__key-or-id__input" readOnly />}
						modified={<input ref="keyOrIdInput" onFocus={this.handleKeyFocus} value={this.props.data.id} className="EditForm__key-or-id__input" readOnly />}
						component="span"
						title="Press <alt> to reveal the ID"
						className="EditForm__key-or-id__field" />

			);
		} else if (list.autokey && this.props.data[list.autokey.path]) {
			return (

					{list.autokey.path}:

						<input ref="keyOrIdInput" onFocus={this.handleKeyFocus} value={this.props.data[list.autokey.path]} className="EditForm__key-or-id__input" readOnly />

			);
		} else if (list.nameField) {
			return (

					ID:

						<input ref="keyOrIdInput" onFocus={this.handleKeyFocus} value={this.props.data.id} className="EditForm__key-or-id__input" readOnly />

			);
		}
	},
	renderNameField () {
		var nameField = this.props.list.nameField;
		var nameIsEditable = this.props.list.nameIsEditable;
		var wrapNameField = field => (

				{field}

		);
		if (nameIsEditable) {
			var nameFieldProps = this.getFieldProps(nameField);
			nameFieldProps.label = null;
			nameFieldProps.size = 'full';
			nameFieldProps.inputProps = {
				className: 'item-name-field',
				placeholder: nameField.label,
				size: 'lg',
			};
			return wrapNameField(
				React.createElement(Fields[nameField.type], nameFieldProps)
			);
		} else {
			return wrapNameField(

{this.props.data.name || '(no name)'}

			);
		}
	},
	renderFormElements () {
		var headings = 0;

		return this.props.list.uiElements.map((el) => {
			if (el.type === 'heading') {
				headings++;
				el.options.values = this.state.values;
				el.key = 'h-' + headings;
				return React.createElement(FormHeading, el);
			}

			if (el.type === 'field') {
				var field = this.props.list.fields[el.field];
				var props = this.getFieldProps(field);
				if (typeof Fields[field.type] !== 'function') {
					return React.createElement(InvalidFieldType, { type: field.type, path: field.path, key: field.path });
				}
				if (props.dependsOn) {
					props.currentDependencies = {};
					Object.keys(props.dependsOn).forEach(dep => {
						props.currentDependencies[dep] = this.state.values[dep];
					});
				}
				props.key = field.path;
				return React.createElement(Fields[field.type], props);
			}
		}, this);
	},
	renderFooterBar () {
		var buttons = [
			<Button
				key="save"
				type="primary"
				disabled={this.state.loading}
				onClick={() => this.updateItem()}
			>
				{this.state.loading ? (

						<Spinner type="inverted" />
						 Saving

				) : (
					'Save'
				)}
			</Button>,
		];
		buttons.push(
			<Button key="reset" onClick={this.confirmReset} type="link-cancel">
				<ResponsiveText hiddenXS="reset changes" visibleXS="reset" />
			</Button>
		);
		if (!this.props.list.nodelete) {
			buttons.push(
				<Button key="del" onClick={this.confirmDelete} type="link-delete" className="u-float-right">
					<ResponsiveText hiddenXS={`delete ${this.props.list.singular.toLowerCase()}`} visibleXS="delete" />
				</Button>
			);
		}
		return (
			<FooterBar className="EditForm__footer">
				{buttons}
			</FooterBar>
		);
	},
	renderTrackingMeta () {
		if (!this.props.list.tracking) return null;

		var elements = [];
		var data = {};

		if (this.props.list.tracking.createdAt) {
			data.createdAt = this.props.data.fields[this.props.list.tracking.createdAt];
			if (data.createdAt) {
				elements.push(
					<FormField key="createdAt" label="Created on">
						<FormInput noedit title={moment(data.createdAt).format('DD/MM/YYYY h:mm:ssa')}>{moment(data.createdAt).format('Do MMM YYYY')}</FormInput>
					</FormField>
				);
			}
		}

		if (this.props.list.tracking.createdBy) {
			data.createdBy = this.props.data.fields[this.props.list.tracking.createdBy];
			if (data.createdBy && data.createdBy.name) {
				let createdByName = getNameFromData(data.createdBy.name);
				if (createdByName) {
					elements.push(
						<FormField key="createdBy" label="Created by">
							<FormInput noedit>{data.createdBy.name.first} {data.createdBy.name.last}</FormInput>
						</FormField>
					);
				}
			}
		}

		if (this.props.list.tracking.updatedAt) {
			data.updatedAt = this.props.data.fields[this.props.list.tracking.updatedAt];
			if (data.updatedAt && (!data.createdAt || data.createdAt !== data.updatedAt)) {
				elements.push(
					<FormField key="updatedAt" label="Updated on">
						<FormInput noedit title={moment(data.updatedAt).format('DD/MM/YYYY h:mm:ssa')}>{moment(data.updatedAt).format('Do MMM YYYY')}</FormInput>
					</FormField>
				);
			}
		}

		if (this.props.list.tracking.updatedBy) {
			data.updatedBy = this.props.data.fields[this.props.list.tracking.updatedBy];
			if (data.updatedBy && data.updatedBy.name) {
				let updatedByName = getNameFromData(data.updatedBy.name);
				if (updatedByName) {
					elements.push(
						<FormField key="updatedBy" label="Updated by">
							<FormInput noedit>{data.updatedBy.name.first} {data.updatedBy.name.last}</FormInput>
						</FormField>
					);
				}
			}
		}

		return Object.keys(elements).length ? (

Meta

				{elements}

		) : null;
	},
	render () {
		return (
			<form ref="editForm" method="post" encType="multipart/form-data" className="EditForm-container">
				<Row>
					<Col lg="3/4">
						{(this.state.alerts) ? <AlertMessages alerts={this.state.alerts} /> : null}
						<Form type="horizontal" className="EditForm" component="div">
							<input type="hidden" name="action" value="updateItem" />
							<input type="hidden" name={Keystone.csrf.key} value={Keystone.csrf.value} />
							{this.renderNameField()}
							{this.renderKeyOrId()}
							{this.renderFormElements()}
							{this.renderTrackingMeta()}
						</Form>
					</Col>
					<Col lg="1/4"></Col>
				</Row>
				{this.renderFooterBar()}
				{this.state.confirmationDialog}
			</form>
		);
	},
});

module.exports = EditForm;
<|endoftext|>


<commit_before>var React = require('react');
var TestUtils = require('react-addons-test-utils');
var ReactDOM = require('react-dom');

describe("Slider", function() {
	var findByTag = TestUtils.scryRenderedDOMComponentsWithTag;
	var findByClass = TestUtils.scryRenderedDOMComponentsWithClass;

	jest.autoMockOff();

	var Carousel = require('../components/Carousel');

	var component, componentInstance;

	function renderComponent (props) {
		componentInstance = TestUtils.renderIntoDocument(
	  		<Carousel {...props} showArrows={true}>

	  		</Carousel>
	  	);
	}

	beforeEach(function () {
		renderComponent({});
	});

	afterEach(function() {
		if (componentInstance && componentInstance.isMounted()) {
	      // Only components with a parent will be unmounted
	      ReactDOM.unmountComponentAtNode(document);
	    }
  	});

  	describe("Basics", function () {
  		describe("changeItem", function () {
  			beforeEach(function () {
				componentInstance.selectItem = jest.genMockFunction();
				componentInstance.getFirstItem = jest.genMockFunction().mockReturnValue(2);
				componentInstance.changeItem({
					target: {
						value: 1
					}
				});
			});

  			it("Should call selectItem sending selectedItem as 1", function () {
  				expect(componentInstance.selectItem.mock.calls[0][0]).toEqual({
					selectedItem: 1
				});
  			});
  		});

  		describe("selectItem", function () {
  			beforeEach(function () {
				componentInstance.setState = jest.genMockFunction();
				componentInstance.handleOnChange = jest.genMockFunction();
				componentInstance.selectItem({
					selectedItem: 1,
					ramdomNumber: 2
				});
			});

  			it("Should call setState sending the argument received", function () {
  				expect(componentInstance.setState.mock.calls[0][0]).toEqual({
					selectedItem: 1,
					ramdomNumber: 2
				});
  			});

  			it("Should call handleOnChange sending only selectedItem", function () {
  				console.log(componentInstance.handleOnChange.mock.calls[0][0]);
				expect(componentInstance.handleOnChange.mock.calls[0][0]).toBe(1);
  			});
  		});
  	});

	it("Should have the right state at the begin", function () {
		expect(componentInstance.state.selectedItem).toBe(0);
		expect(componentInstance.state.hasMount).toBe(false);
	});

	it("Should add a thumb-wrapper container", function () {
		expect(findByClass(componentInstance, 'thumbs-wrapper').length).toBe(1);
	});

	describe("Moving", function () {
		beforeEach(function () {
			componentInstance.showArrows = true;
			componentInstance.lastPosition = 3;
			componentInstance.visibleItems = 3;
		});

		it("Should set the selectedItem from the props", function () {
			renderComponent({selectedItem: 3});
			expect(componentInstance.state.selectedItem).toBe(3);
		});

		it("Should update the position of the Carousel if selectedItem is changed", function () {
			TestUtils.Simulate.click(componentInstance['item2']);
			expect(componentInstance.state.selectedItem).toBe(2);

			TestUtils.Simulate.click(componentInstance['item3']);
			expect(componentInstance.state.selectedItem).toBe(3);
		});
	})

	describe("Selecting", function () {
		it("Should set the index as selectedItem when clicked", function () {
			expect(componentInstance.state.selectedItem).toBe(0);

			TestUtils.Simulate.click(componentInstance['item1']);
			expect(componentInstance.state.selectedItem).toBe(1);

			TestUtils.Simulate.click(componentInstance['item3']);
			expect(componentInstance.state.selectedItem).toBe(3);
		});

		it("Should call a given onSelectItem function when an item is clicked", function () {
			var mockedFunction = jest.genMockFunction();

			renderComponent({onClickItem: mockedFunction});

			TestUtils.Simulate.click(componentInstance['item1']);
			expect(mockedFunction).toBeCalled();
		});
	})

	describe("Navigating", function () {
		beforeEach(function () {
			componentInstance.showArrows = true;
		});

		it("Should disable the left arrow if we are showing the first item", function () {
			TestUtils.Simulate.click(componentInstance['item0']);
			expect(ReactDOM.findDOMNode(componentInstance).querySelectorAll('.carousel-slider .control-prev.control-disabled').length).toBe(1);
		});

		it("Should enable the left arrow if we are showing other than the first item", function () {
			TestUtils.Simulate.click(componentInstance['item1']);
			expect(ReactDOM.findDOMNode(componentInstance).querySelectorAll('.carousel-slider .control-prev.control-disabled').length).toBe(0);
		});

		it("Should disable the right arrow if we reach the lastPosition", function () {
			TestUtils.Simulate.click(componentInstance['item1']);
			expect(ReactDOM.findDOMNode(componentInstance).querySelectorAll('.carousel-slider .control-next.control-disabled').length).toBe(0);

			TestUtils.Simulate.click(componentInstance['item6']);
			expect(ReactDOM.findDOMNode(componentInstance).querySelectorAll('.carousel-slider .control-next.control-disabled').length).toBe(1);
		});
	})

	jest.autoMockOn();
});

<commit_msg>Comment tests<commit_after>var React = require('react');
var TestUtils = require('react-addons-test-utils');
var ReactDOM = require('react-dom');

describe("Slider", function() {
	var findByTag = TestUtils.scryRenderedDOMComponentsWithTag;
	var findByClass = TestUtils.scryRenderedDOMComponentsWithClass;

	jest.autoMockOff();

	var Carousel = require('../components/Carousel');

	var component, componentInstance;

	function renderComponent (props) {
		componentInstance = TestUtils.renderIntoDocument(
  		<Carousel {...props} showArrows={true}>

  		</Carousel>
  	);
	}

	beforeEach(function () {
		renderComponent({});
	});

	afterEach(function() {
		if (componentInstance && componentInstance.isMounted()) {
      // Only components with a parent will be unmounted
      ReactDOM.unmountComponentAtNode(document);
    }
	});

	describe("Basics", function () {
		describe("changeItem", function () {
			beforeEach(function () {
				componentInstance.selectItem = jest.genMockFunction();
				componentInstance.getFirstItem = jest.genMockFunction().mockReturnValue(2);
				componentInstance.changeItem({
					target: {
						value: 1
					}
				});
			});

			it("Should call selectItem sending selectedItem as 1", function () {
				expect(componentInstance.selectItem.mock.calls[0][0]).toEqual({
					selectedItem: 1
				});
			});
		});

		describe("selectItem", function () {
			beforeEach(function () {
				componentInstance.setState = jest.genMockFunction();
				componentInstance.handleOnChange = jest.genMockFunction();
				componentInstance.selectItem({
					selectedItem: 1,
					ramdomNumber: 2
				});
			});

			it("Should call setState sending the argument received", function () {
				expect(componentInstance.setState.mock.calls[0][0]).toEqual({
					selectedItem: 1,
					ramdomNumber: 2
				});
			});

			it("Should call handleOnChange sending only selectedItem", function () {
				console.log(componentInstance.handleOnChange.mock.calls[0][0]);
				expect(componentInstance.handleOnChange.mock.calls[0][0]).toBe(1);
			});
		});
	});

	it("Should have the right state at the begin", function () {
		expect(componentInstance.state.selectedItem).toBe(0);
		expect(componentInstance.state.hasMount).toBe(false);
	});

	it("Should add a thumb-wrapper container", function () {
		expect(findByClass(componentInstance, 'thumbs-wrapper').length).toBe(1);
	});

	describe("Moving", function () {
		beforeEach(function () {
			componentInstance.showArrows = true;
			componentInstance.lastPosition = 3;
			componentInstance.visibleItems = 3;
		});

		it("Should set the selectedItem from the props", function () {
			renderComponent({selectedItem: 3});
			expect(componentInstance.state.selectedItem).toBe(3);
		});

		it("Should update the position of the Carousel if selectedItem is changed", function () {
			TestUtils.Simulate.click(componentInstance['item2']);
			expect(componentInstance.state.selectedItem).toBe(2);

			TestUtils.Simulate.click(componentInstance['item3']);
			expect(componentInstance.state.selectedItem).toBe(3);
		});
	})

	describe("Selecting", function () {
		it("Should set the index as selectedItem when clicked", function () {
			expect(componentInstance.state.selectedItem).toBe(0);

			TestUtils.Simulate.click(componentInstance['item1']);
			expect(componentInstance.state.selectedItem).toBe(1);

			TestUtils.Simulate.click(componentInstance['item3']);
			expect(componentInstance.state.selectedItem).toBe(3);
		});

		it("Should call a given onSelectItem function when an item is clicked", function () {
			var mockedFunction = jest.genMockFunction();

			renderComponent({onClickItem: mockedFunction});

			TestUtils.Simulate.click(componentInstance['item1']);
			expect(mockedFunction).toBeCalled();
		});
	})

	describe("Navigating", function () {
		beforeEach(function () {
			componentInstance.showArrows = true;
		});

		it("Should disable the left arrow if we are showing the first item", function () {
			TestUtils.Simulate.click(componentInstance['item0']);
			expect(ReactDOM.findDOMNode(componentInstance).querySelectorAll('.carousel-slider .control-prev.control-disabled').length).toBe(1);
		});

		it("Should enable the left arrow if we are showing other than the first item", function () {
			TestUtils.Simulate.click(componentInstance['item1']);
			expect(ReactDOM.findDOMNode(componentInstance).querySelectorAll('.carousel-slider .control-prev.control-disabled').length).toBe(0);
		});

		it("Should disable the right arrow if we reach the lastPosition", function () {
			TestUtils.Simulate.click(componentInstance['item1']);
			expect(ReactDOM.findDOMNode(componentInstance).querySelectorAll('.carousel-slider .control-next.control-disabled').length).toBe(0);

			TestUtils.Simulate.click(componentInstance['item6']);
			expect(ReactDOM.findDOMNode(componentInstance).querySelectorAll('.carousel-slider .control-next.control-disabled').length).toBe(1);
		});

		// describe("Infinite Loop", function () {
		// 	beforeEach(function () {
		// 		componentInstance.infiniteLoop = true;
		// 	});
		//
		// 	it("Should enable the prev arrow if we are showing the first item", function () {
		// 		TestUtils.Simulate.click(componentInstance['item0']);
		// 		expect(ReactDOM.findDOMNode(componentInstance).querySelectorAll('.carousel-slider .control-prev.control-disabled').length).toBe(0);
		// 	});
		//
		// 	it("Should enable the right arrow if we reach the lastPosition", function () {
		// 		TestUtils.Simulate.click(componentInstance['item6']);
		// 		expect(ReactDOM.findDOMNode(componentInstance).querySelectorAll('.carousel-slider .control-next.control-disabled').length).toBe(0);
		// 	});
		// });
	})

	jest.autoMockOn();
});
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */

/* More information about these options at jshint.com/docs/options */

/* exported setUpFullScreen, fullScreenElement, isFullScreen,
   requestTurnServers, sendAsyncUrlRequest, randomString */
/* globals chrome */

'use strict';

// Sends the URL request and returns a Promise as the result.
function sendAsyncUrlRequest(method, url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState !== 4) {
        return;
      }
      if (xhr.status !== 200) {
        reject(
            Error('Status=' + xhr.status + ', response=' + xhr.responseText));
        return;
      }
      resolve(xhr.responseText);
    };
    xhr.open(method, url, true);
    xhr.send();
  });
}

// Returns a list of turn servers after requesting it from CEOD.
function requestTurnServers(turnRequestUrl, turnTransports) {
  return new Promise(function(resolve, reject) {
    sendAsyncUrlRequest('GET', turnRequestUrl).then(function(response) {
      var turnServerResponse = parseJSON(response);
      if (!turnServerResponse) {
        reject(Error('Error parsing response JSON: ' + response));
        return;
      }
      // Filter the TURN URLs to only use the desired transport, if specified.
      if (turnTransports.length > 0) {
        filterTurnUrls(turnServerResponse.uris, turnTransports);
      }

      // Create the RTCIceServer objects from the response.
      var turnServers = createIceServers(turnServerResponse.uris,
          turnServerResponse.username, turnServerResponse.password);
      if (!turnServers) {
        reject(Error('Error creating ICE servers from response.'));
        return;
      }
      trace('Retrieved TURN server information.');
      resolve(turnServers);
    }).catch(function(error) {
      reject(Error('TURN server request error: ' + error.message));
      return;
    });
  });
}

// Parse the supplied JSON, or return null if parsing fails.
function parseJSON(json) {
  try {
    return JSON.parse(json);
  } catch (e) {
    trace('Error parsing json: ' + json);
  }
  return null;
}

// Filter a list of TURN urls to only contain those with transport=|protocol|.
function filterTurnUrls(urls, protocol) {
  for (var i = 0; i < urls.length;) {
    var parts = urls[i].split('?');
    if (parts.length > 1 && parts[1] !== ('transport=' + protocol)) {
      urls.splice(i, 1);
    } else {
      ++i;
    }
  }
}

// Start shims for fullscreen
function setUpFullScreen() {
  if (isChromeApp()) {
    document.cancelFullScreen = function() {
      chrome.app.window.current().restore();
    };
  } else {
    document.cancelFullScreen = document.webkitCancelFullScreen ||
        document.mozCancelFullScreen || document.cancelFullScreen;
  }

  if (isChromeApp()) {
    document.body.requestFullScreen = function() {
      chrome.app.window.current().fullscreen();
    };
  } else {
    document.body.requestFullScreen = document.body.webkitRequestFullScreen ||
        document.body.mozRequestFullScreen || document.body.requestFullScreen;
  }

  document.onfullscreenchange = document.onfullscreenchange ||
        document.onwebkitfullscreenchange || document.onmozfullscreenchange;
}

function isFullScreen() {
  if (isChromeApp()) {
    return chrome.app.window.current().isFullscreen();
  }

  return !!(document.webkitIsFullScreen || document.mozFullScreen ||
    document.isFullScreen); // if any defined and true
}

function fullScreenElement() {
  return document.webkitFullScreenElement ||
      document.webkitCurrentFullScreenElement ||
      document.mozFullScreenElement ||
      document.fullScreenElement;
}

// End shims for fullscreen

// Return a random numerical string.
function randomString(strLength) {
  var result = [];
  strLength = strLength || 5;
  var charSet = '0123456789';
  while (strLength--) {
    result.push(charSet.charAt(Math.floor(Math.random() * charSet.length)));
  }
  return result.join('');
}

// Returns true if the code is running in a packaged Chrome App.
function isChromeApp() {
  return (typeof chrome !== 'undefined' &&
          typeof chrome.storage !== 'undefined' &&
          typeof chrome.storage.local !== 'undefined');
}
<commit_msg>Allow sendAsyncUrlRequest to include body content.<commit_after>/*
 *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */

/* More information about these options at jshint.com/docs/options */

/* exported setUpFullScreen, fullScreenElement, isFullScreen,
   requestTurnServers, sendAsyncUrlRequest, randomString */
/* globals chrome */

'use strict';

// Sends the URL request and returns a Promise as the result.
function sendAsyncUrlRequest(method, url, body) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState !== 4) {
        return;
      }
      if (xhr.status !== 200) {
        reject(
            Error('Status=' + xhr.status + ', response=' + xhr.responseText));
        return;
      }
      resolve(xhr.responseText);
    };
    xhr.open(method, url, true);
    xhr.send(body);
  });
}

// Returns a list of turn servers after requesting it from CEOD.
function requestTurnServers(turnRequestUrl, turnTransports) {
  return new Promise(function(resolve, reject) {
    sendAsyncUrlRequest('GET', turnRequestUrl).then(function(response) {
      var turnServerResponse = parseJSON(response);
      if (!turnServerResponse) {
        reject(Error('Error parsing response JSON: ' + response));
        return;
      }
      // Filter the TURN URLs to only use the desired transport, if specified.
      if (turnTransports.length > 0) {
        filterTurnUrls(turnServerResponse.uris, turnTransports);
      }

      // Create the RTCIceServer objects from the response.
      var turnServers = createIceServers(turnServerResponse.uris,
          turnServerResponse.username, turnServerResponse.password);
      if (!turnServers) {
        reject(Error('Error creating ICE servers from response.'));
        return;
      }
      trace('Retrieved TURN server information.');
      resolve(turnServers);
    }).catch(function(error) {
      reject(Error('TURN server request error: ' + error.message));
      return;
    });
  });
}

// Parse the supplied JSON, or return null if parsing fails.
function parseJSON(json) {
  try {
    return JSON.parse(json);
  } catch (e) {
    trace('Error parsing json: ' + json);
  }
  return null;
}

// Filter a list of TURN urls to only contain those with transport=|protocol|.
function filterTurnUrls(urls, protocol) {
  for (var i = 0; i < urls.length;) {
    var parts = urls[i].split('?');
    if (parts.length > 1 && parts[1] !== ('transport=' + protocol)) {
      urls.splice(i, 1);
    } else {
      ++i;
    }
  }
}

// Start shims for fullscreen
function setUpFullScreen() {
  if (isChromeApp()) {
    document.cancelFullScreen = function() {
      chrome.app.window.current().restore();
    };
  } else {
    document.cancelFullScreen = document.webkitCancelFullScreen ||
        document.mozCancelFullScreen || document.cancelFullScreen;
  }

  if (isChromeApp()) {
    document.body.requestFullScreen = function() {
      chrome.app.window.current().fullscreen();
    };
  } else {
    document.body.requestFullScreen = document.body.webkitRequestFullScreen ||
        document.body.mozRequestFullScreen || document.body.requestFullScreen;
  }

  document.onfullscreenchange = document.onfullscreenchange ||
        document.onwebkitfullscreenchange || document.onmozfullscreenchange;
}

function isFullScreen() {
  if (isChromeApp()) {
    return chrome.app.window.current().isFullscreen();
  }

  return !!(document.webkitIsFullScreen || document.mozFullScreen ||
    document.isFullScreen); // if any defined and true
}

function fullScreenElement() {
  return document.webkitFullScreenElement ||
      document.webkitCurrentFullScreenElement ||
      document.mozFullScreenElement ||
      document.fullScreenElement;
}

// End shims for fullscreen

// Return a random numerical string.
function randomString(strLength) {
  var result = [];
  strLength = strLength || 5;
  var charSet = '0123456789';
  while (strLength--) {
    result.push(charSet.charAt(Math.floor(Math.random() * charSet.length)));
  }
  return result.join('');
}

// Returns true if the code is running in a packaged Chrome App.
function isChromeApp() {
  return (typeof chrome !== 'undefined' &&
          typeof chrome.storage !== 'undefined' &&
          typeof chrome.storage.local !== 'undefined');
}
<|endoftext|>


<commit_before>// Map of all apllication controllers

'use strict';

var copy            = require('es5-ext/object/copy')
  , joinControllers = require('mano/utils/join-controllers')
  , globalRoutes    = require('mano/controller/server');

var publicGlobalRoutes = copy(globalRoutes);
delete publicGlobalRoutes.dbupdate;

var publicRoutes = joinControllers(
	require("../../../apps/public/controller"),
	require("../../../apps/public/controller/server-master"),
	{ extendedOnly: true, globalRoutes: publicGlobalRoutes }
);

var nonPublicGlobalRoutes = copy(globalRoutes);
globalRoutes.login = publicRoutes.login;

var joinOptions = { extendedOnly: true, globalRoutes: nonPublicGlobalRoutes };

module.exports = {
	"apps/business-process-demo": joinControllers(
		require("../../../apps/business-process-demo/controller"),
		null,
		joinOptions
	),
	"apps/business-process-submitted": joinControllers(
		require("../../../apps/business-process-submitted/controller"),
		null,
		joinOptions
	),
	"apps/meta-admin": joinControllers(
		require("../../../apps/meta-admin/controller"),
		null,
		joinOptions
	),
	"apps/official-revision": joinControllers(
		require("../../../apps/official-revision/controller"),
		null,
		joinOptions
	),
	"apps/official-processing": joinControllers(
		require("../../../apps/official-processing/controller"),
		null,
		joinOptions
	),
	"apps/official-social-security": joinControllers(
		require("../../../apps/official-social-security/controller"),
		null,
		joinOptions
	),
	"apps/official-front-desk": joinControllers(
		require("../../../apps/official-front-desk/controller"),
		null,
		joinOptions
	),
	"apps/public": publicRoutes,
	"apps/user": joinControllers(
		require("../../../apps/user/controller"),
		null,
		joinOptions
	),
	"apps/users-admin": joinControllers(
		require("../../../apps/users-admin/controller"),
		null,
		joinOptions
	)
};
<commit_msg>Issue #14. Adapt to eregistrations#1647<commit_after>// Map of all apllication controllers

'use strict';

var copy            = require('es5-ext/object/copy')
  , joinControllers = require('mano/utils/join-controllers')
  , globalRoutes    = require('mano/controller/server');

var publicGlobalRoutes = copy(globalRoutes);
delete publicGlobalRoutes.dbupdate;

var publicRoutes = joinControllers(
	require("../../../apps/public/controller"),
	require("../../../apps/public/controller/server-master"),
	{ extendedOnly: true, globalRoutes: publicGlobalRoutes }
);

var nonPublicGlobalRoutes = copy(globalRoutes);
nonPublicGlobalRoutes.login = publicRoutes.login;

var joinOptions = { extendedOnly: true, globalRoutes: nonPublicGlobalRoutes };

module.exports = {
	"apps/business-process-demo": joinControllers(
		require("../../../apps/business-process-demo/controller"),
		null,
		joinOptions
	),
	"apps/business-process-submitted": joinControllers(
		require("../../../apps/business-process-submitted/controller"),
		null,
		joinOptions
	),
	"apps/meta-admin": joinControllers(
		require("../../../apps/meta-admin/controller"),
		null,
		joinOptions
	),
	"apps/official-revision": joinControllers(
		require("../../../apps/official-revision/controller"),
		null,
		joinOptions
	),
	"apps/official-processing": joinControllers(
		require("../../../apps/official-processing/controller"),
		null,
		joinOptions
	),
	"apps/official-social-security": joinControllers(
		require("../../../apps/official-social-security/controller"),
		null,
		joinOptions
	),
	"apps/official-front-desk": joinControllers(
		require("../../../apps/official-front-desk/controller"),
		null,
		joinOptions
	),
	"apps/public": publicRoutes,
	"apps/user": joinControllers(
		require("../../../apps/user/controller"),
		null,
		joinOptions
	),
	"apps/users-admin": joinControllers(
		require("../../../apps/users-admin/controller"),
		null,
		joinOptions
	)
};
<|endoftext|>