All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.primefaces.clock.clock.js Maven / Gradle / Ivy

There is a newer version: 14.0.0
Show newest version
/**
 * __PrimeFaces SimpleDateFormat widget__
 * 
 * Code ported from Tim Down's http://www.timdown.co.uk/code/simpledateformat.php
 * 
 * Helper widget for working with `Date`s and date formats.
 * 
 * @template {PrimeFaces.widget.SimpleDateFormatCfg} [TCfg=PrimeFaces.widget.SimpleDateFormatCfg] Type of the configuration
 * object for this widget.
 * @interface {PrimeFaces.widget.SimpleDateFormatCfg} cfg The configuration for the
 * {@link  SimpleDateFormat| SimpleDateFormat widget}. You can access this configuration via
 * {@link PrimeFaces.widget.BaseWidget.cfg|BaseWidget.cfg}. Please note that this configuration is usually meant to be
 * read-only and should not be modified.
 * 
 * @prop {PrimeFaces.PartialWidgetCfg} cfg The configuration of this widget
 * instance. Please note that no property is guaranteed to be present, you should always check for `undefined` before
 * accessing a property. This is partly because the value of a property is not transmitted from the server to the client
 * when it equals the default.
 * 
 * @prop {string[]} cfg.dayNames Localized day names (`Monday`, `Tuesday` etc.)
 * @prop {string | string[]} cfg.id The client-side ID of this widget, with all parent naming containers, such as
 * `myForm:myWidget`. This is also the ID of the container HTML element for this widget. In case the widget needs
 * multiple container elements (such as {@link Paginator}), this may also be an array if IDs.
 * @prop {string} cfg.locale The locale for formatting dates. 
 * @prop {number} cfg.minimalDaysInFirstWeek Minimal number of days a week is allowed to have to be considered a "full"
 * week, used by `getWeekInMonth` in `getWeekInYear`.
 * @prop {string[]} cfg.monthNames Localized month names (`January`, `February` etc.)
 * @prop {RegExp} cfg.regex A regex for splitting a date format into its components.
 * @prop {Record} cfg.types Object with the different keywords used by the date format.
 * @prop {string} cfg.widgetVar The name of the widget variables of this widget. The widget variable can be used to
 * access a widget instance by calling `PF('myWidgetVar')`.
 */
PrimeFaces.widget.SimpleDateFormat = Class.extend({

    /**
     * A widget class should not have an explicit constructor. Instead, this initialize method is called after the widget
     * was created. You can use this method to perform any initialization that is required. For widgets that need to create
     * custom HTML on the client-side this is also the place where you should call your render method.
     * 
     * @param {Partial} cfg The widget configuration to be used for this widget instance. This widget
     * configuration is usually created on the server by the `javax.faces.render.Renderer` for this component.
     */
    init: function(cfg) {
        this.cfg = cfg;
        this.cfg.regex = /('[^']*')|(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|Z+)|([a-zA-Z]+)|([^a-zA-Z']+)/
        this.cfg.TEXT2 = 0;
        this.cfg.TEXT3 = 1;
        this.cfg.NUMBER = 2;
        this.cfg.YEAR = 3;
        this.cfg.MONTH = 4;
        this.cfg.TIMEZONE = 6;
        this.cfg.types = {
            G : this.cfg.TEXT2,
            y : this.cfg.YEAR,
            M : this.cfg.MONTH,
            w : this.cfg.NUMBER,
            W : this.cfg.NUMBER,
            D : this.cfg.NUMBER,
            d : this.cfg.NUMBER,
            F : this.cfg.NUMBER,
            E : this.cfg.TEXT3,
            a : this.cfg.TEXT2,
            H : this.cfg.NUMBER,
            k : this.cfg.NUMBER,
            K : this.cfg.NUMBER,
            h : this.cfg.NUMBER,
            m : this.cfg.NUMBER,
            s : this.cfg.NUMBER,
            S : this.cfg.NUMBER,
            Z : this.cfg.TIMEZONE
        };

        this.cfg.ONE_DAY = 24 * 60 * 60 * 1000;
        this.cfg.ONE_WEEK = 7 * this.cfg.ONE_DAY;
        this.cfg.DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK = 1;

        var localeSettings = PrimeFaces.getLocaleSettings(this.cfg.locale);
        if(localeSettings) {
            this.cfg.monthNames = localeSettings.monthNames;
            this.cfg.dayNames = localeSettings.dayNames;
        }
        else {
            this.cfg.monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
            this.cfg.dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
        }
    },

    /**
     * Creates a new date object that represents midnighht of the given year, month, and day.
     * @param {number} year A year to set. `0` repesents the year `1900`, `100` the year `2000`.
     * @param {number} month A month (of the year) to set. `0` is January, `11` is `December`.
     * @param {number} day  A day (of the month) to set, in the range `1...31`.
     * @return {Date} A date for the given year, month, and day at at midnight.
     */
    newDateAtMidnight: function(year, month, day) {
        var d = new Date(year, month, day, 0, 0, 0);
        d.setMilliseconds(0);
        return d;
    },

    /**
     * Computes the difference between the two given dates.
     * @param {Date} date1 First input date
     * @param  {Date} date2 Second input date
     * @return {number} Time in milliseconds between the two dates (`date1-date2`).
     */
    getDifference : function(date1, date2) {
        return date1.getTime() - date2.getTime();
    },

    /**
     * Checks whether the first given date lies before the second given date.
     * @param {Date} date1 First input date
     * @param {Date} date2 Second input date
     * @return {boolean} `true` if `date1` lies before `date2`, or `false` otherwise.
     */
    isBefore : function(date1, date2) {
        return date1.getTime() < date2.getTime();
    },

    /**
     * Converts the given date to UTC time, that is, the number of milliseconds between midnight, January 1, 1970
     * Universal Coordinated Time (UTC) (or GMT) and the given date.
     * @param {Date} date Date to convert to UTC.
     * @return {number} The given date, converted to UTC time.
     */
    getUTCTime: function(date) {
        if(date != undefined){
            return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
        }

    },

    /**
     * Finds the difference in milliseconds between the two given date (`date1-date2`).
     * @param {Date} date1 First input date
     * @param {Date} date2 Second input date
     * @return {number} The numer of milliseconds between the two given date (`date1-date2`).
     */
    getTimeSince: function(date1, date2) {
        return this.getUTCTime(date1) - this.getUTCTime(date2);
    },

    /**
     * Finds closest Sunday preceding the given date. If the date is already a Sunday, that day is returned.
     * @param {Date} date Input date.
     * @return {Date} The date at midnight of the first Sunday before the given date. If the given date is already a
     * Sunday, that day is returned.
     */
    getPreviousSunday: function(date) {
        // Using midday avoids any possibility of DST messing things up
        var midday = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0);
        var previousSunday = new Date(midday.getTime() - date.getDay() * this.cfg.ONE_DAY);

        return this.newDateAtMidnight(previousSunday.getFullYear(), previousSunday.getMonth(), previousSunday.getDate());
    },

    /**
     * Computes the ordinal index of the week of the year of the given date.
     * @param {Date} date Date to check.
     * @param {number} minimalDaysInFirstWeek Minimal number of days the first week of the year is allowed to have. If
     * the first week contains less days, the returned output is decremented by one (if you do not want to count, say,
     * 2 days, as week).
     * @return {number} The week of the year of the given date, starting at `0`.
     */
    getWeekInYear : function(date, minimalDaysInFirstWeek) {
        var previousSunday = this.getPreviousSunday(date);
        var startOfYear = this.newDateAtMidnight(date.getFullYear(), 0, 1);
        var numberOfSundays = this.isBefore(previousSunday, startOfYear) ? 0 : 1 + Math.floor(this.getTimeSince(previousSunday,startOfYear) / this.cfg.ONE_WEEK);
        var numberOfDaysInFirstWeek =  7 - startOfYear.getDay();
        var weekInYear = numberOfSundays;
        if (numberOfDaysInFirstWeek < minimalDaysInFirstWeek) {
            weekInYear--;
        }

        return weekInYear;
    },

    /**
     * Computes the ordinal index of the week of the month of the given date.
     * @param {Date} date Date with a month to check.
     * @param {number} minimalDaysInFirstWeek Minimal number of days the first week of the month is allowed to have. If
     * the first week contains less days, the returned output is decremented by one (if you do not want to count, say,
     * 2 days, as week).
     * @return {number} The week of the month of the given date, starting at `0`.
     */
   getWeekInMonth: function(date, minimalDaysInFirstWeek) {
        var previousSunday = this.getPreviousSunday(date);
        var startOfMonth = this.newDateAtMidnight(date.getFullYear(), date.getMonth(), 1);
        var numberOfSundays = this.isBefore(previousSunday,startOfMonth) ? 0 : 1 + Math.floor((this.getTimeSince(previousSunday, startOfMonth)) / this.cfg.ONE_WEEK);
        var numberOfDaysInFirstWeek =  7 - startOfMonth.getDay();
        var weekInMonth = numberOfSundays;
        if (numberOfDaysInFirstWeek >= minimalDaysInFirstWeek) {
            weekInMonth++;
        }

        return weekInMonth;
    },

    /**
     * Computes the ordinal index of the given day in the given year.
     * @param {Date} date A day to check.
     * @return {number} The ordinal index of the given day relative to the beginning of the year, starting at `1`.
     */
    getDayInYear: function(date) {
        var startOfYear = this.newDateAtMidnight(date.getFullYear(), 0, 1);

        return 1 + Math.floor(this.getTimeSince(date, startOfYear) / this.cfg.ONE_DAY);
    },

    /**
     * Finds the currently configured value of how many days a week must have at least to be considered a "full" week.
     * Weeks with less that that number of days are disregarded in `getWeekInMonth` and `getWeekInYear`.
     * @param {unknown} days Unused.
     * @return {number} The minimal number of days a week is allowed to have to be considered a "full" week. 
     */
    getMinimalDaysInFirstWeek: function(days) {
        return this.cfg.minimalDaysInFirstWeek	? this.cfg.DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK : this.cfg.minimalDaysInFirstWeek;
    },

    /**
     * Format sthe given given according to the pattern of the current widget configuration.
     * @param {Date} date A date to format
     * @return {string} The given date as a formatted string.
     */
    format: function(date) {
        var formattedString = "";
        var result;

        var padWithZeroes = function(str, len) {
            while (str.length < len) {
                str = "0" + str;
            }
            return str;
        };

        var formatText = function(data, numberOfLetters, minLength) {
            return (numberOfLetters >= 4) ? data : data.substr(0, Math.max(minLength, numberOfLetters));
        };

        var formatNumber = function(data, numberOfLetters) {
            var dataString = "" + data;
            // Pad with 0s as necessary
            return padWithZeroes(dataString, numberOfLetters);
        };

        var searchString = this.cfg.pattern;
        while ((result = this.cfg.regex.exec(searchString))) {
            var matchedString = result[0];
            var quotedString = result[1];
            var patternLetters = result[2];
            var otherLetters = result[3];
            var otherCharacters = result[4];

            // If the pattern matched is quoted string, output the text between the quotes
            if (quotedString) {
                if (quotedString == "''") {
                    formattedString += "'";
                } else {
                    formattedString += quotedString.substring(1, quotedString.length - 1);
                }
            } else if (otherLetters) {
            // Swallow non-pattern letters by doing nothing here
            } else if (otherCharacters) {
                // Simply output other characters
                formattedString += otherCharacters;
            } else if (patternLetters) {
                // Replace pattern letters
                var patternLetter = patternLetters.charAt(0);
                var numberOfLetters = patternLetters.length;
                var rawData = "";
                switch (patternLetter) {
                    case "G":
                        rawData = "AD";
                        break;
                    case "y":
                        rawData = date.getFullYear();
                        break;
                    case "M":
                        rawData = date.getMonth();
                        break;
                    case "w":
                        rawData = this.getWeekInYear(date, this.getMinimalDaysInFirstWeek());
                        break;
                    case "W":
                        rawData = this.getWeekInMonth(date, this.getMinimalDaysInFirstWeek());
                        break;
                    case "D":
                        rawData = this.getDayInYear(date);
                        break;
                    case "d":
                        rawData = date.getDate();
                        break;
                    case "F":
                        rawData = 1 + Math.floor((date.getDate() - 1) / 7);
                        break;
                    case "E":
                        rawData = this.cfg.dayNames[date.getDay()];
                        break;
                    case "a":
                        rawData = (date.getHours() >= 12) ? "PM" : "AM";
                        break;
                    case "H":
                        rawData = date.getHours();
                        break;
                    case "k":
                        rawData = date.getHours() || 24;
                        break;
                    case "K":
                        rawData = date.getHours() % 12;
                        break;
                    case "h":
                        rawData = (date.getHours() % 12) || 12;
                        break;
                    case "m":
                        rawData = date.getMinutes();
                        break;
                    case "s":
                        rawData = date.getSeconds();
                        break;
                    case "S":
                        rawData = date.getMilliseconds();
                        break;
                    case "Z":
                        rawData = date.getTimezoneOffset(); // This is returns the number of minutes since GMT was this time.
                        break;
                }
                // Format the raw data depending on the type
                switch (this.cfg.types[patternLetter]) {
                    case this.cfg.TEXT2:
                        formattedString += formatText(rawData, numberOfLetters, 2);
                        break;
                    case this.cfg.TEXT3:
                        formattedString += formatText(rawData, numberOfLetters, 3);
                        break;
                    case this.cfg.NUMBER:
                        formattedString += formatNumber(rawData, numberOfLetters);
                        break;
                    case this.cfg.YEAR:
                        if (numberOfLetters <= 3) {
                            // Output a 2-digit year
                            var dataString = "" + rawData;
                            formattedString += dataString.substr(2, 2);
                        } else {
                            formattedString += formatNumber(rawData, numberOfLetters);
                        }
                        break;
                    case this.cfg.MONTH:
                        if (numberOfLetters >= 3) {
                            formattedString += formatText(this.cfg.monthNames[rawData], numberOfLetters, numberOfLetters);
                        } else {
                            // NB. Months returned by getMonth are zero-based
                            formattedString += formatNumber(rawData + 1, numberOfLetters);
                        }
                        break;
                    case this.cfg.TIMEZONE:
                        var isPositive = (rawData > 0);
                        // The following line looks like a mistake but isn't
                        // because of the way getTimezoneOffset measures.
                        var prefix = isPositive ? "-" : "+";
                        var absData = Math.abs(rawData);

                        // Hours
                        var hours = "" + Math.floor(absData / 60);
                        hours = padWithZeroes(hours, 2);
                        // Minutes
                        var minutes = "" + (absData % 60);
                        minutes = padWithZeroes(minutes, 2);

                        formattedString += prefix + hours + minutes;
                        break;
                }
            }

            searchString = searchString.substr(result.index + result[0].length);
        }
        return formattedString;
    }
});

/**
 * __PrimeFaces Clock Widget__
 * 
 * Clock displays server or client datetime live.
 * 
 * @typedef {"client" | "server"} PrimeFaces.widget.Clock.TimeMode Indicates which time the clock widget uses. `client`
 * uses the time from the client (browser), `server` uses the time from the server.
 * 
 * @typedef {"analog" | "digital"} PrimeFaces.widget.Clock.DisplayMode Display mode for the clock widget. `analog`
 * displays an analog clock, `digital` a digitial clock.
 * 
 * @interface {PrimeFaces.widget.Clock.Dimensions} Dimensions Computed dimensions for the individual parts of the analog
 * clock, all in pixels.
 * @prop {number} Dimensions.size Width of the clock element in pixels.
 * @prop {number} Dimensions.half Half width of the clock element in pixels.
 * @prop {number} Dimensions.clock_width Width of the clock face in pixels.
 * @prop {number} Dimensions.hour_sign_min_size Distance in pixels from the center of the circle where the hour mark
 * starts.
 * @prop {number} Dimensions.hour_sign_max_size Distance in pixels from the center of the circle where the hour mark
 * ends.
 * @prop {number} Dimensions.hour_hand_start_position Radial distance in pixels from the circumference of the circle
 * where the hour hand starts. 
 * @prop {number} Dimensions.hour_hand_stroke_width Stroke width in pixels of the hour hand.
 * @prop {number} Dimensions.minute_hand_start_position Radial distance in pixels from the circumference of the circle
 * where the minute hand starts.
 * @prop {number} Dimensions.minute_hand_stroke_width Stroke width in pixels of the minute hand.
 * @prop {number} Dimensions.second_hand_start_position Radial distance in pixels from the circumference of the circle
 * where the seconds hand starts. 
 * @prop {number} Dimensions.second_hand_stroke_width Stroke width in pixels of the seconds hand.
 * @prop {number} Dimensions.pin_width Radius in pixels of the pin at the center of the clock face.
 * 
 * @prop {import("raphael").RaphaelPaper} canvas The canvas for the analog clock.
 * @prop {import("raphael").RaphaelElement} clock The drawn element for the clock outline.
 * @prop {Date} current The currently displayed time.
 * @prop {PrimeFaces.widget.Clock.Dimensions} dimensions Calculated sizes for the analog clock elements.
 * @prop {import("raphael").RaphaelElement[]} hour_sign The drawn elements for the hour signs (1-12).
 * @prop {import("raphael").RaphaelElement} hour_hand The drawn element for the hour hand.
 * @prop {number} interval The set-interval timer ID for the ticking of the clock.
 * @prop {import("raphael").RaphaelElement} minute_hand The drawn element for the minute hand.
 * @prop {import("raphael").RaphaelElement} pin The drawn element for the pin at the center of the clock.
 * @prop {import("raphael").RaphaelElement} second_hand The drawn element for the second hand.
 * 
 * @interface {PrimeFaces.widget.ClockCfg} cfg The configuration for the {@link  Clock| Clock widget}.
 * You can access this configuration via {@link PrimeFaces.widget.BaseWidget.cfg|BaseWidget.cfg}. Please note that this
 * configuration is usually meant to be read-only and should not be modified.
 * @extends {PrimeFaces.widget.BaseWidgetCfg} cfg
 * 
 * @prop {boolean} cfg.autoSync When `mode` is set to `server`: `true` to automatically sync the time with the server
 * according to the specified `syncInterval`, or `false` otherwise.
 * @prop {PrimeFaces.widget.Clock.DisplayMode} cfg.displayMode Whether the clock is displayed as an analog or digital
 * clock.
 * @prop {string} cfg.locale Locale for the clock, determines the time format.
 * @prop {PrimeFaces.widget.Clock.TimeMode} cfg.mode Whether the clock uses the time of the browser or the time from the
 * server.
 * @prop {string} cfg.pattern Datetime format.
 * @prop {number} cfg.syncInterval Defines the sync in ms interval in when `autoSync` is set to `true`.
 * @prop {string} cfg.value The initial time value for the clock to display.
 */
PrimeFaces.widget.Clock = PrimeFaces.widget.BaseWidget.extend({

    /**
     * @override
     * @inheritdoc
     * @param {PrimeFaces.PartialWidgetCfg} cfg
     */
    init: function(cfg) {
        this._super(cfg);

        this.cfg.pattern = this.cfg.pattern||"MM/dd/yyyy HH:mm:ss";
        this.cfg.dateFormat = new PrimeFaces.widget.SimpleDateFormat({
            pattern: this.cfg.pattern,
            locale: this.cfg.locale
        });
        this.current = this.isClient() ? new Date() : new Date(this.cfg.value);

        var $this = this;
        if(this.isAnalogClock()) {
            this.interval = setInterval(function() {
                $this.update();
            }, 1000);

            this.draw();
        }
        else {
            this.start();
        }

        if(!this.isClient() && this.cfg.autoSync) {
            setInterval(function() {
                $this.sync();
            }, this.cfg.syncInterval);
        }
    },

    /**
     * @override
     * @inheritdoc
     * @param {PrimeFaces.PartialWidgetCfg} cfg
     */
    refresh: function(cfg) {
        clearInterval(this.interval);

        this._super(cfg);
    },

    /**
     * Checks whether the time of the client is used for this clock.
     * @return {boolean} `true` if the time of the client is used, or `false` if the time of the server is used.
     */
    isClient: function() {
        return this.cfg.mode === 'client';
    },

    /**
     * Starts this clock if it is not already running.
     */
    start: function() {
        var $this = this;
        this.interval = setInterval(function(){
            $this.updateOutput();
        }, 1000);
    },

    /**
     * Stops this clock it is currently running.
     */
    stop: function() {
        clearInterval(this.interval);
    },

    /**
     * Called after a tick of the clock, updates the visual display of this clock.
     * @private
     */
    updateOutput: function() {
        this.current.setSeconds(this.current.getSeconds() + 1);
        this.jq.text(this.cfg.dateFormat.format(this.current));
    },

    /**
     * Synchronizes this clock so that it shows the current time. This will trigger an AJAX update of this component.
     */
    sync: function() {
        if(!this.isAnalogClock()) {
            this.stop();
        }

        var $this = this,
        options = {
            source: this.id,
            process: this.id,
            async: true,
            global: false,
            params: [{
                name: this.id + '_sync', value: true
            }],
            oncomplete: function(xhr, status, args, data) {
                if($this.isAnalogClock()) {
                    $this.current = new Date(args.datetime);
                }
                else {
                    $this.stop();
                    $this.current = new Date(args.datetime);
                    $this.jq.text($this.cfg.dateFormat.format($this.current));
                    $this.start();
                }
            }
        };

        PrimeFaces.ajax.Request.handle(options);
    },

    /**
     * Draws this clock according the the current widget configuation.
     * @private
     */
    draw: function() {

        this.dimensions = this.getDimensions(this.jq.width());

        this.canvas = Raphael(this.id, this.dimensions.size,this.dimensions.size);

        this.clock = this.canvas.circle(this.dimensions.half,this.dimensions.half, this.dimensions.clock_width);

        this.draw_hour_signs();

        this.draw_hands();

        this.pin = this.canvas.circle(this.dimensions.half,this.dimensions.half, this.dimensions.pin_width);

        this.clock.attr({
            "fill": "#ffffff",
            "stroke": "#4A4A4A",
            "stroke-width": "3"
        });

        for (var i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy