AbstractDialog

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.wicket.jquery.ui.widget.dialog;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;

import com.googlecode.wicket.jquery.core.IJQueryWidget;
import com.googlecode.wicket.jquery.core.JQueryBehavior;
import com.googlecode.wicket.jquery.core.JQueryPanel;
import com.googlecode.wicket.jquery.core.Options;
import com.googlecode.wicket.jquery.core.ajax.IJQueryAjaxAware;
import com.googlecode.wicket.jquery.ui.interaction.behavior.DisplayNoneBehavior;
import com.googlecode.wicket.jquery.ui.widget.dialog.DialogBehavior.ButtonAjaxBehavior;

/**
 * Base class for implementing jQuery dialogs
 * @author Sebastien Briquet - sebfz1
 *
 * @param <T> the type of the model object
 */
public abstract class AbstractDialog<T extends Serializable> extends JQueryPanel implements IDialogListener
{
    private static final long serialVersionUID = 1L;

    /* Default Button labels */
    public static final String LBL_OK = "OK";
    public static final String LBL_NO = "No";
    public static final String LBL_YES = "Yes";
    public static final String LBL_CLOSE = "Close";
    public static final String LBL_CANCEL = "Cancel";
    public static final String LBL_SUBMIT = "Submit";

    /** Default width */
    private static final int WIDTH = 450;

    private IModel<String> title;
    private boolean modal;
    private DialogBehavior widgetBehavior;


    /** Default button */
    private final DialogButton btnOk = new DialogButton(LBL_OK);

    /**
     * Constructor
     * @param id the markupId, an html div suffice to host a dialog.
     * @param title the title of the dialog
     */
    public AbstractDialog(String id, String title)
    {
        this(id, title, null, true);
    }

    /**
     * Constructor
     * @param id the markupId, an html div suffice to host a dialog.
     * @param title the title of the dialog
     */
    public AbstractDialog(String id, IModel<String> title)
    {
        this(id, title, null, true);
    }

    /**
     * Constructor
     * @param id the markupId, an html div suffice to host a dialog.
     * @param title the title of the dialog
     * @param model the model to be used in the dialog.
     */
    public AbstractDialog(String id, String title, IModel<T> model)
    {
        this(id, title, model, true);
    }

    /**
     * Constructor
     * @param id the markupId, an html div suffice to host a dialog.
     * @param title the title of the dialog
     * @param model the model to be used in the dialog.
     */
    public AbstractDialog(String id, IModel<String> title, IModel<T> model)
    {
        this(id, title, model, true);
    }

    /**
     * Constructor
     * @param id the markupId, an html div suffice to host a dialog.
     * @param title the title of the dialog
     * @param modal indicates whether the dialog is modal
     */
    public AbstractDialog(String id, String title, boolean modal)
    {
        this(id, title, null, modal);
    }

    /**
     * Constructor
     * @param id the markupId, an html div suffice to host a dialog.
     * @param title the title of the dialog
     * @param modal indicates whether the dialog is modal
     */
    public AbstractDialog(String id, IModel<String> title, boolean modal)
    {
        this(id, title, null, modal);
    }

    /**
     * Constructor
     * @param id markupId, an html div suffice to host a dialog.
     * @param title the title of the dialog
     * @param modal indicates whether the dialog is modal
     * @param model the model to be used in the dialog
     */
    public AbstractDialog(String id, String title, IModel<T> model, boolean modal)
    {
        this(id, Model.of(title), model, modal);
    }

    /**
     * Constructor
     * @param id markupId, an html div suffice to host a dialog.
     * @param title the title of the dialog
     * @param modal indicates whether the dialog is modal
     * @param model the model to be used in the dialog
     */
    public AbstractDialog(String id, IModel<String> title, IModel<T> model, boolean modal)
    {
        super(id, model);

        this.title = title;
        this.modal = modal;

        this.add(new DisplayNoneBehavior()); //enhancement, fixes issue #22
        this.setOutputMarkupPlaceholderTag(true);
    }


    // Events //

    @Override
    protected void onInitialize()
    {
        super.onInitialize();

        this.add(this.widgetBehavior = this.newWidgetBehavior(JQueryWidget.getSelector(this))); //warning: ButtonAjaxBehavior(s) should be set at this point!
    }


    @Override
    public void onConfigure(JQueryBehavior behavior)
    {
        // class options //
        behavior.setOption("autoOpen", false);
        behavior.setOption("title", Options.asString(this.title.getObject()));
        behavior.setOption("modal", this.modal);
        behavior.setOption("resizable", this.isResizable());
        behavior.setOption("width", this.getWidth());
    }

    /**
     * Triggered when the dialog opens
     * @param target the {@link AjaxRequestTarget}
     */
    protected void onOpen(AjaxRequestTarget target)
    {
    }

    /**
     * Triggered when a button is clicked.
     * This method may be overridden to handle button behaviors, but the dialog will not been closed until <code>super.onClick(event)</code> or {@link #close(AjaxRequestTarget, DialogButton)} is called.
     */
    @Override
    public void onClick(AjaxRequestTarget target, DialogButton button)
    {
        this.close(target, button);
    }

    /**
     * Internal onClick method, fired by the behavior<br/>
     * The purpose of this method is to prevent the behavior calling {@link #onClick(AjaxRequestTarget, DialogButton)} directly, as <code>onClick</code> is implemented by default
     *
     * @param target the {@link AjaxRequestTarget}
     * @param button the {@link DialogButton}
     */
    void internalOnClick(AjaxRequestTarget target, DialogButton button)
    {
        this.onClick(target, button);
    }

    // Properties //
    /**
     * Gets the dialog's buttons.<br/>
     * It is allowed to return a predefined list (ie: DialogButtons#OK_CANCEL#toList()) as long as the buttons state (enable and/or visible) are not modified<br/>
     * <b>Warning: </b>It is not legal to create the buttons to be returned in this method.
     * @return {@link #btnOk} by default
     */
    protected List<DialogButton> getButtons()
    {
        return Arrays.asList(this.btnOk);
    }

    /**
     * Gets the dialog's with
     * @return {@link #WIDTH} by default
     */
    public int getWidth()
    {
        return AbstractDialog.WIDTH;
    }

    /**
     * Gets the dialog's title
     * @return the dialog's title
     */
    public IModel<String> getTitle()
    {
        return this.title;
    }

    /**
     * Sets the dialog's title
     * @param title the dialog's title
     */
    public void setTitle(IModel<String> title)
    {
        if (title == null)
        {
            throw new IllegalArgumentException("argument title must be not null");
        }

        this.title = title;
    }

    /**
     * Gets the modal flag
     * @return the modal flag supplied to the constructor by default
     */
    public boolean isModal()
    {
        return this.modal;
    }

    /**
     * Indicated whether the dialog is resizable
     * @return false by default
     */
    public boolean isResizable()
    {
        return false;
    }

    @Override
    public boolean isDefaultCloseEventEnabled()
    {
        return false;
    }

    /**
     * Gets the model
     * @return the parameterized model
     */
    @SuppressWarnings("unchecked")
    public IModel<T> getModel()
    {
        return (IModel<T>)this.getDefaultModel();
    }

    /**
     * Gets the model object
     * @return the typed model object
     */
    @SuppressWarnings("unchecked")
    public T getModelObject()
    {
        return (T)this.getDefaultModelObject();
    }

    /**
     * Sets the model object
     * @param object the typed model object
     */
    public void setModelObject(T object)
    {
        this.setDefaultModelObject(object);
    }

    // Methods //
    /**
     * Finds a {@link DialogButton} - identified by its text - within the list of buttons returned by {@link #getButtons()}
     * @param text the button's text
     * @return the {@link DialogButton} if found, null otherwise
     */
    public DialogButton findButton(String text)
    {
        for (DialogButton button : this.getButtons())
        {
            if (button != null && button.equals(text))
            {
                return button;
            }
        }

        return null;
    }

    /**
     * Opens the dialogs in ajax.
     *
     * @param target the {@link AjaxRequestTarget}
     */
    public final void open(AjaxRequestTarget target)
    {
        this.onOpen(target);

        if (this.widgetBehavior != null)
        {
            this.widgetBehavior.open(target);
        }
    }

    /**
     * Closes the dialogs in ajax.
     *
     * @param target the {@link AjaxRequestTarget}
     * @param button the button that closes the dialog
     */
    public final void close(AjaxRequestTarget target, DialogButton button)
    {
        if (this.widgetBehavior != null)
        {
            this.widgetBehavior.close(target);
        }

        this.onClose(target, button);
    }


    // IJQueryWidget //
    /**
     * @see IJQueryWidget#newWidgetBehavior(String)
     */
    @Override
    public DialogBehavior newWidgetBehavior(String selector)
    {
        return new DialogBehavior(selector) {

            private static final long serialVersionUID = 1L;

            @Override
            public boolean isDefaultCloseEventEnabled()
            {
                return AbstractDialog.this.isDefaultCloseEventEnabled();
            }

            @Override
            protected List<DialogButton> getButtons()
            {
                return AbstractDialog.this.getButtons();
            }

            @Override
            public void onClick(AjaxRequestTarget target, DialogButton button)
            {
                AbstractDialog.this.internalOnClick(target, button);
            }

            @Override
            public void onClose(AjaxRequestTarget target, DialogButton button)
            {
                AbstractDialog.this.onClose(target, button);
            }

            @Override
            protected ButtonAjaxBehavior newButtonAjaxBehavior(IJQueryAjaxAware source, DialogButton button)
            {
                return AbstractDialog.this.newButtonAjaxBehavior(source, button);
            }
        };
    }


    /**
     * Gets a new {@link ButtonAjaxBehavior} that will be called by the corresponding {@link DialogButton}.<br/>
     * This method mays be overridden internally to provide another behavior
     *
     * @param source the {@link IJQueryAjaxAware} source
     * @param button the button that is passed to the behavior so it can be retrieved via the <code>DialogBehavior#ClickEvent</code>
     * @return the {@link ButtonAjaxBehavior}
     */
    protected ButtonAjaxBehavior newButtonAjaxBehavior(IJQueryAjaxAware source, DialogButton button)
    {
        return new ButtonAjaxBehavior(source, button);
    }
}


/**
 * Event listener shared by the {@link AbstractDialog} widget and the {@link DialogBehavior}
 *
 * @author Sebastien Briquet - sebfz1
 *
 */
interface IDialogListener
{
    /**
     * Indicates whether the default close event (the click on the X-icon) is enabled
     * If true, the {@link #onClose(AjaxRequestTarget, DialogButton)} event will be triggered, with a null {@link DialogButton}
     *
     * @return false by default
     */
    boolean isDefaultCloseEventEnabled();

    /**
     * Triggered when a button is clicked.
     * This method may be overridden to handle button behaviors, but the dialog will not been closed until <code>super.onClick(event)</code> or {@link DialogBehavior#close(AjaxRequestTarget)} is called.
     * @param target the {@link AjaxRequestTarget}
     * @param button the button that closed the dialog
     */
    void onClick(AjaxRequestTarget target, DialogButton button);

    /**
     * Triggered when the dialog closes.
     * @param target the {@link AjaxRequestTarget}
     * @param button the button that closed the dialog
     */
    void onClose(AjaxRequestTarget target, DialogButton button);
}

public abstract class AbstractFormDialog<T extends Serializable> extends AbstractDialog<T>
{
    private static final long serialVersionUID = 1L;

    /**
     * Constructor
     * @param id the markup id
     * @param title the dialog's title
     */
    public AbstractFormDialog(String id, String title)
    {
        super(id, title);
    }

    /**
     * Constructor
     * @param id the markup id
     * @param title the dialog's title
     */
    public AbstractFormDialog(String id, IModel<String> title)
    {
        super(id, title);
    }

    /**
     * Constructor
     * @param id the markup id
     * @param title the dialog's title
     * @param model the dialog's model
     */
    public AbstractFormDialog(String id, String title, IModel<T> model)
    {
        super(id, title, model, true);
    }

    /**
     * Constructor
     * @param id the markup id
     * @param title the dialog's title
     * @param model the dialog's model
     */
    public AbstractFormDialog(String id, IModel<String> title, IModel<T> model)
    {
        super(id, title, model, true);
    }

    /**
     * Constructor
     * @param id the markup id
     * @param title the dialog's title
     * @param modal indicates whether the dialog is modal
     */
    public AbstractFormDialog(String id, String title, boolean modal)
    {
        super(id, title, modal);
    }

    /**
     * Constructor
     * @param id the markup id
     * @param title the dialog's title
     * @param modal indicates whether the dialog is modal
     */
    public AbstractFormDialog(String id, IModel<String> title, boolean modal)
    {
        super(id, title, modal);
    }

    /**
     * Constructor
     * @param id the markup id
     * @param title the dialog's title
     * @param model the dialog's model
     * @param modal indicates whether the dialog is modal
     */
    public AbstractFormDialog(String id, String title, IModel<T> model, boolean modal)
    {
        super(id, title, model, modal);
    }

    /**
     * Constructor
     * @param id the markup id
     * @param title the dialog's title
     * @param model the dialog's model
     * @param modal indicates whether the dialog is modal
     */
    public AbstractFormDialog(String id, IModel<String> title, IModel<T> model, boolean modal)
    {
        super(id, title, model, modal);
    }


    // Properties //
    /**
     * Gets the button that is in charge to submit the form.<br/>
     * It should be in the list of buttons returned by {@link #getButtons()}
     * @return the submit button
     */
    protected abstract DialogButton getSubmitButton();


    /**
     * Returns whether form should be processed the default way. When false (default is true), all
     * validation and form updating is bypassed and the onSubmit method of that button is called
     * directly, and the onSubmit method of the parent form is not called. A common use for this is
     * to create a cancel button.
     *
     * @return defaultFormProcessing
     */
    public boolean getDefaultFormProcessing()
    {
        return true; //default
    }

    /**
     * Gets the form to be validated by this dialog.<br/>
     * Warning, the onSubmit and the onError are being delegated to this dialog. However, it does not prevent the use of Form#onSubmit nor Form#onError
     * @return the form
     */
    public abstract Form<?> getForm();

    /**
     * Returns the form associated to the button.<br/>
     * It means that it will return the form if the button is the submit button and null otherwise.
     * The callback script will differ depending on this.
     *
     * @param button the dialog's button
     * @return the {@link Form} or <code>null</code>
     */
    protected Form<?> getForm(DialogButton button)
    {
        if (button.equals(this.getSubmitButton()))
        {
            return this.getForm();
        }

        return null;
    }


    // Events //
    @Override
    void internalOnClick(AjaxRequestTarget target, DialogButton button)
    {
        final Form<?> form = this.getForm(button);

        if (form != null)
        {
            form.process(new DialogFormSubmitter(target));

            if (!form.hasError())
            {
                this.onClick(target, button); //fires onClick (& closes the dialog by default)
            }
        }
        else
        {
            this.onClick(target, button); //fires onClick (& closes the dialog by default)
        }
    }

    @Override
    public void onClose(AjaxRequestTarget target, DialogButton button)
    {
        //not mandatory to override
    }

    /**
     * Triggered after {@link Form#onError()} (when the form processing has error(s))
     * @param target
     */
    protected abstract void onError(AjaxRequestTarget target);

    /**
     * Triggered after {@link Form#onSubmit()} (the form has been submitted and it does not have error)
     * @param target
     */
    protected abstract void onSubmit(AjaxRequestTarget target);


    // Factories //
    /**
     * Gets the {@link FormButtonAjaxBehavior} associated to the specified button.
     *
     * @return the {@link ButtonAjaxBehavior}
     */
    @Override
    protected ButtonAjaxBehavior newButtonAjaxBehavior(IJQueryAjaxAware source, DialogButton button)
    {
        return new FormButtonAjaxBehavior(source, button, this.getForm(button));
    }


    /**
     * Provides the form-dialog {@link IFormSubmitter}<br/>
     * This is basically the same technic used in AjaxButton class.
     */
    protected class DialogFormSubmitter implements IFormSubmitter
    {
        private final AjaxRequestTarget target;

        /**
         * Constructor
         * @param target the {@link AjaxRequestTarget}
         */
        public DialogFormSubmitter(AjaxRequestTarget target)
        {
            this.target = target;
        }

        @Override
        public Form<?> getForm()
        {
            return AbstractFormDialog.this.getForm();
        }

        @Override
        public boolean getDefaultFormProcessing()
        {
            return AbstractFormDialog.this.getDefaultFormProcessing();
        }

        @Override
        public void onSubmit()
        {
            AbstractFormDialog.this.onSubmit(this.target);
        }

        @Override
        public void onError()
        {
            AbstractFormDialog.this.onError(this.target);
        }

        @Override
        public void onAfterSubmit()
        {
            //wicket6
        }
    }

    /**
     * Provides the button's form-submit behavior
     */
    protected static class FormButtonAjaxBehavior extends ButtonAjaxBehavior
    {
        private static final long serialVersionUID = 1L;

        private final Form<?> form;

        /**
         * Constructor
         * @param source the {@link IJQueryAjaxAware}
         * @param button the {@link DialogButton}
         * @param form the {@link Form}
         */
        public FormButtonAjaxBehavior(IJQueryAjaxAware source, DialogButton button, Form<?> form)
        {
            super(source, button);

            this.form = form;
        }

        /**
         * The formId may intentionally be null
         */
        @Override
        protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
        {
            super.updateAjaxAttributes(attributes);

            if (this.form != null)
            {
                attributes.setMethod(Method.POST);
                attributes.setFormId(this.form.getMarkupId());
            }
        }
    }
}


AbstractDialog 依赖DialogBehavior

public abstract class DialogBehavior extends JQueryBehavior implements IJQueryAjaxAware, IDialogListener
{
    private static final long serialVersionUID = 1L;
    private static final String METHOD = "dialog";

    private JQueryAjaxBehavior onDefaultClose = null;

    /**
     * Constructor
     *
     * @param selector the html selector (ie: "#myId")
     */
    public DialogBehavior(String selector)
    {
        super(selector, METHOD);
    }

    /**
     * Constructor
     *
     * @param selector the html selector (ie: "#myId")
     * @param options the {@link Options}
     */
    public DialogBehavior(String selector, Options options)
    {
        super(selector, METHOD, options);
    }

    // Properties //
    /**
     * Gets the dialog's buttons.<br/>
     *
     * @return the {@link List} of {@link Button}
     */
    protected abstract List<DialogButton> getButtons();

    // Methods //
    @Override
    public void bind(Component component)
    {
        super.bind(component);

        for (DialogButton button : this.getButtons())
        {
            component.add(this.newButtonAjaxBehavior(this, button));
        }

        if (this.isDefaultCloseEventEnabled())
        {
            component.add(this.onDefaultClose = this.newDefaultCloseBehavior());
        }
    }

    /**
     * Opens the dialogs in ajax.<br/>
     * @param target the {@link AjaxRequestTarget}
     */
    public void open(AjaxRequestTarget target)
    {
        target.appendJavaScript(this.$("'open'"));
    }

    /**
     * Closes the dialogs in ajax.<br/>
     * @param target the {@link AjaxRequestTarget}
     */
    public void close(AjaxRequestTarget target)
    {
        target.appendJavaScript(this.$("'close'"));
    }

    // Events //
    @Override
    public void onConfigure(Component component)
    {
        super.onConfigure(component);

        if (this.onDefaultClose != null)
        {
            this.setOption("close", this.onDefaultClose.getCallbackFunction());
        }

        // buttons events //
        StringBuffer buttons = new StringBuffer("[ ");

        int index = 0;
        for(ButtonAjaxBehavior behavior : component.getBehaviors(ButtonAjaxBehavior.class))
        {
            DialogButton button = behavior.getButton();

            if (index++ > 0) { buttons.append(", "); }
            buttons.append("{");
            buttons.append("'id': '").append(button.getMarkupId()).append("', ");
            buttons.append("'text': '").append(button.toString()).append("', ");
            if (!button.isEnabled()) { buttons.append("'disabled': true, "); }
            if (button.getIcon() != null) { buttons.append("icons: { primary: '").append(button.getIcon()).append("' }, "); }
            buttons.append("'click': function() { ").append(behavior.getCallbackScript()).append(" }");
            buttons.append("}");
        }

        buttons.append(" ]");

        this.setOption("buttons", buttons);
    }

    @Override
    public void onAjax(AjaxRequestTarget target, JQueryEvent event)
    {
        if (event instanceof ClickEvent)
        {
            this.onClick(target, ((ClickEvent) event).getButton());
        }

        else if (event instanceof CloseEvent)
        {
            this.onClose(target, null);
        }
    }

    // Factories //
    /**
     * Gets a new ButtonAjaxBehavior that will be called by the corresponding dialog's button.<br/>
     * This method mays be overridden internally to provide another behavior;
     *
     * @param source the {@link IJQueryAjaxAware} source
     * @param button the button that is passed to the behavior so it can be retrieved via the {@link ClickEvent}
     * @return the {@link ButtonAjaxBehavior}
     */
    protected abstract ButtonAjaxBehavior newButtonAjaxBehavior(IJQueryAjaxAware source, DialogButton button);

    /**
     * Gets the ajax behavior that will be triggered when the user clicks on the X-icon
     *
     * @return the {@link JQueryAjaxBehavior}
     */
    protected JQueryAjaxBehavior newDefaultCloseBehavior()
    {
        return new JQueryAjaxBehavior(this) {

            private static final long serialVersionUID = 1L;

            @Override
            public String getCallbackFunction()
            {
                return "function(event, ui) { if (event.button == 0) { " + this.getCallbackScript() + " } }";
            }

            @Override
            protected JQueryEvent newEvent()
            {
                return new CloseEvent();
            }
        };
    }


    // Ajax behaviors //
    /**
     * Provides the {@link JQueryAjaxBehavior} being called by the button(s).
     */
    protected static class ButtonAjaxBehavior extends JQueryAjaxBehavior
    {
        private static final long serialVersionUID = 1L;

        private final DialogButton button;

        /**
         * Constructor
         * @param source the {@link IJQueryAjaxAware}
         * @param button the {@link DialogButton} to attach to the {@link ClickEvent}
         */
        public ButtonAjaxBehavior(IJQueryAjaxAware source, DialogButton button)
        {
            super(source);

            this.button = button;
        }

        /**
         * Gets the {@link DialogButton}
         * @return the {@link DialogButton}
         */
        public DialogButton getButton()
        {
            return this.button;
        }

        @Override
        protected JQueryEvent newEvent()
        {
            return new ClickEvent(this.button);
        }
    }


    // Events classes //
    /**
     * Provides a dialog event that will be transmitted to the {@link AbstractDialog}
     */
    protected static class ClickEvent extends JQueryEvent
    {
        private final DialogButton button;

        public ClickEvent(DialogButton button)
        {
            super();

            this.button = button;
        }

        /**
         * Get the button that has been attached to this event object
         * @return the button
         */
        public DialogButton getButton()
        {
            return this.button;
        }
    }

    /**
     * An event object that will be broadcasted when the user clicks on the X-icon
     */
    protected static class CloseEvent extends JQueryEvent
    {
    }
}

JqueryEvent

public class JQueryEvent
{
    /**
     * Constructor.
     */
    public JQueryEvent()
    {
    }
}

JQueryAjaxBehavior

public abstract class JQueryAjaxBehavior extends AbstractDefaultAjaxBehavior
{
    private static final long serialVersionUID = 1L;

    private final IJQueryAjaxAware source;
    private final Duration duration;


    /**
     * Constructor
     * @param source {@link Behavior} to which the event - returned by {@link #newEvent()} - will be broadcasted.
     */
    public JQueryAjaxBehavior(IJQueryAjaxAware source)
    {
        this(source, Duration.NONE);
    }

    /**
     * Constructor
     * @param source {@link Behavior} to which the event - returned by {@link #newEvent()} - will be broadcasted.
     * @param duration {@link Duration}. If different than {@link Duration#NONE}, an {@link ThrottlingSettings} will be added with the specified {@link Duration}.
     */
    public JQueryAjaxBehavior(IJQueryAjaxAware source, Duration duration)
    {
        this.source = source;
        this.duration = duration;
    }

    @Override
    protected void respond(AjaxRequestTarget target)
    {
        if (this.source != null)
        {
            this.source.onAjax(target, this.newEvent());
        }
    }

    /**
     * Gets the {@link JQueryEvent} to be broadcasted to the {@link IJQueryAjaxAware} source when the behavior will respond
     * @return the {@link JQueryEvent}
     */
    protected abstract JQueryEvent newEvent();


    // wicket 6.x //
    @Override
    protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
    {
        super.updateAjaxAttributes(attributes);

        if (this.duration != Duration.NONE)
        {
            attributes.setThrottlingSettings(new ThrottlingSettings("jquery-throttle", this.duration));
        }
    }

    /**
     * Gets the {@link CallbackParameter}<code>s</code> that *may* be passed to {@link #getCallbackFunction(CallbackParameter...)}<br/>
     * This is a convenience method that allows to define {@link CallbackParameter}<code>s</code> before the invocation of {@link #getCallbackFunction(CallbackParameter...)}.
     *
     * @return an array of {@link CallbackParameter}
     * @see #getCallbackFunction()
     */
    protected CallbackParameter[] getCallbackParameters()
    {
        return new CallbackParameter[] {};
    }

    /**
     * Calls {@link #getCallbackFunction(CallbackParameter...)} by passing {@link CallbackParameter}<code>s</code> from {@link #getCallbackParameters()}
     *
     * @return the javascript function.
     */
    public String getCallbackFunction()
    {
        return super.getCallbackFunction(this.getCallbackParameters()).toString();
    }

}

public abstract class AbstractDefaultAjaxBehavior extends AbstractAjaxBehavior
{

    private static final long serialVersionUID = 1L;

    /** reference to the default indicator gif file. */
    public static final ResourceReference INDICATOR = new PackageResourceReference(
        AbstractDefaultAjaxBehavior.class, "indicator.gif");

    private static final String DYNAMIC_PARAMETER_FUNCTION_TEMPLATE = "function(attrs){%s}";
    private static final String PRECONDITION_FUNCTION_TEMPLATE = "function(attrs){%s}";
    private static final String COMPLETE_HANDLER_FUNCTION_TEMPLATE = "function(attrs, jqXHR, textStatus){%s}";
    private static final String FAILURE_HANDLER_FUNCTION_TEMPLATE = "function(attrs, jqXHR, errorMessage, textStatus){%s}";
    private static final String SUCCESS_HANDLER_FUNCTION_TEMPLATE = "function(attrs, jqXHR, data, textStatus){%s}";
    private static final String AFTER_HANDLER_FUNCTION_TEMPLATE = "function(attrs){%s}";
    private static final String BEFORE_SEND_HANDLER_FUNCTION_TEMPLATE = "function(attrs, jqXHR, settings){%s}";
    private static final String BEFORE_HANDLER_FUNCTION_TEMPLATE = "function(attrs){%s}";

    /**
     * Subclasses should call super.onBind()
     *
     * @see org.apache.wicket.behavior.AbstractAjaxBehavior#onBind()
     */
    @Override
    protected void onBind()
    {
        getComponent().setOutputMarkupId(true);
    }

    /**
     * @see org.apache.wicket.behavior.AbstractAjaxBehavior#renderHead(Component,
     *      org.apache.wicket.markup.head.IHeaderResponse)
     */
    @Override
    public void renderHead(final Component component, final IHeaderResponse response)
    {
        super.renderHead(component, response);

        CoreLibrariesContributor.contributeAjax(component.getApplication(), response);

        RequestCycle requestCycle = component.getRequestCycle();
        Url baseUrl = requestCycle.getUrlRenderer().getBaseUrl();
        CharSequence ajaxBaseUrl = Strings.escapeMarkup(baseUrl.toString());
        response.render(JavaScriptHeaderItem.forScript("Wicket.Ajax.baseUrl=\"" + ajaxBaseUrl +
            "\";", "wicket-ajax-base-url"));

        renderExtraHeaderContributors(component, response);
    }

    /**
     * Renders header contribution by IAjaxCallListener instances which additionally implement
     * IComponentAwareHeaderContributor interface.
     *
     * @param component
     *            the component assigned to this behavior
     * @param response
     *            the current header response
     */
    private void renderExtraHeaderContributors(final Component component,
        final IHeaderResponse response)
    {
        AjaxRequestAttributes attributes = getAttributes();

        List<IAjaxCallListener> ajaxCallListeners = attributes.getAjaxCallListeners();
        for (IAjaxCallListener ajaxCallListener : ajaxCallListeners)
        {
            if (ajaxCallListener instanceof IComponentAwareHeaderContributor)
            {
                IComponentAwareHeaderContributor contributor = (IComponentAwareHeaderContributor)ajaxCallListener;
                contributor.renderHead(component, response);
            }
        }
    }

    /**
     * @return the Ajax settings for this behavior
     * @since 6.0
     */
    protected final AjaxRequestAttributes getAttributes()
    {
        AjaxRequestAttributes attributes = new AjaxRequestAttributes();
        WebApplication application = (WebApplication)getComponent().getApplication();
        AjaxRequestTargetListenerCollection ajaxRequestTargetListeners = application.getAjaxRequestTargetListeners();
        for (AjaxRequestTarget.IListener listener : ajaxRequestTargetListeners)
        {
            if (listener instanceof AjaxRequestTarget.AbstractListener)
            {
                ((AjaxRequestTarget.AbstractListener)listener).updateAjaxAttributes(attributes);
            }
        }
        updateAjaxAttributesBackwardCompatibility(attributes);
        updateAjaxAttributes(attributes);
        return attributes;
    }

    /**
     * Gives a chance to the specializations to modify the attributes.
     *
     * @param attributes
     * @since 6.0
     */
    protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
    {
    }

    /**
     * The code below handles backward compatibility.
     *
     * @param attributes
     */
    private void updateAjaxAttributesBackwardCompatibility(final AjaxRequestAttributes attributes)
    {
        AjaxCallListener backwardCompatibleAjaxCallListener = new AjaxCallListener();
        backwardCompatibleAjaxCallListener.onSuccess(getSuccessScript());
        backwardCompatibleAjaxCallListener.onFailure(getFailureScript());
        backwardCompatibleAjaxCallListener.onPrecondition(getPreconditionScript());
        attributes.getAjaxCallListeners().add(backwardCompatibleAjaxCallListener);

        AjaxChannel channel = getChannel();
        if (channel != null)
        {
            attributes.setChannel(channel);
        }
    }

    /**
     * <pre>
     *                 {
     *                     u: 'editable-label?6-1.IBehaviorListener.0-text1-label',  // url
     *                     m: 'POST',          // method name. Default: 'GET'
     *                     c: 'label7',        // component id (String) or window for page
     *                     e: 'click',         // event name
     *                     sh: [],             // list of success handlers
     *                     fh: [],             // list of failure handlers
     *                     pre: [],            // list of preconditions. If empty set default : Wicket.$(settings{c}) !== null
     *                     ep: {},             // extra parameters
     *                     async: true|false,  // asynchronous XHR or not
     *                     ch: 'someName|d',   // AjaxChannel
     *                     i: 'indicatorId',   // indicator component id
     *                     ad: true,           // allow default
     *                 }
     * </pre>
     *
     * @param component
     *            the component with that behavior
     * @return the attributes as string in JSON format
     */
    protected final CharSequence renderAjaxAttributes(final Component component)
    {
        AjaxRequestAttributes attributes = getAttributes();
        return renderAjaxAttributes(component, attributes);
    }

    /**
     *
     * @param component
     * @param attributes
     * @return the attributes as string in JSON format
     */
    protected final CharSequence renderAjaxAttributes(final Component component,
        AjaxRequestAttributes attributes)
    {
        JSONObject attributesJson = new JSONObject();

        try
        {
            attributesJson.put(AjaxAttributeName.URL.jsonName(), getCallbackUrl());
            Method method = attributes.getMethod();
            if (Method.POST == method)
            {
                attributesJson.put(AjaxAttributeName.METHOD.jsonName(), method);
            }

            if (component instanceof Page == false)
            {
                String componentId = component.getMarkupId();
                attributesJson.put(AjaxAttributeName.MARKUP_ID.jsonName(), componentId);
            }

            String formId = attributes.getFormId();
            if (Strings.isEmpty(formId) == false)
            {
                attributesJson.put(AjaxAttributeName.FORM_ID.jsonName(), formId);
            }

            if (attributes.isMultipart())
            {
                attributesJson.put(AjaxAttributeName.IS_MULTIPART.jsonName(), true);
            }

            String submittingComponentId = attributes.getSubmittingComponentName();
            if (Strings.isEmpty(submittingComponentId) == false)
            {
                attributesJson.put(AjaxAttributeName.SUBMITTING_COMPONENT_NAME.jsonName(),
                    submittingComponentId);
            }

            String indicatorId = findIndicatorId();
            if (Strings.isEmpty(indicatorId) == false)
            {
                attributesJson.put(AjaxAttributeName.INDICATOR_ID.jsonName(), indicatorId);
            }

            for (IAjaxCallListener ajaxCallListener : attributes.getAjaxCallListeners())
            {
                if (ajaxCallListener != null)
                {
                    CharSequence beforeHandler = ajaxCallListener.getBeforeHandler(component);
                    appendListenerHandler(beforeHandler, attributesJson,
                        AjaxAttributeName.BEFORE_HANDLER.jsonName(),
                        BEFORE_HANDLER_FUNCTION_TEMPLATE);

                    CharSequence beforeSendHandler = ajaxCallListener.getBeforeSendHandler(component);
                    appendListenerHandler(beforeSendHandler, attributesJson,
                        AjaxAttributeName.BEFORE_SEND_HANDLER.jsonName(),
                        BEFORE_SEND_HANDLER_FUNCTION_TEMPLATE);

                    CharSequence afterHandler = ajaxCallListener.getAfterHandler(component);
                    appendListenerHandler(afterHandler, attributesJson,
                        AjaxAttributeName.AFTER_HANDLER.jsonName(), AFTER_HANDLER_FUNCTION_TEMPLATE);

                    CharSequence successHandler = ajaxCallListener.getSuccessHandler(component);
                    appendListenerHandler(successHandler, attributesJson,
                        AjaxAttributeName.SUCCESS_HANDLER.jsonName(),
                        SUCCESS_HANDLER_FUNCTION_TEMPLATE);

                    CharSequence failureHandler = ajaxCallListener.getFailureHandler(component);
                    appendListenerHandler(failureHandler, attributesJson,
                        AjaxAttributeName.FAILURE_HANDLER.jsonName(),
                        FAILURE_HANDLER_FUNCTION_TEMPLATE);

                    CharSequence completeHandler = ajaxCallListener.getCompleteHandler(component);
                    appendListenerHandler(completeHandler, attributesJson,
                        AjaxAttributeName.COMPLETE_HANDLER.jsonName(),
                        COMPLETE_HANDLER_FUNCTION_TEMPLATE);

                    CharSequence precondition = ajaxCallListener.getPrecondition(component);
                    appendListenerHandler(precondition, attributesJson,
                        AjaxAttributeName.PRECONDITION.jsonName(), PRECONDITION_FUNCTION_TEMPLATE);
                }
            }

            JSONArray extraParameters = JsonUtils.asArray(attributes.getExtraParameters());

            if (extraParameters.length() > 0)
            {
                attributesJson.put(AjaxAttributeName.EXTRA_PARAMETERS.jsonName(), extraParameters);
            }

            List<CharSequence> dynamicExtraParameters = attributes.getDynamicExtraParameters();
            if (dynamicExtraParameters != null)
            {
                for (CharSequence dynamicExtraParameter : dynamicExtraParameters)
                {
                    String func = String.format(DYNAMIC_PARAMETER_FUNCTION_TEMPLATE,
                        dynamicExtraParameter);
                    JsonFunction function = new JsonFunction(func);
                    attributesJson.append(AjaxAttributeName.DYNAMIC_PARAMETER_FUNCTION.jsonName(),
                        function);
                }
            }

            if (attributes.isAsynchronous() == false)
            {
                attributesJson.put(AjaxAttributeName.IS_ASYNC.jsonName(), false);
            }

            String[] eventNames = attributes.getEventNames();
            if (eventNames.length == 1)
            {
                attributesJson.put(AjaxAttributeName.EVENT_NAME.jsonName(), eventNames[0]);
            }
            else
            {
                for (String eventName : eventNames)
                {
                    attributesJson.append(AjaxAttributeName.EVENT_NAME.jsonName(), eventName);
                }
            }

            AjaxChannel channel = attributes.getChannel();
            if (channel != null)
            {
                attributesJson.put(AjaxAttributeName.CHANNEL.jsonName(), channel);
            }

            if (attributes.isAllowDefault())
            {
                attributesJson.put(AjaxAttributeName.IS_ALLOW_DEFAULT.jsonName(), true);
            }

            if (AjaxRequestAttributes.EventPropagation.BUBBLE.equals(attributes.getEventPropagation()))
            {
                attributesJson.put(AjaxAttributeName.EVENT_PROPAGATION.jsonName(), "bubble");
            }
            else if (AjaxRequestAttributes.EventPropagation.STOP_IMMEDIATE.equals(attributes.getEventPropagation()))
            {
                attributesJson.put(AjaxAttributeName.EVENT_PROPAGATION.jsonName(), "stopImmediate");
            }

            Duration requestTimeout = attributes.getRequestTimeout();
            if (requestTimeout != null)
            {
                attributesJson.put(AjaxAttributeName.REQUEST_TIMEOUT.jsonName(),
                    requestTimeout.getMilliseconds());
            }

            boolean wicketAjaxResponse = attributes.isWicketAjaxResponse();
            if (wicketAjaxResponse == false)
            {
                attributesJson.put(AjaxAttributeName.IS_WICKET_AJAX_RESPONSE.jsonName(), false);
            }

            String dataType = attributes.getDataType();
            if (AjaxRequestAttributes.XML_DATA_TYPE.equals(dataType) == false)
            {
                attributesJson.put(AjaxAttributeName.DATATYPE.jsonName(), dataType);
            }

            ThrottlingSettings throttlingSettings = attributes.getThrottlingSettings();
            if (throttlingSettings != null)
            {
                JSONObject throttlingSettingsJson = new JSONObject();
                throttlingSettingsJson.put(AjaxAttributeName.THROTTLING_ID.jsonName(),
                    throttlingSettings.getId());
                throttlingSettingsJson.put(AjaxAttributeName.THROTTLING_DELAY.jsonName(),
                    throttlingSettings.getDelay().getMilliseconds());
                if (throttlingSettings.getPostponeTimerOnUpdate())
                {
                    throttlingSettingsJson.put(
                        AjaxAttributeName.THROTTLING_POSTPONE_ON_UPDATE.jsonName(), true);
                }
                attributesJson.put(AjaxAttributeName.THROTTLING.jsonName(), throttlingSettingsJson);
            }

            postprocessConfiguration(attributesJson, component);
        }
        catch (JSONException e)
        {
            throw new WicketRuntimeException(e);
        }

        String attributesAsJson = attributesJson.toString();

        return attributesAsJson;
    }

    private void appendListenerHandler(final CharSequence handler, final JSONObject attributesJson,
        final String propertyName, final String functionTemplate) throws JSONException
    {
        if (Strings.isEmpty(handler) == false)
        {
            final JsonFunction function;
            if (handler instanceof JsonFunction)
            {
                function = (JsonFunction)handler;
            }
            else
            {
                String func = String.format(functionTemplate, handler);
                function = new JsonFunction(func);
            }
            attributesJson.append(propertyName, function);
        }
    }

    /**
     * Gives a chance to modify the JSON attributesJson that is going to be used as attributes for
     * the Ajax call.
     *
     * @param attributesJson
     *            the JSON object created by #renderAjaxAttributes()
     * @param component
     *            the component with the attached Ajax behavior
     * @throws JSONException
     *             thrown if an error occurs while modifying {@literal attributesJson} argument
     */
    protected void postprocessConfiguration(JSONObject attributesJson, Component component)
        throws JSONException
    {
    }

    /**
     * @return javascript that will generate an ajax GET request to this behavior with its assigned
     *         component
     */
    public CharSequence getCallbackScript()
    {
        return getCallbackScript(getComponent());
    }

    /**
     * @param component
     *            the component to use when generating the attributes
     * @return script that can be used to execute this Ajax behavior.
     */
    // 'protected' because this method is intended to be called by other Behavior methods which
    // accept the component as parameter
    protected CharSequence getCallbackScript(final Component component)
    {
        CharSequence ajaxAttributes = renderAjaxAttributes(component);
        return "Wicket.Ajax.ajax(" + ajaxAttributes + ");";
    }

    /**
     * Generates a javascript function that can take parameters and performs an AJAX call which
     * includes these parameters. The generated code looks like this:
     *
     * <pre>
     * function(param1, param2) {
     *    var attrs = attrsJson;
     *    var params = {'param1': param1, 'param2': param2};
     *    attrs.ep = jQuery.extend(attrs.ep, params);
     *    Wicket.Ajax.ajax(attrs);
     * }
     * </pre>
     *
     * @param extraParameters
     * @return A function that can be used as a callback function in javascript
     */
    public CharSequence getCallbackFunction(CallbackParameter... extraParameters)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("function (");
        boolean first = true;
        for (CallbackParameter curExtraParameter : extraParameters)
        {
            if (curExtraParameter.getFunctionParameterName() != null)
            {
                if (!first)
                    sb.append(',');
                else
                    first = false;
                sb.append(curExtraParameter.getFunctionParameterName());
            }
        }
        sb.append(") {\n");
        sb.append(getCallbackFunctionBody(extraParameters));
        sb.append("}\n");
        return sb;
    }

    /**
     * Generates the body the {@linkplain #getCallbackFunction(CallbackParameter...) callback
     * function}. To embed this code directly into a piece of javascript, make sure any context
     * parameters are available as local variables, global variables or within the closure.
     *
     * @param extraParameters
     * @return The body of the {@linkplain #getCallbackFunction(CallbackParameter...) callback
     *         function}.
     */
    public CharSequence getCallbackFunctionBody(CallbackParameter... extraParameters)
    {
        AjaxRequestAttributes attributes = getAttributes();
        attributes.setEventNames();
        CharSequence attrsJson = renderAjaxAttributes(getComponent(), attributes);
        StringBuilder sb = new StringBuilder();
        sb.append("var attrs = ");
        sb.append(attrsJson);
        sb.append(";\n");
        sb.append("var params = {");
        boolean first = true;
        for (CallbackParameter curExtraParameter : extraParameters)
        {
            if (curExtraParameter.getAjaxParameterName() != null)
            {
                if (!first)
                    sb.append(',');
                else
                    first = false;
                sb.append('\'')
                    .append(curExtraParameter.getAjaxParameterName())
                    .append("': ")
                    .append(curExtraParameter.getAjaxParameterCode());
            }
        }
        sb.append("};\n");
        if (attributes.getExtraParameters().isEmpty())
            sb.append("attrs." + AjaxAttributeName.EXTRA_PARAMETERS + " = params;\n");
        else
            sb.append("attrs." + AjaxAttributeName.EXTRA_PARAMETERS + " = Wicket.merge(attrs." +
                AjaxAttributeName.EXTRA_PARAMETERS + ", params);\n");
        sb.append("Wicket.Ajax.ajax(attrs);\n");
        return sb;
    }

    /**
     * @return an optional javascript expression that determines whether the request will actually
     *         execute (in form of return XXX;);
     * @deprecated Use {@link org.apache.wicket.ajax.attributes.AjaxRequestAttributes}
     */
    @Deprecated
    protected CharSequence getPreconditionScript()
    {
        return null;
    }

    /**
     * @return javascript that will run when the ajax call finishes with an error status
     */
    @Deprecated
    protected CharSequence getFailureScript()
    {
        return null;
    }

    /**
     * @return javascript that will run when the ajax call finishes successfully
     */
    @Deprecated
    protected CharSequence getSuccessScript()
    {
        return null;
    }

    /**
     * Provides an AjaxChannel for this Behavior.
     *
     * @return an AjaxChannel - Defaults to null.
     * @deprecated Use {@link org.apache.wicket.ajax.attributes.AjaxRequestAttributes}
     */
    @Deprecated
    protected AjaxChannel getChannel()
    {
        return null;
    }

    /**
     * Finds the markup id of the indicator. The default search order is: component, behavior,
     * component's parent hierarchy.
     *
     * @return markup id or <code>null</code> if no indicator found
     */
    protected String findIndicatorId()
    {
        if (getComponent() instanceof IAjaxIndicatorAware)
        {
            return ((IAjaxIndicatorAware)getComponent()).getAjaxIndicatorMarkupId();
        }

        if (this instanceof IAjaxIndicatorAware)
        {
            return ((IAjaxIndicatorAware)this).getAjaxIndicatorMarkupId();
        }

        Component parent = getComponent().getParent();
        while (parent != null)
        {
            if (parent instanceof IAjaxIndicatorAware)
            {
                return ((IAjaxIndicatorAware)parent).getAjaxIndicatorMarkupId();
            }
            parent = parent.getParent();
        }
        return null;
    }

    /**
     * @see org.apache.wicket.behavior.IBehaviorListener#onRequest()
     */
    @Override
    public final void onRequest()
    {
        WebApplication app = (WebApplication)getComponent().getApplication();
        AjaxRequestTarget target = app.newAjaxRequestTarget(getComponent().getPage());

        RequestCycle requestCycle = RequestCycle.get();
        requestCycle.scheduleRequestHandlerAfterCurrent(target);

        respond(target);
    }

    /**
     * @param target
     *            The AJAX target
     */
    protected abstract void respond(AjaxRequestTarget target);

}

wicket-jquery-ui 下载页面https://github.com/sebfz1/wicket-jquery-ui

转载于:https://my.oschina.net/u/1047983/blog/146594

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值