Search code examples
javascriptknockout.jsknockout-components

Knockout component with conditional HTML markup in template:


I'm new with KO and KO components.

I currently have a custom-component.js, which perfectly works, to style all inputs of our project. However, I'd like to get the exact same component working for textareas.

Currently, the template: part of this component looks like :

viewModel: function (params) {

    /*  */

    template: '<span data-bind="template: { afterRender: afterRender }">' +
        '<label class="custom-label" data-bind="css:{\'custom-label-active\':focused}, text:label"></label>' +
        '<input class="form-control" type="text" data-bind="enable: enable, valueUpdate: \'input\', css:{\'custom-label-input\':focused}, attr:{placeholder:label, id:id, type: type}, event:{focus:focus,blur:blur},value:value"/>' +
        '</span>'
});

How should I update it to make it work for textareas too ?

I tried to add a default parameter like this :

this.element = (params.element == undefined || params.element == null) ?  'input' : params.element

but I'm not sure how to update my markdown ?

Should I just replace '<input' by something like '<' + self.element + '...>'
What about the valueUpdate ? (I'm very new to all of this, sorry if it's a dumb question)

Thank you


Solution

  • Your template is shared by all of the component instances. There's no way to dynamically update it using the component's params, other than using knockouts own if, template, with, etc. bindings.

    If you know it will only support a limited set of elements, you can add an if statement for each of them. E.g.:

    <!-- ko if: element === "input" -->
    <input/>
    <!-- /ko -->
    
    <!-- ko if: element === "textarea" -->
    <textarea></textarea>
    <!-- /ko -->
    

    If you want to be able to pass any element, you could look in to using componentInfo.templateNodes`. If that's what you want, let me know. I can extend my answer, but it's a more complicated approach.

    Here's an example using the if binding:

    ko.components.register("custom-input", {
        viewModel: function(params) {
          return {
            elementType: params.elementType,
            value: ko.observable("Your input")
          }
        },
        template: `
        <div>
          <label>
            Your input here:<br>
            <!-- ko if: elementType() === 'input' -->
            <input type="text" data-bind="textInput: value">
            <!-- /ko -->
            
            <!-- ko if: elementType() === 'textarea' -->
            <textarea cols="40" rows="10" data-bind="textInput: value"></textarea>
            <!-- /ko -->
          </label>
        </div>
        `
    });
    
    ko.applyBindings({
      inputType: ko.observable("input")
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    
    <h2>Config:</h2>
    <label>
      <input type="radio" data-bind="checked: inputType" value="input">
      Use input
    </label>
    <label>
      <input type="radio" data-bind="checked: inputType" value="textarea">
      Use text area
    </label>
    
    <h2>Component:</h2>
    <custom-input params="elementType: inputType"></custom-input>