Attributes and Properties page

Learn about the attributes and properties on an element.

Overview

We will learn about:

  • The difference between attributes and properties
  • How to set and read styles on an element

Slides

Setup

Run the following example in CodePen:

<div id="qunit"></div>
<div id="qunit-fixture"></div>
<link rel="stylesheet" href="//code.jquery.com/qunit/qunit-1.12.0.css">
<script src="//code.jquery.com/qunit/qunit-1.12.0.js"></script>
<script src="//bitovi.github.io/academy/static/scripts/jquery-test.js"></script>
<link rel="stylesheet" href="//bitovi.github.io/academy/static/scripts/jquery-test.css">
<script type="module">
(function() {
  $ = function(selector) {
    if ( !(this instanceof $) ) {
      return new $(selector);
    }
    var elements;
    if (typeof selector === "string") {
      elements = document.querySelectorAll(selector);
    } else if ($.isArrayLike(selector)) {
      elements = selector;
    }
    [].push.apply(this, elements);
  };

  $.extend = function(target, object) {
    for (var prop in object) {
      if (object.hasOwnProperty(prop)) {
        target[prop] = object[prop];
      }
    }
    return target;
  };

  // Static methods
  $.extend($, {
    isArray: function(obj) {
      return Object.prototype.toString.call(obj) === "[object Array]";
    },
    isArrayLike: function(obj) {
      return obj &&
        typeof obj === "object" &&
        (   obj.length === 0 ||
            typeof obj.length === "number" &&
            obj.length > 0 &&
            obj.length - 1 in obj );

    },
    each: function(collection, cb) {
      if ($.isArrayLike(collection)) {
        for (var i = 0; i < collection.length; i++) {
          if (cb.call(this, i, collection[i]) === false) {
            break;
          }
        }
      } else {
        for (var prop in collection) {
          if (collection.hasOwnProperty(prop)) {
            if (cb.call(this, prop, collection[prop]) === false) {
              break;
            }
          }
        }
      }
      return collection;
    },
    makeArray: function(arr) {
      if ($.isArray(arr)) {
        return arr;
      }
      var array = [];
      $.each(arr, function(i, item) {
        array[i] = item;
      });
      return array;
    },
    proxy: function(fn, context) {
      return function() {
        return fn.apply(context, arguments);
      };
    }
  });

  function makeTraverser(traverser) {
    return function() {
      var elements = [], args = arguments;
      $.each(this, function(i, element) {
        var els = traverser.apply(element, args);
        if ($.isArrayLike(els)) {
          elements.push.apply(elements, els);
        } else if (els) {
          elements.push(els);
        }
      });
      return $(elements);
    };
  }

  $.extend($.prototype, {
      html: function(newHtml) {
        if(arguments.length) {
          return $.each(this, function(i, element) {
            element.innerHTML = newHtml;
          });
        } else {
          return this[0].innerHTML;
        }
      },
      val: function(newVal) {
        if(arguments.length) {
          return $.each(this, function(i, element) {
            element.value = newVal;
          });
        } else {
          return this[0].value;
        }
      },
      text: function(newText) {
        if (arguments.length) {
          return $.each(this, function(i, element) {
            element.textContent = newText;
          });
        } else {
          return this[0].textContent;
        }
      },
      find: makeTraverser(function(selector) {
        return this.querySelectorAll(selector);
      }),
      parent: makeTraverser(function() {
        return this.parentNode;
      }),
      next: makeTraverser(function() {
        return this.nextElementSibling;
      }),
      prev: makeTraverser(function() {
        return this.previousElementSibling;
      }),
      children: makeTraverser(function() {
        return this.children;
      }),
      attr: function(attrName, value) { },
      css: function(cssPropName, value) { },
      addClass: function(className) { },
      removeClass: function(className) { }
  });

})();
</script>

Each exercise builds on the previous exercise. There is a completed solution at the end of this page.

Exercise: collection.attr( attrName [,value] )

The problem

collection.attr either:

  • Gets the value of an attribute for the first element in the set of matched elements, or
  • sets one or more attributes for every matched element.
<a id="link-less">Bitovi</a>
<script type="module">
  import "https://unpkg.com/jquery@3/dist/jquery.js";

  $("a").attr("href", "https://bitovi.com");
</script>

Click to see test code

QUnit.test("$.fn.attr", function () {
  equal($("#qunit-fixture").attr("id"), "qunit-fixture", "can read id");

  $("#qunit-fixture").html("<span></span><span></span>");

  $("#qunit-fixture span").attr("foo", "bar");

  equal(
    $("#qunit-fixture span")[0].getAttribute("foo"),
    "bar",
    "attribute set successfully"
  );
  equal(
    $("#qunit-fixture span")[1].getAttribute("foo"),
    "bar",
    "attribute set successfully"
  );

  $("#qunit-fixture span")[0].setAttribute("foo", "BAR");

  equal(
    $("#qunit-fixture span").attr("foo"),
    "BAR",
    "read the first item in the collection’s attr"
  );
});

What you need to know

The solution

Click to see the solution

    attr: function(attrName, value) {
      if (arguments.length == 2) {
        return $.each(this, function(i, element) {
          element.setAttribute(attrName, value);
        });
      } else {
        return this[0] && this[0].getAttribute(attrName);
      }
    },

Exercise: collection.css( propertyName [,value] )

The problem

collection.css either:

  • Gets the value of a computed style property for the first element in the set of matched elements,
  • or sets one or more CSS properties for every matched element.
<div>Foo Bar</div>
<script type="module">
  import "https://unpkg.com/jquery@3/dist/jquery.js";

  $("div").css("backgroundColor", "green");
</script>

Click to see test code

QUnit.test("$.fn.css", function () {
  $("#qunit-fixture").html("<span>Content</span><span>Second</span>");

  equal($("#qunit-fixture span").css("padding-left"), "20px");

  $("#qunit-fixture span").css("paddingLeft", "40px");

  equal(
    $("#qunit-fixture span").css("padding-left"),
    "40px",
    "first span set to 40px"
  );
  equal(
    $("#qunit-fixture span:nth-child(2)").css("padding-left"),
    "40px",
    "second span set to 40px"
  );
});

What you need to know

  • The style property is used to set and get the inline style of an element.

    <div id="theDiv">theDiv</div>
    <script type="module">
      theDiv.style.color = "red";
    
      console.log(theDiv.outerHTML);
      // Logs "<div id="theDiv" style="color: red;">theDiv</div>"
    </script>
    
  • The window.getComputedStyle returns an object containing the values of all CSS properties of an element.

    <p id="theP">Hello</p>
    <style>
      p {
        color: green;
      }
    </style>
    <script type="module">
      let computedStyles = window.getComputedStyle(theP);
      console.log(computedStyles.getPropertyValue("color"));
      // logs "rgb(0, 128, 0)"
    </script>
    

The solution

Click to see the solution

    css: function(cssPropName, value) {
      if (arguments.length == 2) {
        return $.each(this, function(i, element) {
          element.style[cssPropName] = value;
        });
      } else {
        return this[0] &&
          window.getComputedStyle(this[0])
            .getPropertyValue(cssPropName);
      }
    },

Bonus Exercise: collection.addClass(className) and collection.removeClass(className)

The problem

collection.addClass adds a class to each element’s className. collection.removeClass removes a class to each element’s className.

The following changes the <div> from green to red after one second.

<style>
  .red {
    background-color: red;
  }
  .green {
    background-color: green;
  }
</style>
<div class="red" id="hi">Hello</div>
<script type="module">
  import "https://unpkg.com/jquery@3/dist/jquery.js";

  setTimeout(function () {
    $("#hi").addClass("green").removeClass("red");
  }, 1000);
</script>

Click to see test code

QUnit.test("$.fn.addClass and $.fn.removeClass", function () {
  var count = function (reg, str) {
    var c = 0;
    str.replace(reg, function () {
      c++;
    });
    return c;
  };

  var $divs = $("#qunit-fixture")
    .html('<div class="foo"></div><div class="foob"></div>')
    .children();

  $divs.addClass("foo");

  equal(1, count(/foo/, $divs[0].className), "only one foo");
  equal(1, count(/foo/, $divs[1].className), "only one foo");

  $divs.addClass("foob");

  equal(1, count(/foob/, $divs[0].className), "only one foo");
  equal(1, count(/foob/, $divs[1].className), "only one foo");

  $divs.removeClass("foob");
  equal(0, count(/foob/, $divs[0].className), "only one foo");
  equal(0, count(/foob/, $divs[1].className), "only one foo");

  $divs.removeClass("foo");
  equal(0, count(/foo/, $divs[0].className), "only one foo");
  equal(0, count(/foo/, $divs[1].className), "only one foo");
});

What you need to know

  • An element’s classList lets you add and remove class names on it.

    <style>
      .red {
        background-color: red;
      }
      .green {
        background-color: green;
      }
    </style>
    <div class="red" id="hi">Hello</div>
    <script type="module">
      setTimeout(function () {
        hi.classList.add("green");
        hi.classList.remove("red");
      }, 1000);
    </script>
    

The solution

Click to see the solution

      addClass: function(className) {
        return $.each(this, function(i, element) {
          element.classList.add(className);
        });
      },
      removeClass: function(className) {
        return $.each(this, function(i, element) {
          element.classList.remove(className);
        });
      }

Complete Solution

Click to see completed solution

<div id="qunit"></div>
<div id="qunit-fixture"></div>
<link rel="stylesheet" href="//code.jquery.com/qunit/qunit-1.12.0.css">
<script src="//code.jquery.com/qunit/qunit-1.12.0.js"></script>
<script src="//bitovi.github.io/academy/static/scripts/jquery-test.js"></script>
<link rel="stylesheet" href="//bitovi.github.io/academy/static/scripts/jquery-test.css">
<script type="module">
(function() {
  $ = function(selector) {
    if ( !(this instanceof $) ) {
      return new $(selector);
    }
    var elements;
    if (typeof selector === "string") {
      elements = document.querySelectorAll(selector);
    } else if ($.isArrayLike(selector)) {
      elements = selector;
    }
    [].push.apply(this, elements);
  };

  $.extend = function(target, object) {
    for (var prop in object) {
      if (object.hasOwnProperty(prop)) {
        target[prop] = object[prop];
      }
    }
    return target;
  };

  // Static methods
  $.extend($, {
    isArray: function(obj) {
      return Object.prototype.toString.call(obj) === "[object Array]";
    },
    isArrayLike: function(obj) {
      return obj &&
        typeof obj === "object" &&
        (   obj.length === 0 ||
            typeof obj.length === "number" &&
            obj.length > 0 &&
            obj.length - 1 in obj );

    },
    each: function(collection, cb) {
      if ($.isArrayLike(collection)) {
        for (var i = 0; i < collection.length; i++) {
          if (cb.call(this, i, collection[i]) === false) {
            break;
          }
        }
      } else {
        for (var prop in collection) {
          if (collection.hasOwnProperty(prop)) {
            if (cb.call(this, prop, collection[prop]) === false) {
              break;
            }
          }
        }
      }
      return collection;
    },
    makeArray: function(arr) {
      if ($.isArray(arr)) {
        return arr;
      }
      var array = [];
      $.each(arr, function(i, item) {
        array[i] = item;
      });
      return array;
    },
    proxy: function(fn, context) {
      return function() {
        return fn.apply(context, arguments);
      };
    }
  });

  function makeTraverser(traverser) {
    return function() {
      var elements = [], args = arguments;
      $.each(this, function(i, element) {
        var els = traverser.apply(element, args);
        if ($.isArrayLike(els)) {
          elements.push.apply(elements, els);
        } else if (els) {
          elements.push(els);
        }
      });
      return $(elements);
    };
  }

  $.extend($.prototype, {
      html: function(newHtml) {
        if(arguments.length) {
          return $.each(this, function(i, element) {
            element.innerHTML = newHtml;
          });
        } else {
          return this[0].innerHTML;
        }
      },
      val: function(newVal) {
        if(arguments.length) {
          return $.each(this, function(i, element) {
            element.value = newVal;
          });
        } else {
          return this[0].value;
        }
      },
      text: function(newText) {
        if (arguments.length) {
          return $.each(this, function(i, element) {
            element.textContent = newText;
          });
        } else {
          return this[0].textContent;
        }
      },
      find: makeTraverser(function(selector) {
        return this.querySelectorAll(selector);
      }),
      parent: makeTraverser(function() {
        return this.parentNode;
      }),
      next: makeTraverser(function() {
        return this.nextElementSibling;
      }),
      prev: makeTraverser(function() {
        return this.previousElementSibling;
      }),
      children: makeTraverser(function() {
        return this.children;
      }),
      attr: function(attrName, value) {
        if (arguments.length == 2) {
          return $.each(this, function(i, element) {
            element.setAttribute(attrName, value);
          });
        } else {
          return this[0] && this[0].getAttribute(attrName);
        }
      },
      css: function(cssPropName, value) {
        if (arguments.length == 2) {
          return $.each(this, function(i, element) {
            element.style[cssPropName] = value;
          });
        } else {
          return this[0] &&
            window.getComputedStyle(this[0])
              .getPropertyValue(cssPropName);
        }
      },
      addClass: function(className) {
        return $.each(this, function(i, element) {
          element.classList.add(className);
        });
      },
      removeClass: function(className) {
        return $.each(this, function(i, element) {
          element.classList.remove(className);
        });
      }
  });

})();
</script>