0

I have a Ruby on Rails project (probably not relevant) with a table which is being generated by java/coffescript.

One of the fields of this table, has a button, that I want to call another javascript function.

I've looked at Using an HTML button to call a JavaScript function but it seems to not find the "doFunction" function...

Currently, I have:

class App.Table extends Backbone.Model
  defaults: {
    showPastResults: true
  }

  initialize: ->
    @set("selectedCohortLabel", @get("cohortLabels")[0])

  headers: =>
   [BLAH BLAH BLAH]

  columns: =>
    columns = []

    [BLAH BLAH BLAH]
    Array::push.apply columns, [['<input id="clickMe" type="button" value="clickme" onclick="doFunction();" />', 5, 5]]    if @get('haveDataBytes')
    columns

  doFunction: ->
    console.log("foo")

and I've also tried to put doFunction outside the class. It always gives:

420450:1 Uncaught ReferenceError: doFunction is not defined
    at HTMLInputElement.onclick (420450:1)
onclick @ 420450:1

So where is doFunction supposed to live so that it can find it at runtime?

caffeinated.tech
  • 6,428
  • 1
  • 21
  • 40
Brian Postow
  • 11,709
  • 17
  • 81
  • 125
  • 1
    Don't write HTML strings. Construct DOM elements and properly attach event listeners. – Bergi Aug 27 '18 at 18:40
  • `doFunction` appears to be a method that you should call on an instance, not as a plain function – Bergi Aug 27 '18 at 18:40
  • I tried putting doFunction outside the class as well. it gave the same thing. I'm doing this inside a javascript backbone table template. I think that the only things I can get in there are strings. I had to muck with the template to make it evaluate the HTML in the firstplace... – Brian Postow Aug 27 '18 at 19:00
  • This is a backbone specific question. HTML should be created in a backbone view, which also has options for attaching event listeners (such as on click) to the generated HTML. Here is [an answer](https://stackoverflow.com/a/29947762/2295592) that may point you in the right direction: – caffeinated.tech Aug 28 '18 at 11:53

1 Answers1

0

Your approach does not really follow the Backbone architecture. In Backbone you model your business domain as Models and Collections of Models. If you follow this rule everything falls into place quite nicely.

The decision to name your model Table is wrong in this context as a table is a representational object. The data itself can be displayed in many ways.

What follows is an example how the application could (or should) be designed in a way that does not work against Backbone.js. The data is modeled as a Collection of Elements, each Element will be represented in a row of the table. Please follow the comments in the source code for further explanation.

File: index.html (I build the application using npm and browserify, which is not shown here. Be sure to provide the necessary dependencies).

<!doctype html>
<html>
    <head>
        <title>Coffeescript And Backbone</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <h2>Elements Table</h2>
            <div class="table"></div>
        </div>

        <script id="tablerow" type="text/x-handlebars-template">
            <td>{{id}}</td>
            <td>{{name}}</td>
            <td>{{value}}</td>
            <td><button class="button">HaveDataBytes</button></td>
        </script>

        <script src="app.js"></script>
    </body>
</html>

File: app.coffee

# The template is integrated in index.html (script#tablerow).
# Here I read and compile the template function for the table row.
#
# This must be called after the template script is in the DOM.
#
# Using a packaging tool like browserify or webpack would allow you
# to precompile the templates during the packaging process, but this
# approach is good for small applications.
compileTemplate = (name) ->
    source = document.getElementById(name).innerHTML
    Handlebars.compile(source)

rowTemplate = compileTemplate('tablerow')

# Define the applications backbone
App =
    View: {}
    Model: {}
    Collection: {}

# This is a dummy dataset of elements that would be fetched from the server
# using the Collection abstraction. Each entry in data will be represented
# in a table view further down.
data = [
    { id: 1, name: "element1", value: "value1", haveDataBytes: true },
    { id: 2, name: "element2", value: "value2", haveDataBytes: false },
    { id: 3, name: "element3", value: "value3", haveDataBytes: true },
    { id: 4, name: "element4", value: "value4", haveDataBytes: true },
    { id: 5, name: "element5", value: "value5", haveDataBytes: false }
]

# The model element takes up each entry from data ...
class App.Model.Element extends Backbone.Model

# and is stored in a collection of Element models
class App.Collection.Elements extends Backbone.Collection
    model: App.Model.Element

Compared to your approach with simple lists for headers and columns in the model the major benefit is that all the data is kept and controlled by Backbone. You could simply update the whole collection, listen to changes and send events, serialize single entries to the server, respond to changes of single entries - full control ...

# The table view
class App.View.ElementsTable extends Backbone.View

    # is a table by itself
    tagName: 'table'

    # it receives the collection of elements
    initialize: (options) ->
        @collection = options.collection

    # and renders each row ...
    render: ->
        @collection.each(@renderRow, @)
        @
    # ... in a element table row view
    renderRow: (row) ->
        rowView = new App.View.ElementTableRow(model: row)
        @$el.append(rowView.render().el)

#  The element table row ...
class App.View.ElementTableRow extends Backbone.View
    # ... is itself a row
    tagName: 'tr'

    # and takes an element model
    initialize: (options) ->
        @model = options.model

    # it handles the click event ...
    events: {
        'click button': 'click'
    }

    # ... with full access to the model and the collection, which is a member
    # of the element model
    click: (evt) ->
        console.log(evt.target.name, @model, @model.collection)
        @$('td').toggleClass('red')

    # render uses the precompiled handlebars template to render the row,
    # no HTML in the view or the model necessary

    render: () ->
        @$el.html(rowTemplate(this.model.toJSON()))
        # Here I show/hide the buttons based on the element models data.
        # It would definitely be better to not render the buttons in first place
        if not @model.get('haveDataBytes')
            @$('button').hide()
        @


# simple start script
$(() ->
    # 'load' the data as a collection
    collection = new App.Collection.Elements(data)

    # check the elements
    console.log(collection.models)

    # create and render the table view
    view = new App.View.ElementsTable(collection: collection)
    $('.table').append(view.render().el)
)

Last but not least, doFunction should live in the view (see click in App.View.ElementTableRow) or be called by the click handler of App.View.ElementTableRow.

chriopp
  • 947
  • 7
  • 12