Skip to content
This repository has been archived by the owner on Dec 1, 2023. It is now read-only.

Commit

Permalink
feat(modal, aside, alert): refactor with $bsCompiler to add support f…
Browse files Browse the repository at this point in the history
…or `controller`, `controllerAs`, `template`, `templateUrl` options (fixes #732, fixes #728, fixes #1394, fixes #1735)
  • Loading branch information
mgcrea committed Jul 12, 2015
1 parent 2d7304c commit 8e54db8
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 56 deletions.
4 changes: 2 additions & 2 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@

<body ontouchstart ng-controller="MainCtrl">

<div ng-include="'views/partials/navbar.html'"></div>
<div ng-include="'views/partials/header.html'"></div>
<!-- <div ng-include="'views/partials/navbar.html'"></div> -->
<!-- <div ng-include="'views/partials/header.html'"></div> -->
<div ng-include="'views/home.html'"></div>
<div ng-include="'views/partials/footer.html'"></div>

Expand Down
10 changes: 5 additions & 5 deletions docs/views/home.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<div class="container bs-docs-container">
<div class="row">
<div class="col-md-2 hidden-sm hidden-xs">
<!-- <div class="col-md-2 hidden-sm hidden-xs">
<div ng-include="'views/partials/affixed-sidebar.html'"></div>
</div>
</div> -->
<div class="col-md-10">
<div ng-include="'views/sections/getting-started.html'"></div>
<!-- <div ng-include="'views/sections/getting-started.html'"></div> -->
<div id="directives">
<div ng-include="'modal/docs/modal.demo.html'"></div>
<div ng-include="'aside/docs/aside.demo.html'"></div>
<!-- <div ng-include="'aside/docs/aside.demo.html'"></div>
<div ng-include="'alert/docs/alert.demo.html'"></div>
<div ng-include="'tooltip/docs/tooltip.demo.html'"></div>
<div ng-include="'popover/docs/popover.demo.html'"></div>
Expand All @@ -21,7 +21,7 @@
<div ng-include="'dropdown/docs/dropdown.demo.html'"></div>
<div ng-include="'navbar/docs/navbar.demo.html'"></div>
<div ng-include="'scrollspy/docs/scrollspy.demo.html'"></div>
<div ng-include="'affix/docs/affix.demo.html'"></div>
<div ng-include="'affix/docs/affix.demo.html'"></div> -->
</div>
</div>
</div>
Expand Down
5 changes: 3 additions & 2 deletions src/alert/alert.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ angular.module('mgcrea.ngStrap.alert', ['mgcrea.ngStrap.modal'])
prefixClass: 'alert',
prefixEvent: 'alert',
placement: null,
template: 'alert/alert.tpl.html',
templateUrl: 'alert/alert.tpl.html',
template: '',
container: false,
element: null,
backdrop: false,
Expand Down Expand Up @@ -74,7 +75,7 @@ angular.module('mgcrea.ngStrap.alert', ['mgcrea.ngStrap.modal'])

// Directive options
var options = {scope: scope, element: element, show: false};
angular.forEach(['template', 'placement', 'keyboard', 'html', 'container', 'animation', 'duration', 'dismissable'], function(key) {
angular.forEach(['template', 'templateUrl', 'placement', 'keyboard', 'html', 'container', 'animation', 'duration', 'dismissable'], function(key) {
if(angular.isDefined(attr[key])) options[key] = attr[key];
});

Expand Down
2 changes: 1 addition & 1 deletion src/alert/test/alert.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('alert', function() {
},
'options-template': {
scope: {alert: {title: 'Title', content: 'Hello alert!', counter: 0}, items: ['foo', 'bar', 'baz']},
element: '<a data-template="custom" bs-alert="alert">click me</a>'
element: '<a data-template-url="custom" bs-alert="alert">click me</a>'
}
};

Expand Down
5 changes: 3 additions & 2 deletions src/aside/aside.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ angular.module('mgcrea.ngStrap.aside', ['mgcrea.ngStrap.modal'])
prefixClass: 'aside',
prefixEvent: 'aside',
placement: 'right',
template: 'aside/aside.tpl.html',
templateUrl: 'aside/aside.tpl.html',
template: '',
contentTemplate: false,
container: false,
element: null,
Expand Down Expand Up @@ -50,7 +51,7 @@ angular.module('mgcrea.ngStrap.aside', ['mgcrea.ngStrap.modal'])
link: function postLink(scope, element, attr, transclusion) {
// Directive options
var options = {scope: scope, element: element, show: false};
angular.forEach(['template', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation'], function(key) {
angular.forEach(['template', 'templateUrl', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation'], function(key) {
if(angular.isDefined(attr[key])) options[key] = attr[key];
});

Expand Down
2 changes: 1 addition & 1 deletion src/aside/test/aside.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('aside', function () {
},
'options-template': {
scope: {aside: {title: 'Title', content: 'Hello aside!', counter: 0}, items: ['foo', 'bar', 'baz']},
element: '<a data-template="custom" bs-aside="aside">click me</a>'
element: '<a data-template-url="custom" bs-aside="aside">click me</a>'
},
'options-html': {
scope: {aside: {title: 'title<br>next', content: 'content<br>next'}},
Expand Down
173 changes: 173 additions & 0 deletions src/helpers/compiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
'use strict';

// NOTICE: This file was forked from the angular-material project (github.com/angular/material)
// MIT Licensed - Copyright (c) 2014-2015 Google, Inc. http://angularjs.org

angular.module('mgcrea.ngStrap.core', [])
.service('$bsCompiler', bsCompilerService);

function bsCompilerService($q, $http, $injector, $compile, $controller, $templateCache) {

/*
* @ngdoc service
* @name $bsCompiler
* @module material.core
* @description
* The $bsCompiler service is an abstraction of angular's compiler, that allows the developer
* to easily compile an element with a templateUrl, controller, and locals.
*
* @usage
* <hljs lang="js">
* $bsCompiler.compile({
* templateUrl: 'modal.html',
* controller: 'ModalCtrl',
* locals: {
* modal: myModalInstance;
* }
* }).then(function(compileData) {
* compileData.element; // modal.html's template in an element
* compileData.link(myScope); //attach controller & scope to element
* });
* </hljs>
*/

/*
* @ngdoc method
* @name $bsCompiler#compile
* @description A helper to compile an HTML template/templateUrl with a given controller,
* locals, and scope.
* @param {object} options An options object, with the following properties:
*
* - `controller` - `{(string=|function()=}` Controller fn that should be associated with
* newly created scope or the name of a registered controller if passed as a string.
* - `controllerAs` - `{string=}` A controller alias name. If present the controller will be
* published to scope under the `controllerAs` name.
* - `template` - `{string=}` An html template as a string.
* - `templateUrl` - `{string=}` A path to an html template.
* - `transformTemplate` - `{function(template)=}` A function which transforms the template after
* it is loaded. It will be given the template string as a parameter, and should
* return a a new string representing the transformed template.
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
* be injected into the controller. If any of these dependencies are promises, the compiler
* will wait for them all to be resolved, or if one is rejected before the controller is
* instantiated `compile()` will fail..
* * `key` - `{string}`: a name of a dependency to be injected into the controller.
* * `factory` - `{string|function}`: If `string` then it is an alias for a service.
* Otherwise if function, then it is injected and the return value is treated as the
* dependency. If the result is a promise, it is resolved before its value is
* injected into the controller.
*
* @returns {object=} promise A promise, which will be resolved with a `compileData` object.
* `compileData` has the following properties:
*
* - `element` - `{element}`: an uncompiled element matching the provided template.
* - `link` - `{function(scope)}`: A link function, which, when called, will compile
* the element and instantiate the provided controller (if given).
* - `locals` - `{object}`: The locals which will be passed into the controller once `link` is
* called. If `bindToController` is true, they will be coppied to the ctrl instead
* - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
*/
this.compile = function(options) {

if(options.template && /\.html$/.test(options.template)) {
console.warn('Deprecated use of `template` option to pass a file. Please use the `templateUrl` option instead.');
options.templateUrl = options.template;
options.template = '';
}

var templateUrl = options.templateUrl;
var template = options.template || '';
var controller = options.controller;
var controllerAs = options.controllerAs;
var resolve = angular.copy(options.resolve || {});
var locals = angular.copy(options.locals || {});
var transformTemplate = options.transformTemplate || angular.identity;
var bindToController = options.bindToController;

// Take resolve values and invoke them.
// Resolves can either be a string (value: 'MyRegisteredAngularConst'),
// or an invokable 'factory' of sorts: (value: function ValueGetter($dependency) {})
angular.forEach(resolve, function(value, key) {
if (angular.isString(value)) {
resolve[key] = $injector.get(value);
} else {
resolve[key] = $injector.invoke(value);
}
});
// Add the locals, which are just straight values to inject
// eg locals: { three: 3 }, will inject three into the controller
angular.extend(resolve, locals);

if (templateUrl) {
resolve.$template = fetchTemplate(templateUrl);
} else {
resolve.$template = $q.when(template);
}

if (options.contentTemplate) {
// TODO(mgcrea): deprecate?
resolve.$template = $q.all([resolve.$template, fetchTemplate(options.contentTemplate)])
.then(function(templates) {
var templateEl = angular.element(templates[0]);
var contentEl = findElement('[ng-bind="content"]', templateEl[0]).removeAttr('ng-bind').html(templates[1]);
// Drop the default footer as you probably don't want it if you use a custom contentTemplate
if(!options.templateUrl) contentEl.next().remove();
return templateEl[0].outerHTML;
});
}

// Wait for all the resolves to finish if they are promises
return $q.all(resolve).then(function(locals) {

var template = transformTemplate(locals.$template);
if (options.html) {
template = template.replace(/ng-bind="/ig, 'ng-bind-html="');
}
// var element = options.element || angular.element('<div>').html(template.trim()).contents();
var element = angular.element('<div>').html(template.trim()).contents();
var linkFn = $compile(element);

// Return a linking function that can be used later when the element is ready
return {
locals: locals,
element: element,
link: function link(scope) {
locals.$scope = scope;

// Instantiate controller if it exists, because we have scope
if (controller) {
var invokeCtrl = $controller(controller, locals, true);
if (bindToController) {
angular.extend(invokeCtrl.instance, locals);
}
var ctrl = invokeCtrl();
// See angular-route source for this logic
element.data('$ngControllerController', ctrl);
element.children().data('$ngControllerController', ctrl);

if (controllerAs) {
scope[controllerAs] = ctrl;
}
}

return linkFn.apply(null, arguments);
}
};
});

};

function findElement(query, element) {
return angular.element((element || document).querySelectorAll(query));
}

var fetchPromises = {};
function fetchTemplate(template) {
if(fetchPromises[template]) return fetchPromises[template];
return (fetchPromises[template] = $http.get(template, {cache: $templateCache})
.then(function(res) {
return res.data;
}));
}

}
7 changes: 7 additions & 0 deletions src/modal/docs/modal.demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ <h3>Live demo <a class="small edit-plunkr" data-module-name="mgcrea.ngStrapDocs"
<small>(using data-template)</small>
</button>

<!-- You can use a custom html template with the `data-template` attr -->
<button type="button" class="btn btn-lg btn-danger" ng-click="showModal()">Modal
<br />
<small>(using service)</small>
</button>


</div>

<div class="callout callout-info">
Expand Down
18 changes: 11 additions & 7 deletions src/modal/docs/modal.demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ angular.module('mgcrea.ngStrapDocs')

// Controller usage example
//
// var myModal = $modal({title: 'Title', content: 'Hello Modal<br />This is a multiline message!', show: false});
// $scope.showModal = function() {
// myModal.$promise.then(myModal.show);
// };
// $scope.hideModal = function() {
// myModal.$promise.then(myModal.hide);
// };
function MyModalController($scope, $q) {
console.warn('in');
$scope.foo = 'bar';
}
var myModal = $modal({title: 'Title', content: 'Hello Modal<br />This is a multiline message!', controller: MyModalController, template: 'modal/docs/modal.demo.tpl.html', show: false});
$scope.showModal = function() {
myModal.$promise.then(myModal.show);
};
$scope.hideModal = function() {
myModal.$promise.then(myModal.hide);
};

});
1 change: 1 addition & 0 deletions src/modal/docs/modal.demo.tpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ <h4 class="modal-title" ng-bind-html="title"></h4>
<h4>Text in a modal</h4>
<p ng-bind-html="content"></p>
<pre>2 + 3 = {{ 2 + 3 }}</pre>
<pre>{{ foo }}</pre>

<h4>Popover in a modal</h4>
<p>This <a href="#" role="button" class="btn btn-default popover-test" data-title="A Title" data-content="And here's some amazing content. It's very engaging. right?" bs-popover>button</a> should trigger a popover on click.</p>
Expand Down
Loading

0 comments on commit 8e54db8

Please sign in to comment.