Archive

Archive for the ‘Javascript’ Category

Create hierarchical objects from flat data

February 19th, 2012

Typically when you have a menu or similar, you might have it organized as following, with several levels hierarchical:

Home
 - Products
  - Hardware
 - About us

Let’s say you get the data from the server in JSON format like this

var menudata = [{
	id: 1,
	name: 'Frontpage',
	parentId: null
}, {
	id: 2,
	name: 'Products',
	parentId: 1
}, {
	id: 3,
	name: 'Hardware',
	parentId: 2
},{
	id: 4,
	name: 'About us',
	parentId: 1
}];

To make this structured in the desired way, I added a method ByHierarchy to linq.js, an excellent javascript library for easy manipulation of data.

You use it like this:

Enumerable.From(menudata).ByHierarchy(function(d) {
	return d.parentId == null;
}, function(parent, child) {
	return parent.id == child.parentId;
}).ToArray();

The first parameter determines what elements should be on the first level. In this example we want all elements that have no parent to be on the first level.
The second parameter tells you how to connect the deeper levels.

The above code will result in the following:

[
  {
    "item": {
      "id": 1,
      "name": "Frontpage",
      "parentId": null
    },
    "level": 1,
    "children": [
      {
        "item": {
          "id": 2,
          "name": "Products",
          "parentId": 1
        },
        "level": 2,
        "children": [
          {
            "item": {
              "id": 3,
              "name": "Hardware",
              "parentId": 2
            },
            "level": 3,
            "children": []
          }
        ]
      },
      {
        "item": {
          "id": 4,
          "name": "About us",
          "parentId": 1
        },
        "level": 2,
        "children": []
      }
    ]
  }
]

(Note that the object on each level also will have a function called “getParent”, that will return the parent if it exists, here it is not visible as this is serialized using JSON.stringify, which excludes functions)

You can also use this in other ways, say an array of numbers:

Enumerable.From([2,4,5,9,3,25,16,64,8,81]).ByHierarchy(function(d) {
	return d < 10;
}, function(parent, child) {
	return parent * parent == child;
});

This will result in a root level of all the numbers less than 10 and the children will be any number that is the parent number squared, and their children will be the same etc. until there is no more numbers. The already existing methods CascadeDepthFirst and CascadeBreadthFirst. Here is an example with CascadeDepthFirst:

Enumerable.From([2,4,5,9,3,25,16,64,8,81]).ByHierarchy(function(d) {
	return d < 10;
}, function(parent, child) {
	return parent * parent == child;
}).OrderBy('$.item').CascadeDepthFirst('$.children', '$.item').ToArray();

(The first parameter to CascadeDepthFirst is the parameter that holds the children, and the other is what to select from each child)

This will produce the following :

[2,4,16,3,9,81,4,16,5,25,8,64,9,81]

Be aware that any method like OrderBy or Where etc is only applied to the top level. Here is an example with Where:

Enumerable.From([2,4,5,9,3,25,16,64,8,81]).ByHierarchy(function(d) {
	return d < 10;
}, function(parent, child) {
	return parent * parent == child;
}).Where('$.item < 4').ToArray();

This will filter so that we only get 2, and 3 at the root level. However the children will still contain 4, 16 and 9, 81.

However if you change the code as follows then you will get only children less than 4 (none in this case).

 return parent * parent == child && child < 4;

All parameters

firstLevel - elements that matches this condition will be on the first level
connectBy - if this condition is met the item will be added as a child to the parent, be aware of possible endless loops. returning true always will create endless children and fail. This method receives 2 arguments, parent, and child
orderBy - a order expression to apply to the children at the time of creating the hierarchy. This does not sort the top level as well, that you have to do after.
ascending - leave out or set to true for ascending sort or false to use descending sort.

The method will create new objects that have this structure:

{
    level : indicating the level of the current item,
    item : the original underlying data,
    children : contains any children,
    getParent : a method that returns the parent if any
}

Let me know if you have some problem or general feedback and I will try to answer them. I might also submit a pull request to the original project, and maybe they will include it as a standard method.

More examples

jQuery version:

<div id="flatlists">
	<ul id="categories" >
		<li data-subcategory="fruits">Fruits</li>
		<li>Clothes</li>
	</ul>

	<ul id="fruits">
		<li data-subcategory="citrusfruits">Citrus fruits</li>
		<li data-subcategory="plumfruits">Plum fruits</li>
	</ul>

	<ul id="plumfruits">
		<li>Red plum</li>
		<li>Yellow plum</li>
	</ul>

	<ul id="citrusfruits">
		<li>Lemon</li>
		<li>Lime</li>
		<li>Orange</li>
	</ul>
</div>

<ul id="resultlist">

</ul>
$('#flatlists li').toEnumerable().ByHierarchy(function(d) {
    return d.parent().attr('id') == 'categories';
}, function(parent, child) {
    return parent.attr('data-subcategory') == child.parent().attr('id');
}).CascadeDepthFirst('$.children').Select('$').ForEach(function(list) {

    list.item.css('padding-left', list.level * 10 + 'px');
    $('#resultlist').append(list.item);

});

Result:

The forked project is hosted at codeplex (since the original is a codeplex project) You can find it here http://linqjs.codeplex.com/SourceControl/network/forks/mokkabonna/ByHierarchy

The code is inspired by this similar LINQ C# method in an answer by Thomas Levesque over at stackoverflow.

Martin Javascript, Uncategorized , , ,

Format your json with userscript

September 8th, 2011

I have created a very small userscript for opera (might work ok with other browsers) that formats /indents/ beautifies the JSON data in a document if it’s only JSON there.

For opera to open the JSON by itself and this to work (the default is to present it as a download), you will have to enable that in opera. Go to preferences -> advanced -> downloads -> Add

Mimetype: application/json
Select open with opera

That will result in your JSON looking like this in the browser:

// ==UserScript==
// @name 		   JSON formatter
// @version        1.0
// @description	   Formats JSON if the document only contains JSON
// @compability    Only tested with Opera, might work with others
// @author		   Martin Hansen
// @website        http://martinhansen.no
// ==/UserScript==
(function(){
	var indentation = 4;//Change this to vary the indentation

	var pre = document.querySelector('body pre:only-child');
	if(!pre) return; //Don't do anything if this don't seem to be a json only document
	try{
		pre.innerHTML = JSON.stringify(JSON.parse(pre.innerHTML), null,indentation);
	}
	catch(e){
		console.log(e);
	}
})();

Either save the sourcecode directly from here, or get it from userscripts.org

Martin Javascript, opera, web

Getting the bearing/heading betweet 2 coordinates

April 14th, 2011

Thanks to rjsteward with his answer over at stackoverflow.com I have now a simple function for getting the bearing between to coordinates:

The function is meant to be used with google maps, so the parameters are google.maps.LatLng objects

function getBearing (from, to) {
            var lat1 = from.lat() * Math.PI / 180;
            var lon1 = from.lng();

            var lat2 = to.lat() * Math.PI / 180;
            var lon2 = to.lng();

            var dLon = (lon2 - lon1) * Math.PI / 180;
            var y = Math.sin(dLon) * Math.cos(lat2);
            var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);

            var bearing = Math.atan2(y, x) * 180 / Math.PI;
            if (bearing < 0) {
                bearing = bearing + 360;
            }
            return bearing;
        }

This allows you to easily set the heading in the streetview, between your “streetviewman” and the actual position you are trying to see. So that you are looking to that direction from the street.

map.getStreetView().setPov({ heading: getBearing(streetViewManPosition, myLocationPosition), zoom: 1, pitch: 0 });

Resulting in something like this:

Martin Javascript , , , ,

jQuery checkbox/radiobutton dependence plugin

March 29th, 2011

I have made a small jQuery plugin that enables you to have checkboxes and radiobuttons that depends on each other in a nested way.

The usage is very simple, just take a collection of radios or checkboxes and define an element they depend on. If you check an element that depends on something, the parent will be checked as well.

In the same way, if you uncheck a parent element that have children, then the children will be unchecked as well.

The plugin is designed to also work with jQuery UI buttons

$('#fruitlist>li>input').dependsOn('#fruits');
$('#citruslist input').dependsOn('#citrus');
<ul>
	<li><input type="radio" name="products" value="books">books</li>
	<li><input type="radio" name="products" id="fruits" value="fruits">fruits
		<ul id="fruitlist">
			<li><input type="checkbox">apples</li>
			<li><input type="checkbox" id="citrus">citrus fruits
				<ul id="citruslist">
					<li><input type="radio" name="citrus">lemons</li>
					<li><input type="radio" name="citrus">oranges</li>
				</ul>
			</li>
		</ul>
	</li>
	<li><input type="radio" name="products" value="pens">pens</li>
</ul>

Demo:


  • books
  • fruits

    • apples
    • citrus fruits

      • lemons
      • oranges
  • pens

Here is the code:

/* jQuery checkbox/radiobutton dependance plugin
* By Martin Hansen http://martinhansen.no
* MIT Licensed.
*/
(function($) {
  $.fn.dependsOn = function(parent) {
    // build main options before element iteration
    if(parent === undefined){ console.log('Parent is required'); return;   }        

    var opts = {parent: parent, value: null};

     //If parent is a radiobutton part of a group, make the group the parent
    if($(opts.parent).attr('type') == 'radio'){
      opts.origparent  = $(opts.parent);
      opts.parent = 'input[name=' +$(opts.parent).attr('name') +']';
    }  

    return this.each(function() {
    var caller = $(this);
    $.data(this, 'dependsOnOptions', opts);//Store the dependency options

    caller.bind('click iterate', function(event){
      var parent = (opts.origparent) ? opts.origparent : $(opts.parent);
      parent.attr('checked', true).trigger('iterate', ['Iterate', 'Event']);
      if (jQuery.ui)parent.button('refresh'); //If jquery ui is loaded try to refesh button
    });              

    $(opts.parent).each(function(i){
      var pp = $(this);
      //Do first time checks
      var checked = pp.attr('checked');
      if(checked){
        $.fn.dependsOn.check(pp, caller, opts);
      }
      //bind for change
      pp.change(function(event){
        $.fn.dependsOn.check($(this), caller, opts);
      });
    });

   });
  };     

   $.fn.dependsOn.check = function(parent, child, opts){
    if (!parent.is(':checked') || !$(opts.origparent).is(':checked')) {
      child.attr('checked', false).change(); //uncheck the checked child, and trigger the change event so that any potential grandchildren also gets updated
      if (jQuery.ui)parent.button('refresh'); //If jquery ui is loaded try to refesh button
    }
   };
})(jQuery);

On github: https://github.com/mokkabonna/jQuery-dependency

Edit:updated the code to use .is(‘:checked’) instead of .attr(‘checked’), as jQuery 1.6.3 returns ‘checked’ instead of true. Thanks to “all” in the comments for making me aware of if.

Martin Javascript