001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.ajax.markup.html.form; 018 019import java.util.Optional; 020 021import org.apache.wicket.Component; 022import org.apache.wicket.ajax.AjaxRequestTarget; 023import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; 024import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior; 025import org.apache.wicket.markup.ComponentTag; 026import org.apache.wicket.markup.html.form.Button; 027import org.apache.wicket.markup.html.form.Form; 028import org.apache.wicket.model.IModel; 029 030/** 031 * An ajax submit button that will degrade to a normal request if ajax is not available or 032 * javascript is disabled. 033 * 034 * @since 1.3 035 * 036 * @author Jeremy Thomerson (jthomerson) 037 * @author Alastair Maw 038 * 039 * 040 */ 041public abstract class AjaxFallbackButton extends Button 042{ 043 private static final long serialVersionUID = 1L; 044 045 private final Form<?> mForm; 046 047 /** 048 * Construct. 049 * 050 * @param id 051 * @param form 052 */ 053 public AjaxFallbackButton(String id, Form<?> form) 054 { 055 this(id, null, form); 056 } 057 058 /** 059 * Construct. 060 * 061 * @param id 062 * @param model 063 * @param form 064 */ 065 public AjaxFallbackButton(String id, IModel<String> model, Form<?> form) 066 { 067 super(id, model); 068 mForm = form; 069 070 add(newAjaxEventBehavior(form, "click")); 071 } 072 073 protected AjaxFormSubmitBehavior newAjaxEventBehavior(Form<?> form, String event) 074 { 075 return new AjaxFormSubmitBehavior(form, event) 076 { 077 private static final long serialVersionUID = 1L; 078 079 @Override 080 protected void onSubmit(AjaxRequestTarget target) 081 { 082 AjaxFallbackButton.this.onSubmit(Optional.of(target)); 083 } 084 085 @Override 086 protected void onAfterSubmit(AjaxRequestTarget target) 087 { 088 AjaxFallbackButton.this.onAfterSubmit(Optional.of(target)); 089 } 090 091 @Override 092 protected void onError(AjaxRequestTarget target) 093 { 094 AjaxFallbackButton.this.onError(Optional.of(target)); 095 } 096 097 @Override 098 public boolean getDefaultProcessing() 099 { 100 return AjaxFallbackButton.this.getDefaultFormProcessing(); 101 } 102 103 @Override 104 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 105 { 106 super.updateAjaxAttributes(attributes); 107 108 // do not allow normal form submit to happen 109 attributes.setPreventDefault(true); 110 111 AjaxFallbackButton.this.updateAjaxAttributes(attributes); 112 } 113 114 @Override 115 public boolean getStatelessHint(Component component) 116 { 117 return AjaxFallbackButton.this.getStatelessHint(); 118 } 119 120 @Override 121 protected boolean shouldTriggerJavaScriptSubmitEvent() 122 { 123 return AjaxFallbackButton.this.shouldTriggerJavaScriptSubmitEvent(); 124 } 125 }; 126 } 127 128 /** 129 * Controls whether or not a JS <code>submit</code> should be triggered on the submitting form. 130 * False by default. 131 * 132 * @return true if <code>submit</code> should be triggered, false otherwise 133 */ 134 protected boolean shouldTriggerJavaScriptSubmitEvent() 135 { 136 return false; 137 } 138 139 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 140 { 141 } 142 143 /** 144 * Listener method invoked on form submit with errors. If ajax failed and this event was 145 * generated via a normal submission, the target argument will be null. 146 * 147 * @param target 148 */ 149 protected void onError(Optional<AjaxRequestTarget> target) 150 { 151 } 152 153 @Override 154 public final void onError() 155 { 156 if (getRequestCycle().find(AjaxRequestTarget.class).isPresent() == false) 157 { 158 onError(Optional.empty()); 159 } 160 } 161 162 @Override 163 public final void onSubmit() 164 { 165 if (getRequestCycle().find(AjaxRequestTarget.class).isPresent() == false) 166 { 167 onSubmit(Optional.empty()); 168 } 169 } 170 171 @Override 172 public final void onAfterSubmit() 173 { 174 if (getRequestCycle().find(AjaxRequestTarget.class).isPresent() == false) 175 { 176 onAfterSubmit(Optional.empty()); 177 } 178 } 179 180 @Override 181 public Form<?> getForm() 182 { 183 return mForm == null ? super.getForm() : mForm; 184 } 185 186 /** 187 * Callback for the onClick event. If ajax failed and this event was generated via a normal 188 * submission, the target argument will be {@link Optional#empty()}. This method will be called 189 * <em>before</em> {@link Form#onSubmit()}. 190 * 191 * @param target 192 * ajax target if this linked was invoked using ajax, {@link Optional#empty()} 193 * otherwise 194 */ 195 protected void onSubmit(final Optional<AjaxRequestTarget> target) 196 { 197 } 198 199 /** 200 * Callback for the onClick event. If ajax failed and this event was generated via a normal 201 * submission, the target argument will be null. This method will be called <em>after</em> 202 * {@link Form#onSubmit()}. 203 * 204 * @param target 205 * ajax target if this linked was invoked using ajax, null otherwise 206 */ 207 protected void onAfterSubmit(final Optional<AjaxRequestTarget> target) 208 { 209 } 210 211 /** 212 * Helper methods that both checks whether the link is enabled and whether the action ENABLE is 213 * allowed. 214 * 215 * @return whether the link should be rendered as enabled 216 */ 217 protected final boolean isButtonEnabled() 218 { 219 return isEnabledInHierarchy(); 220 } 221 222 @Override 223 protected void onComponentTag(ComponentTag tag) 224 { 225 String tagName = tag.getName(); 226 if (!("input".equalsIgnoreCase(tagName) || "button".equalsIgnoreCase(tagName))) 227 { 228 String msg = String.format("%s must be used only with <input type=\"submit\"> or <input type=\"submit\"> markup elements. " + 229 "The fallback functionality doesn't work for other markup elements. " + 230 "Component path: %s, markup element: <%s>.", 231 AjaxFallbackButton.class.getSimpleName(), getClassRelativePath(), tagName); 232 findMarkupStream().throwMarkupException(msg); 233 } 234 235 super.onComponentTag(tag); 236 } 237}