How to track qty changes in cart?

This is not CMS or software specific. Can be used for shopify, magento, wordpress or any other cart page

Assumption:
All the cart items have an input field for quantity in case they want to change qty for any item

Assumption:
All those input fields can be selected using queryselector. Either they have common name attribute or a common class.

Purpose:
We want to change class of update button to show that he has to press update button because quantities have been altered.

So lets get started:

js code:

$(document).ready(function($){
//please change query selector for qty inputs in line 3 and 5
    var ini_values = $("input[name='updates[]']").map(function(){return $(this).val();}).get();
    $(".quantity input").change(function(){
      let new_values = $("input[name='updates[]']").map(function(){return $(this).val();}).get();
      if(JSON.stringify(ini_values)!=JSON.stringify(new_values)) 
      { //change selector for update button
           $("#update-cart").addClass("pressme"); }
    else {
    $("#update-cart").removeClass("pressme");}
    });
  });

css code if needed

.pressme{background: white !important;
    border-color: #109ff6 !important;
  color: black !important;
  animation: blink 1s linear infinite;}
@keyframes blink{
0%{opacity: 0;}
50%{opacity: .5;}
100%{opacity: 1;}
}

Reference:

https://stackoverflow.com/a/24503913/2229148

5 ways to redirect your Web page?

Hi Everyone!

In this post we will be learning about redirects. And see how you can redirect your web-page.

We will see two ways to redirect web-pages:

  • Through your registrars’ Cpanel.
  • Right into your code.

Through cPanel redirect feature

You can easily redirect your visitors from one page to another with the help of the Redirects feature. 

To setup a redirection, access your website’s Control Panel and locate the Redirects menu.

In the Create a Redirect section. You can set up a redirection from one page of your website to another. This also works for subdomains or completely different websites.

If you choose to use HTTPS, make sure the redirected page has a certificate first, because redirecting to a website without an SSL certificate but using the HTTPS protocol for it, will most likely land your visitor to an error page

Now let’s see how you can set redirection by including few lines in your code.

HTML redirects:

The simplest way to redirect to another URL is with the Meta Refresh tag. We can place this meta tag inside the <head> at the top of any HTML page like this:

<meta http-equiv="refresh" content="0; URL='http://new-website.com'" />

JavaScript redirects

Redirecting to another URL with JavaScript is pretty easy, simply change the locationproperty on the window object:

// Use any of the following lines below.

window.location = "http://new-website.com";
window.location.href = "http://new-website.com";
window.location.assign("http://new-website.com");
window.location.replace("http://new-website.com");

Apache redirects

The most common method of redirecting a web page is through adding specific rules to a .htaccess file on an Apache web server.

Redirect 301 / http://www.new-website.com

PHP redirects

With PHP we can use the header function, which is quite straightforward:

<?php
  header('Location: http://www.new-website.com/', true, 301); // Permanent redirection.
// OR
  #header('Location: http://www.new-website.com/', true, 307); // Temporary redirection.
  exit();
?>

This is it for this post. Now the comment section is all yours.

Thank You.

ReactJS: Create your first react app.

React is one of the most in-demand and popular JavaScript library. And the reason is simple it gives you the ability to create complex User Interfaces with minimal and simplified code.

In this article we are going to go through the installation process and the process of creating your first react app.

Creating a react js app.

There are a few different ways of creating a react app. But the simplest way is using a CDN – link. This allows you to start using ReactJS instantly, without any installation setup.

Just include these two lines in your HTML file.

<script crossorigin src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>

But this is not the best way of creating a react app. The best way to create a react app is Using react CLI, but for that, you need to have NodeJS installed.

Once NodeJS is installed in your system. Now there are two ways of creating react app.

The Quick way (npx):

Use the following command in your preffered terminal.

npx create-react-app <app-name>
cd <app-name>
npm start

The first line will create a react app and name it. And the third line will open the app in your default browser. Which looks something like this.

The productive way (NPm):

In this way first we will install NPM (Node package manager) then will create the react app.

That’s why i call this way as the productive way, because soon or late you would have to install NPM then why not now.

Follow these command below.

npm install -g
cd <app-name>
npm start

The first line installs npm globally. And you know what the next two line does.

Actually we can also create a react app via yarn. But we are just not gonna get into it in this post.

So, i hope this was helpful to you.

Thank You.

NodeJS: JavaScript powering the backend.

Node.js is an open-source, cross-platform JavaScript run-time environment that executes JavaScript code outside of a browser. NodeJS is a JavaScript engine that you can install in your own system.

Also, NodeJs makes it possible to use JavaScript in backend development as a backend programming language. (Cool! isn’t it.)

You need to know this.

Node isn’t a program that you simply launch like Word or Photoshop: you won’t find it pinned to the taskbar or in your list of Apps. To use Node you must type command-line instructions, so you need to be comfortable with (or at least know how to start) a command-line tool like the Windows Command Prompt, PowerShell, or the Git shell.

Installation Steps

  1. Download the Windows installer from the Nodes.js® web site.
  2. Run the installer (the .msi file you downloaded in the previous step.)
  3. Follow the prompts in the installer (Accept the license agreement, click the NEXT button a bunch of times and accept the default installation settings).

Now to be sure that everything is fine and to confirm node installation, You need to run the following code in your preferred terminal.

  • Check node version: node -v
  • Check npm version: npm -v
  • Confirm installation of node: node hello.js
Microsoft Windows [Version 10.0.15063]
(c) 2017 Microsoft Corporation. All rights reserved.

C:\Users\docs&gt; node -v
v10.14.2

C:\Users\docs&gt; npm -v
6.4.1

C:\Users\docs&gt; node hello.js
Node is installed!

Here is an actual example from my very own system.

If you have any question in your mind, feel free to comment below.

Thank You.

Understanding “this” keyword in JavaScript.

‘Hi Everyone!

In this article, we are about to discuss  ‘this’ keyword in JavaScript. It is a very important part of object-oriented JS programming. Hence it becomes crucial for serious developers to understand  ‘this’ keyword.

It is also one of the most confused concepts of JavaScript.

What is ‘this’?

Understanding 'this':

To be able to understand what I am about to talk, you should be familiar with the basics of JavaScript. e.x – Variables, Objects, Functions, etc…

There isn’t a single word that describes ‘this' well, so I just think of it as a special variable that changes depending on the situation. Those different situations are captured below.

case 1:

In a regular function (or if you’re not in a function at all), 'this' points to'window' This is the default case.

function logThis() {
console.log(this);
}

logThis(); // window

// In strict mode, `this` will be `undefined` instead of `window`.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode

Case 2:

When a function is called as a method, 'this' points to the object that’s on the left side of the dot.

/*
 * You can also think of this as the "left of the dot" rule. 
 * For example, in myObject.myMethod(), `this` will be myObject
 * because myObject is to the left of the dot.
 *
 * Of course, if you're using this syntax myObject['myMethod'](),
 * technically it would be the "left of the dot or bracket" rule,
 * but that sounds clumsy and generally terrible.
 *
 * If you have multiple dots, the relevant dot is the one closest 
 * to the method call. For example, if you have one.two.hi();
 * `this` inside of hi will be two.
 */

var myObject = {
  myMethod: function() {
    console.log(this);
  }
};

myObject.myMethod(); // myObject

Case 3:

In a function that’s being called as a constructor, 'this' points to the object that the constructor is creating.

function Person(name) {
  this.name = name;
}

var gordon = new Person('gordon');
console.log(gordon); // {name: 'gordon'}

Case 4:

When you explicitly set the value of 'this' manually using,'bind''apply' or,'call' it’s all up to you.

function logThis() {
  console.log(this);
}

var explicitlySetLogThis = logThis.bind({name: 'Gordon'});

explicitlySetLogThis(); // {name: 'Gordon'}

// Note that a function returned from .bind (like `boundOnce` below),
// cannot be bound to a different `this` value ever again.
// In other words, functions can only be bound once.
var boundOnce = logThis.bind({name: 'The first time is forever'});

Case 5:

In a callback function, apply the above rules methodically.

function outerFunction(callback) {
  callback();
}

function logThis() {
  console.log(this);
}

/*
 * Case 1: The regular old default case.
 */
 
outerFunction(logThis); // window

/*
 * Case 2: Call the callback as a method
 */
 
function callAsMethod(callback) {
  var weirdObject = {
    name: "Don't do this in real life"
  };
  
  weirdObject.callback = callback;
  weirdObject.callback();
}

callAsMethod(logThis); // `weirdObject` will get logged to the console

/*
 * Case 3: Calling the callback as a constructor. 
 */
 
function callAsConstructor(callback) {
  new callback();
}

callAsConstructor(logThis); // the new object created by logThis will be logged to the console

/*
 * Case 4: Explicitly setting `this`.
 */
 
function callAndBindToGordon(callback) {
  var boundCallback = callback.bind({name: 'Gordon'});
  boundCallback();
}

callAndBindToGordon(logThis); // {name: 'Gordon'}

// In a twist, we give `callAndBindToGordon` a function that's already been bound.
var boundOnce = logThis.bind({name: 'The first time is forever'});
callAndBindToGordon(boundOnce); // {name: 'The first time is forever'}

Conclusion:

So, this was all about “this”.

The value of ‘this’ keyword depends on the situation, analyze the situation and figure out what ‘this’ represents.

You gave your precious time to read this article.

Thank You.

Everything is a ToDo list.

Hi Everyone!

Today’s topic might not be clear from the heading, so let me make it clear over here. This post is a bit different so brace your self to think a bit.

In the field of programming, it is very important to understand the fundamentals and core concepts of programming. This field requires you to think differently.

Most people find coding quite tough and the reason being the way they think about web-applications. Anyone can learn the syntax of a computer language, and print ‘Hello World’.

But to literally build something you got to Think Differently…

And that’s what we are going to do today. Don’t go to the different purposes of these web-applications, instead try to see the common pattern that I am trying to point out.

Everything is a todo list.

Every web-application is a kind of todo list app. Let us see how.

ToDo List app:

The picture below is from a todo list web app.

Enter your task to be done and it will add it to the list.

Google is a kind of ToDo List app:

Google is a kind of todo list app, instead of tasks in the list there are the search results.

Enter your query and it gives you the list of search results.

E-commerce(Amazon) shopping cart:

A shopping cart is also a kind of todo list app. Instead of tasks, there are products and items on the list that you saved.

Select your product and it will be added to the list of the cart.

In fact, messaging apps are a kind of ToDo list.

All messaging apps are also a kind of todo list. the only difference is that the text input bar is at the bottom.

See the first image of todo list app, I am about to convert it to a messaging app. Here we go.

conclusion:

Every web-application is a kind of todo list app. Very rarely you would found an application that does not fits into this pattern.

Once again (Don’t consider the functionality of these sites, instead try to see the pattern, I am trying to point out.)

Hence,

A todo list app is a great place to start for beginners.  Walk your way through an advance todo list app (like the one shown in the image above.) to get familiar with concepts used in almost all web applications.

Amazing new articles are about to come, stay tuned!

Thank You.

JavaScript debugging dev tools.

Errors can and will happen every time you write some new computer code.

Programming code might contain syntax errors or logical errors. Many of these errors are difficult to diagnose.

Often, when programming code contains errors, nothing will happen. There are no error messages, and you will get no indications where to search for errors.

Searching for (and fixing) errors in programming code is called code debugging. And believe me, that’s not easy but the most valuable skill a developer could have.

Hence, here I am with top JavaScript debugging tools.

Chrome DevTools:

chrome dev tools

Google Chrome DevTools is a set of debugging tools that can be used to debug JavaScript code right from the Chrome browser as they are inbuilt. You can get to interact with HTML, CSS and JavaScript all from the browser simply by clicking the inspect element button.

It has the resources panel for inspecting the storage of web pages such as the HTML5 database, cookies, local storage etc.

You may have been using Chrome all these years without knowing that it has a debugging tool with so much power.

Sublime Web Inspector:

SublimewebinspectorThe sublime web inspector is a javascript debugging tools. The sublime web inspector allows you to debug, whatever you edit in your programs. It works on top of Web Inspector Protocol.

If you use Sublime Text Editor for your JavaScript editing, then Sublime Web Inspector allows you to debug from the same program you edit with. It works on top of WebInspectorProtocol, with all information displayed in console and text files.

Node.js Inspector for Debugging Requests to Node Apps

Debugging the Node.js on your application’s backend can be challenging. The following tools take advantage of the capabilities of the Node.js Inspector to assist you:

 

React Developer Tools

IN THE BROWSER: The React tab shows props and state values if they exist for the selected elements.So, these were the four debugger tools that can make debugging easier for you. You can learn to use them through the tutorials available online for free. Hope you liked the article.

Thank You.

How to remove links of a learndash course which has not started yet? without code changes

jQuery(document).ready(function($){
var nost=0;
if($("body").hasClass("single-sfwd-courses")){
toba='';
$("#lessons_list a.notavailable").removeAttr("href");
$("#lessons_list a.notavailable").each(function(){
toba=$(this).parent().parent().attr("class");
toba = toba.replace("lesson post-",'');
toba = toba.replace(" is_not_sample no-topics",'');
toba = toba.replace(" is_not_sample has-topics",'');
$('#learndash_topic_dots-'+toba+" a").removeAttr("href");
});
}
});

Magento 2 how to show configurable options stock availability on product page

addtocart.phtml

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
// @codingStandardsIgnoreFile
/** @var $block \Magento\Catalog\Block\Product\View */
?>
<?php $_product = $block->getProduct(); ?>
<?php $buttonTitle = __('Add to Cart'); ?>
<?php if ($_product->isSaleable()): ?>
<div class="box-tocart">
<div class="fieldset">
<?php if ($block->shouldRenderQuantity()):
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$StockState = $objectManager->get('\Magento\CatalogInventory\Api\StockStateInterface');?>
<div class="field qty">
<label class="label" for="qty"><span><?= /* @escapeNotVerified */ __('Qty') ?></span></label>
<div class="control">
<?php if($_product->getTypeId()=="simple") { ?>
<select class="input-text qty" name="qty" id="qty" data-validate="<?= $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>">
<?php 
$availableQty= $StockState->getStockQty($_product->getId(), $_product->getStore()->getWebsiteId());
for($repli=1;$repli<=$availableQty;$repli++)
echo "<option value='$repli'>$repli</option>";
?>
</select>
<?php } else { 
$usedProducts = $_product->getTypeInstance()->getUsedProducts($_product);
$productStockd=$objectManager->get('Magento\CatalogInventory\Api\StockRegistryInterface');
$finalconfigqty=[];
foreach ($usedProducts as $child)
{$childid=$child->getId();
$productStockObj = $productStockd->getStockItem($childid);
$availableQty= round($productStockObj->getData('qty')); 
$finalconfigqty['c'.$childid]=$availableQty;
}
echo '<script>
var fichoo='.
json_encode($finalconfigqty).
'</script>';
echo '<select class="input-text qty fichoo" name="qty" id="qty"><option value=1>1</option></select>'; 
?> 
<!--input type="number" name="qty" id="qty" value="<?= $block->getProductDefaultQty() * 1 ?>" title="<?= __('Qty') ?>" class="input-text qty"
data-validate="<?= $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
/--> <?php } ?>
</div>
</div>
<?php endif; ?>
<div class="actions">
<button type="submit"
title="<?= /* @escapeNotVerified */ $buttonTitle ?>"
class="action primary tocart"
id="product-addtocart-button">
<span><?= /* @escapeNotVerified */ $buttonTitle ?></span>
</button>
<?= $block->getChildHtml('', true) ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($block->isRedirectToCartEnabled()) : ?>
<script type="text/x-magento-init">
{
"#product_addtocart_form": {
"Magento_Catalog/product/view/validation": {
"radioCheckboxClosest": ".nested"
}
}
}
</script>
<?php else : ?>
<script type="text/x-magento-init">
{
"#product_addtocart_form": {
"Magento_Catalog/js/validate-product": {}
}
}
</script>
<?php endif; ?>

Another important file is Magento_ConfigurableProduct/web/js/configurable.js

modified function _getSimpleProductId and added function _dofichoo

/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/**
* @api
*/
define([
'jquery',
'underscore',
'mage/template',
'mage/translate',
'priceUtils',
'priceBox',
'jquery/ui',
'jquery/jquery.parsequery'
], function ($, _, mageTemplate, $t, priceUtils) {
'use strict';
$.widget('mage.configurable', {
options: {
superSelector: '.super-attribute-select',
selectSimpleProduct: '[name="selected_configurable_option"]',
priceHolderSelector: '.price-box',
spConfig: {},
state: {},
priceFormat: {},
optionTemplate: '<%- data.label %>' +
'<% if (typeof data.finalPrice.value !== "undefined") { %>' +
' <%- data.finalPrice.formatted %>' +
'<% } %>',
mediaGallerySelector: '[data-gallery-role=gallery-placeholder]',
mediaGalleryInitial: null,
slyOldPriceSelector: '.sly-old-price',
/**
* Defines the mechanism of how images of a gallery should be
* updated when user switches between configurations of a product.
*
* As for now value of this option can be either 'replace' or 'prepend'.
*
* @type {String}
*/
gallerySwitchStrategy: 'replace',
tierPriceTemplateSelector: '#tier-prices-template',
tierPriceBlockSelector: '[data-role="tier-price-block"]',
tierPriceTemplate: ''
},
/**
* Creates widget
* @private
*/
_create: function () {
// Initial setting of various option values
this._initializeOptions();
// Override defaults with URL query parameters and/or inputs values
this._overrideDefaults();
// Change events to check select reloads
this._setupChangeEvents();
// Fill state
this._fillState();
// Setup child and prev/next settings
this._setChildSettings();
// Setup/configure values to inputs
this._configureForValues();
$(this.element).trigger('configurable.initialized');
},
/**
* Initialize tax configuration, initial settings, and options values.
* @private
*/
_initializeOptions: function () {
var options = this.options,
gallery = $(options.mediaGallerySelector),
priceBoxOptions = $(this.options.priceHolderSelector).priceBox('option').priceConfig || null;
if (priceBoxOptions && priceBoxOptions.optionTemplate) {
options.optionTemplate = priceBoxOptions.optionTemplate;
}
if (priceBoxOptions && priceBoxOptions.priceFormat) {
options.priceFormat = priceBoxOptions.priceFormat;
}
options.optionTemplate = mageTemplate(options.optionTemplate);
options.tierPriceTemplate = $(this.options.tierPriceTemplateSelector).html();
options.settings = options.spConfig.containerId ?
$(options.spConfig.containerId).find(options.superSelector) :
$(options.superSelector);
options.values = options.spConfig.defaultValues || {};
options.parentImage = $('[data-role=base-image-container] img').attr('src');
this.inputSimpleProduct = this.element.find(options.selectSimpleProduct);
gallery.data('gallery') ?
this._onGalleryLoaded(gallery) :
gallery.on('gallery:loaded', this._onGalleryLoaded.bind(this, gallery));
},
/**
* Override default options values settings with either URL query parameters or
* initialized inputs values.
* @private
*/
_overrideDefaults: function () {
var hashIndex = window.location.href.indexOf('#');
if (hashIndex !== -1) {
this._parseQueryParams(window.location.href.substr(hashIndex + 1));
}
if (this.options.spConfig.inputsInitialized) {
this._setValuesByAttribute();
}
},
/**
* Parse query parameters from a query string and set options values based on the
* key value pairs of the parameters.
* @param {*} queryString - URL query string containing query parameters.
* @private
*/
_parseQueryParams: function (queryString) {
var queryParams = $.parseQuery({
query: queryString
});
$.each(queryParams, $.proxy(function (key, value) {
this.options.values[key] = value;
}, this));
},
/**
* Override default options values with values based on each element's attribute
* identifier.
* @private
*/
_setValuesByAttribute: function () {
this.options.values = {};
$.each(this.options.settings, $.proxy(function (index, element) {
var attributeId;
if (element.value) {
attributeId = element.id.replace(/[a-z]*/, '');
this.options.values[attributeId] = element.value;
}
}, this));
},
/**
* Set up .on('change') events for each option element to configure the option.
* @private
*/
_setupChangeEvents: function () {
$.each(this.options.settings, $.proxy(function (index, element) {
$(element).on('change', this, this._configure);
}, this));
},
_dofichoo: function(thisstock){var lsstr='';
for(var ah=1;ah<=window.fichoo['c'+thisstock];ah++) lsstr+='<option value='+ah+ ' >'+ ah + '</option>';
$('select.fichoo').html(lsstr);
},
/**
* Iterate through the option settings and set each option's element configuration,
* attribute identifier. Set the state based on the attribute identifier.
* @private
*/
_fillState: function () {
$.each(this.options.settings, $.proxy(function (index, element) {
var attributeId = element.id.replace(/[a-z]*/, '');
if (attributeId && this.options.spConfig.attributes[attributeId]) {
element.config = this.options.spConfig.attributes[attributeId];
element.attributeId = attributeId;
this.options.state[attributeId] = false;
}
}, this));
},
/**
* Set each option's child settings, and next/prev option setting. Fill (initialize)
* an option's list of selections as needed or disable an option's setting.
* @private
*/
_setChildSettings: function () {
var childSettings = [],
settings = this.options.settings,
index = settings.length,
option;
while (index--) {
option = settings[index];
if (index) {
option.disabled = true;
} else {
this._fillSelect(option);
}
_.extend(option, {
childSettings: childSettings.slice(),
prevSetting: settings[index - 1],
nextSetting: settings[index + 1]
});
childSettings.push(option);
}
},
/**
* Setup for all configurable option settings. Set the value of the option and configure
* the option, which sets its state, and initializes the option's choices, etc.
* @private
*/
_configureForValues: function () {
if (this.options.values) {
this.options.settings.each($.proxy(function (index, element) {
var attributeId = element.attributeId;
element.value = this.options.values[attributeId] || '';
this._configureElement(element);
}, this));
}
},
/**
* Event handler for configuring an option.
* @private
* @param {Object} event - Event triggered to configure an option.
*/
_configure: function (event) {
event.data._configureElement(this);
},
/**
* Configure an option, initializing it's state and enabling related options, which
* populates the related option's selection and resets child option selections.
* @private
* @param {*} element - The element associated with a configurable option.
*/
_configureElement: function (element) {
this.simpleProduct = this._getSimpleProductId(element);
if (element.value) {
this.options.state[element.config.id] = element.value;
if (element.nextSetting) {
element.nextSetting.disabled = false;
this._fillSelect(element.nextSetting);
this._resetChildren(element.nextSetting);
} else {
if (!!document.documentMode) { //eslint-disable-line
this.inputSimpleProduct.val(element.options[element.selectedIndex].config.allowedProducts[0]);
} else {
this.inputSimpleProduct.val(element.selectedOptions[0].config.allowedProducts[0]);
}
}
} else {
this._resetChildren(element);
}
this._reloadPrice();
this._displayRegularPriceBlock(this.simpleProduct);
this._displayTierPriceBlock(this.simpleProduct);
this._changeProductImage();
},
/**
* Change displayed product image according to chosen options of configurable product
*
* @private
*/
_changeProductImage: function () {
var images,
initialImages = this.options.mediaGalleryInitial,
galleryObject = $(this.options.mediaGallerySelector).data('gallery');
if (!galleryObject) {
return;
}
images = this.options.spConfig.images[this.simpleProduct];
if (images) {
if (this.options.gallerySwitchStrategy === 'prepend') {
images = images.concat(initialImages);
}
images = $.extend(true, [], images);
images = this._setImageIndex(images);
galleryObject.updateData(images);
$(this.options.mediaGallerySelector).AddFotoramaVideoEvents({
selectedOption: this.simpleProduct,
dataMergeStrategy: this.options.gallerySwitchStrategy
});
} else {
galleryObject.updateData(initialImages);
$(this.options.mediaGallerySelector).AddFotoramaVideoEvents();
}
galleryObject.first();
},
/**
* Set correct indexes for image set.
*
* @param {Array} images
* @private
*/
_setImageIndex: function (images) {
var length = images.length,
i;
for (i = 0; length > i; i++) {
images[i].i = i + 1;
}
return images;
},
/**
* For a given option element, reset all of its selectable options. Clear any selected
* index, disable the option choice, and reset the option's state if necessary.
* @private
* @param {*} element - The element associated with a configurable option.
*/
_resetChildren: function (element) {
if (element.childSettings) {
_.each(element.childSettings, function (set) {
set.selectedIndex = 0;
set.disabled = true;
});
if (element.config) {
this.options.state[element.config.id] = false;
}
}
},
/**
* Populates an option's selectable choices.
* @private
* @param {*} element - Element associated with a configurable option.
*/
_fillSelect: function (element) {
var attributeId = element.id.replace(/[a-z]*/, ''),
options = this._getAttributeOptions(attributeId),
prevConfig,
index = 1,
allowedProducts,
i,
j;
this._clearSelect(element);
element.options[0] = new Option('', '');
element.options[0].innerHTML = this.options.spConfig.chooseText;
prevConfig = false;
if (element.prevSetting) {
prevConfig = element.prevSetting.options[element.prevSetting.selectedIndex];
}
if (options) {
for (i = 0; i < options.length; i++) {
allowedProducts = [];
/* eslint-disable max-depth */
if (prevConfig) {
for (j = 0; j < options[i].products.length; j++) {
// prevConfig.config can be undefined
if (prevConfig.config &&
prevConfig.config.allowedProducts &&
prevConfig.config.allowedProducts.indexOf(options[i].products[j]) > -1) {
allowedProducts.push(options[i].products[j]);
}
}
} else {
allowedProducts = options[i].products.slice(0);
}
if (allowedProducts.length > 0) {
options[i].allowedProducts = allowedProducts;
element.options[index] = new Option(this._getOptionLabel(options[i]), options[i].id);
if (typeof options[i].price !== 'undefined') {
element.options[index].setAttribute('price', options[i].prices);
}
element.options[index].config = options[i];
index++;
}
/* eslint-enable max-depth */
}
}
},
/**
* Generate the label associated with a configurable option. This includes the option's
* label or value and the option's price.
* @private
* @param {*} option - A single choice among a group of choices for a configurable option.
* @return {String} The option label with option value and price (e.g. Black +1.99)
*/
_getOptionLabel: function (option) {
return option.label;
},
/**
* Removes an option's selections.
* @private
* @param {*} element - The element associated with a configurable option.
*/
_clearSelect: function (element) {
var i;
for (i = element.options.length - 1; i >= 0; i--) {
element.remove(i);
}
},
/**
* Retrieve the attribute options associated with a specific attribute Id.
* @private
* @param {Number} attributeId - The id of the attribute whose configurable options are sought.
* @return {Object} Object containing the attribute options.
*/
_getAttributeOptions: function (attributeId) {
if (this.options.spConfig.attributes[attributeId]) {
return this.options.spConfig.attributes[attributeId].options;
}
},
/**
* Reload the price of the configurable product incorporating the prices of all of the
* configurable product's option selections.
*/
_reloadPrice: function () {
$(this.options.priceHolderSelector).trigger('updatePrice', this._getPrices());
},
/**
* Get product various prices
* @returns {{}}
* @private
*/
_getPrices: function () {
var prices = {},
elements = _.toArray(this.options.settings),
hasProductPrice = false;
_.each(elements, function (element) {
var selected = element.options[element.selectedIndex],
config = selected && selected.config,
priceValue = {};
if (config && config.allowedProducts.length === 1 && !hasProductPrice) {
priceValue = this._calculatePrice(config);
hasProductPrice = true;
}
prices[element.attributeId] = priceValue;
}, this);
return prices;
},
/**
* Returns prices for configured products
*
* @param {*} config - Products configuration
* @returns {*}
* @private
*/
_calculatePrice: function (config) {
var displayPrices = $(this.options.priceHolderSelector).priceBox('option').prices,
newPrices = this.options.spConfig.optionPrices[_.first(config.allowedProducts)];
_.each(displayPrices, function (price, code) {
if (newPrices) {
displayPrices.amount = newPrices.amount - displayPrices.amount;
}
});
return displayPrices;
},
/**
* Returns Simple product Id
*  depending on current selected option.
*
* @private
* @param {HTMLElement} element
* @returns {String|undefined}
*/
_getSimpleProductId: function (element) {
// TODO: Rewrite algorithm. It should return ID of
//        simple product based on selected options.
var allOptions = element.config.options,
value = element.value,
config;
config = _.filter(allOptions, function (option) {
return option.id === value;
});
config = _.first(config);
if(! _.isEmpty(config)) this._dofichoo(_.first(config.allowedProducts));
return _.isEmpty(config) ?
undefined :
_.first(config.allowedProducts);
},
/**
* Show or hide regular price block
*
* @param {*} optionId
* @private
*/
_displayRegularPriceBlock: function (optionId) {
if (typeof optionId != 'undefined' &&
this.options.spConfig.optionPrices[optionId].oldPrice.amount != //eslint-disable-line eqeqeq
this.options.spConfig.optionPrices[optionId].finalPrice.amount
) {
$(this.options.slyOldPriceSelector).show();
} else {
$(this.options.slyOldPriceSelector).hide();
}
},
/**
* Callback which fired after gallery gets initialized.
*
* @param {HTMLElement} element - DOM element associated with gallery.
*/
_onGalleryLoaded: function (element) {
var galleryObject = element.data('gallery');
this.options.mediaGalleryInitial = galleryObject.returnCurrentImages();
},
/**
* Show or hide tier price block
*
* @param {*} optionId
* @private
*/
_displayTierPriceBlock: function (optionId) {
var options, tierPriceHtml;
if (typeof optionId != 'undefined' &&
this.options.spConfig.optionPrices[optionId].tierPrices != [] // eslint-disable-line eqeqeq
) {
options = this.options.spConfig.optionPrices[optionId];
if (this.options.tierPriceTemplate) {
tierPriceHtml = mageTemplate(this.options.tierPriceTemplate, {
'tierPrices': options.tierPrices,
'$t': $t,
'currencyFormat': this.options.spConfig.currencyFormat,
'priceUtils': priceUtils
});
$(this.options.tierPriceBlockSelector).html(tierPriceHtml).show();
}
} else {
$(this.options.tierPriceBlockSelector).hide();
}
}
});
return $.mage.configurable;
});