Working attributes select UI
This commit is contained in:
parent
af5749397a
commit
9148e9801b
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -166,12 +166,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-category-select {
|
.product-category-select,
|
||||||
|
.product-attribute-select {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
#product-category-search {
|
#product-category-search,
|
||||||
|
#product-attribute-search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 0 10px;
|
margin: 0 0 10px;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
|
@ -179,7 +181,8 @@
|
||||||
border-color: #E6EAEE;
|
border-color: #E6EAEE;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-categories-list {
|
.product-categories-list,
|
||||||
|
.product-attributes-list {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
@ -255,7 +258,8 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-category-count {
|
.product-category-count,
|
||||||
|
.product-attribute-count {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
border: 1px solid #e9e9e9;
|
border: 1px solid #e9e9e9;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
|
@ -263,6 +267,24 @@
|
||||||
font-size: .8em;
|
font-size: .8em;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-attribute {
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
|
||||||
|
.product-attribute-name {
|
||||||
|
padding: .5em;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-bottom: 1px solid #e9e9e9;
|
||||||
|
|
||||||
|
.product-attribute-count {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:after {
|
&:after {
|
||||||
|
|
|
@ -327,7 +327,7 @@ var ProductsBlockSettingsEditor = function (_React$Component3) {
|
||||||
} else if ('category' === this.state.display) {
|
} else if ('category' === this.state.display) {
|
||||||
extra_settings = wp.element.createElement(_categorySelect.ProductsCategorySelect, this.props);
|
extra_settings = wp.element.createElement(_categorySelect.ProductsCategorySelect, this.props);
|
||||||
} else if ('attribute' === this.state.display) {
|
} else if ('attribute' === this.state.display) {
|
||||||
extra_settings = wp.element.createElement(_attributeSelect.ProductsAttributeSelect, null);
|
extra_settings = wp.element.createElement(_attributeSelect.ProductsAttributeSelect, this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
var menu = this.state.menu_visible ? wp.element.createElement(ProductsBlockSettingsEditorDisplayOptions, { existing: this.state.display ? true : false, update_display_callback: this.updateDisplay }) : null;
|
var menu = this.state.menu_visible ? wp.element.createElement(ProductsBlockSettingsEditorDisplayOptions, { existing: this.state.display ? true : false, update_display_callback: this.updateDisplay }) : null;
|
||||||
|
@ -1493,6 +1493,12 @@ var _wp$components = wp.components,
|
||||||
withAPIData = _wp$components.withAPIData,
|
withAPIData = _wp$components.withAPIData,
|
||||||
Dropdown = _wp$components.Dropdown;
|
Dropdown = _wp$components.Dropdown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute data cache. Needed because it takes a lot of API calls to generate attribute info.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var PRODUCT_ATTRIBUTE_DATA = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the display mode is 'Attribute' search for and select product attributes to pull products from.
|
* When the display mode is 'Attribute' search for and select product attributes to pull products from.
|
||||||
*/
|
*/
|
||||||
|
@ -1500,19 +1506,123 @@ var _wp$components = wp.components,
|
||||||
var ProductsAttributeSelect = exports.ProductsAttributeSelect = function (_React$Component) {
|
var ProductsAttributeSelect = exports.ProductsAttributeSelect = function (_React$Component) {
|
||||||
_inherits(ProductsAttributeSelect, _React$Component);
|
_inherits(ProductsAttributeSelect, _React$Component);
|
||||||
|
|
||||||
function ProductsAttributeSelect() {
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
function ProductsAttributeSelect(props) {
|
||||||
_classCallCheck(this, ProductsAttributeSelect);
|
_classCallCheck(this, ProductsAttributeSelect);
|
||||||
|
|
||||||
return _possibleConstructorReturn(this, (ProductsAttributeSelect.__proto__ || Object.getPrototypeOf(ProductsAttributeSelect)).apply(this, arguments));
|
var _this = _possibleConstructorReturn(this, (ProductsAttributeSelect.__proto__ || Object.getPrototypeOf(ProductsAttributeSelect)).call(this, props));
|
||||||
|
|
||||||
|
_this.state = {
|
||||||
|
selectedAttribute: '',
|
||||||
|
selectedTerms: [],
|
||||||
|
filterQuery: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
_this.setSelectedAttribute = _this.setSelectedAttribute.bind(_this);
|
||||||
|
_this.addTerm = _this.addTerm.bind(_this);
|
||||||
|
_this.removeTerm = _this.removeTerm.bind(_this);
|
||||||
|
return _this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the selected attribute.
|
||||||
|
*
|
||||||
|
* @param slug string Attribute slug.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
_createClass(ProductsAttributeSelect, [{
|
_createClass(ProductsAttributeSelect, [{
|
||||||
key: "render",
|
key: 'setSelectedAttribute',
|
||||||
|
value: function setSelectedAttribute(slug) {
|
||||||
|
this.setState({
|
||||||
|
selectedAttribute: slug,
|
||||||
|
selectedTerms: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a term to the selected attribute's terms.
|
||||||
|
*
|
||||||
|
* @param slug string Term slug.
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'addTerm',
|
||||||
|
value: function addTerm(slug) {
|
||||||
|
var terms = this.state.selectedTerms;
|
||||||
|
terms.push(slug);
|
||||||
|
this.setState({
|
||||||
|
selectedTerms: terms
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a term from the selected attribute's terms.
|
||||||
|
*
|
||||||
|
* @param slug string Term slug.
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'removeTerm',
|
||||||
|
value: function removeTerm(slug) {
|
||||||
|
var newTerms = [];
|
||||||
|
var _iteratorNormalCompletion = true;
|
||||||
|
var _didIteratorError = false;
|
||||||
|
var _iteratorError = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator = this.state.selectedTerms[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
||||||
|
var termSlug = _step.value;
|
||||||
|
|
||||||
|
if (termSlug !== slug) {
|
||||||
|
newTerms.push(termSlug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError = true;
|
||||||
|
_iteratorError = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion && _iterator.return) {
|
||||||
|
_iterator.return();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError) {
|
||||||
|
throw _iteratorError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedTerms: newTerms
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the whole section.
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'render',
|
||||||
value: function render() {
|
value: function render() {
|
||||||
|
|
||||||
|
// @todo Remove this once data is moving around properly.
|
||||||
|
console.log("STATE UPDATED");
|
||||||
|
console.log(this.state);
|
||||||
|
|
||||||
return wp.element.createElement(
|
return wp.element.createElement(
|
||||||
"div",
|
'div',
|
||||||
{ className: "product-attribute-select" },
|
{ className: 'product-attribute-select' },
|
||||||
"TODO: Attribute select screen"
|
wp.element.createElement(ProductAttributeFilter, null),
|
||||||
|
wp.element.createElement(ProductAttributeList, {
|
||||||
|
selectedAttribute: this.state.selectedAttribute,
|
||||||
|
selectedTerms: this.state.selectedTerms,
|
||||||
|
setSelectedAttribute: this.setSelectedAttribute.bind(this),
|
||||||
|
addTerm: this.addTerm.bind(this),
|
||||||
|
removeTerm: this.removeTerm.bind(this)
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}]);
|
}]);
|
||||||
|
@ -1520,5 +1630,284 @@ var ProductsAttributeSelect = exports.ProductsAttributeSelect = function (_React
|
||||||
return ProductsAttributeSelect;
|
return ProductsAttributeSelect;
|
||||||
}(React.Component);
|
}(React.Component);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search area for filtering through the attributes list.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
var ProductAttributeFilter = function ProductAttributeFilter() {
|
||||||
|
return wp.element.createElement(
|
||||||
|
'div',
|
||||||
|
null,
|
||||||
|
wp.element.createElement('input', { id: 'product-attribute-search', type: 'search', placeholder: __('Search for attributes') })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of attributes.
|
||||||
|
*/
|
||||||
|
var ProductAttributeList = withAPIData(function (props) {
|
||||||
|
return {
|
||||||
|
attributes: '/wc/v2/products/attributes'
|
||||||
|
};
|
||||||
|
})(function (_ref) {
|
||||||
|
var attributes = _ref.attributes,
|
||||||
|
selectedAttribute = _ref.selectedAttribute,
|
||||||
|
selectedTerms = _ref.selectedTerms,
|
||||||
|
setSelectedAttribute = _ref.setSelectedAttribute,
|
||||||
|
addTerm = _ref.addTerm,
|
||||||
|
removeTerm = _ref.removeTerm;
|
||||||
|
|
||||||
|
if (!attributes.data) {
|
||||||
|
return __('Loading');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === attributes.data.length) {
|
||||||
|
return __('No attributes found');
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributeElements = [];
|
||||||
|
var _iteratorNormalCompletion2 = true;
|
||||||
|
var _didIteratorError2 = false;
|
||||||
|
var _iteratorError2 = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator2 = attributes.data[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
|
||||||
|
var attribute = _step2.value;
|
||||||
|
|
||||||
|
if (PRODUCT_ATTRIBUTE_DATA.hasOwnProperty(attribute.slug)) {
|
||||||
|
attributeElements.push(wp.element.createElement(ProductAttributeElement, {
|
||||||
|
selectedAttribute: selectedAttribute,
|
||||||
|
selectedTerms: selectedTerms,
|
||||||
|
attribute: attribute,
|
||||||
|
setSelectedAttribute: setSelectedAttribute,
|
||||||
|
addTerm: addTerm,
|
||||||
|
removeTerm: removeTerm
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
attributeElements.push(wp.element.createElement(UncachedProductAttributeElement, {
|
||||||
|
selectedAttribute: selectedAttribute,
|
||||||
|
selectedTerms: selectedTerms,
|
||||||
|
attribute: attribute,
|
||||||
|
setSelectedAttribute: setSelectedAttribute,
|
||||||
|
addTerm: addTerm,
|
||||||
|
removeTerm: removeTerm
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError2 = true;
|
||||||
|
_iteratorError2 = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion2 && _iterator2.return) {
|
||||||
|
_iterator2.return();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError2) {
|
||||||
|
throw _iteratorError2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wp.element.createElement(
|
||||||
|
'div',
|
||||||
|
{ className: 'product-attributes-list' },
|
||||||
|
attributeElements
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches then renders a product attribute term element.
|
||||||
|
*/
|
||||||
|
var UncachedProductAttributeElement = withAPIData(function (props) {
|
||||||
|
return {
|
||||||
|
terms: '/wc/v2/products/attributes/' + props.attribute.id + '/terms'
|
||||||
|
};
|
||||||
|
})(function (_ref2) {
|
||||||
|
var terms = _ref2.terms,
|
||||||
|
selectedAttribute = _ref2.selectedAttribute,
|
||||||
|
selectedTerms = _ref2.selectedTerms,
|
||||||
|
attribute = _ref2.attribute,
|
||||||
|
setSelectedAttribute = _ref2.setSelectedAttribute,
|
||||||
|
addTerm = _ref2.addTerm,
|
||||||
|
removeTerm = _ref2.removeTerm;
|
||||||
|
|
||||||
|
if (!terms.data) {
|
||||||
|
return __('Loading');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === terms.data.length) {
|
||||||
|
return __('No attribute options found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate cache.
|
||||||
|
PRODUCT_ATTRIBUTE_DATA[attribute.slug] = { terms: [] };
|
||||||
|
|
||||||
|
var totalCount = 0;
|
||||||
|
var _iteratorNormalCompletion3 = true;
|
||||||
|
var _didIteratorError3 = false;
|
||||||
|
var _iteratorError3 = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var _iterator3 = terms.data[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
|
||||||
|
var term = _step3.value;
|
||||||
|
|
||||||
|
totalCount += term.count;
|
||||||
|
PRODUCT_ATTRIBUTE_DATA[attribute.slug].terms.push(term);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
_didIteratorError3 = true;
|
||||||
|
_iteratorError3 = err;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (!_iteratorNormalCompletion3 && _iterator3.return) {
|
||||||
|
_iterator3.return();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (_didIteratorError3) {
|
||||||
|
throw _iteratorError3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PRODUCT_ATTRIBUTE_DATA[attribute.slug].count = totalCount;
|
||||||
|
|
||||||
|
return wp.element.createElement(ProductAttributeElement, {
|
||||||
|
selectedAttribute: selectedAttribute,
|
||||||
|
selectedTerms: selectedTerms,
|
||||||
|
attribute: attribute,
|
||||||
|
setSelectedAttribute: setSelectedAttribute,
|
||||||
|
addTerm: addTerm,
|
||||||
|
removeTerm: removeTerm
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A product attribute term element.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var ProductAttributeElement = function (_React$Component2) {
|
||||||
|
_inherits(ProductAttributeElement, _React$Component2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
function ProductAttributeElement(props) {
|
||||||
|
_classCallCheck(this, ProductAttributeElement);
|
||||||
|
|
||||||
|
var _this2 = _possibleConstructorReturn(this, (ProductAttributeElement.__proto__ || Object.getPrototypeOf(ProductAttributeElement)).call(this, props));
|
||||||
|
|
||||||
|
_this2.handleAttributeChange = _this2.handleAttributeChange.bind(_this2);
|
||||||
|
_this2.handleTermChange = _this2.handleTermChange.bind(_this2);
|
||||||
|
return _this2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Propagate and reset values when the selected attribute is changed.
|
||||||
|
*
|
||||||
|
* @param evt Event object
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
_createClass(ProductAttributeElement, [{
|
||||||
|
key: 'handleAttributeChange',
|
||||||
|
value: function handleAttributeChange(evt) {
|
||||||
|
var slug = evt.target.value;
|
||||||
|
|
||||||
|
if (this.props.selectedAttribute === slug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.setSelectedAttribute(slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add or remove selected terms.
|
||||||
|
*
|
||||||
|
* @param evt Event object
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'handleTermChange',
|
||||||
|
value: function handleTermChange(evt) {
|
||||||
|
if (evt.target.checked) {
|
||||||
|
this.props.addTerm(evt.target.value);
|
||||||
|
} else {
|
||||||
|
this.props.removeTerm(evt.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the details for one attribute.
|
||||||
|
*/
|
||||||
|
|
||||||
|
}, {
|
||||||
|
key: 'render',
|
||||||
|
value: function render() {
|
||||||
|
var _this3 = this;
|
||||||
|
|
||||||
|
var attribute = PRODUCT_ATTRIBUTE_DATA[this.props.attribute.slug];
|
||||||
|
var isSelected = this.props.selectedAttribute === this.props.attribute.slug;
|
||||||
|
|
||||||
|
var attributeTerms = null;
|
||||||
|
if (isSelected) {
|
||||||
|
attributeTerms = wp.element.createElement(
|
||||||
|
'ul',
|
||||||
|
{ className: 'product-attribute-terms' },
|
||||||
|
attribute.terms.map(function (term) {
|
||||||
|
return wp.element.createElement(
|
||||||
|
'li',
|
||||||
|
{ className: 'product-attribute-term' },
|
||||||
|
wp.element.createElement(
|
||||||
|
'label',
|
||||||
|
null,
|
||||||
|
wp.element.createElement('input', { type: 'checkbox',
|
||||||
|
value: term.slug,
|
||||||
|
onChange: _this3.handleTermChange,
|
||||||
|
checked: _this3.props.selectedTerms.includes(term.slug)
|
||||||
|
}),
|
||||||
|
term.name,
|
||||||
|
wp.element.createElement(
|
||||||
|
'span',
|
||||||
|
{ className: 'product-attribute-count' },
|
||||||
|
term.count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wp.element.createElement(
|
||||||
|
'div',
|
||||||
|
{ className: 'product-attribute' },
|
||||||
|
wp.element.createElement(
|
||||||
|
'div',
|
||||||
|
{ className: 'product-attribute-name' },
|
||||||
|
wp.element.createElement(
|
||||||
|
'label',
|
||||||
|
null,
|
||||||
|
wp.element.createElement('input', { type: 'radio',
|
||||||
|
value: this.props.attribute.slug,
|
||||||
|
onClick: this.handleAttributeChange,
|
||||||
|
checked: isSelected
|
||||||
|
}),
|
||||||
|
this.props.attribute.name,
|
||||||
|
wp.element.createElement(
|
||||||
|
'span',
|
||||||
|
{ className: 'product-attribute-count' },
|
||||||
|
attribute.count
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
attributeTerms
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return ProductAttributeElement;
|
||||||
|
}(React.Component);
|
||||||
|
|
||||||
/***/ })
|
/***/ })
|
||||||
/******/ ]);
|
/******/ ]);
|
|
@ -173,7 +173,7 @@ class ProductsBlockSettingsEditor extends React.Component {
|
||||||
} else if ( 'category' === this.state.display ) {
|
} else if ( 'category' === this.state.display ) {
|
||||||
extra_settings = <ProductsCategorySelect { ...this.props } />;
|
extra_settings = <ProductsCategorySelect { ...this.props } />;
|
||||||
} else if ( 'attribute' === this.state.display ) {
|
} else if ( 'attribute' === this.state.display ) {
|
||||||
extra_settings = <ProductsAttributeSelect />
|
extra_settings = <ProductsAttributeSelect { ...this.props } />
|
||||||
}
|
}
|
||||||
|
|
||||||
const menu = this.state.menu_visible ? <ProductsBlockSettingsEditorDisplayOptions existing={ this.state.display ? true : false } update_display_callback={ this.updateDisplay } /> : null;
|
const menu = this.state.menu_visible ? <ProductsBlockSettingsEditorDisplayOptions existing={ this.state.display ? true : false } update_display_callback={ this.updateDisplay } /> : null;
|
||||||
|
|
|
@ -1,17 +1,282 @@
|
||||||
const { __ } = wp.i18n;
|
const { __ } = wp.i18n;
|
||||||
const { Toolbar, withAPIData, Dropdown } = wp.components;
|
const { Toolbar, withAPIData, Dropdown } = wp.components;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute data cache. Needed because it takes a lot of API calls to generate attribute info.
|
||||||
|
*/
|
||||||
|
const PRODUCT_ATTRIBUTE_DATA = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the display mode is 'Attribute' search for and select product attributes to pull products from.
|
* When the display mode is 'Attribute' search for and select product attributes to pull products from.
|
||||||
*/
|
*/
|
||||||
export class ProductsAttributeSelect extends React.Component {
|
export class ProductsAttributeSelect extends React.Component {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
constructor( props ) {
|
||||||
|
super( props );
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
selectedAttribute: '',
|
||||||
|
selectedTerms: [],
|
||||||
|
filterQuery: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setSelectedAttribute = this.setSelectedAttribute.bind( this );
|
||||||
|
this.addTerm = this.addTerm.bind( this );
|
||||||
|
this.removeTerm = this.removeTerm.bind( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the selected attribute.
|
||||||
|
*
|
||||||
|
* @param slug string Attribute slug.
|
||||||
|
*/
|
||||||
|
setSelectedAttribute( slug ) {
|
||||||
|
this.setState( {
|
||||||
|
selectedAttribute: slug,
|
||||||
|
selectedTerms: [],
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a term to the selected attribute's terms.
|
||||||
|
*
|
||||||
|
* @param slug string Term slug.
|
||||||
|
*/
|
||||||
|
addTerm( slug ) {
|
||||||
|
let terms = this.state.selectedTerms;
|
||||||
|
terms.push( slug );
|
||||||
|
this.setState( {
|
||||||
|
selectedTerms: terms,
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a term from the selected attribute's terms.
|
||||||
|
*
|
||||||
|
* @param slug string Term slug.
|
||||||
|
*/
|
||||||
|
removeTerm( slug ) {
|
||||||
|
let newTerms = [];
|
||||||
|
for ( let termSlug of this.state.selectedTerms ) {
|
||||||
|
if ( termSlug !== slug ) {
|
||||||
|
newTerms.push( termSlug );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState( {
|
||||||
|
selectedTerms: newTerms,
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the whole section.
|
||||||
|
*/
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
|
// @todo Remove this once data is moving around properly.
|
||||||
|
console.log( "STATE UPDATED" );
|
||||||
|
console.log( this.state );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="product-attribute-select">
|
<div className="product-attribute-select">
|
||||||
TODO: Attribute select screen
|
<ProductAttributeFilter />
|
||||||
|
<ProductAttributeList
|
||||||
|
selectedAttribute={ this.state.selectedAttribute }
|
||||||
|
selectedTerms={ this.state.selectedTerms }
|
||||||
|
setSelectedAttribute={ this.setSelectedAttribute.bind( this ) }
|
||||||
|
addTerm={ this.addTerm.bind( this ) }
|
||||||
|
removeTerm={ this.removeTerm.bind( this ) }
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search area for filtering through the attributes list.
|
||||||
|
*/
|
||||||
|
const ProductAttributeFilter = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<input id="product-attribute-search" type="search" placeholder={ __( 'Search for attributes' ) } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of attributes.
|
||||||
|
*/
|
||||||
|
const ProductAttributeList = withAPIData( ( props ) => {
|
||||||
|
return {
|
||||||
|
attributes: '/wc/v2/products/attributes'
|
||||||
|
};
|
||||||
|
} )( ( { attributes, selectedAttribute, selectedTerms, setSelectedAttribute, addTerm, removeTerm } ) => {
|
||||||
|
if ( ! attributes.data ) {
|
||||||
|
return __( 'Loading' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 0 === attributes.data.length ) {
|
||||||
|
return __( 'No attributes found' );
|
||||||
|
}
|
||||||
|
|
||||||
|
let attributeElements = [];
|
||||||
|
for ( let attribute of attributes.data ) {
|
||||||
|
if ( PRODUCT_ATTRIBUTE_DATA.hasOwnProperty( attribute.slug ) ) {
|
||||||
|
attributeElements.push( <ProductAttributeElement
|
||||||
|
selectedAttribute={ selectedAttribute }
|
||||||
|
selectedTerms={ selectedTerms }
|
||||||
|
attribute={attribute}
|
||||||
|
setSelectedAttribute={ setSelectedAttribute }
|
||||||
|
addTerm={ addTerm }
|
||||||
|
removeTerm={ removeTerm }
|
||||||
|
/> );
|
||||||
|
} else {
|
||||||
|
attributeElements.push( <UncachedProductAttributeElement
|
||||||
|
selectedAttribute={ selectedAttribute }
|
||||||
|
selectedTerms={ selectedTerms }
|
||||||
|
attribute={ attribute }
|
||||||
|
setSelectedAttribute={ setSelectedAttribute }
|
||||||
|
addTerm={ addTerm }
|
||||||
|
removeTerm={ removeTerm }
|
||||||
|
/> );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="product-attributes-list">
|
||||||
|
{ attributeElements }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches then renders a product attribute term element.
|
||||||
|
*/
|
||||||
|
const UncachedProductAttributeElement = withAPIData( ( props ) => {
|
||||||
|
return {
|
||||||
|
terms: '/wc/v2/products/attributes/' + props.attribute.id + '/terms'
|
||||||
|
};
|
||||||
|
} )( ( { terms, selectedAttribute, selectedTerms, attribute, setSelectedAttribute, addTerm, removeTerm } ) => {
|
||||||
|
if ( ! terms.data ) {
|
||||||
|
return __( 'Loading' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 0 === terms.data.length ) {
|
||||||
|
return __( 'No attribute options found' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate cache.
|
||||||
|
PRODUCT_ATTRIBUTE_DATA[ attribute.slug ] = { terms: [] };
|
||||||
|
|
||||||
|
let totalCount = 0;
|
||||||
|
for ( let term of terms.data ) {
|
||||||
|
totalCount += term.count;
|
||||||
|
PRODUCT_ATTRIBUTE_DATA[ attribute.slug ].terms.push( term );
|
||||||
|
}
|
||||||
|
|
||||||
|
PRODUCT_ATTRIBUTE_DATA[ attribute.slug ].count = totalCount;
|
||||||
|
|
||||||
|
return <ProductAttributeElement
|
||||||
|
selectedAttribute={ selectedAttribute }
|
||||||
|
selectedTerms={ selectedTerms }
|
||||||
|
attribute={ attribute }
|
||||||
|
setSelectedAttribute={ setSelectedAttribute }
|
||||||
|
addTerm={ addTerm }
|
||||||
|
removeTerm={ removeTerm }
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A product attribute term element.
|
||||||
|
*/
|
||||||
|
class ProductAttributeElement extends React.Component {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
constructor( props ) {
|
||||||
|
super( props );
|
||||||
|
|
||||||
|
this.handleAttributeChange = this.handleAttributeChange.bind( this );
|
||||||
|
this.handleTermChange = this.handleTermChange.bind( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Propagate and reset values when the selected attribute is changed.
|
||||||
|
*
|
||||||
|
* @param evt Event object
|
||||||
|
*/
|
||||||
|
handleAttributeChange( evt ) {
|
||||||
|
const slug = evt.target.value;
|
||||||
|
|
||||||
|
if ( this.props.selectedAttribute === slug ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.setSelectedAttribute( slug );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add or remove selected terms.
|
||||||
|
*
|
||||||
|
* @param evt Event object
|
||||||
|
*/
|
||||||
|
handleTermChange( evt ) {
|
||||||
|
if ( evt.target.checked ) {
|
||||||
|
this.props.addTerm( evt.target.value );
|
||||||
|
} else {
|
||||||
|
this.props.removeTerm( evt.target.value );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the details for one attribute.
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const attribute = PRODUCT_ATTRIBUTE_DATA[ this.props.attribute.slug ];
|
||||||
|
const isSelected = this.props.selectedAttribute === this.props.attribute.slug;
|
||||||
|
|
||||||
|
let attributeTerms = null;
|
||||||
|
if ( isSelected ) {
|
||||||
|
attributeTerms = (
|
||||||
|
<ul className="product-attribute-terms">
|
||||||
|
{ attribute.terms.map( ( term ) => (
|
||||||
|
<li className="product-attribute-term">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
value={ term.slug }
|
||||||
|
onChange={ this.handleTermChange }
|
||||||
|
checked={ this.props.selectedTerms.includes( term.slug ) }
|
||||||
|
/>
|
||||||
|
{ term.name }
|
||||||
|
<span className="product-attribute-count">{ term.count }</span>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
) ) }
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="product-attribute">
|
||||||
|
<div className="product-attribute-name">
|
||||||
|
<label>
|
||||||
|
<input type="radio"
|
||||||
|
value={ this.props.attribute.slug }
|
||||||
|
onClick={ this.handleAttributeChange }
|
||||||
|
checked={ isSelected }
|
||||||
|
/>
|
||||||
|
{ this.props.attribute.name }
|
||||||
|
<span className="product-attribute-count">{ attribute.count }</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{ attributeTerms }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue