10

My problem:

I am attempting to write a small Knockout JS app within Magento 2, I'm struggling to initialise the app as when I use ko.applyBindings(AppViewModel, document.getElementById("koTest")); it breaks the Knockout used by Magento and throws this error:

Uncaught Error: You cannot apply bindings multiple times to the same element.

I suspect it's because of:

I suspect this is because Magento 2 already uses ko.applyBindings() within app/code/Magento/Ui/view/base/web/js/lib/knockout/bootstrap.js. And as that doesn't specify a node I can't use ko.applyBindings again.

If I don't use ko.applyBindings(AppViewModel, document.getElementById("koTest")) in my code then my app doesn't initialise.

This makes me think I need to somehow use the ko.applyBindings() in knockout/bootstrap.js but I've no idea how, can anyone help? I have little experience with Knockout.

My Code

<script type="text/javascript">
    require([
        'ko'
    ], function(ko) {
        // This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
        function AppViewModel() {

            this.firstName = ko.observable("Bert");
            this.lastName = ko.observable("Bertington");
            this.fullName = ko.computed(function() {
                return this.firstName() + " " + this.lastName();
            }, this);

            this.capitalizeLastName = function() {
                var currentVal = this.lastName();
                this.lastName(currentVal.toUpperCase());
            };
        }

        ko.applyBindings(AppViewModel, document.getElementById("koTest"));
    });
</script>

<!-- This is a *view* - HTML markup that defines the appearance of your UI -->

<div id="koTest">
    <p>First name: <strong data-bind="text: firstName"></strong></p>
    <p>Last name: <strong data-bind="text: lastName"></strong></p>
    <p>Full name: <strong data-bind="text: fullName"></strong></p>

    <p>First name: <input data-bind="value: firstName" /></p>
    <p>Last name: <input data-bind="value: lastName" /></p>
    <p>Full name: <input data-bind="value: fullName" /></p>

    <button data-bind="click: capitalizeLastName">Capitalise</button>
</div>
20

Simple method where you DO NOT need to use html templates

Thanks to Vinai Kopp I've finally got an answer to this, it is much simpler than my previous hacky workaround (I was cleaning nodes). All you need to do is define 'ko' as a dependency and add your code inside a return function.

Below is a simple example that renders out some text passed in via JSON.

app/code/VENODR/MODULE/view/frontend/templates/knockout-example.phtml

Here we tell Magento the scope of our components (this must match data-bind: "scope: 'example-scope'" and pass any additional data. This could be the base URL, a simple message, pretty much anything you want. I've passed a string (PHP echo) as an example

<script type="text/x-magento-init">
{
    "*": {
        "Magento_Ui/js/core/app": {
            "components": {
                "example-scope": {
                    "component": "VENDOR_MODULE/js/knockout-example",
                    "exampleMessage": "<?= __('Hello Magento Stack Exchange!') ?>"
                }
            }
        }
    }
}
</script>

<div data-bind="scope: 'example-scope'">
    <h2 data-bind="text: message"></h2>
</div>

And here we write our Javascript.

app/code/VENDOR/MODULE/view/frontend/web/js/knockout-example.js

define(['ko'], function(ko) {
    return function(config) {
        this.message = ko.observable(config.exampleMessage);
    }
});

 Result

enter image description here

---------------------

Method where you DO need to use HTML templates

If you want to use the HTML templating system within Magento2/Knockout (which I presume you will need for any significant pieces of work) there are a few changes you'll need to make compared to my simplified answer (below).

If you don't require the template functionality then scroll down to my old simplified answer.

The files I'm using for this example are:

  • app/design/frontend/VENDOR/THEME/Magento_Cms/templates/knockout.phtml
  • app/design/frontend/VENDOR/THEME/Magento_Cms/web/js/knockout-example.js
  • app/design/frontend/VENDOR/THEME/Magento_Cms/web/template/test.html

The PHTML template file

The only change to our PHTML template is the call to the getTemplate() function:

<script type="text/x-magento-init">
{
    "*": {
        "Magento_Ui/js/core/app": {
            "components": {
                "example-scope": {
                    "component": "Magento_Cms/js/knockout-example",
                    "exampleMessage": "<?= __('Hello Magento Stack Exchange!') ?>"
                }
            }
        }
    }
}
</script>

<div data-bind="scope: 'example-scope'">
    <h2 data-bind="text: message"></h2>
    <!-- ko template: getTemplate() --><!-- /ko -->
</div>

The JS (component) file

There are a few changes you'll need to make to the JS file, I'll detail these below.

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

    return Component.extend({
        defaults: {
            exampleMessage: 'Hello?',
            template: 'Magento_Cms/test'
        },

        initialize: function() {
            this._super();
            console.log(this.exampleMessage);
            this.message = ko.observable(this.exampleMessage);
        }
    });
});

1 - Your return function now needs to extend the uiComponent module:

return Component.extend({
    ...
});

2 - You need to add an initialize function and call this._super(). this._super() will call the parent component's function with the same name. So in this instance I think it will call initialize of uiComponent.

initialize: function() {
    this._super();
    ...
}.

3 - Optional - You can also set some defaults for your component here, I think this is a good practice to follow as it makes your component easy to work with. When you reuse it you can either keep the defaults or if you want to customise it you can call it with new arguments without altering the component.

For example, if you look at defaults in the JS it sets exampleMessage to 'Hello?' yet the page is rendering the text as Hello Magento Stack Exchange!. This is because I've overwritten exampleMessage in the PHTML file when I've called the component.

The HTML template

I'm still to dig around and see what the HTML templates are capable of, I presume the features mentioned on the Knockout JS documentation can be used in here making them pretty flexible.

I have just added some lorem ipsum text for now, I will likely provide another question/answer once I've figured out what the HTML templates can do.

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Asperiores assumenda beatae blanditiis culpa cupiditate doloremque, expedita ipsum iure magni minima modi molestiae nulla optio porro ratione reiciendis repellat soluta voluptatum!

The result, and overwriting the defaults

As mentioned before you can see that I have overwritten exampleMessage within the template, you can see it working as the text reads Hello Magento Stack Exchange.

enter image description here

If I remove the override in the template file exampleMessage will return to it's default of Hello?. I did need to delete var/view_preprocessed and pub/static/frontend after changing this though. I presume Magento had cached the value.

enter image description here

  • This will work in Magento2.1 – Venkat Sep 30 '16 at 14:29
  • @Venkat - Do you mean you can now easily use Knockout without having to clean the node? Or that my fix does work in 2.1? – Ben Crook Sep 30 '16 at 14:40
  • Your fix will work in 2.1 ? – Venkat Oct 2 '16 at 19:20
  • For me bindings is worked but getting reference error for first input data binding – Venkat Oct 2 '16 at 19:22
  • I think so as KnockoutJS doesn't seem to have changed much since 2.0.X - I haven't tried it in 2.1 though so I'm not 100% sure. Also make sure you do some thorough testing as I'm not sure if this is the best method, it's the only one I can find though. – Ben Crook Oct 3 '16 at 8:56
3

https://github.com/seeni9589/Magento2/tree/master/Smart/Feedback

Custom Feedback form with the knockout js. Hope it helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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