When using AngularJS to compile HTML with $compile and $scope, an error may occur stating: "Unable to convert circular

I have a project that involves creating dynamic HTML and adding it to an object array. This HTML needs to be displayed on the page and be responsive to user interactions, such as clicking. Angular requires me to use $compile to create an angularized template when handling user actions.

Check out the example on Plunkr. When you click on one of the buttons, a popup should appear with the HTML code embedded in the object, visible in the JSON output.

However, I encounter an error "Converting circular structure to JSON" when attempting this implementation. If I skip this step, the ng-click="Go()" function does not get 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>

Answer №1

I managed to achieve success by relocating the compile step to the openPopin function and opting for a more angular-friendly approach when making style property changes. Additionally, I opted to disregard the utilization of window.event, as it is not universally compatible across browsers (and it isn't pertinent to the issue at hand).

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'})
    };
});

This method successfully resolves the issue, but it begs the question: What specific goal are you aiming to accomplish, and could we potentially achieve it in a more angular-centric manner? (By utilizing directives, templates, and other angular tools.)

Answer №2

Appreciate the assistance towr - make sure to check out the last comment above

Below is the provided HTML code snippet:

<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>

Here is the included SCRIPT section:

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);
                    }
                });
            }
        }
    })

Lastly, the CSS styling applied:

    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;
    }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Guide on utilizing getelementsbytagname for retrieving LI values

As I attempt to locate a specific product on amazon.com, my goal is to extract the ASIN for each item. This particular code snippet runs through various search options and retrieves the price of each product from Amazon one by one. However, in addition to ...

Is it possible to apply/render only one scope at a time in AngularJS?

I have a sophisticated application with various components that receive frequent updates. One example is a clock. Is it feasible to trigger $apply / $digest on only a specific section of the page? I don't want every watcher on the page to be invoked ...

input value does not change when the "keyup" event is triggered

I'm encountering an issue with the code below. The code is for a simple To Do app using Javascript. I followed a tutorial step by step, but my app isn't functioning as expected. When I press the enter key, the input value should be added to the l ...

What is the best way to integrate a block of text that is displayed on multiple pages within my application?

On ten different admin pages, I have this recurring block: <div class="displayTable w100p" id="modalTitle"> <div> <div> <span>{{home.modal.title}} {{home.modal.data.number}}</span> < ...

Strategies for maintaining pristine Firebase child paths

I have a list of data that I want to structure in Firebase. However, I encountered an error: Error: Firebase.child failed: The first argument provided is not a valid path. Path names should not include ".", "#", "$", "[", or "]" characters and must be no ...

Differences in behavior across operating systems when pasting content copied from Excel into Next.js

Exploring the Issue I am currently working on a project using Next.js 14 where users can paste data copied from an Excel file into a spreadsheet-like component called react-data-grid. However, I have encountered some inconsistencies when copy-pasting on M ...

Managing various encoding methods when retrieving the XML data feed

I'm attempting to access the feed from the following URL: http://www.chinanews.com/rss/scroll-news.xml using the request module. However, the content I receive appears garbled with characters like ʷ)(й)޹. Upon inspecting the XML, I noticed that ...

Preventing runtime error in Next.js API routes by handling axios exceptions

I am currently working on creating an API route in Next.js that sends a request to my backend and saves the token in the cookies. However, I am facing an issue where 4xx responses are causing an Unhandled runtime error. Despite everything working correctly ...

Collecting values using AJAX in Codeigniter from an input file

I have been exploring examples of AJAX code, and I noticed that when using form-data in AJAX, we need to serialize each element individually. While normal data serialization works fine, I encountered an issue when trying to pass the value of an input file ...

The module 'tcp' is missing in Node.js and cannot be located

The node application crashes unexpectedly at the specified line of code: var tcp = require('tcp'), An error message is displayed: node.js:201 throw e; // process.nextTick error, or 'error' event on first tick ^ Error: C ...

Switch to a different form when clicked using JavaScript

Can you assist me with my code issue? The scenario is as follows: I have two forms - one for registration and one for login. I want to dynamically replace the login form with the register form when the user clicks on the "Sign up" link. Similarly, if the ...

Angular5 causing all divs to have click events at once instead of individually triggered

I am a beginner when it comes to working with Angular and I have encountered an issue. I created a click event on a FAQ page in Angular 5, but the problem is that when I click on one FAQ, they all open up instead of just the targeted one. Here is my TypeS ...

The datetime picker feature in AngularJS ui-grid was malfunctioning

AngularJS project grid requires a datetime picker for filtering. To achieve this, I have integrated the angular-bootstrap-datetimepicker-directive. Now, the filter text box displays the datetime picker. However, changing the date does not trigger the fil ...

Creating an input range element and its event handler dynamically within the ajaxStop function allows for real-time updates based on the

As a beginner in JavaScript development and Ajax, I am currently working on creating a webpage that utilizes Ajax to fetch data from a server. The data is then used to draw geoJSON features on a map using Leaflet. These feature sets need to be toggleable f ...

When using QML, functions like Object.keys, Object.values, and JSON.stringify may return unexpected empty results

I have written a code that exposes a C++ object to QML, and I want to verify the declared properties of the object using native JS methods. However, they are not working as expected. I created a method called FizzBuzzDerived.properties, which functions cor ...

The application suddenly displays a blank white screen after encapsulating my layout with a context provider

Here is the custom layout I created: export const metadata = { title: "Web App", description: "First Project in Next.js", }; export default function CustomLayout({ children }) { return ( <html lang="en"> ...

Turn off the background when the automatic popup box appears

I have implemented a popup box that automatically appears after 5 seconds when the site is loaded. However, if I click outside of the popup box, it closes. I want to disable the background when the popup box is displayed. If I remove the two lines of code ...

The HTTPOnly cookie is not accessible within the getServerSideProps function in Next.js

Why isn't the jid cookie available in getServerSideProps using Next JS? Here's the scenario: https://i.stack.imgur.com/FLRGk.png The jid cookie is generated by an Expressjs API at https://api-dev.example.com. I'm attempting to retrieve thi ...

An effective way to determine the size of a browser through javascript

In an effort to enhance the responsiveness of a website, I have included the following code snippet on one of my pages: document.write(screen.width+'x'+screen.height); However, I am encountering an issue where the code displays my screen resolu ...

What methods can I utilize from Google Maps within Vuex's createStore()?

Currently, I am in the process of configuring my Vuex store to hold an array of Marker objects from the Google Maps Javascript API. Here is a snippet of how my createStore function appears: import { createStore } from "vuex"; export default ...