Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Side by Side Diff: tools/dom/templates/html/impl/impl_Element.darttemplate

Issue 14732003: Implement Model-Driven-Views spec for Dart (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: comment tweaks Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of $LIBRARYNAME; 5 part of $LIBRARYNAME;
6 6
7 class _ChildrenElementList extends ListBase<Element> { 7 class _ChildrenElementList extends ListBase<Element> {
8 // Raw Element. 8 // Raw Element.
9 final Element _element; 9 final Element _element;
10 final HtmlCollection _childElements; 10 final HtmlCollection _childElements;
(...skipping 604 matching lines...) Expand 10 before | Expand all | Expand 10 after
615 } else if (JS('bool', '!!#.mozMatchesSelector', this)) { 615 } else if (JS('bool', '!!#.mozMatchesSelector', this)) {
616 return JS('bool', '#.mozMatchesSelector(#)', this, selectors); 616 return JS('bool', '#.mozMatchesSelector(#)', this, selectors);
617 } else if (JS('bool', '!!#.msMatchesSelector', this)) { 617 } else if (JS('bool', '!!#.msMatchesSelector', this)) {
618 return JS('bool', '#.msMatchesSelector(#)', this, selectors); 618 return JS('bool', '#.msMatchesSelector(#)', this, selectors);
619 } 619 }
620 throw new UnsupportedError("Not supported on this platform"); 620 throw new UnsupportedError("Not supported on this platform");
621 } 621 }
622 $else 622 $else
623 $endif 623 $endif
624 624
625 @Creates('Null')
626 Map<String, StreamSubscription> _attributeBindings;
627
628 // TODO(jmesserly): I'm concerned about adding these to every element.
629 // Conceptually all of these belong on TemplateElement. They are here to
630 // support browsers that don't have <template> yet.
631 // However even in the polyfill they're restricted to certain tags
632 // (see [isTemplate]). So we can probably convert it to a (public) mixin, and
633 // only mix it in to the elements that need it.
634 $if DART2JS
635 @Creates('Null') // Set from Dart code; does not instantiate a native type.
636 $endif
637 var _model;
638
639 $if DART2JS
640 @Creates('Null') // Set from Dart code; does not instantiate a native type.
641 $endif
642 _TemplateIterator _templateIterator;
643
644 $if DART2JS
645 @Creates('Null') // Set from Dart code; does not instantiate a native type.
646 $endif
647 Element _templateInstanceRef;
648
649 // Note: only used if `this is! TemplateElement`
650 $if DART2JS
651 @Creates('Null') // Set from Dart code; does not instantiate a native type.
652 $endif
653 DocumentFragment _templateContent;
654
655 bool _templateIsDecorated;
656
657 // TODO(jmesserly): should path be optional, and default to empty path?
658 // It is used that way in at least one path in JS TemplateElement tests
659 // (see "BindImperative" test in original JS code).
660 @Experimental
661 void bind(String name, model, String path) {
662 _bindElement(this, name, model, path);
663 }
664
665 // TODO(jmesserly): this is static to work around http://dartbug.com/10166
666 // Similar issue for unbind/unbindAll below.
667 static void _bindElement(Element self, String name, model, String path) {
668 if (self._bindTemplate(name, model, path)) return;
669
670 if (self._attributeBindings == null) {
671 self._attributeBindings = new Map<String, StreamSubscription>();
672 }
673
674 self.attributes.remove(name);
675
676 var changed;
677 if (name.endsWith('?')) {
678 name = name.substring(0, name.length - 1);
679
680 changed = (value) {
681 if (_templateBooleanConversion(value)) {
682 self.attributes[name] = '';
683 } else {
684 self.attributes.remove(name);
685 }
686 };
687 } else {
688 changed = (value) {
689 // TODO(jmesserly): escape value if needed to protect against XSS.
690 self.attributes[name] = value == null ? '' : '$value';
691 };
692 }
693
694 self.unbind(name);
695
696 self._attributeBindings[name] =
697 new DataBinding(model, path).bindSync(changed);
698 }
699
700 @Experimental
701 void unbind(String name) {
702 _unbindElement(this, name);
703 }
704
705 static _unbindElement(Element self, String name) {
706 if (self._unbindTemplate(name)) return;
707 if (self._attributeBindings != null) {
708 var binding = self._attributeBindings.remove(name);
709 if (binding != null) binding.cancel();
710 }
711 }
712
713 @Experimental
714 void unbindAll() {
715 _unbindAllElement(this);
716 }
717
718 static void _unbindAllElement(Element self) {
719 self._unbindAllTemplate();
720
721 if (self._attributeBindings != null) {
722 for (var binding in self._attributeBindings.values) {
723 binding.cancel();
724 }
725 self._attributeBindings = null;
726 }
727 }
728
729 // TODO(jmesserly): unlike the JS polyfill, we can't mixin
730 // HTMLTemplateElement at runtime into things that are semantically template
731 // elements. So instead we implement it here with a runtime check.
732 // If the bind succeeds, we return true, otherwise we return false and let
733 // the normal Element.bind logic kick in.
734 bool _bindTemplate(String name, model, String path) {
735 if (isTemplate) {
736 switch (name) {
737 case 'bind':
738 case 'repeat':
739 case 'if':
740 _ensureTemplate();
741 if (_templateIterator == null) {
742 _templateIterator = new _TemplateIterator(this);
743 }
744 _templateIterator.inputs.bind(name, model, path);
745 return true;
746 }
747 }
748 return false;
749 }
750
751 bool _unbindTemplate(String name) {
752 if (isTemplate) {
753 switch (name) {
754 case 'bind':
755 case 'repeat':
756 case 'if':
757 _ensureTemplate();
758 if (_templateIterator != null) {
759 _templateIterator.inputs.unbind(name);
760 }
761 return true;
762 }
763 }
764 return false;
765 }
766
767 void _unbindAllTemplate() {
768 if (isTemplate) {
769 unbind('bind');
770 unbind('repeat');
771 unbind('if');
772 }
773 }
774
775 /**
776 * Gets the template this node refers to.
777 * This is only supported if [isTemplate] is true.
778 */
779 @Experimental
780 Element get ref {
781 _ensureTemplate();
782
783 Element ref = null;
784 var refId = attributes['ref'];
785 if (refId != null) {
786 ref = document.getElementById(refId);
787 }
788
789 return ref != null ? ref : _templateInstanceRef;
790 }
791
792 /**
793 * Gets the content of this template.
794 * This is only supported if [isTemplate] is true.
795 */
796 @Experimental
797 DocumentFragment get content {
798 _ensureTemplate();
799 return _templateContent;
800 }
801
802 /**
803 * Creates an instance of the template.
804 * This is only supported if [isTemplate] is true.
805 */
806 @Experimental
807 DocumentFragment createInstance() {
808 _ensureTemplate();
809
810 var template = ref;
811 if (template == null) template = this;
812
813 var instance = _createDeepCloneAndDecorateTemplates(template.content,
814 attributes['syntax']);
815
816 if (TemplateElement._instanceCreated != null) {
817 TemplateElement._instanceCreated.add(instance);
818 }
819 return instance;
820 }
821
822 /**
823 * The data model which is inherited through the tree.
824 * This is only supported if [isTemplate] is true.
825 *
826 * Setting this will destructive propagate the value to all descendant nodes,
827 * and reinstantiate all of the nodes expanded by this template.
828 *
829 * Currently this does not support propagation through Shadow DOMs.
830 */
831 @Experimental
832 get model => _model;
833
834 @Experimental
835 void set model(value) {
836 _ensureTemplate();
837
838 _model = value;
839 _addBindings(this, model);
840 }
841
842 // TODO(jmesserly): const set would be better
843 static const _TABLE_TAGS = const {
844 'caption': null,
845 'col': null,
846 'colgroup': null,
847 'tbody': null,
848 'td': null,
849 'tfoot': null,
850 'th': null,
851 'thead': null,
852 'tr': null,
853 };
854
855 bool get _isAttributeTemplate => attributes.containsKey('template') &&
856 (localName == 'option' || _TABLE_TAGS.containsKey(localName));
857
858 /**
859 * Returns true if this node is a template.
860 *
861 * A node is a template if [tagName] is TEMPLATE, or the node has the
862 * 'template' attribute and this tag supports attribute form for backwards
863 * compatibility with existing HTML parsers. The nodes that can use attribute
864 * form are table elments (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP
865 * and COL) and OPTION.
866 */
867 // TODO(jmesserly): this is not a public MDV API, but it seems like a useful
868 // place to document which tags our polyfill considers to be templates.
869 // Otherwise I'd be repeating it in several other places.
870 // See if we can replace this with a TemplateMixin.
871 @Experimental
872 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate;
873
874 void _ensureTemplate() {
875 if (!isTemplate) {
876 throw new UnsupportedError('$this is not a template.');
877 }
878 TemplateElement.decorate(this);
879 }
880
625 $!MEMBERS 881 $!MEMBERS
626 } 882 }
627 883
884
628 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); 885 final _START_TAG_REGEXP = new RegExp('<(\\w+)');
629 class _ElementFactoryProvider { 886 class _ElementFactoryProvider {
630 static const _CUSTOM_PARENT_TAG_MAP = const { 887 static const _CUSTOM_PARENT_TAG_MAP = const {
631 'body' : 'html', 888 'body' : 'html',
632 'head' : 'html', 889 'head' : 'html',
633 'caption' : 'table', 890 'caption' : 'table',
634 'td': 'tr', 891 'td': 'tr',
635 'th': 'tr', 892 'th': 'tr',
636 'colgroup': 'table', 893 'colgroup': 'table',
637 'col' : 'colgroup', 894 'col' : 'colgroup',
638 'tr' : 'tbody', 895 'tr' : 'tbody',
639 'tbody' : 'table', 896 'tbody' : 'table',
640 'tfoot' : 'table', 897 'tfoot' : 'table',
641 'thead' : 'table', 898 'thead' : 'table',
642 'track' : 'audio', 899 'track' : 'audio',
643 }; 900 };
644 901
645 // TODO(jmesserly): const set would be better
646 static const _TABLE_TAGS = const {
647 'caption': null,
648 'col': null,
649 'colgroup': null,
650 'tbody': null,
651 'td': null,
652 'tfoot': null,
653 'th': null,
654 'thead': null,
655 'tr': null,
656 };
657
658 @DomName('Document.createElement') 902 @DomName('Document.createElement')
659 static Element createElement_html(String html) { 903 static Element createElement_html(String html) {
660 // TODO(jacobr): this method can be made more robust and performant. 904 // TODO(jacobr): this method can be made more robust and performant.
661 // 1) Cache the dummy parent elements required to use innerHTML rather than 905 // 1) Cache the dummy parent elements required to use innerHTML rather than
662 // creating them every call. 906 // creating them every call.
663 // 2) Verify that the html does not contain leading or trailing text nodes. 907 // 2) Verify that the html does not contain leading or trailing text nodes.
664 // 3) Verify that the html does not contain both <head> and <body> tags. 908 // 3) Verify that the html does not contain both <head> and <body> tags.
665 // 4) Detatch the created element from its dummy parent. 909 // 4) Detatch the created element from its dummy parent.
666 String parentTag = 'div'; 910 String parentTag = 'div';
667 String tag; 911 String tag;
668 final match = _START_TAG_REGEXP.firstMatch(html); 912 final match = _START_TAG_REGEXP.firstMatch(html);
669 if (match != null) { 913 if (match != null) {
670 tag = match.group(1).toLowerCase(); 914 tag = match.group(1).toLowerCase();
671 if (Device.isIE && _TABLE_TAGS.containsKey(tag)) { 915 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) {
672 return _createTableForIE(html, tag); 916 return _createTableForIE(html, tag);
673 } 917 }
674 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; 918 parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
675 if (parentTag == null) parentTag = 'div'; 919 if (parentTag == null) parentTag = 'div';
676 } 920 }
677 921
678 final temp = new Element.tag(parentTag); 922 final temp = new Element.tag(parentTag);
679 temp.innerHtml = html; 923 temp.innerHtml = html;
680 924
681 Element element; 925 Element element;
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
774 const ScrollAlignment._internal(this._value); 1018 const ScrollAlignment._internal(this._value);
775 toString() => 'ScrollAlignment.$_value'; 1019 toString() => 'ScrollAlignment.$_value';
776 1020
777 /// Attempt to align the element to the top of the scrollable area. 1021 /// Attempt to align the element to the top of the scrollable area.
778 static const TOP = const ScrollAlignment._internal('TOP'); 1022 static const TOP = const ScrollAlignment._internal('TOP');
779 /// Attempt to center the element in the scrollable area. 1023 /// Attempt to center the element in the scrollable area.
780 static const CENTER = const ScrollAlignment._internal('CENTER'); 1024 static const CENTER = const ScrollAlignment._internal('CENTER');
781 /// Attempt to align the element to the bottom of the scrollable area. 1025 /// Attempt to align the element to the bottom of the scrollable area.
782 static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); 1026 static const BOTTOM = const ScrollAlignment._internal('BOTTOM');
783 } 1027 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698