]> source.dussan.org Git - gwtquery.git/commitdiff
fix for issue 97
authorJulien Dramaix <julien.dramaix@gmail.com>
Mon, 5 Sep 2011 21:54:44 +0000 (21:54 +0000)
committerJulien Dramaix <julien.dramaix@gmail.com>
Mon, 5 Sep 2011 21:54:44 +0000 (21:54 +0000)
gwtquery-core/src/main/java/com/google/gwt/query/Query.gwt.xml
gwtquery-core/src/main/java/com/google/gwt/query/client/GQuery.java
gwtquery-core/src/main/java/com/google/gwt/query/client/impl/AttributeImpl.java [new file with mode: 0644]
gwtquery-core/src/main/java/com/google/gwt/query/client/impl/AttributeTridentImpl.java [new file with mode: 0644]
gwtquery-core/src/main/java/com/google/gwt/query/client/js/JsUtils.java
gwtquery-core/src/test/java/com/google/gwt/query/client/GQueryCoreTest.java

index a1fe178dfd332e7e8bc8e8699994f1f0de7c8788..aa3e57b7c0a59f4d2a08e939a4d75cdc2a18ca11 100644 (file)
     \r
     <!-- UI implementations -->\r
     <replace-with\r
-               class="gwtquery.plugins.commonui.client.GQuery.GQueryUiImplTrident">\r
+               class="com.google.gwt.query.client.plugins.UiPlugin.GQueryUiImplTrident">\r
                <when-type-is\r
-                       class="gwtquery.plugins.commonui.client.GQuery.GQueryUiImpl" />\r
+                       class="com.google.gwt.query.client.plugins.UiPlugin.GQueryUiImpl" />\r
                <any>\r
                        <when-property-is name="user.agent" value="ie6" />\r
                        <when-property-is name="user.agent" value="ie8" />\r
                </any>\r
        </replace-with>\r
+       \r
+       <!-- Attribute setter implementations -->\r
+    <replace-with\r
+               class="com.google.gwt.query.client.impl.AttributeTridentImpl">\r
+               <when-type-is\r
+                       class="com.google.gwt.query.client.impl.AttributeImpl" />\r
+               <when-property-is name="user.agent" value="ie6" />\r
+\r
+       </replace-with>\r
 \r
     <!-- IE8 needs the iframe where the js of app is loaded set to standard in order \r
          to use the queryAll native selector -->    \r
index 4a4daffd621e41acfc783a4f3ebf23dfe1c5c8cc..6862e7bf4153b59cd1801e8183e66454aafb2b7e 100644 (file)
@@ -34,11 +34,11 @@ import com.google.gwt.dom.client.OptionElement;
 import com.google.gwt.dom.client.SelectElement;\r
 import com.google.gwt.dom.client.Style.Display;\r
 import com.google.gwt.dom.client.Style.HasCssName;\r
-import com.google.gwt.dom.client.StyleInjector.StyleInjectorImpl;\r
 import com.google.gwt.dom.client.TextAreaElement;\r
 import com.google.gwt.query.client.css.HasCssValue;\r
 import com.google.gwt.query.client.css.TakesCssValue;\r
 import com.google.gwt.query.client.css.TakesCssValue.CssSetter;\r
+import com.google.gwt.query.client.impl.AttributeImpl;\r
 import com.google.gwt.query.client.impl.DocumentStyleImpl;\r
 import com.google.gwt.query.client.impl.SelectorEngine;\r
 import com.google.gwt.query.client.js.JsCache;\r
@@ -148,10 +148,12 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
 \r
   // Sizzle POS regex : usefull in some methods\r
   private static final String POS_REGEX = ":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\))?(?=[^\\-]|$)";\r
-\r
\r
   private static JsRegexp tagNameRegex = new JsRegexp("<([\\w:]+)");\r
   \r
   private static final JsNamedArray<TagWrapper> wrapperMap;\r
+  \r
+  private static AttributeImpl attributeDelegate = GWT.create(AttributeImpl.class);\r
 \r
   static {\r
     TagWrapper tableWrapper = new TagWrapper(1, "<table>", "</table>");\r
@@ -885,10 +887,8 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
    * Properties("src: 'test.jpg', alt: 'Test Image'"))\r
    */\r
   public GQuery attr(Properties properties) {\r
-    for (Element e : elements) {\r
-      for (String name : properties.keys()) {\r
-        e.setAttribute(JsUtils.hyphenize(name), properties.getStr(name));\r
-      }\r
+    for (String name : properties.keys()) {\r
+      attributeDelegate.setAttribute(this, JsUtils.hyphenize(name), properties.getStr(name));\r
     }\r
     return this;\r
   }\r
@@ -902,7 +902,7 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
   public String attr(String name) {\r
     return isEmpty() ? "" : get(0).getAttribute(name);\r
   }\r
-\r
+  \r
   /**\r
    * Set a single property to a computed value, on all matched elements.\r
    */\r
@@ -910,36 +910,21 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
     int i = 0;\r
     for (Element e : elements) {\r
       Object val = closure.f(e.<com.google.gwt.dom.client.Element>cast(), i++);\r
-      if (val != null) {\r
-        setElementAttribute(e, key, String.valueOf(val));\r
-      }\r
+      attributeDelegate.setAttribute($(e), key, val);\r
     }\r
     return this;\r
-  }\r
-  \r
+  }  \r
+  
   /**\r
    * Set a single property to a value, on all matched elements.\r
    */\r
-  public GQuery attr(String key, boolean value) {\r
-    String val = value ? key : null;\r
-    for (Element e : elements) {\r
-      setElementAttribute(e, key, val);\r
-    }\r
+  public GQuery attr(String key, Object value) {\r
+    assert key != null : "key cannot be null";\r
+    attributeDelegate.setAttribute(this, key, value);\r
     return this;\r
   }\r
   \r
-  /**\r
-   * Set a single property to a value, on all matched elements.\r
-   */\r
-  public GQuery attr(String key, String value) {\r
-    if (value == null) {\r
-      return removeAttr(key);\r
-    }\r
-    for (Element e : elements) {\r
-      e.setAttribute(key, value);\r
-    }\r
-    return this;\r
-  }\r
\r
 \r
   /**\r
    * Insert content before each of the matched elements. The elements must\r
@@ -3092,9 +3077,7 @@ public class GQuery implements Lazy<GQuery, LazyGQuery> {
    * Remove the named attribute from every element in the matched set.\r
    */\r
   public GQuery removeAttr(String key) {\r
-    for (Element e : elements) {\r
-      e.removeAttribute(key);\r
-    }\r
+    attributeDelegate.removeAttribute(this, key);\r
     return this;\r
   }\r
 \r
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 (file)
index 0000000..15041a2
--- /dev/null
@@ -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 <code>e.setAttribute()</code> 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 (file)
index 0000000..ea65030
--- /dev/null
@@ -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);
+  }
+
+}
index e315aad2a910a21b6eb023aaa89315f42a28dbe3..735478e8deb24098d26130d80e0ff44f3f6ae1ac 100644 (file)
@@ -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 <code>name</code> defined.
+   */
+  public static native boolean hasProperty(JavaScriptObject o, String name)/*-{
+    return name in o;
+  }-*/;
 
   /**
    * Remove duplicates from an elements array
index e3bd3e9f11fc7d0a85673e6fef27fbc9b2761c40..0149df5a6b978c86ccd01a4cf2e1a4321d7a17a2 100644 (file)
@@ -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("<input type='checkbox' id='cb' name='cb' value='1' />");
     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 = $("<div></div>test<!-- a comment-->");
+    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 = $("<input type='text' value='blop'></input>");
+    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() {