Psych Desktop Documentation

This guide will instruct you on how to use psych desktop, everything from installation to usage to administration.

You may also want to check out the wiki for user contributed documentation.

Installation

Step 1: make a checkout from the SVN

To check out a copy from the SVN, use an SVN client such as rapidSVN

Step 2: Upload files to server

Use your favorite FTP client to upload all the files to an installation directory, such as /desktop/ for example.
You also need to make sure you have the pear module 'MDB2'. If it is unavalable, psych desktop will attempt to use the version of MDB2 that ships with it.

Step 3: CHMOD files/folders

You will need to chmod the following files/folders to 777 in order to get the desktop working

Step 4: Create mySQL database and user

Create a mysql database and grant a user all user permissions to the database.

Step 5: Run installer

point your browser to /install.php and follow the directions on the page.

Step 6: Place login page on site

You can point your browser to /desktop/ or whatever your installation dir is, but we’ve included a way for you to put a desktop login box onto your page. Simpily create a div element where you want your login box. Give it an ID of ‘psychdesktop_login’ You can apply CSS formatting to this div. For example:

<div style="border: 1px solid black; background: #eeeeee; width: 280px;" id="psychdesktop_login"></div>

Then, include this script just before the </body> tag, replacing path/to/desktop with your installation dir. It is important that you do not move login.js from your installation dir, this will break the code.

<script type="text/javascript" src="/path/to/desktop/login.js"></script>

Using the desktop

Ok, so when we log in we are greeted with the desktop. You have your apps menu on the bottom left hand corner, and a taskbar hider on the bottom right hand corner, as well as a clock, and a task tray.

Let's run our first app. Go to Apps > Accessories > Calculator. This runs the calculator app. You can resize the window, you can maximize it, minimize it, or close it, just like you would on your operating system.

TODOC: walk through each core app and give a quick demo of it's features, maybe add in screenshots...

Developing Apps

This part of the handbook will go through how to develop apps. You should login to your desktop installation and run the "Katana IDE" application (under the "Development" category)

Introduction to apps

The application system of Psych Desktop

Psych Desktop's application system consists of fetching javascript code from the server, and running it. The application framework consists of many classes that make common tasks easy. No server side code is required to be authored by the developer.

The desktop's framework consists of a private namespace that only core applications and the desktop iteslf should touch, called 'desktop', and a public namespace that both the desktop and apps written for the desktop can use, called 'api'. APIs provide UI elements such as windows, and interactions with the server, such as the filesystem.

In the desktop each app is a constructor which defines a few methods. When the app is loaded, the constructor goes into an array of all the apps on the desktop. When new instances of that app are created, we use the constructor in that array to create those instances. When the constructor is generated, we add a few properties to the app, but core methods will only appear in the api namespace.

Psych Desktop is built using the Dojo Toolkit. In apps you have full access to Dojo 1.0.x (this includes the three namespaces; dojo, dojox and dijit)

Magic methods

In apps, you can define methods, properties, etc. There are a few "magic" methods that apps can have so the desktop knows how to do certain tasks.

  • init - This is called when the new instance is constructed and the app is executed. It accepts a single argument that is an object, and this is used for if the user wants to open a certain file, URL, etc.
  • kill - This is called when the app is being forced to quit. Here you can do things like unhooking events, removing UI elements, etc. If your app has a main window, and you want to kill the app when that main window is closed, you can hook the kill method onto the window's onDestroy event so the application isn't wasting resources.
  • debug - This helps us debug the application by defining what to do when there's an error. In this situation it prints the error onto the console.

Core properties of an app

Properties of the app let the desktop know what is happening with the desktop. You should not change any properties other then the ones you define in your app. A few of the properties tell the desktop what the app is doing, but most of the properties you will be using in your code are for giving APIs your id or instance id. You can use the instance ID to generate unique IDs for html elements, although it is heavily discouraged.

  • id - the app's ID.
  • instance - the instance id of the app.
  • status - The status of the app. You do not directly set this, it is set by the instance API.

Example

Let's take a look at an example app to get a feel for how this comes together:

this.init = function(args)
     {
          api.instances.setActive(this.instance);
          //call a function
          this.func("hello world!, I'm app #"+this.id+"!");
          this.kill();
     }
     this.kill = function()
     {
          //some cleanup stuff maybe
          //tell psychdesktop application is dead
          api.instances.setKilled(this.instance);
     }
     this.func = function(text)
     {
          //this is a function you defined in your library.
          api.ui.alertDialog(text);
     }
     this.debug = function(e)
     {
          api.console(e);
     }

Here's the constructor that the desktop generated from the app we just wrote:

function() {
     this.id= 2; //This is the Application ID from the database.
     this.status = "unknown"; //The status will be unknown when first created, so you MUST set it active onLoad. On an error, this will change to "error" and this.debug(err) will be called.
     this.instance = 4; //The desktop sets this randomly, the first app being instance id 1, second being id 2 and so on...
     this.init = function(args)
     {
          api.instances.setActive(this.instance);
          //call a function
          this.func("hello world!, I'm app #"+this.id+"!");
          this.kill();
     }
     this.kill = function()
     {
          //some cleanup stuff maybe
          //tell psychdesktop application is dead
          api.instances.setKilled(this.instance);
     }
     this.func = function(text)
     {
          //this is a function you defined in your library.
          api.ui.alert(text);
     }
     this.debug = function(e)
     {
          api.console(e);
     }
}

Where applications are stored

Cache

Once an application has been downloaded from the server, it is stored to the desktop.app.apps[id], meaning future instances of this application will be copied from the application cache. If your a developer, and need to see changes you made, clear the cache, you can do the following in the Psych Desktop terminal:

desktop.app.apps = []

This will clear the application cache.

Instances

Instances are stored in desktop.app.instances[instance]. Do NOT clear this, even if you have no applications running, as it can mess with the desktop.

Making UI

In this section we'll learn the best way to make a UI, and some of the wrongs less experienced javascript programmers may run into.

What you will be using

UI in the desktop is handled by two things, Dojo Toolkit's widget system (aka dijit), and Psych Desktop's API widgets, which are basically dojo widgets themselves.

What you won't be using

We strongly discourage you use html elements directly in your code. Why?

  • Dojo widgets are integrated with Psych Desktop's theme system, so some html elements will stick out like a sore thumb
  • Dojo widgets have many more features then standard elements
  • If you refer to elements using ID, it will cause your app more harm then good if another app decides to use that id, or the next version has an element with that ID. Even if you use the app's instance in the ID, it's strongly discouraged.

There are some cases where you may use html elements, which we will get into later in the page.

Your first windowed app

So, now let's create a simple app that shows a window.

this.init = function(args)
{
     this.win = new api.window({
          title: "Hello, world!",
          height: "200px",
          width: "300px"
     });
     dojo.connect(this.win, "onDestroy", this, this.kill);
     this.win2 = new api.window({
          title: "Subwindow",
          height: "100px",
          width: "200px"
     });
     this.win.show();
     this.win2.show();
     api.instances.setActive(this.instance);
}
this.kill = function()
{
     if(!this.win.destroyed) this.win.destroy();
     if(!this.win2.destroyed) this.win2.destroy();
     api.instances.setKilled(this.instance);
}

So this app basically makes two windows, and when the main window is closed the other closes too. We set the onDestroy method to this.kill because it doesn't make sense to make a seperate function that destroys windows when we can re-use the same function the desktop uses when the app is killed. This is all good, but I think we should have something in the window.

Using dojo widgets in windows

Now let's take our app from before, get rid of the second window, and add some widgets to the main window.

this.init = function(args)
{
     //we must use dojo.require to get any widgets we use in our app
     dojo.require("dijit.layout.LayoutContainer");
     dojo.require("dijit.Toolbar");
     dojo.require("dijit.layout.ContentPane");
     this.win = new api.window({
          title: "Hello, world!",
          height: "200px",
          width: "300px",
          bodyWidget: "LayoutContainer"
     });
     dojo.connect(this.win, "onDestroy", this, this.kill);
     //make our widgets
     this.toolbar = new dijit.Toolbar({layoutAlign: "top"});
     this.mainArea = new dijit.layout.ContentPane({layoutAlign: "client"}, document.createElement("div"));
     this.win.addChild(this.toolbar);
     this.win.addChild(this.mainArea);
     //ok, add some buttons to the toolbar
     dojo.forEach([
          {
               label: "Button 1",
               onClick: dojo.hitch(this, this.doSomething),
               iconClass: "icon-16-actions-format-justify-left"
          },
          {
               label: "Button 2",
               onClick: dojo.hitch(this, this.doSomethingElse),
               iconClass: "icon-16-actions-document-properties"
          }
     ], dojo.hitch(this, function(args) {
          var p = new dijit.form.Button(args);
          this.toolbar.addChild(p);
     });
     this.mainArea.setContent("Hello, world!");
     this.win.show();
     api.instances.setActive(this.instance);
}
this.kill = function()
{
     if(!this.win.destroyed) this.win.destroy();
     api.instances.setKilled(this.instance);
}
this.doSomething = function(e)
{
     api.ui.alertDialog("I did something!");
}
this.doSomethingElse = function(e)
{
     api.ui.alertDialog("I did something else!");
}

If you know how to use dojo, you can pick up on what we're doing here. The only thing you need to know is that there's a css file for each theme with icon classes, and these follow the Tango Standard Icon Naming Specification.

For those of you who don't, here's an explanation:
First we changed the window's body widget to a LayoutContainer. The body widget of a window can be anything in the dijit.layout object.
Then, we make two widgets, a toolbar and a Content Pane. We set their layoutAlign property to "top" and "client", so the toolbar is displayed on top while the ContentPane is displayed in the center.
Then we use dojo.forEach to make buttons on the toolbar. We provide the arguments for each button, and a function that makes each button and adds it to the toolbar.
Lastly we set the content of the contentpane and show the window.

You may be wondering "what's dojo.hitch?". Good question. If we just give out any function, it will run that function in the wrong namespace, so using this.* won't work. So why didn't we do this when we used dojo.connect? because dojo.connect asks for the namespace to run it in. If you try to use dojo.hitch, it will throw an error.

Using a textarea in a window

Sometimes there won't be a dojo widget avalable for a certain app, like the textarea in the application IDE. In this case you should make it using DOM as opposed to using setContent and a string in a ContentPane, then using dojo.byId to get it's value.

this.codearea = document.createElement("textarea");
dojo.style(this.codearea, "width", "100%");
dojo.style(this.codearea, "height", "100%");
dojo.style(this.codearea, "border", "0px solid none");
this.pane = new dijit.layout.ContentPane({}, new document.createElement("div"));
this.pane.setContent(this.codearea);

There are other ways of accessing elements through dom, so you can use innerHTML, just avoid using IDs. If anything you can use dojo.query to get elements too.

this.pane = new dijit.layout.ContentPane({}, new document.createElement("div"));
this.pane.setContent("");
this.codearea = dojo.query("textarea", this.pane.domNode)[0];

Packing the application for the end-user

The way Psych Desktop installs new apps is through App Packages.

First, you need to put some metadata into a file called 'appmeta.xml'. This defines some things like the name of the app, it's version, etc.

<?xml version="1.0" encoding="ISO-8859-1"?>
<appMeta>
<name>Checkers</name>
<author>Psychcf</author>
<email>will@psychdesigns.net</email>
<version>1.0</version>
<maturity>Alpha</maturity>
<category>Games</category>
<installFile>app.js</installFile>
<filesRequired>false</filesRequired>
<installMessage>Checkers is being installed...</installMessage>
<installedMessage>...done</installedMessage>
</appMeta>

The next step is to put the actual app into a javascript file called "app.js", just as it looks when you open it in the IDE:

this.init = function(args) {
     // ...
}

Lastly, if your app requires any additional files such as images, you would put those into a directory called 'files'. In most cases you can skip over this.

Compress all those files up in a zip archive. Make sure all the files you made are in the root of the package, and not a subdirectory. Then change the extension to '.appPackage' and you're done!

Porting a dojo 1.0 app to Psych Desktop

If you have written an app using dojo 1.0, you can easily port it to Psych Desktop. Here's what you need to do:

  1. Translate all dijit markup to programmatic dijit.
  2. Add the root widgets to a window (api.window). If you're using a LayoutContainer for the root widget, just add each child element to the window.
  3. Replace certain things with the desktop's APIs (sound, dialogs, etc.)
  4. Change all XHR paths to ../apps/[appname]/, move all custom backends into /apps/[appname]/
  5. [Optional] Use psych desktop's models system to create custom models to use in your app, and create their corresponding database tables.
  6. [Optional] Use Psych Desktop's registry system for storage on a per-user basis.

Making Themes

The desktop's theme system is based on CSS. Making your own theme is just a matter of making the artwork, splitting the images up, and changing the CSS values around.

Start by copying a core theme. Then, replace the images in the /images/ dir with your images. Change the values in the CSS files until each image is sized and spaced correctly.

Next thing you want to do is find a set of icons that fit your theme. If the icons use the Tango Standard Icon Naming Specification, using it is just a matter of copying and pasting it into the icons dir. If not, you must modify icons.css so that each class has a corresponding icon.

Lastly, take the whole dir with the images, css, and icons, and put it in your /desktop/themes/ folder. rename it to your theme's name, and you're theme should be ready to use.

Under the hood

In this part of the manual we'll go through the inner workings of Psych Desktop, and cover how to make modifications to it.

Bootstrap

Psych Desktop is layed out in a modular fashion. We do not use dojo's package system to load all of our modules, although we define certain modules using dojo's packaging system. We use our own loader that uses dojo's script transport system. Each module is required in the bootstrap.load function, and once all of the modules have loaded into the browser, the bootstrap code begins calling functions for each module. The first function it calls is the 'draw' function. This is for drawing DOM elements and dojo widgets. Then bootstrap will call the init function of each module. This is for setting up the module, subscribing to events, setting timeouts/intervals, etc.

The idea is that we should have as little content in the actual page for the desktop as possible. There is no server side scripting on the index page so we can stay independent from one specific server side language.

Subscriptions

using dojo.subscribe, we can hook various events onto the desktop. Here's a list of some that we use:

  • configApply - Called once the configuration has been applied. See "Config" for more info.
  • desktoplogout - Called when the desktop is being logged out of

Config

Configuration is saved in desktop.config. This is a generic json-style object that has two methods; save() and load(). (guess what they are)...

You can reset the config to default values by calling bootstrap.require("desktop.config") and then desktop.config.save().