0

I'm attempting to open an AngularJS modal and pass in as a parameter HTML form controls. So far I've managed to learn how to pass in and receive back values so long as they are tied to hard coded controls in the modal.

The problem seems to be getting the soft coded (form controls that I'm passing as parameters) to get into the same scope as the rest of the scope of the modal. I have poked around looking for an isolated scope but not even sure if this is going in the right direction. Someplace, I read this has to do with transclusion... whatever that is?

Any help would be greatly appreciated.

Here is a plunker to the code shown below.

http://plnkr.co/edit/gg8IJeC62Im0evqmyN0U?p=preview

In my sample, I expect to be able to enter a seed value, then click on the open modal button and see the seed value appear in the soft coded and the hard coded input text boxes. The soft coded text box does not seem to like me very much...:-(

My HTML:

<!DOCTYPE html>
<html ng-app='myApp'>
<head>
    <link data-require="bootstrap-css@*" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
    <script data-require="jquery@>=1.9.1 <3" data-semver="2.1.4" src="https://code.jquery.com/jquery-2.1.4.js"></script>
    <script data-require="bootstrap@*" data-semver="3.3.6" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.js"></script>
    <script data-require="angular.js@1.4.8" data-semver="1.4.8" src="https://code.angularjs.org/1.4.8/angular.js"></script>
    <script data-require="angular-sanitize@*" data-semver="1.4.3" src="https://code.angularjs.org/1.4.3/angular-sanitize.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.min.js"></script>
    <!--link rel="stylesheet" href="style.css" /-->
    <script src="script.js"></script>
</head>
<body>
    <div ng-controller="MainController as MainController">
        <h1>How to pass values and code to Modal and maintain Scope?</h1>
        <div>Enter Seed Value below, then click open model and change the value.</div>
        <br /> Seed Value:
        <input type="text" ng-model="MainController.mySeedValue" />
        <button ng-click="MainController.OpenModal()">OpenModal</button>
        <br />
        <br />
        <div>Assuming the seed value was change while the modal was open , the revised value should be refelected below.</div>
        <br /> Result: {{MainController.myModalResults.mySeedValue}}
    </div>
</body>
</html

My Modal Template:

<div class="modal-header">
    <button class="close" data-dismiss="modal" aria-hidden="true" ng-click="cancel()">&times;</button>
    <span>{{params.header}} Report Configuration</span>
</div>
<div class="modal-body">
    <div class="row">
        params.body: {{ params.body }}
        <br />
        params.mySeedValue: {{params.mySeedValue}}
        <br />
        Soft Coded: <div ng-bind-html="params.body | unsafe"></div>
        <br />
        Hard Coded: <input type="text" ng-model="params.mySeedValue" />
        <br />
    </div>
    <div class="row">
        <button class="btn" ng-click="cancel()">Cancel</button>
        <button class="btn btn-primary" ng-click="ok(params)">Save</button>
    </div>
</div>

My JavaScript File:

var app = angular.module('myApp', ['ui.bootstrap', 'ngSanitize']);

app.controller('MainController', ['$scope', '$uibModal', '$sce', function ($scope, $uibModal, $sce) {
    _this = this;
    this.OpenModal = function () {

        var scope = $scope.$new(true);
        scope.params = {
            'header': 'TestHeader',
            'body': '<input type="text" ng-model="params.mySeedValue" />',
            'mySeedValue': _this.mySeedValue
        };

        var modalInstance = $uibModal.open({
            scope: scope,
            templateUrl: 'myModal.html',
            controller: myModalController,
            backdrop: 'static'
        });


        modalInstance.result.then(function (modalResults) {
            _this.myModalResults = modalResults
            //on ok button press
            console.log("Modal Saved");
        }, function () {
            //on cancel button press
            console.log("Modal Closed");
        });
    };
}]);

var myModalController = function ($scope, $uibModalInstance) {

    $scope.ok = function () {
        $uibModalInstance.close($scope.params);
    };

    $scope.cancel = function () {
        $uibModalInstance.dismiss('cancel');
    };
};

// return safe html code
app.filter('unsafe', function ($sce) {
    return function (val) {
        return $sce.trustAsHtml(val);
    };
});

the End

Harvey Mushman
  • 615
  • 1
  • 11
  • 23

2 Answers2

1

For some time, I've been lucky enough to have a tutor helping me to learn AngularJS. Last night I talked him into another session and within an hour he managed to show me the syntax but explained what was going on through the process. Thank you again Olen Davis, mentor, guru and friend!

Here is a fork of the plunker http://plnkr.co/edit/CevME8WBdZx7mqfoNfsj?p=preview

Essentially, he explained that what I am doing is not recommended for security reasons, there is a chance of script injection from hackers if I'm not very careful how the HTML is handled. And as I explained, the features that I will be loading in HTML strings are part of a selection process for a report writer and all the HTML will come from the server database in a one way path. Not much chance to post anything invalid back.

The answer as shown below was to compile and link the HTML into the MainController scope. A new directive we called "bind-compiled-html" was required to replace the AngularJS ng-bind-html directive. I don't understand all the details and syntax that he showed me but it now works and I can now start researching all parameter passing that occurs for the $compile to work.

Some key points that I hope I recall correctly include: the equals symbol when I'm defining scope of the directive 'bindCompiledHtml' is creating an isolated scope. We create a childScope and to prevent errors on a second request to the directive we added the if(childScope) exists destroy it before creating another new one... something I think he said about a possible memory leak. The if(bindCompiledHtml) ensures I don't generate an error if no string is passed to the $compile. Finely, in the link function we compile the passed HTML and add it to the DOM at the MainController scope ...$scope.$parent.$new().

In the MainController we removed the parameter from $new(true) which was creating an isolated scope. And redefined my seed value as an object to allow me to easily pass more parameters within the MainController scope.

On the modal template the seed value(s) is now called out by referencing the MainController dot key notation.

Here is the code:

Script.js

var app = angular.module('myApp', ['ui.bootstrap', 'ngSanitize']);


// *** data driven injection ***
app.directive('bindCompiledHtml', function ($compile) {
    // return directive defination object
    return {
        scope: {
            bindCompiledHtml: '='
        },
        link: function ($scope, $element) {
            var childScope;
            $scope.$watch('bindCompiledHtml', function (bindCompiledHtml) {
                if (childScope) {
                    childScope.$destroy();
                }
                if (bindCompiledHtml) {
                    var linkFunc = $compile(bindCompiledHtml);
                    childScope = $scope.$parent.$new();
                    linkFunc(childScope, function (compElement) {
                        $element.html('');
                        $element.append(compElement);
                    });
                }
            })
        }
    };
});

app.controller('MainController', ['$scope', '$uibModal', '$sce', function ($scope, $uibModal, $sce) {
    _this = this;
    _this.mySeedValue = { 'key': 'test' };

    this.OpenModal = function () {

        var scope = $scope.$new();
        scope.params = {
            'header': 'TestHeader',
            'body': '<input type="text" ng-model="MainController.mySeedValue.key" />',
        };

        var modalInstance = $uibModal.open({
            scope: scope,
            templateUrl: 'myModal.html',
            controller: myModalController,
            backdrop: 'static'
        });


        modalInstance.result.then(function () {
            //on ok button press
            console.log("Modal Saved");
        }, function () {
            //on cancel button press
            console.log("Modal Closed");
        });
    };
}]);

var myModalController = function ($scope, $uibModalInstance) {

    $scope.ok = function () {
        $uibModalInstance.close();
    };

    $scope.cancel = function () {
        $uibModalInstance.dismiss('cancel');
    };
};

...and the main HTML page

<!DOCTYPE html>
<html ng-app='myApp'>
<head>
    <link data-require="bootstrap-css@*" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
    <script data-require="jquery@>=1.9.1 <3" data-semver="2.1.4" src="https://code.jquery.com/jquery-2.1.4.js"></script>
    <script data-require="bootstrap@*" data-semver="3.3.6" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.js"></script>
    <script data-require="angular.js@1.4.8" data-semver="1.4.8" src="https://code.angularjs.org/1.4.8/angular.js"></script>
    <script data-require="angular-sanitize@*" data-semver="1.4.3" src="https://code.angularjs.org/1.4.3/angular-sanitize.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.min.js"></script>
    <!--link rel="stylesheet" href="style.css" /-->
    <script src="script.js"></script>
</head>
<body>
    <div ng-controller="MainController as MainController">
        <h1>How to pass values and code to Modal and maintain Scope?</h1>
        <div>Enter Seed Value below, then click open model and change the value.</div>
        <br /> Seed Value:
        <input type="text" ng-model="MainController.mySeedValue.key" />
        <button ng-click="MainController.OpenModal()">OpenModal</button>
        <br />
        <br />
        <div>Assuming the seed value was change while the modal was open , the revised value should be refelected below.</div>
        <br /> Result: {{MainController.mySeedValue.key}}
    </div>
</body>
</html>

...and the revised Modal Template page:

<div class="modal-header">
    <button class="close" data-dismiss="modal" aria-hidden="true" ng-click="cancel()">&times;</button>
    <span>{{params.header}} Report Configuration</span>
</div>
<div class="modal-body">
    <div class="row">
        params.body: {{ params.body }}
        <br />
        MainController.mySeedValue.key: {{MainController.mySeedValue.key}}
        <br />
        Soft Coded: <div bind-compiled-html="params.body"></div>
        <br />
        Hard Coded: <input type="text" ng-model="MainController.mySeedValue.key" />
        <br />
    </div>
    <div class="row">
        <button class="btn" ng-click="cancel()">Cancel</button>
        <button class="btn btn-primary" ng-click="ok()">Save</button>
    </div>
</div>

Any feedback is always welcome. (...and hope I only get positive responses,... it would be nice to someday score high enough to comment on other peoples postings :-) .)

Harvey Mushman
  • 615
  • 1
  • 11
  • 23
0

I noticed at least 2 issues.

Problem:

  • you are mixing two approaches to working with controllers, $scope and this.
  • controller is missing for modal template.

Solution:

  • remove "$scope.model" from controllers, that's what angular recommends how you should do it, in future in Angular 2, there will be no $scope.

Angular recommendation: https://docs.angularjs.org/api/ng/directive/ngController
Angular2 $scope omission: http://paislee.io/migrating-to-angularjs-2-0/

  • define "controller as: someAlias" in modal instance. Use this alias in your modal template like someAlias.param, this way you will have complete data binding with the modal.

I have not checked and not written the complete code here, this is just to give you the basic idea to apply to your case.

JavaScript File:

app.controller('MainController', ['$scope', '$uibModal', '$sce', function ($scope, $uibModal, $sce){

    var vm = this;  // "this" here has the scope of "MainController", main html.

        var modalInstance = $uibModal.open({
        templateUrl: 'myModal.html',
        controller: myModalController,
        controllerAs: 'modalcontrollerAlias',
        backdrop: 'static'

        // if you wanna send data from here to the modal's controller, use "resolve" here, and include it in "myModalController", app.controller('myModalController', ['$scope', 'someName', '$uibModalInstance', function ($scope ,someName, uibModalInstance)

//        resolve: {
//            someName: function () {
//                return vm.mySeedValue;
//            }
//        }
    });
}]);

app.controller('myModalController', ['$scope', '$uibModalInstance', function ($scope, uibModalInstance) {

    var vm = this;  // "this" here has the scope of "myModalController", modal template.

        vm.ok = function () {
            $uibModalInstance.close(vm.params);
        };

        vm.cancel = function () {
            $uibModalInstance.dismiss('cancel');
        };
}]);

Modal Template:

<div class="modal-header">
    <button class="close" data-dismiss="modal" aria-hidden="true" ng-click="cancel()">&times;</button>
    <span>{{modalcontrollerAlias.params.header}} Report Configuration</span>
</div>
<div class="modal-body">
    <div class="row">
        params.body: {{ modalcontrollerAlias.params.body }}
        <br />
        params.mySeedValue: {{modalcontrollerAlias.params.mySeedValue}}
        <br />
        Soft Coded: <div ng-bind-html="modalcontrollerAlias.params.body | unsafe"></div>
        <br />
        Hard Coded: <input type="text" ng-model="modalcontrollerAlias.params.mySeedValue" />
        <br />
    </div>
    <div class="row">
        <button class="btn" ng-click="modalcontrollerAlias.cancel()">Cancel</button>
        <button class="btn btn-primary" ng-click="modalcontrollerAlias.ok(modalcontrollerAlias.params)">Save</button>
    </div>
</div>
Faizan Shakeel
  • 171
  • 1
  • 16
  • Thank you for offering your suggestions. I do not seem to be able to get your method to work. A complete working sample including passing the modal an html string and having that string be an input control within an exposited scope (named or otherwise) would be very helpful. I posted a solution and included a working Plunker sample of what I'm trying to achieve. Again thank you for offering your help. – Harvey Mushman Dec 05 '15 at 17:01