March 29, 2013

A DIV and AngularJS Based Dropdown List Box

Introduction

I have been doing research into using AngularJS.  One of the first things I needed for my website was some nice fancy dropdown list boxes.  Of course I did not want to use regular old <select>, what would be the fun in that.  So I set out to build one using DIVs and AngularJS.  This little demo shows the results of my research.

Download Source Code: div-and-angularjs-dropdown-lists.zip

Demo

Below the demo is running in an IFRAME. It may not display the best. If you want you can visit the full page demo at http://ferris.sourceforge.net/div-and-angularjs-dropdown-lists

March 11, 2013

JAXB Unmarshalling To a Sorted Map

Introduction

While using JAXB for my last project, I came across the need to access a list of data in two ways.  First, the data was to be accessed as a sorted list with the sort order being determined by the /Book[@SortOrder] attribute. Second, the data was to be access as a map, supplying a key value from /Book[@Id] to get a specific book by key. With these two requirements, a LinkedHashMap object is clearly the way to go. Book objects are added to the LinkedHashMap in /Book[@SortOrder] order and /Book[@Id] is the map key. My initial thought was to let JAXB unmarshal the XML into a regular List then as a post processing step I would reorder the list and create a map.  But the more I thought about this the more I didn't like this solution.  Since JAXB is doing the work of unmarshalling the XML, I figured JAXB should be able to put it into a LinkedHashMap like I wanted. The purpose of this article is to demonstrate how to get JAXB to create a LinkedHashMap for you so you can access a list of data in both by key and as a correctly sorted list.

The XML

 Let's first consider a simple XML document to demonstrate what's needed.  Below is sample XML document representing search results for books. The list of books has a [@SortOrder] and an [@Id].  The application will need to be able to display the books in the correct sort order but also be able to lookup books quickly by id.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<SearchResults>
    <Books>
        <Book Id="794N-98" SortOrder="5">
            <Title>Open to the Sky</Title>
        </Book>
        <Book Id="998X-78" SortOrder="2">
            <Title>Mars, Friend or Foe?</Title>
        </Book>
        <Book Id="445M-09" SortOrder="3">
            <Title>Jumping Beans Jump</Title>
        </Book
        <Book Id="002I-11" SortOrder="1">
            <Title>Little Worms Go To School</Title>
        </Book>
        <Book Id="951G-42" SortOrder="4">
            <Title>America, The Early Years</Title>
        </Book>
    </Books>
</SearchResults>


The Classes

In order to get JAXB to create a LinkedHashMap for you automatically, you will need need the following classes, with the BooksAdapter class one really doing all of the work for you:

SearchResults.java

Unmarshalled object for the <SearchResults> tag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@XmlRootElement(name="SearchResults")
public class SearchResults
{
    @XmlElement(name="Books")
    @XmlJavaTypeAdapter(BooksAdapter.class)
    private Map<String, Book> books;
    public Map<String, Book> getBooksMap() {
        return books;
    }
     
    public List<Book> getBooksList() {
        return new ArrayList<Book>(books.values());
    }
}

Books.java

Unmarshalled object for the <Books> tag. This is initially needed by JAXB to convert all of the <Book>  elements into a list.  This class is then later used by BooksAdapter to convert it into a LinkedHashMap.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@XmlRootElement(name="Books")
public class Books
{
    private List<Book> books;
     
    @XmlElement(name="Book")
    public List<Book> getBooks() {
        return books;
    }
     
    public void setBooks(List<Book> books) {
        this.books = books;
    }
}

Book.java

Unmarshalled object for the <Book> tag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@XmlRootElement(name="Book")
public class Book
{
    @XmlAttribute(name="SortOrder")
    private Integer sortOrder;
    public Integer getSortOrder() {
        return sortOrder;
    }
 
     
    @XmlAttribute(name="Id")
    private String id;
    public String getId() {
        return id;
    }
     
    @XmlElement(name="Title")
    private String title;
    public String getTitle() {
        return title;
    }
}

BooksAdapter.java

An XmlAdapter implementation which converts the Books object into a LinkedHashMap object where the Book objects are put into the map by key /Book[@Id] and in the order specified by /Book[@SortOrder]. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class BooksAdapter extends XmlAdapter<Books, Map<String, Book>> {
     
    @Override
    public Map<String, Book> unmarshal(Books books) throws Exception {
        Collections.sort(books.getBooks(), new BookComparatorBySortOrder());
        Map<String, Book> map = new LinkedHashMap<String,Book>();
        for (Book book : books.getBooks()) {
            map.put(book.getId(), book);
        }
        return map;
    }
 
    @Override
    public Books marshal(Map<String, Book> map) throws Exception {
        Books books = new Books();
        books.setBooks(new LinkedList<Book>(map.values()));
        return books;
    }
}
 
class BookComparatorBySortOrder implements Comparator<Book> {
    @Override
    public int compare(Book o1, Book o2) {
        return o1.getSortOrder().compareTo(o2.getSortOrder());
    }
     
}

 

Testing

Testing this is pretty easy, just supply the XML document and have JAXB do it's thing. Here is a unit test:

SearchResultsTest.java 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class SearchResultsTest
{
    private SearchResults searchResults;
     
    @Before
    public void before() throws Exception
    {
        // Read file from classpath
        String filename = "/SearchResults.xml";
        InputStream istream = getClass().getResourceAsStream(filename);
        assertNotNull(String.format("Cannot find classpath resource \"%s\"", filename), istream);
         
        // Prepare JAXB objects
        JAXBContext jc = JAXBContext.newInstance(SearchResults.class);
        Unmarshaller u = jc.createUnmarshaller();
 
        // Prepare the input
        InputSource isource = new InputSource(istream);
 
        // Do unmarshalling
        searchResults = (SearchResults)u.unmarshal(isource);
         
        assertNotNull(String.format("Unmarshal returned null for SearchResults object"), searchResults);
         
        istream.close();
        istream = null;
    }
     
    @Test
    public void testBookFindById()
    {
         
        Map<String, Book> books = searchResults.getBooksMap();
        assertNotNull(books);
        assertEquals(5, books.size());
         
        assertEquals("Little Worms Go To School",books.get("002I-11").getTitle());
        assertEquals("Mars, Friend or Foe?",     books.get("998X-78").getTitle());
        assertEquals("Jumpping Beans Jump",      books.get("445M-09").getTitle());
        assertEquals("America, The Early Years", books.get("951G-42").getTitle());
        assertEquals("Open to the Sky",          books.get("794N-98").getTitle()); 
    }
     
    @Test
    public void testBookSorted()
    {
         
        List<Book> books = searchResults.getBooksList();
        assertNotNull(books);
        assertEquals(5, books.size());
         
        assertEquals(new Integer(1), books.get(0).getSortOrder());
        assertEquals(new Integer(2), books.get(1).getSortOrder());
        assertEquals(new Integer(3), books.get(2).getSortOrder());
        assertEquals(new Integer(4), books.get(3).getSortOrder());
        assertEquals(new Integer(5), books.get(4).getSortOrder());     
    }
 
}

Download

You can download a Maven project of this from sourceforge:

http://ferris.cvs.sourceforge.net/viewvc/ferris/ferris-jaxb-sortedmap/