diff --git a/src/.jshintrc b/src/.jshintrc index b32113555d7b..1202b64447a3 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -138,6 +138,7 @@ "JQLitePrototype": false, "addEventListenerFn": false, "removeEventListenerFn": false, + "jqLiteIsTextNode": false, /* apis.js */ "hashKey": false, diff --git a/src/jqLite.js b/src/jqLite.js index 738f47a9b167..a471d3742bc0 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -179,6 +179,75 @@ function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArgu } } +var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; +var HTML_REGEXP = /<|&#?\w+;/; +var TAG_NAME_REGEXP = /<([\w:]+)/; +var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; + +var wrapMap = { + 'option': [1, ''], + + 'thead': [1, '', '
'], + 'col': [2, '', '
'], + 'tr': [2, '', '
'], + 'td': [3, '', '
'], + '_default': [0, "", ""] +}; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +function jqLiteIsTextNode(html) { + return !HTML_REGEXP.test(html); +} + +function jqLiteBuildFragment(html, context) { + var elem, tmp, tag, wrap, + fragment = context.createDocumentFragment(), + nodes = [], i, j, jj; + + if (jqLiteIsTextNode(html)) { + // Convert non-html into a text node + nodes.push(context.createTextNode(html)); + } else { + tmp = fragment.appendChild(context.createElement('div')); + // Convert html into DOM nodes + tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); + wrap = wrapMap[tag] || wrapMap._default; + tmp.innerHTML = '
 
' + + wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>") + wrap[2]; + tmp.removeChild(tmp.firstChild); + + // Descend through wrappers to the right content + i = wrap[0]; + while (i--) { + tmp = tmp.lastChild; + } + + for (j=0, jj=tmp.childNodes.length; j]*)?>/i; + CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/; // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes // The assumption is that future DOM event attribute names will begin with @@ -1259,7 +1258,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (directive.replace) { replaceDirective = directive; - $template = directiveTemplateContents(directiveValue); + if (jqLiteIsTextNode(directiveValue)) { + $template = []; + } else { + $template = jqLite(directiveValue); + } compileNode = $template[0]; if ($template.length != 1 || compileNode.nodeType !== 1) { @@ -1658,27 +1661,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } - function directiveTemplateContents(template) { - var type; - template = trim(template); - if ((type = TABLE_CONTENT_REGEXP.exec(template))) { - type = type[1].toLowerCase(); - var table = jqLite('' + template + '
'); - if (/(thead|tbody|tfoot)/.test(type)) { - return table.children(type); - } - table = table.children('tbody'); - if (type === 'tr') { - return table.children('tr'); - } - return table.children('tr').contents(); - } - return jqLite('
' + - template + - '
').contents(); - } - - function compileTemplateUrl(directives, $compileNode, tAttrs, $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { var linkQueue = [], @@ -1703,7 +1685,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { content = denormalizeTemplate(content); if (origAsyncDirective.replace) { - $template = directiveTemplateContents(content); + if (jqLiteIsTextNode(content)) { + $template = []; + } else { + $template = jqLite(content); + } compileNode = $template[0]; if ($template.length != 1 || compileNode.nodeType !== 1) { diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index faf1c98cbb8c..f9c6f3a2b55f 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -95,6 +95,36 @@ describe('jqLite', function() { expect(fragment.length).toBe(1); expect(fragment[0].nodeType).toBe(11); }); + + + it('should allow construction of ' + })); + directive('replaceWithOptgroup', valueFn({ + replace: true, + template: 'OPTGROUP' + })); })); @@ -746,6 +754,20 @@ describe('$compile', function() { }).not.toThrow(); expect(nodeName_(element)).toMatch(/tfoot/i); })); + + it('should support templates with root tags', inject(function($compile, $rootScope) { + expect(function() { + element = $compile('
')($rootScope); + }).not.toThrow(); + expect(nodeName_(element)).toMatch(/optgroup/i); + })); }); @@ -867,6 +889,14 @@ describe('$compile', function() { replace: true, templateUrl: 'tfoot.html' })); + directive('replaceWithOption', valueFn({ + replace: true, + templateUrl: 'option.html' + })); + directive('replaceWithOptgroup', valueFn({ + replace: true, + templateUrl: 'optgroup.html' + })); } )); @@ -1556,6 +1586,24 @@ describe('$compile', function() { $rootScope.$digest(); expect(nodeName_(element)).toMatch(/tfoot/i); })); + + it('should support templates with root '); + expect(function() { + element = $compile('
')($rootScope); + }).not.toThrow(); + $rootScope.$digest(); + expect(nodeName_(element)).toMatch(/option/i); + })); + + it('should support templates with root tags', inject(function($compile, $rootScope, $templateCache) { + $templateCache.put('optgroup.html', 'OPTGROUP'); + expect(function() { + element = $compile('
')($rootScope); + }).not.toThrow(); + $rootScope.$digest(); + expect(nodeName_(element)).toMatch(/optgroup/i); + })); });