javascriptjqueryarraysdata-bindingknockout.js

How to display images and text using knockout?


I'm learning more about knockout.js. I want to add images to my text data, and display the correct one based on the image link provided in the array with it's matching data. I only want one set to show at a time, so in other words it replaces the data. I don't quite grasp how the data binding stuff works just yet, and how everything displays. I'm stumbling my way through figuring it out.

What I sort of have figured out is the text part, but I'm lost on adding images based on the sets below.

Here's a short example of my data:

 const story = 

  //-------------------------------Initiate Chat
  {"1":{"text":"Pick a chat",
  "image":"http://placekitten.com/300/200",
  "choices":[{"choiceText":"Pick This","storyLink":"2"},{"choiceText":"no pick this","storyLink":"3"},{"choiceText":"nono this one","storyLink":"4"},{"choiceText":"dont pick this","storyLink":"5"},]},
  
   //-------------------------------Chat 1 test
  "2":{"text":"Kittens",
  "image":"https://placekitten.com/300/100",
  "choices":[{"choiceText":"Oops. Scared them.","storyLink":"3"}]},

  "3":{"text":"They are no longer scared",
  "image":"https://placekitten.com/300/100",
  "choices":[{"choiceText":"Poor things.","storyLink":"3"}]},
  
}

An example of working code would help me the most. I keep getting "Unable to Process Binding" Error when I have tried figuring it out. I can't seem to find specific examples where the images are being called along with other data like text as shown above. I appreciate the help!

For example: Knockout Image Not display while binding image and text at same time

this sort of answers my question but I'm not sure how to do it with an array. Should I be using a forEach?


Solution

  • I think it's a good idea to introduce some view models that make it easier to work with your story format.

    In the example below, I created two:

    Once you have your image urls properly mapped between the choices and the main story chunks, you can use the attr: { src } binding to apply it to an image.

    To prevent broken images when no src is available, I've wrapped the <img> tag in an if binding.

    const StoryVM = (model, startId) => {
      const activeStoryId = ko.observable(startId);
    
      const activeStory = ko.pureComputed(() => {
        const data = model[activeStoryId()]
    
        return {
          ...data,
          choices: data.choices.map(ChoiceVM)
        };
      });
    
      const ChoiceVM = ({
        choiceText,
        storyLink,
      }) => {
        return {
          image: storyModel[storyLink]?.image,
          text: choiceText,
          onClick: () => {
            activeStoryId(storyLink);
          }
        }
      }
    
    
      return activeStory;
    }
    
    const storyModel = {"1":{"text":"Pick a chat","image":"http://placekitten.com/300/200","choices":[{"choiceText":"Pick This","storyLink":"2"},{"choiceText":"no pick this","storyLink":"3"},{"choiceText":"nono this one","storyLink":"4"},{"choiceText":"dont pick this","storyLink":"5"}]},"2":{"text":"Kittens","image":"https://placekitten.com/300/100","choices":[{"choiceText":"Oops. Scared them.","storyLink":"3"}]},"3":{"text":"They are no longer scared","image":"https://placekitten.com/200/100","choices":[{"choiceText":"Poor things.","storyLink":"3"}]}};
    
    ko.applyBindings({
      story: StoryVM(storyModel, "1")
    });
    .Choices {
      display: flex;
      gap: 1rem;
    }
    
    .Choice {
      display: flex;
      flex-direction: column;
      width: 200px;
      height: 150px;
      justify-content: flex-end;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    
    
    <div data-bind="with: story">
      <!-- ko if: image -->
      <img data-bind="attr: { src: image }">
      <!-- /ko -->
    
      <h2 data-bind="text: text"></h2>
    
      <div data-bind="foreach: choices" class="Choices">
        <div class="Choice">
          <!-- ko if: image -->
          <img data-bind="attr: { src: image }">
          <!-- /ko -->
          <button data-bind="click: onClick, text: text"></button>
        </div>
      </div>
    </div>