2

I am trying to create a Kanban/task manager that allows users to move 'Cards' from one status to another in order to reflect the current status of a task. Below is the code, but the problem is that the changes made when a card is moved are not persistent -- enter image description hereif the page is refreshed, all of the activity is reset. I'm attaching a screenshot for reference.

<!DOCTYPE html>
    <html>
       <head>
          <meta charset = "utf-8">
          <title>jQuery UI Widget - Default functionality</title>
          <link rel = "stylesheet" href = "//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
          <script src = "https://code.jquery.com/jquery-1.10.2.js"></script>
          <script src = "https://code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
    <!-- css --> 
     <style>
      body {font-family:Roboto;}
      h2 {margin:5px;}
      input[type=text] {margin:10px}
      input[type=button] {margin:10px}  
      .container {width: 20%; float:left;clear: right;margin:10px; border-radius: 5px;}
      .sortable { list-style-type: none; margin:0; padding:2px; min-height:30px; border-radius: 5px;}
      .sortable li { margin: 3px 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; font-size: 1.4em; height: 18px;}
      .sortable li span { position: absolute; margin-left: -1.3em; }
      .card{background-color:white;border-radius:3px;}
      .head-lt {color:#fcfcf4;}
      .head-dk {color:#37474F;}
      </style>
      <script>
      $(function() {
        $( ".sortable" ).sortable({
          connectWith: ".connectedSortable",
          receive: function( event, ui ) {
            $(this).css({"background-color":"#fcfcff4"});
          }
        }).disableSelection();
        $('.add-button').click(function() {
            var txtNewItem = $('#new_text').val();
            $(this).closest('div.container').find('ul').append('<li class="card">'+txtNewItem+'</li>');
        });    
      });
      </script>      
    </head>
    <body>
    <div>
    <div class="container" style="background-color:#EF4931;">
    <h2 class="head-lt">Not started</h2>
    <ul class="sortable connectedSortable">
      <li class="card">Activity A1</li>
      <li class="card">Activity A2</li>
      <li class="card">Activity A3</li>
    </ul>
    <div class="link-div">
        <input type="text" id="new_text" value=""/>
        <input type="button" name="btnAddNew" value="Add" class="add-button"/>
    </div>
    </div>
    <div class="container" style="background-color:#ffd300;">
    <h2 class="head-dk">In-progress</h2>
    <ul class="sortable connectedSortable" >
      <li class="card">Activity B1</li>
      <li class="card">Activity B2</li>
    </ul>
    </div>
    <div class="container" style="background-color:#103c6d;">
    <h2 class="head-lt">Final stage</h2>
    <ul class="sortable connectedSortable" >
      <li class="card">Activity C1</li>
      <li class="card">Activity C2</li>
    </ul>
    </div>
    <div class="container" style="background-color:#B9CEC5;">
    <h2 class="head-dk">Completed</h2>
    <ul class="sortable connectedSortable" >
    </ul>
    </div>
    </div>
    
    <script type="text/javascript">
    function storageAvailable(type) {
        var storage;
        try {
            storage = window[type];
            var x = '__storage_test__';
            storage.setItem(x, x);
            storage.removeItem(x);
            return true;
        }
        catch(e) {
            return e instanceof DOMException && (
                // everything except Firefox
                e.code === 22 ||
                // Firefox
                e.code === 1014 ||
                // test name field too, because code might not be present
                // everything except Firefox
                e.name === 'QuotaExceededError' ||
                // Firefox
                e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
                // acknowledge QuotaExceededError only if there's something already stored
                (storage && storage.length !== 0);
        }
    }
    </script>
    
    
    </body>
    </html>
3
  • Can I ask why you don't use Planner? I guess because you use Classic Sites or have Microsoft Groups disabled. The easiest way would be to adopt the Microsoft strategy and move to modern pages with Planner. I know, sometimes it's not that easy. But surely the most future proof way :)
    – engineerer
    Nov 25, 2020 at 19:41
  • Thanks for responding. You make a good point. I haven't moved to modern experience yet, because I don't want to lose my workflows in SPD. Also, I'm concerned that all of my script will no longer work. I'm hesitant to use any third-party apps (like planner). I've found that making my own solutions is the easiest.
    – TheJDScott
    Nov 25, 2020 at 19:50
  • Planner is first-party and is quick and easy to implement. Plus you can drop the SPD workflows which will be fully retired most likely next year (2010 WFs were removed in Oct).
    – user6024
    Dec 4, 2020 at 18:49

2 Answers 2

4
+50

You need to create a list to store this web part data. When you add a new value, we could create a new item in the list, when you move the option, we could update the list item in list.

My test code for your reference:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>jQuery UI Widget - Default functionality</title>
  <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
  <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
  <script src="https://code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
  <!-- css -->
  <style>
    body {
      font-family: Roboto;
    }

    h2 {
      margin: 5px;
    }

    input[type=text] {
      margin: 10px
    }

    input[type=button] {
      margin: 10px
    }

    .container {
      width: 20%;
      float: left;
      clear: right;
      margin: 10px;
      border-radius: 5px;
    }

    .sortable {
      list-style-type: none;
      margin: 0;
      padding: 2px;
      min-height: 30px;
      border-radius: 5px;
    }

    .sortable li {
      margin: 3px 3px 3px 3px;
      padding: 0.4em;
      padding-left: 1.5em;
      font-size: 1.4em;
      height: 18px;
    }

    .sortable li span {
      position: absolute;
      margin-left: -1.3em;
    }

    .card {
      background-color: white;
      border-radius: 3px;
    }

    .head-lt {
      color: #fcfcf4;
    }

    .head-dk {
      color: #37474F;
    }
  </style>
  <script>
    $(function () {
      getListData();
      var id;
      $(".sortable").sortable({
        connectWith: ".connectedSortable",
        receive: function (event, ui) {
          console.log($(ui.item[0]).parent().prev()[0].innerText)
          $(this).css({ "background-color": "#fcfcff4" });
          UpdateItem(id,$(ui.item[0]).parent().prev()[0].innerText);
        },

        update: function (event, ui) {
          //id = $($(this).children()[0]).attr("idvalue")
          id = ui.item.attr('idvalue');
          
          //console.log($($(this).children()[0]).closest("h2").val())
        }
      }).disableSelection();
      $('.add-button').click(function () {
        CreateListItemWithDetails();
        var txtNewItem = $('#new_text').val();
        $(this).closest('div.container').find('ul').append('<li class="card">' + txtNewItem + '</li>');
        location.reload();
      });
    });
    function getListData() {

      var fullUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getByTitle('test5')/items?$selet=Title,Status,ID";
      console.log(fullUrl);
      $.ajax({
        url: fullUrl,
        type: "GET",
        headers: {
          "accept": "application/json;odata=verbose",
          "content-type": "application/json;odata=verbose",
        },
        success: onQuerySucceeded,
        error: onQueryFailed
      });
    }

    function onQuerySucceeded(data) {

      $.each(data.d.results, function (key, value) {
        if (value.Status == "Not started") {
          var listItemInfo = "<li class='card' IDvalue=" + value.ID + ">" + value.Title + "</li>";
          $("#NotStarted").append(listItemInfo)
        }
        if (value.Status == "In-progress") {
          var listItemInfo = "<li class='card' IDvalue=" + value.ID + ">" + value.Title + "</li>";
          $("#InProgress").append(listItemInfo)
        }
        if (value.Status == "Final stage") {
          var listItemInfo = "<li class='card' IDvalue=" + value.ID + ">" + value.Title + "</li>";
          $("#FinalStage").append(listItemInfo)
        }
        if (value.Status == "Completed") {
          var listItemInfo = "<li class='card' IDvalue=" + value.ID + ">" + value.Title + "</li>";
          $("#Completed").append(listItemInfo)
        }


      });

    }
    function onQueryFailed() {
      alert('Error!');
    }
    function CreateListItemWithDetails() {
      var itemType = GetItemTypeForListName("test5");
      var item = {
        "__metadata": { "type": itemType },
        "Title": $('#new_text').val(),
        "Status": "Not started"

      };

      $.ajax({
        url: _spPageContextInfo.siteAbsoluteUrl + "/_api/web/lists/getbytitle('test5')/items",
        type: "POST",
        contentType: "application/json;odata=verbose",
        data: JSON.stringify(item),
        headers: {
          "Accept": "application/json;odata=verbose",
          "X-RequestDigest": $("#__REQUESTDIGEST").val()
        },
        success: function (data) {
          console.log(data);
        },
        error: function (data) {
          console.log(data);
        }
      });
    }

    // Get List Item Type metadata
    function GetItemTypeForListName(name) {
      return "SP.Data." + name.charAt(0).toUpperCase() + name.split(" ").join("").slice(1) + "ListItem";
    }

    function UpdateItem(id,newStatus) {
      var itemType = GetItemTypeForListName("test5");
      var item = {
        "__metadata": { "type": itemType },        
        "Status": newStatus
      };
      $.ajax({
        url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/GetByTitle('test5')/items(" + id + ")",
        type: "POST",
        headers: {
          "accept": "application/json;odata=verbose",
          "X-RequestDigest": $("#__REQUESTDIGEST").val(),
          "content-Type": "application/json;odata=verbose",
          "IF-MATCH": "*",
          "X-HTTP-Method": "MERGE"
        },
        data: JSON.stringify(item),
        /*where Title is column name and add your desired new data*/
        success: function (data) {
          console.log(data);
        },
        error: function (error) {
          alert(JSON.stringify(error));
        }
      });
    }
  </script>
</head>

<body>
  <div>
    <div class="container" style="background-color:#EF4931;">
      <h2 class="head-lt">Not started</h2>
      <ul class="sortable connectedSortable" id="NotStarted">
      </ul>
      <div class="link-div">
        <input type="text" id="new_text" value="" />
        <input type="button" name="btnAddNew" value="Add" class="add-button" />
      </div>
    </div>
    <div class="container" style="background-color:#ffd300;">
      <h2 class="head-dk">In-progress</h2>
      <ul class="sortable connectedSortable" id="InProgress">

      </ul>
    </div>
    <div class="container" style="background-color:#103c6d;">
      <h2 class="head-lt">Final stage</h2>
      <ul class="sortable connectedSortable" id="FinalStage">

      </ul>
    </div>
    <div class="container" style="background-color:#B9CEC5;">
      <h2 class="head-dk">Completed</h2>
      <ul class="sortable connectedSortable" id="Completed">
      </ul>
    </div>
  </div>

  <script type="text/javascript">
    function storageAvailable(type) {
      var storage;
      try {
        storage = window[type];
        var x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
      }
      catch (e) {
        return e instanceof DOMException && (
          // everything except Firefox
          e.code === 22 ||
          // Firefox
          e.code === 1014 ||
          // test name field too, because code might not be present
          // everything except Firefox
          e.name === 'QuotaExceededError' ||
          // Firefox
          e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
          // acknowledge QuotaExceededError only if there's something already stored
          (storage && storage.length !== 0);
      }
    }
  </script>


</body>

</html>

Test result: enter image description here Store list:

enter image description here

4
  • So you basically created a list called "test5" and every time a change is made in this kanban thing, it creates or updates items in the test5 list? And you created a multi-choice column NewStatus (display name 'Status'). The Title column is the name of the Activity
    – TheJDScott
    Dec 4, 2020 at 15:33
  • Generally speaking, yes, but Status is a single-choice column, and it is ok to change to a multiple-choice column, the code may need to be slightly modified.
    – Amos
    Dec 5, 2020 at 4:08
  • Do yo have any update on this issue?
    – Amos
    Dec 8, 2020 at 5:44
  • Thank you for the help!
    – TheJDScott
    Dec 8, 2020 at 19:26
2

What you did so far is the implementation of the user interface to display data in kanban based on data in the list. The way all the current kanban products works are that as soon as you move an activity from one card to another or change the order of activity...it will emit an event that will tell you what the activity was performed by the user.

You have to write event handlersr of this movement of activities among cards and then make REST API call to update data back to SharePoint with new values, in your case you will have to change the status of that item/activity to where it was moved... So once user does any activity data is udpated to List and on-page refresh it will make a fresh call and load change

Reference link for Calling SharePoint REST API - https://docs.microsoft.com/en-us/sharepoint/dev/sp-add-ins/working-with-lists-and-list-items-with-rest

Hope this helps..!

1
  • Thank you. This is a little over my head, but the comment below created a test example, which I am trying out now. They suggested the same thing you did. So thanks!
    – TheJDScott
    Dec 4, 2020 at 15:34

Your Answer

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

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