Committer: dpetrov
LJSUP-7240: Controlstrip. Calendar now uses plugin from jquery_fn.js and shows only months with entries.hU trunk/htdocs/js/controlstrip.js U trunk/htdocs/js/jquery_fn.js
Modified: trunk/htdocs/js/controlstrip.js =================================================================== --- trunk/htdocs/js/controlstrip.js 2010-12-17 07:46:57 UTC (rev 17959) +++ trunk/htdocs/js/controlstrip.js 2010-12-17 08:25:51 UTC (rev 17960) @@ -64,17 +64,8 @@ }; ControlStrip.Calendar = function( o ) { + o = 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; } @@ -83,321 +74,17 @@ 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() ]; + var onFetch = function( onload ) { + jQuery.getJSON( LiveJournal.getAjaxUrl('get_posting_days'), + { journal: Site.currentJournal }, onload ); } - 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) - { - 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); + this.calendar( { + onFetch: onFetch, + activeUntil: new Date(), + startMonth: new Date( 1999, 3, 1 ), + endMonth: new Date() + } ); }; }( jQuery, window )); Modified: trunk/htdocs/js/jquery_fn.js =================================================================== --- trunk/htdocs/js/jquery_fn.js 2010-12-17 07:46:57 UTC (rev 17959) +++ trunk/htdocs/js/jquery_fn.js 2010-12-17 08:25:51 UTC (rev 17960) @@ -193,7 +193,7 @@ } }); - return this; + return this; } /** jQuery overlay plugin @@ -264,7 +264,6 @@ } }); } - jQuery.fn.calendar = function( o ) { //global variables for all instances var defaultOptions = { @@ -315,9 +314,13 @@ var controller = new Controller(view, model, options); } - function getDateNumber( d ) { + function getDateNumber( d, dropDays ) { + dropDays = dropDays || false; var day = d.getDate().toString(); if( day.length == 1 ) { day = "0" + day; } + if( dropDays ) { + day = ""; + } var month = d.getMonth().toString(); if( month.length == 1 ) { month = "0" + month; } @@ -559,72 +562,124 @@ model.initialize(); } - var Model = function ( selectedDay, view, options ) + var Model = function( selectedDay, view, options ) { - this.enabledMonthsRange = []; - this.datesCache = null; - //if ajax request is already sent, we do not change the model before the answer - this.ajaxPending = false; + this.events = null; this.initialize = function () { + var self = this; var startMonth = options.startMonth || new Date( 1900, 0, 1 ), endMonth = options.endMonth || new Date( 2050, 0, 1 ); + startMonth.setDate( 1 ); this.enabledMonthsRange = [ startMonth, endMonth ]; this.monthDate = new Date( selectedDay ); this.selectedDay = new Date( selectedDay ); - view.initialize(this.monthDate, null, this.getSwitcherStates(this.monthDate)); - this.switchMonth( 0 ); - /* + function bootstrapView() { + view.initialize( self.monthDate, null, self.getSwitcherStates( self.monthDate ) ); + self.switchMonth( 0 ); + } + if( !options.onFetch ) { - view.initialize(this.monthDate, null, this.getSwitcherStates(this.monthDate)); - this.switchMonth( 0 ); + bootstrapView(); } else { - var self = this; - this.fetchEvents( function( events ) { - self.initEvents( events ); - view.initialize(this.monthDate, null, this.getSwitcherStates(this.monthDate)); - self.switchMonth( 0 ); + options.onFetch( function( events ) { + self.events = self.initEvents( events ); + bootstrapView(); } ); } - */ } this.switchMonth = function (go) { + var dir = go || -1; var date = new Date( this.monthDate ); date.setMonth(date.getMonth() + go); - this.switchDate( date, go ); + this.switchDate( date, dir ); } this.switchYear = function (go) { - var date = new Date( this.monthDate ); - date.setFullYear(date.getFullYear() + go, date.getMonth(), date.getDate()); - if( !this.insideRange( this.enabledMonthsRange, date ) ) { - var add = getDateNumber( date ) < getDateNumber( this.enabledMonthsRange[ 0 ] ) ? 1 : -1; - while( !this.insideRange( this.enabledMonthsRange, date ) ) { - date.setMonth( date.getMonth() + add ); + var sgn = ( go > 0 ) ? 1 : ( go < 0 ) ? - 1 : 0, + count = sgn * ( ( Math.abs( go ) ) * 12 ); + this.switchMonth( count ); + } + + this.initEvents = function( data ) { + return function() { + var datesArr = []; + var datesCache = []; + + if( typeof data === "object" ) { + var id = ""; + for( var year in data ) { + for( var month in data[ year ] ) { + id = makeHash( year, month ); + datesCache[ id ] = jQuery.map( data[ year ][ month ], function( item ) { return parseInt( item, 10 ); } ); + datesArr.push( id ); + } + } } - } - this.switchDate( date, go ); + datesArr.sort(); + + function outOfBounds( date ) { + return ( datesArr.length === 0 || date < datesArr[ 0 ] || date > datesArr[ datesArr.length - 1 ] ); + } + + function makeHash( year, month ) { + year = year.toString(); month = month.toString(); + return parseInt( year + ( (month.length == 1) ? "0" : "" ) + month, 10 ); + } + + return { + getPrev: function( date ) { + if( outOfBounds( date ) ) { return false; } + for( var i = datesArr.length - 1; i >= 0 ; --i ) { + if( datesArr[ i ] < date ) { + return datesCache[ i ]; + } + } + return false; + }, + + getNext: function( date ) { + if( outOfBounds( date ) ) { return false; } + for( var i = 0; i < datesArr.length; ++i ) { + if( datesArr[ i ] > date ) { + return datesCache[ i ]; + } + } + return false; + }, + + hasEventsAfter: function( date, dir ) { + var hash = makeHash( date.getFullYear(), date.getMonth() + 1 ); + var arr = jQuery.grep( datesArr, function( n, i ) { + return ( dir > 0 ) ? n > hash : n < hash; + } ); + + return arr.length > 0; + }, + + getEvents: function( date ) { + var hash = makeHash( date.getFullYear(), date.getMonth() + 1 ); + return ( hash in datesCache ) ? datesCache[ hash ] : false; + } + } + }(); } this.switchDate = function( date, dir ) { dir = dir || -1; - if( this.ajaxPending ) { - return; + date = this.fitDate( date, this.monthDate, dir ); + + if( date !== false ) { + this.monthDate = date; + + var events = ( this.events ) ? this.events.getEvents( date ) : null; + view.modelChanged( date, this.selectedDay, events, this.getSwitcherStates( date ) ); } - this.monthDate = date; - - var self = this; - this.fetchMonthEvents( this.monthDate, function( events, date ) { - if( typeof events === "boolean" && events === false ) { - } else { - view.modelChanged(self.monthDate, self.selectedDay, events, self.getSwitcherStates( self.monthDate ) ); - } - }, dir ); } this.selectDate = function( date ) { @@ -632,46 +687,68 @@ view.dateSelected( this.selectedDay ); } - this.fetchMonthEvents = function( date, onFetch ) { - this.ajaxPending = true; + this.getSwitcherStates = function (monthDate) + { + var yearStart = new Date( monthDate.getFullYear(), 0, 1 ), + yearEnd = new Date( monthDate.getFullYear(), 11, 1 ); - //if there is no resource to fetch data, we think, that every month has events - if( !options.onFetchMonth ) { - onFetch( [] ); - this.ajaxPending = false; - } else { - var self = this; - options.onFetchMonth( date, function( data ) { - self.ajaxPending = false; - onFetch( data ); - } ); - } + return { + prevMonth: this.isActivePrev( monthDate ) !== false, + prevYear: this.isActivePrev( yearStart ) !== false, + nextMonth: this.isActiveNext( monthDate ) !== false, + nextYear: this.isActiveNext( yearEnd ) !== false + }; } - this.getSwitcherStates = function (monthDate) - { - var prevMonth = new Date(monthDate); - prevMonth.setMonth(prevMonth.getMonth() - 1); - var pm = this.insideRange(this.enabledMonthsRange, prevMonth); + this.isActiveNext = function( date ) { return this.isActiveDate( date, 1 ); }; + this.isActivePrev = function( date ) { return this.isActiveDate( date, -1 ); }; + this.isActiveDate = function( date, dir ) { + var checkEvents = !!( this.events !== null ); + var d = new Date( date ); + d.setMonth( d.getMonth() + dir ); - var nextMonth = new Date(monthDate) - nextMonth.setMonth(nextMonth.getMonth() + 1); - var nm = this.insideRange(this.enabledMonthsRange, nextMonth); + return this.insideRange( this.enabledMonthsRange, d ) && ( !checkEvents || this.events.hasEventsAfter( d, dir ) ); + } - var prevYear = new Date(monthDate); - prevYear.setFullYear(monthDate.getFullYear() - 1, 11, 31 ); - var py = this.insideRange(this.enabledMonthsRange, prevYear); + this.fitDate = function( date, currentDate, dir ) { + date = new Date( date ); + var checkEvents = !!( this.events !== null ); + if( !this.insideRange( this.enabledMonthsRange, date ) ) { + if( getDateNumber( date, true ) < getDateNumber( this.enabledMonthsRange[ 0 ], true ) ) { + date = new Date( this.enabledMonthsRange[ 0 ] ); + } else { + date = new Date( this.enabledMonthsRange[ 1 ] ); + } + } - var nextYear = new Date(monthDate); - nextYear.setFullYear(monthDate.getFullYear() + 1, 0, 1 ); - var ny = this.insideRange(this.enabledMonthsRange, nextYear); + if( !checkEvents || this.events.getEvents( date ) ) { + return date; + } - return { prevMonth: pm, nextMonth: nm, prevYear: py, nextYear: ny }; + saveDate = new Date( date ); + + var sgn = ( dir > 0 ) ? 1 : -1; + while( this.insideRange( this.enabledMonthsRange, date ) && !this.events.getEvents( date ) ) { + date.setMonth( date.getMonth() + sgn ); + if( this.events.getEvents( date ) ) { + return date; + } + } + + date = saveDate; + while( ( getDateNumber( date, true ) !== getDateNumber( currentDate, true ) ) ) { + if( this.events.getEvents( date ) ) { + return date; + } + date.setMonth( date.getMonth() - sgn ); + } + + return false; } this.insideRange = function (range, iDate) { - return getDateNumber( iDate ) >= getDateNumber( range[0] ) - && getDateNumber( iDate ) <= getDateNumber( range[1] ) + return getDateNumber( iDate, true ) >= getDateNumber( range[0], true ) + && getDateNumber( iDate, true ) <= getDateNumber( range[1], true ); }; }