Port $.inlineEditor to Backbone.

Review Request #9223 — Created Sept. 26, 2017 and submitted

Information

Review Board
release-3.0.x
836c452...

Reviewers

The inline editor view is some of the first JavaScript code we wrote, and boy
does it show. It was built as a jQuery-UI component, and has grown over the
years to have a huge number of options. Unfortunately, the only extensibility
it provided was the ability to render a custom element when in multiline mode.
This was enough to let us add in the rich-text CodeMirror editor, but going
further was out of the question.

This change adds a new Backbone view called InlineEditorView which does
everything that $.inlineEditor can do and more. It provides all the options
that the jQuery-UI implementation does, but can also be subclassed to implement
even more complex behavior or replace the text editors with other input types
(including things as fancy as Selectize).

This updates most (but not quite all) uses of $.inlineEditor to the new view.
Ports for the last uses will be coming in a later change.

  • Ran js-tests.
  • Manually tested all of the builtin review request fields that used inline
    editors.
  • Tested multiline fields with both Markdown and plain text input and switching
    between the two.
  • Tested dirty state calculations and attempting to leave the page with dirty,
    unsaved editors.
Description From Last Updated

"Backbone" and "JavaScript" in the summary/description.

chipx86chipx86

Can this live in Djblets? Seems a more natural place for it, given that's where the prior version lives. I'd …

chipx86chipx86

Col: 56 ['summary'] is better written in dot notation.

reviewbotreviewbot

Class is missing a doc comment.

chipx86chipx86

This is missing support for useEditIconOnly and the logic around it.

chipx86chipx86

Backbone.View.prototype.initialize is guaranteed to be a no-op. We don't need to call it.

chipx86chipx86

Looks like an old type.

chipx86chipx86

Let's do .hide() first. It'll save some churn.

chipx86chipx86

Let's .hide() first here too.

chipx86chipx86

Instead of an extra function, can we pass this.submit.bind(this) and this.cancel.bind(this)?

chipx86chipx86

The key property doesn't look completely supported in Edge and appears completely unsupported in Safari for these keys. Can we …

chipx86chipx86

/** -> /*

chipx86chipx86

Blank line between these.

chipx86chipx86

Can we use .bind(this)?

chipx86chipx86

Can we take options instead, for future enhancement?

chipx86chipx86

Same here.

chipx86chipx86

This is referring to outerHeight itself and not calling it, and isn't accessing the option correctly. Needs manual testing.

chipx86chipx86

This no longer recalculates the dirty state in this method. Was that not needed?

chipx86chipx86

Logic here is different from what we have in the original code. This isn't doing the throttle anymore, which was …

chipx86chipx86

This should go below public methods.

chipx86chipx86

Original code normalized initialValue first. The new code is doing it in the default isFieldDirty, but TextEditorView doesn't benefit from …

chipx86chipx86

Can we call this isDirty()?

chipx86chipx86

Can you compare to 0?

chipx86chipx86

Missing docs.

chipx86chipx86

The original version also set $el.data('text-editor'), which was accessed by other things. Might be worth adding that back?

chipx86chipx86

buttons used to be public, and now it's private. Can we go ahead and make this $buttons?

chipx86chipx86

Instead of accessing a private variable, can we make $field public?

chipx86chipx86

Col: 63 Missing semicolon.

reviewbotreviewbot

Can we call it animationSpeedMS? The abbreviation doesn't save much.

brenniebrennie

Since this isn't a closure over variables, we can pull it out into a function and just pass it in …

brenniebrennie

Should this have aria-hidden="true" since its a visual cue only and we don't want screen-readers to attempt to read the …

brenniebrennie

Any reason we don't set this up in this.events ? Or in delegateEvents ?

brenniebrennie

Should we pul this out into this.$window in initialize?

brenniebrennie

Might as well make this one line.

chipx86chipx86

This can be one line (no need for {})

chipx86chipx86

You can use this._updateDirtyState.bind(this) instead, remove an unnecessary anonymous function.

chipx86chipx86
Checks run (1 failed, 1 succeeded)
flake8 passed.
JSHint failed.

JSHint

david
chipx86
  1. Nice cleanup. I went through each function and compared implementations, and there are some things that are missing or inconsistent. Given the fixes that have gone into inlineEditor over time, I'd like to make sure they're as consistent as possible. Made notes of what I saw.

  2. Show all issues

    "Backbone" and "JavaScript" in the summary/description.

  3. Show all issues

    Can this live in Djblets? Seems a more natural place for it, given that's where the prior version lives. I'd also eventually like to use this in Splat.

    1. Just to keep development simple for now, can we finish iterating on it here, and then I'll prepare a new change with it in djblets?

  4. Show all issues

    Class is missing a doc comment.

  5. Show all issues

    This is missing support for useEditIconOnly and the logic around it.

  6. Show all issues

    Backbone.View.prototype.initialize is guaranteed to be a no-op. We don't need to call it.

  7. Show all issues

    Looks like an old type.

  8. reviewboard/static/rb/js/ui/views/inlineEditorView.es6.js (Diff revision 2)
     
     
     
     
     
     
    Show all issues

    Let's do .hide() first. It'll save some churn.

  9. Show all issues

    Let's .hide() first here too.

  10. reviewboard/static/rb/js/ui/views/inlineEditorView.es6.js (Diff revision 2)
     
     
     
     
     
     
     
    Show all issues

    Instead of an extra function, can we pass this.submit.bind(this) and this.cancel.bind(this)?

  11. Show all issues

    The key property doesn't look completely supported in Edge and appears completely unsupported in Safari for these keys. Can we go back to using keyCode and the constants for now?

  12. Show all issues

    /** -> /*

  13. Show all issues

    Blank line between these.

  14. Show all issues

    Can we use .bind(this)?

  15. reviewboard/static/rb/js/ui/views/inlineEditorView.es6.js (Diff revision 2)
     
     
     
     
     
     
    Show all issues

    Can we take options instead, for future enhancement?

  16. reviewboard/static/rb/js/ui/views/inlineEditorView.es6.js (Diff revision 2)
     
     
     
     
     
     
    Show all issues

    Same here.

  17. Show all issues

    This is referring to outerHeight itself and not calling it, and isn't accessing the option correctly. Needs manual testing.

  18. Show all issues

    This no longer recalculates the dirty state in this method. Was that not needed?

    1. this.dirty() will recalculate the dirty state if needed.

  19. reviewboard/static/rb/js/ui/views/inlineEditorView.es6.js (Diff revision 2)
     
     
     
     
     
     
     
    Show all issues

    Logic here is different from what we have in the original code. This isn't doing the throttle anymore, which was added intentionally. (It was also doing a _.defer(), but this was prior to the _.throttle(), and may not be needed).

    This should also go below public methods.

    1. The setTimeout here does something similar to the throttling but avoids a problem I was encountering where we were calculating the state multiple times while also trying to save.

  20. Show all issues

    This should go below public methods.

  21. Show all issues

    Original code normalized initialValue first. The new code is doing it in the default isFieldDirty, but TextEditorView doesn't benefit from that.

    1. I'll add a call to _normalizeText inside RichTextInlineEditorView's options. We can't do it universally because future fields won't necessarily have text-type values.

    2. In this case, we should rename to normalizeText (make it public).

  22. Show all issues

    Can we call this isDirty()?

  23. Show all issues

    Can you compare to 0?

  24. Show all issues

    Missing docs.

  25. Show all issues

    The original version also set $el.data('text-editor'), which was accessed by other things. Might be worth adding that back?

  26. Show all issues

    buttons used to be public, and now it's private. Can we go ahead and make this $buttons?

  27. Show all issues

    Instead of accessing a private variable, can we make $field public?

  28. 
      
david
Review request changed
Summary:
Port $.inlineEditor to backbone.
Port $.inlineEditor to Backbone.
Description:
~  

The inline editor view is some of the first javascript code we wrote, and boy

  ~

The inline editor view is some of the first JavaScript code we wrote, and boy

    does it show. It was built as a jQuery-UI component, and has grown over the
    years to have a huge number of options. Unfortunately, the only extensibility
    it provided was the ability to render a custom element when in multiline mode.
    This was enough to let us add in the rich-text CodeMirror editor, but going
    further was out of the question.

   
~  

This change adds a new backbone view called InlineEditorView which does

  ~

This change adds a new Backbone view called InlineEditorView which does

    everything that $.inlineEditor can do and more. It provides all the options
    that the jQuery-UI implementation does, but can also be subclassed to implement
    even more complex behavior or replace the text editors with other input types
    (including things as fancy as Selectize).

   
   

This updates most (but not quite all) uses of $.inlineEditor to the new view.

    Ports for the last uses will be coming in a later change.

Commit:
135961518313686f9f07c74dc7cf05ef2b53c294
460208b672ee6073c1d5361e41ba1de17573850e

Checks run (1 failed, 1 succeeded)

flake8 passed.
JSHint failed.

JSHint

david
brennie
  1. Some minor suggestions, this all looks pretty great :)

  2. Show all issues

    Can we call it animationSpeedMS? The abbreviation doesn't save much.

  3. reviewboard/static/rb/js/ui/views/inlineEditorView.es6.js (Diff revision 4)
     
     
     
     
     
     
     
     
     
     
     
    Show all issues

    Since this isn't a closure over variables, we can pull it out into a function and just pass it in instead of recreating it every time we make a new InlineEditorView.

  4. Show all issues

    Should this have aria-hidden="true" since its a visual cue only and we don't want screen-readers to attempt to read the button?

  5. Show all issues

    Any reason we don't set this up in this.events ? Or in delegateEvents ?

    1. We need to be able to defer setting up events if the editor wants autocomplete (or some other functionality that needs preference on events).

  6. Show all issues

    Should we pul this out into this.$window in initialize?

    1. This is only used the once. I don't see a good reason to keep it around.

  7. 
      
david
chipx86
  1. 
      
  2. Show all issues

    Might as well make this one line.

  3. Show all issues

    This can be one line (no need for {})

  4. Show all issues

    You can use this._updateDirtyState.bind(this) instead, remove an unnecessary anonymous function.

  5. 
      
david
chipx86
  1. Ship It!
  2. 
      
david
Review request changed
Status:
Completed
Change Summary:
Pushed to release-3.0.x (16ef508)