I am trying to make a custom attribute list with number boxes for each attributes.
I made a custom partial view based off of ProductAttributes.cshtml called CustomProductAttributes.cshtml and put that partial view in a custom product view in which I point to in our product set up.
My partial view renders as expected on the product when I use that custom Product View (see image):
What I want to happen is when a customer ups the number per attribute line, I want the attribute price to multiply by the value of the numberbox. So if someone enters '2' in the attribute numberbox and the attribute's price is $4 USD, then the base price of the product would increase by $8 USD. Does that make sense?
I am including my base basic CustomProductAttribute.cshtml code, but I need help in determining on how to get the multiplied price of the attribute numberbox x the price of the attribute.
[code]
@model IList<ProductDetailsModel.ProductAttributeModel>
@using Nop.Web.Models.Catalog;
@using Nop.Core.Domain.Catalog;
@using Nop.Core.Domain.Media;
@using Nop.Core.Infrastructure;
@using System.Text;
@using System.Globalization;
@if (Model.Count > 0)
{
<div class="attributes">
<dl>
@foreach (var attribute in Model)
{
string controlId = string.Format("product_attribute_{0}", attribute.Id);
string textPrompt = !string.IsNullOrEmpty(attribute.TextPrompt) ? attribute.TextPrompt : attribute.Name;
<dt id="@string.Format("product_attribute_label_{0}", attribute.Id)">
<label class="text-prompt">
@textPrompt
</label>
@if (attribute.IsRequired)
{
<span class="required">*</span>
}
@if (!string.IsNullOrEmpty(attribute.Description))
{
<div class="attribute-description">
@Html.Raw(attribute.Description)
</div>
}
</dt>
<dd id="@string.Format("product_attribute_input_{0}", attribute.Id)">
@switch (attribute.AttributeControlType)
{
@*///////////////////////////// NEED HELP HERE!!!!
-- CUSTOM ATTRIBUTE NUMBERBOX - NEED TO WIRE ATTRIBUTE VALUES TO THE ADD TO CART BUTTON--
////////////////////////////////////////////////////////////*@
case AttributeControlType.MuseumAttributeList:
{
<ul class="museumAttributeList">
@foreach (var attributeValue in attribute.Values)
{
var attributeName = String.IsNullOrEmpty(attributeValue.PriceAdjustment) ?
attributeValue.Name :
T("Products.ProductAttributes.PriceAdjustment", attributeValue.Name, attributeValue.PriceAdjustment).Text;
<li>
<div>@attributeName</div>
<input type="number" data-val="true" data-val-number="Field Qty must be a number." value="@attributeValue.Id" id="@(controlId)_@(attributeValue.Id)" class="qty-input" placeholder="0" name="@(controlId)" />
</li>
}
</ul>
}
break;
@*
////////////////
END CUSTOM NUMBERBOX
////////////////////////////
*@
case AttributeControlType.DropdownList:
{
<select name="@(controlId)" id="@(controlId)">
@if (!attribute.IsRequired)
{
<option value="0">---</option>
}
@foreach (var attributeValue in attribute.Values)
{
var attributeName = String.IsNullOrEmpty(attributeValue.PriceAdjustment) ?
attributeValue.Name :
T("Products.ProductAttributes.PriceAdjustment", attributeValue.Name, attributeValue.PriceAdjustment).Text;
<option selected="@attributeValue.IsPreSelected" value="@attributeValue.Id">@attributeName</option>
}
</select>
}
break;
case AttributeControlType.RadioList:
{
<ul class="option-list">
@foreach (var attributeValue in attribute.Values)
{
var attributeName = String.IsNullOrEmpty(attributeValue.PriceAdjustment) ?
attributeValue.Name :
T("Products.ProductAttributes.PriceAdjustment", attributeValue.Name, attributeValue.PriceAdjustment).Text;
<li>
<input id="@(controlId)_@(attributeValue.Id)" type="radio" name="@(controlId)" value="@attributeValue.Id" checked="@attributeValue.IsPreSelected" />
<label for="@(controlId)_@(attributeValue.Id)">@attributeName</label>
</li>
}
</ul>
}
break;
case AttributeControlType.Checkboxes:
case AttributeControlType.ReadonlyCheckboxes:
{
<ul class="option-list">
@foreach (var attributeValue in attribute.Values)
{
var attributeName = String.IsNullOrEmpty(attributeValue.PriceAdjustment) ?
attributeValue.Name :
T("Products.ProductAttributes.PriceAdjustment", attributeValue.Name, attributeValue.PriceAdjustment).Text;
<li>
<input id="@(controlId)_@(attributeValue.Id)" type="checkbox" name="@(controlId)" value="@attributeValue.Id" checked="@attributeValue.IsPreSelected" @(attribute.AttributeControlType == AttributeControlType.ReadonlyCheckboxes ? Html.Raw(" disabled=\"disabled\"") : null) />
<label for="@(controlId)_@(attributeValue.Id)">@attributeName</label>
</li>
}
</ul>
}
break;
case AttributeControlType.TextBox:
{
<input name="@(controlId)" type="text" class="textbox" id="@(controlId)" value="@attribute.DefaultValue" />
}
break;
case AttributeControlType.MultilineTextbox:
{
<textarea cols="20" id="@(controlId)" name="@(controlId)">@attribute.DefaultValue</textarea>
}
break;
case AttributeControlType.Datepicker:
{
@Html.DatePickerDropDowns(controlId + "_day", controlId + "_month", controlId + "_year", DateTime.Now.Year, DateTime.Now.Year + 1, attribute.SelectedDay, attribute.SelectedMonth, attribute.SelectedYear)
}
break;
case AttributeControlType.FileUpload:
{
var downloadService = EngineContext.Current.Resolve<Nop.Services.Media.IDownloadService>();
Download download = null;
if (!String.IsNullOrEmpty(attribute.DefaultValue))
{
download = downloadService.GetDownloadByGuid(new Guid(attribute.DefaultValue));
}
//register CSS and JS
Html.AddCssFileParts("~/Scripts/fineuploader/fineuploader-4.2.2.min.css");
Html.AddScriptParts("~/Scripts/fineuploader/jquery.fineuploader-4.2.2.min.js");
//ex. ['jpg', 'jpeg', 'png', 'gif'] or []
var allowedFileExtensions = string.Join(", ", attribute.AllowedFileExtensions.Select(x => "'" + x.Trim() + "'").ToList());
if (download != null)
{
<input id="@(controlId)" name="@(controlId)" type="hidden" value="@download.DownloadGuid" />
}
else
{
<input id="@(controlId)" name="@(controlId)" type="hidden" />
}
@*fine uploader container*@
<div id="@(controlId)uploader"></div>
@*fine uploader template (keep it synchronized to \Content\fineuploader\templates\default.html)*@
<script type="text/template" id="@(controlId)-qq-template">
<div class="qq-uploader-selector qq-uploader">
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
<span>@T("Common.FileUploader.DropFiles")</span>
</div>
<div class="qq-upload-button-selector qq-upload-button">
<div>@T("Common.FileUploader.Upload")</div>
</div>
<span class="qq-drop-processing-selector qq-drop-processing">
<span>@T("Common.FileUploader.Processing")</span>
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
</span>
<ul class="qq-upload-list-selector qq-upload-list">
<li>
<div class="qq-progress-bar-container-selector">
<div class="qq-progress-bar-selector qq-progress-bar"></div>
</div>
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
<span class="qq-edit-filename-icon-selector qq-edit-filename-icon"></span>
<span class="qq-upload-file-selector qq-upload-file"></span>
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
<span class="qq-upload-size-selector qq-upload-size"></span>
<a class="qq-upload-cancel-selector qq-upload-cancel" href="#">@T("Common.FileUploader.Cancel")</a>
<a class="qq-upload-retry-selector qq-upload-retry" href="#">@T("Common.FileUploader.Retry")</a>
<a class="qq-upload-delete-selector qq-upload-delete" href="#">@T("Common.FileUploader.Delete")</a>
<span class="qq-upload-status-text-selector qq-upload-status-text"></span>
</li>
</ul>
</div>
</script>
<script type="text/javascript">
$(document).ready(function() {
$("#@(controlId)uploader").fineUploader({
request: {
endpoint: '@(Url.RouteUrl("UploadFileProductAttribute", new { attributeId = attribute.Id }))'
},
template: "@(controlId)-qq-template",
multiple: false,
validation: {
allowedExtensions: [@Html.Raw(allowedFileExtensions)]
}
}).on("complete", function(event, id, name, responseJSON, xhr) {
$("#@(controlId)").val(responseJSON.downloadGuid);
if (responseJSON.success) {
$("#@(controlId + "downloadurl")").html("<a href='" + responseJSON.downloadUrl + "'>@T("Common.FileUploader.DownloadUploadedFile")</a>");
$("#@(controlId + "remove")").show();
}
if (responseJSON.message) {
alert(responseJSON.message);
}
});
$("#@(controlId + "remove")").click(function(e) {
$("#@(controlId + "downloadurl")").html("");
$("#@(controlId)").val('');
$(this).hide();
});
});
</script>
<div id="@(controlId + "downloadurl")">
@if (download != null)
{
<a href="@(Url.Action("GetFileUpload", "Download", new {downloadId = download.DownloadGuid}))" class="download-uploaded-file">@T("Common.FileUploader.DownloadUploadedFile")</a>
}
</div>
<div>
@if (download != null)
{
<a id="@(controlId + "remove")" class="remove-download-button">@T("Common.FileUploader.RemoveDownload")</a>
}
else
{
<a id="@(controlId + "remove")" class="remove-download-buttonn" style="display: none;">@T("Common.FileUploader.RemoveDownload")</a>
}
</div>
}
break;
case AttributeControlType.ColorSquares:
{
<ul class="option-list color-squares" id="color-squares-@(attribute.Id)">
@foreach (var attributeValue in attribute.Values)
{
var attributeName = String.IsNullOrEmpty(attributeValue.PriceAdjustment) ?
attributeValue.Name :
T("Products.ProductAttributes.PriceAdjustment", attributeValue.Name, attributeValue.PriceAdjustment).Text;
<li @(attributeValue.IsPreSelected ? @Html.Raw(" class=\"selected-value\"") : null)>
<label for="@(controlId)_@(attributeValue.Id)">
<span class="color-container" title="@attributeName">
<span class="color" style="background-color:@(attributeValue.ColorSquaresRgb);"> </span>
</span>
<input id="@(controlId)_@(attributeValue.Id)" type="radio" name="@(controlId)" value="@attributeValue.Id" checked="@attributeValue.IsPreSelected" />
@*uncomment below to display attribute value name*@
@*<span class="name">@attributeName</span>*@
</label>
</li>
}
</ul>
<script type="text/javascript">
$(document).ready(function() {
$('.attributes #color-squares-@(attribute.Id)').delegate('input', 'click', function(event) {
$('.attributes #color-squares-@(attribute.Id)').find('li').removeClass('selected-value');
$(this).closest('li').addClass('selected-value');
});
});
</script>
}
break;
}
</dd>
}
</dl>
</div>
}
@if (Model.Count > 0)
{
//dynamic update support
var attributesHaveConditions = Model.Any(x => x.HasCondition);
var dynamicPriceUpdate = EngineContext.Current.Resolve<CatalogSettings>().AjaxProcessAttributeChange;
var attributeChangeScriptsBuilder = new StringBuilder();
var productId = Model.First().ProductId;
var attributeChangeHandlerFuncName = string.Format("attribute_change_handler_{0}", productId);
if (dynamicPriceUpdate)
{
//generate change event script
foreach (var attribute in Model)
{
string controlId = string.Format("product_attribute_{0}", attribute.Id);
switch (attribute.AttributeControlType)
{
case AttributeControlType.DropdownList:
{
attributeChangeScriptsBuilder.AppendFormat("$('#{0}').change(function(){{{1}();}});\n", controlId, attributeChangeHandlerFuncName);
}
break;
case AttributeControlType.RadioList:
case AttributeControlType.ColorSquares:
{
foreach (var attributeValue in attribute.Values)
{
attributeChangeScriptsBuilder.AppendFormat("$('#{0}_{1}').click(function(){{{2}();}});\n", controlId, attributeValue.Id, attributeChangeHandlerFuncName);
}
}
break;
case AttributeControlType.Checkboxes:
case AttributeControlType.ReadonlyCheckboxes:
{
foreach (var attributeValue in attribute.Values)
{
attributeChangeScriptsBuilder.AppendFormat("$('#{0}_{1}').click(function(){{{2}();}});\n", controlId, attributeValue.Id, attributeChangeHandlerFuncName);
}
}
break;
//TO HANDLE CUSTOM MUSEUM ATTRIBUTE LIST
case AttributeControlType.MuseumAttributeList:
foreach (var attributeValue in attribute.Values)
{
attributeChangeScriptsBuilder.AppendFormat("$('#{0}_{1}').change(function(){{{2}();}});\n", controlId, attributeValue.Id, attributeChangeHandlerFuncName);
}
break;
default:
break;
}
}
//render scripts
//almost the same implementation is used in the \Views\Product\_RentalInfo.cshtml file
<script type="text/javascript">
function @(attributeChangeHandlerFuncName)() {
$.ajax({
cache: false,
url: '@Html.Raw(Url.Action("ProductDetails_AttributeChange", "ShoppingCart", new {productId = productId, validateAttributeConditions = attributesHaveConditions}))',
data: $('#product-details-form').serialize(),
type: 'post',
success: function(data) {
if (data.price) {
$('.price-value-@productId').text(data.price);
}
if (data.sku) {
$('#sku-@productId').text(data.sku);
}
if (data.mpn) {
$('#mpn-@productId').text(data.mpn);
}
if (data.gtin) {
$('#gtin-@productId').text(data.gtin);
}
if (data.stockAvailability) {
$('#stock-availability-value-@productId').text(data.stockAvailability);
}
if (data.enabledattributemappingids) {
for (var i = 0; i < data.enabledattributemappingids.length; i++) {
$('#product_attribute_label_' + data.enabledattributemappingids[i]).show();
$('#product_attribute_input_' + data.enabledattributemappingids[i]).show();
}
}
if (data.enabledattributemappingids) {
for (var i = 0; i < data.disabledattributemappingids.length; i++) {
$('#product_attribute_label_' + data.disabledattributemappingids[i]).hide();
$('#product_attribute_input_' + data.disabledattributemappingids[i]).hide();
}
}
}
});
}
$(document).ready(function() {
@(attributeChangeHandlerFuncName)();
@Html.Raw(attributeChangeScriptsBuilder.ToString())
});
</script>
}
}