-
+
+
+
-
diff --git a/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/uploader.html b/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/uploader.html
index dbb3e1ff2c981..6360bfafa3c32 100644
--- a/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/uploader.html
+++ b/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/uploader.html
@@ -9,10 +9,10 @@
-
+
-
+
@@ -20,12 +20,12 @@
-
-
+
+
diff --git a/app/code/Magento/Ui/view/base/web/templates/form/field.html b/app/code/Magento/Ui/view/base/web/templates/form/field.html
index d793b39c95201..2abc13dde1bb0 100644
--- a/app/code/Magento/Ui/view/base/web/templates/form/field.html
+++ b/app/code/Magento/Ui/view/base/web/templates/form/field.html
@@ -11,7 +11,6 @@
-
@@ -29,14 +28,14 @@
-
-
-
+
+
+
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html
index 57a84fb6cfce4..32625bf194b74 100644
--- a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html
+++ b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html
@@ -20,6 +20,7 @@
_hover: $parent.root.isHovered(option, $element),
_expended: $parent.root.getLevelVisibility($data),
_unclickable: $parent.root.isLabelDecoration($data),
+ _last: $parent.root.addLastElement($data),
'_with-checkbox': $parent.root.showCheckbox
},
click: function(data, event){
diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html
index 5038aa1de0b2b..b8a5b8a05976e 100644
--- a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html
+++ b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html
@@ -67,15 +67,15 @@
@@ -89,10 +89,10 @@
@@ -180,4 +180,4 @@
-
\ No newline at end of file
+
diff --git a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less
index 525b6f7260c65..b2e881110e6c3 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less
@@ -118,6 +118,10 @@ body._in-resize {
}
}
+ &._dragged {
+ outline: 1px solid @data-grid-dragging-copy__outline-color;
+ }
+
thead {
background-color: transparent;
}
@@ -158,6 +162,24 @@ body._in-resize {
}
}
+ &._dragged {
+ td {
+ background: @data-grid-td__dragging__background-color;
+ }
+ }
+
+ &._dragover-top {
+ td {
+ box-shadow: inset 0 @data-grid-horizontal-dragover-mark__width 0 0 @data-grid-horizontal-dragover-mark__color;
+ }
+ }
+
+ &._dragover-bottom {
+ td {
+ box-shadow: inset 0 -@data-grid-horizontal-dragover-mark__width 0 0 @data-grid-horizontal-dragover-mark__color;
+ }
+ }
+
&:not(.data-grid-editable-row) {
&:last-child {
td {
@@ -938,4 +960,4 @@ body._in-resize {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less
index 202dfd67d6401..a624c9b8c812e 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less
@@ -30,12 +30,13 @@
// Tree
@action-multiselect-tree-arrow__color: @color-gray65-almost;
-@action-multiselect-tree-arrow__size: 1.8rem;
+@action-multiselect-tree-arrow__size: 2.2rem;
+@action-multiselect-tree-level__width: 2rem;
@action-multiselect-tree-lines: @action-multiselect-tree-lines__size @action-multiselect-tree-lines__style @action-multiselect-tree-lines__color;
@action-multiselect-tree-lines__color: @color-gray65-almost;
@action-multiselect-tree-lines__size: 1px;
@action-multiselect-tree-lines__style: dashed;
-@action-multiselect-tree-menu-item__margin-left: @action-multiselect-tree-arrow__size + @action-multiselect-menu-item__padding;
+@action-multiselect-tree-menu-item__margin-left: @action-multiselect-tree-arrow__size + @action-multiselect-tree-level__width;
//
// Multiselect default view
@@ -118,6 +119,7 @@
.admin__action-multiselect-item-path {
color: @action-multiselect-menu-item-path__color;
font-size: 1.2rem;
+ font-weight: @font-weight__regular;
padding-left: 1rem;
}
}
@@ -261,8 +263,6 @@
.action-menu-item {
margin-top: .1rem;
- padding-left: .5rem;
- padding-right: .5rem;
}
}
@@ -291,7 +291,7 @@
&._with-checkbox {
.admin__action-multiselect-label {
- padding-left: @control-checkbox-radio__size + .5rem;
+ padding-left: @control-checkbox-radio__size + 1rem;
}
}
}
@@ -300,6 +300,8 @@
position: relative;
.admin__action-multiselect-menu-inner {
+ padding-left: @action-multiselect-tree-menu-item__margin-left - @action-multiselect-menu-item__padding;
+
&:before {
left: @action-multiselect-menu-item__padding + @action-multiselect-tree-arrow__size + @action-multiselect-tree-arrow__size/2;
}
@@ -327,7 +329,7 @@
border-top: @action-multiselect-tree-lines;
height: 1px;
top: @action-multiselect-menu-item__padding + @action-multiselect-tree-arrow__size/2;
- width: @action-multiselect-tree-menu-item__margin-left;
+ width: @action-multiselect-tree-menu-item__margin-left + @action-multiselect-menu-item__padding;
}
// Vertical dotted line
@@ -347,13 +349,15 @@
// Top level on tree
&._root {
+ margin-left: -@action-multiselect-menu-item__padding;
+
&:after {
- left: @action-multiselect-tree-arrow__size;
+ left: @action-multiselect-tree-arrow__size + @action-multiselect-menu-item__padding;
width: @action-multiselect-tree-arrow__size;
}
&:before {
- left: @action-multiselect-tree-arrow__size;
+ left: @action-multiselect-tree-arrow__size + @action-multiselect-menu-item__padding;
top: @action-multiselect-menu-item__padding;
}
@@ -363,6 +367,12 @@
}
}
+ &:first-child {
+ &:before {
+ top: @action-multiselect-menu-item__padding + @action-multiselect-tree-arrow__size/2;
+ }
+ }
+
&:last-child {
&:before {
height: @action-multiselect-menu-item__padding;
@@ -372,13 +382,14 @@
}
.admin__action-multiselect-label {
+ line-height: @action-multiselect-tree-arrow__size;
vertical-align: middle;
- word-break: break-word;
+ word-break: break-all;
&:before {
left: 0;
position: absolute;
- top: .1rem;
+ top: .4rem;
}
}
}
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-collapsible.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-collapsible.less
index 9cd1021e699c2..d297a57309700 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-collapsible.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-collapsible.less
@@ -19,6 +19,11 @@
@control-collapsible-content__padding: @indent__s;
+@control-collapsible-row__dragging__background-color: @color-light-gray0;
+
+@control-collapsible-horizontal-dragover-mark__color: @color-blue-dodger;
+@control-collapsible-horizontal-dragover-mark__height: 3px;
+
//
// Table with collapsible panel
// _____________________________________________
@@ -26,6 +31,45 @@
.admin__control-collapsible {
width: 100%;
+ ._dragged {
+ .admin__collapsible-block-wrapper {
+ .admin__collapsible-title {
+ background: @control-collapsible-row__dragging__background-color;
+ }
+ }
+ }
+
+ ._dragover-top,
+ ._dragover-bottom {
+ .admin__collapsible-block-wrapper {
+ &:before {
+ background: @control-collapsible-horizontal-dragover-mark__color;
+ content: '';
+ display: block;
+ height: @control-collapsible-horizontal-dragover-mark__height;
+ left: 0;
+ position: absolute;
+ right: 0;
+ }
+ }
+ }
+
+ ._dragover-top {
+ .admin__collapsible-block-wrapper {
+ &:before {
+ top: -@control-collapsible-horizontal-dragover-mark__height;
+ }
+ }
+ }
+
+ ._dragover-bottom {
+ .admin__collapsible-block-wrapper {
+ &:before {
+ bottom: -@control-collapsible-horizontal-dragover-mark__height;
+ }
+ }
+ }
+
.admin__collapsible-block-wrapper {
&.fieldset-wrapper {
border: 0;
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-table.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-table.less
index eb66fd69124e4..037358833e9a7 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-table.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-table.less
@@ -16,6 +16,9 @@
@control-table-cell__padding-vertical: 1.3rem;
@control-table-cell__padding-horizontal: 1rem;
+@control-table-horizontal-dragover-mark__color: @color-blue-dodger;
+@control-table-horizontal-dragover-mark__width: 3px;
+
//
.admin__control-table-wrapper {
@@ -55,6 +58,18 @@
}
}
+ &._dragover-top {
+ td {
+ box-shadow: inset 0 @control-table-horizontal-dragover-mark__width 0 0 @control-table-horizontal-dragover-mark__color;
+ }
+ }
+
+ &._dragover-bottom {
+ td {
+ box-shadow: inset 0 -@control-table-horizontal-dragover-mark__width 0 0 @control-table-horizontal-dragover-mark__color;
+ }
+ }
+
&._dragged {
td,
th {
@@ -176,4 +191,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/variables/_data-grid.less b/app/design/adminhtml/Magento/backend/web/css/source/variables/_data-grid.less
index df5f3ad2ea0e2..bea044357cfe4 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/variables/_data-grid.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/variables/_data-grid.less
@@ -47,9 +47,14 @@
@data-grid-bulk-edit-panel__background-color: @color-lazy-sun-white;
+@data-grid-td__dragging__background-color: @color-light-gray0;
@data-grid-td__dragging__opacity: 95%;
-@data-grid-dragging-copy__border-color: @color-blue-pure;
@data-grid-dragging-copy__border: 1px solid @data-grid-dragging-copy__border-color;
+@data-grid-dragging-copy__border-color: @color-blue-pure;
+@data-grid-dragging-copy__outline-color: @color-blue-pure;
+
+@data-grid-horizontal-dragover-mark__color: @color-blue-dodger;
+@data-grid-horizontal-dragover-mark__width: 3px;
@data-grid-spinner__size: 4rem;
diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js
new file mode 100644
index 0000000000000..00eef94e2b203
--- /dev/null
+++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js
@@ -0,0 +1,276 @@
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+/*eslint max-nested-callbacks: 0*/
+
+define([
+ 'jquery',
+ 'Magento_Ui/js/form/element/file-uploader'
+], function ($, FileUploader) {
+ 'use strict';
+
+ describe('Magento_Ui/js/form/element/file-uploader', function () {
+ var component;
+
+ beforeEach(function () {
+ component = new FileUploader({
+ dataScope: 'abstract'
+ });
+ });
+
+ describe('initUploader method', function () {
+ it('creates instance of file uploader', function () {
+ var elem = document.createElement('input');
+
+ spyOn($.fn, 'fileupload');
+
+ component.initUploader(elem);
+
+ expect($.fn.fileupload).toHaveBeenCalled();
+ });
+ });
+
+ describe('isFileAllowed method', function () {
+ var invalidFile,
+ validFile;
+
+ invalidFile = {
+ size: 2000,
+ name: 'name.txt'
+ };
+
+ validFile = {
+ size: 500,
+ name: 'name.jpg'
+ };
+
+ it('validates file extension', function () {
+ var valid,
+ invalid;
+
+ component.allowedExtensions = ['jpg'];
+ component.maxFileSize = false;
+
+ valid = component.isFileAllowed(validFile);
+ invalid = component.isFileAllowed(invalidFile);
+
+ expect(valid.passed).toBe(true);
+ expect(invalid.passed).toBe(false);
+ });
+
+ it('validates file size', function () {
+ var valid,
+ invalid;
+
+ component.allowedExtensions = [];
+ component.maxFileSize = 1000;
+
+ valid = component.isFileAllowed(validFile);
+ invalid = component.isFileAllowed(invalidFile);
+
+ expect(valid.passed).toBe(true);
+ expect(invalid.passed).toBe(false);
+ });
+ });
+
+ describe('formatSize method', function () {
+ it('converts bytes value to a more readable string representation', function () {
+ var bytes = 28912,
+ expected = '28 KB',
+ result = component.formatSize(bytes);
+
+ expect(result).toEqual(expected);
+ });
+ });
+
+ describe('reset method', function () {
+ it('restores initial files set', function () {
+ var file1 = {},
+ file2 = {};
+
+ component.initialValue = [file1];
+
+ component.addFile(file2);
+ component.reset();
+
+ expect(component.value()).toEqual(jasmine.arrayContaining([file1]));
+ expect(component.value()).not.toEqual(jasmine.arrayContaining([file2]));
+ });
+ });
+
+ describe('hasChanged method', function () {
+ it('checks if files set is different from its initial value', function () {
+ component.initialValue = [{}];
+
+ component.addFile({});
+
+ expect(component.hasChanged()).toBe(true);
+
+ component.reset();
+
+ expect(component.hasChanged()).toBe(false);
+ });
+ });
+
+ describe('clear method', function () {
+ it('removes all files from collection', function () {
+ var file = {};
+
+ component.addFile(file);
+
+ expect(component.value().length).toBeGreaterThan(0);
+
+ component.clear();
+
+ expect(component.value().length).toEqual(0);
+ });
+
+ it('returns instance of component', function () {
+ var instance = component.clear();
+
+ expect(instance).toEqual(component);
+ });
+ });
+
+ describe('addFile method', function () {
+ it('adds single file to collection', function () {
+ var file1 = {},
+ file2 = {};
+
+ this.isMultipleFiles = false;
+
+ component.addFile(file1);
+ component.addFile(file2);
+
+ expect(component.value()).toEqual(jasmine.arrayContaining([file2]));
+ expect(component.value().length).toEqual(1);
+ });
+
+ it('adds multiple files to collection', function () {
+ var file1 = {},
+ file2 = {};
+
+ this.isMultipleFiles = true;
+
+ component.addFile(file1);
+ component.addFile(file2);
+
+ expect(component.value()).toEqual(jasmine.arrayContaining([file1, file2]));
+
+ this.isMultipleFiles = false;
+ });
+
+ it('returns instance of component', function () {
+ var instance = component.addFile({});
+
+ expect(instance).toEqual(component);
+ });
+ });
+
+ describe('removeFile method', function () {
+ it('removes single file from collection', function () {
+ var file = {};
+
+ component.addFile(file);
+ component.removeFile(file);
+
+ expect(component.value()).not.toEqual(jasmine.arrayContaining([file]));
+ });
+
+ it('returns instance of component', function () {
+ var instance = component.removeFile({});
+
+ expect(instance).toEqual(component);
+ });
+ });
+
+ describe('getFile method', function () {
+ it('returns instance of a file found by search criteria', function () {
+ var matchedFile,
+ file = {};
+
+ component.addFile(file);
+
+ matchedFile = component.getFile(function (item) {
+ return item === file;
+ });
+
+ expect(matchedFile).toEqual(file);
+ });
+ });
+
+ describe('hasData method', function () {
+ it('checks that collection has some items', function () {
+ var file = {};
+
+ component.addFile(file);
+
+ expect(component.hasData()).toBe(true);
+
+ component.clear();
+
+ expect(component.hasData()).toBe(false);
+ });
+ });
+
+ describe('onLoadingStart method', function () {
+ it('sets isLoading flag to be true', function () {
+ component.isLoading = false;
+ component.onLoadingStart();
+
+ expect(component.isLoading).toBe(true);
+ });
+ });
+
+ describe('onLoadingStop method', function () {
+ it('drops isLoading flag', function () {
+ component.isLoading = true;
+ component.onLoadingStop();
+
+ expect(component.isLoading).toBe(false);
+ });
+ });
+
+ describe('onFileUploaded handler', function () {
+ it('calls addFile method if upload was successful', function () {
+ spyOn(component, 'addFile');
+
+ component.onFileUploaded({}, {
+ result: {
+ error: false
+ }
+ });
+
+ expect(component.addFile).toHaveBeenCalled();
+ });
+
+ it('calls notifyError method if upload resulted in error', function () {
+ spyOn(component, 'notifyError');
+ spyOn(component, 'addFile');
+
+ component.onFileUploaded({}, {
+ result: {
+ error: true
+ }
+ });
+
+ expect(component.notifyError).toHaveBeenCalled();
+ expect(component.addFile).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('onElementRender handler', function () {
+ it('invokes initUploader method', function () {
+ var input = document.createElement('input');
+
+ spyOn(component, 'initUploader');
+
+ component.onElementRender(input);
+
+ expect(component.initUploader).toHaveBeenCalledWith(input);
+ });
+ });
+ });
+});
diff --git a/lib/web/mage/calendar.js b/lib/web/mage/calendar.js
index 53275c077e6e4..c8f63dc49f29a 100644
--- a/lib/web/mage/calendar.js
+++ b/lib/web/mage/calendar.js
@@ -493,6 +493,23 @@
// Overrides the "today" button functionality to select today's date when clicked.
$.datepicker._gotoTodayOriginal = $.datepicker._gotoToday;
+ /**
+ * overwrite jQuery UI _showDatepicker function for proper HTML generation conditions.
+ *
+ */
+ $.datepicker._showDatepickerOriginal = $.datepicker._showDatepicker;
+
+ /**
+ * Triggers original method showDataPicker for rendering calendar
+ * @param {HTMLObject} input
+ * @private
+ */
+ $.datepicker._showDatepicker = function (input) {
+ if (!input.disabled) {
+ $.datepicker._showDatepickerOriginal.call(this, input);
+ }
+ };
+
/**
* _gotoToday
* @param {Object} el
diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js
index 4639919bec8ec..2409465f593a2 100644
--- a/lib/web/mage/validation.js
+++ b/lib/web/mage/validation.js
@@ -147,17 +147,17 @@
*
* @return {Boolean}
*/
- function tableSingleValidation (value, element) {
+ function tableSingleValidation(value, element) {
var empty = $(element).closest('table')
.find('input.required-option:visible')
- .filter(function(i, el){
+ .filter(function (i, el) {
return $.mage.isEmpty(el.value);
})
.length;
return empty === 0;
}
- /**
+ /**
* Collection of validation rules including rules from additional-methods.js
* @type {Object}
*/
@@ -1334,7 +1334,6 @@
element = $(element);
var form = element.get(0).form,
validator = form ? $(form).data('validator') : null;
-
if (validator) {
return validator.element(element.get(0));
} else {
@@ -1350,6 +1349,61 @@
}
};
+ var originValidateDelegate = $.fn.validateDelegate;
+
+ $.fn.validateDelegate = function () {
+ if (!this[0].form) {
+ return this;
+ }
+
+ return originValidateDelegate.apply(this, arguments);
+ };
+
+ /**
+ * Validate single element.
+ *
+ * @param {Element} element
+ * @returns {*}
+ */
+ $.validator.validateSingleElement = function (element) {
+ var errors = {},
+ valid = true,
+ validateConfig = {
+ errorElement: 'label'
+ },
+ form, validator, classes;
+
+ element = $(element);
+ form = element.get(0).form;
+ validator = form ? $(form).data('validator') : null;
+
+ if (validator) {
+ return validator.element(element.get(0));
+ }
+
+ classes = element.prop('class').split(' ');
+ validator = element.parent().data('validator') ||
+ $.mage.validation(validateConfig, element.parent()).validate;
+
+ element.removeClass(validator.settings.errorClass);
+ validator.toHide = validator.toShow;
+ validator.hideErrors();
+ validator.toShow = validator.toHide = $([]);
+
+ $.each(classes, $.proxy(function (i, className) {
+ if (this.methods[className] && !this.methods[className](element.val(), element.get(0))) {
+ valid = false;
+ errors[element.get(0).name] = this.messages[className];
+ validator.invalid[element.get(0).name] = true;
+ validator.showErrors(errors);
+
+ return valid;
+ }
+ }, this));
+
+ return valid;
+ };
+
$.widget("mage.validation", {
options: {
meta: "validate",