diff --git a/README.txt b/README.txt index 5db12af..e4d2265 100755 --- a/README.txt +++ b/README.txt @@ -71,6 +71,7 @@ We can now ask our concepts for their related concepts: TODO: Work with views... + Resources and what they have to do with Concepts ================================================ @@ -107,8 +108,17 @@ below) via the getClients() method: >>> conc[0] is zope True -Views: Menus, Menu Items, Listings, Simple Content, etc -======================================================= + +Views/Nodes: Menus, Menu Items, Listings, Pages, etc +==================================================== + +Note: the term "view" here is not directly related to the special +Zop 3 term "view" (a multiadapter for presentation purposes) but basically +bears the common sense meaning: an object (that may be persistent or +created on the fly) that provides a view to content of whatever kind. + +Views (or nodes - that's the only type of views existing at the moment) +thus provide the presentation space to concepts and resources. We first need a view manager: @@ -196,6 +206,59 @@ view class's target attribute: >>> m111.target is zope3 True +Node views +---------- + + >>> from loops.browser.node import NodeView + >>> view = NodeView(m1, TestRequest()) + >>> view.menu() + {'url': 'http://127.0.0.1/loops/views/m1', + 'items': [{'url': 'http://127.0.0.1/loops/views/m1/m11', 'items': [], + 'selected': False, 'title': u'Zope'}], + 'selected': True, 'title': u'Menu'} + >>> view.content() + {'url': 'http://127.0.0.1/loops/views/m1', 'body': u'', 'items': [], + 'title': u'Menu'} + + >>> view = NodeView(m11, TestRequest()) + >>> view.menu() + {'url': 'http://127.0.0.1/loops/views/m1', + 'items': [{'url': 'http://127.0.0.1/loops/views/m1/m11', 'items': [], + 'selected': True, 'title': u'Zope'}], + 'selected': False, 'title': u'Menu'} + >>> view.content() + {'url': 'http://127.0.0.1/loops/views/m1/m11', 'body': u'', + 'items': [{'url': 'http://127.0.0.1/loops/views/m1/m11/m112', + 'body': u'', 'items': [], 'title': u'Zope 3'}], + 'title': u'Zope'} + +Ordering Nodes +-------------- + +Let's add some more nodes and reorder them: + + >>> m113 = Node() + >>> m11['m113'] = m113 + >>> m114 = Node() + >>> m11['m114'] = m114 + >>> m11.keys() + ['m111', 'm112', 'm113', 'm114'] + + >>> m11.moveSubNodesByDelta(['m113'], -1) + >>> m11.keys() + ['m111', 'm113', 'm112', 'm114'] + +A special management view provides methods for moving objects down, up, +to the bottom, and to the top + + >>> from loops.browser.node import OrderedContainerView + >>> view = OrderedContainerView(m11, TestRequest()) + >>> view.moveToBottom(('m113',)) + >>> m11.keys() + ['m111', 'm112', 'm114', 'm113'] + >>> view.moveUp(('m114',), 1) + >>> m11.keys() + ['m111', 'm114', 'm112', 'm113'] Fin de partie ============= diff --git a/browser/configure.zcml b/browser/configure.zcml index b63d3a3..6445acc 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -7,7 +7,7 @@ - + @@ -199,13 +199,6 @@ view="AddLoopsViewManager.html" /> - - + + + + + + + + + + diff --git a/browser/contents.pt b/browser/contents.pt new file mode 100644 index 0000000..efa9557 --- /dev/null +++ b/browser/contents.pt @@ -0,0 +1,216 @@ + + +
+
+ + + + +
+ + + + +
+ Error message +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 NameTitleSizeCreatedModified
+ + + + foo   + +      +      + +    
+ +
+ + + + + + + +
+ + + + + +
+ +
+
+ + + +
+
+ +
+ +
+ + +
+ +
+
+
+ + + + +
+ +
+ + diff --git a/browser/view.css b/browser/node.css similarity index 100% rename from browser/view.css rename to browser/node.css diff --git a/browser/node.pt b/browser/node.pt index 76127d6..3a06417 100644 --- a/browser/node.pt +++ b/browser/node.pt @@ -6,14 +6,14 @@ - @@ -36,7 +36,7 @@

Navigation

- diff --git a/browser/node.py b/browser/node.py index d98e3a4..a7c39d4 100644 --- a/browser/node.py +++ b/browser/node.py @@ -24,6 +24,7 @@ $Id$ from zope.cachedescriptors.property import Lazy from zope.app import zapi +from zope.app.container.browser.contents import JustContents from zope.app.dublincore.interfaces import ICMFDublinCore from zope.proxy import removeAllProxies from zope.security.proxy import removeSecurityProxy @@ -32,6 +33,10 @@ from loops.interfaces import IConcept class NodeView(object): + def __init__(self, context, request): + self.context = context + self.request = request + def render(self, text): if not text: return u'' @@ -49,7 +54,7 @@ class NodeView(object): d = dc.modified or dc.created return d and d.strftime('%Y-%m-%d %H:%M') or '' - def getContent(self, item=None): + def content(self, item=None): if item is None: item = self.context.getPage() if item is None: @@ -57,11 +62,11 @@ class NodeView(object): result = {'title': item.title, 'url': zapi.absoluteURL(item, self.request), 'body': self.render(item.body), - 'items': [self.getContent(child) + 'items': [self.content(child) for child in item.getTextItems()]} return result - def getMenu(self, item=None): + def menu(self, item=None): if item is None: item = self.context.getMenu() if item is None: @@ -69,7 +74,53 @@ class NodeView(object): result = {'title': item.title, 'url': zapi.absoluteURL(item, self.request), 'selected': item == self.context, - 'items': [self.getMenu(child) + 'items': [self.menu(child) for child in item.getMenuItems()]} return result + +class OrderedContainerView(JustContents): + """ A view providing the necessary methods for moving sub-objects + within an ordered container. + """ + + @Lazy + def url(self): + return zapi.absoluteURL(self.context, self.request) + + @Lazy + def orderable(self): + return len(self.context) > 1 + + def checkMoveAction(self): + request = self.request + for var in request: + if var.startswith('move_'): + params = [] + if 'delta' in request: + params.append('delta=' + request['delta']) + if 'ids' in request: + for id in request['ids']: + params.append('ids:list=' + id) + request.response.redirect('%s/%s?%s' + % (self.url, var, '&'.join(params))) + return True + return False + + def moveDown(self, ids, delta=1): + self.context.moveSubNodesByDelta(ids, int(delta)) + self.request.response.redirect(self.url + '/contents.html') + + def moveUp(self, ids, delta=1): + self.context.moveSubNodesByDelta(ids, -int(delta)) + self.request.response.redirect(self.url + '/contents.html') + + def moveToBottom(self, ids): + self.context.moveSubNodesByDelta(ids, len(self.context)) + self.request.response.redirect(self.url + '/contents.html') + + def moveToTop(self, ids): + self.context.moveSubNodesByDelta(ids, -len(self.context)) + self.request.response.redirect(self.url + '/contents.html') + + diff --git a/interfaces.py b/interfaces.py index a2dfaea..c376ad9 100644 --- a/interfaces.py +++ b/interfaces.py @@ -169,9 +169,13 @@ class IView(Interface): required=False) target = Attribute('Target object that is referenced by this view') + +class IBaseNode(IOrderedContainer): + """ Common abstract base class for different types of nodes + """ -class INode(IView, IOrderedContainer): +class INode(IView, IBaseNode): """ A node is a view that may contain other views, thus building a menu or folder hierarchy. @@ -227,8 +231,14 @@ class INode(IView, IOrderedContainer): a menu). """ + def moveSubNodesByDelta(names, delta): + """ Move the sub-nodes specified by the list of names up + (negative delta) or down (positive delta) by the number of places + specified by delta. + """ -class IViewManager(IContainer): + +class IViewManager(IBaseNode): """ A manager/container for views. """ contains(IView) diff --git a/util.py b/util.py new file mode 100644 index 0000000..2421daa --- /dev/null +++ b/util.py @@ -0,0 +1,46 @@ +# +# Copyright (c) 2006 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Utility functions. + +$Id$ +""" + +def moveByDelta(objs, toMove, delta): + """ Return the list given by objs resorted in a way that the elements + of toMove (which must be in the objs list) have been moved by delta. + """ + result = [obj for obj in objs if obj not in toMove] + if delta < 0: + objs = list(reversed(objs)) + result.reverse() + toMove = sorted(toMove, lambda x,y: cmp(objs.index(x), objs.index(y))) + for element in toMove: + newPos = min(len(result), objs.index(element) + abs(delta)) + result.insert(newPos, element) + if delta < 0: + result.reverse() + return result + +def nl2br(text): + if not text: return text + if '\n' in text: # Unix or DOS line endings + return '
\n'.join(x.replace('\r', '') for x in text.split('\n')) + else: # gracefully handle Mac line endigns + return '
\n'.join(text.split('\r')) diff --git a/view.py b/view.py index 2beb1bc..11c5b9f 100644 --- a/view.py +++ b/view.py @@ -34,6 +34,7 @@ from cybertools.relation.registry import IRelationsRegistry, getRelations from interfaces import IView, INode from interfaces import IViewManager, INodeContained from interfaces import ILoopsContained +from util import moveByDelta class View(object): @@ -122,8 +123,10 @@ class Node(View, OrderedContainer): def getTextItems(self): return self.getChildNodes(['text']) + def moveSubNodesByDelta(self, names, delta): + self.updateOrder(moveByDelta(self.keys(), names, delta)) -class ViewManager(BTreeContainer): +class ViewManager(OrderedContainer): implements(IViewManager, ILoopsContained)