diff --git a/lona/client/context.js b/lona/client/context.js index fc13aaf6..23de39d0 100644 --- a/lona/client/context.js +++ b/lona/client/context.js @@ -20,6 +20,10 @@ Lona.LonaContext = function(settings) { this.settings.follow_http_redirects = true; }; + if(typeof(this.settings.scroll_to_top_on_view_start) == 'undefined') { + this.settings.scroll_to_top_on_view_start = true; + }; + // state ------------------------------------------------------------------ this._windows = {}; this._connect_hooks = []; diff --git a/lona/client/window.js b/lona/client/window.js index 5b4fcedd..9815b4c8 100644 --- a/lona/client/window.js +++ b/lona/client/window.js @@ -396,6 +396,15 @@ Lona.LonaWindow = function(lona_context, root, window_id) { this._clear(); }; + // scroll to top + // If the a new view gets started with the page scrolled down, and + // the page has a fixed height set in css, for example the min-height + // of the body is set to 100%, the new view starts scrolled to the + // bottom, which is counterintuitive to end users. + if(this.lona_context.settings.scroll_to_top_on_view_start) { + window.scrollTo(0, 0); + }; + // encode message var message = [ this._window_id, diff --git a/lona/routing.py b/lona/routing.py index 5b34a639..41944861 100644 --- a/lona/routing.py +++ b/lona/routing.py @@ -35,27 +35,26 @@ def __init__(self, raw_pattern, view, name='', interactive=True, # string or regex else: - if self.raw_pattern.endswith(OPTIONAL_TRAILING_SLASH_PATTERN): + raw_pattern = self.raw_pattern + + if raw_pattern.endswith(OPTIONAL_TRAILING_SLASH_PATTERN): self.optional_trailing_slash = True - groups = ABSTRACT_ROUTE_RE.findall(self.raw_pattern) + raw_pattern = \ + raw_pattern[:-len(OPTIONAL_TRAILING_SLASH_PATTERN)] + + groups = ABSTRACT_ROUTE_RE.findall(raw_pattern) # path is no pattern but simple string if not groups: - self.path = self.raw_pattern - self.format_string = self.raw_pattern - - if self.optional_trailing_slash: - suffix_len = len(OPTIONAL_TRAILING_SLASH_PATTERN) * -1 - - self.path = self.path[:suffix_len] - self.format_string = self.path[:suffix_len] + self.path = raw_pattern + self.format_string = raw_pattern return pattern_names = [i[0] for i in groups] patterns = [(i[0], i[2] or DEFAULT_PATTERN) for i in groups] - cleaned_pattern = ABSTRACT_ROUTE_RE.sub('{}', self.raw_pattern) + cleaned_pattern = ABSTRACT_ROUTE_RE.sub('{}', raw_pattern) # setup format string self.format_string = cleaned_pattern.format( @@ -68,7 +67,7 @@ def __init__(self, raw_pattern, view, name='', interactive=True, *[ROUTE_PART_FORMAT_STRING.format(*i) for i in patterns], ), - (OPTIONAL_TRAILING_SLASH_PATTERN + (r'(/)?' if self.optional_trailing_slash else ''), ), ) diff --git a/tests/test_routing.py b/tests/test_routing.py index bf463aa3..496102b5 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -79,6 +79,7 @@ def setup(self): self.routes = [ Route('/foo(/)', None), Route('/bar/', None), + Route('/foobar/(/)', None), ] self.router = Router() @@ -98,6 +99,20 @@ def test_optional_slash_resolves_with_slash(self): assert route == self.routes[0] assert match_info == {} + def test_optional_slash_resolves_without_slash_and_argument(self): + match, route, match_info = self.router.resolve('/foobar/foobar') + + assert match + assert route == self.routes[2] + assert match_info == {'arg': 'foobar'} + + def test_optional_slash_resolves_with_slash_and_argument(self): + match, route, match_info = self.router.resolve('/foobar/foobar/') + + assert match + assert route == self.routes[2] + assert match_info == {'arg': 'foobar'} + def test_required_slash_doesnt_resolve_without_slash(self): match, route, match_info = self.router.resolve('/bar') @@ -117,6 +132,7 @@ def setup(self): routes = [ Route('/foo//', None, name='foo'), Route('/bar/', None, name='bar'), + Route('/foobar/(/)', None, name='foobar'), ] self.router = Router() self.router.add_routes(*routes) @@ -126,6 +142,11 @@ def test_reverse_with_arg(self): assert url == '/foo/bar/' + def test_reverse_with_arg_and_optional_slash(self): + url = self.router.reverse('foobar', arg='foobar') + + assert url == '/foobar/foobar' + def test_reverse_without_arg(self): url = self.router.reverse('bar')