Categories


Archives


Recent Posts


Categories


Magento 2: Defaults, uiElement, Observables, and the Fundamental Problem of Userland Object Systems

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

I was chatting with Vinai Kopp in my Patron Slack channel the other day, and via a conversation about the listens default we ended up stumbling across some strange behavior in Magento 2’s uiElement object system. Beyond being a useful bit of information that working Magento 2 developers need to be aware of, it’s also a great example of the problem with “userland” object systems as well as the perfect framing device for launching our next in-depth series.

Userland Object Systems

First, a quick uiElement refresher. The uiElement object system allows end-user-programmers to create javascript constructor functions that inherit from one another. this small program (created and ran in the javascript console) covers the basics.

//normally you'd pull in the RequireJS modules via define
UiElement = requirejs('uiElement');

//here we define a constructor function that inherits everything
//from the UiElement constructor function, with an additional 
//default
OurConstructorFunction = UiElement.extend({
    'defaults':{
          'foo':'bar'
     }
});

//here we instantiate an object from 
//our new constructor function
object = new OurConstructorFunction;

//here we see the foo property has
//a default value of "bar"
console.log(object.foo);

This is an example of what I like to call a userland object system. While the original concept of userland was invented to talk about OS level programming and kernel memory vs. user memory, over the years its use has expanded to draw a distinction between what sort of things the users of a system can do vs. what’s reserved for the system developers. For example, if you try to look at the constructor function of the base Object object in javascript

console.log(Object.constructor)
Function() { [native code] }

Your debugger will return the text “native code“. This native code is implemented in the javascript engine (V8 in chrome) and is not accessible to userland users.

Javascript, however, is very liberal in what it allows you to do with objects. It’s flexible enough to build new object systems without recompiling a custom V8 javascript runtime (which is fortunate, since it would be hard to get users to install that runtime!).

Magento 2’s uiElement object system is an example of a userland object system. Magento 1 developers will be familiar with PrototypeJS’s Class system as another.

The Observables Problem

While userland object systems are neat, useful, and are tools all modern software developers need to be aware of, userland object systems can often lead to unexpected results. Here’s the one Vinai and I ran into.

UiElement = requirejs('uiElement');
ko        = requirejs('ko');

//here we define a constructor function that inherits everything
//from the UiElement constructor function, with an additional 
//default
OurConstructorFunction = UiElement.extend({
    'defaults':{
          'foo': ko.observable('A default value')
     }
});

object1 = new OurConstructorFunction;

object2 = new OurConstructorFunction;

//view the default observable values
console.log(object1.foo());
console.log(object2.foo());

//change the value of the `object1` observable 
//but not the `object2` observable
object1.foo("Changed Value");

//view the observable values, and see that **both** have changed
console.log(object1.foo());
console.log(object2.foo());

If you follow along with the comments, you can see that the foo default value is, for some reasons, linked between objects. Folks working in languages like Java, C#, or PHP are probably scratching their heads wondering how this could happen. Folks working in languages like Ruby and Python are probably scratching their head, wondering why I’d think the behavior should be otherwise.

The problem here is this

console.log(object1.foo === object2.foo);    
true

When used with objects, the === operator checks if the two variables refer to the same object. The problem here isn’t a linking of object properties, it’s that the foo property contains the same object. That’s because the observable is instantiated when we define the new constructor function

'defaults':{
      'foo': ko.observable('A default value')
 }

When Magento’s core uiElement object system code assigns defaults, the syntax boils down to object.foo = defaults.foo. For numbers and strings, this doesn’t present a problem. For objects, it means every assigned default will refer to the same object.

As to whether this is a bug or the right system behavior — that’s hard to say, and it’s one of the biggest problems with userland object systems. Coming from “boilerplate” languages like Java, C#, or PHP, this seems like the wrong behavior. The intent of creating an observable in the object defaults seems to be

Make a new observable for each instantiated object

rather than the behavior we see above.

However, in languages like ruby or python, where class and object creation is a more dynamic affair, the above behavior would seem correct and the blame would lay with the end-user-programmer for forgetting the observables are objects.

Wrap Up

The solution to this fundamental problem is as timeless as it is boring: Good documentation, and a clear understanding of the philosophy of the system creator. While Magento has improved their UI Component documentation, the results read like a desperate translator trying to capture the essences of a fast talking native speaker who’s unaware they’re being observed. This process creates useful artifacts, but seems unlikely to produce the clear understanding programmers require.

That’s why this is the first article in a new series that will dive deeply into the bowels of the uiElement object system. The benefit of open source userland object systems is that, regardless of their originating culture, anyone willing to spend the time and effort can understand and explain them. That’s what we hope to do with this series.

Series NavigationMagento 2: Javascript Primer for uiElement Internals >>