===================
Formlib KSS actions
===================

This doctest demonstrates KSS-based inline validation and editing for formlib
forms.

First, let's set up KSS debug mode:

    >>> from zope.interface import alsoProvides
    >>> from kss.core.tests.base import IDebugRequest
    >>> from zope.publisher.browser import TestRequest
    >>> from zope.annotation.interfaces import IAttributeAnnotatable

    >>> def make_request(form={}):
    ...     request = TestRequest()
    ...     request.form.update(form)
    ...     alsoProvides(request, IDebugRequest)
    ...     alsoProvides(request, IAttributeAnnotatable)
    ...     return request

Then, we will create a simple test form and a context for it to operate on.

    >>> from zope.interface import Interface
    >>> from zope import schema
    >>> class IWibble(Interface):
    ...     title = schema.TextLine(title=u"Title", required=True)
    ...     text = schema.Text(title=u"Description")
    ...     size = schema.Int(title=u"Size")
    
    >>> from plone.app.content.item import Item
    >>> from zope.interface import implements
    >>> class Wibble(Item):
    ...     implements(IWibble)
    ...     title = u""
    ...     text = u""
    ...     size = 0

    
    >>> from zope.formlib import form
    >>> from plone.app.form import base
    >>> from Products.Five import BrowserView
    >>> class EditWibble(BrowserView, base.EditForm):
    ...     form_fields = form.FormFields(IWibble)
    ...     label = "Edit wibble"

    >>> from zope.component import provideAdapter
    >>> from zope.publisher.interfaces.browser import IBrowserRequest
    >>> provideAdapter(adapts=(IWibble, IBrowserRequest),
    ...                provides=Interface,
    ...                factory=EditWibble,
    ...                name=u"edit")

Let's verify that this worked:

    >>> from zope.component import getMultiAdapter
    >>> context = Wibble('wibble')
    >>> request = make_request()
    >>> getMultiAdapter((context, request), name=u"edit")
    <EditWibble object at ...>

    >>> del context, request

Inline editing
--------------

Inline editing is effectuated by KSS bindings that invoke, cancel or complete
the editing process.

Let's first simulate editing the title. To make it more realistic, we will
set the title first.

    >>> context = Wibble('wibble')
    >>> context.title = u"Test title"

When inline editing is invoked, KSS actions will be returned that render an
inline form next to the original field.

    >>> request = make_request()
    >>> view = getMultiAdapter((context, request,), name=u"kss_formlib_inline_edit_begin")
    >>> view.begin(formname=u'edit', fieldname=u'form.title')
    [{'selectorType': 'css', 
      'params': {'name': u'display', 'value': u'none'}, 
      'name': 'setStyle', 
      'selector': '.portalMessage'}, 
     {'selectorType': 'htmlid', 
      'params': {'html': u'<![CDATA[<dt>Info</dt><dd></dd>]]>', 'withKssSetup': u'True'}, 
      'name': 'replaceInnerHTML', 
      'selector': 'kssPortalMessage'},
     {'selectorType': 'htmlid', 
      'params': {'name': u'class', 'value': u'portalMessage info'}, 
      'name': 'setAttribute', 'selector': 'kssPortalMessage'}, 
     {'selectorType': 'htmlid', 
      'params': {'name': u'display', 'value': u'none'}, 
      'name': 'setStyle', 'selector': 'kssPortalMessage'}, 
     {'selectorType': 'htmlid', 
      'params': {'value': u'hiddenStructure'}, 
      'name': 'addClass', 'selector': u'title-display'}, 
     {'selectorType': 'htmlid', 
      'params': {'html':  ..., 'withKssSetup': u'True'}, 
      'name': 'insertHTMLAfter', 
      'selector': u'title-display'}]

Other KSS actions are bound to the rendered form. One is to cancel. This 
simply removes the inline form created by the begin() action.

    >>> request = make_request()
    >>> view = getMultiAdapter((context, request,), name=u"kss_formlib_inline_edit_cancel")
    >>> view.cancel(fieldname=u'form.title')
    [{'selectorType': 'htmlid', 
      'params': {'value': u'hiddenStructure'}, 
      'name': 'removeClass', 
      'selector': u'form.title-display'}, 
     {'selectorType': 'htmlid', 
      'params': {}, 
      'name': 'deleteNode', 
      'selector': u'form.title-form'}]

In this case, the underlying context should not have been changed:

    >>> context.title
    u'Test title'
    
Alternatively, the user may choose to save. If there is a validation error,
we will return this (note that inline validation may also apply, and thus
may catch an error earlier).

    >>> request = make_request()
    >>> request.form['form.title'] = u''
    >>> view = getMultiAdapter((context, request,), name=u"kss_formlib_inline_edit_save")
    >>> view.save(formname=u'edit', fieldname=u'form.title')
    [{'selectorType': 'css', 
      'params': {'html': u'<![CDATA[<span class="error">Required input is missing.</span>]]>', 'withKssSetup': u'True'}, 
      'name': 'replaceInnerHTML', 
      'selector': u'#formfield-form-title div.fieldErrorBox'}, 
     {'selectorType': 'htmlid', 
      'params': {'value': u'error'}, 
      'name': 'addClass', 
      'selector': u'formfield-form-title'}]

    >>> context.title
    u'Test title'

When the error is corrected, things look different:

    >>> request = make_request()
    >>> request.form['form.title'] = u'New <b>title</b>'
    >>> view = getMultiAdapter((context, request,), name=u"kss_formlib_inline_edit_save")
    >>> view.save(formname=u'edit', fieldname=u'form.title')
    [{'selectorType': 'css', 
      'params': {}, 
      'name': 'clearChildNodes', 
      'selector': u'#formfield-form-title div.fieldErrorBox'}, 
     {'selectorType': 'htmlid', 
      'params': {'value': u'error'}, 
      'name': 'removeClass', 
      'selector': u'formfield-form-title'}, 
     {'selectorType': 'htmlid', 
      'params': {'html': u'<![CDATA[New &#60;b&#62;title&#60;/b&#62;]]>', 'withKssSetup': u'True'}, 
      'name': 'replaceInnerHTML', 'selector': u'form.title-display'}, 
     {'selectorType': 'htmlid', 
      'params': {'value': u'hiddenStructure'}, 
      'name': 'removeClass', 
      'selector': u'form.title-display'}, 
     {'selectorType': 'htmlid', 
      'params': {}, 'name': 
      'deleteNode', 
      'selector': u'form.title-form'}]

    >>> context.title
    u'New <b>title</b>'
    
In the example above, notice how the instruction to render the return value
escapes the HTML. To get around that, the template author can use the KSS 
attribute 'structure'. This is mainly useful for rich text fields. This
needs to be passed to both edit-begin and edit-save.

    >>> context.text = u"Body <em>text</em>"

    >>> request = make_request()
    >>> view = getMultiAdapter((context, request,), name=u"kss_formlib_inline_edit_begin")
    >>> view.begin(formname=u'edit', fieldname=u'form.text', structure='true')
    [{'selectorType': 'css', 
      'params': {'name': u'display', 'value': u'none'}, 
      'name': 'setStyle', 
      'selector': '.portalMessage'}, 
     {'selectorType': 'htmlid', 
      'params': {'html': u'<![CDATA[<dt>Info</dt><dd></dd>]]>', 'withKssSetup': u'True'}, 
      'name': 'replaceInnerHTML', 
      'selector': 'kssPortalMessage'},
     {'selectorType': 'htmlid', 
      'params': {'name': u'class', 'value': u'portalMessage info'}, 
      'name': 'setAttribute', 'selector': 'kssPortalMessage'}, 
     {'selectorType': 'htmlid', 
      'params': {'name': u'display', 'value': u'none'}, 
      'name': 'setStyle', 'selector': 'kssPortalMessage'}, 
     {'selectorType': 'htmlid', 
      'params': {'value': u'hiddenStructure'}, 
      'name': 'addClass', 'selector': u'text-display'}, 
     {'selectorType': 'htmlid', 
      'params': {'html': ..., 'withKssSetup': u'True'}, 
      'name': 'insertHTMLAfter', 
      'selector': u'text-display'}]

    >>> request = make_request()
    >>> request.form['form.text'] = u'New <strong>text</strong>'
    >>> view = getMultiAdapter((context, request,), name=u"kss_formlib_inline_edit_save")
    >>> view.save(formname=u'edit', fieldname=u'form.text', structure='true')
    [{'selectorType': 'css', 
      'params': {}, 
      'name': 'clearChildNodes', 
      'selector': u'#formfield-form-text div.fieldErrorBox'}, 
     {'selectorType': 'htmlid', 
      'params': {'value': u'error'}, 
      'name': 'removeClass', 
      'selector': u'formfield-form-text'}, 
     {'selectorType': 'htmlid', 
      'params': {'html':  u'<![CDATA[New <strong>text</strong>]]>', 'withKssSetup': u'True'}, 
      'name': 'replaceInnerHTML', 
      'selector': u'form.text-display'}, 
     {'selectorType': 'htmlid', 
      'params': {'value': u'hiddenStructure'}, 
      'name': 'removeClass', 'selector': u'form.text-display'}, 
     {'selectorType': 'htmlid', 
      'params': {}, 
      'name': 'deleteNode', 
      'selector': u'form.text-form'}]

    >>> context.text
    u'New <strong>text</strong>'
