Search code examples
javascriptwebpackecmascript-6coffeescript

From Coffeescript to ES6 using Rails webpacker, how to manage classes?


I'm not a well skilled javascript expert. I'm migrating a very old app to webpacker. I have a lot of coffeescript files like this:

class @SectionTable
  constructor: ->
    @table  = $('#site_section')
    @_dnd()

  _dnd: ->
    @table.tableDnD onDrop: (table, row) ->
      data = $.tableDnD.serialize()
      $.ajax
        type: 'POST'
        url: '/admin/sections/reorder'
        data: data

$ -> new SectionTable()

I already created a structure for my Javascript files in webpacker.

I have some page specific scripts and some global script that I init using a init.js file like this

import timePicker from './timePicker.js';
import Redactor from './redactor.js';

(function init() {
  const dtPicker = timePicker();
  const redactor = Redactor();
  document.addEventListener("turbolinks:load", () => {
    dtPicker.init();
    redactor.init();
  });
}());

And then, inside timePicker.js, I init single components

import 'bootstrap-datetime-picker/js/bootstrap-datetimepicker.js';
import 'bootstrap-datetime-picker/js/locales/bootstrap-datetimepicker.it.js';

const timePicker = () => {
    const initDateTimePicker = () => {
      const dateTime = $('.datetime');
      if (dateTime.length > 0) {
        $('.datetime').datetimepicker({
           todayHighlight: true,
           autoclose: true,
           pickerPosition: 'bottom-left',
           todayBtn: true,
           format: 'hh:ii dd/mm/yyyy'
         });
      }
    };
    const init = () => {
      initDateTimePicker();
   };
   return {
     init,
   };
};

export default timePicker;

I cannot find a way to adjust my coffeescript objects inside the new logic. The coffeescript above is very simple, but I have also some complex objects like this:

@Cover = {}

class Cover.Preview
  constructor: ->
    @elements    = {} # preview elements, for each tab/preview box
    @element     = $('#cover_format')
    @container   = $('#cover_preview')
    @button      = $('#change_format')
    @url         = @element.data('url')
    @setFormat()
    @bindChange()

  addElement: (element, position) ->
    position = element.position
    @elements[position] = element

  bindChange: ->
    @button.click (event) =>
      event.preventDefault()
      @setFormat()
      $.ajax
        url:      "#{@url}?format=#{@format}"
        dataType: 'html'
        success: (html) =>
          @container.html html
          @rebindDrag()
          @repopulate()

  setFormat: -> @format = @element.val()

  rebindDrag: ->
    Cover.FormElement.init()
    Cover.Preview.Tile.init()

  repopulate: ->
    for position, tile of Cover.Preview.Tile.all
      tile.redraw Cover.preview.elements[position]



$ ->
  Cover.preview = new Cover.Preview()

I understand that I have a couple of ways to to this:

1) keep coffeescript and add coffeescript files loader inside webpacker, but I cannot understand how to Init my coffee defined objects inside the init file (and not in the coffee file like now)

2) convert from coffee to ES6, I try with the online tool and I have this result

this.SectionTable = class SectionTable {
  constructor() {
    this.table  = $('#dday_section');
    this._dnd();
  }

  _dnd() {
    return this.table.tableDnD({onDrop(table, row) {
      const data = $.tableDnD.serialize();
      return $.ajax({
        type: 'POST',
        url: '/admin/sections/reorder',
        data
      });
    }
    });
  }
};

$(() => new SectionTable());

How can I add a modular approach? So basically I want to create the new SectionTable in my init file.


Solution

  • Just an example:

    import $ from 'jquery';
    
    export class SectionTable {
      constructor() {
       this.table = $('#site_section');
       this._dnd();
    }
      _dnd() {
       this.table.tableDnD.onDrop((table, row) => {
          const data = $.tableDnD.serialize();
          $.ajax({
            type: 'POST',
            url: '/admin/sections/reorder',
            data: data
          });
       });
      }
    }
    // if you need a single instance like seems to from your code
    export default new SectionTable();
    
    // otherfile.js
    // this is just to show you how you can import only some classes from a file when
    // export is used
    import SectionTableSingleTon, { SectionTable } from './somewhere/SectionTable';
    
    const sectionTable = new SectionTable();
    

    just be cafeful with 'this' with object methods. If you need to pass it around bind it in the constructor.

    constructor() {
       this.someMethod = this.someMethod.bind(this);
    
    }
    attachListener(){
       $('button').click(this.someMethod);
    }
    somemethod(){
    // correct this
    }
    
    

    And you don't need iife anymore inside an esm module