Welcome to the third in my series of building a (somewhat) real-world application using StrongLoop. In the first entry I built the beginnings of a simple blog engine. I defined two models (entry and category) and whipped up a quick front end for the blog. In the last entry I locked down the APIs so that unauthenticated visitors couldn't create content. Today I'm going to demonstrate an administrator for my blog. My administrator will be a desktop tool built with Electron and Ionic. I first blogged about mixing Ionic and Electron about six months ago. It is still rather easy and you can check out the results on the GitHub repo for this project when your done reading. (I'll include the link at the end.)
Let's take a quick tour through the app and then I'll demonstrate it in action with a quick video. On startup, the application prompts you to login. Please do not blame Ionic for my poor color choices.
After login, you're presented with a list of existing blog entries as well as a button to add a new one. For this quick demonstration, I did not add editing or deleting capabilities, but it wouldn't be too difficult.
Clicking Add Entry brings you to a simple form:
And that's it. As I said, proper edit/delete isn't built in yet, but that's all it would take to turn this into a real CRUD desktop app for the server.
To be clear, I'm really barely scratching the surface of what Electron can do. I've basically used it as a simple wrapper for a web view and nothing more. Off the top of my head - here are some more interesting features I could add to it:
-
Drag and drop images. I could capture the drop event - upload the file to the server, and automatically inject the HTML for the image into the source. This is how WordPress does it and it would certainly be possible with Electron.
-
Of course, I could customize the icon like a "proper" desktop application.
-
And probably more that I'm not thinking about.
So how about the code? First and foremost I want to point out that StrongLoop has an AngularJS library and I that I should have made use of it. I did not. I want to - eventually - but I thought it might be a good opportunity to work more with AngularJS's $resource feature. To be honest, I had a few problems with it and I should have taken that as a clue to just switch to StrongLoop's stuff, but I was stubborn.
Here's how I designed my Services:
angular.module('starter.services', [])
.factory('userService', function($q,$resource) {
return $resource('http://localhost:3000/api/appusers/:id',{},
{
'login':{
'method':'POST',
'url':'http://localhost:3000/api/appusers/login'
}
});
})
.factory('entryService', function($q,$resource) {
return $resource('http://localhost:3000/api/entries/:id');
});
As you can see - I simply $resource-wrapped my two main APIs - one for users and one for entries. (I'm still not really supporting categories yet.) For users I had to add the custom login method that ships out of the box. On the calling side, here is the controller code for doing login.
.controller('loginCtrl', ['$scope', '$rootScope', 'userService', '$state', '$http',
function($scope, $rootScope, userService, $state, $http) {
$scope.user = {username:'raymondcamden@gmail.com',password:'password'};
$scope.doLogin = function() {
if($scope.user.username === '' || $scope.user.password === '') {
return;
}
userService.login({email:$scope.user.username,password:$scope.user.password},function(res) {
$rootScope.authToken = res.id; // don't really need to keep it
$http.defaults.headers.common['Authorization'] = $rootScope.authToken;
$state.go('root.Home');
},function(e) {
//for right now - generic error
alert('Login Failed');
});
};
}])
First off - I'm hard coding the username and password in there just to save me on typing. That's a pro-tip there. The login call is pretty simple, but I need to remember the auth token returned by the Loopback API. I both store it in rootScope (that's bad, right? I'm ok with bad) and add it to my $http headers. I did that because I had trouble getting custom headers to work with $resource. That's most likely my fault, but this worked for now. You can read more about authenticating requests and StrongLoop at the docs: Making authenticated requests.
Listing entries is simple - I had to include the ordering argument in the controller code which also feels like a mistake (it should be in the service I think), but it worked well enough:
entryService.query({"filter[order]":"published desc"},function(res) {
$scope.entries = res;
}, function(e) {
console.log('bad '+JSON.stringify(e));
});
Finally - here's how I save a new entry. Note I automate the slug and published values.
$scope.doSave = function() {
var postedDate = new Date();
var newEntry = new entryService();
newEntry.title = $scope.entry.title;
newEntry.body = $scope.entry.body;
newEntry.released = true;
newEntry.published = new Date();
//not perfect...
newEntry.slug = newEntry.title.replace(/ /g,'-');
newEntry.$save();
$state.go('root.Home');
}
You can find the source code for my app here: https://github.com/cfjedimaster/StrongLoopDemos/tree/master/blog2/client/electron-quick-start. I also built a quick video showing the app in action. Enjoy!