My favorites | Sign in
Project Home Wiki Source
READ-ONLY: This project has been archived. For more information see this post.
Search
for
GettingStarted  
Updated Sep 28, 2010 by denis.ha...@gmail.com

Jibe KickStart: A Simple Jibe Application

In this tutorial, we will show you an example of a Jibe application developed using Eclipse IDE. We will try to keep things simple but also introduce some of the practices which are used on the larger scale projects in order to keep the code well structured and complexity under control,

What are we going to build?

We will build simple TODO list application from scratch. After that we will show how easy it is to integrate built application with Content Manager application. Please be noted that this tutorial has been tested with Alfresco 3.3g.

Preparation

As a preparation for this tutorial, you should

The steps

We will complete the following steps:

  1. Show simple panel component in the viewport
  2. Register a backing method for the component
  3. Create and show custom grid component
  4. Fill the grid with data from the server
  5. Create actions definition and action bar
  6. Create event handlers
  7. Create server side API
  8. Create actions evaluation
  9. Include built application into existing Content Manager application
Let's get started.

Show simple panel component in the view-port

After both projects are deployed, start the server and navigate to http://localhost:8080/alfresco/service/jibe. After you successfully log in with your Alfresco credentials, you should see a default Jibe screen with JibeSource logo and a log out action.

Jibe framework has the feature that different deployed applications bootstrap themselves into this start up screen. In that case loading and navigation between applications is handled by framework itself. At the moment, we have not deployed any application and the application area is empty.

We will not use above mentioned feature, but we will rather replace it with our own definition. In order to acheive that, open the file /org.jibeframework.todolist/src/main/resources/config/viewport-config.xml, and paste the following snippet as a child of jibe-config element

<component-def id="core.viewPort" replace="true">
    <xtype>viewport</xtype>
    <layout>absolute</layout>
    <items>
	<component id="todoTasks">
              <xtype>panel</xtype>
	      <pageX>20</pageX>
	      <pageY>20</pageY>
	      <width>300</width>
	      <height>300</height>
	      <html>Hello from Jibe component</html>
	</component>
     </items>
</component-def>

Do not forget to deploy the application.

Configuration will be automatically reloaded without server restart. If you refresh the browser, initial page will be replaced with a simple panel.

Child elements correspond to ExtJs component configuration properties, with exception of component element. It is a special element which marks a component boundary.

You can experiment with this configuration by adding additional elements under component element. For example, you can add a title to the panel

<title>ToDo List</title>

Register a backing method for the component

It is possible to define a backing Java method for the component which can be used to alter component appearance.

Therefore, let's Java class which will contain this method.

package org.jibeframework.todolist;

import org.jibeframework.core.annotation.Controller;
import org.jibeframework.core.annotation.Component;
import org.jibeframework.core.ui.ParamSet;

@Controller("TodoListBean")
public class TodoListBean {
	@Component
	public void backingMethod(ParamSet params) {
	}
}

It is also required to register this Java class in the Spring configuration. Open /org.jibeframework.todolist/src/main/resources/context/beans-context.xml and add the following as a child of beans element

<bean class="org.jibeframework.todolist.TodoListBean" />

The next step will be to alter the component definition configuration and bind this method with component

<component-def id="core.viewPort" replace="true">
    <xtype>viewport</xtype>
    <layout>absolute</layout>
    <items>
	<component id="todoTasks" method="TodoListBean.backingMethod">
              <xtype>panel</xtype>
	      <pageX>20</pageX>
	      <pageY>20</pageY>
	      <width>300</width>
	      <height>300</height>
	      <html>Hello from Jibe component</html>
	</component>
     </items>
</component-def>

Within the method we have access to the enclosed elements of the component.

@Component
public void backingMethod(ParamSet params) {
    params.put("html", "Hello from backing method!!!");
}

If we reload the page now, we can see that inside of backing bean, different html property value has been injected.

Create and show custom grid component

When developing applications with ExtJs it is always a good practice to encapsulate as much as possible of configuration code into custom component class. Following this practice we will create a custom grid component.

Within the /org.jibeframework.todolist/src/main/resources/scripts folder you will find an empty MyScript.js file. Rename this file to TodoTasksGrid.js

Next thing to do is to register this JavaScript file. Open /org.jibeframework.todolist/src/main/resources/context/bootstrap-context.xml and find this config

<bean class="org.jibeframework.core.app.bootstrap.BootstrapJavaScript" init-method="init" depends-on="jibe.system.JavaScriptBootstrap">
        <property name="scripts">
		<list>
			<value>alfresco/module/org.jibeframework.todolist/scripts/MyScript.js</value>
		</list>
	</property>
</bean>

Replace MyScript.js with TodoTasksGrid.js and add the following code to the file

jibe.TodoTasksGrid = Ext.extend(Ext.grid.GridPanel, {
   constructor : function(config) {
     var overrides = {
	store : new Ext.data.JsonStore({
            root : 'results',
            id : 'taskId',
            fields : ['id', 'description', {name : 'completed',type : 'boolean'}]
        }),
	colModel : new Ext.grid.ColumnModel({
            columns : [{
		header : 'Description',
		dataIndex : 'description'
		}]}),
	view : new Ext.grid.GridView({
            forceFit : true,
            emptyText : 'No Tasks to display',
            getRowClass : function(r) {
		if (r.data.completed) {
                     return 'task-completed';
		}
                return '';
	   }})};
      Ext.apply(config, overrides);
      jibe.TodoTasksGrid.superclass.constructor.call(this, config);
    }
});

Ext.reg('jibe-tasks-grid', jibe.TodoTasksGrid);

Since we will apply task-completed css class for the task rows which are completed, we have to define this css class. For that purpose, create file todo_list.css inside of resources/css folder and add the following

.task-completed .x-grid3-cell-inner {
	text-decoration: line-through;
	color: gray;
}
.todoTasksIcon {
	background-image: url(../../../images/icons/reassign_task.gif)
		!important;
}

Next, replace configuration with

<component-def id="core.viewPort" replace="true">
	<xtype>viewport</xtype>
	<layout>absolute</layout>
	<items>
		<component id="todoTasks" method="TodoListBean.backingMethod">
			<xtype>jibe-tasks-grid</xtype>
                        <iconCls>todoTasksIcon</iconCls>
                        <title>ToDo list</title>
			<pageX>20</pageX>
			<pageY>20</pageY>
			<width>300</width>
			<height>300</height>
		</component>
	</items>
</component-def>

Fill the grid with data from the server

Next thing we would like to do is to fill the instantiated grid with some data from the server. For that purpose we will add a new operation to the operations queue.

package org.jibeframework.todolist;

import org.alfresco.util.GUID;
import org.jibeframework.core.Context;
import org.jibeframework.core.annotation.Controller;
import org.jibeframework.core.annotation.Component;
import org.jibeframework.core.data.operation.UpdateGridDataModelOperation;
import org.jibeframework.core.ui.params.ParamSet;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

@Controller("TodoListBean")
public class TodoListBean {
	@Component
	public void backingMethod(ParamSet params) {
		JsonArray model = new JsonArray();
		model.add(createTask("Do something"));
		model.add(createTask("Do something else"));
		Context context = Context.getCurrentContext();
		context.getResponse().add(new UpdateGridDataModelOperation("todoTasks", model));
	}

	private JsonObject createTask(String description) {
		JsonObject task = new JsonObject();
		task.addProperty("id", GUID.generate());
		task.addProperty("completed", false);
		task.addProperty("description", description);
		return task;
	}
}

Create actions definition and action bar

Next this we would like to do is add a toolbar at the bottom of our component. The toolbar should contain a textfield which will serve to add new task and a button which will remove completed tasks

First we will define the button. Open the viewport-config.xml file and add the following xml.

<actions module="todo">
	<action-group id="tasks">
		<action id="removeCompleted"/>
	</action-group>
</actions>

Here we defined an action removeCompleted in the todo.tasks namespace.

Next, we will define the icon and the text for the button. Open the /org.jibeframework.todolist_example/src/main/resources/messages/bundle.properties file and add the following

jibe.action.todo.tasks.removeCompleted=
jibe.action.todo.tasks.removeCompleted.description=Remove completed tasks
jibe.action.todo.tasks.removeCompleted.icon=jibe/cog_delete.png

You can notice the convention used.

Next, we will define the action bar. In the viewport-config.xml add the following

<action-bar id="todoTasksGrid.toolbar">
      <component id="addTaskField">
	   <xtype>text-field</xtype>
	   <width>230</width>
	   <emptyText>Add a new task</emptyText>
      </component>
      <spacer />
      <action-ref id="todo.tasks.removeCompleted" />
</action-bar>

You can see that we added a component, a spacer and a reference to the action created before.

In the end we will attach our action-bar as a bottom toolbar of our component. For that purpose we will change our component configuration and add an additional element.

<component-def id="core.viewPort" replace="true">
	<xtype>viewport</xtype>
	<layout>absolute</layout>
	<items>
		<component id="todoTasks" method="TodoListBean.backingMethod">
			<xtype>jibe-tasks-grid</xtype>
                        <iconCls>todoTasksIcon</iconCls>
                        <title>ToDo list</title>
			<pageX>20</pageX>
			<pageY>20</pageY>
			<width>300</width>
			<height>300</height>
                        <bbar handler="core.toolbar">
			     <action-bar>todoTasksGrid.toolbar</action-bar>
			</bbar>
		</component>
	</items>
</component-def>

You might want to check the method org.jibeframework.core.app.controller.ParamHandlers.provideToolbar(BaseParam) and see how toolbar json is provided.

If you refresh the page now, you should see the toolbar attached to the bottom. The toolbar is not fully functional since you will get a javascript error if you click on the button and nothing will happen if you hit the enter key when in text field. Those issues will be fixed in the next chapter.

Handling events

At the begining we are going to do is to attach a keypressed event listener to text field.

In order to change existing component behavior (ie add listeners) , there are two options. You can either extend the component or create the plugin. For the tasks grid, we have opted to extend the component while for the text field we will create plugin. You can read more about those two approaches in the ExtJs community manual

Before we start, an additional js file called Plugins.js should be created and registered in scripts folder. By now, you should be familiar with JavaScript file registration process, but here is a reminder.

<bean class="org.jibeframework.core.app.bootstrap.BootstrapJavaScript" init-method="init" depends-on="jibe.system.JavaScriptBootstrap">
   <property name="scripts">
    <list>
      <value>alfresco/module/org.jibeframework.todolist/scripts/TodoTasksGrid.js</value>
      <value>alfresco/module/org.jibeframework.todolist/scripts/Plugins.js</value>
    </list>
  </property>
</bean>

After the file has been registered, add the following code snippet to it

Ext.namespace('jibe.plugins');
jibe.plugins.AttachEnterKeyListener = {
   init : function(textfield) {
      textfield.enableKeyEvents = true;  
      textfield.addListener('keypress', function(textfield, e) { 
        if (e.getKey() == 13) {
	   jibe.EventManager.fireEvent(this, 'enterpress', this.getValue()); 
        }
      }, textfield);
   }
};

The code itself is pretty simple, a key press listener is bound to a text field and if key code is 13 (enter key) a specific 'enterpress' event will be fired. More details about Jibe specific event handling mechanism can be found in the events handling section

Now we will register our newly created plugin with addTaskField text field. For that purpose change the action-bar configuration slightly by adding the plugins element

<action-bar id="todoTasksGrid.toolbar">
      <component id="addTaskField">
	   <xtype>text-field</xtype>
	   <width>230</width>
	   <emptyText>Add a new task</emptyText>
           <plugins>jibe.plugins.AttachEnterKeyListener</plugins>
      </component>
      <spacer />
      <action-ref id="todo.tasks.removeCompleted" />
</action-bar>

Next thing to do is to create and register Handlers.js file in scripts folder. This is where we will store our event handlers.

Add the following code to this file. This is the handler function for the enterpress event published from the addTaskField component.

jibe.EventHandler.addTaskField = function() {
    return {
	enterpress : function(value) {
            console.log(value); //remove this if not using FireBug
	}
    }
}();

You can see the convention for the event handler function jibe.EventHandler.<componentId>.<eventId>.

Next, we will add a function which will be executed when button on the toolbar is clicked. For that purpose, there is convention existing. jibe.ActionHandler.<action-module>.<action-group>.<action-id> is the function that will be invoked when action defined in xml is clicked. Following this convention, add the following code to Handlers.js

jibe.ActionHandler.todo = function() {
    return {
	tasks : {
           removeCompleted : function() {
		console.log('remove completed clicked')
	   }
	}
   }
}();

The final event we would like to handle in our small application is double click on the task row within the tasks grid. That action will toggle completion status of the task being clicked. In order to achieve this, first we have to change TasksGrid.js a bit to add a listener. Add the listener property to the overrides object. That way, overrides will become

var overrides = {
        listeners : {
	    rowdblclick : function(grid, rowIndex) {
	      jibe.EventManager.fireEvent(grid, 'taskDblClicked', grid.store.getAt(rowIndex));
	    }
	},
	store : new Ext.data.JsonStore({
            root : 'results',
            id : 'taskId',
            fields : ['id', 'description', {name : 'completed',type : 'boolean'}]
        }),
	colModel : new Ext.grid.ColumnModel({
            columns : [{
		header : 'Description',
		dataIndex : 'description'
		}]}),
	view : new Ext.grid.GridView({
            forceFit : true,
            emptyText : 'No Tasks to display',
            getRowClass : function(r) {
		if (r.data.completed) {
                     return 'task-completed';
		}
                return '';
	   }})};

From the code above, we can see that for the rowdblclick event, we are publishing application specific taskDblClicked event with task record being clicked as a parameter. The handler for this event will be placed inside of Handlers.js and will follow already familiar convention

jibe.EventHandler.todoTasks = function() {
   return {
      taskDblClicked : function(task) {
	console.log(task);
      }
   }
}();

Having completed this step we created handler functions for the events of our interest. These functions are still empty and in the next step we will create server side API which will be invoked from our handlers.

Create server side API

Looking at the requirements for our application we can recognize that our API should have three methods:

  1. Add the task
  2. Toggle task completion
  3. Remove completed tasks

Open org.jibeframework.todolist.TodoListBean, add three empty methods and make some changes in private utility methods which will allow us to store our todo list in the user perference in Alfresco.

package org.jibeframework.todolist;

import org.alfresco.util.GUID;
import org.jibeframework.core.Context;
import org.jibeframework.core.annotation.Component;
import org.jibeframework.core.annotation.Controller;
import org.jibeframework.core.annotation.Remote;
import org.jibeframework.core.data.node.UserNode;
import org.jibeframework.core.data.operation.UpdateGridDataModelOperation;
import org.jibeframework.core.ui.param.ParamSet;
import org.jibeframework.core.util.GSONUtil;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

@Controller("TodoListBean")
public class TodoListBean {
   @Remote
   public void addTask(Context context) {
      System.out.println("TodoListBean.addTask()");
   }

   @Remote
   public void removeCompletedTasks(Context context) {
      System.out.println("TodoListBean.removeCompletedTasks()");
   }

   @Remote
   public void toggleTask(Context context) {
      System.out.println("TodoListBean.toggleTask()");
   }

   @Component
   public void backingMethod(ParamSet params) {
      Context context = Context.getCurrentContext();
      context.getResponse().add(new UpdateGridDataModelOperation("todoTasks", getModel()));
   }

   private JsonArray getModel() {
      String modelJson = (String) UserNode.createForCurrentUser().getFromPreferences("app:todoList");
      if (modelJson == null) {
	  modelJson = "[]";
      }
      JsonArray model = GSONUtil.toJsonTree(modelJson).getAsJsonArray();
      return model;
   }

   private JsonObject createTask(String taskDescription) {
      JsonObject task = new JsonObject();
      task.addProperty("taskId", GUID.generate());
      task.addProperty("taskCompleted", false);
      task.addProperty("taskDescription", taskDescription);
      return task;
   }

   private void saveModel(JsonArray model) {
      UserNode.createForCurrentUser().saveToPreferences("app:todoList", model.toString());
   }
}

As you can see, newly added public methods are annotated with @Remote annotation which exposes it for remote invocation from the client. In order to invoke new methods change the Handlers.js accordingly.

jibe.EventHandler.addTaskField = function() {
   return {
      enterpress : function(value) {
	 jibe.RemoteMethod.invoke('TodoListBean.addTask');
      }
   }
}();

jibe.ActionHandler.todo = function() {
   return {
      tasks : {
	removeCompleted : function() {
	   jibe.RemoteMethod.invoke('TodoListBean.removeCompletedTasks');
	}
      }
    }
}();
jibe.EventHandler.todoTasks = function() {
    return {
	taskDblClicked : function(task) {
            jibe.RemoteMethod.invoke('TodoListBean.toggleTask');
	}
    }
}();

You can recognize the pattern that methods are identified with value of @Controller class annotation and method name.

After starting the server, we will be able to see that methods are invoked in response to actions on the client by looking at the console output.

Let's now make the full implementation of add task requirement. First, we will have to change the handler for the addTaskField so that value of text field will be sent to the server in http request

jibe.EventHandler.addTaskField = function() {
   return {
      enterpress : function(value) {
	 jibe.RemoteMethod.invoke('TodoListBean.addTask',{
                                      taskDesc:value
                                   });
      }
   }
}();

After that, we have to change addTask remote method

@Remote
public void addTask(Context context) {
    String taskDesc = context.getRequest().getParam("taskDesc");
    JsonArray model = getModel();
    model.add(createTask(taskDesc));
    saveModel(model);
    context.getResponse().add(new UpdateGridDataModelOperation("todoTasks", model));
}

The only thing which should be unfamiliar is the first line where parameter is pulled out of http request.

In case you did everything correctly, you should be able to add the task in to the grid and it will be persisted in the user preferences.

Now, we will implement remove tasks requirement. Change the handler first

jibe.EventHandler.todoTasks = function() {
   return {
       taskDblClicked : function(task) {
	  jibe.RemoteMethod.invoke('TodoListBean.toggleTask', {
					taskId : task.data.id
				});
	}
   }
}();

and than remote method

@Remote
public void toggleTask(Context context) {
   String taskId = context.getRequest().getParam("taskId");
   JsonArray model = getModel();
   for (JsonElement el : model) {
      JsonObject task = el.getAsJsonObject();
      if (task.get("id").getAsString().equals(taskId)) {
	  boolean taskCompleted = task.get("completed").getAsBoolean();
	  task.addProperty("completed", !taskCompleted);
      }
   }
   saveModel(model);
   context.getResponse().add(new UpdateGridDataModelOperation("todoTasks", model));
}

For the remove completed tasks, we do not have to change the handler, only removeCompletedTasks have to be adjusted

@Remote
public void removeCompletedTasks(Context context) {
   JsonArray filtered = new JsonArray();
   for (JsonElement task : getModel()) {
      if (!task.getAsJsonObject().get("completed").getAsBoolean()) {
	 filtered.add(task);
      }
   }
   saveModel(filtered);
   context.getResponse().add(new UpdateGridDataModelOperation("todoTasks", filtered));
}

Create actions evaluation

The final requirement we would like to fulfill is to manage the visual status of remove completed tasks action. That is, we would like the action to be disabled if there is no completed tasks to remove.

We will start by adding action evaluator to the TodoListBean

@ActionEvaluator
public EvaluationResult evaluateRemoveCompletedTasks() {
   boolean hasCompleted = false;
   for (JsonElement task : getModel()) {
      if (task.getAsJsonObject().get("completed").getAsBoolean()) {
	hasCompleted = true;
	break;
      }
   }
   return new EvaluationResult(false, !hasCompleted);
}

and actions configuration has to be slightly changed

<actions module="todo">
   <action-group id="tasks">
      <action id="removeCompleted">
	  <eval trigger="TodoListBean\.\w*" method="TodoListBean.evaluateRemoveCompletedTasks" />
      </action>
   </action-group>
</actions>

With this step, we have completed our application.

Include built application into existing Content Manager application

The last step will be to include our application into Content Manager. To do that, we will slightly change the configuration.

<items-def id="repo.sidebarBottom">
    <component id="todoTasks" method="TodoListBean.backingMethod">
        <xtype>jibe-tasks-grid</xtype>
	<title>Todo list</title>
	<iconCls>todoTasksIcon</iconCls>
	<bbar handler="core.toolbar">
	    <action-bar>todoTasksGrid.toolbar</action-bar>
	</bbar>
    </component>
</items-def>
Powered by Google Project Hosting