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
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.
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.
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!
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');
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.