How to create views in javascript


#1

Hi guys! :),

I am trying to develop package and I plugins I looked into used space-pen to create their custom views and it looks nice. I would like to develop in in Javascript so I compiled the code and it was pretty mess. For example this is example from great Git Merge conflicts package:

@content: (state) ->
            @div class: 'merge-conflicts tool-panel panel-bottom padded', =>
              @div class: 'panel-heading', =>
                @text 'Conflicts'
                @span class: 'pull-right icon icon-fold', click: 'minimize', 'Hide'
                @span class: 'pull-right icon icon-unfold', click: 'restore', 'Show'
              @div outlet: 'body', =>
                @ul class: 'block list-group', outlet: 'pathList', =>
                  for {path: p, message} in state.conflicts
                    @li click: 'navigate', class: 'list-item navigate', =>
                      @span class: 'inline-block icon icon-diff-modified status-modified path', p
                      @div class: 'pull-right', =>
                        @button click: 'stageFile', class: 'btn btn-xs btn-success inline-block-tight stage-ready', style: 'display: none', 'Stage'
                        @span class: 'inline-block text-subtle', message
                        @progress class: 'inline-block', max: 100, value: 0
                        @span class: 'inline-block icon icon-dash staged'
                @div class: 'block pull-right', =>
                  @button class: 'btn btn-sm', click: 'quit', 'Quit'

Can you provide partial example of how would you write this nicely without using coffee script?


#2

In my experience the JS created by the CS compiler is as nice as it’s going to get. I’m not really sure what you are asking for.


#3

I am using an ES6 transpiler, which lets you write in a style that is closer to CoffeeScript, if you choose. You would have:

this.div({class: "merge-conflicts tool-panel panel-bottom padded"}, () => {
  this.div({class: "panel-heading"}, () => {
    // etc.
  });
});

The benefit is that in the long-term, this code will run natively in the browser without transpilation.


#4

I wrote a sample JS packages some time ago, the view looked like this:


#5

Hi @mark_hahn,

well the code I got from compiling the code above looks like this:(this is imo really messy)

MergeConflictsView.content = function(state) {
    return this.div({
      "class": 'merge-conflicts tool-panel panel-bottom padded'
    }, (function(_this) {
      return function() {
        _this.div({
          "class": 'panel-heading'
        }, function() {
          _this.text('Conflicts');
          _this.span({
            "class": 'pull-right icon icon-fold',
            click: 'minimize'
          }, 'Hide');
          return _this.span({
            "class": 'pull-right icon icon-unfold',
            click: 'restore'
          }, 'Show');
        });
        return _this.div({
          outlet: 'body'
        }, function() {
          _this.ul({
            "class": 'block list-group',
            outlet: 'pathList'
          }, function() {
            var message, p, _i, _len, _ref2, _ref3, _results;
            _ref2 = state.conflicts;
            _results = [];
            for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
              _ref3 = _ref2[_i], p = _ref3.path, message = _ref3.message;
              _results.push(_this.li({
                click: 'navigate',
                "class": 'list-item navigate'
              }, function() {
                _this.span({
                  "class": 'inline-block icon icon-diff-modified status-modified path'
                }, p);
                return _this.div({
                  "class": 'pull-right'
                }, function() {
                  _this.button({
                    click: 'stageFile',
                    "class": 'btn btn-xs btn-success inline-block-tight stage-ready',
                    style: 'display: none'
                  }, 'Stage');
                  _this.span({
                    "class": 'inline-block text-subtle'
                  }, message);
                  _this.progress({
                    "class": 'inline-block',
                    max: 100,
                    value: 0
                  });
                  return _this.span({
                    "class": 'inline-block icon icon-dash staged'
                  });
                });
              }));
            }
            return _results;
          });
          return _this.div({
            "class": 'block pull-right'
          }, function() {
            return _this.button({
              "class": 'btn btn-sm',
              click: 'quit'
            }, 'Quit');
          });
        });
      };
    })(this));
  };

#6

Hi @abe, thank you for your example. Your example does have content method, but it is rather too simple to show the mess. Please see example I have provided in comment above.

@bolinfest this looks better , I might give it a try althought I have never played with ES6 transpiler. Could you please tell me how exactly are you working with in, while developing Atom package?
Also why did you use it in first place? Was it because problem I am having (code written in JS is messy) or was it something else?


#7

@Trudko, @bolinfest: It seems that JS fat arrows are available in Atom natively:


#8

@abe That’s awesome: the future is here! Or at least it’s on its way…

In my ordinary Chrome browser, I go to chrome://flags/#enable-javascript-harmony to Enable Experimental JavaScript, so I get this feature in my day-to-day browser, as well. I believe that Atom has always been bundling Chromium with this option enabled, which is great.

It appears that things like the fat arrow work today, but other ES6 constructs, such as classes, have not made it into Chromium yet. Therefore, you still need a transpiler to get some of those other features.

Personally, I use jstransform, which is bundled with react-tools, so my package.json includes the following snippet:

  "devDependencies": {
    "react-tools": "~0.11.2"
  },
  "scripts": {
    "prepublish": "node_modules/.bin/jsx --harmony --strip-types src/ lib/"
  }

Then I use gulp to watch the files in my src directory and write the transformed versions into the lib directory during iterative development. I went with gulp because of a discussion on this thread. I’ll try to write up a blog post in the near future that explains this workflow better.

Traceur is also a very popular ES6 transpiler from Google, though as you might expect, it does not recognize jsx, which I use since I write packages that use React.

@Trudko if you drop in the snippet that @abe suggested for doing inheritance and then use the fat arrow, then I think you’ll be in pretty good shape without having to set up a full-on transpiler toolchain. I’m curious to hear how that works out!


#9

@abe @bolinfest

First of all thanks for the info!

I have tried to use arrow functions but in example provided by @bolinfest. I got error TypeError: undefined is not a function. Undefined in this case is this on second line.


#10

@Trudko Could you include the full code sample?


#11

Also, does anyone know what flags I need to pass to Node (v0.10.29) to get things like Promise or arrows? I’m passing --harmony, but it doesn’t seem to work. Is Atom configuring v8/Chromium in an extra special way?


#12

@bolinfest

I have tried your example(I have also tried my own which was similiar to yours)

MyView.content = function(state) {
  this.div({class: "merge-conflicts tool-panel panel-bottom padded"}, () => {
    this.div({class: "panel-heading"}, () => {
      // etc.
    });
  })
};

Error (on line this.div({class: “panel-heading”}, () => {) Uncaught TypeError: undefined is not a function .


#13

IMHO al JS is really messy. Besides the extra returns, which can be easily stripped, what bothers you? A few blank lines and it would look like any other JS.


#14

Is MyView a subclass of View? If not, then this will not point to a View, in which case this.div will be undefined, which I expect is the source of your error.


#15

@bolinfest here is whole view.

var _atom = require('atom');
var view =  _atom.View;
var helpers = require('atom-helpers');


function ArrowFunctionView() {
  ArrowFunctionView.__super__.constructor.apply(this, arguments);
}

helpers.extends(ArrowFunctionView, view);

ArrowFunctionView.prototype.initialize = function() {
  return this;
};

ArrowFunctionView.content = function(state) {
  this.div({class: "merge-conflicts tool-panel panel-bottom padded"}, () => {
    this.div({class: "panel-heading"}, () => {
      // etc.
    });
  })
};

module.exports = ArrowFunctionView;

Error is /home/Trudko/github/arrow-function/lib/arrow-function-view.js:18
line 18 is this.div({class: “panel-heading”}, () => { line 17 is ok so this is view.


Moving my package from CoffeeScript to JavaScript
#16

@mark_hahn well JS can be messy . The use of arrow functions might help, but there seems to be some issue. I am more courious if there is some help from atom it self other than using coffee script.

I guess I can combine javascript for logic with space-pen for views.


#17

Yes, coffeescript happens to be very good at DSLs like space-pen. I have yet to see a decent JS DSL. CS and CSON and any other YAML-like tool are killers for such things.

BTW, I am very biased but I think the more you are around CS the more you’ll gravitate to it. I truly believe it has inherent advantages.


#18

#19

#20

Haha, I wasn’t sure if someone was going to recall that stuff :).