Overview
In this tutorial we will create simple Custom Source for the File Picker from the static list of files. In more complex use cases you can build Custom Source based on your dynamically generated list or API response.
You can find full Custom Source API reference here.
Github
Source code is available at Github Repository
Create a Plugin with basic information
First, we need to set up basic information about our custom source. Let’s name it My new Custom Source
and give it a unique key, myCustomSource
.
All new lines will be commended in code.
const myNewCustomSource = {
label: 'My new Custom Source',
name: 'myCustomSource',
icon: '<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="18" cy="18" fill="#eee" r="18"/><path d="m9.9 18c0-1.71 1.39-3.1 3.1-3.1h4v-1.9h-4c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9h-4c-1.71 0-3.1-1.39-3.1-3.1zm4.1 1h8v-2h-8zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4v1.9h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" fill="#000" fill-opacity=".7"/></g></svg>',
mounted: (element, actions) => {
},
unmounted: (element) => {
}
}
Create an HTML list for example
Let’s create some HTML list that will be used as our sample file list
const sourceUrlsArray = [{
url: 'https://picsum.photos/200/200',
name: 'Sample image 200x200',
},{
url: 'https://picsum.photos/300/300',
name: 'Sample image 300x300',
},{
url: 'https://picsum.photos/400/400',
name: 'Sample image 400x400',
},{
url: 'https://picsum.photos/500/500',
name: 'Sample image 500x500',
},{
url: 'https://picsum.photos/600/600',
name: 'Sample image 600x600',
}] // this can be obtained form any place, for tutorial purposes we will set it static
const myNewCustomSource = {
label: 'My new Custom Source',
name: 'myCustomSource',
icon: '<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="18" cy="18" fill="#eee" r="18"/><path d="m9.9 18c0-1.71 1.39-3.1 3.1-3.1h4v-1.9h-4c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9h-4c-1.71 0-3.1-1.39-3.1-3.1zm4.1 1h8v-2h-8zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4v1.9h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" fill="#000" fill-opacity=".7"/></g></svg>',
mounted: (element, actions) => {
// create new list for plugin view
const list = document.createElement('ul');
list.setAttribute('class', 'fsp-picker--custom-source');
sourceUrlsArray.forEach((element, idx) => {
// add list element
const li = document.createElement("li");
const span = document.createElement("span");
// create thumbnail view
const thumb = document.createElement("img");
thumb.setAttribute("src", element.url);
thumb.width = "50";
thumb.height = "50";
// append elements to list
li.appendChild(thumb);
span.innerHTML = `${element.name}`;
li.appendChild(span);
list.appendChild(li);
});
// append list to picker placeholder
element.appendChild(list);
},
unmounted: (element) => {
}
}
Handle click event
Now we have to handle user click event on our file list so we will know what files where selected for upload:
const sourceUrlsArray = [{
url: 'https://picsum.photos/200/200',
name: 'Sample image 200x200',
},{
url: 'https://picsum.photos/300/300',
name: 'Sample image 300x300',
},{
url: 'https://picsum.photos/400/400',
name: 'Sample image 400x400',
},{
url: 'https://picsum.photos/500/500',
name: 'Sample image 500x500',
},{
url: 'https://picsum.photos/600/600',
name: 'Sample image 600x600',
}] // this can be obtained form any place, for tutorial purposes we will set it static
const myNewCustomSource = {
label: 'My new Custom Source',
name: 'myCustomSource',
icon: '<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="18" cy="18" fill="#eee" r="18"/><path d="m9.9 18c0-1.71 1.39-3.1 3.1-3.1h4v-1.9h-4c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9h-4c-1.71 0-3.1-1.39-3.1-3.1zm4.1 1h8v-2h-8zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4v1.9h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" fill="#000" fill-opacity=".7"/></g></svg>',
mounted: (element, actions) => {
const list = document.createElement('ul');
list.setAttribute('class', 'fsp-picker--custom-source');
// handle click on element
const handleClick = (e) => {
const idx = parseInt(e.currentTarget.dataset.idx);
console.log('clicked index', idx);
};
sourceUrlsArray.forEach((element, idx) => {
const li = document.createElement("li");
const span = document.createElement("span");
// lets set idx as dataset attribute
li.dataset.idx = idx;
// and set id attr, so we can have easy access to element
li.setAttribute('id', `file-${idx}`)
const thumb = document.createElement("img");
thumb.setAttribute("src", element.url);
thumb.width = "20";
thumb.height = "20";
li.appendChild(thumb);
span.innerHTML = `${element.name}`;
li.appendChild(span);
li.addEventListener('click', handleClick);
list.appendChild(li);
});
element.appendChild(list);
},
unmounted: (element) => {
}
}
Add file to upload list after click
Now that we can handle user click, let’s handle adding files to the File Picker upload list:
const sourceUrlsArray = [{
url: 'https://picsum.photos/200/200',
name: 'Sample image 200x200',
},{
url: 'https://picsum.photos/300/300',
name: 'Sample image 300x300',
},{
url: 'https://picsum.photos/400/400',
name: 'Sample image 400x400',
},{
url: 'https://picsum.photos/500/500',
name: 'Sample image 500x500',
},{
url: 'https://picsum.photos/600/600',
name: 'Sample image 600x600',
}] // this can be obtained form any place, for tutorial purposes we will set it static
const myNewCustomSource = {
label: 'My new Custom Source',
name: 'myCustomSource',
icon: '<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="18" cy="18" fill="#eee" r="18"/><path d="m9.9 18c0-1.71 1.39-3.1 3.1-3.1h4v-1.9h-4c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9h-4c-1.71 0-3.1-1.39-3.1-3.1zm4.1 1h8v-2h-8zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4v1.9h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" fill="#000" fill-opacity=".7"/></g></svg>',
mounted: (element, actions) => {
const list = document.createElement('ul');
list.setAttribute('class', 'fsp-picker--custom-source');
const toUpload = [];
// handle click on element
const handleClick = (e) => {
e.stopPropagation();
const element = e.currentTarget;
const idx = parseInt(element.dataset.idx);
// selected file
const file = sourceUrlsArray[idx];
// if file is selected for upload mark it and add to toUpload list if not deselect it
if (toUpload[idx]) {
// remove selected class from element
element.classList.remove('file-selected');
// remove element from toupload list
delete toUpload[idx];
} else {
// add selected class to element
element.classList.add('file-selected');
// set element to toDupload list
toUpload[idx] = file;
}
return false;
};
sourceUrlsArray.forEach((element, idx) => {
const li = document.createElement("li");
const span = document.createElement("span");
li.dataset.idx = idx;
li.setAttribute('id', `file-${idx}`)
const thumb = document.createElement("img");
thumb.setAttribute("src", element.url);
thumb.width = "20";
thumb.height = "20";
li.appendChild(thumb);
span.innerHTML = `${element.name}`;
li.appendChild(span);
li.addEventListener('click', handleClick);
list.appendChild(li);
});
element.appendChild(list);
},
unmounted: (element) => {
}
}
Upload files and display summary
Now, that we can select files, let’s learn how we can upload them and display a summary view using actions.fetchAndAddURL and actions.showSummaryView. You can find more actions in Custom Source API reference here.:
const sourceUrlsArray = [{
url: 'https://picsum.photos/200/200',
name: 'Sample image 200x200',
},{
url: 'https://picsum.photos/300/300',
name: 'Sample image 300x300',
},{
url: 'https://picsum.photos/400/400',
name: 'Sample image 400x400',
},{
url: 'https://picsum.photos/500/500',
name: 'Sample image 500x500',
},{
url: 'https://picsum.photos/600/600',
name: 'Sample image 600x600',
}];
const myNewCustomSource = {
label: 'My new Custom Source',
name: 'myCustomSource',
icon: '<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="18" cy="18" fill="#eee" r="18"/><path d="m9.9 18c0-1.71 1.39-3.1 3.1-3.1h4v-1.9h-4c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9h-4c-1.71 0-3.1-1.39-3.1-3.1zm4.1 1h8v-2h-8zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4v1.9h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" fill="#000" fill-opacity=".7"/></g></svg>',
mounted: (element, actions) => {
const list = document.createElement('ul');
list.setAttribute('class', 'fsp-picker--custom-source');
let toUpload = {};
const handleClick = (e) => {
e.stopPropagation();
const element = e.currentTarget;
const idx = parseInt(element.dataset.idx);
const file = sourceUrlsArray[idx];
if (toUpload[idx]) {
element.classList.remove('file-selected');
delete toUpload[idx];
} else {
element.classList.add('file-selected');
toUpload[idx] = file;
}
// we can add some state to upload btn if files array is empty
const finishBtn = document.querySelector('.fsp-picker--custom-source-footer button');
if (toUpload.length === 0) {
finishBtn.classList.add('btn-disabled');
} else {
finishBtn.classList.remove('btn-disabled');
}
return false;
};
const handleFinishClick = (e) => {
e.stopPropagation();
// if there is no files to upload just do nothing
if (!toUpload || Object.keys(toUpload).length === 0) {
return;
}
const todo = [];
// lets take all selected files list and add it to upload queue in picker
Object.values(toUpload).forEach((file) => todo.push(actions.fetchAndAddUrl(file.url)));
Promise.all(todo).then(() => {
console.log('All files has been added to queue');
// after adding all files to queue we can switch view to upload summary
actions.showSummaryView();
// cleanup toUpload object
toUpload = {};
});
return false;
};
sourceUrlsArray.forEach((element, idx) => {
const li = document.createElement("li");
const span = document.createElement("span");
li.dataset.idx = idx;
li.setAttribute('id', `file-${idx}`)
const thumb = document.createElement("img");
thumb.setAttribute("src", element.url);
thumb.width = "20";
thumb.height = "20";
li.appendChild(thumb);
span.innerHTML = `${element.name}`;
li.appendChild(span);
li.addEventListener('click', handleClick);
list.appendChild(li);
});
// lets add button that will handle finish selecting file process
// header
const divHeader = document.createElement('div');
divHeader.setAttribute('class', 'fsp-picker--custom-source-header');
let buttonViewType = document.createElement('span');
buttonViewType.classList.add('view-type');
divHeader.appendChild(buttonViewType);
// View type change handler
const handleViewTypeClick = (e) => {
e.stopPropagation();
if (element.classList.contains('grid')) {
element.classList.remove('grid');
} else {
element.classList.add('grid');
}
};
buttonViewType.addEventListener('click', handleViewTypeClick);
// lets add button that will handle finish selecting file process
const divFooter = document.createElement('div');
divFooter.setAttribute('class', 'fsp-picker--custom-source-footer');
const finishBtn = document.createElement('button');
finishBtn.innerText = 'View/Edit selected';
finishBtn.classList.add('btn-disabled');
if (actions.filesList && actions.filesList.length > 0) {
finishBtn.classList.remove('btn-disabled');
}
const footerSpan = document.createElement('span');
footerSpan.innerHTML = 'Selected Files: ' + actions.count;
divFooter.appendChild(footerSpan);
divFooter.appendChild(finishBtn);
finishBtn.addEventListener('click', handleFinishClick);
element.appendChild(divHeader);
element.appendChild(list);
element.appendChild(divFooter);
},
unmounted: (element) => {
}
}
Add Custom Source to the File Picker
const apikey = YOUR_APIKEY;
const client = filestack.init(apikey);
const options = {
fromSources: [ 'local_file_system', myNewCustomSource, 'googledrive', 'unsplash', 'facebook', 'instagram']
maxFiles: 20,
uploadInBackground: false,
onUploadDone: (res) => console.log(res),
};
const picker = client.picker(options);
picker.open();
Have questions? We’re here to help! Contact Support