суббота, 7 января 2012 г.

GWT and HTML5 client-side storage

     В GWT 2.3 появилась возможность хранения данных на клиентской стороне(веб-хранилище). Хранилище позволяет веб приложению получить преимущества хранения при работе с HTML5 совместимым браузером. 
   В HTML5 спецификация хранения является стандартизированным способом предоставления большого количества хранения данных на клиентской стороне и адекватное разделение между хранением в сессии и в локальном постоянном хранилище. Также она обеспечивает хранение сгенерированных событий и обработку заинтересованными слушателями. 
    Без возможности HTML5 на клиентской стороне хранилище ограничено хранением предоставляемым через куки (cookie) (4Kb для куки, 20 куки для домена). Если используются куки, то они обеспечивают хранения как для сессии так и локально, при этом одновременно доступны во всех окнах и вкладках браузера. Куки отправляются с каждым запросом на этот домен, что влияет на пропускную способность. Также механизм обработки куки является громоздким. 
     HTML5 предоставляет локальное хранилище размером в 5MB на домен, неограниченное хранилище сессии и успешное разделение между хранением в сессии и локально. Хранение в сессии доступно только от его происходящих вкладок или окон, нет распределение между всеми окнами браузера и вкладками. Процесс доступа к сессии и локальному хранилищу является простым, состоящий из простого чтения или записи ключей-значений в строковом виде. 
     Преимущества, которые мы получаем при использовании локального хранилища
- уменьшение трафика сети 
- ускорение отображения 
- кэширования данных через RPC вызовы 
- загрузка кэшированных данных при старте 
- сохранение временного состояния 
- восстановление состояния при перевхождении 
- предотвращение потери работы при разрыве сети 
  HTML5 веб хранилище определяет два типа хранения ключ-значение: хранение в сессии(SessionStorage) и локально(LocalStorage). Основное отличие состоит в том, как долго хранятся данные и как используются совместно. 
     LocalStorage позволяет хранить 5Mb для приложения в браузере, пока пользователь не почистит кеш. Данные доступны между каждым окном и вкладками одного браузера.  
     SessionStorage позволяет хранить без ограничений, сохраняется только в оригинальном окне или вкладке. Доступ можно получить только в открытом окне или вкладке. 
Локальное хранилище создается для каждого браузера и доступно в любом его окне или вкладке. 
     Как хранятся события при работе с хранилищем? 
Когда данные добавляются, модифицируются или удаляются из хранилища(LocalStorage или SessionStorage) StorageEvent возбуждаются внутри текущей вкладки или окна браузера. Это событие содержит объект хранилища, в котором происходит событие URL документа, к которому относится это хранилище, старое и новое значение ключа, которое было изменено. Любой слушатель зарегистрированный на это событие может обработать его. 
     Рассмотрим работу с хранилищем на примере. 
Для получение объекта хранилища используются методы: Storage.getLocalStorageIfSupported() or Storage.getSessionStorageIfSupported() в зависимости от типа хранения, которое вы хотите получить. 
В примере используется след. операции при работе с хранилищем: 
- проверка поддержки браузером хранилища 
- получение объекта хранилища для браузера 
- запись, чтение, удаление данных хранилища 
- обработка событий хранилища 
Класс StorageActionHandler отвечает за возможность записи, чтения и удаления значений из хранилища. Класс ManageEventHandlers занимается обработкой событий. И класс StorageEventHandler выводит подробную информацию о событии в текстовую область. 
  Для вывода информации из хранилища используется виджет BooksWidget с таблицей, в которую вставляются данные ключ-значение.
/**
* @author Nikolaenko Dmitry
*/
public class BooksWidget<T extends Book> extends DialogBox {
    public static BooksWidgetUiBinder uiBinder = GWT.create(BooksWidgetUiBinder.class);

    public interface BooksWidgetUiBinder extends UiBinder<Widget, BooksWidget<?>> {}

    @UiField CellTable<T> bookTable;
    @UiField Anchor close;

    public BooksWidget() {
        setWidget(uiBinder.createAndBindUi(this));
        setText("Detective books");
    }

    public void setData(List<T> books) {
        bookTable.addColumn(new TextColumn<T>() {
            @Override
            public String getValue(T object) {
                return object.getId();
            }
        }, "Id");
        bookTable.addColumn(new TextColumn<T>() {
            @Override
            public String getValue(T object) {
                return object.getTitle();
            }
        }, "Title");

        bookTable.setRowCount(books.size(), true);
        bookTable.setRowData(0, books);
    }

    @UiHandler("close")
    void onCloseClicked(ClickEvent event) {
        hide();
    }
}


* This source code was highlighted with Source Code Highlighter.
     Главный класс WebStorageGwtApp, где и происходят все основные операции, он же является стартовой точкой GWT приложения:
/**
* @author Dmitry Nikolaenko
*/
public class WebStorageGwtApp implements EntryPoint {
 
    public enum WebStorage {
        LOCAL,
        SESSION;
    }
    public enum StorageAction {
        WRITE,
        READ,
        DELETE;
    }
    public enum StorageEvents {
        ADD,
        DELETE;
    }
 
    private static List<Book> books = new LinkedList<Book>();
 
    static {
        books.add(new Book("132529", "The Hound of the Baskervilles"));
        books.add(new Book("745353", "The Man in the Brown Suit"));
        books.add(new Book("248941", "Liberty Bar"));
        books.add(new Book("478134", "The Case of the Turning Tide"));
        books.add(new Book("328473", "The Dogs of War"));
    }
 
    private List<StorageEvent.Handler> handlers = new ArrayList<StorageEvent.Handler>();
    private TextArea eventArea;
    private Label handlersLabel;
 
    private Storage local;
    private Storage session;

    public void onModuleLoad() {
        FlowPanel main = new FlowPanel();
        main.getElement().getStyle().setPosition(Position.ABSOLUTE);

        eventArea = new TextArea();
        eventArea.setStyleName("eventArea");
        eventArea.setText("StorageEvent info: ");
        handlersLabel = new Label("#Handlers: 0");
     
        main.add(eventArea);
        main.add(handlersLabel);

        if (!Storage.isLocalStorageSupported() && !Storage.isSessionStorageSupported()) {
            Window.alert("Web Storage NOT supported in this browser!");
            return;
        }

        local = Storage.getLocalStorageIfSupported();
        session = Storage.getSessionStorageIfSupported();
     
        /** handling storage events */
        main.add(new Button("Add a Handler", new ManageEventHandlers(StorageEvents.ADD)));
        main.add(new Button("Delete a Handler", new ManageEventHandlers(StorageEvents.DELETE)));
     
        /** read action */
        main.add(new Button("Read data from Local Storage",
                new StorageActionHandler(WebStorage.LOCAL, StorageAction.READ)));
        main.add(new Button("Read data from Session Storage",
                new StorageActionHandler(WebStorage.SESSION, StorageAction.READ)));
     
        /** write action */
        main.add(new Button("Write data to Local Storage",
                new StorageActionHandler(WebStorage.LOCAL, StorageAction.WRITE)));
        main.add(new Button("Write data to Session Storage",
                new StorageActionHandler(WebStorage.SESSION, StorageAction.WRITE)));
     
        /** delete action */
        main.add(new Button("Delete data from Local Storage",
                new StorageActionHandler(WebStorage.LOCAL, StorageAction.DELETE)));
        main.add(new Button("Delete data from Session Storage",
                new StorageActionHandler(WebStorage.SESSION, StorageAction.DELETE)));
     
        RootPanel.get().add(main);
    }
 
    /**
     * handler to manage the storage
     */
    private class StorageActionHandler implements ClickHandler {
     
        private WebStorage webStorage;
        private StorageAction action;
        private Storage storage;
     
        private StorageActionHandler(WebStorage storage, StorageAction action) {
            this.webStorage = storage;
            this.action = action;
        }

        @Override
        public void onClick(ClickEvent event) {
            storage = webStorage.equals(WebStorage.LOCAL) ? local : session;
         
            if (action.equals(StorageAction.WRITE)) {
                for (Book key : books) {
                    storage.setItem(key.getId(), key.getTitle());
                }
            } else if (action.equals(StorageAction.READ)) {
                BooksWidget<Book> booksWidget = new BooksWidget<Book>();
             
                List<Book> storageBooks = new LinkedList<Book>();
                for (int i = 0; i < storage.getLength(); i++) {
                    storageBooks.add(new Book(storage.key(i), storage.getItem(storage.key(i))));
                }
                booksWidget.setData(storageBooks);
                booksWidget.show();
            } else {
                storage.clear();
            }
        }
    }
 
    /**
     * class that responsible for manage event handlers
     */
    private class ManageEventHandlers implements ClickHandler {
     
        private StorageEvents storageEvents;
     
        private ManageEventHandlers(StorageEvents event) {
            this.storageEvents = event;
        }

        @Override
        public void onClick(ClickEvent event) {
            if (storageEvents.equals(StorageEvents.ADD)) {
                StorageEvent.Handler handler = new StorageEventHandler(handlers.size() + 1);
                handlers.add(handler);
                Storage.addStorageEventHandler(handler);
            } else {
                if (handlers.size() > 0) {
                    StorageEvent.Handler handler = handlers.remove(handlers.size() - 1);
                    Storage.removeStorageEventHandler(handler);
                }
            }
            handlersLabel.setText("#Handlers: " + handlers.size());
        }
    }
 
    /**
     * class that responsible for update information in {@link eventArea} element
     */
    private class StorageEventHandler implements StorageEvent.Handler {
        private int number;

        private StorageEventHandler(int number) {
            this.number = number;
        }

        public void onStorageChange(StorageEvent event) {
            StringBuilder sb = new StringBuilder();
            sb.append(eventArea.getText());
            sb.append("\nStorageEvent: Handler=").append(number);
            sb.append(", key=").append(event.getKey());
            sb.append(", oldValue=").append(event.getOldValue());
            sb.append(", newValue=").append(event.getNewValue());
            sb.append(", url=").append(event.getUrl());
            sb.append(", timestamp=").append(new Date());
         
            eventArea.setText(sb.toString());
        }
    }
}


* This source code was highlighted with Source Code Highlighter.
Вот так выглядит работающие приложение:
Eclipse проект можно скачать по следующей ссылке.

1 комментарий:

  1. Хорошая статья. Здесь немного подробнее о лакольном хранилище. http://plutov.by/post/html5_local_storage

    ОтветитьУдалить