Friday, May 21, 2010

Simple Google Web Toolkit Project with Spring Roo

After watching Google I/O 2010 Keynote (Day 1, pt. 9) from Youbube, I cannot wait to have a try on Spring-Roo 1.1.0M1 with GWT 2.1 Milestone 1. In this post, I will follow Ben's demonstration on Google I/O to create the same project using SpringSource tool suite with Google integration.

1. Setup development enviornment

Download SpringSource Tool Suite (STS). We need version 2.3.3.M1 which has Google integration.
Then use its extensions dashboard to install the Google Plugin for Eclipse:



2. Create Roo project

In STS, click "File->New->Roo Project",
Then "Next", "Finish".
You will see our "demo" project appears in Package Explorer after output some building information from Console. Now switch to "Roo Shell" tab, we are ready to type in some commands.


3. Develop our project using Roo.

It's time to start our Roo journey, type command in "Roo Shell":

setup persistence:
persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY
create Employee entity class with two properties:
entity --class ~.server.domain.Employee
field string --fieldName firstName
field string --fieldName lastName

add gwt support:
gwt setup
However, I occurs an error after this command,
"The project 'demo' does not have any GWT SDKs on its build path"
So I add GWT Library to this project manually:
right click on "demo" project, "Build Path"->"Add Libraries", choose "Google Web Toolkit".

let's roo do pack:
perform package

That's all, let's quit Roo:
quit

now open any command line tool you prefer, "cd" into the "demo" project directory,
mvn gwt:run


in the "GWT Development Mode" windows, click "Launch Default Browser":


Tuesday, May 18, 2010

Tracking change with google-diff-match-patch and jQuery

Change tracking is one of the most important features in collaborative editing tools, such as Microsoft Word, Wikipeda or Google Docs. In this tutorial, you will learn how to use google-diff-match-patch and jQuery to view difference between two versions and accept or reject change.

name of tut

Demo Page

Source Code


Introduction

First, let's have a look at the final example of our change tracking system.

web page

This application will compare new version text with old version and show result in the Diff Viewer. When you hover your mouse on the difference, deletion or insertion, the "Accept Change" context menu will appear. If you click it, the old version text will be modified according to the difference.

The diff operation is implemented via google-diff-match-patch library with a little hack. Change applied via jQuery DOM manipulation.


What is google-diff-match-patch

google-diff-match-patch is Diff, Match and Patch libraries for Plain Text. It provides:


The Diff Match and Patch libraries offer robust algorithms to perform the operations required for synchronizing plain text.

  1. Diff:
    • Compare two blocks of plain text and efficiently return a list of differences.
    • Diff Demo
  2. Match:
    • Given a search string, find its best fuzzy match in a block of plain text. Weighted for both accuracy and location.
    • Match Demo
  3. Patch:
    • Apply a list of patches onto plain text. Use best-effort to apply patch even when the underlying text doesn't match.
    • Patch Demo

Currently available in Java, JavaScript, C++, C#, Lua and Python. Regardless of language, each library features the same API and the same functionality. All versions also have comprehensive test harnesses.


In this tutorial, we will only use its Diff API and we won't dig into the algorithm but how to use this wonderful api to implement our own changes tracking editor.


Why not use patch library shipped with google-diff-match-patch?

The function I want to implement is similar to Microsoft Word's "Track Changes". The change is accepted one by one in any order. Unfortunately, we cannot use the patch library shipped with google-diff-match-patch. The reason is because patches in google-diff-match-patch are kind of package things, you can only apply all patches at one time. This is, if you want to change the second difference, the first one must be applied at first.

Therefore, there is no way to add ":)" without change "Hello" to "hi" at first. So we need another way to accept change one by one in any order.

Step 1 Make google-diff-match-patch generate prettier html code and ready to apply change

Let's play a little bit with the Diff Demo and try it with two simple texts:

Version 1. Hello, World!

Version 2. Hi, World:)

The html code generated is:

After checked the source code, we can know that difference can be INSERT, DELETE or EQUAL and the function we interested is diff_prettyHtml(). As you can see from the above output, this function generates html code for the diff result. The EQUAL texts will be placed in a <span> tag. And in HTML, there are <del> and <ins> tags perfectly to present delete and insert texts.

However, the html code is not pretty enough for us, so we need to provide another one, so called "prettierHtml()".

diff_match_patch.prototype.diff_prettierHtml = function(diffs) {
  var html = [];
  var i = 0;
  for (var x = 0; x < diffs.length; x++) {
    var op = diffs[x][0];    // Operation (insert, delete, equal)
    var data = diffs[x][1];  // Text of change.
    var text = data;
    switch (op) {
      case DIFF_INSERT:
        html[x] = '' + text + '';
        break;
      case DIFF_DELETE:
        html[x] = '' + text + '';
        break;
      case DIFF_EQUAL:
      html[x] = '' + text + '';
        break;
    }
    if (op !== DIFF_DELETE) {
      i += data.length;
    }
  }
  return html.join('');
};

This function is basically copied from diff_prettyHtml(). I made two changes:

1. Do not escape special character, such as new line, less than, or bigger than.

2. Remove dirty inline style and title for each difference (not necessary).

The modified code will generate following html code:


Step 2 Calculate differences

 This is done by google-diff-match-patch, but we would like to render result html code by our own function.

function calcDiffs() {

 var dmp = new diff_match_patch();
 var differences = dmp.diff_main($("#text1").val(), $("#text2").val());
 var ds = dmp.diff_prettierHtml(differences);

 $("#viewer").html(ds);

}

Step 3 Register Accept Change context menu

Thanks to google-diff-match-patch, all the modifications are placed either in <del> or <ins> tag, so we just binding mouseover event to all <del> and <ins> element. In addition, since those elements come and go frequently, we need to binding them with jQuery live() function.


function registerContextMenu(){
  $('del, ins').live('mouseover', function(event) {
  
   current= $(this);
   $(".highlight").removeClass("highlight");
   current.addClass("highlight");
  
      var top= event.pageY +5;
   $('#contextmenu_holder').css( {
    top :top + 'px',
    left : event.pageX + 'px'
   }).show();
 });
}

Step 4Accept Change



Now have a second look at the diff output html code, changes can be applied by its tag name. Continune with Hello World example, if we want to accept deletion of "ello", we just remove <del>ello</del>from DOM tree. Similarly, if the insertion can be implement via replace <ins> tag with <span>.

function registerAcceptChange(){
$('#accept).click(function () {

 if (current.is('del')) {

  current.remove();

 }
 if (current.is('ins')) {
  current.replaceWith('' + current.html() + '');
 }
 resetText();
 $('#contextmenu_holder').hide();
 return false;
});
}

Of course, the text version 1 need to be recalculated after any change applied. The calculation process is also interesting, we need to join text between <span> and <del> tag.

function resetText() {
 var fulltext = "";
 $("#viewer").children().each(function() {
  if (!$(this).is('ins')) {
   fulltext = fulltext + $(this).html();
  }
 });
 $("#text1").val(fulltext)
}

Step 5Pull all together

 This is done by google-diff-match-patch, but we would like to render result html code by our own function.

$(function() {
  calcDiffs();
  registerContextMenu();
  registerAcceptChange();
  //hide context menu when click anywhere
  $(document).click(function() {
   $('#contextmenu_holder').hide();
   $(".highlight").removeClass("highlight");
  });
  //caculate difference when text change
  $('textarea').change(function() {
     calcDiffs();
  });

});

Conclusion

I hope this tutorial explained clearly how to use google-diff-match-patch and jQuery to implement text change tracking system. The functions we implemented can be further extended with undo/redo.

At last, I want to thank all the open source community. With their excellent jobs, it is possible to implement complicated function with just a few lines of codes.