0

So I am working with Knockout inside Magento 2.3.4, and I am setting a custom observable value on initialize, and then I am trying to access that observable and change the value inside a function. Every time I try, I keep getting "it is not a function", and it won't let me either retrieve and read the current observable value, or set a new one. When I try to run .isObservable() on it it comes up as false. I have looked through various examples of how to do it and tried all of them and none of them work. Currently my knockout JS form looks like this:

define([
'jquery',
'uiComponent',
'ko'
], function($, Component, ko) {
'use strict';

return Component.extend({
   defaults: {
       template: 'Shmoop_Cms/career-form'
   },
    progressText: ko.observable(false),

    initialize: function() {
       var self = this;

       this._super();

           this.progressText('1 of 15 questions completed');

        return this;
    },

    showNext: function() {
       let dataIndex = parseInt($('.quiz-card.show').attr('data-index')) + 1;
       alert(ko.isObservable(this.progressText));
       alert(this.progressText());
       
       this.progressText(dataIndex + ' of 15 questions completed');
    }

});

});

I am able to set the progressText value initially inside that initialize function without issue, and it recognizes there that it is an observable. Why does it say it's not an observable inside by "showNext" function?

FYI I have also tried adding "var self = this" inside my function, I have also tried "self.progressText()" instead of "this.progressText()", nothing worked.

Edited to add: I am calling this variable observable in the template and displaying it there, like this:

<p class="progress-text" data-bind="text: $parent.progressText"></p>

This correctly displays the text value that I assign in the initialize function, but never updates after the showNext function is called.

Please help.

2 Answers 2

1

Try this approach instead using Magento built in way for creating observable in uiComponents:

define([
    'jquery',
    'uiComponent',
    'ko'
], function($, Component, ko) {
    'use strict';

    return Component.extend({
        defaults: {
            template: 'Shmoop_Cms/career-form',
            progressText: "",
            tracks: {
                progressText: true
            }
        },

        initialize: function() {
            var self = this;

            this._super();
            this.progressText = '1 of 15 questions completed';

            return this;
        },

        showNext: function() {
            let dataIndex = parseInt($('.quiz-card.show').attr('data-index')) + 1;
            alert(this.progressText);

            this.progressText = dataIndex + ' of 15 questions completed';
        }
    });
});

Edited from here. If you can not reach this.progressText in showNext it might be a different problem. I made a simple example locally and tested it and it worked fine. Test the code below and see if it works for you as well. In the case it might be some other problem:

app/code/StackExchange/DataBinding/view/frontend/web/js/binding.js

define([
    'jquery',
    'uiComponent',
    'ko'
], function($, Component, ko) {
    'use strict';

    return Component.extend({
        defaults: {
            progressText: "",
            dataIndex: 1,
            tracks: {
                progressText: true
            }
        },

        initialize: function() {
            var self = this;

            this._super();
            this.progressText = '1 of 15 questions completed';

            return this;
        },

        showNext: function() {
            this.dataIndex++;
            this.progressText = this.dataIndex + ' of 15 questions completed';
        }
    });
});

app/code/StackExchange/DataBinding/view/frontend/templates/binding.phtml

<?php declare(strict_types=1);
/** @var \Magento\Framework\View\Element\Template $block */
/** @var \Magento\Framework\Escaper $escaper */
?>
<script type="text/x-magento-init">
        {
            "*": {
                "Magento_Ui/js/core/app": {
                    "components": {
                        "binding": {
                            "component": "StackExchange_DataBinding/js/binding"
                        }
                    }
                }
            }
        }
</script>
<div data-bind="scope: 'binding'">
    <button data-bind="click: showNext">Show next</button>
    <span data-bind="html: progressText"></span>
</div>

When I am clicking the button it is when showNext is called, meaning that it can reach this.progressText as expected:

enter image description here

4
  • I have tried doing it the way you have your example above, and in that initialize function it sets it correctly and I can pull the "progressText" variable into the html template, but when I try to set it within that showNext function, it doesn't update the global variable and the template doesn't update. Am I missing a piece of it? How would you display that variable in the template after the function ran? My html template has this line currently: <p class="progress-text" data-bind="text: $parent.progressText"></p>
    – Erica S.
    Nov 4, 2020 at 21:02
  • Are you sure, it works for me. Did you assign like this.progressText = "New value" without the parantheses?
    – Kristoffer
    Nov 5, 2020 at 5:16
  • Yes I did it exactly like the above. It seems like the same issue as before, when I set it in that "initialize" function, that works, but when I set it in the showNext function... it seems to treat it like an independent variable. If I console.log what this.progressText is inside the function, it DOES update correctly, but globally that value doesn't change so it doesn't update it in the template.
    – Erica S.
    Nov 6, 2020 at 15:28
  • I updated the response, please have a look. I think you have problem somewhere else, maybe something with the binding in your template?
    – Kristoffer
    Nov 12, 2020 at 10:09
0

To those who are still finding solution on this page,

var self = this;
this.testVariable = ko.observable();
this.getServiceListSuccess = function(response) {
    // The following line gives me error as 'this.testFunction is not a 
    //function'
    self.testVariable();
};

Make the application scope as self. This inside the function refers to the scope of that function only. So use self to use application scope.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.