1

I have an app where I need to create html and add it to an object array. This html should then be outputted into page. Simple enough. However, I also need to make the html responsive to user actions (click) in which case angular requires me to use $compile to create an angularized template.

See the Plunkr. In this example, what should happen is that when you click on one of the buttons, a popup is generated with the html code embedded in the object, which you can see in the JSON output.

As soon as I do this, I get the error Converting circular structure to JSON. If I don't, the ng-click="Go()" is not called.

SCRIPT

        var template = "<ul class='unstyled'>" +
                "<li ng-click='go()' style='background-color:lightcyan;'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
                "<li ng-click='go()'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
                "<li ng-click='go()' style='background-color:lightcyan;'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
                "</ul>";

       // template = $compile(template)($scope);

        $scope.data = [
            {"id": 1, "html": template},
            {"id": 2, "html": template}
        ];

        $scope.go = function () {
            alert('It works');
        };

        $scope.openPopin = function (html) {
            var e = window.event;
            var popin = document.getElementById('popin');
            var innerdiv = document.getElementById('innerdiv').innerHTML=html;
            popin.style.top= e.pageY - 20+"px";
            popin.style.left = e.pageX - 20+"px";
            popin.style.marginLeft = -500+"px";
            popin.style.marginTop = -100+"px";
        };

        $scope.closePopin = function () {
            var popin = document.getElementById('popin');
            popin.style.top = -500+"px";
            popin.style.left = -500+"px";
        };

HTML

  <div class="popin grey-border" id="popin">
      <button class="close" ng-click="closePopin()">&times;</button>
      <div id="innerdiv"></div>
  </div>

  <pre>{{ data |json }} </pre>

  <br/>
  <table style="float: right;">
      <tr ng-repeat="d in data" id="{{$index}}">
          <td>{{ d.id }} -
              <button class="btn btn-mini btn-info" ng-click="openPopin(d.html)"><i class="icon-info-sign"></i></button>
          </td>
      </tr>
  </table>
1
  • If I try the plunker I get a different error: Error: e is undefined. (Using FF 26) Probably a cross-browser compatibility problem. Commented Dec 19, 2013 at 18:53

2 Answers 2

2

I got it to work (for me) by moving the compile step to the openPopin function, and replacing the style-property changes with a more angular alternative. And I'm also ignoring the window.event which is not cross-browser compatible (and not part of the issue).

app.controller('MainCtrl', function($scope, $compile) {
    var template = "<ul class='unstyled'>" +
            "<li ng-click='go()' style='background-color:lightcyan;'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
            "<li ng-click='go()'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
            "<li ng-click='go()' style='background-color:lightcyan;'><ul class='inline'><li>1...</li><li>1...</li></ul></li>" +
            "</ul>";

    $scope.data = [
        {"id": 1, "html": template},
        {"id": 2, "html": template}
    ];

    $scope.go = function () {
        console.log("go");
    };

    $scope.openPopin = function (html) {
        var popin = document.getElementById('popin');
        var innerdiv = document.getElementById('innerdiv');
        innerdiv.innerHTML=html;
        $compile(innerdiv)($scope);
        angular.element(popin).css({top:'20px', left:'20px'});
    };

    $scope.closePopin = function () {
        var popin = document.getElementById('popin');
        angular.element(popin).css({top:'-500px', left:'-500px'})
    };
});

So, that's one way to get it working. But the question is, what are you really trying to do, and can't we do it in a more angular way? (Using directives, templates and other tools angular provides.)

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for that - still can't seem to get it working though. I originally built the thing using a directive but had so many problems with it I decided to simplify it a bit. Basically I have a list of competition events delivered from the server. A competitor should enter their personal bests (PB). However, there are some personal bests already in the database, from which they should be able to choose. This data is saved with the event object. So, they click on an event and then click on one of the PBs from the list in the popup. The popup closes and the PB is copied to the event data.
I made a jsfiddle using an alternative approach: jsfiddle.net/W96bb It uses a scope property tied to the popin to determine when to show it, and uses ng-repeat to populate the popin.
That's a nice approach. I've had another go at the directive and added it below. The solution for me was compiling the innerdiv rather than the html directly that you mentioned in your answer, which is what led me to be able to get the directive working. Many thanks for your help. I've added my code below.
0

Thanks towr for your help - see the last comment above

HTML

<script type="text/ng-template" id="cmpbpopin.html">
    <button class="btn btn-mini btn-info"><i class="icon-info-sign"></i></button>
    <div class="popin grey-border">
        <button class="close-button">&times;</button>
        <div></div>
    </div>
</script>

<table style="float: right;">
  <tr ng-repeat="d in data" id="{{$index}}">
    <td>{{ d.id }}</td>
    <td>
        <div cm-pb-popup="d.html"></div>
    </td>
 </tr>
</table>
</body>

SCRIPT

var app = angular.module('app', []);

    app.controller('Ctrl', function ($scope, $compile, $http) {

        var template = "<table class='pblist table table-condensed table-hover'>" +
                "<tr ng-click='go()'><td>1...</td><td>1...</td></tr>" +
                "<tr ng-click='go()'><td>1...</td><td>1...</td></tr>" +
                "<tr ng-click='go()'><td>1...</td><td>1...</td></tr>" +
                "</table>";

        $scope.data = [
            {"id": 1, "html": template},
            {"id": 2, "html": template}
        ];

    });

    app.directive("cmPbPopup", function ($compile, $timeout) {
        return{
            templateUrl: "cmpbpopin.html",
            scope: {
                cmPbPopup: "="
            },
            link: function (scope, elem, attrs) {
                elem.bind("click", function (e) {
                    var popupDiv = elem.find('div');
                    var innerDiv = popupDiv.find('div');
                    var closeButton = popupDiv.find('.close-button')

                    if (e.srcElement.nodeName != 'DIV') {
                        if (e.srcElement.className == 'close-button') {
                            closePopup();
                        } else if(e.srcElement.nodeName == 'TR' || e.srcElement.nodeName == 'TD'){
                            // set values in scope
                            closePopup();
                        }
                        else {
                            innerDiv.html(scope.cmPbPopup);
                            $compile(innerDiv)(scope);
                            popupDiv.css({
                                        'top': e.pageY - e.offsetY + 20,
                                        'left': e.pageX - e.offsetX -10,
                                        'height': 100,
                                        'width': 500,
                                        'marginLeft': -500});
                            $timeout(function (){
                                closeButton.css('display', 'block');
                            },500);
                        }
                    }

                    function closePopup(){
                        popupDiv.css({
                            'height': 0,
                            'width': 0,
                            'marginLeft': 0});
                        $timeout(function (){
                            popupDiv.css({
                                'top': -500,
                                'left': -500
                            });
                        },500);
                    }
                });
            }
        }
    })

CSS

    div.popin {
        position: absolute;
        width: 0;
        height: 0;
        top: -500px;
        left: -500px;
        background-color: #ffffff;
        transition: width 0.5s, height 0.5s, margin-left 0.5s;
        -webkit-transition: width 0.5s, height 0.5s, margin-left 0.5s; /* Safari */
        overflow: hidden;
    }

    div.popin div {
        position: absolute;
        top: 0 !important;
        left: 500px !important;
        width: 470px !important;
        transition: width 0.2s, height 0.2s, margin-left 0.2s;
        -webkit-transition: width 0.2s, height 0.2s, margin-left 0.2s;   
    }

    .close-button{
        width: 20px;
        height: 20px;
        float: right;
        font-size: 20px;
        font-weight: bold;
        line-height: 20px;
        color: #000000;
        text-shadow: 0 1px 0 #ffffff;
        opacity: 0.2;
        filter: alpha(opacity=20);
    }

    .pblist{
        margin-left: 10px !important;
        margin-top: 10px;
        width: 470px;
        float: left;
    }

    .grey-border {
        border: 1px #d3d3d3 solid;
        -webkit-border-radius: 4px;
        -moz-border-radius: 4px;
        border-radius: 4px;
        padding: 3px;
    }

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.