ruby-on-railssimple-formstimulusjsesbuildstimulus-rails

simple_form is not capturing Rails Stimulus controller


simple_form is not capturing markup for Stimulus in Rails 7.1, Ruby 3.2.0, esbuild. Or maybe something is wrong with my html.erb

Update: I've changed this to reflect where I now see the problem (before I thought it was a Stimulus issue).

Two different actions. One is a copy and the other is supposed to pickup an onchange in a dropdown menu. The first works, the second doesn't.

<div data-controller="location-select">
  <span>Simple Stimulus demo  <%= link_to "based on", "https://stimulus.hotwired.dev/handbook/building-something-real" %></span>
  <input data-location-select-target="source" type="text" value="This will be copied" readonly>
  <button data-action="location-select#copy">Copy to Clipboard</button>
  <span>Copying to clipboard above works. But selecting a location below does not call method locationSelected</span>

  <%= form.association :location,
      label_method: :address,
      collection: Location.all.order(:address),
      value_method: :id,
      include_blank: true,
      data: { action: "onchange->location-select#locationSelected" }
  %>
</div>


// app/javascript/controllers/location_select_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "source" ] // for copy demo
  connect() {
    console.log("location_select_controller connect method")
  }

  locationSelected() {
    console.log("SUCCESS location_select_controller locationSelected method. SUCCESS) // never happens
  }

 copy(event) {
   event.preventDefault() 
   navigator.clipboard.writeText(this.sourceTarget.value)
   console.log(`location_select_controller copy method and \"${this.sourceTarget.value}\" was copied to clipboard`)
 }
}

Resulting HTML

<div data-controller="location-select">
    <span>Simple Stimulus demo  <a href="https://stimulus.hotwired.dev/handbook/building-something-real">based on</a></span>
    <input data-location-select-target="source" type="text" value="This will be copied" readonly>
    <button data-action="location-select#copy">Copy to Clipboard</button>
    <span>Above works. But selecting a location below does not call method locationSelected</span>
  
    <div class="input select required year_location field_without_errors"><label class="select required" for="year_location_id"><abbr title="required">*</abbr> Location</label><select class="select required" name="year[location_id]" id="year_location_id"><option value="" label=" "></option>
<option selected="selected" value="188">10 N Main St</option>
<option value="234">1000 E 7th St</option>
etc.
</div>

value is the location id

I'm either doing something wrong or simple_form is not putting in the data-action … in the HTML. So the question is, how to write the html.erb.

Below is the what is needed, but I think the above points to the root of the problem. I just discovered the issue while digging into the following. In other words, the rest can probably be ignored.

data: { controller: "snippet-draw", action: "change->snippet-draw#connect" } is being ignored. In fact removing this line changes nothing. The page still works; the connect method is invoked

Want the :doc to update when selected. In normal use the doc (an image) is shown on the page and an area selected on it. Just the region can be shown on other pages. The doc is displayed on the page for 'edit' with an existing doc; but doesn't change the newly selected doc nor for 'new' items. (The table is named 'years' because they are tied to dates. Confusing choice of table name.)

html.erb for both edit and new

<%= simple_form_for @year do |form| %> 
  <div data-controller="snippet-draw">
    <%= form.association :doc, 
           label_method: :id_page, 
           collection: Doc.all, 
           value_method: :id, 
           label: "Select document(s) that support this",
           class: "fw-bold",
           data: { controller: "snippet-draw", action: "change->snippet-draw#connect" }
    %>

app/javascript/controllers/snippet_draw_controller.js. This should only be relevant up through the first few lines connect method as the change (docURL) should be known then.

import { Controller } from "@hotwired/stimulus"

import Map from 'ol/Map';
import View from 'ol/View';
import Polygon from 'ol/geom/Polygon';
import Draw, {createRegularPolygon, createBox} from 'ol/interaction/Draw';
import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
import ImageLayer from 'ol/layer/Image';
import {OSM, Vector as VectorSource} from 'ol/source';
import Static from 'ol/source/ImageStatic';
import Projection from 'ol/proj/Projection';
import {getCenter} from 'ol/extent';

export default class extends Controller {
  static targets = [ "snippetCoords"] 

  static values = {
    url: String,
    width: Number,
    height: Number,
  }

  connect(){
    const docURL = this.urlValue;
    const docWidth  = this.widthValue;
    const docHeight = this.heightValue;
    const extent = [0, 0, docWidth, docHeight];
    console.log(`44. ${Date.now()} docURL:  ${docURL}`)
  
    let source = new VectorSource(); 

    let drawLayer = new VectorLayer({
      source: source
    });    

    let projection = new Projection({
      units: 'pixels',
      extent: extent,
    });

    let docSource = new Static({
      attributions: '',
      url: docURL,
      projection: projection,
      imageExtent: extent
    });

    let docLayer = new ImageLayer({ 
      source: docSource,
      extent: extent,
    });

    let staticView = new View({
      projection: projection,
      center: getCenter(extent),
      zoom: 2.5, 
      maxZoom: 4,
    });

    let map = new Map({
      layers: [docLayer],
      target: 'snmap',
      view: staticView,
      extent: extent,
    });

    function getSnippetCoordinates(coordsOutputTarget) {
      let draw = new Draw({
        source: source,
        type: 'Circle',
        geometryFunction: createBox()
      });
      map.addInteraction(draw);
       draw.on('drawend', function(event) {
           let boxCoords = event.feature.values_.geometry.flatCoordinates;
         let boxCoordsStringified = JSON.stringify(boxCoords);
         let boxCoordsStringifiedParsed = JSON.parse(boxCoordsStringified);
         var truncCoords = function(element,index,array){
           return Math.trunc(element);
         };
         boxCoordsStringifiedParsedMap = boxCoordsStringifiedParsed.map(truncCoords);
         document.getElementById('year_snippet_coords').innerHTML = boxCoordsStringifiedParsedMap; 
       });
       }

    const someText = "Drawn coordinates will appear here after drawing a rectangle. (From snippet_draw_controller:113. Dev only since don't get added to dB";
    this.snippetCoordsTarget.textContent = `${someText}`;
    getSnippetCoordinates(this.snippetCoordsTarget);
    } // end connect    
  } 

Browser console on load years/xxx/edit

36. docURL: application-….js:76185 
44. 1687813148832 docURL:  application-….js:76180 
36. docURL: /rails/active_storage/blobs/redirect/some.jpg application-….js:76185 
44. 1687813148836 docURL:  /rails/active_storage/blobs/redirect/some.jpg

No change in console when a new doc select is selected. I also don't understand why connect is called twice


Solution

  • input_html: { data: { action: "change->location-select#locationSelected" }}

    replaces

    data: { action: "onchange->location-select#locationSelected" }

    I thought I'd answered this. Found solution at SO.

    Now to get to the main problem.