Using ActionText without webpack - ruby-on-rails

I try to implement ActionTest with old way asset pipeline (without Webpack) on rails 6
Almost all is good, except loading of #rails/actiontext
in my application.js I've
//= require trix
//= require #rails/actiontext
The riche editor appear, I can change bold/italic text, but can't add image (not uploaded)
I've an JS error : Uncaught SyntaxError: Cannot use import statement outside a module
on line : import { AttachmentUpload } from "./attachment_upload" from attachment_uplaod.js in actiontext.
Any way to achieve this without webpack?
thanks

I don't know what will be the official way, but I did it this way as I'm waiting for an updated install generator:
Prerequisites
hotwire-rails
CSS
Copy the CSS file in your asset pipeline (https://github.com/basecamp/trix/tree/main/dist)
JS Libraries
In app/assets/javascripts/libraries create these two files
Updated content may be found on https://www.skypack.dev
// app/assets/javascripts/libraries/actiontext#6.1.4.js
export * from 'https://cdn.skypack.dev/pin/#rails/actiontext#v6.1.4-znF92tREya92yxvegJvq/mode=imports/optimized/#rails/actiontext.js';
export { default } from 'https://cdn.skypack.dev/pin/#rails/actiontext#v6.1.4-znF92tREya92yxvegJvq/mode=imports,min/optimized/#rails/actiontext.js';
// app/assets/javascripts/libraries/trix#1.3.1.js
export * from 'https://cdn.skypack.dev/pin/trix#v1.3.1-EGGvto9zyvcAYsikgQxN/mode=imports/optimized/trix.js';
export { default } from 'https://cdn.skypack.dev/pin/trix#v1.3.1-EGGvto9zyvcAYsikgQxN/mode=imports,min/optimized/trix.js';
Import through Stimulus
In app/assets/javascripts/controllers create this file
//app/assets/javascripts/controllers/trix_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
connect() {
import("actiontext").catch(err => null)
import("trix").catch(err => null)
}
}
On pages where trix/action_text should be loaded, add a data-controller="trix" to the relevant div
And voilà !
https://github.com/rails/rails/issues/41221#issuecomment-871853505

Got Action Text working by copying the actiontext scripts into a custom file, and removing the imports and exports.
And second, you will need to require activestorage in your application.js to make use of DirectUpload.
application.js
//= require trix
//=# require #rails/actiontext
//= require activestorage
//= require actiontext
actiontext.js
// Copied from node_modules/#rails/actiontext/app/javascript/actiontext/attachment_upload.js
class AttachmentUpload {
constructor(attachment, element) {
this.attachment = attachment;
this.element = element;
// Requires `require activestorage` in application.js
this.directUpload = new ActiveStorage.DirectUpload(
attachment.file,
this.directUploadUrl,
this
);
}
start() {
this.directUpload.create(this.directUploadDidComplete.bind(this));
}
directUploadWillStoreFileWithXHR(xhr) {
xhr.upload.addEventListener("progress", event => {
const progress = (event.loaded / event.total) * 100;
this.attachment.setUploadProgress(progress);
});
}
directUploadDidComplete(error, attributes) {
if (error) {
throw new Error(`Direct upload failed: ${error}`);
}
this.attachment.setAttributes({
sgid: attributes.attachable_sgid,
url: this.createBlobUrl(attributes.signed_id, attributes.filename)
});
}
createBlobUrl(signedId, filename) {
return this.blobUrlTemplate
.replace(":signed_id", signedId)
.replace(":filename", encodeURIComponent(filename));
}
get directUploadUrl() {
return this.element.dataset.directUploadUrl;
}
get blobUrlTemplate() {
return this.element.dataset.blobUrlTemplate;
}
}
// Copied from node_modules/#rails/actiontext/app/javascript/actiontext/index.js
addEventListener("trix-attachment-add", event => {
const { attachment, target } = event;
if (attachment.file) {
const upload = new AttachmentUpload(attachment, target);
upload.start();
}
});
This still uses ES6 syntax, so if you want to support older browsers and aren't using Babel, you might want to rewrite or transpile this to ES5.

Related

Rails Webpacker does not compile subdirectory

EDIT: Solution: make sure you spell the class-methods correctly. My error stemmed from typing contructor() within the class (please refer to the source-code of SocialShareModal.js.
Also, make sure your linter in your editor of choice works correctly! Mine did not. It would have spared me hours if it actually did :-)
I am running a Rails-application (ruby v 2.6.2 / Rails v 6.0.2) using webpacker. My JavaScript has been working like a charm, up until I tried putting component-related JS into a dedicated sub-directory of my app/javascript-folder.
This is what my JS-file-tree looks like:
javascript
├──channels
├──custom
│ ├──components (new & not working)
│ ├──config
│ ├──helpers (these are working somehow)
│ └──pages
├──config
└──packs
In application.js I import a custom Router.js, initialize it with my custom routes to then, on various subpages, initialize my custom JS-classes. It all worked so far (and continues to) with classes which live in the helpers-folder, however the classes which live in the new components-folder won't work. I am unsure if they are even picked up and compiled by webpack.
application.js:
import routes from '../custom/config/routes'
import Router from '../custom/Router'
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require('channels')
...
class myApp {
constructor() {
this._initRouter()
}
/**
* Initializes the router and its routes
* #private
*/
_initRouter () {
this._router = new Router(routes)
}
}
document.addEventListener('turbolinks:load', function() {
window.myApp = new myApp()
})
routes.js:
import Page from '../pages/page'
// Frontend
import Root from '../pages/frontend/root'
import SignInPage from '../pages/frontend/signInPage'
// Dashboard => Admin
import AdminAccountsEditPage from '../pages/dashboard/admin/accounts/edit'
// Dashboard => User
import WelcomePage from '../pages/dashboard/user/welcomePage'
export default [
// Frontend
['', Root],
['accounts/sign_in', SignInPage],
// Dashboard => Admin
['admin/accounts/(.*)', AdminAccountsEditPage],
// Dashboard => User
['dashboard/willkommen', WelcomePage],
// Catch all for when there is no exact match:
['(.*)', Page]
]
Router.js:
/* global location */
export default class Router {
constructor(routes) {
this.routes = routes
this.handleRoute()
}
/**
* Checks if there's a javascript for the current route, requires the class and
* instantiates it
* #private
*/
handleRoute() {
let { pathname } = location
// Remove leading and trailing slashes
pathname = pathname.replace(/^\/|\/$/g, '')
// Go through routes and check which one matches
for (let i = 0; i < this.routes.length; i++) {
const [route, PageClass] = this.routes[i]
const regexp = new RegExp(`^${route}$`, 'i')
if (route === true || regexp.test(pathname)) {
this.currentPage = new PageClass()
break
}
}
}
}
Page.js:
import tippy from 'tippy.js'
import 'tippy.js/dist/tippy.css'
import FlashMessageHelper from '../helpers/FlashMessageHelper'
import AddToWishlistHelper from '../helpers/AddToWishlistHelper'
import SocialShareModal from '../components/SocialShareModal' // importing it
export default class Page {
constructor() {
new tippy('[data-tippy-content]')
new FlashMessageHelper() // working
new AddToWishlistHelper() // working
new SocialShareModal() // NOT working (not initializing)
}
}
SocialShareModal.js
export default class SocialShareModal {
get modalSelector() { return '.modal' }
get triggerModalSelector() { return '.js-trigger-modal' }
get copyToClipBoardButtonSelector() { return '.js-copy-to-clipboard' }
contructor() { // As you can see, the error resided here
console.log('SocialShareModal constructor called')
this.init()
}
init() {
let modalButton = document.querySelector(this.triggerModalSelector)
modal.addEventListener('click', handleModalTrigger)
window.addEventListener('scroll', this.handleTestScroll)
}
handleModalTrigger() {
let modal = document.querySelector(this.modalSelector)
modal.classList.add('is-active')
}
}
I've done lots of reading, but can't seem to figure out the issue, as I'm not super-comfortable with webpack. Any suggestions on how to solve this?
Edit: added source-code for application.js, routes.js, Router.js, Page.js & SocialShareModal.js to provide more context.

Rails Webpacker - How to access objects defined in webpack entry file from views [HTML file]

I have a Rails 6 application and using Webpacker for assets.
I have the following code in file app/javascript/packs/application.js :
export var Greeter = {
hello: function() {
console.log('hello');
}
}
And I have the following script in one of my view (HTML) file:
<script>
$(document).ready(function(){
Greeter.hello();
});
</script>
Note: I am using JQuery and it is working fine.
I am getting the following error:
Uncaught ReferenceError: Greeter is not defined.
How can we use libraryTarget and library to expose the bundled modules, so that it can be accessed from HTML files as well ?
Or, is there any other way of doing it using Rails Webpacker ?
Any help would be much appreciated!
To do this without directly mutating the window object in your application code, you'll want to export Greeter as a named export from your application.js pack and extend the Webpack config output to designate the library name and target var (or window will also work).
// config/webpack/environment.js
environment.config.merge({
output: {
library: ['Packs', '[name]'], // exports to "Packs.application" from application pack
libraryTarget: 'var',
}
})
// app/javascript/packs/application.js
export {
Greeter
}
<script>
$(document).ready(function(){
Packs.application.Greeter.hello();
});
</script>
The library name is arbitrary. Using the [name] placeholder is optional but allows you to export to separate modules if you're using multiple "packs".
As I cannot comment rossta's answer, here is what I had to do. My default config was:
// config/webpack/environment.js
const { environment } = require('#rails/webpacker')
module.exports = environment
and I just had to add the additionnal config in it:
// config/webpack/environment.js
const { environment } = require('#rails/webpacker')
environment.config.merge({
output: {
library: ['Packs', '[name]'], // exports to "Packs.application" from application pack
libraryTarget: 'var',
}
})
module.exports = environment
After that, as mentioned by rossta, each symbol which is exported in app/javascript/packs/application.js can be accessed from the DOM as Packs.application.<symbol>.
in app/javascript/packs/application.js:
import Greeter from '../greeter.js'
Greeter.hello()
and in app/javascript/greeter.js:
export default {
hello : function(){
console.log('hello')
}
}
I could fix the issue exposing Greeter object to window as follows:
export var Greeter = {
hello: function() {
console.log('hello');
}
}
window.Greeter = Greeter;
However, I am still looking for a Webpack way of accomplishing this.

Rails 5.2 + Trix + ActiveStorage

How can I upload images in Trix editor with Rails 5.2 configured with ActiveStorage?
I saw some videos using others uploaders, but could not adapt the idea to ActiveStorage.
Other (maybe) solution is: use ActionText with Rails 5.2. Is it safe to use already?
Active Storage has direct upload js, you need just add:
//= require activestorage
to your application.js, and then create trix-attachment-add event listener:
document.addEventListener('trix-attachment-add', function (event) {
var file = event.attachment.file;
if (file) {
var upload = new window.ActiveStorage.DirectUpload(file,'/rails/active_storage/direct_uploads', window);
upload.create((error, attributes) => {
if (error) {
return false;
} else {
return event.attachment.setAttributes({
url: `/rails/active_storage/blobs/${attributes.signed_id}/${attributes.filename}`,
href: `/rails/active_storage/blobs/${attributes.signed_id}/${attributes.filename}`,
});
}
});
}
});
Hope this helps you!

How to import library-generated IIEF style JavaScript file?

Is there any way to import some library-generated code which does not have export vars? Which module options should be set in tsconfig.json file?
Documents
about "Module" system in TypeScript
Environment
typescript #2.3.2
js-routes #1.3.3
Example
js-routes generate a code like below based on routes.rb:
/*
File generated by js-routes 1.3.3
Based on Rails routes of MyApplication
*/
(function() {
...
return root.Routes;
};
if (typeof define === "function" && define.amd) {
define([], function() {
return createGlobalJsRoutesObject();
});
} else {
createGlobalJsRoutesObject();
}
}).call(this);
If you import in 'module=es2015' style,
import { Routes } from '../path/to//generated/js_routes';
tsc warns:
[ts] File '/path/to/client/generated/js_routes.js' is not a module.
You can try to require the file, and see what's the output of that:
import Routes = require('../path/to//generated/js_routes');

visit is not defined in Ember App

I'm trying to get some TDD happening with Ember and Konacha - I get this error when I try to use visit
ReferenceError: visit is not defined
at Context.<anonymous> (http://localhost:3500/assets/api_spec.js?body=1:24:5)
at Test.Runnable.run (http://localhost:3500/assets/mocha.js:4336:32)
at Runner.runTest (http://localhost:3500/assets/mocha.js:4724:10)
at http://localhost:3500/assets/mocha.js:4802:12
at next (http://localhost:3500/assets/mocha.js:4649:14)
at http://localhost:3500/assets/mocha.js:4659:7
at next (http://localhost:3500/assets/mocha.js:4597:23)
at http://localhost:3500/assets/mocha.js:4621:7
at Hook.Runnable.run (http://localhost:3500/assets/mocha.js:4338:5)
at next (http://localhost:3500/assets/mocha.js:4609:10)
api_spec.rb
//= require spec_helper
describe("fake server", function() {
beforeEach(function() {
this.server = sinon.fakeServer.create();
});
afterEach(function() {
this.server.restore();
});
it("#transition off Landing", function() {
visit("/").then(function() {
var mock = sinon.mock(testHelper.lookup('route', 'index'));
mock.expects('transitionTo').once();
mock.verify();
mock.restore();
});
});
}
here's my spec_helper.rb
//= require sinon
//= require ember-mocha-adapter
//= require application
mocha.ui('bdd');
mocha.globals(['Ember', 'App', 'DS', 'MD5']);
mocha.timeout(500);
chai.Assertion.includeStack = true;
ENV =
{
TESTING:true
};
window.server = sinon.fakeServer.create();
window.testHelper = {
lookup: function(object, object_name) {
name = object_name || "main";
return App.__container__.lookup(object + ":" + name);
}
}
App.Router.reopen({
location: 'none'
});
Konacha.reset = Ember.K;
How do I make ember play nice with visit?
Cheers!
visit is only injected when you call
App.injectTestHelpers();
Additionally you must be running a debug build of Ember.
But it only works with qunit, you'll need to write your own visit if you're using a different testing framework.

Resources