From b676a330d97dd626c133209135aa6e5e288b5f8c Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Sun, 26 Apr 2009 04:43:27 +0000 Subject: [PATCH] --- Makefile | 18 + docs/Makefile | 82 + docs/conf.py | 190 ++ docs/index.txt | 228 ++ docs/templates/layout.html | 8 + examples/example.html | 73 + examples/gcal.html | 54 + examples/json.html | 41 + examples/json_events.php | 25 + full_calendar.css | 135 ++ full_calendar.js | 726 ++++++ gcal.js | 55 + jquery/jquery.js | 4376 ++++++++++++++++++++++++++++++++++++ jquery/ui.core.js | 519 +++++ jquery/ui.draggable.js | 766 +++++++ version.txt | 1 + 16 files changed, 7297 insertions(+) create mode 100644 Makefile create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.txt create mode 100644 docs/templates/layout.html create mode 100644 examples/example.html create mode 100644 examples/gcal.html create mode 100644 examples/json.html create mode 100644 examples/json_events.php create mode 100644 full_calendar.css create mode 100644 full_calendar.js create mode 100644 gcal.js create mode 100644 jquery/jquery.js create mode 100644 jquery/ui.core.js create mode 100644 jquery/ui.draggable.js create mode 100644 version.txt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0399baf --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ + +FILES =\ + *.js\ + *.css\ + jquery\ + examples + +zip: + @mkdir -p build/full_calendar + @cp -rt build/full_calendar ${FILES} + @sed -i "s/Version:/& `cat version.txt`/" build/full_calendar/full_calendar.js + @mkdir -p dist + @cd build;\ + zip -r ../dist/full_calendar_`cat ../version.txt`.zip * + +clean: + @rm -rf build/* + @rm -rf dist/* diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..1fee6dd --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,82 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html web pickle htmlhelp latex changes linkcheck + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview over all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + +clean: + -rm -rf build/* + +html: + mkdir -p build/html build/doctrees + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html + @echo + @echo "Build finished. The HTML pages are in build/html." + +arshaw: + mkdir -p build/html build/doctrees + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html + cp -r build/html/* /var/www/arshaw/pages/fullcalendar/docs/ + @echo + @echo "Build finished. The HTML pages are in build/html." + +pickle: + mkdir -p build/pickle build/doctrees + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +web: pickle + +json: + mkdir -p build/json build/doctrees + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + mkdir -p build/htmlhelp build/doctrees + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in build/htmlhelp." + +latex: + mkdir -p build/latex build/doctrees + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex + @echo + @echo "Build finished; the LaTeX files are in build/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + mkdir -p build/changes build/doctrees + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes + @echo + @echo "The overview file is in build/changes." + +linkcheck: + mkdir -p build/linkcheck build/doctrees + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in build/linkcheck/output.txt." diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..cf7a292 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# +# FullCalendar documentation build configuration file, created by +# sphinx-quickstart on Sat Apr 11 19:03:41 2009. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If your extensions are in another directory, add it here. If the directory +# is relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# General configuration +# --------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['templates'] + +# The suffix of source filenames. +source_suffix = '.txt' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'FullCalendar' +copyright = u'2009, Adam Shaw' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = open('../version.txt').read() +# The full version, including alpha/beta/rc tags. +release = version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# Options for HTML output +# ----------------------- + +# The style sheet to use for HTML and HTML Help pages. A file of that name +# must exist either in Sphinx' static/ path, or in one of the custom paths +# given in html_static_path. +html_style = 'default.css' + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, the reST sources are included in the HTML build as _sources/. +#html_copy_source = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +html_file_suffix = '.php' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'FullCalendardoc' + + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', 'FullCalendar.tex', ur'FullCalendar Documentation', + ur'Adam Shaw', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/docs/index.txt b/docs/index.txt new file mode 100644 index 0000000..797a4e9 --- /dev/null +++ b/docs/index.txt @@ -0,0 +1,228 @@ + +.. highlight:: javascript + +Usage +===== + +.. code-block:: javascript + + .fullCalendar({ + // initializes a calendar + // see options, data provider, and triggered events below + }) + + .fullCalendar('nextMonth') // move the calendar one month ahead + + .fullCalendar('currentMonth') // move to current month + + .fullCalendar('prevMonth') // move back one month + + .fullCalendar('gotoMonth', year, month) + // go to an arbitrary month. 'month' is zero-based + + +Options +======= + + **year**, **month**: integers + The month that will be displayed when the calendar first loads. + ``month`` is zero-based (January is 0, February is 1, etc). + + **draggable**: boolean, default:``false`` + Determines if all events on the calendar can be dragged & dropped. If + ``true``, requires `jQuery UI `_ core and draggables. + Can be overridden on a per-event basis with the ``draggable`` property of + each :ref:`CalEvent ` object. + + **fixedWeeks**: boolean, default:``true`` + If ``true``, the calendar will always be 6 weeks tall. If ``false``, the + calendar's height will vary per month. + + **abbrevDayHeadings**: boolean, default:``true`` + Whether to display "Sun" versus "Sunday" for days of the week. + + **title**: boolean, default:``true`` + Determines whether a title such as "January 2009" will be displayed at the + top of the calendar. + + **buttons**: boolean/hash, default:``true`` + Determines whether navigation buttons will be displayed at the top of the + calendar. A hash with keys 'today', 'prev', and 'next' will determine if + each individual button is displayed. Strings can be provided to change + each button's text. + + **showTime**: boolean/ ``"guess"``, default:``"guess"`` + Determines if times such as "8a" or "1p" will be displayed before each + event's title. ``"guess"`` displays times only for events with non-zero + start or end times. + + **eventDragOpacity**: float + The opacity of an event element while it is being dragged (0.0 - 1.0) + + **eventRevertDuration**: integer + Controls the duration (in milliseconds) of the animation of an event + snapping back into place. + + +.. _EventDataProvider: + +Event Data Provider +=================== + + **events**: array/string/function + An array of :ref:`CalEvent` can be used to hardcode events into the + calendar. + + Or, a URL can be provided. This URL should return JSON for an array of + :ref:`CalEvent`. GET parameters, determined by the ``startParam`` and + ``endParam`` options, will be inserted into the URL. These parameters + indicate the UNIX timestamp of the start of the first visible day + (inclusive) and the end of the last visible day (exclusive). + + Or, a function can be provided for custom fetching. The function is + queried every time event data is needed. The function is passed a ``start`` + Date object, an ``end`` Date object, and a function to be called when + fetching is complete. Here is the general form:: + + events: function(start, end, callback) { + + // do some asynchronous ajax + $.getJSON("/myscript", + { + start: start.getTime(), + end: end.getTime() + }, + function(result) { + + // format the result into an array of 'CalEvent' objects + // (not seen here) + + // then, pass the 'calevent' array to the callback + callback(calevents); + + }); + + } + + **startParam**: string, default:``"start"`` + This GET parameter will be inserted into the URL when ``event`` is a URL + string. Example of a resulting URL: ``/myjsonscript?start=1238562000`` + + **endParam**: string, default:``"end"`` + This GET parameter will be inserted into the URL when ``event`` is a URL + string. Example of a resulting URL: ``/myjsonscript?end=1240691444`` + + +.. _TriggeredEvents: + +Triggered Events +================ + + **monthDisplay**: function(year, month, monthTitle) + Triggered once when the calendar loads and every time the + calendar's month is changed. ``month`` is zero-based. ``monthTitle`` + contains the new title of the month (ex: "January 2009") + + **loading**: function(isLoading) + Triggered with a ``true`` argument when the calendar begins fetching + events via AJAX. Triggered with ``false`` when done. + + **dayClick**: function(dayDate) + Triggered when the user clicks on a day. ``dayDate`` is a Date object with + it's time set to 00:00:00. + + ``this`` is set to the TD element of the clicked day. + + **eventRender**: function(calEvent, element) + Triggered before an element is rendered for the given ``calEvent``. + ``element`` is the jQuery element that will be used by default. You can modify + this element or return a brand new element that will be used instead. + + **eventClick**, **eventMouseover**, **eventMouseout**: function(calEvent, domEvent) + Triggered on click/mouseover/mouseout actions for an event. + ``calEvent`` holds that event's information (date, title, etc). + ``domEvent`` holds the native DOM event (with information about click position, etc). + + ``this`` is set to the event's TABLE element + + For ``eventClick``, return ``false`` to prevent the browser from going to + calEvent's URL. + + **eventDragStart**, **eventDragStop**: function(calEvent, domEvent, ui) + Triggered before/after an event is dragged (but not necessarily moved to a new day). + ``calEvent`` holds that event's information (date, title, etc). + ``domEvent`` holds the native DOM event (with information about click position, etc). + ``ui`` holds the jQuery UI object. + + ``this`` is set to the event's TABLE element + + **eventDrop**: function(calEvent, dayDelta, domEvent, ui) + Triggered when dragging stops and the event has moved to a *different* day. + ``dayDelta`` holds the number of days the event was moved forward (a positive number) + or backwards (a negative number). + + ``dayDelta`` is elegant for dealing with multi-day and repeating events. + If updating a remote database, just add the ``dayDelta`` to the start + and end times of all events with the given ``calEvent.id`` + + +.. _CalEvent: + +CalEvent Objects +================ + +A ``CalEvent`` is a data structure that frequents FullCalendar's API. It is +used when a custom event-fetcher needs to report to the :ref:`EventDataProvider`. +It is also used in various :ref:`TriggeredEvents`. Here are the properties of a +``CalEvent``\: + + **id**: integer/string, + Uniquely identifies the given event. Absolutely essential for multi-day + and repeating events to be dragged and dropped correctly. + + **title**: string, + The text on an event's element + + **date**: date + Alias for ``start`` + + **start**: date + A javascript Date object indicating the date/time an event begins. + Events with ambiguous time-of-day should use 00:00:00. + + When reporting to the :ref:`EventDataProvider`, for convenience, + one can also use a string in IETF format (ex: "Wed, 18 Oct 2009 13:00:00 EST"), + a string in ISO8601 format (ex: "2009-11-05T13:15:30Z") or an integer + UNIX timestamp. + + **end**: date (optional) + A javascript Date object indicating the date/time an event ends + (exclusively). If an event has an ambiguous end time, ``end`` should be + set to midnight of the next day. This is implied if ``end`` is omitted. + (For convenience with the :ref:`EventDataProvider`). + + IETF and ISO8601 strings can be used for the :ref:`EventDataProvider`. + + **draggable**: boolean (optional) + Overrides the master ``draggable`` property for this single event. + + **showTime**: boolean/ ``"guess"`` (optional) + Overrides the master ``showTime`` property for this single event. + +When giving events to the :ref:`EventDataProvider`, one can include other +properties beyond the ones listed. This is useful if you want to earmark your +events with additional data to be retrieved later during a +:ref:`Triggered Event `. + + +Extras +====== + +FullCalendar provides some extra date utilities\: + + **$.parseISO8601(string, ignoreTimezone)** + Parses an ISO8601 string and returns a ``Date`` object + + **$.ISO8601String(date)** + Takes a ``Date`` object and returns an ISO8601 string + diff --git a/docs/templates/layout.html b/docs/templates/layout.html new file mode 100644 index 0000000..3524703 --- /dev/null +++ b/docs/templates/layout.html @@ -0,0 +1,8 @@ + + + + + +{% block body %}{% endblock %} + + diff --git a/examples/example.html b/examples/example.html new file mode 100644 index 0000000..6e2ba84 --- /dev/null +++ b/examples/example.html @@ -0,0 +1,73 @@ + + + + + + + + + + + + +
+ + diff --git a/examples/gcal.html b/examples/gcal.html new file mode 100644 index 0000000..951df3b --- /dev/null +++ b/examples/gcal.html @@ -0,0 +1,54 @@ + + + + + + + + + + + + +
+ + diff --git a/examples/json.html b/examples/json.html new file mode 100644 index 0000000..47edc3b --- /dev/null +++ b/examples/json.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + +
+

json_events.php needs to be running in the same directory.

+ + diff --git a/examples/json_events.php b/examples/json_events.php new file mode 100644 index 0000000..bf39ba6 --- /dev/null +++ b/examples/json_events.php @@ -0,0 +1,25 @@ + 1, + 'title' => "Event1", + 'start' => "$year-$month-10", + 'url' => "http://yahoo.com/" + ), + + array( + 'id' => 2, + 'title' => "Event2", + 'start' => "$year-$month-20", + 'end' => "$year-$month-22", + 'url' => "http://yahoo.com/" + ) + + )); + +?> diff --git a/full_calendar.css b/full_calendar.css new file mode 100644 index 0000000..c49f715 --- /dev/null +++ b/full_calendar.css @@ -0,0 +1,135 @@ + +/* top area w/ month title and buttons */ + +.full-calendar-header { + text-align: left; + } + +.full-calendar-buttons { + float: right; + } + +.full-calendar-buttons input { + vertical-align: middle; + font-size: 1.0em; + } + +.full-calendar-prev, +.full-calendar-next { + width: 40px; + margin-left: 5px; + } + + + +/* borders */ + +.full-calendar-month { + clear: both; + overflow: hidden; /* + ^ prevents draggable events from leaving. + reason for our long-winded border css. + borders now look consistent across doctypes. */ + border: 1px solid #ccc; /* border color & style */ + } + +.full-calendar-month table { + border-collapse: collapse; + border-spacing: 0; + } + +.full-calendar-month th.day-heading, +.full-calendar-month td.day { + padding: 0; + vertical-align: top; + border-style: solid; /* border style */ + border-color: #ccc; /* border color */ + border-width: 1px 0 0 1px; + } + +.full-calendar-month tr.day-headings th { + border-top: 0; + } + +.full-calendar-month th.sun, +.full-calendar-month td.sun { + border-left: 0; + } + + + +/* day styling */ + +.full-calendar-month td.today { + background: #FFFFCC; + } + +.full-calendar-month .day-number { + text-align: right; + padding-right: 2px; + } + +.full-calendar-month .other-month .day-number { + color: #bbb; + } + +.full-calendar-month .day-content { + padding: 2px 2px 0; /* distance between events and day edges */ + } + +.full-calendar-month td.day { + /* FullCalendar automatically chooses a height, but this can be overridden: */ + /* height: 100px !important; */ + } + + + +/* event styling */ + +.full-calendar-month .event { + margin-bottom: 2px; + font-size: .85em; + cursor: pointer; + text-align: left; + } + +.full-calendar-month .ui-draggable-dragging td { + cursor: move; + } + +.full-calendar-month .event td { + background: #C1D9EC; + padding: 0; + } + +.full-calendar-month .event td.ne, +.full-calendar-month .event td.nw, +.full-calendar-month .event td.se, +.full-calendar-month .event td.sw { + background: none; + width: 1px; /* <-- remove if you dont want "rounded" corners */ + height: 1px; /* <-- */ + } + +.full-calendar-month .nobg td { + background: none; + } + +.full-calendar-month .event td.c { + padding: 0 2px; + } + +.full-calendar-month .event-time { + font-weight: bold; + } + + + +/* the rectangle that covers a day when dragging an event */ + +.full-calendar-month .over-day { + background: #ADDBFF; + opacity: .2; + filter: alpha(opacity=20); + } + diff --git a/full_calendar.js b/full_calendar.js new file mode 100644 index 0000000..5e8c5ed --- /dev/null +++ b/full_calendar.js @@ -0,0 +1,726 @@ +/* + * Version: + * + */ + +(function($) { + + $.fn.fullCalendar = function(options) { + + if (typeof options == 'string') { + var args = Array.prototype.slice.call(arguments, 1); + this.each(function() { + $.data(this, 'fullCalendar')[options].apply(this, args); + }); + return this; + } + + options = options || {}; + + var showTime = typeof options.showTime == 'undefined' ? 'guess' : options.showTime; + var startParam = options.startParam || 'start'; + var endParam = options.endParam || 'end'; + var bo = options.buttons; + + this.each(function() { + + + + var date = options.year ? new Date(options.year, options.month || 0, 1) : new Date(); + var start, end, today, numWeeks; + var events = $.isArray(options.events) ? cleanEvents(options.events) : null; + var ignoreResizes = false; + + function updateMonth() { + clearEvents(); + render(); + } + + function today() { + date = new Date(); + updateMonth(); + } + + function prevMonth() { + addMonths(date, -1); + updateMonth(); + } + + function nextMonth() { + addMonths(date, 1); + updateMonth(); + } + + function gotoMonth(year, month) { + date = new Date(year, month, 1); + updateMonth(); + } + + $.data(this, 'fullCalendar', { + today:today, prevMonth:prevMonth, nextMonth:nextMonth, gotoMonth:gotoMonth + }); + + + + + + + var titleElement, todayButton, monthElement; + var header = $("
").appendTo(this); + + if (bo != false) { + var buttons = $("
").appendTo(header); + todayButton = + $("") + .click(today); + var prevButton = + $("") + .click(prevMonth); + var nextButton = + $("") + .click(nextMonth); + if (typeof bo == 'object') { + if (bo.today != false) { + if (typeof bo.today == 'string') todayButton.val(bo.today); + buttons.append(todayButton); + } + if (bo.prev != false) { + if (typeof bo.prev == 'string') prevButton.val(bo.prev); + buttons.append(prevButton); + } + if (bo.next != false) { + if (typeof bo.next == 'string') nextButton.val(bo.next); + buttons.append(nextButton); + } + }else{ + buttons + .append(todayButton) + .append(prevButton) + .append(nextButton); + } + } + + if (options.title !== false) + titleElement = $("

").appendTo(header); + + monthElement = $("
").appendTo(this); + + + + + + + var tbody, glass, monthTitle; + + function render() { + + ignoreResizes = true; + date.setDate(1); + clearTime(date); + var year = date.getFullYear(); + var month = date.getMonth(); + monthTitle = monthNames[month] + ' ' + year; + if (titleElement) titleElement.text(monthTitle); + + clearTime(date); + start = cloneDate(date); + addDays(start, -start.getDay()); + end = cloneDate(date); + addMonths(end, 1); + addDays(end, (7 - end.getDay()) % 7); + numWeeks = Math.round((end.getTime() - start.getTime()) / 604800000); + if (options.fixedWeeks != false) { + addDays(end, (6 - numWeeks) * 7); + numWeeks = 6; + } + + today = clearTime(new Date()); + if (todayButton) { + if (today.getFullYear() == year && today.getMonth() == month) { + todayButton.css('visibility', 'hidden'); + }else{ + todayButton.css('visibility', 'visible'); + } + } + + if (!tbody) { + + tbody = ""; + for (var i=0; i<7; i++) { + tbody += + "" + + (options.abbrevDayHeadings!=false ? dayAbbrevs[i] : dayNames[i]) + + ""; + } + var d = cloneDate(start); + for (var i=0; i"; + for (var j=0; j<7; j++) { + tbody += + "
" + d.getDate() + "
" + + "
"; + addDays(d, 1); + } + tbody += ""; + } + tbody += ""; + tbody = $(tbody) + .appendTo($("") + .appendTo(monthElement)); + + glass = $("
") + .appendTo(monthElement) + .click(function(ev, ui) { + if (options.dayClick) { + buildDayGrid(); + var td = dayTD(ev.pageX, ev.pageY); + if (td) return options.dayClick.call(td, dayDate(td)); + } + }); + + }else{ + + var diff = numWeeks - (tbody.find('tr').length - 1); + if (diff < 0) { + tbody.find('tr:gt(' + numWeeks + ')').remove(); + } + else if (diff > 0) { + var trs = ""; + for (var i=0; i"; + for (var j=0; j<7; j++) { + trs += + "
"; + } + trs += ""; + } + if (trs) tbody.append(trs); + } + + var d = cloneDate(start); + tbody.find('td').each(function() { + if (d.getMonth() == month) { + $(this).removeClass('other-month'); + }else{ + $(this).addClass('other-month'); + } + if (d.getTime() == today.getTime()) { + $(this).addClass('today'); + }else{ + $(this).removeClass('today'); + } + $(this.childNodes[0]).text(d.getDate()); + addDays(d, 1); + }); + + } + + resizeTable(); + + if (typeof options.events == 'string') { + if (options.loading) options.loading(true); + var jsonOptions = {}; + jsonOptions[startParam] = Math.round(start.getTime() / 1000); + jsonOptions[endParam] = Math.round(end.getTime() / 1000); + $.getJSON(options.events, jsonOptions, function(data) { + events = cleanEvents(data); + renderEvents(events); + if (options.loading) options.loading(false); + }); + } + else if ($.isFunction(options.events)) { + if (options.loading) options.loading(true); + options.events(start, end, + function(data) { + events = cleanEvents(data); + renderEvents(events); + if (options.loading) options.loading(false); + }); + } + else renderEvents(events); + + ignoreResizes = false; + + if (options.monthDisplay) + options.monthDisplay(date.getFullYear(), date.getMonth(), monthTitle); + + } + + + + + + + var eventMatrix = []; + + function renderEvents() { + eventMatrix = []; + var i = 0; + var ws = cloneDate(start); + var we = addDays(cloneDate(ws), 7); + while (ws.getTime() < end.getTime()) { + var segs = []; + $.each(events, function(j, event) { + if (event.end.getTime() > ws.getTime() && event.start.getTime() < we.getTime()) { + var ss, se, isStart, isEnd; + if (event.start.getTime() < ws.getTime()) { + ss = cloneDate(ws); + isStart = false; + }else{ + ss = cloneDate(event.start); + isStart = true; + } + if (event.end.getTime() > we.getTime()) { + se = cloneDate(we); + isEnd = false; + }else{ + se = cloneDate(event.end); + isEnd = true; + } + ss = clearTime(ss); + se = clearTime((se.getHours()==0 && se.getMinutes()==0) ? se : addDays(se, 1)); + segs.push({ + event: event, start: ss, end: se, + isStart: isStart, isEnd: isEnd, msLength: se - ss + }); + } + }); + segs.sort(function(a, b) { return b.msLength - a.msLength; }); + var levels = []; + $.each(segs, function(j, seg) { + var l = 0; // level index + while (true) { + var collide = false; + if (levels[l]) { + for (var k=0; k levels[l][k].start.getTime() && + seg.start.getTime() < levels[l][k].end.getTime()) { + collide = true; + break; + } + } + } + if (collide) { + l++; + continue; + }else{ + break; + } + } + if (levels[l]) levels[l].push(seg); + else levels[l] = [seg]; + }); + eventMatrix[i] = levels; + addDays(ws, 7); + addDays(we, 7); + i++; + } + _renderEvents(); + } + + + + + var eventElements = []; // [[event, element], ...] + + function _renderEvents() { + for (var i=0; i") + .append("" + + (seg.isStart ? "") + .append("" + + (seg.isStart ? "") + .append("" + + (seg.isStart ? ""); + buildEventText(element.find('td.c'), event, + typeof event.showTime == 'undefined' ? showTime : event.showTime); + if (options.eventRender) { + var res = options.eventRender(event, element); + if (typeof res != 'undefined') { + if (res === false) continue; + if (res !== true) element = $(res); + } + } + element + .css({ + position: 'absolute', + top: top, + left: left1, + width: left2 - left1, + 'z-index': 3 + }) + .appendTo(monthElement); + initEventElement(event, element); + var h = element.outerHeight({margin:true}); + if (h > maxh) maxh = h; + } + height += maxh; + top += maxh; + } + innerDiv.height(height); + } + } + + + + + function initEventElement(event, element) { + element.click(function(ev) { + if (!element.hasClass('ui-draggable-dragging')) { + if (options.eventClick) { + var res = options.eventClick.call(this, event, ev); + if (res === false) return false; + } + if (event.url) window.location.href = event.url; + } + }); + if (options.eventMouseover) + element.mouseover(function(ev) { + options.eventMouseover.call(this, event, ev); + }); + if (options.eventMouseout) + element.mouseout(function(ev) { + options.eventMouseout.call(this, event, ev); + }); + if (typeof event.draggable != 'undefined') { + if (event.draggable) + draggableEvent(event, element); + } + else if (options.draggable) { + draggableEvent(event, element); + } + eventElements.push([event, element]); + } + + + + + var dragStartTD, dragTD; + var dayOverlay; + + function draggableEvent(event, element) { + element.draggable({ + zIndex: 3, + delay: 50, + opacity: options.eventDragOpacity, + revertDuration: options.eventRevertDuration, + start: function(ev, ui) { + // hide other elements with same event + for (var i=0; i") + .appendTo(monthElement); + buildDayGrid(); + dragTD = dragStartTD = null; + eventDrag(this, ev, ui); + if (options.eventDragStart) + options.eventDragStart.call(this, event, ev, ui); + }, + drag: function(ev, ui) { + eventDrag(this, ev, ui); + }, + stop: function(ev, ui) { + if (!dragTD || dragTD == dragStartTD) { + // show all events + for (var i=0; i dayY0 + dayY[r+1]) r++; + while (c < cmax && x > dayX0 + dayX[c+1]) c++; + if (r < 0 || r >= rmax || c < 0 || c >= cmax) + return currTD = null; + else if (!currTD || r != currR || c != currC) { + currR = r; + currC = c; + currTD = tbody.find('tr:eq('+(r+1)+') td:eq('+c+')').get(0); + currTDX = dayX[c]; + currTDY = dayY[r]; + currTDW = dayX[c+1] - currTDX; + currTDH = dayY[r+1] - currTDY; + return currTD; + } + return currTD; + } + + function dayDate(node) { + var i, tds = tbody.get(0).getElementsByTagName('td'); + for (i=0; i") + .text((h%12 || 12) + (h<12 ? 'a' : 'p') + ' ')); + } + } + element.append($("") + .text(event.title)); + } + + + function cleanEvents(events) { + $.each(events, function(i, event) { + if (event.date) event.start = event.date; + event.start = cleanDate(event.start); + event.end = cleanDate(event.end); + if (!event.end) event.end = addDays(cloneDate(event.start), 1); + }); + return events; + } + + + + // date utils + + var monthNames = ['January','February','March','April','May','June','July','August','September','October','November','December']; + var dayNames = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; + var dayAbbrevs = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; + + function addMonths(d, n, keepTime) { + d.setMonth(d.getMonth() + n); + if (keepTime) return d; + return clearTime(d); + } + + function addDays(d, n, keepTime) { + d.setDate(d.getDate() + n); + if (keepTime) return d; + return clearTime(d); + } + + function clearTime(d) { + d.setHours(0); + d.setMinutes(0); + d.setSeconds(0); + d.setMilliseconds(0); + return d; + } + + function cloneDate(d) { + return new Date(+d); + } + + function cleanDate(d) { + if (typeof d == 'string') + return $.parseISO8601(d, true) || Date.parse(d) || new Date(parseInt(d)); + if (typeof d == 'number') + return new Date(d * 1000); + return d; + } + + $.parseISO8601 = function(s, ignoreTimezone) { + // derived from http://delete.me.uk/2005/03/iso8601.html + var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + + "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" + + "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"; + var d = s.match(new RegExp(regexp)); + if (!d) return null; + var offset = 0; + var date = new Date(d[1], 0, 1); + if (d[3]) { date.setMonth(d[3] - 1); } + if (d[5]) { date.setDate(d[5]); } + if (d[7]) { date.setHours(d[7]); } + if (d[8]) { date.setMinutes(d[8]); } + if (d[10]) { date.setSeconds(d[10]); } + if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); } + if (!ignoreTimezone) { + if (d[14]) { + offset = (Number(d[16]) * 60) + Number(d[17]); + offset *= ((d[15] == '-') ? 1 : -1); + } + offset -= date.getTimezoneOffset(); + } + return new Date(Number(date) + (offset * 60 * 1000)); + }; + + $.ISO8601String = function(date) { + // derived from http://delete.me.uk/2005/03/iso8601.html + var zeropad = function (num) { return ((num < 10) ? '0' : '') + num; } + return date.getUTCFullYear() + + "-" + zeropad(date.getUTCMonth() + 1) + + "-" + zeropad(date.getUTCDate()) + + "T" + zeropad(date.getUTCHours()) + + ":" + zeropad(date.getUTCMinutes()) + + ":" + zeropad(date.getUTCSeconds()) + + "Z"; + }; + + + + // general utils + + if (!$.isArray) { // not in jQuery pre 1.3 + $.isArray = function(obj) { + return toString.call(obj) === "[object Array]"; + }; + } + +})(jQuery); diff --git a/gcal.js b/gcal.js new file mode 100644 index 0000000..56c1161 --- /dev/null +++ b/gcal.js @@ -0,0 +1,55 @@ +(function($) { + + $.fn.gcalFullCalendar = function(options) { + + var feedURL; + if (options && typeof options.events == 'string') { + feedURL = options.events; + } + else return this; + + feedURL = feedURL.replace(/\/basic$/, '/full'); + + $.extend(options, { + + events: function(start, end, callback) { + $.getJSON(feedURL + "?alt=json-in-script&callback=?", + { + 'start-min': $.ISO8601String(start), + 'start-max': $.ISO8601String(end), + 'singleevents': true + }, + function(data) { + var events = []; + if (data.feed.entry) + $.each(data.feed.entry, function(i, entry) { + var url; + $.each(entry['link'], function(j, link) { + if (link.type == 'text/html') url = link.href; + }); + events.push({ + id: entry['gCal$uid']['value'], + url: url, + title: entry['title']['$t'], + start: $.parseISO8601(entry['gd$when'][0]['startTime'], true), + end: $.parseISO8601(entry['gd$when'][0]['endTime'], true), + location: entry['gd$where'][0]['valueString'], + description: entry['content']['$t'], + allDay: entry['gd$when'][0]['startTime'].indexOf('T') == -1, + draggable: false + }); + }); + callback(events); + }); + }, + + eventRender: function(event, element) { + if (!event.allDay) element.addClass('nobg'); + } + + }); + + return this.fullCalendar(options); + }; + +})(jQuery); diff --git a/jquery/jquery.js b/jquery/jquery.js new file mode 100644 index 0000000..9263574 --- /dev/null +++ b/jquery/jquery.js @@ -0,0 +1,4376 @@ +/*! + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){ + +var + // Will speed up references to window, and allows munging its name. + window = this, + // Will speed up references to undefined, and allows munging its name. + undefined, + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + // Map over the $ in case of overwrite + _$ = window.$, + + jQuery = window.jQuery = window.$ = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this[0] = selector; + this.length = 1; + this.context = selector; + return this; + } + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + var match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) + selector = jQuery.clean( [ match[1] ], context ); + + // HANDLE: $("#id") + else { + var elem = document.getElementById( match[3] ); + + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem && elem.id != match[3] ) + return jQuery().find( selector ); + + // Otherwise, we inject the element directly into the jQuery object + var ret = jQuery( elem || [] ); + ret.context = document; + ret.selector = selector; + return ret; + } + + // HANDLE: $(expr, [context]) + // (which is just equivalent to: $(content).find(expr) + } else + return jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) + return jQuery( document ).ready( selector ); + + // Make sure that old selector state is passed along + if ( selector.selector && selector.context ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return this.setArray(jQuery.isArray( selector ) ? + selector : + jQuery.makeArray(selector)); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.3.2", + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num === undefined ? + + // Return a 'clean' array + Array.prototype.slice.call( this ) : + + // Return just the object + this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery( elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) + ret.selector = this.selector + (this.selector ? " " : "") + selector; + else if ( name ) + ret.selector = this.selector + "." + name + "(" + selector + ")"; + + // Return the newly-formed element set + return ret; + }, + + // Force the current matched set of elements to become + // the specified array of elements (destroying the stack in the process) + // You should use pushStack() in order to do this, but maintain the stack + setArray: function( elems ) { + // Resetting the length to 0, then using the native Array push + // is a super-fast way to populate an object with array-like properties + this.length = 0; + Array.prototype.push.apply( this, elems ); + + return this; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem && elem.jquery ? elem[0] : elem + , this ); + }, + + attr: function( name, value, type ) { + var options = name; + + // Look for the case where we're accessing a style value + if ( typeof name === "string" ) + if ( value === undefined ) + return this[0] && jQuery[ type || "attr" ]( this[0], name ); + + else { + options = {}; + options[ name ] = value; + } + + // Check to see if we're setting style values + return this.each(function(i){ + // Set all the styles + for ( name in options ) + jQuery.attr( + type ? + this.style : + this, + name, jQuery.prop( this, options[ name ], type, i, name ) + ); + }); + }, + + css: function( key, value ) { + // ignore negative width and height values + if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) + value = undefined; + return this.attr( key, value, "curCSS" ); + }, + + text: function( text ) { + if ( typeof text !== "object" && text != null ) + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + + var ret = ""; + + jQuery.each( text || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + ret += this.nodeType != 1 ? + this.nodeValue : + jQuery.fn.text( [ this ] ); + }); + }); + + return ret; + }, + + wrapAll: function( html ) { + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).clone(); + + if ( this[0].parentNode ) + wrap.insertBefore( this[0] ); + + wrap.map(function(){ + var elem = this; + + while ( elem.firstChild ) + elem = elem.firstChild; + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function( html ) { + return this.each(function(){ + jQuery( this ).contents().wrapAll( html ); + }); + }, + + wrap: function( html ) { + return this.each(function(){ + jQuery( this ).wrapAll( html ); + }); + }, + + append: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.appendChild( elem ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.insertBefore( elem, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery( [] ); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: [].push, + sort: [].sort, + splice: [].splice, + + find: function( selector ) { + if ( this.length === 1 ) { + var ret = this.pushStack( [], "find", selector ); + ret.length = 0; + jQuery.find( selector, this[0], ret ); + return ret; + } else { + return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){ + return jQuery.find( selector, elem ); + })), "find", selector ); + } + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function(){ + if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var html = this.outerHTML; + if ( !html ) { + var div = this.ownerDocument.createElement("div"); + div.appendChild( this.cloneNode(true) ); + html = div.innerHTML; + } + + return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0]; + } else + return this.cloneNode(true); + }); + + // Copy the events from the original to the clone + if ( events === true ) { + var orig = this.find("*").andSelf(), i = 0; + + ret.find("*").andSelf().each(function(){ + if ( this.nodeName !== orig[i].nodeName ) + return; + + var events = jQuery.data( orig[i], "events" ); + + for ( var type in events ) { + for ( var handler in events[ type ] ) { + jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + } + } + + i++; + }); + } + + // Return the cloned set + return ret; + }, + + filter: function( selector ) { + return this.pushStack( + jQuery.isFunction( selector ) && + jQuery.grep(this, function(elem, i){ + return selector.call( elem, i ); + }) || + + jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ + return elem.nodeType === 1; + }) ), "filter", selector ); + }, + + closest: function( selector ) { + var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null, + closer = 0; + + return this.map(function(){ + var cur = this; + while ( cur && cur.ownerDocument ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) { + jQuery.data(cur, "closest", closer); + return cur; + } + cur = cur.parentNode; + closer++; + } + }); + }, + + not: function( selector ) { + if ( typeof selector === "string" ) + // test special case where just one selector is passed in + if ( isSimple.test( selector ) ) + return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); + else + selector = jQuery.multiFilter( selector, this ); + + var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; + return this.filter(function() { + return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; + }); + }, + + add: function( selector ) { + return this.pushStack( jQuery.unique( jQuery.merge( + this.get(), + typeof selector === "string" ? + jQuery( selector ) : + jQuery.makeArray( selector ) + ))); + }, + + is: function( selector ) { + return !!selector && jQuery.multiFilter( selector, this ).length > 0; + }, + + hasClass: function( selector ) { + return !!selector && this.is( "." + selector ); + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if( jQuery.nodeName( elem, 'option' ) ) + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) + return value; + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Everything else, we just grab the value + return (elem.value || "").replace(/\r/g, ""); + + } + + return undefined; + } + + if ( typeof value === "number" ) + value += ''; + + return this.each(function(){ + if ( this.nodeType != 1 ) + return; + + if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) + this.checked = (jQuery.inArray(this.value, value) >= 0 || + jQuery.inArray(this.name, value) >= 0); + + else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(value); + + jQuery( "option", this ).each(function(){ + this.selected = (jQuery.inArray( this.value, values ) >= 0 || + jQuery.inArray( this.text, values ) >= 0); + }); + + if ( !values.length ) + this.selectedIndex = -1; + + } else + this.value = value; + }); + }, + + html: function( value ) { + return value === undefined ? + (this[0] ? + this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") : + null) : + this.empty().append( value ); + }, + + replaceWith: function( value ) { + return this.after( value ).remove(); + }, + + eq: function( i ) { + return this.slice( i, +i + 1 ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ), + "slice", Array.prototype.slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function(elem, i){ + return callback.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + domManip: function( args, table, callback ) { + if ( this[0] ) { + var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), + scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), + first = fragment.firstChild; + + if ( first ) + for ( var i = 0, l = this.length; i < l; i++ ) + callback.call( root(this[i], first), this.length > 1 || i > 0 ? + fragment.cloneNode(true) : fragment ); + + if ( scripts ) + jQuery.each( scripts, evalScript ); + } + + return this; + + function root( elem, cur ) { + return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; + } + } +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +function evalScript( i, elem ) { + if ( elem.src ) + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild( elem ); +} + +function now(){ + return +new Date; +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) + target = {}; + + // extend jQuery itself if only one argument is passed + if ( length == i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) + // Extend the base object + for ( var name in options ) { + var src = target[ name ], copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) + continue; + + // Recurse if we're merging object values + if ( deep && copy && typeof copy === "object" && !copy.nodeType ) + target[ name ] = jQuery.extend( deep, + // Never move original objects, clone them + src || ( copy.length != null ? [ ] : { } ) + , copy ); + + // Don't bring in undefined values + else if ( copy !== undefined ) + target[ name ] = copy; + + } + + // Return the modified object + return target; +}; + +// exclude the following css properties to add px +var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, + // cache defaultView + defaultView = document.defaultView || {}, + toString = Object.prototype.toString; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) + window.jQuery = _jQuery; + + return jQuery; + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + // check if an element is in a (or is an) XML document + isXMLDoc: function( elem ) { + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); + }, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && /\S/.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + if ( jQuery.support.scriptEval ) + script.appendChild( document.createTextNode( data ) ); + else + script.text = data; + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, length = object.length; + + if ( args ) { + if ( length === undefined ) { + for ( name in object ) + if ( callback.apply( object[ name ], args ) === false ) + break; + } else + for ( ; i < length; ) + if ( callback.apply( object[ i++ ], args ) === false ) + break; + + // A special, fast, case for the most common use of each + } else { + if ( length === undefined ) { + for ( name in object ) + if ( callback.call( object[ name ], name, object[ name ] ) === false ) + break; + } else + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + } + + return object; + }, + + prop: function( elem, value, type, i, name ) { + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, i ); + + // Handle passing in a number to a CSS property + return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, classNames ) { + jQuery.each((classNames || "").split(/\s+/), function(i, className){ + if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) + elem.className += (elem.className ? " " : "") + className; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, classNames ) { + if (elem.nodeType == 1) + elem.className = classNames !== undefined ? + jQuery.grep(elem.className.split(/\s+/), function(className){ + return !jQuery.className.has( classNames, className ); + }).join(" ") : + ""; + }, + + // internal only, use hasClass("class") + has: function( elem, className ) { + return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( var name in options ) + elem.style[ name ] = old[ name ]; + }, + + css: function( elem, name, force, extra ) { + if ( name == "width" || name == "height" ) { + var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; + + function getWH() { + val = name == "width" ? elem.offsetWidth : elem.offsetHeight; + + if ( extra === "border" ) + return; + + jQuery.each( which, function() { + if ( !extra ) + val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; + if ( extra === "margin" ) + val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0; + else + val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; + }); + } + + if ( elem.offsetWidth !== 0 ) + getWH(); + else + jQuery.swap( elem, props, getWH ); + + return Math.max(0, Math.round(val)); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret, style = elem.style; + + // We need to handle opacity special in IE + if ( name == "opacity" && !jQuery.support.opacity ) { + ret = jQuery.attr( style, "opacity" ); + + return ret == "" ? + "1" : + ret; + } + + // Make sure we're using the right name for getting the float value + if ( name.match( /float/i ) ) + name = styleFloat; + + if ( !force && style && style[ name ] ) + ret = style[ name ]; + + else if ( defaultView.getComputedStyle ) { + + // Only "float" is needed here + if ( name.match( /float/i ) ) + name = "float"; + + name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + + var computedStyle = defaultView.getComputedStyle( elem, null ); + + if ( computedStyle ) + ret = computedStyle.getPropertyValue( name ); + + // We should always get a number back from opacity + if ( name == "opacity" && ret == "" ) + ret = "1"; + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var left = style.left, rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = ret || 0; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + } + + return ret; + }, + + clean: function( elems, context, fragment ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { + var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); + if ( match ) + return [ context.createElement( match[1] ) ]; + } + + var ret = [], scripts = [], div = context.createElement("div"); + + jQuery.each(elems, function(i, elem){ + if ( typeof elem === "number" ) + elem += ''; + + if ( !elem ) + return; + + // Convert html string into DOM nodes + if ( typeof elem === "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? + all : + front + ">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase(); + + var wrap = + // option or optgroup + !tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "
" + + "
" + + "
" + + "
" : '') + + "" + + (seg.isEnd ? "" : '') + "
" : '') + + "" + + (seg.isEnd ? "" : '') + "
" : '') + + "" + + (seg.isEnd ? "" : '') + "
", "
" ] || + + !tags.indexOf("", "" ] || + + // matched above + (!tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + // IE can't serialize and