can3p (can3p) wrote in changelog,
can3p
can3p
changelog

[livejournal] r17816: LJSUP-7240: ControlStrip

Committer: dpetrov
LJSUP-7240: ControlStrip
U   trunk/htdocs/js/controlstrip.js
U   trunk/htdocs/js/jquery_fn.js
Modified: trunk/htdocs/js/controlstrip.js
===================================================================
--- trunk/htdocs/js/controlstrip.js	2010-11-29 09:46:51 UTC (rev 17815)
+++ trunk/htdocs/js/controlstrip.js	2010-11-29 10:03:05 UTC (rev 17816)
@@ -1,10 +1,391 @@
-jQuery(function($){
-	!document.getElementById('lj_controlstrip') &&
-		$.get(LiveJournal.getAjaxUrl('controlstrip'),
-			{ user: Site.currentJournal },
-			function(data)
+(function( $, top ) {
+
+var ControlStrip = top.ControlStrip = {};
+
+var CONFIG = {
+	rootSelector: ".w-cs",
+	overlaysSelectors: [".w-cs-share", ".w-cs-filter", ".i-calendar"],
+	showOverlayClass: "w-cs-hover",
+	calendarSelector: ".i-calendar"
+}
+
+var options, elements;
+
+ControlStrip.init = function( o ) {
+	options = $.extend( {}, CONFIG, o );
+
+	elements = {
+		root: $( options.rootSelector ),
+		calendar: $( options.calendarSelector ),
+		overlays: $( options.overlaysSelectors.join(',') )
+	}
+
+	elements.root.find('input')
+		.filter('[type=\'text\']').labeledPlaceholder().end()
+		.filter('[type=\'password\']').labeledPlaceholder().end();
+
+	ControlStrip.initOverlays( elements.overlays );
+	if( elements.calendar.size() > 0 ) {
+		var d = new Date().setFullYear( 2010, 7, 15 );
+		ControlStrip.Calendar.call( elements.calendar );
+	}
+}
+
+ControlStrip.initOverlays = function( nodes ) {
+	nodes.removeClass( options.showOverlayClass );
+
+	nodes.each( function() {
+		var $this = $( this ),
+			outTimer = null;
+
+		$this.mouseover( function() {
+			nodes.removeClass( options.showOverlayClass );
+			$this.addClass( options.showOverlayClass );
+			clearTimeout( outTimer );
+		} )
+		.mouseout( function() {
+			outTimer = setTimeout( function() {
+				$this.removeClass( options.showOverlayClass );
+			}, 600 );
+		} );
+	} );
+};
+
+ControlStrip.Calendar = function( o ) {
+
+	var options = {
+		monthNames: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ],
+		dayRef: "/%Y/%M/%D",
+		currentDate: new Date(),
+		startAtSunday: true
+	},
+	$ = jQuery;
+
+	o = $.extend( {}, options, o);
+
+	if( ControlStrip.Calendar.MonthNames ) {
+		o.monthNames = ControlStrip.Calendar.MonthNames;
+	}
+
+	if( ControlStrip.Calendar.StartAtSunday ) {
+		o.startAtSunday = ControlStrip.Calendar.StartAtSunday;
+	}
+
+	function getDaysInMonth( date ) {
+		var monthArr = [ 31, (isLeapYear(date.getFullYear())?29:28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
+		return monthArr[ date.getMonth() ];
+	}
+
+	function isLeapYear(year){
+		return ( year % 4 === 0 && (year % 100 != 0 ) ) || ( year % 400 == 0);
+	}
+
+	var View = function (nodes, styles, controller)
+	{
+		this.activeLabelsEvents = {}
+
+		this.initialize = function (monthDate, events, switcherStates)
+		{
+			this.tbody = this.catchTableStructure();
+
+			//this.fillDates(monthDate, events);
+			//this.fillLabels(monthDate, switcherStates)
+			this.switcherStates = switcherStates;
+			this.bindEvents();
+		}
+
+		this.modelChanged = function (monthDate, events, switcherStates)
+		{
+			//we have a 30% speedup when we temporary remove tbody from dom
+			this.tbody.remove();
+			this.fillDates(monthDate, events)
+			this.fillLabels(monthDate, switcherStates)
+			this.tbody.appendTo( nodes.table );
+		}
+
+		this.catchTableStructure = function() {
+			var tbody = nodes.table[0].tBodies[0];
+			nodes.lastRow = $( tbody.rows[ tbody.rows.length - 1 ] );
+			nodes.daysCells = [];
+
+			for( row = 0, rowsCount = tbody.rows.length; row < rowsCount; ++row ) {
+				for( cell = 0, cellsCount = tbody.rows[ row ].cells.length; cell < cellsCount; ++cell ) {
+					nodes.daysCells.push( $( tbody.rows[ row ].cells[ cell ] ) );
+				}
+			}
+
+			return $( tbody );
+		}
+
+		this.fillDates = function (monthDate, events)
+		{
+			var firstDay = new Date(monthDate);
+			firstDay.setDate(1);
+
+			var offset;
+			if( o.startAtSunday ) {
+				offset = firstDay.getDay() - 1;
+			} else {
+				offset = (firstDay.getDay() == 0 ? 5: firstDay.getDay() - 2);
+			}
+
+			var length = getDaysInMonth( monthDate );
+
+			var prevMonth = new Date(monthDate);
+			prevMonth.setMonth(monthDate.getMonth() - 1);
+			var prLength = getDaysInMonth( prevMonth );
+
+			var nextMonth = new Date(monthDate)
+			nextMonth.setMonth(monthDate.getMonth() + 1)
+
+			for (var h = offset, l = prLength; h >= 0; h--)
+			{	// head
+				var iDate = new Date(prevMonth);
+				iDate.setDate(l--);
+
+				this.formDayString( iDate, nodes.daysCells[ h ]);
+			}
+
+			for (var i = 1; i <= length; i++)
+			{	// body
+				var iDate = new Date(monthDate);
+				iDate.setDate(i);
+
+				this.formDayString( iDate, nodes.daysCells[ i + offset ], true, events );
+			}
+
+			for (var j = i + offset, k = 1; j < nodes.daysCells.length; j++)
+			{	// tail
+				var iDate = new Date(nextMonth);
+				iDate.setDate(k);
+
+				this.formDayString( iDate, nodes.daysCells[j] );
+				++k;
+			}
+
+			this.toggleExtraWeek(monthDate.getDay() > 5);
+		}
+
+		this.toggleExtraWeek  = function (show)
+		{
+			nodes.lastRow[show ? 'show' : 'hide']();
+		}
+
+		this.formDayString = function( d, label, isActive, events )
+		{
+			events = events || [];
+			isActive = isActive || false;
+
+			function getDateNumber( d ) {
+				var day = d.getDate().toString();
+				if( day.length == 1 ) { day = "0" + day; }
+
+				var month = d.getMonth().toString();
+				if( month.length == 1 ) { month = "0" + month; }
+
+				return parseInt( d.getFullYear().toString() + month + day, 10)
+			}
+
+			var today = new Date(),
+				isInFuture = ( getDateNumber( d ) > getDateNumber( today ) ),
+				isCurrentDay = ( getDateNumber( d ) == getDateNumber( o.currentDate ) ),
+				hasEvents = ( $.inArray( d.getDate(), events ) > -1 );
+
+			label[isCurrentDay ? 'addClass' : 'removeClass']( styles.current );
+
+			if( !isActive || isInFuture ) {
+				label.addClass( styles.inactive ).html(d.getDate());
+			} else if( hasEvents ) {
+
+				var ref = o.dayRef,
+					subs = [ [ '%Y', d.getFullYear()], [ '%M', d.getMonth() + 1], [ '%D', d.getDate()] ];
+				for( var i = 0; i < subs.length; ++i ) {
+					ref = ref.replace( subs[i][0], subs[i][1] );
+				}
+
+				label.removeClass( styles.inactive ).html( $("<a>")
+						.html( d.getDate() )
+						.attr( 'href', ref ) );
+			} else {
+				label.removeClass( styles.inactive ).html(d.getDate());
+			}
+		}
+
+		this.bindEvents = function ()
+		{
+			var that = this;
+			for (var sws in this.switcherStates) {
+				nodes[sws].mousedown( function (item) { return function ( ev ) {
+					if(that.switcherStates[item]) {
+						that.controller[item]();
+					}
+
+					ev.preventDefault();
+				} }(sws));
+			}
+		}
+
+		this.fillLabels = function (monthDate, switcherStates)
+		{
+			this.switcherStates = switcherStates
+			for (var sws in switcherStates)
 			{
-				$(data).appendTo(document.body).ljAddContextualPopup();
+				if(!switcherStates[sws]) {
+					nodes[sws].addClass(this.disabledStyle(sws));
+				}
+				else {
+					nodes[sws].removeClass(this.disabledStyle(sws));
+				}
 			}
-		);
-});
+			nodes.monthLabel.html( o.monthNames[ monthDate.getMonth() ] );
+			nodes.yearLabel.html ( monthDate.getFullYear() );
+		}
+
+		this.disabledStyle = function (sws)
+		{
+			if(sws == 'prevMonth' || sws == 'prevYear') return styles.prevDisabled;
+			else return styles.nextDisabled;
+		}
+	}
+
+	var Controller = function (view, model)
+	{
+		this.prevMonth = function () { model.switchMonth(-1); };
+		this.nextMonth = function () { model.switchMonth( 1); };
+
+		this.prevYear  = function () { model.switchYear(-1); };
+		this.nextYear  = function () { model.switchYear( 1); };
+
+		view.controller = this;
+		model.initialize();
+	}
+
+	var Model = function ( selectedDay, view)
+	{
+		this.enabledMonthsRange = [];
+		this.datesCache = {};
+		//if ajax request is already sent, we do not change the model before the answer
+		this.ajaxPending = false;
+
+		this.initialize = function ()
+		{
+			var startDate = new Date();
+			startDate.setFullYear( 1999, 3, 15 );
+			this.enabledMonthsRange = [ startDate, new Date()];
+			this.monthDate = new Date( selectedDay );
+			view.initialize(this.monthDate, null, this.getSwitcherStates(this.monthDate));
+			this.switchMonth( 0 );
+		}
+
+		this.switchMonth = function (go)
+		{
+			this.monthDate.setMonth(this.monthDate.getMonth() + go);
+			var self = this;
+			this.fetchMonthEvents( this.monthDate, function( events ) {
+				view.modelChanged(self.monthDate, events, self.getSwitcherStates( self.monthDate ) );
+			} )
+		}
+
+		this.switchYear = function (go)
+		{
+			this.monthDate.setFullYear(this.monthDate.getFullYear() + go, this.monthDate.getMonth(), this.monthDate.getDate());
+			while( !this.insideRange( this.enabledMonthsRange, this.monthDate ) ) {
+				this.monthDate.setMonth( this.monthDate.getMonth() - 1 );
+			}
+			var self = this;
+			this.fetchMonthEvents( this.monthDate, function( events ) {
+				view.modelChanged(self.monthDate, events, self.getSwitcherStates( self.monthDate ) );
+			} )
+		}
+
+		//now we generate random data, because endpoint does not exist. FIXIT
+		this.fetchMonthEvents = function( date, onFetch ) {
+			if( this.ajaxPending ) {
+				return;
+			}
+			this.ajaxPending = true;
+			var hash = date.getFullYear() + "-",
+				month = (date.getMonth() + 1).toString();
+
+			hash += ( (month.length == 1) ? "0" : "" ) + month;
+
+			if( hash in this.datesCache ) {
+				this.ajaxPending = false;
+				onFetch( this.datesCache[ hash ].slice( 0 ) );
+			} else {
+				var self = this;
+				//here we emulate ajax request
+				setTimeout( function() {
+					var daysNum = date.getMonth(),
+						result = [];
+
+					for( var i = 1; i <= daysNum; ++i ) {
+						if( Math.random() > 0.5 ) {
+							(function( day ) {
+								result.push( day );
+							}(i));
+						}
+					}
+
+					self.datesCache[ hash ] = result.slice( 0 );
+					self.ajaxPending = false;
+					onFetch( result.slice( 0 ) );
+				}, 0 );
+			}
+		}
+
+		this.getSwitcherStates = function (monthDate)
+		{
+			var prevMonth = new Date(monthDate);
+			prevMonth.setMonth(prevMonth.getMonth() - 1);
+			var pm = this.insideRange(this.enabledMonthsRange, prevMonth);
+
+			var nextMonth = new Date(monthDate)
+			nextMonth.setMonth(nextMonth.getMonth() + 1);
+			var nm = this.insideRange(this.enabledMonthsRange, nextMonth);
+
+			var prevYear = new Date(monthDate);
+			prevYear.setFullYear(monthDate.getFullYear() - 1, monthDate.getMonth(), monthDate.getDate());
+			var py = this.insideRange(this.enabledMonthsRange, prevYear);
+
+			var nextYear = new Date(monthDate);
+			nextYear.setFullYear(monthDate.getFullYear() + 1, 0, 1 );
+			var ny = this.insideRange(this.enabledMonthsRange, nextYear);
+
+			return { prevMonth: pm, nextMonth: nm, prevYear: py, nextYear: ny };
+		}
+
+		this.insideRange = function (range, iDate) { return iDate >= range[0] && iDate <= range[1] };
+	}
+
+	var nodes =
+	{
+		container: this,
+		table: this.find('table'),
+
+		prevMonth: this.find('.cal-nav-month .cal-nav-prev'),
+		nextMonth: this.find('.cal-nav-month .cal-nav-next'),
+		prevYear:  this.find('.cal-nav-year .cal-nav-prev'),
+		nextYear:  this.find('.cal-nav-year .cal-nav-next'),
+
+		monthLabel: this.find('.cal-nav-month .cal-month'),
+		yearLabel: this.find('.cal-nav-year .cal-year')
+	}
+
+	var styles =
+	{
+		inactive : 'other',
+		hasEvents: '',
+		current  : 'current',
+		future: 'other',
+
+		nextDisabled : 'cal-nav-next-dis',
+		prevDisabled : 'cal-nav-prev-dis'
+	}
+
+	var view = new View(nodes, styles);
+	var model = new Model( o.currentDate, view);
+	var controller = new Controller(view, model);
+};
+
+}( jQuery, window ));

Modified: trunk/htdocs/js/jquery_fn.js
===================================================================
--- trunk/htdocs/js/jquery_fn.js	2010-11-29 09:46:51 UTC (rev 17815)
+++ trunk/htdocs/js/jquery_fn.js	2010-11-29 10:03:05 UTC (rev 17816)
@@ -100,6 +100,50 @@
 	return this;
 }
 
+//this one is fields type agnostic but creates additional label elements, which need to be styled
+jQuery.fn.labeledPlaceholder = function() {
+	if ('placeholder' in document.createElement('input')) {
+		return this;
+	}
+
+	function focus_action( input, label ) {
+		label.hide();
+	}
+
+	function blur_action( input, label ) {
+		if( input.val().length === 0 ) {
+			label.show();
+		}
+	}
+
+	return this.each( function() {
+		var $this = jQuery( this ),
+			placeholder = $this.attr( 'placeholder' );
+
+		if( !placeholder || placeholder.length === 0 ) { return; }
+
+		var label = jQuery( "<label></label>")
+				.css({
+					position: "absolute",
+					cursor: "text",
+					display: "none"
+					})
+				.addClass('placeholder-label')
+				.mousedown(function( ev ) {
+					setTimeout( function() {
+						focus_action( $this, label )
+						$this.focus();
+					}, 0);
+				} )
+				.html( placeholder )
+				.insertBefore( $this );
+		$this.focus( function() { focus_action( $this, label ) } )
+			.blur( function() { blur_action( $this, label ) } );
+
+		blur_action( $this, label );
+	} );
+}
+
 jQuery.fn.input = function(fn)
 {
 	return fn ? this.bind('input keyup paste', fn) : this.trigger('input');

Tags: can3p, js, livejournal
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments