Blog-Archiv

Samstag, 30. April 2016

JS Map Key Gotcha

Another JavaScript gotcha:

  • if you use a non-string as map key, JS will use its string-representation as key
  • thus, with a for-in loop, you don't get out what you've put in!

Demo Source


An empty map (object) is created and assigned to variable map. Keys of different types are stored into that map, using the key also as value. The map is then iterated by a for-in loop, to receive both key and value inside the loop. Key and value types are written to the output, which finally is displayed.

Press "Run" to see that this yields:

typeof key = string, typeof(value) = object
typeof key = string, typeof(value) = function
typeof key = string, typeof(value) = number

JavaScript converts every map key to a string!

Background

You could also say that every object member is identified by a string, and nothing else but a string.

There is no difference between an object and a map in JS. For example, would you regard the following to be valid JavaScript code?


There is no difference between addressing the value via map["key"] or map.key. Thus you could also establish functions with spaces in their name on objects/maps, but you need to address them as map values then:


Consequences

Such becomes painful when you want to map things to integers, and you assume you can use these map keys as numbers then. Consider following real world example:


As you can see, the output is empty.

Now replace the

if (width === currentWidth)

in the searchWidth() function by

if (width == currentWidth)

and click "Run". Now it will work as expected. Reason is that JS performs a type conversion when encountering the "==" operator. It tries to bring both comparison values to the same type. It does not do this when using the "===" operator (identity comparison). The same applies to "!=" and "!==".
But that is not the way how we should fix this!

Mind that you always SHOULD use "===" for comparisons, this is the better programming style. It would prove that none of your variables changes its type on the fly. So the way to fix this is NOT replacing "===" by "==", but converting the string back to a number:


var intWidth = window.parseInt(width);
if (intWidth === currentWidth) {
  ....

What we learn:

  • whenever we put something as key into a map, we must convert it back to its original type when retrieving it from the map in a for-in loop.



Sonntag, 24. April 2016

JS Revealing Module Pattern

In my Blog about Encapsulation and Inheritance I listed the characteristics of an object-oriented language.
In my Blog about the JS Module Pattern I tried to explain why encapsulation is necessary to hide complexity.
In this Blog I will try to bring the Revealing Module Pattern into a reusable shape, and explain why singletons are not enough to achieve a good JavaScript code base.

Requirements

What would we like to achieve?

  • Encapsulation: having (at least) private and public fields and functions, private functions can access public functions and fields, public functions can access private functions and fields
  • Inheritance: a public function can be overwritten to implement a slightly different behaviour, and private functions calling it must then automatically meet the override
  • Calling super: a sub-object can use an overwritten implementation of its super-object
  • Abstract functions: a super-object can call functions that are implemented in the sub-object object only

Example Code

The Revealing Module Pattern is called "revealing" because it exposes just its public parts through the return-object.

In text-area below you can try out the example code by pressing the "Run" button, and you can also modify it.


I call the function animal a "module" because it contains nested functions, and it returns an object that has been built inside.

I do not consider "module" to be an adequate term for that, but I comply to the JS tradition. As "module", in this context, we could understand something that is more than a function or object (map). We can't call it "class" because JS has no type-concept.

Source Code Explanations

Every local variable inside animal, like belly or species, will survive the function call when it is referenced by the returned object in some way (and thus is in its closure). We could call such variables private instance-fields. The same for inner functions. None of the private variables and functions, like belly or getBelly(), is visible or accessible from outside.

On the other hand, any function and variable in the return-object is public. They could be overwritten by any callee, simply assigning another function to the function's name. But mind that these overrides do not have access to the private parts of the module. Also the private functions can not call the public ones, because these are just in the return object on bottom, and that object does not have a name that could be referenced.

Code inside Return

Now you could say "I can do this much shorter":

var animal = function(givenSpecies)
{
  var species = givenSpecies;
  var belly = "nothing";

  return {
    hasEaten: function() {
      return belly;
    },
    eat: function(food) {
      belly = food;
    },
    toString: function() {
      return species+" has eaten "+belly;
    }
  };
};

Putting code into the returned functions is not a good practice.

(1) First drawback is that private functions can not call the public ones, because these are in an anonymous return object.

(2) Second drawback is that functions inside the return-object can call each other only by specifying this, it would not work without:

var animal = function(givenSpecies)
{
  ....

  return {
    hasEaten: function() {
      return belly;
    },
    ....
    ,
    toString: function() {
      return species+" has eaten "+this.hasEaten();
    }
  };
};

(3) Third drawback is that you increase the nesting level. The deeper the nesting level, the more the problem of global variables comes into sight (nobody knows why they have a wrong value at the right time:-). The more lines of code see a variable, the more side-effects will take place, the more unstable everything will be. Holding variable values is the primary reason for encapsulation, but having deeply nested inner logic below is counterproductive.

var animal = function(givenSpecies)
{
  var belly = "nothing";

  ....

  return {
    sound: function() {
      return {
        loud: function() {
          // belly is read/writable here 
        },
        quiet: function() {
          // belly is read/writable here 
        }
      };
    },
    ....
  };
};

Mind that a variable like belly is visible and writable anywhere inside the animal() function, even in the deepest nested object or function.

Always try to keep the nesting level of your JS code as low as possible. It will get unreadable else, and variables in upper scopes will get uncontrollable.

Singletons prevent Inheritance

Mostly the Revealing Module Pattern is shown like this:

var animal = (function(givenSpecies)
{
  ....

  return {
    ....
  };

})();

The difference to the pattern above is that animal now is an object-instance. You can not call it any more as function to produce a new instance. It is an IIFE.

The resulting animal object is called singleton. Such is also used in object-oriented languages, but just in special cases. A singleton reduces the functionality to just one instance. If you replace functions or properties on it, the singleton instance itself would be changed. The only way to use a JS singleton for inheritance is copying it, but then you may copy a particular state of the singleton.

Instantiation modules, or let's call them factory functions, are much more useful than singletons. In the following code, animal is extended to be a named cat. Try out this example, it shows that different instances can have different food in their private belly.

var animal = function(givenSpecies)
{
  var species = givenSpecies;
  var belly = "nothing";

  var getBelly = function() {
    return belly;
  };
 
  var setBelly = function(food) {
    belly = food;
  };
 
  var toString = function() {
    return species+" has eaten "+getBelly();
  };

  return {
    hasEaten: getBelly,
    eat: setBelly,
    toString: toString
  };
};

var cat = function(givenName)
{
  var name = givenName;
  var self = animal("Cat");

  var superToString = self.toString;

  self.toString = function() {
    return name+" "+superToString();
  };

  return self;
};

var garfield = cat("Garfield");
garfield.eat("mouse");
alert(garfield.toString());

var catbert = cat("Catbert");
catbert.eat("rat");
alert(catbert.toString());

In this example, a module cat extends animal. It instantiates animal, and then replaces its toString() function. To be able to reuse the super-implementation of toString(), it stores the old function-pointer into a local variable superToString before replacing it.

Do not make it difficult to reuse source code in JS modules by implementing them as singletons. But mind that a singleton would make sense when you have no instance variables, because you would not need to instantiate it before using it, and that makes life simpler. You could call this a "data-less module".

Naming the Return

The module cat looks different than animal. Here the return-object has a name, and that name could be referenced also by private functions.

Hence the named-return form would be preferable. So why not doing also the base object with a named return object? In the following the return-object is named self, but you can give it any name except JS reserved words like this. Some call it that, another good name would be instance.

var animal = function(givenSpecies)
{
  var species = givenSpecies;
  var belly = "nothing";
  var self = {};

  var getBelly = function() {
    return belly;
  };
 
  var setBelly = function(food) {
    belly = food;
  };
 
  var sound = function() {
    if (self.hasEaten() !== "nothing")  // call public from private
      return "hummm ...";
    return "grumble ...";
  };
 
  self.hasEaten = getBelly;

  self.eat = setBelly;

  self.sound = sound;

  self.toString = function() {
    return species+" has eaten "+getBelly(); // call private from public
  };

  return self;
};

var dog = animal("Dog");
alert(dog.toString()+", sounds: "+dog.sound());
dog.eat("sausage");
alert(dog.toString()+", sounds: "+dog.sound());

Yes, you are right, why have both private and public functions doing the same?
This is just an example how public functions could call privates, and vice versa. In a real world you would do it shorter.

Mind that even if you override animal now and overwrite the hasEaten() function, the sound() implementation would work correctly by calling the replaced hasEaten(), not the original one.

Naming the return definitely is an advantage. Which now has led the Revealing Module Pattern to be what is called functional inheritance.


Now let's check if we have met our start-requirements. There is everything except the abstract function. But this is really easy: just call the function in super-object, and make sure that it is implemented then in the module that extends the super-object. The JS interpreter will search for the function only when it actually is launched, not before. In a strongly typed language like Java this would not be possible, but JavaScript is a rubber language :-)




Montag, 18. April 2016

What's HTML label for?

I saw this many times before I asked myself "Why do they do this?". I'm talking about the HTML-5 <label for="....."> tag.

    <label for="prologue">Prologue</label>
    <input type="checkbox" id="prologue"/>

The label tag is mostly used together with some input field. The label's for attribute points to the id of the input-field it labels. That means you need to create a page-globally unique id, and then write it into both the label and the input tag.

Isn't this much ado about nothing? Couldn't we simply use a span to label the field? Why use an expensive id when we must position the label before or after the input field anyway?

There are two things to say about this.

Label Forwards Clicks

The label will forward mouse clicks to the input field. That means, clicking the label is like clicking onto the input field.
A checkbox would toggle its value from a click onto its label. It wouldn't do this without the label-for.
A text field would show the editing cursor and be ready for receiving keyboard input, just from clicking onto its label.

Here is a something for trying out:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    
    <title>The "label" tag forwards click events</title>
  </head>
  
  <body>

    <label for="prologue">Prologue</label>
    <input type="checkbox" id="prologue"/>

    <label for="textarea">Text</label>
    <textarea id="textarea" cols="20" rows="6" style="vertical-align: top;"></textarea>
        
  </body>
</html>

After I had accepted the fact that label makes sense, I asked myself if it is worth. Needing to create an id on the input-field and referring to it in the label makes this somehow heavyweight. But fortunately there is a lightweight variant available.

Label can Enclose Input

Use this instead of the code above:

    <label>Prologue
      <input type="checkbox"/>
    </label>

    <br>

    <label>Text
      <textarea cols="20" rows="6" style="vertical-align: top;"></textarea>
    </label>

This does exactly the same.
Enclosing the input into the label tag makes the id dispensable.




Mittwoch, 13. April 2016

Three Steps Toward Progress

This is not about progress bars or activity-indicators :-) Here and now I want to describe the process of progress. How you can improve your abilities of any kind.

This is not a concept that I invented. A lot of people tell about this in different ways. But only few people understand the relevance of the process, and thus are able to advance in their skills. Most are trapped in their attitude to be high above the average, and that they don't need to progress because they are already there.


The Three Steps of Progress

(Let these be small steps. You will get there safe and in time.)

1. Do it

To try it means being on the way. Don't think you don't have the talent, that you are not gifted for this, that others are much better, so it does not make sense to try it. Don't even think about such things. Don't let your "best friends" discourage you.

Do not argue about the why. Just do it. You want to make a melody? Simply do it. You don't need to learn the piano. Sing it, hum it, whistle it. No need to please your parents, relatives, friends. Do it for yourself.

Don't retreat because others are so high above you, and the media show you every day how little you are. Want to become a runner? So take your feet and run. You don't need the newest jogging shoes, and super-light sweat shirts, and and and. Just do it. At least you will gain experience.

2. Finish it

You are on the way. Frustration may be rising. You may be confused, exhausted, no idea how this can go on. Either you can overcome this crisis and continue, or you find a way how to finish it gracefully. Finish it in a way that you can call an end. You want to look back without anger. Don't let yourself fall onto the ground, gasping for air, better find some beautiful final tones to finish this melody.

Do not break your attempts. Try to make them round. Take something nice with you. Set a final step, and then say "It was fun, see you next time!".

Better have an awkward undertaking than a cancelled one. There is a slight difference between these: the cancelled one will be regarded to be hopeless.

3. Look Back

Keep a record of what you done. Collect souvenirs, write a diary. When you learn to create a melody, record on your phone all the melodies you invent. When you are jogging, make a mark on your calendar ever day you go running, and note the distance you made.

Then, after some time, look back. It is important to make breaks. Sum up all the marked days on calendar, listen to all the melodies you recorded. Evaluate it. Is this the direction you wanted to go? Are you satisfied with your achievement? Did you like to do it, was it fun, did you gain experience? If any of this is true, your attempts were successful. Now decide in which way you will continue, and plan your next step.

In case you are disappointed, keep a record about what you've done anyway. Again make a memorable end to all these attempts when you really found out that this is not for you. Write down your experience, you might want to read it later. One day you may want to continue because things have changed. And then it would be interesting to hear all those melodies, or come to know how far you could run.


Nothing in life is in vain. It is up to you to learn from it, for instance, couldn't you use the experience from composing melodies to get a better runner?

Do not believe people that tell you "Why move, we won't get anywhere". Even running in circles is better than sitting around. Movement is life.




Montag, 11. April 2016

JS Get / Set Element Width / Height

This is another Blog about HTML Element Dimensions. It contains JavaScript (JS) functions to calculate the outer and inner width of elements, and to set these dimensions accordingly. I will cover block and inline-block elements (but not inline elements), and the infamous box-sizing CSS property.

Mind that my usage of the terms outer and inner for width and height differs from the jQuery meanings for outer and inner. While jQuery's .outerWidth() delivers element.offsetWidth (optionally including margin), and .innerWidth() delivers element.clientWidth, my understanding of "inner" is the usable element space within, i.e. the inner rectangle without padding, and with "outer" I describe the space an element would take as a whole, i.e. including margin.

The reason why I won't cover inline elements is that these can not be resized by setting CSS width and height. If you want to size a SPAN, you need to set it display: inline-block, or its position: fixed or absolute. Further inline elements do not include top and bottom margins into their height (will be painted over other elements when used), while they do include left and right margins into their width.

Demo

Here is some test HTML. Four different DIV elements are arranged vertically, the first two have box-sizing: content-box, the second two box-sizing: border-box. The third element has display: inline-block, all others are display: block (DIV default). All four elements have different margins, borders and paddings.

It is easy to visualize a border, you just need to give it a width and a colour. To visualize margins and paddings is not so easy. Thus I decided to enclose each demo element into a DIV wrapper that has display: inline-block (does not expand to whole width) and a dotted black border. This will visualize the margin as the space between the coloured and the dotted black border. To visualize the padding, I nested a DIV element into the demo DIV, which has 100% height to always fill up its parent entirely, and a yellow background colour. Thus it renders the padding as the space between the yellow area and the coloured border.

The challenge now is to set all these demo elements to same size. As I talked about inner and outer size, both can be done here:

  • when you trigger "Set Inner Size", all yellow rectangles should have the same dimension
  • when you trigger "Set Outer Size", all black dotted borders should have the same dimension

Pixels


DIV 1

DIV 2

DIV 3

DIV 4

Right-click on an element and trigger "Inspect Element" to see what your browser debugger displays as "Box Model". You need to focus the element with CSS class sameSize, not its parent or child (which are just for visualizing margin and padding).


Demo HTML and CSS Source

Click on left arrow to see the demo HTML and CSS.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>JS Read and Write HTML Element Width and Height</title>
    
    <style type="text/css">
      .outer {
        border: 1px dotted black; /* this border makes margin of nested DIV visible */
        margin: 4px; /* keep a little distance to other outer DIVs */
        display: inline-block; /* wrap around the element to show margin, no width 100% */
        text-align: initial; /* reset center alignment */
      }
      .inner {
        background-color: yellow; /* color makes parent padding visible */
        height: 100%; /* spread background-color to bottom */
        font-style: "sans-serif";
        font-size: 75%;
      }
      
      #one {
        border: 10px solid LightGreen;
        padding: 10px;
        margin: 10px;
      }
      #two {
        border: 20px solid orange;
        padding: 20px;
        margin: 20px;
      }
      #three {
        border: 30px solid cyan;
        padding: 30px;
        margin: 30px;
        box-sizing: border-box;
        display: inline-block;
      }
      #four {
        border: 40px solid magenta;
        padding: 40px;
        margin: 40px;
        box-sizing: border-box;
      }
    </style>
    
  </head>
     
  <body>

    <div style="text-align: center;">
  
      <div class="outer">
        <div id="one" class="sameSize">
          <div class="inner">DIV 1</div>
        </div>
      </div>
    
      <br>
        
      <div class="outer">
        <div id="two" class="sameSize">
          <div class="inner">DIV 2</div>
        </div>
      </div>
        
      <br>
        
      <div class="outer">
        <div id="three" class="sameSize">
          <div class="inner">DIV 3</div>
        </div>
      </div>
        
      <br>
        
      <div class="outer">
        <div id="four" class="sameSize">
          <div class="inner">DIV 4</div>
        </div>
      </div>
        
    </div>
        
  </body>
</html>

JS Source

Here comes the JS source code working in this demo. First the module frame:

    var elementDimensions = (function()
    {
      "use strict";
      
      var self = {};

      .....

      return self;
      
    })();

The page-gobal variable elementDimensions evaluates to a singleton object created from an immediately invoked function expression (IIFE). A function that creates an object (or a function) containing inner functions is called "module" in JS.

All following JS source goes to where the "...." is.

Reading Dimensions

      self.getInnerSize = function(element) {
        return {
          width:  self.getInnerWidth(element),
          height: self.getInnerHeight(element)
        };
      };
      
      self.getInnerWidth = function(element) {
        var style = window.getComputedStyle(element);
        var borderWidth = window.parseInt(style["border-left-width"]) + window.parseInt(style["border-right-width"]);
        var paddingWidth = window.parseInt(style["padding-left"]) + window.parseInt(style["padding-right"]);
        return element.offsetWidth - borderWidth - paddingWidth;
      };
      
      self.getInnerHeight = function(element) {
        var style = window.getComputedStyle(element);
        var borderHeight = window.parseInt(style["border-top-width"]) + window.parseInt(style["border-bottom-width"]);
        var paddingHeight = window.parseInt(style["padding-top"]) + window.parseInt(style["padding-bottom"]);
        return element.offsetHeight - borderHeight - paddingHeight;
      };
      
      
      
      self.getOuterSize = function(element) {
        return {
          width:  self.getOuterWidth(element),
          height: self.getOuterHeight(element)
        };
      };
      
      self.getOuterWidth = function(element) {
        var style = window.getComputedStyle(element);
        var marginWidth = window.parseInt(style["margin-left"]) + window.parseInt(style["margin-right"]);
        return element.offsetWidth + marginWidth;
      };
      
      self.getOuterHeight = function(element) {
        var style = window.getComputedStyle(element);
        var isInlineElement = (style.display === "inline");
        var marginHeight = isInlineElement ? 0 : window.parseInt(style["margin-top"]) + window.parseInt(style["margin-bottom"]);
        return element.offsetHeight + marginHeight;
      };

For the inner width and height we can ignore box-sizing, it will be the same for content-box and border-box. Simply subtract the padding and border from offsetWidth. This would also work for inline elements. Although they do not have a clientWidth, their offsetWidth should always be present when they are visible.

The same applies to outer width and height. We simply add margins to offsetWidth. Just when it is an inline element, we must not add them when calculating the height the element claims.

Writing Dimensions

      self.setInnerSize = function(element, innerWidth, innerHeight) {
         self.setInnerWidth(element, innerWidth);
         self.setInnerHeight(element, innerHeight);
      };
      
      self.setInnerWidth = function(element, innerWidth) {
        var style = window.getComputedStyle(element);
        var cssWidth = innerWidth;
        
        if (style["box-sizing"] === "border-box") {
          var borderWidth = window.parseInt(style["border-left-width"]) + window.parseInt(style["border-right-width"]);
          cssWidth += borderWidth;
          var paddingWidth = window.parseInt(style["padding-left"]) + window.parseInt(style["padding-right"]);
          cssWidth += paddingWidth;
        }
        
        if (cssWidth > 0)
          element.style["width"] = cssWidth+"px";
      };
      
      self.setInnerHeight = function(element, innerHeight) {
        var style = window.getComputedStyle(element);
        var cssHeight = innerHeight;
        
        if (style["box-sizing"] === "border-box") {
          var borderHeight = window.parseInt(style["border-top-width"]) + window.parseInt(style["border-bottom-width"]);
          cssHeight += borderHeight;
          var paddingHeight = window.parseInt(style["padding-top"]) + window.parseInt(style["padding-bottom"]);
          cssHeight += paddingHeight;
        }
        
        if (cssHeight > 0)
          element.style["height"] = cssHeight+"px";
      };
      
      
      
      self.setOuterSize = function(element, outerWidth, outerHeight) {
        self.setOuterWidth(element, outerWidth);
        self.setOuterHeight(element, outerHeight);
      };
      
      self.setOuterWidth = function(element, outerWidth) {
        var style = window.getComputedStyle(element);
        var marginWidth = window.parseInt(style["margin-left"]) + window.parseInt(style["margin-right"]);
        var cssWidth = outerWidth - marginWidth;
        
        if (style["box-sizing"] !== "border-box") {
          var borderWidth = window.parseInt(style["border-left-width"]) + window.parseInt(style["border-right-width"]);
          cssWidth -= borderWidth;
          var paddingWidth = window.parseInt(style["padding-left"]) + window.parseInt(style["padding-right"]);
          cssWidth -= paddingWidth;
        }
        
        if (cssWidth > 0)
          element.style["width"] = cssWidth+"px";
      };
      
      self.setOuterHeight = function(element, outerHeight) {
        var style = window.getComputedStyle(element);
        var marginHeight = window.parseInt(style["margin-top"]) + window.parseInt(style["margin-bottom"]);
        var cssHeight = outerHeight - marginHeight;
        
        if (style["box-sizing"] !== "border-box") {
          var borderHeight = window.parseInt(style["border-top-width"]) + window.parseInt(style["border-bottom-width"]);
          cssHeight -= borderHeight;
          var paddingHeight = window.parseInt(style["padding-top"]) + window.parseInt(style["padding-bottom"]);
          cssHeight -= paddingHeight;
        }
        
        if (cssHeight > 0)
          element.style["height"] = cssHeight+"px";
      };

Writing dimensions is more complex. We need to consider the box-sizing CSS property. When we want to set the inner width of an element with box-sizing: border-box, we need to add its border and padding to the given size.

Symmetrically we need to subtract the border and padding from given size when setting the outer width of an element with box-sizing: content-box.

Remember:

  • The CSS width on box-sizing: border-box is the same as offsetWidth, but for box-sizing: content-box (default), offsetWidth includes border and padding. Margins are never included, neither in clientWidth nor offsetWidth.

For the current state of this utility go to my homepage.