src/source/howto-paragraph.rst
changeset 90 06915aa059c5
parent 87 a5c0ec15b437
child 97 9783741e866d
--- a/src/source/howto-paragraph.rst	Fri May 25 14:28:29 2018 +0200
+++ b/src/source/howto-paragraph.rst	Tue Jun 19 16:11:57 2018 +0200
@@ -1,5 +1,328 @@
 .. _paragraphhowto:
 
 
-How to create a new paragraph type?
-===================================
+How to create a Paragraph type?
+===============================
+
+Paragraphs or Blocks are components that contain elements/fields and provide one or many renderer methods to compose
+the front office website
+
+
+Create a Paragraph
+""""""""""""""""""
+
+In this example we will create a contact paragraph to display at the user, who to contact.
+
+1) Interface
+------------
+
+At first we create a new paragraph interface.
+
+.. code-block:: python
+
+    CONTACT_PHONE_PARAGRAPH_TYPE = 'PhoneContact'
+    CONTACT_PHONE_PARAGRAPH_RENDERERS = 'PyAMS.paragraph.contact.phone.renderers'
+
+
+    class IContactPhoneParagraph(IBaseParagraph):
+    """Contact with phone number paragraph interface"""
+
+    name = TextLine(title=_("Contact identity"),
+                    description=_("Name of the contact"),
+                    required=True)
+
+    photo = ImageField(title=_("Photo"),
+                       description=_("Use 'browse' button to select contact picture"),
+                       required=False)
+
+    phone = TextLine(title=_("Phone Number"),
+                     description=_("Name of the contact", required=False))
+
+    renderer = Choice(title=_("Contact template"),
+                      description=_("Presentation template used for this contact"),
+                      vocabulary=CONTACT_PHONE_PARAGRAPH_RENDERERS,
+                      default='default')
+
+
+
+
+2) Implement the interface
+--------------------------
+
+An implementation of the interface
+
+.. code-block:: python
+
+    @implementer(IContactPhoneParagraph)
+    @factory_config(provided=IContactPhoneParagraph)
+    class ContactPhoneParagraph(BaseParagraph):
+        """Contact paragraph"""
+
+
+        icon_class = 'fa-phone'
+        icon_hint = _("Phone number card")
+
+        name = FieldProperty(IContactPhoneParagraph['name'])
+        _photo = FileProperty(IContactPhoneParagraph['photo'])
+
+        renderer = FieldProperty(IContactParagraph['renderer'])
+
+        @property
+        def photo(self):
+            return self._photo
+
+        @photo.setter
+        def photo(self, value):
+            self._photo = value
+            if IImage.providedBy(self._photo):
+                alsoProvides(self._photo, IResponsiveImage)
+
+
+3) Renderers Vocabulary
+-----------------------
+
+The list of rendered available for a paragraph is build automatically and is based on adapters that provide this interface
+
+.. code-block:: python
+
+    @vocabulary_config(name=CONTACT_PARAGRAPH_RENDERERS)
+    class ContactParagraphRendererVocabulary(RenderersVocabulary):
+        """Contact Phone paragraph renderers vocabulary"""
+
+        content_interface = IContactPhoneParagraph
+
+
+.. seealso::
+
+    :ref:`rendererhowto`
+
+
+Paragraph in the ZMI
+""""""""""""""""""""
+
+
+To display and manage the new paragraph in the ZMI, you should create this associated forms
+
+1) Paragraph factory
+--------------------
+
+To create a new element instance inside the zodb, we need a container to this object. PyAMS provide
+`IParagraphContainerTarget`, it's the default container for all paragraphs. We could use this container interface
+as context to create a new paragraph.
+
+
+Declaration of the **factory** of `ContactPhoneParagraph`
+
+.. code-block:: python
+
+    @utility_config(name=CONTACT_PHONE_PARAGRAPH_TYPE, provides=IParagraphFactory)
+    class ContactPhoneParagraphFactory(BaseParagraphFactory):
+        """Contact paragraph factory"""
+
+        name = _("Contact Phone card")
+        content_type = ContactPhoneParagraph
+        secondary_menu = True
+
+
+Definition of a form to create a new ContactPhone instance
+
+.. code-block:: python
+
+    from pyams_form.form import ajax_config
+
+
+    @pagelet_config(name='add-contact-phone-paragraph.html', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+                    permission=MANAGE_CONTENT_PERMISSION)
+    @ajax_config(name='add-contact-phone-paragraph.json', context=IParagraphContainerTarget, layer=IPyAMSLayer,
+                 base=BaseParagraphAJAXAddForm)
+    class ContactPhoneParagraphAddForm(AdminDialogAddForm):
+        """Contact phone paragraph add form"""
+
+        legend = _("Add new contact phone card")
+        dialog_class = 'modal-large'
+        icon_css_class = 'fa fa-fw fa-phone'
+
+        fields = field.Fields(IContactPhoneParagraph).omit('__parent__', '__name__', 'visible')
+        edit_permission = MANAGE_CONTENT_PERMISSION
+
+         def create(self, data):
+            return ContactPhoneParagraph()
+
+        def add(self, object):
+            IParagraphContainer(self.context).append(object)
+
+
+2) Create the Paragraph addform button in the menu
+--------------------------------------------------
+
+We have created a new form and we want add a quick access button to create a new paragraph
+
+.. code-block:: python
+
+    @viewlet_config(name='add-contact-phone-paragraph.menu', context=IParagraphContainerTarget, view=IParagraphContainerView,
+                    layer=IPyAMSLayer, manager=IToolbarAddingMenu, weight=600)
+    class ContactPhoneParagraphAddMenu(BaseParagraphAddMenu):
+        """Contact paragraph add menu"""
+
+        label = _("Contact card...")
+        label_css_class = 'fa fa-fw fa-id-card-o'
+        url = 'add-contact-paragraph.html'
+        paragraph_type = CONTACT_PARAGRAPH_TYPE
+
+
+
+3) Create Edit inner form
+-------------------------
+
+.. code-block:: python
+
+    @adapter_config(context=(IContactPhoneParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+                permission=MANAGE_CONTENT_PERMISSION)
+    @ajax_config(name='inner-properties.json', context=IContactPhoneParagraph, layer=IPyAMSLayer,
+             base=BaseParagraphAJAXEditForm)
+    @implementer(IInnerForm)
+    class ContactPhoneParagraphInnerEditForm(ContactPhoneParagraphPropertiesEditForm):
+        """Contact paragraph inner edit form"""
+
+        legend = None
+
+        @property
+        def buttons(self):
+            if self.mode == INPUT_MODE:
+                return button.Buttons(IParagraphEditFormButtons)
+            else:
+                return button.Buttons()
+
+        def get_ajax_output(self, changes):
+            output = super(ContactParagraphInnerAJAXEditForm, self).get_ajax_output(changes)
+            updated = changes.get(IBaseParagraph, ())
+            if 'title' in updated:
+                output.setdefault('events', []).append(get_json_paragraph_refresh_event(self.context, self.request))
+            updated = changes.get(IContactParagraph, ())
+            if ('photo' in updated) or ('renderer' in updated):
+                # we have to commit transaction to be able to handle blobs...
+                if 'photo' in updated:
+                    ITransactionManager(self.context).get().commit()
+                output.setdefault('events', []).append(get_json_form_refresh_event(self.context, self.request,
+                                                                                   ContactParagraphInnerEditForm))
+            return output
+
+
+4) Create an Edit modal form
+-----------------------------
+
+This form is used inside modals popup
+
+
+.. code-block:: python
+
+    @ajax_config(name='properties.json', context=IContactPhoneParagraph, request_type=IPyAMSLayer,
+                     permission=MANAGE_CONTENT_PERMISSION, renderer='json', xhr=True)
+    @pagelet_config(name='properties.html', context=IContactParagraph, layer=IPyAMSLayer,
+                    permission=MANAGE_CONTENT_PERMISSION)
+    class ContactPhoneParagraphPropertiesEditForm(BaseParagraphPropertiesEditForm):
+        """Contact phone paragraph properties edit form"""
+
+        prefix = 'contact_properties.'
+
+        legend = _("Edit contact card properties")
+        icon_css_class = 'fa fa-fw fa-id-card-o'
+
+        fields = field.Fields(IContactParagraph).omit('__parent__', '__name__', 'visible')
+        fields['renderer'].widgetFactory = RendererFieldWidget
+
+        edit_permission = MANAGE_CONTENT_PERMISSION
+
+
+.. note::
+
+    Select the new content block types in ZMI to make it available in tools
+
+    .. image:: _static/select_paragraph.png
+
+
+How to associate links or Illustrations to a Paragraph ?
+========================================================
+
+Adding the following marker interface, we add new behavior to the Paragraph `ContactPhoneParagraph` .
+
+
+Paragraph in the ZMI
+""""""""""""""""""""
+
+1) Inner form
+-------------
+
+.. code-block:: python
+
+    @adapter_config(context=(IContactPhoneParagraph, IPyAMSLayer), provides=IParagraphInnerEditor)
+    @implementer(IInnerForm, IPropertiesEditForm, IAssociationParentForm)
+    class ContactPhoneParagraphInnerEditForm(ContactPhoneParagraphPropertiesEditForm):
+        """Contact paragraph inner edit form"""
+
+        legend = None
+        ajax_handler = 'inner-properties.json'
+
+
+Marker interfaces:
+
+:py:class:`IPropertiesEditForm`
+
+:py:class:`IAssociationParentForm`
+
+
+.. image:: _static/associations_form.png
+
+
+
+2) Inside a dedicated menu
+--------------------------
+
+.. image:: _static/select_links_n_attachment.png
+
+
+.. code-block:: python
+
+    @implementer(IContactPhoneParagraph, IIllustrationTarget,ILinkContainerTarget,IExtFileContainerTarget))
+    @factory_config(provided=IContactPhoneParagraph)
+    class ContactPhoneParagraph(BaseParagraph):
+        """Contact paragraph"""
+            ...
+
+
+Marker interfaces:
+
+    +--------------------------------+
+    |:py:class:`IIllustrationTarget` |
+    +===================+============+
+    |                   |            |
+    +-------------------+------------+
+
+    +---------------------------------+
+    |:py:class:`ILinkContainerTarget` |
+    +==============+==================+
+    |              | Add internal link|
+    |              +------------------+
+    |              | Add external link|
+    |              +------------------+
+    |              | Add mailto link  |
+    +--------------+------------------+
+
+    +------------------------------------+
+    |:py:class:`IExtFileContainerTarget` |
+    +================+===================+
+    |                | Add external file |
+    |                +-------------------+
+    |                | Add image         |
+    |                +-------------------+
+    |                | Add video         |
+    |                +-------------------+
+    |                | Add audio file    |
+    +----------------+-------------------+
+
+
+ZMI overview:
+.. image:: _static/select_add_links.png
+
+
+