From 7bf233bbee18d3869a44d4aa27b049abd77a49bc Mon Sep 17 00:00:00 2001
From: Arnaud Fontaine <arnaud.fontaine@nexedi.com>
Date: Fri, 19 Jul 2013 18:57:44 +0900
Subject: [PATCH] Add save button to Ace Editor to save source code while
 staying on the same page.

This has several benefits:

* No need to exit maximize/fullscreen modes to save.
* The cursor position in the editor does not change.

This implementation is hackish because it is too Component-specific, but until
RenderJS is being used, there is no probably no better way.
---
 .../erp5_ace_editor/ace_editor_support.xml    | 165 +++++++++++++++++-
 bt5/erp5_ace_editor/bt/change_log             |   3 +
 bt5/erp5_ace_editor/bt/revision               |   2 +-
 .../portal_skins/erp5_core/Base_edit.xml      |   5 +-
 .../Component_view/my_error_message_list.xml  |  24 ---
 .../my_translated_validation_state_title.xml  |   8 +-
 .../ERP5/bootstrap/erp5_core/bt/change_log    |   3 +
 product/ERP5/bootstrap/erp5_core/bt/revision  |   2 +-
 product/ERP5Type/mixin/component.py           |  14 +-
 9 files changed, 190 insertions(+), 36 deletions(-)

diff --git a/bt5/erp5_ace_editor/SkinTemplateItem/portal_skins/erp5_ace_editor/ace_editor_support.xml b/bt5/erp5_ace_editor/SkinTemplateItem/portal_skins/erp5_ace_editor/ace_editor_support.xml
index 7671fb77c8..1c66056b58 100644
--- a/bt5/erp5_ace_editor/SkinTemplateItem/portal_skins/erp5_ace_editor/ace_editor_support.xml
+++ b/bt5/erp5_ace_editor/SkinTemplateItem/portal_skins/erp5_ace_editor/ace_editor_support.xml
@@ -69,11 +69,46 @@
   .ace_line {\n
     color: black !important;\n
   }\n
+\n
+  .ace_editor_save_button {\n
+    position: absolute;\n
+    top: 5px;\n
+    right: 20px;\n
+    width: 30px;\n
+    height: 30px;\n
+    border: none;\n
+    background-color: transparent;\n
+    cursor: pointer;\n
+  }\n
+\n
+  .ace_editor_maximize_fullscreen_message {\n
+    display: table;\n
+    position: absolute;\n
+    bottom: 0;\n
+    right: 20px;\n
+    z-index: 424242;\n
+    padding: 20px;\n
+    background-color: #DAE6F6;\n
+    border: 1px solid #97B0D1;\n
+    opacity: 0.3;\n
+    cursor: pointer;\n
+    font-weight: bold;\n
+  }\n
+\n
+  .ace_editor_maximize_fullscreen_error_message {\n
+    background-color: red;\n
+  }\n
+\n
+  .ace_editor_maximize_fullscreen_message > div {\n
+    font-size: 14px;\n
+    display: table-cell;\n
+    vertical-align: middle;\n
+  }\n
 \n
   #maximize_message {\n
     display: block !important;\n
     position: absolute !important;\n
-    top: 0 !important;\n
+    bottom: 0 !important;\n
     right: 0px !important;\n
     z-index: 4243 !important;\n
     padding: 10px;\n
@@ -139,10 +174,16 @@
   <script type="text/javascript"\n
           tal:attributes="src string:${portal_url}/ace/mode-python.js"></script>\n
   <script type="text/javascript"\n
-          tal:define=\'fullscreen_button string:<input type="button" value="Fullscreen" onclick="switchToFullScreen()" class="ace_editor_action_button" />\'\n
+          tal:define=\'fullscreen_button string:<input type="button" value="Fullscreen" onclick="switchToFullScreen()" class="ace_editor_action_button" />;\n
+                      save_button string:<button class="ace_editor_save_button" onclick="saveDocument(event)"><img src="images/save2.png" width="30" height="30" border="0" /></button>;\'\n
+\n
           tal:content="structure string:\n
   ace_editor_container_div = null;\n
   ace_editor = null;\n
+\n
+  function maximizeFullscreenRemoveSaveMessage() {\n
+    $(\'.ace_editor_maximize_fullscreen_message\').remove();\n
+  }\n
 \n
   function switchToFullScreen(id) {\n
     element = document.getElementById(\'${container_div_id}\');\n
@@ -151,10 +192,10 @@
       if (element.requestFullScreen) {\n
         element.requestFullScreen();\n
       }\n
-      else if (element.mozRequestFullScreen) {\n
+      else if(element.mozRequestFullScreen) {\n
         element.mozRequestFullScreen();\n
       }\n
-      else if (element.webkitRequestFullScreen) {\n
+      else if(element.webkitRequestFullScreen) {\n
         element.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);\n
       }\n
       ace_editor.resize();\n
@@ -181,6 +222,7 @@
 \n
   function unmaximize(event) {\n
     if(event.keyCode == 27) {\n
+      maximizeFullscreenRemoveSaveMessage();\n
       $(\'body\').css(\'overflow\', \'visible\');\n
       ace_editor_container_div.removeClass(\'maximize\');\n
       ace_editor_container_div.appendTo(ace_editor_container_div_parent_before_maximized);\n
@@ -207,6 +249,104 @@
     $(document).keyup(unmaximize);\n
     ace_editor.resize();\n
   }\n
+\n
+  // Save source code only through an AJAX request\n
+  function saveDocument(event) {\n
+    event.stopPropagation();\n
+    event.preventDefault();\n
+\n
+    clickSaveButton(\'Base_edit\');\n
+\n
+    /* If the save is successful, then update validation state field (requires\n
+     *  ace_editor_validation_state CSS class to be set on the field) and error\n
+     * message (requires error CSS class to be set on the field) on the main\n
+     * page. If inside maximize/fullscreen mode, display an box with the\n
+     * result as well\n
+     */\n
+    function successHandler(data) {\n
+      transition_message = $(\'#transition_message\');\n
+      transition_message.css(\'opacity\', 0.0);\n
+      transition_message.html(data);\n
+      transition_message.animate({opacity: 1.0},\n
+                                 {duration: 3000, queue: false});\n
+\n
+      var maximize_fullscreen_message = data;\n
+      var error_arr = [];\n
+\n
+      var validation_state_span = $(\'div.input > .ace_editor_validation_state\');\n
+      if(validation_state_span.length) {\n
+        // Animate field to emphasize the change\n
+        function getTranslatedValidationStateTitleHandler(data) {\n
+          validation_state_span.css(\'opacity\', 0.0);\n
+          validation_state_span.html(data);\n
+          validation_state_span.animate({opacity: 1.0},\n
+                                        {duration: 3000, queue: false});\n
+        }\n
+\n
+        $.ajax({type: \'GET\',\n
+                url: \'getTranslatedValidationStateTitle\',\n
+                success: getTranslatedValidationStateTitleHandler});\n
+      }\n
+\n
+      var error_element = $(\'div.input > .error\');\n
+      if(error_element.length) {\n
+        // Animate field to emphasize the change\n
+        function getErrorMessageListHandler(data) {\n
+          error_arr = $.parseJSON(data);\n
+          error_element.css(\'opacity\', 0.0);\n
+          error_element.html(error_arr.join(\'<br />\'));\n
+          error_element.animate({opacity: 1.0},\n
+                                {duration: 3000, queue: false});\n
+        }\n
+\n
+        $.ajax({type: \'GET\',\n
+                async: false,\n
+                url: \'getErrorMessageList\',\n
+                data: \'as_json:int=1\',\n
+                success: getErrorMessageListHandler});\n
+      }\n
+\n
+      if($(\'.maximize\').length ||\n
+         (document.fullScreenElement && document.fullScreenElement !== null &&\n
+          (document.mozFullScreen || document.webkitIsFullScreen))) {\n
+        var msg_elem_classes = \'ace_editor_maximize_fullscreen_message\';\n
+        if(error_arr.length) {\n
+          maximize_fullscreen_message = error_arr.join(\'<br />\');\n
+          msg_elem_classes += \' ace_editor_maximize_fullscreen_error_message\';\n
+        }\n
+\n
+        // Clear previous saving message if any\n
+        maximizeFullscreenRemoveSaveMessage();\n
+\n
+        msg_elem = $(\'<div class=&quot;\' + msg_elem_classes + \'&quot;>\' +\n
+                     \'<div>\' + maximize_fullscreen_message + \'</div></div>\');\n
+\n
+        msg_elem.appendTo($(\'#${div_id}\'));\n
+\n
+        function animateMessageComplete() {\n
+          if(!error_arr.length)\n
+            $(this).remove();\n
+          else\n
+            $(this).bind(\'click\', function() { $(this).remove() });\n
+        }\n
+        msg_elem.animate({opacity: 1.0}, 1500, animateMessageComplete);\n
+      }\n
+    }\n
+\n
+    function errorHandler(data, textStatus) {\n
+      alert(\'Saving failed: \' + textStatus);\n
+    }\n
+\n
+    var edit_data = $(\'form#main_form\').serialize();\n
+    edit_data += \'&message_only:int=1\';\n
+    $.ajax({type: \'POST\',\n
+            url: \'Base_edit\',\n
+            data: edit_data,\n
+            success: successHandler,\n
+            error: errorHandler});\n
+\n
+    return false;\n
+  }\n
 \n
   window.onload = function() {\n
     ace_editor_container_div = $(\'#${container_div_id}\');\n
@@ -225,11 +365,26 @@
     ace_editor.getSession().on(\'change\', function() {\n
       textarea.val(ace_editor.getSession().getValue());\n
     });\n
+\n
+    /* Only display the source code saving button if the main save button is\n
+     * displayed. This specific save button allows to save without reloading the\n
+     * page (and thus keep the cursor position and mode (maximize/fullscreen)\n
+     * through an AJAX request.\n
+     *\n
+     * TODO: Use RenderJS instead to avoid this ugly hack as only some fields\n
+     *       are reloaded and this is not generic at all.\n
+     */\n
+    if($$(\'div.actions > button.save[name=Base_edit:method]\').length)\n
+      $$(\'${save_button}\').appendTo($(\'#${div_id}\'));\n
 \n
     if(typeof document.cancelFullScreen != \'undefined\' ||\n
        (typeof document.mozFullScreenEnabled != \'undefined\' && document.mozFullScreenEnabled) ||\n
-       typeof document.webkitCancelFullScreen != \'undefined\')\n
+       typeof document.webkitCancelFullScreen != \'undefined\') {\n
+      $$(document).bind(\'webkitfullscreenchange mozfullscreenchange fullscreenchange\',\n
+                        maximizeFullscreenRemoveSaveMessage);\n
+\n
       $$(\'${fullscreen_button}\').insertAfter($$(\'input.ace_editor_action_button\'));\n
+    }\n
   };">\n
   </script>\n
 </tal:block>
diff --git a/bt5/erp5_ace_editor/bt/change_log b/bt5/erp5_ace_editor/bt/change_log
index c2fcbe3b66..5b7d8abd04 100644
--- a/bt5/erp5_ace_editor/bt/change_log
+++ b/bt5/erp5_ace_editor/bt/change_log
@@ -1,3 +1,6 @@
+2013-07-19 arnaud.fontaine
+* Add save button to Ace Editor to save source code while staying on the same page.
+
 2013-07-09 arnaud.fontaine
 * ZODB Components: Follow ERP5 Python indentation style in Ace editor.
 
diff --git a/bt5/erp5_ace_editor/bt/revision b/bt5/erp5_ace_editor/bt/revision
index f11c82a4cb..9a037142aa 100644
--- a/bt5/erp5_ace_editor/bt/revision
+++ b/bt5/erp5_ace_editor/bt/revision
@@ -1 +1 @@
-9
\ No newline at end of file
+10
\ No newline at end of file
diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.xml
index caf66afaed..af33d0d256 100644
--- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.xml
+++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.xml
@@ -287,6 +287,9 @@ try:\n
 except ActivityPendingError,e:\n
   message = Base_translateString("%s" % e)\n
 \n
+if message_only:\n
+  return message\n
+\n
 ignore_layout = int(ignore_layout)\n
 editable_mode = int(editable_mode)\n
 spp = context.getPhysicalPath()\n
@@ -332,7 +335,7 @@ return result\n
         </item>
         <item>
             <key> <string>_params</string> </key>
-            <value> <string>form_id, selection_index=0, selection_name=\'\', dialog_id=\'\', ignore_layout=0, editable_mode=1, silent_mode=0, field_prefix=\'my_\', key_prefix=None, listbox_edit=None</string> </value>
+            <value> <string>form_id, selection_index=0, selection_name=\'\', dialog_id=\'\', ignore_layout=0, editable_mode=1, silent_mode=0, field_prefix=\'my_\', key_prefix=None, listbox_edit=None, message_only=False</string> </value>
         </item>
         <item>
             <key> <string>id</string> </key>
diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Component_view/my_error_message_list.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Component_view/my_error_message_list.xml
index 9db83bc78f..f2d81ac436 100644
--- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Component_view/my_error_message_list.xml
+++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Component_view/my_error_message_list.xml
@@ -12,7 +12,6 @@
               <list>
                 <string>css_class</string>
                 <string>editable</string>
-                <string>enabled</string>
                 <string>height</string>
                 <string>title</string>
               </list>
@@ -64,12 +63,6 @@
                     <key> <string>editable</string> </key>
                     <value> <string></string> </value>
                 </item>
-                <item>
-                    <key> <string>enabled</string> </key>
-                    <value>
-                      <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
-                    </value>
-                </item>
                 <item>
                     <key> <string>field_id</string> </key>
                     <value> <string></string> </value>
@@ -105,10 +98,6 @@
                     <key> <string>editable</string> </key>
                     <value> <int>0</int> </value>
                 </item>
-                <item>
-                    <key> <string>enabled</string> </key>
-                    <value> <int>1</int> </value>
-                </item>
                 <item>
                     <key> <string>field_id</string> </key>
                     <value> <string>my_lines_field</string> </value>
@@ -135,17 +124,4 @@
       </dictionary>
     </pickle>
   </record>
-  <record id="2" aka="AAAAAAAAAAI=">
-    <pickle>
-      <global name="TALESMethod" module="Products.Formulator.TALESField"/>
-    </pickle>
-    <pickle>
-      <dictionary>
-        <item>
-            <key> <string>_text</string> </key>
-            <value> <string>here/hasErrorMessageList</string> </value>
-        </item>
-      </dictionary>
-    </pickle>
-  </record>
 </ZopeData>
diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Component_view/my_translated_validation_state_title.xml b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Component_view/my_translated_validation_state_title.xml
index f8bf5216b5..d756aff23c 100644
--- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Component_view/my_translated_validation_state_title.xml
+++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Component_view/my_translated_validation_state_title.xml
@@ -9,7 +9,9 @@
         <item>
             <key> <string>delegated_list</string> </key>
             <value>
-              <list/>
+              <list>
+                <string>css_class</string>
+              </list>
             </value>
         </item>
         <item>
@@ -69,6 +71,10 @@
             <key> <string>values</string> </key>
             <value>
               <dictionary>
+                <item>
+                    <key> <string>css_class</string> </key>
+                    <value> <string>ace_editor_validation_state</string> </value>
+                </item>
                 <item>
                     <key> <string>field_id</string> </key>
                     <value> <string>my_view_mode_translated_workflow_state_title</string> </value>
diff --git a/product/ERP5/bootstrap/erp5_core/bt/change_log b/product/ERP5/bootstrap/erp5_core/bt/change_log
index 39c8c33d2d..48c7976a65 100644
--- a/product/ERP5/bootstrap/erp5_core/bt/change_log
+++ b/product/ERP5/bootstrap/erp5_core/bt/change_log
@@ -1,3 +1,6 @@
+2013-07-19 arnaud.fontaine
+* Add save button to Ace Editor to save source code while staying on the same page.
+
 2013-07-09 arnaud.fontaine
 * ZODB COmponents: Cosmetic: Fix Component Validation Workflow description.
 
diff --git a/product/ERP5/bootstrap/erp5_core/bt/revision b/product/ERP5/bootstrap/erp5_core/bt/revision
index 9585095e65..fb4a45ee67 100644
--- a/product/ERP5/bootstrap/erp5_core/bt/revision
+++ b/product/ERP5/bootstrap/erp5_core/bt/revision
@@ -1 +1 @@
-41117
\ No newline at end of file
+41118
\ No newline at end of file
diff --git a/product/ERP5Type/mixin/component.py b/product/ERP5Type/mixin/component.py
index f13694f518..3357641a95 100644
--- a/product/ERP5Type/mixin/component.py
+++ b/product/ERP5Type/mixin/component.py
@@ -282,14 +282,22 @@ class ComponentMixin(PropertyRecordableMixin, Base):
 
   security.declareProtected(Permissions.AccessContentsInformation,
                             'getErrorMessageList')
-  def getErrorMessageList(self):
+  def getErrorMessageList(self, as_json=False):
     """
     Return the checkConsistency errors which may have occurred when
     the Component has been modified after being validated once
     """
     current_workflow = self.workflow_history['component_validation_workflow'][-1]
-    return [error.translate()
-            for error in current_workflow.get('error_message', [])]
+    error_list = [error.translate()
+                  for error in current_workflow.get('error_message', [])]
+
+    # Dirty hack until RenderJS is used to save the source code
+    # (erp5_ace_editor/ace_editor_support)
+    if as_json:
+      import json
+      return json.dumps(error_list)
+
+    return error_list
 
   security.declareProtected(Permissions.ModifyPortalContent, 'load')
   def load(self, namespace_dict, validated_only=False, text_content=None):
-- 
2.30.9