diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
index 25aaaea0ffe2b98a6e46a7e133c53b743603530e..284f241b3ba54f834f2d5057bf1a67458645aa03 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
@@ -10,7 +10,7 @@
 
 <?php $_divId = 'tree-div_' . time() ?>
 <div id="<?php /* @escapeNotVerified */ echo $_divId ?>" class="tree"></div>
-<script id="ie-deferred-loader" defer="defer" src=""></script>
+<script id="ie-deferred-loader" defer="defer" src="//:"></script>
 <script>
     require([
         'jquery',
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
index 9e303fe921dfd328453c0f736a107fa4be70e6fa..ae65d2658978a3f2e74dc04278ce9d803dda471a 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
@@ -11,7 +11,7 @@
 <?php $_divId = 'tree' . $block->getId() ?>
 <div id="<?php /* @escapeNotVerified */ echo $_divId ?>" class="tree"></div>
 <!--[if IE]>
-<script id="ie-deferred-loader" defer="defer" src=""></script>
+<script id="ie-deferred-loader" defer="defer" src="//:"></script>
 <![endif]-->
 <script>
 require(['jquery', "prototype", "extjs/ext-tree-checkbox"], function(jQuery){
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
index f1bba6cc3c07ca0603e149afc8f0ad24b342a467..b3a7002ef417ecb3cc14006aa34b6844a32d8a67 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
@@ -38,7 +38,7 @@
             <span class="title"><?php /* @escapeNotVerified */ echo __('Unassigned Attributes') ?></span>
         </div>
         <div id="tree-div2" class="attribute-set-tree"></div>
-        <script id="ie-deferred-loader" defer="defer" src=""></script>
+        <script id="ie-deferred-loader" defer="defer" src="//:"></script>
         <script>
             define("tree-panel",
                 [
diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/button.js b/app/code/Magento/Ui/view/base/web/js/form/components/button.js
new file mode 100644
index 0000000000000000000000000000000000000000..b5373ff1648b7f2c3db97e8333551781c09b09e1
--- /dev/null
+++ b/app/code/Magento/Ui/view/base/web/js/form/components/button.js
@@ -0,0 +1,103 @@
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+    'uiElement',
+    'uiRegistry',
+    'uiLayout',
+    'mageUtils'
+], function (Element, registry, layout, utils) {
+    'use strict';
+
+    return Element.extend({
+        defaults: {
+            additionalClasses: {},
+            displayArea: 'outsideGroup',
+            displayAsLink: false,
+            elementTmpl: 'ui/form/element/button',
+            template: 'ui/form/components/button/simple'
+        },
+
+        /**
+         * Initializes component.
+         *
+         * @returns {Object} Chainable.
+         */
+        initialize: function () {
+            return this._super()
+                ._setClasses();
+        },
+
+        /**
+         * Performs configured actions
+         */
+        action: function () {
+            this.actions.forEach(this.applyAction, this);
+        },
+
+        /**
+         * Apply action on target component,
+         * but previously create this component from template if it is not existed
+         *
+         * @param {Object} action - action configuration
+         */
+        applyAction: function (action) {
+            var targetName = action.targetName,
+                params = action.params,
+                actionName = action.actionName,
+                target;
+
+            if (!registry.has(targetName)) {
+                this.getFromTemplate(targetName);
+            }
+            target = registry.async(targetName);
+
+            if (target && typeof target === 'function' && actionName) {
+                target(actionName, params);
+            }
+        },
+
+        /**
+         * Create target component from template
+         *
+         * @param {Object} targetName - name of component,
+         * that supposed to be a template and need to be initialized
+         */
+        getFromTemplate: function (targetName) {
+            var parentName = targetName.split('.'),
+                index = parentName.pop(),
+                child;
+
+            parentName = parentName.join('.');
+            child = utils.template({
+                parent: parentName,
+                name: index,
+                nodeTemplate: targetName
+            });
+            layout([child]);
+        },
+
+        /**
+         * Extends 'additionalClasses' object.
+         *
+         * @returns {Object} Chainable.
+         */
+        _setClasses: function () {
+            if (typeof this.additionalClasses === 'string') {
+                this.additionalClasses = this.additionalClasses
+                    .trim()
+                    .split(' ')
+                    .reduce(function (classes, name) {
+                        classes[name] = true;
+
+                        return classes;
+                    }, {}
+                );
+            }
+
+            return this;
+        }
+    });
+});
diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/element/links.js b/app/code/Magento/Ui/view/base/web/js/lib/core/element/links.js
index c769be3775f46ddba47d5d9110974fc2bd5782da..1ac9b45a46e86a9df232db8993932a886c3bcd8c 100644
--- a/app/code/Magento/Ui/view/base/web/js/lib/core/element/links.js
+++ b/app/code/Magento/Ui/view/base/web/js/lib/core/element/links.js
@@ -51,7 +51,7 @@ define([
         }
 
         if (owner.component !== target.component) {
-            value = utils.copy(value);
+            value = data.inversionValue ? !utils.copy(value) : utils.copy(value);
         }
 
         component.set(property, value);
@@ -149,6 +149,11 @@ define([
     function transfer(owner, data) {
         var args = _.toArray(arguments);
 
+        if (data.target.substr(0,1) === '!') {
+            data.target = data.target.substr(1);
+            data.inversionValue = true;
+        }
+
         if (owner.name === data.target) {
             args.unshift(owner);
 
diff --git a/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/outer_click.js b/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/outer_click.js
index 839a87481a38631267f295c9908abf2873d74f4d..7fa3ff22af8975b62d23b3f9c07ff3ab8d813c39 100644
--- a/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/outer_click.js
+++ b/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/outer_click.js
@@ -15,6 +15,30 @@ define([
         onlyIfVisible: true
     };
 
+    /**
+     * Checks if element sis visible.
+     *
+     * @param {Element} el
+     * @returns {Boolean}
+     */
+    function isVisible(el) {
+        var style = window.getComputedStyle(el),
+            visibility = {
+                display: 'none',
+                visibility: 'hidden',
+                opacity: '0'
+            },
+            visible = true;
+
+        _.each(visibility, function (val, key) {
+            if (style[key] === val) {
+                visible = false;
+            }
+        });
+
+        return visible;
+    }
+
     /**
      * Document click handler which in case if event target is not
      * a descendant of provided container element,
@@ -33,7 +57,7 @@ define([
         }
 
         if (config.onlyIfVisible) {
-            if (!_.isNull(container.offsetParent)) {
+            if (!_.isNull(container.offsetParent) && isVisible(container)) {
                 callback();
             }
         } else {
diff --git a/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js b/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js
new file mode 100644
index 0000000000000000000000000000000000000000..cccf95e00bf54f2a667e85fab4d5f12b435728a5
--- /dev/null
+++ b/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js
@@ -0,0 +1,315 @@
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+    'Magento_Ui/js/lib/view/utils/async',
+    'uiCollection',
+    'uiRegistry',
+    'underscore',
+    './modal'
+], function ($, Collection, registry, _) {
+    'use strict';
+
+    return Collection.extend({
+        defaults: {
+            template: 'ui/modal/modal-component',
+            options: {
+                title: '',
+                buttons: [],
+                keyEventHandlers: {}
+            },
+            valid: true,
+            listens: {
+                state: 'onState'
+            },
+            modalClass: 'modal-component'
+        },
+
+        /**
+         * Initializes component.
+         *
+         * @returns {Object} Chainable.
+         */
+        initialize: function () {
+            this._super();
+            _.bindAll(this,
+                'initModal',
+                'openModal',
+                'closeModal',
+                'toggleModal',
+                'setPrevValues',
+                'actionCancel',
+                'validate');
+            this.initializeContent();
+
+            return this;
+        },
+
+        /**
+         * Initializes modal configuration
+         *
+         * @returns {Object} Chainable.
+         */
+        initConfig: function () {
+            return this._super()
+                .initSelector()
+                .initModalEvents();
+        },
+
+        /**
+         * Configure modal selector
+         *
+         * @returns {Object} Chainable.
+         */
+        initSelector: function () {
+            this.contentSelector = '.' + this.modalClass;
+            this.options.modalClass = this.name.replace(/\./g, '_');
+            this.rootSelector = '.' + this.options.modalClass;
+
+            return this;
+        },
+
+        /**
+         * Configure modal keyboard handlers
+         * and outer click
+         *
+         * @returns {Object} Chainable.
+         */
+        initModalEvents: function () {
+            this.options.keyEventHandlers.escapeKey = this.options.outerClickHandler = this.actionCancel.bind(this);
+
+            return this;
+        },
+
+        /**
+         * Initialize modal's content components
+         */
+        initializeContent: function () {
+            $.async(this.contentSelector, this, this.initModal);
+        },
+
+        /**
+         * Init toolbar section so other components will be able to place something in it
+         */
+        initToolbarSection: function () {
+            this.set('toolbarSection', this.modal.data('modal').modal.find('header').get(0));
+        },
+
+        /**
+         * Initializes observable properties.
+         *
+         * @returns {Object} Chainable.
+         */
+        initObservable: function () {
+            this._super();
+            this.observe('state');
+
+            return this;
+        },
+
+        /**
+         * Wrap content in a modal of certain type
+         *
+         * @param {HTMLElement} element
+         * @returns {Object} Chainable.
+         */
+        initModal: function (element) {
+            if (!this.modal) {
+                this.overrideModalButtonCallback();
+                this.options.modalCloseBtnHandler = this.actionCancel;
+                this.modal = $(element).modal(this.options);
+                this.initToolbarSection();
+
+                if (this.waitCbk) {
+                    this.waitCbk();
+                    this.waitCbk = null;
+                }
+            }
+
+            return this;
+        },
+
+        /**
+         * Open modal
+         */
+        openModal: function () {
+            if (this.modal) {
+                this.state(true);
+            } else {
+                this.waitCbk = this.openModal;
+            }
+        },
+
+        /**
+         * Close modal
+         */
+        closeModal: function () {
+            if (this.modal) {
+                this.state(false);
+            } else {
+                this.waitCbk = this.closeModal;
+            }
+        },
+
+        /**
+         * Toggle modal
+         */
+        toggleModal: function () {
+            if (this.modal) {
+                this.state(!this.state());
+            } else {
+                this.waitCbk = this.toggleModal;
+            }
+        },
+
+        /**
+         * Wrap content in a modal of certain type
+         *
+         * @param {Boolean} state
+         */
+        onState: function (state) {
+            if (state) {
+                this.modal.modal('openModal');
+                this.applyData();
+            } else {
+                this.modal.modal('closeModal');
+            }
+        },
+
+        /**
+         * Validate everything validatable in modal
+         */
+        validate: function (elem) {
+            if (typeof elem.validate === 'function') {
+                this.valid = this.valid & elem.validate().valid;
+            } else if (elem.elems) {
+                elem.elems().forEach(this.validate, this);
+            }
+        },
+
+        /**
+         * Reset data from provider
+         */
+        resetData: function () {
+            this.elems().forEach(this.resetValue, this);
+        },
+
+        /**
+         * Update 'applied' property with data from modal content
+         */
+        applyData: function () {
+            var applied = {};
+
+            this.elems().forEach(this.gatherValues.bind(this, applied), this);
+            this.applied = applied;
+        },
+
+        /**
+         * Gather values from modal content
+         *
+         * @param {Array} applied
+         * @param {HTMLElement} elem
+         */
+        gatherValues: function (applied, elem) {
+            if (typeof elem.value === 'function') {
+                applied[elem.index] = elem.value();
+            } else if (elem.elems) {
+                elem.elems().forEach(this.gatherValues.bind(this, applied), this);
+            }
+        },
+
+        /**
+         * Set to previous values from modal content
+         *
+         * @param {HTMLElement} elem
+         */
+        setPrevValues: function (elem) {
+            if (typeof elem.value === 'function') {
+                this.modal.focus();
+                elem.value(this.applied[elem.index]);
+            } else if (elem.elems) {
+                elem.elems().forEach(this.setPrevValues, this);
+            }
+        },
+
+        /**
+         * Triggers some method in every modal child elem, if this method is defined
+         *
+         * @param {Object} action - action configuration,
+         * must contain actionName and targetName and
+         * can contain params
+         */
+        triggerAction: function (action) {
+            var targetName = action.targetName,
+                params = action.params,
+                actionName = action.actionName,
+                target;
+
+            target = registry.async(targetName);
+
+            if (target && typeof target === 'function' && actionName) {
+                target(actionName, params);
+            }
+        },
+
+        /**
+         * Override modal buttons callback placeholders with real callbacks
+         */
+        overrideModalButtonCallback: function () {
+            var buttons = this.options.buttons;
+
+            if (buttons && buttons.length) {
+                buttons.forEach(function (button) {
+                    button.click = this.getButtonClickHandler(button.actions);
+                }, this);
+            }
+        },
+
+        /**
+         * Generate button click handler based on button's 'actions' configuration
+         */
+        getButtonClickHandler: function (actionsConfig) {
+            var actions = actionsConfig.map(
+                function (actionConfig) {
+                    if (_.isObject(actionConfig)) {
+                        return this.triggerAction.bind(this, actionConfig);
+                    }
+
+                    return this[actionConfig] ? this[actionConfig].bind(this) : function () {};
+                }, this);
+
+            return function () {
+                actions.forEach(
+                    function (action) {
+                        action();
+                    }
+                );
+            };
+        },
+
+        /**
+         * Cancels changes in modal:
+         * returning elems values to the previous state,
+         * and close modal
+         */
+        actionCancel: function () {
+            this.elems().forEach(this.setPrevValues, this);
+            this.closeModal();
+        },
+
+        /**
+         * Accept changes in modal by not preventing them.
+         * Can be extended by exporting 'gatherValues' result somewhere
+         */
+        actionDone: function () {
+            this.valid = true;
+            this.elems().forEach(this.validate, this);
+
+            if (this.valid) {
+                this.closeModal();
+            }
+        }
+    });
+});
diff --git a/app/code/Magento/Ui/view/base/web/js/modal/modal.js b/app/code/Magento/Ui/view/base/web/js/modal/modal.js
index 23011d607cdadc301ee6cf4cebeb148b400c0260..5dcfc53459baad6607edce1f13be49e708ead692 100644
--- a/app/code/Magento/Ui/view/base/web/js/modal/modal.js
+++ b/app/code/Magento/Ui/view/base/web/js/modal/modal.js
@@ -118,13 +118,14 @@ define([
                 'closeModal'
             );
 
+            _.extend(this.keyEventHandlers, this.options.keyEventHandlers);
             this.options.transitionEvent = transitionEvent;
             this._createWrapper();
             this._renderModal();
             this._createButtons();
             $(this.options.trigger).on('click', _.bind(this.toggleModal, this));
             this._on(this.modal.find(this.options.modalCloseBtn), {
-                'click': this.closeModal
+                'click': this.options.modalCloseBtnHandler ? this.options.modalCloseBtnHandler : this.closeModal
             });
             this._on(this.element, {
                 'openModal': this.openModal,
@@ -374,7 +375,8 @@ define([
          * Creates overlay, append it to wrapper, set previous click event on overlay.
          */
         _createOverlay: function () {
-            var events;
+            var events,
+                outerClickHandler = this.options.outerClickHandler || this.closeModal;
 
             this.overlay = $('.' + this.options.overlayClass);
 
@@ -386,7 +388,7 @@ define([
             }
             events = $._data(this.overlay.get(0), 'events');
             events ? this.prevOverlayHandler = events.click[0].handler : false;
-            this.options.clickableOverlay ? this.overlay.unbind().on('click', this.closeModal) : false;
+            this.options.clickableOverlay ? this.overlay.unbind().on('click', outerClickHandler) : false;
         },
 
         /**
diff --git a/app/code/Magento/Ui/view/base/web/templates/form/components/button/simple.html b/app/code/Magento/Ui/view/base/web/templates/form/components/button/simple.html
new file mode 100644
index 0000000000000000000000000000000000000000..d562e699c843a584fabd5f35905ca5e92ef8ad96
--- /dev/null
+++ b/app/code/Magento/Ui/view/base/web/templates/form/components/button/simple.html
@@ -0,0 +1,7 @@
+<!--
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<render args="elementTmpl"/>
\ No newline at end of file
diff --git a/app/code/Magento/Ui/view/base/web/templates/form/element/button.html b/app/code/Magento/Ui/view/base/web/templates/form/element/button.html
new file mode 100644
index 0000000000000000000000000000000000000000..8f337ce7acb966b57b511b1535ceedb6117cdc03
--- /dev/null
+++ b/app/code/Magento/Ui/view/base/web/templates/form/element/button.html
@@ -0,0 +1,14 @@
+<!--
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<button type="button"
+        css="
+            'action-advanced': $data.displayAsLink,
+            'action-secondary': !$data.displayAsLink
+        "
+        click="action"
+        text="title">
+</button>
\ No newline at end of file
diff --git a/app/code/Magento/Ui/view/base/web/templates/modal/modal-component.html b/app/code/Magento/Ui/view/base/web/templates/modal/modal-component.html
new file mode 100644
index 0000000000000000000000000000000000000000..e39c27e0741232496319e1385a667b181ee623f7
--- /dev/null
+++ b/app/code/Magento/Ui/view/base/web/templates/modal/modal-component.html
@@ -0,0 +1,12 @@
+<!--
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+
+<div data-bind="css: modalClass">
+    <!-- ko foreach: { data: elems, as: 'element' } -->
+    <!-- ko template: element.getTemplate() --><!-- /ko -->
+    <!-- /ko -->
+</div>
\ No newline at end of file
diff --git a/app/code/Magento/Widget/view/adminhtml/templates/catalog/category/widget/tree.phtml b/app/code/Magento/Widget/view/adminhtml/templates/catalog/category/widget/tree.phtml
index 135880f5f314214efdc4d95540f489a5fc7bd8fe..89d2b75a3b882322a91f953208242673130ae5b9 100644
--- a/app/code/Magento/Widget/view/adminhtml/templates/catalog/category/widget/tree.phtml
+++ b/app/code/Magento/Widget/view/adminhtml/templates/catalog/category/widget/tree.phtml
@@ -10,7 +10,7 @@
 
 <?php $_divId = 'tree' . $block->getId() ?>
 <div id="<?php /* @escapeNotVerified */ echo $_divId ?>" class="tree"></div>
-<script id="ie-deferred-loader" defer="defer" src=""></script>
+<script id="ie-deferred-loader" defer="defer" src="//:"></script>
 <script>
 require(['jquery', "prototype", "extjs/ext-tree-checkbox"], function(jQuery){
 
diff --git a/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml b/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml
index 6831ca32a007b1c5abe9f67b322e76fe5ee38749..a98b8df1fd5deb0e3b50ee395f4a9102cca68083 100644
--- a/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml
+++ b/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml
@@ -15,7 +15,7 @@
         <div class="actions"><?php echo $block->getAddLayoutButtonHtml() ?></div>
     </div>
 </fieldset>
-<script id="ie-deferred-loader" defer="defer" src=""></script>
+<script id="ie-deferred-loader" defer="defer" src="//:"></script>
 <script>
 require([
     'jquery',