8

We are new to AngularJS but are working on an AngularJS/Web API application that updates a data model from an AngularJS Bootstrap popover/directive.

We've successfully updated the database from the directive/popover, however are having trouble figuring out how to refresh the data on the page with the updated data without reloading the page.

Main Page CSHTML:

<div ng-app="FFPA" ng-controller="myCtrl">
   <div svg-floorplan="dataset"></div>
</div>

Popover HTML:

<div>
   <div>
      ID: {{ person.Id }}<br />
      Name: {{ person.fullName }}<br />
      Current Cube/Office: {{ person.seatId }}
      <br />
      Dept: {{ person.deptId }}
      <br />
      Job Desc: {{ person.jobDesc}}
      <br />
      Phone:{{ person.phone}}
      <br />
      <!--<input type="button" value="Click Me" ng-click="changeName()">-->
   </div>
   <div class="hiddenDiv" ng-hide="toggle">
      <div class="form-group">
         <label for="floor">Floor</label>
         <input id="floor" ng-model="person.floor" type="text" ng-trim="true"  class="form-control" />
      </div>
      <div class="form-group">
         <label for="section">Section</label>
         <input id="section" ng-model="person.section" ng-trim="true" type="text" class="form-control" />
      </div>
      <div class="form-group">
         <label for="offCubeNum">offCubeNum</label>
         <input id="offCubeNum" ng-model="person.offCubeNum" ng-trim="true" type="text" class="form-control" />
      </div>
      <div class="form-group">
         <label for="cbCube">Cubicle?</label>
         <input id="cbCube" ng-model="person.cbCube" type="checkbox" size="1" class="checkbox" />
      </div>
   </div>
   <div ng-hide="buttonToggle">
      <input type="button" value="Move" class="btn btn-success" ng-click="moveEmp()">
      <input type="button" value="Term" class="btn btn-danger" ng-click="changeName()">
   </div>
   <div ng-hide="submitToggle">
      <input type="button" value="Submit" class="btn btn-success" ng-click="submitMove()">
      <input type="button" value="Cancel" class="btn btn-warning" ng-click="cancel()">
   </div>
</div>

The main page initially gets data from a service in the angular controller:

var app = angular.module('FFPA', ['ngAnimate', 'ngSanitize', 'ui.bootstrap', 'ui.router']);
    app.controller('myCtrl', function ($scope, dataService) {
    $scope.test = 'test';
        dataService.getData().then(function (data) {
            //The reduce() method reduces the array to a single value.
            $scope.dataset = data.reduce(function (obj, item) {
                obj[item.seatId.trim()] = item;
                item.fullName = item.fName + ' ' + item.lName;
                item.deptId = item.deptId;
                item.jobDesc = item.jobDesc;
                item.phone = item.phone;

                return obj;

            }, {});
        });
    });

Get Data Service:

    angular.module('FFPA').service('dataService', function ($http) {
    this.getData = function () {
        //web api call
        return $http.get("api/Controller/GetData).then(
          function (response) {
              return response.data;
          }, function () {
              return { err: "could not get data" };
          }
        );
    }
});

The Update Service is called from the Popover Directive.

Update Service:

    angular.module('FFPA').service('updateService', function ($http) {
    this.putData = function (oc) {

        //web api call
        return $http.put("api/Controller/PutUpdateData", oc).then(
          function (response) {

              return response.data;
          }, function () {
              return { err: "could not update data" };
          }
        );
    }
});

Here is a snippet from our Popover directive where the update occurs and where we thought we could refresh the scope, and the data for the page:

updateService.putData(data).then(function (response) {
   if (response == false)
    alert("Move Failed!");
   else {
    alert("Move Succeeded.");
    //$window.location.reload() causes a page reload..not desirable
    //$window.location.reload();
     $state.reload();
}
});

We tried a $state.reload(); in the popover directive just after updateService.putData(data), however this caused -> Error: Cannot transition to abstract state '[object Object]' error.

Here is the full Popover Directive:

angular.module('FFPA').directive('svgFloorplanPopover', ['$compile', 'updateService', 'vacancyService', 'addService', 'terminateService', '$window', '$state', function ($compile, updateService, vacancyService, addService, terminateService, $window, $state) {
return {
    restrict: 'A',
    scope: {
        'person': '=svgFloorplanPopover',
         //UPDATE 8-MAY-2017
         onDataUpdate: '&'
    },
    link: function (scope, element, attrs) {
        scope.moveToggle   = true;               //hide move toggle
        scope.addToggle    = true;                //hide add toggle
        scope.submitToggle = true;             //hide submit toggle


        scope.$watch("person", function () {
            if (scope.person) {
                if (scope.person.vacant == true) {
                    scope.addToggle         = false;  //show add button
                    scope.empInfoToggle     = true;   //hide emp info
                }
                else
                    scope.moveToggle = false; //show move
            }
        });

        //add employee---------------------------------------------------------
        scope.addEmp = function () {
            scope.addToggle = scope.addToggle === false ? true : false;

            scope.buttonToggle = true;
            scope.submitToggle = false;

            var data = {
                deptId: scope.person.deptId,
                divisionId: scope.person.divisionId,
                empId: scope.person.empId,
                floor: scope.person.floor,
                fName: scope.person.fName,
                lName: scope.person.lName,

                jobDesc: scope.person.jobDesc,
                officeCode: scope.person.officeCode,
                phone: scope.person.phone,
                section: scope.person.section,
                seat: scope.person.seat,
                seatId: scope.person.seatId,
                seatTypeId: scope.person.seatTypeId,
                vacant: scope.person.vacant
            };


            //call to update/move the employee 
            //updateService.putData(scope.person).then(function () {
            addService.putData(data).then(function (response) {
                if (response == false)
                    alert("Create Failed!");
                else {
                    alert("Create Succeeded.");
                      //UPDATE 8-MAY-2017
                      $scope.onDataUpdate({ person: $scope.person, moreData: $scope.moreData });
                    //$window.location.reload();
                    //$route.reload();
                    //scope.toggle = false;
                }
            });
        }


        //cancel function---------------------------------------------------------
        scope.cancel = function () {}

        //Term emp---------------------------------------------------------
        scope.termEmp = function () {
            var data = {
                seatId: scope.person.seatId,
                floor: scope.person.floor
            };
            terminateService.putData(data).then(function (response) {
                if (response == false)
                    alert("Term Failed!");
                else {
                    alert("Term Succeeded.");
                    $window.location.reload();
                    //$route.reload();
                    //scope.toggle = false;
                }

            });
        }


        //move employee---------------------------------------------------------
        scope.moveEmp = function () {
            scope.toggle = scope.toggle === false ? true : false;
            scope.buttonToggle = true;
            scope.submitToggle = false;
            if (scope.person && scope.person.fullName.indexOf('changed') === -1) {
                //scope.person.fullName += ' move?';
            }

            //Json object to send to controller to check for vacancy
            var data = {
                floor: scope.person.floor,
                section: scope.person.section,
                seat: scope.person.offCubeNum
            };

            //can't send object via $http.get (?) stringigy json and cast to Office object in controller.
            var json = JSON.stringify(data);

            //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
            //CHECK VACANCY service call
            //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
            vacancyService.getData(json)
                .then(function (response) {
                if (response == false)
                    alert("cube/office occupied");
                else{

                    //+++++++++++++++++++++++++++++++++++++++++++
                    //UPDATE service call
                    //+++++++++++++++++++++++++++++++++++++++++++
                    //CONSTS
                    var CONSTFLOORPREFIX    = "f";
                    var CONSTSEAT           = "s";
                    var CONSTC              = "c"

                    var floor   = scope.person.floor;
                    var section = scope.person.section;

                    var offCube = scope.person.offCubeNum;
                    scope.person.oldSeatId = scope.person.seatId;

                    var newOfficeId = CONSTFLOORPREFIX + floor + CONSTSEAT;          //f3s 

                    //IF CUBE
                    if (scope.person.cbCube) {
                        var trimSection = section.trim();
                        newOfficeId += trimSection + CONSTC;                        //f3s313c
                        var trimOffCube = offCube.trim();
                        newOfficeId += trimOffCube;
                    }
                    else { 
                        newOfficeId += 0 + CONSTC + section;                                  //f3s0c
                    }



                    scope.person.seatId = newOfficeId;

                    //Json object to send to controller to check for vacancy
                    var data = {
                        Id: scope.person.Id,
                        seatId: scope.person.seatId,
                        oldSeatId: scope.person.oldSeatId,
                        empId: scope.person.empId,
                        lName: scope.person.lName,
                        fName: scope.person.fName,
                        refacName: scope.person.refacName,
                        deptId: scope.person.deptId,
                        divisionId: scope.person.divisionId,
                        jobDesc: scope.person.jobDesc,
                        seatTypeId: scope.person.seatTypeId,
                        officeCode: scope.person.officeCode,
                        phone: scope.person.phone,
                        floor: scope.person.floor,
                        section: scope.person.section,
                        seat: scope.person.seat,
                        vacant: scope.person.vacant
                    };


                    //call to update/move the employee 
                    //updateService.putData(scope.person).then(function () {
                    updateService.putData(data).then(function (response) {
                        if (response == false)
                            alert("Move Failed!");
                        else {
                            alert("Move Succeeded.");
                            //$window.location.reload();
                            $state.reload();
                            //$route.reload();
                            //scope.toggle = false;
                        }

                    });
                }//end else
            });
        }

        if (element[0].querySelector('text') != null){
            scope.htmlPopover = './HTML/popoverTemplate.html';
            element[0].setAttribute('uib-popover-template', "htmlPopover");
            element[0].setAttribute('popover-append-to-body', 'true');
            element[0].setAttribute('popover-trigger', "'click'");
            //element[0].setAttribute('popover-trigger', "'mouseenter'");
            element[0].setAttribute('popover-placement', 'right');
            element[0].removeAttribute('svg-floorplan-popover');
            $compile(element)(scope);
        }
    }
}
}]);

UPDATED: 8-MAY-2017 Originally there is one additional data service and a directive that we left out of this post since it may be considered not essential information, however recently added since it may be needed.


SVG Load Directive:

angular.module('FFPA').directive('svgFloorplan', ['$compile', function ($compile) {
return {
    restrict: 'A',  //restrict attributes
    templateUrl: './SVG/HQ3RD-FLOOR3v10.svg',
    scope: {
        'dataset': '=svgFloorplan'
    },
    link: {
        pre: function (scope, element, attrs) {
            //filter groups based on a cube/office id
            var groups = element[0].querySelectorAll("g[id^='f3']");
            //groups.style("pointer-events", "all");
            scope.changeName = function (groupId) {
                if (scope.dataset[groupId] && scope.dataset[groupId].lastName.indexOf('changed') === -1) {
                    scope.dataset[groupId].lastName += ' changed';
                }
            }

            groups.forEach(function (group) {
                var groupId = group.getAttribute('id');
                if (groupId) {
                    //set vacancy colors on vacant cubes
                    scope.$watch("dataset", function () {
                        if (scope.dataset) {
                            if (typeof scope.dataset[groupId] !== "undefined") {

                                //vacant cubes and offices hover
                                if (scope.dataset[groupId].vacant == true) {
                                    //seat type id 1 = cube
                                    if (scope.dataset[groupId].seatTypeId == 1){
                                        d3.select(group).select("rect").style("fill", "#99ff33").style("opacity", 0.4)
                                            .style("pointer-events", "all")
                                            .on('mouseover', function () {
                                             d3.select(this).style('opacity', 0.9);
                                         })
                                        .on('mouseout', function () {
                                            d3.select(this).style('opacity', 0.4);
                                        })
                                    }
                                    //vacant office
                                    else {
                                        d3.select(group).select("path").style("stroke", "#ffffff").style("opacity", 1.0);
                                        d3.select(group).select("path").style("fill", "#99ff33").style("opacity", 0.4)
                                        .style("pointer-events", "all")
                                         .on('mouseover', function () {
                                             d3.select(this).style('opacity', 0.9);
                                         })
                                        .on('mouseout', function () {
                                            d3.select(this).style('opacity', 0.4);
                                        })
                                    }
                                }
                                else {                              //Occupied 
                                    //seat type id 1 = cube
                                    if (scope.dataset[groupId].seatTypeId == 1) {
                                        d3.select(group).select("rect").style("fill", "#30445d").style("opacity", 0.0)
                                         .style("pointer-events", "all")
                                         .on('mouseover', function () {
                                             d3.select(this).style('opacity', 1.0);
                                             d3.select(group).select('text').style("fill", "#FFFFFF");
                                         })
                                        .on('mouseout', function () {
                                            d3.select(this).style('opacity', 0.0);
                                            d3.select(group).select('text').style("fill", "#000000");
                                        })

                                        //TODO: cubes have rects and on the north side of the building wall, paths.
                                        d3.select(group).select("path").style("fill", "#30445d").style("opacity", 0.0)
                                            .style("pointer-events", "all")
                                            .on('mouseover', function () {
                                                d3.select(this).style('opacity', 1.0);
                                                d3.select(group).select('text').style("fill", "#FFFFFF");
                                            })
                                        .on('mouseout', function () {
                                            d3.select(this).style('opacity', 0.0);
                                            d3.select(group).select('text').style("fill", "#000000");
                                        })
                                    }
                                    //occupied office
                                    else {
                                        //d3.select(group).select("path").style("stroke", "#ffffff").style("opacity", 0.8);
                                        d3.select(group).select("path").style("fill", "#5A8CC9").style("opacity", 1.0)
                                         .style("pointer-events", "all")
                                         .on('mouseover', function () {
                                             //alert("office");
                                             d3.select(this).style("fill", "#2d4768").style('opacity', 1.0);
                                             d3.select(group).selectAll('text').style("fill", "#FFFFFF");
                                         })
                                        .on('mouseout', function () {
                                            d3.select(this).style("fill", "#5A8CC9").style('opacity', 1.0);
                                            d3.select(group).selectAll('text').style("fill", "#000000");
                                        })
                                    }
                                }//end occupied else
                            }
                        }
                    });
                     //UPDATE 8-MAY-2017->Implementation Question
                    scope.onDataUpdateInController = function (person, moreData) { };
                    var datasetBinding = "dataset['" + groupId + "']";
                    group.setAttribute('svg-floorplan-popover', datasetBinding);

                    //UPDATE 8-MAY-2017
                    //on-data-update corresponds to onDataUpdate item on svgFloorplanPopover's scope.
                    group.setAttribute('on-data-update', onDataUpdateInController);


                    $compile(group)(scope);
                }
            });
        }


    }
}
}]);

Vacancy Service (check before update):

angular.module('FFPA').service('vacancyService', function ($http) {
...
}

The main question is:

How can we have our application refresh our page with the updated data without reloading the page?

We used to be able to do this in UpdatePanels in ASP.Net webforms back in the day. I think they were partial postbacks/AJAX calls..

EDITED 2-AUG-2017

+++++++++++++++++++++++++++++++++++

Even though the bounty was automatically awarded, we still don't have an answer to this question. Without any implementation context the answers given are not useful.

Can anyone expand on the answers given to give us an idea on how this problem can be solved?

Thanks

Tikhon
  • 947
  • 2
  • 18
  • 32
  • Do you need to refresh the view or just update the bounded data to be the returned ajax response? – OB Kenobi Apr 18 '17 at 20:23
  • I think just a refresh of the updated bound data will work. – Tikhon Apr 18 '17 at 20:28
  • Can you add a working fiddle or plnkr? – OB Kenobi Apr 18 '17 at 20:30
  • Sure. We have a working plunk, but I'll need to add an update directive to it. http://plnkr.co/edit/WRh1uESNUCl9swWEiSg5?p=info – Tikhon Apr 18 '17 at 20:36
  • Please update and let me know, I think I got several solutions for you. – OB Kenobi Apr 18 '17 at 20:40
  • Thanks. Hopefully get it done tomorrow.. – Tikhon Apr 18 '17 at 21:44
  • I don't think it's possible to use Angular/Javascript to update files directly in a filesystem as we do in our WebApi. Unless I'm wrong about this, I'm not sure I'll be able to update our plunk to simulate what's happening in our code. – Tikhon Apr 19 '17 at 17:17
  • 1
    Ok, did you try my answer? You can also try and bind the user init function on your parent controller to your directive and call it with the response for the update. – OB Kenobi Apr 20 '17 at 12:29
  • I was able to get this to work: $window.location.reload(); which was addressed in one of the links provided, however it seems to post the page back, where we were hoping to refresh the page without a postback. There was another solution where $state was injected into a controller. We tried injecting $state into our directive to do $state.reload, however received an error: angular.js:14362 Error: [$injector:unpr] Unknown provider: $stateProvider <- $state <- svgFloorplanPopoverDirective. I thought that we may have forgotten an include directive for ui-router. Thanks again for your help – Tikhon Apr 20 '17 at 12:38
  • Are you using the ui-router module? – OB Kenobi Apr 23 '17 at 06:21
  • We are not currently using the ui-router module...thanks – Tikhon Apr 23 '17 at 20:06

3 Answers3

3

Just add your data on $scope object and use it in your view, whenever you update or modify the data just eg: consider you have a function to get the data where you are making rest call to your db

$scope.getdata=function(){
$http.get(url).success(function(data)
{   $scope.data=data;
});

Whenever you modify your data just call this function in your case on click of close of directive/popup

rakesh
  • 4,368
  • 1
  • 19
  • 13
  • Can you give an example of how to implement this solution given our current implementation, say for scope.moveEmp? – Tikhon May 10 '17 at 12:56
  • $scope cannot be injected into a directive as far as I know. – Tikhon May 12 '17 at 20:18
  • This answer without any context in regards to the current application implementation is not useful. – Tikhon May 15 '17 at 18:57
  • As far as I understood what you have done is fine, since you are referring `person` from `dataset` all the changes done on `person` will be reflected into `$scope.dataset`. But I think in your directive you have pass the attribute as `svg-floorplan="dataset.seatId"`, where `seatId` is id of item in your `scope.dataset`. Otherwise your updates are going into `dataset` rather than `dataset['seatid']` obj. which is your actual data. – rakesh May 15 '17 at 20:50
1

To refresh your view (not bind the received data) use the answers for the following questions:

Using ngRoute Module How to reload or re-render the entire page using AngularJS

Using ui-router Module Reloading current state - refresh data

With that I would recommend you to assign the received data to your bounded $scope property.

I'll add a full example after you'll provide an updated plnkr :)

Community
  • 1
  • 1
OB Kenobi
  • 305
  • 2
  • 12
  • Finally got back on this and installed Angular UI-Router. Also implemented the following: var app = angular.module('FFPA', ['ngAnimate', 'ngSanitize', 'ui.bootstrap', 'ui.router']); Also injected $state into the directive that updates. After the update, added this line: $state.reload(); Receive this error: angular.js:14362 Error: Cannot transition to abstract state '[object Object]' – Tikhon May 01 '17 at 18:03
1

Please try the following steps:

1. Create a method in svgFloorplanPopover directive and call it by passing in the data

In your svgFloorplanPopover directive, add onDataUpdate item in the scope declaration:

...
scope: {
  'person': '=svgFloorplanPopover',
  onDataUpdate: '&'
}
...

and where you are trying to reload state, instead of reloading the state or page, call the below code. This is to create an event system which is fired from within the directive to let the controller or parent directive know that data has changed and view can now be updated.

$scope.onDataUpdate({person: $scope.person, moreData: $scope.moreData});

2. Create a method in svgFloorplan to accept the passed data

Since you are using nested directive approach, you'll need to use the below code in svgFloorplan directive.

group.setAttribute('svg-floorplan-popover', datasetBinding);    
group.setAttribute('on-data-update', onDataUpdateInController);

on-data-update corresponds to onDataUpdate item on svgFloorplanPopover's scope.

Declare onDataUpdateInController method on the scope of svgFloorplan directive.

scope.onDataUpdateInController = function(person, moreData) {};    

The object properties that you pass from within the directive are laid out flat to the number of parameters.

If you need to pass this data further up to your controller where svgFloorplan is declared. Repeat the above two steps for svgFloorplan directive.

I hope this approach is clear. It is no different than what is explained in Angular Directives, section Creating a Directive that Wraps Other Elements and code where a close button is added. Here is the direct link to the code in plunkr.

Just a question: Are you going to use these directives separately from each other? If no, you may try to create one directive instead of making them two. This will reduce complexity.

Ritesh Jagga
  • 1,387
  • 1
  • 11
  • 23
  • 1. Updated the code in the question to reflect updates suggested and also included the SVGFloorPlan directive code, however have a question regarding the implementation of: scope.onDataUpdateInController = function (person, moreData) { }; 2. Received error after code update: angular.js:14362 ReferenceError: onDataUpdateInController is not defined 3. I think the plunk you provided is empty. Thanks for your help! – Tikhon May 08 '17 at 14:52
  • Finally got back to this Ritesh. Not sure I understand how to "Declare onDataUpdateInController method on the scope of svgFloorplan directive." – Tikhon Aug 01 '17 at 19:27
  • Even though the bounty was awarded automatically, I don't feel like any of the answers have actually answered my original question and since my experience level using Angular is minimal and the complexity of this application is a bit overwhelming, without clear instructions on how these additions are implemented, I am having great difficulty in getting your suggestions to work. Thanks for any help you can provide. – Tikhon Aug 01 '17 at 21:02