[Zope-CVS] CVS: Products/CompositePage - designuis.py:1.2
README.txt:1.6 __init__.py:1.5 composite.py:1.10 slot.py:1.15
tool.py:1.9
Shane Hathaway
shane at zope.com
Thu Feb 26 16:38:42 EST 2004
Update of /cvs-repository/Products/CompositePage
In directory cvs.zope.org:/tmp/cvs-serv1031
Modified Files:
README.txt __init__.py composite.py slot.py tool.py
Added Files:
designuis.py
Log Message:
Merged composite-flat-ui-branch.
This adds a manual slotting interface (non-WYSIWYG). The manual
UI still supports drag and drop, but has many links and no
context menus.
=== Products/CompositePage/designuis.py 1.1 => 1.2 ===
--- /dev/null Thu Feb 26 16:38:42 2004
+++ Products/CompositePage/designuis.py Thu Feb 26 16:38:11 2004
@@ -0,0 +1,306 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Page design UI classes.
+
+$Id$
+"""
+
+import os
+import re
+
+import Globals
+from Acquisition import aq_base, aq_inner, aq_parent
+from OFS.SimpleItem import SimpleItem
+from Products.PageTemplates.PageTemplateFile import PageTemplateFile
+from AccessControl import ClassSecurityInfo
+from AccessControl.ZopeGuards import guarded_getattr
+
+from rawfile import RawFile, InterpolatedFile
+
+
+_common = os.path.join(os.path.dirname(__file__), "common")
+_zmi = os.path.join(os.path.dirname(__file__), "zmi")
+_cmf = os.path.join(os.path.dirname(__file__), "cmf")
+_manual = os.path.join(os.path.dirname(__file__), "manual")
+
+start_of_head_search = re.compile("(<head[^>]*>)", re.IGNORECASE).search
+start_of_body_search = re.compile("(<body[^>]*>)", re.IGNORECASE).search
+end_of_body_search = re.compile("(</body[^>]*>)", re.IGNORECASE).search
+
+default_html_page = """<html>
+<head>
+<title>Composite Page</title>
+</head>
+<body>
+%s
+</body>
+</html>
+"""
+
+close_dialog_html = '''<html>
+<script type="text/javascript">
+if (window.opener)
+ window.opener.location.reload();
+window.close();
+</script>
+</html>
+'''
+
+class CommonUI (SimpleItem):
+ """Basic page design UI.
+
+ Adds editing features to a rendered composite.
+ """
+
+ security = ClassSecurityInfo()
+
+ security.declarePublic(
+ "pdlib_js", "design_js", "pdstyles_css", "designstyles_css")
+ pdlib_js = RawFile("pdlib.js", "text/javascript", _common)
+ edit_js = RawFile("edit.js", "text/javascript", _common)
+ pdstyles_css = RawFile("pdstyles.css", "text/css", _common)
+ editstyles_css = InterpolatedFile("editstyles.css", "text/css", _common)
+ target_image = RawFile("target.gif", "image/gif", _common)
+ target_image_hover = RawFile("target_hover.gif", "image/gif", _common)
+ target_image_active = RawFile("target_active.gif", "image/gif", _common)
+ element_image = RawFile("element.gif", "image/gif", _common)
+
+ header_templates = (PageTemplateFile("header.pt", _common),)
+ top_templates = ()
+ bottom_templates = (PageTemplateFile("bottom.pt", _common),)
+
+ changeViewForm = PageTemplateFile("changeViewForm.pt", _common)
+
+ workspace_view_name = "view" # To be overridden
+
+ security.declarePublic("getFragments")
+ def getFragments(self, composite):
+ """Returns the fragments to be inserted in design mode.
+ """
+ params = {
+ "tool": aq_parent(aq_inner(self)),
+ "ui": self,
+ "composite": composite,
+ }
+ header = ""
+ top = ""
+ bottom = ""
+ for t in self.header_templates:
+ header += t.__of__(self)(**params)
+ for t in self.top_templates:
+ top += t.__of__(self)(**params)
+ for t in self.bottom_templates:
+ bottom += t.__of__(self)(**params)
+ return {"header": header, "top": top, "bottom": bottom}
+
+
+ security.declarePrivate("render")
+ def render(self, composite):
+ """Renders a composite, adding scripts and styles.
+ """
+ text = composite()
+ fragments = self.getFragments(composite)
+ match = start_of_head_search(text)
+ if match is None:
+ # Turn it into a page.
+ text = default_html_page % text
+ match = start_of_head_search(text)
+ if match is None:
+ raise CompositeError("Could not find header")
+ if fragments['header']:
+ index = match.end(0)
+ text = "%s%s%s" % (text[:index], fragments['header'], text[index:])
+ if fragments['top']:
+ match = start_of_body_search(text)
+ if match is None:
+ raise CompositeError("No 'body' tag found")
+ index = match.end(0)
+ text = "%s%s%s" % (text[:index], fragments['top'], text[index:])
+ if fragments['bottom']:
+ match = end_of_body_search(text)
+ if match is None:
+ raise CompositeError("No 'body' end tag found")
+ m = match
+ while m is not None:
+ # Find the *last* occurrence of "</body>".
+ match = m
+ m = end_of_body_search(text, match.end(0))
+ index = match.start(0)
+ text = "%s%s%s" % (text[:index], fragments['bottom'], text[index:])
+ return text
+
+
+ security.declarePublic("showElement")
+ def showElement(self, path, RESPONSE):
+ """Redirects to the workspace for an element.
+ """
+ root = self.getPhysicalRoot()
+ obj = root.restrictedTraverse(path)
+ RESPONSE.redirect("%s/%s" % (
+ obj.absolute_url(), self.workspace_view_name))
+
+
+ security.declarePublic("previewElement")
+ def previewElement(self, path, RESPONSE):
+ """Redirects to the preview for an element.
+ """
+ root = self.getPhysicalRoot()
+ obj = root.restrictedTraverse(path)
+ RESPONSE.redirect(obj.absolute_url())
+
+
+ security.declarePublic("showSlot")
+ def showSlot(self, path, RESPONSE):
+ """Redirects to (and possibly creates) the workspace for a slot.
+ """
+ from composite import Composite
+
+ obj = self.getPhysicalRoot()
+ parts = str(path).split('/')
+ for name in parts:
+ obj = obj.restrictedTraverse(name)
+ try:
+ is_comp = isinstance(obj, Composite)
+ except TypeError:
+ is_comp = 0 # Python 2.1 bug
+ if is_comp:
+ gen = guarded_getattr(obj, "generateSlots")
+ gen()
+ RESPONSE.redirect("%s/%s" % (
+ obj.absolute_url(), self.workspace_view_name))
+
+
+ security.declarePublic("getViewChangeInfo")
+ def getViewChangeInfo(self, paths):
+ """Returns information for changing the view applied to objects.
+ """
+ root = self.getPhysicalRoot()
+ tool = aq_parent(aq_inner(self))
+ obs = []
+ all_choices = None # {view -> 1}
+ current = None
+ for path in str(paths).split(':'):
+ ob = root.restrictedTraverse(path)
+ obs.append(ob)
+ renderer = tool.getRendererFor(ob)
+ m = guarded_getattr(renderer, "getInlineView")
+ view = m()
+ if current is None:
+ current = view
+ elif current and current != view:
+ # The current view isn't the same for all of the elements,
+ # so there is no common current view. Spell this condition
+ # using a non-string value.
+ current = 0
+ m = guarded_getattr(renderer, "listAllowableInlineViews")
+ views = m()
+ d = {}
+ for view in views:
+ d[view] = 1
+ if all_choices is None:
+ all_choices = d
+ else:
+ for view in all_choices.keys():
+ if not d.has_key(view):
+ del all_choices[view]
+ views = all_choices.keys()
+ views.sort()
+ return {"obs": obs, "views": views, "current_view": current}
+
+
+ security.declarePublic("changeView")
+ def changeView(self, paths, view, REQUEST=None):
+ """Changes the view for objects.
+ """
+ info = self.getViewChangeInfo(paths)
+ if view not in info["views"]:
+ raise KeyError("View %s is not among the choices" % view)
+ tool = aq_parent(aq_inner(self))
+ for ob in info["obs"]:
+ renderer = tool.getRendererFor(ob)
+ m = guarded_getattr(renderer, "setInlineView")
+ m(view)
+ if REQUEST is not None:
+ return close_dialog_html
+
+Globals.InitializeClass(CommonUI)
+
+
+
+class ZMIUI (CommonUI):
+ """Page design UI meant to fit the Zope management interface.
+
+ Adds editing features to a rendered composite.
+ """
+ security = ClassSecurityInfo()
+
+ workspace_view_name = "manage_workspace"
+
+ security.declarePublic("zmi_edit_js")
+ zmi_edit_js = RawFile("zmi_edit.js", "text/javascript", _zmi)
+
+ header_templates = CommonUI.header_templates + (
+ PageTemplateFile("header.pt", _zmi),)
+ top_templates = CommonUI.top_templates + (
+ PageTemplateFile("top.pt", _zmi),)
+ bottom_templates = (PageTemplateFile("bottom.pt", _zmi),
+ ) + CommonUI.bottom_templates
+
+Globals.InitializeClass(ZMIUI)
+
+
+
+class CMFUI (CommonUI):
+ """Page design UI meant to fit CMF.
+
+ Adds CMF-specific scripts and styles to a page.
+ """
+ security = ClassSecurityInfo()
+
+ workspace_view_name = "view"
+
+ security.declarePublic("cmf_edit_js")
+ cmf_edit_js = RawFile("cmf_edit.js", "text/javascript", _cmf)
+
+ header_templates = CommonUI.header_templates + (
+ PageTemplateFile("header.pt", _cmf),)
+ bottom_templates = (PageTemplateFile("bottom.pt", _cmf),
+ ) + CommonUI.bottom_templates
+
+Globals.InitializeClass(CMFUI)
+
+
+
+class ManualUI (CommonUI):
+ """Non-WYSIWYG page design UI.
+ """
+ security = ClassSecurityInfo()
+
+ body = PageTemplateFile("body.pt", _manual)
+ manual_styles_css = InterpolatedFile(
+ "manual_styles.css", "text/css", _manual)
+ header_templates = (PageTemplateFile("header.pt", _manual),)
+ manual_js = RawFile("manual.js", "text/javascript", _manual)
+
+ security.declarePublic("render")
+ def render(self, composite):
+ """Renders a composite, adding scripts and styles.
+
+ Returns an HTML fragment (not a full page).
+ """
+ slot_data = composite.getSlotData()
+ pt = self.body.__of__(composite)
+ return pt(slot_data=slot_data)
+
+Globals.InitializeClass(ManualUI)
=== Products/CompositePage/README.txt 1.5 => 1.6 ===
--- Products/CompositePage/README.txt:1.5 Mon Oct 13 13:21:47 2003
+++ Products/CompositePage/README.txt Thu Feb 26 16:38:11 2004
@@ -131,20 +131,20 @@
Rendering in edit mode:
-When requested, the composite renders its template and slots with edit
-mode turned on. In edit mode, slots add 'class', 'source_path',
-'target_path', and 'target_index' attributes to HTML tags to mark
-movable objects and available drop targets. Slots add HTML markup for
-drop targets automatically. When rendering using the single() method,
-slots provide a drop target only if the slot is empty. When rendering
-using the multiple() method, slots insert drop targets between the
-elements and to the beginning and end of the slot.
-
-After the composite is rendered, the rendered HTML is passed through a
-transformer. The transformer uses regular expressions to find the
-'head' and 'body' tags. Then the transformer inserts scripts, styles,
-and HTML elements. The result of the transformation is sent back to
-the browser.
+When requested, the composite calls upon a "UI" object to render its
+template and slots with edit mode turned on. In edit mode, slots add
+'class', 'source_path', 'target_path', and 'target_index' attributes
+to HTML tags to mark movable objects and available drop targets.
+Slots add HTML markup for drop targets automatically. When rendering
+using the single() method, slots provide a drop target only if the
+slot is empty. When rendering using the multiple() method, slots
+insert drop targets between the elements and to the beginning and end
+of the slot.
+
+The UI object can use various mechanisms to make the page editable.
+Most UI objects use regular expressions to find the 'head' and 'body'
+tags. Then the UI object inserts scripts, styles, and HTML elements.
+The result of the transformation is sent back to the browser.
Drag and drop:
@@ -192,10 +192,9 @@
CompositePage provides a default user interface that integrates with
the Zope management interface, but mechanisms are provided for
-integrating with any user interface. Look at transformers.py, the
-'common' subdirectory, and the 'zmi' subdirectory for guidance.
-Simple customizations probably do not require more code than the 'zmi'
-transformer.
+integrating with any user interface. Look at design.py, the 'common'
+subdirectory, and the 'zmi' subdirectory for guidance. Simple
+customizations probably do not require more code than ZMIUI.
=== Products/CompositePage/__init__.py 1.4 => 1.5 ===
--- Products/CompositePage/__init__.py:1.4 Fri Dec 26 15:43:30 2003
+++ Products/CompositePage/__init__.py Thu Feb 26 16:38:11 2004
@@ -15,11 +15,12 @@
$Id$
"""
-import tool, composite, slot, slotdef, transformers, interfaces
+import tool, composite, slot, slotdef, designuis, interfaces
-tool.registerTransformer("common", transformers.CommonTransformer())
-tool.registerTransformer("zmi", transformers.ZMITransformer())
-tool.registerTransformer("cmf", transformers.CMFTransformer())
+tool.registerUI("common", designuis.CommonUI())
+tool.registerUI("zmi", designuis.ZMIUI())
+tool.registerUI("cmf", designuis.CMFUI())
+tool.registerUI("manual", designuis.ManualUI())
def initialize(context):
=== Products/CompositePage/composite.py 1.9 => 1.10 ===
--- Products/CompositePage/composite.py:1.9 Wed Dec 31 12:32:14 2003
+++ Products/CompositePage/composite.py Thu Feb 26 16:38:11 2004
@@ -28,7 +28,7 @@
from AccessControl.ZopeGuards import guarded_getattr
from interfaces import ISlot, CompositeError
-from slot import Slot
+from slot import Slot, getIconURL
from macro import renderMacro, getRootMacro
import perm_names
@@ -39,10 +39,13 @@
"""Automatically makes slots available to the template.
"""
_slot_class = Slot
+ _v_used_slots = None
def __getitem__(self, name):
composite = aq_parent(aq_inner(self))
slots = composite.filled_slots
+ if self._v_used_slots is not None:
+ self._v_used_slots.append(name)
try:
return slots[name]
except (KeyError, AttributeError):
@@ -57,6 +60,17 @@
s._p_jar = jar
return s.__of__(slots)
+ def _beginCollection(self):
+ """Starts collecting the names of slots used.
+ """
+ self._v_used_slots = []
+
+ def _endCollection(self):
+ """Stops collecting slot names and returns the names in order of use.
+ """
+ res = self._v_used_slots
+ self._v_used_slots = None
+ return res
class Composite(Folder):
@@ -73,6 +87,7 @@
+ Folder.manage_options[2:]
)
+ default_ui = "common"
template_path = "template"
_v_editing = 0
_v_rendering = 0
@@ -138,25 +153,19 @@
index_html = None
security.declareProtected(perm_names.change_composites, "design")
- def design(self, transformer="common"):
+ def design(self, ui=None):
"""Renders the composite with editing features.
"""
- tool = aq_get(self, "composite_tool", None, 1)
- if tool is None:
- raise CompositeError("No composite_tool found")
-
# Never cache a design view.
req = getattr(self, "REQUEST", None)
if req is not None:
req["RESPONSE"].setHeader("Cache-Control", "no-cache")
-
+ ui_obj = self.getUI(ui)
self._v_editing = 1
try:
- text = self()
+ return ui_obj.render(self)
finally:
self._v_editing = 0
- tf = guarded_getattr(tool.transformers, transformer)
- return tf.transform(self, text)
security.declareProtected(perm_names.change_composites,
"manage_designForm")
@@ -165,8 +174,78 @@
"""
return self.design("zmi")
+ security.declareProtected(perm_names.change_composites, "getUI")
+ def getUI(self, ui=None):
+ """Returns a UI object.
+ """
+ if not ui:
+ ui = self.default_ui
+ tool = aq_get(self, "composite_tool", None, 1)
+ if tool is None:
+ raise CompositeError("No composite_tool found")
+ return guarded_getattr(tool.uis, ui)
+
+ security.declareProtected(perm_names.change_composites, "getSlotNames")
+ def getSlotNames(self):
+ """Returns the names of the slots in order of use.
+
+ May return duplicates.
+ """
+ self.slots._beginCollection()
+ try:
+ self()
+ finally:
+ names = self.slots._endCollection()
+ return names
+
+ security.declareProtected(perm_names.change_composites, "getSlotData")
+ def getSlotData(self):
+ """Prepares information about slot contents for presentation.
+ """
+ contents = [] # [{name, slot_info}]
+ seen = {}
+ names = self.getSlotNames()
+ if hasattr(self, 'portal_url'):
+ icon_base_url = self.portal_url()
+ else:
+ icon_base_url = self.REQUEST['BASEPATH1']
+ for name in names:
+ if seen.has_key(name):
+ # Don't show duplicate uses of a slot.
+ continue
+ seen[name] = 1
+ slot = self.slots[name]
+ elements = []
+ index = 0
+ slot_values = slot.objectValues()
+ for element in slot_values:
+ icon = getIconURL(element, icon_base_url)
+ element_info = {
+ 'title': element.title_or_id(),
+ 'icon': icon,
+ 'source_path': '/'.join(element.getPhysicalPath()),
+ 'index': index,
+ 'next_index': index + 1,
+ 'can_move_up': (index > 0),
+ 'can_move_down': (index < len(slot_values) - 1),
+ 'view': 'xxx',
+ 'available_views': ('yyy', 'zzz'),
+ }
+ elements.append(element_info)
+ index += 1
+ slot_info = {
+ 'title': name, # XXX need to get a real slot title somehow.
+ 'slot': slot,
+ 'target_path': '/'.join(slot.getPhysicalPath()),
+ 'elements': elements,
+ }
+ contents.append(slot_info)
+ return contents
+
security.declareProtected(perm_names.view, "isEditing")
def isEditing(self):
+ """Returns true if currently rendering in design mode.
+ """
return self._v_editing
Globals.InitializeClass(Composite)
=== Products/CompositePage/slot.py 1.14 => 1.15 ===
--- Products/CompositePage/slot.py:1.14 Tue Jan 6 11:38:28 2004
+++ Products/CompositePage/slot.py Thu Feb 26 16:38:11 2004
@@ -166,19 +166,7 @@
text = self._handleError(editing)
if editing:
- base = aq_base(obj)
-
- if hasattr(base, 'getIcon'):
- icon = str(obj.getIcon())
- elif hasattr(base, 'icon'):
- icon = str(obj.icon)
- else:
- icon = ""
- if icon and '://' not in icon:
- if not icon.startswith('/'):
- icon = '/' + icon
- icon = icon_base_url + icon
-
+ icon = getIconURL(obj, icon_base_url)
title = obj.title_and_id()
path = escape('/'.join(obj.getPhysicalPath()))
res.append(edit_tag % (path,
@@ -220,6 +208,20 @@
Globals.InitializeClass(Slot)
+
+def getIconURL(obj, icon_base_url):
+ base = aq_base(obj)
+ if hasattr(base, 'getIcon'):
+ icon = str(obj.getIcon())
+ elif hasattr(base, 'icon'):
+ icon = str(obj.icon)
+ else:
+ icon = ""
+ if icon and '://' not in icon:
+ if not icon.startswith('/'):
+ icon = '/' + icon
+ icon = icon_base_url + icon
+ return icon
addSlotForm = PageTemplateFile("addSlotForm", _www)
=== Products/CompositePage/tool.py 1.8 => 1.9 ===
--- Products/CompositePage/tool.py:1.8 Sat Dec 27 23:32:47 2003
+++ Products/CompositePage/tool.py Thu Feb 26 16:38:11 2004
@@ -30,22 +30,22 @@
from utils import copyOf
-_transformers = {}
+_uis = {}
-def registerTransformer(name, obj):
- """Registers a transformer for use with the composite tool.
+def registerUI(name, obj):
+ """Registers a page design UI for use with the composite tool.
"""
- if _transformers.has_key(name):
- raise RuntimeError("There is already a transformer named %s" % name)
+ if _uis.has_key(name):
+ raise RuntimeError("There is already a UI named %s" % name)
obj._setId(name)
- _transformers[name] = obj
+ _uis[name] = obj
-class Transformers(SimpleItem):
- """The container of transformer objects.
+class DesignUIs(SimpleItem):
+ """The container of design user interface objects.
- Makes page transformers accessible through URL traversal.
+ Makes page design UIs accessible through URL traversal.
"""
def __init__(self, id):
@@ -53,7 +53,7 @@
def __getattr__(self, name):
try:
- return _transformers[name]
+ return _uis[name]
except KeyError:
raise AttributeError, name
@@ -75,8 +75,8 @@
security = ClassSecurityInfo()
- security.declarePublic("transformers")
- transformers = Transformers("transformers")
+ security.declarePublic("uis")
+ uis = DesignUIs("uis")
_properties = Folder._properties + (
{'id': 'default_inline_views', 'mode': 'w', 'type': 'lines',
More information about the Zope-CVS
mailing list