diff --git a/trac/htdocs/css/ticket.css b/trac/htdocs/css/ticket.css
--- a/trac/htdocs/css/ticket.css
+++ b/trac/htdocs/css/ticket.css
@@ -136,6 +136,7 @@ label[for=comment] { float: right }
 form .field .wikitoolbar { margin-left: -1px }
 form .field div.trac-resizable { width: 100% }
 
+#propertyform { margin-bottom: 2em; }
 #properties { white-space: nowrap; line-height: 160%; padding: .5em }
 #properties table { border-spacing: 0; width: 100%; padding: 0 .5em }
 #properties table th {
diff --git a/trac/htdocs/js/auto_preview.js b/trac/htdocs/js/auto_preview.js
--- a/trac/htdocs/js/auto_preview.js
+++ b/trac/htdocs/js/auto_preview.js
@@ -1,6 +1,94 @@
-// Automatic preview through XHR
+// Automatic form submission and preview through XHR
 
 (function($) {
+  // Enable automatic submission of forms.
+  //
+  // This method can be applied to a single form, where it enables
+  // auto-submission on all the editable elements that it contains.
+  // It can also be applied on a list of elements, in which case it
+  // enables auto-submission only for these elements.
+  //
+  // Arguments:
+  //  - `args`: additional form data to be passed with the XHR.
+  //  - `update`: the function that is called with the submission reply. It
+  //              is called with the request data and the reply.
+  $.fn.autoSubmit = function(args, update) {
+    if (this.length == 0 || auto_preview_timeout <= 0)
+      return this;
+    if (this[0].nodeName == 'FORM') {
+      var form = this;
+      var inputs = this.find("textarea, select, :text, :checkbox, :radio");
+    } else {
+      var form = this.closest('form');
+      var inputs = this;
+    }
+    var timeout = auto_preview_timeout * 1000;
+    var timer = null;
+    var updating = false;
+    var queued = false;
+    
+    // Return true iff the values have changed
+    function values_changed(new_values) {
+      if (values.length != new_values.length)
+        return true;
+      for (var i in values) {
+        var value = values[i], new_value = new_values[i];
+        if ((value.name != new_value.name) || (value.value != new_value.value))
+          return true;
+      }
+      return false;
+    }
+    
+    // Request a preview through XHR
+    function request() {
+      if (!updating) {
+        var new_values = form.serializeArray();
+        if (values_changed(new_values)) {
+          values = new_values;
+          updating = true;
+          
+          // Construct request data
+          var data = values.slice(0);
+          for (var key in args)
+            data.push({name: key, value: args[key]});
+          
+          $.ajax({
+            type: form.attr('method'), url: form.attr('action'),
+            data: data, traditional: true, dataType: "html",
+            success: function(reply) {
+              if (queued)
+                timer = setTimeout(request, timeout);
+              updating = false;
+              queued = false;
+              update(data, reply);
+            },
+            error: function(req, err, exc) {
+              updating = false;
+              queued = false;
+            },
+          });
+        }
+      }
+    }
+    
+    // Trigger a request after the given timeout
+    function trigger() {
+      if (!updating) {
+        if (timer)
+          clearTimeout(timer);
+        timer = setTimeout(request, timeout);
+      } else {
+        queued = true;
+      }
+      return true;
+    }
+
+    var values = form.serializeArray();
+    return inputs.each(function() {
+      $(this).keydown(trigger).keypress(trigger).change(trigger).blur(trigger);
+    });
+  };
+
   // Enable automatic previewing to <textarea> elements.
   //
   // Arguments:
@@ -56,5 +144,5 @@
       
       $(this).keydown(trigger).keypress(trigger).blur(request);
     });
-  }
+  };
 })(jQuery);
diff --git a/trac/ticket/templates/ticket.html b/trac/ticket/templates/ticket.html
--- a/trac/ticket/templates/ticket.html
+++ b/trac/ticket/templates/ticket.html
@@ -19,23 +19,6 @@
         $("div.description").find("h1,h2,h3,h4,h5,h6").addAnchor(_("Link to this section"));
         $(".foldable").enableFolding(false, true);
       <py:when test="ticket.exists">
-        var args = {realm: "ticket", id: ${ticket.id}, escape_newlines: ${int(preserve_newlines)}}
-        $("#comment").autoPreview("${href.wiki_render()}", args, function(textarea, text, rendered) {
-            $("#ticketchange div.comment").html(rendered);
-            if (rendered)
-              $("#ticketchange").show();
-            else if ($("#ticketchange ul.changes").length == 0)
-              $("#ticketchange").hide();
-        });
-        $("#trac-comment-editor textarea").autoPreview("${href.wiki_render()}", args,
-                                                       function(textarea, text, rendered) {
-          var comment = $("#trac-comment-editor").next("div.comment");
-          comment.html(rendered);
-          if (rendered)
-            comment.show();
-          else
-            comment.hide();
-        });
         $("#modify").parent().toggleClass("collapsed");
         $(".trac-topnav a").click(function() { $("#modify").parent().removeClass("collapsed"); });
 
@@ -50,15 +33,31 @@
         }
         actions.click(updateActionFields);
         updateActionFields();
-      </py:when>
-      <py:otherwise>
-        $("#field-summary").focus();
-      </py:otherwise>
-      <py:if test="preview_mode">
+        
+        $("#propertyform").autoSubmit({preview: '1'}, function(data, reply) {
+          var items = $(reply);
+          $("#ticket").replaceWith(items.filter('#ticket'));
+          var changes = $("#ticketchange-content").html(items.filter('ul.changes, div.comment'));
+          $("#ticketchange").toggle(changes.children().length != 0);
+        });
+        $("#trac-comment-editor").autoSubmit({preview_comment: '1'}, function(data, reply) {
+          var comment = $("#trac-comment-editor").next("div.comment").html(reply);
+          comment.toggle(comment.children().length != 0);
+        });
+        <py:if test="preview_mode">
         $("#changelog").parent().toggleClass("collapsed");
         $("#attachments").toggleClass("collapsed");
         $("#trac-add-comment").scrollToTop();
-      </py:if>
+        </py:if>
+      </py:when>
+      <py:otherwise>
+        $("#propertyform").autoSubmit({preview: '1'}, function(data, reply) {
+          $('#ticket').replaceWith(reply);
+        });
+        <py:if test="not preview_mode">
+        $("#field-summary").focus();
+        </py:if>
+      </py:otherwise>
       });
     </script>
   </head>
@@ -77,7 +76,7 @@
                   has_edit_comment = 'TICKET_EDIT_COMMENT' in perm(ticket.resource);
                   has_property_editor = not version and version != 0 and not cnum_edit
                                         and (can_append or can_modify or can_edit or can_create)">
-      <div class="trac-topnav" py:if="(ticket.exists or preview_mode) and has_property_editor">
+      <div class="trac-topnav" py:if="ticket.exists and has_property_editor">
         <a href="#propertyform" title="Go to the ticket editor">Modify</a> &darr;
       </div>
       <h1 id="trac-ticket-title" py:choose="">
@@ -101,9 +100,10 @@
         </py:otherwise>
       </h1>
 
-      <xi:include href="ticket_box.html" py:if="ticket.exists or preview_mode"/>
 
       <py:if test="ticket.exists">
+        <xi:include href="ticket_box.html" py:with="preview_mode = change_preview.fields"/>
+        
         <!--! do not show attachments for old versions of this ticket or for new tickets -->
         <py:if test="not version and version != 0 and ticket.exists">
           <xi:include href="list_of_attachments.html"
@@ -127,8 +127,10 @@
                              show_editor = can_edit_comment and str(change.cnum) == cnum_edit;
                              show_history = str(change.cnum) == cnum_hist;
                              max_version = max(change.comment_history);
-                             comment_version = (max_version, int(cversion or 0))[show_history]">
-              <div class="change" id="${'cnum' in change and 'trac-change-%d' % change.cnum or None}">
+                             comment_version = (max_version, int(cversion or 0))[show_history];
+                             show_buttons = not show_editor and comment_version == max_version">
+              <div class="change${not show_buttons and ' trac-nobuttons' or ''}"
+                   id="${'cnum' in change and 'trac-change-%d' % change.cnum or None}">
                 <h3 class="change">
                   <span class="threading" py:if="'cnum' in change"
                         py:with="change_replies = replies.get(str(change.cnum), [])">
@@ -150,7 +152,7 @@
                   </span>
                   <i18n:msg params="date, author">Changed ${dateinfo(change.date)} ago by ${authorinfo(change.author)}</i18n:msg>
                 </h3>
-                <py:if test="not show_editor and comment_version == max_version">
+                <py:if test="show_buttons">
                   <form py:if="'cnum' in change and can_edit_comment" method="get" action="#comment:${change.cnum}">
                     <div class="inlinebuttons">
                       <input type="hidden" name="cnum_edit" value="${change.cnum}"/>
@@ -197,10 +199,10 @@
       <!--! End of the section we don't show on initial new tickets -->
 
       <form py:if="has_property_editor" method="post" id="propertyform"
-            action="${ticket.exists and href.ticket(ticket.id) + '#trac-add-comment' or href.newticket()}">
+            action="${href.ticket(ticket.id) + '#trac-add-comment' if ticket.exists
+                      else href.newticket() + '#ticket'}">
         <!--! Add comment -->
-        <div py:if="ticket.exists and can_append" class="field"
-             py:with="show_comment_preview = (change_preview.fields or change_preview.comment) and cnum_edit is None">
+        <div py:if="ticket.exists and can_append" class="field">
           <div class="trac-nav">
             <a href="#content" title="View ticket fields and description">View</a> &uarr;
           </div>
@@ -221,17 +223,20 @@
             may have failed.
           </div>
           <!--! Preview of ticket changes -->
-          <div id="ticketchange" class="ticketdraft" style="${not show_comment_preview and 'display: none' or None}">
+          <div id="ticketchange" class="ticketdraft"
+               style="${(not (change_preview.fields or change_preview.comment)
+                         or cnum_edit is not None) and 'display: none' or None}">
             <h3 class="change" id="${'cnum' in change_preview and 'comment:%d' % change_preview.cnum or None}">
               <span class="threading" py:if="'replyto' in change_preview">
                 in reply to: ${commentref('&uarr;&nbsp;', change_preview.replyto)}
               </span>
               <i18n:msg params="author">Changed by ${authorinfo(change_preview.author)}</i18n:msg>
             </h3>
-            <xi:include href="ticket_change.html" py:with="change = change_preview"/>
+            <div id="ticketchange-content"><xi:include href="ticket_change.html" py:with="change = change_preview"/></div>
           </div>
         </div>
 
+
         <div>
           <h2 py:if="ticket.exists" class="foldable">Modify Ticket</h2>
           <div id="modify">
@@ -409,6 +414,8 @@
 
       </form>
 
+      <xi:include href="ticket_box.html" py:if="not ticket.exists" py:with="preview_mode = True"/>
+      
       <div id="help" i18n:msg="">
         <strong>Note:</strong> See
         <a href="${href.wiki('TracTickets')}">TracTickets</a> for help on using
diff --git a/trac/ticket/templates/ticket_change.html b/trac/ticket/templates/ticket_change.html
--- a/trac/ticket/templates/ticket_change.html
+++ b/trac/ticket/templates/ticket_change.html
@@ -13,7 +13,7 @@ Arguments:
       xmlns:i18n="http://genshi.edgewall.org/i18n"
       py:with="show_editor = value_of('show_editor', False)" py:strip="">
   <ul py:if="change.fields" class="changes">
-    <li py:for="field_name, field in change.fields.items()">
+    <li py:for="field_name, field in sorted(change.fields.iteritems(), key=lambda item: item[1].label.lower())">
       <strong>${field.label}</strong>
       <py:choose>
         <py:when test="field_name == 'attachment'"><i18n:msg params="name">
@@ -35,7 +35,8 @@ Arguments:
       </py:choose>
     </li>
   </ul>
-  <form py:if="show_editor" id="trac-comment-editor" method="post" action="#comment:${change.cnum}">
+  <form py:if="show_editor" id="trac-comment-editor" method="post"
+        action="${href.ticket(ticket.id) + '#comment:%d' % change.cnum}">
     <div>
       <textarea name="edited_comment" class="wikitext trac-resizable" rows="10" cols="78">
 ${(edited_comment, change.comment)[edited_comment is None]}</textarea>
@@ -55,10 +56,11 @@ Arguments:
          class="comment searchable ticketdraft" style="${not text and 'display: none' or None}" xml:space="preserve">
       ${wiki_to_html(context, text, escape_newlines=preserve_newlines)}
     </div>
-    <div py:otherwise="" py:choose="" class="comment searchable" xml:space="preserve">
-      <py:when test="show_history">${wiki_to_html(context, change.comment_history[int(cversion)].comment,
-                                                  escape_newlines=preserve_newlines)}</py:when>
-      <py:otherwise>${wiki_to_html(context, change.comment, escape_newlines=preserve_newlines)}</py:otherwise>
+    <div py:when="show_history" class="comment searchable" xml:space="preserve">
+      ${wiki_to_html(context, change.comment_history[int(cversion)].comment, escape_newlines=preserve_newlines)}
+    </div>
+    <div py:when="change.comment" class="comment searchable" xml:space="preserve">
+      ${wiki_to_html(context, change.comment, escape_newlines=preserve_newlines)}
     </div>
   </py:choose>
 </html>
diff --git a/trac/ticket/templates/ticket_preview.html b/trac/ticket/templates/ticket_preview.html
new file mode 100644
--- /dev/null
+++ b/trac/ticket/templates/ticket_preview.html
@@ -0,0 +1,12 @@
+<!--!
+Render a ticket box preview and a ticket change preview, to be sent as a reply
+to a ticket change auto-preview.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      xmlns:i18n="http://genshi.edgewall.org/i18n"
+      py:strip="">
+  <xi:include href="ticket_box.html" py:with="can_append = 'TICKET_APPEND' in perm(ticket.resource)"/>
+  <xi:include href="ticket_change.html" py:with="change = change_preview"/>
+</html>
diff --git a/trac/ticket/web_ui.py b/trac/ticket/web_ui.py
--- a/trac/ticket/web_ui.py
+++ b/trac/ticket/web_ui.py
@@ -36,7 +36,7 @@ from trac.ticket.api import TicketSystem
 from trac.ticket.model import Milestone, Ticket, group_milestones
 from trac.ticket.notification import TicketNotifyEmail
 from trac.timeline.api import ITimelineEventProvider
-from trac.util import as_bool, get_reporter_id
+from trac.util import as_bool, as_int, get_reporter_id
 from trac.util.datefmt import format_datetime, from_utimestamp, \
                               to_utimestamp, utc
 from trac.util.text import exception_to_unicode, obfuscate_email_address, \
@@ -441,19 +441,28 @@ class TicketModule(Component):
 
         data['fields'] = fields
 
+        if req.get_header('X-Requested-With') == 'XMLHttpRequest':
+            data['preview_mode'] = True
+            return 'ticket_box.html', data, None
+
         add_stylesheet(req, 'common/css/ticket.css')
         add_script(req, 'common/js/folding.js')
         Chrome(self.env).add_wiki_toolbars(req)
+        Chrome(self.env).add_auto_preview(req)
         return 'ticket.html', data, None
 
     def _process_ticket_request(self, req):
         id = int(req.args.get('id'))
-        version = req.args.get('version', None)
-        if version is not None:
-            try:
-                version = int(version)
-            except ValueError:
-                version = None
+        version = as_int(req.args.get('version'), None)
+        xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest'
+        
+        if xhr and 'preview_comment' in req.args:
+            context = web_context(req, 'ticket', id, version)
+            escape_newlines = self.must_preserve_newlines
+            rendered = format_to_html(self.env, context,
+                                      req.args.get('edited_comment', ''),
+                                      escape_newlines=escape_newlines)
+            req.send(rendered.encode('utf-8'))
 
         req.perm('ticket', id, version).require('TICKET_VIEW')
         ticket = Ticket(self.env, id, version=version)
@@ -572,6 +581,10 @@ class TicketModule(Component):
         self._insert_ticket_data(req, ticket, data,
                                  get_reporter_id(req, 'author'), field_changes)
 
+        if xhr:
+            data['preview_mode'] = bool(data['change_preview']['fields'])
+            return 'ticket_preview.html', data, None
+
         mime = Mimeview(self.env)
         format = req.args.get('format')
         if format:
diff --git a/trac/wiki/web_api.py b/trac/wiki/web_api.py
--- a/trac/wiki/web_api.py
+++ b/trac/wiki/web_api.py
@@ -13,6 +13,7 @@
 
 from trac.core import *
 from trac.resource import Resource
+from trac.util import as_int
 from trac.web.api import IRequestHandler
 from trac.web.chrome import web_context
 from trac.wiki.formatter import format_to
@@ -36,12 +37,7 @@ class WikiRenderer(Component):
             req.perm.require('TRAC_ADMIN')
         realm = req.args.get('realm', 'wiki')
         id = req.args.get('id')
-        version = req.args.get('version')
-        if version is not None:
-            try:
-                version = int(version)
-            except ValueError:
-                version = None
+        version = as_int(req.args.get('version'), None)
         text = req.args.get('text', '')
         flavor = req.args.get('flavor')
         options = {}

