DavyJonesLocker / client_side_validations

Client Side Validations made easy for Ruby on Rails
MIT License
2.69k stars 403 forks source link

CSV with Rails 6, Webpacker and Turbolinks isn’t working #767

Closed bmclean closed 5 years ago

bmclean commented 5 years ago

Steps to reproduce

The client side validations are initialized on initial page load but not on Turbolinks navigation. For example, using the Rails example below if you navigate through to the high_scores/new form the validations don’t work but if you refresh the page while on the high_scores/new form the validations work fine.

The problem seems to be that window.Turbolinks isn’t defined in the client-side-validations.js:

start: function start() {
  if (window.Turbolinks != null && window.Turbolinks.supported) {
    var initializeOnEvent = window.Turbolinks.EVENTS != null ? 'page:change' : 'turbolinks:load';
    $(document).on(initializeOnEvent, function () {
      return $(ClientSideValidations.selectors.forms).validate();
    });
  } else {
    $(function () {
      return $(ClientSideValidations.selectors.forms).validate();
    });
  }
}

If I add import Turbolinks from 'turbolinks’; and change the window.Turbolinks references to Turbolinks in the client-side-validations.js source the validations work correctly after navigation. However, I am not sure what the proper fix should be.

System configuration

Rails version: 6.0.0 Ruby version: 2.6.3 Client Side Validations version: 16.0.1 Turbolinks version: 5.2.0

Simplifed Example

I generated a new Rails 6.0.0 application as follows: rails new rails6 --skip-spring

The original config/webpack/environment.js originally looked like this:

const { environment } = require('@rails/webpacker')

module.exports = environment

I added jQuery: yarn add jquery

I added the client_side_validations gem: gem 'client_side_validations', '16.0.1’

I added jquery to config/webpack/environment.js:

const { environment } = require('@rails/webpacker')
const webpack = require('webpack')

environment.plugins.append('Provide', new webpack.ProvidePlugin({
  $: 'jquery',
  jQuery: 'jquery'
}))

module.exports = environment

I added the CSV JavaScript file: yarn add @client-side-validations/client-side-validations

and imported client-side-validations in app/javascript/packs/application.js:

require("@rails/ujs").start()
require("turbolinks").start()

window.jQuery = $
window.$ = $

// Client Side Validations
import '@client-side-validations/client-side-validations'

I generated a scaffold and enabled CSV on the form:

rails generate scaffold HighScore game:string score:integer

<%= form_with(model: high_score, local: true, validate: true) do |form| %>

Model validations:

Here are the model validations:
class HighScore < ApplicationRecord
  validates :game, presence: true
  validates :score, presence: true
end
bmclean commented 5 years ago

The sample Rails app is here: https://github.com/bmclean/rails6-for-csv

rnarang commented 5 years ago

Experiencing this as well! In the meantime, I'm manually hooking up the Turbolinks events after importing CSV:

import '@client-side-validations/client-side-validations'
$(document).on("turbolinks:load", function () {
  return $(ClientSideValidations.selectors.forms).validate();
});

(See https://github.com/DavyJonesLocker/client_side_validations/blob/master/dist/client-side-validations.js#L253)

tagliala commented 5 years ago

Thanks for the detailed bug report.

I hope to have a chance to check this during this weekend

tagliala commented 5 years ago

Hi,

thanks for reporting this.

I've found where the issues is.

I was using the import syntax, so that worked.

When I've switched to the default require syntax, CSV was imported at the wrong time.

Please just replace import with require

require('@client-side-validations/client-side-validations')

PS: since I'm specifying the esm module in package.json, you could omit the path to the esm module. Also, in my (poor) knowledge of node and modules, esm is a better choice for a webpack module. Turbolinks is doing something similar between 5.2 and 5.3 (now they are loading an umd module, that will change, you will not require to use .start() on Turbolinks 5.3)

I'm going to fix the readme in a short while

tagliala commented 5 years ago

Readme fixed, let me know if that worked

bmclean commented 5 years ago

Yes, changing the application.js to this:

require("@rails/ujs").start()
require("turbolinks").start()

// Client Side Validations
require("@client-side-validations/client-side-validations")

works!

Thank you for the explanation.

Anything using import gets sorted to the top of the file, and using require will cause it to stay put. So the run-order only changes when using import. Does that sound correct?

tagliala commented 5 years ago

Anything using import gets sorted to the top of the file, and using require will cause it to stay put. So the run-order only changes when using import. Does that sound correct?

Sounds correct.

Since I'm not an expert JavaScript module developer, I did this terrible thing:

import 'src/application.scss'

console.log('Code 1')

import 'src/module1.js' // Outputs 'Module 1'

require('src/require_module1.js') // Outputs 'Require Module 1'

console.log('Code 2')

import 'src/module2.js' // Outputs 'Module 2'

require('src/require_module2.js') // Outputs 'Require Module 2'

Output:

Module 1
Module 2
Code 1
Require Module 1
Code 2
Require Module 2
bmclean commented 5 years ago

Awesome. Thank you @tagliala!

tagliala commented 5 years ago

@bmclean you're welcome. I've fixed my previous comment because I've inverted umd and esm

esm is automatically loaded as a module when you import it in webpack because it is specified in package.json: https://github.com/DavyJonesLocker/client_side_validations/blob/v16.0.1/package.json#L37

rokumatsumoto commented 5 years ago

@tagliala I followed js section and could not make it work with import syntax. It works when I replace import with require or after refreshing page. (i guess it's related with Turbolinks)

application.js

import Rails from 'rails-ujs';
Rails.start();

import * as ActiveStorage from 'activestorage';
ActiveStorage.start();

import Turbolinks from 'turbolinks';
Turbolinks.start();

import LocalTime from 'local-time';
LocalTime.start();

import 'bootstrap/dist/js/bootstrap';

import '@client-side-validations/client-side-validations';
import '@client-side-validations/simple-form/dist/simple-form.bootstrap4';

// require('@client-side-validations/client-side-validations')
// require('@client-side-validations/simple-form/dist/simple-form.bootstrap4')

package.json

{
  "name": "boyutluseyler",
  "private": true,
  "dependencies": {
    "@client-side-validations/client-side-validations": "^0.0.5",
    "@client-side-validations/simple-form": "^0.0.1",
    "@fortawesome/fontawesome-free": "^5.8.1",
    "@rails/webpacker": "^4.0.7",
    "activestorage": "^5.2.3",
    "axios": "^0.19.0",
    "blueimp-file-upload": "^9.31.0",
    "bootstrap": "^4.3.1",
    "jquery": "^3.4.0",
    "local-time": "^2.1.0",
    "lodash-es": "^4.17.14",
    "noty": "^3.2.0-beta",
    "popper.js": "^1.15.0",
    "rails-ujs": "^5.2.3",
    "resolve-url-loader": "^3.1.0",
    "sanitize-html": "^1.20.1",
    "turbolinks": "^5.2.0",
    "vue": "^2.6.10",
    "vue-3d-model": "^1.1.0-aplha.0",
    "vue-loader": "^15.7.0",
    "vue-template-compiler": "^2.6.10",
    "vue-turbolinks": "^2.0.4",
    "vuedraggable": "^2.23.0",
    "vuex": "^3.1.1"
  },
  "devDependencies": {
    "@gitlab/eslint-config": "^1.6.0",
    "eslint": "^5.16.0",
    "eslint-import-resolver-webpack": "^0.11.1",
    "prettier": "^1.18.2",
    "webpack-bundle-analyzer": "^3.3.2",
    "webpack-dev-server": "^3.4.1"
  },
  "resolutions": {
    "@rails/webpacker/webpack/micromatch/snapdragon/base/cache-base/set-value": "^2.0.1"
  }
}

Anything using import gets sorted to the top of the file, and using require will cause it to stay put. So the run-order only changes when using import. Does that sound correct?

In my case, isn't it supposed to be working?

bmclean commented 5 years ago

Hi @rokumatsumoto. I had to change this in your example to get it to run: import Rails from '@rails/ujs'; but I think the problem is that Turbolinks.start() occurs after client-side-validations is imported.

rokumatsumoto commented 5 years ago

Hi @bmclean. https://github.com/DavyJonesLocker/client_side_validations/blob/master/dist/client-side-validations.js#L250

Yes, I placed debugger pointers inside if and else blocks and it has never reached validate(); line.

tagliala commented 5 years ago

Hi, I think this maybe related to the following: turbolinks/turbolinks#481

Basically, imports are sorted at the top of file, so you cannot simply call .start() on Turbolinks before you can import client side validations.

Anyway, Turbolinks 5.3 does an automatic start, just like CSV, and I'm using that beta version so I didn't check old 5.2 with import syntax

Maybe I have to fix instructions one more time, please let me know

rokumatsumoto commented 5 years ago

Hi @tagliala. It would have been nice to have a special section for Turbolinks 5.2 and below.

tagliala commented 5 years ago

@rokumatsumoto thanks for the feedback.

Please take a look at the new readme, it should be cleared