What's in it for you
This tutorial explains how to build a first Experience app using a combination of Showpad developer tools we provide. It assumes an intermediate level knowledge of Showpad and front-end development. If you’re new to development, we recommend taking a look at the overview of developer tools we provide first.
We're going to make an experience app with following offline capabilities:
- query assets by tags
- add assets to a collection
- share assets
- upload assets
You can find several notes where we explain tips, tricks, and facts for more advanced developers. You can safely skip this in the tutorial. At the bottom of this page, we provide before and after ZIP files, that show you the starting and ending state of the project.
Note: The current windows app uses IE11 as render engine. Our app will not work because our syntax will be ES6 flavoured.
You need this to succeed
- admin access to a showpad domain
- tagged assets used for search
- Showpad ultimate licence
- NodeJs to install our SDK
- Install the Showpad SDK
- OAuth client
- Authentication
- Manifest.json
- Config.json
- Index.html
- SDK development server
- window.onShowpadLibLoaded
- Translations
- ShowpadLib.getAssetsByTags
- ShowpadLib.getAssetPreviewUrl
- ShowpadLib.addAssetsToCollection & ShowpadLib.share
- ShowpadLib.upload & ShowpadLib.displayToast
- Let's ship our app
- Endnote
Do this step by step
We've prepared a ZIP file with our folder structure and some files in place, download this Experience app boilerplate ZIP and extract it in a local folder on your system.
You can do this with the following command:
npm i @showpad/sdk -g
You will need an OAuth client to authenticate yourself and our application.
- Go to the Showpad backend, click on the cog wheel and press integrations.
- Click Manage OAuth Clients and press the plus button to create a new one. Use the following parameters for your OAuth client:
- Name: Developer client
- Redirect URL: https://yourdomain.showpad.biz
- Description: OAuth client for developing experience apps
- Website: https://yourdomain.showpad.biz
- This clients need to: check all boxes
We now have our OAuth client but we need to authenticate our user for this client.
- Change the bold parameters of this url and paste it into a browser tab. https://yourdomain.showpad.biz/api/v2/oauth2/authorize?client_id=clientid&redirect_uri=https://yourdomain.showpad.biz&response_type=code
- Accept the request and your currently logged in user will now be authenticated for this client.
We're going to use the SDK to make our authentication file.
- Go in your command line tool to our directory and use following command.
Showpad auth
- You will be prompted with some questions. Your Showpad username is your email. There should be a new .showpadconfig.json file in the directory.
The manifest.json file contains general information about your experience app. Make sure that your identifier is unique for every experience app you make and to increase the version number every time you upload a new version to Showpad. Copy-paste following structure in src/manifest.json. You can set the author to your name.
{ "identifier": "com.org.showpad.my.first.experience.app", "name": "My first experience app", "version": "0.0.1", "description": "Tutorial for making an experience app", "author": "Showpad" }
The config.json file determines the structure of the labels and contents that can be altered by the showpad admin.
- Labels can be settings or translations and are text based.
- Contents define the assets that your app and the user assigned to your app should have access to. These assets will be available offline. Make sure that the config.json does not contain object keys that could be reserved by showpad like type, value, … If it is in the following list, you can not use it as a custom key: https://showpad.pages.showpad.io/public-sdk/config.json.html.
We're going to add some translations in the labels and request assets with tags that we can later search for. Copy paste following structure in src/config.json and alter the ["tag1", "tag2", "tag3"] with tags you actually use in the backend. Make sure there are assets with the tags you've entered.
{ "version": 1, "labels": { "translations": { "title": { "value": "Tutorial", "description": "Title" }, "search_button": { "value": "Search", "description": "Search button" }, "add_to_collection_button": { "value": "Add to collection", "description": "Add to collection button" }, "share_button": { "value": "Share", "description": "Share button" }, "upload_button": { "value": "Upload", "description": "Upload button" } } }, "contents": { "assets": { "search": { "type": "tags", "value": { "or": [ "tag1", "tag2", "tag3" ] }, "description": "Searchable assets" } } } }
The index.html is the entry point of our Experience app. You can set up your structure and add all the necessary resources like css and js here. The boilerplate already contains our structure. Now everything is in place, let's start the development of our app.
For developing Experience apps. We will use the SDK development server. This will inject the ShowpadLib into our application and mimics the behaviour of our app running in an Iframe, which will also be the case in the platform.
- Type following in your command line tool (still in the root of our directory) to start the server.
showpad experience serve
- A new tab opens with the following url: http://localhost:9000/. You already see some styled elements in place because we loaded some CSS in our index.html.
Note: If you want to develop with a framework like React, you can start your development server at a different port (let's say 8080) and point the Showpad server to this. Make sure to make your public path relative.
showpad experience serve --host http://localhost:8080
We want to know when showpad has loaded our application. If we try to call methods of the ShowpadLib earlier, it will fail.
- The next thing we want to do is to load our configuration from config.json to access our labels, contents and assets. The assets object contains the assets you've defined in the contents. These assets contain all the necessary information for the ShowpadLib methods we're going to use.
- Once the config is loaded, we're going to set this objects to the window so we can access them easily throughout the app. Copy paste following code in src/js/app.js.
function init () { console.log(window.labels) console.log(window.contents) console.log(window.assets) } window.onShowpadLibLoaded = () => { window.ShowpadLib.getShowpadApi((apiConfig) => { let url = window.location.href.split('configUrl=')[1].split('&')[0] url = decodeURIComponent(url)
fetch(url, { method: 'GET', headers: { 'Authorization': 'Bearer ' + apiConfig.accessToken, 'Content-Type': 'application/json; charset=utf-8' } }) .then(function (response) { return response.json() })
.then(function (data) {
window.labels = data.labels
window.contents = data.contents
window.assets = data.assets init()
}) })
}
You should now see your defined labels and contents. In the asset object, we see some mock-id info. This is the SDK telling us it's mocking our assets.
Note: When developing, you can encounter a situation where not the latest labels and contents are loaded. This can be due to various situations where you for example already uploaded a version for testing. You can solve this in two ways. The first one is to change the identifier from your app. The second one is to locally load your config.json and overwrite your objects.
Note: It can also be the case that the assets returned are not updated. You could write a function where you load the assets from the contents through the REST API and place them on the object. Beware that the result from the SDK will be different from the REST API so we don't encourage this automation. The best practice is to define your structure upfront the best you can, upload the experience to Showpad and the next time you develop, you will have the up to date assets you defined in your contents.
Localization of your experience app is very powerful. You can add different language fields for the same object or can just duplicate your experience app and translate each of them in a different language. We're going to use the translations we defined in our config.json to set our title and fill our buttons. When our app is ready and uploaded, the admin will be able to alter these translations without coding.
Add the following function to src/js/app.js and call it in the init function. Our app should now contain these translations.
function translations () { document.getElementsByTagName("h1")[0].innerHTML = window.labels.translations.title.value document.querySelector('#search #search_submit').innerHTML = window.labels.translations.search_button.value document.querySelector('#actions #actions_collection').innerHTML = window.labels.translations.add_to_collection_button.value document.querySelector('#actions #actions_share').innerHTML = window.labels.translations.share_button.value document.querySelector('#upload #upload_submit').innerHTML = window.labels.translations.upload_button.value }
The next thing we add is the capability to search for assets by tags. You can search for all assets that are assigned to this user. This means that it's not necessarily contained by the tags we defined in our config.json. For this, we use the ShowpadLib.getAssetsByTags() method. All ShowpadLib methods are mocked by the SDK. You can see on the following link what the limitations are and what the expected result should look like.
- Add the following function to src/js/app.js and call it in the init function.
function search () { document.querySelector('#search #search_submit').addEventListener('click', (e) => {
e.preventDefault()
ShowpadLib.getAssetsByTags([document.querySelector('#search input[type=text]').value], (assets) => {
console.log(assets) }) }) } - If you now search for an asset you've defined in the config.json, you should see it in the console.log window.
Note: Be aware that ShowpadLib functions with the same payload are debounced when repeated quickly. Always check the returned response to verify if a call came through.
We now want to display our results to the user. We can fetch a thumbnail by using the ShowpadLib.getAssetPreviewUrl() method where we pass the id, slug and a size of our thumbnail. The default value of this is 400 and the max value is 1600.
We also want our user to open the actual asset to see if it is the correct one. Showpad has a unique file scheme where it will intercept these requests and open the asset viewer. This url is build up the following way: showpad://file/ + assetSlug. The asset viewer also provides native capabilities like sharing or adding assets to a collection.
Our results will also contain a checkbox to identify which assets we want to share or add to a collection. Add following function to src/js/app.js to display our results. Call this function in the callback of your search results and pass the assets to it.
function displayResults (assets) { document.getElementById('results').innerHTML = '' for (let key in assets) { let asset = assets[key] let assetPreviewUrl = ShowpadLib.getAssetPreviewUrl(asset.id, asset.slug, 400); let result = document.createElement('div') result.className = 'result' let html = '<a href=showpad://file/' + asset.slug + ‘?modal=1>’ html += '<img src="' + ShowpadLib.getAssetPreviewUrl(asset.id, asset.slug, 400) + '" />' html += '</a><input class="results_checkbox" type="checkbox" data-slug=' + asset.slug +'>' result.innerHTML = html document.getElementById('results').appendChild(result); } }
After we submit a search, results should now appear. We can click on the image to open the native showpad asset viewer. We're now implementing code to keep track of the assets we mark as active by toggling the checkbox. Copy-paste the following function to src/js/app.js and call it in your init() function.
function actions () {
document.querySelector('body').addEventListener('click', (e) => {
if (e.target.classList.contains('results_checkbox')) {
if (e.target.checked) {
activeAssets.push(e.target.dataset.slug) } else { activeAssets = activeAssets.filter(asset => asset !== e.target.dataset.slug); } } })
}
As you can see we store our assets in the activeAssets array. Add this to the top of your file like this:
let activeAssets = []
When searching, we want to make we don't include our previous results so clear your activeAssets in the search event listener.
ShowpadLib.addAssetsToCollection & ShowpadLib.share
We now want to add our activeAssets array to a collection, for this, we add an event listener to the button and check if our activeAssets array is not empty. If not, we call the ShowpadLib.addAssetsToCollection() method and pass our assets. The callback provides us with the ID of the collection our assets are added to. Copy paste following code in the actions() function in src/js/app.js.
document.querySelector('#actions #actions_collection').addEventListener('click', (e) => { e.preventDefault() if (activeAssets.length > 0) { ShowpadLib.addAssetsToCollection(activeAssets, (collectionId) => {
if (collectionId) console.log('Assets are added to collection with id: ' + collectionId) }) } })
We should see a response from the SDK indicating our call went through. We want to do exactly the same with our share button. For this we use the ShowpadLib.share() method. We have to pass a parameter if we want to share by link or by email. In our example, we're choosing for email. Copy-paste the following code in the actions() function in src/js/app.js.
document.querySelector('#actions #actions_share').addEventListener('click', (e) => {
e.preventDefault() if (activeAssets.length > 0) {
ShowpadLib.share('email', activeAssets, (result) => {
if (result === 'success') console.log('Assets are shared with the client')
})
}
})
Note: You can always check which parameters there are, and which are required here. Notice the version number in the upper right corner. Not all Showpad platforms use the same version so some of them might miss the features you need. You can check the following link which platforms has what version.
If we test our app, the sharing should now also return a callback. We've created an app that can search for assets by tags, open the assets, add the assets to collections and share the assets. Another important thing in custom apps is the generation of assets based on input and the possibility to save this offline.
ShowpadLib.upload & ShowpadLib.displayToast
The upload method lets users upload generated content to myfiles. It requires a filename and a file and will return an event emitter which has several states. We generate a .txt file from a textarea field and pass it to the method. With the ShowpadLib.displayToast() method, we can show these statuses to the user with a native toast on each platform. Copy-paste the following function to src/js/app.js and call it in the init function().
function upload () {
document.querySelector('#upload #upload_submit').addEventListener('click', (e) => {
e.preventDefault() const filename = 'my_first_experience_app.txt';
const file = new File([document.querySelector('#upload textarea').value], filename); const upload = ShowpadLib.upload({
file: file,
filename: filename }) upload.on('queued', () => { ShowpadLib.displayToast({ type: "info", text: "queued" }) }) upload.on('uploading', (data) => { ShowpadLib.displayToast({ type: "info", text: "uploading (" + parseInt((data.bytesSent / data.bytesTotal)*100) + "%)" }) }) upload.on('processing', () => { ShowpadLib.displayToast({ type: "info", text: "processing" }) }) upload.on('success', (data) => { ShowpadLib.displayToast({ type: "success", text: "success" }) }) })
}
When testing this in our app, you will see the SDK responding with the upload and several toast responses. The data object in the success handler returns our asset.
Note: When offline, the asset will stay in queued status. Waiting for the success handler to release your app could block your interface offline. We suggest a non obstructive UI with graceful notifications about the status.
At the time of writing, it is not possible for the uploaded asset to be shared or added to a collection.
Our app is now ready to be deployed. We'll use the SDK to package our app. Go to your command line tool to the root directory of the app and enter the following command:
showpad experience package
- You will see the SDK validating all of our files and creating a .showpad file.
- You can now go to the backend of your domain and open the channel builder.
- Make a new channel with the type Experience app and select our newly generated .showpad file.
- After uploading, you should see our app but without the JS initialized. This is because we call our init() function in the Showpad.getShowpadApi() call which isn't executed in the preview. This will work in the frontend.
If you click on edit, you will see the structure we defined in the config.json. As you can see, you can alter the translations and add, remove tags to our application. - Click on publish experience and wait for the platform to release our app. This can take a minute.
If you now go to the frontend and open our new channel. The tutorial app should appear in all its glory. Go ahead and test all functionalities.
You can find the completed version here Experience app codebase to compare differences with your codebase.
The developer pages are your go-to source while developing experience apps.
We welcome you to ask your questions in the community part, so fellow developers can answer your questions and everyone can learn from everyone.
Happy coding!