Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
fix(dialog): enable support for html content for alert and confirm di…
Browse files Browse the repository at this point in the history
…alogs

*  dialog`::onShow( )` now uses `wrapSimpleContent( )` to wrap simple strings in `<p>` tags
*  dialog basic demo `showConfirm( )` uses HTML span tag to illustrate use of HMTL content
*  implemented generic `md-template` to safely inject HTML content into an element

BREAKING CHANGE: dialog content text is now injected into **div.md-dialog-content-body**

Before the template used was:

```html
<md-dialog-content>
   <h2></h2>
   <p></p>
</md-dialog-content>
```

Now uses:

```html
<md-dialog-content>
   <h2></h2>
   <div class="md-dialog-content-body">
     <p></p>
    </div>
</md-dialog-content>
```

Fixes #1495.
  • Loading branch information
ThomasBurleson committed Aug 15, 2015
1 parent 9fce4b9 commit 81e94b4
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/components/dialog/demoBasicUsage/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ angular.module('dialogDemo1', ['ngMaterial'])
// Appending dialog to document.body to cover sidenav in docs app
var confirm = $mdDialog.confirm()
.title('Would you like to delete your debt?')
.content('All of the banks have agreed to forgive you your debts.')
.content('All of the banks have agreed to <span class="debt-be-gone">forgive</span> you your debts.')
.ariaLabel('Lucky day')
.targetEvent(ev)
.ok('Please do it!')
Expand Down
6 changes: 6 additions & 0 deletions src/components/dialog/demoBasicUsage/style.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#popupContainer {
position:relative;
}

.debt-be-gone {
font-weight: bold;
color:blue;
text-decoration: underline;
}
37 changes: 28 additions & 9 deletions src/components/dialog/dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
* @ngdoc module
* @name material.components.dialog
*/
angular.module('material.components.dialog', [
'material.core',
'material.components.backdrop'
])
angular
.module('material.components.dialog', [
'material.core',
'material.components.backdrop'
])
.directive('mdDialog', MdDialogDirective)
.provider('$mdDialog', MdDialogProvider);

Expand All @@ -32,8 +33,6 @@ function MdDialogDirective($$rAF, $mdTheming) {
};
}



/**
* @ngdoc service
* @name $mdDialog
Expand Down Expand Up @@ -408,7 +407,7 @@ function MdDialogProvider($$interimElementProvider) {
'<md-dialog md-theme="{{ dialog.theme }}" aria-label="{{ dialog.ariaLabel }}">',
' <md-dialog-content role="document" tabIndex="-1">',
' <h2 class="md-title">{{ dialog.title }}</h2>',
' <p>{{ dialog.content }}</p>',
' <div class="md-dialog-content-body" md-template="::dialog.content"></div>',
' </md-dialog-content>',
' <div class="md-actions">',
' <md-button ng-if="dialog.$type == \'confirm\'"' +
Expand Down Expand Up @@ -459,6 +458,8 @@ function MdDialogProvider($$interimElementProvider) {
element = $mdUtil.extractElementByName(element, 'md-dialog');
angular.element($document[0].body).addClass('md-dialog-is-showing');

wrapSimpleContent();

captureSourceAndParent(element, options);
configureAria(element.find('md-dialog'), options);
showBackdrop(scope, element, options);
Expand Down Expand Up @@ -494,6 +495,26 @@ function MdDialogProvider($$interimElementProvider) {
}
}

/**
* Wrap any simple content [specified via .content("")] in <p></p> tags.
* otherwise accept HTML content within the dialog content area...
* NOTE: Dialog uses the md-template directive to safely inject HTML content.
*/
function wrapSimpleContent() {
if ( controller ) {
var HTML_END_TAG = /<\/[\w-]*>/gm;
var content = controller.content;

var hasHTML = HTML_END_TAG.test(content);
if (!hasHTML) {
content = $mdUtil.supplant("<p>{0}</p>", [content]);
}

// Publish updated dialog content body... to be compiled by mdTemplate directive
controller.content = content;
}
}

}

/**
Expand Down Expand Up @@ -538,8 +559,6 @@ function MdDialogProvider($$interimElementProvider) {
// In case the user provides a raw dom element, always wrap it in jqLite
options.parent = angular.element(options.parent || $rootElement);



}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/components/dialog/dialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ md-dialog {
padding-top: 0;
}
}

.md-dialog-content-body {
width:100%;
}
}

.md-actions {
Expand Down
70 changes: 56 additions & 14 deletions src/components/dialog/dialog.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('$mdDialog', function() {
$timeout.flush(); // flush to start animations
$$rAF.flush(); // flush animations
$animate.triggerCallbacks();
//$rootScope.$digest();
$timeout.flush(); // flush responses after animation completions
}
}));
Expand All @@ -42,8 +43,8 @@ describe('$mdDialog', function() {
.theme('some-theme')
.ok('Next')
).then(function() {
resolved = true;
});
resolved = true;
});

$rootScope.$apply();
runAnimation();
Expand Down Expand Up @@ -130,7 +131,7 @@ describe('$mdDialog', function() {
'ok', 'cancel', 'targetEvent', 'theme'
]);

it('shows a basic confirm dialog', inject(function($rootScope, $mdDialog, $animate) {
it('shows a basic confirm dialog with simple text content', inject(function($rootScope, $mdDialog, $animate) {
var parent = angular.element('<div>');
var rejected = false;
$mdDialog.show(
Expand All @@ -149,15 +150,13 @@ describe('$mdDialog', function() {

var container = angular.element(parent[0].querySelector('.md-dialog-container'));
var dialog = parent.find('md-dialog');
expect(dialog.attr('role')).toBe('dialog');

var title = parent.find('h2');
expect(title.text()).toBe('Title');

var content = parent.find('p');
expect(content.text()).toBe('Hello world');

var buttons = parent.find('md-button');

expect(dialog.attr('role')).toBe('dialog');
expect(title.text()).toBe('Title');
expect(content.text()).toBe('Hello world');
expect(buttons.length).toBe(2);
expect(buttons.eq(0).text()).toBe('Next');
expect(buttons.eq(1).text()).toBe('Forget it');
Expand All @@ -169,17 +168,60 @@ describe('$mdDialog', function() {
expect(rejected).toBe(true);
}));

it('shows a basic confirm dialog with HTML content', inject(function($rootScope, $mdDialog, $animate) {
var parent = angular.element('<div>');

$mdDialog.show(
$mdDialog.confirm({
parent: parent,
ok: 'Next',
cancel: 'Back',
title: 'Which Way ',
content: '<div class="mine">Choose</div>'
})
);

runAnimation();

var container = angular.element(parent[0].querySelector('.md-dialog-container'));
var content = angular.element(container[0].querySelector('.mine'));

expect(content.text()).toBe('Choose');
}));

it('shows a basic confirm dialog with HTML content using custom types', inject(function($rootScope, $mdDialog, $animate) {
var parent = angular.element('<div>');

$mdDialog.show(
$mdDialog.confirm({
parent: parent,
ok: 'Next',
cancel: 'Back',
title: 'Which Way ',
content: '<my-content class="mine">Choose</my-content>'
})
);

runAnimation();

var container = angular.element(parent[0].querySelector('.md-dialog-container'));
var content = angular.element(container[0].querySelector('.mine'));

expect(content.text()).toBe('Choose');
}));

it('should focus `md-button.dialog-close` on open', inject(function($mdDialog, $rootScope, $document, $timeout, $mdConstant) {
jasmine.mockElementFocus(this);

var parent = angular.element('<div>');
$mdDialog.show({
template: '<md-dialog>' +
'<div class="md-actions">' +
'<button class="dialog-close">Close</button>' +
'</div>' +
template: '' +
'<md-dialog>' +
' <div class="md-actions">' +
' <button class="dialog-close">Close</button>' +
' </div>' +
'</md-dialog>',
parent: parent,
parent: parent
});
runAnimation();

Expand Down
6 changes: 3 additions & 3 deletions src/components/tabs/js/tabsDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ function MdTabs () {
ng-disabled="tab.scope.disabled"\
md-swipe-left="$mdTabsCtrl.nextPage()"\
md-swipe-right="$mdTabsCtrl.previousPage()"\
md-template="::tab.label"\
md-tabs-template="::tab.label"\
md-scope="::tab.parent"></md-tab-item>\
<md-ink-bar></md-ink-bar>\
</md-pagination-wrapper>\
Expand All @@ -165,7 +165,7 @@ function MdTabs () {
ng-focus="$mdTabsCtrl.hasFocus = true"\
ng-blur="$mdTabsCtrl.hasFocus = false"\
ng-repeat="tab in $mdTabsCtrl.tabs"\
md-template="::tab.label"\
md-tabs-template="::tab.label"\
md-scope="::tab.parent"></md-dummy-tab>\
</div>\
</md-tabs-canvas>\
Expand All @@ -187,7 +187,7 @@ function MdTabs () {
\'md-no-scroll\': $mdTabsCtrl.dynamicHeight\
}">\
<div\
md-template="::tab.template"\
md-tabs-template="::tab.template"\
md-connected-if="tab.isActive()"\
md-scope="::tab.parent"\
ng-if="$mdTabsCtrl.enableDisconnect || tab.shouldRender()"></div>\
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
angular
.module('material.components.tabs')
.directive('mdTemplate', MdTemplate);
.directive('mdTabsTemplate', MdTabsTemplate);

function MdTemplate ($compile, $mdUtil) {
function MdTabsTemplate ($compile, $mdUtil) {
return {
restrict: 'A',
link: link,
scope: {
template: '=mdTemplate',
template: '=mdTabsTemplate',
connected: '=?mdConnectedIf',
compileScope: '=mdScope'
},
Expand Down
35 changes: 31 additions & 4 deletions src/core/core.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

/**
* Initialization function that validates environment
* requirements.
Expand All @@ -10,8 +9,8 @@ angular
'material.core.gestures',
'material.core.theming'
])
.config( MdCoreConfigure );

.directive('mdTemplate', MdTemplateDirective)
.config(MdCoreConfigure);

function MdCoreConfigure($provide, $mdThemingProvider) {

Expand All @@ -24,7 +23,35 @@ function MdCoreConfigure($provide, $mdThemingProvider) {
.backgroundPalette('grey');
}

function rAFDecorator( $delegate ) {
function MdTemplateDirective($compile) {
return {
restrict: 'A',
scope: {
template: '=mdTemplate'
},
link: function postLink(scope, element) {
scope.$watch('template', assignSafeHTML);

/**
* To add safe HTML: assign and compile in
* isolated scope.
*/
function assignSafeHTML(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);

// Compile the new DOM and link it to the current scope.
// NOTE: we only compile .childNodes so that we don't get
// into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
}
};

}

function rAFDecorator($delegate) {
/**
* Use this to throttle events that come in often.
* The throttled function will always use the *last* invocation before the
Expand Down

0 comments on commit 81e94b4

Please sign in to comment.