diff --git a/app/code/Magento/Ui/etc/ui_configuration.xsd b/app/code/Magento/Ui/etc/ui_configuration.xsd index bcfad17477f78..43704813eb968 100644 --- a/app/code/Magento/Ui/etc/ui_configuration.xsd +++ b/app/code/Magento/Ui/etc/ui_configuration.xsd @@ -48,6 +48,7 @@ + @@ -99,7 +100,6 @@ - @@ -131,34 +131,26 @@ - - - - - - - - - - + + - + - + - - + - + - + - + + diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition.xml b/app/code/Magento/Ui/view/base/ui_component/etc/definition.xml index 93df217f70483..c84a0bd9f587e 100755 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition.xml +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition.xml @@ -206,25 +206,23 @@ - + Magento_Ui/js/form/element/abstract - ui/form/field + ui/dynamic-rows/cells/action-delete + ui/dynamic-rows/cells/action-delete - - + + - + Magento_Ui/js/form/element/abstract - - ui/dynamic-rows/cells/action-delete - ui/dynamic-rows/cells/action-delete - + ui/form/field - + diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dnd.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dnd.js index bedbccb7b86e1..45c99579d0938 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dnd.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dnd.js @@ -50,6 +50,11 @@ define([ defaults: { rootSelector: '${ $.recordsProvider }:div.admin__field', tableSelector: '${ $.rootSelector } -> table.admin__dynamic-rows', + separatorsClass: { + top: '_dragover-top', + bottom: '_dragover-bottom' + }, + step: 'auto', recordsCache: [], draggableElement: {}, draggableElementClass: '_dragged', @@ -115,22 +120,21 @@ define([ */ mousedownHandler: function (data, elem, event) { var recordNode = this.getRecordNode(elem), - originRecord = $(elem).parents('tr'); + originRecord = $(elem).parents('tr'), + drEl = this.draggableElement; $(recordNode).addClass(this.draggableElementClass); $(originRecord).addClass(this.draggableElementClass); - this.draggableElement.originRow = originRecord; - this.draggableElement.instance = recordNode = this.processingStyles(recordNode, elem); - this.draggableElement.instanceCtx = this.getRecord(originRecord[0]); - this.draggableElement.eventMousedownY = event.pageY; - this.draggableElement.minYpos = + this.step = this.step === 'auto' ? originRecord.height() / 2 : this.step; + drEl.originRow = originRecord; + drEl.instance = recordNode = this.processingStyles(recordNode, elem); + drEl.instanceCtx = this.getRecord(originRecord[0]); + drEl.eventMousedownY = event.pageY; + drEl.minYpos = this.table.offset().top - originRecord.offset().top + this.table.outerHeight() - this.table.find('tbody').outerHeight(); - this.draggableElement.maxYpos = - this.draggableElement.minYpos + - this.table.find('tbody').outerHeight() - originRecord.outerHeight(); + drEl.maxYpos = drEl.minYpos + this.table.find('tbody').outerHeight() - originRecord.outerHeight(); this.tableWrapper.append(recordNode); - this.body.bind('mousemove', this.mousemoveHandler); this.body.bind('mouseup', this.mouseupHandler); }, @@ -141,17 +145,28 @@ define([ * @param {Object} event - mouse move event */ mousemoveHandler: function (event) { - var positionY = event.pageY - this.draggableElement.eventMousedownY, + var depEl = this.draggableElement, + positionY = event.pageY - depEl.eventMousedownY, processingPositionY = positionY + 'px', - processingMaxYpos = this.draggableElement.maxYpos + 'px', - processingMinYpos = this.draggableElement.minYpos + 'px'; - - if (positionY > this.draggableElement.minYpos && positionY < this.draggableElement.maxYpos) { - $(this.draggableElement.instance)[0].style[transformProp] = 'translateY(' + processingPositionY + ')'; - } else if (positionY < this.draggableElement.minYpos) { - $(this.draggableElement.instance)[0].style[transformProp] = 'translateY(' + processingMinYpos + ')'; - } else if (positionY >= this.draggableElement.maxYpos) { - $(this.draggableElement.instance)[0].style[transformProp] = 'translateY(' + processingMaxYpos + ')'; + processingMaxYpos = depEl.maxYpos + 'px', + processingMinYpos = depEl.minYpos + 'px', + depElement = this.getDepElement(depEl.instance, positionY); + + if (depElement) { + depEl.depElement ? depEl.depElement.elem.removeClass(depEl.depElement.className) : false; + depEl.depElement = depElement; + depEl.depElement.insert !== 'none' ? depEl.depElement.elem.addClass(depElement.className) : false; + } else if (depEl.depElement && depEl.depElement.insert !== 'none') { + depEl.depElement.elem.removeClass(depEl.depElement.className); + depEl.depElement.insert = 'none'; + } + + if (positionY > depEl.minYpos && positionY < depEl.maxYpos) { + $(depEl.instance)[0].style[transformProp] = 'translateY(' + processingPositionY + ')'; + } else if (positionY < depEl.minYpos) { + $(depEl.instance)[0].style[transformProp] = 'translateY(' + processingMinYpos + ')'; + } else if (positionY >= depEl.maxYpos) { + $(depEl.instance)[0].style[transformProp] = 'translateY(' + processingMaxYpos + ')'; } }, @@ -159,14 +174,22 @@ define([ * Mouse up handler */ mouseupHandler: function () { - var depElement = this._getDepElement(this.draggableElement.instance), - depElementCtx = this.getRecord(depElement[0]); + var depElementCtx, + drEl = this.draggableElement; + + if (drEl.depElement) { + depElementCtx = this.getRecord(drEl.depElement.elem[0]); + drEl.depElement.elem.removeClass(drEl.depElement.className); - this.setPosition(depElement, depElementCtx, this.draggableElement); - this.draggableElement.originRow.removeClass(this.draggableElementClass); + if (drEl.depElement.insert !== 'none') { + this.setPosition(drEl.depElement.elem, depElementCtx, drEl); + } + } + + drEl.originRow.removeClass(this.draggableElementClass); this.body.unbind('mousemove', this.mousemoveHandler); this.body.unbind('mouseup', this.mouseupHandler); - this.draggableElement.instance.remove(); + drEl.instance.remove(); this.draggableElement = {}; }, @@ -178,64 +201,69 @@ define([ * @param {Object} dragData - data draggable element */ setPosition: function (depElem, depElementCtx, dragData) { - var prevElem = depElem.prev(), - nextElem = depElem.next(), - depElemPosition = parseInt(depElementCtx.position, 10), - prevElemCtx, - prevElemPosition; - - if (prevElem[0] === dragData.originRow[0]) { - dragData.instanceCtx.position = depElemPosition; - depElementCtx.position = depElemPosition - 1; - - return false; - } + var depElemPosition = parseInt(depElementCtx.position, 10); - if (!prevElem.length) { - depElemPosition = --depElemPosition ? depElemPosition : 1; + if (dragData.depElement.insert === 'after') { + dragData.instanceCtx.position = depElemPosition + 1; + } else if (dragData.depElement.insert === 'before') { dragData.instanceCtx.position = depElemPosition; - - return false; } + }, - if (!nextElem.length) { - depElemPosition = ++depElemPosition; - dragData.instanceCtx.position = depElemPosition; - - return false; - } + /** + * Get dependency element + * + * @param {Object} curInstance - current element instance + * @param {Number} position + */ - prevElemCtx = this.getRecord(prevElem[0]); - prevElemPosition = prevElemCtx.position; + getDepElement: function (curInstance, position) { + var recordsCollection = this.table.find('tbody > tr'), + curInstancePositionTop = $(curInstance).position().top, + curInstancePositionBottom = curInstancePositionTop + $(curInstance).height(); - if (prevElemPosition === depElemPosition - 1) { - dragData.instanceCtx.position = depElemPosition; - } else { - dragData.instanceCtx.position = --depElemPosition; + if (position < 0) { + return this._getDepElement(recordsCollection, 'before', curInstancePositionTop); + } else if (position > 0) { + return this._getDepElement(recordsCollection, 'after', curInstancePositionBottom); } - }, /** - * Get dependency element + * Get dependency element private * - * @param {Object} curInstance - current element instance + * @param {Array} collection - record collection + * @param {String} position - position to add + * @param {Number} dragPosition - position drag element */ - _getDepElement: function (curInstance) { - var recordsCollection = this.table.find('tbody > tr'), - curInstancePosition = $(curInstance).position().top, - i = 0, - length = recordsCollection.length, - result, + _getDepElement: function (collection, position, dragPosition) { + var rec, + rangeEnd, rangeStart, - rangeEnd; + result, + className, + i = 0, + length = collection.length; for (i; i < length; i++) { - rangeStart = recordsCollection.eq(i).position().top; - rangeEnd = rangeStart + recordsCollection.eq(i).height(); + rec = collection.eq(i); + + if (position === 'before') { + rangeStart = collection.eq(i).position().top; + rangeEnd = rangeStart + this.step; + className = this.separatorsClass.top; + } else if (position === 'after') { + rangeEnd = rec.position().top + rec.height(); + rangeStart = rangeEnd - this.step; + className = this.separatorsClass.bottom; + } - if (curInstancePosition > rangeStart && curInstancePosition < rangeEnd) { - result = recordsCollection.eq(i); + if (dragPosition > rangeStart && dragPosition < rangeEnd) { + result = { + elem: rec, + insert: rec[0] === this.draggableElement.originRow[0] ? 'none' : position, + className: className + }; } } diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js index 5c8f99636b84b..24c4f66a7a1d3 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js @@ -4,12 +4,9 @@ */ define([ - 'ko', - 'mageUtils', 'underscore', - 'uiLayout', './dynamic-rows' -], function (ko, utils, _, layout, dynamicRows) { +], function (_, dynamicRows) { 'use strict'; return dynamicRows.extend({ diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js index 270018712a9eb..724c6910383e6 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js @@ -74,7 +74,7 @@ define([ this._super() .initChildren() .initDnd() - .isColumnsHeader() + .setColumnsHeaderListener() .initDefaultRecord(); return this; @@ -118,7 +118,7 @@ define([ * * @returns {Object} Chainable. */ - isColumnsHeader: function () { + setColumnsHeaderListener: function () { if (this.columnsHeaderAfterRender) { this.on('elems', this.renderColumnsHeader.bind(this)); } @@ -217,6 +217,7 @@ define([ sorted = this.elems().sort(function (propOne, propTwo) { return parseInt(propOne.position, 10) - parseInt(propTwo.position, 10); }); + updatedCollection = this.updatePosition(sorted, position, elem.name); this.elems(updatedCollection); }, @@ -317,9 +318,9 @@ define([ this.removeMaxPosition(); recordsData = this._getDataByProp(recordId); this._updateData(recordsData); - this._sortAfterDelete(); --this.recordIterator; } + this._sort(); }, /** @@ -339,7 +340,7 @@ define([ /** * Sort elems by position property */ - _sortAfterDelete: function () { + _sort: function () { this.elems(this.elems().sort(function (propOne, propTwo) { return parseInt(propOne.position, 10) - parseInt(propTwo.position, 10); })); diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/record.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/record.js index b15d1782ee724..c822170cf15ed 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/record.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/record.js @@ -5,9 +5,8 @@ define([ 'underscore', - 'uiRegistry', 'uiCollection' -], function (_, registry, uiCollection) { +], function (_, uiCollection) { 'use strict'; return uiCollection.extend({ @@ -118,11 +117,7 @@ define([ nameIsEqual = this.name + '.' + this.positionProvider === elem.name; dataScopeIsEqual = this.dataScope === elem.dataScope; - if (nameIsEqual || dataScopeIsEqual) { - return false; - } - - if (_.isFunction(elem.reset)) { + if (!(nameIsEqual || dataScopeIsEqual) && _.isFunction(elem.reset)) { elem.reset(); } }, this); @@ -136,14 +131,15 @@ define([ * @returns {Collection} Chainable. */ clear: function () { - var elems = this.elems(); + var elems = this.elems(), + nameIsEqual, + dataScopeIsEqual; _.each(elems, function (elem) { - if (this.name + '.' + this.positionProvider === elem.name || this.dataScope === elem.dataScope) { - return false; - } + nameIsEqual = this.name + '.' + this.positionProvider === elem.name; + dataScopeIsEqual = this.dataScope === elem.dataScope; - if (_.isFunction(elem.clear)) { + if (!(nameIsEqual || dataScopeIsEqual) && _.isFunction(elem.reset)) { elem.clear(); } }, this); diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js b/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js index 872a3d73b9da1..9c24808a4f9b4 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js @@ -55,7 +55,9 @@ define([ */ function setProperty(array, separator, level, path) { var i = 0, - length; + length, + nextLevel, + nextPath; array = _.compact(array); length = array.length; @@ -71,9 +73,9 @@ define([ } if (array[i].hasOwnProperty(separator)) { - level++; - path = path ? path + '.' + array[i].label : array[i].label; - setProperty.call(this, array[i][separator], separator, level, path); + nextLevel = level + 1; + nextPath = path ? path + '.' + array[i].label : array[i].label; + setProperty.call(this, array[i][separator], separator, nextLevel, nextPath); } } @@ -225,7 +227,10 @@ define([ addLastElement: function (data) { if (!data.hasOwnProperty(this.separator)) { !this.cacheOptions.lastOptions ? this.cacheOptions.lastOptions = [] : false; - this.cacheOptions.lastOptions.push(data); + + if (!_.findWhere(this.cacheOptions.lastOptions, {value: data.value})) { + this.cacheOptions.lastOptions.push(data); + } return true; } @@ -369,10 +374,8 @@ define([ * Filtered options list by value from filter options list */ filterOptionsList: function () { - var i = 0, - array = [], - curOption, - value = this.filterInputValue().trim().toLowerCase(); + var value = this.filterInputValue().trim().toLowerCase(), + array = []; if (value === '') { this.renderPath = false; @@ -383,16 +386,12 @@ define([ } this.showPath ? this.renderPath = true : false; - this.options(this.cacheOptions.plain); if (this.filterInputValue()) { - for (i; i < this.options().length; i++) { - curOption = this.options()[i].label.toLowerCase(); - if (curOption.indexOf(value) > -1) { - array.push(this.options()[i]); /*eslint max-depth: [2, 4]*/ - } - } + array = this.selectType === 'optgroup' ? + this._getFilteredArray(this.cacheOptions.lastOptions, value) : + this._getFilteredArray(this.cacheOptions.plain, value); if (!value.length) { this.options(this.cacheOptions.plain); @@ -402,11 +401,39 @@ define([ this._setItemsQuantity(array.length); } this.cleanHoveredElement(); + + return false; + } + + this.options(this.cacheOptions.plain); + }, + + /** + * Filtered options list by value from filter options list + * + * @param {Array} list - option list + * @param {String} value + * + * @returns {Array} filters result + */ + _getFilteredArray: function (list, value) { + var i = 0, + array = [], + curOption; + + for (i; i < list.length; i++) { + curOption = list[i].label.toLowerCase(); + + if (curOption.indexOf(value) > -1) { + array.push(list[i]); /*eslint max-depth: [2, 4]*/ + } } + + return array; }, /** - * Get path to current oprion + * Get path to current option * * @param {Object} data - option data * @returns {String} path @@ -708,6 +735,26 @@ define([ * selected first option in list */ pageDownKeyHandler: function () { + var el, + nextEl, + nextData, + nextIndex; + + if (!this.listVisible()) { + return false; + } + + if (this.filterInputValue()) { + el = !_.isNull(this.hoverElIndex()) ? + this._getElemByData(this.cacheOptions.plain[this.hoverElIndex()]) : false; + nextEl = el ? el.next() : $(this.cacheUiSelect).find('li:visible').eq(0); + nextIndex = nextEl.length ? nextEl.index() : 0; + nextData = this.options()[nextIndex]; + this.hoverElIndex(this.getOptionIndex(nextData)); + + return false; + } + if (!_.isNull(this.hoverElIndex()) && this.hoverElIndex() !== this.cacheOptions.plain.length - 1) { this._setHoverToElement(1); this._scrollTo(this.hoverElIndex()); @@ -719,6 +766,28 @@ define([ this._scrollTo(this.hoverElIndex()); }, + /** + * Get jQuery element by option data + * + * @param {Object} data - option data + * + * @returns {Object} jQuery element + */ + _getElemByData: function (data) { + var i = 0, + list = $(this.cacheUiSelect).find('li'), + length = this.options().length, + result; + + for (i; i < length; i++) { + if (this.options()[i].value === data.value) { + result = $(list[i]); + } + } + + return result; + }, + /** * Set hover to visible element * @@ -727,13 +796,21 @@ define([ * @param {Array} list - collection items */ _setHoverToElement: function (direction, index, list) { - var modifiedIndex; + var modifiedIndex, + curData, + canBeHovered = true; list = list || $(this.cacheUiSelect).find('li'); - index = index || this.hoverElIndex(); + index = index || _.isNumber(index) ? index : this.hoverElIndex(); modifiedIndex = index + direction; + modifiedIndex < 0 ? modifiedIndex = this.cacheOptions.plain.length - 1 : false; + curData = this.cacheOptions.plain[modifiedIndex]; + + if (this.selectType === 'optgroup' && !_.findWhere(this.cacheOptions.lastOptions, {value: curData.value})) { + canBeHovered = false; + } - if (list.eq(modifiedIndex).is(':visible')) { + if (list.eq(modifiedIndex).is(':visible') && canBeHovered) { this.hoverElIndex(modifiedIndex); } else { this._setHoverToElement(direction, modifiedIndex, list); @@ -773,6 +850,38 @@ define([ * selected last option in list */ pageUpKeyHandler: function () { + var el, + nextEl, + nextIndex, + nextData; + + if (!this.listVisible()) { + return false; + } + + if (this.filterInputValue()) { + el = !_.isNull(this.hoverElIndex()) ? + this._getElemByData(this.cacheOptions.plain[this.hoverElIndex()]) : false; + nextEl = el ? el.prev() : $(this.cacheUiSelect).find('li:visible').eq(this.options().length-1); + nextIndex = nextEl.length ? nextEl.index() : this.options().length-1; + nextData = this.options()[nextIndex]; + this.hoverElIndex(this.getOptionIndex(nextData)); + + return false; + } + + + if (this.filterInputValue()) { + el = !_.isNull(this.hoverElIndex()) ? + this._getElemByData(this.cacheOptions.plain[this.hoverElIndex()]) : false; + nextEl = el ? el.next() : $(this.cacheUiSelect).find('li:visible').eq(0); + nextIndex = nextEl.length ? nextEl.index() : 0; + nextData = this.options()[nextIndex]; + this.hoverElIndex(this.getOptionIndex(nextData)); + + return false; + } + if (this.hoverElIndex()) { this._setHoverToElement(-1); this._scrollTo(this.hoverElIndex()); diff --git a/app/code/Magento/Ui/view/base/web/js/form/form.js b/app/code/Magento/Ui/view/base/web/js/form/form.js index 56254af6e38d5..72e8a32c807a4 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/form.js +++ b/app/code/Magento/Ui/view/base/web/js/form/form.js @@ -10,7 +10,8 @@ define([ 'uiCollection', 'mageUtils', 'jquery', - 'Magento_Ui/js/core/app' + 'Magento_Ui/js/core/app', + 'mage/validation' ], function (_, loader, resolver, adapter, Collection, utils, $, app) { 'use strict'; @@ -27,12 +28,11 @@ define([ /** * Collect form data. * - * @param {String} selector + * @param {Array} items * @returns {Object} */ - function collectData(selector) { - var items = document.querySelectorAll(selector), - result = {}; + function collectData(items) { + var result = {}; items = Array.prototype.slice.call(items); @@ -102,8 +102,28 @@ define([ return save.promise(); } + /** + * Check if fields is valid. + * + * @param {Array}items + * @returns {Boolean} + */ + function isValidFields(items) { + var result = true; + + _.each(items, function (item) { + if (!$.validator.validateSingleElement(item)) { + result = false; + } + }); + + return result; + } + return Collection.extend({ defaults: { + additionalFields: [], + additionalInvalid: false, selectorPrefix: false, eventPrefix: '.${ $.index }', ajaxSave: false, @@ -195,7 +215,7 @@ define([ save: function (redirect, data) { this.validate(); - if (!this.source.get('params.invalid')) { + if (!this.additionalInvalid && !this.source.get('params.invalid')) { this.setAdditionalData(data) .submit(redirect); } @@ -221,7 +241,7 @@ define([ * @param {String} redirect */ submit: function (redirect) { - var additional = collectData(this.selector), + var additional = collectData(this.additionalFields), source = this.source; _.each(additional, function (value, name) { @@ -246,8 +266,10 @@ define([ * Validates each element and returns true, if all elements are valid. */ validate: function () { + this.additionalFields = document.querySelectorAll(this.selector); this.source.set('params.invalid', false); this.source.trigger('data.validate'); + this.set('additionalInvalid', !isValidFields(this.additionalFields)); }, /** diff --git a/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/preview.html b/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/preview.html index da95040389166..6d62941473a73 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/preview.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/preview.html @@ -6,11 +6,24 @@ -->
- Uploaded Image + + +
-
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 @@ -
+
- +
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 @@
-
+
-
    + class="admin__control-checkbox" + type="checkbox" + tabindex="-1" + data-bind="attr: { 'checked': $parent.isSelected(option.value) }">
@@ -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",