# HG changeset patch # User Marcin Kuzminski # Date 2013-04-22 12:11:40 # Node ID 13241a4075e920b34354d8192ca8d903d98c522f # Parent 371898dc9a919addb52844691c5ff3d01bc058e8 Unified the paginators for pylons and YUI. - YUI based paginators now behave more like the ones generated with pylons - introduced new custom pylons paginator for customizations needed to unify both diff --git a/rhodecode/controllers/admin/admin.py b/rhodecode/controllers/admin/admin.py --- a/rhodecode/controllers/admin/admin.py +++ b/rhodecode/controllers/admin/admin.py @@ -27,17 +27,17 @@ import logging from pylons import request, tmpl_context as c, url from sqlalchemy.orm import joinedload -from webhelpers.paginate import Page from whoosh.qparser.default import QueryParser +from whoosh.qparser.dateparse import DateParserPlugin from whoosh import query from sqlalchemy.sql.expression import or_, and_, func +from rhodecode.model.db import UserLog, User from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator from rhodecode.lib.base import BaseController, render -from rhodecode.model.db import UserLog, User from rhodecode.lib.utils2 import safe_int, remove_prefix, remove_suffix from rhodecode.lib.indexers import JOURNAL_SCHEMA -from whoosh.qparser.dateparse import DateParserPlugin +from rhodecode.lib.helpers import Page log = logging.getLogger(__name__) diff --git a/rhodecode/controllers/admin/notifications.py b/rhodecode/controllers/admin/notifications.py --- a/rhodecode/controllers/admin/notifications.py +++ b/rhodecode/controllers/admin/notifications.py @@ -30,15 +30,13 @@ from pylons import request from pylons import tmpl_context as c, url from pylons.controllers.util import redirect, abort -from webhelpers.paginate import Page - +from rhodecode.model.db import Notification +from rhodecode.model.notification import NotificationModel +from rhodecode.model.meta import Session +from rhodecode.lib.auth import LoginRequired, NotAnonymous from rhodecode.lib.base import BaseController, render -from rhodecode.model.db import Notification - -from rhodecode.model.notification import NotificationModel -from rhodecode.lib.auth import LoginRequired, NotAnonymous from rhodecode.lib import helpers as h -from rhodecode.model.meta import Session +from rhodecode.lib.helpers import Page from rhodecode.lib.utils2 import safe_int diff --git a/rhodecode/controllers/journal.py b/rhodecode/controllers/journal.py --- a/rhodecode/controllers/journal.py +++ b/rhodecode/controllers/journal.py @@ -29,21 +29,21 @@ from sqlalchemy import or_ from sqlalchemy.orm import joinedload from sqlalchemy.sql.expression import func -from webhelpers.paginate import Page from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed from webob.exc import HTTPBadRequest from pylons import request, tmpl_context as c, response, url from pylons.i18n.translation import _ +from rhodecode.controllers.admin.admin import _journal_filter +from rhodecode.model.db import UserLog, UserFollowing, Repository, User +from rhodecode.model.meta import Session +from rhodecode.model.repo import RepoModel import rhodecode.lib.helpers as h +from rhodecode.lib.helpers import Page from rhodecode.lib.auth import LoginRequired, NotAnonymous from rhodecode.lib.base import BaseController, render -from rhodecode.model.db import UserLog, UserFollowing, Repository, User -from rhodecode.model.meta import Session from rhodecode.lib.utils2 import safe_int, AttributeDict -from rhodecode.controllers.admin.admin import _journal_filter -from rhodecode.model.repo import RepoModel from rhodecode.lib.compat import json log = logging.getLogger(__name__) diff --git a/rhodecode/controllers/search.py b/rhodecode/controllers/search.py --- a/rhodecode/controllers/search.py +++ b/rhodecode/controllers/search.py @@ -28,20 +28,18 @@ import urllib from pylons.i18n.translation import _ from pylons import request, config, tmpl_context as c +from whoosh.index import open_dir, EmptyIndexError +from whoosh.qparser import QueryParser, QueryParserError +from whoosh.query import Phrase, Wildcard, Term, Prefix +from webhelpers.util import update_params + from rhodecode.lib.auth import LoginRequired from rhodecode.lib.base import BaseRepoController, render from rhodecode.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \ IDX_NAME, WhooshResultWrapper - -from webhelpers.paginate import Page -from webhelpers.util import update_params - -from whoosh.index import open_dir, EmptyIndexError -from whoosh.qparser import QueryParser, QueryParserError -from whoosh.query import Phrase, Wildcard, Term, Prefix from rhodecode.model.repo import RepoModel from rhodecode.lib.utils2 import safe_str, safe_int - +from rhodecode.lib.helpers import Page log = logging.getLogger(__name__) diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -36,7 +36,7 @@ from webhelpers.text import chop_at, col convert_misc_entities, lchop, plural, rchop, remove_formatting, \ replace_whitespace, urlify, truncate, wrap_paragraphs from webhelpers.date import time_ago_in_words -from webhelpers.paginate import Page +from webhelpers.paginate import Page as _Page from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \ convert_boolean_attrs, NotGiven, _make_safe_id_component @@ -809,6 +809,143 @@ def gravatar_url(email_address, size=30) return gravatar_url +class Page(_Page): + """ + Custom pager to match rendering style with YUI paginator + """ + + def _get_pos(self, cur_page, max_page, items): + edge = (items / 2) + 1 + if (cur_page <= edge): + radius = max(items / 2, items - cur_page) + elif (max_page - cur_page) < edge: + radius = (items - 1) - (max_page - cur_page) + else: + radius = items / 2 + + left = max(1, (cur_page - (radius))) + right = min(max_page, cur_page + (radius)) + return left, cur_page, right + + def _range(self, regexp_match): + """ + Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8'). + + Arguments: + + regexp_match + A "re" (regular expressions) match object containing the + radius of linked pages around the current page in + regexp_match.group(1) as a string + + This function is supposed to be called as a callable in + re.sub. + + """ + radius = int(regexp_match.group(1)) + + # Compute the first and last page number within the radius + # e.g. '1 .. 5 6 [7] 8 9 .. 12' + # -> leftmost_page = 5 + # -> rightmost_page = 9 + leftmost_page, _cur, rightmost_page = self._get_pos(self.page, + self.last_page, + (radius * 2) + 1) + nav_items = [] + + # Create a link to the first page (unless we are on the first page + # or there would be no need to insert '..' spacers) + if self.page != self.first_page and self.first_page < leftmost_page: + nav_items.append(self._pagerlink(self.first_page, self.first_page)) + + # Insert dots if there are pages between the first page + # and the currently displayed page range + if leftmost_page - self.first_page > 1: + # Wrap in a SPAN tag if nolink_attr is set + text = '..' + if self.dotdot_attr: + text = HTML.span(c=text, **self.dotdot_attr) + nav_items.append(text) + + for thispage in xrange(leftmost_page, rightmost_page + 1): + # Hilight the current page number and do not use a link + if thispage == self.page: + text = '%s' % (thispage,) + # Wrap in a SPAN tag if nolink_attr is set + if self.curpage_attr: + text = HTML.span(c=text, **self.curpage_attr) + nav_items.append(text) + # Otherwise create just a link to that page + else: + text = '%s' % (thispage,) + nav_items.append(self._pagerlink(thispage, text)) + + # Insert dots if there are pages between the displayed + # page numbers and the end of the page range + if self.last_page - rightmost_page > 1: + text = '..' + # Wrap in a SPAN tag if nolink_attr is set + if self.dotdot_attr: + text = HTML.span(c=text, **self.dotdot_attr) + nav_items.append(text) + + # Create a link to the very last page (unless we are on the last + # page or there would be no need to insert '..' spacers) + if self.page != self.last_page and rightmost_page < self.last_page: + nav_items.append(self._pagerlink(self.last_page, self.last_page)) + + return self.separator.join(nav_items) + + def pager(self, format='~2~', page_param='page', partial_param='partial', + show_if_single_page=False, separator=' ', onclick=None, + symbol_first='<<', symbol_last='>>', + symbol_previous='<', symbol_next='>', + link_attr={'class': 'pager_link'}, + curpage_attr={'class': 'pager_curpage'}, + dotdot_attr={'class': 'pager_dotdot'}, **kwargs): + + self.curpage_attr = curpage_attr + self.separator = separator + self.pager_kwargs = kwargs + self.page_param = page_param + self.partial_param = partial_param + self.onclick = onclick + self.link_attr = link_attr + self.dotdot_attr = dotdot_attr + + # Don't show navigator if there is no more than one page + if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page): + return '' + + from string import Template + # Replace ~...~ in token format by range of pages + result = re.sub(r'~(\d+)~', self._range, format) + + # Interpolate '%' variables + result = Template(result).safe_substitute({ + 'first_page': self.first_page, + 'last_page': self.last_page, + 'page': self.page, + 'page_count': self.page_count, + 'items_per_page': self.items_per_page, + 'first_item': self.first_item, + 'last_item': self.last_item, + 'item_count': self.item_count, + 'link_first': self.page > self.first_page and \ + self._pagerlink(self.first_page, symbol_first) or '', + 'link_last': self.page < self.last_page and \ + self._pagerlink(self.last_page, symbol_last) or '', + 'link_previous': self.previous_page and \ + self._pagerlink(self.previous_page, symbol_previous) \ + or HTML.span(symbol_previous, class_="yui-pg-previous"), + 'link_next': self.next_page and \ + self._pagerlink(self.next_page, symbol_next) \ + or HTML.span(symbol_next, class_="yui-pg-next") + }) + + return literal(result) + + #============================================================================== # REPO PAGER, PAGER FOR REPOSITORY #============================================================================== diff --git a/rhodecode/public/css/style.css b/rhodecode/public/css/style.css --- a/rhodecode/public/css/style.css +++ b/rhodecode/public/css/style.css @@ -1591,7 +1591,7 @@ div.form div.fields div.field div.button border-radius: 4px 0px 0px 4px; } -#content div.box div.pagination-wh > :last-child{ +#content div.box div.pagination-wh > :last-child { border-radius: 0px 4px 4px 0px; border-right: 1px solid #cfcfcf; } diff --git a/rhodecode/public/js/rhodecode.js b/rhodecode/public/js/rhodecode.js --- a/rhodecode/public/js/rhodecode.js +++ b/rhodecode/public/js/rhodecode.js @@ -2207,6 +2207,289 @@ var MultiSelectWidget = function(selecte } + + +var YUI_paginator = function(links_per_page, containers){ + // my custom paginator + (function () { + + var Paginator = YAHOO.widget.Paginator, + l = YAHOO.lang, + setId = YAHOO.util.Dom.generateId; + + Paginator.ui.MyFirstPageLink = function (p) { + this.paginator = p; + + p.subscribe('recordOffsetChange',this.update,this,true); + p.subscribe('rowsPerPageChange',this.update,this,true); + p.subscribe('totalRecordsChange',this.update,this,true); + p.subscribe('destroy',this.destroy,this,true); + + // TODO: make this work + p.subscribe('firstPageLinkLabelChange',this.update,this,true); + p.subscribe('firstPageLinkClassChange',this.update,this,true); + }; + + Paginator.ui.MyFirstPageLink.init = function (p) { + p.setAttributeConfig('firstPageLinkLabel', { + value : 1, + validator : l.isString + }); + p.setAttributeConfig('firstPageLinkClass', { + value : 'yui-pg-first', + validator : l.isString + }); + p.setAttributeConfig('firstPageLinkTitle', { + value : 'First Page', + validator : l.isString + }); + }; + + // Instance members and methods + Paginator.ui.MyFirstPageLink.prototype = { + current : null, + leftmost_page: null, + rightmost_page: null, + link : null, + span : null, + dotdot : null, + getPos : function(cur_page, max_page, items){ + var edge = parseInt(items / 2) + 1; + if (cur_page <= edge){ + var radius = Math.max(parseInt(items / 2), items - cur_page); + } + else if ((max_page - cur_page) < edge) { + var radius = (items - 1) - (max_page - cur_page); + } + else{ + var radius = parseInt(items / 2); + } + + var left = Math.max(1, (cur_page - (radius))) + var right = Math.min(max_page, cur_page + (radius)) + return [left, cur_page, right] + }, + render : function (id_base) { + var p = this.paginator, + c = p.get('firstPageLinkClass'), + label = p.get('firstPageLinkLabel'), + title = p.get('firstPageLinkTitle'); + + this.link = document.createElement('a'); + this.span = document.createElement(); + + var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5); + this.leftmost_page = _pos[0]; + this.rightmost_page = _pos[2]; + + setId(this.link, id_base + '-first-link'); + this.link.href = '#'; + this.link.className = c; + this.link.innerHTML = label; + this.link.title = title; + YAHOO.util.Event.on(this.link,'click',this.onClick,this,true); + + setId(this.span, id_base + '-first-span'); + this.span.className = c; + this.span.innerHTML = label; + + this.current = p.getCurrentPage() > 1 ? this.link : this.span; + return this.current; + }, + update : function (e) { + var p = this.paginator; + var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5); + this.leftmost_page = _pos[0]; + this.rightmost_page = _pos[2]; + + if (e && e.prevValue === e.newValue) { + return; + } + + var par = this.current ? this.current.parentNode : null; + if (this.leftmost_page > 1) { + if (par && this.current === this.span) { + par.replaceChild(this.link,this.current); + this.current = this.link; + } + } else { + if (par && this.current === this.link) { + par.replaceChild(this.span,this.current); + this.current = this.span; + } + } + }, + destroy : function () { + YAHOO.util.Event.purgeElement(this.link); + this.current.parentNode.removeChild(this.current); + this.link = this.span = null; + }, + onClick : function (e) { + YAHOO.util.Event.stopEvent(e); + this.paginator.setPage(1); + } + }; + + })(); + (function () { + + var Paginator = YAHOO.widget.Paginator, + l = YAHOO.lang, + setId = YAHOO.util.Dom.generateId; + + Paginator.ui.MyLastPageLink = function (p) { + this.paginator = p; + + p.subscribe('recordOffsetChange',this.update,this,true); + p.subscribe('rowsPerPageChange',this.update,this,true); + p.subscribe('totalRecordsChange',this.update,this,true); + p.subscribe('destroy',this.destroy,this,true); + + // TODO: make this work + p.subscribe('lastPageLinkLabelChange',this.update,this,true); + p.subscribe('lastPageLinkClassChange', this.update,this,true); + }; + + Paginator.ui.MyLastPageLink.init = function (p) { + p.setAttributeConfig('lastPageLinkLabel', { + value : -1, + validator : l.isString + }); + p.setAttributeConfig('lastPageLinkClass', { + value : 'yui-pg-last', + validator : l.isString + }); + p.setAttributeConfig('lastPageLinkTitle', { + value : 'Last Page', + validator : l.isString + }); + + }; + + Paginator.ui.MyLastPageLink.prototype = { + + current : null, + leftmost_page: null, + rightmost_page: null, + link : null, + span : null, + dotdot : null, + na : null, + getPos : function(cur_page, max_page, items){ + var edge = parseInt(items / 2) + 1; + if (cur_page <= edge){ + var radius = Math.max(parseInt(items / 2), items - cur_page); + } + else if ((max_page - cur_page) < edge) { + var radius = (items - 1) - (max_page - cur_page); + } + else{ + var radius = parseInt(items / 2); + } + + var left = Math.max(1, (cur_page - (radius))) + var right = Math.min(max_page, cur_page + (radius)) + return [left, cur_page, right] + }, + render : function (id_base) { + var p = this.paginator, + c = p.get('lastPageLinkClass'), + label = p.get('lastPageLinkLabel'), + last = p.getTotalPages(), + title = p.get('lastPageLinkTitle'); + + var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5); + this.leftmost_page = _pos[0]; + this.rightmost_page = _pos[2]; + + this.link = document.createElement('a'); + this.span = document.createElement(); + this.na = this.span.cloneNode(false); + + setId(this.link, id_base + '-last-link'); + this.link.href = '#'; + this.link.className = c; + this.link.innerHTML = label; + this.link.title = title; + YAHOO.util.Event.on(this.link,'click',this.onClick,this,true); + + setId(this.span, id_base + '-last-span'); + this.span.className = c; + this.span.innerHTML = label; + + setId(this.na, id_base + '-last-na'); + + if (this.rightmost_page < p.getTotalPages()){ + this.current = this.link; + } + else{ + this.current = this.span; + } + + this.current.innerHTML = p.getTotalPages(); + return this.current; + }, + + update : function (e) { + var p = this.paginator; + + var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5); + this.leftmost_page = _pos[0]; + this.rightmost_page = _pos[2]; + + if (e && e.prevValue === e.newValue) { + return; + } + + var par = this.current ? this.current.parentNode : null, + after = this.link; + if (par) { + + // only show the last page if the rightmost one is + // lower, so we don't have doubled entries at the end + if (!(this.rightmost_page < p.getTotalPages())){ + after = this.span + } + + if (this.current !== after) { + par.replaceChild(after,this.current); + this.current = after; + } + } + this.current.innerHTML = this.paginator.getTotalPages(); + + }, + destroy : function () { + YAHOO.util.Event.purgeElement(this.link); + this.current.parentNode.removeChild(this.current); + this.link = this.span = null; + }, + onClick : function (e) { + YAHOO.util.Event.stopEvent(e); + this.paginator.setPage(this.paginator.getTotalPages()); + } + }; + + })(); + + var pagi = new YAHOO.widget.Paginator({ + rowsPerPage: links_per_page, + alwaysVisible: false, + template : "{PreviousPageLink} {MyFirstPageLink} {PageLinks} {MyLastPageLink} {NextPageLink}", + pageLinks: 5, + containerClass: 'pagination-wh', + currentPageClass: 'pager_curpage', + pageLinkClass: 'pager_link', + nextPageLinkLabel: '>', + previousPageLinkLabel: '<', + containers:containers + }) + + return pagi +} + + + // global hooks after DOM is loaded YUE.onDOMReady(function(){ diff --git a/rhodecode/templates/admin/repos/repos.html b/rhodecode/templates/admin/repos/repos.html --- a/rhodecode/templates/admin/repos/repos.html +++ b/rhodecode/templates/admin/repos/repos.html @@ -79,20 +79,7 @@ var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{ sortedBy:{key:"name",dir:"asc"}, - paginator: new YAHOO.widget.Paginator({ - rowsPerPage: 25, - alwaysVisible: false, - template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", - pageLinks: 5, - containerClass: 'pagination-wh', - currentPageClass: 'pager_curpage', - pageLinkClass: 'pager_link', - nextPageLinkLabel: '>', - previousPageLinkLabel: '<', - firstPageLinkLabel: '<<', - lastPageLinkLabel: '>>', - containers:['user-paginator'] - }), + paginator: YUI_paginator(25, ['user-paginator']), MSG_SORTASC:"${_('Click to sort ascending')}", MSG_SORTDESC:"${_('Click to sort descending')}", diff --git a/rhodecode/templates/admin/users/user_edit_my_account.html b/rhodecode/templates/admin/users/user_edit_my_account.html --- a/rhodecode/templates/admin/users/user_edit_my_account.html +++ b/rhodecode/templates/admin/users/user_edit_my_account.html @@ -178,20 +178,7 @@ function table_renderer(data){ var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{ sortedBy:{key:"name",dir:"asc"}, - paginator: new YAHOO.widget.Paginator({ - rowsPerPage: 50, - alwaysVisible: false, - template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", - pageLinks: 5, - containerClass: 'pagination-wh', - currentPageClass: 'pager_curpage', - pageLinkClass: 'pager_link', - nextPageLinkLabel: '>', - previousPageLinkLabel: '<', - firstPageLinkLabel: '<<', - lastPageLinkLabel: '>>', - containers:['user-paginator'] - }), + paginator: YUI_paginator(50, ['user-paginator']), MSG_SORTASC:"${_('Click to sort ascending')}", MSG_SORTDESC:"${_('Click to sort descending')}", diff --git a/rhodecode/templates/index_base.html b/rhodecode/templates/index_base.html --- a/rhodecode/templates/index_base.html +++ b/rhodecode/templates/index_base.html @@ -131,20 +131,7 @@ var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{ sortedBy:{key:"name",dir:"asc"}, - paginator: new YAHOO.widget.Paginator({ - rowsPerPage: ${c.visual.dashboard_items}, - alwaysVisible: false, - template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", - pageLinks: 5, - containerClass: 'pagination-wh', - currentPageClass: 'pager_curpage', - pageLinkClass: 'pager_link', - nextPageLinkLabel: '>', - previousPageLinkLabel: '<', - firstPageLinkLabel: '<<', - lastPageLinkLabel: '>>', - containers:['user-paginator'] - }), + paginator: YUI_paginator(${c.visual.dashboard_items},['user-paginator']), MSG_SORTASC:"${_('Click to sort ascending')}", MSG_SORTDESC:"${_('Click to sort descending')}", diff --git a/rhodecode/templates/journal/journal.html b/rhodecode/templates/journal/journal.html --- a/rhodecode/templates/journal/journal.html +++ b/rhodecode/templates/journal/journal.html @@ -199,20 +199,7 @@ var myDataTable = new YAHOO.widget.DataTable("watched_repos_list_wrap", myColumnDefs, myDataSource,{ sortedBy:{key:"name",dir:"asc"}, - paginator: new YAHOO.widget.Paginator({ - rowsPerPage: 25, - alwaysVisible: false, - template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", - pageLinks: 5, - containerClass: 'pagination-wh', - currentPageClass: 'pager_curpage', - pageLinkClass: 'pager_link', - nextPageLinkLabel: '>', - previousPageLinkLabel: '<', - firstPageLinkLabel: '<<', - lastPageLinkLabel: '>>', - containers:['watched-user-paginator'] - }), + paginator: YUI_paginator(25, ['watched-user-paginator']), MSG_SORTASC:"${_('Click to sort ascending')}", MSG_SORTDESC:"${_('Click to sort descending')}", @@ -302,20 +289,7 @@ var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{ sortedBy:{key:"name",dir:"asc"}, - paginator: new YAHOO.widget.Paginator({ - rowsPerPage: 25, - alwaysVisible: false, - template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}", - pageLinks: 5, - containerClass: 'pagination-wh', - currentPageClass: 'pager_curpage', - pageLinkClass: 'pager_link', - nextPageLinkLabel: '>', - previousPageLinkLabel: '<', - firstPageLinkLabel: '<<', - lastPageLinkLabel: '>>', - containers:['user-paginator'] - }), + paginator: YUI_paginator(25, ['user-paginator']), MSG_SORTASC:"${_('Click to sort ascending')}", MSG_SORTDESC:"${_('Click to sort descending')}", diff --git a/rhodecode/templates/journal/journal_data.html b/rhodecode/templates/journal/journal_data.html --- a/rhodecode/templates/journal/journal_data.html +++ b/rhodecode/templates/journal/journal_data.html @@ -34,9 +34,9 @@ %endfor %endfor - +
- ${c.journal_pager.pager('$link_previous ~2~ $link_next')} + ${c.journal_pager.pager('$link_previous ~2~ $link_next')}
+ %else:
${_('No entries yet')}