WEBDOOD.COM

JSON to XML

by webdood on Feb.02, 2015, under Software Development

I had a (somewhat bizarre) request to come up with a scheme for having JSON that gets piped out to XML. Figured I’d capture the results. It works pretty well.

///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ServiceDescriptionGeneratorSupport.js
// ======================================
// NOTE - conversion from XML to JSON done by utility found at http://www.utilities-online.info/xmltojson
///////////////////////////////////////////////////////////////////////////////////////////////////////////
var serviceDescription = {
  _structure : {
    "types" : ["Cache", "Flume", "Hadoop", "Hive", "MessagingSystem", "NoSQLDatabase", "RelationalDatabase", "Script", "Search", "Sqoop", "WebService" ],
    "services" : {
      "Cache": {
        "InstanceTemplate": {
          "Service": {
            "DNSName": "poscache",
            "Description": "Caching system for the pos service",
            "Type": "Cache",
            "Zone": "2",
            "NumOfInstances": "1",
            "Host": {
              "Vendor": "memcached",
              "Version": "1.4.15",
              "Name": "posmemcache",
              "ConfigProperties": {
                "Protocol": "TCP",
                "Port": "11230",
                "MaxConns": "1024",
                "MemorySize": "5G"
              }
            }
          }
        }
      },
      "Flume": {
        "InstanceTemplate": {
          "Service": {
            "DNSName": "datafeedflume",
            "Poolname": "datafeedflume",
            "ServiceName": "datafeedflume",
            "Description": "This is the flume service required for hadoop.",
            "Type": "Flume",
            "Zone": "2",
            "Host": {
              "DependentPackages": {
                "Package": [
                  {
                    "-type": "Java",
                    "-version": "1.6.22",
                    "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                  },
                  {
                    "-type": "Hadoop",
                    "-version": "2.0.5-alpha"
                  }
                ]
              },
              "Vendor": "Apache",
              "Version": "1.4.0"
            }
          }
        }
      },
      "Hadoop": {
        "InstanceTemplate": {
          "Service": {
            "DNSName": "datafeedhadoop",
            "Poolname": "datafeedhadoop",
            "ServiceName": "datafeedhadoop",
            "PodType": "datawarehouse",
            "Description": "This is the datafeed Hadoop cluster.",
            "Type": "Hadoop",
            "Zone": "2",
            "Host": {
              "Vendor": "Apache",
              "Version": "2.0.5-alpha",
              "DependentPackages": {
                "Package": {
                  "-type": "Java",
                  "-version": "1.6.22",
                  "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                }
              },
              "Components": {
                "HDFS": {
                  "NameNode": {
                    "DNSName": "datafeedhadooppnn",
                    "InstallRequires": {
                      "Memory": "16G",
                      "DiskSpace": "300G"
                    },
                    "ConfigProperties": { "dfs.namenode.name.dir": "/home/hadoop/dfs/name" }
                  },
                  "SecondaryNameNode": {
                    "DNSName": "datafeedhadoopsnn",
                    "InstallRequires": {
                      "Memory": "16G",
                      "DiskSpace": "300G"
                    },
                    "ConfigProperties": { "dfs.namenode.checkpoint.dir": "/home/hadoop/dfs/checkpoint-data" }
                  },
                  "DataNode": {
                    "NumInstances": "5",
                    "DNSName": "datafeedhadooppnn, datafeedhadoopsnn, datafeedhive, datafeedhadoopdn{1..2}",
                    "InstallRequires": {
                      "Memory": "16G",
                      "DiskSpace": "300G"
                    },
                    "ConfigProperties": { "dfs.datanode.data.dir": "/home/hadoop/dfs/data" }
                  },
                  "ConfigProperties": {
                    "dfs.replication": "3",
                    "hadoop.tmp.dir": "/home/hadoop/dfs/tmp"
                  }
                },
                "MapReduce": {
                  "mapreduce.framework.name": "yarn",
                  "Vendor": "Apache",
                  "Version": "2.0.5-alpha",
                  "ConfigProperties": {
                    "mapred.system.dir": "/home/hadoop/mapred/system",
                    "mapred.local.dir": "/home/hadoop/mapred/local"
                  }
                }
              }
            }
          }
        }
      },
      "Hive" : {
        "InstanceTemplate": {
          "Service": {
            "DNSName": "datafeedhive",
            "Poolname": "datafeedhive",
            "ServiceName": "datafeedhive",
            "Description": "Hive to be deployed for datafeedhadoop cluster",
            "Type": "Hive",
            "Zone": "2",
            "Host": {
              "DependentPackages": {
                "Package": [
                  {
                    "-type": "Java",
                    "-version": "1.6.22",
                    "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                  },
                  {
                    "-type": "Hadoop",
                    "-version": "2.0.5-alpha"
                  }
                ]
              },
              "Vendor": "Apache",
              "Version": "0.10.0",
              "Uses": {
                "Use": {
                  "-RelationalDatabase": "datafeedhivedb",
                  "-lookup": "jdbc/datafeedhivedb",
                  "-scope": "resource"
                }
              },
              "Schema": {
                "SCLocation": {
                  "CronJob": { "-location": "https://subversion.palm.com/services/CloudServicesV2/Development/DataWarehouse/datafeed" }
                }
              }
            }
          }
        }
      },
      "MessagingSystem" : {
        "InstanceTemplate": {
          "Service": {
            "DNSName": "posactivemq",
            "Description": "This is a JMS server used by account services.",
            "Zone": "2",
            "Type": "MessagingSystem",
            "NumberOfInstances": "1",
            "Host": {
              "Vendor": "Apache ActiveMQ",
              "Version": "5.2.0",
              "Name": "posactivemq",
              "InstallRequires": { "DiskSpace": "33MB" },
              "DependantPackages": {
                "Package": {
                  "-type": "Java",
                  "-version": "1.6.22",
                  "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                }
              },
              "TransportConnectors": {
                "Connector": [
                  {
                    "protocol": "tcp",
                    "port": "61616"
                  },
                  {
                    "protocol": "ssl",
                    "port": "61617"
                  },
                  {
                    "protocol": "stomp",
                    "port": "61613"
                  },
                  {
                    "protocol": "xmpp",
                    "port": "61222"
                  }
                ]
              }
            }
          }
        }
      },
      "NoSQLDatabase" : {
        "InstanceTemplate": {
          "Service": {
            "ServiceName": "sharedriak",
            "DNSName": "sharedriak",
            "Description": "Shared Riak cluster",
            "Zone": "3",
            "Vendor": "Basho",
            "Type": "NoSQLDatabase",
            "ContainerType": "Riak",
            "Version": "1.4.2",
            "LBConfigurations": {
              "VIPProperties": {
                "Type": "Internal",
                "Protocol": "HTTP",
                "Port": "80",
                "Interface": "Top-Down"
              },
              "NodeProperties": {
                "MinNodes": "3",
                "MaxNodes": "5",
                "Port": "8298"
              },
              "HealthMonitorProperties": {
                "Type": "HTTP",
                "Path": "/ping"
              }
            },
            "Host": [
              {
                "DNSName": "sharedriak001",
                "DependentPackages": {
                  "Package": [
                    {
                      "-type": "Erlang",
                      "-version": "R15",
                      "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                    },
                    {
                      "-type": "Riak",
                      "-version": "1.4.6",
                      "-location": "https://subversion.palm.com/services/CloudServicesV2/Databases"
                    }
                  ]
                },
                "ConfigProperties": { "SCLocation": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized/sharedriak" },
                "InstallRequires": {
                  "DiskSpace": "1T",
                  "Memory": "4G",
                  "MaxOpenFiles": "64000"
                },
                "Uses": {
                  "Use": { "-Nas": "platformnas" }
                }
              },
              {
                "DNSName": "sharedriak002",
                "DependentPackages": {
                  "Package": [
                    {
                      "-type": "Erlang",
                      "-version": "R15",
                      "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                    },
                    {
                      "-type": "Riak",
                      "-version": "1.4.6",
                      "-location": "https://subversion.palm.com/services/CloudServicesV2/Databases"
                    }
                  ]
                },
                "ConfigProperties": { "SCLocation": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized/sharedriak" },
                "InstallRequires": {
                  "DiskSpace": "1T",
                  "Memory": "4G",
                  "MaxOpenFiles": "64000"
                },
                "Uses": {
                  "Use": { "-Nas": "platformnas" }
                }
              },
              {
                "DNSName": "sharedriak003",
                "DependentPackages": {
                  "Package": [
                    {
                      "-type": "Erlang",
                      "-version": "R15",
                      "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                    },
                    {
                      "-type": "Riak",
                      "-version": "1.4.6",
                      "-location": "https://subversion.palm.com/services/CloudServicesV2/Databases"
                    }
                  ]
                },
                "ConfigProperties": { "SCLocation": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized/sharedriak" },
                "InstallRequires": {
                  "DiskSpace": "1T",
                  "Memory": "4G",
                  "MaxOpenFiles": "64000"
                },
                "Uses": {
                  "Use": { "-Nas": "platformnas" }
                }
              }
            ]
          }
        }
      },
      "RelationalDatabase" : {
        "InstanceTemplate": {
          "Service": {
            "DNSName": "posdb",
            "Description": "Database schema to host data for pos services.",
            "Type": "RelationalDatabase",
            "Zone": "2",
            "Host": {
              "Vendor": "mySQL",
              "Version": "5.5.31",
              "InstallRequires": { "TableSpace": "2T" },
              "Schema": {
                "Name": "posdb",
                "ConfigProperties": { "Port": "3306" },
                "SCLocation": { "Base": "http://subversion.palm.com/services/CloudServicesV2/Data/" },
                "AccessControl": {
                  "Account": {
                    "Name": "posuser",
                    "AccessLevel": "DML",
                    "Password": "posuser"
                  }
                }
              }
            }
          },
          "Properties": {
            "Provision": {
              "Property": [
                {
                  "-type": "Credential",
                  "-name": "Username",
                  "-value": "posuser"
                },
                {
                  "-type": "Credential",
                  "-name": "Password",
                  "-value": "posuser"
                }
              ]
            }
          }
        }
      },
      "Script" : {
        "InstanceTemplate": {
          "Service": {
            "DNSName": "roboscheduler",
            "ServiceName": "roboscheduler",
            "PodType": "core",
            "Description": "This service is to set cronjobs on hosts",
            "Type": "Script",
            "Zone": "admin",
            "Host": {
              "Container": {
                "-name": "Script",
                "ConfigProperties": {
                  "ConfigLocation": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized/roboscheduler"
                },
                "Application": {
                  "-location": "http://subversion.palm.com/services/CloudServicesV2/roboscripts/roboscheduler/",
                  "ConfigProperties": { "SCLocation": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized/roboscheduler" },
                  "ContextRoot" : "",
                  "MasterProject" : "",
                  "AboutPages" : "",
                  "VersionPage" : "",
                  "PostDeployCommand" : "",
                  "Uses": ""
                }
              }
            }
          }
        }
      },
      "Search" : {
        "InstanceTemplate": {
          "Service": {
            "ServiceName": "searches",
            "DNSName": "searches",
            "Description": "This is elastic search for the search service.",
            "Zone": "3",
            "Type": "Search",
            "ContainerType": "elasticSearch",
            "Vendor": "ElasticSearch",
            "Version": "0.19.11-3",
            "LBConfigurations": {
              "VIPProperties": {
                "Type": "Internal",
                "Protocol": "HTTP",
                "Port": "80",
                "Interface": "Top-Down"
              },
              "NodeProperties": {
                "NumNodes": "3",
                "Port": "9400"
              },
              "HealthMonitorProperties": {
                "Type": "HTTP",
                "Path": "/_nodes/_local"
              }
            },
            "Cluster": {
              "ClusterName": "MDR",
              "PathData": "/var/lib/elasticsearch/MDR",
              "PathLogs": "/var/log/elasticsearch",
              "MinMasterNodes": "2",
              "Host": [
                {
                  "DNSName": "search001",
                  "Type": "Both",
                  "DependentPackages": {
                    "Package": {
                      "-type": "Java",
                      "-version": "1.6.22",
                      "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                    }
                  },
                  "InstallRequires": {
                    "ES_HEAP_SIZE": "2G",
                    "LogsSpace": "5G",
                    "DataSpace": "1G",
                    "MaxOpenFiles": "64K"
                  },
                  "Config": { "Location": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized" }
                },
                {
                  "DNSName": "search002",
                  "Type": "Both",
                  "DependentPackages": {
                    "Package": {
                      "-type": "Java",
                      "-version": "1.6.22",
                      "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                    }
                  },
                  "InstallRequires": {
                    "ES_HEAP_SIZE": "2G",
                    "LogsSpace": "5G",
                    "DataSpace": "1G",
                    "MaxOpenFiles": "64K"
                  },
                  "Config": { "Location": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized" }
                },
                {
                  "DNSName": "search003",
                  "Type": "Both",
                  "DependentPackages": {
                    "Package": {
                      "-type": "Java",
                      "-version": "1.6.22",
                      "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                    }
                  },
                  "InstallRequires": {
                    "ES_HEAP_SIZE": "2G",
                    "LogsSpace": "5G",
                    "DataSpace": "1G",
                    "MaxOpenFiles": "64K"
                  },
                  "Config": { "Location": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized" }
                },
                {
                  "DNSName": "xcesnode001",
                  "Type": "Both",
                  "DependentPackages": {
                    "Package": {
                      "-type": "Java",
                      "-version": "1.6.22",
                      "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                    }
                  },
                  "InstallRequires": {
                    "ES_HEAP_SIZE": "2G",
                    "LogsSpace": "5G",
                    "DataSpace": "1G",
                    "MaxOpenFiles": "64K"
                  },
                  "Config": { "Location": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized" }
                }
              ]
            }
          }
        }
      },
      "Sqoop" : {
        "InstanceTemplate": {
          "Service": {
            "DNSName": "datafeedsqoop",
            "Poolname": "datafeedsqoop",
            "ServiceName": "datafeedsqoop",
            "PodType": "datawarehouse",
            "Description": "This is the Sqoop service, which should be installed on same server with hive.",
            "Type": "Sqoop",
            "Zone": "2",
            "Host": {
              "DependentPackages": {
                "Package": [
                  {
                    "-type": "Java",
                    "-version": "1.6.22",
                    "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                  },
                  {
                    "-type": "Hadoop",
                    "-version": "2.2.0"
                  }
                ]
              },
              "Vendor": "Apache",
              "Version": "2.0.5-alpha"
            }
          }
        }
      },
      "WebService" : {
        "InstanceTemplate": {
          "Service": {
            "DNSName": "pos",
            "Poolname": "pos",
            "PodType": "all",
            "ServiceName": "pos",
            "Description": "This is the proxy of services.",
            "Type": "WebService",
            "Zone": "1",
            "LBConfigurations": {
              "VIPProperties": {
                "Type": "External",
                "Protocol": "HTTPS",
                "Port": "443",
                "Interface": "Top-Down"
              },
              "NodeProperties": {
                "MinNodes": "8",
                "MaxNodes": "128",
                "Port": "9760"
              },
              "HealthMonitorProperties": {
                "Type": "HTTP",
                "Path": "/healthcheck"
              },
              "Certificates": {
                "Cert": {
                  "-type": "LB-SSL",
                  "Properties": {
                    "ClientType": "External",
                    "Wildcard": "false",
                    "Issuer": "Verisign"
                  }
                }
              }
            },
            "Host": {
              "InstallRequires": {
                "Memory": "1024M",
                "DiskSpace": "400M"
              },
              "DependentPackages": {
                "Package": {
                  "-type": "Java",
                  "-version": "1.6.22",
                  "-location": "https://subversion.palm.com/services/CloudServicesV2/Libraries"
                }
              },
              "Container": {
                "-name": "Tomcat",
                "-version": "6.0.43",
                "-location": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Base",
                "ConfigProperties": { "ConfigLocation": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized/pos" },
                "Application": {
                  "-location": "https://subversion.palm.com/services/CloudServicesV2/Development/Platform/pos",
                  "ConfigProperties": { "SCLocation": "https://subversion.palm.com/services/CloudServicesV2/Configurations/Customized/pos" },
                  "MasterProject": "pos",
                  "AboutPage": "/about",
                  "VersionPage": "/cnc/version?component=pos",
                  "Uses": {
                    "Use": [
                      {
                        "-RelationalDatabase": "posdb",
                        "-lookup": "jdbc/posdb",
                        "-scope": "resource"
                      },
                      {
                        "-Cache": "poscache",
                        "-lookup": "poscache_servers",
                        "-scope": "context"
                      },
                      {
                        "-MessagingSystem": "posactivemq",
                        "-lookup": "posactivemq_servers",
                        "-scope": "resource"
                      }
                    ]
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  ////////////////////////////////////////////////////////////////////////////////////////////////
  // init
  // ====
  ////////////////////////////////////////////////////////////////////////////////////////////////
  init : function() {
    var serviceTypes = this._structure.types.sort(),
      bigString = '


';
    for (var i=0, iEnd=serviceTypes.length;i' + serviceTypes[i] + '

';
    }
    document.getElementById('serviceTypes').innerHTML = bigString;
    this.generateXML();
  },
  ////////////////////////////////////////////////////////////////////////////////////////////////
  // generateXML
  // ===========
  ////////////////////////////////////////////////////////////////////////////////////////////////
  generateXML : function() {
    var oTypePicker = document.getElementById('serviceTypes'),
      serviceType = oTypePicker.options[oTypePicker.selectedIndex].value,
      bigString = "";
    if (serviceType!=="choose") {
      bigString = this.XMLSupport.jsonToXML(null, this._structure.services[ serviceType ], 0);
    }
    bigString = bigString.replace(/')
            break;
          case (this.utility.isArray(value)) :                        // If we have an array, we have a number of nodes with the same name
            retVal.push(this.arrayToXML(nodeName, value, level));
            break;
          case (this.utility.isObject(value)) :                       // If we have another JSON Object, recurse
            retVal.push(this.jsonToXML(nodeName, value, level));
            break;
          default :                                                   // We have a simple scalar value
            retVal.push(this.scalarToXML(nodeName, value, level));
            break;
        }
      }
      return retVal;
    },
    ////////////////////////////////////////////////////////////////////////////////////////////////
    // jsonToXML(STRING nodeName, OBJECT node, INT level) - takes an arbitrary JSON structure
    // ==================================================   and returns well-formed XML.
    // This is a recursive-friendly function. "level" indicates how many (2X) spaces indentation
    ////////////////////////////////////////////////////////////////////////////////////////////////
    jsonToXML : function(nodeName, node, level) {
      var attributePile  = [],
          elementPile    = [],
          indentation    = new Array(level * 2 + 1).join(this._constants.indentationCharacter);

      for (var key in node) {
        if (node.hasOwnProperty(key)) {
          var value = node[ key ];
          if (key.charAt(0)===this._constants.attributePrefix) {          // If we are dealing with an attribute on a node
            attributePile.push(' ' + key.substring(1) + '="' + value + '"');  // Push the attribute onto a stack
          } else {
            switch(true) {
              case (typeof(value)==="undefined" || value===null) :        // If we have no attributes or content, it is a self-closing, empty XML node
                elementPile.push(indentation + '<' + key + ' />')
                break;
              case (this.utility.isArray(value)) :                        // If we have an array, we have a number of nodes with the same name
                var arrayAsXML = this.arrayToXML(key, value, level + 1);
                for (var i=0,iEnd=arrayAsXML.length;i 1) ? new Array((level-1) * 2 + 1).join(this._constants.indentationCharacter) : "",
          retVal     = elementPile.join("");
      if (!(typeof(nodeName) == "undefined" || nodeName===null)) {
        if (elementPile.length>0) {
          if (retVal.match(/\n/)) {
            retVal = '<' + nodeName + attributes + '>\n' + retVal + closingIndentation + '\n';
          } else {
            retVal = '<' + nodeName + attributes + '>' + retVal + closingIndentation + '\n';
          }
        } else {
          retVal = '<' + nodeName + attributes + ' />\n';
        }
      }
      return retVal;
    },
    ////////////////////////////////////////////////////////////////////////////////////////////////
    // scalarToXML(STRING nodeName, STRING value, INT level) - takes a scalar value and returns well-formed XML.
    // =====================================================
    // This is a recursive-friendly function. "level" indicates how many (2X) spaces indentation
    ////////////////////////////////////////////////////////////////////////////////////////////////
    scalarToXML : function(nodeName, value) {
      var retVal      = '<' + nodeName + '>' + value + '\n';
      return retVal;
    },
    ////////////////////////////////////////////////////////////////////////////////////////////////
    // utility - XMLSupport utility methods go here
    // *******
    ////////////////////////////////////////////////////////////////////////////////////////////////
    utility : {
      isArray : function(anArray) {
        return Object.prototype.toString.apply(anArray) === "[object Array]";
      },
      isObject : function( aThing ) {
        return (typeof aThing==="object");
      }
    }
  }
}
Leave a Comment more...

<CANVAS> Spinner Revisited

by webdood on Jul.11, 2012, under Canvas, Javascript, Software Development

I previously invented a Javascript-only, CANVAS-based spinner. I went to re-use this code today and found it to be less than easy to use.

Therefore, I rejiggered a few things to make it so you can create an number of spinners on page, with each spinner having its own start, stop, display and toggle method exposed.

      function Spinner( oHTMLElement, params ) {
        if (typeof(oHTMLElement)=="string")  { oHTMLElement = document.getElementById(oHTMLElement); }
        if (oHTMLElement) {
          if (params===undefined) { params={} };
          var oCanvas = document.createElement('canvas'),
              context = null,
              height = parseFloat( getComputedStyle( oHTMLElement, null)["height"] ),
              width  = parseFloat( getComputedStyle( oHTMLElement, null)["width"] ),
              radius = Math.min(height / 2, width / 2),
              innerRadius     = params["innerRadius"] || radius * 2 * .27833333,
              outerRadius     = params["outerRadius"] || radius * .99,
              finWidth        = params["finWidth"]    ||  .08 * radius,
              finColors       = params["finColors"]   || [ "e8e8e8","f1f1f1", "fafafa", "b2b2b2", "b6b6b6", "b9b9b9", "bebebe", "c3c3c3", "c9c9c9", "d0d0d0", "d7d7d7", "dfdfdf" ],
              strokeStyle     = params["strokeStyle"] || "rgba(128,128,128,1)",
              strokeWidth     = (params["strokeWidth"]!==undefined) ? params["strokeWidth"] : 2,    // Specifies whether an outline will appear around each fin
              finColorOffset  = 0,      // This is a counter that is mod-ed around 12 to draw the spinner with appropriate color as indexed into the finColors array
              spinnerInterval = null;   // Handle to setInterval operation generating spin animation
          // Set up the <canvas> object
          oCanvas.width  = width;
          oCanvas.height = height;
          context  = oCanvas.getContext('2d');
          context.translate( radius, radius );
          oHTMLElement.appendChild( oCanvas );
          draw();
        }
        ///////////////////////////////////////////////////////////////////////////////////
        // Spinner.display(boolean bDisplay) - show/hides the parent oHTMLElement        //
        // =================================   (external method)                         //
        ///////////////////////////////////////////////////////////////////////////////////
        this.display = function( bDisplay ) {
          if ( bDisplay ) {
            oHTMLElement.style.display = 'block';
          } else {
            oHTMLElement.style.display = 'none';
          }
        }
        ///////////////////////////////////////////////////////////////////////////////////
        // draw() - this clears the previous spinner drawn, then loops through the       //
        // ======   12 fins, rotating the canvas 30 degrees each time and drawing each   //
        // fin with the appropriate color from the finColors array (internal method)     //
        ///////////////////////////////////////////////////////////////////////////////////
        function draw() {
          context.clearRect(-width,-height,width*2,height*2);
          for (var i=0;i<12;i++) {
            drawFin( finColors[ (i + finColorOffset) % 12] );
            context.rotate(-360/12 * Math.PI/180);
          }
        }
        ///////////////////////////////////////////////////////////////////////////////////
        // drawFin( string fillColor ) - this draws a single fin.  Each is made up of a  //
        // ==========================   half circle on either end of a rectangle.        //
        //  Trust me, this took a while to work out. But I overcame! (internal method)   //
        ///////////////////////////////////////////////////////////////////////////////////
        function drawFin( fillColor ) {
          var ctx = context;
          ctx.fillStyle = fillColor;
          ctx.strokeStyle = strokeStyle;
          ctx.lineWidth   = strokeWidth;
          ctx.beginPath();
          ctx.arc(outerRadius-finWidth,0,finWidth,-Math.PI/2,Math.PI/2, false);
          ctx.rect(innerRadius,-finWidth,outerRadius-innerRadius-finWidth,finWidth*2);
          ctx.arc(innerRadius,0,finWidth,Math.PI/2,-Math.PI/2,false);
          ctx.closePath();
          if (strokeWidth > 0) {
            ctx.stroke();
          }
          ctx.fill();
        }
        ///////////////////////////////////////////////////////////////////////////////////
        // Spinner.start() - sets up an interval that continues to call spin().          //
        // ===============   (external method)                                           //
        ///////////////////////////////////////////////////////////////////////////////////
        this.start = function() {
          this.display( true );
          spinnerInterval = setInterval( function() { spin(); }, 100);
        }
        ///////////////////////////////////////////////////////////////////////////////////
        // Spinner.stop() - cancels the interval used to continuously call spin().       //
        // ==============   (external method)                                            //
        ///////////////////////////////////////////////////////////////////////////////////
        this.stop = function() {
          clearInterval( spinnerInterval );
          spinnerInterval = null;
        }
        ///////////////////////////////////////////////////////////////////////////////////
        // spin() - this increments the offset into the fin color array. Really, the     //
        // ======   spinner is not "spinning", per se, but rather re-drawing the fins    //
        // in-place with a set of continuously changing colors.  (internal method)       //
        ///////////////////////////////////////////////////////////////////////////////////
        function spin() {
          finColorOffset += 1;
          draw();
        }
        this.toggle = function() {
          if (spinnerInterval === null) {
            this.start();
          } else {
            this.stop();
          }
        }
      }

Please give credit when you steal this!
by Shannon Norrell, 2010-2012

Working Example

Leave a Comment more...

Javascript extractedWebkitTranslate3DProperty WebKitCSSMatrix

by webdood on Jul.04, 2012, under CSS3, Safari-Specific, Software Development, WebKitCSSMatrix

For me, there are many times where I need to get at the x, y or z value of a -webkit-transform: translate3d({x}, {y}, {z}) property.

I had originally written the below method using .style.webkitTransform and then doing some fancy manipulations to extract out the x, y or z values from the style. However, you will find that elements will not have webkitTransform applied as a “style” to them when it is being set from a css class.

Therefore, I rewrote the method to use getComputedStyle and using its return value for ‘-webkit-transform’.

The interesting thing about this is that it returns a string representation of a WebKitCSSMatrix. Now I had to figure out how to get at the values of x, y and z from this structure.

With some trial and error, I found the following property correspondence:

.m41 – corresponds to the ‘x’ value of a WebKitCSSMatrix
.m42 – corresponds to the ‘y’ value of a WebKitCSSMatrix
.m43 – corresponds to the ‘z’ value of a WebKitCSSMatrix

Here is the main extractedWebkitTranslate3DProperty method, followed by its helper method getStyle:

extractedWebkitTranslate3DProperty

    ///////////////////////////////////////////////////////////////////////////////////////
    // extractedWebkitTranslate3DProperty([object|string] oHTMLElement, string property) //
    // ================================================================================= //
    // Extracts x,y,or z value from a webkitTranslate3D style. Returns a string value.    //
    ///////////////////////////////////////////////////////////////////////////////////////
    function extractedWebkitTranslate3DProperty( oHTMLElement, property ) {
      var retVal = 0;
      if (typeof(oHTMLElement)=="string")  { oHTMLElement = document.getElementById(oHTMLElement); }
      if (property !== undefined) {
        var webkitTransform = getStyle(oHTMLElement,'-webkit-transform');
        if (webkitTransform && webkitTransform!=='none' && webkitTransform.indexOf('matrix') > -1) {
          switch (property.toLowerCase()) {
            case "x" :
              retVal = new WebKitCSSMatrix(webkitTransform).m41;
              break;
            case "y" :
              retVal = new WebKitCSSMatrix(webkitTransform).m42;
              break;
            case "z" :
              retVal = new WebKitCSSMatrix(webkitTransform).m43;
              break;
          }
        }
      }
      return parseFloat(retVal);
    }

getStyle

    ////////////////////////////////////////////////////////////////////////////////
    // getStyle([object|string] oHTMLElement, string cssProperty)                 //
    // ==========================================================                 //
    // Returns value of a cssProperty                                             //
    ////////////////////////////////////////////////////////////////////////////////
    function getStyle(oHTMLElement, cssProperty) {
      if (typeof(oHTMLElement)=="string")  { oHTMLElement = document.getElementById(oHTMLElement); }
      var retVal = "";
      if(document.defaultView && document.defaultView.getComputedStyle){
        retVal = document.defaultView.getComputedStyle(oHTMLElement, null).getPropertyValue(cssProperty);
      } else {
        if (oHTMLElement.currentStyle){
          cssProperty = cssProperty.replace(/\-(\w)/g, function (strMatch, p1){
            return p1.toUpperCase();
          });
          retVal = oHTMLElement.currentStyle[cssProperty];
        }
      }
      return retVal;
    }
Leave a Comment :, , , , more...

Javascript animateObjectFromTo

by webdood on Jul.04, 2012, under Javascript, Objects, Software Development

This method will animate an object from a given left, top to another left, top over a specified number of milliseconds at a specified frame rate.

This is an example of how it might be called:

  var myDiv = document.getElementById('myDiv),
      currentPosition = window.getComputedStyle(myDiv,null),
      currentLeft     = parseFloat(currentPosition["left"]),
      currentTop      = parseFloat(currentPosition["top"]);
  animateObjectFromTo(
    myDiv,
    { top:currentTop, left:currentLeft },
    { top:0, left:currentLeft },
    250
  );

Notice how the method uses an inner function for the animation heavy lifting.

///////////////////////////////////////////////////////////////////////////////////////////
//  animateObjectFromTo([object|string] oHTMLElement, json From, json To, int totalTimeInMilliseconds, OPTIONAL int framesPerSecond)
//   Sets up an animation that interpolates frame animation from a beginning top, left coordinate
//   to an ending top, left coordinate over a number of milliseconds at a specified frame rate
//      totalTimeInMilliseconds - default is 1 second
//      framesPerSecond - default is 30
/////////////////////////////////////////////////////////////////////////////////////////
    animateObjectFromTo : function(oHTMLElement, from, to, totalTimeInMilliseconds, framesPerSecond) {
      if (typeof(oHTMLElement)=="string")  {
        oHTMLElement = document.getElementById(oHTMLElement);
      }
      totalTimeInMilliseconds = totalTimeInMilliseconds || 1000;
      framesPerSecond = framesPerSecond || 30;
      var currentFrame      = 0,
          numberOfFrames    = parseInt(totalTimeInMilliseconds / framesPerSecond),
          deltaTimePerFrame = totalTimeInMilliseconds/numberOfFrames,
          deltaXPerFrame    = (to.left - from.left)/numberOfFrames || 0,
          deltaYPerFrame    = (to.top - from.top)/numberOfFrames || 0;

      animate();

      function animate() {
        if (currentFrame<numberOfFrames) {
          var oCurrentStyle = document.defaultView.getComputedStyle(oHTMLElement, null);
          oHTMLElement.style.left = parseFloat(oCurrentStyle["left"]) + deltaXPerFrame + "px";
          oHTMLElement.style.top  = parseFloat(oCurrentStyle["top"]) + deltaYPerFrame + "px";
          currentFrame += 1;
          setTimeout( function() { animate(); }, deltaTimePerFrame );
        }
      }

shannon norrell

Leave a Comment more...

Javascript String Ellipse to Length

by webdood on Apr.10, 2012, under Canvas, Javascript, Software Development, Strings

A perennial problem with HTML is correctly dealing with ellipsis on text. There is the text-overflow:ellipsis trick, but there are some situations where you need to ellipse a string at a certain number of pixels.

I think I have come up with a pretty clever solution to this.
By adding a prototype to the String Object and using a dynamically generated <canvas> tag, we can accurately measure the width of a string based on a provided font and fontSize, adding ellipsis as necessary.

UPDATE

I was asked by an associate if there was a way to retain the original value of the string before it was ellipsed. In answer to that, I added the lines in red.
Now there is a property called “.originalValue” added to each string that has been ellipsed.

////////////////////////////////////////////////////////////////////////////////
//
// String.ellipseToLength(int length, OPTIONAL string fontSize, OPTIONAL string fontFamily)
// ======================
// Returns an ellipsed string measured to length using CANVAS's measureText method
// By passing in fontSize and FontFamily, you will get accurate measurements
//
////////////////////////////////////////////////////////////////////////////////
String.prototype.ellipseToLength = function( length, fontSize, fontFamily) {
  var bContinue = true,
      ctx = document.createElement('canvas').getContext('2d'),
      measuredLength,
      originalValue = retVal = this.toString(),
      firstChar = retVal.charAt(0);
  fontSize    = (typeof fontSize==="undefined") ? "12pt" : fontSize;
  fontFamily  = (typeof fontFamily==="undefined") ? "Arial" : fontFamily;
  ctx.font = fontSize + " " + fontFamily;
  if ((typeof length==="undefined") || length===0 || retVal.length===0 || ctx.measureText(firstChar).width >= length) {   // Special weird case the first character is greater than the length asked for ...
    retVal = "...";
  } else {
    measuredLength = ctx.measureText(retVal).width;
    if (measuredLength > length) {  // If the measured length of the string is larger than that specified, then more work is required
      while (bContinue) {
        retVal = retVal.slice(0,-1);
        measuredLength = ctx.measureText(retVal + '...').width;
        if (measuredLength <= length) {
          retVal = retVal + '...';
          bContinue = false;
        }

        if (retVal.length===0) {
          retVal = "..."
          bContinue = false;
        }
      }
    }
  }
  retVal.__proto__.originalValue = originalValue;
  return retVal;
}
Leave a Comment more...

Javascript Number roundToPrecision

by webdood on Nov.17, 2011, under Javascript, Numbers

Needed a cross-browser function round to a given precision. Cannot rely on toFixed()

Number.prototype.roundToPrecision = function( precision ) {
  if (precision===undefined) { precision = 2; }
  var multiplier = Math.pow(10,precision);
  return Math.round(this*multiplier)/multiplier;
}
Leave a Comment more...

Javascript Number ensureDecimalPlaces

by webdood on Nov.17, 2011, under Arrays, Javascript, Numbers

Needed a function ensure that a certain number of digits appeared after a decimal point today.

For instance 1.3 should be 1.300, 1 should be 1.000 etc.

Number.prototype.ensureDecimalPlaces = function( decimals ) {
  var tempString = this.toString(),
      pileOfPads = "000000000000000000000000",
      decimalLocation = tempString.indexOf('.');
  if (decimalLocation === -1) {
    retVal = tempString + '.' + pileOfPads.substring(0,decimals);
  } else {
    retVal = tempString + pileOfPads;
    retVal = retVal.substring(0,decimalLocation+decimals+1);
  }
  return retVal;
}

Example use:

var x = 12.3
console.log(x.ensureDecimalPlaces(3)) returns 12.300

Leave a Comment more...

HTML5 Green Screen Technique using <VIDEO> and <CANVAS>

by webdood on Sep.09, 2011, under Canvas, HTML5, Safari-Specific, Software Development

This technique allows you to composite a green-screen video over a web page.
The basic setup consists of a hidden <VIDEO> tag, another hidden <CANVAS> tag, where each frame of video is captured for further processing and a visible <CANVAS> tag where each processed frame is displayed.
When a frame of video is captured and placed into our “canvasProcessor”, each pixel is analyzed in turn. A pixel is represented by a CanvasPixelArray consisting of four values: (R)ed, (G)reen, (B)lue and (A)lpha.
When we find a pixel that fits in the range of “green”, we change that one to have a 0 (A)lpha value (and thus it becomes invisible).

Here is link to a working example.

<!DOCTYPE HTML><
<html>
<head>
  <title>Green Screen Test</title>
  <script type="text/javascript">
    greenScreenEngine = {
      _animator : null,
      _canvasProcessorContext : null,
      _canvasTargetContext : null,
      _height : 0,
      _width : 0,
      _video : null,
      handlerFor : {
        pause : function() {
          clearTimeout( this._animator );
        },
        play : function() {
          greenScreenEngine.parseAndProcessFrame();
        }
      },
      parseAndProcessFrame : function() {
        if (this._video.paused || this._video.ended) {
          return;
        }
        this._canvasProcessorContext.drawImage(this._video,0,0,this._width,this._height)
        var videoFrame     = this._canvasProcessorContext.getImageData(0,0,this._width,this._height),
            numberOfPixels = videoFrame.data.length / 4;
        for (var i=0;i 100 && b < 100) {      // Here we look for what we perceive to be a green pixel
            videoFrame.data[i * 4 + 3] = 0;        // If we find one, make its alpha channel transparent
          }
        }
        this._canvasTargetContext.putImageData(videoFrame,0,0);
        this._animator = setTimeout( function() { greenScreenEngine.parseAndProcessFrame() },0);
      },
      setCanvasProcessor : function( canvasProcessorID, scale ) {
        scale = (scale!==undefined) ? scale : 1;
        this._height = this._video.videoHeight * scale;
        this._width  = this._video.videoWidth * scale;
        var oCanvas = document.getElementById(canvasProcessorID);
        oCanvas.height = this._height;
        oCanvas.width  = this._width;
        this._canvasProcessorContext = oCanvas.getContext("2d");
        this._canvasProcessorContext.drawImage(this._video,0,0,this._width,this._height)
      },
      setCanvasTarget : function( canvasTargetID ) {
        var oCanvas = document.getElementById(canvasTargetID);
        oCanvas.height = this._height;
        oCanvas.width  = this._width;
        this._canvasTargetContext =  oCanvas.getContext("2d");
      },
      setSourceVideo : function( videoID ) {
        this._video = document.getElementById( videoID );
        this._video.addEventListener("play", greenScreenEngine.handlerFor.play)
        this._video.addEventListener("pause", greenScreenEngine.handlerFor.pause)
      }
    }
    function setup() {
      greenScreenEngine.setSourceVideo("video-green-screen");
      greenScreenEngine.setCanvasProcessor("canvas-processor",.5);
      greenScreenEngine.setCanvasTarget("canvas-target");
      greenScreenEngine._video.play();
    }
  </script>
</head>
<body>
  <div>
    <video id="video-green-screen" src="talkingHead.mp4" controls="true" tabindex="0" oncanplay="setup()" style="display:none;"></video>
  </div>
  <div>
    <canvas id="canvas-processor" style="display:none;"></canvas>
    <canvas id="canvas-target" style="-webkit-transform:rotate(90deg);position:absolute;bottom:79px;"></canvas>
  </div>
</body>
</html>>

Note - For the video I've used here, I had to play with the values a bit to find just the right range of values that mean "green." Also, the source video was rotated -90deg, so I had to apply a webkit-transform to it. To see how everything works, just comment out the display:none CSS.

Shannon Norrell

1 Comment more...

HTML5 Canvas – Drawing Dashed Lines (dashedLineFromTo)

by webdood on Jul.18, 2011, under Canvas, HTML5, Javascript

There is no way built-in way to draw dashed lines using <canvas>.
Therefore, I came up with a prototype method that hangs off of the CanvasRenderingContext2D Object that I am calling “dashedLineFromTo”

As you may expect, it simply draws a bit of a line, then skips a bit and draws more until it is done “connecting the dots” between point A and B.

My particular application involves generating a line graph with dashed lines.
This is, in effect, a series of dashed lines and adds a slight nuance inasmuch as when one dashed line is completed and another picks up where it left off, we must keep track of if the previous line ended in a lineTo (ie drawn pixels) or a moveTo (ie just moving the drawing cursor) and, with the new line segment, begin it with a lineTo or a moveTo to “finish off” the pixels that were left unpainted in the previous segment.

This sounds more complicated than it is.

    var __dashedLineFromTo = {
      isDrawing : true,
      unFinishedPixelsFromLastDash : 0
    }
    CanvasRenderingContext2D.prototype.dashedLineFromTo = function(from,to) {
      var x=from[0], y=from[1],
          dashLength        = 2,
          dx                = (to[0] - x) + .00000001,
          dy                = to[1] - y,
          slope             = dy/dx,
          distanceRemaining = Math.sqrt(dx*dx + dy*dy),
          bUnfinishedPixels = false,
          theDashLength,
          xStep;
      this.moveTo(x,y);
      while (distanceRemaining>=0.1) {
        if (__dashedLineFromTo.unFinishedPixelsFromLastDash === 0) {
          theDashLength = dashLength;
        } else {
          theDashLength = __dashedLineFromTo.unFinishedPixelsFromLastDash;
          __dashedLineFromTo.unFinishedPixelsFromLastDash = 0;
          __dashedLineFromTo.isDrawing = !__dashedLineFromTo.isDrawing
        }
        if (dashLength > distanceRemaining) { dashLength = distanceRemaining; bUnfinishedPixels=true; }
        xStep = Math.sqrt( theDashLength*theDashLength / (1 + slope*slope) );
        x += xStep;
        y += slope*xStep;
        this[__dashedLineFromTo.isDrawing ? 'lineTo' : 'moveTo'](x,y);
        distanceRemaining -= theDashLength;
        __dashedLineFromTo.isDrawing = !__dashedLineFromTo.isDrawing;
      }
      if (bUnfinishedPixels) {
        __dashedLineFromTo.unFinishedPixelsFromLastDash = theDashLength;
      }
    }
    function init() {
      var canvas = document.getElementById("canvas");
      var ctx = canvas.getContext("2d");
      ctx.beginPath();
      ctx.lineWidth = 2;
      ctx.lineCap = 'butt';
      ctx.beginPath();
      ctx.dashedLineFromTo([0.2, 1.2], [99.9, 12.8]);
      ctx.dashedLineFromTo([99.9, 12.8], [118.5, 5.0]);
      ctx.dashedLineFromTo([118.5, 5.0], [148.5, 105.0]);
      ctx.dashedLineFromTo([148.5, 105.0], [178.5, 55.0]);
      ctx.dashedLineFromTo([178.5, 55.0], [218.5, 97.3]);
      ctx.closePath();
      ctx.stroke();
    }
<body onload="init()">
   <canvas id="canvas" width="912" height="339"></canvas>
 </body>

Here is a working example:

Shannon Norrell

3 Comments more...

Javascript Bitwise Operators or Math.Floor? Which is better?

by webdood on Jul.15, 2011, under Software Development

I realized today that there are three ways to get at the floor of a value in Javascript.
There is:

  • the well-known Math.floor method
  • the lesser well-known Left-Shift <<operator (that I personally always use)
  • the even less well-known Double NOT ~~ operator
  • They all work similarly. Which is faster?

    I developed a test harness to compare the three different methods.

    The results? Math.floor is definitely the slowest.
    Safari processes the Left-shift << about 15% faster than the Double NOT ~~ operator
    Chrome effectively processes ~~ and << at the same speed
    Firefox processes Double NOT ~~ operator 6% faster than the Left-shift << operator
    IE8 processes Left-shift << operator 5% faster than the Double NOT ~~ operator

    Conclusion: use Left-shift << for a generalized, faster approach to getting at the floor of a value.

    Shannon Norrell

    Leave a Comment more...

    Looking for something?

    Use the form below to search the site:

    Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

    Blogroll

    A few highly recommended websites...

    • A List Apart
    • Dive into HTML5
    • Javascript: The Good Parts
    • QuirksMode.org