1
0
Fork 0
forked from 0x2620/oxjs

update Ox.URL

This commit is contained in:
rlx 2011-09-22 03:20:27 +00:00
commit 2cb6d89a0a
3 changed files with 315 additions and 106 deletions

View file

@ -4,23 +4,46 @@
Ox.URL <f> URL controller
(options) -> <o> URL controller
options <o> Options object
findKeys <[o]> Find keys {id: "", type: ""}
type can be "string" or "number"
getItemId <f> Tests if a string matches an item
findKeys <[o]> Find keys
id <s> Find key id
type <s> Value type ("string" or "number")
getItem <f> Tests if a string matches an item
(string, callback) -> <u> undefined
string <s> The string to be tested
callback <f> callback function
id <s> Matching item id, or empty
getSpanId <f> Tests if a string matches a span
(string, callback) -> <u> undefined
getSpan <f> Tests if a string matches a span
(item, view, string, callback) -> <u> undefined
item <s> The item id, or empty
view <s> The view, or empty
string <s> The string to be tested
callback <f> callback function
callback <f> Callback function
id <s> Matching span id, or empty
view <s> Matching view, or empty
pages <[s]> List of pages
sortKeys <[o]> Sort keys {id: "", operator: ""}
operator is the default operator ("+" or "-")
sortKeys <o> Sort keys for list and item views for all types
typeA <o> Sort keys for this type
list <o> Sort keys for list views for this type
viewA <[o]> Sort keys for this view
id <s> Sort key id
operator <s> Default sort operator ("+" or "-")
item <o> Sort keys for item views for this type
viewA <[o]> Sort keys for this view
id <s> Sort key id
operator <s> Default sort operator ("+" or "-")
spanType <o> Span types for list and item views for all types
typeA <o> Span types for this type
list <o> Span types for list views for this type
viewA <s> Span type for this view
Can be "date", "duration" or "location"
item <o> Span types for item views for this type
viewA <s> Span type for this view
Can be "date", "duration" or "location"
types <[s]> List of types
views <o> List of views {type: {'list': [...], 'item': [...]}}
views <o> List and item views for all types
typeA <o> Views for type "typeA"
list <[s]> List views for this type
item <[s]> Item views for this type
@*/
/*
@ -82,12 +105,12 @@ example.com/clip/+clip.duration/subtitles=foo
in ascending order. (In pan.do/ra's clip view, annotation=foo is always per
clip. There is no way to show all clips of all items where any clip matches
subtitles=foo, this doesn't seem to be needed.)
example.com/map/Paris/duration/title!=london
Ff "map" is a default type list view and "Paris" is a place id, this will
example.com/map/@paris/duration/title!=london
If "map" is a default type list view and "paris" is a place name, this will
zoom the map to Paris, show all places of items that match title!=london,
and when a place is selected sort matching clips by item duration in
default order.
example.com/calendar/1900,2000/clip.duration/event=hiroshima
example.com/calendar/1900,2000/clip:duration/event=hiroshima
If "calendar" is a default type list view, this will zoom the calendar to
the 20th century, show all events of all items that match event=hiroshima,
and when an event is selected sort matching clips by clip duration in
@ -95,6 +118,21 @@ example.com/calendar/1900,2000/clip.duration/event=hiroshima
always per item. There is no way to show all events of all clips that match
event=hiroshima, this doesn't seem to be needed.)
example.com/2001/2001 -> example.com/0062622/video/00:33:21
2001 matches an item title (word match), the second 2001 is a valid duration
example.com/2002/2002 -> example.com/calendar/2002/2002
2002 is a valid duration, but no list view supports durations. Then it is
read as a year, and we get calendar view with find *=2002
example.com/@paris/paris -> example.com/map/ABC/paris
paris matches a place name (case-insensitive), so we get map view, zoomed to
Paris, with find *=paris
example.com/@renaissance/renaissance -> example.com/calendar/ABC/renaissance
renaissaince matches an event name (case-insensitive), so we get calendar
view, zoomed to the Renaissance, with find *=renaissance
example.com/@foo/foo -> example.com/map/@foo/foo
foo doesn't match a place or event name, but getSpan() sets the map query to
foo and returns @foo, so we get map view, zoomed to Foo, with find *=foo
*/
Ox.URL = function(options) {
@ -102,18 +140,19 @@ Ox.URL = function(options) {
var self = {}, that = {};
self.options = Ox.extend({
// fixme: find keys are also per type/list|item/view
// since one can search for layer properties in some item views
findKeys: [],
getItemId: null,
getSpanId: null,
getItem: null,
getSpan: null,
pages: [],
sortKeys: [],
spanType: {},
sortKeys: {},
types: [],
views: {}
}, options);
self.sortKeyIds = self.options.sortKeys.map(function(sortKey) {
return sortKey.id;
});
Ox.print('Ox.URL options', self.options)
function constructCondition(condition) {
var key = condition.key == '*' ? '' : condition.key,
@ -133,6 +172,10 @@ Ox.URL = function(options) {
return [key, operator, value].join('');
}
function constructDate(date) {
return Ox.formatDate(date, '%Y-%m-%d', true);
}
function constructDuration(duration) {
return Ox.formatDuration(duration, 3).replace(/\.000$/, '');
}
@ -149,44 +192,60 @@ Ox.URL = function(options) {
}).join(find.operator);
}
function constructSort(sort) {
function constructLocation(location) {
return location.join(',');
}
function constructSort(sort, state) {
return sort.map(function(sort) {
return (
sort.operator == Ox.getObjectById(self.options.sortKeys, sort.key).operator
? '' : sort.operator
sort.operator == Ox.getObjectById(self.options.sortKeys[state.type][
!state.item ? 'list' : 'item'
][state.view], sort.key).operator ? '' : sort.operator
) + sort.key;
}).join(',')
}
function constructSpan(span) {
return span.map(function(point) {
return /^[0-9-\.:]+$/.test(str) ? constuctDuration(point) : point;
function constructSpan(span, state) {
var spanType = self.options.spanType[state.type][
!state.item ? 'list' : 'item'
][state.view];
return (Ox.isArray(span) ? span : [span]).map(function(point) {
return Ox.isNumber(point) ? (
spanType == 'date' ? constructDate(point)
: spanType == 'duration' ? constructDuration(point)
: constructLocation(point)
) : point;
}).join(',');
}
function constructURL(state) {
var parts = [];
if (self.options.types.indexOf(state.type) > 0) {
parts.push(state.type);
if (state.page) {
parts.push(state.page);
} else {
if (self.options.types.indexOf(state.type) > 0) {
parts.push(state.type);
}
if (state.item) {
parts.push(state.item);
}
if (self.options.views[state.type][
state.item ? 'item' : 'list'
].indexOf(state.view) > -1) {
parts.push(state.view);
}
if (state.span) {
parts.push(constructSpan(state.span, state));
}
if (state.sort && state.sort.length) {
parts.push(constructSort(state.sort, state));
}
if (state.find) {
parts.push(constructFind(state.find));
}
}
if (state.item) {
parts.push(item);
}
if (self.options.views[state.type][
state.item ? 'item' : 'list'
].indexOf(state.view) > 0) {
parts.push(state.view);
}
if (state.span) {
parts.push(constructSpan(state.span));
}
if (state.sort.length) {
parts.push(constructSort(state.sort));
}
if (state.find) {
parts.push(constructFind(state.find));
}
return parts.join('/');
return '/' + parts.join('/');
}
function decodeValue(str) {
@ -205,8 +264,29 @@ Ox.URL = function(options) {
return ret;
}
function isNumericalSpan(str) {
return str.split(',').every(function(str) {
return /^[0-9-\.:]+$/.test(str);
});
}
function getSpanType(str, types) {
Ox.print('getSpanType', str, types)
var canBeDate = types.indexOf('date') > -1,
canBeDuration = types.indexOf('duration') > -1,
canBeLocation = types.indexOf('location') > -1,
length = str.split(',').length;
return canBeDate && /\d-/.test(str) ? 'date'
: canBeDuration && /:/.test(str) ? 'duration'
: canBeLocation && length == 4 ? 'location'
// leaves us with [-]D[.D][,[-]D[.D]]
: canBeDuration ? 'duration'
: canBeDate && !/\./.test(str) ? 'date'
: canBeLocation && length == 2 ? 'location' : ':'
}
function parseCondition(str) {
var condition,
var condition = {},
operators = ['!==', '==', '!=', '=', '!<', '<', '!>', '>'],
split;
Ox.forEach(operators, function(operator) {
@ -220,7 +300,11 @@ Ox.URL = function(options) {
return false;
}
});
if (!condition.operator) {
if (
!condition.operator
|| Ox.getPositionById(self.options.findKeys, condition.key) == -1
) {
// missing operator or unknown key
condition = {key: '*', value: str, operator: '='};
}
if (['=', '!='].indexOf(condition.operator) > -1) {
@ -233,11 +317,17 @@ Ox.URL = function(options) {
}
}
if (condition.value.indexOf(':') > -1) {
condition.value = condition.value.split(':')
condition.value = condition.value.split(':').map(decodeValue);
} else {
condition.value = decodeValue(condition.value);
}
return condition;
}
function parseDate(str) {
return Ox.formatDate(Ox.parseDate(str, true), '%Y-%m-%d');
}
function parseDuration(str) {
var parts = str.split(':').reverse();
while (parts.length > 3) {
@ -284,18 +374,36 @@ Ox.URL = function(options) {
return find;
}
function parseSort(str) {
function parseLocation(str) {
return str.split(',').map(function(str, i) {
return Ox.limit(parseInt(str, 10), -90 * (i + 1), 90 * (i + 1));
});
}
function parseSort(str, state) {
return str.split(',').map(function(str) {
var hasOperator = /^[\+-]/.test(str);
return {
key: hasOperator ? str.substr(1) : str,
operator: hasOperator ? str[0] : Ox.getObjectById(self.options.sortKeys, str).operator
operator: hasOperator
? str[0]
: Ox.getObjectById(self.options.sortKeys[state.type][
!state.item ? 'list' : 'item'
][state.view], str).operator
};
});
}
function parseSpan(str, callback) {
return str.split(',').map(parseDuration);
function parseSpan(str, type) {
var split = str.split(',');
if (split.length == 4) {
split = [split[0] + ',' + split[1], split[2] + ',' + split[3]];
}
return split.map(
type == 'date' ? parseDate
: type == 'duration' ? parseDuration
: parseLocation
);
}
function parseURL(str, callback) {
@ -305,72 +413,135 @@ Ox.URL = function(options) {
state = {};
if (parts.length == 0) {
state.page = '';
callback(state);
} else if (self.options.pages.indexOf(parts[0]) > -1) {
state.page = parts[0];
callback(state);
} else {
if (self.options.types.indexOf(parts[0]) > -1) {
// type
state.type = parts[0];
parts.shift();
} else {
// set to default type
state.type = self.options.types[0];
}
if (parts.length) {
if (self.options.views.list.indexOf(parts[0]) > -1) {
if (self.options.views[state.type].list.indexOf(parts[0]) > -1) {
// list view
state.item = '';
state.view = parts[0];
parts.shift();
parseBeyondItem(false);
parseBeyondItem();
} else {
self.options.getItemId(parts[0], function(itemId) {
if (itemId) {
state.item = itemId;
// test for item id or name
self.options.getItem(parts[0], function(item) {
state.item = item;
if (item) {
parts.shift();
}
parseBeyondItem(!!itemId);
parseBeyondItem();
});
}
} else {
callback(state);
}
}
function parseBeyondItem(itemId) {
if (parts.length && itemId && self.options.views.item.indexOf(parts[0]) > -1) {
function parseBeyondItem() {
var span, spanType, spanTypes;
if (
parts.length && state.item
&& self.options.views[state.type].item.indexOf(parts[0]) > -1
) {
// item view
state.view = parts[0];
parts.shift();
}
Ox.print('pBI', state, parts.join('/'));
if (parts.length) {
if (parts.split(',').every(function(str) {
return /^[0-9-\.:]+$/.test(str);
})) {
state.span = parseSpan(parts[0]);
parts.shift()
parseBeyondSpan();
} else {
self.options.getSpanId(parts[0], function(spanId) {
if (spanId) {
state.span = spanId;
if (isNumericalSpan(parts[0])) {
// test for numerical span
spanTypes = self.options.spanType[state.type][
!state.item ? 'list' : 'item'
];
// if no view is given then parse the span anyway,
// but make sure the span type could match a view
spanType = state.view
? spanTypes[state.view]
: getSpanType(parts[0], Ox.unique(Ox.values(spanTypes)));
Ox.print('SPAN TYPE', spanType)
if (spanType) {
span = parseSpan(parts[0], spanType);
if (span) {
if (!state.view) {
// if no view is given then switch to the first
// view that supports a span of this type
Ox.forEach(self.options.views[state.type][
!state.item ? 'list' : 'item'
], function(view) {
if (spanTypes[view] == spanType) {
state.view = view;
state.span = span;
parts.shift();
return false;
}
});
} else {
state.span = span;
parts.shift();
}
}
}
}
if (!state.span && /^[A-Z@]/.test(parts[0])) {
// test for span id or name
self.options.getSpan(state.item, state.view, parts[0], function(span, view) {
if (span) {
if (!state.view) {
// set list or item view
state.view = view;
}
state.span = span;
parts.shift();
}
parseBeyondSpan();
});
} else {
parseBeyondSpan();
}
} else {
callback(state);
}
}
function parseBeyondSpan() {
if (!state.view) {
// set to default list or item view
state.view = self.options.views[state.type][
!state.item ? 'list' : 'item'
][0];
}
Ox.print('pBS', state)
var sortKeyIds = (self.options.sortKeys[state.type][
!state.item ? 'list' : 'item'
][state.view] || []).map(function(sortKey) {
return sortKey.id;
});
if (parts.length && parts[0].split(',').every(function(str) {
return self.sortKeyIds.indexOf(str.replace(/^[\+-]/, '')) > -1;
return sortKeyIds.indexOf(str.replace(/^[\+-]/, '')) > -1;
})) {
state.sort = parseSort(parts[0]);
// sort
state.sort = parseSort(parts[0], state);
parts.shift();
}
if (parts.length) {
// find
state.find = parseFind(parts.join('/'));
}
callback(state);
}
}
that._constructURL = function(state) {
that._construct = function(state) {
return constructURL(state);
};