diff options
author | James Lal <james@lightsofapollo.com> | 2012-10-02 23:27:19 -0700 |
---|---|---|
committer | James Lal <james@lightsofapollo.com> | 2012-10-02 23:27:19 -0700 |
commit | 13966ebe577c18436332923fb367a3f6ab7b288e (patch) | |
tree | 2e92f1083cfc7ce43c95ff207d7ea79944a98081 /caldav.js | |
parent | da93e0810d194a2b484f89e0bb4b121e41ad5c0c (diff) | |
download | jsCalDAV-13966ebe577c18436332923fb367a3f6ab7b288e.tar.gz |
replace templates with much improved query/filter interface. Update CalendarQuery to reflect these changes
Diffstat (limited to 'caldav.js')
-rw-r--r-- | caldav.js | 504 |
1 files changed, 335 insertions, 169 deletions
@@ -1662,6 +1662,324 @@ function write (chunk) { @namespace */ (function(module, ns) { + var Template = ns.require('template'); + + /** + * Builds a node of a calendar-data or filter xml element. + * + * @param {QueryBuilder} builder instance. + * @param {String} name component/prop name (like RRULE/VEVENT). + * @param {Boolean} isProp is this node a property tag? + */ + function Node(builder, name, isProp) { + this.name = name; + this.builder = builder; + this.isProp = !!isProp; + + this.comps = Object.create(null); + this.props = Object.create(null); + } + + Node.prototype = { + + /** + * Hook for adding custom node content. + * (for unsupported or custom filters) + * + * Usually you never want to use this. + * + * @type {Null|Array} + */ + content: null, + + /** + * Appends custom string content into node. + * + * @param {String} string content. + */ + appendString: function(string) { + if (!this.content) { + this.content = []; + } + + if (typeof(string) !== 'string') { + string = string.toString(); + } + + this.content.push(string); + }, + + _timeRange: null, + + /** + * Adds a time range element to the node. + * + * Example: + * + * var node; + * + * // key/values not validated or converted + * // but directly piped into the time-range element. + * node.setTimeRange({ + * start: '20060104T000000Z', + * end: '20060105T000000Z' + * }); + * + * // when null removes element + * node.setTimeRange(null); + * + * @param {Object|Null} range time range or null to remove. + */ + setTimeRange: function(range) { + this._timeRange = range; + }, + + /** + * Removes a property from the output. + * @param {String} name prop. + */ + removeProp: function(name) { + delete this.props[name]; + }, + + /** + * Removes a component from the output. + * + * @param {String} name comp. + */ + removeComp: function(name) { + delete this.comps[name]; + }, + + _addNodes: function(type, nodes) { + // return value when is array + var result = this; + + if (!Array.isArray(nodes)) { + // clear out the return value as we will + // now use the first node. + result = null; + } + + nodes = (Array.isArray(nodes)) ? nodes : [nodes]; + + var idx = 0; + var len = nodes.length; + var name; + var node; + + for (; idx < len; idx++) { + name = nodes[idx]; + node = new Node(this.builder, name, type === 'props'); + this[type][name] = node; + } + + // when we where not given an array of nodes + // assume we want one specific one so set that + // as the return value. + if (!result) + result = node; + + return result; + }, + + /** + * Adds one or more props. + * If property already exists will not add + * duplicates but return the existing property. + * + * @param {String|Array[String]} prop one or more properties to add. + * @return {Node|Self} returns a node or self when given an array. + */ + prop: function(prop) { + return this._addNodes('props', prop); + }, + + /** + * Adds one or more comp. + * If comp already exists will not add + * duplicates but return the existing comp. + * + * @param {String|Array[String]} comp one or more components to add. + * @return {Node|Self} returns a node or self when given an array. + */ + comp: function(comp) { + return this._addNodes('comps', comp); + }, + + xmlAttributes: function() { + return { name: this.name }; + }, + + /** + * Transform tree into a string. + * + * NOTE: order is not preserved at all here. + * It is highly unlikely that order is a + * factor for calendar-data or filter + * but this is fair warning for other uses. + */ + toString: function() { + var content = ''; + var key; + var template = this.builder.template; + + if (this._timeRange) { + content += template.tag( + ['caldav', 'time-range'], + this._timeRange + ); + } + + // render out children + for (key in this.props) { + content += this.props[key].toString(); + } + + for (key in this.comps) { + content += this.comps[key].toString(); + } + + if (this.content) { + content += this.content.join(''); + } + + // determine the tag name + var tag; + if (this.isProp) { + tag = this.builder.propTag; + } else { + tag = this.builder.compTag; + } + + // build the xml element and return it. + return template.tag( + tag, + this.xmlAttributes(), + content + ); + } + }; + + /** + * Query builder can be used to build xml document fragments + * for calendar-data & calendar-filter. + * (and any other xml document with a similar structure) + * + * Options: + * - template: (Caldav.Template instance) + * - tag: container tag (like 'calendar-data') + * - attributes: attributes for root + * - compTag: name of comp[onent] tag name (like 'comp') + * - propTag: name of property tag (like 'prop') + * + * @param {Object} options query builder options. + */ + function QueryBuilder(options) { + if (!options) + options = {}; + + if (!(options.template instanceof Template)) { + throw new TypeError( + '.template must be an instance' + + ' of Caldav.Template given "' + options.template + '"' + ); + } + + for (var key in options) { + if (options.hasOwnProperty(key)) { + this[key] = options[key]; + } + } + } + + QueryBuilder.prototype = { + tag: ['caldav', 'calendar-data'], + + compTag: ['caldav', 'comp'], + + propTag: ['caldav', 'prop'], + + attributes: null, + + _limitRecurrenceSet: null, + + /** + * Adds the recurrence set limit child to the query. + * Directly maps to the caldav 'limit-recurrence-set' element. + * + * Examples: + * + * var builder; + * + * // no validation or formatting is done. + * builder.setRecurrenceSetLimit({ + * start: '20060103T000000Z', + * end: '20060103T000000Z' + * }); + * + * // use null to clear value + * builder.setRecurrenceSetLimit(null); + * + * @param {Object|Null} limit see above. + */ + setRecurrenceSetLimit: function(limit) { + this._limitRecurrenceSet = limit; + }, + + /** + * @param {String} name component name (like VCALENDAR). + * @return {QueryBuilder.Node} node instance. + */ + setComp: function(name) { + return this._compRoot = new Node(this, name); + }, + + /** + * Returns the root node of the document fragment. + */ + getComp: function() { + return this._compRoot; + }, + + toString: function() { + var content = ''; + var comp = this.getComp(); + + if (this._limitRecurrenceSet) { + content += this.template.tag( + ['caldav', 'limit-recurrence-set'], + this._limitRecurrenceSet + ); + } + + if (comp) { + content += comp.toString(); + } + + return this.template.tag( + this.tag, + this.attributes, + content + ); + } + + }; + + QueryBuilder.Node = Node; + module.exports = QueryBuilder; + +}.apply( + this, + (this.Caldav) ? + [Caldav('query_builder'), Caldav] : + [module, require('./caldav')] +)); + +/** +@namespace +*/ +(function(module, ns) { var Native; if (typeof(window) === 'undefined') { @@ -1900,153 +2218,6 @@ function write (chunk) { )); (function(module, ns) { - function CalendarData() { - this._hasItems = false; - this.struct = {}; - } - - CalendarData.prototype = { - - rootName: 'calendar-data', - compName: 'comp', - propName: 'prop', - - /** - * Appends a list of fields - * to a given iCalendar field set. - * - * @param {String} type iCal fieldset (VTODO, VEVENT,...). - */ - select: function(type, list) { - if (typeof(list) === 'undefined') { - list = true; - } - - var struct = this.struct; - this._hasItems = true; - - if (!(type in struct)) { - struct[type] = []; - } - - if (list instanceof Array) { - struct[type] = struct[type].concat(list); - } else { - struct[type] = list; - } - - return this; - }, - - /** - * Accepts an object full of arrays - * recuse when encountering another object. - */ - _renderFieldset: function(template, element) { - var tag; - var value; - var i; - var output = ''; - var elementOutput = ''; - - for (tag in element) { - value = element[tag]; - for (i = 0; i < value.length; i++) { - if (typeof(value[i]) === 'object') { - elementOutput += this._renderFieldset( - template, - value[i] - ); - } else { - elementOutput += template.tag( - ['caldav', this.propName], - { name: value[i] } - ); - } - } - output += template.tag( - ['caldav', this.compName], - { name: tag }, - elementOutput || null - ); - elementOutput = ''; - } - - return output; - }, - - _defaultRender: function(template) { - return template.tag(['caldav', this.rootName]); - }, - - /** - * Renders CalendarData with a template. - * - * @param {WebCals.Template} template calendar to render. - * @return {String} <calendardata /> xml output. - */ - render: function(template) { - if (!this._hasItems) { - return this._defaultRender(template); - } - - var struct = this.struct; - var output = template.tag( - ['caldav', this.rootName], - template.tag( - ['caldav', this.compName], - { name: 'VCALENDAR' }, - this._renderFieldset(template, struct) - ) - ); - - return output; - } - }; - - - module.exports = CalendarData; - -}.apply( - this, - (this.Caldav) ? - [Caldav('templates/calendar_data'), Caldav] : - [module, require('../caldav')] -)); -(function(module, ns) { - - var CalendarData = ns.require('templates/calendar_data'); - - function CalendarFilter() { - CalendarData.call(this); - } - - CalendarFilter.prototype = { - - __proto__: CalendarData.prototype, - - add: CalendarData.prototype.select, - - _defaultRender: function(template) { - var inner = this._renderFieldset(template, { VCALENDAR: [{ VEVENT: true }] }); - return template.tag(['caldav', this.rootName], inner); - }, - - compName: 'comp-filter', - rootName: 'filter' - }; - - module.exports = CalendarFilter; - -}.apply( - this, - (this.Caldav) ? - [Caldav('templates/calendar_filter'), Caldav] : - [module, require('../caldav')] -)); - -(function(module, ns) { - var Base = { name: 'base', @@ -2721,8 +2892,7 @@ function write (chunk) { (function(module, ns) { var Propfind = ns.require('request/propfind'); - var CalendarData = ns.require('templates/calendar_data'); - var CalendarFilter = ns.require('templates/calendar_filter'); + var Builder = ns.require('query_builder'); /** * Creates a calendar query request. @@ -2737,10 +2907,19 @@ function write (chunk) { this.xhr.headers['Depth'] = this.depth || 1; this.xhr.method = 'REPORT'; - this.fields = new CalendarData(); - this.filters = new CalendarFilter(); this.template.rootTag = ['caldav', 'calendar-query']; + + this.data = new Builder({ + template: this.template + }); + + this.filter = new Builder({ + template: this.template, + tag: ['caldav', 'filter'], + propTag: ['caldav', 'prop-filter'], + compTag: ['caldav', 'comp-filter'] + }); } CalendarQuery.prototype = { @@ -2752,14 +2931,14 @@ function write (chunk) { props = this._props.join(''); - if (this.fields) { - props += this.fields.render(this.template); + if (this.data) { + props += this.data.toString(); } content = this.template.tag('prop', props); - if (this.filters) { - content += this.filters.render(this.template); + if (this.filter) { + content += this.filter.toString(); } return this.template.render(content); @@ -3013,19 +3192,6 @@ function write (chunk) { [module, require('../caldav')] )); -(function(module, ns) { - - module.exports = { - CalendarData: ns.require('templates/calendar_data'), - CalendarFilter: ns.require('templates/calendar_filter') - }; - -}.apply( - this, - (this.Caldav) ? - [Caldav('templates'), Caldav] : - [module, require('../caldav')] -)); /** @namespace */ @@ -3206,9 +3372,9 @@ function write (chunk) { exports.Responder = ns.require('responder'); exports.Sax = ns.require('sax'); exports.Template = ns.require('template'); + exports.QueryBuilder = ns.require('query_builder'); exports.Xhr = ns.require('xhr'); exports.Request = ns.require('request'); - exports.Templates = ns.require('templates'); exports.Connection = ns.require('connection'); exports.Resources = ns.require('resources'); |