npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@algonauti/ember-active-storage

v2.3.0

Published

Power your ember.js application with activestorage

Downloads

3,471

Readme

ember-active-storage

CI Ember Observer Score

Installation

ember install @algonauti/ember-active-storage

Usage

The addon provides an activeStorage service that allows you to:

  • send files to your Rails backend's direct upload controller;
  • listen to upload progress events.

Assuming your template has a file input like:

<input type="file" {{on "change" (fn this.upload)}} />

and your ember model has an avatar attribute defined as has_one_attached :avatar on its corresponding Active Record model, then in your component (or controller) the upload action would look like:

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';

export default class UploadComponent extends Component {
  @service
  activeStorage;

  @tracked
  uploadProgress = 0;

  @action
  upload(event) {
    const files = event.target.files;
    if (files) {
      const directUploadURL = '/rails/active_storage/direct_uploads';

      for (var i = 0; i < files.length; i++) {
        this.activeStorage
          .upload(files.item(i), directUploadURL, {
            onProgress: (progress, event) => {
              this.uploadProgress = progress;
            },
          })
          .then((blob) => {
            const signedId = blob.signedId;

            this.model.avatar = signedId;
          });
      }
    }
  }
}
  • directUploadURL is the path referencing ActiveStorage::DirectUploadsController on your Rails backend (or a custom one built on top of that).
  • The uploadProgress property will hold a value between 0 and 100 that you might use in your template to show upload progress.
  • After the upload promise is resolved and signedId is set in your model, when a model.save() is triggered, the Rails backend will use such signedId to associate an ActiveStorage::Attachment record to your backend model's record.

Events

loadstart, load, loadend, error, abort, timeout events invokes onLoadstart, onLoad, onLoadend, onError, onAbort, onTimeout accordingly. For example; If you want to use the loadend event in your app, you can use like;

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';

export default class UploadComponent extends Component {
  @service
  activeStorage;

  @tracked
  uploadProgress = 0;

  @action
  upload(event) {
    const files = event.target.files;
    if (files) {
      const directUploadURL = '/rails/active_storage/direct_uploads';

      for (var i = 0; i < files.length; i++) {
        this.activeStorage
          .upload(files.item(i), directUploadURL, {
            onProgress: (progress, event) => {
              this.uploadProgress = progress;
            },
            onLoadend: (event) => {
              debug(`Event captured ${event}`); // https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent
            },
          })
          .then((blob) => {
            const signedId = blob.signedId;

            this.model.avatar = signedId;
          });
      }
    }
  }
}

XHR object

If you need the actual XHR object in your app, you can use the onXHROpened event. It returns the XHR object reference. For example:

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';

export default class UploadComponent extends Component {
  @service
  activeStorage;

  @tracked
  uploadProgress = 0;

  @tracked
  xhrs = [];

  @action
  upload(event) {
    const files = event.target.files;
    if (files) {
      const directUploadURL = '/rails/active_storage/direct_uploads';

      for (var i = 0; i < files.length; i++) {
        this.activeStorage
          .upload(files.item(i), directUploadURL, {
            onProgress: (progress, event) => {
              this.uploadProgress = progress;
            },
            onXHROpened: (xhr) => {
              this.xhrs.push(xhr);  // so you can loop over this.xhrs and invoke abort()
            },
          })
          .then((blob) => {
            const signedId = blob.signedId;

            this.model.avatar = signedId;
          });
      }
    }
  }
}

Metadata

ActiveStorage supports metadata for direct uploads. That is a nice way to provide extra information to the rails app.

class DirectUploadsController < ActiveStorage::DirectUploadsController
  def create
    # blob_args[:metadata]['additional_type']
    # => my_type
    blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args)
    render json: direct_upload_json(blob)
  end
end
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';

export default class UploadComponent extends Component {
  @service
  activeStorage;

  @action
  upload(event) {
    const files = event.target.files;
    if (files) {
      const directUploadURL = '/rails/active_storage/direct_uploads';

      for (var i = 0; i < files.length; i++) {
        this.activeStorage
          .upload(files.item(i), directUploadURL, {
            metadata: {
              additional_type: 'my_type'
            },
          })
          .then((blob) => {
            const signedId = blob.signedId;

            this.model.avatar = signedId;
          });
      }
    }
  }
}

Configuration

There is an ember-active-storage ENV config with only one parameter called url. With this config help, you can omit the upload url now. For example:

ENV['ember-active-storage'] = {
  url: 'http://your-domain/rails/active_storage/direct_uploads',
};

Now you can call the upload function without the upload url.

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';

export default class UploadComponent extends Component {
  @service
  activeStorage;

  @tracked
  uploadProgress = 0;

  @action
  upload(event) {
    const files = event.target.files;
    if (files) {
      for (var i = 0; i < files.length; i++) {
        this.activeStorage
          .upload(files.item(i), {
            onProgress: (progress, event) => {
              this.uploadProgress = progress;
            },
          })
          .then((blob) => {
            const signedId = blob.signedId;

            this.model.avatar = signedId;
          });
      }
    }
  }
}

Sending authentication headers

It's pretty common that you want to protect with authentication the direct uploads endpoint on your Rails backend. If that's the case, the activeStorage service will need to send authentication headers together with the direct upload request.

To achieve that, you'll need to extend the activeStorage service provided by the addon and add a headers computed property. For example, if you're using ember-simple-auth, it will be a 2-steps process. First you'll need to define an authenticatedHeaders computed property in your session service, like this:

// app/services/session.js
import Service from '@ember/service';
import { inject as service } from '@ember/service';

export default class MySessionService extends Service {
  @service
  session;

  get authenticatedHeaders() {
    const { access_token } = this.session.authenticated;

    return { Authorization: `Bearer ${access_token}` };
  }
}

Then, you will alias that property in your activeStorage service, like this:

// app/services/active-storage.js
import ActiveStorage from '@algonauti/ember-active-storage/services/active-storage';
import { inject as service } from '@ember/service';

export default class ActiveStorageService extends ActiveStorage {
  @service('my-session')
  session;

  get headers() {
    this.session.authenticatedHeaders;
  }
}

Also note: if the download endpoint is protected as well, and you're using an ajax request to download files, then don't forget to include the same headers in that request as well.

Contributing

Installation

  • git clone <repository-url>
  • cd ember-active-storage
  • yarn install

Linting

  • yarn lint:js
  • yarn lint:js --fix

Running tests

  • ember test – Runs the test suite on the current Ember version
  • ember test --server – Runs the test suite in "watch mode"
  • yarn test – Runs ember try:each to test your addon against multiple Ember versions

Running the dummy application

For more information on using ember-cli, visit https://ember-cli.com/.

License

This project is licensed under the MIT License.