Magento – Knockout js child components

knockoutjsmagento2

I am making a custom module before adding a product on cart with 3 options :

enter image description here

The upload option should display an upload form like :

enter image description here

My initial idea was to make two knockout components :

  • Component to display options
  • Component to show the download form

Each component should be independent just communicating with passed variables :

My first component looks like :

JS component to display options:

define([
  'jquery',
  'uiComponent',
  'mage/validation',
  'mage/url',
  'mage/translate',
  'ko',
  ], function ($, Component, validation, url, $t, ko, loadAction) {
      'use strict';
      var optionActive = ko.observable("");

      return Component.extend({
          defaults: {
              template: 'Sebfie_Prescription/prescription'
          },
          options: {

          },
          initialize: function () {
              this._super();
              this.initButtons();
              optionActive.subscribe(this.onOptionChange.bind(this));
          },

          initButtons: function() {
            this.options.addToCartBtn = $("#product-addtocart-button");
            this.options.nextBtn = $(".wizard-footer .btn-next");
            this.disableAddButton();
            this.options.nextBtn.click(function() {
                $("#itoris_dynamicoptions_add_to_cart").click();
            });
          },

          onOptionChange: function(value) {
            if (value == "upload_prescription") {
                this.onUploadPrescription();
            }
            if (value == "user_prescription") {
                this.onUserPrescription();
            }
            if (value == "later_prescription") {
                this.onLaterPrescription();
            }
          },

          onLaterPrescription: function() {
            this.enableAddButton();
          },

          onUserPrescription: function() {
            this.disableAddButton();
          },

          onUploadPrescription: function() {
            this.disableAddButton();
          },

          disableNextButton: function() {
            this.options.nextBtn.addClass("disabled").attr("aria-disabled","true");
          },

          enableNextButton: function() {
            this.options.nextBtn.removeClass("disabled").removeAttr("aria-disabled");
          },

          disableAddButton: function() {
            this.options.addToCartBtn.addClass("disabled").attr("aria-disabled","true");
          },

          enableAddButton: function() {
            this.options.addToCartBtn.removeClass("disabled").removeAttr("aria-disabled");
          },

          userImage: function() {
            return window.prescription_images.use_previous;
          },
          uploadImage: function() {
            return window.prescription_images.upload;
          },
          laterImage: function() {
            return window.prescription_images.do_it_later;
          },
          setActive: function (value) {
              optionActive(value);
          },
          getOptionActive: function() {
              return optionActive();
          },
          getOptionActiveKo: function() {
            return optionActive;
          }
      });
  }
)

Template Sebfie_Prescription/prescription.html :

<div class="prescription-upload-wrapper">
  <h3>PROVIDE YOUR PRESCRIPTION DETAILS</h3>
  <input name="prescription_type" type="hidden" data-bind="attr: {value: getOptionActive()}"/>
  <!-- ko if: getOptionActive() != 'upload_prescription' -->
  <div class="row">
    <div class="col-md-4">
      <div class="choice" data-bind="click: setActive('user_prescription'), css: { active: getOptionActive() == 'user_prescription' }">
        <div class="img">
          <img data-bind="attr: { src: userImage() }" />
        </div>
        <div class="description">
          <strong>Use a previous prescription</strong>
          Please sign-in here
        </div>
      </div>
    </div>
    <div class="col-md-4">
      <div class="choice" data-bind="click: setActive('upload_prescription'), css: { active: getOptionActive() == 'upload_prescription' }">
        <div class="img">
          <img data-bind="attr: { src: uploadImage() }" />
        </div>
        <div class="description">
          <strong>Upload a prescription</strong>
          Take a picture of your prescription of upload it into your portal by choosing this option.
        </div>
      </div>
    </div>
    <div class="col-md-4">
        <div class="choice" data-bind="click: setActive('later_prescription'), css: { active: getOptionActive() == 'later_prescription' }">
          <div class="img">
            <img data-bind="attr: { src: laterImage() }" />
          </div>
          <div class="description">
            <strong>DO IT LATER</strong>
            You will receive an e-mail explaining how you can provide your prescription, after you have placed the order.
          </div>
        </div>
    </div>
  </div>
  <!-- /ko -->
  <!-- ko if: getOptionActive() == 'upload_prescription' -->
  <div data-bind='scope: "prescriptionUpload", mageInit: {"prescriptionUpload": {option: getOptionActiveKo()}}'>
    <!-- ko template: getTemplate() --><!-- /ko -->
  </div>
  <!-- /ko -->
</div>

Component Sebfie_Prescriotion/type/upload.html

define([
  'jquery',
  'uiComponent',
  'mage/validation',
  'mage/url',
  'mage/translate',
  'mage/template',
  'ko',
  ], function ($, Component, validation, url, $t, template, ko, loadAction) {
      'use strict';
      var photoUrl = ko.observable();

      return Component.extend({
          defaults: {
              template: 'Hellooptical_Prescription/type/upload',
              select_option: null
          },
          initialize: function (params) {
              this.select_option = params.option;
              this._super();
          },

          fileUpload: function(data, e) {
            var file    = e.target.files[0];
            var reader  = new FileReader();

            reader.onloadend = function (onloadend_e)
            {
                var result = reader.result; // Here is your base 64 encoded file. Do with it what you want.
                photoUrl(result);
            };

            if(file)
            {
                reader.readAsDataURL(file);
            }
          },

          photoUrl: function() {
              return photoUrl();
          }
      });
  }
)

Template type/upload.html

<input name="prescription_file" data-bind="event: {change: fileUpload}" type="file" accept="image/*" class="fileChooser"/>
<img class="myImage" data-bind="attr: {src: photoUrl() }"/>

But with this I got error :

Uncaught RangeError: Unable to process binding "template: function(){return getTemplate() }"

In fact the scope of :

<div data-bind='mageInit: {"prescriptionUpload": {option: getOptionActiveKo()}}'>
    <!-- ko template: getTemplate() --><!-- /ko -->
</div>

Keeps to be the parent scope.

Best Answer

change your code like this.

define([
  'jquery',
  'uiComponent',
  'mage/validation',
  'mage/url',
  'mage/translate',
  'mage/template',
  'ko',
  ], function ($, Component, validation, url, $t, template, ko) {
      'use strict';
      var photoUrl = ko.observable();

      return Component.extend({
          defaults: {
              template: 'Hellooptical_Prescription/type/upload',
              select_option: null
          },
          initialize: function (params) {
              this.select_option = params.option;
              this._super();
          },

          fileUpload: function(data, e) {
            var file    = e.target.files[0];
            var reader  = new FileReader();

            reader.onloadend = function (onloadend_e)
            {
                var result = reader.result; // Here is your base 64 encoded file. Do with it what you want.
                photoUrl(result);
            };

            if(file)
            {
                reader.readAsDataURL(file);
            }
          },

          photoUrl: function() {
              return photoUrl();
          }
      });
  }
)
Related Topic