A fundamental requirement of any Content Management System is the ability to include pictures, videos, or any other media in our web pages.
The ShaneBow MediaPicker
system allows us to do this with simple reusable code that hides
all the ugly details and a intuitive UI.
This post shows the client side usage and coding in a future post we will explore the back end and API.
Requirements
- jQuery - someday we'll rewrite it to use just vanilla javascript!
- bootstrap 3+ - the dialog is structured as a bootstrap modal.
Using the MediaPicker
Dialog
Let's say we want to insert an image into a blog post. The image that we want to insert on the page must be on a file server someplace. Consider these possibilities:
- The file is already on our server: Great! But, we need a way to select it and insert it into the blog post.
- The file is on the user's computer: In this case we need to upload the file from the local machine to the server — and get a URL that points to where it got uploaded to, so we can reference it in the post.
Our MediaPicker
class pops up a dialog with Remote and Upload tabs to handle these two
possibilities. (There is a third possibility that the file is on somebody else's server:
Don't do this! See Avoid Hotlinking in the sidebar)
The dialog also features a search input to filter remote media by title and description.
Coding the MediaPicker
The MediaPicker
class is designed to be flexible enough to handle most any use case.
Below, we demonstrate the following use cases:
- Avatar - use the simple default settings to choose a single image, such as an avatar
- Gallery - use
append
mode to grow a collection with an arbitrary number of items - Table Column - use the
onChange
hook for maximum flexibility - Custom - use custom mode to insert images into dynamic elements
const picker = new MediaPicker(selector, options);
Avatar Example
In this example from the timelines project, the event editor allows us to assign an image to each event. To set the image, just click on it and choose either a local or remote image.
It uses the default settings and so it painless to use.
As with all the images on this page, you can click this one to enlarge it — which you want to do to get a better look at my twin boys! or go check out the rendered page on sirijanda.com!!
Markup
We just need a <div>
with a single <img>
inside.
The markup shows that after the MediaPicker
has been invoked, the src
attribute is automatically updated
to the new URL and the image is assigned an mid
, which stands for media id.
<div id="thumb" class="ar ar16x9">
<img mid="850" src="/uploads/850.jpg" />
</div>
Javascript
// instantiate a media picker instance
const picker = new MediaPicker('#thumb');
// get a CSV of the media ids in '#thumb' div
// by default is a string with zero or one id...
// do this in the form submit handler
let mid = picker.csvIDs();
Gallery Example
This is another example from the timelines project, showing how the editor for a timeline object allows the user to attach an arbitrary number of images.
The user clicks on Attachments to
to launch the Media Picker
in order to add media to the attachments, which are rendered as a gallery on
the finished page at alasnome.com.
Markup
The markup is straightforward, we just need an element to click and a div
to hold the images:
<!-- click this to open MediaPicker dialog -->
<a id="btn-media-pick" href="#">
<i class="icon-file-plus position-left"></i>
Attachments
</a>
<div id="gallery" class="row same-height-row">
<!-- MediaPicker appends selected/uploaded media here -->
</div>
Javascript
As in the prior example, we specify where the images go in the first argument ('#gallery'
)
to the MediaPicker
constructor.
But, now we also need to add a couple of simple options:
btn
is a selector for the thing to click on to bring up the dialog — it doesn't have to be an actual button!mode: 'append'
tells the picker to add images to the div rather than the defaultreplace
mode.
// instantiate a media picker instance
const picker = new MediaPicker('#gallery', {
btn: '#btn-media-pick',
mode: 'append',
});
// get CSV of media ids in '#gallery' div.
// In append mode this returns 0 or more ids...
// do this in the form submit handler
let mids = picker.csvIDs();
Again, we use the csvIDs()
method to get a list of media ids stored in the database. Do this
in the submit handler for the editor.
Table Column Example
This example illustrates two options to the MediaPicker
that facilitate countless custom applications.
At ThaiDrills we are constantly building data sets that link thai and english, with illustrative media (images, video, audio).
In the table shown we click in the img column to select or upload images to the server.
Let's look at the code and then it will be obvious how the techniques can be generalized to handle any collection of nodes on a page.
Markup
<table id="my-table" class="ar ar16x9">
<tbody>
. . .
<tr> . . . <td class="my-class" /></td><tr>
<tr> . . . <td class="my-class" /></td><tr>
. . .
</tbody>
</table>
Javascript
new MediaPicker('#my-table', {
selector: 'td.my-class',
onChange: ($img,$this) => {
const mid = $img.attr('mid');
const $tr = $this.closest('tr');
const row = $tr.index();
// do something with the image data
// In our use case, we ajax the eng-thai-img
// connection straight to the server
},
});
When the selector: 'td.my-class'
option is used, says to launch the dialog when any
node matching this selector is clicked — provided it is contained by the
node specified by the first argument to the constructor (i.e. '#my-table'
).
In other words, this is equivalent to the jQuery, $('#my-table').on('click', 'td.my-class', ...)
.
The onChange
hook allows you to specify a function that is called whenever the image is
changed. The routine is passed two jQuery objects: one for the new image, and one for it's
immediate parent.
Custom
Sometimes, none of the above techniques will work. This is generally the case when you need
to insert media into dynamic elements that didn't exist when the MediaPicker
was instantiated.
For instance, ThaiTrills produces Touch Books where each page of a book has a different background image. However, there is one button in the toolbar to set the background image for the current page.
This is a case that is suited to the custom mode
which makes this problem easy to handle.
Here are the main considerations:
* use the button
as the MediaPicker
element (i.e. the first argument to constructor)
* specify mode: 'custom'
as an option
* use the onChange
option to do whatever you want with the selected image
my.pageBgImgPicker = new MediaPicker('#img-pick-btn', {
mode: 'custom',
verbose:!1,
onChange: ($img) => {
console.log('background image changed');
my.$pages.find('.active').append($img);
},
});