Ask A Question

Notifications

You’re not receiving notifications from this thread.

Migrating From jQuery to Vanilla Javascript Discussion

Felipe Funes Felipe Funes

`document.querySelectorAll` looks like return an array, but in fact it is an object. It's important to keep this in mind in order to avoid some problems iterating the dom elements.

Reply

Hey Felipe,

Yeah, I think I know what you mean. querySelectorAll returns an object but since you can do something like object[0] to get the first element in the object, it looks like you might be working with an array. Great point.

Reply

This is a great video.

Is it possible to remove jQuery if you are using Bootstrap 4? I know it's a dependency but maybe if you are not using all aspects of Bootstrap you can remove it.

Anyway, removing jQuery is a good way to remove 250kb (30kb gzipped) from your website's download which is not bad at all.

Reply

The next major version of Bootstrap will be without jQuery

Reply

Is it possible to change the default render for other one?

When Trix render I would like to use a different partial. For example _user_trix.html.erb so I can include some special content.

Reply

Hi Franklin,

I believe you can define a #to_trix_content_attachment_partial_path on your User model, e.g.

class User < ApplicationRecord
    include ActionText::Attachable
    ...
    def to_trix_content_attachment_partial_path
        'users/user_trix'
    end
end
Reply

Hi Adam and Franklin,

I had a similar issue using a Profile model and rendering a _profile_mention.html.erb partial for the mentions.

Adding this to_trix_content_attachment_partial_path method pointing to my _profile_mention.html.erb partial did the trick when editing my rich text.
But when displaying my rich text (@article.content), it still renders the _profile.html.erb partial.
Any idea how I could fix that please?

Reply

I have the same problem. Did you manage to solve it? (I'm on Rails 7)

Reply

Hey Chris,

Awesome episode as usual! Few quick questions:

  1. Since ActionText is storing a global_id reference to records in order to display the updated partial at render time, does that mean it's making a separate DB query to retrieve each of those records? For instance, if I @mention 10 separate users, will it make 10 separate calls? Especially if I'm creating a commenting system that allows @mentions, and there could be 10-20 comments, each with several @mentions, etc...
  2. I notice you used Zurb Tribute for the @mention library; you've done an episode before using AtJs, any benefits to Tribute over AtJs, or just preference?

I really do like the concept of storing a reference to the partial instead of the hard-coded HTML. I'm actually in a situation where I stored the HTML snippets in the text itself and now want to change it, but am struggling with how to do that using the Froala editor. I'll eventually migrate to ActionText in a few months after Rails 6 has been vetted in the wild.

Always appreciate your timely and very applicable videos!

Reply

I will be in a similar situation and my solution will either be to cache the mention or store it in redis using the sgid and set it to auto expire in a few hours. Avatar may be out of date for a short duration because of the cache but it is a small price to pay to reduce load on the DB.

Reply

Great episode. How Trix works has become a little bit clearer. Thanks!

Reply

any way to implement @mentions if I am using rails strictly for an API?

Reply

Hi @chris I have a little app that has a venue and user that I would like the user to be able to call something like " Hey @user do you want to go to #venue on saturday?" or the like.

is that possible?

Reply

Is it possible to use ActionText in an Vue-App?

Reply

If you set a data map for the Rails path, can you replace fetch(`/mentions.json?query=${text}`) with this.data.get("url")? I'm having issues with it where I get an undefined error in console.

Reply

I'm building an app that will have this feature, but I also am dealing with multi-tenancy. So how do I pass the account_id ( from multitenancy) in the fetchUsers call, here's what I have and it's not working ( I'm getting an undefined error).
mentions_controller.js

initialize() {
    this.account = this.data.get("account")
  }

fetchUsers(text, callback, account) {
    let account = this.account
    fetch(`/accounts/${account}/mentions.json?query=${text}`)
    .then(response => response.json())
    .then(users => callback(users))
    .catch(error => callback([]))
  }

and in _form.html.erb

<%= f.rich_text_area :problem, placeholder: 'The raw idea, use case, something that motivates us to work on this.', data: { controller: "mentions", target: "mentions.field", mentions_account: current_account.id} %>

I know the account_id is getting to the initialize method, but not the fetchUsers method. Any thoughts?

Reply

I'm a bit of a javascript n00b but I found a solution... My updated fetchUsers method...

fetchUsers(text, callback, account) {
    let accountId = document.querySelector("trix-editor").dataset.mentionsAccount
    console.log(`This is from fetchUsers ${accountId}`)
    fetch(`/accounts/${accountId}/mentions.json?query=${text}`)
    .then(response => response.json())
    .then(users => callback(users))
    .catch(error => callback([]))
  }
Reply

@Chris, I know this episode is a little old, but this is still something I'm dealing with today.

I understand the rationale behind the move to get rid of jQuery in web apps: jQuery was from a time when we needed cross-browser compatability, when vanilla JS didn't provide functionality, that vanilla JS is now good and fast, etc...

And I agree with many of the points.

However, in almost evey project I've written in the last 2 years that doesn't use jQuery, one of the first things I find myself doing is writing a JS class that has a lot of helper functions that look very similar to jQuery.

This is easiest to see with dynamically appending elements to the document using something like Rails' <action>.js.erb format.

Writing this:

const fragment = document.createRange().createContextualFragment("<%= j render(partial: '...') %>")

document.querySelector("#some-selector").appendChild(fragment)

Seems a lot more painful than this:

$("#some-selector").append("<%= j render(partial: '...') %>")

I end up writing a class like the below where I throw in all my helper functions, essentially mimicking jQuery:

# JS helper class attached to window.Global (using webpack)
export default class Global {
  static append(selector, html) {
    const fragment = document.createRange().createContextualFragment(html)
    document.querySelector(selector).appendChild(fragment)
  }
}

I understand if someone is using a framework like React/Angular/VueJS then they wouldn'd use jQuery, but that's because they're using another framework which abstracts complexity.

Do you find that you and other developers are writing your own helper functions to abstract some of the complexity of vanilla JS away? How do you handle trying to write less code that is easier to maintain with the move away from jQuery?

Reply

Hi all - I'm getting some unexpected behavior here, and I was hoping one of you might know of a way to fix it. The _pasteHtml function in mentions_controller.js is deleting more text than it should before the @mention. Here's what I'm seeing: https://streamable.com/ye7rni.

I'm using code that's identical to the tutorial.

Thanks for any insight.

Reply

Hey John, I had the same issue. Maybe try this out when you are setting the selected range?

this.editor.setSelectedRange([position - endPos + startPos, position + 1]);
Reply

That did the trick... thank you for sharing!

Reply

Maybe obvious to some, but I wanted to know why this is the case. My understanding now is that position comes from Trix and begins counting from the start of the rich text area, while startPos and endPos come from Tribute and begin counting from the start of the current line.

setSelectedRange is a Trix method, so it requires start and end position values relative to the beginning of the rich text area, which is why you need to find the length of the Tribute mention and subtract that from the current Trix position.

Another (verbose) way to write _pasteHtml:

_pasteHtml(html, startPos, endPos) {
  let position = this.editor.getPosition()
  let tributeLength = endPos - startPos
  let trixStartPos = position - tributeLength
  let trixEndPos = position
  this.editor.setSelectedRange([trixStartPos, trixEndPos])
  this.editor.deleteInDirection(“backward”)
}
Reply

Benjamin and John, thanks a bunch for sharing this.

Reply

Thanks a lot Benjamin :)

Reply

Thank you so much! Was wrestling with this and having a solution with an explanation is pure gold!

Reply

Thank you so much Benjamin and John

Reply

Hey Chris,

This is probably my favorite series as I'm learning to use both actiontext and stimulus, so thanks for the videos

Would it be possible to add another model as an attachment and post stuff on that model from this editor?
Imagine that I have a 'Tasks' model and that I also have an 'New Tasks' button in the trix toolbar, the would potentially give me a task list. Would it then be possible to, when I save the document, to also save a new "task list" in my task model.

This might be a no-brainer but as a designer is not that easy for me to figure it out by myself yet.

Reply

Have a look at this video: https://gorails.com/episodes/notifications-action-text-user-mentions

It shows how to extract the all the mentioned users within an action text to notify them via email. You could do something similar for your tasks. Except in your case it will be tasks instead of users, and instead of sending emails, you create a list of tasks.

Reply

Thanks Marc, I think I got it. Will try to replicate it with this method.

Reply

Hi Chris, I saw your PR related to to_trix_content_attachment_partial_path so I figured you might know this:

Is it possible to specify a partial path for an attachable, without overriding to_partial_path?

I have @mentions implemented in my application just like you show in this video. I use render @user all over the place but that users/_user.html.erb partial is very different from what I'd like the @mentions to look. So I want to specify a different partial path for rendering users within an Action Text.

I could replace my existing render @user specifying a different partial, but that just seems a like hack. I imagine there must be a to_action_text_partial_path-like method somewhere, but I haven't found any yet.

Reply

Found this! https://github.com/rails/rails/issues/35218#issuecomment-617001327

Let's hope Rails 6.1 will add this.

Reply

Great video! I also integrated these mentions with the noticed gem and it seems to be working well. I'm having one issue, though, where when I try to render the rich text body in plain texts for things like SMSs it's stripping out any of the text associated with the user's name, not just the styling, since it's all treated as an attachment. Do you know of a way around this?

Reply

Hey! Sorry this is probably too late to help in your case but you can override the: attachable_plain_text_representation(caption) method on your ActionText::Attachable model. By default this method uses the caption (for images) but since we aren't setting a caption for @-mentions, the text is blank if you call to_plain_text.

In this example, you might override the method to return something like "@#{name}" for the plain text formatting.

Reply

Hi Chris, or anyone who has some thoughts,
I am utilizing this, but for some purposes have to call the .to_plain_text call on the model I'm using (Comment). When I do that, ActionText removes the @mention, and I'm not quite sure how to add a to_plain_text process for the @mentions. Any ideas?

Reply

Did you ever find a solution to this Mike? Having the same problemo

Reply

When I leave the json partial in place for the user I get the JSON rendered in the post content instead of the HTML partial which is meant to render it as a span—any idea why the jbuilder partial would be preferred when rendering the content in a regular HTML view?

Reply

Thank you Man 🙏

Reply

Hi, this is perfect, but I have got a problem if I want to run this into the modal. Does anyone know what kind of problem that can be please? :/

Reply

fixed with:
position: absolute;
z-index: 9999;

in tribute-container class :)

Reply

Hi Chris,
Thank you for this amazing video. I followed everything, however, I still got undefined when I hit enter on the name.

Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 86,946+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.