如何在点击时调用自定义元素成员方法?

时间:2014-12-18 20:20:26

标签: javascript angularjs web-component

AKA访问自定义元素方法时如何绕过或避开Referencing DOM nodes in Angular expressions is disallowed

所以我正在使用David Shapiro的小型库angular-custom-element来试验自定义元素等功能,以便将来证明我们的组件。我已经走得很远,他们似乎注册了。但是,在该示例中,它尝试在指令示例中显示在ng-click内调用元素的成员函数。

 template: '<div ng-click="el.elementMethod()">{{ el.propertyNameOne }} CUSTEM ELEMENT CLICK ME</div>',

以下是完整代码和可运行代码段。在您单击“CUSTEM ELEMENT”

之前,它不会抛出错误

我尝试在方法中添加'return true'来绕过其他帖子建议的安全性,但无济于事。是否有替代实施?

angular.module('appy', []);

angular.module('appy.elementDirectives', ['customElements']);

var app = angular.module('appy', [
    'appy',
    'appy.elementDirectives'
]);



app.config( function($customElementsProvider ) {
        'use strict';           
        var definitions =  [
                {
                  name: 'shiny-button',
                  definition: {
                    parent: HTMLButtonElement,
                    properties: {
    
                        propertyNameOne: {
                            get: function () {
                                // do any value calculations
                                var valueVar = 'testFooValue';
                                return valueVar;
                            },
                            set: function (val) {
                                // do any value calculations
                                val = val + 'X';
                                return val;
                            },
                            attribute: {
                                name: 'property-one'
                            }
                        },
                        propertyNameTwo: {
    
                            attribute: {},
                            value: 'hello',
                            readOnly: true
                        },
                        booleanProperty: {
                            attribute: {
                                name: 'bool-prop',
                                // note that "true" here just signifies that the attr should
                                // treated as a boolean,
                                boolean: true // default is false
                            },
                            value: true // default is false
                        }
                    },
    
                    // In all callbacks "this" referes to the element instance
                    callbacks: {
                        created: function () {
                          console.log('created')
                          
                        },
    
                        // is called when the element is inserted into the DOM
                        attached: function () {
                            console.log('attached')
                        },
    
                        // is called when the element is removed from the DOM
                        detached: function () {
                            // include any cleanup logic
                            console.log('detached')
                        },
    
                        // called upon any attribute change including attr set programatically
                        // during element instantiation (but not if the elem already exists in markup)
                        attributeChanged: function (attr, oldVal, newVal) {
                            console.log('attributeChanged', attr, oldVal, newVal)
                        }
                    },
                    members: {
    
                        elementMethod: function (args) {
                            alert("member click");
                            console.log('clicked member');
                            return true;
                        },
    
    
                        memberNameOne: {
                            get: function (val) {
                                return val;
                            },
                            set: function (val) {
                                val = val + 'X';
                                return val;
                            },
                            value: 'blah blah',
                            readOnly: true
                      }
                    } 
                }
            }];

        console.log("Register Element");
        //$customElementsProvider.register( 'shiny-button', definitions[0].definition );
        $customElementsProvider.registerCollection(definitions);
});

 

app.controller('MainCtrl', function($scope) { 
  var that = this;
  
  $scope.showTest = true;
  
   $scope.testButtonClick = function(){
      alert("testButton click")
    }
  
});

app.directive('shinyButton', function($customElements) {
  return {
            restrict:'E',
            scope: {
                nothingHereYet: '=propertyNameOne'
            },
            replace: false,
            template: '<div ng-click="el.elementMethod()">{{ el.propertyNameOne }} CUSTEM ELEMENT CLICK ME</div>',
           
            controllerAs:'shinyCtrl',
            
            // 3.4 at a minimum inject $scope and $element into your controller
                    controller: function($scope, $element, $attrs, $document, $log){

                        // 4. This is the only line of code that is required.
                        // this command takes care of binding all custom properties
                        // to the $scope including triggering a $digest() when
                        // any custom property is changed outside of Angular
                        // After this line you can enjoy the full power of AngularJS
                        // when interacting with your Custom Element
                        $customElements.$watchElement($scope, $element);

                        $document.on('member:changed', function(evt){
                            if(evt.detail.propName == 'a protopype prop we need to watch'){
                                // i.e. $scope.el.__proto__.memberNameOne
                                $scope.$digest();
                            }
                        });

                        // 4.2
                        // bind to an event on the element
                        // since all prop changes generate a change event
                        // other frameworks in the page can import and react
                        // to the same component
                        $element.on('prop:changed', function(evt){
                            $log.log(evt.detail); 
                            $scope.$emit(evt.detail);
                        });
                        
                        
                        

                        // 4.3
                        // gets the original custom elem config obj mostly for any debug
                        var info = $customElements.info($element);

                        //$customElements.$importElement($scope, $element, ['array','of','property','names']);

                    },
                    link: function(scope, iElement, iAttrs, controller){
                        // ...
                    }
            
        };
});





/*! (C) WebReflection Mit Style License */

/*
 * AngularCustomElement
 * https://github.com/dgs700/angular-custom-element

 * Version: 0.2.0 - 2014-11-01
 * License: MIT
 */
 
 
 
 /* Requires a Custom Element polyfill for all browsers other than Chrome
 Recommended:  https://github.com/WebReflection/document-register-element */
/*! (C) WebReflection Mit Style License */
(function(e,t,n,r){"use strict";function q(e,t){for(var n=0,r=e.length;n<r;n++)J(e[n],t)}function R(e){for(var t=0,n=e.length,r;t<n;t++)r=e[t],$(r,c[z(r)])}function U(e){return function(t){g.call(L,t)&&(J(t,e),q(t.querySelectorAll(h),e))}}function z(e){var t=e.getAttribute("is");return d.call(l,t?t.toUpperCase():e.nodeName)}function W(e){var t=e.currentTarget,n=e.attrChange,r=e.prevValue,i=e.newValue;t.attributeChangedCallback&&e.attrName!=="style"&&t.attributeChangedCallback(e.attrName,n===e.ADDITION?null:r,n===e.REMOVAL?null:i)}function X(e){var t=U(e);return function(e){t(e.target)}}function V(e,t){var n=this;O.call(n,e,t),B.call(n,{target:n})}function $(e,t){N(e,t),I?I.observe(e,_):(H&&(e.setAttribute=V,e[i]=F(e),e.addEventListener(u,B)),e.addEventListener(o,W)),e.createdCallback&&(e.created=!0,e.createdCallback(),e.created=!1)}function J(e,t){var n,r=z(e),i="attached",s="detached";-1<r&&(C(e,c[r]),r=0,t===i&&!e[i]?(e[s]=!1,e[i]=!0,r=1):t===s&&!e[s]&&(e[i]=!1,e[s]=!0,r=1),r&&(n=e[t+"Callback"])&&n.call(e))}if(r in t)return;var i="__"+r+(Math.random()*1e5>>0),s="extends",o="DOMAttrModified",u="DOMSubtreeModified",a=/^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/,f=["ANNOTATION-XML","COLOR-PROFILE","FONT-FACE","FONT-FACE-SRC","FONT-FACE-URI","FONT-FACE-FORMAT","FONT-FACE-NAME","MISSING-GLYPH"],l=[],c=[],h="",p=t.documentElement,d=l.indexOf||function(e){for(var t=this.length;t--&&this[t]!==e;);return t},v=n.prototype,m=v.hasOwnProperty,g=v.isPrototypeOf,y=n.defineProperty,b=n.getOwnPropertyDescriptor,w=n.getOwnPropertyNames,E=n.getPrototypeOf,S=n.setPrototypeOf,x=!!n.__proto__,T=n.create||function K(e){return e?(K.prototype=e,new K):this},N=S||(x?function(e,t){return e.__proto__=t,e}:w&&b?function(){function e(e,t){for(var n,r=w(t),i=0,s=r.length;i<s;i++)n=r[i],m.call(e,n)||y(e,n,b(t,n))}return function(t,n){do e(t,n);while(n=E(n));return t}}():function(e,t){for(var n in t)e[n]=t[n];return e}),C=S||x?function(e,t){g.call(t,e)||$(e,t)}:function(e,t){e[i]||(e[i]=n(!0),$(e,t))},k=e.MutationObserver||e.WebKitMutationObserver,L=(e.HTMLElement||e.Element||e.Node).prototype,A=L.cloneNode,O=L.setAttribute,M=t.createElement,_=k&&{attributes:!0,characterData:!0,attributeOldValue:!0},D=k||function(e){H=!1,p.removeEventListener(o,D)},P=!1,H=!0,B,j,F,I;k||(p.addEventListener(o,D),p.setAttribute(i,1),p.removeAttribute(i),H&&(B=function(e){var t=this,n,r,s;if(t===e.target){n=t[i],t[i]=r=F(t);for(s in r){if(!(s in n))return j(0,t,s,n[s],r[s],"ADDITION");if(r[s]!==n[s])return j(1,t,s,n[s],r[s],"MODIFICATION")}for(s in n)if(!(s in r))return j(2,t,s,n[s],r[s],"REMOVAL")}},j=function(e,t,n,r,i,s){var o={attrChange:e,currentTarget:t,attrName:n,prevValue:r,newValue:i};o[s]=e,W(o)},F=function(e){for(var t,n,r={},i=e.attributes,s=0,o=i.length;s<o;s++)t=i[s],n=t.name,n!=="setAttribute"&&(r[n]=t.value);return r})),t[r]=function(n,r){y=n.toUpperCase(),P||(P=!0,k?(I=function(e,t){function n(e,t){for(var n=0,r=e.length;n<r;t(e[n++]));}return new k(function(r){for(var i,s,o=0,u=r.length;o<u;o++)i=r[o],i.type==="childList"?(n(i.addedNodes,e),n(i.removedNodes,t)):(s=i.target,s.attributeChangedCallback&&i.attributeName!=="style"&&s.attributeChangedCallback(i.attributeName,i.oldValue,s.getAttribute(i.attributeName)))})}(U("attached"),U("detached")),I.observe(t,{childList:!0,subtree:!0})):(t.addEventListener("DOMNodeInserted",X("attached")),t.addEventListener("DOMNodeRemoved",X("detached"))),t.addEventListener("readystatechange",function(e){q(t.querySelectorAll(h),"attached")}),t.createElement=function(e,n){var r,i=M.apply(t,arguments);return n&&i.setAttribute("is",e=n.toLowerCase()),r=d.call(l,e.toUpperCase()),-1<r&&$(i,c[r]),i},L.cloneNode=function(e){var t=A.call(this,!!e),n=z(t);return-1<n&&$(t,c[n]),e&&R(t.querySelectorAll(h)),t});if(-1<d.call(l,y))throw new Error("A "+n+" type is already registered");if(!a.test(y)||-1<d.call(f,y))throw new Error("The type "+n+" is invalid");var i=function(){return t.createElement(p,u&&y)},o=r||v,u=m.call(o,s),p=u?r[s]:y,g=l.push(y)-1,y;return h=h.concat(h.length?",":"",u?p+'[is="'+n.toLowerCase()+'"]':p),i.prototype=c[g]=m.call(o,"prototype")?o.prototype:T(L),q(t.querySelectorAll(h),"attached"),i}})(window,document,Object,"registerElement");
/*
 * AngularCustomElement
 * https://github.com/dgs700/angular-custom-element

 * Version: 0.2.0 - 2014-11-01
 * License: MIT
 */
!function(){"use strict";function a(){function a(a,b){function c(a){var b=parseFloat(a);return!isNaN(b)&&isFinite(b)?b:a}function e(a,b,c,d,e,f){if(!c)return b;c.setterCalled[e]=!0;try{e&&f?b?c.setAttribute(e,""):c.removeAttribute(e):e&&c.setAttribute(e,b),c.onPropChange(b)}catch(g){}return d=f?!!c[a]:c[a],c.propChangeNotify(c,a,b,d,e,"prop:changed"),b}if("string"!=typeof a||!/.*-.*/.test(a))return console.error("Invalid element name: ",a),this;if(a=a.toLowerCase(),d[a])return this;var f=b.parent?b.parent.prototype:b["extends"]?Object.create(document.createElement(b["extends"]).constructor).prototype:HTMLElement.prototype,g=b["extends"]||null,h=b.properties||{},i=b.members||{},j={prototype:{}},k=function(){};for(var l in i)!function(a,b){function c(a,b,c){return document.dispatchEvent(new CustomEvent("member:changed",{detail:{propName:c,newValue:a,oldValue:b||null}})),a}var d,e,f,g,h,i,l=this;"function"==typeof b[a]?j.prototype[a]={enumerable:!0,value:b[a]}:(d=b[a],i=d.readOnly||!1,g=d.value||null,e="function"==typeof d.get?function(){return g=d.get.call(l,g)}:function(){return g},f=i?k:"function"==typeof d.set?function(b){b=d.set.call(l,b),h=this[a]||null,g=c(b,h,a)}:function(b){h=this[a]||null,g=c(b,h,a)},j.prototype[a]={get:e,set:f,enumerable:!0})}(l,i);j.prototype.registerCallback={value:function(a,b){a.onPropChange=b},enumerable:!0},j.prototype.propChangeNotify={value:function(a,b,c,d,e,f){a.dispatchEvent(new CustomEvent(f,{detail:{propName:b,newValue:c,oldValue:d,attrName:e||null}}))},enumerable:!0},j.prototype.definition={value:b};var m,n=b.callbacks||{},o=n.created||k,p=n.attached||k,q=n.detached||k,r=n.attributeChanged||k,s={},t=[];for(m in h)!function(a,b){t.push(function(d){var f,g,h,i,l,m,n,o,p;o=!1,l=null,g=null,f=b[a],n=f.readOnly||!1,p=f.attribute?!0:!1,o=p&&f.attribute.boolean?!0:!1,p&&!n&&d?(g=f.attribute.name?f.attribute.name:a.toLowerCase(),s[g]={name:a,bool:o},o?l=d.hasAttribute(g)?!0:f.value?!0:!1:(l=d.hasAttribute(g)?d.getAttribute(g):f.value?f.value:null,l=c(l)),d.hasAttribute(g)||(o&&l?d.setAttribute(g,""):o?d.removeAttribute(g):d.setAttribute(g,l)),d.setterCalled[g]=!1):o&&!d?l=f.value?!0:!1:(l=f.value?f.value:null,l=c(l)),h="function"==typeof f.get?f.get:function(){return l},i=n?k:"function"==typeof f.set?function(b){b=f.set.call(d,b),b=e(a,b,d,m,g,o),l=b}:function(b){l=e(a,b,d,m,g,o)},d?Object.defineProperty(d,a,{get:h,set:i,enumerable:!0,configurable:!0}):j.prototype[a]={get:h,set:i,enumerable:!0}})}(m,h),t.forEach(function(a){a.call(this,null)});j.prototype.createdCallback={enumerable:!0,value:function(){this.setterCalled={};var a=this;t.forEach(function(b){b.call(a,a)});var b=o?o.apply(this,arguments):null;return b}},j.prototype.attributeChangedCallback={enumerable:!0,value:function(a,b,d){if(s[a]&&!this.setterCalled[a]){var e=s[a];e.bool&&""===d&&(d=!0),this.setterCalled[a]=!1,this[e.name]=e.bool?!!d:c(d)}var f=r?r.apply(this,arguments):null;return f}},j.prototype.attachedCallback={enumerable:!0,value:function(){this.classList.remove("unresolved");var a=p?p.apply(this,arguments):null;return a}},j.prototype.detachedCallback={enumerable:!0,value:function(){var a=q?q.apply(this,arguments):null;return a}};var u={prototype:Object.create(f,j.prototype)};return g&&(u["extends"]=g),d[a]=document.registerElement(a,u),this}function b(a){return Array.isArray(a)?(a.forEach(function(a){var b=a.name,c=a.definition;return"string"!=typeof b||c!==Object(c)?(console.warn("bad element definition format"),!1):void e.register(b,c)}),!0):(console.error("parameter to registerCollection must be an array"),!1)}function c(){return{info:function(a){return a[0].__proto__.definition},$watchElement:function(a,b,c){c=c||!1,a.el=b[0],c||a.el.registerCallback(a.el,function(){return setTimeout(function(){a.$digest()},0),!0})},$importElement:function(a,b,c,d){var e=null,f=null;return Array.isArray(c)&&(c.forEach(function(a,b,c){c[b]=c[b].toLowerCase()}),e=new MutationObserver(function(b){b.forEach(function(b){-1!==c.indexOf(b.attributeName)&&a.$digest()})}).observe(b[0],{attributes:!0,childList:!0,characterData:!0,attributeOldValue:!0})),d&&(f=b.addEventListener(d,function(){a.$digest()})),Array.isArray(c)&&d?{observer:e,eventBinding:f}:!1}}}var d={};window.registeredElements=this.registeredElements=d,this.register=a;var e=this;this.registerCollection=b,this.$get=c,c.$inject=["$window"]}angular.module("customElements",["ng"]).provider("$customElements",a)}(window);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE html>
<html ng-app="appy">

<head>
  <meta charset="utf-8" />
  <title>AngularJS Plunker</title>
  <link data-require="bootstrap-css@*" data-semver="3.2.0" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />
  <script>
    document.write('<base href="' + document.location + '" />');
  </script>
  <link rel="stylesheet" href="style.css" />
  <script data-require="angular.js@1.2.x" src="https://code.angularjs.org/1.2.6/angular.js" data-semver="1.2.25"></script>
  <script  src="angular-custom-element.js"></script>

</head>
<body ng-controller="MainCtrl as vm">

<shiny-button>this will be replaced by directive template</shiny-button>

<br/><br/>

<div ng-show="showTest" ng-click="testButtonClick()">Test Click Div</div>


</html>

1 个答案:

答案 0 :(得分:1)

这是因为AngularJS 1.x还不是Web组件友好的。它假定你必须是愚蠢的,如果你直接从角度宇宙中的w /引用DOM属性,因为该代码可能是恶意的,或者它只是违反了原始的概念,即角度代码应该完全从外部世界封装,框架处理所有外部相互作用。虽然在大多数情况下都是如此,但在我们知道自定义元素很快将成为我们构建可重用UI组件的方式的核心之前,它就出现了。

在NG 2.0之前,你可能需要做这样的事情:

替换这个:

<div ng-click="el.elementMethod()">

用这个:

<div ng-click="callMember()">

$scope.callMember = function(){
    $scope.el.elementMethod();
};

使用替换代码没有错误。

但有趣的是,通过{{ el.propertyNameOne }}进行相同的DOM访问不会导致Angular抛出相同的错误。去图。