From 7cec110463172eccc470e0281cf969039c9406c6 Mon Sep 17 00:00:00 2001 From: Julien Dramaix Date: Mon, 5 Sep 2011 21:54:44 +0000 Subject: [PATCH] fix for issue 97 --- .../java/com/google/gwt/query/Query.gwt.xml | 13 +- .../com/google/gwt/query/client/GQuery.java | 47 ++-- .../gwt/query/client/impl/AttributeImpl.java | 223 ++++++++++++++++++ .../client/impl/AttributeTridentImpl.java | 118 +++++++++ .../google/gwt/query/client/js/JsUtils.java | 8 + .../gwt/query/client/GQueryCoreTest.java | 90 +++++-- 6 files changed, 449 insertions(+), 50 deletions(-) create mode 100644 gwtquery-core/src/main/java/com/google/gwt/query/client/impl/AttributeImpl.java create mode 100644 gwtquery-core/src/main/java/com/google/gwt/query/client/impl/AttributeTridentImpl.java diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml b/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml index a1fe178d..aa3e57b7 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml +++ b/gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml @@ -83,14 +83,23 @@ + class="com.google.gwt.query.client.plugins.UiPlugin.GQueryUiImplTrident"> + class="com.google.gwt.query.client.plugins.UiPlugin.GQueryUiImpl" /> + + + + + + + diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java index 4a4daffd..6862e7bf 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java @@ -34,11 +34,11 @@ import com.google.gwt.dom.client.OptionElement; import com.google.gwt.dom.client.SelectElement; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.HasCssName; -import com.google.gwt.dom.client.StyleInjector.StyleInjectorImpl; import com.google.gwt.dom.client.TextAreaElement; import com.google.gwt.query.client.css.HasCssValue; import com.google.gwt.query.client.css.TakesCssValue; import com.google.gwt.query.client.css.TakesCssValue.CssSetter; +import com.google.gwt.query.client.impl.AttributeImpl; import com.google.gwt.query.client.impl.DocumentStyleImpl; import com.google.gwt.query.client.impl.SelectorEngine; import com.google.gwt.query.client.js.JsCache; @@ -148,10 +148,12 @@ public class GQuery implements Lazy { // Sizzle POS regex : usefull in some methods private static final String POS_REGEX = ":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\))?(?=[^\\-]|$)"; - + private static JsRegexp tagNameRegex = new JsRegexp("<([\\w:]+)"); private static final JsNamedArray wrapperMap; + + private static AttributeImpl attributeDelegate = GWT.create(AttributeImpl.class); static { TagWrapper tableWrapper = new TagWrapper(1, "", "
"); @@ -885,10 +887,8 @@ public class GQuery implements Lazy { * Properties("src: 'test.jpg', alt: 'Test Image'")) */ public GQuery attr(Properties properties) { - for (Element e : elements) { - for (String name : properties.keys()) { - e.setAttribute(JsUtils.hyphenize(name), properties.getStr(name)); - } + for (String name : properties.keys()) { + attributeDelegate.setAttribute(this, JsUtils.hyphenize(name), properties.getStr(name)); } return this; } @@ -902,7 +902,7 @@ public class GQuery implements Lazy { public String attr(String name) { return isEmpty() ? "" : get(0).getAttribute(name); } - + /** * Set a single property to a computed value, on all matched elements. */ @@ -910,36 +910,21 @@ public class GQuery implements Lazy { int i = 0; for (Element e : elements) { Object val = closure.f(e.cast(), i++); - if (val != null) { - setElementAttribute(e, key, String.valueOf(val)); - } + attributeDelegate.setAttribute($(e), key, val); } return this; - } - + } + /** * Set a single property to a value, on all matched elements. */ - public GQuery attr(String key, boolean value) { - String val = value ? key : null; - for (Element e : elements) { - setElementAttribute(e, key, val); - } + public GQuery attr(String key, Object value) { + assert key != null : "key cannot be null"; + attributeDelegate.setAttribute(this, key, value); return this; } - /** - * Set a single property to a value, on all matched elements. - */ - public GQuery attr(String key, String value) { - if (value == null) { - return removeAttr(key); - } - for (Element e : elements) { - e.setAttribute(key, value); - } - return this; - } + /** * Insert content before each of the matched elements. The elements must @@ -3092,9 +3077,7 @@ public class GQuery implements Lazy { * Remove the named attribute from every element in the matched set. */ public GQuery removeAttr(String key) { - for (Element e : elements) { - e.removeAttribute(key); - } + attributeDelegate.removeAttribute(this, key); return this; } diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/AttributeImpl.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/AttributeImpl.java new file mode 100644 index 00000000..15041a28 --- /dev/null +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/AttributeImpl.java @@ -0,0 +1,223 @@ +/* + * Copyright 2011, 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.impl; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.InputElement; +import com.google.gwt.query.client.GQuery; +import com.google.gwt.query.client.js.JsRegexp; +import com.google.gwt.query.client.js.JsUtils; + +/** + * Helper class for setting and getting attribute on an element. + * + */ +public class AttributeImpl { + + public interface AttributeSetter { + public boolean isRemoval(Object value); + + public void setAttribute(Element e, String name, Object value); + } + + /** + * Default setter using e.setAttribute() method + * + */ + protected static class DefaultSetter implements AttributeSetter { + + private static AttributeSetter INSTANCE; + + public static AttributeSetter getInstance() { + if (INSTANCE == null) { + INSTANCE = new DefaultSetter(); + } + return INSTANCE; + } + + protected DefaultSetter(){} + + public boolean isRemoval(Object value) { + return value == null; + } + + public void setAttribute(Element e, String key, Object value) { + e.setAttribute(key, String.valueOf(value)); + } + + } + + /** + * value must be set on element directly + * + */ + protected static class ValueAttrSetter extends DefaultSetter { + + private static AttributeSetter INSTANCE; + + public static AttributeSetter getInstance() { + if (INSTANCE == null) { + INSTANCE = new ValueAttrSetter(); + } + return INSTANCE; + } + + protected ValueAttrSetter(){} + + public void setAttribute(Element e, String key, Object value) { + + e.setPropertyObject("value", String.valueOf(value)); + + super.setAttribute(e, key, value); + } + + + } + + /** + * Boolean attribute : + * - Ensure to set a boolean value to the element's property with the same name if this last exists + * - Ensure to set an attribute having the value equals to the name (i.e checked="checked") + */ + private static class BooleanAttrSetter extends DefaultSetter { + + private static AttributeSetter INSTANCE; + + public static AttributeSetter getInstance() { + if (INSTANCE == null) { + INSTANCE = new BooleanAttrSetter(); + } + return INSTANCE; + } + + protected BooleanAttrSetter(){} + + public boolean isRemoval(Object value) { + return super.isRemoval(value) || Boolean.FALSE.equals(value); + } + + public void setAttribute(Element e, String key, Object value) { + + if (JsUtils.hasProperty(e, key)) { + e.setPropertyBoolean(key, true); + } + + super.setAttribute(e, key, key.toLowerCase()); + } + + } + + /** + * For button and inputs, the type cannot be changed once the element is attached to the dom ! + * + */ + private static class TypeAttrSetter extends DefaultSetter { + + private static AttributeSetter INSTANCE; + + private static JsRegexp NOT_AUTHORIZED_NODE = new JsRegexp("^(?:button|input)$","i"); + public static AttributeSetter getInstance() { + if (INSTANCE == null) { + INSTANCE = new TypeAttrSetter(); + } + return INSTANCE; + } + + protected TypeAttrSetter(){} + + public void setAttribute(Element e, String name, Object value) { + String tag = e.getNodeName(); + + if (NOT_AUTHORIZED_NODE.test(tag) && GQuery.$(e).parents("body").length() > 0){ + //TODO maybe it's better to simply do nothing... + throw new RuntimeException("You cannot change type of button or input element if the element is already attached to the dom"); + } + if ("input".equals(tag.toLowerCase()) && "radio".equals(value)){ + //some browser (IE6-9) resets value property of the input when you change the type to radio button + InputElement ie = InputElement.as(e); + String keepValue = ie.getValue(); + + super.setAttribute(ie, "type", value); + + ie.setValue(keepValue); + + }else{ + super.setAttribute(e, name, value); + } + } + } + + private static final JsRegexp BOOLEAN_ATTR_REGEX = new JsRegexp( + "^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$", + "i"); + + public void removeAttribute(GQuery gQuery, String key){ + + for (Element e : gQuery.elements()) { + if (e.getNodeType() != 1) { + continue; + } + e.removeAttribute( key ); + + if (BOOLEAN_ATTR_REGEX.test(key) && JsUtils.hasProperty(e, key)){ + e.setPropertyBoolean(key, false); + } + } + } + + public void setAttribute(GQuery gQuery, String key, Object value) { + + AttributeSetter setter = getAttributeSetter(key); + + if (setter.isRemoval(value)) { + gQuery.removeAttr(key); + return; + } + + value = fixValue(key, value); + + for (Element e : gQuery.elements()) { + int nodeType = e.getNodeType(); + + //don't set attribute on text, comment and attributes nodes + if (nodeType == 3 || nodeType == 8 || nodeType == 2) { + continue; + } + + setter.setAttribute(e, key, value); + } + + } + + protected Object fixValue(String key, Object value) { + return value; + } + + protected AttributeSetter getAttributeSetter(String key) { + + if ("type".equalsIgnoreCase(key)) { + return TypeAttrSetter.getInstance(); + + } else if ("value".equals("key")){ + return ValueAttrSetter.getInstance(); + + } else if (BOOLEAN_ATTR_REGEX.test(key)) { + return BooleanAttrSetter.getInstance(); + } + return DefaultSetter.getInstance(); + } + +} diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/AttributeTridentImpl.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/AttributeTridentImpl.java new file mode 100644 index 00000000..ea650306 --- /dev/null +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/impl/AttributeTridentImpl.java @@ -0,0 +1,118 @@ +/* + * Copyright 2011, 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.impl; + +import com.google.gwt.dom.client.Element; + +/** + * Helper class for setting and getting attribute on an element. + * + */ +public class AttributeTridentImpl extends AttributeImpl { + + /** + * use {@link AttrNodeSetter} for button element + * + */ + protected static class IEValueAttrSetter extends ValueAttrSetter { + + private static AttributeSetter INSTANCE; + + public static AttributeSetter getInstance() { + if (INSTANCE == null) { + INSTANCE = new IEValueAttrSetter(); + } + return INSTANCE; + } + + private IEValueAttrSetter() { + + } + + public void setAttribute(Element e, String key, Object value) { + + if ("button".equals(e.getTagName())) { + AttrNodeSetter.getInstance().setAttribute(e, key, value); + return; + } + + super.setAttribute(e, key, value); + } + + } + private static class AttrNodeSetter extends DefaultSetter { + + private static AttributeSetter INSTANCE; + + public static AttributeSetter getInstance() { + if (INSTANCE == null) { + INSTANCE = new AttrNodeSetter(); + } + return INSTANCE; + } + + private AttrNodeSetter() { + } + + @Override + public void setAttribute(Element e, String key, Object value) { + if (!setAttributeNode(e, key, value)) { + super.setAttribute(e, key, value); + } + + } + + } + + private static native String getAttributeNode(Element e, String name)/*-{ + var attrNode = e.getAttributeNode(name); + if (attrNode && attrNode.nodeValue !== "") { + return attrNode.nodeValue; + } + return null; + }-*/; + + private static native boolean setAttributeNode(Element e, String name, + Object value)/*-{ + var attrNode = e.getAttributeNode(name); + if (attrNode) { + attrNode.nodeValue = value; + return true; + } + return false; + }-*/; + + @Override + protected Object fixValue(String key, Object value) { + if (("width".equalsIgnoreCase(key) || "height".equalsIgnoreCase(key)) + && (key == null || "".equals(key))) { + return "auto"; + } + return value; + } + + @Override + protected AttributeSetter getAttributeSetter(String key) { + if (!"className".equals(key) + && ("name".equals(key) || "title".equals(key) || key.contains(":") || key.startsWith("on"))) { + return AttrNodeSetter.getInstance(); + } else if ("value".equals(key)) { + return IEValueAttrSetter.getInstance(); + } + return super.getAttributeSetter(key); + } + +} diff --git a/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java b/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java index e315aad2..735478e8 100644 --- a/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java +++ b/gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java @@ -136,6 +136,14 @@ public class JsUtils { public static native boolean truth(Object a) /*-{ return a ? true : false; }-*/; + + + /** + * Check if an object have already a property with name name defined. + */ + public static native boolean hasProperty(JavaScriptObject o, String name)/*-{ + return name in o; + }-*/; /** * Remove duplicates from an elements array diff --git a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryCoreTest.java b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryCoreTest.java index e3bd3e9f..0149df5a 100644 --- a/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryCoreTest.java +++ b/gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryCoreTest.java @@ -19,21 +19,11 @@ import static com.google.gwt.query.client.GQuery.$; import static com.google.gwt.query.client.GQuery.$$; import static com.google.gwt.query.client.GQuery.document; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import junit.framework.Assert; - import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.InputElement; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.OptionElement; -import com.google.gwt.dom.client.SelectElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.junit.client.GWTTestCase; @@ -54,6 +44,14 @@ import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextArea; import com.google.gwt.user.client.ui.Widget; +import junit.framework.Assert; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + /** * Test class for testing gwtquery-core api. */ @@ -978,19 +976,79 @@ public class GQueryCoreTest extends GWTTestCase { } - public void testCheckedAttr_Issue97() { + public void testAttr_Issue97() { $(e).html(""); assertNull($("#cb:checked", e).val()); + $("#cb", e).attr("checked", "checked"); - assertEquals("1", $("#cb:checked", e).val()); + assertEquals(1, $("#cb:checked", e).length()); + assertEquals(true, InputElement.as($("#cb", e).get(0)).isChecked()); + assertEquals("checked", $("#cb", e).get(0).getAttribute("checked")); + assertEquals(true, $("#cb", e).get(0).getPropertyBoolean("checked")); + $("#cb", e).removeAttr("checked"); - assertNull($("#cb:checked", e).val()); + assertEquals(0, $("#cb:checked", e).length()); + assertEquals(false, InputElement.as($("#cb", e).get(0)).isChecked()); + assertEquals("", $("#cb", e).get(0).getAttribute("checked")); + assertEquals(false, $("#cb", e).get(0).getPropertyBoolean("checked")); + $("#cb", e).attr("checked", true); - assertEquals("1", $("#cb:checked", e).val()); + assertEquals(1, $("#cb:checked", e).length()); + assertEquals(true, InputElement.as($("#cb", e).get(0)).isChecked()); + assertEquals("checked", $("#cb", e).get(0).getAttribute("checked")); + assertEquals(true, $("#cb", e).get(0).getPropertyBoolean("checked")); + $("#cb", e).attr("checked", false); - assertNull($("#cb:checked", e).val()); + assertEquals(0, $("#cb:checked", e).length()); + assertEquals(false, InputElement.as($("#cb", e).get(0)).isChecked()); + assertEquals("", $("#cb", e).get(0).getAttribute("checked")); + assertEquals(false, $("#cb", e).get(0).getPropertyBoolean("checked")); + $("#cb", e).attr("checked", ""); - assertNull($("#cb:checked", e).val()); + assertEquals(1, $("#cb:checked", e).length()); + assertEquals(true, InputElement.as($("#cb", e).get(0)).isChecked()); + assertEquals("checked", $("#cb", e).get(0).getAttribute("checked")); + assertEquals(true, $("#cb", e).get(0).getPropertyBoolean("checked")); + + GQuery gq = $("
test"); + gq.attr("class", "test1"); + + assertEquals("test1", gq.get(0).getClassName()); + assertEquals("test1", gq.attr("class")); + assertNull(gq.get(0).getPropertyString("class")); + + gq.removeAttr("class"); + assertEquals("", gq.get(0).getClassName()); + assertEquals("", gq.attr("class")); + + //test on value + $("#cb", e).attr("value", "mail"); + assertEquals("mail", InputElement.as($("#cb", e).get(0)).getValue()); + assertEquals("mail", $("#cb", e).get(0).getAttribute("value")); + + $("#cb", e).removeAttr("value"); + assertEquals("", InputElement.as($("#cb", e).get(0)).getValue()); + assertEquals("", $("#cb", e).get(0).getAttribute("value")); + + try{ + $("#cb", e).attr("type", "hidden"); + fail("Cannnot change a type of an element already attached to the dom"); + }catch (Exception e){} + + gq = $(""); + gq.attr("type", "radio"); + assertEquals("radio", InputElement.as(gq.get(0)).getType()); + assertEquals("blop", InputElement.as(gq.get(0)).getValue()); + + + + gq.attr(Properties.create("{class:'test2', disabled:true}")); + InputElement ie = InputElement.as(gq.get(0)); + + assertEquals("test2", ie.getClassName()); + assertEquals(true, ie.isDisabled()); + assertEquals("disabled", ie.getAttribute("disabled")); + } public void testWidthHeight() { -- 2.39.5