Trix: File attachment in toolbar

Created on 18 Apr 2016  路  21Comments  路  Source: basecamp/trix

Hi, I have question for trix community. Is possible to add attach icon into toolbar?

I can add support for drag file to textarea (even image with caption), but I can't find how to add icon into toolbar for attach file.

Most helpful comment

screen shot 2016-04-20 at 8 53 04 am

JavaScript

var buttonHTML, fileInputHTML;

buttonHTML = "<button type=\"button\" class=\"attach\" data-action=\"x-attach\">Attach Files</button>";

fileInputHTML = "<input type=\"file\" multiple>";

$(Trix.config.toolbar.content).find(".button_group.block_tools").append(buttonHTML);

CoffeeScript

buttonHTML = """<button type="button" class="attach" data-action="x-attach">Attach Files</button>"""
fileInputHTML = """<input type="file" multiple>"""

$(Trix.config.toolbar.content).find(".button_group.block_tools").append(buttonHTML)

https://gist.github.com/javan/20e574f1cae443ba6c59#file-attachment_tool-coffee-L1


CSS

button.attach::before {
  background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA%2FPjxzdmcgaGVpZ2h0PSIxNnB4IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyNCAxNiIgd2lkdGg9IjI0cHgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48dGl0bGUvPjxkZXNjLz48ZGVmcy8%2BPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSI%2BPGcgZmlsbD0iIzAwMDAwMCIgaWQ9IkNvcmUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjYuMDAwMDAwLCAtNDYuMDAwMDAwKSI%2BPGcgaWQ9ImJhY2t1cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI2LjAwMDAwMCwgNDYuMDAwMDAwKSI%2BPHBhdGggZD0iTTE5LjQsNiBDMTguNywyLjYgMTUuNywwIDEyLDAgQzkuMSwwIDYuNiwxLjYgNS40LDQgQzIuMyw0LjQgMCw2LjkgMCwxMCBDMCwxMy4zIDIuNywxNiA2LDE2IEwxOSwxNiBDMjEuOCwxNiAyNCwxMy44IDI0LDExIEMyNCw4LjQgMjEuOSw2LjIgMTkuNCw2IEwxOS40LDYgWiBNMTQsOSBMMTQsMTMgTDEwLDEzIEwxMCw5IEw3LDkgTDEyLDQgTDE3LDkgTDE0LDkgTDE0LDkgWiIgaWQ9IlNoYXBlIi8%2BPC9nPjwvZz48L2c%2BPC9zdmc%2B); 
}

https://github.com/tanin47/trix/pull/1/files#diff-5415e2a6f4eeb577a9ac3212c8cdc99dR115

All 21 comments

screen shot 2016-04-20 at 8 53 04 am

JavaScript

var buttonHTML, fileInputHTML;

buttonHTML = "<button type=\"button\" class=\"attach\" data-action=\"x-attach\">Attach Files</button>";

fileInputHTML = "<input type=\"file\" multiple>";

$(Trix.config.toolbar.content).find(".button_group.block_tools").append(buttonHTML);

CoffeeScript

buttonHTML = """<button type="button" class="attach" data-action="x-attach">Attach Files</button>"""
fileInputHTML = """<input type="file" multiple>"""

$(Trix.config.toolbar.content).find(".button_group.block_tools").append(buttonHTML)

https://gist.github.com/javan/20e574f1cae443ba6c59#file-attachment_tool-coffee-L1


CSS

button.attach::before {
  background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA%2FPjxzdmcgaGVpZ2h0PSIxNnB4IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyNCAxNiIgd2lkdGg9IjI0cHgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48dGl0bGUvPjxkZXNjLz48ZGVmcy8%2BPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSI%2BPGcgZmlsbD0iIzAwMDAwMCIgaWQ9IkNvcmUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjYuMDAwMDAwLCAtNDYuMDAwMDAwKSI%2BPGcgaWQ9ImJhY2t1cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI2LjAwMDAwMCwgNDYuMDAwMDAwKSI%2BPHBhdGggZD0iTTE5LjQsNiBDMTguNywyLjYgMTUuNywwIDEyLDAgQzkuMSwwIDYuNiwxLjYgNS40LDQgQzIuMyw0LjQgMCw2LjkgMCwxMCBDMCwxMy4zIDIuNywxNiA2LDE2IEwxOSwxNiBDMjEuOCwxNiAyNCwxMy44IDI0LDExIEMyNCw4LjQgMjEuOSw2LjIgMTkuNCw2IEwxOS40LDYgWiBNMTQsOSBMMTQsMTMgTDEwLDEzIEwxMCw5IEw3LDkgTDEyLDQgTDE3LDkgTDE0LDkgTDE0LDkgWiIgaWQ9IlNoYXBlIi8%2BPC9nPjwvZz48L2c%2BPC9zdmc%2B); 
}

https://github.com/tanin47/trix/pull/1/files#diff-5415e2a6f4eeb577a9ac3212c8cdc99dR115

buttonHTML should be:

buttonHTML = "<button type=\"button\" class=\"attach\" data-trix-action=\"x-attach\">Attach Files</button>";

Thanks @step1profit and @chrise86@ That's the best solution for now and is more-or-less what we do to add a file upload button in Basecamp.

Should this work out of the box with data-trix-action="x-attach" ?
What do you do with fileInputHTML ?

Just posted a functional gist i'm using to do exactly this in the app i'm working on : https://gist.github.com/pmhoudry/a0dc6905872a41a316135d42a5537ddb

Add css, an actual url to talk to and maybe csrf protection and you're good to go !

There's also a few warnings if the file is empty, or too big, or if the user tries to quit the page when an upload is pending.

What could cause upload to fail? Here is the message:"Upload failed. Try to reload the page

I got HTML and UI for attach file in toolbar of editor.

But I m not getting that how to be done it work functional.

means click attach file and Images uploaded to editor..

Thanks in Advace

Here's what will work in trix as of today:

buttonHTML = """<button type="button" class="icon attach" data-trix-action="x-attach" title="Attach Files">Attach Files</button>"""

$(Trix.config.toolbar.content).find(".button_group.block_tools").append(buttonHTML)

$(document).on "trix-action-invoke", (event) ->
  if event.originalEvent.actionName is "x-attach"
    editorElement = event.target

    fileInput = $("""<input type="file" multiple>""")
    fileInput.on "change", ->
      for file in this.files
        editorElement.editor.insertFile(file)

    fileInput.click()

And here's the cloud upload icon encoded using the trix build process.

  trix-toolbar {
    button.attach::before {
      background-image: url(data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3Cpath%20d%3D%22M19.4%2010a7.5%207.5%200%200%200-14-2A6%206%200%200%200%206%2020h13a5%205%200%201%200%20.4-10zM14%2013v4h-4v-4H7l5-5%205%205h-3z%22%2F%3E%3C%2Fsvg%3E);
    }
  }

@javan, since this is such a simple enhancement, would you consider adding it to the project proper?

馃憤 To add this safely, even when we have multiple trix-editors in the same view.

@danielmcoelho, at a cursory glance, I think this would be safe for multiple editors given I think trix shares the toolbar config and the action-invoke works with the events target editor.

To follow this up Firefox does't allow the triggering of the file selection 'popup' via a non-click event. trie-action-invoke is triggered in this case by the mousedown event. I ended up doing this:

# Custom Attach Files button
buttonHTML = """<button type="button" class="icon attach" data-trix-action="x-attach" title="Attach Files">Attach Files</button>"""
fileInput = $("""<input type="file" multiple>""")

$(Trix.config.toolbar.content).find(".button_group.block_tools").append(buttonHTML)

$(document).on "click", 'button[data-trix-action=x-attach]', (event) ->
  editorElement = document.querySelector("trix-editor")

  fileInput.on "change", ->
    for file in this.files
      editorElement.editor.insertFile(file)

  fileInput.click()

It's not as nice because I couldn't figure out a cleaner way of linking the button clicked to the editor it belongs to. @javan is there a way to do this from the toolbar element. I suppose I could find the closest toolbar element to the button, then query its siblings for the editor element?

coffee script editorElement = $(this).closest('trix-toolbar').siblings('trix-editor')[0]

It's easier to use _trix-initialize_ event to attach the button to the toolbar and add an event listener on that button at the same time. You may look at my code posted earlier, I use a variant of it in production. You just have to adapt it to your needs and figure out the server side.

document.addEventListener('trix-initialize', function(e){
    trix = e.target;
    toolBar = trix.toolbarElement;

    button = document.createElement("button");
    button.setAttribute("type", "button");
    button.setAttribute("class", "attach");
    button.setAttribute("data-trix-action", "x-attach");
    button.setAttribute("title", "Attach a file");
    button.setAttribute("tabindex", "-1");
    button.innerText = "Attach a file";

    uploadButton = toolBar.querySelector('.button_group.block_tools').appendChild(button);
    uploadButton.addEventListener('click', function() { ... }
}

Thanks @pmhoudry, that's a great help :) I'll look into modifying things in that way.

Here's the final working version using @pmhoudry's idea:

$(document).on 'trix-initialize', (event) ->
  editorElement = event.target
  toolbarElement = $(editorElement.toolbarElement);

  attachButton = $("""<button type="button" class="icon attach" data-trix-action="x-attach" title="Attach Files">Attach Files</button>""")
  toolbarElement.find('.button_group.block_tools').append(attachButton)

  fileInput = $("""<input type="file" multiple>""").on 'change', ->
    for file in this.files
      editorElement.editor.insertFile(file)

  attachButton.on 'click', (event) ->
    fileInput.click()

We found that calling .click() on an <input type="file"> doesn't work on iOS unless it's attached to the DOM. Here's an approximation of what we do in Basecamp:

buttonAction = "x-attach"
buttonSelector = "button[data-trix-action='#{buttonAction}']"
buttonHTML = """<button type="button" class="attach" data-trix-action="#{buttonAction}">Attach Files</button>"""

$(Trix.config.toolbar.content).find(".button_group.block_tools").append(buttonHTML)

$(document).on "trix-initialize", ($event) ->
  editorElement = $event.target
  {toolbarElement} = editorElement

  $(toolbarElement).find(buttonSelector).on "click", ->
    editorElement.focus()

    pickFiles (files) ->
      for file in files
        editorElement.editor.insertFile(file)

    false

pickFiles = (callback) ->
  $fileInput = $("""<input type="file" multiple>""")
  $fileInput.hide().appendTo("body")

  uninstall = ->
    if $fileInput
      $fileInput.remove()
      $fileInput = null

  $fileInput.on "change", (event) ->
    callback(@files)
    uninstall()

  $fileInput.click()

  requestAnimationFrame ->
    $(document).one("click", uninstall)

Thanks @javan, I've not noticed that myself. Do you remember what version of iOS Safari it was happening on?

I don't recall. I fixed the problem in Basecamp on Jul 28, 2016 and was able to reproduce it then so probably whatever iOS version was current then.

Guys, I've been trying all solutions that you've posted but none work, the Trix.config.toolbar.contentobject doesn't exist so I can't even add the so called button and if it did the css classes used in the toolbar are different from .find(".button_group.block_tools")

Has anyone been able to add a "File attachment button" in the toolbar in this 2017 version of Trix?
Thanks

Ok, managed to work around the new changes for v0.11.1.

Here is a new gist based on everyone's contribution on the issue:
https://gist.github.com/goncalvesjoao/3c51fe09c04e29f613145d42858a0a9c

Hope this helps someone.

PS: This code is expecting your CRUD attachment controller to return a json response with an ID in it, so that we can later destroy it when we remove it from the Trix editor.

I figured I would quickly pitch in some of what we're doing now in v1.0.0 to make this work in our React app in case anyone is stuck trying to do this currently.
Despite the last comment being a year ago for v0.11.1, this thread was super helpful (stole a ton of inspiration from @goncalvesjoao gist)

We initialize everything in a function using the onEditorReady prop, which adds the button to the toolbar and adds the event listeners.

trixOnEditorReady = (trix) => {
    this.trixEditor = trix

    this.trixAddAttachmentButtonToToolbar()

    document.addEventListener("trix-file-accept", this.trixFileAcceptEvent)
    document.addEventListener("trix-attachment-add", this.trixAddAttachmentEvent)
    document.addEventListener("trix-attachment-remove", this.trixRemoveAttachmentEvent)
  }

trixAddAttachmentButtonToToolbar = () => {
    let trixBlockButtons = document.querySelector(".trix-button-group--block-tools")
    const buttonHTML = `
      <button
        type="button"
        class="trix-button trix-button--icon trix-button--icon-attach-files"
        data-trix-action="x-attach" title="Attach Files"
        tabindex="-1"
      >Attach Files</button>
    `
    trixBlockButtons.innerHTML += buttonHTML

    document.querySelector(".trix-button--icon-attach-files")
      .addEventListener("click", this.trixAddAttachment)
  }

trixAddAttachment = () => {
    const fileInput = document.createElement("input")

    fileInput.setAttribute("type", "file")
    fileInput.setAttribute("accept", ".jpg, .png, .gif")
    fileInput.setAttribute("multiple", "")

    fileInput.addEventListener("change", () => {
      const { files } = fileInput
      Array.from(files).forEach(this.insertAttachment)
    })

    fileInput.click()
  }

insertAttachment = (file) => {
    this.trixEditor.insertFile(file)
  }

trixFileAcceptEvent = ({ file: { name }}) => {
    const [extension] = name.split('.').slice(-1)
    if (['png', 'jpg', 'gif'].indexOf(extension.toLowerCase()) === -1) {
      e.preventDefault()
    }
  }

  trixAddAttachmentEvent = (e) => {
    this.uploadAttachment(e.attachment)
  }

// the uploadAttachment function is a custom ajax request that sets the following
//   .on('progress', ({ percent }) => {
//            attachment.setUploadProgress(Math.floor(percent))
//          })
//          .end((error) => {
//            if (!error) {
//              attachment.setAttributes({
//                url: fileURL
//              })
//            }

Button CSS:

.trix-button--icon-attach-files::before {
    background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA%2FPjxzdmcgaGVpZ2h0PSIxNnB4IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyNCAxNiIgd2lkdGg9IjI0cHgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48dGl0bGUvPjxkZXNjLz48ZGVmcy8%2BPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSI%2BPGcgZmlsbD0iIzAwMDAwMCIgaWQ9IkNvcmUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMjYuMDAwMDAwLCAtNDYuMDAwMDAwKSI%2BPGcgaWQ9ImJhY2t1cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTI2LjAwMDAwMCwgNDYuMDAwMDAwKSI%2BPHBhdGggZD0iTTE5LjQsNiBDMTguNywyLjYgMTUuNywwIDEyLDAgQzkuMSwwIDYuNiwxLjYgNS40LDQgQzIuMyw0LjQgMCw2LjkgMCwxMCBDMCwxMy4zIDIuNywxNiA2LDE2IEwxOSwxNiBDMjEuOCwxNiAyNCwxMy44IDI0LDExIEMyNCw4LjQgMjEuOSw2LjIgMTkuNCw2IEwxOS40LDYgWiBNMTQsOSBMMTQsMTMgTDEwLDEzIEwxMCw5IEw3LDkgTDEyLDQgTDE3LDkgTDE0LDkgTDE0LDkgWiIgaWQ9IlNoYXBlIi8%2BPC9nPjwvZz48L2c%2BPC9zdmc%2B);
  }

Cheers!

Just find this solution for Rails, thanks to @lucaswjohnson

Please note that this solution is only tested on Rails 6.0.0.beta1. Use it at your own risk.

// In your custom js file, for example:
// /app/javascript/utils/trix.js
export const trixOnEditorReady = () => {
  trixAddAttachmentButtonToToolbar();
};

const trixAddAttachmentButtonToToolbar = () => {
  let trixBlockButtons = document.querySelector(".trix-button-group--block-tools");
  const buttonHTML = `
      <button
        type="button"
        class="trix-button trix-button--icon trix-button--icon-attach-files"
        data-trix-action="x-attach" title="Attach Files"
        tabindex="-1"
      >Attach Files</button>
    `;
  trixBlockButtons.innerHTML += buttonHTML;

  document.querySelector(".trix-button--icon-attach-files")
    .addEventListener("click", trixAddAttachment)
};

const trixAddAttachment = () => {
  const fileInput = document.createElement("input");

  fileInput.setAttribute("type", "file");
  fileInput.setAttribute("accept", ".jpg, .png, .gif");
  fileInput.setAttribute("multiple", "");

  fileInput.addEventListener("change", () => {
    const {files} = fileInput;
    Array.from(files).forEach(insertAttachment)
  });

  fileInput.click()
};

const insertAttachment = (file) => {
  const trixEditor = document.querySelector("trix-editor").editor;
  trixEditor.insertFile(file);
};

After that, we should invoke trixOnEditorReady on document ready. Please be careful when you use Torbolinks:

// For example, in app/javascript/packs/application.js

// import {trixOnEditorReady} from "../utils/trix";

export function onReady(fn) {
  if (window.Turbolinks) {
    $(document).on('turbolinks:load', function () {
      fn();
    });
  } else {
    $(document).ready(function () {
      fn();
    });
  }
}

onReady(() => {
  trixOnEditorReady();
}

It would be great that Trix has APIs to customise the toolbar easily or just add an image insertion button on demand because it is not easy to dray-and-drop on mobile devices.

Was this page helpful?
0 / 5 - 0 ratings