Как создать всплывающее окно начальной загрузки пользовательского интерфейса AngularJS с содержимым HTML?

Я хочу создать загрузочное всплывающее окно с тегом pre, содержащим предварительно подготовленный объект JSON. Наивная реализация,

<span popover='<pre>{[ some_obj | json:"  " ]}</pre>'
      popover-trigger='mouseenter'>

экранирует содержимое, прежде чем вставить его во всплывающее окно. Каков наилучший способ указать тело всплывающего окна с содержимым html?


person Andrey Fedorov    schedule 23.05.2013    source источник
comment
Моя угловая директива всплывающих окон spector-gadget позволяет вам немного использовать HTML в содержимом всплывающих окон. более безобидно. Может быть лучшим решением, если вы не возражаете против зависимости.   -  person Doug T.    schedule 04.10.2014
comment
@ Андрей Федоров, вы должны принять ответ Мэтью Лотиана вместо текущего.   -  person U r s u s    schedule 21.07.2015


Ответы (8)


ОБНОВЛЕНИЕ:

Как видно из этого, теперь вы сможете сделать это, не переопределяя шаблон по умолчанию.

ОРИГИНАЛ:

Начиная с angular 1.2+ ng-bind-html-unsafe был удален. Вы должны использовать службу $sce Ссылка.

Вот фильтр для создания надежного HTML.

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

Вот перезаписанный шаблон Angular Bootstrap 0.11.2, в котором используется этот фильтр.

// update popover template for binding unsafe html
angular.module("template/popover/popover.html", []).run(["$templateCache", function ($templateCache) {
    $templateCache.put("template/popover/popover.html",
      "<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
      "  <div class=\"arrow\"></div>\n" +
      "\n" +
      "  <div class=\"popover-inner\">\n" +
      "      <h3 class=\"popover-title\" ng-bind-html=\"title | unsafe\" ng-show=\"title\"></h3>\n" +
      "      <div class=\"popover-content\"ng-bind-html=\"content | unsafe\"></div>\n" +
      "  </div>\n" +
      "</div>\n" +
      "");
}]);

РЕДАКТИРОВАТЬ: Вот реализация Plunker.

РЕДАКТИРОВАТЬ 2: Поскольку этот ответ продолжает получать хиты, я буду обновлять его как можно лучше. В качестве справки здесь приведен шаблон из загрузочный репозиторий angular-ui. Если это изменится, шаблону переопределения потребуются соответствующие обновления и добавление атрибутов ng-bind-html=\"title | unsafe\" и ng-bind-html=\"content | unsafe\" для продолжения работы.

Чтобы узнать об обновленной беседе, проверьте проблему здесь.

person Matthew.Lothian    schedule 24.02.2014
comment
@zoidbergi Фильтр следует добавить в модуль или любые модули, используемые вашим основным модулем приложения. Переопределение шаблона должно быть включено после пользовательского интерфейса Angular и Bootstrap, чтобы оно не переопределялось бутстрапом. Я добавлю пример, чтобы было легче увидеть, как это работает. - person Matthew.Lothian; 20.03.2014
comment
@ Андрей Федоров, так как у него столько же голосов, сколько у текущего (теперь неправильного) ответа, не могли бы вы помочь будущим зрителям, установив указанный выше ответ на отмеченный? - person Matthew.Lothian; 31.03.2014
comment
Работает отлично. Я не понимаю, почему это не является частью официального кода на angular-ui. github.io/bootstrap/#/popover - person Logus Graphics; 20.04.2015

Используйте директиву popover-template.

Если вы используете версию angular-ui, равную или выше 0.13.0, лучше всего использовать директиву popover-template. Вот как это использовать:

<button popover-template="'popover.html'">My HTML popover</button>

<script type="text/ng-template" id="popover.html">
    <div>
        Popover content
    </div>
</script>

NB: Не забывайте заключать в кавычки имя шаблона в popover-template="'popover.html'".

См. демонстрационный плункер.


В качестве примечания: шаблон всплывающего окна можно внедрить в специальный html-файл вместо объявления его в элементе <script type="text/ng-template>, как указано выше.

См. второй демонстрационный плункер.

person Michael P. Bazos    schedule 26.09.2015
comment
Плюс один для НБ. Напомнил мне, чего мне не хватало. - person samazi; 02.06.2016
comment
извините за комментарий 2 года спустя, но я получаю ошибку angularjs: шаблон для директивы «mainmenu» должен иметь ровно один корневой элемент. templates/components/shared/menu/mainmenu/menu.view.html что я должен здесь изменить? - person Atlas; 01.09.2017

Я разместил решение в проекте github: https://github.com/angular-ui/bootstrap/issues/520

Если вы хотите добавить эту функциональность в свой проект, вот патч.

Добавьте эти директивы:

angular.module("XXX")
    .directive("popoverHtmlUnsafePopup", function () {
      return {
        restrict: "EA",
        replace: true,
        scope: { title: "@", content: "@", placement: "@", animation: "&", isOpen: "&" },
        templateUrl: "template/popover/popover-html-unsafe-popup.html"
      };
    })

    .directive("popoverHtmlUnsafe", [ "$tooltip", function ($tooltip) {
      return $tooltip("popoverHtmlUnsafe", "popover", "click");
    }]);

И добавьте шаблон:

<div class="popover {{placement}}" ng-class="{ in: isOpen(), fade: animation() }">
  <div class="arrow"></div>

  <div class="popover-inner">
      <h3 class="popover-title" ng-bind="title" ng-show="title"></h3>
      <div class="popover-content" bind-html-unsafe="content"></div>
  </div>
</div>

Использование: <button popover-placement="top" popover-html-unsafe="On the <b>Top!</b>" class="btn btn-default">Top</button>

Посмотреть на plunkr: http://plnkr.co/edit/VhYAD04ETQsJ2dY3Uum3?p=preview

person user1067920    schedule 24.07.2014
comment
Поскольку используемый шаблон определяется непосредственно в директиве, можем ли мы каким-либо образом выбрать шаблон непосредственно из HTML? Так как нам может понадобиться другой шаблон в разное время. - person Aniket Sinha; 30.07.2014

Вам нужно изменить шаблон всплывающего окна по умолчанию, чтобы указать, что вы хотите разрешить HTML-контент. Посмотрите на div popover-content, теперь он имеет привязку к свойству content, что позволяет использовать небезопасный HTML:

 angular.module("template/popover/popover.html", []).run(["$templateCache", function ($templateCache) {
        $templateCache.put("template/popover/popover.html",
            "<div class='popover {{placement}}' ng-class='{ in: isOpen(), fade: animation() }'>" + 
            "<div class='arrow'></div><div class='popover-inner'>" + 
            "<h3 class='popover-title' ng-bind='title' ng-show='title'></h3>" + 
            "<div class='popover-content' ng-bind-html-unsafe='content'></div>" +
            "<button class='btn btn-cancel' ng-click='manualHide()'>Cancel</button>" +
            "<button class='btn btn-apply' ng-click='manualHide()'>Apply</button></div></div>");
    }]);
person Andres    schedule 20.12.2013
comment
Это больше не правильно с angular 1.2+, так как ng-bind-html-unsafe был удален. - person Matthew.Lothian; 24.02.2014
comment
См. правильный ответ для Angular UI 1.2 ниже. stackoverflow.com/a/21979258/48082 - person Cheeso; 03.09.2014

Для всех ваших обычных потребностей Bootstrap popover вы можете использовать следующую директиву angular. Он удаляет беспорядок из шаблона HTML и очень прост в использовании.

Вы можете настроить всплывающее окно title, content, placement, постепенное появление/исчезновение delay, trigger, а также указать, следует ли обрабатывать контент как html. Это также предотвращает переполнение и отсечение контента.

Связанный плункер со всеми кодами здесь http://plnkr.co/edit/MOqhJi

Скриншот

imgur

Использование

<!-- HTML -->
<div ng-model="popup.content" popup="popup.options">Some element</div>

/* JavaScript */
this.popup = {
  content: 'Popup content here',
  options: {
    title: null,
    placement: 'right', 
    delay: { show: 800, hide: 100 }
  }
}; 

JavaScript

/**
 * Popup, a Bootstrap popover wrapper.
 *
 * Usage: 
 *  <div ng-model="model" popup="options"></div>
 * 
 * Remarks: 
 *  To prevent content overflow and clipping, use CSS
 *  .popover { word-wrap: break-word; }
 *  Popup without title and content will not be shown.
 *
 * @param {String}  ngModel           popup content
 * @param {Object}  options           popup options
 * @param {String}  options.title     title
 * @param {Boolean} options.html      content should be treated as html markup
 * @param {String}  options.placement placement (top, bottom, left or right)
 * @param {String}  options.trigger   trigger event, default is hover
 * @param {Object}  options.delay     milliseconds or { show:<ms>, hide:<ms> }
 */
app.directive('popup', function() {
  return {
    restrict: 'A',
    require: 'ngModel',
    scope: {
      ngModel: '=',
      options: '=popup'
    },
    link: function(scope, element) {
      scope.$watch('ngModel', function(val) {
        element.attr('data-content', val);
      });

      var options = scope.options || {} ; 

      var title = options.title || null;
      var placement = options.placement || 'right';
      var html = options.html || false;
      var delay = options.delay ? angular.toJson(options.delay) : null;
      var trigger = options.trigger || 'hover';

      element.attr('title', title);
      element.attr('data-placement', placement);
      element.attr('data-html', html);
      element.attr('data-delay', delay);
      element.popover({ trigger: trigger });
    }
  };
});
person Mikko Viitala    schedule 28.01.2015
comment
Это не использует угловую загрузку пользовательского интерфейса, как спрашивал OP, а вместо этого полагается на jQuery и bootstrap.js, не уверенный, насколько это обычно. Другой мудрый хороший ответ. - person Matthew.Lothian; 02.03.2015

См. https://github.com/jbruni/bootstrap-bower-jbruni, которые позволяют использовать всплывающий шаблон

person Thierry    schedule 04.07.2014

Следующий стиль CSS, кажется, сделал то, что я хотел в моем конкретном случае:

.popover-content {
  white-space: pre;
  font-family: monospace;
}

Общий вопрос остается открытым.

person Andrey Fedorov    schedule 24.05.2013

Вот скрипка моего решения, которое:

  • Доступен (вы можете использовать клавиши табуляции для активации/деактивации).
  • Позволяет пользователю наводить курсор на всплывающее окно, при этом всплывающее окно остается открытым.
  • Разрешает несколько всплывающих окон на странице, но только одно всплывающее окно может быть активировано в любой момент времени.
  • Не зависит от какой-либо третьей стороны, хотя стили всплывающих окон начальной загрузки были заимствованы.

Это работает так: мы создаем сколько угодно всплывающих окон, которые у нас будут на странице, в массиве popover (см. TODO в комментариях о том, как это подключить).

Затем каждый раз, когда пользователь наводит курсор мыши на элемент, который должен вызвать всплывающее окно, мы активируем это конкретное всплывающее окно в массиве popover. Когда пользователь больше не наводит курсор на элемент, мы устанавливаем тайм-аут для этого конкретного popover в массиве. Если этот тайм-аут истек, он выполняет быструю проверку, чтобы увидеть, перенавел ли пользователь курсор или перефокусировал (с помощью табуляции) элемент. Если да, то мы сохраняем всплывающее окно живым. Если нет, мы скрываем всплывающее окно.

Для CSS я не хотел полагаться на использование начальной загрузки, поэтому я позаимствовал стили непосредственно из начальной загрузки. Если вы попытаетесь использовать стили всплывающих окон бутстрапа, вы можете столкнуться с каким-то странным поведением, когда загрузчик запускает свои собственные скрипты в нашем пользовательском всплывающем окне, что нам не нужно.

HTML:

 <section>
    <a href="#" 
       ng-mouseover="showPopover(i)" 
       ng-mouseleave="hidePopover(i)"
       ng-focus="showPopover(i)"
       ng-blur="hidePopover(i)">
        I trigger a popover - {{i}}
    </a>
    <popover popover-show="popover[i].popoverTracker">
      <div class="arrow"></div>
      <div class="custom-popover-content"
           ng-mouseover="showPopover(i)" 
           ng-mouseleave="hidePopover(i)"
           ng-focus="showPopover(i)"
           ng-blur="hidePopover(i)">
        <a href="#"
           ng-focus="showPopover(i)"
           ng-blur="hidePopover(i)">You can tab into me, I'm accessible!</a>
        <br/>
        <a href="#" 
           ng-focus="showPopover(i)"
           ng-blur="hidePopover(i)">You can tab into me, I'm accessible!</a>
      </div>
    </popover>
  </section> 

Угловой контроллер и директива:

angular.module('controllers', []);
angular.module('directives', []);
angular.module('myApp', ['ngAnimate', 'controllers', 'directives']);

angular.module('controllers')
    .controller('MainCtrl', function ($scope, $timeout) {

    $scope.popover = [];

    (function init() {
        // TODO: Make this dynamic so that we can pass it a value and it will generate the right amount
        // Initializing the array of popovers on startup
      createPopoverTrackers(20);
        })();

    // Creating an array of popovers equal to the number of popovers on the page
    function createPopoverTrackers(count) {
        for(var i = 0; i < count; i++) {
        $scope.popover.push({
            popoverTracker: false,
          popoverKeepAlive: false,
          timer: null
        })
      }
    }

    // A user has focused on an element that has an associated popover
    $scope.queueOpenPopover = function(index) {
    // Show our specified tracker
    $scope.popover[index].popoverTracker = true;

     // Hide the rest
     Object.keys($scope.popover)
        .filter(function(trackerIndex) {
          return trackerIndex != index
       })
      .forEach(function(trackerIndex) {
          $scope.popover[trackerIndex].popoverTracker = false;
          $scope.popover[trackerIndex].popoverKeepAlive = false;

          const timer = $scope.popover[trackerIndex].timer;
          if(timer) {
             $timeout.cancel(timer);
             $scope.popover[trackerIndex].timer = null;
          }
      })
        };

    // Queuing up the demise of the popover
    $scope.queueKillPopover = function(index) {
      $scope.popover[index].timer = $timeout(function() {
             if (!$scope.popover[index].popoverKeepAlive) {
          // Popover or the popover trigger were not hovered within the time limit, kill it!
          $scope.popover[index].popoverTracker = false;
        }
        }, 700);
    };

    // When a user focuses into the actual popover itself or it's trigger,  we need to keep it alive
    $scope.showPopover = function(index) {
        $scope.popover[index].popoverKeepAlive = true;
      $scope.queueOpenPopover(index);
    };

    // The user has removed focus from the popover or it's trigger, set this to false so the timer knows to kill it
    $scope.hidePopover = function(index) {
        $scope.popover[index].popoverKeepAlive = false;
      $scope.queueKillPopover(index);
    };
});

angular.module('directives')
    .directive('popover', function () {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        scope: {
            'popoverShow': '='
        },
        template: '<div class="custom-popover bottom" ng-show="popoverShow" ng-transclude></div>'
    };
});

CSS позаимствован из бутстрапа:

.custom-popover {
    position: absolute;
    z-index: 1010;
    max-width: 276px;
    padding: 1px;
    text-align: left;
    white-space: normal;
    background-color: #fff;
    border: 1px solid rgba(0,0,0,0.2);
    border-radius: 6px;
    box-shadow: 0 5px 10px rgba(0,0,0,0.2);
    background-clip: padding-box;
}

.custom-popover .arrow,
.custom-popover .arrow:after {
  position: absolute;
  display: block;
  width: 0;
  height: 0;
  border-color: transparent;
  border-style: solid;
}

.custom-popover .arrow {
  border-width: 11px;
}

.custom-popover .arrow:after {
  border-width: 10px;
  content: "";
}

.custom-popover.bottom {
  margin-top: 10px;
}

.custom-popover.bottom .arrow {
  top: -11px;
  left: 50%;
  margin-left: -11px;
  border-bottom-color: rgba(0, 0, 0, 0.25);
  border-top-width: 0;
}

.custom-popover.bottom .arrow:after {
  top: 1px;
  margin-left: -10px;
  border-bottom-color: #ffffff;
  border-top-width: 0;
  content: " ";
}

.custom-popover-content {
  padding: 9px 14px;
}
person maxshuty    schedule 13.03.2020