├── .gitignore ├── bower.json ├── package.json ├── LICENSE ├── src └── multi-transclude.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /.tmp/ 3 | /.idea/ 4 | /bower_components/ 5 | /node_modules/ 6 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-multi-transclude", 3 | "version": "0.1.5", 4 | "homepage": "https://github.com/zachsnow/ng-multi-transclude", 5 | "authors": [ 6 | "Zach Snow " 7 | ], 8 | "description": "Richer transclusion for AngularJS", 9 | "dependencies": { 10 | "angular": ">=1.3.0" 11 | }, 12 | "main": "src/multi-transclude.js", 13 | "keywords": [ 14 | "ng-multi-transclude", 15 | "multi-transclude" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "*.md", 21 | "LICENSE" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-multi-transclude", 3 | "version": "0.1.5", 4 | "description": "Richer transclusion for AngularJS", 5 | "main": "src/multi-transclude.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/zachsnow/ng-multi-transclude.git" 9 | }, 10 | "keywords": [ 11 | "ng-multi-transclude", 12 | "multi-transclude" 13 | ], 14 | "author": "Zach Snow ", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/zachsnow/ng-multi-transclude/issues" 18 | }, 19 | "homepage": "https://github.com/zachsnow/ng-multi-transclude" 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Zach Snow (http://zachsnow.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/multi-transclude.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var module = angular.module('multi-transclude', []); 3 | 4 | var Ctrl = ['$scope', '$element', '$transclude', function($scope, $element, $transclude){ 5 | // Ensure we're transcluding or nothing will work. 6 | if(!$transclude){ 7 | throw new Error( 8 | 'Illegal use of ngMultiTransclude controller. No directive ' + 9 | 'that requires a transclusion found.' 10 | ); 11 | } 12 | 13 | // There's not a good way to ask Angular to give you the closest 14 | // controller from a list of controllers, we get all multi-transclude 15 | // controllers and select the one that is the child of the other. 16 | this.$element = $element; 17 | this.isChildOf = function(otherCtrl){ 18 | return otherCtrl.$element[0].contains(this.$element[0]); 19 | }; 20 | 21 | // Destination for transcluded content. 22 | var toTransclude; 23 | $scope.$on('$destroy', function(){ 24 | if(toTransclude){ 25 | toTransclude.remove(); 26 | toTransclude = null; 27 | } 28 | }); 29 | 30 | // A temporary container for transcluded content, so that content will not 31 | // be detached from the DOM during link. This ensures that controllers and 32 | // other data parent nodes are accessible within the transcluded content. 33 | var transcludeContainer = angular.element('
'); 34 | 35 | // Transclude content that matches name into element. 36 | this.transclude = function(name, element){ 37 | for(var i = 0; i < toTransclude.length; ++i){ 38 | // Uses the argument as the `name` attribute directly, but we could 39 | // evaluate it or interpolate it or whatever. 40 | var el = angular.element(toTransclude[i]); 41 | if(el.attr('name') === name){ 42 | element.empty(); 43 | element.append(el); 44 | return; 45 | } 46 | } 47 | }; 48 | 49 | // Should be called after all transclusions are complete to clean up the 50 | // temporary container. 51 | this.transcluded = function(){ 52 | if(transcludeContainer){ 53 | transcludeContainer.remove(); 54 | transcludeContainer = null; 55 | } 56 | }; 57 | 58 | // Transclude content and keep track of it; be sure to keep it in the DOM 59 | // by attaching it to `$element`. 60 | $transclude(function(clone){ 61 | toTransclude = clone; 62 | 63 | transcludeContainer.append(clone); 64 | $element.append(transcludeContainer); 65 | }); 66 | }]; 67 | 68 | module.directive('ngMultiTemplate', function(){ 69 | return { 70 | transclude: true, 71 | templateUrl: function(element, attrs){ 72 | return attrs.ngMultiTemplate; 73 | }, 74 | controller: Ctrl, 75 | link: function(scope, element, attrs, ctrl){ 76 | ctrl.transcluded(); 77 | } 78 | }; 79 | }); 80 | 81 | module.directive('ngMultiTranscludeController', function(){ 82 | return { 83 | controller: Ctrl, 84 | link: function(scope, element, attrs, ctrl){ 85 | ctrl.transcluded(); 86 | } 87 | }; 88 | }); 89 | 90 | module.directive('ngMultiTransclude', function(){ 91 | return { 92 | require: ['?^ngMultiTranscludeController', '?^ngMultiTemplate'], 93 | link: function(scope, element, attrs, ctrls){ 94 | // Find the deepest controller (closes to this element). 95 | var ctrl1 = ctrls[0]; 96 | var ctrl2 = ctrls[1]; 97 | var ctrl; 98 | if(ctrl1 && ctrl2){ 99 | ctrl = ctrl1.isChildOf(ctrl2) ? ctrl1 : ctrl2; 100 | } 101 | else { 102 | ctrl = ctrl1 || ctrl2; 103 | } 104 | 105 | // A multi-transclude parent directive must be present. 106 | if(!ctrl){ 107 | throw new Error('Illegal use of ngMultiTransclude. No wrapping controller.') 108 | } 109 | 110 | // Receive transcluded content. 111 | ctrl.transclude(attrs.ngMultiTransclude, element); 112 | } 113 | }; 114 | }); 115 | })(); 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ng-multi-transclude 2 | 3 | **NOTE:** It looks like [AngularJS 1.5 will make this library obsolete](https://docs.angularjs.org/api/ng/directive/ngTransclude#multi-slot-transclusion). Hurray! 4 | 5 | Richer transclusion for AngularJS; see 6 | or check out this [demo](http://plnkr.co/edit/kMH2lYJ20LqNjgqwJ6W6?p=preview). 7 | 8 | This is still somewhat of an experiment. 9 | 10 | ## Dependencies 11 | 12 | 1. AngularJS (duh). 13 | 14 | ## Installation 15 | 16 | * Load `multi-transclude.js`. 17 | 18 | * Add `multi-transclude` as a dependency to your angular module. 19 | 20 | ```javascript 21 | angular.module('yourModule', [ 22 | // ... other dependencies ... 23 | 'multi-transclude' 24 | ]); 25 | ``` 26 | 27 | * Use `ng-multi-transclude`, `ng-multi-template`, and 28 | `ng-multi-transclude-controller` in your templates. 29 | 30 | ## Description 31 | 32 | Transclusion in AngularJS allows you to write a directive that is 33 | parameterized by a block of HTML. *Multi-transclusion* allows you to 34 | write directives that are parameters by *multiple* blocks of HTML. 35 | 36 | Consider an example: a fancy cancel button that has a 37 | text-replaced icon and a title that should be styled specially. 38 | In the former case, we might have a directive `ng-button` that populates 39 | the following template: 40 | 41 | ```html 42 | 46 | ``` 47 | 48 | And use it thus: 49 | 50 | ```html 51 |
52 | Are you sure you want to delete the thing? 53 |
54 | ``` 55 | 56 | Which would generate: 57 | 58 | ```html 59 | 65 | ``` 66 | 67 | With multi-transclusion, you can write directives whose templates 68 | have several "holes" that you can populate individually, by name. 69 | Let's expand our example to a directive `ng-multi-button`, that 70 | has both a title and a *hint*, both of which should be allowed 71 | to be arbitrary templates: 72 | 73 | ```html 74 | 84 | ``` 85 | 86 | Now we can populate each block independently, reusing the structure 87 | in the directive's template instead of forcing each use 88 | of `ng-button` to include its own hint. 89 | 90 | ```html 91 |
92 | 93 | Are you sure you want to delete the thing? 94 | 95 | 96 | When you delete the thing it's gone forever, 97 | so be extra careful! 98 | 99 |
100 | ``` 101 | 102 | ## Usage 103 | 104 | The `multi-transclude` library includes 3 directives: the eponymous 105 | `ng-multi-transclude`, along with `ng-multi-template` and `ng-multi-transclude-controller`. 106 | 107 | The simplest case is when you'd like to define a template (either inline 108 | in a directive definition, via `template`, or in a ` 120 | ``` 121 | 122 | When you want to instantiate your template, use the `ng-multi-template` directive, 123 | populating the named blocks. Note that you *must* declare your block parameters 124 | as immediate children of the `ng-multi-template` usage. 125 | 126 | ```html 127 |
128 |
...
129 |
...
130 |
131 | ``` 132 | 133 | Sometimes you'd like to define your own directive that, along with a multi-transclusion 134 | template, has a fancy link function. To do that you need to use `ng-multi-transclude-controller` 135 | to "wrap" all instances of `ng-multi-transclude` in your template: 136 | 137 | ```javascript 138 | app.directive('ngAnotherDirective', function(){ 139 | return { 140 | templateUrl: 'another-template', 141 | link: function(scope, element, attrs){ 142 | // Some fancy logic. 143 | } 144 | } 145 | }); 146 | ``` 147 | 148 | ```html 149 | 158 | ``` 159 | 160 | Then you can use your new custom directive as follows: 161 | 162 | ```html 163 |
164 |
...
165 |
...
166 |
167 | ``` 168 | 169 | You can provide default block content in your template, too; this content will 170 | be used if there is no matching block passed to the directive: 171 | 172 | ```html 173 | 179 | ``` 180 | 181 | To see something like this in action, check out this 182 | [demo](http://plnkr.co/edit/kMH2lYJ20LqNjgqwJ6W6?p=preview). 183 | --------------------------------------------------------------------------------