I have an emerce site that has products with multiple attributes (e.g. size, colour, etc,.)
On each product page there is a dropdown for each attribute with a class of 'attribute_price'
.
I have also preloaded hidden inputs onto the page from my database with the pricing for each product with a class of 'hidden_attribute_value'
.
So not every bination of size and colour is an option. For example, we might have 'small_red'
or 'medium_red'
but no 'large_red'
So if they select 'large'
from the size dropdown menu 'red'
should not be an option for the colour.
What I have so far is:
$("select.attribute_price").on("change", function(){
var id = event.target.id;
// determine which dropdown was changed (size or colour)
var attribute_value = document.getElementById(id).value+'_';
// get the value of the dropdown that they selected
var other_attribute_ids = []
var i;
var other_attributes = document.getElementsByClassName("attribute_price");
for(i=0; i<other_attributes.length; i++){
if(other_attributes[i].id != id){
var other_attribute_id = document.getElementById(other_attributes[i].id).id;
other_attribute_ids.push(other_attribute_id);
}
}
// create an array of all of the other dropdown ids excluding the one they changed
var all_attribute_ids = []
var i;
var all_attributes = document.getElementsByClassName("hidden_attribute_value");
for(i=0; i<all_attributes.length; i++){
all_attribute_ids.push(all_attributes[i].id);
}
// create an array of all of the possible values that it can be
});
So I have a variable 'attribute_value'
which would be something like 'red_' or 'blue_'.
I have an array called 'all_attribute_values'
which has the ids of hidden inputs for all possible binations. These would have values like 'small_red_' or 'small_blue'.
And I have an array called 'other_attribute_ids'
which has the id of the other dropdown menus that haven't been selected.
So if an item in 'all_attribute_values'
does not contain 'attribute_value'
remove that option from 'other_attribute_ids'
.
I have an emerce site that has products with multiple attributes (e.g. size, colour, etc,.)
On each product page there is a dropdown for each attribute with a class of 'attribute_price'
.
I have also preloaded hidden inputs onto the page from my database with the pricing for each product with a class of 'hidden_attribute_value'
.
So not every bination of size and colour is an option. For example, we might have 'small_red'
or 'medium_red'
but no 'large_red'
So if they select 'large'
from the size dropdown menu 'red'
should not be an option for the colour.
What I have so far is:
$("select.attribute_price").on("change", function(){
var id = event.target.id;
// determine which dropdown was changed (size or colour)
var attribute_value = document.getElementById(id).value+'_';
// get the value of the dropdown that they selected
var other_attribute_ids = []
var i;
var other_attributes = document.getElementsByClassName("attribute_price");
for(i=0; i<other_attributes.length; i++){
if(other_attributes[i].id != id){
var other_attribute_id = document.getElementById(other_attributes[i].id).id;
other_attribute_ids.push(other_attribute_id);
}
}
// create an array of all of the other dropdown ids excluding the one they changed
var all_attribute_ids = []
var i;
var all_attributes = document.getElementsByClassName("hidden_attribute_value");
for(i=0; i<all_attributes.length; i++){
all_attribute_ids.push(all_attributes[i].id);
}
// create an array of all of the possible values that it can be
});
So I have a variable 'attribute_value'
which would be something like 'red_' or 'blue_'.
I have an array called 'all_attribute_values'
which has the ids of hidden inputs for all possible binations. These would have values like 'small_red_' or 'small_blue'.
And I have an array called 'other_attribute_ids'
which has the id of the other dropdown menus that haven't been selected.
So if an item in 'all_attribute_values'
does not contain 'attribute_value'
remove that option from 'other_attribute_ids'
.
- So if I'm understanding correctly, you have a drop down that needs to be affected by certain products and it can be multiple drop downs? – rhuntington Commented Oct 1, 2018 at 17:01
- @rhuntington, hi yeah sorry its hard to explain as its all being generated by php, JS isn't my strongest language. So any one product can have any number of attributes size and colour are just examples – Paddy Hallihan Commented Oct 2, 2018 at 8:03
- Maybe looking into AJAX would be a good call of action for you - you can send the option to a script, do the logic, return the available options and append the options to the new select – treyBake Commented Oct 11, 2018 at 8:19
- I guess plexity of configuration will be quite a huge problem when you would have more advanced logic in the future, how plex will the configuration bee? – Icepickle Commented Oct 11, 2018 at 8:24
- 1 could you post html for the product with 5 attributes? – Ercan Peker Commented Oct 13, 2018 at 21:15
5 Answers
Reset to default 5I have created a sample html based on your usecase. Solution is likewise, but you should get inspiration for solving yours.
I have considered independent attributes, so the solution will scale to new attributes with different values. I have also considered that server response is not editable.
I have a quick link in jsfiddle to checkout the solution
https://jsfiddle/nfLx6aok/1/
<head>
<script src="https://ajax.googleapis./ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<select id="size" class="attribute_price">
<option value="small">Small</option>
<option value="large">Large</option>
</select>
<select id="color" class="attribute_price">
<option value="red">Red</option>
<option value="green">Green</option>
<option value="black">Black</option>
</select>
<select id="pattern" class="attribute_price">
<option value="solids">Solids</option>
<option value="checked">Checked</option>
<option value="dots">Dots</option>
</select>
<input type="hidden" id="small_red_solids" class="hidden_attribute_value">
<input type="hidden" id="small_black_dots" class="hidden_attribute_value">
<input type="hidden" id="large_green_checked" class="hidden_attribute_value">
<script>
// on page load
$( document ).ready(function() {
renderOptions();
});
$("select.attribute_price").on("change", function () {
renderOptions();
});
function renderOptions() {
// create an array of all of the possible values that it can be
// allowed_attribute_values = ['small_red', 'large_blue']
var allowed_attribute_values = [];
var all_attributes = document.getElementsByClassName("hidden_attribute_value");
for (var i = 0; i < all_attributes.length; i++) {
allowed_attribute_values.push(all_attributes[i].id);
}
function getAllPossibleValues(current_level, all_attributes) {
var depth_index = all_attributes.length;
var selected_bination = '';
for (var i = 0; i < depth_index; i++) {
if (i <= current_level) {
selected_bination += all_attributes[i].value;
if (i != all_attributes.length - 1) {
selected_bination += '_';
}
}
}
// hide all lower options
for (var i = current_level + 1; i < depth_index; i++) {
var selectedIdOptions = all_attributes[i].options;
all_attributes[i].value = null
for (var j = 0; j < selectedIdOptions.length; j++) {
// hide all lower options
selectedIdOptions[j].hidden = true;
var el = allowed_attribute_values.find(a => a.includes(selected_bination + selectedIdOptions[j].value));
if (el) {
selectedIdOptions[j].hidden = false;
}
}
}
}
if (event) {
var id = event.target.id;
} else {
var id = document.getElementsByClassName("attribute_price")[0].id;
}
var other_attributes = document.getElementsByClassName("attribute_price");
for (var i = 0; i < other_attributes.length; i++) {
if (other_attributes[i].id == id) {
allPossibleValues = getAllPossibleValues(i, other_attributes);
// we dont want to go deeper as of now
break;
}
}
}
</script>
this would work with any number of dropdown.
you can generate random attributes to test.
$(document).ready(function () {
/* generates random attributes */
var div_attributes = $('#div_attributes');
var select_colors = $('#select_colors');
var select_sizes = $('#select_sizes');
var select_attr0 = $('#select_attr0');
var select_attr1 = $('#select_attr1');
$('#btnGenerate').on('click', function () {
var result = "";
//
var index = getRandomArbitrary(1, select_sizes.find('option').get().length);
var random_attribute = select_sizes.find('option').eq(index).attr('value');
result += random_attribute;
//
index = getRandomArbitrary(1, select_colors.find('option').get().length);
random_attribute = select_colors.find('option').eq(index).attr('value');
result += '_' + random_attribute;
//
index = getRandomArbitrary(1, select_attr0.find('option').get().length);
random_attribute = select_attr0.find('option').eq(index).attr('value');
result += '_' + random_attribute;
//
index = getRandomArbitrary(1, select_attr1.find('option').get().length);
random_attribute = select_attr1.find('option').eq(index).attr('value');
result += '_' + random_attribute;
$('<div>' + result + '</div>').appendTo(div_attributes);
div_attributes.find('div').each(function () {
var item = $(this);
attributes.push(item.text());
});
SetFirstSelect();
});
var attributes = [];
//sets first select
SetFirstSelect();
function SetFirstSelect() {
$.each(attributes, function (i, val) {
var attribute = val.split('_')[0];
$('.attribute_price').eq(0).find('option[value="' + attribute + '"]').show();
});
}
//control attributes array
var selected_val = [];
$('.attribute_price').on('change', function () {
var item = $(this);
var index = item.index('.attribute_price');
selected_val[index] = item.val();
var select = $('.attribute_price').eq(index + 1);
var selected_attribute = item.val();
for (var i = index + 1; i < $('.attribute_price').get().length; i++) {
$('.attribute_price').eq(i).find('option').hide();
$('.attribute_price').eq(i).prop('selectedIndex', 0)
}
var selected_val_str = selected_val[0];
for (var i = 1; i <= index; i++) {
selected_val_str += '_' + selected_val[i];
}
$.each(attributes, function (j, val) {
if (val.indexOf(selected_val_str) >= 0) {
var attribute1 = val.split('_')[index + 1];
select.find('option[value="' + attribute1 + '"]').show();
}
});
});
function getRandomArbitrary(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
});
.attribute_price option {
display: none;
}
.container {
margin:30px;
}
.row > div {
padding:10px;
}
.btn {
margin-top:20px;
}
<script
src="https://code.jquery./jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<!-- Latest piled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn./bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn./bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<!-- Latest piled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn./bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<div class="container">
<div class="row">
<div style="width:50%; float:left">
<input type="button" class="btn btn-primary" id="btnGenerate" value="generate random attributes" />
<div id="div_attributes"></div>
</div>
<div style="width:50%; float:left">
<div class="form-group">
<label>Sizes</label>
<select class="form-control attribute_price" id="select_sizes">
<option value="">select size</option>
<option value="small">small</option>
<option value="large">large</option>
<option value="medium">medium</option>
</select>
</div>
<div class="form-group">
<label>color</label>
<select id="select_colors" class="form-control attribute_price">
<option value="">select color</option>
<option value="black">black</option>
<option value="yellow">yellow</option>
<option value="red">red</option>
</select>
</div>
<div class="form-group">
<label>attrType0</label>
<select id="select_attr0" class="form-control attribute_price">
<option value="">select attr0</option>
<option value="attr00">attr00</option>
<option value="attr01">attr01</option>
<option value="attr02">attr02</option>
</select>
</div>
<div class="form-group">
<label>attrType1</label>
<select id="select_attr1" class="form-control attribute_price">
<option value="">select attr1</option>
<option value="attr10">attr10</option>
<option value="attr11">attr11</option>
<option value="attr12">attr12</option>
</select>
</div>
</div>
</div>
</div>
I don't know if I have understood exactly what you are trying to explain or not but here's what I understood you have a website with suppose dropdowns for say clothes having attributes size, price, color, brand and you have an array of objects containing all of these attributes for each object.
I will be explaining this in reactjs since i am more familiar with it than php
so for each dropdown you can have an onchange handler which invokes a function where you check the values of the other dropdowns. If You select size M then enable and populate the dropdown list after filtering clothes with size M. when that is done invoke a similar function for checking the other attributes.
at this time all of your dropdowns will be active, now if the user makes any changes to the first dropdown i.e. size you can either reset the other dropdowns to be reselected or just pass the new list depending on how you are handling
Here is a similar thing i had done for setting date
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July','August', 'September', 'October', 'November', 'December']
const Month30 = ['4', '6', '9', '11']
const Month31 = ['1', '3', '5', '7', '8', '10', '12']
class Dropdown extends React.Component {
constructor(props) {
super(props)
this.state = {
birthDay: '',
birthMonth: '',
birthYear: ''
}
}
ponentDidMount() {
}
getMonthTotalDays = (birthMonth, birthYear) => {
if (birthMonth === '02') {
if (birthYear !== '' && birthYear % 4 === 0) {
return 29
} else {
return 28
}
} else if (Month30.includes(birthMonth)) {
return 30
} else {
return 31
}
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
}, () => {
const { birthMonth, birthYear, birthDay } = this.state
const days = this.getMonthTotalDays(birthMonth, birthYear)
if (birthDay > days) {
this.setState({ birthDay: '' })
}
})
}
renderMonths = () => {
return months.map((month, i) => {
if(i < 9){
return (<option key={`${month}-${i}`} value={'0' + (i + 1)}>{month}</option>)
}
else
return (<option key={`${month}-${i}`} value={i + 1}>{month}</option>)
})
}
renderDay = () => {
const { birthMonth, birthDay, birthYear } = this.state
const daysOptions = []
let days = this.getMonthTotalDays(birthMonth, birthYear)
for (let day=1; day<=days; day++) {
daysOptions.push(<option key={`'date-'${day}`} value={day}> { day } </option>)
}
return daysOptions
}
renderYears = () => {
const toYear = (new Date()).getFullYear() - 16
const yearOptions = []
for (let year = 1960; year <= toYear; year++) {
yearOptions.push(<option key={`year-${year}`} value={year}> { year } </option>)
}
return yearOptions
}
render() {
const { birthDay, birthMonth, birthYear } = this.state
return (
<div>
<label>Month</label>
<select
name="birthMonth"
value={ birthMonth }
onChange={this.handleChange}
>
<option disabled selected value=''>Month</option>
{ this.renderMonths() }
</select>
<label>Day</label>
<select
name='birthDay'
value={ birthDay }
onChange={this.handleChange}
>
<option disabled selected value=''>Day</option>
{ this.renderDay() }
</select>
<label>Year</label>
<select
name='birthYear'
value={ birthYear }
onChange={this.handleChange}
>
<option disabled selected value=''>Year</option>
{ this.renderYears() }
</select>
</div>
)
}
}
ReactDOM.render(
<Dropdown />,
document.getElementById('drop')
);
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="drop"></div>
Here is a solution that takes a skus
array as input and create a dropdown for each attribute. When any dropdown value changes, the options in all other dropdowns are updated to show only options consistent with picked options.
https://codepen.io/rockysims/pen/PyJbbv
<script src="https://code.jquery./jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script type="text/javascript">
skus = [{
id: 1,
attributes: {
color: "red",
size: "small",
shape: "circle"
}
}, {
id: 2,
attributes: {
color: "red",
size: "medium",
shape: "square"
}
}, {
id: 3,
attributes: {
color: "yellow",
size: "small",
shape: "circle"
}
}, {
id: 4,
attributes: {
color: "yellow",
size: "medium",
shape: "square"
}
}, {
id: 5,
attributes: {
color: "yellow",
size: "large",
shape: "square"
}
}, {
id: 6,
attributes: {
color: "green",
size: "medium",
shape: "square"
}
}, {
id: 7,
attributes: {
color: "green",
size: "large",
shape: "square"
}
}];
$(function() {
const allOptionsByAttrName = buildAllOptionsByAttrName();
//create dropdowns
for (let attrName in allOptionsByAttrName) {
const dropdownId = attrName + "Dropdown";
const options = allOptionsByAttrName[attrName];
let html = "";
html += attrName + ": ";
html += buildDropdownHtml(dropdownId, options);
html += "<br/>";
$("#dropdowns").append(html);
}
//on dropdown changes, update options of all dropdowns
for (let changedAttrName in allOptionsByAttrName) {
$("#" + changedAttrName + "Dropdown").on('change', function() {
//build pickedOptionByAttrName
const pickedOptionByAttrName = {};
for (let attrName in allOptionsByAttrName) {
const dropdown = $("#" + attrName + "Dropdown");
pickedOptionByAttrName[attrName] = dropdown.val();
}
refreshAvailableOptions(pickedOptionByAttrName);
});
}
});
function buildAllOptionsByAttrName() {
const allOptionsByAttrName = {};
for (let sku of skus) {
for (let attrName in sku.attributes) {
allOptionsByAttrName[attrName] = allOptionsByAttrName[attrName] || [];
if (allOptionsByAttrName[attrName].indexOf(sku.attributes[attrName]) == -1) {
allOptionsByAttrName[attrName].push(sku.attributes[attrName]);
}
}
}
return allOptionsByAttrName;
}
function buildDropdownHtml(dropdownId, options) {
let html = "";
html += "<select id='" + dropdownId + "'>";
html += "<option value=''>";
html += "";
html += "</option>";
for (let option of options) {
html += "<option value='" + option + "'>";
html += option;
html += "</option>";
}
html += "</select>";
return html;
}
function refreshAvailableOptions(pickedOptionByAttrName) {
for (let attrName in pickedOptionByAttrName) {
//build availableOptions
const dropdown = $("#" + attrName + "Dropdown");
const options = $("#" + attrName + "Dropdown option");
const availableOptions = buildAvailableOptions(pickedOptionByAttrName, attrName);
availableOptions.push(""); //nothing picked option
//show available options and hide others
options.each(function() {
const option = $(this);
const optionIsAvailable = availableOptions.indexOf(option.val()) != -1;
if (optionIsAvailable) {
option.show();
} else {
option.hide();
}
});
}
}
function buildAvailableOptions(pickedOptionByAttrName, attrNameToBuildFor) {
//build availableSkus
const availableSkus = skus.filter(function(sku) {
let available = true;
for (let attrName in pickedOptionByAttrName) {
if (attrName != attrNameToBuildFor) {
const pickedOption = pickedOptionByAttrName[attrName];
if (pickedOption) {
available = available && sku.attributes[attrName] == pickedOption;
}
}
}
return available;
});
//build availableOptions
const availableOptions = [];
for (let sku of availableSkus) {
if (availableOptions.indexOf(sku.attributes[attrNameToBuildFor]) == -1) {
availableOptions.push(sku.attributes[attrNameToBuildFor]);
}
}
return availableOptions;
}
</script>
<div id="dropdowns">
</div>
If you prefer not to dynamically create the dropdowns, ment out the for loop under //create dropdowns
and replace <div id="dropdowns"></div>
with the following:
<div id="dropdowns">
color:
<select id="colorDropdown">
<option value=""></option>
<option value="red">red</option>
<option value="yellow">yellow</option>
<option value="green">green</option>
</select><br/>
size:
<select id="sizeDropdown">
<option value=""></option>
<option value="small">small</option>
<option value="medium">medium</option>
<option value="large">large</option>
</select><br/>
shape:
<select id="shapeDropdown">
<option value=""></option>
<option value="circle">circle</option>
<option value="square">square</option>
</select><br>
</div>
If you can identify in PHP which options for other categories are available for certain drop-down category-value selection. You can incrementally flag each option as excluded for each selected value to keep it disabled:
JS:
$(document).ready(function () {
$(".dropdown").change(function(){
var val = $(this).val();
var id = $(this).attr('id');
$.get('get_options.php', {category: id, value:val}, function(data) {
$(".dropdown:not(#"+id+")").each(function() {
var cat = $(this).attr('id');
$(this).find('option').each(function() {
var cat_val = $(this).val();
var options = data[cat];
var exluded = $(this).attr('exlude');
if ($.inArray(cat_val, options) !== -1) {
$(this).attr('exlude', exluded-1);
if(exluded == 1) {
$(this).prop('disabled', false);
}
} else {
$(this).attr('exlude', exluded+1);
$(this).prop('disabled', true);
}
});
});
}, 'json');
});
});
HTML:
<div id="dropdowns">
color:
<select id="color" class="dropdown">
<option value=""></option>
<option value="red">red</option>
<option value="yellow">yellow</option>
<option value="green">green</option>
</select><br/>
size:
<select id="size" class="dropdown">
<option value=""></option>
<option value="small">small</option>
<option value="medium">medium</option>
<option value="large">large</option>
</select><br/>
shape:
<select id="shape" class="dropdown">
<option value=""></option>
<option value="circle">circle</option>
<option value="square">square</option>
<option value="triangle">triangle</option>
</select><br>
**SAMPLE DATA: ** example if user selects color first, return all available options for other drop-down categories.
{"size": ["small","large"],"shape":["circle"]}
or in php:
$data = array(
'size' => array(
'small', 'large'
),
'shape' => array(
'circle'
),
);
echo json_encode($data);