понедельник, 27 февраля 2012 г.

GWT обертка для JQuery Slider виджета

   С помощью JavaScript мы можем сделать очень красивые и интересные вещи, например, динамическое зумирование изображений, создание сложных анимаций без flash, разработка мобильных приложений и т.д. На GWT тоже можно сделать, но не всегда получается легко. В GWT проекте мы можем легко и безопасно использовать JQuery через JSNI
   JQuery - javascript библиотека, использование которой делает разработку javascript кода намного быстрее, проще и приятнее. Библиотека помогает легко получать доступ к любому элементу DOM, обращаться к атрибутам и содержимому элементов DOM и манипулировать ими. 
   Рассмотрим создание GWT обертки для JQuery Slider виджета, который имеет больше возможностей и легче настраивается чем любой коробочный ползунок в GWT. Для работы с JQuery в хостовую страницу следует добавить след. строки:
  <link media="all" type="text/css"
    href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.6/themes/base/jquery-ui.css" rel="stylesheet">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"
    type="text/javascript" charset="utf-8"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.6/jquery-ui.min.js"
    type="text/javascript" charset="utf-8"></script>


* This source code was highlighted with Source Code Highlighter.
  Выбор правильного базового класса является важной частью создания полнофункционального GWT компонента. JQuery UI Slider DIV элемент прикрепляется к странице, по этому GWT Slider обертка будет наследоваться от Widget.
public class Slider extends Widget {

    public enum Option {
        DISABLED("disabled"),
        ANIMATE("animate"),
        MAX("max"),
        MIN("min"),
        ORIENTATION("orientation"),
        RANGE("range"),
        STEP("step"),
        VALUE("value"),
        VALUES("values");
 
        private String sliderName;
 
        private Option(String name) {
            sliderName = name;
        }
 
        public String getName() {
            return sliderName;
        }
    }

    private JSONObject defaultOptions;
    private List<SliderListener> listeners = new ArrayList<SliderListener>();
 
     /**
     * Slider with the specified ID
     * @param id - id of the element to create
     * @param options - JSONObject of any possible option, can be null for defaults
     */
    public Slider(String id, JSONObject options) {
        super();
        Element div = DOM.createDiv();
        this.setElement(div);
        div.setId(id);
 
        this.defaultOptions = options;
        if (defaultOptions == null) {
            defaultOptions = getOptions(0, 100, new int[]{0});
        }
    }

    /**
     * Create an options JSONObject. Use SliderOption for keys.
     * @param min - default minimum of the slider
     * @param max - default maximum of the slider
     * @param defaultValues - default points of each anchor
     * @return a JSONObject of Slider options
     */
    public static JSONObject getOptions(int min, int max, int[] defaultValues) {
        JSONObject options = new JSONObject();
        options.put(Option.MIN.getName(), new JSONNumber(min));
        options.put(Option.MAX.getName(), new JSONNumber(max));
        JSONArray arr = convertToJSONArray(defaultValues);
        options.put(Option.VALUES.getName(), arr);
        return options;
    }
}


* This source code was highlighted with Source Code Highlighter.
  Создаем DIV элемент и устанавливаем в качестве элемента в конструкторе. В перечислении Option хранятся все параметры, которые можно задать для ползунка. Javadoc комментарии для каждого варианта скопированы из документации JQuery UI Slider. Статический метод getOptions() позволяет создать опции через создание JSONObject. 
     Теперь давайте свяжем GWT с JQuery через JSNI. Метод onLoad() является хорошим местом для связки JQuery после того, как GWT часть будет загружена.
@Override
protected void onLoad() {
    createSlider(this, getElement().getId(), defaultOptions.getJavaScriptObject());
    super.onLoad();
}

private native void createSlider(Slider slider, String id, JavaScriptObject options) /*-{
    options.start = function(event, ui) {
        slider.@com.dmitrynikol.slider.client.widget.Slider::fireOnStartEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/core/client/JsArrayInteger;)(event, ui.values);
    };
    options.slide = function(event, ui) {
        return slider.@com.dmitrynikol.slider.client.widget.Slider::fireOnSlideEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/core/client/JsArrayInteger;)(event, ui.values);
    };
    options.change = function(event, ui) {
        var hasChange = event.originalEvent ? true : false;
        slider.@com.dmitrynikol.slider.client.widget.Slider::fireOnChangeEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/core/client/JsArrayInteger;Z)(event, ui.values, hasChange);
    };
    options.stop = function(event, ui) {
        slider.@com.dmitrynikol.slider.client.widget.Slider::fireOnStopEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/core/client/JsArrayInteger;)(event, ui.values);
    };
    
    $wnd.$("#" + id).slider(options);
}-*/;


* This source code was highlighted with Source Code Highlighter.
    В дополнение к опциям, установленным в конструкторе, метод createSlider() также отображает события ползунка на соответствующие fireOnXEvent() методы, которые передают JavaScript значения в Java. Это является основой обработки событий. 
     Класс SliderEvent отвечает за все события ползунка, а интерфейс SliderListener слушает ползунок. Каждое событие fire-метода уведомляет всех слушателей и передает native событие.
    /*
     * Fire event methods
     */

    private void fireOnStartEvent(Event event, JsArrayInteger values) {
        int[] vals = convertToIntArray(values);
        SliderEvent sliderEvent = new SliderEvent(event, this, vals);
 
        for (SliderListener listener : listeners) {
            listener.onStart(sliderEvent);
        }
    }

    private boolean fireOnSlideEvent(Event event, JsArrayInteger values) {
        int[] vals = convertToIntArray(values);
        SliderEvent sliderEvent = new SliderEvent(event, this, vals);
 
        for (SliderListener listener : listeners) {
            listener.onStart(sliderEvent);
        }
 
        boolean answer = true;
        for (SliderListener listener : listeners) {
            if (!listener.onSlide(sliderEvent)) {
                // if some of the listeners returns false - return false
                // but let them all do their thing
                answer = false;
            }
        }
 
        return answer;
    }

    private void fireOnChangeEvent(Event event, JsArrayInteger values, boolean hasOriginalEvent) {
        int[] vals = convertToIntArray(values);
        SliderEvent sliderEvent = new SliderEvent(event, this, vals, hasOriginalEvent);
 
        for (SliderListener listener : listeners) {
            listener.onChange(sliderEvent);
        }
    }

    private void fireOnStopEvent(Event event, JsArrayInteger values) {
        int[] vals = convertToIntArray(values);
        SliderEvent sliderEvent = new SliderEvent(event, this, vals);
 
        for (SliderListener listener : listeners) {
            listener.onStop(sliderEvent);
        }
    }


* This source code was highlighted with Source Code Highlighter.
     После инициализации мы можем изменить параметры ползунка с помощью методов-оберток. Каждый из этих методов вызывает соответствующий метод JSNI, чтобы получить или установить опцию.
    /*
     * Wrappers for JSNI methods.
     * Methods calls corresponding JSNI methods to get/set some options
     */

    public void setIntOption(Option option, int value) {
        setIntOption(getElement().getId(), option.getName(), value);
    }

    public int getIntOption(Option option) {
        return getIntOption(getElement().getId(), option.getName());
    }

    public void setBooleanOption(Option option, boolean value) {
        setBooleanOption(getElement().getId(), option.getName(), value);
    }

    public boolean getBooleanOption(Option option) {
        return getBooleanOption(getElement().getId(), option.getName());
    }

    public void setStringOption(Option option, String value) {
        setStringOption(getElement().getId(), option.getName(), value);
    }

    public boolean getStringOption(Option option) {
        return getBooleanOption(getElement().getId(), option.getName());
    }

    /*
     * JSNI methods 
     */

    private native void setIntOption(String id, String option, int value) /*-{
        $wnd.$("#" + id).slider("option", option, value);
    }-*/;

    private native int getIntOption(String id, String option) /*-{
        return $wnd.$("#" + id).slider("option", option);
    }-*/;

    private native void setBooleanOption(String id, String option, boolean value) /*-{
        $wnd.$("#" + id).slider("option", option, value);
    }-*/;

    private native boolean getBooleanOption(String id, String option) /*-{
        return $wnd.$("#" + id).slider("option", option);
    }-*/;

    private native void setStringOption(String id, String option, String value) /*-{
        $wnd.$("#" + id).slider("option", option, value);
    }-*/;

    private native boolean getStringOption(String id, String option) /*-{
        return $wnd.$("#" + id).slider("option", option);
    }-*/;

    private native void setValues(String id, JavaScriptObject values) /*-{
        $wnd.$("#" + id).slider("option", "values", values);
    }-*/;

    private native int getValue(String id, int index) /*-{
        $wnd.$("#" + id).slider("values", index);
    }-*/;


* This source code was highlighted with Source Code Highlighter.
     Slider виджет будет убирать после себя через вызов destroy на ползунке в методе onUnload(), который вызывается немедленно перед тем, как виджет будет отделен от документа в браузере.
    @Override
    protected void onUnload() {
        destroySlider(getElement().getId());
        super.onUnload();
    }
    private native void destroySlider(String id) /*-{
        $wnd.$("#" + id).slider("destroy");
    }-*/;


* This source code was highlighted with Source Code Highlighter.
     Главный стартовый класс WrapperJQuerySlider:
public class WrapperJQuerySlider implements EntryPoint, SliderListener {

    private Slider defaultSlider;
    private Slider stepSlider;
    private Slider rangeSlider;

    private Label defaultLabel;
    private Label stepLabel;
    private Label rangeLabel;

    public void onModuleLoad() {
  
        defaultLabel = new Label("Default value: 0");
        defaultSlider = new Slider("slider");
        defaultSlider.addListener(this);
        RootPanel.get().add(defaultLabel);
        RootPanel.get().add(defaultSlider);
  
        stepLabel = new Label("Step value: 100");
        JSONObject options = Slider.getOptions(20, 180, new int[]{100});
        options.put(Option.STEP.getName(), new JSONNumber(20));
        stepSlider = new Slider(Option.STEP.getName(), options);
        stepSlider.addListener(this);
        RootPanel.get().add(stepLabel);
        RootPanel.get().add(stepSlider);
  
        rangeLabel = new Label("Range values: $50 - $80");
        options = Slider.getOptions(10, 200, new int[]{50, 80});
        options.put(Option.RANGE.toString(), JSONBoolean.getInstance(true)); 
        rangeSlider = new Slider(Option.RANGE.getName(), options);
        rangeSlider.addListener(this);
        RootPanel.get().add(rangeLabel);
        RootPanel.get().add(rangeSlider);
    }

    @Override
    public boolean onSlide(SliderEvent event) {
        Slider slider = event.getSlider();
        if (slider == this.defaultSlider) {
            defaultLabel.setText("Default value: " + event.getValues()[0]);
        } else if (slider == stepSlider) {
            stepLabel.setText("Step value: " + event.getValues()[0]);
        } else if (slider == rangeSlider) {
            rangeLabel.setText("Range values: $" + event.getValues()[0] + " - $" + event.getValues()[1]);
        }
  
        return true;
    }

    @Override
    public void onStart(SliderEvent event) {}

    @Override
    public void onChange(SliderEvent event) {}

    @Override
    public void onStop(SliderEvent event) {}
}


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