Nunchy Nunchy - 3 months ago 9
CSS Question

Using hasOwnProperty() to reference objects

I want to write a function in JavaScript that will apply a style object to an html element but only if that object exists.

Example, if I have the following object:

objMyObject {
"idstring": {
"style" : {
"opacity": "0.50"
}
}
};


So I use hasOwnProperty() to first check that "style" exists before trying to apply it - and this works fine.

However, let's imagine that my object has multiple styles:

objMyObject {
"idstring1": {
"style" : {
"opacity": "0.50"
}
},

"idstring2": {
"style": {
"opacity": "0.99"
}
},

"idstring3": {
"style": {
"opacity": "0.50"
}
}

};


Now, sometimes these individual style objects can be the same. The above examples are short but the objects can be much larger and hold multiple style properties, so it would be better if I could do something like:

objMyObject {
"idstring1": {
"style" : {
"opacity": "0.50"
}
},

"idstring2": {
"style": {
"opacity": "0.99"
}
},

"idstring3": {
// This particular style object has the same properties as
// idstring1
"apply": "idstring1"
}

};


So I could then do something like this:

if (objMyObject.hasOwnProperty("idstring3")) {
if (objMyObject.idString3.hasOwnProperty("apply"))
// Apply an existing, alternative style object.
//
$("#el").css(objMyObject.idstring3.apply)
else if (objMyObject.idstring3.hasOwnProperty("style"))
$("#el").css(objMyObject.idString3.style)
}


The problem is that the first call to hasOwnProperty():

if (objMyObject.hasOwnProperty("idstring3"))


Evaluates as true, which is great. However, subsequent calls fail even though the objects contain either a "style" or "apply" object.

Here's the full code I'm working with:

// JavaScript object - the "mouseout" object should apply the
// "init" object.
//
var objHeader = {
"targetID": "header",

"init": {
"style": {
"backgroundColor": "#FF0000",
"opacity": "0.00"
}
},

"load": {
"style": {
"opacity": "0.50"
}
},

"mouseover": {
"style": {
"opacity": "0.99"
}
},

// Here, the elApply() function should apply the "style" defined
// in "init".
//
"mouseout": {
"apply": "init"
}
};


Here's the two functions I'm using:

// Apply a style to an object - will return false on error or true
// on success.
//
// State is the object to be applied, for example:
//
// elApply(objHeader, "mouseout");
//
// To apply the style attached to "mouseout".
//
function elApply(
objElement,
objState
) {
// Example - if elInit calls elApply(objHeader, "mouseout");
// then will first look for objHeader.mouseout.apply, if that
// exists then it should point to another object with a style
// to be applied - if this is the case true is returned here
// and all is well.
//
if (elApply(objElement.objState, "apply")) {
alert("Applied 'apply' to " + objState);
return true;
}

// No "apply" exists, so attempt to apply a "style"
// object.
//
if (objElement.objState.hasOwnProperty("style")) {
$("#" + objElement.targetID).css(objElement.objState.style);
return true;
}

alert("Neither '" + objState + "' nor 'apply' exist in this object!");
return false;
}

function elInit(
objElement
) {
$(document).ready(function() {
var targetID = objElement.targetID;
elApply(objElement, "mouseout");
});
}


So it works like this:


  1. The elInit() function calls elApply(objElement, "mouseout");

  2. The "mouseout" property exists, and the elApply() function evaluates objElement.hasOwnProperty("mouseout") as true

  3. Checks to see if objElement.mouseout.apply exists, but apparently objElement.mouseout is undefined, despite the earlier hasOwnProperty() call suggesting "mouseout" exists.



Hope this is clear, any ideas? It's not a big deal, just be neater if the objects weren't repetitive and I could re-use already defined objects.

Perhaps there's a fundamental flaw in my logic/implementation or understanding of hasOwnProperty()? If so, is there a way for sibling objects within a parent object to reference one another in such a way?

Much obliged.

Answer

I would suggest to populate all missing styles in your object beforehand. Your elStyle() function then becomes much simpler, as it doesn't have to care about the apply property anymore.

Technically, copying a style from one element to another is just a copy of a reference to an object. So, it's a cheap operation.

The reason for the do{} while() loop in this code is to support apply 'chains' (like mouseout0 > mouseout > init in my example). You can safely remove it if this case is never encountered.

var objHeader = {
    "targetID": "header",

    "init": {
        "style": { "backgroundColor": "#FF0000", "opacity": "0.75" }
    },
    "load": {
        "style": { "opacity": "0.50" }
    },
    "mouseover": {
        "style": { "opacity": "0.99" }
    },
    "mouseout0": {
        "apply": "mouseout"
    },
    "mouseout": {
        "apply": "init"
    }
};

function populateStyle(o) {
  var success;
  
  do {
    success = false;

    Object.keys(o).filter(
      function(k) {
        return o[k].hasOwnProperty('apply');
      }
    ).forEach(
      function(k) {
        if(o.hasOwnProperty(o[k].apply) && o[o[k].apply].hasOwnProperty('style')) {
          o[k].style = o[o[k].apply].style;
          delete o[k].apply;
          success = true;
        }
      }
    );
  } while(success);
}

function elStyle(objElement, state) {
  if(objElement.hasOwnProperty(state) && objElement[state].hasOwnProperty('style')) {
    $('#' + objElement.targetID).css(objElement[state].style);
  }
  else {
    // error: style undefined
  }
}

populateStyle(objHeader);
elStyle(objHeader, 'mouseout0');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="header">Hello World</div>