diff --git a/reviewboard/reviews/fields.py b/reviewboard/reviews/fields.py
index 7070592d10bd8bbc89d317c4bb5829be14047591..9b36c6dd9b3c65f0fe26b6696c1205e23e1ca700 100644
--- a/reviewboard/reviews/fields.py
+++ b/reviewboard/reviews/fields.py
@@ -141,7 +141,7 @@ class BaseReviewRequestFieldSet(object):
 
     @classmethod
     def is_empty(cls):
-        """Returns whether the fieldset is empty.
+        """Return whether the fieldset is empty.
 
         A fieldset is empty if there are no field classes registered.
         An empty fieldset will not be displayed on the page.
@@ -150,7 +150,7 @@ class BaseReviewRequestFieldSet(object):
 
     @classmethod
     def add_field(cls, field_cls):
-        """Adds a field class to this fieldset.
+        """Add a field class to this fieldset.
 
         The field will be rendered inside this fieldset on the page.
 
@@ -162,7 +162,7 @@ class BaseReviewRequestFieldSet(object):
 
     @classmethod
     def remove_field(cls, field_cls):
-        """Removes a field class from this fieldset.
+        """Remove a field class from this fieldset.
 
         The field class must have been previously added to this fieldset.
         """
@@ -243,7 +243,7 @@ class BaseReviewRequestField(object):
 
     @property
     def value(self):
-        """Returns the value loaded from the database.
+        """Return the value loaded from the database.
 
         This will fetch the value with the associated ReviewRequest or
         ReviewRequestDraft, and then cache it for future lookups.
@@ -254,7 +254,7 @@ class BaseReviewRequestField(object):
         return self._value
 
     def has_value_changed(self, old_value, new_value):
-        """Returns whether the value has changed.
+        """Return whether the value has changed.
 
         By default, it performs an inequality check on the values. This
         can be overridden to perform more specialized checks.
@@ -262,7 +262,7 @@ class BaseReviewRequestField(object):
         return old_value != new_value
 
     def record_change_entry(self, changedesc, old_value, new_value):
-        """Records information on the changed values in a ChangeDescription.
+        """Record information on the changed values in a ChangeDescription.
 
         By default, the values are stored as-is along with the field ID.
         This can be overridden to perform more specialized storage.
@@ -372,7 +372,7 @@ class BaseReviewRequestField(object):
         ])
 
     def get_change_entry_sections_html(self, info):
-        """Returns sections of change entries with titles and rendered HTML.
+        """Return sections of change entries with titles and rendered HTML.
 
         By default, this just returns a single section for the field, with
         the field's title and rendered change HTML.
@@ -385,7 +385,7 @@ class BaseReviewRequestField(object):
         }]
 
     def render_change_entry_html(self, info):
-        """Renders a change entry to HTML.
+        """Render a change entry to HTML.
 
         By default, this returns a simple "changed from X to Y" using the old
         and new values. This can be overridden to generate more specialized
@@ -440,7 +440,7 @@ class BaseReviewRequestField(object):
             return ''
 
     def render_change_entry_value_html(self, info, value):
-        """Renders the value for a change description string to HTML.
+        """Render the value for a change description string to HTML.
 
         By default, this just converts the value to text and escapes it.
         This can be overridden to customize how the value is displayed.
@@ -448,7 +448,7 @@ class BaseReviewRequestField(object):
         return escape(six.text_type(value or ''))
 
     def load_value(self, review_request_details):
-        """Loads a value from the review request or draft.
+        """Load a value from the review request or draft.
 
         By default, this loads the value as-is from the extra_data field.
         This can be overridden if you need to deserialize the value in some
@@ -460,7 +460,7 @@ class BaseReviewRequestField(object):
         return review_request_details.extra_data.get(self.field_id)
 
     def save_value(self, value):
-        """Saves the value in the review request or draft.
+        """Save the value in the review request or draft.
 
         By default, this saves the value as-is in the extra_data field.
         This can be overridden if you need to serialize the value in some
@@ -492,7 +492,7 @@ class BaseReviewRequestField(object):
         self.save_value(self.load_value(review_request_details))
 
     def render_value(self, value):
-        """Renders the value in the field.
+        """Render the value in the field.
 
         By default, this converts to text and escapes it. This can be
         overridden if you need to render it in a more specific way.
@@ -502,7 +502,7 @@ class BaseReviewRequestField(object):
         return escape(six.text_type(value or ''))
 
     def should_render(self, value):
-        """Returns whether the field should be rendered.
+        """Return whether the field should be rendered.
 
         By default, the field is always rendered, but this can be overridden
         if you only want to show under certain conditions (such as if it has
@@ -513,7 +513,7 @@ class BaseReviewRequestField(object):
         return True
 
     def get_css_classes(self):
-        """Returns the list of CSS classes to apply to the element.
+        """Return the list of CSS classes to apply to the element.
 
         By default, this will include the contents of ``default_css_classes``,
         and ``required`` if it's a required field.
@@ -529,14 +529,15 @@ class BaseReviewRequestField(object):
         return css_classes
 
     def get_data_attributes(self):
-        """Returns any data attributes to include in the element.
+        """Return any data attributes to include in the element.
 
-        By default, this returns nothing.
+        By default, this returns a dictionary with the key 'raw-value' which
+        stores the field's value as-is.
         """
-        return {}
+        return {'raw-value': self.value}
 
     def as_html(self):
-        """Returns the field rendered as HTML.
+        """Return the field rendered as HTML.
 
         By default, this just calls ``render_value`` with the value
         from the database.
@@ -594,7 +595,7 @@ class BaseCommaEditableField(BaseEditableField):
     one_line_per_change_entry = True
 
     def has_value_changed(self, old_value, new_value):
-        """Returns whether two values have changed.
+        """Return whether two values have changed.
 
         If ``order_matters`` is set to ``True``, this will do a strict
         list comparison. Otherwise, it will compare the items in both
@@ -629,7 +630,7 @@ class BaseCommaEditableField(BaseEditableField):
             return self.serialize_change_entry_for_list(field_info)
 
     def render_value(self, values):
-        """Renders the list of items.
+        """Render the list of items.
 
         This will call out to ``render_item`` for every item. The list
         of rendered items will be separated by a comma and a space.
@@ -640,14 +641,14 @@ class BaseCommaEditableField(BaseEditableField):
         ])
 
     def render_item(self, item):
-        """Renders an item from the list.
+        """Render an item from the list.
 
         By default, this will convert the item to text and then escape it.
         """
         return escape(six.text_type(item or ''))
 
     def render_change_entry_html(self, info):
-        """Renders a change entry to HTML.
+        """Render a change entry to HTML.
 
         By default, this returns HTML containing a list of removed items,
         and a list of added items. This can be overridden to generate
@@ -688,7 +689,7 @@ class BaseCommaEditableField(BaseEditableField):
         return ''.join(s)
 
     def render_change_entry_value_html(self, info, values):
-        """Renders a list of items for change description HTML.
+        """Render a list of items for change description HTML.
 
         By default, this will call ``render_change_entry_item_html`` for every
         item in the list. The list of rendered items will be separated by a
@@ -700,7 +701,7 @@ class BaseCommaEditableField(BaseEditableField):
         ])
 
     def render_change_entry_item_html(self, info, item):
-        """Renders an item for change description HTML.
+        """Render an item for change description HTML.
 
         By default, this just converts the value to text and escapes it.
         This can be overridden to customize how the value is displayed.
@@ -729,7 +730,7 @@ class BaseTextAreaField(BaseEditableField):
             return '%s_text_type' % self.field_id
 
     def is_text_markdown(self, value):
-        """Returns whether the text is in Markdown format.
+        """Return whether the text is in Markdown format.
 
         This can be overridden if the field needs to check something else
         to determine if the text is in Markdown format.
@@ -762,7 +763,7 @@ class BaseTextAreaField(BaseEditableField):
                 source_text_type
 
     def get_css_classes(self):
-        """Returns the list of CSS classes.
+        """Return the list of CSS classes.
 
         If Markdown is enabled, and the text is in Markdown format,
         this will add a "rich-text" field.
@@ -796,7 +797,7 @@ class BaseTextAreaField(BaseEditableField):
         return attrs
 
     def render_value(self, text):
-        """Returns the value of the field.
+        """Return the value of the field.
 
         If Markdown is enabled, and the text is not in Markdown format,
         the text will be escaped.
@@ -809,7 +810,7 @@ class BaseTextAreaField(BaseEditableField):
             return escape(text)
 
     def should_render_as_markdown(self, value):
-        """Returns whether the text should be rendered as Markdown.
+        """Return whether the text should be rendered as Markdown.
 
         By default, this checks if the field is set to always render
         any text as Markdown, or if the given text is in Markdown format.
@@ -910,6 +911,360 @@ class BaseTextAreaField(BaseEditableField):
                 % line)
 
 
+
+
+class BaseCheckboxField(BaseReviewRequestField):
+    """Base class for a checkbox field.
+
+    This field's value will be either True or False.
+    """
+    default_css_classes = ['custom-editable']
+    is_editable = True
+
+    @property
+    def default_value(self):
+        """Return the default value of the checkbox field (True/False).
+
+        Default to False. Override this to change the default.
+        When overriding be sure to use the @property annotation.
+        """
+        return None
+
+    def get_data_attributes(self):
+        attrs = super(BaseCheckboxField, self).get_data_attributes()
+        attrs.update({'custom-editor': 'checkbox'})
+        return attrs
+
+    def load_value(self, review_request_details):
+        value = review_request_details.extra_data.get(self.field_id)
+        if value is not None:
+            return value
+        else:
+            return self.default_value
+
+    def render_change_entry_html(self, info):
+        old_value = ''
+        new_value = ''
+
+        if 'old' in info:
+            old_value = info['old'][0]
+
+        if 'new' in info:
+            new_value = info['new'][0]
+
+        s = ['<table class="changed">']
+
+        # Explicitly show adding/removing the False value.
+        if old_value is not None:
+            s.append(self.render_change_entry_removed_value_html(
+                info, old_value))
+
+        if new_value is not None:
+            s.append(self.render_change_entry_added_value_html(
+                info, new_value))
+
+        s.append('</table>')
+
+        return ''.join(s)
+
+    def render_change_entry_value_html(self, info, value):
+        """Render the value for a change description string to HTML.
+
+        By default, this just converts the boolean value to 'Yes' or 'No'.
+        This can be overridden to customize how the value is displayed.
+        """
+        if value:
+            return escape(six.text_type('Yes'))
+        else:
+            return escape(six.text_type('No'))
+
+    def as_html(self):
+        """Return the field rendered as a checkbox tag.
+
+        If the value is true the checkbox will initially display as checked.
+        """
+        s = ['<input type="checkbox"']
+
+        if self.value:
+            s.append(' checked="checked"')
+
+        s.append('/>')
+
+        return ''.join(s)
+
+
+class BaseDropdownField(BaseReviewRequestField):
+    """Base class for a dropdown field.
+
+    """
+    default_css_classes = ['custom-editable']
+    is_editable = True
+
+    @property
+    def default_value(self):
+        """Return the default value of the dropdown field.
+
+        This should ideally be an option defined in get_dropdown_options.
+        Defaults to None. Override this method to change the default.
+        When overriding be sure to use the @property annotation.
+        """
+        return None
+
+    def get_data_attributes(self):
+        attrs = super(BaseDropdownField, self).get_data_attributes()
+        attrs.update({'custom-editor': 'dropdown'})
+        return attrs
+
+    def load_value(self, review_request_details):
+        value = review_request_details.extra_data.get(self.field_id)
+        if value is not None:
+            return value
+        else:
+            return self.default_value
+
+    def get_dropdown_options(self):
+        """Return an array of (value, label) tuples for the dropdown options.
+
+        The values of the dropdown must be unique.
+        Returns nothing by default, you must override this and provide options.
+        """
+        return []
+
+    def render_change_entry_value_html(self, info, value):
+        """Render the value for a change description string to HTML.
+
+        By default, this just displays the option labels instead of values.
+        This can be overridden to customize how the change entry is displayed.
+        """ 
+        label = dict(self.get_dropdown_options())[value]
+        return escape(six.text_type(label or ''))
+
+    def as_html(self):
+        """Return the field rendered as a select tag with options.
+
+        The current value of the field will initially be selected.
+        """
+        s = ['<select>']
+
+        # Add the default blank option.
+        s += ['<option value=""></option>']
+
+        for value, label in self.get_dropdown_options():
+            s += ['<option value="', str(value), '"']
+
+            if value == self.value:
+                s.append(' selected="selected"')
+
+            s += ['>', str(label), '</option>']
+
+        s.append('</select>')
+
+        return ''.join(s)
+
+class BaseRadioField(BaseReviewRequestField):
+    """Base class for radio button field
+
+    """
+    default_css_classes = ['custom-editable']
+    is_editable = True
+
+    @property
+    def default_value(self):
+        """Return the default value of the radio field.
+
+        This should ideally be an option defined in get_dropdown_options.
+        Defaults to None. Override this method to change the default.
+        When overriding be sure to use the @property annotation.
+        """
+        return None
+
+    def get_data_attributes(self):
+        attrs = super(BaseRadioField, self).get_data_attributes()
+        attrs.update({'custom-editor': 'radio'})
+        return attrs
+
+    def load_value(self, review_request_details):
+        value = review_request_details.extra_data.get(self.field_id)
+        if value is not None:
+            return value
+        else:
+            return self.default_value
+
+    def get_radio_options(self):
+        """Return an array of (value, label) tuples for the radio options.
+
+        The values of the radio must be unique.
+        Returns nothing by default, you must override this and provide options.
+        """
+        return []
+
+    def render_change_entry_value_html(self, info, value):
+        """Render the value for a change description string to HTML.
+
+        By default, this just displays the option labels instead of values.
+        This can be overridden to customize how the change entry is displayed.
+        """
+        label = dict(self.get_radio_options())[value]
+        return escape(six.text_type(label or ''))
+
+    def as_html(self):
+        """Return the field rendered as a select tag with options.
+
+        The current value of the field will initially be selected.
+        """
+        s = []
+
+        for value, label in self.get_radio_options():
+            s += ['<input type="radio" name="', str(self.field_id),
+                  '_radio"', ' value="', str(value), '"']
+
+            if value == self.value:
+                s.append(' checked="checked"')
+
+            s += ['/>', str(label), '<br />']
+
+        return ''.join(s)
+
+class BaseDatetimeField(BaseReviewRequestField):
+    """Base class for a datetime picker.
+
+    """
+    default_css_classes = ['custom-editable']
+    is_editable = True
+
+    @property
+    def default_value(self):
+        """Return the default value of the datetime field.
+
+        Any default date should be in UTC as: YYYY-MM-DDTHH:mm+ZZZZ
+        Defaults to None. Override this method to change the default.
+        When overriding be sure to use the @property annotation.
+        """
+        return None
+
+    def get_data_attributes(self):
+        attrs = super(BaseDatetimeField, self).get_data_attributes()
+        attrs.update({'custom-editor': 'datetime'})
+        return attrs
+
+    def load_value(self, review_request_details):
+        value = review_request_details.extra_data.get(self.field_id)
+        if value is not None:
+            return value
+        else:
+            return self.default_value
+
+    def as_html(self):
+        """Return the field rendered as empty, all elements will be 
+        controlled on js side.
+
+        """
+        return ''
+
+class BaseDateField(BaseReviewRequestField):
+    """Base class for a datetime picker.
+    """
+    default_css_classes = ['custom-editable']
+    is_editable = True    
+    
+    @property
+    def default_value(self):
+        """Return the default value of the datetime field.
+
+        Any default date should be in UTC as: YYYY-MM-DDTHH:mm+ZZZZ
+        Defaults to None. Override this method to change the default.
+        When overriding be sure to use the @property annotation.
+        """
+        return None
+
+    def get_data_attributes(self):
+        attrs = super(BaseDateField, self).get_data_attributes()
+        attrs.update({'custom-editor': 'date'})
+        return attrs
+
+    def load_value(self, review_request_details):
+        value = review_request_details.extra_data.get(self.field_id)
+        if value is not None:
+            return value
+        else:
+            return self.default_value
+
+    def as_html(self):
+        """Return the field rendered as empty, all elements will be 
+        controlled on js side.
+
+        """
+        return ''
+
+class BaseTimeField(BaseReviewRequestField):
+    """Base class for a time picker.
+    The time recorded in database is UTC-based.
+
+    """
+    default_css_classes = ['custom-editable']
+    is_editable = True
+
+    @property
+    def default_value(self):
+        """Return the default value of the datetime field.
+
+        Any default date should be in UTC as: YYYY-MM-DDTHH:mm+ZZZZ
+        Defaults to None. Override this method to change the default.
+        When overriding be sure to use the @property annotation.
+        """
+        return None
+
+    def get_data_attributes(self):
+        attrs = super(BaseTimeField, self).get_data_attributes()
+        attrs.update({'custom-editor': 'time'})
+        return attrs
+
+    def load_value(self, review_request_details):
+        value = review_request_details.extra_data.get(self.field_id)
+        if value is not None:
+            return value
+        else:
+            return self.default_value
+
+    def as_html(self):
+        """Return the field rendered as a select tag with options.
+
+        The current value of the field will initially be selected.
+        """
+        return ''
+
+def _populate_defaults():
+    """Populate the default list of fieldsets and their fields."""
+    global _populated
+
+    if not _populated:
+        from reviewboard.reviews.builtin_fields import builtin_fieldsets
+
+        _populated = True
+
+        for fieldset_cls in builtin_fieldsets:
+            register_review_request_fieldset(fieldset_cls)
+
+
+def _register_field(field_cls):
+    """Register a field.
+
+    This will check if the field has already been registered before
+    adding it. It's called internally when first adding a fieldset, or
+    when adding a field to a fieldset.
+    """
+    field_id = field_cls.field_id
+
+    if field_id in _all_fields:
+        raise KeyError(
+            '"%s" is already a registered review request field. '
+            'Field IDs must be unique across all fieldsets.'
+            % field_id)
+
+    _all_fields[field_id] = field_cls
+
+
+
 def get_review_request_fields():
     """Yield all registered field classes.
 
@@ -923,7 +1278,7 @@ def get_review_request_fields():
 
 def get_review_request_fieldsets(include_main=False,
                                  include_change_entries_only=False):
-    """Returns a list of all registered fieldset classes.
+    """Return a list of all registered fieldset classes.
 
     As an internal optimization, the "main" fieldset can be filtered out,
     to help with rendering the side of the review request page.
diff --git a/reviewboard/static/rb/js/views/reviewRequestEditorView.js b/reviewboard/static/rb/js/views/reviewRequestEditorView.js
index d6f6edfd21d6e513b93a36a7e9e97aadc4cefd72..a4a5a6d352157ed3b4ef77d043593288be3df6f5 100644
--- a/reviewboard/static/rb/js/views/reviewRequestEditorView.js
+++ b/reviewboard/static/rb/js/views/reviewRequestEditorView.js
@@ -603,7 +603,8 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
                   '_onMuteClicked', '_onUnmuteClicked');
 
         this._fieldEditors = {};
-        this._hasFields = (this.$('.editable').length > 0);
+        this._hasFields = (this.$('.editable').length > 0 ||
+            this.$('.custom-editable').length > 0);
 
         if (this._hasFields) {
             _.each(this.defaultFields, function(fieldInfo) {
@@ -726,7 +727,8 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
          * extensions.
          */
         if (this._hasFields) {
-            _.each(this.$('.field.editable'), function(field) {
+            _.each(this.$('.field.editable, .field.custom-editable'),
+                function(field) {
                 var $field = $(field),
                     fieldID = $field.data('field-id'),
                     isCommaEditable,
@@ -735,9 +737,8 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
                     rawValue;
 
                 if (!this._fieldEditors[fieldID] &&
-                    $field.hasClass('editable')) {
-                    isCommaEditable = $field.hasClass('comma-editable');
-
+                    ($field.hasClass('editable') ||
+                     $field.hasClass('custom-editable'))) {
                     fieldInfo = {
                         fieldID: fieldID
                     };
@@ -750,29 +751,45 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
                         extraData[fieldID] = rawValue || '';
                     }
 
-                    $field.removeAttr('data-raw-value');
+                    if ($field.hasClass('editable')) {
+                        isCommaEditable = $field.hasClass('comma-editable');
+                        $field.removeAttr('data-raw-value');
+
 
-                    if ($field.data('allow-markdown')) {
+                    /*if ($field.data('allow-markdown')) {
                         fieldInfo.allowMarkdown = true;
                         richTextFieldID = fieldID + 'RichText';
                         extraData[richTextFieldID] =
                             $field.hasClass('rich-text');
-                    }
+                    }*/
+                        if ($field.data('allow-markdown')) {
+                            fieldInfo.allowMarkdown = true;
+
+                            if (fieldID === 'text') {
+                                richTextFieldID = 'rich_text';
+                            } else {
+                                richTextFieldID = fieldID + '_rich_text';
+                            }
 
-                    if (isCommaEditable) {
-                        fieldInfo.useEditIconOnly = true;
-                        fieldInfo.formatter = function(view, data, $el) {
-                            data = data || [];
-                            $el.html(data.join(', '));
-                        };
-                    } else if (fieldInfo.allowMarkdown) {
-                        fieldInfo.formatter = function(view, data, $el,
-                                                       fieldOptions) {
-                            view.formatText($el, {
-                                newText: data,
-                                fieldOptions: fieldOptions
-                            });
-                        };
+                            extraData[richTextFieldID] =
+                                $field.hasClass('rich-text');
+                        }
+
+                        if (isCommaEditable) {
+                            fieldInfo.useEditIconOnly = true;
+                            fieldInfo.formatter = function (view, data, $el) {
+                                data = data || [];
+                                $el.html(data.join(', '));
+                            };
+                        } else if (fieldInfo.allowMarkdown) {
+                            fieldInfo.formatter = function (view, data, $el,
+                                                            fieldOptions) {
+                                view.formatText($el, {
+                                    newText: data,
+                                    fieldOptions: fieldOptions
+                                });
+                            };
+                        }
                     }
 
                     this.registerField(fieldInfo);
@@ -907,21 +924,85 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
      */
     setupFieldEditor: function(fieldID) {
         var fieldOptions = this._fieldEditors[fieldID],
-            $el = this.$(fieldOptions.selector);
+            $el = this.$(fieldOptions.selector),
+            newFieldView = null;
 
         if ($el.length === 0) {
             return;
         }
 
-        this._buildEditor($el, fieldOptions);
+        var editableProp = (fieldOptions.statusField
+                            ? 'statusEditable'
+                            : 'editable');
+        var enable = this.model.get(editableProp);
+
+        if($el.attr('data-custom-editor') === 'checkbox'){
+            newFieldView = new RB.CheckboxFieldView({el : $el, fieldOptions: fieldOptions});
+            newFieldView.buildFieldEditor(enable);
+        }
+        else if($el.attr('data-custom-editor') === 'dropdown'){
+            newFieldView = new RB.DropdownFieldView({el: $el, fieldOptions: fieldOptions});
+            newFieldView.buildFieldEditor(enable);
+        }
+        else if($el.attr('data-custom-editor') === 'radio'){
+            newFieldView = new RB.RadioButtonFieldView({el: $el, fieldOptions: fieldOptions});
+            newFieldView.buildFieldEditor(enable);
+        }
+        else if($el.attr('data-custom-editor') === 'datetime'){
+            newFieldView = new RB.DateTimeFieldView({el: $el, fieldOptions: fieldOptions});
+            newFieldView.buildFieldEditor(enable);
+        }
+        else if ($el.attr('data-custom-editor') === 'time'){
+            newFieldView = new RB.TimeFieldView({el:$el, fieldOptions: fieldOptions});
+            newFieldView.buildFieldEditor(enable);
+        }
+        else if ($el.attr('data-custom-editor') === 'date'){
+            newFieldView = new RB.DateFieldView({el:$el, fieldOptions:fieldOptions});
+            newFieldView.buildFieldEditor(enable);
+        }
+        else {
+            this._buildEditor($el, fieldOptions);
+            if (_.has(fieldOptions, 'autocomplete')) {
+                this._buildAutoComplete($el, fieldOptions.autocomplete);
+                $el.inlineEditor('setupEvents');
+            }
+
+            /* TODO: Support listening to extraData fields. */
+            if (fieldOptions.fieldName === 'closeDescription') {
+                listenObj = this.model.get('reviewRequest');
+            } else {
+                listenObj = this.draft;
+            }
 
-        if (_.has(fieldOptions, 'autocomplete')) {
-            this._buildAutoComplete($el, fieldOptions.autocomplete);
-            $el.inlineEditor('setupEvents');
+            this.listenTo(listenObj, 'change:' + fieldOptions.fieldName,
+                          _.bind(this._formatField, this, fieldOptions));
         }
+        
+        if (newFieldView !== null){
+            this.listenTo(newFieldView, 'saveDraft',_.bind(function(fieldView){
+                this.model.setDraftField(
+                     fieldView.fieldOptions.fieldName,
+                     fieldView.content(),
+                     _.defaults({
+                         error: function(error){
+                            this._$warning
+                                .delay(6000)
+                                .fadeOut(400, function(){
+                                    $(this).hide();
+                                })
+                                .show()
+                                .html(error.errorText);
+                         },
+                         success: function(){
+                            this.showBanner();
+                         }
+                     },
+                     fieldOptions, {}),
+                     this);
+            },
+            this));
 
-        this.listenTo(this.model, 'fieldChanged:' + fieldOptions.fieldName,
-                      _.bind(this._formatField, this, fieldOptions));
+        } 
     },
 
     /*
@@ -1759,4 +1840,4 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
 });
 
 
-})();
\ No newline at end of file
+})();
diff --git a/reviewboard/static/rb/js/views/reviewRequestFieldView.js b/reviewboard/static/rb/js/views/reviewRequestFieldView.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a27c230601c7339a1ada9c6ed2bfedeaf0cecc2
--- /dev/null
+++ b/reviewboard/static/rb/js/views/reviewRequestFieldView.js
@@ -0,0 +1,388 @@
+(function(){
+RB.ReviewRequestFieldView = Backbone.View.extend({
+    className: "reviewRequestField",
+    /*
+     * You should always pass in {el: element, fieldOptions: fieldOptions}
+     */
+    initialize: function(options){
+        this.fieldOptions = options.fieldOptions;
+    },
+    render:function(){},
+    buildFieldEditor: function(editorView){
+    },
+    /*
+     * This function will be used to define the "save function"
+     */
+    //attachToModel:function(model){}, 
+    /*
+     * This function return the current value of this field,
+     * the value of the field should be independent from events.
+     * i.e. we do not need to know what is clicked in order to
+     * return the value of this field.
+     */
+    content:function(){return null;},
+    triggerSave: function(){
+        this.trigger('saveDraft', this);
+    }
+
+});
+
+RB.TextFieldView = RB.ReviewRequestFieldView.extend({
+    className: "TextField",
+    initialize: function(options){
+        RB.ReviewRequestFieldView.prototype.initialize.apply(this,[options]);
+    },
+    
+    modelAttrs: ['richTextAttr', 'fieldName'],
+    buildFieldEditor: function(enable, options){
+        var el = this.$el[0],
+            id = el.id,
+            multiline = this.$el.hasClass('field-text-area'),
+            f_options = {
+                cls: id + '-editor',
+                editIconClass: 'rb-icon rb-icon-edit',
+                enabled: enable,
+                multiline: multiline,
+                useEditIconOnly: fieldOptions.useEditIconOnly,
+                showRequiredFlag: this.$el.hasClass('required'),
+                deferEventSetup: _.has(fieldOptions, 'autocomplete')
+            },
+            richText = options.richText,
+            rawValue = options.rawValue,
+            this_view = this;
+        if (this.fieldOptions.allowMarkdown){
+            _.extend(
+                options,
+                RB.TextEditorView.getInlineEditorOptions({
+                    minHeight: 0,
+                    richText: richText
+                }),
+                {
+                    matchHeight: false,
+                    hasRawValue: true,
+                    rawValue: rawValue
+                });
+        }
+
+        this.$el.inlineEditor(options)
+            .on({
+                beginEdit: function(){
+                    this_view.trigger('incrEditCount', this_view);
+                },
+                cancel: function(){
+                    this_view.trigger('scheduleResize', this_view);
+                    this_view.trigger('decrEditCount', this_view);
+                },
+                complete: function(){
+                    this_view.trigger('saveDraft', this_view);
+                }
+            });
+    }
+});
+
+RB.MarkdownCapableView = RB.TextFieldView.extend({
+});
+
+RB.RadioButtonFieldView = RB.ReviewRequestFieldView.extend({
+    classeName: "radioButtonField",
+    initialize: function(options){
+        RB.ReviewRequestFieldView.prototype.initialize.apply(this, [options]);
+    },
+    modelAttrs: [],
+
+    buildFieldEditor: function(enable){
+        var $radioButtons = this.$el.find('input');
+        if (enable){
+            var this_view = this;
+            $radioButtons.click(function(){
+                this_view.trigger('saveDraft', this_view);
+            });
+        } else {
+            $radioButtons.attr('disabled', true);
+        }
+   },
+    
+    content: function(){
+        return this.$el.find('input:checked').first().val();
+    }
+});
+
+RB.CheckboxFieldView = RB.ReviewRequestFieldView.extend({  
+    className: "checkboxField",
+    initialize: function(options){
+        RB.ReviewRequestFieldView.prototype.initialize.apply(this, [options]);
+    },
+    modelAttrs:[],
+
+    buildFieldEditor: function(enable){
+        var $checkboxEditor = this.$el.find('input').first(),
+            this_view = this;
+        if (enable){
+            $checkboxEditor.click(function(){
+                this_view.trigger('saveDraft', this_view);
+            });
+        } else {
+            $checkboxEditor.attr('disabled', true);
+        }
+     },
+
+    content: function(){
+        return this.$el.find('input').first().is(':checked');
+    },
+});
+
+RB.DateTimeFieldView = RB.ReviewRequestFieldView.extend({
+    className: "datetimeField",
+    initialize: function(options){
+        RB.ReviewRequestFieldView.prototype.initialize.apply(this, [options]);
+    },
+    modelAttr:[],
+
+    buildFieldEditor: function(enable){
+
+        var $datePicker = $("<input type='text'/>"),
+            $hourPicker = $("<select/>"),
+            $minutePicker = $("<select/>"),
+            $timezonePicker = $("<select/>"),
+            this_view = this,
+            initialValue = this.$el.data('raw-value'),
+            initialTime, m, h, z;
+        
+        function _send_save_signal(){
+            this_view.trigger('saveDraft', this_view);
+        }
+
+        function _pad(num){
+            return (num < 10) ? ("0" + num) : "" + num;
+        }
+
+        for (h = 0; h < 24; h++){
+            $hourPicker.append($('<option>',{
+                value: _pad(h),
+                text: _pad(h)
+            }));
+        }
+
+        for (m = 0; m < 60; m++){
+            $minutePicker.append($('<option>',{
+                value: _pad(m),
+                text: _pad(m)
+            }));
+        }
+
+        for (z = -14; z <= 14; z++){
+            var formatted_offset = moment().utcOffset(z).format("Z");
+            $timezonePicker.append($('<option>',{
+                value: formatted_offset,
+                text: formatted_offset
+            }));
+        }
+
+        this.$el.append($datePicker);
+        this.$el.append($hourPicker);
+        this.$el.append($minutePicker);
+        this.$el.append($timezonePicker);
+ 
+        if (initialValue && initialValue !== 'None'){
+            initialTime = moment.parseZone(initialValue);
+
+            if (initialTime.isValid()){
+                $datePicker.val(initialTime.format('MM/DD/YYYY'));
+                $hourPicker.val(_pad(initialTime.hour()));
+                $minutePicker.val(_pad(initialTime.minute()));
+                $timezonePicker.val(initialTime.format("Z"));
+            }   
+        }
+      
+        $datePicker.datepicker({
+            showButtonPanel: true,
+            changeMonth: true,
+            changeYear: true
+        });
+
+        if (enable){
+            $datePicker.change(function(){
+                _send_save_signal();
+            });
+            $minutePicker.change(function(){
+                _send_save_signal();
+            });
+            $hourPicker.change(function(){
+                _send_save_signal();
+            });
+            $timezonePicker.change(function(){
+                _send_save_signal();
+            });
+        } else {
+            $datePicker.attr('disabled', true);
+            $minutePicker.attr('disabled', true);
+            $hourPicker.attr('disabled', true);
+            $timezonePicker.attr('disabled', true);
+        }
+
+   },
+    
+    content: function(){
+        var $datePicker = this.$el.find('input').first(),
+            $hourPicker = this.$el.find('select').eq(0),
+            $minutePicker = this.$el.find('select').eq(1),
+            $timezonePicker = this.$el.find('select').eq(2),
+            time = '';
+        
+        time += $datePicker.val() + ' ';
+        time += $hourPicker.val() + ':' + $minutePicker.val() + ' ';
+        time += $timezonePicker.val();
+        return moment.parseZone(time, 'MM/DD/YYYY hh:mm Z').format();
+    }    
+});
+
+RB.DateFieldView = RB.ReviewRequestFieldView.extend({
+    className: "dateField",
+
+    initialize: function(options){
+        RB.ReviewRequestFieldView.prototype.initialize.apply(this, [options]);
+    },
+
+    modelAttr:[],
+    
+    buildFieldEditor: function(enable){
+        var $datePicker = $("<input type='text'>"),
+            this_view = this,
+            initialValue = this.$el.data('raw-value');
+        
+        this.$el.append($datePicker);
+        
+        if (initialValue && initialValue !== 'None'){
+            $datePicker.val(initialValue);
+        }
+
+        $datePicker.datepicker({
+            showButtonPanel: true,
+            changeMonth: true,
+            changeYear: true
+        });
+
+        if (enable){
+            $datePicker.change(function(){
+                this_view.trigger('saveDraft', this_view);
+            });
+        } else {
+            $datePicker.attr('disabled', true);
+        }        
+    },
+
+    content: function(){
+        return this.$el.find('input').first().val();
+    }
+    
+});
+
+RB.TimeFieldView = RB.ReviewRequestFieldView.extend({
+    className: "timeField",
+
+    initialize: function(options){
+        RB.ReviewRequestFieldView.prototype.initialize.apply(this, [options]);        
+    },
+
+    modelAttr: [],
+
+    buildFieldEditor: function(enable){
+        var $hourPicker = $('<select/>'),
+            $minutePicker = $('<select/>'),
+            $timezonePicker = $('<select/>'),
+            this_view = this,
+            initialValue = this.$el.data('raw-value'),
+            initialTime, h, m, z;
+        
+        function _send_save_signal(){
+            this_view.trigger('saveDraft', this_view);
+        }
+
+        function _pad(num){
+            return (num < 10) ? ("0" + num) : "" + num;
+        }
+        
+        for (h = 0; h < 24; h++){
+            $hourPicker.append($('<option>',{
+                value: _pad(h),
+                text: _pad(h)
+            }));
+        }
+
+        for (m = 0; m < 60; m++){
+            $minutePicker.append($('<option>',{
+                value: _pad(m),
+                text: _pad(m)
+            }));
+        }
+
+        for (z = -14; z <= 14; z++){
+            var formatted_offset = moment().utcOffset(z).format("Z");
+            $timezonePicker.append($('<option>',{
+                value: formatted_offset,
+                text: formatted_offset
+            }));
+        }
+
+        this.$el.append($hourPicker);
+        this.$el.append($minutePicker);
+        this.$el.append($timezonePicker);
+        
+        if (initialValue && initialValue !== 'None'){
+            initialTime = moment.parseZone(initialValue, "HH:mmZ");
+            if (initialTime.isValid()){
+                $hourPicker.val(_pad(initialTime.hour()));
+                $minutePicker.val(_pad(initialTime.minute()));
+                $timezonePicker.val(initialTime.format("Z"));
+            }
+        }
+
+        if (enable){
+            $minutePicker.change(_send_save_signal);
+            $hourPicker.change(_send_save_signal);
+            $timezonePicker.change(_send_save_signal);
+        } else {
+            $minutePicker.attr('disabled', true);
+            $hourPicker.attr('disabled', true);
+            $timezonePicker.attr('disabled', true);
+        }
+   },
+
+    content: function(){
+        var $hourPicker = this.$el.find('select').eq(0),
+            $minutePicker = this.$el.find('select').eq(1),
+            $timezonePicker = this.$el.find('select').eq(2),
+            time = '';
+        time += $hourPicker.val() + ':' + $minutePicker.val() + ' ';
+        time += $timezonePicker.val();
+        return time;
+    }
+});
+
+RB.DropdownFieldView = RB.ReviewRequestFieldView.extend({
+    className: "dropdownField",
+
+    initialize: function(options){
+        RB.ReviewRequestFieldView.prototype.initialize.apply(this, [options]);
+    },
+    modelAttr:[],
+
+    buildFieldEditor: function(enable){
+        var $dropdownEditor = this.$el.find('select').first();
+        
+        if (enable){
+            var this_view = this;
+            $dropdownEditor.change(function(){
+                this_view.trigger('saveDraft', this_view);
+            });
+        } else {
+            $dropdownEditor.attr('disabled', true);
+        }
+    },
+
+    content: function(){
+        return this.$el.find('select').first().val();
+    }
+});
+
+})();
diff --git a/reviewboard/static/rb/js/views/tests/reviewRequestFieldViewTests.js b/reviewboard/static/rb/js/views/tests/reviewRequestFieldViewTests.js
new file mode 100644
index 0000000000000000000000000000000000000000..81132b786a9c9636e39de88b2c624336f3754926
--- /dev/null
+++ b/reviewboard/static/rb/js/views/tests/reviewRequestFieldViewTests.js
@@ -0,0 +1,265 @@
+suite('rb/views/ReviewRequestFieldView', function(){
+
+    var field_template = _.template([
+            '<div',
+            ' id="<%= id %>"',
+            ' class="field custom-editable"',
+           '/>' 
+        ].join(''));
+    
+    beforeEach(function(){
+        
+    });
+
+    describe('Date field', function(){
+        var view, $el;
+
+        beforeEach(function(){
+            $el = $(field_template({id: 'date_test'}));
+            view = new RB.DateFieldView($el, {});               
+        });
+
+        describe('Immutable Date field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(false);  
+            });
+
+           it('Disabled', function(){
+                expect(view.$el.find('input').attr('disabled')).toBe('disabled');
+            });
+        });
+
+        describe('Mutable Date field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(true);
+            });
+            
+            it('Input Datepicker', function(){
+                expect(view.$el.find('input').hasClass('hasDatePicker'));
+            });
+
+            it('Trigger saveDraft', function(){
+                var saveDraftSpy = jasmine.createSpy();
+                view.on('saveDraft', saveDraftSpy);
+                view.$el.find('input').trigger('change');
+                expect(saveDraftSpy).toHaveBeenCalled();
+            });
+        });
+    });
+
+    describe('Time field', function(){
+        var view, $el;
+        
+        beforeEach(function(){
+            $el = $(field_template({id: 'time_test'}));
+            view = new RB.TimeFieldView($el, {});
+        });
+
+        describe('Immutable Time field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(false);
+            });
+
+            it('Disabled', function(){
+                expect(view.$el.find('select').attr('disabled')).toBe('disabled');
+            });
+        });
+
+        describe('Mutable Time field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(true);
+            });
+        
+            it('Correct Elements', function(){
+                expect(view.$el.find('select').length).toBe(3);
+            });
+
+            describe('Trigger saveDraft', function(){
+                var saveDraftSpy;
+                beforeEach(function(){
+                    saveDraftSpy = jasmine.createSpy();
+                    view.on('saveDraft', saveDraftSpy);
+                });
+
+                it('Hour', function(){
+                    view.$el.find('select').eq(0).change();
+                    expect(saveDraftSpy).toHaveBeenCalled();
+                });
+                
+                it('Minute', function(){
+                    view.$el.find('select').eq(1).change();
+                    expect(saveDraftSpy).toHaveBeenCalled();
+                });
+
+                it('Timezone Offset', function(){
+                    view.$el.find('select').eq(2).change();
+                    expect(saveDraftSpy).toHaveBeenCalled();
+                });
+            });
+        });
+    });
+
+    describe('Datetime field', function(){
+        var view, $el;
+        beforeEach(function(){
+            $el = $(field_template({id: 'datetime_test'}));
+            view = new RB.DateTimeFieldView($el, {});
+        });
+
+        describe('Immutable Datetime field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(false);
+            });
+
+            it('Disabled', function(){
+                expect(view.$el.find('select').attr('disabled')).toBe('disabled');
+                expect(view.$el.find('input').attr('disabled')).toBe('disabled');
+            });
+        });
+
+        describe('Mutable Datetime field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(true);
+            });
+            
+            it('Correct Elements', function(){
+                expect(view.$el.find('select').length).toBe(3);
+                expect(view.$el.find('input').hasClass('hasDatePicker'));
+            });
+
+            describe('Trigger saveDraft', function(){
+                var saveDraftSpy;
+                beforeEach(function(){
+                    saveDraftSpy = jasmine.createSpy();
+                    view.on('saveDraft', saveDraftSpy);
+                });
+
+                it('Hour', function(){
+                    view.$el.find('select').eq(0).change();
+                    expect(saveDraftSpy).toHaveBeenCalled();
+                });
+                
+                it('Minute', function(){
+                    view.$el.find('select').eq(1).change();
+                    expect(saveDraftSpy).toHaveBeenCalled();
+                });
+
+                it('Timezone Offset', function(){
+                    view.$el.find('select').eq(2).change();
+                    expect(saveDraftSpy).toHaveBeenCalled();
+                });
+
+                it('Date', function(){
+                    view.$el.find('input').first().change();
+                    expect(saveDraftSpy).toHaveBeenCalled();
+                });
+ 
+
+            });
+        });
+ 
+    });
+
+    describe('Dropdown field', function(){
+        var view, $el;
+        beforeEach(function(){
+            $el = $(field_template({id: 'dropdown_test'}));
+            var $selector = $('<select/>');
+            $selector.append('<option value="option1"> option1 </option>');
+            $selector.append('<option value="option2"> option2 </option>');
+            $el.append($selector);
+            view = new RB.DropdownFieldView({el: $el});
+        });
+
+        describe('Immutable Dropdown field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(false);
+            });
+
+            it('Disabled', function(){
+                expect(view.$el.find('select').first().attr('disabled')).toBe('disabled');
+            });
+        });
+
+        describe('Mutable Dropdown field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(true);
+            });
+
+            it('trigger saveDraft', function(){
+                var saveDraftSpy = jasmine.createSpy();
+                view.on('saveDraft', saveDraftSpy);
+                view.$el.find('select').first().change();
+                expect(saveDraftSpy).toHaveBeenCalled();
+            });
+        });
+    });
+    
+    describe('Radiobutton field',function(){
+        var view, $el;
+        beforeEach(function(){
+            $el = $(field_template({id: 'radiobutton_test'}));
+            $input1 = $('<input type="radio" name="r1" value="r1" /><br/>');
+            $input2 = $('<input type="radio" name="r2" value="r2" /><br/>');
+            $el.append($input1);
+            $el.append($input2);
+            view = new RB.RadioButtonFieldView({el: $el});
+        });
+
+        describe('Immutable Radio field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(false);
+            });
+            
+            it('Disabled', function(){
+                expect(view.$el.find('input').attr('disabled')).toBe('disabled');
+            });
+        });
+
+        describe('Mutable Radio field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(true);
+            });
+
+            it('trigger saveDraft', function(){
+                var saveDraftSpy = jasmine.createSpy();
+                view.on('saveDraft', saveDraftSpy);
+                view.$el.find('input').first().click();
+                expect(saveDraftSpy).toHaveBeenCalled();
+            });
+        });
+    
+    });
+
+    describe('Checkbox field', function(){
+        var view, $el;
+        beforeEach(function(){
+            $el = $(field_template({id: 'checkbox_test'}));
+            $el.append($('<input type="checkbox"/>'));
+            view = new RB.CheckboxFieldView({el: $el});
+        });
+
+        describe('Immutable Checkbox field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(false);
+            });
+
+            it('Disabled', function(){
+                expect(view.$el.find('input').attr('disabled')).toBe('disabled');
+            });
+        });
+
+        describe('Mutable Checkbox field', function(){
+            beforeEach(function(){
+                view.buildFieldEditor(true);
+            });
+
+            it('trigger saveDraft', function(){
+                var saveDraftSpy = jasmine.createSpy();
+                view.on('saveDraft', saveDraftSpy);
+                view.$el.find('input').first().click();
+                expect(saveDraftSpy).toHaveBeenCalled();
+            });
+        });   
+    });
+});
diff --git a/reviewboard/staticbundles.py b/reviewboard/staticbundles.py
index bb42045379c745182364fcca4801064509531fba..e1b8fbcfca64adaa0aadead98d8a8d5608048a6f 100644
--- a/reviewboard/staticbundles.py
+++ b/reviewboard/staticbundles.py
@@ -105,6 +105,7 @@ PIPELINE_JAVASCRIPT = dict({
             'rb/js/views/tests/reviewBoxViewTests.js',
             'rb/js/views/tests/reviewDialogViewTests.js',
             'rb/js/views/tests/reviewRequestEditorViewTests.js',
+            'rb/js/views/tests/reviewRequestFieldViewTests.js',
             'rb/js/views/tests/reviewReplyDraftBannerViewTests.js',
             'rb/js/views/tests/reviewReplyEditorViewTests.js',
             'rb/js/views/tests/reviewViewTests.es6.js',
@@ -251,6 +252,7 @@ PIPELINE_JAVASCRIPT = dict({
             'rb/js/views/reviewDialogView.es6.js',
             'rb/js/views/reviewReplyDraftBannerView.js',
             'rb/js/views/reviewReplyEditorView.js',
+            'rb/js/views/reviewRequestFieldView.js',
             'rb/js/views/reviewRequestEditorView.js',
             'rb/js/views/reviewView.es6.js',
             'rb/js/views/screenshotThumbnailView.js',
