Web Component Interface

The sample templates that are provided with the charting library are constructed with W3C standard Web Components. Web Components are a relatively new part of the HTML5 standard which allow developers to create their own tags and attach logic to drive them. The great power of Web Components is that they are "composable". That is, you can assemble Web Components from other Web Components, which provides an opportunity for re-use and modularity.

User interfaces built with Web Components can be relatively transparent when compared with frameworks such as React, Angular, Ember, et al. This is primarily because the browser automatically handles initialization of Web Components whereas any other framework requires significant "scaffolding" to allow a set of tags to be tied with business logic. That scaffolding hides operational details, often behind large chunks of "magic" (digest loops, zones, etc) or preprocessing (jsx, etc). Web Component interfaces end up being relatively transparent, requiring less proprietary knowledge, offering fewer "pitfalls" (i.e. lost in the digest loop), and doing away with the need for debugging plugins.

Unfortunately, the Web Component specification hasn't adequately addressed the crucial need for a standardized approach to data binding. Data binding has been an integral part of web development since Backbone.js and Knockout.js hit the scene. It is the core value provided by a framework. When Web Components were first announced, a native capability called "object-observe" was touted as the solution for data binding. Unfortunately it wasn't a fully considered approach and was later eliminated. Currently there is no native interface on the horizon and so all frameworks have been forced to come up with their own solution for data binding (resulting in a virtual menagerie of exotic tricks such as: "digest loops" (angular.js), "virtual dom" (react), "object getter/setters" (vue.js), zones (angular2), object proxies (coming soon!)).

While it is not our intention to provide a comprehensive data binding library, some basic capabilities are necessary to take advantage of Web Components. componentUI.js implements these capabilities and provides a very simple infrastructure for building out interfaces with Web Components. Of course much of the code in this file is chart centric, and specific to using chartiq.js, but you should be able to use this technology to build out your application if you so choose.

Web Component 101

1) Web Components are custom tags

One of the nice things about Web Components is that they can be self-contained. A Web Component starts as a "custom tag". You can name your tag anything you wish but it must contain a hyphen. Note: all HTML tags, including custom tags, are case iNsEnSiTiVe. By convention, all ChartIQ tags begin with "cq-".

Example Web Component :

<cq-dialog></cq-dialog>

Web Components behave like other tags. They can be styled with CSS and they can be referenced with jquery. Note: One notable difference is that, unlike a div, custom tags default to display: inline rather than display:block.

Example: Manipulating Web Components :

<style>
    cq-dialog {
      background-color: black;
    }
</style>
<script>
$("cq-dialog").html("Cool!")
</script>

2) Web Components are JavaScript objects

A custom tag gets turned into a live Web Component when you register it:

var Dialog = {
    prototype: Object.create(HTMLElement)
};
document.registerElement("cq-dialog", Dialog);

Web Components must implement the HTMLElement interface, so they are typically created using "inheritance". Here we're using Object.create to implement inheritance because it is concise. Since Web Components are JavaScript objects it is very easy to make them do things! Inside a component, "this" refers to the component itself. So you can combine HTML operations and business logic in one place.

Example: A component comes alive :

Dialog.coolLikeJamesDean=function(){
  this.innerHTML="I've got the bullets!"
};

One of the tough problems for most frameworks is making components work together. Usually this results in tight coupling which makes most single page applications (SPA) increasingly difficult to manage as they grow in scale. Web Components live in the DOM, which means that they are automatically organized and that selectors can be used to link them.

Example: Interact with a web component using a selector :

document.querySelector("cq-dialog").coolLikeJamesDean();

This means that we can use our old friend JQuery to build out out components, albeit with some awkward syntax:

Example: Two techniques for calling web components with JQuery :

$("cq-dialog")[0].coolLikeJamesDean();

or

$("cq-dialog").each(function(){
  this.coolLikeJamesDean();
});

The infrastructure in componentUI.js makes use of JQuery as a core part of its infrastructure. This makes the code very lightweight and familiar to most developers.

3) Web Components "construct" and "destruct", kind of...

Every Web Component automatically provides three callbacks:

  • createdCallback - Called only once, when the component is created
  • attachedCallback - Called when the component is attached to the DOM. This can get called multiple times if you detach and reattach.
  • detachedCallback - Called when the component is detached from the DOM. This can get called multiple times if you reattach.

attachedCallback is where we end up doing most of our initialization work. This is because a Web Component that is attached knows where it is in the DOM. For instance, once attached, a Web Component can find its parents. Because attachedCallback can be called multiple times, one needs to be careful not to create code that isn't "re-entrant" safe (code that breaks if it is called multiple times). In the library, we typically use a flag to protect the code.

Example: Typical pattern for attachedCallback() :

Dialog.attachedCallback=function(){
  if(this.attached) return;
  // do something
  this.attached=true;
};

Generally createdCallback and attachedCallback are interchangeable, with nuances. One pitfall is to watch out for cloneNode(). When you use cloneNode() you create an entirely new Web Component but without any of your initializations.

Anyone who has used a framework knows that destruction is a key consideration. With Web Components, detachedCallback() operates as a sort of destructor, and the logical place to perform cleanup operations. We do get off somewhat easy. Since Web Components are HTML tags, any attached events are automatically destroyed, and closure references released, when the tag is garbage collected. It is rarely necessary to do much cleanup as a result.

4) Web Components can use templates

The template tag provides a clean, easy way to build dynamic content. Content inside a template tag is ignored by the browser, but is still visible when you inspect the HTML which is very nice! You can access templates using good old JQuery.

Example: Let's create a list :

<cq-dialog>
    <template item>
        <li></li>
    </template>
    <ul></ul>
</cq-dialog>

<script>
  Dialog.createListItem=function(str){
    var ul=$(this).find("ul");
    var template=$(this).find("template[item]");
    var newItem=CIQ.UI.makeFromTemplate(template)
    newItem.text(str);
    ul.append(newItem);
  }
</script>

In the example above we use a couple of patterns. First we create a template tag and fill it with the raw html that we want to use to build list items. We've given it an attribute name to make it easy to find with a selector. Note: if you only have a single template in your Web Component you don't need an attribute to references.

Inside of our component logic, we've used JQuery to find our list, and to find our template. Then we use the convenience method makeFromTemplate() to convert the template into live HTML. Finally we use JQuery to add the new HTML.

This is an "imperative" approach to dynamic content. It is very simple to follow. The component infrastructure does not provide a "declarative" (data binding) approach. This is beyond the scope of the library and of course many templating libraries already exist if you'd like to use them.

5) Web Components are composable

You can compose Web Components from other Web Components. This can be done at two levels. You can do this within a single HTML page.

Example: Compose one component from another :

<cq-dialog>
    <cq-close></cq-close>
</cq-dialog>

In this example, the innards of the Web Component are visible. Developers can customize the component quite simply by changing the HTML. So long as the component is written "defensively" (without making too many assumptions about its content), this provides a great way to provide ready made components that are still customizable.

Web Components can also be designed to be "opaque", the contents are visible but not easily changed. This is done by putting components in a separate file and then importing them. The cq-instant-chart component uses this technique.

Example: Importing a component :

<link rel="import" href="instant-chart.html">
<html>
    <cq-instant-chart></cq-instant-chart>
</html>

The contents will become visible in the browser inspector after the component is imported, and can be manipulated programatically, but your initial code is kept very clean. All the details are hidden.

Note: The specification for Web Components allows for truly opaque components using "Shadow DOM". This hides the HTML contents of a Web Component even at run-time. Shadow DOM is not fully supported in most browsers and the Polyfill is extremely heavy so ChartIQ has opted not to use this technique.

Working With The ChartIQ Web Component Framework

Loading the Framework

To use the ChartIQ Web Component Framework you should include the following third party libraries:

  • object-observe.js (included in thirdparty folder of ChartIQ library)
  • webcomponents-lite.min.js (polyfill, included in thirdparty folder of ChartIQ library)
  • jquery.js (any version)

Then include componentsUI.js, which is part of the ChartIQ library. sample-template-advanced.html and sample-template-basic.html contain example code. HTML tags that begin with "cq-" are Web Components that become enabled when you include componentsUI.js.

The global event "WebComponentsReady" will be fired when the web component infrastructure is initialized. This event occurs both with native web components and also when the polyfill is running. You should wait for this event before running any initialization code. For instance, CIQ.UI.begin() starts the ChartIQ UI and should be run after WebComponentsReady has been fired.

cq-ui-manager & cq-context

Two special tags are required to run the framework. cq-ui-manager is a component that manages menus and dialogs. It does so by monitoring touch & mouse events on the page and then instantiating menus and dialogs. For instance, when a user taps on the screen, they expect that any open menus will be closed. This is one of the responsibilities that cq-ui-manager assumes.

cq-context is a special tag that "groups" a set of components to a particular chart. Any component that is nested within a cq-context will look to that context in order to find its chart. For instance, menu items within a cq-context will interact with the chart engine that is attached to the context.

Charts are attached to a context by instantiating a Context object:

new CIQ.UI.Context(stxx, $("cq-context"));

To host multiple charts on a screen you should set up multiple cq-context tags and associate each ChartEngine with one of those tags.

BaseComponent & ContextTag

Any component that needs to interact with the framework should be derived from the BaseComponent class that is included in componentUI.js. Any component derived from BaseComponent can use the built in data binding and key capture capabilities.

Components that need to interact with a chart can be derived from ContextTag. This tag automatically locates the cq-context that is parent to the tag. It coordinates to make sure that the context knows about the tag, and that the tag knows about the context. this.context.stx will then reliably provide a reference to the chart object.

A third type of component called a DialogContentTag can be used for components that potentially need to interact with multiple charts. Dialogs are a perfect example, where a single dialog on a page may interact with any chart on the page depending on which button the user pressed. In this case this.context.stx changes depending on the state of the tag.

Helpers

In addition to Web Components, the UI framework includes a set of classes called "Helpers". The job of a Helper is to bridge functionality between a component and a chart engine (i.e. Layout, StudyEdit). A Helper can also be used to implement data binding on a section of DOM that isn't componentized (i.e. StudyMenu).

Helpers are always associated with a particular chart context. They must be created in your JavaScript:

var context=new CIQ.UI.Context(stxx, $("cq-context"));
var layout=new CIQ.UI.Layout(context);

Since Helpers do not live in the DOM, they rely on the context to connect them for data binding. They do this by advertising themselves with the method advertiseAs(). Generally, you don't need to worry about Helpers unless you need to add additional bindings. If so, we recommend extending the CIQ.UI.Layout prototype with additional methods.

Bindings

The UI framework uses attributes for binding. Three types of binding are supported:

1) stxtap - User click/tap (or hits enter in input tag)

<div stxtap='coolLikeJamesDean()'> will call the method coolLikeJamesDean() when the user clicks/taps on that div tag. The framework will look for the nearest parent object that has that function.

You can also target Helpers with this code <div stxtap='Layout.coolLikeJamesDean()'>. In this case, the nearest ContextTag will activate the binding, and use its context to find an associated Layout helper.

Example, bindings are automatically associated with the nearest parent component :

<script>
...
MyTag.prototype.coolLikeJamesDean=function(node){
  alert("Your clicked " + node.innerHTML);
};
</script>

<cq-my-tag>
    <div>
        <div stxtap="coolLikeJamesDean()">Be cool</div>
    </div>
</cq-my-tag>

2) stxbind - Keeps DOM in sync with the contents of a JavaScript variable

Unlike more comprehensive data binding systems, stxbind defers the job of binding to a member call. For instance, <div stxbind='keepMeCool()'> simply calls the method "keepMeCool()" when the component is instantiated. It is the job of keepMeCool() to monitor a JS variable and update the DOM.

Example, establish a binding :

<script>
MyTag.prototype.keepMeCool=function(node){
  var self=this;
  myWatchFunction(this.temperature, function(){
    node.text(self.temperature);
  });
};
</script>
<cq-my-tag>
    <div stxbind="keepMeCool()"></div>
</cq-my-tag>

In this example, myWatchFunction is left to the developer to implement. There are three recommended approaches:

  • Manually track updates - Stash the node and keep it updated by checking for user input. This is a typical imperative approach.

    var watched=[];
    
    MyTag.prototype.keepMeCool=function(node){
      watched.push({variable:"temperature", node: node});
    };
    
    MyMenu.prototype.changeTemperature=function(newTemp){
      ...
      watched.forEach(function(item){
        if(item.variable=="temperature") item.node.text(newTemp);
      });
    };

  • Use a chart injection - Check the value of a chart variable each time it is rendered. In this example, we move the temperature value into stx.layout. The injection will run every time the chart renders, effectively keeping your UI up to date with the value that is associated with the chart.

    MyTag.prototype.keepMeCool=function(node){
      this.context.stx.append("draw", function(){
          node.text(this.layout.temperature);
      });
    };

  • Use object observe - Create an actual data binding. object-observe is a digest loop that periodically checks the values of variables to see if they've changed. CIQ.UI.observe is a convenience method that can be used to set a value, change a class name or get a callback whenever a variable's contents changes. In this example we use a callback, which offers the most flexibility.

    MyTag.prototype.keepMeCool=function(node){
        var self=this;
        function changeTemperature(params, node){
            node.text(self.temperature);
        }
    
        CIQ.UI.observe({
            selector: node,
            obj: this,
            member: "temperature",
            action: "callback",
            value: changeTemperature
        });
    }

3) stxsetget - Two way binding

stxsetget is a convenience method for adding both stxtap and stxbind with a single attribute. The main difference is that "set" and "get" are prepended to the attribute value when called. Two way binding is used for instance when building the radio button display menu in sample-template-advanced.html.

<script>
MyTag.prototype.getTemperature=function(node){
  ...
};

MyTag.prototype.setTemperature=function(node, value){
  ...
};
</script>
<cq-my-tag>
    <div stxsetget="Temperature"></div>
</cq-my-tag>

Of course there is no restriction on including other data binding or templating engines if you have more demanding data binding needs.

Customizing HTML & CSS

Customizing the provided templates is really as easy as modifying the HTML and CSS. Simply delete a component from the HTML if you don't wish to use it. If you want to make it look different you can reorganize the HTML inside the component and modify the associated CSS.

The CSS used by the templates is actually built using SASS, and can be found in the scss folders in the "sass" directory. The SASS is compiled into chartiq.css. You should never modify chartiq.css directly because this will make it very difficult to upgrade in the future. Instead, we recommend adding additional CSS in your own file and then including it after chartiq.css. All CSS values can be overridden in this manner. Following this approach will guarantee easy upgrades!

Example, overriding the height of the cq-toolbar :

mycss.css
cq-toolbar { height: 60px; }

myhtml.html
<link rel="stylesheet" type="text/css" href="chartiq/css/chartiq.css">
<link rel="stylesheet" type="text/css" href="mycss.css">

An exception to this rule is if you wish to change the colors or fonts for the entire template. In this case, we recommend making a copy of sass/chartiq.scss. Change the variables to contain the colors and styles that you prefer. Then use a SASS builder to generate a new chartiq.css file.

Component Specifics

cq-marker - Add this attribute to any component in order to make it a "Marker". Markers are DOM objects that are superimposed on the chart panel. See Markers.

cq-scroll - creates a scrollable area. Scrollable areas can use perfect-scrollbar to create more elegant scrollbars than native. They also accept cursor up, cursor down, and enter keystrokes.

cq-toggle - creates a toggle div that can automatically be bound. See WebComponents.cq-toggle.

cq-clickable - creates an div that when clicked runs a method on a different component. See WebComponents.cq-clickable.

cq-dialog - creates a dialog that is centered on the screen. A cq-dialog should typically contain a component that is derived from CIQ.UI.DialogContentTag. Generally, you would call open() or close() on the inner content tag instead of interacting directly with the dialog.

Example, open a dialog :

<cq-dialog>
    <cq-my-dialog-contents>
    </cq-my-dialog-contents>
</cq-dialog>
<script>
    $("cq-my-dialog-contents")[0].open();
</script>

cq-menu - creates a drop down menu. Menus appear when they are tapped or clicked (not hover). Every cq-menu must contain a cq-menu-dropdown that contains the contents of the menu. The CSS for these two components has been carefully constructed so that menuing is extremely lightweight.

Example, pattern for menus :

<cq-menu class="ciq-menu">
    <span>Menu Label</span>
    <cq-menu-dropdown>
        <cq-item>My menu item</cq-item>
    </cq-menu-dropdown>
</cq-menu>

cq-color-picker - establishes a color picker component for the page. Only a single color picker exists on the page. It is relocated dynamically whenever it is opened. The color picker is triggered by a cq-swatch component which displays a clickable swatch of color. See WebComponents.cq-color-picker.

See WebComponents.html for a full list of available components and their documentation.

Data Integration

Lookup.Driver

The cq-lookup tags require a Lookup.Driver helper in to process "look ahead" (autocomplete) keystrokes and to fetch results. CIQ.UI.Lookup.Driver provides a default implementation that queries against ChartIQ's symbol server.

To create your own driver, simply derive a new class from CIQ.UI.Lookup.Driver and implement an acceptText() method. This method will be passed the text entered by the user, and any lookup filters that the user has selected. Results should be returned as an array of objects, each containing a data item (in whatever format you wish) and a display array with displayable values.

Example, sample Lookup.Driver :

CIQ.UI.Lookup.Driver.MyDriver=function(){};
CIQ.UI.Lookup.Driver.MyDriver.ciqInheritsFrom(CIQ.UI.Lookup.Driver);

CIQ.UI.Lookup.Driver.MyDriver.prototype.acceptText=function(text, filter, maxResults, cb){
    fetchYourResults(function(){
        var results=[];
        results.push({
              data:{}, // your result symbolObject
              display:["AAPL","Apple Computer Corp","NASDAQ"]
        });
        cb(results);
    });
};

NameValueStore

Some supplied web components require a name value store in order to persist their state across sessions. By default, the library comes with a NameValueStore implementation that uses the browser's localStorage. You can however create your own which could use a network service for persistence.

To use an alternative store, you should first create a class that conforms to the NameValueStore interface. This should implement get, set and remove methods.

Example, create a custom NameValueStore :

function MyNVS={
  get: function(field, cb){
    cb(null, result);
  },
  set: functon(field, value, cb){
    // set
  },
  remove: function:(field, cb){
    // remove
  }
}

Next, you will need to initialize the desired components with this NameValueStore. cq-themes and cq-views currently use this interface to store values.

Example, initialize components with custom NameValueStore :

$("cq-themes")[0].initialize({
  builtInThemes: {"ciq-day":"Day"},
  nameValueStore: new MyNVS();
});

$("cq-views")[0].initialize({
  nameValueStore: new MyNVS();
})

Advanced Topics

ModalTag

You can derive your components from ModalTag in order to automatically give them modal capabilities. This means that when you mouse over the tag, stx.modalBegin() and stx.modalEnd() are called, disabling mouse interaction with the associated chart. A ModalTag is typically used when superimposing a component on to a chart, often as a cq-marker. For this reason, ModalTag is derived from ContextTag and must have a cq-context in its ancestor tree.

KeystrokeHub

This is a singleton helper that manages keystrokes. In HTML, you would typically register for key events on an input tag. The KeystrokeHub handles the more complicated problem of handling key strokes that are independent of input tags. Examples include global hotkeys, passing keystrokes to a chart, or navigating the menuing system with keys.

You must initialize a KeystrokeHub with JavaScript code and associate it with a context. (You can optionally dynamically associate the context by calling setContext()). The KeystrokeHub registers itself with the cq-ui-manager so that it is universally available.

Components receive keystrokes by registering "claims". The KeystrokeHub distributes keystrokes to all components who have staked a claim by calling their keyStroke() method. A component can then decide whether to act on the keystroke, and if so, return true so that no other component acts on the stroke. It is the responsibility of the component to decide whether it should act. For instance, cq-scroll components only act on cursor up, cursor down, and enter keystrokes. Furthermore, they only act on them if the component itself is visible.

If a keystroke is not claimed by any component then the KeystrokeHub will attempt to satisfy any global hotkeys by calling CIQ.UI.KeystrokeHub.defaultHotKeys(). A default implementation is provided but you can override this prototype method if you desire.

For both components as well as hotkeys, the default browser key code will be passed for most keys. For special keys, a string is passed to indicate the key that was pressed. For instance, "up" is passed when the cursor up key is pressed.

Lifts

From time to time, an interactive page runs into formatting constraints. A typical constraint occurs in dialogs, where interactive (pop-up) data is hidden by the edge of the dialog. The most common example of this is when implementing a select-box inside of a dialog. Native select-boxes are ugly, but custom select-boxes get cut off when inside dialogs.

By adding the cq-lift attribute to a tag, the cq-ui-manager will temporarily "lift" the tag outside of its container, freeing it of its bounding box. It does this by using absolute positioning. When the tag is acted upon, the lift is released and the tag goes back into its dialog.

containerExecute

This provides a convenient pattern for nested composable components. containerExecute will traverse up an ancestor tree looking for a component that contains a given method.

As an example, consider the tag cq-close tag. We would like to nest this tag arbitrarily inside any type of component and have it automatically close the parent component. But we might want to wrap it in some formatting, which decouples the tag in the DOM. In this circumstance, we have coded the cq-close component to emit a containerExecute that will call the "close()" method on the nearest parent that contains that method. So long as a tag implements this interface, cq-close can be nested inside.

An analogy is exception handling. Calling containerExecute is like throwing an exception. It will traverse up the stack until it finds a handler and then passes the message to that handler.

makeFromTemplate

Instantiating templates actually takes a fair amount of boilerplate, namely because they exist as a DocumentFragment and not as an actual DOM element. Particularly when using jQuery this makes for some terse code. makeFromTemplate() encapsulates that information so that you can easily create a template using jQuery selectors.

Resizables

Sadly, there is no support in browsers for DOM elements to receive resize events (only window resizing is an event in the browser). In dynamic single page applications though, we frequently encounter interfaces that have DOM elements changing size based on user interactive. For instance a menu may change in height as elements are added or removed.

Luckily, a workaround exists. The component library makes use of resize listeners that are based on the little known, and seldom used "object" tag. An object tag behaves like an iframe but without the overhead. By embedding invisible "object" tags, we can get a window resize event on the object itself - whenever the containing div is resized. Simply call CIQ.addResizeListener(node, cb) to receive resize events. Note, be sure to call CIQ.removeResizeListener(node, fn) when cleaning up.

Additionally, any component registered this way will also have its resize() method called whenever the window itself is resized. This allows dialogs and menus to resize themselves accordingly.

jQuery extensions

To make life with Web Components easier, the library adds several extensions to jQuery. These can be found in componentUI.js and include:

parentsAndMe - returns a set of parents but also including the node itself

cqvirtual & cqrender - quick and dirty virtual dom

guaranteedWidth & guaranteedHeight - Deals with unstyled "wrapper" tags that technically have no dimension

emptyExceptForTemplate - just like empty() but doesn't remove any templates

truthyAttr - tells you if an attribute exists, or is true

attrBetter, removeAttrBetter, textBetter() - just like attr(), removeAttr() and text() except more efficient because they check before modifying

Data binding mechanics

You must call CIQ.UI.begin() in order to start data binding. This is primarily due to the potential for importing web components from other files. Once this has been called, components derived from BaseComponent automatically process their own data binding events.

You can create components dynamically and add them to the DOM. Components are processed for data binding when they are attached, however processing only occurs once. You cannot remove a component and then add it somewhere else and expect the data bindings to change.

One of the complications with any framework is initialization. Many of the components require access to a cq-context, but there is no guarantee that the CIQ.UI.Context exists when those components are created. To address this, we leverage the DOM. Contexts are discovered by traversing the DOM. The actual CIQ.UI.Context implementation is stored in the cq-context tag as \$("cq-context")[0].CIQ.UI.context. Each component also registers itself in \$("cq-context")[0].CIQ.UI.components[]. In this way, whoever gets created last (the component or the context) is responsible for linking themselves up. This is done by calling the setContext() method on the component itself.

Finally, this method of "implicit" data binding creates potential ambiguities. As a rule therefore, the framework requires that data bindings occur on their nearest ancestor component. In order to enforce this policy, and to ensure the existence of a full ancestor tree, bindings occur on the "nextTick" after a component is added to the DOM. This ensures that the DOM tree is fully established when all decisions about data binding occur.

Limits to the architecture

While it is not a capability of the software, the architecture technically makes it possible (with additional syntax) for data bindings to target specific ancestors, or even unrelated components. However, since bindings occur at the time a component is added to the DOM, the architecture would not easily support "runtime" bindings - those that must be acted upon at the tick of user click.

object-observe provides a convenient and lightweight observable mechanism however its limits must be understood. object-observe binds directly to objects, not to a representation of an object chain. This means that a binding will not change if a parent object is replaced. This can lead to unintuitive results.

Example, we don't get an alert for frank. Also a memory leak! :

var parent={
  child: {
    name: "harry"
  }
};

Object.observe(parent.child.name, cb);

parent.child={name: "frank"};

We've created the method CIQ.dataBindSafeAssignment to specifically deal with this scenario.

Example, safe way to deal with the same issue :

var parent={
  child: {
    name: "harry"
  }
};

Object.observe(parent.child.name, cb);

CIQ.dataBindSafeAssignment(parent.child, {name: "frank"});

Even still, you must be careful to target the correct level in an object chain. More comprehensive frameworks such as sightglass take on this challenge by constructing a "chain of observables" to detect if any link in a chain has been modified.


Next Steps: