├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── angular_bind_polymer.js ├── bower.json ├── index.html ├── karma.conf.js ├── package.json └── test ├── BindingSpec.js ├── PolymerSetup.js └── x-double.html /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | watch: { 5 | code: { 6 | files: ['index.html', 'angular_bind_polymer.js', 'bower_components/**'], 7 | options: { 8 | livereload: true 9 | } 10 | } 11 | } 12 | }); 13 | 14 | grunt.loadNpmTasks('grunt-contrib-watch'); 15 | }; 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Chris Strom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-bind-polymer 2 | ==================== 3 | 4 | Angular directive for *double* variable binding of Polymer attributes. 5 | 6 | N.B. This will not work with raw paper/core elements because they do not publish their attributes. See note below on Polymer Usage for details. 7 | 8 | Installation 9 | ------------ 10 | 11 | Use bower to install: 12 | 13 | ``` 14 | $ bower install angular-bind-polymer 15 | ``` 16 | 17 | Usage 18 | ----- 19 | 20 | Script order is important. The web components platform (polyfills) need to be loaded first, followed by the Angular library and then this library (angular-bind-polymer): 21 | 22 | ```html 23 | 24 | 25 | 26 | ``` 27 | 28 | The Angular module needs to be inititialized before the Polymer elements are imported otherwise Polymer will overwrite the attributes before angular-bind-polymer has a chance to process them. 29 | 30 | Add `eee-c.angularBindPolymer` as dependency for your Angular application: 31 | 32 | ```javascript 33 | var PizzaStoreApp = angular.module('pizzaStoreApp', [ 34 | 'eee-c.angularBindPolymer' 35 | ]); 36 | ``` 37 | 38 | The final piece of setup is to import the Polymer elements: 39 | 40 | ```html 41 | 42 | ``` 43 | 44 | To bind values from Polymer elements, apply the `bind-polymer` directive: 45 | 46 | ```html 47 | 48 |

49 | ```
50 | 
51 | Changes from the `` custom element will now update the `pizzaState` variable in local scope.
52 | 
53 | _Note:_ changes in Angular's scope are already bound. That is, changes to `pizzaState` will update the `` custom element without this or any other modules. This directive is soley used to watch for changes in custom elements for the purposes of updating a bound variable in Angular's scope.
54 | 
55 | Support in Polymer
56 | ------------------
57 | 
58 | This will only work if the Polymer element publishes _and_ reflects attributes. That is, it is insufficient to declare attributes:
59 | 
60 | ```html
61 | 
62 | 
63 |   
64 |   
67 | 
68 | ```
69 | 
70 | To mark the `out` attribute as reflectable, declare it as such with the `publish` property:
71 | 
72 | ```html
73 | 
74 |   
75 |   
83 | 
84 | ```
85 | 
86 | Unfortunately, core and paper elements tend not to reflect attributes at this time. This means that this directive will not work with them. Hopefully this will change in the near future.
87 | 
88 | Contributors
89 | ------------
90 | 
91 | Much thanks to everyone that has helped with suggestions. In particular, thanks to the following for code improvements:
92 | 
93 |  * [Jan-Willem Gmelig Meyling](https://github.com/JWGmeligMeyling)
94 |  * [Mark Daggett](https://github.com/heavysixer)
95 |  * [Pascal Precht](https://github.com/PascalPrecht)
96 | 


--------------------------------------------------------------------------------
/angular_bind_polymer.js:
--------------------------------------------------------------------------------
 1 | angular.module('eee-c.angularBindPolymer', []).
 2 | directive('bindPolymer', ['$parse', function($parse) {
 3 |   'use strict';
 4 |   return {
 5 |     restrict: 'A',
 6 |     scope : false,
 7 |     compile: function bindPolymerCompile($element, $attr) {
 8 |       var attrMap = {};
 9 | 
10 |       for (var prop in $attr) {
11 |         var dash_prop = prop.
12 |           replace(/([a-z])([A-Z])/g, '$1-$2').
13 |           toLowerCase();
14 | 
15 |         if (angular.isString($attr[prop])) {
16 |           var _match = $attr[prop].match(/\{\{\s*([\.\w]+)\s*\}\}/);
17 |           if (_match) {
18 |             // console.log(prop + ': ' + _match[1])
19 |             attrMap[prop] = $parse(_match[1]);
20 |             if (dash_prop != prop) {
21 |               attrMap[dash_prop] = $parse(_match[1]);
22 |             }
23 |           }
24 |         }
25 |       }
26 | 
27 |       return function bindPolymerLink(scope, element, attrs) {
28 | 
29 |         // When Polymer sees a change to the bound variable,
30 |         // $apply / $digest the changes here in Angular
31 |         var observer = new MutationObserver(function processMutations(mutations) {
32 |           mutations.forEach(function processMutation(mutation) {
33 |             var attributeName, newValue, oldValue, getter;
34 |             attributeName = mutation.attributeName;
35 | 
36 |             if(attributeName in attrMap) {
37 |               newValue = element.attr(attributeName);
38 |               getter = attrMap[attributeName];
39 |               oldValue = getter(scope);
40 | 
41 |               if(oldValue != newValue && angular.isFunction(getter.assign)) {
42 |                 getter.assign(scope, newValue);
43 |               }
44 |             }
45 |           });
46 |           scope.$apply();
47 |         });
48 | 
49 |         observer.observe(element[0], {attributes: true});
50 |         scope.$on('$destroy', observer.disconnect.bind(observer));
51 |       }
52 |     }
53 |   };
54 | }]);
55 | 


--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "angular-bind-polymer",
 3 |   "version": "0.1.1",
 4 |   "authors": [
 5 |     "Chris Strom "
 6 |   ],
 7 |   "description": "Angular directive for *double* variable binding of Polymer attributes.",
 8 |   "main": "angular_bind_polymer.js",
 9 |   "keywords": [
10 |     "angular",
11 |     "polymer"
12 |   ],
13 |   "license": "MIT",
14 |   "homepage": "http://github.com/eee-c/angular-bind-polymer",
15 |   "ignore": [
16 |     "**/.*",
17 |     "node_modules",
18 |     "bower_components",
19 |     "test",
20 |     "tests"
21 |   ],
22 |   "dependencies": {
23 |     "angular": "~1.3.13"
24 |   },
25 |   "devDependencies": {
26 |     "polymer": "Polymer/polymer",
27 |     "angular-mocks": "~1.3.13"
28 |   }
29 | }
30 | 


--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     Smoke Test
 5 |     
 6 | 
 7 |     
 8 |     
 9 | 
10 |     
11 |     
12 |     
13 | 
14 |     
15 |     
20 | 
21 |     
22 |     
23 |   
24 |   
25 |     
26 |

Smoke Test: angular-bind-polymer

27 | 28 | 32 | 33 |
34 |

35 | in:
36 | 37 |


38 |       

39 |

40 | out: 41 |


42 |       

43 | 44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Apr 21 2014 21:54:40 GMT-0400 (EDT) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'bower_components/webcomponentsjs/webcomponents.js', 19 | 'bower_components/angular/angular.js', 20 | 'bower_components/angular*/angular-*.js', 21 | 'angular_bind_polymer.js', 22 | 23 | 'test/PolymerSetup.js', 24 | 25 | {pattern: 'bower_components/**', included: false, served: true}, 26 | {pattern: 'test/*.html', included: false, served: true}, 27 | 'test/*Spec.js' 28 | ], 29 | 30 | 31 | // list of files to exclude 32 | exclude: [ 33 | 34 | ], 35 | 36 | 37 | // preprocess matching files before serving them to the browser 38 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 39 | preprocessors: { 40 | 41 | }, 42 | 43 | 44 | // test results reporter to use 45 | // possible values: 'dots', 'progress' 46 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 47 | reporters: ['dots'], 48 | 49 | 50 | // web server port 51 | port: 9876, 52 | 53 | 54 | // enable / disable colors in the output (reporters and logs) 55 | colors: true, 56 | 57 | 58 | // level of logging 59 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 60 | logLevel: config.LOG_INFO, 61 | 62 | 63 | // enable / disable watching file and executing tests whenever any file changes 64 | autoWatch: true, 65 | 66 | 67 | // start these browsers 68 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 69 | browsers: ['Chrome'], 70 | 71 | 72 | // Continuous Integration mode 73 | // if true, Karma captures browsers, runs the tests and exits 74 | singleRun: false 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bind-polymer", 3 | "repository": "https://github.com/eee-c/angular-bind-polymer", 4 | "devDependencies": { 5 | "grunt": "^0.4.5", 6 | "grunt-contrib-watch": "^0.6.1", 7 | "karma": "^0.12.24", 8 | "karma-chrome-launcher": "^0.1.5", 9 | "karma-jasmine": "^0.2.2" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/BindingSpec.js: -------------------------------------------------------------------------------- 1 | describe('Custom Elements on their own', function(){ 2 | var container, customElement; 3 | beforeEach(function(done){ 4 | container = document.createElement('div'); 5 | document.body.appendChild(container); 6 | 7 | container.innerHTML = ''; 8 | customElement = container.children[0]; 9 | 10 | setTimeout(done, 0); 11 | }); 12 | 13 | afterEach(function(){ 14 | container.remove(); 15 | }); 16 | 17 | it('works', function(){ 18 | expect(customElement.getAttribute('out')).toEqual('84'); 19 | }); 20 | }); 21 | 22 | describe('Double binding', function(done){ 23 | // Build in setup, check expectations in tests 24 | var ngElement, polymerElement; 25 | 26 | beforeEach(function(done){ 27 | // The angular element is the first child (the
 tag)
 28 |     ngElement = container1.children[0];
 29 |     polymerElement = container1.children[1];
 30 | 
 31 |     polymerElement.setAttribute('in', '2');
 32 | 
 33 |     // Must wait one event loop for ??? to do its thing
 34 |     setTimeout(done, 0); // One event loop for Polymer to process
 35 |   });
 36 | 
 37 |   it('sees polymer update properly', function(){
 38 |     expect(polymerElement.getAttribute('out')).toEqual('4');
 39 |   });
 40 | 
 41 |   // The actual test
 42 |   it('sees values from polymer', function(){
 43 |     expect(ngElement.innerHTML).toEqual('4');
 44 |   });
 45 | });
 46 | 
 47 | describe('Custom Elements with dash attributes', function(){
 48 |   // Build in setup, check expectations in tests
 49 |   var ngElement, polymerElement;
 50 | 
 51 |   beforeEach(function(done){
 52 |     // The angular element is the first child (the 
 tag)
 53 |     ngElement = container2.children[0];
 54 |     polymerElement = container2.children[1];
 55 | 
 56 |     polymerElement.setAttribute('in', '2');
 57 | 
 58 |     // Must wait one event loop for ??? to do its thing
 59 |     setTimeout(done, 0); // One event loop for Polymer to process
 60 |   });
 61 | 
 62 |   it('sees polymer update properly', function(){
 63 |     expect(polymerElement.getAttribute('out-value')).toEqual('4');
 64 |   });
 65 | 
 66 |   // The actual test
 67 |   it('sees values from polymer', function(){
 68 |     expect(ngElement.innerHTML).toEqual('4');
 69 |   });
 70 | });
 71 | 
 72 | describe('Double binding multiple polymer instances', function(done){
 73 |   // Build in setup, check expectations in tests
 74 |   var ngElementA, polymerElementA;
 75 |   var ngElementB, polymerElementB;
 76 | 
 77 |   beforeEach(function(done){
 78 |     // The angular element is the first child (the 
 tag)
 79 |     ngElementA =      container3.children[0];
 80 |     polymerElementA = container3.children[1];
 81 |     ngElementB =      container3.children[2];
 82 |     polymerElementB = container3.children[3];
 83 | 
 84 |     polymerElementA.setAttribute('in', '2');
 85 |     polymerElementB.setAttribute('in', '4');
 86 | 
 87 |     // Must wait one event loop for ??? to do its thing
 88 |     setTimeout(done, 0); // One event loop for Polymer to process
 89 |   });
 90 | 
 91 |   it('sees first polymer update properly', function(){
 92 |     expect(polymerElementA.getAttribute('out')).toEqual('4');
 93 |   });
 94 | 
 95 |   // The actual test
 96 |   it('sees values from first polymer', function(){
 97 |     expect(ngElementA.innerHTML).toEqual('4');
 98 |   });
 99 | 
100 |   it('sees second polymer update properly', function(){
101 |     expect(polymerElementB.getAttribute('out')).toEqual('8');
102 |   });
103 | 
104 |   // The actual test
105 |   it('sees values from second polymer', function(){
106 |     expect(ngElementB.innerHTML).toEqual('8');
107 |   });
108 | });
109 | 
110 | describe('binding objects', function(){
111 |   // Build in setup, check expectations in tests
112 |   var ngElement, polymerElement;
113 | 
114 |   beforeEach(function(done){
115 |     // The angular element is the first child (the 
 tag)
116 |     ngElement = object_container.children[0];
117 |     polymerElement = object_container.children[1];
118 | 
119 |     polymerElement.setAttribute('in', '2');
120 | 
121 |     // Must wait one event loop for ??? to do its thing
122 |     setTimeout(done, 0); // One event loop for Polymer to process
123 |   });
124 | 
125 |   it('sees polymer update properly', function(){
126 |     expect(polymerElement.getAttribute('out')).toEqual('4');
127 |   });
128 | 
129 |   // The actual test
130 |   it('sees values from polymer', function(){
131 |     expect(ngElement.innerHTML).toEqual('4');
132 |   });
133 | });
134 | 


--------------------------------------------------------------------------------
/test/PolymerSetup.js:
--------------------------------------------------------------------------------
 1 | // 1. Load Polymer before any code that touches the DOM.
 2 | // *** This is done in karma.conf to avoid script loading race    ***
 3 | // *** conditions. It is OK to do it here once the Spec files are ***
 4 | // *** of non-trivial size.                                       ***
 5 | // var script = document.createElement("script");
 6 | // script.src = "/base/bower_components/platform/platform.js";
 7 | // document.getElementsByTagName("head")[0].appendChild(script);
 8 | 
 9 | // Container to hold angular and polymer elements
10 | var container = document.createElement('div');
11 | container.setAttribute('ng-app', 'acceptanceTest');
12 | document.body.appendChild(container);
13 | 
14 | var container1 = document.createElement('div');
15 | container1.innerHTML =
16 |   '
' +
17 |   '';
18 | container.appendChild(container1);
19 | 
20 | var container2 = document.createElement('div');
21 | container2.innerHTML =
22 |   '
' +
23 |   '';
24 | container.appendChild(container2);
25 | 
26 | var container3 = document.createElement('div');
27 | container3.innerHTML =
28 |   '
' +
29 |   '' +
30 |   '
' +
31 |   '';
32 | container.appendChild(container3);
33 | 
34 | var object_container = document.createElement('div');
35 | object_container.innerHTML =
36 |   '
' +
37 |   '';
38 | container.appendChild(object_container);
39 | 
40 | 
41 | // Load the angular-bind-polymer directive
42 | angular.module('acceptanceTest', [
43 |   'eee-c.angularBindPolymer'
44 | ]);
45 | 
46 | // Settimeout to give angular a chance to process the directive. Don't
47 | // care about mulitple link tags — it'll just get loaded once.
48 | beforeEach(function(done){
49 |   setTimeout(function(){
50 |       var link = document.createElement("link");
51 |       link.rel = "import";
52 |       link.href = "/base/test/x-double.html";
53 |       document.getElementsByTagName("head")[0].appendChild(link);
54 |       done();
55 |     },
56 |     0
57 |   );
58 | });
59 | 
60 | // Delay Jasmine specs until Polymer is ready
61 | var POLYMER_READY = false;
62 | beforeEach(function(done) {
63 |   function waitForPolymer() {
64 |     if (Polymer && Polymer.whenReady) {
65 |       Polymer.whenReady(done);
66 |       return;
67 |     }
68 |     if (Polymer && Polymer.whenPolymerReady) {
69 |       Polymer.whenPolymerReady(done);
70 |       return;
71 |     }
72 |     if (HTMLImports && HTMLImports.whenReady) {
73 |       HTMLImports.whenReady(done);
74 |       return;
75 |     }
76 |     setTimeout(waitForPolymer, 200);
77 |   }
78 |   waitForPolymer();
79 | 
80 |   if (POLYMER_READY) done();
81 | });
82 | 


--------------------------------------------------------------------------------
/test/x-double.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |   
31 | 
32 | 


--------------------------------------------------------------------------------