MuddyMudSkipper
MuddyMudSkipper

Reputation: 79

How do I make popovers work from JSON content? (Angular, AngularStrap, JSON)

I can't for the life of me figure out how to get angular strap popovers to work in returned JSON content (data attributes are in JSON file).

Plunkr: http://plnkr.co/edit/jVmHwIwJ0KOKCnX6QjVa?p=preview

Any insight would be greatly appreciated. thanks a bunch!!

HTML

<!-- Search -->
<div class="well"> 
  <p>Search the term "content"</p>
  <form role="form">
    <div my-search ng-model="selectedContent" class="form-group clearfix search">
      <input type="text" ng-model="selectedContent" ng-options="query as query.searchQuery for query in searchData" bs-typeahead="bs-typeahead" class="form-control search-field"/>
      <button type="button" class="btn btn-primary search-btn" ng-click="updateModel()"><span class="glyphicon glyphicon-search"></span></button>
    </div>
  </form>
</div>

<!-- Dynamic Content -->
<div class="well">
  <h4>{{clickedContent.contentTitle}}</h4>
  <ul>
    <li ng-repeat="item in clickedContent.headlines" ng-bind-html="item.headline"></li>
  </ul>
</div>

JSON

      [
        {
            "contentId": 1,
            "searchQuery": "Content set 1 dummy query vestibulum abcdefghijklmnop",
            "contentTitle": "Pretaining to content set 1",
            "popoverTitle": "Query info",
            "popoverContent": "Interesting info about query",
            "headlines": [
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>1st headline in content set 1</a>"
                },
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>2nd headline in content set 1</a>"
                },
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>3rd headline in content set 1</a>"
                }
            ]

        },
        {
            "contentId": 2,
            "searchQuery": "Content set 2 dummy query vestibulum abcdefghijklmnop",
            "contentTitle": "Pretaining to content set 2",
            "popoverTitle": "Query info",
            "popoverContent": "Interesting info about query",
            "headlines": [
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>1st headline in content set 2</a>"
                },
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>2nd headline in content set 2<a/>"
                },
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>3rd headline in content set 2</a>"
                }
            ]
        },
        {
            "contentId": 3,
            "searchQuery": "Content set 3 dummy query vestibulum abcdefghijklmnop",
            "contentTitle": "Pretaining to content set 3",
            "popoverTitle": "Query info",
            "popoverContent": "Interesting info about query",
            "headlines": [
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>1st headline in content set 3</a>"
                },
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>2nd headline in content set 3</a>"
                },
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>3rd headline in content set 3</a>"
                }
            ]
        },
            {
            "contentId": 4,
            "searchQuery": "Content set 4 dummy query vestibulum abcdefghijklmnop",
            "contentTitle": "Pretaining to content set 4",
            "popoverTitle": "Query info",
            "popoverContent": "Interesting info about query",
            "headlines": [
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>1st headline in content set 4</a>"
                },
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>2nd headline in content set 4</a>"
                },
                {
                  "headline": "<a href='#' data-placement='bottom', data-trigger='hover' data-title='Headline details' data-content='details about headline' bs-popover>3rd headline in content set 4</a>"
                }
            ]
        }
    ]

JS

            var app = angular.module('demoApp', ['ngAnimate', 'ngSanitize', 'mgcrea.ngStrap'])
            .config(function ($typeaheadProvider) {
              angular.extend($typeaheadProvider.defaults, {
                template: 'ngstrapTypeahead.html',
                container: 'body'
              }); 
            });

            app.directive('mySearch', function(){
              return {
                restrict: 'A',
                require: 'ngModel',
                link: function($scope, $element, $attrs, ngModel){
                  ngModel.$render = function(){
                     if (angular.isObject($scope.selectedContent)) {
                       $scope.clickedContent = $scope.selectedContent;
                     }
                  }
                   $scope.updateModel = function() {
                     $scope.clickedContent = $scope.selectedContent;
                  }
                }
              }

            })

            function MainController($scope, $templateCache, $http) {

              $scope.selectedContent = '';

              $http.get('searchData.json').then(function(response){
                $scope.searchData = response.data;
                return $scope.searchData;
              });

            };

Upvotes: 1

Views: 1278

Answers (2)

Zuriel
Zuriel

Reputation: 1848

I think this is what you are want. if so, You are welcome :)

Working Example

basically 2 things I did. I don't like to send data to a function for unsafe-html, a better way is to make a filter and pipe date through that.. much nicer as you can do it inline without sending them to a function. I called this filter "unsafe" its in your app.js

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

Second.. like someone menitioned above in comments, just because you can "render" it doesn't mean that its been compiled.. so you have to run a compile directive to reprocess the rendered HTML and turn that into code we care about. I called that directive "compile-template"

app.directive('compileTemplate', function($compile, $parse){
 return {
link: function(scope, element, attr){
  var parsed = $parse(attr.ngBindHtml);
  function getStringValue() { return (parsed(scope) || '').toString(); }

        //Recompile if the template changes
        scope.$watch(getStringValue, function() {
            $compile(element, null, -9999)(scope);  //The -9999 makes it skip directives so that we do not recompile ourselves
          });
      }         
    }
  });

and you would write your "dynamic content" like this:

    <!-- Dynamic Content -->
<div class="well">
  <h4>{{clickedContent.contentTitle}}</h4>
  <ul>
    <li ng-repeat="item in clickedContent.headlines" ng-bind-html="item.headline | unsafe" compile-template></li>
  </ul>
</div>

now you can see the example works.

please give rep if this helped! thanks!!!

Upvotes: 1

Kiruse
Kiruse

Reputation: 1743

The problem you're facing is that AngularJS, for the sake of your clients' security, automatically sanitizes HTML input in the ng-bind-html attribute. It automatically removes any potentially malicious attributes, such as JS events, data prefixed attributes, the id attribute (possibly to prevent conflicts...), and style.

The documentation shows how to bypass this auto-sanitization.

In essence:

  1. Add a method of any name to your $scope in your MainController.
  2. Use $templateCache.trustAsHtml(<html>) to return raw HTML.
  3. Call that function in the ng-bind-html attribute instead to retrieve the raw HTML.

Unfortunately I am not very experienced with AngularJS and couldn't get it to work in your example. (I don't know how to pass a more or less dynamic variable to the function, I had hoped ng-bind-html=\"rawHtml(item.headline)\" would've been good enough, but it wasn't.)

Good luck!

Upvotes: 0

Related Questions