Ox.URL: add hash parser, add tests

This commit is contained in:
rolux 2012-10-31 12:57:55 +01:00
parent 64085cdc04
commit ed4dd70c8e

View file

@ -44,13 +44,217 @@ Ox.URL <f> URL controller
typeA <o> Views for type "typeA"
list <[s]> List views for this type
item <[s]> Item views for this type
<script>
Ox.test.url = Ox.URL({
findKeys: [
{id: 'name', type: 'string'},
{id: 'population', type: 'integer'}
],
getItem: function(str, callback) {
callback(/^\d+$/.test(str) ? str : '');
},
getSpan: function(item, view, str, callback) {
if (!item && (!view || view == 'map')) {
callback(str.replace(/^@/, ''), 'map');
} else {
callback();
}
},
pages: ['about', 'faq', 'help'],
sortKeys: {
countries: {
list: {
grid: [
{id: 'name', operator: '+'},
{id: 'population', operator: '-'},
{id: 'cities', operator: '-'}
]
},
item: {
cities: [
{id: 'name', operator: '+'},
{id: 'population', operator: '-'}
]
}
},
cities: {
list: {
grid: [
{id: 'name', operator: '+'},
{id: 'population', operator: '-'}
]
}
}
},
spanType: {
countries: {
list: {map: 'location'},
item: {map: 'location'}
},
cities: {
list: {map: 'location'},
item: {map: 'location'}
}
},
types: ['countries', 'cities'],
views: {
countries: {
list: ['grid', 'map'],
item: ['info', 'map', 'cities']
},
cities: {
list: ['grid', 'map'],
item: ['info', 'map']
}
}
});
Ox.test.result = {
'/': {},
'/faq#1': {
page: 'faq',
hash: {anchor: '1'}
},
'/cities': {
type: 'cities',
item: '',
view: 'grid'
},
'/map': {
type: 'countries',
item: '',
view: 'map'
},
'/-45,-90,45,90': {
type: 'countries',
item: '',
view: 'map',
span: [[-45, -90], [45, 90]]
},
'/@New%20York': {
type: 'countries',
item: '',
view: 'map',
span: 'New York'
},
'/name': {
type: 'countries',
item: '',
view: 'grid',
sort: [{key: 'name', operator: '+'}]
},
'/-name,population': {
type: 'countries',
item: '',
view: 'grid',
sort: [
{key: 'name', operator: '-'},
{key: 'population', operator: '-'}
]
},
'/2342': {
type: 'countries',
item: '2342',
view: 'info'
},
'/2342/map': {
type: 'countries',
item: '2342',
view: 'map'
},
'/2342/name': {
type: 'countries',
item: '2342',
view: 'cities',
sort: [{key: 'name', operator: '+'}]
},
'/foo': {
type: 'countries',
item: '',
view: 'grid',
find: {
conditions: [
{key: '*', operator: '=', value: 'foo'}
],
operator: '&'
}
},
'/population=1000,2000': {
type: 'countries',
item: '',
view: 'grid',
find: {
conditions: [
{key: 'population', operator: '=', value: ['1000', '2000']}
],
operator: '&'
}
},
'/population>0&(name=a*|name=*z)': {
type: 'countries',
item: '',
view: 'grid',
find: {
conditions: [
{key: 'population', operator: '>', value: '0'},
{
conditions: [
{key: 'name', operator: '^', value: 'a'},
{key: 'name', operator: '$', value: 'z'}
],
operator: '|'
}
],
operator: '&'
}
},
'/#a?k=v&l=w': {
hash: {
anchor: 'a',
query: [
{key: 'k', value: 'v'},
{key: 'l', value: 'w'}
]
}
}
};
</script>
> !!Ox.test.url.parse('/', function(o) { Ox.test(o, Ox.test.result['/']); })
true
> !!Ox.test.url.parse('/faq#1', function(o) { Ox.test(o, Ox.test.result['/faq#1']); })
true
> !!Ox.test.url.parse('/cities', function(o) { Ox.test(o, Ox.test.result['/cities']); })
true
> !!Ox.test.url.parse('/map', function(o) { Ox.test(o, Ox.test.result['/map']); })
true
> !!Ox.test.url.parse('/-45,-90,45,90', function(o) { Ox.test(o, Ox.test.result['/-45,-90,45,90']); })
true
> !!Ox.test.url.parse('/@New%20York', function(o) { Ox.test(o, Ox.test.result['/@New%20York']); })
true
> !!Ox.test.url.parse('/name', function(o) { Ox.test(o, Ox.test.result['/name']); })
true
> !!Ox.test.url.parse('/-name,population', function(o) { Ox.test(o, Ox.test.result['/-name,population']); })
true
> !!Ox.test.url.parse('/2342', function(o) { Ox.test(o, Ox.test.result['/2342']); })
true
> !!Ox.test.url.parse('/2342/map', function(o) { Ox.test(o, Ox.test.result['/2342/map']); })
true
> !!Ox.test.url.parse('/2342/name', function(o) { Ox.test(o, Ox.test.result['/2342/name']); })
true
> !!Ox.test.url.parse('/foo', function(o) { Ox.test(o, Ox.test.result['/foo']); })
true
> !!Ox.test.url.parse('/population=1000,2000', function(o) { Ox.test(o, Ox.test.result['/population=1000,2000']); })
true
> !!Ox.test.url.parse('/population>0&(name=a*|name=*z)', function(o) { Ox.test(o, Ox.test.result['/population>0&(name=a*|name=*z)']); })
true
> !!Ox.test.url.parse('/#a?k=v&l=w', function(o) { Ox.test(o, Ox.test.result['/#a?k=v&l=w']); })
true
@*/
/*
example.com[/page]
example.com[/page][#hash]
or
example.com[/type][/item][/view][/span][/sort][/find]
example.com[/type][/item][/view][/span][/sort][/find][#hash]
page Special page, like "about" or "contact"
type Section a.k.a. item type, like "movies", "edits", "texts" etc.
@ -69,6 +273,7 @@ find Query, like a=x or a=x&b=y or a=x&(b=y|c=z). A query object has the form
object has the form {key: '', value: '' or ['', ''], operator: ''}
(comparison operator) or {conditions: [], operator: ''} (logical
operator). Condition strings can be more than just "k=v", see below.
hash Anchor and/or query, like 'a' or '?k=v' or 'a?k=v&l=w'.
String Key Value Operator
v * v = any text or string contains or any number is
@ -158,7 +363,11 @@ Ox.URL = function(options) {
views: {}
}, options);
Ox.Log('Core', 'Ox.URL options', self.options)
if (Ox.every(self.options.findKeys, function(findKey) {
return findKey.id != '*';
})) {
self.options.findKeys.push({id: '*', type: 'string'});
}
self.previousTitle = '';
self.previousURL = '';
@ -207,6 +416,13 @@ Ox.URL = function(options) {
}).join(find.operator);
}
function constructHash(hash) {
return (hash.anchor || '')
+ hash.query ? '?' + hash.query.map(function(query) {
return encodeValue(query.key) + '=' + encodeValue(query.value);
}) : '';
}
function constructLocation(location) {
return location.join(',');
}
@ -262,7 +478,8 @@ Ox.URL = function(options) {
parts.push(constructFind(state.find));
}
}
return '/' + Ox.filter(parts).join('/');
return '/' + Ox.filter(parts).join('/')
+ (state.hash ? '#' + constructHash(state.hash) : '');
}
function constructValue(str, key) {
@ -407,6 +624,23 @@ Ox.URL = function(options) {
return find;
}
function parseHash(str) {
var split = str.split('?');
return Ox.extend({
anchor: decodeValue(split[0])
}, split[1] ? {
query: split[1].split('&').filter(function(kv) {
return kv.indexOf('=') > -1;
}).map(function(kv) {
var split = kv.split('=');
return {
key: decodeValue(split[0]),
value: decodeValue(split[1])
};
})
} : {});
}
function parseLocation(str) {
return str.split(',').map(function(str, i) {
return Ox.limit(parseInt(str, 10), -90 * (i + 1), 90 * (i + 1));
@ -441,9 +675,9 @@ Ox.URL = function(options) {
function parseURL(str, callback) {
// fixme: removing trailing slash makes it impossible to search for '/'
str = str.replace(/(^\/|\/$)/g, '');
var parts = str.split('/'),
state = {};
var split = str.split('#'),
parts = split.shift().replace(/(^\/|\/$)/g, '').split('/'),
state = split.length ? {hash: parseHash(split.join('#'))} : {};
if (parts[0] == '') {
// empty URL
callback(state);
@ -479,6 +713,9 @@ Ox.URL = function(options) {
});
}
} else {
state.item = '';
// set to default view
state.view = self.options.views[state.type].list[0];
callback(state);
}
}