Sunday, August 23, 2020

Proposal: Autonomous UAV Navigation System























A proposal for a project to develop an advanced autonomous UAV navigation system.
Full description and development is on the project page:

http://alxyly.blogspot.com/p/uav.html


Proposal: Air Flow Simulation for Pandemic Control




















A proposal for the CFD project to help with pandemic research.
Full description and development is on the project page:

http://alxyly.blogspot.com/p/air.html

Sunday, March 25, 2018

ML with Python (numpy, pandas), my tools + code snippets (Part 1)

Contents:



1. Jupyter notebooks

Jupyter is part of Anaconda pack of packages, but I prefer simple commands:
$ python3 -m pip install jupyter
Jupyter notebooks run a web server that can be queried either directly from web console, or via terminal. To find what localhost:port is used by a running server:
$ jupyter notebook list
To stop a server:
$ jupyter notebook stop 8888 
To open a notebook file (with .ipynb extension), in terminal point to a notebook directory and run:
$ jupyter notebook
This starts a server, then opens a notebook's home directory in a browser. Click a notebook to run it. Keep terminal shell open and running a server as an attached process. To stop a server use Ctrl-C, or just close a terminal.
Shift-Return to run selected cell and advance to the next.

2. Paths to Python components and packages

>>> import sys  
>>> print('\n'.join(sys.path))


3. PYZO editor for .py

PYZO works for me. It is lightweight, has debugging options, and is configurable.
Supports cells, blocks of code that can be executed with a simple shortcut (Cmd-Return). Define cells by delimiting with #$$ optional commentary lines.
Some shortcuts (original and my own custom):
---------- EDITOR: 
Opt-Tab    - select previous file 
F1         - focus to shell panel 
F2         - focus to file editor 
Cmd-/      - comment selection  
Cmd-Opt-/  - uncomment  
---------- RUN:  
Cmd-R      - run file as a script (in console restarts interpreter)  
Cmd-Return - run cell with cursor (cells are delimited by #%%)   
Opt-Return - run selection  
---------- DEBUGGER:  
Cmd-B      - toggle breakpoint  
F6         - step over  
F7         - step in  
F8         - step out  
Ctrl-Cmd-Y - continue  
Ctrl-Cmd-. - stop debugging 



4. Basics of numpy arrays

Many ways to init arrays. There are two main options - shape and dtype.
>>> x = np.ndarray(shape=(2, 2), dtype=np.int8, order='C') 
>>> print(x) 
[[1 0] [1 1]]
Internal buffer is linear. Shape can be changed easily without reallocation if total element count remains the same:
>>> x.shape = (1,4) 
>>> print(x) 
[[1 0 1 1]]
Other array constructors:
>>> print(np.array((1, 2, 3))) 
[1 2 3] 
>>> print(np.zeros((2, 3))) 
[[0. 0. 0.] 
 [0. 0. 0.]] 
>>> print(np.empty((2,))) 
[7.74860419e-304 7.74860419e-304]

Array construction using list comprehensions. Note, unspecified dimension size of -1 infers the required element count in that dimension:
>>> x = np.array([(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]) 
>>> print(x) 
[[1 3] 
 [1 4] 
 [2 3] 
 [2 1] 
 [2 4] 
 [3 1] 
 [3 4]] 
>>> x.shape = (2, -1) 
>>> print(x) 
[[1 3 1 4 2 3 2] 
 [1 2 4 3 1 3 4]] 
>>> x.shape = (-1) 
>>> print(x) 
[1 3 1 4 2 3 2 1 2 4 3 1 3 4]



Numpy arrays in list comprehension expressions:
>>> y = np.array([e for e in x if not e % 2]) 
>>> print(y) 
[4 2 2 2 4 4]

Range into 2D array:
>>> x = np.arange(15).reshape(5, -1).T 
>>> print(x) 
[[ 0 3 6 9 12] 
 [ 1 4 7 10 13] 
 [ 2 5 8 11 14]]

Numpy array operations perform like native C memory access ops (or parallelized vectors in GPU). Therefore much faster than Python's list comprehensions:
#%% Timing init 
import time 
x1 = np.arange(1000000) 
x2 = np.arange(1000000) 

#%% Timing native 
t0 = time.time() 
for _ in range(10): 
    x1 **= 2 
t1 = time.time() 
print("x1 time = ", t1 - t0) 

#%% Timing list comprehension 
t0 = time.time() 
for _ in range(10): 
    x2 = np.array([x ** 2 for x in x1]) 
t1 = time.time() 
print("x2 time = ", t1 - t0) 
#%%
---------------------------------------- 
x1 time = 0.015141725540161133 
x2 time = 4.744795083999634



5. Numpy.random

To generate standard numpy arrays filled with random numbers:
np.random.rand(d0,..dn)     - with each value uniformly in range [0, 1). 
np.random.randn(d0,..dn)    - with each value in Gaussian distribution, where mean = 0, variance = 1 (sigma squared). 
sigma * np.random.randn(d0,..dn) + mean    - a full normal distribution.
There are lots of other useful functions, i.e. np.random.choice(...).


6. Array operations

Slice of an array returns a data structure that defines subrange and points at the original array. Note that the second index (i.e. in [3:10]) points beyond the last element:
x1 = np.arange(12) 
print(x1) 
x1_slice = x1[3:10] 
print('x1_slice      = ', x1_slice) 
print('x1_slice[0:3] = ', x1_slice[0:3]) 
x1_slice[0:3] = 100 
print(x1) 
---------------------------------------- 
[ 0 1 2 3 4 5 6 7 8 9 10 11] 
x1_slice      = [3 4 5 6 7 8 9] 
x1_slice[0:3] = [3 4 5] 
[ 0 1 2 100 100 100 6 7 8 9 10 11]


Multi-dim array slices:
x1 = np.arange(12).reshape(4,-1) 
print(x1) 
x1_slice = x1[2:4] 
print(x1_slice) 
print(x1_slice[0][1:3]) 
---------------------------------------- 
[[  0  1  2] 
 [  3  4  5] 
 [  6  7  8] 
 [  9 10 11]]

[[ 6 7 8] 
 [ 9 10 11]] 

[7 8]


Slicing across multiple dimensions:
print(x1[:2, 1:3]) 
x1[:, 1:2] = 100 
print(x1) 
---------------------------------------- 
[[1 2] 
 [4 5]] 


[[  0 100  2]  
 [  3 100  5] 
 [  6 100  8] 
 [  9 100 11]]



7. Boolean indexing

Taken and modified from the pydata-book.
A boolean operation with array will return an array of boolean element-wise results. Boolean array when used as index will pick array elements where boolean component is True.
Boolean array size must be equal to data array size at the index:
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Joe']) 
data = np.random.randint(1, 10, (5, 4)) 
print(names) 
print(data) 
mask = (names == 'Bob') | (names == 'Will') 
print(mask) 
print(data[mask]) 
data[mask, 1:3] = 100. 
print(data) 
data[data > 5] = 0 
print(data) 
----------------------------------------  
['Bob' 'Joe' 'Will' 'Bob' 'Joe'] 

[[1 4 3 6] 
 [6 1 3 3] 
 [7 1 6 1] 
 [3 3 9 4] 
 [4 7 8 9]] 

[ True False True True False] 

[[1 4 3 6] 
 [7 1 6 1] 
 [3 3 9 4]] 

[[  1 100 100   6] 
 [  6   1   3   3] 
 [  7 100 100   1] 
 [  3 100 100   4] 
 [  4   7   8   9]] 

[[1 0 0 0] 
 [0 1 3 3] 
 [0 0 0 1] 
 [3 0 0 4] 
 [4 0 0 0]]



8. Fancy indexing

Passing list of numbers into an indexer will treat it as an index picker. Allows assembling a new array from a combination of elements of another array:
x1 = np.arange(20).reshape((5, 4)) 
print(x1) 
x2 = x1[[1, 4, 2, 2], [0, 3, 1, 2]] 
print(x2) 
x1[[1, 4, 2, 2], [0, 3, 1, 2]] = 100 
print(x1) 
----------------------------------------   
[[ 0  1  2  3] 
 [ 4  5  6  7] 
 [ 8  9 10 11] 
 [12 13 14 15] 
 [16 17 18 19]] 

[ 4 19 9 10] 

[[  0   1   2   3] 
 [100   5   6   7] 
 [  8 100 100  11] 
 [ 12  13  14  15] 
 [ 16  17  18 100]]



9. Transposition

Transposed matrix is pointing at the same data (no copying takes place):
x1 = np.arange(15).reshape((3, 5)) 
print(x1) 
x2 = x1.T 
print(x2) 
x2[2] = 100 
print(x1) 
----------------------------------------    
[[ 0  1  2  3  4] 
 [ 5  6  7  8  9] 
 [10 11 12 13 14]] 

[[ 0 5 10] 
 [ 1 6 11] 
 [ 2 7 12] 
 [ 3 8 13] 
 [ 4 9 14]] 

[[  0  1 100  3  4] 
 [  5  6 100  8  9] 
 [ 10 11 100 13 14]]








References:

1. https://github.com/wesm/pydata-book
2. https://leemendelowitz.github.io/blog/how-does-python-find-packages.html
3. https://docs.python.org/3/installing/index.html
4. http://nbviewer.jupyter.org/github/pydata/pydata-book/blob/2nd-edition/ch02.ipynb#
5. https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences


Wednesday, March 21, 2018

Microsoft's Imagine Cup Challenge - Team Heuristic Clinic

A month ago I prepared a quick team and a project for consideration at Microsoft's Imagine Cup. It was an intriguing experience to document details, to present in a video, and to polish a previously developed prototype - a wireless motion/gesture capturing app and a set of sensors.
I understood there was a slim chance to actually win the Cup, so I instead focused on setting up this project for possible future applications elsewhere.

The team is called Heuristic Clinic, the project is Applied Neural Diagnosis. Brief description can be found here: Heuristic Clinic, and the resulting certificate can be viewed here.
They were kind to offer $500 worth of Azure cloud, but I had to decline because the biggest expense is funding client-side development. Appreciated.


©

2018 Big Idea Challenge

Top 50 - 2018 Big Idea Challenge

This Certificate is Presented To:

alexey l

Team Heuristic Clinic

Congratulations and thank you for your hard work and dedication as a competitor in Imagine Cup 2018. You are now part of an elite international community of students who have shown remarkable creativity and innovation to push technology forward.
©
Pablo Veramendi
Imagine Cup Competition Manager
Microsoft Corporation

Sunday, June 25, 2017

Martian sandstorm

Now a fully interactive volumetric sand storm, probably somewhere on Mars.
This marks an intermediate milestone following months of research into volume rendering. It uses finite differences solver for a fluid dynamics equation, some additional research into graphics pipeline on a Mac and iOS.
Note the shadows, they depend on volume density, and also note the unbounded nature of volumes. Other methods would produce visible banding artifacts.


Saturday, June 17, 2017

Unexpected volumetric clouds

I was playing with code that I am testing to produce fluid dynamic effects, and I came across a weird cloud generating setup.
The advection part is failing at some point here and causes gas density to freeze in some regions of the map. It remains static and appears as a cloud, and I think this can be further improved to produce realistic looking real cloud. What's missing is a better phase function, and shading based on anisotropic scattering of different wavelenths.




Monday, May 29, 2017

Fun with self-shadowing volumetrics

As a preparation to a full-fledged interactive volume rendering, here is the first functional volumetric composition. It uses a few techniques to achieve a self-shadowing appearance, as you would expect from a real-life volume of transparent matter.


Wednesday, May 24, 2017

Volumetric hints.

A few volume rendering hints.

1. Cannot discard writing into render-targets selectively in an MRT frame-buffer. This arises, for example, when two targets are used to store two custom denormalized depth buffers. A frame-buffer has a depth attachment as well, but it is used in a regular sense of a depth buffer.

2. Volume rendering can use analytic volume buffer or a set of depth textures. To limit volume to a custom 3D shape requires preliminary step to render that shape into two depth attachments, one with inverted normals and inverted depth test:
// Enable depth test, inverted, front to back.
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_GREATER);

// Render only back faces.
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);

// Clear the buffers.
// Note that the most distant shape will overwrite all others.
glClearDepth(0.0);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//... Render the shape of the volume, i.e. a cloud.

// Enable depth test, back to front.
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);

// Render only front faces.
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);

// Clear the buffers.
glClearDepth(1.0);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//... Render the same shape of the volume.


3. In a fragment shader calculate the distance to a rasterized point. Do it in fragment instead of in a vertex, hoping for speedup due to hardware interpolation. If two vertices spread out evenly in a view space, their depths will be equal and much bigger than the distance to a front clipping plane. This results in artifacts when moving towards a volume and crossing it.

4. A depth buffer attachment can now be shared between with other frame-buffers. It works for simple convex shapes as volumes that can be rendered from inside and outside. The real work is to be done elsewhere to modulate this shape with other detail, i.e. noise.

Saturday, May 13, 2017

Volumetric rendering with custom volume shapes


It took me a week to figure out a problem with a vertex shader not able to interpolate a single float output.
I started with a multi-pass stage that produced a volume from a shape, then fed that volume to a volumetric pass and observed unexplainable artefacts. When flying through a scene and entering a volume the buffer that was clipped by a view frustum's near plane would mangle a default buffer value (set by glClear). Attempts to fix that by playing with glEnable(GL_DEPTH_CLAMP), glDepthRange(-1.0, 1.0) etc did not help.
What did work was switching from a float output to a whole vec3, and calculating depth in a fragment shader. Inefficient, and annoying.

Friday, April 14, 2017

Real-time atmospheric effects

This demonstrates a few atmospheric effects that I've added to a custom engine. It is real-time, using stages in shader pipeline, and very customizable (fog, lighting, composition etc).
In addition, the demo is showing Fourier transform to simulate ocean waves.
More to follow.

Wednesday, May 25, 2016

On UI composition, embedded view controllers, weak vars, multi-cast delegates.

Interface Builder is great for composing plain interfaces, where each view controller is represented by a single instance of view in a view hierarchy.

Embedded view controllers are different.

Even though you can create multiple Embed segues connecting different places in a storyboard to the same embedded view controller - during app initialization the framework will create as many instances of that controller as there are segues.

This is counter-intuitive at first. A collection of views is an ordered tree, where each node has a reference to its single parent. But in a storyboard the view controllers can be connected by Embed segues arbitrarily and can form cycles.

It appears Apple gets away with this by forcing each Embed segue to create a new instance of a destination view controller.

For example, the following storyboard with embedded view controllers (part of a project in a coursera.org course) has many parent-child Embed relations:




Note the three parent Container views, and two of them share a view controller.

A parent view controller will have outlets to instances of child view controllers. Those outlets are declared weak and fished out of the storyboard in an override of prepareForSegue() method. If there are multiple instances of the same embedded view controller (as in the screenshot) - you’d want them stored in an array of weak references. But the challenge with Swift 2 is that it does not support collections of weak references.

In a simple case - having separate weak refs is ok. The prepareForSegue() implementation might look like this:

    // MARK: - Navigation

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        // !!! Embed segues are processed before viewDidLoad().
        switch segue.destinationViewController {
        case let vc as MyHobbiesCollectionViewController:
            // Note that this embedded VC has multiple instances,
            // need additional id check (i.e. by segue.identifier).
            switch segue.identifier {
                case .Some("My1stHobbiesEmbedSegue"):
                    my1stHobbiesCVC = vc;
            default:
                my2ndHobbiesCVC = vc;
            }
           
            // Also prepare non-embed segues.
        case let vc as EditHobbiesViewController:
            break;
        default: break;
        }

    }


In other more advanced cases, a recommended solution is to declare a weak ref in a structure and then have an array of those structures. Hopefully Apple is working to fix this minor annoyance.

A few words about how a hierarchy of view controllers should communicate with the rest of the tree.

KVO and NSNotificationCenter are ok in general case, but they lack type-safety of messages, and require not very nice symmetry of subscrbe/unsubscribe calls. Instead, I prefer declaring protocols for each child view controller, then a parent would implement those protocols, and assign itself to weak ref delegates of its children.

Events of the child controllers will first travel up a hierarchy, where a parent will route them to peers.

This messaging system could make use of a collection of weak ref delegates, which would be referred to as multi-cast delegates. But this will have to wait till a version of Swift that implements weak ref collections natively.

Tuesday, May 10, 2016

Quick Auto Layout rig for a responsive tab bar control

Let’s say I need to create a tab bar with 3 tabs, each having a button and an indicator. It needs to be responsive (autoresizeable) and each tab equally sized with the others.





Using Auto Layout here is how I do it.

1. Select a view that renders a tab (here called “TabView”). Copy-paste as many as needed. Customize each copy.





2. Add missing constraints manually. Select each tab view, uncheck “Constrain to margins”, and add space constraints. The first tab needs 4 constraints, the rest - only 3.







3. Add size constraints (width or height) by selecting all tab views, then Equal Widths.







4. Finally, update all frames in a parent view.





Embed in a parent controller, declare protocols to handle taps, etc.

Saturday, February 20, 2016

Slide-out menu (with hamburger).

This short piece is about interactive slide-out menu in iOS. The demo is using an embedded container view for a menu view controller, and that controller handles animations and interaction.

The following implementation will be used for interaction-animation control where view hierarchy remains mostly static.




1. Menu view consists of two container views - a full screen dim view and an overlapping menu view. Interaction is registered by a pan gesture recognizer and is converted to a normalized “progress” property value. Setting this property on a menu view controller updates both the alpha property of the dim view and an origin of a menu view (via a horizontal leading constraint). 


import UIKit


class RootViewController: UIViewController, UIGestureRecognizerDelegate{

    @IBOutlet weak var menuContainerView: UIView!
    @IBOutlet weak var mainContainerView: UIView!
    
    weak var menuVC: MenuViewController!;

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        
        // Instead of fishing for an embedded controller in a prepareForSegue.
        for vc in childViewControllers {
            if let mvc = vc as? MenuViewController {
                menuVC = mvc;
            }
        }
    }

    
    // MARK: - Gesture Recognizer
    
    var initialProgress: Float = 0.0;
    var ignoreGesture = false;

    @IBAction func handlePanGesture(recognizer: UIPanGestureRecognizer) {
        let vel = recognizer.velocityInView(view);
        let velThreshold = menuVC.velocityThreshold;

        switch(recognizer.state) {
        case .Began:
            ignoreGesture = abs(vel.y) > abs(vel.x);
            if (ignoreGesture) {
                return;
            }
            initialProgress = menuVC.progress;
            recognizer.setTranslation(CGPointZero, inView: view)
            
        case .Changed:
            if (ignoreGesture) {
                return;
            }
            let translation = recognizer.translationInView(view);
            let tX = Float(translation.x);
            let progress = initialProgress + tX / velThreshold;
            // The following can be used to prevent menu progress overshot (not a good "responsive UI").
            // progress = min(1.0, max(0.0, progress));
            menuVC.progress = progress;
            
        case .Ended:
            if (ignoreGesture) {
                return;
            }
            let velX = Float(vel.x);
            let expand = ((menuVC.progress > 0.5 && velX > -velThreshold) || (menuVC.progress < 0.5 && velX > velThreshold));
            menuVC.animateMenu(expand);
            
        default:
            break
        }
    }
    
}



2. Progress property setter does a few things differently if the value is to be animated. Because menu works in conjunction with the embedding superview, the progress will enable/disable the superview as needed so that collapsed menu stays out of way when user interacts with the main content. To animate this property from its current value - there is a function animateMenu:.


import UIKit

class MenuViewController: UIViewController {
    @IBOutlet weak var dimView: UIView!
    @IBOutlet weak var menuView: UIView!
    @IBOutlet weak var menuLeadingConstraint: NSLayoutConstraint!

    var _useAnimation = false;
    var _progress: Float = 0.0;
    
    // To animate to a value set the _useAnimation true.
    var progress: Float {
        get { return _progress; }
        set {
            if (abs(newValue) > 1.0) {
                _progress = min(1.5, pow(abs(newValue), 0.2) * (newValue >= 0.0 ? 1.0 : -1.0));
            }
            else {
                _progress = newValue;
            }
            if (newValue > 1e-5 && !menuEnabled) {
                // Don't just set to condition via assignment, "false" will break animation (0.0 will collapse the menu before animation begins).
                menuEnabled = true;
            }
            
            let animations = {
                self.dimView.alpha = CGFloat(min(1.0, self._progress) / 2);
                let menuOriginX = CGFloat(self.velocityThreshold * self._progress) - self.menuView.frame.width;
                self.menuLeadingConstraint.constant = menuOriginX;
                self.view.layoutIfNeeded();
            };
            let completion: (Bool -> Void) = { finished in
                if (newValue <= 1e-5) {
                    self.menuEnabled = false;
                }
                self._useAnimation = false;
            };

            if (_useAnimation) {
                UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseOut, animations: animations, completion: completion);
            } else {
                animations();
                completion(true);
            }
        }
    }
    
    private(set) var velocityThreshold: Float = 100.0;
    
    // Enables/disables container view.
    var menuEnabled: Bool {
        get {
            return !view.superview!.hidden;
        }
        set {
            view.superview!.hidden = !newValue;
        }
    }

    func animateMenu(expand: Bool) {
        _useAnimation = true;
        
        if (expand) {
            progress = 1;
        } else {
            progress = 0;
        }
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        
        // Initial menu view offset is a design-time position for an "expanded" state.
        velocityThreshold = Float(menuView.frame.width + menuView.frame.origin.x);
    }
    
    override func viewDidAppear(animated: Bool) {
        menuEnabled = true;
        progress = 1.0;
        animateMenu(false);
    }

    @IBAction func tapDimView(sender: AnyObject) {
        animateMenu(false);
    }
}




Other ways to implement this, such as using UIScrollView or custom transition animators, are more appropriate if standard transitions (on navigation controller) are desired. I find them either lacking in brevity of code or having tight dependence on standard view controllers.






Sunday, November 8, 2015

Handlebars Essentials 3 - Template Precompilation

Assuming the project has templates in a dedicated folder, this step will optimize the way pages are rendered with Handlebars.

Why precompilation? Because Handlebars is designed to convert (compile) templates from their source form (with double-curly-braces) to a Javascript. And this step is expensive on a thin client. So, there is a quick and relatively easy extra step during development to compile all templates into a single .js file. That file is to be referenced by a <script> tag from the host page.


1. Make sure that Node.js is installed and up-to-date.

2. Use the following to install Handlebars module for Node.js.
$ sudo npm install -g handlebars


3. Place all templates into a subfolder, say “templates”. Then, from the root of the project do this:
$ handlebars templates/* -f templates/templates.js

This creates a single .js file with the code for all precompiled templates. To minimize the output and strip out spaces, use this instead:
$ handlebars templates/* -m -f templates/templates.js


4. Add a reference to the newly created templates.js.
<script src="templates/templates.js"></script>
<script src="js/initModel.js"></script>


5. App.js - replace the function to load templates with the following code. Note that partial templates still need to be loaded from the source as before, it appears this is the only way to register partials in Handlebars at present. And this is the only change required.
  function loadTemplatesAndRender() {
    indexTemplate = Handlebars.templates["index.hbs"];
    itemsTemplate = Handlebars.templates["items.hbs"];
    itemTemplate = Handlebars.templates["item.hbs"];

    // Note, the item template is a partial,
    // therefore also needs to be loaded as a source.
    var t3 = $.get("templates/item.hbs", function (data) {
      Handlebars.registerPartial("item", data);
    }, "html");
    // Wait for templates to finish loading.
    $.when(t3).done(function() {
      // Also wait for $(document).ready().
      $(function() {
        //alert("ALL DONE");
        renderPage();
        renderItems();
      })
    })
  }


UPDATE: There is a simpler way to "register" partials. Use the following somewhere after templates are loaded:
Handlebars.partials = Handlebars.templates;

For this to work the partial's reference needs to point to a template by file name, like so:
{{#each items}}
  {{> item.hbs language=@root.language}}
{{else}}
  {{language.noItemsFound}}
{{/each}}


Handlebars Essentials 2

This continues exploring Handlebars library.
Here you will see how to split markup into parts, with the goal to move templates and partials into their own units, and thus allow granular code management.
It will demonstrate how to build an MVC style framework of managing page elements, with pages being simple containers for subviews (hierarchies of views), and client-side logic in controllers being responsible for controlling a single view by feeding data from a model data source.

There is one deficiency here that will be tackled in the next post. It is about optimizing templates and speeding up page load by pre-compilation.


1. Index.html - this is a bare-bones host page for templated elements.
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bunnies</title>
    <link rel="stylesheet" href="css/styles.css">
    <script src="lib/jquery/jquery-2.1.4.min.js"></script>
    <script src="lib/handlebars/handlebars-v4.0.4.js"></script>

    <script src="js/initModel.js"></script>
</head>

<body>
    <div id="main">
    </div>
    <script src="js/app.js"></script>
</body>
</html>


2. InitModel.js - loads and initializes a model object. It is a simple skeletal code to load from a local storage, initialize, and define basic operations on items.
(function() {

  var model = window.model = {};
  model.items = [];
  model.lang = 0;
  model.languages = [
    {
      langId: "lang0",
      siteTitle: "App Prototype (with Handlebars)",
      page1Name: "Home",
      page2Name: "Favorites",
      page3Name: "Contact us",
      lang1Name: "Eng",
      lang2Name: "C++",
      noItemsFound: "No items found",
      one: "Call one",
      two: "Call two",
      good: "Good for you",
      bad: "Bad for you"
    },
    {
      langId: "lang1",
      siteTitle: "template<T> void App (std::shared_ptr<Handlebars>);",
      page1Name: "home();",
      page2Name: "favorites();",
      page3Name: "info();",
      lang1Name: "std::shared_ptr<Eng>();",
      lang2Name: "this",
      noItemsFound: "NULL",
      one: "one();",
      two: "two();",
      good: "result = true;",
      bad: "result = false;"
    },
  ];

  // MARK: - System support functions.

  function parseUrl() {
    var queryParams = window.location.search.slice(1).split('&');
    var paramsObj = {};
    queryParams.forEach(function(val) {
      if (val) {
        var pair = val.split("=");
        paramsObj[pair[0]] = pair[1];
      }
    });
    return paramsObj;
  };

  // MARK: - Init functions.

  window.model.initLanguage = function() {
    var wasUnarchived = this.unarchiveLanguage();
    console.log(this.lang);

    var lang = parseUrl().lang;
    if (lang == undefined) {
      lang = this.lang;
    }
    console.log(lang);
    if (this.lang == lang) {
      return;
    }

    this.lang = lang;
    this.archiveLanguage();
  }

  function Item(id, image, name, isGood, moreInfo) {
    this.id = id;
    this.image = image;
    this.name = name;
    this.isGood = isGood;
    this.moreInfo = moreInfo;
    return this;
  }

  window.model.initItems = function() {
    if (this.unarchiveItems()) {
      return;
    }
    this.items = [
      new Item("bunny0", "bubby_bunny.jpg", "Bubby Bunny", false),
      new Item("bunny1", "cloudy_bunny.jpg", "Cloudy Bunny", false),
      new Item("bunny2", "meditating_cat.jpg", "Meditating Cat", true)
    ];
    this.archiveItems();
  }

  // MARK: - Item operations.

  window.model.chooseItem = function(itemId) {
    var item = this.findItem(itemId);
    if (item) {
      item.moreInfo = true;
      this.archiveItems();
    }
  }

  window.model.clearItem = function(itemId) {
    var item = this.findItem(itemId);
    if (item) {
      item.moreInfo = false;
      this.archiveItems();
    }
  }

  window.model.findItem = function(itemId) {
    var item;
    this.items.forEach(function(val) {
      if (val.id === itemId) {
        item = val;
      }
    });
    return item;
  }

  // MARK: - Archiver.

  window.model.archive = function() {
    this.archiveLanguage();
    this.archiveItems();
  }

  window.model.unarchive = function() {
    var result = this.unarchiveLanguage()
              && this.unarchiveItems();
    return result;
  }

  window.model.archiveLanguage = function() {
    window.localStorage.setItem("model.language", JSON.stringify(this.lang));
  }

  window.model.archiveItems = function() {
    window.localStorage.setItem("model.items", JSON.stringify(this.items));
  }

  window.model.unarchiveLanguage = function() {
    var data = window.localStorage.getItem("model.language");
    if (!data) {
      return false;
    }
    //console.log(data);
    this.lang = JSON.parse(data);
    return true;
  }

  window.model.unarchiveItems = function() {
    var data = window.localStorage.getItem("model.items");
    if (!data) {
      return false;
    }
    var json = JSON.parse(data);

    json.forEach(function(val) {
      model.items.push(new Item(val.id,
        val.image, val.name, val.isGood, val.moreInfo));
    });
    return true;
  }

  model.initLanguage();
  model.initItems();

})();


3. App.js - loads templates and binds controls. Note that it parallelizes loading for the three templates, and each loaded template is then compiled in the same async context because there is no need to involve a window yet. Then the loader awaits on all three to finish, and finally, after the document is ready, renders templates into markup by supplying a model. In this scenario the single app.js handles all template logic, and if needed the more involved code can be moved to separate units, that can be named to match their templates.
(function() {

  var indexTemplate = null;
  var itemsTemplate = null;
  var itemTemplate = null;

  loadTemplatesAndRender();

  function loadTemplatesAndRender() {
    var t1 = $.get("templates/index.hbs", function (data) {
      var compiled = Handlebars.compile(data);
      indexTemplate = compiled;
    }, "html");
    var t2 = $.get("templates/items.hbs", function (data) {
      var compiled = Handlebars.compile(data);
      itemsTemplate = compiled;
    }, "html");
    // Note, the item template is a partial,
    // compiling just in case it’s used as a standalone template.
    var t3 = $.get("templates/item.hbs", function (data) {
      Handlebars.registerPartial("item", data);
      var compiled = Handlebars.compile(data);
      //console.log(compiled);
      itemTemplate = compiled;
    }, "html");
    // Wait for templates to finish loading.
    $.when(t1, t2, t3).done(function() {
      // Also wait for $(document).ready().
      $(function() {
        //alert("ALL DONE");
        renderPage();
        renderItems();
      })
    })
  }

  function renderPage() {
    var compiled = indexTemplate;
    // language is set by the initModel.
    var rendered = compiled(model.languages[model.lang]);
    $("#main").html(rendered);
  }

  function renderItems() {
    var compiled = itemsTemplate;
    // items and a language are set by the initModel.
    var rendered = compiled({
      items: model.items,
      language: model.languages[model.lang]
    });
    $("#theItems").html(rendered);
    attachButtons();
  }

  function attachButtons() {
    $(".button-one").click(function() {
      var id = $(this).closest(".item-card").data("item-id");
      model.chooseItem(id);
      renderItems();
    });

    $(".button-two").click(function() {
      var id = $(this).closest(".item-card").data("item-id");
      model.clearItem(id);
      renderItems();
    });
  }

})();


4. Index.hbs - the top-level template. The binding tags will be replaced by texts from the model.languages[lang] dictionary. And “theItems” element will host renderings of the deeper levels of templates.
<div class="header">
  <div class="title">
      <h1>{{siteTitle}}</h1>
  </div>
  <div>
    <nav class="navigation">
      <a href="?option=page1">{{page1Name}}</a>
      <a href="?option=page2">{{page2Name}}</a>
      <a href="?option=page3">{{page3Name}}</a>
    </nav>
  </div>
</div>
<div id="languageSwitch">
  <nav class="navigation">
    <a href="?lang=0">{{lang1Name}}</a>
    <a href="?lang=1">{{lang2Name}}</a>
  </nav>
</div>
<div class="content">
  <div id="theItems">
  </div>
  <footer class="footer">
  </footer>
</div>


5. Items.hbs - a template-iterator to render all items from the model.items array. It is using a partial to render individual items. In this case the partial is hard-coded and accepts a language object from the root template. For a conditional template selection a helper function can be used (as described in prior post).
{{#each items}}
  {{> item language=@root.language}}
{{else}}
  {{language.noItemsFound}}
{{/each}}


6. Item.hbs - a final template to render an individual item’s card. It has a simple markup to present each item’s name, picture, additional info, and two buttons. The placeholders refer to properties of an item. Handlebars will assign the source item as a context to this template.
<div class="item-card" data-item-id="{{id}}">
  <div class="card-title" style="background: url('images/{{image}}') center 15% no-repeat #46B6AC;">
      <h2>{{name}}</h2>
  </div>
  <div class="card-border">
    <button class="button-one">
      {{language.one}}
    </button>
    <button class="button-two">
      {{language.two}}
    </button>
  </div>
  {{#if moreInfo}}
    {{#if isGood}}
      <span class="result-good">{{language.good}}</span>
    {{else}}
      <span class="result-bad">{{language.bad}}</span>
    {{/if}}
  {{/if}}
</div>