use dojo.Dialog for entry forms; set up INote resource as first example (and start to clean-up retrieving views for resources)

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1352 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2006-09-19 21:16:26 +00:00
parent d5532f1c72
commit 980114d649
16 changed files with 341 additions and 44 deletions

View file

@ -18,6 +18,7 @@ with lower-level aspects like type or state management.
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
>>> site = placefulSetUp(True)
>>> from zope import component
>>> from zope.app import zapi
>>> from zope.app.tests import ztapi
>>> from zope.interface import Interface
@ -122,7 +123,7 @@ type manager.
>>> from cybertools.typology.interfaces import ITypeManager
>>> from loops.interfaces import ILoopsObject
>>> from loops.type import LoopsTypeManager
>>> from loops.type import LoopsTypeManager, LoopsType
>>> ztapi.provideAdapter(ILoopsObject, ITypeManager, LoopsTypeManager)
>>> from loops.concept import ConceptTypeSourceList
@ -545,6 +546,8 @@ and of a lot of other stuff needed for the rendering machine.
... with=(IBrowserRequest,))
>>> m112.target = doc1
>>> component.provideAdapter(LoopsType)
>>> view = NodeView(m112, TestRequest())
>>> view.renderTarget()
u''

View file

@ -42,6 +42,7 @@ from cybertools.browser.view import GenericView
from cybertools.relation.interfaces import IRelationRegistry
from cybertools.typology.interfaces import IType, ITypeManager
from loops.interfaces import IView
from loops.type import ITypeConcept
from loops import util
from loops.util import _
@ -135,18 +136,39 @@ class BaseView(GenericView):
def value(self):
return self.context
@Lazy
def type(self):
return IType(self.context)
@Lazy
def typeProvider(self):
type = self.type
if type is not None:
return type.typeProvider
@Lazy
def typeInterface(self):
provider = self.typeProvider
if provider is not None:
tc = ITypeConcept(provider)
return tc.typeInterface
@Lazy
def typeAdapter(self):
ifc = self.typeInterface
if ifc is not None:
return ifc(self.context)
@Lazy
def typeTitle(self):
type = IType(self.context)
type = self.type
return type is not None and type.title or None
@Lazy
def typeUrl(self):
type = IType(self.context)
if type is not None:
provider = type.typeProvider
if provider is not None:
return zapi.absoluteURL(provider, self.request)
provider = self.typeProvider
if provider is not None:
return zapi.absoluteURL(provider, self.request)
return None
def viewIterator(self, objs):
@ -161,6 +183,12 @@ class BaseView(GenericView):
for t in ITypeManager(self.context).types])
+ [('loops:*', 'Any')])
def conceptTypesForSearch(self):
general = [('loops:concept:*', 'Any Concept'),]
return util.KeywordVocabulary(general + sorted([(t.tokenForSearch, t.title)
for t in ITypeManager(self.context).types
if 'concept' in t.qualifiers]))
@Lazy
def uniqueId(self):
return zapi.getUtility(IRelationRegistry).getUniqueIdForObject(self.context)

View file

@ -324,6 +324,15 @@
permission="zope.View"
/>
<zope:adapter
name="note.html"
for="loops.interfaces.IResource
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.interface.Interface"
factory="loops.browser.resource.NoteView"
permission="zope.View"
/>
<!-- media asset -->
<!--<editform
@ -548,6 +557,15 @@
permission="zope.View"
/>
<!-- forms (end-user view) -->
<page
name="create_object.html"
for="loops.interfaces.INode"
class="loops.browser.node.CreateObject"
permission="zope.ManageContent"
/>
<!-- inner HTML views -->
<page

70
browser/form_macros.pt Normal file
View file

@ -0,0 +1,70 @@
<metal:block define-macro="create">
<div>
<form method="get">
<input type="hidden" name="form.action" value="create"
tal:attributes="value view/form_action" />
<table cellpadding="3" class="form">
<tr>
<th colspan="4">
<span tal:replace="view/title"
i18n:translate="">Create Information Object</span>
</th>
</tr>
<tr tal:repeat="widget view/widgets">
<td class="label"
tal:define="hint widget/hint">
<label tal:attributes="for widget/name">
<span class="required" tal:condition="widget/required"
>*</span><span i18n:translate=""
tal:content="widget/label">label</span>
</label>
</td>
<td class="field" colspan="3"
tal:define="hint widget/hint">
<div class="widget" tal:content="structure widget">
<input type="text" />
</div>
<div class="error"
tal:condition="widget/error">
<span tal:replace="structure widget/error">error</span>
</div>
</td>
</tr>
<tr>
<td colspan="4" class="headline">Assign Concept(s)</td>
</tr>
<tr>
<td><label for="dlg.create.search.type">Type:</label></td>
<td>
<select name="dlg.create.search.type" id="dlg.create.search.type"
tal:attributes="onChange
string:setConceptTypeForComboBox(
'dlg.create.search.type', 'dlg.create.search.text')">
<tal:types repeat="type view/conceptTypesForSearch">
<option value="loops:*"
i18n:translate=""
tal:attributes="value type/token"
tal:content="type/title">Topic</option>
</tal:types>
</select>&nbsp;&nbsp;
</td>
<td><label for="dlg.create.search.text">Search text:</label></td>
<td>
<input dojoType="comboBox" mode="remote" autoComplete="False"
name="dlg.create.search.text" id="dlg.create.search.text"
tal:attributes="dataUrl
string:${context/@@absolute_url}/listConceptsForComboBox.js?searchString=%{searchString}&searchType=" />
</td>
</tr>
<tr>
<td colspan="4">
<input type="button" value="Cancel"
onclick="createObjectDlg.hide()">
<input type="submit" value="Save"
onclick="createObjectDlg.hide()">
</td>
</tr>
</table>
</form>
</div>
</metal:block>

View file

@ -84,3 +84,34 @@ div.image {
font-weight: bold;
}
/* dojo stuff */
.dojoComboBox {
width: 200px;
}
.dojoDialog {
background: #eee;
border: 1px solid #999;
-moz-border-radius: 5px;
padding: 4px;
}
.dojoDialog th {
font-size: 120%;
padding: 0 5px 8px 5px;
}
.dojoDialog .headline {
font-weight: bold;
}
.dojoDialog input.text {
width: 100%;
margin-right: 10px;
}
.dojoDialog input.submit {
font-weight: bold;
}

View file

@ -13,9 +13,9 @@ function focusOpener() {
}
}
function listConceptsForComboBox() {
/*function listConceptsForComboBox() {
return [['Zope', 'zope'], ]
}
}*/
function submitReplacing(targetId, formId, actionUrl) {
dojo.io.updateNode(targetId, {
@ -27,17 +27,21 @@ function submitReplacing(targetId, formId, actionUrl) {
}
function inlineEdit(id, saveUrl) {
var iconNode = dojo.byId("inlineedit_icon");
iconNode.style.visibility = "hidden";
var editor = dojo.widget.fromScript("Editor",
{items: ["save", "|", "formatblock", "|",
"insertunorderedlist", "insertorderedlist", "|",
"bold", "italic", "|", "createLink", "insertimage"],
//dojo.require('dojo.widget.Editor');
var iconNode = dojo.byId('inlineedit_icon');
iconNode.style.visibility = 'hidden';
var editor = dojo.widget.fromScript('Editor',
{items: ['save', '|', 'formatblock', '|',
'insertunorderedlist', 'insertorderedlist', '|',
'bold', 'italic', '|', 'createLink', 'insertimage'],
saveUrl: saveUrl,
closeOnSave: true,
onSave: function(){
htmlEditing: true,
onSave: function() {
this.disableToolbar(true);
iconNode.style.visibility = "visible";}
iconNode.style.visibility = 'visible';
//window.location.reload();
}
}, dojo.byId(id));
return false;
}
@ -50,3 +54,19 @@ function setConceptTypeForComboBox(typeId, cbId) {
dp.searchUrl = newUrl;
}
var createObjectDlg = false;
function createObjectDialog() {
//createObjectDlg = dojo.widget.byId('createObject');
//createObjectDlg = false;
dojo.require('dojo.widget.Dialog');
dojo.require('dojo.widget.ComboBox');
if (!createObjectDlg) {
createObjectDlg = dojo.widget.fromScript('Dialog',
{bgColor: 'white', bgOpacity: 0.5, toggle: 'fade', toggleDuration: 250,
executeScripts: true,
href: 'create_object.html'
}, dojo.byId('createObject'));
}
createObjectDlg.show();
}

View file

@ -22,7 +22,7 @@ View class for Node objects.
$Id$
"""
from zope import component, interface
from zope import component, interface, schema
from zope.cachedescriptors.property import Lazy
from zope.app import zapi
from zope.app.annotation.interfaces import IAnnotations
@ -34,6 +34,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.dottedname.resolve import resolve
from zope.event import notify
from zope.formlib.form import Form, FormFields
from zope.formlib.namedtemplate import NamedTemplate
from zope.proxy import removeAllProxies
from zope.security import canAccess, canWrite
@ -50,6 +51,7 @@ from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode
from loops.interfaces import IViewConfiguratorSchema
from loops.resource import MediaAsset
from loops import util
from loops.util import _
from loops.browser.common import BaseView
from loops.browser.concept import ConceptView
@ -70,16 +72,16 @@ class NodeView(BaseView):
resourceName='loops.css', media='all')
cm.register('js', 'loops.js', resourceName='loops.js')
cm.register('portlet_left', 'navigation', title='Navigation',
subMacro=self.template.macros['menu'])
subMacro=self.template.macros['menu'])
if not IUnauthenticatedPrincipal.providedBy(self.request.principal):
cm.register('portlet_right', 'clipboard', title='Clipboard',
subMacro=self.template.macros['clipboard'])
subMacro=self.template.macros['clipboard'])
# this belongs to loops.organize; how to register portlets
# from sub- (other) packages?
# see controller / configurator: use multiple configurators;
# register additional configurators (adapters) from within package.
cm.register('portlet_right', 'worklist', title='Worklist',
subMacro=self.template.macros['worklist'])
cm.register('portlet_right', 'actions', title='Actions',
subMacro=self.template.macros['actions'])
@Lazy
def view(self):
@ -156,6 +158,7 @@ class NodeView(BaseView):
def renderTarget(self):
target = self.target
#targetAdapter = target.typeAdapter
return target is not None and target.render() or u''
@Lazy
@ -164,9 +167,10 @@ class NodeView(BaseView):
@Lazy
def bodyMacro(self):
# TODO: replace by: return self.target.macroName
# TODO: replace by something like: return self.target.macroName
target = self.targetObject
if target is None or IDocument.providedBy(target):
if (target is None or IDocument.providedBy(target)
or (IResource.providedBy(target) and target.contentType.startswith('text/'))):
return 'textbody'
if IConcept.providedBy(target):
return 'conceptbody'
@ -295,6 +299,12 @@ class NodeView(BaseView):
cm.register('js-execute', jsCall, jsCall=jsCall)
return 'return inlineEdit("%s", "%s/inline_save")' % (id, self.virtualTargetUrl)
def registerDojoDialog(self):
self.registerDojo()
cm = self.controller.macros
jsCall = 'dojo.require("dojo.widget.Dialog")'
cm.register('js-execute', jsCall, jsCall=jsCall)
# inner HTML views
@ -314,10 +324,43 @@ class InlineEdit(NodeView):
def save(self):
target = self.virtualTargetObject
target.data = self.request.form['editorContent']
data = self.request.form['editorContent']
if type(data) != unicode:
try:
data = data.decode('ISO-8859-15') # IE hack
except UnicodeDecodeError:
print 'loops.browser.node.InlineEdit.save():', data
return
# data = data.decode('UTF-8')
target.data = data
notify(ObjectModifiedEvent(target, Attributes(IResource, 'data')))
class CreateObject(NodeView, Form):
template = ViewPageTemplateFile('form_macros.pt')
@property
def macro(self): return self.template.macros['create']
form_fields = FormFields(
schema.TextLine(__name__='title', title=_(u'Title')),
schema.Text(__name__='body', title=_(u'Body Text')),
schema.TextLine(__name__='linkUrl', title=_(u'Link'), required=False),
)
title = _(u'Enter Note')
form_action = 'create_note'
def __init__(self, context, request):
super(CreateObject, self).__init__(context, request)
self.setUpWidgets()
self.widgets['body'].height = 3
def __call__(self):
return innerHtml(self)
# special (named) views for nodes
class SpecialNodeView(NodeView):

View file

@ -194,12 +194,20 @@
<!-- portlets -->
<metal:actions define-macro="clipboard">
<div class="menu-2">loops Development</div>
<div class="menu-2">loops Development</div>
</metal:actions>
<metal:actions define-macro="worklist">
<div class="menu-2">Create Resource...</div>
<metal:actions define-macro="actions">
<div class="menu-2"
tal:define="dummy view/registerDojo">
<a href="#"
onclick="createObjectDialog(); return false;">
Create Resource...
</a>
<span id="createObject">
</span>
</div>
</metal:actions>

View file

@ -23,6 +23,7 @@ $Id$
"""
from zope.cachedescriptors.property import Lazy
from zope import component
from zope.app import zapi
from zope.app.catalog.interfaces import ICatalog
from zope.app.dublincore.interfaces import ICMFDublinCore
@ -39,6 +40,7 @@ from loops.interfaces import IBaseResource, IDocument, IMediaAsset
from loops.browser.common import EditForm, BaseView
from loops.browser.concept import ConceptRelationView, ConceptConfigureView
from loops.browser.node import NodeView
from loops.interfaces import ITypeConcept
renderingFactories = {
'text/plain': 'zope.source.plaintext',
@ -60,7 +62,8 @@ class ResourceEditForm(EditForm):
fields = FormFields(IBaseResource)
typeInterface = self.typeInterface
if typeInterface is not None:
fields = FormFields(fields, typeInterface)
omit = [f for f in typeInterface if f in IBaseResource]
fields = FormFields(fields.omit(*omit), typeInterface)
return fields
@ -94,6 +97,20 @@ class ResourceView(BaseView):
subMacro=self.template.macros['related'],
position=0, info=self)
@Lazy
def view(self):
context = self.context
tp = IType(context).typeProvider
if tp:
viewName = ITypeConcept(tp).viewName
if viewName:
return component.queryMultiAdapter((context, self.request),
name=viewName)
if context.contentType.startswith('text/'):
# TODO: This should be controlled by resourceType
return DocumentView(context, self.request)
return self
def show(self):
data = self.context.data
response = self.request.response
@ -173,6 +190,9 @@ class DocumentView(ResourceView):
def macro(self):
return ResourceView.template.macros['render']
@Lazy
def view(self): return self
def render(self):
""" Return the rendered content (data) of the context object.
"""
@ -189,3 +209,16 @@ class DocumentView(ResourceView):
return (self.inlineEditingActive
and self.context.contentType == 'text/html'
and canWrite(self.context, 'data'))
class NoteView(DocumentView):
@property
def macro(self):
return ResourceView.template.macros['render_note']
@property
def linkUrl(self):
ad = self.typeAdapter
return ad and ad.linkUrl or ''

View file

@ -17,6 +17,16 @@
</metal:block>
<metal:block define-macro="render_note">
<metal:render use-macro="item/template/macros/render" />
<div class="content-1" id="1.link">
<a href="."
tal:attributes="href item/linkUrl"
tal:condition="item/linkUrl">more...</a>
</div>
</metal:block>
<metal:block define-macro="image">
<div tal:attributes="ondblclick python: item.openEditWindow('edit.html')">
<h3 tal:content="item/title">Title</h3>

View file

@ -319,6 +319,14 @@
set_schema="loops.interfaces.IFile" />
</class>
<adapter factory="loops.resource.NoteAdapter" trusted="True" />
<class class="loops.resource.NoteAdapter">
<require permission="zope.View"
interface="loops.interfaces.INote" />
<require permission="zope.ManageContent"
set_schema="loops.interfaces.INote" />
</class>
<adapter factory="loops.setup.SetupManager" />
<adapter factory="loops.external.NodesLoader" />

View file

@ -532,7 +532,13 @@ class ITypeConcept(Interface):
source="loops.TypeInterfaceSource",
required=False)
# viewName = schema.TextLine()
viewName = schema.TextLine(
title=_(u'View name'),
description=_(u'Name of a special view be used for presenting '
'objects of this type.'),
default=u'',
required=False)
# storage = schema.Choice()
@ -554,13 +560,24 @@ class IImage(IResourceAdapter):
"""
class ITextDocument(IResourceAdapter):
class ITextDocument(IResourceAdapter, IDocumentSchema):
""" A resource containing some sort of plain text that may be rendered and
edited without necessarily involving a special external application
(like e.g. OpenOffice); typical content types are text/html, text/xml,
text/restructured, etc.
"""
class INote(ITextDocument):
""" Typically a short piece of text; in addition a note may contain
a URL linking it to more information.
"""
linkUrl = schema.TextLine(
title=_(u'Link URL'),
description=_(u'An (optional) link associated with this note'),
default=u'',
required=False)
# view configurator stuff

View file

@ -32,8 +32,7 @@ from zope.security.proxy import removeSecurityProxy
from cybertools.organize.interfaces import IPerson as IBasePerson
from loops.organize.util import getPrincipalFolder, authPluginId
_ = MessageFactory('zope')
from loops.util import _
ANNOTATION_KEY = 'loops.organize.person'
@ -49,7 +48,7 @@ def raiseValidationError(info):
class UserId(schema.TextLine):
def _validate(self, userId):
from loops.organize.party import getPersonForUser
if not userId:

View file

@ -45,7 +45,7 @@ from cybertools.relation.interfaces import IRelatable
from cybertools.typology.interfaces import ITypeManager
from interfaces import IBaseResource, IResource
from interfaces import IFile
from interfaces import IFile, INote
from interfaces import IDocument, IDocumentSchema, IDocumentView
from interfaces import IMediaAsset, IMediaAssetView
from interfaces import IResourceManager, IResourceManagerContained
@ -196,6 +196,16 @@ class FileAdapter(ResourceAdapterBase):
implements(IFile)
class NoteAdapter(ResourceAdapterBase):
""" A type adapter for providing note functionality for resources.
"""
implements(INote)
_schemas = list(INote) + list(IBaseResource)
#contentType = u'text/restructured'
class DocumentWriteFileAdapter(object):
implements(IWriteFile)

View file

@ -63,12 +63,6 @@ class Search(BaseView):
self.maxRowNum = n
return n
def conceptTypesForSearch(self):
general = [('loops:concept:*', 'Any Concept'),]
return util.KeywordVocabulary(general + sorted([(t.tokenForSearch, t.title)
for t in ITypeManager(self.context).types
if 'concept' in t.qualifiers]))
def initDojo(self):
self.registerDojo()
cm = self.controller.macros

11
type.py
View file

@ -33,7 +33,7 @@ from cybertools.typology.type import BaseType, TypeManager
from cybertools.typology.interfaces import ITypeManager
from loops.interfaces import ILoopsObject, IConcept, IResource
from loops.interfaces import ITypeConcept
from loops.interfaces import IResourceAdapter, IFile, IImage, ITextDocument
from loops.interfaces import IResourceAdapter, IFile, IImage, ITextDocument, INote
from loops.concept import Concept
from loops.resource import Resource, Document, MediaAsset
from loops.common import AdapterBase
@ -43,7 +43,7 @@ class LoopsType(BaseType):
adapts(ILoopsObject)
factoryMapping = dict(concept=Concept, resource=Resource)
factoryMapping = dict(concept=Concept, resource=Resource, document=Document)
containerMapping = dict(concept='concepts', resource='resources')
@Lazy
@ -83,6 +83,11 @@ class LoopsType(BaseType):
@Lazy
def factory(self):
ti = self.typeInterface
#if ti is not None:
# fn = getattr(ti, 'factoryName', None)
# if fn:
# return self.factoryMapping.get(fn, Concept)
return self.factoryMapping.get(self.qualifiers[0], Concept)
@Lazy
@ -241,7 +246,7 @@ class TypeInterfaceSourceList(object):
implements(schema.interfaces.IIterableSource)
typeInterfaces = (ITypeConcept, IFile, ITextDocument)
typeInterfaces = (ITypeConcept, IFile, ITextDocument, INote)
def __init__(self, context):
self.context = context