In this patch there are many fixes found when playing with plugins.
Also I have refactored Effects and Transitions plugins so as it is
easier to extend and implement customized animations.
Now we use properties instead of the Fx.css3 hack to disable modern
animations.
-->
<define-linker name="stddoctype" class="com.google.gwt.query.linker.IFrameWithDocTypeLinker"/>
+ <!-- Let the user select type of animations to use -->
+ <define-property name="animation" values="js,transition" />
+ <set-property name="animation" value="transition" />
+ <set-property name="animation" value="js">
+ <any>
+ <when-property-is name="user.agent" value="ie6" />
+ <when-property-is name="user.agent" value="ie8" />
+ <when-property-is name="user.agent" value="ie9" />
+ </any>
+ </set-property>
+ <!-- Animations based on JS loops -->
+ <replace-with class="com.google.gwt.query.client.plugins.effects.ClipAnimation">
+ <when-type-assignable
+ class="com.google.gwt.query.client.plugins.Effects.GQAnimation" />
+ <when-property-is name="animation" value="js" />
+ </replace-with>
+ <!-- Animations based on CSS3 transitions -->
+ <replace-with class="com.google.gwt.query.client.plugins.effects.TransitionsAnimation.TransitionsClipAnimation">
+ <when-type-assignable
+ class="com.google.gwt.query.client.plugins.Effects.GQAnimation" />
+ <when-property-is name="animation" value="transition" />
+ </replace-with>
</module>
<!-- Inherit GQuery module -->
<inherits name='com.google.gwt.query.Query'/>
-
+
<source path="client"/>
- <super-source path="super"/>
-
+ <super-source path="super"/>
+
<!-- Detect whether querySelectorAll is available -->
<define-property name="selectorCapability" values="native,js"/>
<property-provider name="selectorCapability">
<when-property-is name="user.agent" value="ie6" />
</set-property>
<set-property name="selectorCapability" value="native" >
- <any>
- <when-property-is name="user.agent" value="safari" />
- </any>
+ <when-property-is name="user.agent" value="safari" />
</set-property>
-
+
<!-- Selector Engines -->
<replace-with class="com.google.gwt.query.client.impl.SelectorEngineNativeMin">
<when-type-assignable class="com.google.gwt.query.client.impl.SelectorEngineImpl"/>
* Utility method to get a string representation with the content
* of the arguments array. It recursively visits arrays and inspect
* object to print an appropriate representation of them.
- *
+ *
* It is very useful to debug arguments passed in nested promises.
- *
+ *
* It is protected so as it can be used in Inner functions.
- *
+ *
* Output example:
* <pre>
* [0](com.google.gwt.query.client.plugins.QueuePlugin) <div>a1</div><div>a2</div>
/**
* Return the first element of the arguments list.
- *
+ *
* @deprecated use getArgument(idx) instead.
*/
@Deprecated
/**
* Utility method for safety getting a JavaScriptObject present at a certain
* position in the list of arguments composed by arrays.
- *
+ *
*/
@SuppressWarnings("unchecked")
public <T extends JavaScriptObject> T getArgumentJSO(int argIdx, int pos) {
/**
* Utility method for safety getting an array present at a certain
* position in the list of arguments.
- *
+ *
* Useful for Deferred chains where result of each resolved
* promise is set as an array in the arguments list.
- *
+ *
* Always returns an array.
*/
public Object[] getArgumentArray(int idx) {
/**
* Return the argument in the position idx or null if it doesn't exist.
- *
+ *
* Note: if the return type doesn't match the object, you
* will get a casting exception.
*/
/**
* Safety return the argument in the position idx.
- *
+ *
* If the element class is not of the requested type it returns null and
* you don't get casting exeption.
*/
/**
* Safety return the argument in the position idx.
- *
+ *
* If the element class is not of the requested type it returns null and
* you don't get casting exeption.
*/
/**
* Utility method for safety getting an object present at a certain
* position in the list of arguments composed by arrays.
- *
+ *
* Useful for Deferred chains where result of each resolved
* promise is set as an array in the arguments list.
- *
+ *
* When the object found in the array doesn't match the type required it returns a null.
- *
+ *
* Note: If type is null, we don't check the class of the object found andd you could
* eventually get a casting exception.
- *
+ *
*/
@SuppressWarnings("unchecked")
public <T> T getArgument(int argIdx, int pos, Class<? extends T> type) {
/**
* Return the index in a loop execution.
- *
+ *
* Used in GQuery.each()
*/
public int getIndex() {
/**
* Override this method for bound event handlers if you wish to deal with
* per-handler user data.
+ *
+ * @return boolean false means stop propagation and prevent default
*/
public boolean f(Event e, Object... arg) {
setArguments(arg);
/**
* Override this method for bound event handlers.
*
- * @return boolean: false means stop propagation and prevent default
+ * @return boolean false means stop propagation and prevent default
*/
public boolean f(Event e) {
setEvent(e);
}
public final native boolean isEmpty() /*-{
- for (k in this) return false;
+ for (k in this) if (this.hasOwnProperty(k)) return false;
return true;
}-*/;
var key, keys=[];
// Chrome in DevMode sets '__gwt_ObjectId' to JS objects
// GWT sets '$H' when calling getHashCode (see com/google/gwt/core/client/impl/Impl.java)
- for(key in this) if (key != '__gwt_ObjectId' && key != '$H') keys.push(String(key));
+ for(key in this) if (this.hasOwnProperty(key) && key != '__gwt_ObjectId' && key != '$H') keys.push(String(key));
return keys;
}-*/;
*/
package com.google.gwt.query.client.js;
+import static com.google.gwt.query.client.GQuery.browser;
+
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayMixed;
+import com.google.gwt.core.client.JsonUtils;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
* Default JsUtils implementation.
*/
public static class JsUtilsImpl {
- public native Properties parseJSON(String json) /*-{
- return $wnd.JSON.parse(json);
- }-*/;
+ public Properties parseJSON(String json) {
+ return JsonUtils.safeEval(json);
+ }
public native String JSON2String(JavaScriptObject o) /*-{
return $wnd.JSON.stringify(o);
* IE JsUtils implemetation.
*/
public static class JsUtilsImplIE6 extends JsUtilsImpl {
- public static final native Properties evalImpl(String properties) /*-{
- return eval(properties);
- }-*/;
-
@Override
public Properties parseJSON(String json) {
- // No checks to the passed string so json should be
- // a well-formed json string.
- return evalImpl("(" + json + ")");
+ return JsonUtils.unsafeEval(json);
}
@Override
@Override
public JsArray<Element> unique(JsArray<Element> a) {
// in IE6 XML elements does not support adding hashId to the object
- if (a.length() > 0 && isXML(a.get(0))) {
+ if (browser.ie6 && isXML(a.get(0))) {
return a;
}
return super.unique(a);
}
/**
- * Check if an object has already a property with name <code>name</code>
- * defined.
+ * Check if an object has a property with <code>name</code> defined.
+ * It supports dots in the name meaning checking nested properties.
+ *
+ * Example:
+ * <pre>
+ * // Check whether a browser supports touch events
+ * hasProperty(window, "ontouchstart");
+ * </pre>
*/
- public static native boolean hasProperty(JavaScriptObject o, String name) /*-{
+ public static native boolean hasProperty(JavaScriptObject o, String name)/*-{
return o && name in o;
+ var p = name.split('.');
+ for (var i in p) {
+ if (!(o && p[i] in o)) return false;
+ o = o[p[i]];
+ }
+ return true;
}-*/;
/**
/**
* Return whether a node is detached to the DOM.
- *
+ *
* Be careful : This method works only on node that should be inserted within the body node.
*/
public static boolean isDetached(Node n) {
return a
}-*/;
+ /**
+ * Run any arbitrary function in javascript scope using the window as the base object.
+ * It checks the existence of the function and object hierarchy before executing it.
+ * It's very useful in order to avoid writing jsni blocks for very simple snippets.
+ *
+ * Note that GWT 3.0 jsinterop will come with a method similar, so we might deprecate
+ * this in the future.
+ *
+ * Example
+ * <pre>
+ * // Create a svg node in our document.
+ * Element svg = jsni("document.createElementNS", "http://www.w3.org/2000/svg", "svg");
+ * // Append it to the dom
+ * $(svg).appendTo(document);
+ * // show the svg element in the debug console
+ * jsni("console.log", svg);
+ * </pre>
+ *
+ * @param meth the literal name of the function to call, dot separators are allowed.
+ * @param args an array with the arguments to pass to the function.
+ * @return the java ready boxed object returned by the jsni method or null, if the
+ * call return a number we will get a Double, if it returns a boolean we get a java
+ * Boolean, strings comes as java String, otherwise we get the javascript object.
+ */
+ public static <T> T jsni(String meth, Object... args) {
+ return runJavascriptFunction(null, meth, args);
+ }
+
/**
* Call via jsni any arbitrary function present in a Javascript object.
*
* $(svg).appendTo(document);
* </pre>
*
- * @param o the javascript object where the function is.
- * @param meth the literal name of the function to call.
+ * @param o the javascript object where the function is, it it is null we use window.
+ * @param meth the literal name of the function to call, dot separators are allowed.
* @param args an array with the arguments to pass to the function.
- * @return the javascript object returned by the jsni method or null.
+ * @return the java ready boxed object returned by the jsni method or null, if the
+ * call return a number we will get a Double, if it returns a boolean we get a java
+ * Boolean, strings comes as java String, otherwise we get the javascript object.
*/
public static <T> T runJavascriptFunction(JavaScriptObject o, String meth, Object... args) {
- return runJavascriptFunctionImpl(o, meth, JsObjectArray.create().add(args)
- .<JsArrayMixed> cast());
+ return runJavascriptFunctionImpl(o, meth, JsObjectArray.create().add(args).<JsArrayMixed>cast());
}
- private static native <T> T runJavascriptFunctionImpl(JavaScriptObject o, String meth,
- JsArrayMixed args) /*-{
- return (f = o && o[meth])
- && @com.google.gwt.query.client.js.JsUtils::isFunction(*)(f)
+ private static native <T> T runJavascriptFunctionImpl(JavaScriptObject o, String meth, JsArrayMixed args) /*-{
+ var f = o || window, p = meth.split('.');
+ for (var i in p) {
+ o = f;
+ f = f[p[i]];
+ if (!f) return null;
+ }
+ return @com.google.gwt.query.client.js.JsUtils::isFunction(*)(f)
&& @com.google.gwt.query.client.js.JsCache::gwtBox(*)([f.apply(o, args)]);
}-*/;
/**
* Returns a QueryString representation of a JavascriptObject.
- *
+ *
* TODO: jquery implementation accepts a second parameter (traditional)
*/
public static String param(JavaScriptObject js) {
package com.google.gwt.query.client.plugins;
import com.google.gwt.animation.client.Animation;
+import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQuery;
import com.google.gwt.query.client.Properties;
-import com.google.gwt.query.client.plugins.effects.ClipAnimation;
-import com.google.gwt.query.client.plugins.effects.ClipAnimation.Direction;
import com.google.gwt.query.client.plugins.effects.Fx;
import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.Easing;
import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.EasingCurve;
-import com.google.gwt.query.client.plugins.effects.TransitionsAnimation.TransitionsClipAnimation;
/**
* Effects plugin for Gwt Query.
// Each Animation is associated to one element
protected Element e;
protected Properties prps;
+ protected Easing easing;
+ protected Function[] funcs;
+ protected Effects g;
- protected GQAnimation setElement(Element element) {
+ public GQAnimation setElement(Element element) {
e = element;
+ g = $(e).as(Effects);
return this;
}
- protected GQAnimation setProperties(Properties properties) {
+ public GQAnimation setProperties(Properties properties) {
prps = properties == null ? Properties.create() : properties;
+ if (prps.defined("easing")) {
+ try {
+ easing = EasingCurve.valueOf(prps.getStr("easing"));
+ } catch (Exception ignore) {
+ }
+ }
+ return this;
+ }
+
+ public GQAnimation setEasing(Easing ease) {
+ easing = ease != null ? ease : EasingCurve.swing;
+ return this;
+ }
+
+ public GQAnimation setCallback(Function... f) {
+ funcs = f;
return this;
}
super(gq);
}
- private void queueAnimation(final Element e, final GQAnimation anim, final int duration) {
+ /**
+ * Queue an animation for an element.
+ *
+ * The goal of this method is to reuse animations.
+ * @param e
+ * @param anim
+ * @param duration
+ */
+ public void queueAnimation(final GQAnimation anim, final int duration) {
if (isOff()) {
anim.onStart();
anim.onComplete();
} else {
- queue(e, DEFAULT_NAME, new Function() {
+ queue(anim.e, DEFAULT_NAME, new Function() {
public void cancel(Element e) {
Animation anim = (Animation) data(e, GQAnimation.ACTUAL_ANIMATION, null);
if (anim != null) {
* @param easing the easing function to use for the transition
*/
public Effects animate(Object stringOrProperties, int duration, Easing easing, Function... funcs) {
-
final Properties p = (stringOrProperties instanceof String)
? (Properties) $$((String) stringOrProperties)
: (Properties) stringOrProperties;
if (p.getStr("duration") != null) {
duration = p.getInt("duration");
}
-
duration = Math.abs(duration);
for (Element e : elements()) {
- if (Fx.css3) {
- new TransitionsClipAnimation(easing, e, p, funcs).run(duration);
- } else {
- queueAnimation(e, new ClipAnimation(easing, e, p, funcs), duration);
- }
+ GQAnimation a = createAnimation();
+ a.setEasing(easing).setProperties(p).setElement(e).setCallback(funcs);
+ queueAnimation(a, duration);
}
return this;
}
+ /**
+ * Override this to create plugins with customized animation implementation.
+ *
+ * By default it uses deferred binding.
+ */
+ protected GQAnimation createAnimation() {
+ return GWT.create(GQAnimation.class);
+ }
+
/**
*
* The animate() method allows you to create animation effects on any numeric
return animate(stringOrProperties, duration, EasingCurve.linear, funcs);
}
- /**
- * Animate the set of matched elements using the clip or scale property. It is possible to show or
- * hide a set of elements, specify the direction of the animation and the start corner of the
- * effect. Finally it executes the set of functions passed as arguments.
- *
- * @deprecated use animate() instead
- */
- @Deprecated
- public Effects clip(ClipAnimation.Action a, ClipAnimation.Corner c,
- ClipAnimation.Direction d, Function... f) {
- return clip(a, c, d, Speed.DEFAULT, f);
- }
-
- /**
- * Animate the set of matched elements using the clip or scale property. It is possible to show or
- * hide a set of elements, specify the direction of the animation and the start corner of the
- * effect. Finally it executes the set of functions passed as arguments.
- *
- * @deprecated use animate() instead
- */
- @Deprecated
- public Effects clip(final ClipAnimation.Action a,
- final ClipAnimation.Corner c, final ClipAnimation.Direction d,
- final int duration, final Function... f) {
- for (Element e : elements()) {
- if (Fx.css3) {
- new TransitionsClipAnimation(e, a, c, d, null, null, f).run(duration);
- } else {
- queueAnimation(e, new ClipAnimation(e, a, c, d, f), duration);
- }
- }
- return this;
- }
-
- /**
- * Animate the set of matched elements using the clip or scale property. It is possible to show or
- * hide a set of elements, specify the direction of the animation and the start corner of the
- * effect. Finally it executes the set of functions passed as arguments.
- *
- * @deprecated use animate() instead
- */
- @Deprecated
- public Effects clip(ClipAnimation.Action a, ClipAnimation.Corner c,
- Function... f) {
- return clip(a, c, Direction.BIDIRECTIONAL, Speed.DEFAULT, f);
- }
-
/**
* Reveal all matched elements by adjusting the clip or scale property firing an optional callback
* after completion. The effect goes from the center to the perimeter.
*/
package com.google.gwt.query.client.plugins.effects;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQuery;
import com.google.gwt.query.client.Properties;
import com.google.gwt.query.client.js.JsUtils;
import com.google.gwt.query.client.plugins.Effects;
+import com.google.gwt.query.client.plugins.Effects.GQAnimation;
/**
* Animation which uses the css clip property to show/hide an element.
private Corner corner;
private Direction direction;
private GQuery back = Effects.$();
- private Effects g;
private Action currentAction;
- public ClipAnimation(Element elem, Properties p, Function... funcs) {
- this(null, elem, p, funcs);
- }
-
- public ClipAnimation(Easing easing, Element elem, Properties p, Function... funcs) {
- super(easing, elem, p, funcs);
+ @Override
+ public GQAnimation setProperties(Properties p) {
corner = Corner.CENTER;
try {
corner = Corner.valueOf(getNormalizedValue("clip-origin", p));
action = Action.valueOf(getNormalizedValue("clip-action", p));
} catch (Exception e) {
}
- g = GQuery.$(e).as(Effects.Effects);
+ return super.setProperties(p);
}
public static String getNormalizedValue(String value, Properties p) {
return JsUtils.hyphenize(p.getStr(value)).replace("-", "_").toUpperCase();
}
- public ClipAnimation(Element elem, Action a, Corner c, Direction d, Easing easing,
- Properties p, final Function... funcs) {
- super(easing, elem, p, funcs);
- this.action = a;
- this.corner = c;
- this.direction = d;
- g = GQuery.$(e).as(Effects.Effects);
- }
-
- public ClipAnimation(Element elem, Action a, Corner c, Direction d, final Function... funcs) {
- this(elem, a, c, d, null, Properties.create(), funcs);
- }
-
@Override
public void onComplete() {
super.onComplete();
*/
public static boolean off = false;
- public static boolean css3 = Transitions.transition != null;
-
/**
* A pojo to store css3 transition values.
*/
*/
package com.google.gwt.query.client.plugins.effects;
-import static com.google.gwt.query.client.GQuery.$;
-
import com.google.gwt.dom.client.Element;
-import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQuery;
-import com.google.gwt.query.client.Properties;
import com.google.gwt.query.client.js.JsObjectArray;
import com.google.gwt.query.client.plugins.Effects;
import com.google.gwt.query.client.plugins.Effects.GQAnimation;
.compile("^([+-]=)?([0-9+-.]+)(.*)?$");
protected static final RegExp REGEX_NON_PIXEL_ATTRS =
- RegExp.compile("z-?index|font-?weight|opacity|zoom|line-?height|scale|rotation|^\\$", "i");
+ RegExp.compile("z-?index|font-?weight|opacity|zoom|line-?height|scale|rotat|^\\$", "i");
private static final RegExp REGEX_COLOR_ATTR = RegExp.compile(".*color$", "i");
if (REGEX_COLOR_ATTR.test(key)) {
return computeFxColorProp(e, key, val);
}
-
return computeFxNumericProp(e, key, val, hidden);
}
return new Fx(key, val, start, end, unit, rkey);
}
- protected Easing easing;
protected JsObjectArray<Fx> effects;
- private Function[] funcs;
- private Effects g;
-
- public PropertiesAnimation(Element elem, Properties p, Function... funcs) {
- this(null, elem, p, funcs);
- }
-
- public PropertiesAnimation(Easing ease, Element elem, Properties p, Function... funcs) {
- try {
- easing = EasingCurve.valueOf(p.getStr("easing"));
- } catch (Exception e) {
- }
- if (easing == null) {
- easing = ease;
- }
- if (easing == null) {
- easing = EasingCurve.swing;
- }
- this.funcs = funcs;
- setProperties(p);
- setElement(elem);
-
- g = $(e).as(Effects.Effects);
- }
@Override
public void onCancel() {
@Override
public void onComplete() {
super.onComplete();
- for (int i = 0; i < effects.length(); i++) {
+ for (int i = 0; effects != null && i < effects.length(); i++) {
Fx fx = effects.get(i);
if ("hide".equals(fx.value)) {
g.hide();
--- /dev/null
+/*
+ * Copyright 2014, The gwtquery team.
+ *
+ * Licensed 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.google.gwt.query.client.plugins.effects;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.query.client.GQuery;
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+
+/**
+ * A dictionary class with all the properties of an element transform
+ * which is able to return the correct syntax for setting the css transform
+ * property.
+ */
+public class Transform {
+
+ private static final String TRANSFORM = "_t_";
+
+ protected static final RegExp transformRegex = RegExp.compile("^(scale([XYZ]|)|translate([XYZ]|3d)|rotate([XYZ]|3d)?|perspective|skew[XYZ]|x|y)$");
+ private static final RegExp transform3dRegex = RegExp.compile("^(rotate([XYZ]|3d)|perspective)$");
+
+ private HashMap<String, List<String>> map = new HashMap<String, List<String>>();
+
+ /**
+ * Return the Transform dictionary object of a element.
+ */
+ public static Transform getInstance(Element e) {
+ return getInstance(e, null);
+ }
+
+ public static boolean isTransform(String propName) {
+ return transformRegex.test(propName);
+ }
+
+ /**
+ * Return the Transform dictionary object of an element, but reseting
+ * historical values and setting them to the initial value passed.
+ */
+ public static Transform getInstance(Element e, String initial) {
+ Transform t = GQuery.data(e, TRANSFORM);
+ if (t == null || initial != null && !initial.isEmpty()) {
+ t = new Transform(initial);
+ GQuery.data(e, TRANSFORM, t);
+ }
+ return t;
+ }
+
+ public Transform(String s) {
+ parse(s);
+ }
+
+ public String get(String prop) {
+ return listToStr(map.get(prop), ",");
+ }
+
+ private String listToStr(List<String> l, String sep) {
+ String v = "";
+ if (l != null) {
+ for (String s : l) {
+ v += (v.isEmpty() ? "" : sep) + s;
+ }
+ }
+ return v;
+ }
+
+ private void parse(String s) {
+ if (s != null) {
+ RegExp re = RegExp.compile("([a-zA-Z0-9]+)\\((.*?)\\)", "g");
+ for (MatchResult r = re.exec(s); r != null; r = re.exec(s)) {
+ setFromString(r.getGroup(1), r.getGroup(2));
+ }
+ }
+ }
+
+ public void set(String prop, String ...val) {
+ setter(prop, val);
+ }
+
+ public void setFromString(String prop, String ...val) {
+ if (val.length == 1) {
+ String[] vals = val[0].split("[\\s*,\\s*]");
+ set(prop, vals);
+ } else {
+ set(prop, val);
+ }
+ }
+
+ private void setter(String prop, String ...val) {
+ if (prop.matches("(rotate[XYZ]?|skew[XYZ])")) {
+ map.put(prop, unit(val[0], "deg"));
+ } else if ("scale".equals(prop)) {
+ String x = val.length < 1 ? "1" : val[0];
+ String y = val.length < 2 ? x : val[1];
+ map.put(prop, Arrays.asList(x, y));
+ } else if ("perspective".equals(prop)) {
+ map.put(prop, unit(val[0], "px"));
+ } else if ("x".equals(prop)) {
+ setter("translate", val[0], null);
+ } else if ("y".equals(prop)) {
+ setter("translate", null, val[0]);
+ } else if (prop.matches("(translate[XYZ])")) {
+ map.put(prop, unit(val[0], "px"));
+ } else if ("translate".equals(prop)) {
+ if (map.get("translateX") == null) {
+ map.put("translateX", unit("0", "px"));
+ }
+ if (val[0] != null) {
+ map.put("translateX", unit(val[0], "px"));
+ }
+ if (map.get("translateY") == null) {
+ map.put("translateY", unit("0", "px"));
+ }
+ if (val.length > 1 && val[1] != null) {
+ map.put("translateY", unit(val[1], "px"));
+ }
+ if (map.get("translateZ") == null) {
+ map.put("translateZ", unit("0", "px"));
+ }
+ if (val.length > 2 && val[2] != null) {
+ map.put("translateZ", unit(val[0], "px"));
+ }
+ map.put("translate", Arrays.asList(map.get("translateX").get(0), map.get("translateY").get(0), map.get("translateY").get(0)));
+ } else {
+ map.put(prop, unit(val[0], ""));
+ }
+ }
+
+ /**
+ * Converts the dictionary to a transition css string.
+ */
+ public String toString() {
+ // purposely using string addition, since my last tests demonstrate
+ // that string addition performs better than string builders in gwt-prod.
+ String ret = "";
+ for (Entry<String, List<String>> e: map.entrySet()) {
+ if (Transitions.has3d || !transform3dRegex.test(e.getKey())) {
+ String v = listToStr(e.getValue(), ",");
+ ret += (ret.isEmpty() ? "" : " ") + e.getKey() + "(" + v + ")";
+ }
+ }
+ return ret;
+ }
+
+ private List<String> unit(String val, String unit) {
+ return Arrays.asList(val + (val.endsWith(unit) ? "" : unit));
+ }
+}
\ No newline at end of file
*/
package com.google.gwt.query.client.plugins.effects;
-import com.google.gwt.core.client.Duration;
+import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.GQuery;
import com.google.gwt.query.client.Properties;
import com.google.gwt.query.client.js.JsUtils;
+import com.google.gwt.query.client.plugins.Effects;
import com.google.gwt.query.client.plugins.Plugin;
import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.Easing;
import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.EasingCurve;
-import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.query.client.plugins.effects.TransitionsAnimation.TransitionsClipAnimation;
import com.google.gwt.regexp.shared.RegExp;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Timer;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map.Entry;
/**
* Transitions and transformation plugin for gQuery.
* </pre>
*/
-public class Transitions extends GQuery {
-
- /**
- * A dictionary class with all the properties of an element transform
- * which is able to return the correct syntax for setting css properties.
- */
- public static class Transform {
-
- private static final RegExp transform3dRegex = RegExp.compile("^(rotate([XY]|3d)|perspective)$");
-
- private HashMap<String, List<String>> map = new HashMap<String, List<String>>();
-
- public Transform(String s) {
- parse(s);
- }
-
- public String get(String prop) {
- return listToStr(map.get(prop), ",");
- }
-
- private String listToStr(List<String> l, String sep) {
- String v = "";
- if (l != null) {
- for (String s : l) {
- v += (v.isEmpty() ? "" : sep) + s;
- }
- }
- return v;
- }
-
- private void parse(String s) {
- if (s != null) {
- RegExp re = RegExp.compile("([a-zA-Z0-9]+)\\((.*?)\\)", "g");
- for (MatchResult r = re.exec(s); r != null; r = re.exec(s)) {
- setFromString(r.getGroup(1), r.getGroup(2));
- }
- }
- }
-
- public void set(String prop, String ...val) {
- setter(prop, val);
- }
-
- public void setFromString(String prop, String ...val) {
- if (val.length == 1) {
- String[] vals = val[0].split("[\\s*,\\s*]");
- set(prop, vals);
- } else {
- set(prop, val);
- }
- }
-
- private void setter(String prop, String ...val) {
- if (prop.matches("(rotate[XY]?|skew[XY])")) {
- map.put(prop, unit(val[0], "deg"));
- } else if ("scale".equals(prop)) {
- String x = val.length < 1 ? "1" : val[0];
- String y = val.length < 2 ? x : val[1];
- map.put(prop, Arrays.asList(x, y));
- } else if ("perspective".equals(prop)) {
- map.put(prop, unit(val[0], "px"));
- } else if ("x".equals(prop)) {
- setter("translate", val[0], null);
- } else if ("y".equals(prop)) {
- setter("translate", null, val[0]);
- } else if ("translate".equals(prop)) {
- if (map.get("translateX") == null) {
- map.put("translateX", unit("0", "px"));
- }
- if (val[0] != null) {
- map.put("translateX", unit(val[0], "px"));
- }
- if (map.get("translateY") == null) {
- map.put("translateY", unit("0", "px"));
- }
- if (val[1] != null) {
- map.put("translateY", unit(val[1], "px"));
- }
- map.put("translate", Arrays.asList(map.get("translateX").get(0), map.get("translateY").get(0)));
- }
- }
-
- /**
- * Converts the dictionary to a transition css string.
- */
- public String toString() {
- // purposely using string addition, since my last tests demonstrate
- // that string addition performs better than string builders in gwt-prod.
- String ret = "";
- for (Entry<String, List<String>> e: map.entrySet()) {
- if (has3d || !transform3dRegex.test(e.getKey())) {
- String v = listToStr(e.getValue(), ",");
- ret += (ret.isEmpty() ? "" : " ") + e.getKey() + "(" + v + ")";
- }
- }
- return ret;
- }
-
- private List<String> unit(String val, String unit) {
- return Arrays.asList(val + (val.endsWith(unit) ? "" : unit));
- }
- }
+public class Transitions extends Effects {
// Used to check supported properties in the browser
- private static Style divStyle = DOM.createDiv().getStyle();
+ protected static final Style divStyle = Document.get().createDivElement().getStyle();
- private static final String prefix = browser.msie ? "ms" : browser.opera ? "o" : browser.mozilla ? "moz" : browser.webkit ? "webkit" : "";
+ public static final String prefix = browser.msie ? "ms" : browser.opera ? "o" : browser.mozilla ? "moz" : browser.webkit ? "webkit" : "";
private static final String transform = getVendorPropertyName("transform");
- private static final String TRANSFORM = "_t_";
private static final String transformOrigin = getVendorPropertyName("transformOrigin");
- protected static final RegExp transformRegex = RegExp.compile("^(scale|translate|rotate([XY]|3d)?|perspective|skew[XY]|x|y)$");
protected static final String transition = getVendorPropertyName("transition");
// passing an invalid transition property in chrome, makes disable all transitions in the element
- private static final RegExp invalidTransitionNamesRegex = RegExp.compile("^(.*transform.*|duration|easing|delay|clip-.*)$");
+ public static final RegExp invalidTransitionNamesRegex = RegExp.compile("^(.*transform.*|duration|function|easing|delay|clip-.*)$");
- private static final String transitionDelay = getVendorPropertyName("transitionDelay");
- private static final String transitionEnd = browser.mozilla || browser.msie ? "transitionend" : (prefix + "transitionEnd");
+ protected static final String transitionDelay = getVendorPropertyName("transitionDelay");
+ protected static final String transitionEnd = browser.mozilla || browser.msie ? "transitionend" : (prefix + "TransitionEnd");
- public static boolean has3d = supportsTransform3d();
+ public static final boolean has3d = supportsTransform3d();
public static final Class<Transitions> Transitions = GQuery.registerPlugin(
Transitions.class, new Plugin<Transitions>() {
}
});
- private static String getVendorPropertyName(String prop) {
+ public static String getVendorPropertyName(String prop) {
+ assert divStyle != null;
// we prefer vendor specific names by default
String vendorProp = JsUtils.camelize("-" + prefix + "-" + prop);
if (JsUtils.hasProperty(divStyle, vendorProp)) {
}
private static String property(String prop) {
- if (transformRegex.test(prop)) {
+ if (Transform.isTransform(prop)) {
return transform;
}
return prop.replaceFirst("^(margin|padding).+$", "$1");
@Override
public String css(String prop, boolean force) {
if ("transform".equals(prop)) {
- return isEmpty() ? "" : getTransform(get(0), null).toString();
+ return isEmpty() ? "" : Transform.getInstance(get(0), null).toString();
} else if ("transformOrigin".equals(prop)) {
return super.css(transformOrigin, force);
} else if ("transition".equals(prop)) {
return super.css(transition, force);
- } else if (transformRegex.test(prop)) {
- return isEmpty() ? "" : getTransform(get(0), null).get(prop);
+ } else if (Transform.isTransform(prop)) {
+ return isEmpty() ? "" : Transform.getInstance(get(0), null).get(prop);
} else {
- String ret = super.css(prop, force);
- return ret;
+ return super.css(prop, force);
}
}
public Transitions css(String prop, String value) {
if ("transform".equals(prop)) {
for (Element e : elements()) {
- Transform t = getTransform(e, value);
+ Transform t = Transform.getInstance(e, value);
getStyleImpl().setStyleProperty(e, transform, t.toString());
}
} else if ("transformOrigin".equals(prop)) {
super.css(transformOrigin, value);
} else if ("transition".equals(prop)) {
super.css(transition, value);
- } else if (transformRegex.test(prop)) {
+ } else if (Transform.isTransform(prop)) {
for (Element e : elements()) {
- Transform t = getTransform(e, null);
+ Transform t = Transform.getInstance(e, null);
t.setFromString(prop, value);
getStyleImpl().setStyleProperty(e, transform, t.toString());
}
return this;
}
- private List<String> filterTransitionPropertyNames(Properties p) {
+ public static List<String> filterTransitionPropertyNames(Properties p) {
List<String> ret = new ArrayList<String>();
for (String s : p.keys()) {
if (invalidTransitionNamesRegex.test(s)) {
return ret;
}
- private Transform getTransform(Element e, String initial) {
- Transform t = data(e, TRANSFORM);
- if (t == null || initial != null && !initial.isEmpty()) {
- t = new Transform(initial);
- data(e, TRANSFORM, t);
- }
- return t;
- }
-
/**
* The transition() method allows you to create animation effects on any numeric HTML Attribute,
* CSS property, or color using CSS3 transformations and transitions.
.transition("{x: +100, width: +40px}", 2000, EasingCurve.easeOut);
* </pre>
*/
- public Transitions transition(Object stringOrProperties, final int duration, final Easing easing, final int delay, final Function... funcs) {
- if (isEmpty()) {
- return this;
- }
-
- final Properties cssProps = (stringOrProperties instanceof String)
- ? (Properties) $$((String) stringOrProperties)
- : (Properties) stringOrProperties;
-
- final String ease = easing == null ? "ease" : easing.toString();
- final List<String> transProps = filterTransitionPropertyNames(cssProps);
- final double queuedAt = delay > 0 ? Duration.currentTimeMillis() : 0;
-
- // Use gQuery queue, so as we can chain transitions, animations etc.
- queue(new Function() {
- public void f() {
- // This is called once per element
- final String oldTransitionValue = $(this).css(transition);
- // Recompute delay based on the time spent in the queue
- int d = Math.max(0, delay - (int) (Duration.currentTimeMillis() - queuedAt));
- // Generate transition value
- String attribs = duration + "ms" + " " + ease + " " + d + "ms";
- String newTransitionValue = "";
- for (String s : transProps) {
- newTransitionValue += (newTransitionValue.isEmpty() ? "" : ", ") + s + " " + attribs;
- }
-
- final Transitions thisTrans = $(this).as(Transitions);
- // Configure animation using transition property
- thisTrans.css(transition, newTransitionValue);
- // Set all css properties for this transition using the css method in this class
- thisTrans.css(cssProps);
-
- // TODO: Use transitionEnd events once GQuery supports non-bit events
- // last time I tried, setting 'transitionEnd' made custom events fail (slideEnter)
- new Timer() {
- public void run() {
- thisTrans.css(transition, oldTransitionValue).each(funcs).dequeue();
- }
- }.schedule(d + duration);
+ public Transitions transition(Object stringOrProperties, final int duration, final Easing easing,
+ final int delay, final Function... funcs) {
+ if (!isEmpty()) {
+ Properties p = (stringOrProperties instanceof String)
+ ? $$((String) stringOrProperties)
+ : (Properties) stringOrProperties;
+ for (Element e : elements()) {
+ queueAnimation(new TransitionsClipAnimation().setEasing(easing).setProperties(p)
+ .setElement(e).setCallback(funcs), duration);
}
- });
-
+ }
return this;
}
public Transitions transition(Object stringOrProperties, int duration, String easing, int delay) {
return transition(stringOrProperties, duration, EasingCurve.valueOf(easing), delay);
}
+
+ @Override
+ protected GQAnimation createAnimation() {
+ return new TransitionsAnimation();
+ }
}
import static com.google.gwt.query.client.plugins.effects.ClipAnimation.getNormalizedValue;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.query.client.Function;
import com.google.gwt.query.client.Properties;
+import com.google.gwt.query.client.plugins.Effects.GQAnimation;
import com.google.gwt.query.client.plugins.effects.ClipAnimation.Action;
import com.google.gwt.query.client.plugins.effects.ClipAnimation.Corner;
import com.google.gwt.query.client.plugins.effects.ClipAnimation.Direction;
import com.google.gwt.query.client.plugins.effects.Fx.TransitFx;
import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.user.client.Timer;
+
+import java.util.List;
/**
* Animation effects on any numeric CSS3 property or transformation
private Direction direction;
private Action currentAction;
- public TransitionsClipAnimation(Element elem, Properties p, Function... funcs) {
- this(null, elem, p, funcs);
- }
-
- public TransitionsClipAnimation(Easing easing, Element elem, Properties p, Function... funcs) {
- super(easing, elem, p, funcs);
+ @Override
+ public GQAnimation setProperties(Properties p) {
corner = Corner.CENTER;
try {
corner = Corner.valueOf(getNormalizedValue("clip-origin", p));
action = Action.valueOf(getNormalizedValue("clip-action", p));
} catch (Exception e) {
}
- }
-
- public TransitionsClipAnimation(Element elem, Action a, Corner c, Direction d, Easing easing,
- Properties p, final Function... funcs) {
- super(easing, elem, p, funcs);
- this.action = a;
- this.corner = c;
- this.direction = d;
+ return super.setProperties(p);
}
public void onStart() {
g.hide();
}
g.css("transformOrigin", "");
- g.css("transform", "scale(1 1)");
+ g.css("transform", "");
}
}
}
String cur = g.css(key, true);
- String trsStart = cur, trsEnd = trsStart;
+ String trsStart = cur.matches("auto|initial") ? "" : cur, trsEnd = trsStart;
if ("show".equals(val)) {
g.saveCssAttrs(key);
protected Transitions g;
protected int delay = 0;
+ private String oldTransitionValue;
- public TransitionsAnimation(Element elem, Properties p, Function... funcs) {
- this(null, elem, p, funcs);
+ @Override
+ public GQAnimation setProperties(Properties p) {
+ delay = p.getInt("delay");
+ return super.setProperties(p);
}
- public TransitionsAnimation(Easing easing, Element elem, Properties p, Function... funcs) {
- super(easing, elem, p, funcs);
- delay = p.getInt("delay");
- g = $(e).as(Transitions.Transitions);
+ @Override
+ public GQAnimation setElement(Element elem) {
+ g = $(elem).as(Transitions.Transitions);
+ return super.setElement(elem);
}
- private Properties getFxProperties(boolean isStart) {
+ public TransitionsAnimation setDelay(int delay) {
+ this.delay = delay;
+ return this;
+ }
+
+ public Properties getFxProperties(boolean isStart) {
Properties p = $$();
for (int i = 0; i < effects.length(); i++) {
TransitFx fx = (TransitFx) effects.get(i);
@Override
protected Fx getFx(Element e, String key, String val, boolean hidden) {
- return computeFxProp(e, key, val, hidden);
+ return Transitions.invalidTransitionNamesRegex.test(key) ? null : computeFxProp(e, key, val, hidden);
}
@Override
}
@Override
+ public void onComplete() {
+ g.css(Transitions.transition, oldTransitionValue);
+ super.onComplete();
+ }
+
public void run(int duration) {
+ // Calculate all Fx values for this animation
onStart();
-
// Compute initial properties
Properties p = getFxProperties(true);
- g.css(p)
- // Some browsers need after setting initial properties re-flow (FF 24.4.0).
- .offset();
+ g.css(p);
+ // Some browsers need re-flow after setting initial properties (FF 24.4.0).
+ g.offset();
// Compute final properties
p = getFxProperties(false);
- g.transition(p, duration, easing, delay, new Function() {
- public void f() {
+
+ // Save old transition value
+ oldTransitionValue = g.css(Transitions.transition);
+
+ // Set new transition value
+ String newTransitionValue = "";
+ List<String> transProps = Transitions.filterTransitionPropertyNames(p);
+ String attribs = duration + "ms" + " " + easing + " " + delay + "ms";
+ for (String s : transProps) {
+ newTransitionValue += (newTransitionValue.isEmpty() ? "" : ", ") + s + " " + attribs;
+ }
+ g.css(Transitions.transition, newTransitionValue);
+
+ // Set new css properties so as the element is animated
+ g.css(p);
+
+ // Wait until transition has finished to run finish animation and dequeue
+ new Timer() {
+ public void run() {
onComplete();
}
- });
+ }.schedule(delay + duration);
}
}
$("button", e)
.click(new Function(){public void f() {
- Fx.css3 = false;
$("p", e).append(" Started... ");
GQuery.when( effect ).done(new Function(){public void f() {
$("p", e).append(" Finished! ");
$("button", e)
.bind("click", new Function(){public void f() {
- Fx.css3 = false;
$("p", e).append(" Started... ");
$("div",e).each(new Function(){public Object f(Element e, int i) {
*/
package com.google.gwt.query.client;
-import static com.google.gwt.query.client.GQuery.*;
+import static com.google.gwt.query.client.GQuery.$;
+import static com.google.gwt.query.client.GQuery.$$;
+import static com.google.gwt.query.client.GQuery.Effects;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.junit.Platform;
import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.query.client.GQuery.Offset;
-import com.google.gwt.query.client.plugins.effects.Fx;
+import com.google.gwt.query.client.plugins.Effects.GQAnimation;
import com.google.gwt.query.client.plugins.effects.Fx.ColorFx;
import com.google.gwt.query.client.plugins.effects.Fx.TransitFx;
import com.google.gwt.query.client.plugins.effects.PropertiesAnimation;
import com.google.gwt.query.client.plugins.effects.PropertiesAnimation.EasingCurve;
import com.google.gwt.query.client.plugins.effects.TransitionsAnimation;
+import com.google.gwt.query.client.plugins.effects.TransitionsAnimation.TransitionsClipAnimation;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Label;
}
public void gwtSetUp() {
- Fx.css3 = false;
if (e == null) {
testPanel = new HTML();
RootPanel.get().add(testPanel);
.toString());
prop1 = GQuery.$$("marginTop: '-110px', marginLeft: '-110px', top: '50%', left: '50%', width: '174px', height: '174px', padding: '20px'");
- PropertiesAnimation an = new PropertiesAnimation(EasingCurve.swing, g.get(0), prop1);
- an.onStart();
- an.onComplete();
+ GQAnimation an = new PropertiesAnimation().setEasing(EasingCurve.swing).setElement(g.get(0)).setProperties(prop1);
+ an.run(0);
assertEquals("cssprop=marginTop value=0 start=-110 end=0 unit=px",
PropertiesAnimation.computeFxProp(g.get(0), "marginTop", "0", false)
.toString());
prop1 = GQuery.$$("marginTop: '0', marginLeft: '0', top: '0%', left: '0%', width: '100px', height: '100px', padding: '5px'");
- an = new PropertiesAnimation(EasingCurve.swing, g.get(0), prop1);
- an.onStart();
- an.onComplete();
+ an = new PropertiesAnimation().setEasing(EasingCurve.swing).setElement(g.get(0)).setProperties(prop1);
+ an.run(0);
assertEquals("cssprop=marginTop value=-110px start=0 end=-110 unit=px",
PropertiesAnimation.computeFxProp(g.get(0), "marginTop", "-110px",
.toString());
}
+ public void testTransitionsAnimation() {
+ final GQuery m = $("<div style='top: 10px; width:50px'>foo</div>").appendTo(e);
+
+ TransitionsClipAnimation a = new TransitionsClipAnimation();
+ a.setElement(m.get(0));
+ a.setProperties($$("clip-action: show, clip-origin: top-right, scaleZ: 0.5, delay: 30, left: 100, top: +=50, rotateZ: 90, rotateY: 45deg, easing: custom, duration: 400"));
+ a.onStart();
+
+ Properties from = a.getFxProperties(true);
+ Properties to = a.getFxProperties(false);
+
+ // HTMLUnit and chrome return different decimal part
+ assertEquals("0px", from.getStr("left").replace(".0", ""));
+ assertEquals("100px", to.getStr("left").replace(".0", ""));
+ assertEquals("10px", from.getStr("top").replace(".0", ""));
+ assertEquals("60px", to.getStr("top").replace(".0", ""));
+ assertEquals("0", from.getStr("rotateZ").replace(".0", ""));
+ assertEquals("90", to.getStr("rotateZ").replace(".0", ""));
+ assertEquals("0", from.getStr("rotateY").replace(".0", ""));
+ assertEquals("45", to.getStr("rotateY").replace(".0", ""));
+ assertEquals("0 0", from.getStr("scale").replace(".0", ""));
+ assertEquals("1 1", to.getStr("scale"));
+ assertNull(to.get("delay"));
+ assertNull(to.get("easing"));
+
+ // HTMLUnit and chrome return different values
+ assertTrue(m.attr("style").contains("rigin: 100% 0%") || m.attr("style").contains("rigin: right top"));
+ assertTrue(m.attr("style").contains("top: 10px"));
+
+ a.run(1);
+ assertTrue(m.attr("style").contains("rigin: 100% 0%") || m.attr("style").contains("rigin: right top"));
+ assertTrue(m.attr("style").contains("top: 60px"));
+ }
+
public void testColorEffectParsing(){
String html = "<div id='test' style='color: #112233'>Test</div>";
$(e).html(html);