Application Configuration

Before we can continue, we have to configure the application to point to the Baasic application.
Navigate to src/themes/soundbox/app.conf.json and change the following code to point to your Baasic application.

{
    "apiRootUrl": "<api-url>",
    "apiVersion": "<api-version>",
    "apiKey": "<api-key>"
}

If you don’t have a Baasic account, you can register it here. For more information on creating applications take a look at this post.

Google reCAPTCHA Configuration

Setting up reCAPTCHA using Baasic is simple. First, you should obtain public and private reCAPTCHA keys, which can be done here, and then change the following line inside src/app/app.js to contain your public key:

    .constant('recaptchaKey', '<recaptcha-key>')

Next, log into your Baasic account, navigate to the app you set up, and change the reCAPTCHA private key.

Baasic website reCAPTCHA configuration

Using Dynamic Resources - Creating albums

Creating JSON schemas

In order to use Dynamic Resources, we first need to create a Dynamic Resource Schema through the Baasic dashboard. The Dynamic Resource Schemas use the standard JSON Schema format.
Navigate to Dynamic Resources inside the Baasic dashboard, select Add new schema, and set up your object definitions through the editor.

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "title": "Unique Identifier",
      "hidden": true,
      "readonly": true
    },
    "name": {
      "type": "string"
    },
    "coverId": {
      "type": "string"
    },
    "artistId": {
      "type": "string"
    },
    "playlist": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "pic": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "author": {
            "type": "string"
          }
        }
      }
    },
    "aboutAlbum": {
      "type": "string"
    },
    "releaseYear": {
      "type": "number"
    }
  }
}

This Schema has several properties with different uses, like properties for keeping album data, such as name, description and release year; but also holds information about the album cover ID and URLs for audio files contained in the album’s playlist. This way we are keeping all of our custom data inside a single schema, which makes it simple to use and doesn’t require writing loads of other custom code.

Wrapping the DynamicResourceService

We can create a wrapper around the DynamicResourceService called AlbumsService, which will create an abstraction layer between the DynamicResourceService and the rest of the application. The wrapper is simple but keeps the code clean and easy to understand.

(function(angular) {
    'use strict';
    angular.module('media-gallery')
    .service('albumsService', ['baasicApiHttp', 'baasicDynamicResourceService', function (baasicApiHttp, dynamicResourceService) {
        var resourceName = 'albums';

        this.get = function get(id, options) {
            return dynamicResourceService.get(resourceName, id, options);
        };

        this.find = function find(options) {
            return dynamicResourceService.find(resourceName, options);
        };

        this.create = function create(album) {
            return dynamicResourceService.create(resourceName, album);
        };

        this.update = function update(album) {
            album.createDate = new Date();
            return dynamicResourceService.update(album);
        };

        this.remove = function remove(album) {
            return dynamicResourceService.remove(album);
        };

        this.next = function next(dataList) {
            var nextLink = dataList.links('next');
            if (nextLink) {
                return baasicApiHttp.get(nextLink.href);
            }
        };

        this.previous = function previous(dataList) {
            var prevLink = dataList.links('previous');
            if (prevLink) {
                return baasicApiHttp.get(prevLink.href);
            }
        };
    }
    ]);
}(angular));

Our service now has all CRUD features, as well as .next and .previous functions that help us with pagination.
It now acts as a core part of our application and is equal to other Baasic services and modules.

In this example, the wrapper doesn’t seem to have any special purpose, but in more complex applications, this is a great place to store your business logic and keep it separate from the rest of the code.

Using filesService

filesService is another Baasic service that comes out-of-the-box. It allows us to perform CRUD and batch operations in order to work with multiple files or streams, and filesService.stream methods enable streaming data from Baasic servers.
You can embed these streams directly in your AngularJS templates using ng-src, so you don’t have to write additional code for retrieving data from the backend (unless you intend to do more than just displaying it to the user).

    function loadAlbums() {
        $scope.albums = [];
        albumsService.find({
            page: 1,
            rpp: 10,
            search: $stateParams.artistId,
            orderBy: 'releaseYear',
            orderDirection : 'desc'
        })
        .success(function(data) {                    
            $scope.albums = data.item;
            ...                    
        })
        ...
    }
<div class="row">
    <div class="profile__list" ng-if="albums.length" ng-repeat="album in albums">
        <div class="album">
            <img src="./assets/img/img.png" ng-src="{{apiUrl}}/file-streams/{{album.coverId}}?rnd={{album.rnd}}" />
        </div>
        ...
    </div>
</div>

For uploading files, Baasic uses standard JavaScript interfaces - File and File Blob. You can read more about them here.

Each song is uploaded to its path, which consists of the album ID and the file name. This allows easier browsing through files inside the dashboard, and the path is later used for retrieving song data and the file blob itself (which is needed for reproducing the audio).

After the song has been created and uploaded to the backend, it is then pushed into the playlist of the album and is visible to the other application users.

if($scope.file) {
    var file = $scope.file.blob;
    var path = $scope.albumId + '/' + $scope.file.blob.name;
}

var uploadSong = function(){
    filesService.streams.create(path, file)
        .success(function(){
        })
        .error(function(error){
            $scope.error = error;
        })
        .finally(function(){
            getSongData();                                  
        });                             
};

var getSongData = function(){
    return filesService.find(path)
        .success(function(songData){
            $scope.song = songData.item[0];
            ...
        })
        .error(function(error){
            $scope.error = error;
        })
        .finally(function(){
            $scope.album.playlist.push($scope.song);
            updateAlbum();
        });
};

Playing audio files from Baasic

Starter kit uses the SoundManager2 player to reproduce audio files retrieved from Baasic. More information about SoundManager2 and its API can be found here. The app retrieves album and then injects playlist in the player. Playlist contains paths to the covers of the albums and song streams that are stored in Baasic files module and retrieves them.

Using the file reader API – displaying images on select

When selecting files for upload, it is good practice to show them to the user. This allows users to see the changes before they take any permanent effect. We are using JavaScript FileReader API to read the selected image as a data URL, which is then passed into our template. The method is called when the file input change, and besides previewing the image, it also does some validation.

$scope.previewSelectedImage = function previewSelectedImage() {
    $timeout(function() {
        if($scope.profile.avatar.blob.type === 'image/png' || $scope.profile.avatar.blob.type === 'image/jpeg' || $scope.profile.avatar.blob.type === 'image/jpg' ) {
            $scope.invalidFileType = false;
            scope.hasImageSelected = true;
            FileReader.readAsDataURL($scope.profile.avatar.blob, $scope)
                .then(function(response){
                    $scope.selectedImage = response;
                }, function(error) {
                    $scope.error = error;
                });
        }
        else {
            $scope.invalidFileType = true;
        }
        $scope.file = $scope.profile.avatar;
    });
};

Administration from the Baasic dashboard

As an Administrator, through the Baasic dashboard, you can view all users and their profiles, as well as all files uploaded to the Media Vault, and albums inside the Dynamic Resources. You can also add, remove, and modify files and resources and check on the application metrics, such as a number of requests, which can help you monitor user activity.

Conclusion

The examples in this blog post show how to integrate multiple Baasic modules and use Dynamic Resources to create an application which can be highly customized. It also demonstrates the usage of Baasic’s powerful SDK, so we don’t have to worry about making API calls but rather use simple services that will do the heavy lifting for us.

Source code can be found on GitHub and demo can be found here.

If you have any questions or need help please leave a comment below or get in touch on GitHub.

Feel free to leave a comment

comments powered by Disqus