dataSourceEndpointArray.forEach((e) => {
            let endpoint = e;
            if (endpoint.uri) {
                console.warn('dataSourceEndpoint expects URI in "target". Please update your JSON to reflect the new syntax');
                endpoint = merge(endpoint, {
                    target: endpoint
                });
                delete endpoint.uri;
            }

            createViewModel.call(context, {
                type: 'action',
                actionType: 'ajax',
                options: endpoint
            }).action({
                callback: function (error, results) {
                    let resultsByKey,
                        keyMapArray = endpoint.keyMap || [{}],
                        newDataObject = {};

                    count += 1;

                    if (!Array.isArray(keyMapArray)) {
                        keyMapArray = [keyMapArray];
                    }

                    if (!error) {
                        keyMapArray.forEach((keyMap) => {
                            resultsByKey = keyMap.resultsKey ? get(results, keyMap.resultsKey) : results;
                            // optional: keyMap.dataKey path to extend dataObject on
                            if (keyMap.dataKey) {
                                newDataObject[keyMap.dataKey] = resultsByKey;
                            } else if (keyMap.storeKey) {
                                noticeboard.setValue(keyMap.storeKey, resultsByKey);
                            } else {
                                newDataObject = resultsByKey;
                            }
                            extend(dataObject, newDataObject);
                        });
                    }

                    if (count === dataSourceEndpointArray.length) {
                        updateData(dataObject);
                        if (!mappedChildNodes().length) {
                            mappedChildNodes(createViewModels.call(context, node.children || []));
                        }
                    }
                }
            });
        });
 tabObj.setActiveTab = function () {
     // merge the params from route with current query as not to overwrite it
     // also merge with the tabObj.tabDef.params so that the tab initializes correctly
     // for the objectContains check below, if the tabDef.params is part of the query it will use that tab as the initial tab
     // so we need to make sure, that if we're setting a tabRoute, that the params of the last tab are overwriten with the params in the current tab
     // so you dont get another route accidentally
     createViewModel.call(context, {
         type: 'action',
         actionType: 'route',
         options: {
             target: tab.route.target,
             params: merge(
                 getCurrent().query,
                 tabObj.tabDef.params,
                 tab.route.params
             )
         }
     }).action();
 }
 selectedItem.subscribe((item) => {
     const action = _.cloneDeep(node.selection),
         selectionCtx = _.cloneDeep(context);
     selectionCtx.data = item;
     createViewModel.call(selectionCtx, action).action();
 });
 gridHeaderItems = gridHeaderItems.map(item => createViewModel.call(gridContext, item));
    export default function (node, metadata) {
        var context = this,
            createViewModel = createViewModelUnbound.bind(context), //ensures context is passed
            options = node.options || {},
            mappedChildNodes,
            activeTabRegion = observable({}),
            initialized = false,
            subs = [],
            query = getCurrent().query,
            tabs = [],
            initialActiveTab;

        // when tabs are selected update the tab route
        // do not update tabroute on initialization, just replace
        function setTabRoute(tabDef) {
            var currentRoute = getCurrent().route + (getCurrent().path ? '/' + getCurrent().path : '' ),
                query = getCurrent().query;
            setRoute(tabDef.target || currentRoute, merge(query,tabDef.params), false, initialized);
            initialized = true;
        }

        // zip the children tabs with the tab defs
        node.children.forEach(function (tab, index) {
            var tabTemplate = observable(), // can by dynamic because of ajax tabs
                tabDef = typeof node.headers[index] === 'string' ? { text: node.headers[index] } : node.headers[index],
                tabName = tabDef.computed ? computed(function () {
                    return formatText(tabDef.text, context.getValue);
                }) : tabDef.text,
                tabObj = {
                    tabName: tabName,
                    tabDef: tabDef,
                    tabTemplate: tabTemplate,
                    keepCache: tab.cache || node.cache
                },
                tabViewModel;

           // sets the active tab region to the tab template
           // sets newRoute if defined, else sets tabRoute
           tabObj.setActiveTab = function (newRoute) {
               if (options.validations && options.validations[tabDef.text]) {
                   notify(options.validations[tabDef.text], {
                       successCallback: function() {
                           activeTabRegion(tabTemplate());
                           if (options.setRoute !== false) {
                               setTabRoute(newRoute || tabDef);
                           }
                       }
                   });
               } else {
                    activeTabRegion(tabTemplate());
                    if (options.setRoute !== false) {
                        setTabRoute(newRoute || tabDef);
                    }
               }
           };

           // tab is active when the active region is the template
           tabObj.isActive = computed(function () {
                return activeTabRegion() === tabTemplate();
            });

            // create tabViewModel from type if defined
            // else call createViewModel
            if (tabTypes[tab.type]) {
                tabViewModel = tabTypes[tab.type].call(context, tabObj, tab)
            } else {
                tabViewModel = createViewModel(tab)
                tabTemplate(template('metadata_item_template', tabViewModel));
            }

            extend(tabObj, tabViewModel);

            // visible expression binding using context' getValue
            if(has(tabDef.visible)) {
                tabObj.visible = is(tabDef.visible, 'boolean') ?
                    tabDef.visible
                    : computed(function() {
                        return evaluate(tabDef.visible, context.getValue || function (id) {
                            console.error('Trying to evaluate a binding when getValue isnt specified on the context', tabObj);
                        });
                    });
            } else {
                tabObj.visible = true;
            }

            tabs.push(tabObj);

            // sets the active tab if defined in the query or tabDef
            if(tabDef.isActive || tabDef.params && query && objectContains(query, tabDef.params)) {
                initialActiveTab = tabObj;
            }
        });

        if (initialActiveTab) {
            initialActiveTab.setActiveTab();
        } else if (!activeTabRegion().template) {
            // initialize to first tab if we havent routed to a specific tab

            // will set first visible tab to active tab
            let initialTab = tabs.filter((tab) => {
                return unwrap(tab.visible);
            })[0];

            initialTab && initialTab.setActiveTab();
        }

        // receive events to set active tab
        if(node.id) {
            subs.push(receive(node.id +'.setActiveTab', function (params) {
                tabs.some(function(tab) {
                    // if activeTab: 'x' is in both objects, set the active tab
                    // and write this better..maybe use ids
                    if(objectContains(params, tab.tabDef.params)) {
                        tab.setActiveTab(merge(cloneDeep(tab.tabDef), { params: params }));
                    }
                });
            }));

            subs.push(receive(node.id+'.setNextTab', function () {
                var currentIndex =  0,
                    newIndex;

                tabs.some(function(tab, index) {
                    if (activeTabRegion() == tab.tabTemplate()) {
                        currentIndex = index;
                    }
                })

                while(newIndex === undefined) {
                    if (!tabs[(currentIndex+1) % tabs.length].isChild) {
                        newIndex = currentIndex +1;
                    } else {
                        currentIndex++
                    }
                }

                tabs[newIndex].setActiveTab();
            }));
        }

        return merge(node, {
            tabs: tabs,
            mappedChildNodes: tabs,
            activeTabRegion: activeTabRegion,
            context: this,
            dispose: function () {
                subs.forEach(function (sub) {
                    sub.dispose();
                });

                tabs.forEach(function (tab) {
                    var tabViewModels = tab.tabTemplate() && tab.tabTemplate().template.data;
                    // why is tab viewmodels an array, anyway?
                    if (tabViewModels && !Array.isArray(tabViewModels)) {
                        tabViewModels = [tabViewModels];
                    }
                    (tabViewModels || []).forEach(function(vm) {
                        vm && vm.dispose && vm.dispose();
                    });
                });
            }
        });
    };
 click: function () {
     createViewModel.call({
         data: data
     }, ctx.$parent.deleteAction).action();
 }