<commit_before>'use strict';

var assign = require('object-assign'),
	PropTypes = require('prop-types'),
    createClass = require('create-react-class'),
	moment = require('moment'),
	React = require('react'),
	CalendarContainer = require('./src/CalendarContainer')
;

var TYPES = PropTypes;
var Datetime = createClass({
	propTypes: {
		// value: TYPES.object | TYPES.string,
		// defaultValue: TYPES.object | TYPES.string,
		onFocus: TYPES.func,
		onBlur: TYPES.func,
		onChange: TYPES.func,
		onViewModeChange: TYPES.func,
		locale: TYPES.string,
		utc: TYPES.bool,
		input: TYPES.bool,
		// dateFormat: TYPES.string | TYPES.bool,
		// timeFormat: TYPES.string | TYPES.bool,
		inputProps: TYPES.object,
		timeConstraints: TYPES.object,
		viewMode: TYPES.oneOf(['years', 'months', 'days', 'time']),
		isValidDate: TYPES.func,
		open: TYPES.bool,
		strictParsing: TYPES.bool,
		closeOnSelect: TYPES.bool,
		closeOnTab: TYPES.bool
	},

	getDefaultProps: function() {
		var nof = function() {};
		return {
			className: '',
			defaultValue: '',
			inputProps: {},
			input: true,
			onFocus: nof,
			onBlur: nof,
			onChange: nof,
			onViewModeChange: nof,
			timeFormat: true,
			timeConstraints: {},
			dateFormat: true,
			strictParsing: true,
			closeOnSelect: false,
			closeOnTab: true,
			utc: false
		};
	},

	getInitialState: function() {
		var state = this.getStateFromProps( this.props );

		if ( state.open === undefined )
			state.open = !this.props.input;

		state.currentView = this.props.dateFormat ? (this.props.viewMode || state.updateOn || 'days') : 'time';

		return state;
	},

	getStateFromProps: function( props ) {
		var formats = this.getFormats( props ),
			date = props.value || props.defaultValue,
			selectedDate, viewDate, updateOn, inputValue
		;

		if ( date && typeof date === 'string' )
			selectedDate = this.localMoment( date, formats.datetime );
		else if ( date )
			selectedDate = this.localMoment( date );

		if ( selectedDate && !selectedDate.isValid() )
			selectedDate = null;

		viewDate = selectedDate ?
			selectedDate.clone().startOf('month') :
			this.localMoment().startOf('month')
		;

		updateOn = this.getUpdateOn(formats);

		if ( selectedDate )
			inputValue = selectedDate.format(formats.datetime);
		else if ( date.isValid && !date.isValid() )
			inputValue = '';
		else
			inputValue = date || '';

		return {
			updateOn: updateOn,
			inputFormat: formats.datetime,
			viewDate: viewDate,
			selectedDate: selectedDate,
			inputValue: inputValue,
			open: props.open
		};
	},

	getUpdateOn: function( formats ) {
        if ( formats.date.match(/[lLD]/) ) {
			return 'days';
		} else if ( formats.date.indexOf('M') !== -1 ) {
			return 'months';
		} else if ( formats.date.indexOf('Y') !== -1 ) {
			return 'years';
		}

		return 'days';
	},

	getFormats: function( props ) {
		var formats = {
				date: props.dateFormat || '',
				time: props.timeFormat || ''
			},
			locale = this.localMoment( props.date, null, props ).localeData()
		;

		if ( formats.date === true ) {
			formats.date = locale.longDateFormat('L');
		}
		else if ( this.getUpdateOn(formats) !== 'days' ) {
			formats.time = '';
		}

		if ( formats.time === true ) {
			formats.time = locale.longDateFormat('LT');
		}

		formats.datetime = formats.date && formats.time ?
			formats.date + ' ' + formats.time :
			formats.date || formats.time
		;

		return formats;
	},

	componentWillReceiveProps: function( nextProps ) {
		var formats = this.getFormats( nextProps ),
			updatedState = {}
		;

		if ( nextProps.value !== this.props.value ||
			formats.datetime !== this.getFormats( this.props ).datetime ) {
			updatedState = this.getStateFromProps( nextProps );
		}

		if ( updatedState.open === undefined ) {
			if ( this.props.closeOnSelect && this.state.currentView !== 'time' ) {
				updatedState.open = false;
			} else {
				updatedState.open = this.state.open;
			}
		}

		if ( nextProps.viewMode !== this.props.viewMode ) {
			updatedState.currentView = nextProps.viewMode;
		}

		if ( nextProps.locale !== this.props.locale ) {
			if ( this.state.viewDate ) {
				var updatedViewDate = this.state.viewDate.clone().locale( nextProps.locale );
				updatedState.viewDate = updatedViewDate;
			}
			if ( this.state.selectedDate ) {
				var updatedSelectedDate = this.state.selectedDate.clone().locale( nextProps.locale );
				updatedState.selectedDate = updatedSelectedDate;
				updatedState.inputValue = updatedSelectedDate.format( formats.datetime );
			}
		}

		if ( nextProps.utc !== this.props.utc ) {
			if ( nextProps.utc ) {
				if ( this.state.viewDate )
					updatedState.viewDate = this.state.viewDate.clone().utc();
				if ( this.state.selectedDate ) {
					updatedState.selectedDate = this.state.selectedDate.clone().utc();
					updatedState.inputValue = updatedState.selectedDate.format( formats.datetime );
				}
			} else {
				if ( this.state.viewDate )
					updatedState.viewDate = this.state.viewDate.clone().local();
				if ( this.state.selectedDate ) {
					updatedState.selectedDate = this.state.selectedDate.clone().local();
					updatedState.inputValue = updatedState.selectedDate.format(formats.datetime);
				}
			}
		}

		this.setState( updatedState );
	},

	onInputChange: function( e ) {
		var value = e.target === null ? e : e.target.value,
			localMoment = this.localMoment( value, this.state.inputFormat ),
			update = { inputValue: value }
		;

		if ( localMoment.isValid() && !this.props.value ) {
			update.selectedDate = localMoment;
			update.viewDate = localMoment.clone().startOf('month');
		} else {
			update.selectedDate = null;
		}

		return this.setState( update, function() {
			return this.props.onChange( localMoment.isValid() ? localMoment : this.state.inputValue );
		});
	},

	onInputKey: function( e ) {
		if ( e.which === 9 && this.props.closeOnTab ) {
			this.closeCalendar();
		}
	},

	showView: function( view ) {
		var me = this;
		return function() {
			me.state.currentView !== view && me.props.onViewModeChange( view );
			me.setState({ currentView: view });
		};
	},

	setDate: function( type ) {
		var me = this,
			nextViews = {
				month: 'days',
				year: 'months'
			}
		;
		return function( e ) {
			me.setState({
				viewDate: me.state.viewDate.clone()[ type ]( parseInt(e.target.getAttribute('data-value'), 10) ).startOf( type ),
				currentView: nextViews[ type ]
			});
			me.props.onViewModeChange( nextViews[ type ] );
		};
	},

	addTime: function( amount, type, toSelected ) {
		return this.updateTime( 'add', amount, type, toSelected );
	},

	subtractTime: function( amount, type, toSelected ) {
		return this.updateTime( 'subtract', amount, type, toSelected );
	},

	updateTime: function( op, amount, type, toSelected ) {
		var me = this;

		return function() {
			var update = {},
				date = toSelected ? 'selectedDate' : 'viewDate'
			;

			update[ date ] = me.state[ date ].clone()[ op ]( amount, type );

			me.setState( update );
		};
	},

	allowedSetTime: ['hours', 'minutes', 'seconds', 'milliseconds'],
	setTime: function( type, value ) {
		var index = this.allowedSetTime.indexOf( type ) + 1,
			state = this.state,
			date = (state.selectedDate || state.viewDate).clone(),
			nextType
		;

		// It is needed to set all the time properties
		// to not to reset the time
		date[ type ]( value );
		for (; index < this.allowedSetTime.length; index++) {
			nextType = this.allowedSetTime[index];
			date[ nextType ]( date[nextType]() );
		}

		if ( !this.props.value ) {
			this.setState({
				selectedDate: date,
				inputValue: date.format( state.inputFormat )
			});
		}
		this.props.onChange( date );
	},

	updateSelectedDate: function( e, close ) {
		var target = e.target,
			modifier = 0,
			viewDate = this.state.viewDate,
			currentDate = this.state.selectedDate || viewDate,
			date
		;

		if (target.className.indexOf('rdtDay') !== -1) {
			if (target.className.indexOf('rdtNew') !== -1)
				modifier = 1;
			else if (target.className.indexOf('rdtOld') !== -1)
				modifier = -1;

			date = viewDate.clone()
				.month( viewDate.month() + modifier )
				.date( parseInt( target.getAttribute('data-value'), 10 ) );
		} else if (target.className.indexOf('rdtMonth') !== -1) {
			date = viewDate.clone()
				.month( parseInt( target.getAttribute('data-value'), 10 ) )
				.date( currentDate.date() );
		} else if (target.className.indexOf('rdtYear') !== -1) {
			date = viewDate.clone()
				.month( currentDate.month() )
				.date( currentDate.date() )
				.year( parseInt( target.getAttribute('data-value'), 10 ) );
		}

		date.hours( currentDate.hours() )
			.minutes( currentDate.minutes() )
			.seconds( currentDate.seconds() )
			.milliseconds( currentDate.milliseconds() );

		if ( !this.props.value ) {
			var open = !( this.props.closeOnSelect && close );
			if ( !open ) {
				this.props.onBlur( date );
			}

			this.setState({
				selectedDate: date,
				viewDate: date.clone().startOf('month'),
				inputValue: date.format( this.state.inputFormat ),
				open: open
			});
		} else {
			if ( this.props.closeOnSelect && close ) {
				this.closeCalendar();
			}
		}

		this.props.onChange( date );
	},

	openCalendar: function() {
		if (!this.state.open) {
			this.setState({ open: true }, function() {
				this.props.onFocus();
			});
		}
	},

	closeCalendar: function() {
		this.setState({ open: false }, function () {
			this.props.onBlur( this.state.selectedDate || this.state.inputValue );
		});
	},

	handleClickOutside: function() {
		if ( this.props.input && this.state.open && !this.props.open ) {
			this.setState({ open: false }, function() {
				this.props.onBlur( this.state.selectedDate || this.state.inputValue );
			});
		}
	},

	localMoment: function( date, format, props ) {
		props = props || this.props;
		var momentFn = props.utc ? moment.utc : moment;
		var m = momentFn( date, format, props.strictParsing );
		if ( props.locale )
			m.locale( props.locale );
		return m;
	},

	componentProps: {
		fromProps: ['value', 'isValidDate', 'renderDay', 'renderMonth', 'renderYear', 'timeConstraints'],
		fromState: ['viewDate', 'selectedDate', 'updateOn'],
		fromThis: ['setDate', 'setTime', 'showView', 'addTime', 'subtractTime', 'updateSelectedDate', 'localMoment', 'handleClickOutside']
	},

	getComponentProps: function() {
		var me = this,
			formats = this.getFormats( this.props ),
			props = {dateFormat: formats.date, timeFormat: formats.time}
		;

		this.componentProps.fromProps.forEach( function( name ) {
			props[ name ] = me.props[ name ];
		});
		this.componentProps.fromState.forEach( function( name ) {
			props[ name ] = me.state[ name ];
		});
		this.componentProps.fromThis.forEach( function( name ) {
			props[ name ] = me[ name ];
		});

		return props;
	},

	render: function() {
		var className = 'rdt' + (this.props.className ?
                  ( Array.isArray( this.props.className ) ?
                  ' ' + this.props.className.join( ' ' ) : ' ' + this.props.className) : ''),
			children = []
		;

		if ( this.props.input ) {
			children = [ React.createElement('input', assign({
				key: 'i',
				type: 'text',
				className: 'form-control',
				onFocus: this.openCalendar,
				onChange: this.onInputChange,
				onKeyDown: this.onInputKey,
				value: this.state.inputValue
			}, this.props.inputProps ))];
		} else {
			className += ' rdtStatic';
		}

		if ( this.state.open )
			className += ' rdtOpen';

		return React.createElement('div', {className: className}, children.concat(
			React.createElement('div',
				{ key: 'dt', className: 'rdtPicker' },
				React.createElement( CalendarContainer, {view: this.state.currentView, viewProps: this.getComponentProps(), onClickOutside: this.handleClickOutside })
			)
		));
	}
});

// Make moment accessible through the Datetime class
Datetime.moment = moment;

module.exports = Datetime;
<commit_msg>Add TODO regarding complex ternary expression<commit_after>'use strict';

var assign = require('object-assign'),
	PropTypes = require('prop-types'),
    createClass = require('create-react-class'),
	moment = require('moment'),
	React = require('react'),
	CalendarContainer = require('./src/CalendarContainer')
;

var TYPES = PropTypes;
var Datetime = createClass({
	propTypes: {
		// value: TYPES.object | TYPES.string,
		// defaultValue: TYPES.object | TYPES.string,
		onFocus: TYPES.func,
		onBlur: TYPES.func,
		onChange: TYPES.func,
		onViewModeChange: TYPES.func,
		locale: TYPES.string,
		utc: TYPES.bool,
		input: TYPES.bool,
		// dateFormat: TYPES.string | TYPES.bool,
		// timeFormat: TYPES.string | TYPES.bool,
		inputProps: TYPES.object,
		timeConstraints: TYPES.object,
		viewMode: TYPES.oneOf(['years', 'months', 'days', 'time']),
		isValidDate: TYPES.func,
		open: TYPES.bool,
		strictParsing: TYPES.bool,
		closeOnSelect: TYPES.bool,
		closeOnTab: TYPES.bool
	},

	getDefaultProps: function() {
		var nof = function() {};
		return {
			className: '',
			defaultValue: '',
			inputProps: {},
			input: true,
			onFocus: nof,
			onBlur: nof,
			onChange: nof,
			onViewModeChange: nof,
			timeFormat: true,
			timeConstraints: {},
			dateFormat: true,
			strictParsing: true,
			closeOnSelect: false,
			closeOnTab: true,
			utc: false
		};
	},

	getInitialState: function() {
		var state = this.getStateFromProps( this.props );

		if ( state.open === undefined )
			state.open = !this.props.input;

		state.currentView = this.props.dateFormat ? (this.props.viewMode || state.updateOn || 'days') : 'time';

		return state;
	},

	getStateFromProps: function( props ) {
		var formats = this.getFormats( props ),
			date = props.value || props.defaultValue,
			selectedDate, viewDate, updateOn, inputValue
		;

		if ( date && typeof date === 'string' )
			selectedDate = this.localMoment( date, formats.datetime );
		else if ( date )
			selectedDate = this.localMoment( date );

		if ( selectedDate && !selectedDate.isValid() )
			selectedDate = null;

		viewDate = selectedDate ?
			selectedDate.clone().startOf('month') :
			this.localMoment().startOf('month')
		;

		updateOn = this.getUpdateOn(formats);

		if ( selectedDate )
			inputValue = selectedDate.format(formats.datetime);
		else if ( date.isValid && !date.isValid() )
			inputValue = '';
		else
			inputValue = date || '';

		return {
			updateOn: updateOn,
			inputFormat: formats.datetime,
			viewDate: viewDate,
			selectedDate: selectedDate,
			inputValue: inputValue,
			open: props.open
		};
	},

	getUpdateOn: function( formats ) {
        if ( formats.date.match(/[lLD]/) ) {
			return 'days';
		} else if ( formats.date.indexOf('M') !== -1 ) {
			return 'months';
		} else if ( formats.date.indexOf('Y') !== -1 ) {
			return 'years';
		}

		return 'days';
	},

	getFormats: function( props ) {
		var formats = {
				date: props.dateFormat || '',
				time: props.timeFormat || ''
			},
			locale = this.localMoment( props.date, null, props ).localeData()
		;

		if ( formats.date === true ) {
			formats.date = locale.longDateFormat('L');
		}
		else if ( this.getUpdateOn(formats) !== 'days' ) {
			formats.time = '';
		}

		if ( formats.time === true ) {
			formats.time = locale.longDateFormat('LT');
		}

		formats.datetime = formats.date && formats.time ?
			formats.date + ' ' + formats.time :
			formats.date || formats.time
		;

		return formats;
	},

	componentWillReceiveProps: function( nextProps ) {
		var formats = this.getFormats( nextProps ),
			updatedState = {}
		;

		if ( nextProps.value !== this.props.value ||
			formats.datetime !== this.getFormats( this.props ).datetime ) {
			updatedState = this.getStateFromProps( nextProps );
		}

		if ( updatedState.open === undefined ) {
			if ( this.props.closeOnSelect && this.state.currentView !== 'time' ) {
				updatedState.open = false;
			} else {
				updatedState.open = this.state.open;
			}
		}

		if ( nextProps.viewMode !== this.props.viewMode ) {
			updatedState.currentView = nextProps.viewMode;
		}

		if ( nextProps.locale !== this.props.locale ) {
			if ( this.state.viewDate ) {
				var updatedViewDate = this.state.viewDate.clone().locale( nextProps.locale );
				updatedState.viewDate = updatedViewDate;
			}
			if ( this.state.selectedDate ) {
				var updatedSelectedDate = this.state.selectedDate.clone().locale( nextProps.locale );
				updatedState.selectedDate = updatedSelectedDate;
				updatedState.inputValue = updatedSelectedDate.format( formats.datetime );
			}
		}

		if ( nextProps.utc !== this.props.utc ) {
			if ( nextProps.utc ) {
				if ( this.state.viewDate )
					updatedState.viewDate = this.state.viewDate.clone().utc();
				if ( this.state.selectedDate ) {
					updatedState.selectedDate = this.state.selectedDate.clone().utc();
					updatedState.inputValue = updatedState.selectedDate.format( formats.datetime );
				}
			} else {
				if ( this.state.viewDate )
					updatedState.viewDate = this.state.viewDate.clone().local();
				if ( this.state.selectedDate ) {
					updatedState.selectedDate = this.state.selectedDate.clone().local();
					updatedState.inputValue = updatedState.selectedDate.format(formats.datetime);
				}
			}
		}

		this.setState( updatedState );
	},

	onInputChange: function( e ) {
		var value = e.target === null ? e : e.target.value,
			localMoment = this.localMoment( value, this.state.inputFormat ),
			update = { inputValue: value }
		;

		if ( localMoment.isValid() && !this.props.value ) {
			update.selectedDate = localMoment;
			update.viewDate = localMoment.clone().startOf('month');
		} else {
			update.selectedDate = null;
		}

		return this.setState( update, function() {
			return this.props.onChange( localMoment.isValid() ? localMoment : this.state.inputValue );
		});
	},

	onInputKey: function( e ) {
		if ( e.which === 9 && this.props.closeOnTab ) {
			this.closeCalendar();
		}
	},

	showView: function( view ) {
		var me = this;
		return function() {
			me.state.currentView !== view && me.props.onViewModeChange( view );
			me.setState({ currentView: view });
		};
	},

	setDate: function( type ) {
		var me = this,
			nextViews = {
				month: 'days',
				year: 'months'
			}
		;
		return function( e ) {
			me.setState({
				viewDate: me.state.viewDate.clone()[ type ]( parseInt(e.target.getAttribute('data-value'), 10) ).startOf( type ),
				currentView: nextViews[ type ]
			});
			me.props.onViewModeChange( nextViews[ type ] );
		};
	},

	addTime: function( amount, type, toSelected ) {
		return this.updateTime( 'add', amount, type, toSelected );
	},

	subtractTime: function( amount, type, toSelected ) {
		return this.updateTime( 'subtract', amount, type, toSelected );
	},

	updateTime: function( op, amount, type, toSelected ) {
		var me = this;

		return function() {
			var update = {},
				date = toSelected ? 'selectedDate' : 'viewDate'
			;

			update[ date ] = me.state[ date ].clone()[ op ]( amount, type );

			me.setState( update );
		};
	},

	allowedSetTime: ['hours', 'minutes', 'seconds', 'milliseconds'],
	setTime: function( type, value ) {
		var index = this.allowedSetTime.indexOf( type ) + 1,
			state = this.state,
			date = (state.selectedDate || state.viewDate).clone(),
			nextType
		;

		// It is needed to set all the time properties
		// to not to reset the time
		date[ type ]( value );
		for (; index < this.allowedSetTime.length; index++) {
			nextType = this.allowedSetTime[index];
			date[ nextType ]( date[nextType]() );
		}

		if ( !this.props.value ) {
			this.setState({
				selectedDate: date,
				inputValue: date.format( state.inputFormat )
			});
		}
		this.props.onChange( date );
	},

	updateSelectedDate: function( e, close ) {
		var target = e.target,
			modifier = 0,
			viewDate = this.state.viewDate,
			currentDate = this.state.selectedDate || viewDate,
			date
		;

		if (target.className.indexOf('rdtDay') !== -1) {
			if (target.className.indexOf('rdtNew') !== -1)
				modifier = 1;
			else if (target.className.indexOf('rdtOld') !== -1)
				modifier = -1;

			date = viewDate.clone()
				.month( viewDate.month() + modifier )
				.date( parseInt( target.getAttribute('data-value'), 10 ) );
		} else if (target.className.indexOf('rdtMonth') !== -1) {
			date = viewDate.clone()
				.month( parseInt( target.getAttribute('data-value'), 10 ) )
				.date( currentDate.date() );
		} else if (target.className.indexOf('rdtYear') !== -1) {
			date = viewDate.clone()
				.month( currentDate.month() )
				.date( currentDate.date() )
				.year( parseInt( target.getAttribute('data-value'), 10 ) );
		}

		date.hours( currentDate.hours() )
			.minutes( currentDate.minutes() )
			.seconds( currentDate.seconds() )
			.milliseconds( currentDate.milliseconds() );

		if ( !this.props.value ) {
			var open = !( this.props.closeOnSelect && close );
			if ( !open ) {
				this.props.onBlur( date );
			}

			this.setState({
				selectedDate: date,
				viewDate: date.clone().startOf('month'),
				inputValue: date.format( this.state.inputFormat ),
				open: open
			});
		} else {
			if ( this.props.closeOnSelect && close ) {
				this.closeCalendar();
			}
		}

		this.props.onChange( date );
	},

	openCalendar: function() {
		if (!this.state.open) {
			this.setState({ open: true }, function() {
				this.props.onFocus();
			});
		}
	},

	closeCalendar: function() {
		this.setState({ open: false }, function () {
			this.props.onBlur( this.state.selectedDate || this.state.inputValue );
		});
	},

	handleClickOutside: function() {
		if ( this.props.input && this.state.open && !this.props.open ) {
			this.setState({ open: false }, function() {
				this.props.onBlur( this.state.selectedDate || this.state.inputValue );
			});
		}
	},

	localMoment: function( date, format, props ) {
		props = props || this.props;
		var momentFn = props.utc ? moment.utc : moment;
		var m = momentFn( date, format, props.strictParsing );
		if ( props.locale )
			m.locale( props.locale );
		return m;
	},

	componentProps: {
		fromProps: ['value', 'isValidDate', 'renderDay', 'renderMonth', 'renderYear', 'timeConstraints'],
		fromState: ['viewDate', 'selectedDate', 'updateOn'],
		fromThis: ['setDate', 'setTime', 'showView', 'addTime', 'subtractTime', 'updateSelectedDate', 'localMoment', 'handleClickOutside']
	},

	getComponentProps: function() {
		var me = this,
			formats = this.getFormats( this.props ),
			props = {dateFormat: formats.date, timeFormat: formats.time}
		;

		this.componentProps.fromProps.forEach( function( name ) {
			props[ name ] = me.props[ name ];
		});
		this.componentProps.fromState.forEach( function( name ) {
			props[ name ] = me.state[ name ];
		});
		this.componentProps.fromThis.forEach( function( name ) {
			props[ name ] = me[ name ];
		});

		return props;
	},

	render: function() {
		// TODO: Make a function or clean up this code,
		// logic right now is really hard to follow
		var className = 'rdt' + (this.props.className ?
                  ( Array.isArray( this.props.className ) ?
                  ' ' + this.props.className.join( ' ' ) : ' ' + this.props.className) : ''),
			children = []
		;

		if ( this.props.input ) {
			children = [ React.createElement('input', assign({
				key: 'i',
				type: 'text',
				className: 'form-control',
				onFocus: this.openCalendar,
				onChange: this.onInputChange,
				onKeyDown: this.onInputKey,
				value: this.state.inputValue
			}, this.props.inputProps ))];
		} else {
			className += ' rdtStatic';
		}

		if ( this.state.open )
			className += ' rdtOpen';

		return React.createElement('div', {className: className}, children.concat(
			React.createElement('div',
				{ key: 'dt', className: 'rdtPicker' },
				React.createElement( CalendarContainer, {view: this.state.currentView, viewProps: this.getComponentProps(), onClickOutside: this.handleClickOutside })
			)
		));
	}
});

// Make moment accessible through the Datetime class
Datetime.moment = moment;

module.exports = Datetime;
<|endoftext|>


<commit_before>(function(µ,SMOD,GMOD,HMOD,SC){

	var DBObj=GMOD("DBObj"),
		FIELD=GMOD("DBField");

	SC=SC({
		rel:"DBRel"
	});

	var DOWNLOAD=µ.Class(DBObj,{
		objectType:"Download",
		init:function(param)
		{
			param=param||{};

			this.mega(param);

			this.appOrigin=param.appOrigin||null;

			this.addField("name",		FIELD.TYPES.STRING	,param.name);
			this.addField("filename",	FIELD.TYPES.STRING	,param.filename);
			this.addField("filepath",	FIELD.TYPES.STRING	,param.filepath);
			this.addField("filesize",	FIELD.TYPES.INT		,DOWNLOAD.parseFilesize(param.filesize));
			this.addField("state",		FIELD.TYPES.STRING	,param.state||DOWNLOAD.states.PENDING);
			this.addField("messages",	FIELD.TYPES.JSON	,param.messages||[]); //{String[]}
			this.addField("dataSource",	FIELD.TYPES.JSON	,param.dataSource); // information to start download String = {url:"string"}
			this.addField("orderIndex",	FIELD.TYPES.INT		,param.orderIndex);

			// for the download progress
			this.startTime=param.startTime||null;
			this.startSize=param.startSize||0;
			this.time=param.time||null;
			this.size=param.size||null;

			this.addRelation("package",	DOWNLOAD.Package	,SC.rel.TYPES.PARENT,"children","packageID");

			Object.defineProperty(this,"formattedFilesize",{
				configurable:false,
				enumerable:true,
				get:()=>DOWNLOAD.formatFileSize(this.filesize),
			});

		},
		addMessage:function(text,time)
		{
			if(!time)time=Date.now();
			this.messages.push({text:text,time:time});
		},
		toUpdateJSON:function()
		{
			var jsonObject=this.toJSON();

			jsonObject.startTime=this.startTime;
			jsonObject.startSize=this.startSize;
			jsonObject.time=this.time;
			jsonObject.size=this.size;

			return jsonObject;
		},
		fromJSON:function(jsonObject)
		{
			DBObj.prototype.fromJSON.call(this,jsonObject);

			this.startTime=jsonObject.startTime||null;
			this.startSize=jsonObject.startSize||0;
			this.time=jsonObject.time||null;
			this.size=jsonObject.size||null;

			return this;
		},
		getSpeed:function()
		{
			return this.getCurrentSpeed(this.startSize,this.startTime);
		},
		getCurrentSpeed:function(lastSize,lastTime)
		{
			if(!this.size) return 0;
			else return (this.size - lastSize)/((this.time - lastTime)/1000);
		}
	});
	DOWNLOAD.states={
		DISABLED:"disabled",
		PENDING:"pending",
		RUNNING:"running",
		DONE:"done",
		FAILED:"failed"
	};
	DOWNLOAD.parseFilesize=function(filesize)
	{
		if(typeof filesize=="string")
		{
			var match=/(\d+(?:\.\d+)?)([kmgtp])b?/i.exec(filesize);
			if(match)
			{
				filesize=parseFloat(match[1]);
				switch (match[2])
				{
					case "P":
					case "p":
						filesize*=1E3;
					case "T":
					case "t":
						filesize*=1E3;
					case "G":
					case "g":
						filesize*=1E3;
					case "M":
					case "m":
					default:
						filesize*=1E3;
					case "K":
					case "k":
						filesize*=1E3;
				}
			}
			else filesize=0;
		}
		return filesize;
	};
	DOWNLOAD.formatFilesize=function(filesize)
	{
		if(filesize>1e15) return (filesize/1e12).toFixed(1)+"P";
		else if(filesize>1e12) return (filesize/1e12).toFixed(1)+"T";
		else if(filesize>1e9) return (filesize/1e9).toFixed(1)+"G";
		else if(filesize>1e6) return (filesize/1e6).toFixed(1)+"M";
		else if(filesize>1e3) return (filesize/1e3).toFixed(1)+"K";
		else if (!filesize) return "0";
		else return filesize+"B";
	};
	DOWNLOAD.sortByOrderIndex=function sortByOrderIndex(item1,item2) // ASC null last
	{
		var a=item1.orderIndex,b=item2.orderIndex;
		if(a==null)
		{
			if(b==null) return 0;
			else return 1;
		}
		else if (b==null) return -1;
		else return a-b;
	};

	DOWNLOAD.Package=µ.Class(DBObj,{
		objectType:"Package",
		init:function(param)
		{
			param=param||{};

			this.mega(param);

			this.addField("name",		FIELD.TYPES.STRING	,param.name);
			this.addField("orderIndex",	FIELD.TYPES.INT		,param.orderIndex);

			this.addRelation("children",	DOWNLOAD.Package.downloadClass,	SC.rel.TYPES.CHILD,	"package");

			this.addRelation("package",		DOWNLOAD.Package,				SC.rel.TYPES.PARENT,"subPackages","packageID");
			this.addRelation("subPackages",	DOWNLOAD.Package,				SC.rel.TYPES.CHILD,	"package");
		},
		getItems:function()
		{
			return this.getChildren("subPackages").concat(this.getChildren("children")).sort(DOWNLOAD.sortByOrderIndex);
		}
	});
	DOWNLOAD.Package.downloadClass=DOWNLOAD;

	SMOD("Download",DOWNLOAD);
	if(typeof module!=="undefined")module.exports=DOWNLOAD;

})(Morgas,Morgas.setModule,Morgas.getModule,Morgas.hasModule,Morgas.shortcut);
<commit_msg>app connection<commit_after>(function(µ,SMOD,GMOD,HMOD,SC){

	var DBObj=GMOD("DBObj"),
		FIELD=GMOD("DBField");

	SC=SC({
		rel:"DBRel"
	});

	var DOWNLOAD=µ.Class(DBObj,{
		objectType:"Download",
		init:function(param)
		{
			param=param||{};

			this.mega(param);

			this.appOrigin=param.appOrigin||null;

			this.addField("name",		FIELD.TYPES.STRING	,param.name);
			this.addField("filename",	FIELD.TYPES.STRING	,param.filename);
			this.addField("filepath",	FIELD.TYPES.STRING	,param.filepath);
			this.addField("filesize",	FIELD.TYPES.INT		,DOWNLOAD.parseFilesize(param.filesize));
			this.addField("state",		FIELD.TYPES.STRING	,param.state||DOWNLOAD.states.PENDING);
			this.addField("messages",	FIELD.TYPES.JSON	,param.messages||[]); //{String[]}
			this.addField("dataSource",	FIELD.TYPES.JSON	,param.dataSource); // information to start download String = {url:"string"}
			this.addField("orderIndex",	FIELD.TYPES.INT		,param.orderIndex);

			// for the download progress
			this.startTime=param.startTime||null;
			this.startSize=param.startSize||0;
			this.time=param.time||null;
			this.size=param.size||null;

			// for download from other apps
			this.addField("appName",	FIELD.TYPES.STRING	,param.appName);
			this.addField("remoteID",	FIELD.TYPES.INT		,param.remoteID);

			this.addRelation("package",	DOWNLOAD.Package	,SC.rel.TYPES.PARENT,"children","packageID");

			Object.defineProperty(this,"formattedFilesize",{
				configurable:false,
				enumerable:true,
				get:()=>DOWNLOAD.formatFileSize(this.filesize),
			});

		},
		addMessage:function(text,time)
		{
			if(!time)time=Date.now();
			this.messages.push({text:text,time:time});
		},
		toUpdateJSON:function()
		{
			var jsonObject=this.toJSON();

			jsonObject.startTime=this.startTime;
			jsonObject.startSize=this.startSize;
			jsonObject.time=this.time;
			jsonObject.size=this.size;

			return jsonObject;
		},
		fromJSON:function(jsonObject)
		{
			DBObj.prototype.fromJSON.call(this,jsonObject);

			this.startTime=jsonObject.startTime||null;
			this.startSize=jsonObject.startSize||0;
			this.time=jsonObject.time||null;
			this.size=jsonObject.size||null;

			return this;
		},
		getSpeed:function()
		{
			return this.getCurrentSpeed(this.startSize,this.startTime);
		},
		getCurrentSpeed:function(lastSize,lastTime)
		{
			if(!this.size) return 0;
			else return (this.size - lastSize)/((this.time - lastTime)/1000);
		}
	});
	DOWNLOAD.states={
		DISABLED:"disabled",
		PENDING:"pending",
		RUNNING:"running",
		DONE:"done",
		FAILED:"failed"
	};
	DOWNLOAD.parseFilesize=function(filesize)
	{
		if(typeof filesize=="string")
		{
			var match=/(\d+(?:\.\d+)?)([kmgtp])b?/i.exec(filesize);
			if(match)
			{
				filesize=parseFloat(match[1]);
				switch (match[2])
				{
					case "P":
					case "p":
						filesize*=1E3;
					case "T":
					case "t":
						filesize*=1E3;
					case "G":
					case "g":
						filesize*=1E3;
					case "M":
					case "m":
					default:
						filesize*=1E3;
					case "K":
					case "k":
						filesize*=1E3;
				}
			}
			else filesize=0;
		}
		return filesize;
	};
	DOWNLOAD.formatFilesize=function(filesize)
	{
		if(filesize>1e15) return (filesize/1e12).toFixed(1)+"P";
		else if(filesize>1e12) return (filesize/1e12).toFixed(1)+"T";
		else if(filesize>1e9) return (filesize/1e9).toFixed(1)+"G";
		else if(filesize>1e6) return (filesize/1e6).toFixed(1)+"M";
		else if(filesize>1e3) return (filesize/1e3).toFixed(1)+"K";
		else if (!filesize) return "0";
		else return filesize+"B";
	};
	DOWNLOAD.sortByOrderIndex=function sortByOrderIndex(item1,item2) // ASC null last
	{
		var a=item1.orderIndex,b=item2.orderIndex;
		if(a==null)
		{
			if(b==null) return 0;
			else return 1;
		}
		else if (b==null) return -1;
		else return a-b;
	};

	DOWNLOAD.Package=µ.Class(DBObj,{
		objectType:"Package",
		init:function(param)
		{
			param=param||{};

			this.mega(param);

			this.addField("name",		FIELD.TYPES.STRING	,param.name);
			this.addField("orderIndex",	FIELD.TYPES.INT		,param.orderIndex);

			this.addRelation("children",	DOWNLOAD.Package.downloadClass,	SC.rel.TYPES.CHILD,	"package");

			this.addRelation("package",		DOWNLOAD.Package,				SC.rel.TYPES.PARENT,"subPackages","packageID");
			this.addRelation("subPackages",	DOWNLOAD.Package,				SC.rel.TYPES.CHILD,	"package");
		},
		getItems:function()
		{
			return this.getChildren("subPackages").concat(this.getChildren("children")).sort(DOWNLOAD.sortByOrderIndex);
		}
	});
	DOWNLOAD.Package.downloadClass=DOWNLOAD;

	SMOD("Download",DOWNLOAD);
	if(typeof module!=="undefined")module.exports=DOWNLOAD;

})(Morgas,Morgas.setModule,Morgas.getModule,Morgas.hasModule,Morgas.shortcut);
<|endoftext|>


<commit_before>define(function (require, exports, module) {
    //TODO: 改名: commonUtil
    "use strict";

    var derive = require("base/derive");
    var assert = require("base/assert");
    var NativeObject = require("boost/nativeObject/NativeObject");

    var LIGHT_API_TYPE_ID = -7;
    /**
     * 供背景页用作与前景页通信的中介（向前景页发送消息，监听前景页发来的消息）
     */
    var LightApiNativeObject = derive(NativeObject, function () {
        NativeObject.call(this, null, LIGHT_API_TYPE_ID);
    }, {
        __onEvent: function (type, event) {
            switch (type) {
                case "subscribe":
                case "unsubscribe":
                case "subscribestate":
                    this.dispatchEvent({
                        type: type,
                        data: event.data
                    });
                    break;
            }
            return event && event.propagationStoped;
        },
        follow: function (conf) {
            this.__callNative("subscribe", [conf.appid, false]); //TODO: conf.is_silence参数的支持？
        },
        unfollow: function (conf) {
            this.__callNative("unsubscribe", [conf.appid]);
        },
        checkFollow: function (conf) {
            this.__callNative("querySubscribe", [conf.appid]);
        },
        share: function (conf) {
            this.__callNative("shareApp", [{
                title: conf.title,
                url: conf.linkUrl,
                content: conf.content,
                weixin_send_url: true,
                share_type: 0, //mediaType不生效，全为0
                weibo_title: conf.title,
                weixin_title: conf.title,
                weixin_timeline_title: conf.title,
                weixin_description: conf.content,
                thumb_img_url: conf.imageUrl,
                img_url: conf.imageUrl
            }]);
        },

        showInputMethod: function () {
            this.__callNative("showInputMethod", []);
        },
        hideInputMethod: function () {
            this.__callNative("hideInputMethod", []);
        },
        getO2OWindowSize: function (callback) {
            this.__callNative("getNativeLayerSize", [], callback);
        },
        getLocatedCity: function (callback) {
            this.__callNative("getLocationCityName", [], callback);
        }
    });

    module.exports = new LightApiNativeObject();
});
<commit_msg>改关注接口：事件->回调、增加isSilence参数、业务上改为右上角菜单在新版时使用自己的提示、旧版时使用native默认的提示<commit_after>define(function (require, exports, module) {
    //TODO: 改名: commonUtil
    "use strict";

    var derive = require("base/derive");
    var assert = require("base/assert");
    var NativeObject = require("boost/nativeObject/NativeObject");

    var LIGHT_API_TYPE_ID = -7;
    /**
     * 供背景页用作与前景页通信的中介（向前景页发送消息，监听前景页发来的消息）
     */
    var LightApiNativeObject = derive(NativeObject, function () {
        NativeObject.call(this, null, LIGHT_API_TYPE_ID);
    }, {
        __onEvent: function (type, event) {
            switch (type) {
                case "subscribe":
                case "unsubscribe":
                case "subscribestate":
                    this.dispatchEvent({
                        type: type,
                        data: event.data
                    });
                    break;
            }
            return event && event.propagationStoped;
        },
        follow: function (conf, callback) {
            this.__callNative("subscribe", [conf], callback);
        },
        unfollow: function (conf, callback) {
            this.__callNative("unsubscribe", [conf], callback);
        },
        checkFollow: function (conf, callback) {
            this.__callNative("querySubscribe", [conf], callback);
        },
        share: function (conf) {
            this.__callNative("shareApp", [{
                title: conf.title,
                url: conf.linkUrl,
                content: conf.content,
                weixin_send_url: true,
                share_type: 0, //mediaType不生效，全为0
                weibo_title: conf.title,
                weixin_title: conf.title,
                weixin_timeline_title: conf.title,
                weixin_description: conf.content,
                thumb_img_url: conf.imageUrl,
                img_url: conf.imageUrl
            }]);
        },

        showInputMethod: function () {
            this.__callNative("showInputMethod", []);
        },
        hideInputMethod: function () {
            this.__callNative("hideInputMethod", []);
        },
        getO2OWindowSize: function (callback) {
            this.__callNative("getNativeLayerSize", [], callback);
        },
        getLocatedCity: function (callback) {
            this.__callNative("getLocationCityName", [], callback);
        }
    });

    module.exports = new LightApiNativeObject();
});
<|endoftext|>


<commit_before>/* Project specific Javascript goes here. */

/*
Formatting hack to get around crispy-forms unfortunate hardcoding
in helpers.FormHelper:

    if template_pack == 'bootstrap4':
        grid_colum_matcher = re.compile('\w*col-(xs|sm|md|lg|xl)-\d+\w*')
        using_grid_layout = (grid_colum_matcher.match(self.label_class) or
                             grid_colum_matcher.match(self.field_class))
        if using_grid_layout:
            items['using_grid_layout'] = True

Issues with the above approach:

1. Fragile: Assumes Bootstrap 4's API doesn't change (it does)
2. Unforgiving: Doesn't allow for any variation in template design
3. Really Unforgiving: No way to override this behavior
4. Undocumented: No mention in the documentation, or it's too hard for me to find
*/
$('.form-group').removeClass('row');

/* Notifications JS basic client */
$(function () {
    $('#notifications').popover({
        html: true,
        trigger: 'manual',
        container: "body" ,
        placement: "bottom",
    });

    $("#notifications").click(function () {
        if ($(".popover").is(":visible")) {
            $("#notifications").popover('hide');
        }
        else {
            $("#notifications").popover('dispose');
            $.ajax({
                url: '/notifications/latest-notifications/',
                cache: false,
                success: function (data) {
                    $("#notifications").popover({
                        html: true,
                        trigger: 'focus',
                        container: "body" ,
                        placement: "bottom",
                        content: data,
                    });
                    $("#notifications").popover('show');
                    $("#notifications").removeClass("btn-danger")
                },
            });
        }
        return false;
    });

    // Code block to manage WebSocket connections
    // Try to correctly decide between ws:// and wss://
    var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws";
    var ws_path = ws_scheme + '://' + window.location.host + "/notifications/";
    var webSocket = new channels.WebSocketBridge();
    webSocket.connect(ws_path);

    // Helpful debugging
    webSocket.socket.onopen = function () {
        console.log("Connected to " + ws_path);
    };

    webSocket.socket.onclose = function () {
        console.error("Disconnected from " + ws_path);
    };

    // Listen the WebSocket bridge created throug django-channels library.
    webSocket.listen(function(event) {
        switch (event.key) {
            case "notification":
                if (event.username != currentUser) {
                    $("#notifications").addClass("btn-danger");
                }
                break;

            case "new_feed":
                if (event.username != currentUser) {
                    check_new_feeds();
                }
                break;
            case "liked":
                update_feeds();
                break;

            case "commented":
                track_comments();
                update_feeds();
                break;

            default:
                console.log('error: ', event)
                break;
        }
    });
});
<commit_msg>Helping to keep the notifications button on when is appropiate.<commit_after>/* Project specific Javascript goes here. */

/*
Formatting hack to get around crispy-forms unfortunate hardcoding
in helpers.FormHelper:

    if template_pack == 'bootstrap4':
        grid_colum_matcher = re.compile('\w*col-(xs|sm|md|lg|xl)-\d+\w*')
        using_grid_layout = (grid_colum_matcher.match(self.label_class) or
                             grid_colum_matcher.match(self.field_class))
        if using_grid_layout:
            items['using_grid_layout'] = True

Issues with the above approach:

1. Fragile: Assumes Bootstrap 4's API doesn't change (it does)
2. Unforgiving: Doesn't allow for any variation in template design
3. Really Unforgiving: No way to override this behavior
4. Undocumented: No mention in the documentation, or it's too hard for me to find
*/
$('.form-group').removeClass('row');

/* Notifications JS basic client */
$(function () {
    var emptyMessage = 'You have no unread notification';

    function checkNotifications() {
        $.ajax({
            url: '/notifications/latest-notifications/',
            cache: false,
            success: function (data) {
                if (!data.includes(emptyMessage)) {
                    $("#notifications").addClass("btn-danger")
                }
            },
        });
    };

    checkNotifications();

    $('#notifications').popover({
        html: true,
        trigger: 'manual',
        container: "body" ,
        placement: "bottom",
    });

    $("#notifications").click(function () {
        if ($(".popover").is(":visible")) {
            $("#notifications").popover('hide');
            checkNotifications();
        }
        else {
            $("#notifications").popover('dispose');
            $.ajax({
                url: '/notifications/latest-notifications/',
                cache: false,
                success: function (data) {
                    $("#notifications").popover({
                        html: true,
                        trigger: 'focus',
                        container: "body" ,
                        placement: "bottom",
                        content: data,
                    });
                    $("#notifications").popover('show');
                    $("#notifications").removeClass("btn-danger")
                },
            });
        }
        return false;
    });

    // Code block to manage WebSocket connections
    // Try to correctly decide between ws:// and wss://
    var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws";
    var ws_path = ws_scheme + '://' + window.location.host + "/notifications/";
    var webSocket = new channels.WebSocketBridge();
    webSocket.connect(ws_path);

    // Helpful debugging
    webSocket.socket.onopen = function () {
        console.log("Connected to " + ws_path);
    };

    webSocket.socket.onclose = function () {
        console.error("Disconnected from " + ws_path);
    };

    // Listen the WebSocket bridge created throug django-channels library.
    webSocket.listen(function(event) {
        switch (event.key) {
            case "notification":
                /* if (event.username != currentUser) {
                    $("#notifications").addClass("btn-danger");
                }
                break; */
                $("#notifications").addClass("btn-danger");

            case "new_feed":
                if (event.username != currentUser) {
                    check_new_feeds();
                }
                break;
            case "liked":
                update_feeds();
                break;

            case "commented":
                track_comments();
                update_feeds();
                break;

            default:
                console.log('error: ', event)
                break;
        }
    });
});
<|endoftext|>


<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

/*global $, Config */

var sakai = sakai || {};

/**
 * @name sakai.createpage
 *
 * @class createpage
 *
 * @description
 * createpage widget
 *
 * @version 0.0.1
 * @param {String} tuid Unique id of the widget
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.createpage = function(tuid, showSettings){

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    // jQuery DOM objects
    var $rootel = $("#" + tuid);
    var $createpageContainer = $("#createpage_container", $rootel);
    var $selectPageType = $("#createpage_page_type", $rootel);
    var $inputPageTitleContainer = $("#createpage_title_container", $rootel);
    var $inputPageTitle = $("#createpage_title", $rootel);
    var $createpageForm = $("#createpage_form", $rootel);
    var $createpageSubmit = $("#createpage_save", $rootel);
    var $createpageCancel = $("#createpage_cancel", $rootel);
    var $createpageProcessing = $("#createpage_processing", $rootel);
    var $createpageTemplates = $("#createpage_templates", $rootel);
    var $createpageTemplatesTemplate = $("#createpage_templates_template", $rootel);

    // CSS Selectors
    var errorMsgClass = "createpage_error_msg";
    var invalidFieldClass = "invalid";
    var buttonClass = "s3d-button";
    var radioButtons = "createpagetemplate_radio";

    ///////////////////////
    // Utility functions //
    ///////////////////////

    /**
     * This function is called before the jqModal hides. It removes any error
     * notices, resets values for select and text inputs and hides templates
     */
    var resetModalDialog = function (hash) {
        // hide the overlay
        hash.w.hide();
        hash.o.remove();

        // remove any errors
        $("." + errorMsgClass).hide();
        $inputPageTitle.removeClass(invalidFieldClass);

        // clear input
        $inputPageTitle.val("");

        // reset page type select element
        $selectPageType.val("blank");

        // hide templates
        $createpageTemplates.hide();
    };

    /**
     * Mark the page title field as erroneous and show an error message
     */
    var showPageTitleError = function () {
        $("." + errorMsgClass).show();
        $inputPageTitle.addClass(invalidFieldClass);
        $inputPageTitle.focus();
    };

    /**
     * Show page templates available to the user
     */
    var showTemplates = function () {
        var json = {
            templates: []
        };
        if(sakai.sitespages && sakai.sitespages.mytemplates) {
            // create a custom list of templates with just the info we care about
            for(t in sakai.sitespages.mytemplates) {
                if(sakai.sitespages.mytemplates.hasOwnProperty(t)) {
                    var template = {
                        "id": t,
                        "name": sakai.sitespages.mytemplates[t].name,
                        "desc": sakai.sitespages.mytemplates[t].description
                    };
                    json.templates.push(template);
                }
            }
        }
        // pass the array to HTML view
        $createpageTemplates.html($.TemplateRenderer($createpageTemplatesTemplate,
            json));
        $createpageTemplates.show();

        // select the first item
        $("input:radio:first", $createpageContainer).attr("checked","checked");

        // if no templates, hide the create button
        if(json.templates.length === 0) {
            $createpageSubmit.hide();
        }
    };

    /**
     * Toggle between showing or hiding the "Processing..." message to the user
     */
    var toggleProcessing = function(){
        if($createpageProcessing.is(":visible")) {
            $createpageProcessing.hide();
            $("#createpage_container " + "." + buttonClass).show();
        } else {
            $("#createpage_container " + "." + buttonClass).hide();
            $createpageProcessing.show();
        }
    };

    ////////////////////
    // Event Handlers //
    ////////////////////

    /**
     * Validate input and initiate creating pages when the user submits the form
     */
    $createpageForm.submit(function () {
        // check if we have valid input
        var pageTitle = $inputPageTitle.val();
        if(!pageTitle) {
            showPageTitleError();
        } else {
            // we've got a valid title
            toggleProcessing();  // display "Processing..."
            var pageType = $selectPageType.val();
            if(pageType === "blank") {
                sakai.sitespages.createNewPage("", handleNewPageCreation, pageTitle);
            } else if(pageType === "template") {
                // fetch the selected template and its content to create a new page
                var selectedTemplate = $("input:radio:checked", $createpageContainer).val();
                sakai.sitespages.createNewPage(
                    sakai.sitespages.mytemplates[selectedTemplate]["pageContent"]["sakai:pagecontent"],
                    handleNewPageCreation, pageTitle);
            } else {
                sakai.sitespages.addDashboardPage(pageTitle, handleNewPageCreation);
            }
        }
    });

    /**
     * Callback function called upon completion of sakai.sitespages.createNewPage
     * called on submit event of the create page form.
     * @param {Boolean} success true if the call succeeded, false otherwise
     */
    var handleNewPageCreation = function (success) {
        toggleProcessing();  // hide "Processing..."
        if(success) {
            // hide the modal
            $createpageContainer.jqmHide();
        } else {
            fluid.log("createpage.js - handleNewPageCreation: creating page failed.");
            alert("Sorry, your page could not be created at this time. Please try again later or contact your administrator for assistance.");
        }
    };

    /**
     * Show available templates when the user selects Create page from template
     * in the page type dropdown. If the other types are selected, focus the
     * input are immediately.
     */
    $selectPageType.change(function () {
        var pageType = $selectPageType.val();
        if(pageType === "template") {
            // display templates
            showTemplates();
        } else {
            // hide templates, ensure submit button is visible
            $createpageTemplates.hide();
            $createpageSubmit.show();
        }
        $inputPageTitle.focus();
    });

    /**
     * Delete the template when the user clicks the delete link
     */
    $createpageContainer.delegate("a.createpage_delete_template_link", "click",
    function () {
        var templateDeleted = this.id.split("_")[1];
        delete sakai.sitespages.mytemplates[templateDeleted];
        // Save updated template preferences
        // -- this has a bug right now. The save is not overwriting the templates
        // -- at /~userid/private/templates as expected - maybe a special jcr flag (i.e. :replace)?
        sakai.api.Server.saveJSON("/~" + sakai.data.me.user.userid + "/private/templates",
            sakai.sitespages.mytemplates, function(success, response) {
            if (success) {
                showTemplates();
            } else {
                fluid.log("createpage.js: Failed to delete template with id="+templateDeleted);
            }
        });
    });

    //////////////////////////////
    // Initialization function  //
    //////////////////////////////

    /**
     * Public init function that shows the createpage modal dialog
     */
    sakai.createpage.initialise = function(){
        // add jqModal functionality to the container
        $createpageContainer.jqm({
            modal: true,
            overlay: 20,
            toTop: true,
            onHide: resetModalDialog
        })
        .jqmAddClose($createpageCancel);

        // show container
        $createpageContainer.jqmShow();
        $inputPageTitle.focus();
    };

};

sakai.api.Widgets.widgetLoader.informOnLoad("createpage");<commit_msg>SAKIII-801 Disabling the submit button instead of hiding if a user selects to create a page from template, but no templates exist.<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

/*global $, Config */

var sakai = sakai || {};

/**
 * @name sakai.createpage
 *
 * @class createpage
 *
 * @description
 * createpage widget
 *
 * @version 0.0.1
 * @param {String} tuid Unique id of the widget
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.createpage = function(tuid, showSettings){

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    // jQuery DOM objects
    var $rootel = $("#" + tuid);
    var $createpageContainer = $("#createpage_container", $rootel);
    var $selectPageType = $("#createpage_page_type", $rootel);
    var $inputPageTitleContainer = $("#createpage_title_container", $rootel);
    var $inputPageTitle = $("#createpage_title", $rootel);
    var $createpageForm = $("#createpage_form", $rootel);
    var $createpageSubmit = $("#createpage_save", $rootel);
    var $createpageCancel = $("#createpage_cancel", $rootel);
    var $createpageProcessing = $("#createpage_processing", $rootel);
    var $createpageTemplates = $("#createpage_templates", $rootel);
    var $createpageTemplatesTemplate = $("#createpage_templates_template", $rootel);

    // CSS Selectors
    var errorMsgClass = "createpage_error_msg";
    var invalidFieldClass = "invalid";
    var buttonClass = "s3d-button";
    var radioButtons = "createpagetemplate_radio";

    ///////////////////////
    // Utility functions //
    ///////////////////////

    /**
     * This function is called before the jqModal hides. It removes any error
     * notices, resets values for select and text inputs and hides templates
     */
    var resetModalDialog = function (hash) {
        // hide the overlay
        hash.w.hide();
        hash.o.remove();

        // remove any errors
        $("." + errorMsgClass).hide();
        $inputPageTitle.removeClass(invalidFieldClass);

        // clear input
        $inputPageTitle.val("");

        // reset page type select element
        $selectPageType.val("blank");

        // hide templates
        $createpageTemplates.hide();

        // hide processing message
        hideProcessing();
    };

    /**
     * Mark the page title field as erroneous and show an error message
     */
    var showPageTitleError = function () {
        $("." + errorMsgClass).show();
        $inputPageTitle.addClass(invalidFieldClass);
        $inputPageTitle.focus();
    };

    /**
     * Show page templates available to the user
     */
    var showTemplates = function () {
        var json = {
            templates: []
        };
        if(sakai.sitespages && sakai.sitespages.mytemplates) {
            // create a custom list of templates with just the info we care about
            for(t in sakai.sitespages.mytemplates) {
                if(sakai.sitespages.mytemplates.hasOwnProperty(t)) {
                    var template = {
                        "id": t,
                        "name": sakai.sitespages.mytemplates[t].name,
                        "desc": sakai.sitespages.mytemplates[t].description
                    };
                    json.templates.push(template);
                }
            }
        }
        // pass the array to HTML view
        $createpageTemplates.html($.TemplateRenderer($createpageTemplatesTemplate,
            json));
        $createpageTemplates.show();

        // select the first item
        $("input:radio:first", $createpageContainer).attr("checked","checked");

        // if no templates, disable the create button
        if(json.templates.length === 0) {
            $createpageSubmit.attr("disabled","disabled");
        }
    };

    /**
     * Show the "Processing..." message to the user
     */
    var showProcessing = function () {
        $("#createpage_container " + "." + buttonClass).hide();
        $createpageProcessing.show();
    };

    /**
     * Hide the "Processing..." message to the user
     */
    var hideProcessing = function () {
        $createpageProcessing.hide();
        $("#createpage_container " + "." + buttonClass).show();
    };

    ////////////////////
    // Event Handlers //
    ////////////////////

    /**
     * Validate input and initiate creating pages when the user submits the form
     */
    $createpageForm.submit(function () {
        // check if we have valid input
        var pageTitle = $inputPageTitle.val();
        if(!pageTitle) {
            showPageTitleError();
        } else {
            // we've got a valid title
            showProcessing();  // display "Processing..."
            var pageType = $selectPageType.val();
            if(pageType === "blank") {
                sakai.sitespages.createNewPage("", handleNewPageCreation, pageTitle);
            } else if(pageType === "template") {
                // fetch the selected template and its content to create a new page
                var selectedTemplate = $("input:radio:checked", $createpageContainer).val();
                sakai.sitespages.createNewPage(
                    sakai.sitespages.mytemplates[selectedTemplate]["pageContent"]["sakai:pagecontent"],
                    handleNewPageCreation, pageTitle);
            } else {
                sakai.sitespages.addDashboardPage(pageTitle, handleNewPageCreation);
            }
        }
    });

    /**
     * Callback function called upon completion of sakai.sitespages.createNewPage
     * called on submit event of the create page form.
     * @param {Boolean} success true if the call succeeded, false otherwise
     */
    var handleNewPageCreation = function (success) {
        hideProcessing();
        if(success) {
            // hide the modal
            $createpageContainer.jqmHide();
        } else {
            fluid.log("createpage.js - handleNewPageCreation: creating page failed.");
            alert("Sorry, your page could not be created at this time. Please try again later or contact your administrator for assistance.");
        }
    };

    /**
     * Show available templates when the user selects Create page from template
     * in the page type dropdown. If the other types are selected, focus the
     * input are immediately.
     */
    $selectPageType.change(function () {
        var pageType = $selectPageType.val();
        if(pageType === "template") {
            // display templates
            showTemplates();
        } else {
            // hide templates, ensure submit button is visible
            $createpageTemplates.hide();
            $createpageSubmit.removeAttr("disabled");
        }
        $inputPageTitle.focus();
    });

    /**
     * Delete the template when the user clicks the delete link
     */
    $createpageContainer.delegate("a.createpage_delete_template_link", "click",
    function () {
        var templateDeleted = this.id.split("_")[1];
        delete sakai.sitespages.mytemplates[templateDeleted];
        // Save updated template preferences
        // -- this has a bug right now. The save is not overwriting the templates
        // -- at /~userid/private/templates as expected - maybe a special jcr flag (i.e. :replace)?
        sakai.api.Server.saveJSON("/~" + sakai.data.me.user.userid + "/private/templates",
            sakai.sitespages.mytemplates, function(success, response) {
            if (success) {
                showTemplates();
            } else {
                fluid.log("createpage.js: Failed to delete template with id="+templateDeleted);
            }
        });
    });

    //////////////////////////////
    // Initialization function  //
    //////////////////////////////

    /**
     * Public init function that shows the createpage modal dialog
     */
    sakai.createpage.initialise = function(){
        // add jqModal functionality to the container
        $createpageContainer.jqm({
            modal: true,
            overlay: 20,
            toTop: true,
            onHide: resetModalDialog
        })
        .jqmAddClose($createpageCancel);

        // show container
        $createpageContainer.jqmShow();
        $inputPageTitle.focus();
    };

};

sakai.api.Widgets.widgetLoader.informOnLoad("createpage");<|endoftext|>


<commit_before>angular.module('wedding.controllers', [])

.controller('AppCtrl', function($scope, $state, AuthService, AUTH_EVENTS, $rootScope, $firebaseObject, $ionicHistory, $ionicPopup, $timeout, $ionicSideMenuDelegate) {

  // listen for the $ionicView.enter event:
  //$scope.$on('$ionicView.enter', function(e) {
  //});
	$scope.isAuthenticated = AuthService.isAuthenticated();
	$scope.currentUser = AuthService.getUser();
    if($scope.currentUser) {
		$scope.user = $firebaseObject(fbase.child('users').child($scope.currentUser.uid));
	}

	$scope.logout = function() {
		AuthService.logout();
		//$ionicHistory.clearCache();
        //$ionicHistory.clearHistory();
        $ionicHistory.nextViewOptions({ disableBack: true, historyRoot: true });
		$state.go('app.dashboard');
		$ionicSideMenuDelegate.toggleLeft(false);
	}

	$rootScope.$on(AUTH_EVENTS.notAuthorized, function(event) {
	    var alertPopup = $ionicPopup.alert({
	      title: 'Unauthorized!',
	      template: 'Please login to access this resource.'
	    });
	});

	$rootScope.$on(AUTH_EVENTS.updateUser, function(event) {
	    $scope.isAuthenticated = AuthService.isAuthenticated();
	    $scope.currentUser = AuthService.getUser();
	    if($scope.currentUser) {
			$scope.user = $firebaseObject(fbase.child('users').child($scope.currentUser.uid));
		}
	});

	$rootScope.$on("ERROR_HANDLER", function(event, err) {
	    var alertPopup = $ionicPopup.alert({
	      title: 'Error Message',
	      template: err
	    });
	});
})

.controller('LoginCtrl', function($scope, AuthService, $state, $rootScope, AUTH_EVENTS) {

	$scope.loginData = {};

	$scope.loginEmail = function(){
		AuthService.login($scope.loginData).then(function (user) {
	      	$rootScope.$broadcast(AUTH_EVENTS.updateUser);
			$state.go('app.profile');
	    }, function (err) {
	      	$rootScope.$broadcast("ERROR_HANDLER", err);
	    });
	};

})

.controller('SignupCtrl', function($scope, AuthService, $state, $ionicPopup) {

	$scope.signupData = {};

	$scope.signupEmail = function(){
		AuthService.signup($scope.signupData).then(function (user) {
			$state.go('login');
			$ionicAnalytics.track('Signup', {
			    name: $scope.signupData.name
			});
	    }, function (err) {
	    	$rootScope.$broadcast("ERROR_HANDLER", err);
	    });
	};

})

.controller('GuestListCtrl', function($scope, AuthService, $state, $FirebaseArray, $ionicPopup, $firebaseArray, $ionicModal, $stateParams) {

	/*var ref = new Firebase(base + '/guests/' + AuthService.getUser().uid);
	var posts = $firebaseArray(ref);*/

	$scope.guests = $firebaseArray(fbase.child("guests/" + AuthService.getUser().uid));
	console.log($scope.guests);
	$ionicModal.fromTemplateUrl('addGuest.html', {
	    scope: $scope,
	    animation: 'slide-in-up'
	}).then(function(modal) {
	    $scope.modal = modal;
	});

	$scope.guest = {};

	// Add new Guest
	$scope.saveGuest = function() {
	    console.log($scope.guest)
		fbase.child("guests").child(AuthService.getUser().uid).push($scope.guest);
	    $scope.modal.hide();
	};

	  // Execute action on hide modal
	$scope.$on('modal.hidden', function() {
	    // Execute action
	});

})

.controller('budgetCalCtrl', function($scope, $firebaseArray, AuthService) {

	$scope.budgetCalculator = $firebaseArray(fbase.child("budgetCalculator/" + AuthService.getUser().uid));
	$scope.addAmont = function(amount){
		fbase.child("budgetCalculator").child(AuthService.getUser().uid).push(amount);
	};

	$scope.getTotal = function(budgetCalculator){
		var totalBudget = 0;
		for (i = 0; i < budgetCalculator.length; i++) {
			totalBudget = Number(totalBudget) + Number(budgetCalculator[i].$value);
		}
		$scope.totalBudget = totalBudget;
	};

})

.controller('GuestCtrl', function($scope, AuthService, $state, $firebaseArray, $stateParams,$firebaseObject) {
	$scope.guest = $firebaseObject(fbase.child("guests").child(AuthService.getUser().uid).child($stateParams.guestId));
	$scope.deleteGuest = function () {
		fbase.child("guests").child(AuthService.getUser().uid).child($stateParams.guestId).remove();
		$state.go(app.guestlist)
	}
})

.controller('VendorListCtrl', function($scope, AuthService, $state, $FirebaseArray, $ionicPopup, $firebaseArray, $ionicModal, $stateParams) {

	/*var ref = new Firebase(base + '/vendors/' + AuthService.getUser().uid);
	var posts = $firebaseArray(ref);*/

	$scope.vendors = $firebaseArray(fbase.child("vendors/" + AuthService.getUser().uid));
	console.log($scope.vendors);
	$ionicModal.fromTemplateUrl('addVendor.html', {
	    scope: $scope,
	    animation: 'slide-in-up'
	}).then(function(modal) {
	    $scope.modal = modal;
	});

	$scope.vendor = {};

	// Add new Vendor
	$scope.saveVendor = function() {
	    console.log($scope.vendor)
		fbase.child("vendors").child(AuthService.getUser().uid).push($scope.vendor);
	    $scope.modal.hide();
	};

	  // Execute action on hide modal
	$scope.$on('modal.hidden', function() {
	    // Execute action
	});

})

.controller('VendorCtrl', function($scope, AuthService, $state, $firebaseArray, $stateParams,$firebaseObject) {
	$scope.vendor = $firebaseObject(fbase.child("vendors").child(AuthService.getUser().uid).child($stateParams.vendorId));
	$scope.deleteVendor = function () {
		fbase.child("vendors").child(AuthService.getUser().uid).child($stateParams.vendorId).remove();
		$state.go(app.vendorlist)
	}
});

<commit_msg>added loader and changed login in Guest<commit_after>angular.module('wedding.controllers', [])

.controller('AppCtrl', function($scope, $state, AuthService, AUTH_EVENTS, $rootScope, $ionicLoading, $firebaseObject, $ionicHistory, $ionicPopup, $timeout, $ionicSideMenuDelegate) {

  // listen for the $ionicView.enter event:
  //$scope.$on('$ionicView.enter', function(e) {
  //});
	$scope.isAuthenticated = AuthService.isAuthenticated();
	$scope.currentUser = AuthService.getUser();
    if($scope.currentUser) {
		$scope.user = $firebaseObject(fbase.child('users').child($scope.currentUser.uid));
	}

	$scope.logout = function() {
		AuthService.logout();
		//$ionicHistory.clearCache();
        //$ionicHistory.clearHistory();
        $ionicHistory.nextViewOptions({ disableBack: true, historyRoot: true });
		$state.go('app.dashboard');
		$ionicSideMenuDelegate.toggleLeft(false);
	}

	$rootScope.$on(AUTH_EVENTS.notAuthorized, function(event) {
	    var alertPopup = $ionicPopup.alert({
	      title: 'Unauthorized!',
	      template: 'Please login to access this resource.'
	    });
	});

	$rootScope.$on(AUTH_EVENTS.updateUser, function(event) {
	    $scope.isAuthenticated = AuthService.isAuthenticated();
	    $scope.currentUser = AuthService.getUser();
	    if($scope.currentUser) {
			$scope.user = $firebaseObject(fbase.child('users').child($scope.currentUser.uid));
		}
	});

	$rootScope.$on("ERROR_HANDLER", function(event, err) {
	    var alertPopup = $ionicPopup.alert({
	      title: 'Error Message',
	      template: err
	    });
	});

	$rootScope.$on('loading:show', function(event, message) {
	    $ionicLoading.show({template: message})
	})

	$rootScope.$on('loading:hide', function() {
	    $ionicLoading.hide()
	})

})

.controller('LoginCtrl', function($scope, AuthService, $state, $rootScope, AUTH_EVENTS) {

	$scope.loginData = {};

	$scope.loginEmail = function(){
		AuthService.login($scope.loginData).then(function (user) {
	      	$rootScope.$broadcast(AUTH_EVENTS.updateUser);
			$state.go('app.profile');
	    }, function (err) {
	      	$rootScope.$broadcast("ERROR_HANDLER", err);
	    });
	};

})

.controller('SignupCtrl', function($scope, $rootScope, AuthService, $state, $ionicPopup, $ionicAnalytics) {

	$scope.signupData = {};

	$scope.signupEmail = function(){
		AuthService.signup($scope.signupData).then(function (user) {
			$state.go('login');
			$ionicAnalytics.track('Signup', {
			    name: $scope.signupData.name
			});
	    }, function (err) {
	    	$rootScope.$broadcast("ERROR_HANDLER", err);
	    });
	};

})

.controller('GuestListCtrl', function($scope, $rootScope, AuthService, $state, $firebaseArray, $ionicPopup, $firebaseArray, $ionicModal, $stateParams) {

	$scope.guests = $firebaseArray(fbase.child("guests").child(AuthService.getUser().uid));

	$rootScope.$broadcast('loading:show', 'Loading Guests...');

	$scope.guests.$loaded().then(function(notes) {
	   $rootScope.$broadcast('loading:hide');
	});

	$ionicModal.fromTemplateUrl('addGuest.html', {
	    scope: $scope,
	    animation: 'slide-in-up'
	}).then(function(modal) {
	    $scope.modal = modal;
	});

	$scope.guest = {};

	// Add new Guest
	$scope.saveGuest = function() {
		$firebaseArray(fbase.child("guests").child(AuthService.getUser().uid)).$add($scope.guest);
	    $scope.modal.hide();
	};

})

.controller('GuestCtrl', function($scope, AuthService, $state, $firebaseArray, $stateParams,$firebaseObject) {
	$scope.guest = $firebaseObject(fbase.child("guests").child(AuthService.getUser().uid).child($stateParams.guestId));

	$scope.deleteGuest = function () {
		$firebaseObject(fbase.child("guests").child(AuthService.getUser().uid).child($stateParams.guestId)).$remove();
		$state.go('app.guestList');
	}
})

.controller('budgetCalCtrl', function($scope, $firebaseArray, AuthService) {

	$scope.budgetCalculator = $firebaseArray(fbase.child("budgetCalculator/" + AuthService.getUser().uid));
	$scope.addAmont = function(amount){
		fbase.child("budgetCalculator").child(AuthService.getUser().uid).push(amount);
	};

	$scope.getTotal = function(budgetCalculator){
		var totalBudget = 0;
		for (i = 0; i < budgetCalculator.length; i++) {
			totalBudget = Number(totalBudget) + Number(budgetCalculator[i].$value);
		}
		$scope.totalBudget = totalBudget;
	};

})

.controller('VendorListCtrl', function($scope, AuthService, $state, $FirebaseArray, $ionicPopup, $firebaseArray, $ionicModal, $stateParams) {

	/*var ref = new Firebase(base + '/vendors/' + AuthService.getUser().uid);
	var posts = $firebaseArray(ref);*/

	$scope.vendors = $firebaseArray(fbase.child("vendors/" + AuthService.getUser().uid));
	console.log($scope.vendors);
	$ionicModal.fromTemplateUrl('addVendor.html', {
	    scope: $scope,
	    animation: 'slide-in-up'
	}).then(function(modal) {
	    $scope.modal = modal;
	});

	$scope.vendor = {};

	// Add new Vendor
	$scope.saveVendor = function() {
	    console.log($scope.vendor)
		fbase.child("vendors").child(AuthService.getUser().uid).push($scope.vendor);
	    $scope.modal.hide();
	};

	  // Execute action on hide modal
	$scope.$on('modal.hidden', function() {
	    // Execute action
	});

})

.controller('VendorCtrl', function($scope, AuthService, $state, $firebaseArray, $stateParams,$firebaseObject) {
	$scope.vendor = $firebaseObject(fbase.child("vendors").child(AuthService.getUser().uid).child($stateParams.vendorId));
	$scope.deleteVendor = function () {
		fbase.child("vendors").child(AuthService.getUser().uid).child($stateParams.vendorId).remove();
		$state.go(app.vendorlist)
	}
});

<|endoftext|>