articles

Home / DeveloperSection / Articles / Integrating Knockout.js with RESTful APIs

Integrating Knockout.js with RESTful APIs

Integrating Knockout.js with RESTful APIs

Ravi Vishwakarma 47 26-Jun-2024

Integrating Knockout.js with RESTful APIs allows you to fetch data from a server and update your UI dynamically. Here's a beginner's guide to fetching data and updating it using Knockout.js:

1. Setting Up Your HTML

Start by setting up your HTML structure where you want to display data fetched from the API.

<!doctype html>
<html lang="en">

<head>
    <!-- Meta tags for character set and viewport configuration -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Title of the webpage -->
    <title>Bootstrap demo</title>

    <!-- Link to Bootstrap CSS for styling -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">

    <!-- Link to Knockout.js library for MVVM pattern support -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.min.js"
        integrity="sha512-vs7+jbztHoMto5Yd/yinM4/y2DOkPLt0fATcN+j+G4ANY2z4faIzZIOMkpBmWdcxt+596FemCh9M18NUJTZwvw=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.4/knockout.validation.min.js"
        integrity="sha512-b99MDNv5TqiZtPKH2UeHzDAVydmgrOJEPtaPPEF8AgV86eYyqINFI/K7/7f0+R4WNTAVv8KvvpjwfOYHv5rd5g=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>

    <!-- Link to jQuery library -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>

<body>
    <!-- Main container for the content -->
    <div class="container my-3">
        <!-- Header for the section -->
        <h2>Data from Server</h2>

        <div class="d-flex w-100 justify-content-between ">
            <!-- Button trigger modal -->
            <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#postStaticBackdrop">
                New Post 
            </button>
            <div>
                <input type="search" data-bind="value: searchText, valueUpdate: 'input'" class="form-control"
                    placeholder="Search ..." />
            </div>
        </div>
        <hr />
        <p class="fw-semibold text-black-50 text-end">
            <span class="" data-bind="text: filteredItemsLength"></span>
            <span>data found.</span>
        </p>
        <div data-bind="css: { 'd-none': filteredItemsLength() !== 0 }">
            <hr/>
            <div class="d-flex justify-content-center fw-bold">
                <div class="text-center">
                    <p class=" fs-2 ">
                        <span class="me-2">😕</span>
                        No records were found
                    </p>
                    <!-- <p class="fw-normal">try somthing new keywords</p> -->
                </div>
            </div>
        </div>
        <!-- Responsive row to display items fetched from the server -->
        <div class="row g-3 row-cols-1 row-cols-md-2 row-cols-lg-4 row-cols-xl-4" data-bind="foreach: filteredItems">
            <!-- Column for each item -->
            <div class="col">
                <!-- Card to display item details -->
                <div class="card h-100">
                    <div class="card-body">
                        <!-- Card title bound to item's title -->
                        <h5 class="card-title text-capitalize" data-bind="text: title"></h5>
                        <!-- Card text bound to item's body -->
                        <p class="card-text" data-bind="text: body"></p>
                    </div>
                    <div class="card-footer border-0 pt-0">
                        <!-- Footer with item id and a link -->
                        <p class="m-0 d-flex justify-content-between">
                            <!-- Item id displayed -->
                            <span class="fs-5 fw-bold" data-bind="text: id"></span>
                            <!-- Link to comments page for the item -->
                            <a
                                data-bind="attr: { href: 'https://jsonplaceholder.typicode.com/posts/' + id + '/comments' }">
                                <!-- SVG icon inside the link -->
                                <svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="currentColor"
                                    class="bi bi-arrow-right-circle-fill" viewBox="0 0 16 16">
                                    <path
                                        d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0M4.5 7.5a.5.5 0 0 0 0 1h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5z" />
                                </svg>
                            </a>
                        </p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>
<!-- Modal -->
<div class="modal fade" id="postStaticBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="postStaticBackdropLabel" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
      <div class="modal-content">
        <div class="modal-header">
          <h1 class="modal-title fs-5" id="postStaticBackdropLabel">Add New Post</h1>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
            <form class="row g-3" data-bind="submit: function() { addPost({ title: newTitle(), body: newBody(), userId: userId() }); }">
                <input type="hidden" data-bind="value: userId"/>
                <div class="col-12">
                    <label class="form-label fw-bold">Title</label>
                    <input type="text" placeholder="Title" id="newTitle" name="newTitle" data-bind="value: newTitle, valueUpdate: 'input'" class="form-control">
                </div>
                <div class="col-12">
                    <label class="form-label fw-bold">Body</label>
                    <textarea rows="5" placeholder="Body" id="newBody" name="newBody" data-bind="value: newBody, valueUpdate: 'input'" class="form-control"></textarea>
                </div>
                <div class="col-12">
                    <button type="submit" class="btn btn-primary">Add Post</button>
                </div>
            </form>
        </div>
      </div>
    </div>
  </div>


<!-- Link to external JavaScript file -->
<script src="app.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</html>

2. Creating Your ViewModel

In your JavaScript file (app.js), define your ViewModel, and include functions to fetch data from the API and update your UI.

// app.js
// Initialize Knockout validation
ko.validation.init({
    registerExtenders: true, // Register custom validation rules
    messagesOnModified: true, // Show validation messages as soon as a field is modified
    insertMessages: true, // Insert validation messages next to the input elements
    parseInputAttributes: true, // Parse HTML5 input attributes for validation rules
    errorClass: 'text-danger fw-semibold', // CSS class for validation error messages
    messageTemplate: null // Use default message template
}, true);

class AppViewModel {
    constructor() {
        var self = this;

        // Observable array to hold the data fetched from the server
        self.items = ko.observableArray([]);
        self.searchText = ko.observable('');
        self.newTitle = ko.observable('').extend({
            required: { message: "Title is required." },
            minLength: { params: 2, message: "Title must be at least 2 characters." }
        });
        self.newBody = ko.observable('').extend({
            required: { message: "Body is required." },
            minLength: { params: 2, message: "Body must be at least 2 characters." }
        });

        self.userId = ko.observable('11');
        // Function to fetch data from the API
        self.fetchData = function() {
            fetch('https://jsonplaceholder.typicode.com/posts')
                .then(response => response.json())
                .then(data => {
                    self.items(data);
                })
                .catch(error => {
                    console.error('Error fetching data:', error);
                });
        };



        // Computed observable to filter the items based on the filter criteria
        self.filteredItems = ko.computed(function () {
            //console.log(self.items().reverse());
            // Reverse the items array
            var reversedItems = self.items().slice().reverse();

            //var filter = self.filter().toLowerCase(); // Convert filter criteria to lowercase
            if (!self.searchText) {
                return reversedItems; // If no filter, return all items
            } else {
                // Filter the items based on the filter criteria
                return ko.utils.arrayFilter(reversedItems, function (item) {
                    return JSON.stringify(item).includes(self.searchText().toLowerCase()); // Case-insensitive match
                });
            }
        });

        // Function to reverse the observable array
        self.reverseArray = function() {
            self.items(self.items().slice().reverse());
        };

        self.filteredItemsLength = ko.computed(function () {
            return self.filteredItems().length;
        });
        self.fetchData();
        
        // Initialize validation
        self.errors = ko.validation.group(self);
    }
}

// Apply the Knockout bindings to the AppViewModel
ko.applyBindings(new AppViewModel());

Understanding the Code

HTML: <ul data-bind="foreach: posts">: This binds to the posts observable array in your ViewModel, iterating over each post to display its title and body.

JavaScript (ViewModel):

  • self.posts = ko.observableArray([]);: This defines an observable array posts to store fetched data.
  • self.fetchData = function() { ... };: This function uses fetch() to make a GET request to the RESTful API (https://jsonplaceholder.typicode.com/posts in this example). Upon successful response, it updates the posts observable array with the fetched data.

Fetching Data:

  • fetch('https://jsonplaceholder.typicode.com/posts'): Initiates a GET request to fetch data from the specified API endpoint.
  • .then(response => response.json()): Converts the response to JSON format.
  • .then(data => { self.posts(data); }): Updates the posts observable array with the fetched data.
  • .catch(error => { console.error('Error fetching data:', error); });: Handles any errors that occur during the fetch operation.

 

3. Updating Data and Sending Changes

To update data or send changes back to the server (POST, PUT, DELETE requests), you would typically define functions in your ViewModel similar to fetchData() but tailored for specific HTTP methods (fetch() supports various HTTP methods).

// Function to add a new post
        self.addPost = function(newPost) {
            // Check if the form is valid
            if (self.errors().length === 0) {
                fetch('https://jsonplaceholder.typicode.com/posts', {
                    method: 'POST',
                    body: JSON.stringify(newPost),
                    headers: {
                        'Content-type': 'application/json; charset=UTF-8',
                    },
                })
                .then(response => response.json())
                .then(data => {
                    self.items.push(data); // Assuming the server responds with the created post object
                    self.newTitle(''); // Clear input after successful addition
                    self.newBody(''); // Clear input after successful addition
                    // Hide modal using jQuery
                    $('#postStaticBackdrop').modal('hide');
                    self.errors.showAllMessages(false); // Hide all validation messages
                })
                .catch(error => {
                    console.error('Error adding post:', error);
                });
            } else {
                // Show validation errors
                self.errors.showAllMessages(true);
            }
        };

Output -

Integrating Knockout.js with RESTful APIs

 

 

 

 

 

 

 

 


Hi, my self Ravi Vishwakarma. I have completed my studies at SPICBB Varanasi. now I completed MCA with 76% form Veer Bahadur Singh Purvanchal University Jaunpur. SWE @ MindStick | Software Engineer | Web Developer | .Net Developer | Web Developer | Backend Engineer | .NET Core Developer

Leave Comment

Comments

Liked By