Merge remote-tracking branch 'mgiuffrida/master' into patch-1

This commit is contained in:
Michael Giuffrida 2016-02-03 21:45:59 -08:00
commit f960f251c1
127 changed files with 11912 additions and 3811 deletions

24
.travis.yml Normal file
View File

@ -0,0 +1,24 @@
language: node_js
sudo: false
node_js: stable
addons:
firefox: latest
sauce_connect: true
apt:
sources:
- google-chrome
- ubuntu-toolchain-r-test
packages:
- google-chrome-stable
- g++-4.8
before_script:
- npm install -g bower
- bower install
script:
- xvfb-run wct
- "if [ \"${TRAVIS_PULL_REQUEST}\" = \"false\" ]; then wct -s 'default'; fi"
env:
global:
- secure: eFrp9xwSYG579rAR9/XyXYKh+UtIQJ1xS5q5PABu4ndYFckdJb8o3m7oXqRSikbiPWbCSd3Fkd6+ZKXlcQFdjJ+nx9gFitGthtH93qkvZCO3XhWEEBPj65igIo3hrRSOB6dIWyiZcnoH9IXLLQtKXY9uL1NCqCcyVHg1omPKr9w=
- secure: EAjjkzqZ8z+PEvdo2N2MiIjkqUYAjVkQABKyMw6N4hUa6YSNEW9PYyn4I0d9Rdvc25GwJ+oLQFdeQepioAkNfp6wYUj2IdMIfmmKa1aJWo5DWvOMDYp3ufRhIoiVi4ZVpLg9sTjw+078UhUQFWE44rwfUtHiIb2UbWe2/ueLOiM=
- CXX=g++-4.8

View File

@ -1,5 +1,521 @@
# Change Log
##[v1.2.4](https://github.com/Polymer/polymer/tree/v1.2.4) (2016-01-27)
- Fixes #3337. When a doc fragment is added, only update the invalidation state of the insertion point list of the shadyRoot IFF it is not already invalid. This fixes an issue that was detected when an a doc fragment that did not include an insertion point was added after one that did but before distribution. ([commit](https://github.com/Polymer/polymer/commit/d26b003))
- fix build output with new vulcanize ([commit](https://github.com/Polymer/polymer/commit/c317711))
- Revert style properties change from fd5778470551f677c2aa5827398681abb1994a88 ([commit](https://github.com/Polymer/polymer/commit/0a0b580))
- Fix shadow dom test. ([commit](https://github.com/Polymer/polymer/commit/6b83911))
- Add shadow root support. (tests broken) ([commit](https://github.com/Polymer/polymer/commit/4b7da35))
- Ensure dom-if moved into doc fragment is torn down. Fixes #3324 ([commit](https://github.com/Polymer/polymer/commit/6c4f5d5))
- improve test. ([commit](https://github.com/Polymer/polymer/commit/d70c40a))
- Update comment. ([commit](https://github.com/Polymer/polymer/commit/aa14687))
- In addition to fragments, also handle non-distributed elements more completely. ([commit](https://github.com/Polymer/polymer/commit/fe2699e))
- Simplify fix for fragment children management. ([commit](https://github.com/Polymer/polymer/commit/713377e))
- Fix test under polypill. ([commit](https://github.com/Polymer/polymer/commit/25da63d))
- Ensure fragments added via Polymer.dom always have elements removed, even when distribution does not select those elements. ([commit](https://github.com/Polymer/polymer/commit/101eb3d))
- Fixes #3321. Only let dom-repeat insert elements in attached if it has been previously detached; correctly avoid re-adding children in document fragments to an element's logical linked list if they are already there. ([commit](https://github.com/Polymer/polymer/commit/9f2464d))
- Ugh ([commit](https://github.com/Polymer/polymer/commit/172d93c))
- Fixes #3308. Use an explicit undefined check to test if logical tree information exists. ([commit](https://github.com/Polymer/polymer/commit/9106398))
- add test ([commit](https://github.com/Polymer/polymer/commit/b1ea014))
- use class attribute in applyElementScopeSelector ([commit](https://github.com/Polymer/polymer/commit/07d8c06))
- Remove reference to _composedChildren ([commit](https://github.com/Polymer/polymer/commit/9f85acd))
- Fix typo in documentation for set() ([commit](https://github.com/Polymer/polymer/commit/aa47515))
- Fix typo in dom-tree-api ([commit](https://github.com/Polymer/polymer/commit/ae98a7c))
- Correct use of document.contains to document.documentElement.contains on IE. ([commit](https://github.com/Polymer/polymer/commit/0e74810))
- Ensure querySelector always returns `null` when a node is not found. Also optimize querySelector such that the matcher halts on the first result. ([commit](https://github.com/Polymer/polymer/commit/b9e5cce))
- Fixes #3295. Only cache a false-y result for an element's owner shady root iff the element is currently in the document. ([commit](https://github.com/Polymer/polymer/commit/6e16619))
- Use local references to wrapper functions; add test element tree to native shadow tests; reorder test elements. ([commit](https://github.com/Polymer/polymer/commit/47ee2ca))
- Remove leftover garbage line ([commit](https://github.com/Polymer/polymer/commit/d7567b7))
- Removes the case where activeElement could be in the light DOM of a ShadowRoot. ([commit](https://github.com/Polymer/polymer/commit/e848af8))
- DOM API implementation of `activeElement`. ([commit](https://github.com/Polymer/polymer/commit/2984576))
- Remove call to `wrap` in deepContains ([commit](https://github.com/Polymer/polymer/commit/4cbdef7))
- Fixes #3270. ([commit](https://github.com/Polymer/polymer/commit/7d0485b))
- Include more styling tests under ShadowDOM. Fix custom-style media query test to work under both shadow/shady. ([commit](https://github.com/Polymer/polymer/commit/33a24bb))
- Remove duplicate code related to dom traversal in Polymer.dom. ([commit](https://github.com/Polymer/polymer/commit/555252b))
- Fix parsing of minimized css output also for mixins ([commit](https://github.com/Polymer/polymer/commit/87d02e0))
- Set position to relative to make Safari to succeed top/bottom tests ([commit](https://github.com/Polymer/polymer/commit/94f505a))
- Fix parsing of minimized css output ([commit](https://github.com/Polymer/polymer/commit/f92f9ff))
- Fix for `Polymer.dom(...)._query()` method doesn't exist which causes `Polymer.updateStyles()` to fail ([commit](https://github.com/Polymer/polymer/commit/0eea7a6))
- Minor factoring of dom patching. ([commit](https://github.com/Polymer/polymer/commit/8c95014))
- use destination insertion points when calculating the path ([commit](https://github.com/Polymer/polymer/commit/3f8b6ee))
- Store all dom tree data in `__dom` private storage; implement composed patching via a linked list. ([commit](https://github.com/Polymer/polymer/commit/9a3bead))
- Modernize the build ([commit](https://github.com/Polymer/polymer/commit/2b69bb1))
- Add more globals to whitelist for safari ([commit](https://github.com/Polymer/polymer/commit/82b2443))
- Shady patching: patch element accessors in composed tree; fixes HTMLImports polyfill support. ([commit](https://github.com/Polymer/polymer/commit/d135fef))
- remove unused code; minor changes based on review. ([commit](https://github.com/Polymer/polymer/commit/c3fbd10))
- added polymer-mini and polymer-micro to main ([commit](https://github.com/Polymer/polymer/commit/da5d781))
- Updates the patch-don experiment to work with recent changes. ([commit](https://github.com/Polymer/polymer/commit/b9e6859))
- Fixes #3113 ([commit](https://github.com/Polymer/polymer/commit/fadd455))
- Polymer.dom: when adding a node, only remove the node from its existing location if it's not a fragment and has a parent. ([commit](https://github.com/Polymer/polymer/commit/9915627))
- Consistently use TreeApi.Composed api for composed dom manipulation; use TreeApi.Logical methods to get node leaves. Avoid making a Polymer.dom when TreeApi.Logical can provide the needed info. ([commit](https://github.com/Polymer/polymer/commit/5033fdb))
- Produce nicer error on malformed observer ([commit](https://github.com/Polymer/polymer/commit/0e248f5))
- Deduplicate setup and verifying in notify-path test suite ([commit](https://github.com/Polymer/polymer/commit/68707ad))
- more explicit tests for debouncer wait and no-wait behavior ([commit](https://github.com/Polymer/polymer/commit/8ef7bac))
- speed up microtask testing ([commit](https://github.com/Polymer/polymer/commit/9bef4c0))
- ensure isDebouncerActive returns a Boolean ([commit](https://github.com/Polymer/polymer/commit/3916493))
- add more debouncer tests ([commit](https://github.com/Polymer/polymer/commit/0206852))
- remove dead debounce test assertion ([commit](https://github.com/Polymer/polymer/commit/9b898c2))
- Factoring of distribution logic in both add and remove cases. ([commit](https://github.com/Polymer/polymer/commit/8272d5e))
- Minor typo in docs: call the debounce callback ([commit](https://github.com/Polymer/polymer/commit/02c5c79))
- Correct test to avoid using `firstElementChild` on a documentFragment since it is not universally supported. ([commit](https://github.com/Polymer/polymer/commit/dfa6a44))
- Remove all TODOs ([commit](https://github.com/Polymer/polymer/commit/6467ae1))
- Revert "Add .gitattributes to solve line endings cross-OS (merge after other PRs)" ([commit](https://github.com/Polymer/polymer/commit/b6b8293))
- Make renderedItemCount readOnly & add tests. ([commit](https://github.com/Polymer/polymer/commit/e39d5ba))
- Revert "Fix parsing of minimized css output" ([commit](https://github.com/Polymer/polymer/commit/d3145e8))
- Custom setProperty for bindings to hidden textNodes. Fixes #3157. ([commit](https://github.com/Polymer/polymer/commit/c6be10d))
- Ensure dom-if in host does not restamp when host detaches. Fixes #3125. ([commit](https://github.com/Polymer/polymer/commit/bb85e2b))
- Avoid making a copy of childNodes when a dom fragment is inserted in the logical tree. ([commit](https://github.com/Polymer/polymer/commit/dcbafbf))
- Slightly faster `findAnnotatedNodes` ([commit](https://github.com/Polymer/polymer/commit/43fc853))
- Add .gitattributes to solve line endings cross-OS ([commit](https://github.com/Polymer/polymer/commit/94c2bc2))
- Ensure literals are excluded from parent props. Fixes #3128. Fixes #3121. ([commit](https://github.com/Polymer/polymer/commit/526fa3c))
- Fix parsing of minimized css output ([commit](https://github.com/Polymer/polymer/commit/d458690))
- Disable chunked dom-repeat tests on IE due to CI rAF flakiness. ([commit](https://github.com/Polymer/polymer/commit/7fe5e2b))
- Add comment. ([commit](https://github.com/Polymer/polymer/commit/d8ecd45))
- Make Polymer.dom.flush reentrant-safe. Fixes #3115. ([commit](https://github.com/Polymer/polymer/commit/644105a))
- Fixes #3108. Moves `debounce` functionality from polymer-micro to polymer-mini. The functionality belongs at the mini tier and was never actually functional in micro. ([commit](https://github.com/Polymer/polymer/commit/3df4ef2))
- Clarify this is for IE. ([commit](https://github.com/Polymer/polymer/commit/63782fa))
- Patch rAF to setTimeout to reduce flakiness on CI. ([commit](https://github.com/Polymer/polymer/commit/35abadc))
- added missing semicolons, removed some unused variables ([commit](https://github.com/Polymer/polymer/commit/00ed797))
- ?Node ([commit](https://github.com/Polymer/polymer/commit/9385891))
- Remove closures holding element references after mouseup/touchend ([commit](https://github.com/Polymer/polymer/commit/811f766))
- set class attribute instead of using classname ([commit](https://github.com/Polymer/polymer/commit/690838a))
- Include wildcard character in identifier. Fixes #3084. ([commit](https://github.com/Polymer/polymer/commit/c36d6c1))
- Revert fromAbove in applyEffectValue. Add test. Fixes #3077. ([commit](https://github.com/Polymer/polymer/commit/156122c))
- loosen isLightDescendant's @param type to Node ([commit](https://github.com/Polymer/polymer/commit/c635797))
- Put beforeRegister in the behaviorProperties. ([commit](https://github.com/Polymer/polymer/commit/445b6cd))
- ES5 strict doesn't like function declarations inside inner blocks. ([commit](https://github.com/Polymer/polymer/commit/51d3fa6))
- Fixes #3065: Add dom-repeat.renderedItemCount property ([commit](https://github.com/Polymer/polymer/commit/b589f70))
- Minor factoring; ensure base properties set on instance. ([commit](https://github.com/Polymer/polymer/commit/da15ff0))
- Fix typos. ([commit](https://github.com/Polymer/polymer/commit/c12d3ed))
- Simplify more. ([commit](https://github.com/Polymer/polymer/commit/186e053))
- Improvements to regex. ([commit](https://github.com/Polymer/polymer/commit/a3d17d5))
- Give dom-repeat#_targetFrameTime a type ([commit](https://github.com/Polymer/polymer/commit/adad9ce))
- [skip ci] update travis config to firefox latest ([commit](https://github.com/Polymer/polymer/commit/608ce9f))
- Add a couple of tests. ([commit](https://github.com/Polymer/polymer/commit/108b7f9))
- Suppress warnings and expected errors in test suite ([commit](https://github.com/Polymer/polymer/commit/92d6fcb))
- Use linked-list for element tree traversal. Factor Polymer.DomApi into shadow/shady modules. ([commit](https://github.com/Polymer/polymer/commit/306cc81))
- Avoid throwing with invalid keys/paths. Fixes #3018. ([commit](https://github.com/Polymer/polymer/commit/5076ee0))
- Use stricter binding parsing for efficiency and correctness. Fixes #2705. ([commit](https://github.com/Polymer/polymer/commit/04cd184))
- Simpler travis config ([commit](https://github.com/Polymer/polymer/commit/68b457d))
- [ci skip] Update Changelog ([commit](https://github.com/Polymer/polymer/commit/7e7600a))
- Fix for incorrect CSS selectors specificity as reported in #2531 Fix for overriding mixin properties, fixes #1873 Added awareness from `@apply()` position among other rules so that it is preserved after CSS variables/mixing substitution. `Polymer.StyleUtil.clearStyleRules()` method removed as it is not used anywhere. Some unused variables removed. Typos, unused variables and unnecessary escaping in regexps corrected. Tests added. ([commit](https://github.com/Polymer/polymer/commit/fd57784))
- Fix for method parsing in computed binding ([commit](https://github.com/Polymer/polymer/commit/c2e43d3))
- Fix doc typo. ([commit](https://github.com/Polymer/polymer/commit/8886c2c))
- Filtering causes unexpected issues ([commit](https://github.com/Polymer/polymer/commit/df22564))
- Fix using value$ on input element ([commit](https://github.com/Polymer/polymer/commit/05a1e95))
- added missing semicolons, removed some unused variables ([commit](https://github.com/Polymer/polymer/commit/338574d))
##[v1.2.3](https://github.com/Polymer/polymer/tree/v1.2.3) (2015-11-16)
- Call decorate instead of bootstrap for template prepping ([commit](https://github.com/Polymer/polymer/commit/e2a2cfd))
- Fix global leak test. Necessary due to changes to test harness. ([commit](https://github.com/Polymer/polymer/commit/134766f))
- Defer property application only when a custom-style is first created. ([commit](https://github.com/Polymer/polymer/commit/4bf0e13))
- Update comment. ([commit](https://github.com/Polymer/polymer/commit/27e1dcd))
- Simplify custom-style property deferment. ([commit](https://github.com/Polymer/polymer/commit/a970493))
- [ci skip] update changelog ([commit](https://github.com/Polymer/polymer/commit/98acb3a))
- Fixes #2692. Ensures that custom-style properties are applied async but before next render so that all properties are defined before any are consumed by custom-styles. Also refines dom-module's early upgrade code so that it does not affect other elements (corrects for example, custom-styles upgrading before expected). ([commit](https://github.com/Polymer/polymer/commit/b829f2a))
- Remove undesired full-stop from outputs ([commit](https://github.com/Polymer/polymer/commit/68d5c55))
- Fix Formatting ([commit](https://github.com/Polymer/polymer/commit/724e1bc))
##[v1.2.2](https://github.com/Polymer/polymer/tree/v1.2.2) (2015-11-12)
- use local reference for wrap. ([commit](https://github.com/Polymer/polymer/commit/b15e5b9))
- Add Polymer.DomApi.wrap ([commit](https://github.com/Polymer/polymer/commit/6cf974a))
- For correctness, bind listeners must use a property's current value rather than its passed value. ([commit](https://github.com/Polymer/polymer/commit/aca404f))
- Explicitly making an element's `_template` falsy is now considered an allowable setting. This means the element stamps no content, doesn't collect any styles, and avoids looking up a dom-module. This helps address #2708 and the 5 elements Polymer registers that have no template have been set with `_template: null`. ([commit](https://github.com/Polymer/polymer/commit/b905a37))
- Make test work under native Shadow DOM. ([commit](https://github.com/Polymer/polymer/commit/4f9c2bd))
- In `_notifyListener`, only use `e.detail` if the event has a detail. This is necessary for `::eventName` compatibility where `eventName` is a native event like `change`. ([commit](https://github.com/Polymer/polymer/commit/3ece552))
- Fix TOC re: host event listeners. ([commit](https://github.com/Polymer/polymer/commit/ce32459))
- Fix compound bindings with braces in literals ([commit](https://github.com/Polymer/polymer/commit/561b28b))
- Re-enable listeners of the form 'a.b' (todo: make this more efficient). ([commit](https://github.com/Polymer/polymer/commit/139257b))
- Avoid stomping on property objects when mixing behaviors. ([commit](https://github.com/Polymer/polymer/commit/ec4d313))
- Update test to avoid template polypill issues. ([commit](https://github.com/Polymer/polymer/commit/fa96ff3))
- Ensure parent node exists when stamping. Fixes #2685. ([commit](https://github.com/Polymer/polymer/commit/62f2d2a))
- Add global leak test to runner. ([commit](https://github.com/Polymer/polymer/commit/dc2255c))
- Add global leak test. ([commit](https://github.com/Polymer/polymer/commit/7f71b4c))
- Fix typo that prevented correct functioning of Polymer.dom under Shadow DOM and add tests to catch. ([commit](https://github.com/Polymer/polymer/commit/cdc9fde))
- maintain compatibility with older `_notifyChange` arguments. ([commit](https://github.com/Polymer/polymer/commit/f5aec30))
- Weird assignment fix ([commit](https://github.com/Polymer/polymer/commit/9e6f77a))
- add comment. ([commit](https://github.com/Polymer/polymer/commit/f2d5f44))
- For efficiency, use cached events in data system, for property and path changes. ([commit](https://github.com/Polymer/polymer/commit/da71dfe))
- Fixes #2690 ([commit](https://github.com/Polymer/polymer/commit/d8b78d4))
- change after render method to `Polymer.RenderStatus.afterNextRender` ([commit](https://github.com/Polymer/polymer/commit/8949c04))
- When effect values are applied via bindings, use fromAbove gambit to avoid unnecessary wheel spinning. (This is now possible since we have fast lookup for readOnly where we want to avoid doing the set at all). ([commit](https://github.com/Polymer/polymer/commit/c520907))
- do readOnly check for configured properties where they are handed down, rather than when they are consumed. ([commit](https://github.com/Polymer/polymer/commit/24bcedb))
- Minor cleanup. ([commit](https://github.com/Polymer/polymer/commit/0b21506))
- Avoid creating unnecessary placeholders for full refresh. ([commit](https://github.com/Polymer/polymer/commit/996289a))
- Simplify ([commit](https://github.com/Polymer/polymer/commit/c5e1135))
- Fix typo. ([commit](https://github.com/Polymer/polymer/commit/680c56d))
- Update docs. ([commit](https://github.com/Polymer/polymer/commit/352ccbe))
- _removeInstance -> _detachAndRemoveInstance ([commit](https://github.com/Polymer/polymer/commit/ba7a16f))
- Remove limit & chunkCount API. Refactor insert/remove. ([commit](https://github.com/Polymer/polymer/commit/f447c0e))
- add back deepContains (got removed incorrectly in merge). ([commit](https://github.com/Polymer/polymer/commit/d53ab57))
- fix line endings. ([commit](https://github.com/Polymer/polymer/commit/0233d6d))
- revert host attributes ordering change optimization as it was not worth the trouble (barely measurable and more cumbersome impl). ([commit](https://github.com/Polymer/polymer/commit/f9894a0))
- rename host functions fix typos afterFirstRender is now raf+setTimeout dom-repeat: remove cruft ([commit](https://github.com/Polymer/polymer/commit/d82840b))
- Fix Gestures when using SD polyfill ([commit](https://github.com/Polymer/polymer/commit/96e4bfa))
- Fix for multiple consequent spaces present in CSS selectors, fixes #2670 ([commit](https://github.com/Polymer/polymer/commit/ecddb56))
- avoid configuration work when unnecessary ([commit](https://github.com/Polymer/polymer/commit/e0fbfbe))
- lazily create effect objects so we can more easily abort processing. avoid forEach ([commit](https://github.com/Polymer/polymer/commit/66df196))
- provides support for memoizing pathFn on effect; only process effects/listeners if they exist. ([commit](https://github.com/Polymer/polymer/commit/a2376b6))
- memoize pathFn on effect (note: notifyPath change made in previous commit); avoid forEach. ([commit](https://github.com/Polymer/polymer/commit/d93340a))
- Avoid using .slice and .forEach ([commit](https://github.com/Polymer/polymer/commit/d2c02a9))
- Added support for short unicode escape sequences, fixes #2650 ([commit](https://github.com/Polymer/polymer/commit/2c87145))
- Fix for BEM-like CSS selectors under media queries, fixes #2639. Small optimization for produced CSS (empty rules produced semicolon before, now empty string). ([commit](https://github.com/Polymer/polymer/commit/35c89f1))
- Fix parsing of custom properties with 'var' in value ([commit](https://github.com/Polymer/polymer/commit/61abfbd))
- Clean up cruft. ([commit](https://github.com/Polymer/polymer/commit/59c27fa))
- Add tests and fix issues. ([commit](https://github.com/Polymer/polymer/commit/e99e5fa))
- dom-repeat chunked/throttled render API ([commit](https://github.com/Polymer/polymer/commit/e9aebd7))
- Fix formatting. ([commit](https://github.com/Polymer/polymer/commit/56734a7))
- Add notes on running unit tests. ([commit](https://github.com/Polymer/polymer/commit/492f310))
- Corrected method name. Fixes #2649. ([commit](https://github.com/Polymer/polymer/commit/5168604))
- Fix typos in more efficient array copying. ([commit](https://github.com/Polymer/polymer/commit/53f3a7d))
- Adds `Polymer.RenderStatus.afterFirstRender` method. Call to perform tasks after an element first renders. ([commit](https://github.com/Polymer/polymer/commit/71b5c2a))
- More efficient array management in Polymer.DomApi. ([commit](https://github.com/Polymer/polymer/commit/320d5c7))
- Fixes #2652 ([commit](https://github.com/Polymer/polymer/commit/e35c4e9))
- [ci skip] update changelog ([commit](https://github.com/Polymer/polymer/commit/0dc69df))
- Fix whitespace around bindings. ([commit](https://github.com/Polymer/polymer/commit/d7d0ed6))
- Add support for `strip-whitespace`. Should fix #2511. ([commit](https://github.com/Polymer/polymer/commit/35a1b94))
- Improve efficiency of attribute configuration. ([commit](https://github.com/Polymer/polymer/commit/f7d86e9))
- Remove use of Function.bind ([commit](https://github.com/Polymer/polymer/commit/25aab8b))
- fix typos. ([commit](https://github.com/Polymer/polymer/commit/5fb20da))
- Re-use data change events. Remove unused/undocumented listener object specific node listening feature. ([commit](https://github.com/Polymer/polymer/commit/8bdedf3))
- Add flattened properties to dom-bind, templatizer, optimize by 'liming properties that are protected/private and not readOnly from list. ([commit](https://github.com/Polymer/polymer/commit/2ba08ec))
- Use flattened list of properties for fast access during configuration and attribute->property ([commit](https://github.com/Polymer/polymer/commit/acdd242))
- Assemble effect strings at prototype time. ([commit](https://github.com/Polymer/polymer/commit/4745e8f))
- Fallback to string lookup to fix support for extra effects. ([commit](https://github.com/Polymer/polymer/commit/d3c4611))
- Fix typo. ([commit](https://github.com/Polymer/polymer/commit/ead9adb))
- Correct NodeList copying. ([commit](https://github.com/Polymer/polymer/commit/1d29e19))
- Avoid Polymer.dom.setAttribute when unneeded. ([commit](https://github.com/Polymer/polymer/commit/9c5a404))
- More efficient iteration. ([commit](https://github.com/Polymer/polymer/commit/23a9a06))
- Avoid forEach ([commit](https://github.com/Polymer/polymer/commit/ebeaf80))
- Copy dom NodeList faster than slice. ([commit](https://github.com/Polymer/polymer/commit/8cad475))
- Avoid function lookup by string. ([commit](https://github.com/Polymer/polymer/commit/e2674bc))
- Add test for parsing multi-line css comments ([commit](https://github.com/Polymer/polymer/commit/6f21ae6))
##[v1.2.1](https://github.com/Polymer/polymer/tree/v1.2.1) (2015-10-29)
- Fix test for SD polyfill ([commit](https://github.com/Polymer/polymer/commit/dd8b3e9))
- Add pre-condition check for completeness. ([commit](https://github.com/Polymer/polymer/commit/89304dc))
- Find non distributed children with deepContains ([commit](https://github.com/Polymer/polymer/commit/8e6f55a))
- Ensure outer paths aren't forwarded to instance props. Fixes #2556. ([commit](https://github.com/Polymer/polymer/commit/01273e9))
- Add `Polymer.dom.deepContains` ([commit](https://github.com/Polymer/polymer/commit/279bf63))
- [ci skip] Update CHANGELOG ([commit](https://github.com/Polymer/polymer/commit/e1f83d2))
- isLightDescendant should return false for self ([commit](https://github.com/Polymer/polymer/commit/a0debf4))
- Fix for mixins declaration with space before colon. Allow any space character or even `{` and `}` (before and after capturing pattern correspondingly) as pattern boundaries instead of new lines only. In minified sources there might be no space, semicolon or line start, so we need to account that as well. ([commit](https://github.com/Polymer/polymer/commit/883aa5c))
##[v1.2.0](https://github.com/Polymer/polymer/tree/v1.2.0) (2015-10-22)
- A simpler travis config ([commit](https://github.com/Polymer/polymer/commit/3338b67))
- Fix #2587: When Polymer.dom(el).appendChild(node) is called, cleanup work must be performed on the existing parent of node. This change fixes a missing case in this cleanup work: if the existing parent has a observer via `Polymer.dom(parent).observeNodes`, it needs to be notified that node is being removed even if the node does not have specific logical info. For example, if an observed node has no Shady DOM and has a child that is removed. A test for this case was added. ([commit](https://github.com/Polymer/polymer/commit/0d4f418))
- add fancy travis status badge to the readme ([commit](https://github.com/Polymer/polymer/commit/e29fca8))
- Do not configure compound property/attribute binding if literal if empty. Fixes #2583. ([commit](https://github.com/Polymer/polymer/commit/ca4724a))
- Update .travis.yml ([commit](https://github.com/Polymer/polymer/commit/ef366c5))
- Remove web-component-tester cache. ([commit](https://github.com/Polymer/polymer/commit/4ae23ce))
- Fix IE10 regressions. Fixes #2582 * Copy attribute list before modifying it * Fall back to document for current document if no currentScript ([commit](https://github.com/Polymer/polymer/commit/ee65e68))
- Allow _atEndOfMicrotask to be patchable. ([commit](https://github.com/Polymer/polymer/commit/e2d8446))
- contributing copy fixup ([commit](https://github.com/Polymer/polymer/commit/ed22c50))
- Update CONTRIBUTING.md ([commit](https://github.com/Polymer/polymer/commit/0c21efc))
- Add travis config ([commit](https://github.com/Polymer/polymer/commit/6fb7684))
- Factor into functions. ([commit](https://github.com/Polymer/polymer/commit/b2117dc))
- Fix deepEqual on Safari 9 due to Safari enumeration bug. ([commit](https://github.com/Polymer/polymer/commit/445d603))
- ensure distribution observers see all changes that can come from attributes under native Shadow DOM; +minor factoring ([commit](https://github.com/Polymer/polymer/commit/344f5cc))
- Add <content>.getDistributedNodes observation. Refactor flush. ([commit](https://github.com/Polymer/polymer/commit/8b1face))
- Add docs ([commit](https://github.com/Polymer/polymer/commit/0ede79a))
- Make shadow attribute tracking automatic based on detecting a <content select> that depends on attributes; add tests. ([commit](https://github.com/Polymer/polymer/commit/54911a7))
- Add comments. ([commit](https://github.com/Polymer/polymer/commit/758c483))
- Fix typo. ([commit](https://github.com/Polymer/polymer/commit/74a87a0))
- Replace _compoundInitializationEffect with statically-initialized literals in the template for attributes & textContent, and by configuring literal values of properties in _configureAnnotationReferences. ([commit](https://github.com/Polymer/polymer/commit/2f1bd31))
- Simplify change tracking by always dirty checking at the observer level. Under Shadow DOM, use a deep MO to watch for attributes. ([commit](https://github.com/Polymer/polymer/commit/669acaa))
- Fix URL to component.kitchen ([commit](https://github.com/Polymer/polymer/commit/d9af504))
- Update the Google+ community link ([commit](https://github.com/Polymer/polymer/commit/c6684e5))
- Fixes from review. ([commit](https://github.com/Polymer/polymer/commit/a300862))
- Remove compound binding limitation from primer. ([commit](https://github.com/Polymer/polymer/commit/b1c1b35))
- Exclude compound bindings from configure; revisit later. ([commit](https://github.com/Polymer/polymer/commit/1035e2d))
- Apply effect value from compound parts. ([commit](https://github.com/Polymer/polymer/commit/c30ac10))
- Store binding parts in notes. ([commit](https://github.com/Polymer/polymer/commit/1026498))
- Fix missing var ([commit](https://github.com/Polymer/polymer/commit/68edb83))
- Add radix for correctness. ([commit](https://github.com/Polymer/polymer/commit/a79f012))
- Separate public & private get, flip conditions, add notifyPath API. ([commit](https://github.com/Polymer/polymer/commit/97503ec))
- Fix typo in comments. ([commit](https://github.com/Polymer/polymer/commit/e59dbef))
- Improvements to path API. Fixes #2509. * Allows `set` to take paths with array #keys * Allows `notifyPath` to take paths with array indices * Exposes public notifySplices API ([commit](https://github.com/Polymer/polymer/commit/10021cc))
- Fix merge issue. ([commit](https://github.com/Polymer/polymer/commit/85c23e1))
- Denote keys with # to disambiguate from index. Fixes #2007. ([commit](https://github.com/Polymer/polymer/commit/85d8a3a))
- update CHANGELOG to 1.1.5 ([commit](https://github.com/Polymer/polymer/commit/b2b23c4))
- make tests work on polyfill. ([commit](https://github.com/Polymer/polymer/commit/9ff2ee4))
- add `observeNodes` tests. ([commit](https://github.com/Polymer/polymer/commit/bd90b57))
- Add optional attribute tracking to support better distributed node notifications under shadow dom. ([commit](https://github.com/Polymer/polymer/commit/8242a98))
- Add `Polymer.dom().notifyObservers` method to 'kick' observers, for example, when attributes change under Shadow DOM. ([commit](https://github.com/Polymer/polymer/commit/07261e4))
- Add mutation tracking for distributedNodes. ([commit](https://github.com/Polymer/polymer/commit/b11f86b))
- Factor dom-api's into separate helpers. ([commit](https://github.com/Polymer/polymer/commit/effedcb))
- Adds `Polymer.dom(element).observeChildren(callback)` api ([commit](https://github.com/Polymer/polymer/commit/6499e83))
- Adds `getEffectiveChildNodes`, `getEffectiveChildren`, `getEffectiveTextContent` ([commit](https://github.com/Polymer/polymer/commit/f34fb45))
##[v1.1.5](https://github.com/Polymer/polymer/tree/v1.1.5) (2015-10-08)
- Simplify ([commit](https://github.com/Polymer/polymer/commit/79dfe1f))
- Clean up templatizer _pathEffectorImpl. ([commit](https://github.com/Polymer/polymer/commit/1a89bcf))
- Add issue link. ([commit](https://github.com/Polymer/polymer/commit/e4c2433))
- Missing var keyword ([commit](https://github.com/Polymer/polymer/commit/45fcbcf))
- Make sure we only actually call _listen once ([commit](https://github.com/Polymer/polymer/commit/837e9b8))
- Add templatizer tests. Fix issues from tests. ([commit](https://github.com/Polymer/polymer/commit/2d97cd7))
- Use 'value' in place of 'object' when referring to detail. ([commit](https://github.com/Polymer/polymer/commit/f17be35))
- Allow any type, not just objects, as the detail for fire. ([commit](https://github.com/Polymer/polymer/commit/ec59f57))
- Make model param of stamp method optional. ([commit](https://github.com/Polymer/polymer/commit/a2e1e64))
- add test to ensure unlisten events do not fire ([commit](https://github.com/Polymer/polymer/commit/bf2f694))
- add tests ([commit](https://github.com/Polymer/polymer/commit/900d82b))
- Only one real listener per `listen` call ([commit](https://github.com/Polymer/polymer/commit/8bd380a))
- add util method for shadow children ([commit](https://github.com/Polymer/polymer/commit/1e9110a))
- Add notify-path API to templatized template. Fixes #2505. ([commit](https://github.com/Polymer/polymer/commit/2e086fe))
- Parent property values should come from template. Fixes #2504. ([commit](https://github.com/Polymer/polymer/commit/23c883b))
- Added note about including a clear repro case. ([commit](https://github.com/Polymer/polymer/commit/e18f009))
- added request to submit an issue before sending a PR ([commit](https://github.com/Polymer/polymer/commit/6ed836f))
- update CHANGELOG to 1.1.4 ([commit](https://github.com/Polymer/polymer/commit/c2b7c31))
##[v1.1.4](https://github.com/Polymer/polymer/tree/v1.1.4) (2015-09-25)
- :memo: Update description ([commit](https://github.com/Polymer/polymer/commit/6afb8be))

View File

@ -2,9 +2,9 @@
There are many ways to contribute to the Polymer project! We welcome and truly appreciate contribution in all forms - issues and pull requests to the [main library](https://github.com/polymer/polymer), issues and pull requests to the [elements the Polymer team maintains](https://github.com/polymerelements), issues and pull requests to one of our many [Polymer-related tools](https://github.com/polymer), and of course we love to hear about any Polymer elements that you build to share with the community!
# Logistics
## Logistics
## Communicating with the Polymer team
### Communicating with the Polymer team
Beyond Github, we try to have a variety of different lines of communication open:
@ -14,17 +14,17 @@ Beyond Github, we try to have a variety of different lines of communication open
* [Mailing list](https://groups.google.com/forum/#!forum/polymer-dev)
* [Slack channel](https://bit.ly/polymerslack)
## The Polymer Repositories
### The Polymer Repositories
Because of the component-based nature of the Polymer project, we tend to have lots of different repositories. Our main repository for the Polymer library itself is at [github.com/Polymer/polymer](https://github.com/polymer/polymer). File any issues or pull requests that have to do with the core library on that repository, and we'll take a look ASAP.
We keep all of the element "product lines" that the Polymer team maintains and distributes in the [PolymerElements](https://github.com/polymerelements) organization. For any element-specific issues or pull requests, file directly on the element's repository, such as the `paper-button` repository at [github.com/polymerelements/paper-button](https://github.com/polymerelements/paper-button). Of course, the elements built by the Polymer team are just a tiny fraction of all the Polymer-based elements out there - catalogs of other web components include [customelements.io](http://www.customelements.io) and [component.kitchen](http://www.component.kitchen).
We keep all of the element "product lines" that the Polymer team maintains and distributes in the [PolymerElements](https://github.com/polymerelements) organization. For any element-specific issues or pull requests, file directly on the element's repository, such as the `paper-button` repository at [github.com/polymerelements/paper-button](https://github.com/polymerelements/paper-button). Of course, the elements built by the Polymer team are just a tiny fraction of all the Polymer-based elements out there - catalogs of other web components include [customelements.io](http://www.customelements.io) and [component.kitchen](http://component.kitchen).
The GoogleWebComponents element product line is maintained by teams all across Google, and so is kept in a separate organization: the [GoogleWebComponents](https://github.com/googlewebcomponents) org. Feel free to file issues and PR's on those elements directly in that organization.
We also track each element product line overall in "meta-repos", named as `$PRODUCTLINE-elements`. These include [paper-elements](https://github.com/polymerelements/paper-elements), [iron-elements](https://github.com/polymerelements/iron-elements), [gold-elements](https://github.com/polymerelements/gold-elements), and more. Feel free to file issues for element requests on those meta-repos, and the README in each repo tracks a roadmap for the product line.
## Contributor License Agreement
### Contributor License Agreement
You might notice our friendly CLA-bot commenting on a pull request you open if you haven't yet signed our CLA. We use the same CLA for all open-source Google projects, so you only have to sign it once. Once you complete the CLA, all your pull-requests will automatically get the `cla: yes` tag.
@ -32,9 +32,9 @@ If you've already signed a CLA but are still getting bothered by the awfully ins
[Complete the CLA](https://cla.developers.google.com/clas)
# Contributing
## Contributing
## Filing bugs
### Filing bugs
The Polymer team heavily uses (and loves!) Github for all of our software management. We use Github issues to track all bugs and features.
@ -46,14 +46,109 @@ We love examples for addressing issues - issues with a Plunkr, jsFiddle, or jsBi
Occasionally we'll close issues if they appear stale or are too vague - please don't take this personally! Please feel free to re-open issues we've closed if there's something we've missed and they still need to be addressed.
## Contributing Code to Elements
### Contributing Pull Requests
PR's are even better than issues. We gladly accept community pull requests. In general across the core library and all of the elements, there are a few necessary steps before we can accept a pull request:
- Open an issue describing the problem that you are looking to solve in your PR (if one is not already open), and your approach to solving it. This makes it easier to have a conversation around the best general approach for solving your problem, outside of the code itself.
- Sign the [CLA](https://cla.developers.google.com/clas), as described above.
- Fork the repo you're making the fix on to your own Github account.
- Code!
- Ideally, squash your commits into a single commit with a clear message of what the PR does. If it absolutely makes sense to keep multiple commits, that's OK - or perhaps consider making two separate PR's.
- **Include tests that test the range of behavior that changes with your PR.** If you PR fixes a bug, make sure your tests capture that bug. If your PR adds new behavior, make sure that behavior is fully tested. Every PR *must* include associated tests. (See [Unit tests](#unit-tests) for more.)
- Submit your PR, making sure it references the issue you created.
- If your PR fixes a bug, make sure the issue includes clear steps to reproduce the bug so we can test your fix.
If you've completed all of these steps the core team will do its best to respond to the PR as soon as possible.
#### Contributing Code to Elements
Though the aim of the Polymer library is to allow lots of flexibility and not get in your way, we work to standardize our elements to make them as toolable and easy to maintain as possible.
All elements should follow the [Polymer element style guide](http://polymerelements.github.io/style-guide/), which defines how to specify properties, documentation, and more. It's a great guide to follow when building your own elements as well, for maximum standardization and toolability. For instance, structuring elements following the style guide will ensure that they work with the [`iron-component-page`](https://github.com/polymerelements/iron-component-page) element, an incredibly easy way to turn any raw element directly into a documentation page.
## Contributing Code to the Polymer library
#### Contributing Code to the Polymer library
We follow the most common javascript and HTML style guidelines for how we structure our code - in general, look at the code and you'll know how to contribute! If you'd like a bit more structure, the [Google Javascript Styleguide](https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) is a good place to start.
Polymer also participates in Google's [Patch Rewards Program](http://www.google.com/about/appsecurity/patch-rewards/), where you can earn cold, hard cash for qualifying security patches to the Polymer library. Visit the [patch rewards page](http://www.google.com/about/appsecurity/patch-rewards/) to find out more.
## Unit tests
All Polymer projects use [`web-component-tester`](https://github.com/Polymer/web-component-tester) for unit tests.
The [`polyserve`](https://github.com/PolymerLabs/polyserve) utility is helpful for running tests in the browser.
For maximum flexibility, install `web-component-tester` and `polyserve` locally:
npm install -g polyserve web-component-tester
### Running the Polymer library unit tests
To run the Polymer library unit tests:
1. Clone the [Polymer repo](https://github.com/polymer/polymer).
2. Install the dependencies:
npm install && bower install
3. Run the tests:
npm test
Or if you have `web-component-tester` installed locally:
wct
To run individual test suites:
<code>npm test <var>path/to/suite</var></code>
Or:
<code>wct <var>path/to/suite</var></code>
For example:
npm test test/unit/template.html
You can also run tests in the browser:
polyserve
Navigate to:
[`http://localhost:8080/components/polymer/test/runner.html`](http://localhost:8080/components/polymer/test/runner.html)
### Running Polymer element unit tests
To run the element unit tests, you need a global
install of `web-component-tester` or `polyserve` (or both).
1. Clone the element repo.
2. Install the dependencies.
bower install
3. Run the tests:
wct
Or run the tests in a browser:
polyserve
Navigate to:
<code>http://localhost:8080/components/<var>element-name</var>/test/runner.html</code>
### Configuring `web-component-tester`
By default, `web-component-tester` runs tests on all installed browsers. You can configure it
to run tests on a subset of available browsers, or to run tests remotely using Sauce Labs.
See the [`web-component-tester` README](https://github.com/Polymer/web-component-tester) for
information on configuring the tool.

View File

@ -3,7 +3,7 @@
<a name="feature-list"></a>
Below is a description of current Polymer features, followed by individual feature guides.
See [the full Polymer.Base API documentation](http://polymer.github.io/polymer/) for details on specific methods and properties.
See [the full Polymer.Base API documentation](http://polymer.github.io/polymer/) for details on specific methods and properties.
<a name="polymer-micro"></a>
**Basic Custom Element sugaring**
@ -38,7 +38,7 @@ See [the full Polymer.Base API documentation](http://polymer.github.io/polymer/)
| Feature | Usage
|---------|-------
| [Local node marshaling](#node-marshaling) | this.$.\<id>
| [Event listener setup](#event-listeners)| listeners: { \<node>.\<event>: function, ... }
| [Host event listener setup](#event-listeners)| listeners: { \<event>: function, ... }
| [Annotated event listener setup](#annotated-listeners) | \<element on-[event]=”function”>
| [Gesture event support](#gesture-events) | \<element on-[gesture-event]=”function”>
@ -167,8 +167,8 @@ MyElement = Polymer({
is: 'my-element',
factoryImpl: function(foo, bar) {
el.foo = foo;
el.configureWithBar(bar);
this.foo = foo;
this.configureWithBar(bar);
},
configureWithBar: function(bar) {
@ -1085,17 +1085,10 @@ Polymer({
Properties of the custom element may be bound into text content or properties of local DOM elements using binding annotations in the template.
To bind to textContent, the binding annotation must currently span the entire content of the tag:
```html
<dom-module id="user-view">
<template>
<!-- Supported -->
First: <span>{{first}}</span><br>
Last: <span>{{last}}</span>
<!-- Not currently supported! -->
<div>First: {{first}}</div>
<div>Last: {{last}}</div>

View File

@ -1,5 +1,7 @@
# Polymer
[![Build Status](https://travis-ci.org/Polymer/polymer.svg?branch=master)](https://travis-ci.org/Polymer/polymer)
Polymer lets you build encapsulated, re-usable elements that work just like HTML elements, to use in building web applications.
```html
@ -82,7 +84,7 @@ Polymer({
<my-property-namecard my-name="Jim"></my-property-namecard>
```
> Hi! My name is Jim.
> Hi! My name is Jim
**Bind data into your element using the familiar mustache-syntax**
@ -113,7 +115,7 @@ Polymer({
<my-bound-namecard my-name="Josh"></my-bound-namecard>
```
> Hi! My name is Josh.
> Hi! My name is Josh
**Style the internals of your element, without the style leaking out**
@ -174,7 +176,7 @@ Beyond Github, we try to have a variety of different lines of communication avai
* [Blog](https://blog.polymer-project.org/)
* [Twitter](https://twitter.com/polymer)
* [Google+ Community](https://plus.sandbox.google.com/u/0/communities/115626364525706131031?cfem=1)
* [Google+ community](https://plus.google.com/communities/115626364525706131031)
* [Mailing list](https://groups.google.com/forum/#!forum/polymer-dev)
* [Slack channel](https://bit.ly/polymerslack)

View File

@ -1,8 +1,10 @@
{
"name": "polymer",
"version": "1.1.4",
"version": "1.2.4",
"main": [
"polymer.html"
"polymer.html",
"polymer-mini.html",
"polymer-micro.html"
],
"license": "http://polymer.github.io/LICENSE.txt",
"ignore": [
@ -18,7 +20,7 @@
"url": "https://github.com/Polymer/polymer.git"
},
"dependencies": {
"webcomponentsjs": "^0.7.2"
"webcomponentsjs": "^0.7.20"
},
"devDependencies": {
"web-component-tester": "*"

View File

@ -42,8 +42,10 @@ var cleanupPipe = lazypipe()
// remove leading whitespace and comments
.pipe(polyclean.leftAlignJs)
// remove html wrapper
.pipe(replace, '<html><head><meta charset="UTF-8">', '')
.pipe(replace, '</head><body></body></html>', '')
.pipe(replace, '<html><head>', '')
.pipe(replace, '<meta charset="UTF-8">', '')
.pipe(replace, '</head><body><div hidden="" by-vulcanize="">', '')
.pipe(replace, '</div></body></html>', '')
;
function vulcanizeWithExcludes(target, excludes) {

View File

@ -1,6 +1,6 @@
{
"name": "Polymer",
"version": "1.1.4",
"version": "1.2.4",
"description": "The Polymer library makes it easy to create your own web components. Give your element some markup and properties, and then use it on a site. Polymer provides features like dynamic templates and data binding to reduce the amount of boilerplate you need to write",
"main": "polymer.html",
"directories": {
@ -17,7 +17,7 @@
"lazypipe": "^0.2.3",
"polyclean": "^1.2.0",
"run-sequence": "^1.1.0",
"web-component-tester": "^3.3.21"
"web-component-tester": "^4"
},
"scripts": {
"build": "gulp",

View File

@ -14,7 +14,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<link rel="import" href="src/micro/constructor.html">
<link rel="import" href="src/micro/properties.html">
<link rel="import" href="src/micro/attributes.html">
<link rel="import" href="src/micro/debouncer.html">
<script>
Polymer.version = 'master';
@ -27,12 +26,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_registerFeatures: function() {
// identity
this._prepIs();
// attributes
this._prepAttributes();
// shared behaviors
this._prepBehaviors();
// factory
this._prepConstructor();
// fast access to property info
this._prepPropertyInfo();
},
_prepBehavior: function(b) {
@ -45,8 +44,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_initFeatures: function() {
// install host attributes
this._marshalHostAttributes();
// setup debouncers
this._setupDebouncers();
// acquire behaviors
this._marshalBehaviors();
}

View File

@ -14,6 +14,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<link rel="import" href="src/mini/ready.html">
<link rel="import" href="src/mini/shady.html">
<link rel="import" href="src/mini/shadow.html">
<link rel="import" href="src/mini/debouncer.html">
<script>
@ -24,8 +25,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_registerFeatures: function() {
// identity
this._prepIs();
// attributes
this._prepAttributes();
// shared behaviors
this._prepBehaviors();
// factory
@ -34,6 +33,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._prepTemplate();
// dom encapsulation
this._prepShady();
// fast access to property info
this._prepPropertyInfo();
},
_prepBehavior: function(b) {
@ -41,14 +42,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_initFeatures: function() {
// manage local dom
this._poolContent();
// host stack
this._pushHost();
// instantiate template
this._stampTemplate();
// host stack
this._popHost();
this._registerHost();
if (this._template) {
// manage local dom
this._poolContent();
// host stack
this._beginHosting();
// instantiate template
this._stampTemplate();
// host stack
this._endHosting();
}
// install host attributes
this._marshalHostAttributes();
// setup debouncers

View File

@ -28,8 +28,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_registerFeatures: function() {
// identity
this._prepIs();
// attributes
this._prepAttributes();
// factory
this._prepConstructor();
// template
@ -44,6 +42,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._prepEffects();
// shared behaviors
this._prepBehaviors();
// fast access to property info
this._prepPropertyInfo();
// accessors part 2
this._prepBindings();
// dom encapsulation
@ -57,28 +57,41 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_initFeatures: function() {
// manage local dom
this._poolContent();
// setup gestures
this._setupGestures();
// manage configuration
this._setupConfigure();
// setup style properties
this._setupStyleProperties();
// host stack
this._pushHost();
// instantiate template
this._stampTemplate();
// host stack
this._popHost();
// concretize template references
this._marshalAnnotationReferences();
// setup debouncers
this._setupDebouncers();
// setup shady
this._setupShady();
this._registerHost();
if (this._template) {
// manage local dom
this._poolContent();
// host stack
this._beginHosting();
// instantiate template
this._stampTemplate();
// host stack
this._endHosting();
// concretize template references
this._marshalAnnotationReferences();
}
// concretize effects on instance
this._marshalInstanceEffects();
// install host attributes
this._marshalHostAttributes();
// acquire instance behaviors
this._marshalBehaviors();
/*
TODO(sorvell): It's *slightly() more efficient to marshal attributes prior
to installing hostAttributes, but then hostAttributes must be separately
funneled to configure, which is cumbersome.
Since this delta seems hard to measure we will not bother atm.
*/
// install host attributes
this._marshalHostAttributes();
// acquire initial instance attribute values
this._marshalAttributes();
// top-down initial distribution, configuration, & ready callback
@ -87,7 +100,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_marshalBehavior: function(b) {
// establish listeners on instance
this._listenListeners(b.listeners);
if (b.listeners) {
this._listenListeners(b.listeners);
}
}
});

View File

@ -75,39 +75,117 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
parseAnnotations: function(template) {
var list = [];
var content = template._content || template.content;
this._parseNodeAnnotations(content, list);
this._parseNodeAnnotations(content, list,
template.hasAttribute('strip-whitespace'));
return list;
},
// add annotations gleaned from subtree at `node` to `list`
_parseNodeAnnotations: function(node, list) {
_parseNodeAnnotations: function(node, list, stripWhiteSpace) {
return node.nodeType === Node.TEXT_NODE ?
this._parseTextNodeAnnotation(node, list) :
// TODO(sjmiles): are there other nodes we may encounter
// that are not TEXT_NODE but also not ELEMENT?
this._parseElementAnnotations(node, list);
this._parseElementAnnotations(node, list, stripWhiteSpace);
},
_testEscape: function(value) {
var escape = value.slice(0, 2);
if (escape === '{{' || escape === '[[') {
return escape;
_bindingRegex: (function() {
var IDENT = '(?:' + '[a-zA-Z_$][\\w.:$-*]*' + ')';
var NUMBER = '(?:' + '[-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?' + ')';
var SQUOTE_STRING = '(?:' + '\'(?:[^\'\\\\]|\\\\.)*\'' + ')';
var DQUOTE_STRING = '(?:' + '"(?:[^"\\\\]|\\\\.)*"' + ')';
var STRING = '(?:' + SQUOTE_STRING + '|' + DQUOTE_STRING + ')';
var ARGUMENT = '(?:' + IDENT + '|' + NUMBER + '|' + STRING + '\\s*' + ')';
var ARGUMENTS = '(?:' + ARGUMENT + '(?:,\\s*' + ARGUMENT + ')*' + ')';
var ARGUMENT_LIST = '(?:' + '\\(\\s*' +
'(?:' + ARGUMENTS + '?' + ')' +
'\\)\\s*' + ')';
var BINDING = '(' + IDENT + '\\s*' + ARGUMENT_LIST + '?' + ')'; // Group 3
var OPEN_BRACKET = '(\\[\\[|{{)' + '\\s*';
var CLOSE_BRACKET = '(?:]]|}})';
var NEGATE = '(?:(!)\\s*)?'; // Group 2
var EXPRESSION = OPEN_BRACKET + NEGATE + BINDING + CLOSE_BRACKET;
return new RegExp(EXPRESSION, "g");
})(),
// TODO(kschaaf): We could modify this to allow an escape mechanism by
// looking for the escape sequence in each of the matches and converting
// the part back to a literal type, and then bailing if only literals
// were found
_parseBindings: function(text) {
var re = this._bindingRegex;
var parts = [];
var lastIndex = 0;
var m;
// Example: "literal1{{prop}}literal2[[!compute(foo,bar)]]final"
// Regex matches:
// Iteration 1: Iteration 2:
// m[1]: '{{' '[['
// m[2]: '' '!'
// m[3]: 'prop' 'compute(foo,bar)'
while ((m = re.exec(text)) !== null) {
// Add literal part
if (m.index > lastIndex) {
parts.push({literal: text.slice(lastIndex, m.index)});
}
// Add binding part
// Mode (one-way or two)
var mode = m[1][0];
var negate = Boolean(m[2]);
var value = m[3].trim();
var customEvent, notifyEvent, colon;
if (mode == '{' && (colon = value.indexOf('::')) > 0) {
notifyEvent = value.substring(colon + 2);
value = value.substring(0, colon);
customEvent = true;
}
parts.push({
compoundIndex: parts.length,
value: value,
mode: mode,
negate: negate,
event: notifyEvent,
customEvent: customEvent
});
lastIndex = re.lastIndex;
}
// Add a final literal part
if (lastIndex && lastIndex < text.length) {
var literal = text.substring(lastIndex);
if (literal) {
parts.push({
literal: literal
});
}
}
if (parts.length) {
return parts;
}
},
_literalFromParts: function(parts) {
var s = '';
for (var i=0; i<parts.length; i++) {
var literal = parts[i].literal;
s += literal || '';
}
return s;
},
// add annotations gleaned from TextNode `node` to `list`
_parseTextNodeAnnotation: function(node, list) {
var v = node.textContent;
var escape = this._testEscape(v);
if (escape) {
// NOTE: use a space here so the textNode remains; some browsers
// (IE) evacipate an empty textNode.
node.textContent = ' ';
var parts = this._parseBindings(node.textContent);
if (parts) {
// Initialize the textContent with any literal parts
// NOTE: default to a space here so the textNode remains; some browsers
// (IE) evacipate an empty textNode following cloneNode/importNode.
node.textContent = this._literalFromParts(parts) || ' ';
var annote = {
bindings: [{
kind: 'text',
mode: escape[0],
value: v.slice(2, -2).trim()
name: 'textContent',
parts: parts,
isCompound: parts.length !== 1
}]
};
list.push(annote);
@ -116,7 +194,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
// add annotations gleaned from Element `node` to `list`
_parseElementAnnotations: function(element, list) {
_parseElementAnnotations: function(element, list, stripWhiteSpace) {
var annote = {
bindings: [],
events: []
@ -124,7 +202,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (element.localName === 'content') {
list._hasContent = true;
}
this._parseChildNodesAnnotations(element, annote, list);
this._parseChildNodesAnnotations(element, annote, list, stripWhiteSpace);
// TODO(sjmiles): is this for non-ELEMENT nodes? If so, we should
// change the contract of this method, or filter these out above.
if (element.attributes) {
@ -145,30 +223,46 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// add annotations gleaned from children of `root` to `list`, `root`'s
// `annote` is supplied as it is the annote.parent of added annotations
_parseChildNodesAnnotations: function(root, annote, list, callback) {
_parseChildNodesAnnotations: function(root, annote, list, stripWhiteSpace) {
if (root.firstChild) {
for (var i=0, node=root.firstChild; node; node=node.nextSibling, i++) {
var node = root.firstChild;
var i = 0;
while (node) {
var next = node.nextSibling;
if (node.localName === 'template' &&
!node.hasAttribute('preserve-content')) {
this._parseTemplate(node, i, list, annote);
}
// collapse adjacent textNodes: fixes an IE issue that can cause
// collapse adjacent textNodes: fixes an IE issue that can cause
// text nodes to be inexplicably split =(
// note that root.normalize() should work but does not so we do this
// manually.
if (node.nodeType === Node.TEXT_NODE) {
var n = node.nextSibling;
var n = next;
while (n && (n.nodeType === Node.TEXT_NODE)) {
node.textContent += n.textContent;
next = n.nextSibling;
root.removeChild(n);
n = n.nextSibling;
n = next;
}
// optionally strip whitespace
if (stripWhiteSpace && !node.textContent.trim()) {
root.removeChild(node);
// decrement index since node is removed
i--;
}
}
var childAnnotation = this._parseNodeAnnotations(node, list, callback);
if (childAnnotation) {
childAnnotation.parent = annote;
childAnnotation.index = i;
// if this node didn't get evacipated, parse it.
if (node.parentNode) {
var childAnnotation = this._parseNodeAnnotations(node, list,
stripWhiteSpace);
if (childAnnotation) {
childAnnotation.parent = annote;
childAnnotation.index = i;
}
}
node = next;
i++;
}
}
},
@ -202,14 +296,15 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// TODO(sjmiles): the distinction between an `annotation` and
// `annotation data` is not as clear as it could be
_parseNodeAttributeAnnotations: function(node, annotation) {
for (var i=node.attributes.length-1, a; (a=node.attributes[i]); i--) {
var n = a.name, v = a.value;
// id (unless actually an escaped binding annotation)
if (n === 'id' && !this._testEscape(v)) {
annotation.id = v;
}
// Make copy of original attribute list, since the order may change
// as attributes are added and removed
var attrs = Array.prototype.slice.call(node.attributes);
for (var i=attrs.length-1, a; (a=attrs[i]); i--) {
var n = a.name;
var v = a.value;
var b;
// events (on-*)
else if (n.slice(0, 3) === 'on-') {
if (n.slice(0, 3) === 'on-') {
node.removeAttribute(n);
annotation.events.push({
name: n.slice(3),
@ -217,53 +312,41 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
}
// bindings (other attributes)
else {
var b = this._parseNodeAttributeAnnotation(node, n, v);
if (b) {
annotation.bindings.push(b);
}
else if (b = this._parseNodeAttributeAnnotation(node, n, v)) {
annotation.bindings.push(b);
}
// static id
else if (n === 'id') {
annotation.id = v;
}
}
},
// construct annotation data from a generic attribute, or undefined
_parseNodeAttributeAnnotation: function(node, n, v) {
var escape = this._testEscape(v);
if (escape) {
var customEvent;
// Cache name (`n` will be mangled)
var name = n;
// Mode (one-way or two)
var mode = escape[0];
v = v.slice(2, -2).trim();
// Negate
var not = false;
if (v[0] == '!') {
v = v.substring(1);
not = true;
}
_parseNodeAttributeAnnotation: function(node, name, value) {
var parts = this._parseBindings(value);
if (parts) {
// Attribute or property
var origName = name;
var kind = 'property';
if (n[n.length-1] == '$') {
name = n.slice(0, -1);
if (name[name.length-1] == '$') {
name = name.slice(0, -1);
kind = 'attribute';
}
// Custom notification event
var notifyEvent, colon;
if (mode == '{' && (colon = v.indexOf('::')) > 0) {
notifyEvent = v.substring(colon + 2);
v = v.substring(0, colon);
customEvent = true;
// Initialize attribute bindings with any literal parts
var literal = this._literalFromParts(parts);
if (literal && kind == 'attribute') {
node.setAttribute(name, literal);
}
// Clear attribute before removing, since IE won't allow removing
// `value` attribute if it previously had a value (can't
// unconditionally set '' before removing since attributes with `$`
// can't be set using setAttribute)
if (node.localName == 'input' && n == 'value') {
node.setAttribute(n, '');
if (node.localName === 'input' && origName === 'value') {
node.setAttribute(origName, '');
}
// Remove annotation
node.removeAttribute(n);
node.removeAttribute(origName);
// Case hackery: attributes are lower-case, but bind targets
// (properties) are case sensitive. Gambit is to map dash-case to
// camel-case: `foo-bar` becomes `fooBar`.
@ -273,30 +356,32 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
return {
kind: kind,
mode: mode,
name: name,
value: v,
negate: not,
event: notifyEvent,
customEvent: customEvent
parts: parts,
literal: literal,
isCompound: parts.length !== 1
};
}
},
// instance-time
_localSubTree: function(node, host) {
return (node === host) ? node.childNodes :
(node._lightChildren || node.childNodes);
},
findAnnotatedNode: function(root, annote) {
// recursively ascend tree until we hit root
var parent = annote.parent &&
Polymer.Annotations.findAnnotatedNode(root, annote.parent);
// unwind the stack, returning the indexed node at each level
return !parent ? root :
Polymer.Annotations._localSubTree(parent, root)[annote.index];
if (parent) {
// note: marginally faster than indexing via childNodes
// (http://jsperf.com/childnodes-lookup)
for (var n=parent.firstChild, i=0; n; n=n.nextSibling) {
if (annote.index === i++) {
return n;
}
}
} else {
return root;
}
}
};

View File

@ -63,8 +63,8 @@ Polymer.Async = {
}
};
new (window.MutationObserver || JsMutationObserver)
(Polymer.Async._atEndOfMicrotask.bind(Polymer.Async))
.observe(Polymer.Async._twiddle, {characterData: true});
new window.MutationObserver(function() {
Polymer.Async._atEndOfMicrotask();
}).observe(Polymer.Async._twiddle, {characterData: true});
</script>

View File

@ -44,10 +44,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// https://code.google.com/p/chromium/issues/detail?id=516550
// To allow querying style/layout data in attached, we defer it
// until we are sure rendering is ready.
var self = this;
Polymer.RenderStatus.whenReady(function() {
this.isAttached = true;
this._doBehavior('attached'); // abstract
}.bind(this));
self.isAttached = true;
self._doBehavior('attached'); // abstract
});
},
// reserved for canonical behavior
@ -57,9 +58,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
// reserved for canonical behavior
attributeChangedCallback: function(name) {
attributeChangedCallback: function(name, oldValue, newValue) {
// TODO(sorvell): consider filtering out changes to host attributes
// note: this was barely measurable with 3 host attributes.
this._attributeChangedImpl(name); // abstract
this._doBehavior('attributeChanged', arguments); // abstract
this._doBehavior('attributeChanged', [name, oldValue, newValue]); // abstract
},
_attributeChangedImpl: function(name) {
@ -77,9 +80,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/
extend: function(prototype, api) {
if (prototype && api) {
Object.getOwnPropertyNames(api).forEach(function(n) {
var n$ = Object.getOwnPropertyNames(api);
for (var i=0, n; (i<n$.length) && (n=n$[i]); i++) {
this.copyOwnProperty(n, api, prototype);
}, this);
}
}
return prototype || api;
},

View File

@ -11,21 +11,20 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.Bind = {
// for prototypes (usually)
_dataEventCache: {},
// for prototypes (usually)
prepareModel: function(model) {
model._propertyEffects = {};
model._bindListeners = [];
Polymer.Base.mixin(model, this._modelApi);
},
_modelApi: {
_notifyChange: function(property) {
var eventName = Polymer.CaseMap.camelToDashCase(property) + '-changed';
Polymer.Base.fire(eventName, {
value: this[property]
}, {bubbles: false, node: this});
_notifyChange: function(source, event, value) {
value = value === undefined ? this[source] : value;
event = event || Polymer.CaseMap.camelToDashCase(source) + '-changed';
this.fire(event, {value: value},
{bubbles: false, cancelable: false, _useCache: true});
},
// TODO(sjmiles): removing _notifyListener from here breaks accessors.html
@ -79,13 +78,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_effectEffects: function(property, value, effects, old, fromAbove) {
effects.forEach(function(fx) {
//console.log(fx);
var fn = Polymer.Bind['_' + fx.kind + 'Effect'];
if (fn) {
fn.call(this, property, value, fx.effect, old, fromAbove);
}
}, this);
for (var i=0, l=effects.length, fx; (i<l) && (fx=effects[i]); i++) {
fx.fn.call(this, property, value, fx.effect, old, fromAbove);
}
},
_clearPath: function(path) {
@ -101,6 +96,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// a prepared model can acquire effects
ensurePropertyEffects: function(model, property) {
if (!model._propertyEffects) {
model._propertyEffects = {};
}
var fx = model._propertyEffects[property];
if (!fx) {
fx = model._propertyEffects[property] = [];
@ -110,10 +108,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
addPropertyEffect: function(model, property, kind, effect) {
var fx = this.ensurePropertyEffects(model, property);
fx.push({
var propEffect = {
kind: kind,
effect: effect
});
effect: effect,
fn: Polymer.Bind['_' + kind + 'Effect']
};
fx.push(propEffect);
return propEffect;
},
createBindings: function(model) {
@ -169,6 +170,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// ReadOnly properties have a private setter only
// TODO(kschaaf): Per current Bind factoring, we shouldn't
// be interrogating the prototype here
// TODO(sorvell): we want to avoid using `getPropertyInfo` here, but
// this requires more data in `_propertyInfo`
var info = model.getPropertyInfo && model.getPropertyInfo(property);
if (info && info.readOnly) {
// Computed properties are read-only (no property setter), but also don't
@ -187,8 +190,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_addAnnotatedListener: function(model, index, property, path, event) {
if (!model._bindListeners) {
model._bindListeners = [];
}
var fn = this._notedListenerFactory(property, path,
this._isStructured(path), this._isEventBogus);
this._isStructured(path));
var eventName = event ||
(Polymer.CaseMap.camelToDashCase(property) + '-changed');
model._bindListeners.push({
@ -208,27 +214,28 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return e.path && e.path[0] !== target;
},
_notedListenerFactory: function(property, path, isStructured, bogusTest) {
return function(e, target) {
if (!bogusTest(e, target)) {
if (e.detail && e.detail.path) {
this.notifyPath(this._fixPath(path, property, e.detail.path),
e.detail.value);
_notedListenerFactory: function(property, path, isStructured) {
return function(target, value, targetPath) {
if (targetPath) {
this._notifyPath(this._fixPath(path, property, targetPath), value);
} else {
// TODO(sorvell): even though we have a `value` argument, we *must*
// lookup the current value of the property. Multiple listeners and
// queued events during configuration can theoretically lead to
// divergence of the passed value from the current value, but we
// really need to track down a specific case where this happens.
value = target[property];
if (!isStructured) {
this[path] = value;
} else {
var value = target[property];
if (!isStructured) {
this[path] = target[property];
} else {
// TODO(kschaaf): dirty check avoids null references when the object has gone away
if (this.__data__[path] != value) {
this.set(path, value);
}
// TODO(kschaaf): dirty check avoids null references when the object has gone away
if (this.__data__[path] != value) {
this.set(path, value);
}
}
}
};
},
// for instances
prepareInstance: function(inst) {
@ -236,15 +243,29 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
setupBindListeners: function(inst) {
inst._bindListeners.forEach(function(info) {
var b$ = inst._bindListeners;
for (var i=0, l=b$.length, info; (i<l) && (info=b$[i]); i++) {
// Property listeners:
// <node>.on.<property>-changed: <path]> = e.detail.value
//console.log('[_setupBindListener]: [%s][%s] listening for [%s][%s-changed]', this.localName, info.path, info.id || info.index, info.property);
//
// TODO(sorvell): fix templatizer to support this before uncommenting
// Optimization: only add bind listeners if the bound property is notifying...
var node = inst._nodes[info.index];
node.addEventListener(info.event, inst._notifyListener.bind(inst, info.changedFn));
//var p = node._propertyInfo && node._propertyInfo[info.property];
//if (node._prepParentProperties || !node._propertyInfo || (p && p.notify)) {
this._addNotifyListener(node, inst, info.event, info.changedFn);
//}
};
},
// TODO(sorvell): note, adding these synchronously may impact performance,
// measure and consider if we can defer until after first paint in some cases at least.
_addNotifyListener: function(element, context, event, changedFn) {
element.addEventListener(event, function(e) {
return context._notifyListener(changedFn, e);
});
}
};
</script>

View File

@ -14,15 +14,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_shouldAddListener: function(effect) {
return effect.name &&
effect.mode === '{' &&
!effect.negate &&
effect.kind != 'attribute'
;
effect.kind != 'attribute' &&
effect.kind != 'text' &&
!effect.isCompound &&
effect.parts[0].mode === '{' &&
!effect.parts[0].negate;
},
_annotationEffect: function(source, value, effect) {
if (source != effect.value) {
value = this.get(effect.value);
value = this._get(effect.value);
this.__data__[effect.value] = value;
}
var calc = effect.negate ? !value : value;
@ -30,17 +31,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// are used, since the target element may not dirty check (e.g. <input>)
if (!effect.customEvent ||
this._nodes[effect.index][effect.name] !== calc) {
return this._applyEffectValue(calc, effect);
return this._applyEffectValue(effect, calc);
}
},
_reflectEffect: function(source) {
this.reflectPropertyToAttribute(source);
_reflectEffect: function(source, value, effect) {
this.reflectPropertyToAttribute(source, effect.attribute, value);
},
_notifyEffect: function(source, value, effect, old, fromAbove) {
if (!fromAbove) {
this._notifyChange(source);
this._notifyChange(source, effect.event, value);
}
},
@ -77,7 +78,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (args) {
var fn = this[effect.method];
if (fn) {
this.__setProperty(effect.property, fn.apply(this, args));
this.__setProperty(effect.name, fn.apply(this, args));
} else {
this._warn(this._logf('_computeEffect', 'compute method `' +
effect.method + '` not defined'));
@ -95,7 +96,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (effect.negate) {
computedvalue = !computedvalue;
}
this._applyEffectValue(computedvalue, effect);
this._applyEffectValue(effect, computedvalue);
}
} else {
computedHost._warn(computedHost._logf('_annotatedComputationEffect',
@ -115,7 +116,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (arg.literal) {
v = arg.value;
} else if (arg.structured) {
v = Polymer.Base.get(name, model);
v = Polymer.Base._get(name, model);
} else {
v = model[name];
}

View File

@ -46,12 +46,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
} else {
this.pmap[item] = key;
}
return key;
return '#' + key;
},
removeKey: function(key) {
this._removeFromMap(this.store[key]);
delete this.store[key];
if (key = this._parseKey(key)) {
this._removeFromMap(this.store[key]);
delete this.store[key];
}
},
_removeFromMap: function(item) {
@ -69,32 +71,48 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
getKey: function(item) {
var key;
if (item && typeof item == 'object') {
return this.omap.get(item);
key = this.omap.get(item);
} else {
return this.pmap[item];
key = this.pmap[item];
}
if (key != undefined) {
return '#' + key;
}
},
getKeys: function() {
return Object.keys(this.store);
return Object.keys(this.store).map(function(key) {
return '#' + key;
});
},
_parseKey: function(key) {
if (key && key[0] == '#') {
return key.slice(1);
}
},
setItem: function(key, item) {
var old = this.store[key];
if (old) {
this._removeFromMap(old);
if (key = this._parseKey(key)) {
var old = this.store[key];
if (old) {
this._removeFromMap(old);
}
if (item && typeof item == 'object') {
this.omap.set(item, key);
} else {
this.pmap[item] = key;
}
this.store[key] = item;
}
if (item && typeof item == 'object') {
this.omap.set(item, key);
} else {
this.pmap[item] = key;
}
this.store[key] = item;
},
getItem: function(key) {
return this.store[key];
if (key = this._parseKey(key)) {
return this.store[key];
}
},
getItems: function() {
@ -115,15 +133,15 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// corresponding to the added/removed items
_applySplices: function(splices) {
// Dedupe added and removed keys to a final added/removed map
var keyMap = {}, key, i;
splices.forEach(function(s) {
var keyMap = {}, key;
for (var i=0, s; (i<splices.length) && (s=splices[i]); i++) {
s.addedKeys = [];
for (i=0; i<s.removed.length; i++) {
key = this.getKey(s.removed[i]);
for (var j=0; j<s.removed.length; j++) {
key = this.getKey(s.removed[j]);
keyMap[key] = keyMap[key] ? null : -1;
}
for (i=0; i<s.addedCount; i++) {
var item = this.userArray[s.index + i];
for (var j=0; j<s.addedCount; j++) {
var item = this.userArray[s.index + j];
key = this.getKey(item);
key = (key === undefined) ? this.add(item) : key;
keyMap[key] = keyMap[key] ? null : 1;
@ -132,7 +150,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// further changes to the array by the time the splice is consumed
s.addedKeys.push(key);
}
}, this);
}
// Convert added/removed key map to added/removed arrays
var removed = [];
var added = [];

View File

@ -11,11 +11,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
/*
Extremely simple css parser. Intended to be not more than what we need
and definitely not necessarly correct =).
and definitely not necessarily correct =).
*/
Polymer.CssParse = (function() {
var api = {
return {
// given a string of css, return a simple rule tree
parse: function(text) {
text = this._clean(text);
@ -31,7 +31,7 @@ Polymer.CssParse = (function() {
_lex: function(text) {
var root = {start: 0, end: text.length};
var n = root;
for (var i=0, s=0, l=text.length; i < l; i++) {
for (var i=0, l=text.length; i < l; i++) {
switch (text[i]) {
case this.OPEN_BRACE:
//console.group(i);
@ -43,7 +43,7 @@ Polymer.CssParse = (function() {
n = {start: i+1, parent: p, previous: previous};
p.rules.push(n);
break;
case this.CLOSE_BRACE:
case this.CLOSE_BRACE:
//console.groupEnd(n.start);
n.end = i+1;
n = n.parent || root;
@ -60,6 +60,8 @@ Polymer.CssParse = (function() {
if (node.parent) {
var ss = node.previous ? node.previous.end : node.parent.start;
t = text.substring(ss, node.start-1);
t = this._expandUnicodeEscapes(t);
t = t.replace(this._rx.multipleSpaces, ' ');
// TODO(sorvell): ad hoc; make selector include only after last ;
// helps with mixin syntax
t = t.substring(t.lastIndexOf(';')+1);
@ -84,9 +86,21 @@ Polymer.CssParse = (function() {
if (r$) {
for (var i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) {
this._parseCss(r, text);
}
}
}
return node;
return node;
},
// conversion of sort unicode escapes with spaces like `\33 ` (and longer) into
// expanded form that doesn't require trailing space `\000033`
_expandUnicodeEscapes : function(s) {
return s.replace(/\\([0-9a-f]{1,6})\s/gi, function() {
var code = arguments[1], repeat = 6 - code.length;
while (repeat--) {
code = '0' + code;
}
return '\\' + code;
});
},
// stringify parsed css.
@ -99,17 +113,17 @@ Polymer.CssParse = (function() {
if (r$ && (preserveProperties || !this._hasMixinRules(r$))) {
for (var i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) {
cssText = this.stringify(r, preserveProperties, cssText);
}
}
} else {
cssText = preserveProperties ? node.cssText :
this.removeCustomProps(node.cssText);
cssText = preserveProperties ? node.cssText :
this.removeCustomProps(node.cssText);
cssText = cssText.trim();
if (cssText) {
cssText = ' ' + cssText + '\n';
}
}
}
// emit rule iff there is cssText
// emit rule if there is cssText
if (cssText) {
if (node.selector) {
text += node.selector + ' ' + this.OPEN_BRACE + '\n';
@ -123,7 +137,7 @@ Polymer.CssParse = (function() {
},
_hasMixinRules: function(rules) {
return (rules[0].selector.indexOf(this.VAR_START) >= 0);
return rules[0].selector.indexOf(this.VAR_START) === 0;
},
removeCustomProps: function(cssText) {
@ -138,7 +152,7 @@ Polymer.CssParse = (function() {
},
removeCustomPropApply: function(cssText) {
return cssText
return cssText
.replace(this._rx.mixinApply, '')
.replace(this._rx.varApply, '');
},
@ -157,11 +171,12 @@ Polymer.CssParse = (function() {
_rx: {
comments: /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
port: /@import[^;]*;/gim,
customProp: /(?:^|[\s;])--[^;{]*?:[^{};]*?(?:[;\n]|$)/gim,
mixinProp: /(?:^|[\s;])--[^;{]*?:[^{;]*?{[^}]*?}(?:[;\n]|$)?/gim,
customProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,
mixinProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,
mixinApply: /@apply[\s]*\([^)]*?\)[\s]*(?:[;\n]|$)?/gim,
varApply: /[^;:]*?:[^;]*var[^;]*(?:[;\n]|$)?/gim,
varApply: /[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,
keyframesRule: /^@[^\s]*keyframes/,
multipleSpaces: /\s+/g
},
VAR_START: '--',
@ -170,10 +185,6 @@ Polymer.CssParse = (function() {
};
// exports
return api;
})();
</script>

View File

@ -1,172 +1,200 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="style-util.html">
<link rel="import" href="style-transformer.html">
<link rel="import" href="style-defaults.html">
<!--
The `custom-style` extension of the native `<style>` element allows defining styles
in the main document that can take advantage of several special features of
Polymer's styling system:
* Document styles defined in a `custom-style` will be shimmed to ensure they do
not leak into local DOM when running on browsers without non-native Shadow DOM.
* Shadow DOM-specific `/deep/` and `::shadow` combinators will be shimmed on
browsers without non-native Shadow DOM.
* Custom properties used by Polymer's shim for cross-scope styling
may be defined in an `custom-style`.
* An `include` attribute may be specified to pull in style data from a
`dom-module` matching the include attribute. By using `include`, style data
can be shared between multiple `custom-style` elements.
Example:
```html
<!doctype html>
<html>
<head>
<script src="components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="components/polymer/polymer.html">
<style is="custom-style">
/* Will be prevented from affecting local DOM of Polymer elements */
* {
box-sizing: border-box;
}
/* Can use /deep/ and ::shadow combinators */
body /deep/ .my-special-view::shadow #thing-inside {
background: yellow;
}
/* Custom properties that inherit down the document tree may be defined */
body {
--my-toolbar-title-color: green;
}
</style>
</head>
<body>
...
</body>
</html>
```
Note, all features of `custom-style` are available when defining styles as part of Polymer elements (e.g. `<style>` elements within `<dom-module>`'s used for defining Polymer elements. The `custom-style` extension should only be used for defining document styles, outside of a custom element's local DOM.
-->
<script>
(function() {
var nativeShadow = Polymer.Settings.useNativeShadow;
var propertyUtils = Polymer.StyleProperties;
var styleUtil = Polymer.StyleUtil;
var cssParse = Polymer.CssParse;
var styleDefaults = Polymer.StyleDefaults;
var styleTransformer = Polymer.StyleTransformer;
Polymer({
is: 'custom-style',
extends: 'style',
properties: {
// include is a property so that it deserializes
/**
* Specify `include` to identify a `dom-module` containing style data which
* should be used within the `custom-style`. By using `include` style data
* may be shared between multiple different `custom-style` elements.
*/
include: String
},
ready: function() {
// NOTE: we cannot just check attached because custom elements in
// HTMLImports do not get attached.
this._tryApply();
},
attached: function() {
this._tryApply();
},
_tryApply: function() {
if (!this._appliesToDocument) {
// only apply variables iff this style is not inside a dom-module
if (this.parentNode &&
(this.parentNode.localName !== 'dom-module')) {
this._appliesToDocument = true;
// used applied element from HTMLImports polyfill or this
var e = this.__appliedElement || this;
styleDefaults.addStyle(e);
// we may not have any textContent yet due to parser yielding
// if so, wait until we do...
if (e.textContent || this.include) {
this._apply();
} else {
var observer = new MutationObserver(function() {
observer.disconnect();
this._apply();
}.bind(this));
observer.observe(e, {childList: true});
}
}
}
},
// polyfill this style with root scoping and
// apply custom properties!
_apply: function() {
// used applied element from HTMLImports polyfill or this
var e = this.__appliedElement || this;
if (this.include) {
e.textContent = styleUtil.cssFromModules(this.include, true) +
e.textContent;
}
if (e.textContent) {
styleUtil.forEachStyleRule(styleUtil.rulesForStyle(e), function(rule) {
styleTransformer.documentRule(rule);
});
this._applyCustomProperties(e);
}
},
_applyCustomProperties: function(element) {
this._computeStyleProperties();
var props = this._styleProperties;
var rules = styleUtil.rulesForStyle(element);
element.textContent = styleUtil.toCssText(rules, function(rule) {
var css = rule.cssText = rule.parsedCssText;
if (rule.propertyInfo && rule.propertyInfo.cssText) {
// remove property assignments
// so next function isn't confused
// NOTE: we have 3 categories of css:
// (1) normal properties,
// (2) custom property assignments (--foo: red;),
// (3) custom property usage: border: var(--foo); @apply(--foo);
// In elements, 1 and 3 are separated for efficiency; here they
// are not and this makes this case unique.
css = cssParse.removeCustomPropAssignment(css);
// replace with reified properties, scenario is same as mixin
rule.cssText = propertyUtils.valueForProperties(css, props);
}
});
}
});
})();
</script>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="style-util.html">
<link rel="import" href="style-transformer.html">
<link rel="import" href="style-defaults.html">
<!--
The `custom-style` extension of the native `<style>` element allows defining styles
in the main document that can take advantage of several special features of
Polymer's styling system:
* Document styles defined in a `custom-style` will be shimmed to ensure they do
not leak into local DOM when running on browsers without non-native Shadow DOM.
* Shadow DOM-specific `/deep/` and `::shadow` combinators will be shimmed on
browsers without non-native Shadow DOM.
* Custom properties used by Polymer's shim for cross-scope styling
may be defined in an `custom-style`.
* An `include` attribute may be specified to pull in style data from a
`dom-module` matching the include attribute. By using `include`, style data
can be shared between multiple `custom-style` elements.
Example:
```html
<!doctype html>
<html>
<head>
<script src="components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="components/polymer/polymer.html">
<style is="custom-style">
/* Will be prevented from affecting local DOM of Polymer elements */
* {
box-sizing: border-box;
}
/* Can use /deep/ and ::shadow combinators */
body /deep/ .my-special-view::shadow #thing-inside {
background: yellow;
}
/* Custom properties that inherit down the document tree may be defined */
body {
--my-toolbar-title-color: green;
}
</style>
</head>
<body>
...
</body>
</html>
```
Note, all features of `custom-style` are available when defining styles as part of Polymer elements (e.g. `<style>` elements within `<dom-module>`'s used for defining Polymer elements. The `custom-style` extension should only be used for defining document styles, outside of a custom element's local DOM.
-->
<script>
(function() {
var nativeShadow = Polymer.Settings.useNativeShadow;
var propertyUtils = Polymer.StyleProperties;
var styleUtil = Polymer.StyleUtil;
var cssParse = Polymer.CssParse;
var styleDefaults = Polymer.StyleDefaults;
var styleTransformer = Polymer.StyleTransformer;
Polymer({
is: 'custom-style',
extends: 'style',
_template: null,
properties: {
// include is a property so that it deserializes
/**
* Specify `include` to identify a `dom-module` containing style data which
* should be used within the `custom-style`. By using `include` style data
* may be shared between multiple different `custom-style` elements.
*/
include: String
},
ready: function() {
// NOTE: we cannot just check attached because custom elements in
// HTMLImports do not get attached.
this._tryApply();
},
attached: function() {
this._tryApply();
},
_tryApply: function() {
if (!this._appliesToDocument) {
// only apply variables iff this style is not inside a dom-module
if (this.parentNode &&
(this.parentNode.localName !== 'dom-module')) {
this._appliesToDocument = true;
// used applied element from HTMLImports polyfill or this
var e = this.__appliedElement || this;
styleDefaults.addStyle(e);
// we may not have any textContent yet due to parser yielding
// if so, wait until we do...
if (e.textContent || this.include) {
this._apply(true);
} else {
var self = this;
var observer = new MutationObserver(function() {
observer.disconnect();
self._apply(true);
});
observer.observe(e, {childList: true});
}
}
}
},
// polyfill this style with root scoping and
// apply custom properties!
_apply: function(deferProperties) {
// used applied element from HTMLImports polyfill or this
var e = this.__appliedElement || this;
if (this.include) {
e.textContent = styleUtil.cssFromModules(this.include, true) +
e.textContent;
}
if (e.textContent) {
styleUtil.forEachStyleRule(styleUtil.rulesForStyle(e), function(rule) {
styleTransformer.documentRule(rule);
});
// Allow all custom-styles defined in this turn to register
// before applying any properties. This helps ensure that all properties
// are defined before any are consumed.
// Premature application of properties can occur in 2 cases:
// (1) A property `--foo` is consumed in a custom-style
// before another custom-style produces `--foo`.
// In general, we require custom properties to be defined before being
// used in elements so supporting this for custom-style
// is dubious but is slightly more like native properties where this
// is supported.
// (2) A set of in order styles (A, B) are re-ordered due to a parser
// yield that makes A wait for textContent. This reorders its
// `_apply` after B.
// This case should only occur with native webcomponents.
var self = this;
var fn = function fn() {
self._applyCustomProperties(e);
}
if (this._pendingApplyProperties) {
cancelAnimationFrame(this._pendingApplyProperties);
this._pendingApplyProperties = null;
}
if (deferProperties) {
this._pendingApplyProperties = requestAnimationFrame(fn);
} else {
fn();
}
}
},
_applyCustomProperties: function(element) {
this._computeStyleProperties();
var props = this._styleProperties;
var rules = styleUtil.rulesForStyle(element);
element.textContent = styleUtil.toCssText(rules, function(rule) {
var css = rule.cssText = rule.parsedCssText;
if (rule.propertyInfo && rule.propertyInfo.cssText) {
// remove property assignments
// so next function isn't confused
// NOTE: we have 3 categories of css:
// (1) normal properties,
// (2) custom property assignments (--foo: red;),
// (3) custom property usage: border: var(--foo); @apply(--foo);
// In elements, 1 and 3 are separated for efficiency; here they
// are not and this makes this case unique.
css = cssParse.removeCustomPropAssignment(css);
// replace with reified properties, scenario is same as mixin
rule.cssText = propertyUtils.valueForProperties(css, props);
}
});
}
});
})();
</script>

View File

@ -24,7 +24,10 @@ Polymer.Debounce = (function() {
var Debouncer = function(context) {
this.context = context;
this.boundComplete = this.complete.bind(this);
var self = this;
this.boundComplete = function() {
self.complete();
}
};
Debouncer.prototype = {

View File

@ -0,0 +1,70 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<script>
(function() {
'use strict';
var DomApi = Polymer.DomApi.ctor;
var useShadow = Polymer.Settings.useShadow;
/**
* DomApi.classList allows maniuplation of `classList` compatible with
* Polymer.dom. The general usage is
* `Polymer.dom(node).classList.method(arguments)` where methods and arguments
* match native DOM.
*/
Object.defineProperty(DomApi.prototype, 'classList', {
get: function() {
if (!this._classList) {
this._classList = new DomApi.ClassList(this);
}
return this._classList;
},
configurable: true
});
DomApi.ClassList = function(host) {
this.domApi = host;
this.node = host.node;
}
DomApi.ClassList.prototype = {
add: function() {
this.node.classList.add.apply(this.node.classList, arguments);
this._distributeParent();
},
remove: function() {
this.node.classList.remove.apply(this.node.classList, arguments);
this._distributeParent();
},
toggle: function() {
this.node.classList.toggle.apply(this.node.classList, arguments);
this._distributeParent();
},
_distributeParent: function() {
if (!useShadow) {
this.domApi._maybeDistributeParent();
}
},
contains: function() {
return this.node.classList.contains.apply(this.node.classList,
arguments);
}
}
})();
</script>

View File

@ -0,0 +1,97 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<script>
(function() {
'use strict';
var DomApi = Polymer.DomApi.ctor;
var Settings = Polymer.Settings;
/**
* DomApi.DistributedNodesObserver notifies when the list returned by
* a <content> element's `getDistributedNodes()` may have changed.
* It is not meant to be used directly; it is used by
* `Polymer.dom(node).observeNodes(callback)` to observe changes to
* `<content>.getDistributedNodes()`.
*/
DomApi.DistributedNodesObserver = function(domApi) {
DomApi.EffectiveNodesObserver.call(this, domApi);
};
DomApi.DistributedNodesObserver.prototype =
Object.create(DomApi.EffectiveNodesObserver.prototype);
Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, {
// NOTE: ShadyDOM distribute provokes notification of these observers
// so no setup is required.
_setup: function() {},
_cleanup: function() {},
// no need to update sub-elements since <content> does not nest
// (but note that <slot> will)
_beforeCallListeners: function() {},
_getEffectiveNodes: function() {
return this.domApi.getDistributedNodes();
}
});
if (Settings.useShadow) {
Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, {
// NOTE: Under ShadowDOM we must observe the host element for
// changes.
_setup: function() {
if (!this._observer) {
var root = this.domApi.getOwnerRoot();
var host = root && root.host;
if (host) {
var self = this;
this._observer = Polymer.dom(host).observeNodes(function() {
self._scheduleNotify();
});
// NOTE: we identify this listener as needed for <content>
// notification so that enableShadowAttributeTracking
// can find these observers an ensure that we pass always
// pass notifications down.
this._observer._isContentListener = true;
if (this._hasAttrSelect()) {
Polymer.dom(host).observer.enableShadowAttributeTracking();
}
}
}
},
_hasAttrSelect: function() {
var select = this.node.getAttribute('select');
return select && select.match(/[[.]+/);
},
_cleanup: function() {
var root = this.domApi.getOwnerRoot();
var host = root && root.host;
if (host) {
Polymer.dom(host).unobserveNodes(this._observer);
}
this._observer = null;
}
});
}
})();
</script>

View File

@ -0,0 +1,268 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<script>
(function() {
'use strict';
var DomApi = Polymer.DomApi.ctor;
var Settings = Polymer.Settings;
var hasDomApi = Polymer.DomApi.hasDomApi;
/**
* DomApi.EffectiveNodesObserver tracks changes to an element's
* effective child nodes, the same list returned from
* `Polymer.dom(node).getEffectiveChildNodes()`.
* It is not meant to be used directly; it is used by
* `Polymer.dom(node).observeNodes(callback)` to observe changes.
*/
DomApi.EffectiveNodesObserver = function(domApi) {
this.domApi = domApi;
this.node = this.domApi.node;
this._listeners = [];
};
DomApi.EffectiveNodesObserver.prototype = {
addListener: function(callback) {
if (!this._isSetup) {
this._setup();
this._isSetup = true;
}
var listener = {fn: callback, _nodes: []};
this._listeners.push(listener);
this._scheduleNotify();
return listener;
},
removeListener: function(handle) {
var i = this._listeners.indexOf(handle);
if (i >= 0) {
this._listeners.splice(i, 1);
handle._nodes = [];
}
if (!this._hasListeners()) {
this._cleanup();
this._isSetup = false;
}
},
_setup: function() {
this._observeContentElements(this.domApi.childNodes);
},
_cleanup: function() {
this._unobserveContentElements(this.domApi.childNodes);
},
_hasListeners: function() {
return Boolean(this._listeners.length);
},
_scheduleNotify: function() {
if (this._debouncer) {
this._debouncer.stop();
}
this._debouncer = Polymer.Debounce(this._debouncer,
this._notify);
this._debouncer.context = this;
Polymer.dom.addDebouncer(this._debouncer);
},
notify: function() {
if (this._hasListeners()) {
this._scheduleNotify();
}
},
_notify: function(mxns) {
this._beforeCallListeners();
this._callListeners();
},
_beforeCallListeners: function() {
this._updateContentElements();
},
_updateContentElements: function() {
this._observeContentElements(this.domApi.childNodes);
},
_observeContentElements: function(elements) {
for (var i=0, n; (i < elements.length) && (n=elements[i]); i++) {
if (this._isContent(n)) {
n.__observeNodesMap = n.__observeNodesMap || new WeakMap();
if (!n.__observeNodesMap.has(this)) {
n.__observeNodesMap.set(this, this._observeContent(n));
}
}
}
},
_observeContent: function(content) {
var self = this;
var h = Polymer.dom(content).observeNodes(function() {
self._scheduleNotify();
});
h._avoidChangeCalculation = true;
return h;
},
_unobserveContentElements: function(elements) {
for (var i=0, n, h; (i < elements.length) && (n=elements[i]); i++) {
if (this._isContent(n)) {
h = n.__observeNodesMap.get(this);
if (h) {
Polymer.dom(n).unobserveNodes(h);
n.__observeNodesMap.delete(this);
}
}
}
},
_isContent: function(node) {
return (node.localName === 'content');
},
_callListeners: function() {
var o$ = this._listeners;
var nodes = this._getEffectiveNodes();
for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) {
var info = this._generateListenerInfo(o, nodes);
if (info || o._alwaysNotify) {
this._callListener(o, info);
}
}
},
_getEffectiveNodes: function() {
return this.domApi.getEffectiveChildNodes()
},
_generateListenerInfo: function(listener, newNodes) {
if (listener._avoidChangeCalculation) {
return true;
}
var oldNodes = listener._nodes;
var info = {
target: this.node,
addedNodes: [],
removedNodes: []
};
var splices = Polymer.ArraySplice.calculateSplices(newNodes, oldNodes);
// process removals
for (var i=0, s; (i<splices.length) && (s=splices[i]); i++) {
for (var j=0, n; (j < s.removed.length) && (n=s.removed[j]); j++) {
info.removedNodes.push(n);
}
}
// process adds
for (var i=0, s; (i<splices.length) && (s=splices[i]); i++) {
for (var j=s.index; j < s.index + s.addedCount; j++) {
info.addedNodes.push(newNodes[j]);
}
}
// update cache
listener._nodes = newNodes;
if (info.addedNodes.length || info.removedNodes.length) {
return info;
}
},
_callListener: function(listener, info) {
return listener.fn.call(this.node, info);
},
enableShadowAttributeTracking: function() {}
};
if (Settings.useShadow) {
var baseSetup = DomApi.EffectiveNodesObserver.prototype._setup;
var baseCleanup = DomApi.EffectiveNodesObserver.prototype._cleanup;
var beforeCallListeners = DomApi.EffectiveNodesObserver
.prototype._beforeCallListeners;
Polymer.Base.extend(DomApi.EffectiveNodesObserver.prototype, {
_setup: function() {
if (!this._observer) {
var self = this;
this._mutationHandler = function(mxns) {
if (mxns && mxns.length) {
self._scheduleNotify();
}
};
this._observer = new MutationObserver(this._mutationHandler);
this._boundFlush = function() {
self._flush();
}
Polymer.dom.addStaticFlush(this._boundFlush);
// NOTE: subtree true is way too aggressive, but it easily catches
// attribute changes on children. These changes otherwise require
// attribute observers on every child. Testing has shown this
// approach to be more efficient.
// TODO(sorvell): do we still need to include an option to defeat
// attribute tracking?
this._observer.observe(this.node, { childList: true });
}
baseSetup.call(this);
},
_cleanup: function() {
this._observer.disconnect();
this._observer = null;
this._mutationHandler = null;
Polymer.dom.removeStaticFlush(this._boundFlush);
baseCleanup.call(this);
},
_flush: function() {
if (this._observer) {
this._mutationHandler(this._observer.takeRecords());
}
},
enableShadowAttributeTracking: function() {
if (this._observer) {
// provoke all listeners needed for <content> observation
// to always call listeners when no-op changes occur (which may
// affect lower distributions.
this._makeContentListenersAlwaysNotify();
this._observer.disconnect();
this._observer.observe(this.node, {
childList: true,
attributes: true,
subtree: true
});
var root = this.domApi.getOwnerRoot();
var host = root && root.host;
if (host && Polymer.dom(host).observer) {
Polymer.dom(host).observer.enableShadowAttributeTracking();
}
}
},
_makeContentListenersAlwaysNotify: function() {
for (var i=0, h; i < this._listeners.length ; i++) {
h = this._listeners[i];
h._alwaysNotify = h._isContentListener;
}
}
});
}
})();
</script>

111
src/lib/dom-api-event.html Normal file
View File

@ -0,0 +1,111 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<script>
Polymer.EventApi = (function() {
'use strict';
var DomApi = Polymer.DomApi.ctor;
var Settings = Polymer.Settings;
/**
* DomApi.Event allows maniuplation of events compatible with
* the scoping concepts in Shadow DOM and compatible with both Shady DOM
* and Shadow DOM. The general usage is
* `Polymer.dom(event).property`. The `path` property returns `event.path`
* matching Shadow DOM. The `rootTarget` property returns the first node
* in the `path` and is the original event target. The `localTarget` property
* matches event.target under Shadow DOM and is the scoped event target.
*/
DomApi.Event = function(event) {
this.event = event;
};
if (Settings.useShadow) {
DomApi.Event.prototype = {
get rootTarget() {
return this.event.path[0];
},
get localTarget() {
return this.event.target;
},
get path() {
return this.event.path;
}
};
} else {
DomApi.Event.prototype = {
get rootTarget() {
return this.event.target;
},
get localTarget() {
var current = this.event.currentTarget;
var currentRoot = current && Polymer.dom(current).getOwnerRoot();
var p$ = this.path;
for (var i=0; i < p$.length; i++) {
if (Polymer.dom(p$[i]).getOwnerRoot() === currentRoot) {
return p$[i];
}
}
},
// TODO(sorvell): simulate event.path. This probably incorrect for
// non-bubbling events.
get path() {
if (!this.event._path) {
var path = [];
var current = this.rootTarget;
while (current) {
path.push(current);
var insertionPoints = Polymer.dom(current).getDestinationInsertionPoints();
if (insertionPoints.length) {
for (var i = 0; i < insertionPoints.length - 1; i++) {
path.push(insertionPoints[i]);
}
current = insertionPoints[insertionPoints.length - 1];
} else {
current = Polymer.dom(current).parentNode || current.host;
}
}
// event path includes window in most recent native implementations
path.push(window);
this.event._path = path;
}
return this.event._path;
}
};
}
var factory = function(event) {
if (!event.__eventApi) {
event.__eventApi = new DomApi.Event(event);
}
return event.__eventApi;
};
return {
factory: factory
};
})();
</script>

View File

@ -0,0 +1,91 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<script>
/**
* `Polymer.dom.flush()` causes any asynchronously queued actions to be
* flushed synchronously. It should be used sparingly as calling it frequently
* can negatively impact performance since work is often deferred for
* efficiency. Calling `Polymer.dom.flush()` is useful, for example, when
* an element has to measure itself and is unsure about the state of its
* internal or compoased DOM.
*/
Polymer.Base.extend(Polymer.dom, {
_flushGuard: 0,
_FLUSH_MAX: 100,
_needsTakeRecords: !Polymer.Settings.useNativeCustomElements,
_debouncers: [],
_staticFlushList: [],
_finishDebouncer: null,
// flush and debounce exposed as statics on Polymer.dom
flush: function() {
this._flushGuard = 0;
this._prepareFlush();
while (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) {
// Avoid using an index in this loop to ensure flush is safe to be
// called reentrantly from a debouncer callback being flushed
while (this._debouncers.length) {
this._debouncers.shift().complete();
}
// clear the list of debouncers
if (this._finishDebouncer) {
this._finishDebouncer.complete();
}
this._prepareFlush();
this._flushGuard++;
}
if (this._flushGuard >= this._FLUSH_MAX) {
console.warn('Polymer.dom.flush aborted. Flush may not be complete.')
}
},
_prepareFlush: function() {
// TODO(sorvell): There is currently not a good way
// to process all custom elements mutations under SD polyfill because
// these mutations may be inside shadowRoots.
// again make any pending CE mutations that might trigger debouncer
// additions go...
if (this._needsTakeRecords) {
CustomElements.takeRecords();
}
for (var i=0; i < this._staticFlushList.length; i++) {
this._staticFlushList[i]();
}
},
// add to the static list of methods to call when flushing
addStaticFlush: function(fn) {
this._staticFlushList.push(fn);
},
// remove a function from the static list of methods to call when flushing
removeStaticFlush: function(fn) {
var i = this._staticFlushList.indexOf(fn);
if (i >= 0) {
this._staticFlushList.splice(i, 1);
}
},
addDebouncer: function(debouncer) {
this._debouncers.push(debouncer);
// ensure the list of active debouncers is cleared when done.
this._finishDebouncer = Polymer.Debounce(this._finishDebouncer,
this._finishFlush);
},
_finishFlush: function() {
Polymer.dom._debouncers = [];
}
});
</script>

146
src/lib/dom-api-shadow.html Normal file
View File

@ -0,0 +1,146 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<script>
(function() {
'use strict';
var Settings = Polymer.Settings;
var TreeApi = Polymer.TreeApi;
var DomApi = Polymer.DomApi;
// *************** Configure DomApi for Shadow DOM!! ***************
if (!Settings.useShadow) {
return;
}
Polymer.Base.extend(DomApi.prototype, {
querySelectorAll: function(selector) {
return TreeApi.arrayCopy(this.node.querySelectorAll(selector));
},
getOwnerRoot: function() {
var n = this.node;
while (n) {
if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) {
return n;
}
n = n.parentNode;
}
},
importNode: function(externalNode, deep) {
var doc = this.node instanceof Document ? this.node :
this.node.ownerDocument;
return doc.importNode(externalNode, deep);
},
getDestinationInsertionPoints: function() {
var n$ = this.node.getDestinationInsertionPoints &&
this.node.getDestinationInsertionPoints();
return n$ ? TreeApi.arrayCopy(n$) : [];
},
getDistributedNodes: function() {
var n$ = this.node.getDistributedNodes &&
this.node.getDistributedNodes();
return n$ ? TreeApi.arrayCopy(n$) : [];
}
});
Object.defineProperties(DomApi.prototype, {
activeElement: {
get: function() {
var node = DomApi.wrap(this.node);
var activeElement = node.activeElement;
// Prevents `activeElement` from returning elements outside of the
// ShadowRoot, even if they would become descendants of the ShadowRoot
// in the composed tree. See w3c/webcomponents#358.
return node.contains(activeElement) ? activeElement : null;
},
configurable: true
},
childNodes: {
get: function() {
return TreeApi.arrayCopyChildNodes(this.node);
},
configurable: true
},
children: {
get: function() {
return TreeApi.arrayCopyChildren(this.node);
},
configurable: true
},
// textContent / innerHTML
textContent: {
get: function() {
return this.node.textContent;
},
set: function(value) {
return this.node.textContent = value;
},
configurable: true
},
innerHTML: {
get: function() {
return this.node.innerHTML;
},
set: function(value) {
return this.node.innerHTML = value;
},
configurable: true
}
});
var forwardMethods = function(m$) {
for (var i=0; i < m$.length; i++) {
forwardMethod(m$[i]);
}
};
var forwardMethod = function(method) {
DomApi.prototype[method] = function() {
return this.node[method].apply(this.node, arguments);
}
};
forwardMethods(['cloneNode', 'appendChild', 'insertBefore',
'removeChild', 'replaceChild', 'setAttribute', 'removeAttribute',
'querySelector']);
var forwardProperties = function(f$) {
for (var i=0; i < f$.length; i++) {
forwardProperty(f$[i]);
}
};
var forwardProperty = function(name) {
Object.defineProperty(DomApi.prototype, name, {
get: function() {
return this.node[name];
},
configurable: true
});
};
forwardProperties(['parentNode', 'firstChild', 'lastChild',
'nextSibling', 'previousSibling', 'firstElementChild',
'lastElementChild', 'nextElementSibling', 'previousElementSibling']);
})();
</script>

645
src/lib/dom-api-shady.html Normal file
View File

@ -0,0 +1,645 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<link rel="import" href="dom-innerHTML.html">
<script>
(function() {
'use strict';
var Settings = Polymer.Settings;
var DomApi = Polymer.DomApi;
var dom = DomApi.factory;
var TreeApi = Polymer.TreeApi;
var getInnerHTML = Polymer.domInnerHTML.getInnerHTML;
var CONTENT = DomApi.CONTENT;
// *************** Configure DomApi for Shady DOM!! ***************
if (Settings.useShadow) {
return;
}
var nativeCloneNode = Element.prototype.cloneNode;
var nativeImportNode = Document.prototype.importNode;
Polymer.Base.extend(DomApi.prototype, {
_lazyDistribute: function(host) {
// note: only try to distribute if the root is not clean; this ensures
// we don't distribute before initial distribution
if (host.shadyRoot && host.shadyRoot._distributionClean) {
host.shadyRoot._distributionClean = false;
Polymer.dom.addDebouncer(host.debounce('_distribute',
host._distributeContent));
}
},
appendChild: function(node) {
return this.insertBefore(node);
},
// cases in which we may not be able to just do standard native call
// 1. container has a shadyRoot (needsDistribution IFF the shadyRoot
// has an insertion point)
// 2. container is a shadyRoot (don't distribute, instead set
// container to container.host.
// 3. node is <content> (host of container needs distribution)
insertBefore: function(node, ref_node) {
if (ref_node && TreeApi.Logical.getParentNode(ref_node) !== this.node) {
throw Error('The ref_node to be inserted before is not a child ' +
'of this node');
}
// remove node from its current position iff it's in a tree.
if (node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
var parent = TreeApi.Logical.getParentNode(node);
// notify existing parent that this node is being removed.
if (parent) {
if (DomApi.hasApi(parent)) {
dom(parent).notifyObserver();
}
this._removeNode(node);
} else {
this._removeOwnerShadyRoot(node);
}
}
if (!this._addNode(node, ref_node)) {
if (ref_node) {
// if ref_node is <content> replace with first distributed node
ref_node = ref_node.localName === CONTENT ?
this._firstComposedNode(ref_node) : ref_node;
}
// if adding to a shadyRoot, add to host instead
var container = this.node._isShadyRoot ? this.node.host : this.node;
if (ref_node) {
TreeApi.Composed.insertBefore(container, node, ref_node);
} else {
TreeApi.Composed.appendChild(container, node);
}
}
this.notifyObserver();
return node;
},
// Try to add node. Record logical info, track insertion points, perform
// distribution iff needed. Return true if the add is handled.
_addNode: function(node, ref_node) {
var root = this.getOwnerRoot();
if (root) {
// note: we always need to see if an insertion point is added
// since this saves logical tree info; however, invalidation state
// needs
var ipAdded = this._maybeAddInsertionPoint(node, this.node);
// invalidate insertion points IFF not already invalid!
if (!root._invalidInsertionPoints) {
root._invalidInsertionPoints = ipAdded;
}
this._addNodeToHost(root.host, node);
}
if (TreeApi.Logical.hasChildNodes(this.node)) {
TreeApi.Logical.recordInsertBefore(node, this.node, ref_node);
}
// if not distributing and not adding to host, do a fast path addition
var handled = this._maybeDistribute(node) ||
this.node.shadyRoot;
// if shady is handling this node,
// the actual dom may not be removed if the node or fragment contents
// remain undistributed so we ensure removal here.
// NOTE: we only remove from existing location iff shady dom is involved.
// This is because a node fragment is passed to the native add method
// which expects to see fragment children. Regular elements must also
// use this check because not doing so causes separation of
// attached/detached and breaks, for example,
// dom-if's attached/detached checks.
if (handled) {
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
while (node.firstChild) {
TreeApi.Composed.removeChild(node, node.firstChild);
}
} else {
var parent = TreeApi.Composed.getParentNode(node);
if (parent) {
TreeApi.Composed.removeChild(parent, node);
}
}
}
return handled;
},
/**
Removes the given `node` from the element's `lightChildren`.
This method also performs dom composition.
*/
removeChild: function(node) {
if (TreeApi.Logical.getParentNode(node) !== this.node) {
throw Error('The node to be removed is not a child of this node: ' +
node);
}
if (!this._removeNode(node)) {
// if removing from a shadyRoot, remove form host instead
var container = this.node._isShadyRoot ? this.node.host : this.node;
// not guaranteed to physically be in container; e.g.
// undistributed nodes.
var parent = TreeApi.Composed.getParentNode(node);
if (container === parent) {
TreeApi.Composed.removeChild(container, node);
}
}
this.notifyObserver();
return node;
},
// Try to remove node: update logical info and perform distribution iff
// needed. Return true if the removal has been handled.
// note that it's possible for both the node's host and its parent
// to require distribution... both cases are handled here.
_removeNode: function(node) {
// important that we want to do this only if the node has a logical parent
var logicalParent = TreeApi.Logical.hasParentNode(node) &&
TreeApi.Logical.getParentNode(node);
var distributed;
var root = this._ownerShadyRootForNode(node);
if (logicalParent) {
// distribute node's parent iff needed
distributed = dom(node)._maybeDistributeParent();
TreeApi.Logical.recordRemoveChild(node, logicalParent);
// remove node from root and distribute it iff needed
if (root && this._removeDistributedChildren(root, node)) {
root._invalidInsertionPoints = true;
this._lazyDistribute(root.host);
}
}
this._removeOwnerShadyRoot(node);
if (root) {
this._removeNodeFromHost(root.host, node);
}
return distributed;
},
replaceChild: function(node, ref_node) {
this.insertBefore(node, ref_node);
this.removeChild(ref_node);
return node;
},
_hasCachedOwnerRoot: function(node) {
return Boolean(node._ownerShadyRoot !== undefined);
},
getOwnerRoot: function() {
return this._ownerShadyRootForNode(this.node);
},
_ownerShadyRootForNode: function(node) {
if (!node) {
return;
}
var root = node._ownerShadyRoot;
if (root === undefined) {
if (node._isShadyRoot) {
root = node;
} else {
var parent = TreeApi.Logical.getParentNode(node);
if (parent) {
root = parent._isShadyRoot ? parent :
this._ownerShadyRootForNode(parent);
} else {
root = null;
}
}
// memo-ize result for performance but only memo-ize a false-y
// result if node is in the document. This avoids a problem where a root
// can be cached while an element is inside a fragment.
// If this happens and we cache the result, the value can become stale
// because for perf we avoid processing the subtree of added fragments.
if (root || document.documentElement.contains(node)) {
node._ownerShadyRoot = root;
}
}
return root;
},
_maybeDistribute: function(node) {
// TODO(sorvell): technically we should check non-fragment nodes for
// <content> children but since this case is assumed to be exceedingly
// rare, we avoid the cost and will address with some specific api
// when the need arises. For now, the user must call
// distributeContent(true), which updates insertion points manually
// and forces distribution.
var fragContent = (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) &&
!node.__noContent && dom(node).querySelector(CONTENT);
var wrappedContent = fragContent &&
(TreeApi.Logical.getParentNode(fragContent).nodeType !==
Node.DOCUMENT_FRAGMENT_NODE);
var hasContent = fragContent || (node.localName === CONTENT);
// There are 2 possible cases where a distribution may need to occur:
// 1. <content> being inserted (the host of the shady root where
// content is inserted needs distribution)
// 2. children being inserted into parent with a shady root (parent
// needs distribution)
if (hasContent) {
var root = this.getOwnerRoot();
if (root) {
// note, insertion point list update is handled after node
// mutations are complete
this._lazyDistribute(root.host);
}
}
var needsDist = this._nodeNeedsDistribution(this.node);
if (needsDist) {
this._lazyDistribute(this.node);
}
// Return true when distribution will fully handle the composition
// Note that if a content was being inserted that was wrapped by a node,
// and the parent does not need distribution, return false to allow
// the nodes to be added directly, after which children may be
// distributed and composed into the wrapping node(s)
return needsDist || (hasContent && !wrappedContent);
},
/* note: parent argument is required since node may have an out
of date parent at this point; returns true if a <content> is being added */
_maybeAddInsertionPoint: function(node, parent) {
var added;
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE &&
!node.__noContent) {
var c$ = dom(node).querySelectorAll(CONTENT);
for (var i=0, n, np, na; (i<c$.length) && (n=c$[i]); i++) {
np = TreeApi.Logical.getParentNode(n);
// don't allow node's parent to be fragment itself
if (np === node) {
np = parent;
}
na = this._maybeAddInsertionPoint(n, np);
added = added || na;
}
} else if (node.localName === CONTENT) {
TreeApi.Logical.saveChildNodes(parent);
TreeApi.Logical.saveChildNodes(node);
added = true;
}
return added;
},
_updateInsertionPoints: function(host) {
var i$ = host.shadyRoot._insertionPoints =
dom(host.shadyRoot).querySelectorAll(CONTENT);
// ensure <content>'s and their parents have logical dom info.
for (var i=0, c; i < i$.length; i++) {
c = i$[i];
TreeApi.Logical.saveChildNodes(c);
TreeApi.Logical.saveChildNodes(TreeApi.Logical.getParentNode(c));
}
},
_nodeNeedsDistribution: function(node) {
return node && node.shadyRoot &&
DomApi.hasInsertionPoint(node.shadyRoot);
},
// a node being added is always in this same host as this.node.
_addNodeToHost: function(host, node) {
if (host._elementAdd) {
host._elementAdd(node);
}
},
_removeNodeFromHost: function(host, node) {
if (host._elementRemove) {
host._elementRemove(node);
}
},
_removeDistributedChildren: function(root, container) {
var hostNeedsDist;
var ip$ = root._insertionPoints;
for (var i=0; i<ip$.length; i++) {
var content = ip$[i];
if (this._contains(container, content)) {
var dc$ = dom(content).getDistributedNodes();
for (var j=0; j<dc$.length; j++) {
hostNeedsDist = true;
var node = dc$[j];
var parent = TreeApi.Composed.getParentNode(node);
if (parent) {
TreeApi.Composed.removeChild(parent, node);
}
}
}
}
return hostNeedsDist;
},
_contains: function(container, node) {
while (node) {
if (node == container) {
return true;
}
node = TreeApi.Logical.getParentNode(node);
}
},
_removeOwnerShadyRoot: function(node) {
// optimization: only reset the tree if node is actually in a root
if (this._hasCachedOwnerRoot(node)) {
var c$ = TreeApi.Logical.getChildNodes(node);
for (var i=0, l=c$.length, n; (i<l) && (n=c$[i]); i++) {
this._removeOwnerShadyRoot(n);
}
}
node._ownerShadyRoot = undefined;
},
// TODO(sorvell): This will fail if distribution that affects this
// question is pending; this is expected to be exceedingly rare, but if
// the issue comes up, we can force a flush in this case.
_firstComposedNode: function(content) {
var n$ = dom(content).getDistributedNodes();
for (var i=0, l=n$.length, n, p$; (i<l) && (n=n$[i]); i++) {
p$ = dom(n).getDestinationInsertionPoints();
// means that we're composed to this spot.
if (p$[p$.length-1] === content) {
return n;
}
}
},
// TODO(sorvell): consider doing native QSA and filtering results.
querySelector: function(selector) {
// match selector and halt on first result.
var result = this._query(function(n) {
return DomApi.matchesSelector.call(n, selector);
}, this.node, function(n) {
return Boolean(n);
})[0];
return result || null;
},
querySelectorAll: function(selector) {
return this._query(function(n) {
return DomApi.matchesSelector.call(n, selector);
}, this.node);
},
getDestinationInsertionPoints: function() {
return this.node._destinationInsertionPoints || [];
},
getDistributedNodes: function() {
return this.node._distributedNodes || [];
},
_clear: function() {
while (this.childNodes.length) {
this.removeChild(this.childNodes[0]);
}
},
setAttribute: function(name, value) {
this.node.setAttribute(name, value);
this._maybeDistributeParent();
},
removeAttribute: function(name) {
this.node.removeAttribute(name);
this._maybeDistributeParent();
},
_maybeDistributeParent: function() {
if (this._nodeNeedsDistribution(this.parentNode)) {
this._lazyDistribute(this.parentNode);
return true;
}
},
cloneNode: function(deep) {
var n = nativeCloneNode.call(this.node, false);
if (deep) {
var c$ = this.childNodes;
var d = dom(n);
for (var i=0, nc; i < c$.length; i++) {
nc = dom(c$[i]).cloneNode(true);
d.appendChild(nc);
}
}
return n;
},
importNode: function(externalNode, deep) {
// for convenience use this node's ownerDoc if the node isn't a document
var doc = this.node instanceof Document ? this.node :
this.node.ownerDocument;
var n = nativeImportNode.call(doc, externalNode, false);
if (deep) {
var c$ = TreeApi.Logical.getChildNodes(externalNode);
var d = dom(n);
for (var i=0, nc; i < c$.length; i++) {
nc = dom(doc).importNode(c$[i], true);
d.appendChild(nc);
}
}
return n;
},
_getComposedInnerHTML: function() {
return getInnerHTML(this.node, true);
}
});
Object.defineProperties(DomApi.prototype, {
activeElement: {
get: function() {
var active = document.activeElement;
if (!active) {
return null;
}
var isShadyRoot = !!this.node._isShadyRoot;
if (this.node !== document) {
// If this node isn't a document or shady root, then it doesn't have
// an active element.
if (!isShadyRoot) {
return null;
}
// If this shady root's host is the active element or the active
// element is not a descendant of the host (in the composed tree),
// then it doesn't have an active element.
if (this.node.host === active ||
!this.node.host.contains(active)) {
return null;
}
}
// This node is either the document or a shady root of which the active
// element is a (composed) descendant of its host; iterate upwards to
// find the active element's most shallow host within it.
var activeRoot = dom(active).getOwnerRoot();
while (activeRoot && activeRoot !== this.node) {
active = activeRoot.host;
activeRoot = dom(active).getOwnerRoot();
}
if (this.node === document) {
// This node is the document, so activeRoot should be null.
return activeRoot ? null : active;
} else {
// This node is a non-document shady root, and it should be
// activeRoot.
return activeRoot === this.node ? active : null;
}
},
configurable: true
},
childNodes: {
get: function() {
var c$ = TreeApi.Logical.getChildNodes(this.node);
return Array.isArray(c$) ? c$ : TreeApi.arrayCopyChildNodes(this.node);
},
configurable: true
},
children: {
get: function() {
if (TreeApi.Logical.hasChildNodes(this.node)) {
return Array.prototype.filter.call(this.childNodes, function(n) {
return (n.nodeType === Node.ELEMENT_NODE);
});
} else {
return TreeApi.arrayCopyChildren(this.node);
}
},
configurable: true
},
parentNode: {
get: function() {
return TreeApi.Logical.getParentNode(this.node);
},
configurable: true
},
firstChild: {
get: function() {
return TreeApi.Logical.getFirstChild(this.node);
},
configurable: true
},
lastChild: {
get: function() {
return TreeApi.Logical.getLastChild(this.node);
},
configurable: true
},
nextSibling: {
get: function() {
return TreeApi.Logical.getNextSibling(this.node);
},
configurable: true
},
previousSibling: {
get: function() {
return TreeApi.Logical.getPreviousSibling(this.node);
},
configurable: true
},
firstElementChild: {
get: function() {
return TreeApi.Logical.getFirstElementChild(this.node);
},
configurable: true
},
lastElementChild: {
get: function() {
return TreeApi.Logical.getLastElementChild(this.node);
},
configurable: true
},
nextElementSibling: {
get: function() {
return TreeApi.Logical.getNextElementSibling(this.node);
},
configurable: true
},
previousElementSibling: {
get: function() {
return TreeApi.Logical.getPreviousElementSibling(this.node);
},
configurable: true
},
// textContent / innerHTML
textContent: {
get: function() {
var nt = this.node.nodeType;
if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
return this.node.textContent;
} else {
var tc = [];
for (var i = 0, cn = this.childNodes, c; c = cn[i]; i++) {
if (c.nodeType !== Node.COMMENT_NODE) {
tc.push(c.textContent);
}
}
return tc.join('');
}
},
set: function(text) {
var nt = this.node.nodeType;
if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
this.node.textContent = text;
} else {
this._clear();
if (text) {
this.appendChild(document.createTextNode(text));
}
}
},
configurable: true
},
innerHTML: {
get: function() {
var nt = this.node.nodeType;
if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
return null;
} else {
return getInnerHTML(this.node);
}
},
set: function(text) {
var nt = this.node.nodeType;
if (nt !== Node.TEXT_NODE || nt !== Node.COMMENT_NODE) {
this._clear();
var d = document.createElement('div');
d.innerHTML = text;
// here, appendChild may move nodes async so we cannot rely
// on node position when copying
var c$ = TreeApi.arrayCopyChildNodes(d);
for (var i=0; i < c$.length; i++) {
this.appendChild(c$[i]);
}
}
},
configurable: true
}
});
DomApi.hasInsertionPoint = function(root) {
return Boolean(root && root._insertionPoints.length);
};
})();
</script>

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@ Polymer.domInnerHTML = (function() {
case '>':
return '&gt;';
case '"':
return '&quot;'
return '&quot;';
case '\u00A0':
return '&nbsp;';
}
@ -116,7 +116,6 @@ Polymer.domInnerHTML = (function() {
node = node.content;
var s = '';
var c$ = Polymer.dom(node).childNodes;
c$ = composed ? node._composedChildren : c$;
for (var i=0, l=c$.length, child; (i<l) && (child=c$[i]); i++) {
s += getOuterHTML(child, node, composed);
}

View File

@ -6,7 +6,7 @@
var lcModules = {};
var findModule = function(id) {
return modules[id] || lcModules[id.toLowerCase()];
}
};
/**
* The `dom-module` element registers the dom it contains to the name given
@ -72,8 +72,8 @@
if (!m) {
// If polyfilling, a script can run before a dom-module element
// is upgraded. We force the containing document to upgrade
// and try again to workaround this polyfill limitation.
forceDocumentUpgrade();
// dom-modules and try again to workaround this polyfill limitation.
forceDomModulesUpgrade();
m = findModule(id);
}
if (m && selector) {
@ -97,12 +97,20 @@
var cePolyfill = window.CustomElements && !CustomElements.useNative;
document.registerElement('dom-module', DomModule);
function forceDocumentUpgrade() {
function forceDomModulesUpgrade() {
if (cePolyfill) {
var script = document._currentScript || document.currentScript;
var doc = script && script.ownerDocument;
if (doc) {
CustomElements.upgradeAll(doc);
var doc = script && script.ownerDocument || document;
// find all dom-modules
var modules = doc.querySelectorAll('dom-module');
// minimize work by going backwards and stopping if we find an
// upgraded module.
for (var i= modules.length-1, m; (i >=0) && (m=modules[i]); i--) {
if (m.__upgraded__) {
return;
} else {
CustomElements.upgrade(m);
}
}
}
}

302
src/lib/dom-tree-api.html Normal file
View File

@ -0,0 +1,302 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<link rel="import" href="dom-innerHTML.html">
<script>
(function() {
'use strict';
// native add/remove
var nativeInsertBefore = Element.prototype.insertBefore;
var nativeAppendChild = Element.prototype.appendChild;
var nativeRemoveChild = Element.prototype.removeChild;
/**
* TreeApi is a dom manipulation library used by Shady/Polymer.dom to
* manipulate composed and logical trees.
*/
var TreeApi = Polymer.TreeApi = {
// sad but faster than slice...
arrayCopyChildNodes: function(parent) {
var copy=[], i=0;
for (var n=parent.firstChild; n; n=n.nextSibling) {
copy[i++] = n;
}
return copy;
},
arrayCopyChildren: function(parent) {
var copy=[], i=0;
for (var n=parent.firstElementChild; n; n=n.nextElementSibling) {
copy[i++] = n;
}
return copy;
},
arrayCopy: function(a$) {
var l = a$.length;
var copy = new Array(l);
for (var i=0; i < l; i++) {
copy[i] = a$[i];
}
return copy;
}
};
Polymer.TreeApi.Logical = {
hasParentNode: function(node) {
return Boolean(node.__dom && node.__dom.parentNode);
},
hasChildNodes: function(node) {
return Boolean(node.__dom && node.__dom.childNodes !== undefined);
},
getChildNodes: function(node) {
// note: we're distinguishing here between undefined and false-y:
// hasChildNodes uses undefined check to see if this element has logical
// children; the false-y check indicates whether or not we should rebuild
// the cached childNodes array.
return this.hasChildNodes(node) ? this._getChildNodes(node) :
node.childNodes;
},
_getChildNodes: function(node) {
if (!node.__dom.childNodes) {
node.__dom.childNodes = [];
for (var n=node.__dom.firstChild; n; n=n.__dom.nextSibling) {
node.__dom.childNodes.push(n);
}
}
return node.__dom.childNodes;
},
// NOTE: __dom can be created under 2 conditions: (1) an element has a
// logical tree, or (2) an element is in a logical tree. In case (1), the
// element will store firstChild/lastChild, and in case (2), the element
// will store parentNode, nextSibling, previousSibling. This means that
// the mere existence of __dom is not enough to know if the requested
// logical data is available and instead we do an explicit undefined check.
getParentNode: function(node) {
return node.__dom && node.__dom.parentNode !== undefined ?
node.__dom.parentNode : node.parentNode;
},
getFirstChild: function(node) {
return node.__dom && node.__dom.firstChild !== undefined ?
node.__dom.firstChild : node.firstChild;
},
getLastChild: function(node) {
return node.__dom && node.__dom.lastChild !== undefined ?
node.__dom.lastChild : node.lastChild;
},
getNextSibling: function(node) {
return node.__dom && node.__dom.nextSibling !== undefined ?
node.__dom.nextSibling : node.nextSibling;
},
getPreviousSibling: function(node) {
return node.__dom && node.__dom.previousSibling !== undefined ?
node.__dom.previousSibling : node.previousSibling;
},
getFirstElementChild: function(node) {
return node.__dom && node.__dom.firstChild !== undefined ?
this._getFirstElementChild(node) : node.firstElementChild;
},
_getFirstElementChild: function(node) {
var n = node.__dom.firstChild;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.nextSibling;
}
return n;
},
getLastElementChild: function(node) {
return node.__dom && node.__dom.lastChild !== undefined ?
this._getLastElementChild(node) : node.lastElementChild;
},
_getLastElementChild: function(node) {
var n = node.__dom.lastChild;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.previousSibling;
}
return n;
},
getNextElementSibling: function(node) {
return node.__dom && node.__dom.nextSibling !== undefined ?
this._getNextElementSibling(node) : node.nextElementSibling;
},
_getNextElementSibling: function(node) {
var n = node.__dom.nextSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.nextSibling;
}
return n;
},
getPreviousElementSibling: function(node) {
return node.__dom && node.__dom.previousSibling !== undefined ?
this._getPreviousElementSibling(node) : node.previousElementSibling;
},
_getPreviousElementSibling: function(node) {
var n = node.__dom.previousSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.previousSibling;
}
return n;
},
// Capture the list of light children. It's important to do this before we
// start transforming the DOM into "rendered" state.
// Children may be added to this list dynamically. It will be treated as the
// source of truth for the light children of the element. This element's
// actual children will be treated as the rendered state once this function
// has been called.
saveChildNodes: function(node) {
if (!this.hasChildNodes(node)) {
node.__dom = node.__dom || {};
node.__dom.firstChild = node.firstChild;
node.__dom.lastChild = node.lastChild;
node.__dom.childNodes = [];
for (var n=node.firstChild; n; n=n.nextSibling) {
n.__dom = n.__dom || {};
n.__dom.parentNode = node;
node.__dom.childNodes.push(n);
n.__dom.nextSibling = n.nextSibling;
n.__dom.previousSibling = n.previousSibling;
}
}
},
recordInsertBefore: function(node, container, ref_node) {
container.__dom.childNodes = null;
// handle document fragments
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// TODO(sorvell): remember this for patching:
// the act of setting this info can affect patched nodes
// getters; therefore capture childNodes before patching.
for (var n=node.firstChild; n; n=n.nextSibling) {
this._linkNode(n, container, ref_node);
}
} else {
this._linkNode(node, container, ref_node);
}
},
_linkNode: function(node, container, ref_node) {
node.__dom = node.__dom || {};
container.__dom = container.__dom || {};
if (ref_node) {
ref_node.__dom = ref_node.__dom || {};
}
// update ref_node.previousSibling <-> node
node.__dom.previousSibling = ref_node ? ref_node.__dom.previousSibling :
container.__dom.lastChild;
if (node.__dom.previousSibling) {
node.__dom.previousSibling.__dom.nextSibling = node;
}
// update node <-> ref_node
node.__dom.nextSibling = ref_node;
if (node.__dom.nextSibling) {
node.__dom.nextSibling.__dom.previousSibling = node;
}
// update node <-> container
node.__dom.parentNode = container;
if (ref_node) {
if (ref_node === container.__dom.firstChild) {
container.__dom.firstChild = node;
}
} else {
container.__dom.lastChild = node;
if (!container.__dom.firstChild) {
container.__dom.firstChild = node;
}
}
// remove caching of childNodes
container.__dom.childNodes = null;
},
recordRemoveChild: function(node, container) {
node.__dom = node.__dom || {};
container.__dom = container.__dom || {};
if (node === container.__dom.firstChild) {
container.__dom.firstChild = node.__dom.nextSibling;
}
if (node === container.__dom.lastChild) {
container.__dom.lastChild = node.__dom.previousSibling;
}
var p = node.__dom.previousSibling;
var n = node.__dom.nextSibling;
if (p) {
p.__dom.nextSibling = n;
}
if (n) {
n.__dom.previousSibling = p;
}
// When an element is removed, logical data is no longer tracked.
// Explicitly set `undefined` here to indicate this. This is disginguished
// from `null` which is set if info is null.
node.__dom.parentNode = node.__dom.previousSibling =
node.__dom.nextSibling = undefined;
// remove caching of childNodes
container.__dom.childNodes = null;
}
}
// TODO(sorvell): composed tree manipulation is made available
// (1) to maninpulate the composed tree, and (2) to track changes
// to the tree for optional patching pluggability.
Polymer.TreeApi.Composed = {
getChildNodes: function(node) {
return Polymer.TreeApi.arrayCopyChildNodes(node);
},
getParentNode: function(node) {
return node.parentNode;
},
// composed tracking needs to reset composed children here in case
// they may have already been set (this shouldn't happen but can
// if dependency ordering is incorrect and as a result upgrade order
// is unexpected)
clearChildNodes: function(node) {
node.textContent = '';
},
insertBefore: function(parentNode, newChild, refChild) {
return nativeInsertBefore.call(parentNode, newChild, refChild || null);
},
appendChild: function(parentNode, newChild) {
return nativeAppendChild.call(parentNode, newChild);
},
removeChild: function(parentNode, node) {
return nativeRemoveChild.call(parentNode, node);
}
};
})();
</script>

View File

@ -1,92 +0,0 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<script>
Polymer.EventApi = (function() {
var Settings = Polymer.Settings;
var EventApi = function(event) {
this.event = event;
};
if (Settings.useShadow) {
EventApi.prototype = {
get rootTarget() {
return this.event.path[0];
},
get localTarget() {
return this.event.target;
},
get path() {
return this.event.path;
}
};
} else {
EventApi.prototype = {
get rootTarget() {
return this.event.target;
},
get localTarget() {
var current = this.event.currentTarget;
var currentRoot = current && Polymer.dom(current).getOwnerRoot();
var p$ = this.path;
for (var i=0; i < p$.length; i++) {
if (Polymer.dom(p$[i]).getOwnerRoot() === currentRoot) {
return p$[i];
}
}
},
// TODO(sorvell): simulate event.path. This probably incorrect for
// non-bubbling events.
get path() {
if (!this.event._path) {
var path = [];
var o = this.rootTarget;
while (o) {
path.push(o);
o = Polymer.dom(o).parentNode || o.host;
}
// event path includes window in most recent native implementations
path.push(window);
this.event._path = path;
}
return this.event._path;
}
};
}
var factory = function(event) {
if (!event.__eventApi) {
event.__eventApi = new EventApi(event);
}
return event.__eventApi;
};
return {
factory: factory
};
})();
</script>

View File

@ -107,6 +107,7 @@ Polymer.ObserveJsBehavior = {
},
_removeObserveJsObserver: function(object, path) {
var obs = this._observeJsStatic.observerMap.get(object);
var el = obs.elements[this._observeJsId];
if (--el.paths[path] === 0) {
delete el.paths[path];
@ -171,4 +172,4 @@ Polymer.ObserveJsBehavior = {
// Poll observe-js all the time
setInterval(Platform.performMicrotaskCheckpoint, 125);
</script>
</script>

View File

@ -22,7 +22,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
(function() {
// ******* Only patch if we're using Shady DOM *******
if (Polymer.Settings.useShadow) {
return;
}
var baseFinishDistribute = Polymer.Base._finishDistribute;
var TreeApi = Polymer.TreeApi;
var DomApi = Polymer.DomApi;
var dom = Polymer.dom;
var nativeInsertBefore = Element.prototype.insertBefore;
var nativeAppendChild = Element.prototype.appendChild;
var nativeRemoveChild = Element.prototype.removeChild;
// NOTE: any manipulation of a node must occur in a patched parent
// so that the parent can cleanup the node's composed and logical
@ -30,13 +42,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// likely to be satisfied.
// Also note that any use of qS/qSA must be done in a patched node.
Polymer.Base._finishDistribute = function() {
var hasDistributed = this.root._hasDistributed;
baseFinishDistribute.call(this);
if (!this.__patched) {
if (!hasDistributed) {
for (var n=this.firstChild; n; n=n.nextSibling) {
Polymer.dom(n);
};
Polymer.dom(this);
Polymer.dom(this.root);
Array.prototype.forEach.call(this.childNodes, function(c) {
Polymer.dom(c);
});
// TODO(sorvell): ensure top element's parents are wrapped (helped A2
// since it uses qSA on the fragment containing stamped custom elements)
// note that getOwnerRoot will patch all parents but there should be an
@ -48,34 +61,31 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
};
var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded;
var getComposedChildren = Polymer.DomApi.getComposedChildren;
var nativeShadow = Polymer.Settings.useShadow;
var excluded = ['head'];
Polymer.telemetry.patched = 0;
// experimental: support patching selected native api.
Polymer.DomApi.ctor.prototype.patch = function(force) {
if (nativeShadow || this.node.__patched ||
(this.node.localName && excluded.indexOf(this.node.localName) >= 0)) {
return;
}
var ctor = Polymer.DomApi.ctor;
Polymer.DomApi.ctor = function(node) {
Polymer.DomApi.patch(node);
return new ctor(node);
}
getComposedChildren(this.node);
saveLightChildrenIfNeeded(this.node);
if (!this.node._lightParent) {
this.node._lightParent = this.node.parentNode;
}
if (!this.node._composedParent) {
this.node._composedParent = this.node.parentNode;
}
// TODO(sorvell): correctly patch non-element nodes.
if (this.node.nodeType !== Node.TEXT_NODE) {
this.node.__patched = true;
patchImpl.patch(this.node);
Polymer.DomApi.ctor.prototype = ctor.prototype;
Polymer.DomApi.patch = function(node) {
if (!node.__patched &&
(!node.localName || !excluded.indexOf(node.localName) >= 0)) {
TreeApi.Logical.saveChildNodes(node);
if (!TreeApi.Composed.hasParentNode(node)) {
TreeApi.Composed.saveParentNode(node);
}
TreeApi.Composed.saveChildNodes(node);
// TODO(sorvell): correctly patch non-element nodes.
if (node.nodeType !== Node.TEXT_NODE) {
node.__patched = true;
patchImpl.patch(node);
}
}
};
@ -90,9 +100,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this.unpatch();
};
var log = false;
// allows attribute setting to be patched
var nativeSetAttribute = Element.prototype.setAttribute;
Polymer.DomApi.ctor.prototype.setAttribute = function(name, value) {
nativeSetAttribute.call(this.node, name, value);
this._maybeDistributeParent();
};
var factory = Polymer.DomApi.factory;
var nativeRemoveAttribute = Element.prototype.removeAttribute;
Polymer.DomApi.ctor.prototype.removeAttribute = function(name, value) {
nativeRemoveAttribute.call(this.node, name);
this._maybeDistributeParent();
};
var log = false;
var patchImpl = {
@ -101,7 +123,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
methods: ['appendChild', 'insertBefore', 'removeChild', 'replaceChild',
'querySelector', 'querySelectorAll', 'getDestinationInsertionPoints',
'cloneNode', 'importNode'],
'cloneNode', 'setAttribute', 'removeAttribute'],
// <content>: getDistributedNodes
accessors: ['parentNode', 'childNodes',
@ -160,7 +182,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
obj['_$' + name + '$_'] = orig;
obj[name] = function() {
log && console.log(this, name, arguments);
return factory(this)[name].apply(this.__domApi, arguments);
return dom(this)[name].apply(this.__domApi, arguments);
};
},
@ -170,14 +192,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
var info = {
get: function() {
log && console.log(this, name);
return factory(this)[name];
return dom(this)[name];
},
configurable: true
};
if (writable) {
info.set = function(value) {
factory(this)[name] = value;
dom(this)[name] = value;
};
}
Object.defineProperty(obj, name, info);
@ -216,20 +237,365 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
};
Polymer.DomApi.getLightChildren = function(node) {
var children = node._lightChildren;
return children ? children : Polymer.DomApi.getComposedChildren(node);
}
Polymer.Base.instanceTemplate = function(template) {
var m = document._$importNode$_ || document.importNode;
var dom =
m.call(document, template._content || template.content, true);
return dom;
}
Polymer.DomApi.patchImpl = patchImpl;
// NOTE: patch logical implementations here so we can use
// composed getters
// TODO(sorvell): may need to patch saveChildNodes iff the tree has
// already been distributed.
TreeApi.Logical.recordInsertBefore = function(node, container, ref_node) {
container.__dom.childNodes = null;
// handle document fragments
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// TODO(sorvell): remember this for patching:
// the act of setting this info can affect patched nodes
// getters; therefore capture childNodes before patching.
for (var n=TreeApi.Composed.getFirstChild(node); n;
n=TreeApi.Composed.getNextSibling(n)) {
this._linkNode(n, container, ref_node);
}
} else {
this._linkNode(node, container, ref_node);
}
}
TreeApi.Logical.getParentNode = function(node) {
return node.__dom && node.__dom.parentNode ||
TreeApi.Composed.getParentNode(node);
};
TreeApi.Logical.getFirstChild = function(node) {
return node.__dom && node.__dom.firstChild ||
TreeApi.Composed.getFirstChild(node);
};
TreeApi.Logical.getLastChild = function(node) {
return node.__dom && node.__dom.lastChild ||
TreeApi.Composed.getLastChild(node);
};
TreeApi.Logical.getNextSibling = function(node) {
return node.__dom && node.__dom.nextSibling ||
TreeApi.Composed.getNextSibling(node);
};
TreeApi.Logical.getPreviousSibling = function(node) {
return node.__dom && node.__dom.previousSibling ||
TreeApi.Composed.getPreviousSibling(node);
};
TreeApi.Logical.getFirstElementChild = function(node) {
return node.__dom && node.__dom.firstChild ?
this._getFirstElementChild(node) :
TreeApi.Composed.getFirstElementChild(node);
};
TreeApi.Logical.getLastElementChild = function(node) {
return node.__dom && node.__dom.lastChild ?
this._getLastElementChild(node) :
TreeApi.Composed.getLastElementChild(node);
};
TreeApi.Logical.getNextElementSibling = function(node) {
return node.__dom && node.__dom.nextSibling ?
this._getNextElementSibling(node) :
TreeApi.Composed.getNextElementSibling(node);
};
TreeApi.Logical.getPreviousElementSibling = function(node) {
return node.__dom && node.__dom.previousSibling ?
this._getPreviousElementSibling(node) :
TreeApi.Composed.getPreviousElementSibling(node);
};
// TODO(sorvell): This is largely copy/pasted from the Logical tree
// implementation. The code could be factored such that it could be shared
// but there are perf trade offs to consider.
TreeApi.Composed = {
hasParentNode: function(node) {
return Boolean(node.__dom && node.__dom.$parentNode !== undefined);
},
hasChildNodes: function(node) {
return Boolean(node.__dom && node.__dom.$childNodes !== undefined);
},
getChildNodes: function(node) {
return this.hasChildNodes(node) ? this._getChildNodes(node) :
(!node.__patched && TreeApi.arrayCopy(node.childNodes));
},
_getChildNodes: function(node) {
if (!node.__dom.$childNodes) {
node.__dom.$childNodes = [];
for (var n=node.__dom.$firstChild; n; n=n.__dom.$nextSibling) {
node.__dom.$childNodes.push(n);
}
}
return node.__dom.$childNodes;
},
getComposedChildNodes: function(node) {
return node.__dom.$childNodes;
},
getParentNode: function(node) {
return this.hasParentNode(node) ? node.__dom.$parentNode :
(!node.__patched && node.parentNode);
},
getFirstChild: function(node) {
return node.__patched ? node.__dom.$firstChild : node.firstChild;
},
getLastChild: function(node) {
return node.__patched ? node.__dom.$lastChild : node.lastChild;
},
getNextSibling: function(node) {
return node.__patched ? node.__dom.$nextSibling : node.nextSibling;
},
getPreviousSibling: function(node) {
return node.__patched ? node.__dom.$previousSibling : node.previousSibling;
},
getFirstElementChild: function(node) {
return node.__patched ? this._getFirstElementChild(node) :
node.firstElementChild;
},
_getFirstElementChild: function(node) {
var n = node.__dom.$firstChild;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.$nextSibling;
}
return n;
},
getLastElementChild: function(node) {
return node.__patched ? this._getLastElementChild(node) :
node.firstElementChild;
},
_getLastElementChild: function(node) {
var n = node.__dom.$lastChild;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.$previousSibling;
}
return n;
},
getNextElementSibling: function(node) {
return node.__patched ? this._getNextElementSibling(node) :
node.nextElementSibling;
},
_getNextElementSibling: function(node) {
var n = node.__dom.$nextSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.$nextSibling;
}
return n;
},
getPreviousElementSibling: function(node) {
return node.__patched ? this._getPreviousElementSibling(node) :
node.previousElementSibling;
},
_getPreviousElementSibling: function(node) {
var n = node.__dom.$previousSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.$previousSibling;
}
return n;
},
saveChildNodes: function(node) {
if (!this.hasChildNodes(node)) {
node.__dom = node.__dom || {};
node.__dom.$firstChild = node.firstChild;
node.__dom.$lastChild = node.lastChild;
node.__dom.$childNodes = [];
for (var n=node.firstChild; n; n=n.nextSibling) {
n.__dom = n.__dom || {};
n.__dom.$parentNode = node;
node.__dom.$childNodes.push(n);
n.__dom.$nextSibling = n.nextSibling;
n.__dom.$previousSibling = n.previousSibling;
}
}
},
recordInsertBefore: function(node, container, ref_node) {
container.__dom.$childNodes = null;
// handle document fragments
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// TODO(sorvell): remember this for patching:
// the act of setting this info can affect patched nodes
// getters; therefore capture childNodes before patching.
for (var n=this.getFirstChild(node); n; n=this.getNextSibling(n)) {
this._linkNode(n, container, ref_node);
}
} else {
this._linkNode(node, container, ref_node);
}
},
_linkNode: function(node, container, ref_node) {
node.__dom = node.__dom || {};
container.__dom = container.__dom || {};
if (ref_node) {
ref_node.__dom = ref_node.__dom || {};
}
// update ref_node.previousSibling <-> node
node.__dom.$previousSibling = ref_node ? ref_node.__dom.$previousSibling :
container.__dom.$lastChild;
if (node.__dom.$previousSibling) {
node.__dom.$previousSibling.__dom.$nextSibling = node;
}
// update node <-> ref_node
node.__dom.$nextSibling = ref_node;
if (node.__dom.$nextSibling) {
node.__dom.$nextSibling.__dom.$previousSibling = node;
}
// update node <-> container
node.__dom.$parentNode = container;
if (ref_node) {
if (ref_node === container.__dom.$firstChild) {
container.__dom.$firstChild = node;
}
} else {
container.__dom.$lastChild = node;
if (!container.__dom.$firstChild) {
container.__dom.$firstChild = node;
}
}
// remove caching of childNodes
container.__dom.$childNodes = null;
},
recordRemoveChild: function(node, container) {
node.__dom = node.__dom || {};
container.__dom = container.__dom || {};
if (node === container.__dom.$firstChild) {
container.__dom.$firstChild = node.__dom.$nextSibling;
}
if (node === container.__dom.$lastChild) {
container.__dom.$lastChild = node.__dom.$previousSibling;
}
var p = node.__dom.$previousSibling;
var n = node.__dom.$nextSibling;
if (p) {
p.__dom.$nextSibling = n;
}
if (n) {
n.__dom.$previousSibling = p;
}
node.__dom.$parentNode = node.__dom.$previousSibling =
node.__dom.$nextSibling = null;
// remove caching of childNodes
container.__dom.$childNodes = null;
},
// composed tracking needs to reset composed children here in case
// they may have already been set (this shouldn't happen but can
// if dependency ordering is incorrect and as a result upgrade order
// is unexpected)
clearChildNodes: function(node) {
if (node.__dom && node.__dom.$parentNode) {
node.__dom.$parentNode.__dom.$childNodes = [];
}
node.textContent = '';
},
saveParentNode: function(node) {
node.__dom = node.__dom || {};
node.__dom.$parentNode = node.parentNode;
},
insertBefore: function(parentNode, newChild, refChild) {
this.saveChildNodes(parentNode);
// remove from current location.
if (this.hasParentNode(newChild)) {
var oldParent = this.getParentNode(newChild);
if (oldParent) {
this._removeChild(oldParent, newChild);
}
}
this._addChild(parentNode, newChild, refChild);
return nativeInsertBefore.call(parentNode, newChild, refChild || null);
},
appendChild: function(parentNode, newChild) {
this.saveChildNodes(parentNode);
// remove from current location.
if (this.hasParentNode(newChild)) {
var oldParent = this.getParentNode(newChild);
if (oldParent) {
this._removeChild(oldParent, newChild);
}
}
this._addChild(parentNode, newChild);
return nativeAppendChild.call(parentNode, newChild);
},
removeChild: function(parentNode, node) {
var currentParent = this.getParentNode(node);
this.saveChildNodes(parentNode);
this._removeChild(parentNode, node);
if (currentParent === parentNode) {
// TODO(sorvell); abort if the composedParent is not expected...
if (!node.__patched && node.parentNode !== node.__dom.$parentNode) {
return;
}
return nativeRemoveChild.call(parentNode, node);
}
},
_addChild: function(parentNode, newChild, refChild) {
if (newChild.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
var c$ = this.getChildNodes(newChild);
for (var j=0; j < c$.length; j++) {
this._addChild(parentNode, c$[j], refChild);
}
} else {
newChild.__dom = newChild.__dom || {};
newChild.__dom.$parentNode = parentNode;
var c$ = this.getComposedChildNodes(parentNode);
if (c$) {
var i = c$.indexOf(refChild);
i = i === -1 ? c$.length : i;
c$.splice(i, 0, newChild);
}
}
},
_removeChild: function(parentNode, node) {
node.__dom = node.__dom || {};
node.__dom.$parentNode = null;
var c$ = this.getComposedChildNodes(parentNode);
if (c$) {
var i = c$.indexOf(node);
if (i >= 0) {
c$.splice(i, 1);
}
}
}
};
// patch important nodes
if (window.document) {
Polymer.dom(document);
if (document.body) {
Polymer.dom(document.body);
}
}
})();
</script>

View File

@ -193,7 +193,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// remote api
shadowize = function() {
var shadowize = function() {
var idx = Number(this.attributes.idx.value);
//alert(idx);
var node = drillable[idx];

View File

@ -60,8 +60,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return prototype.constructor;
};
window.Polymer = Polymer;
if (userPolymer) {
for (var i in userPolymer) {
Polymer[i] = userPolymer[i];
@ -98,4 +96,3 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
};
</script>

View File

@ -37,9 +37,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_makeReady: function() {
this._ready = true;
this._callbacks.forEach(function(cb) {
cb();
});
for (var i=0; i < this._callbacks.length; i++) {
this._callbacks[i]();
}
this._callbacks = [];
},
@ -47,6 +47,45 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
requestAnimationFrame(function() {
Polymer.RenderStatus._makeReady();
});
},
_afterNextRenderQueue: [],
_waitingNextRender: false,
afterNextRender: function(element, fn, args) {
this._watchNextRender();
this._afterNextRenderQueue.push([element, fn, args]);
},
_watchNextRender: function() {
if (!this._waitingNextRender) {
this._waitingNextRender = true;
var fn = function() {
Polymer.RenderStatus._flushNextRender();
}
if (!this._ready) {
this.whenReady(fn);
} else {
requestAnimationFrame(fn);
}
}
},
_flushNextRender: function() {
var self = this;
// we want to defer after render until just after the paint.
setTimeout(function() {
self._flushRenderCallbacks(self._afterNextRenderQueue);
self._afterNextRenderQueue = [];
self._waitingNextRender = false;
});
},
_flushRenderCallbacks: function(callbacks) {
for (var i=0, h; i < callbacks.length; i++) {
h = callbacks[i];
h[1].apply(h[0], h[2] || Polymer.nar);
};
}
};

View File

@ -21,10 +21,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var user = window.Polymer || {};
// via url
location.search.slice(1).split('&').forEach(function(o) {
var parts = location.search.slice(1).split('&');
for (var i=0, o; (i < parts.length) && (o=parts[i]); i++) {
o = o.split('=');
o[0] && (user[o[0]] = o[1] || true);
});
}
var wantShadow = (user.dom === 'shadow');
var hasShadow = Boolean(Element.prototype.createShadowRoot);

View File

@ -12,7 +12,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.StyleCache = function() {
this.cache = {};
}
};
Polymer.StyleCache.prototype = {

View File

@ -67,7 +67,7 @@ Polymer.StyleExtends = (function() {
if (target.parent !== source.parent) {
this._cloneAndAddRuleToParent(source, target.parent);
}
target.extends = target.extends || (target.extends = []);
target.extends = target.extends || [];
target.extends.push(source);
// TODO: this misses `%foo, .bar` as an unetended selector but
// this seems rare and could possibly be unsupported.

View File

@ -1,382 +1,382 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="style-transformer.html">
<script>
Polymer.StyleProperties = (function() {
'use strict';
var nativeShadow = Polymer.Settings.useNativeShadow;
var matchesSelector = Polymer.DomApi.matchesSelector;
var styleUtil = Polymer.StyleUtil;
var styleTransformer = Polymer.StyleTransformer;
return {
// decorates styles with rule info and returns an array of used style
// property names
decorateStyles: function(styles) {
var self = this, props = {};
styleUtil.forRulesInStyles(styles, function(rule) {
self.decorateRule(rule);
self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
});
// return this list of property names *consumes* in these styles.
var names = [];
for (var i in props) {
names.push(i);
}
return names;
},
// decorate a single rule with property info
decorateRule: function(rule) {
if (rule.propertyInfo) {
return rule.propertyInfo;
}
var info = {}, properties = {};
var hasProperties = this.collectProperties(rule, properties);
if (hasProperties) {
info.properties = properties;
// TODO(sorvell): workaround parser seeing mixins as additional rules
rule.rules = null;
}
info.cssText = this.collectCssText(rule);
rule.propertyInfo = info;
return info;
},
// collects the custom properties from a rule's cssText
collectProperties: function(rule, properties) {
var info = rule.propertyInfo;
if (info) {
if (info.properties) {
Polymer.Base.mixin(properties, info.properties);
return true;
}
} else {
var m, rx = this.rx.VAR_ASSIGN;
var cssText = rule.parsedCssText;
var any;
while (m = rx.exec(cssText)) {
// note: group 2 is var, 3 is mixin
properties[m[1]] = (m[2] || m[3]).trim();
any = true;
}
return any;
}
},
// returns cssText of properties that consume variables/mixins
collectCssText: function(rule) {
var customCssText = '';
var cssText = rule.parsedCssText;
// NOTE: we support consumption inside mixin assignment
// but not production, so strip out {...}
cssText = cssText.replace(this.rx.BRACKETED, '')
.replace(this.rx.VAR_ASSIGN, '');
var parts = cssText.split(';');
for (var i=0, p; i<parts.length; i++) {
p = parts[i];
if (p.match(this.rx.MIXIN_MATCH) || p.match(this.rx.VAR_MATCH)) {
customCssText += p + ';\n';
}
}
return customCssText;
},
collectPropertiesInCssText: function(cssText, props) {
var m;
while (m = this.rx.VAR_CAPTURE.exec(cssText)) {
props[m[1]] = true;
var def = m[2];
if (def && def.match(this.rx.IS_VAR)) {
props[def] = true;
}
}
},
// turns custom properties into realized values.
reify: function(props) {
// big perf optimization here: reify only *own* properties
// since this object has __proto__ of the element's scope properties
var names = Object.getOwnPropertyNames(props);
for (var i=0, n; i < names.length; i++) {
n = names[i];
props[n] = this.valueForProperty(props[n], props);
}
},
// given a property value, returns the reified value
// a property value may be:
// (1) a literal value like: red or 5px;
// (2) a variable value like: var(--a), var(--a, red), or var(--a, --b);
// (3) a literal mixin value like { properties }. Each of these properties
// can have values that are: (a) literal, (b) variables, (c) @apply mixins.
valueForProperty: function(property, props) {
// case (1) default
// case (3) defines a mixin and we have to reify the internals
if (property) {
if (property.indexOf(';') >=0) {
property = this.valueForProperties(property, props);
} else {
// case (2) variable
var self = this;
var fn = function(all, prefix, value, fallback) {
var propertyValue = (self.valueForProperty(props[value], props) ||
(props[fallback] ?
self.valueForProperty(props[fallback], props) :
fallback));
return prefix + (propertyValue || '');
};
property = property.replace(this.rx.VAR_MATCH, fn);
}
}
return property && property.trim() || '';
},
// note: we do not yet support mixin within mixin
valueForProperties: function(property, props) {
var parts = property.split(';');
for (var i=0, p, m; i<parts.length; i++) {
if (p = parts[i]) {
m = p.match(this.rx.MIXIN_MATCH);
if (m) {
p = this.valueForProperty(props[m[1]], props);
} else {
var pp = p.split(':');
if (pp[1]) {
pp[1] = pp[1].trim();
pp[1] = this.valueForProperty(pp[1], props) || pp[1];
}
p = pp.join(':');
}
parts[i] = (p && p.lastIndexOf(';') === p.length - 1) ?
// strip trailing ;
p.slice(0, -1) :
p || '';
}
}
return parts.join(';');
},
applyProperties: function(rule, props) {
var output = '';
// dynamically added sheets may not be decorated so ensure they are.
if (!rule.propertyInfo) {
this.decorateRule(rule);
}
if (rule.propertyInfo.cssText) {
output = this.valueForProperties(rule.propertyInfo.cssText, props);
}
rule.cssText = output;
},
// Test if the rules in these styles matche the given `element` and if so,
// collect any custom properties into `props`.
propertyDataFromStyles: function(styles, element) {
var props = {}, self = this;
// generates a unique key for these matches
var o = [], i = 0;
styleUtil.forRulesInStyles(styles, function(rule) {
// TODO(sorvell): we could trim the set of rules at declaration
// time to only include ones that have properties
if (!rule.propertyInfo) {
self.decorateRule(rule);
}
// match element against transformedSelector: selector may contain
// unwanted uniquification and parsedSelector does not directly match
// for :host selectors.
if (element && rule.propertyInfo.properties &&
matchesSelector.call(element, rule.transformedSelector
|| rule.parsedSelector)) {
self.collectProperties(rule, props);
// produce numeric key for these matches for lookup
addToBitMask(i, o);
}
i++;
});
return {properties: props, key: o};
},
// Test if a rule matches scope crteria (* or :root) and if so,
// collect any custom properties into `props`.
scopePropertiesFromStyles: function(styles) {
if (!styles._scopeStyleProperties) {
styles._scopeStyleProperties =
this.selectedPropertiesFromStyles(styles, this.SCOPE_SELECTORS);
}
return styles._scopeStyleProperties;
},
// Test if a rule matches host crteria (:host) and if so,
// collect any custom properties into `props`.
//
// TODO(sorvell): this should change to collecting properties from any
// :host(...) and then matching these against self.
hostPropertiesFromStyles: function(styles) {
if (!styles._hostStyleProperties) {
styles._hostStyleProperties =
this.selectedPropertiesFromStyles(styles, this.HOST_SELECTORS);
}
return styles._hostStyleProperties;
},
selectedPropertiesFromStyles: function(styles, selectors) {
var props = {}, self = this;
styleUtil.forRulesInStyles(styles, function(rule) {
if (!rule.propertyInfo) {
self.decorateRule(rule);
}
for (var i=0; i < selectors.length; i++) {
if (rule.parsedSelector === selectors[i]) {
self.collectProperties(rule, props);
return;
}
}
});
return props;
},
transformStyles: function(element, properties, scopeSelector) {
var self = this;
var hostSelector = styleTransformer
._calcHostScope(element.is, element.extends);
var rxHostSelector = element.extends ?
'\\' + hostSelector.slice(0, -1) + '\\]' :
hostSelector;
var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector +
this.rx.HOST_SUFFIX);
return styleTransformer.elementStyles(element, function(rule) {
self.applyProperties(rule, properties);
if (rule.cssText && !nativeShadow) {
self._scopeSelector(rule, hostRx, hostSelector,
element._scopeCssViaAttr, scopeSelector);
}
});
},
// Strategy: x scope shim a selector e.g. to scope `.x-foo-42` (via classes):
// non-host selector: .a.x-foo -> .x-foo-42 .a.x-foo
// host selector: x-foo.wide -> x-foo.x-foo-42.wide
_scopeSelector: function(rule, hostRx, hostSelector, viaAttr, scopeId) {
rule.transformedSelector = rule.transformedSelector || rule.selector;
var selector = rule.transformedSelector;
var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' +
scopeId + ']' :
'.' + scopeId;
var parts = selector.split(',');
for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) {
parts[i] = p.match(hostRx) ?
p.replace(hostSelector, hostSelector + scope) :
scope + ' ' + p;
}
rule.selector = parts.join(',');
},
applyElementScopeSelector: function(element, selector, old, viaAttr) {
var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) :
element.className;
var v = old ? c.replace(old, selector) :
(c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector;
if (c !== v) {
if (viaAttr) {
element.setAttribute(styleTransformer.SCOPE_NAME, v);
} else {
element.className = v;
}
}
},
applyElementStyle: function(element, properties, selector, style) {
// calculate cssText to apply
var cssText = style ? style.textContent || '' :
this.transformStyles(element, properties, selector);
// if shady and we have a cached style that is not style, decrement
var s = element._customStyle;
if (s && !nativeShadow && (s !== style)) {
s._useCount--;
if (s._useCount <= 0 && s.parentNode) {
s.parentNode.removeChild(s);
}
}
// apply styling always under native or if we generated style
// or the cached style is not in document(!)
if (nativeShadow || (!style || !style.parentNode)) {
// update existing style only under native
if (nativeShadow && element._customStyle) {
element._customStyle.textContent = cssText;
style = element._customStyle;
// otherwise, if we have css to apply, do so
} else if (cssText) {
// apply css after the scope style of the element to help with
// style predence rules.
style = styleUtil.applyCss(cssText, selector,
nativeShadow ? element.root : null, element._scopeStyle);
}
}
// ensure this style is our custom style and increment its use count.
if (style) {
style._useCount = style._useCount || 0;
// increment use count if we changed styles
if (element._customStyle != style) {
style._useCount++;
}
element._customStyle = style;
}
return style;
},
// customStyle properties are applied if they are truthy or 0. Otherwise,
// they are skipped; this allows properties previously set in customStyle
// to be easily reset to inherited values.
mixinCustomStyle: function(props, customStyle) {
var v;
for (var i in customStyle) {
v = customStyle[i];
if (v || v === 0) {
props[i] = v;
}
}
},
rx: {
VAR_ASSIGN: /(?:^|[;\n]\s*)(--[\w-]*?):\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\n])|$)/gi,
MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i,
// note, this supports:
// var(--a)
// var(--a, --b)
// var(--a, fallback-literal)
// var(--a, fallback-literal(with-one-nested-parens))
VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi,
VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi,
IS_VAR: /^--/,
BRACKETED: /\{[^}]*\}/g,
HOST_PREFIX: '(?:^|[^.#[:])',
HOST_SUFFIX: '($|[.:[\\s>+~])'
},
HOST_SELECTORS: [':host'],
SCOPE_SELECTORS: [':root'],
XSCOPE_NAME: 'x-scope'
};
function addToBitMask(n, bits) {
var o = parseInt(n / 32);
var v = 1 << (n % 32);
bits[o] = (bits[o] || 0) | v;
}
})();
</script>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="style-transformer.html">
<script>
Polymer.StyleProperties = (function() {
'use strict';
var nativeShadow = Polymer.Settings.useNativeShadow;
var matchesSelector = Polymer.DomApi.matchesSelector;
var styleUtil = Polymer.StyleUtil;
var styleTransformer = Polymer.StyleTransformer;
return {
// decorates styles with rule info and returns an array of used style
// property names
decorateStyles: function(styles) {
var self = this, props = {};
styleUtil.forRulesInStyles(styles, function(rule) {
self.decorateRule(rule);
self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
});
// return this list of property names *consumes* in these styles.
var names = [];
for (var i in props) {
names.push(i);
}
return names;
},
// decorate a single rule with property info
decorateRule: function(rule) {
if (rule.propertyInfo) {
return rule.propertyInfo;
}
var info = {}, properties = {};
var hasProperties = this.collectProperties(rule, properties);
if (hasProperties) {
info.properties = properties;
// TODO(sorvell): workaround parser seeing mixins as additional rules
rule.rules = null;
}
info.cssText = this.collectCssText(rule);
rule.propertyInfo = info;
return info;
},
// collects the custom properties from a rule's cssText
collectProperties: function(rule, properties) {
var info = rule.propertyInfo;
if (info) {
if (info.properties) {
Polymer.Base.mixin(properties, info.properties);
return true;
}
} else {
var m, rx = this.rx.VAR_ASSIGN;
var cssText = rule.parsedCssText;
var any;
while (m = rx.exec(cssText)) {
// note: group 2 is var, 3 is mixin
properties[m[1]] = (m[2] || m[3]).trim();
any = true;
}
return any;
}
},
// returns cssText of properties that consume variables/mixins
collectCssText: function(rule) {
var customCssText = '';
var cssText = rule.parsedCssText;
// NOTE: we support consumption inside mixin assignment
// but not production, so strip out {...}
cssText = cssText.replace(this.rx.BRACKETED, '')
.replace(this.rx.VAR_ASSIGN, '');
var parts = cssText.split(';');
for (var i=0, p; i<parts.length; i++) {
p = parts[i];
if (p.match(this.rx.MIXIN_MATCH) || p.match(this.rx.VAR_MATCH)) {
customCssText += p + ';\n';
}
}
return customCssText;
},
collectPropertiesInCssText: function(cssText, props) {
var m;
while (m = this.rx.VAR_CAPTURE.exec(cssText)) {
props[m[1]] = true;
var def = m[2];
if (def && def.match(this.rx.IS_VAR)) {
props[def] = true;
}
}
},
// turns custom properties into realized values.
reify: function(props) {
// big perf optimization here: reify only *own* properties
// since this object has __proto__ of the element's scope properties
var names = Object.getOwnPropertyNames(props);
for (var i=0, n; i < names.length; i++) {
n = names[i];
props[n] = this.valueForProperty(props[n], props);
}
},
// given a property value, returns the reified value
// a property value may be:
// (1) a literal value like: red or 5px;
// (2) a variable value like: var(--a), var(--a, red), or var(--a, --b);
// (3) a literal mixin value like { properties }. Each of these properties
// can have values that are: (a) literal, (b) variables, (c) @apply mixins.
valueForProperty: function(property, props) {
// case (1) default
// case (3) defines a mixin and we have to reify the internals
if (property) {
if (property.indexOf(';') >=0) {
property = this.valueForProperties(property, props);
} else {
// case (2) variable
var self = this;
var fn = function(all, prefix, value, fallback) {
var propertyValue = (self.valueForProperty(props[value], props) ||
(props[fallback] ?
self.valueForProperty(props[fallback], props) :
fallback));
return prefix + (propertyValue || '');
};
property = property.replace(this.rx.VAR_MATCH, fn);
}
}
return property && property.trim() || '';
},
// note: we do not yet support mixin within mixin
valueForProperties: function(property, props) {
var parts = property.split(';');
for (var i=0, p, m; i<parts.length; i++) {
if (p = parts[i]) {
m = p.match(this.rx.MIXIN_MATCH);
if (m) {
p = this.valueForProperty(props[m[1]], props);
} else {
var pp = p.split(':');
if (pp[1]) {
pp[1] = pp[1].trim();
pp[1] = this.valueForProperty(pp[1], props) || pp[1];
}
p = pp.join(':');
}
parts[i] = (p && p.lastIndexOf(';') === p.length - 1) ?
// strip trailing ;
p.slice(0, -1) :
p || '';
}
}
return parts.join(';');
},
applyProperties: function(rule, props) {
var output = '';
// dynamically added sheets may not be decorated so ensure they are.
if (!rule.propertyInfo) {
this.decorateRule(rule);
}
if (rule.propertyInfo.cssText) {
output = this.valueForProperties(rule.propertyInfo.cssText, props);
}
rule.cssText = output;
},
// Test if the rules in these styles matches the given `element` and if so,
// collect any custom properties into `props`.
propertyDataFromStyles: function(styles, element) {
var props = {}, self = this;
// generates a unique key for these matches
var o = [], i = 0;
styleUtil.forRulesInStyles(styles, function(rule) {
// TODO(sorvell): we could trim the set of rules at declaration
// time to only include ones that have properties
if (!rule.propertyInfo) {
self.decorateRule(rule);
}
// match element against transformedSelector: selector may contain
// unwanted uniquification and parsedSelector does not directly match
// for :host selectors.
if (element && rule.propertyInfo.properties &&
matchesSelector.call(element, rule.transformedSelector
|| rule.parsedSelector)) {
self.collectProperties(rule, props);
// produce numeric key for these matches for lookup
addToBitMask(i, o);
}
i++;
});
return {properties: props, key: o};
},
// Test if a rule matches scope criteria (* or :root) and if so,
// collect any custom properties into `props`.
scopePropertiesFromStyles: function(styles) {
if (!styles._scopeStyleProperties) {
styles._scopeStyleProperties =
this.selectedPropertiesFromStyles(styles, this.SCOPE_SELECTORS);
}
return styles._scopeStyleProperties;
},
// Test if a rule matches host criteria (:host) and if so,
// collect any custom properties into `props`.
//
// TODO(sorvell): this should change to collecting properties from any
// :host(...) and then matching these against self.
hostPropertiesFromStyles: function(styles) {
if (!styles._hostStyleProperties) {
styles._hostStyleProperties =
this.selectedPropertiesFromStyles(styles, this.HOST_SELECTORS);
}
return styles._hostStyleProperties;
},
selectedPropertiesFromStyles: function(styles, selectors) {
var props = {}, self = this;
styleUtil.forRulesInStyles(styles, function(rule) {
if (!rule.propertyInfo) {
self.decorateRule(rule);
}
for (var i=0; i < selectors.length; i++) {
if (rule.parsedSelector === selectors[i]) {
self.collectProperties(rule, props);
return;
}
}
});
return props;
},
transformStyles: function(element, properties, scopeSelector) {
var self = this;
var hostSelector = styleTransformer
._calcHostScope(element.is, element.extends);
var rxHostSelector = element.extends ?
'\\' + hostSelector.slice(0, -1) + '\\]' :
hostSelector;
var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector +
this.rx.HOST_SUFFIX);
return styleTransformer.elementStyles(element, function(rule) {
self.applyProperties(rule, properties);
if (rule.cssText && !nativeShadow) {
self._scopeSelector(rule, hostRx, hostSelector,
element._scopeCssViaAttr, scopeSelector);
}
});
},
// Strategy: x scope shim a selector e.g. to scope `.x-foo-42` (via classes):
// non-host selector: .a.x-foo -> .x-foo-42 .a.x-foo
// host selector: x-foo.wide -> x-foo.x-foo-42.wide
_scopeSelector: function(rule, hostRx, hostSelector, viaAttr, scopeId) {
rule.transformedSelector = rule.transformedSelector || rule.selector;
var selector = rule.transformedSelector;
var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' +
scopeId + ']' :
'.' + scopeId;
var parts = selector.split(',');
for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) {
parts[i] = p.match(hostRx) ?
p.replace(hostSelector, hostSelector + scope) :
scope + ' ' + p;
}
rule.selector = parts.join(',');
},
applyElementScopeSelector: function(element, selector, old, viaAttr) {
var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) :
(element.getAttribute('class') || '');
var v = old ? c.replace(old, selector) :
(c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector;
if (c !== v) {
if (viaAttr) {
element.setAttribute(styleTransformer.SCOPE_NAME, v);
} else {
element.setAttribute('class', v);
}
}
},
applyElementStyle: function(element, properties, selector, style) {
// calculate cssText to apply
var cssText = style ? style.textContent || '' :
this.transformStyles(element, properties, selector);
// if shady and we have a cached style that is not style, decrement
var s = element._customStyle;
if (s && !nativeShadow && (s !== style)) {
s._useCount--;
if (s._useCount <= 0 && s.parentNode) {
s.parentNode.removeChild(s);
}
}
// apply styling always under native or if we generated style
// or the cached style is not in document(!)
if (nativeShadow || (!style || !style.parentNode)) {
// update existing style only under native
if (nativeShadow && element._customStyle) {
element._customStyle.textContent = cssText;
style = element._customStyle;
// otherwise, if we have css to apply, do so
} else if (cssText) {
// apply css after the scope style of the element to help with
// style precedence rules.
style = styleUtil.applyCss(cssText, selector,
nativeShadow ? element.root : null, element._scopeStyle);
}
}
// ensure this style is our custom style and increment its use count.
if (style) {
style._useCount = style._useCount || 0;
// increment use count if we changed styles
if (element._customStyle != style) {
style._useCount++;
}
element._customStyle = style;
}
return style;
},
// customStyle properties are applied if they are truthy or 0. Otherwise,
// they are skipped; this allows properties previously set in customStyle
// to be easily reset to inherited values.
mixinCustomStyle: function(props, customStyle) {
var v;
for (var i in customStyle) {
v = customStyle[i];
if (v || v === 0) {
props[i] = v;
}
}
},
rx: {
VAR_ASSIGN: /(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\s}])|$)/gi,
MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i,
// note, this supports:
// var(--a)
// var(--a, --b)
// var(--a, fallback-literal)
// var(--a, fallback-literal(with-one-nested-parentheses))
VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi,
VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi,
IS_VAR: /^--/,
BRACKETED: /\{[^}]*\}/g,
HOST_PREFIX: '(?:^|[^.#[:])',
HOST_SUFFIX: '($|[.:[\\s>+~])'
},
HOST_SELECTORS: [':host'],
SCOPE_SELECTORS: [':root'],
XSCOPE_NAME: 'x-scope'
};
function addToBitMask(n, bits) {
var o = parseInt(n / 32);
var v = 1 << (n % 32);
bits[o] = (bits[o] || 0) | v;
}
})();
</script>

View File

@ -1,275 +1,275 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="style-util.html">
<script>
Polymer.StyleTransformer = (function() {
var nativeShadow = Polymer.Settings.useNativeShadow;
var styleUtil = Polymer.StyleUtil;
/* Transforms ShadowDOM styling into ShadyDOM styling
* scoping:
* elements in scope get scoping selector class="x-foo-scope"
* selectors re-written as follows:
div button -> div.x-foo-scope button.x-foo-scope
* :host -> scopeName
* :host(...) -> scopeName...
* ::content -> ' ' NOTE: requires use of scoping selector and selectors
cannot otherwise be scoped:
e.g. :host ::content > .bar -> x-foo > .bar
* ::shadow, /deep/: processed simimlar to ::content
* :host-context(...): scopeName..., ... scopeName
*/
var api = {
// Given a node and scope name, add a scoping class to each node
// in the tree. This facilitates transforming css into scoped rules.
dom: function(node, scope, useAttr, shouldRemoveScope) {
this._transformDom(node, scope || '', useAttr, shouldRemoveScope);
},
_transformDom: function(node, selector, useAttr, shouldRemoveScope) {
if (node.setAttribute) {
this.element(node, selector, useAttr, shouldRemoveScope);
}
var c$ = Polymer.dom(node).childNodes;
for (var i=0; i<c$.length; i++) {
this._transformDom(c$[i], selector, useAttr, shouldRemoveScope);
}
},
element: function(element, scope, useAttr, shouldRemoveScope) {
if (useAttr) {
if (shouldRemoveScope) {
element.removeAttribute(SCOPE_NAME);
} else {
element.setAttribute(SCOPE_NAME, scope);
}
} else {
// note: if using classes, we add both the general 'style-scope' class
// as well as the specific scope. This enables easy filtering of all
// `style-scope` elements
if (scope) {
// note: svg on IE does not have classList so fallback to class
if (element.classList) {
if (shouldRemoveScope) {
element.classList.remove(SCOPE_NAME);
element.classList.remove(scope);
} else {
element.classList.add(SCOPE_NAME);
element.classList.add(scope);
}
} else if (element.getAttribute) {
var c = element.getAttribute(CLASS);
if (shouldRemoveScope) {
if (c) {
element.setAttribute(CLASS, c.replace(SCOPE_NAME, '')
.replace(scope, ''));
}
} else {
element.setAttribute(CLASS, c + (c ? ' ' : '') +
SCOPE_NAME + ' ' + scope);
}
}
}
}
},
elementStyles: function(element, callback) {
var styles = element._styles;
var cssText = '';
for (var i=0, l=styles.length, s, text; (i<l) && (s=styles[i]); i++) {
var rules = styleUtil.rulesForStyle(s);
cssText += nativeShadow ?
styleUtil.toCssText(rules, callback) :
this.css(rules, element.is, element.extends, callback,
element._scopeCssViaAttr) + '\n\n';
}
return cssText.trim();
},
// Given a string of cssText and a scoping string (scope), returns
// a string of scoped css where each selector is transformed to include
// a class created from the scope. ShadowDOM selectors are also transformed
// (e.g. :host) to use the scoping selector.
css: function(rules, scope, ext, callback, useAttr) {
var hostScope = this._calcHostScope(scope, ext);
scope = this._calcElementScope(scope, useAttr);
var self = this;
return styleUtil.toCssText(rules, function(rule) {
if (!rule.isScoped) {
self.rule(rule, scope, hostScope);
rule.isScoped = true;
}
if (callback) {
callback(rule, scope, hostScope);
}
});
},
_calcElementScope: function (scope, useAttr) {
if (scope) {
return useAttr ?
CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX :
CSS_CLASS_PREFIX + scope;
} else {
return '';
}
},
_calcHostScope: function(scope, ext) {
return ext ? '[is=' + scope + ']' : scope;
},
rule: function (rule, scope, hostScope) {
this._transformRule(rule, this._transformComplexSelector,
scope, hostScope);
},
// transforms a css rule to a scoped rule.
_transformRule: function(rule, transformer, scope, hostScope) {
var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP);
for (var i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) {
p$[i] = transformer.call(this, p, scope, hostScope);
}
// NOTE: save transformedSelector for subsequent matching of elements
// agsinst selectors (e.g. when calculating style properties)
rule.selector = rule.transformedSelector =
p$.join(COMPLEX_SELECTOR_SEP);
},
_transformComplexSelector: function(selector, scope, hostScope) {
var stop = false;
var hostContext = false;
var self = this;
selector = selector.replace(SIMPLE_SELECTOR_SEP, function(m, c, s) {
if (!stop) {
var info = self._transformCompoundSelector(s, c, scope, hostScope);
stop = stop || info.stop;
hostContext = hostContext || info.hostContext;
c = info.combinator;
s = info.value;
} else {
s = s.replace(SCOPE_JUMP, ' ');
}
return c + s;
});
if (hostContext) {
selector = selector.replace(HOST_CONTEXT_PAREN,
function(m, pre, paren, post) {
return pre + paren + ' ' + hostScope + post +
COMPLEX_SELECTOR_SEP + ' ' + pre + hostScope + paren + post;
});
}
return selector;
},
_transformCompoundSelector: function(selector, combinator, scope, hostScope) {
// replace :host with host scoping class
var jumpIndex = selector.search(SCOPE_JUMP);
var hostContext = false;
if (selector.indexOf(HOST_CONTEXT) >=0) {
hostContext = true;
} else if (selector.indexOf(HOST) >=0) {
// :host(...) -> scopeName...
selector = selector.replace(HOST_PAREN, function(m, host, paren) {
return hostScope + paren;
});
// now normal :host
selector = selector.replace(HOST, hostScope);
// replace other selectors with scoping class
} else if (jumpIndex !== 0) {
selector = scope ? this._transformSimpleSelector(selector, scope) :
selector;
}
// remove left-side combinator when dealing with ::content.
if (selector.indexOf(CONTENT) >= 0) {
combinator = '';
}
// process scope jumping selectors up to the scope jump and then stop
// e.g. .zonk ::content > .foo ==> .zonk.scope > .foo
var stop;
if (jumpIndex >= 0) {
selector = selector.replace(SCOPE_JUMP, ' ');
stop = true;
}
return {value: selector, combinator: combinator, stop: stop,
hostContext: hostContext};
},
_transformSimpleSelector: function(selector, scope) {
var p$ = selector.split(PSEUDO_PREFIX);
p$[0] += scope;
return p$.join(PSEUDO_PREFIX);
},
documentRule: function(rule) {
// reset selector in case this is redone.
rule.selector = rule.parsedSelector;
this.normalizeRootSelector(rule);
if (!nativeShadow) {
this._transformRule(rule, this._transformDocumentSelector);
}
},
normalizeRootSelector: function(rule) {
if (rule.selector === ROOT) {
rule.selector = 'body';
}
},
_transformDocumentSelector: function(selector) {
return selector.match(SCOPE_JUMP) ?
this._transformComplexSelector(selector, SCOPE_DOC_SELECTOR) :
this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELECTOR);
},
SCOPE_NAME: 'style-scope'
};
var SCOPE_NAME = api.SCOPE_NAME;
var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' +
':not(.' + SCOPE_NAME + ')';
var COMPLEX_SELECTOR_SEP = ',';
var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g;
var HOST = ':host';
var ROOT = ':root';
// NOTE: this supports 1 nested () pair for things like
// :host(:not([selected]), more general support requires
// parsing which seems like overkill
var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g;
var HOST_CONTEXT = ':host-context';
var HOST_CONTEXT_PAREN = /(.*)(?:\:host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))(.*)/;
var CONTENT = '::content';
var SCOPE_JUMP = /\:\:content|\:\:shadow|\/deep\//;
var CSS_CLASS_PREFIX = '.';
var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~=';
var CSS_ATTR_SUFFIX = ']';
var PSEUDO_PREFIX = ':';
var CLASS = 'class';
// exports
return api;
})();
</script>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="style-util.html">
<script>
Polymer.StyleTransformer = (function() {
var nativeShadow = Polymer.Settings.useNativeShadow;
var styleUtil = Polymer.StyleUtil;
/* Transforms ShadowDOM styling into ShadyDOM styling
* scoping:
* elements in scope get scoping selector class="x-foo-scope"
* selectors re-written as follows:
div button -> div.x-foo-scope button.x-foo-scope
* :host -> scopeName
* :host(...) -> scopeName...
* ::content -> ' '
* ::shadow, /deep/: processed similar to ::content
* :host-context(...): scopeName..., ... scopeName
*/
var api = {
// Given a node and scope name, add a scoping class to each node
// in the tree. This facilitates transforming css into scoped rules.
dom: function(node, scope, useAttr, shouldRemoveScope) {
this._transformDom(node, scope || '', useAttr, shouldRemoveScope);
},
_transformDom: function(node, selector, useAttr, shouldRemoveScope) {
if (node.setAttribute) {
this.element(node, selector, useAttr, shouldRemoveScope);
}
var c$ = Polymer.dom(node).childNodes;
for (var i=0; i<c$.length; i++) {
this._transformDom(c$[i], selector, useAttr, shouldRemoveScope);
}
},
element: function(element, scope, useAttr, shouldRemoveScope) {
if (useAttr) {
if (shouldRemoveScope) {
element.removeAttribute(SCOPE_NAME);
} else {
element.setAttribute(SCOPE_NAME, scope);
}
} else {
// note: if using classes, we add both the general 'style-scope' class
// as well as the specific scope. This enables easy filtering of all
// `style-scope` elements
if (scope) {
// note: svg on IE does not have classList so fallback to class
if (element.classList) {
if (shouldRemoveScope) {
element.classList.remove(SCOPE_NAME);
element.classList.remove(scope);
} else {
element.classList.add(SCOPE_NAME);
element.classList.add(scope);
}
} else if (element.getAttribute) {
var c = element.getAttribute(CLASS);
if (shouldRemoveScope) {
if (c) {
element.setAttribute(CLASS, c.replace(SCOPE_NAME, '')
.replace(scope, ''));
}
} else {
element.setAttribute(CLASS, (c ? c + ' ' : '') +
SCOPE_NAME + ' ' + scope);
}
}
}
}
},
elementStyles: function(element, callback) {
var styles = element._styles;
var cssText = '';
for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) {
var rules = styleUtil.rulesForStyle(s);
cssText += nativeShadow ?
styleUtil.toCssText(rules, callback) :
this.css(rules, element.is, element.extends, callback,
element._scopeCssViaAttr) + '\n\n';
}
return cssText.trim();
},
// Given a string of cssText and a scoping string (scope), returns
// a string of scoped css where each selector is transformed to include
// a class created from the scope. ShadowDOM selectors are also transformed
// (e.g. :host) to use the scoping selector.
css: function(rules, scope, ext, callback, useAttr) {
var hostScope = this._calcHostScope(scope, ext);
scope = this._calcElementScope(scope, useAttr);
var self = this;
return styleUtil.toCssText(rules, function(rule) {
if (!rule.isScoped) {
self.rule(rule, scope, hostScope);
rule.isScoped = true;
}
if (callback) {
callback(rule, scope, hostScope);
}
});
},
_calcElementScope: function (scope, useAttr) {
if (scope) {
return useAttr ?
CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX :
CSS_CLASS_PREFIX + scope;
} else {
return '';
}
},
_calcHostScope: function(scope, ext) {
return ext ? '[is=' + scope + ']' : scope;
},
rule: function (rule, scope, hostScope) {
this._transformRule(rule, this._transformComplexSelector,
scope, hostScope);
},
// transforms a css rule to a scoped rule.
_transformRule: function(rule, transformer, scope, hostScope) {
var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP);
for (var i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) {
p$[i] = transformer.call(this, p, scope, hostScope);
}
// NOTE: save transformedSelector for subsequent matching of elements
// against selectors (e.g. when calculating style properties)
rule.selector = rule.transformedSelector =
p$.join(COMPLEX_SELECTOR_SEP);
},
_transformComplexSelector: function(selector, scope, hostScope) {
var stop = false;
var hostContext = false;
var self = this;
selector = selector.replace(CONTENT_START, HOST + ' $1');
selector = selector.replace(SIMPLE_SELECTOR_SEP, function(m, c, s) {
if (!stop) {
var info = self._transformCompoundSelector(s, c, scope, hostScope);
stop = stop || info.stop;
hostContext = hostContext || info.hostContext;
c = info.combinator;
s = info.value;
} else {
s = s.replace(SCOPE_JUMP, ' ');
}
return c + s;
});
if (hostContext) {
selector = selector.replace(HOST_CONTEXT_PAREN,
function(m, pre, paren, post) {
return pre + paren + ' ' + hostScope + post +
COMPLEX_SELECTOR_SEP + ' ' + pre + hostScope + paren + post;
});
}
return selector;
},
_transformCompoundSelector: function(selector, combinator, scope, hostScope) {
// replace :host with host scoping class
var jumpIndex = selector.search(SCOPE_JUMP);
var hostContext = false;
if (selector.indexOf(HOST_CONTEXT) >=0) {
hostContext = true;
} else if (selector.indexOf(HOST) >=0) {
// :host(...) -> scopeName...
selector = selector.replace(HOST_PAREN, function(m, host, paren) {
return hostScope + paren;
});
// now normal :host
selector = selector.replace(HOST, hostScope);
// replace other selectors with scoping class
} else if (jumpIndex !== 0) {
selector = scope ? this._transformSimpleSelector(selector, scope) :
selector;
}
// remove left-side combinator when dealing with ::content.
if (selector.indexOf(CONTENT) >= 0) {
combinator = '';
}
// process scope jumping selectors up to the scope jump and then stop
// e.g. .zonk ::content > .foo ==> .zonk.scope > .foo
var stop;
if (jumpIndex >= 0) {
selector = selector.replace(SCOPE_JUMP, ' ');
stop = true;
}
return {value: selector, combinator: combinator, stop: stop,
hostContext: hostContext};
},
_transformSimpleSelector: function(selector, scope) {
var p$ = selector.split(PSEUDO_PREFIX);
p$[0] += scope;
return p$.join(PSEUDO_PREFIX);
},
documentRule: function(rule) {
// reset selector in case this is redone.
rule.selector = rule.parsedSelector;
this.normalizeRootSelector(rule);
if (!nativeShadow) {
this._transformRule(rule, this._transformDocumentSelector);
}
},
normalizeRootSelector: function(rule) {
if (rule.selector === ROOT) {
rule.selector = 'body';
}
},
_transformDocumentSelector: function(selector) {
return selector.match(SCOPE_JUMP) ?
this._transformComplexSelector(selector, SCOPE_DOC_SELECTOR) :
this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELECTOR);
},
SCOPE_NAME: 'style-scope'
};
var SCOPE_NAME = api.SCOPE_NAME;
var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' +
':not(.' + SCOPE_NAME + ')';
var COMPLEX_SELECTOR_SEP = ',';
var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g;
var HOST = ':host';
var ROOT = ':root';
// NOTE: this supports 1 nested () pair for things like
// :host(:not([selected]), more general support requires
// parsing which seems like overkill
var HOST_PAREN = /(:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g;
var HOST_CONTEXT = ':host-context';
var HOST_CONTEXT_PAREN = /(.*)(?::host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))(.*)/;
var CONTENT = '::content';
var SCOPE_JUMP = /::content|::shadow|\/deep\//;
var CSS_CLASS_PREFIX = '.';
var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~=';
var CSS_ATTR_SUFFIX = ']';
var PSEUDO_PREFIX = ':';
var CLASS = 'class';
var CONTENT_START = new RegExp('^(' + CONTENT + ')');
// exports
return api;
})();
</script>

View File

@ -22,7 +22,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
toCssText: function(rules, callback, preserveProperties) {
if (typeof rules === 'string') {
rules = this.parser.parse(rules);
}
}
if (callback) {
this.forEachStyleRule(rules, callback);
}
@ -44,19 +44,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return style.__cssRules;
},
clearStyleRules: function(style) {
style.__cssRules = null;
},
forEachStyleRule: function(node, callback) {
if (!node) {
return;
}
var s = node.parsedSelector;
var skipRules = false;
if (node.type === this.ruleTypes.STYLE_RULE) {
callback(node);
} else if (node.type === this.ruleTypes.KEYFRAMES_RULE ||
} else if (node.type === this.ruleTypes.KEYFRAMES_RULE ||
node.type === this.ruleTypes.MIXIN_RULE) {
skipRules = true;
}
@ -79,8 +74,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (!afterNode) {
var n$ = target.querySelectorAll('style[scope]');
afterNode = n$[n$.length-1];
}
target.insertBefore(style,
}
target.insertBefore(style,
(afterNode && afterNode.nextSibling) || target.firstChild);
return style;
},
@ -99,7 +94,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
cssFromModule: function(moduleId, warnIfNotFound) {
var m = Polymer.DomModule.import(moduleId);
if (m && !m._cssText) {
m._cssText = this._cssFromElement(m);
m._cssText = this.cssFromElement(m);
}
if (!m && warnIfNotFound) {
console.warn('Could not find style data in module named', moduleId);
@ -108,17 +103,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
// support lots of ways to discover css...
_cssFromElement: function(element) {
cssFromElement: function(element) {
var cssText = '';
// if element is a template, get content from its .content
var content = element.content || element;
var e$ = Array.prototype.slice.call(
var e$ = Polymer.TreeApi.arrayCopy(
content.querySelectorAll(this.MODULE_STYLES_SELECTOR));
for (var i=0, e; i < e$.length; i++) {
e = e$[i];
// look inside templates for elements
if (e.localName === 'template') {
cssText += this._cssFromElement(e);
cssText += this.cssFromElement(e);
} else {
// style elements inside dom-modules will apply to the main document
// we don't want this, so we remove them here.
@ -142,7 +137,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
return cssText;
},
resolveCss: Polymer.ResolveUrl.resolveCss,
parser: Polymer.CssParse,
ruleTypes: Polymer.CssParse.types
@ -151,4 +146,4 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
})();
</script>
</script>

View File

@ -66,6 +66,7 @@ is false, `selected` is a property representing the last selected item. When
Polymer({
is: 'array-selector',
_template: null,
properties: {
@ -130,6 +131,7 @@ is false, `selected` is a property representing the last selected item. When
}
} else {
this.unlinkPaths('selected');
this.unlinkPaths('selectedItem');
}
// Initialize selection
if (this.multi) {
@ -197,7 +199,7 @@ is false, `selected` is a property representing the last selected item. When
}
} else {
this.push('selected', item);
skey = this._selectedColl.getKey(item);
var skey = this._selectedColl.getKey(item);
this.linkPaths('selected.' + skey, 'items.' + key);
}
} else {

View File

@ -62,11 +62,15 @@ elements to the template itself as the binding scope.
is: 'dom-bind',
extends: 'template',
_template: null,
created: function() {
// Ensure dom-bind doesn't stamp until all possible dependencies
// have resolved
Polymer.RenderStatus.whenReady(this._markImportsReady.bind(this));
var self = this;
Polymer.RenderStatus.whenReady(function() {
self._markImportsReady();
});
},
_ensureReady: function() {
@ -119,7 +123,10 @@ elements to the template itself as the binding scope.
config[prop] = this[prop];
}
// Pass values set before attached as initialConfig to _setupConfigure
this._setupConfigure = this._setupConfigure.bind(this, config);
var setupConfigure = this._setupConfigure;
this._setupConfigure = function() {
setupConfigure.call(this, config);
};
},
attached: function() {
@ -145,8 +152,9 @@ elements to the template itself as the binding scope.
this._prepBehaviors();
this._prepConfigure();
this._prepBindings();
this._prepPropertyInfo();
Polymer.Base._initFeatures.call(this);
this._children = Array.prototype.slice.call(this.root.childNodes);
this._children = Polymer.TreeApi.arrayCopyChildNodes(this.root);
}
this._insertChildren();
this.fire('dom-change');

View File

@ -28,6 +28,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
is: 'dom-if',
extends: 'template',
_template: null,
/**
* Fired whenever DOM is added or removed/hidden by this template (by
@ -72,13 +73,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
detached: function() {
// TODO(kschaaf): add logic to re-stamp in attached?
this._teardownInstance();
if (!this.parentNode ||
(this.parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE &&
(!Polymer.Settings.hasShadow ||
!(this.parentNode instanceof ShadowRoot)))) {
this._teardownInstance();
}
},
attached: function() {
if (this.if && this.ctor) {
// TODO(sorvell): should not be async, but node can be attached
// NOTE: ideally should not be async, but node can be attached
// when shady dom is in the act of distributing/composing so push it out
this.async(this._ensureInstance);
}
@ -115,25 +120,38 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_ensureInstance: function() {
if (!this._instance) {
// TODO(sorvell): pickup stamping logic from x-repeat
this._instance = this.stamp();
var root = this._instance.root;
// TODO(sorvell): this incantation needs to be simpler.
var parent = Polymer.dom(Polymer.dom(this).parentNode);
parent.insertBefore(root, this);
var parentNode = Polymer.dom(this).parentNode;
// Guard against element being detached while render was queued
if (parentNode) {
var parent = Polymer.dom(parentNode);
if (!this._instance) {
this._instance = this.stamp();
var root = this._instance.root;
parent.insertBefore(root, this);
} else {
var c$ = this._instance._children;
if (c$ && c$.length) {
// Detect case where dom-if was re-attached in new position
var lastChild = Polymer.dom(this).previousSibling;
if (lastChild !== c$[c$.length-1]) {
for (var i=0, n; (i<c$.length) && (n=c$[i]); i++) {
parent.insertBefore(n, this);
}
}
}
}
}
},
_teardownInstance: function() {
if (this._instance) {
var c = this._instance._children;
if (c) {
var c$ = this._instance._children;
if (c$ && c$.length) {
// use first child parent, for case when dom-if may have been detached
var parent = Polymer.dom(Polymer.dom(c[0]).parentNode);
c.forEach(function(n) {
var parent = Polymer.dom(Polymer.dom(c$[0]).parentNode);
for (var i=0, n; (i<c$.length) && (n=c$[i]); i++) {
parent.removeChild(n);
});
}
}
this._instance = null;
}
@ -160,7 +178,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// notifying parent.<path> path change on each row
_forwardParentPath: function(path, value) {
if (this._instance) {
this._instance.notifyPath(path, value, true);
this._instance._notifyPath(path, value, true);
}
}

View File

@ -107,6 +107,7 @@ Then the `observe` property should be configured as follows:
is: 'dom-repeat',
extends: 'template',
_template: null,
/**
* Fired whenever DOM is added or removed by this template (by
@ -188,7 +189,50 @@ Then the `observe` property should be configured as follows:
* This is useful in rate-limiting shuffing of the view when
* item changes may be frequent.
*/
delay: Number
delay: Number,
/**
* Count of currently rendered items after `filter` (if any) has been applied.
* If "chunking mode" is enabled, `renderedItemCount` is updated each time a
* set of template instances is rendered.
*
*/
renderedItemCount: {
type: Number,
notify: true,
readOnly: true
},
/**
* Defines an initial count of template instances to render after setting
* the `items` array, before the next paint, and puts the `dom-repeat`
* into "chunking mode". The remaining items will be created and rendered
* incrementally at each animation frame therof until all instances have
* been rendered.
*/
initialCount: {
type: Number,
observer: '_initializeChunking'
},
/**
* When `initialCount` is used, this property defines a frame rate to
* target by throttling the number of instances rendered each frame to
* not exceed the budget for the target frame rate. Setting this to a
* higher number will allow lower latency and higher throughput for
* things like event handlers, but will result in a longer time for the
* remaining items to complete rendering.
*/
targetFramerate: {
type: Number,
value: 20
},
_targetFrameTime: {
type: Number,
computed: '_computeFrameTime(targetFramerate)'
}
},
behaviors: [
@ -201,18 +245,29 @@ Then the `observe` property should be configured as follows:
created: function() {
this._instances = [];
this._pool = [];
this._limit = Infinity;
var self = this;
this._boundRenderChunk = function() {
self._renderChunk();
};
},
detached: function() {
this.__isDetached = true;
for (var i=0; i<this._instances.length; i++) {
this._detachRow(i);
this._detachInstance(i);
}
},
attached: function() {
var parentNode = Polymer.dom(this).parentNode;
for (var i=0; i<this._instances.length; i++) {
Polymer.dom(parentNode).insertBefore(this._instances[i].root, this);
// only perform attachment if the element was previously detached.
if (this.__isDetached) {
this.__isDetached = false;
var parent = Polymer.dom(Polymer.dom(this).parentNode);
for (var i=0; i<this._instances.length; i++) {
this._attachInstance(i, parent);
}
}
},
@ -231,9 +286,8 @@ Then the `observe` property should be configured as follows:
}
},
_sortChanged: function() {
_sortChanged: function(sort) {
var dataHost = this._getRootDataHost();
var sort = this.sort;
this._sortFn = sort && (typeof sort == 'function' ? sort :
function() { return dataHost[sort].apply(dataHost, arguments); });
this._needFullRefresh = true;
@ -242,9 +296,8 @@ Then the `observe` property should be configured as follows:
}
},
_filterChanged: function() {
_filterChanged: function(filter) {
var dataHost = this._getRootDataHost();
var filter = this.filter;
this._filterFn = filter && (typeof filter == 'function' ? filter :
function() { return dataHost[filter].apply(dataHost, arguments); });
this._needFullRefresh = true;
@ -253,6 +306,42 @@ Then the `observe` property should be configured as follows:
}
},
_computeFrameTime: function(rate) {
return Math.ceil(1000/rate);
},
_initializeChunking: function() {
if (this.initialCount) {
this._limit = this.initialCount;
this._chunkCount = this.initialCount;
this._lastChunkTime = performance.now();
}
},
_tryRenderChunk: function() {
// Debounced so that multiple calls through `_render` between animation
// frames only queue one new rAF (e.g. array mutation & chunked render)
if (this.items && this._limit < this.items.length) {
this.debounce('renderChunk', this._requestRenderChunk);
}
},
_requestRenderChunk: function() {
requestAnimationFrame(this._boundRenderChunk);
},
_renderChunk: function() {
// Simple auto chunkSize throttling algorithm based on feedback loop:
// measure actual time between frames and scale chunk count by ratio
// of target/actual frame time
var currChunkTime = performance.now();
var ratio = this._targetFrameTime / (currChunkTime - this._lastChunkTime);
this._chunkCount = Math.round(this._chunkCount * ratio) || 1;
this._limit += this._chunkCount;
this._lastChunkTime = currChunkTime;
this._debounceTemplate(this._render);
},
_observeChanged: function() {
this._observePaths = this.observe &&
this.observe.replace('.*', '.').split(' ');
@ -271,6 +360,7 @@ Then the `observe` property should be configured as follows:
this._keySplices = [];
this._indexSplices = [];
this._needFullRefresh = true;
this._initializeChunking();
this._debounceTemplate(this._render);
} else if (change.path == 'items.splices') {
this._keySplices = this._keySplices.concat(change.value.keySplices);
@ -322,9 +412,11 @@ Then the `observe` property should be configured as follows:
var c = this.collection;
// Choose rendering path: full vs. incremental using splices
if (this._needFullRefresh) {
// Full refresh when items, sort, or filter change, or when render() called
this._applyFullRefresh();
this._needFullRefresh = false;
} else {
} else if (this._keySplices.length) {
// Incremental refresh when splices were queued
if (this._sortFn) {
this._applySplicesUserSort(this._keySplices);
} else {
@ -335,17 +427,41 @@ Then the `observe` property should be configured as follows:
this._applySplicesArrayOrder(this._indexSplices);
}
}
} else {
// Otherwise only limit changed; no change to instances, just need to
// upgrade more placeholders to instances
}
this._keySplices = [];
this._indexSplices = [];
// Update final _keyToInstIdx and instance indices
// Update final _keyToInstIdx and instance indices, and
// upgrade/downgrade placeholders
var keyToIdx = this._keyToInstIdx = {};
for (var i=0; i<this._instances.length; i++) {
for (var i=this._instances.length-1; i>=0; i--) {
var inst = this._instances[i];
if (inst.isPlaceholder && i<this._limit) {
inst = this._insertInstance(i, inst.__key__);
} else if (!inst.isPlaceholder && i>=this._limit) {
inst = this._downgradeInstance(i, inst.__key__);
}
keyToIdx[inst.__key__] = i;
inst.__setProperty(this.indexAs, i, true);
if (!inst.isPlaceholder) {
inst.__setProperty(this.indexAs, i, true);
}
}
// Reset the pool
// TODO(kschaaf): Reuse pool across turns and nested templates
// Requires updating parentProps and dealing with the fact that path
// notifications won't reach instances sitting in the pool, which
// could result in out-of-sync instances since simply re-setting
// `item` may not be sufficient if the pooled instance happens to be
// the same item.
this._pool.length = 0;
// Set rendered item count
this._setRenderedItemCount(this._instances.length);
// Notify users
this.fire('dom-change');
// Check to see if we need to render more items
this._tryRenderChunk();
},
// Render method 1: full refesh
@ -368,38 +484,39 @@ Then the `observe` property should be configured as follows:
}
}
}
// capture reference for use in filter/sort fn's
var self = this;
// Apply user filter to keys
if (this._filterFn) {
keys = keys.filter(function(a) {
return this._filterFn(c.getItem(a));
}, this);
return self._filterFn(c.getItem(a));
});
}
// Apply user sort to keys
if (this._sortFn) {
keys.sort(function(a, b) {
return this._sortFn(c.getItem(a), c.getItem(b));
}.bind(this));
return self._sortFn(c.getItem(a), c.getItem(b));
});
}
// Generate instances and assign items and keys
for (var i=0; i<keys.length; i++) {
var key = keys[i];
var inst = this._instances[i];
if (inst) {
inst.__setProperty('__key__', key, true);
inst.__setProperty(this.as, c.getItem(key), true);
inst.__key__ = key;
if (!inst.isPlaceholder && i < this._limit) {
inst.__setProperty(this.as, c.getItem(key), true);
}
} else if (i < this._limit) {
this._insertInstance(i, key);
} else {
this._instances.push(this._insertRow(i, key));
this._insertPlaceholder(i, key);
}
}
// Remove any extra instances from previous state
for (; i<this._instances.length; i++) {
this._detachRow(i);
for (var j=this._instances.length-1; j>=i; j--) {
this._detachAndRemoveInstance(j);
}
this._instances.splice(keys.length, this._instances.length-keys.length);
},
_keySort: function(a, b) {
return this.collection.getKey(a) - this.collection.getKey(b);
},
_numericSort: function(a, b) {
@ -414,19 +531,17 @@ Then the `observe` property should be configured as follows:
var c = this.collection;
var instances = this._instances;
var keyMap = {};
var pool = [];
var sortFn = this._sortFn || this._keySort.bind(this);
// Dedupe added and removed keys to a final added/removed map
splices.forEach(function(s) {
for (var i=0; i<s.removed.length; i++) {
var key = s.removed[i];
for (var i=0, s; (i<splices.length) && (s=splices[i]); i++) {
for (var j=0; j<s.removed.length; j++) {
var key = s.removed[j];
keyMap[key] = keyMap[key] ? null : -1;
}
for (var i=0; i<s.added.length; i++) {
var key = s.added[i];
for (var j=0; j<s.added.length; j++) {
var key = s.added[j];
keyMap[key] = keyMap[key] ? null : 1;
}
}, this);
}
// Convert added/removed key map to added/removed arrays
var removedIdxs = [];
var addedKeys = [];
@ -448,42 +563,42 @@ Then the `observe` property should be configured as follows:
var idx = removedIdxs[i];
// Removed idx may be undefined if item was previously filtered out
if (idx !== undefined) {
pool.push(this._detachRow(idx));
instances.splice(idx, 1);
this._detachAndRemoveInstance(idx);
}
}
}
// capture reference for use in filter/sort fn's
var self = this;
// Add instances for added keys
if (addedKeys.length) {
// Filter added keys
if (this._filterFn) {
addedKeys = addedKeys.filter(function(a) {
return this._filterFn(c.getItem(a));
}, this);
return self._filterFn(c.getItem(a));
});
}
// Sort added keys
addedKeys.sort(function(a, b) {
return this._sortFn(c.getItem(a), c.getItem(b));
}.bind(this));
return self._sortFn(c.getItem(a), c.getItem(b));
});
// Insertion-sort new instances into place (from pool or newly created)
var start = 0;
for (var i=0; i<addedKeys.length; i++) {
start = this._insertRowUserSort(start, addedKeys[i], pool);
start = this._insertRowUserSort(start, addedKeys[i]);
}
}
},
_insertRowUserSort: function(start, key, pool) {
_insertRowUserSort: function(start, key) {
var c = this.collection;
var item = c.getItem(key);
var end = this._instances.length - 1;
var idx = -1;
var sortFn = this._sortFn || this._keySort.bind(this);
// Binary search for insertion point
while (start <= end) {
var mid = (start + end) >> 1;
var midKey = this._instances[mid].__key__;
var cmp = sortFn(c.getItem(midKey), item);
var cmp = this._sortFn(c.getItem(midKey), item);
if (cmp < 0) {
start = mid + 1;
} else if (cmp > 0) {
@ -497,7 +612,7 @@ Then the `observe` property should be configured as follows:
idx = end + 1;
}
// Insert instance at insertion point
this._instances.splice(idx, 0, this._insertRow(idx, key, pool));
this._insertPlaceholder(idx, key);
return idx;
},
@ -507,70 +622,88 @@ Then the `observe` property should be configured as follows:
// rows are as placeholders, and placeholders are updated to
// actual rows at the end to take full advantage of removed rows
_applySplicesArrayOrder: function(splices) {
var pool = [];
var c = this.collection;
splices.forEach(function(s) {
for (var i=0, s; (i<splices.length) && (s=splices[i]); i++) {
// Detach & pool removed instances
for (var i=0; i<s.removed.length; i++) {
var inst = this._detachRow(s.index + i);
if (!inst.isPlaceholder) {
pool.push(inst);
}
for (var j=0; j<s.removed.length; j++) {
this._detachAndRemoveInstance(s.index);
}
this._instances.splice(s.index, s.removed.length);
// Insert placeholders for new rows
for (var i=0; i<s.addedKeys.length; i++) {
var inst = {
isPlaceholder: true,
key: s.addedKeys[i]
};
this._instances.splice(s.index + i, 0, inst);
}
}, this);
// Replace placeholders with actual instances (from pool or newly created)
// Iterate backwards to ensure insertBefore refrence is never a placeholder
for (var i=this._instances.length-1; i>=0; i--) {
var inst = this._instances[i];
if (inst.isPlaceholder) {
this._instances[i] = this._insertRow(i, inst.key, pool, true);
for (var j=0; j<s.addedKeys.length; j++) {
this._insertPlaceholder(s.index+j, s.addedKeys[j]);
}
}
},
_detachRow: function(idx) {
_detachInstance: function(idx) {
var inst = this._instances[idx];
if (!inst.isPlaceholder) {
var parentNode = Polymer.dom(this).parentNode;
for (var i=0; i<inst._children.length; i++) {
var el = inst._children[i];
Polymer.dom(inst.root).appendChild(el);
}
return inst;
}
return inst;
},
_insertRow: function(idx, key, pool, replace) {
var inst;
if (inst = pool && pool.pop()) {
inst.__setProperty(this.as, this.collection.getItem(key), true);
inst.__setProperty('__key__', key, true);
} else {
inst = this._generateRow(idx, key);
_attachInstance: function(idx, parent) {
var inst = this._instances[idx];
if (!inst.isPlaceholder) {
parent.insertBefore(inst.root, this);
}
var beforeRow = this._instances[replace ? idx + 1 : idx];
var beforeNode = beforeRow ? beforeRow._children[0] : this;
var parentNode = Polymer.dom(this).parentNode;
Polymer.dom(parentNode).insertBefore(inst.root, beforeNode);
return inst;
},
_generateRow: function(idx, key) {
_detachAndRemoveInstance: function(idx) {
var inst = this._detachInstance(idx);
if (inst) {
this._pool.push(inst);
}
this._instances.splice(idx, 1);
},
_insertPlaceholder: function(idx, key) {
this._instances.splice(idx, 0, {
isPlaceholder: true,
__key__: key
});
},
_stampInstance: function(idx, key) {
var model = {
__key__: key
};
model[this.as] = this.collection.getItem(key);
model[this.indexAs] = idx;
var inst = this.stamp(model);
return this.stamp(model);
},
_insertInstance: function(idx, key) {
var inst = this._pool.pop();
if (inst) {
// TODO(kschaaf): If the pool is shared across turns, parentProps
// need to be re-set to reused instances in addition to item/key
inst.__setProperty(this.as, this.collection.getItem(key), true);
inst.__setProperty('__key__', key, true);
} else {
inst = this._stampInstance(idx, key);
}
var beforeRow = this._instances[idx + 1];
var beforeNode = beforeRow && !beforeRow.isPlaceholder ? beforeRow._children[0] : this;
var parentNode = Polymer.dom(this).parentNode;
Polymer.dom(parentNode).insertBefore(inst.root, beforeNode);
this._instances[idx] = inst;
return inst;
},
_downgradeInstance: function(idx, key) {
var inst = this._detachInstance(idx);
if (inst) {
this._pool.push(inst);
}
inst = {
isPlaceholder: true,
__key__: key
};
this._instances[idx] = inst;
return inst;
},
@ -605,7 +738,7 @@ Then the `observe` property should be configured as follows:
// for notifying items.<key-for-inst>.<path> change up to host
_forwardInstancePath: function(inst, path, value) {
if (path.indexOf(this.as + '.') === 0) {
this.notifyPath('items.' + inst.__key__ + '.' +
this._notifyPath('items.' + inst.__key__ + '.' +
path.slice(this.as.length + 1), value);
}
},
@ -614,18 +747,24 @@ Then the `observe` property should be configured as follows:
// Called as side-effect of a host property change, responsible for
// notifying parent path change on each inst
_forwardParentProp: function(prop, value) {
this._instances.forEach(function(inst) {
inst.__setProperty(prop, value, true);
}, this);
var i$ = this._instances;
for (var i=0, inst; (i<i$.length) && (inst=i$[i]); i++) {
if (!inst.isPlaceholder) {
inst.__setProperty(prop, value, true);
}
}
},
// Implements extension point from Templatizer
// Called as side-effect of a host path change, responsible for
// notifying parent path change on each inst
_forwardParentPath: function(path, value) {
this._instances.forEach(function(inst) {
inst.notifyPath(path, value, true);
}, this);
var i$ = this._instances;
for (var i=0, inst; (i<i$.length) && (inst=i$[i]); i++) {
if (!inst.isPlaceholder) {
inst._notifyPath(path, value, true);
}
}
},
// Called as a side effect of a host items.<key>.<path> path change,
@ -636,10 +775,10 @@ Then the `observe` property should be configured as follows:
var key = path.substring(0, dot < 0 ? path.length : dot);
var idx = this._keyToInstIdx[key];
var inst = this._instances[idx];
if (inst) {
if (inst && !inst.isPlaceholder) {
if (dot >= 0) {
path = this.as + '.' + path.substring(dot+1);
inst.notifyPath(path, value, true);
inst._notifyPath(path, value, true);
} else {
inst.__setProperty(this.as, value, true);
}

View File

@ -25,6 +25,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
is: 'dom-template',
extends: 'template',
_template: null,
behaviors: [
Polymer.Templatizer

View File

@ -87,6 +87,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* @param {HTMLTemplateElement} template The template to process.
*/
templatize: function(template) {
this._templatized = template;
// TODO(sjmiles): supply _alternate_ content reference missing from root
// templates (not nested). `_content` exists to provide content sharing
// for nested templates.
@ -108,20 +109,23 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// archetypes do special caching
this._customPrepAnnotations(archetype, template);
// forward parent properties to archetype
this._prepParentProperties(archetype, template);
// setup accessors
archetype._prepEffects();
this._customPrepEffects(archetype);
archetype._prepBehaviors();
archetype._prepPropertyInfo();
archetype._prepBindings();
// forward parent properties to archetype
this._prepParentProperties(archetype, template);
// boilerplate code
archetype._notifyPath = this._notifyPathImpl;
archetype._notifyPathUp = this._notifyPathUpImpl;
archetype._scopeElementClass = this._scopeElementClassImpl;
archetype.listen = this._listenImpl;
archetype._showHideChildren = this._showHideChildrenImpl;
archetype.__setPropertyOrig = this.__setProperty;
archetype.__setProperty = this.__setPropertyImpl;
// boilerplate code
var _constructor = this._constructorImpl;
var ctor = function TemplateInstance(model, host) {
@ -166,6 +170,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
},
__setPropertyImpl: function(property, value, fromAbove, node) {
if (node && node.__hideTemplateChildren__ && property == 'textContent') {
property = '__polymerTextContent__';
}
this.__setPropertyOrig(property, value, fromAbove, node);
},
_debounceTemplate: function(fn) {
Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', fn));
},
@ -192,8 +203,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (!c._notes) {
var rootDataHost = archetype._rootDataHost;
if (rootDataHost) {
Polymer.Annotations.prepElement =
rootDataHost._prepElement.bind(rootDataHost);
Polymer.Annotations.prepElement = function() {
rootDataHost._prepElement();
}
}
c._notes = Polymer.Annotations.parseAnnotations(template);
Polymer.Annotations.prepElement = null;
@ -223,28 +235,41 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// assume that the template is not a Poylmer.Base, so prep it
// for binding
Polymer.Bind.prepareModel(proto);
Polymer.Base.prepareModelNotifyPath(proto);
}
// Create accessors for each parent prop that forward the property
// to template instances through abstract _forwardParentProp API
// that should be implemented by Templatizer users
for (prop in parentProps) {
var parentProp = this._parentPropPrefix + prop;
// TODO(sorvell): remove reference Bind library functions here.
// Needed for effect optimization.
var effects = [{
kind: 'function',
effect: this._createForwardPropEffector(prop)
effect: this._createForwardPropEffector(prop),
fn: Polymer.Bind._functionEffect
}, {
kind: 'notify'
kind: 'notify',
fn: Polymer.Bind._notifyEffect,
effect: {event:
Polymer.CaseMap.camelToDashCase(parentProp) + '-changed'}
}];
Polymer.Bind._createAccessors(proto, parentProp, effects);
}
}
// capture this reference for use below
var self = this;
// Instance setup
if (template != this) {
Polymer.Bind.prepareInstance(template);
template._forwardParentProp =
this._forwardParentProp.bind(this);
template._forwardParentProp = function(source, value) {
self._forwardParentProp(source, value);
}
}
this._extendTemplate(template, proto);
template._pathEffector = function(path, value, fromAbove) {
return self._pathEffectorImpl(path, value, fromAbove);
}
}
},
@ -257,7 +282,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_createHostPropEffector: function(prop) {
var prefix = this._parentPropPrefix;
return function(source, value) {
this.dataHost[prefix + prop] = value;
this.dataHost._templatized[prefix + prop] = value;
};
},
@ -272,14 +297,15 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// Similar to Polymer.Base.extend, but retains any previously set instance
// values (_propertySetter back on instance once accessor is installed)
_extendTemplate: function(template, proto) {
Object.getOwnPropertyNames(proto).forEach(function(n) {
var n$ = Object.getOwnPropertyNames(proto);
for (var i=0, n; (i<n$.length) && (n=n$[i]); i++) {
var val = template[n];
var pd = Object.getOwnPropertyDescriptor(proto, n);
Object.defineProperty(template, n, pd);
if (val !== undefined) {
template._propertySetter(n, val);
}
});
}
},
// Extension points for Templatizer sub-classes
@ -290,35 +316,40 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// _forwardParentPath: function(path, value) { },
// _forwardParentProp: function(prop, value) { },
_notifyPathImpl: function(path, value) {
_notifyPathUpImpl: function(path, value) {
var dataHost = this.dataHost;
var dot = path.indexOf('.');
var root = dot < 0 ? path : path.slice(0, dot);
// Call extension point for Templatizer sub-classes
dataHost._forwardInstancePath.call(dataHost, this, path, value);
if (root in dataHost._parentProps) {
dataHost.notifyPath(dataHost._parentPropPrefix + path, value);
dataHost._templatized.notifyPath(dataHost._parentPropPrefix + path, value);
}
},
// Overrides Base notify-path module
_pathEffector: function(path, value, fromAbove) {
_pathEffectorImpl: function(path, value, fromAbove) {
if (this._forwardParentPath) {
if (path.indexOf(this._parentPropPrefix) === 0) {
this._forwardParentPath(path.substring(8), value);
var subPath = path.substring(this._parentPropPrefix.length);
var model = this._modelForPath(subPath);
if (model in this._parentProps) {
this._forwardParentPath(subPath, value);
}
}
}
Polymer.Base._pathEffector.apply(this, arguments);
Polymer.Base._pathEffector.call(this._templatized, path, value, fromAbove);
},
_constructorImpl: function(model, host) {
this._rootDataHost = host._getRootDataHost();
this._setupConfigure(model);
this._pushHost(host);
this._registerHost(host);
this._beginHosting();
this.root = this.instanceTemplate(this._template);
this.root.__noContent = !this._notes._hasContent;
this.root.__styleScoped = true;
this._popHost();
this._endHosting();
this._marshalAnnotatedNodes();
this._marshalInstanceEffects();
this._marshalAnnotatedListeners();
@ -373,7 +404,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* `stamp`.
*
* @method stamp
* @param {Object} model An object containing key/values to serve as the
* @param {Object=} model An object containing key/values to serve as the
* initial data configuration for the instance. Note that properties
* from the host used in the template are automatically copied into
* the model.
@ -383,8 +414,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
stamp: function(model) {
model = model || {};
if (this._parentProps) {
var templatized = this._templatized;
for (var prop in this._parentProps) {
model[prop] = this[this._parentPropPrefix + prop];
model[prop] = templatized[this._parentPropPrefix + prop];
}
}
return new this.ctor(model, this);
@ -430,25 +462,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
}
// TODO(sorvell): note, using the template as host is ~5-10% faster if
// elements have no default values.
// _constructorImpl: function(model, host) {
// this._setupConfigure(model);
// host._beginHost();
// this.root = this.instanceTemplate(this._template);
// host._popHost();
// this._marshalTemplateContent();
// this._marshalAnnotatedNodes();
// this._marshalInstanceEffects();
// this._marshalAnnotatedListeners();
// this._ready();
// },
// stamp: function(model) {
// return new this.ctor(model, this.dataHost);
// }
};
</script>

View File

@ -60,18 +60,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.Base._addFeature({
_prepAttributes: function() {
this._aggregatedAttributes = {};
},
// prototype time
_addHostAttributes: function(attributes) {
if (!this._aggregatedAttributes) {
this._aggregatedAttributes = {};
}
if (attributes) {
this.mixin(this._aggregatedAttributes, attributes);
}
},
// instance time
_marshalHostAttributes: function() {
this._applyAttributes(this, this._aggregatedAttributes);
if (this._aggregatedAttributes) {
this._applyAttributes(this, this._aggregatedAttributes);
}
},
/* apply attributes to node but avoid overriding existing values */
@ -81,7 +84,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// since shimming classes would make it work
// inconsisently under native SD
if (!this.hasAttribute(n) && (n !== 'class')) {
this.serializeValueToAttribute(attr$[n], n, this);
var v = attr$[n];
this.serializeValueToAttribute(v, n, this);
}
}
},
@ -91,20 +95,26 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_takeAttributesToModel: function(model) {
for (var i=0, l=this.attributes.length; i<l; i++) {
this._setAttributeToProperty(model, this.attributes[i].name);
if (this.hasAttributes()) {
for (var i in this._propertyInfo) {
var info = this._propertyInfo[i];
if (this.hasAttribute(info.attribute)) {
this._setAttributeToProperty(model, info.attribute, i, info);
}
}
}
},
_setAttributeToProperty: function(model, attrName) {
_setAttributeToProperty: function(model, attribute, property, info) {
// Don't deserialize back to property if currently reflecting
if (!this._serializing) {
var propName = Polymer.CaseMap.dashToCamelCase(attrName);
var info = this.getPropertyInfo(propName);
if (info.defined ||
(this._propertyEffects && this._propertyEffects[propName])) {
var val = this.getAttribute(attrName);
model[propName] = this.deserialize(val, info.type);
var property = property || Polymer.CaseMap.dashToCamelCase(attribute);
// fallback to property lookup
// TODO(sorvell): check for _propertyInfo existence because of dom-bind
info = info || (this._propertyInfo && this._propertyInfo[property]);
if (info && !info.readOnly) {
var v = this.getAttribute(attribute);
model[property] = this.deserialize(v, info.type);
}
}
},
@ -118,12 +128,15 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* `properties` configuration to achieve automatic attribute reflection.
*
* @method reflectPropertyToAttribute
* @param {string} name Property name to reflect.
* @param {string} property Property name to reflect.
* @param {*=} attribute Attribute name to reflect.
* @param {*=} value Property value to refect.
*/
reflectPropertyToAttribute: function(name) {
reflectPropertyToAttribute: function(property, attribute, value) {
this._serializing = true;
this.serializeValueToAttribute(this[name],
Polymer.CaseMap.camelToDashCase(name));
value = (value === undefined) ? this[property] : value;
this.serializeValueToAttribute(value,
attribute || Polymer.CaseMap.camelToDashCase(property));
this._serializing = false;
},
@ -147,9 +160,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// this._warn(this._logf('serializeValueToAttribute',
// 'serializing long attribute values can lead to poor performance', this));
// }
(node || this)
[str === undefined ? 'removeAttribute' : 'setAttribute']
(attribute, str);
node = node || this;
if (str === undefined) {
node.removeAttribute(attribute);
} else {
node.setAttribute(attribute, str);
}
},
/**

View File

@ -79,7 +79,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_flattenBehaviorsList: function(behaviors) {
var flat = [];
behaviors.forEach(function(b) {
for (var i=0; i < behaviors.length; i++) {
var b = behaviors[i];
if (b instanceof Array) {
flat = flat.concat(this._flattenBehaviorsList(b));
}
@ -89,32 +90,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
} else {
this._warn(this._logf('_flattenBehaviorsList', 'behavior is null, check for missing or 404 import'));
}
}, this);
}
return flat;
},
_mixinBehavior: function(b) {
Object.getOwnPropertyNames(b).forEach(function(n) {
switch (n) {
case 'hostAttributes':
case 'registered':
case 'properties':
case 'observers':
case 'listeners':
case 'created':
case 'attached':
case 'detached':
case 'attributeChanged':
case 'configure':
case 'ready':
break;
default:
if (!this.hasOwnProperty(n)) {
this.copyOwnProperty(n, b, this);
}
break;
var n$ = Object.getOwnPropertyNames(b);
for (var i=0, n; (i<n$.length) && (n=n$[i]); i++) {
if (!Polymer.Base._behaviorProperties[n] && !this.hasOwnProperty(n)) {
this.copyOwnProperty(n, b, this);
}
}, this);
}
},
_prepBehaviors: function() {
@ -133,9 +119,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_doBehavior: function(name, args) {
this.behaviors.forEach(function(b) {
this._invokeBehavior(b, name, args);
}, this);
for (var i=0; i < this.behaviors.length; i++) {
this._invokeBehavior(this.behaviors[i], name, args);
}
this._invokeBehavior(this, name, args);
},
@ -147,12 +133,29 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_marshalBehaviors: function() {
this.behaviors.forEach(function(b) {
this._marshalBehavior(b);
}, this);
for (var i=0; i < this.behaviors.length; i++) {
this._marshalBehavior(this.behaviors[i]);
}
this._marshalBehavior(this);
}
});
// special properties on behaviors are not mixed in and are instead
// either processed specially (e.g. listeners, properties) or available
// for calling via doBehavior (e.g. created, ready)
Polymer.Base._behaviorProperties = {
hostAttributes: true,
beforeRegister: true,
registered: true,
properties: true,
observers: true,
listeners: true,
created: true,
attached: true,
detached: true,
attributeChanged: true,
ready: true
}
</script>

View File

@ -105,12 +105,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* @param {string} property Name of property to introspect.
* @return {Object} Property descriptor for specified property.
*/
// TODO(sorvell): This function returns the first property object found
// and this is not the property info Polymer acts on for readOnly or type
// This api should be combined with _propertyInfo.
getPropertyInfo: function(property) {
var info = this._getPropertyInfo(property, this.properties);
if (!info) {
this.behaviors.some(function(b) {
return info = this._getPropertyInfo(property, b.properties);
}, this);
for (var i=0; i < this.behaviors.length; i++) {
info = this._getPropertyInfo(property, this.behaviors[i].properties);
if (info) {
return info;
}
};
}
return info || Polymer.nob;
},
@ -127,7 +133,48 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
p.defined = true;
}
return p;
}
},
// union properties, behaviors.properties, and propertyEffects
_prepPropertyInfo: function() {
this._propertyInfo = {};
for (var i=0, p; i < this.behaviors.length; i++) {
this._addPropertyInfo(this._propertyInfo, this.behaviors[i].properties);
}
this._addPropertyInfo(this._propertyInfo, this.properties);
this._addPropertyInfo(this._propertyInfo, this._propertyEffects);
},
// list of propertyInfo with {readOnly, type, attribute}
_addPropertyInfo: function(target, source) {
if (source) {
var t, s;
for (var i in source) {
t = target[i];
s = source[i];
// optimization: avoid info'ing properties that are protected and
// not read only since they are not needed for attributes or
// configuration.
if (i[0] === '_' && !s.readOnly) {
continue;
}
if (!target[i]) {
target[i] = {
type: typeof(s) === 'function' ? s : s.type,
readOnly: s.readOnly,
attribute: Polymer.CaseMap.camelToDashCase(i)
}
} else {
if (!t.type) {
t.type = s.type;
}
if (!t.readOnly) {
t.readOnly = s.readOnly;
}
}
}
}
},
});

View File

@ -17,7 +17,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
(document._currentScript || document.currentScript).parentNode;
if (module.localName === 'dom-module') {
var id = module.id || module.getAttribute('name')
|| module.getAttribute('is')
|| module.getAttribute('is');
this.is = id;
}
}

View File

@ -7,6 +7,9 @@ The complete set of contributors may be found at http://polymer.github.io/CONTRI
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../lib/async.html">
<link rel="import" href="../lib/debounce.html">
<script>
Polymer.Base._addFeature({
@ -24,7 +27,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* debouncedClickAction: function(e) {
* // will not call `processClick` more than once per 100ms
* this.debounce('click', function() {
* this.processClick;
* this.processClick();
* }, 100);
* }
*
@ -49,7 +52,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/
isDebouncerActive: function(jobName) {
var debouncer = this._debouncers[jobName];
return debouncer && debouncer.finish;
return !!(debouncer && debouncer.finish);
},
/**

View File

@ -56,37 +56,39 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// In order not to over-emphaisize this technical difference, we expose
// one concept to the user and it maps to the dom-related meaning of host.
//
// 1. set this element's `host` and push this element onto the `host`'s
// set this element's `host` and push this element onto the `host`'s
// list of `client` elements
// 2. establish this element as the current hosting element (allows
// any elements we stamp to easily set host to us).
_pushHost: function(host) {
// this.dataHost reflects the parent element who manages
// any bindings for the element. Only elements originally
// stamped from Polymer templates have a dataHost, and this
// never changes
_registerHost: function(host) {
// NOTE: The `dataHost` of an element never changes.
this.dataHost = host = host ||
this.dataHost = host = host ||
Polymer.Base._hostStack[Polymer.Base._hostStack.length-1];
// this.dataHost reflects the parent element who manages
// any bindings for the element. Only elements originally
// stamped from Polymer templates have a dataHost, and this
// never changes
if (host && host._clients) {
host._clients.push(this);
}
this._beginHost();
this._clients = null;
this._clientsReadied = false;
},
_beginHost: function() {
// establish this element as the current hosting element (allows
// any elements we stamp to easily set host to us).
_beginHosting: function() {
Polymer.Base._hostStack.push(this);
if (!this._clients) {
this._clients = [];
}
},
_popHost: function() {
_endHosting: function() {
// this element is no longer the current hosting element
Polymer.Base._hostStack.pop();
},
_tryReady: function() {
this._readied = false;
if (this._canReady()) {
this._ready();
}
@ -99,9 +101,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_ready: function() {
// extension point
this._beforeClientsReady();
// prepare root
this._setupRoot();
this._readyClients();
if (this._template) {
// prepare root
this._setupRoot();
this._readyClients();
}
this._clientsReadied = true;
this._clients = null;
// extension point
this._afterClientsReady();
this._readySelf();
@ -112,8 +118,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._beginDistribute();
// now fully prepare localChildren
var c$ = this._clients;
for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) {
c._ready();
if (c$) {
for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) {
c._ready();
}
}
// perform actual dom composition
this._finishDistribute();
@ -123,8 +131,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// if (!Polymer.Settings.useNativeCustomElements) {
// CustomElements.takeRecords();
// }
this._clientsReadied = true;
this._clients = null;
},
// mark readied and call `ready`

View File

@ -8,16 +8,25 @@ Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../lib/array-splice.html">
<link rel="import" href="../lib/dom-tree-api.html">
<link rel="import" href="../lib/dom-api.html">
<link rel="import" href="../lib/dom-api-shady.html">
<link rel="import" href="../lib/dom-api-shadow.html">
<link rel="import" href="../lib/dom-api-flush.html">
<link rel="import" href="../lib/dom-api-event.html">
<link rel="import" href="../lib/dom-api-classlist.html">
<link rel="import" href="../lib/dom-api-effective-nodes-observer.html">
<link rel="import" href="../lib/dom-api-distributed-nodes-observer.html">
<script>
(function() {
/**
Implements a pared down version of ShadowDOM's scoping, which is easy to
polyfill across browsers.
*/
var DomApi = Polymer.DomApi;
var TreeApi = Polymer.TreeApi;
Polymer.Base._addFeature({
_prepShady: function() {
@ -25,11 +34,25 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._useContent = this._useContent || Boolean(this._template);
},
_setupShady: function() {
// object shaping...
this.shadyRoot = null;
if (!this.__domApi) {
this.__domApi = null;
}
if (!this.__dom) {
this.__dom = null;
}
if (!this._ownerShadyRoot) {
this._ownerShadyRoot = undefined;
}
},
// called as part of content initialization, prior to template stamping
_poolContent: function() {
if (this._useContent) {
// capture lightChildren to help reify dom scoping
saveLightChildrenIfNeeded(this);
TreeApi.Logical.saveChildNodes(this);
}
},
@ -42,7 +65,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// removed from document by shadyDOM distribution
// so we ensure this here
if (!this.dataHost) {
upgradeLightChildren(this._lightChildren);
upgradeLogicalChildren(TreeApi.Logical.getChildNodes(this));
}
}
},
@ -50,6 +73,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_createLocalRoot: function() {
this.shadyRoot = this.root;
this.shadyRoot._distributionClean = false;
this.shadyRoot._hasDistributed = false;
this.shadyRoot._isShadyRoot = true;
this.shadyRoot._dirtyRoots = [];
// capture insertion point list
@ -60,11 +84,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// a. for shadyRoot
// b. for insertion points (fallback)
// c. for parents of insertion points
saveLightChildrenIfNeeded(this.shadyRoot);
TreeApi.Logical.saveChildNodes(this.shadyRoot);
for (var i=0, c; i < i$.length; i++) {
c = i$[i];
saveLightChildrenIfNeeded(c);
saveLightChildrenIfNeeded(c.parentNode);
TreeApi.Logical.saveChildNodes(c);
TreeApi.Logical.saveChildNodes(c.parentNode);
}
this.shadyRoot.host = this;
},
@ -95,19 +119,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/
distributeContent: function(updateInsertionPoints) {
if (this.shadyRoot) {
var dom = Polymer.dom(this);
if (updateInsertionPoints) {
dom._updateInsertionPoints(this);
}
this.shadyRoot._invalidInsertionPoints =
this.shadyRoot._invalidInsertionPoints || updateInsertionPoints;
// Distribute the host that's the top of this element's distribution
// tree. Distributing that host will *always* distibute this element.
var host = getTopDistributingHost(this);
dom._lazyDistribute(host);
Polymer.dom(this)._lazyDistribute(host);
}
},
_distributeContent: function() {
if (this._useContent && !this.shadyRoot._distributionClean) {
if (this.shadyRoot._invalidInsertionPoints) {
Polymer.dom(this)._updateInsertionPoints(this);
this.shadyRoot._invalidInsertionPoints = false;
}
// logically distribute self
this._beginDistribute();
this._distributeDirtyRoots();
@ -116,7 +142,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_beginDistribute: function() {
if (this._useContent && hasInsertionPoint(this.shadyRoot)) {
if (this._useContent && DomApi.hasInsertionPoint(this.shadyRoot)) {
// reset distributions
this._resetDistribution();
// compute which nodes should be distributed where
@ -141,15 +167,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// so that attachment that provokes additional distribution (e.g.
// adding something to your parentNode) works
this.shadyRoot._distributionClean = true;
if (hasInsertionPoint(this.shadyRoot)) {
if (DomApi.hasInsertionPoint(this.shadyRoot)) {
this._composeTree();
// NOTE: send a signal to insertion points that we have distributed
// which informs effective children observers
notifyContentObservers(this.shadyRoot);
} else {
if (!this.shadyRoot._hasDistributed) {
this.textContent = '';
// reset composed children here in case they may have already
// been set (this shouldn't happen but can if dependency ordering
// is incorrect and as a result upgrade order is unexpected)
this._composedChildren = null;
TreeApi.Composed.clearChildNodes(this);
this.appendChild(this.shadyRoot);
} else {
// simplified non-tree walk composition
@ -157,6 +182,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._updateChildNodes(this, children);
}
}
// NOTE: send a signal to any Polymer.dom node observers
// to report the initial set of childNodes
if (!this.shadyRoot._hasDistributed) {
notifyInitialDistribution(this);
}
this.shadyRoot._hasDistributed = true;
}
},
@ -174,7 +204,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// Alternatively we could just polyfill it somewhere.
// Note that the arguments are reversed from what you might expect.
node = node || this;
return matchesSelector.call(node, selector);
return DomApi.matchesSelector.call(node, selector);
},
// Many of the following methods are all conceptually static, but they are
@ -182,7 +212,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_resetDistribution: function() {
// light children
var children = getLightChildren(this);
var children = TreeApi.Logical.getChildNodes(this);
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child._destinationInsertionPoints) {
@ -204,7 +234,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// these with the "content root" to arrive at the composed tree.
_collectPool: function() {
var pool = [];
var children = getLightChildren(this);
var children = TreeApi.Logical.getChildNodes(this);
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (isInsertionPoint(child)) {
@ -250,7 +280,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
// Fallback content if nothing was distributed here
if (!anyDistributed) {
var children = getLightChildren(content);
var children = TreeApi.Logical.getChildNodes(content);
for (var j = 0; j < children.length; j++) {
distributeNodeInto(children[j], content);
}
@ -263,7 +293,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._updateChildNodes(this, this._composeNode(this));
var p$ = this.shadyRoot._insertionPoints;
for (var i=0, l=p$.length, p, parent; (i<l) && (p=p$[i]); i++) {
parent = p._lightParent || p.parentNode;
parent = TreeApi.Logical.getParentNode(p);
if (!parent._useContent && (parent !== this) &&
(parent !== this.shadyRoot)) {
this._updateChildNodes(parent, this._composeNode(parent));
@ -274,7 +304,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// Returns the list of nodes which should be rendered inside `node`.
_composeNode: function(node) {
var children = [];
var c$ = getLightChildren(node.shadyRoot || node);
var c$ = TreeApi.Logical.getChildNodes(node.shadyRoot || node);
for (var i = 0; i < c$.length; i++) {
var child = c$[i];
if (isInsertionPoint(child)) {
@ -294,7 +324,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// Ensures that the rendered node list inside `container` is `children`.
_updateChildNodes: function(container, children) {
var composed = getComposedChildren(container);
var composed = TreeApi.Composed.getChildNodes(container);
var splices =
Polymer.ArraySplice.calculateSplices(children, composed);
// process removals
@ -304,8 +334,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// to remove it; this can happen if Polymer.dom moves a node and
// then schedules its previous host for distribution resulting in
// the node being removed here.
if (getComposedParent(n) === container) {
remove(n);
if (TreeApi.Composed.getParentNode(n) === container) {
TreeApi.Composed.removeChild(container, n);
}
composed.splice(s.index + d, 1);
}
@ -316,12 +346,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
next = composed[s.index];
for (var j=s.index, n; j < s.index + s.addedCount; j++) {
n = children[j];
insertBefore(container, n, next);
TreeApi.Composed.insertBefore(container, n, next);
// TODO(sorvell): is this splice strictly needed?
composed.splice(j, 0, n);
}
}
// ensure composed parent is set
ensureComposedParent(container, children);
},
_matchesContentSelect: function(node, contentElement) {
@ -361,14 +390,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded;
var getLightChildren = Polymer.DomApi.getLightChildren;
var matchesSelector = Polymer.DomApi.matchesSelector;
var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint;
var getComposedChildren = Polymer.DomApi.getComposedChildren;
var getComposedParent = Polymer.DomApi.getComposedParent;
var removeFromComposedParent = Polymer.DomApi.removeFromComposedParent;
function distributeNodeInto(child, insertionPoint) {
insertionPoint._distributedNodes.push(child);
var points = child._destinationInsertionPoints;
@ -394,9 +415,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// dirty a shadyRoot if a change may trigger reprojection!
function maybeRedistributeParent(content, host) {
var parent = content._lightParent;
// only get logical parent.
var parent = TreeApi.Logical.getParentNode(content);
if (parent && parent.shadyRoot &&
hasInsertionPoint(parent.shadyRoot) &&
DomApi.hasInsertionPoint(parent.shadyRoot) &&
parent.shadyRoot._distributionClean) {
parent.shadyRoot._distributionClean = false;
host.shadyRoot._dirtyRoots.push(parent);
@ -413,36 +435,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return node.localName == 'content';
}
var nativeInsertBefore = Element.prototype.insertBefore;
var nativeRemoveChild = Element.prototype.removeChild;
function insertBefore(parentNode, newChild, refChild) {
var newChildParent = getComposedParent(newChild);
if (newChildParent !== parentNode) {
removeFromComposedParent(newChildParent, newChild);
}
// remove child from its old parent first
remove(newChild);
// insert it into the real DOM
nativeInsertBefore.call(parentNode, newChild, refChild || null);
newChild._composedParent = parentNode;
}
function remove(node) {
var parentNode = getComposedParent(node);
if (parentNode) {
node._composedParent = null;
// remove it from the real DOM
nativeRemoveChild.call(parentNode, node);
}
}
function ensureComposedParent(parent, children) {
for (var i=0, n; i < children.length; i++) {
children[i]._composedParent = parent;
}
}
// returns the host that's the top of this host's distribution tree
function getTopDistributingHost(host) {
while (host && hostNeedsRedistribution(host)) {
@ -454,18 +446,33 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// Return true if a host's children includes
// an insertion point that selects selectively
function hostNeedsRedistribution(host) {
var c$ = Polymer.dom(host).children;
var c$ = TreeApi.Logical.getChildNodes(host);
for (var i=0, c; i < c$.length; i++) {
c = c$[i];
if (c.localName === 'content') {
if (c.localName && c.localName === 'content') {
return host.domHost;
}
}
}
function notifyContentObservers(root) {
for (var i=0, c; i < root._insertionPoints.length; i++) {
c = root._insertionPoints[i];
if (DomApi.hasApi(c)) {
Polymer.dom(c).notifyObserver();
}
}
}
function notifyInitialDistribution(host) {
if (DomApi.hasApi(host)) {
Polymer.dom(host).notifyObserver();
}
}
var needsUpgrade = window.CustomElements && !CustomElements.useNative;
function upgradeLightChildren(children) {
function upgradeLogicalChildren(children) {
if (needsUpgrade && children) {
for (var i=0; i < children.length; i++) {
CustomElements.upgrade(children[i]);

View File

@ -25,8 +25,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_prepTemplate: function() {
// locate template using dom-module
this._template =
this._template || Polymer.DomModule.import(this.is, 'template');
if (this._template === undefined) {
this._template = Polymer.DomModule.import(this.is, 'template');
}
// stick finger in footgun
if (this._template && this._template.hasAttribute('is')) {
this._warn(this._logf('_prepTemplate', 'top-level Polymer template ' +
@ -34,10 +35,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
'Move inside simple <template>.'));
}
// bootstrap the template if it has not already been
if (this._template && !this._template.content && HTMLTemplateElement.bootstrap) {
if (this._template && !this._template.content &&
window.HTMLTemplateElement && HTMLTemplateElement.decorate) {
HTMLTemplateElement.decorate(this._template);
// recurse if necessary
HTMLTemplateElement.bootstrap(this._template.content);
}
},

View File

@ -122,13 +122,16 @@ TODO(sjmiles): this module should produce either syntactic metadata
this._notes = [];
} else {
// TODO(sorvell): ad hoc method of plugging behavior into Annotations
Polymer.Annotations.prepElement = this._prepElement.bind(this);
var self = this;
Polymer.Annotations.prepElement = function(element) {
self._prepElement(element);
}
if (this._template._content && this._template._content._notes) {
this._notes = this._template._content._notes;
} else {
this._notes = Polymer.Annotations.parseAnnotations(this._template);
this._processAnnotations(this._notes);
}
this._processAnnotations(this._notes);
Polymer.Annotations.prepElement = null;
}
},
@ -139,9 +142,14 @@ TODO(sjmiles): this module should produce either syntactic metadata
// Parse bindings for methods & path roots (models)
for (var j=0; j<note.bindings.length; j++) {
var b = note.bindings[j];
b.signature = this._parseMethod(b.value);
if (!b.signature) {
b.model = this._modelForPath(b.value);
for (var k=0; k<b.parts.length; k++) {
var p = b.parts[k];
if (!p.literal) {
p.signature = this._parseMethod(p.value);
if (!p.signature) {
p.model = this._modelForPath(p.value);
}
}
}
}
// Recurse into nested templates & bind parent props
@ -154,10 +162,12 @@ TODO(sjmiles): this module should produce either syntactic metadata
bindings.push({
index: note.index,
kind: 'property',
mode: '{',
name: '_parent_' + prop,
model: prop,
value: prop
parts: [{
mode: '{',
model: prop,
value: prop
}]
});
}
note.bindings = note.bindings.concat(bindings);
@ -171,24 +181,31 @@ TODO(sjmiles): this module should produce either syntactic metadata
// templates.
_discoverTemplateParentProps: function(notes) {
var pp = {};
notes.forEach(function(n) {
for (var i=0, n; (i<notes.length) && (n=notes[i]); i++) {
// Find all bindings to parent.* and spread them into _parentPropChain
n.bindings.forEach(function(b) {
if (b.signature) {
var args = b.signature.args;
for (var k=0; k<args.length; k++) {
pp[args[k].model] = true;
for (var j=0, b$=n.bindings, b; (j<b$.length) && (b=b$[j]); j++) {
for (var k=0, p$=b.parts, p; (k<p$.length) && (p=p$[k]); k++) {
if (p.signature) {
var args = p.signature.args;
for (var kk=0; kk<args.length; kk++) {
var model = args[kk].model;
if (model) {
pp[model] = true;
}
}
} else {
if (p.model) {
pp[p.model] = true;
}
}
} else {
pp[b.model] = true;
}
});
}
// Merge child _parentProps into this _parentProps
if (n.templateContent) {
var tpp = n.templateContent._parentProps;
Polymer.Base.mixin(pp, tpp);
}
});
}
return pp;
},
@ -210,52 +227,94 @@ TODO(sjmiles): this module should produce either syntactic metadata
},
// push configuration references at configure time
_configureAnnotationReferences: function() {
this._configureTemplateContent();
_configureAnnotationReferences: function(config) {
var notes = this._notes;
var nodes = this._nodes;
for (var i=0; i<notes.length; i++) {
var note = notes[i];
var node = nodes[i];
this._configureTemplateContent(note, node);
this._configureCompoundBindings(note, node);
}
},
// nested template contents have been stored prototypically to avoid
// unnecessary duplication, here we put references to the
// indirected contents onto the nested template instances
_configureTemplateContent: function() {
this._notes.forEach(function(note, i) {
if (note.templateContent) {
// note: we can rely on _nodes being set here and having the same
// index as _notes
this._nodes[i]._content = note.templateContent;
_configureTemplateContent: function(note, node) {
if (note.templateContent) {
// note: we can rely on _nodes being set here and having the same
// index as _notes
node._content = note.templateContent;
}
},
// Compound bindings utilize private storage on the node to store
// the current state of each value that will be concatenated to generate
// the final property/attribute/text value
// Here we initialize the private storage array on the node with any
// literal parts that won't change (could get fancy and use WeakMap),
// and configure property bindings to children with the literal parts
// (textContent and annotations were already initialized in the template)
_configureCompoundBindings: function(note, node) {
var bindings = note.bindings;
for (var i=0; i<bindings.length; i++) {
var binding = bindings[i];
if (binding.isCompound) {
// Create compound storage map
var storage = node.__compoundStorage__ ||
(node.__compoundStorage__ = {});
var parts = binding.parts;
// Copy literals from parts into storage for this binding
var literals = new Array(parts.length);
for (var j=0; j<parts.length; j++) {
literals[j] = parts[j].literal;
}
var name = binding.name;
storage[name] = literals;
// Configure properties with their literal parts
if (binding.literal && binding.kind == 'property') {
if (node._configValue) {
node._configValue(name, binding.literal);
} else {
node[name] = binding.literal;
}
}
}
}, this);
}
},
// construct `$` map (from id annotations)
_marshalIdNodes: function() {
this.$ = {};
this._notes.forEach(function(a) {
for (var i=0, l=this._notes.length, a; (i<l) && (a=this._notes[i]); i++) {
if (a.id) {
this.$[a.id] = this._findAnnotatedNode(this.root, a);
}
}, this);
}
},
// concretize `_nodes` map (from anonymous annotations)
_marshalAnnotatedNodes: function() {
if (this._nodes) {
this._nodes = this._nodes.map(function(a) {
return this._findAnnotatedNode(this.root, a);
}, this);
if (this._notes && this._notes.length) {
var r = new Array(this._notes.length);
for (var i=0; i < this._notes.length; i++) {
r[i] = this._findAnnotatedNode(this.root, this._notes[i]);
}
this._nodes = r;
}
},
// install event listeners (from event annotations)
_marshalAnnotatedListeners: function() {
this._notes.forEach(function(a) {
for (var i=0, l=this._notes.length, a; (i<l) && (a=this._notes[i]); i++) {
if (a.events && a.events.length) {
var node = this._findAnnotatedNode(this.root, a);
a.events.forEach(function(e) {
for (var j=0, e$=a.events, e; (j<e$.length) && (e=e$[j]); j++) {
this.listen(node, e.name, e.value);
}, this);
}
}
}, this);
}
}
});

View File

@ -46,13 +46,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// storage for configuration
_setupConfigure: function(initialConfig) {
this._config = {};
// don't accept undefined values in intialConfig
for (var i in initialConfig) {
if (initialConfig[i] !== undefined) {
this._config[i] = initialConfig[i];
this._handlers = [];
this._aboveConfig = null;
if (initialConfig) {
// don't accept undefined values in intialConfig
for (var i in initialConfig) {
if (initialConfig[i] !== undefined) {
this._config[i] = initialConfig[i];
}
}
}
this._handlers = [];
},
// static attributes are deserialized into _config
@ -67,7 +70,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// at configure time values are stored in _config
_configValue: function(name, value) {
this._config[name] = value;
var info = this._propertyInfo[name];
if (!info || !info.readOnly) {
this._config[name] = value;
}
},
// Override polymer-mini thunk
@ -87,17 +93,22 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// get individual default values from property configs
var config = {};
// mixed-in behaviors
this.behaviors.forEach(function(b) {
this._configureProperties(b.properties, config);
}, this);
for (var i=0; i < this.behaviors.length; i++) {
this._configureProperties(this.behaviors[i].properties, config);
}
// prototypical behavior
this._configureProperties(this.properties, config);
// TODO(sorvell): it *may* be faster to loop over _propertyInfo but
// there are some test issues.
//this._configureProperties(this._propertyInfo, config);
// override local configuration with configuration from above
this._mixinConfigure(config, this._aboveConfig);
this.mixin(config, this._aboveConfig);
// this is the new _config, which are the final values to be applied
this._config = config;
// pass configuration data to bindings
this._distributeConfig(this._config);
if (this._clients && this._clients.length) {
this._distributeConfig(this._config);
}
},
_configureProperties: function(properties, config) {
@ -115,14 +126,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
},
_mixinConfigure: function(a, b) {
for (var prop in b) {
if (!this.getPropertyInfo(prop).readOnly) {
a[prop] = b[prop];
}
}
},
// distribute config values to bound nodes.
_distributeConfig: function(config) {
var fx$ = this._propertyEffects;
@ -131,12 +134,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var fx = fx$[p];
if (fx) {
for (var i=0, l=fx.length, x; (i<l) && (x=fx[i]); i++) {
if (x.kind === 'annotation') {
// TODO(kschaaf): compound bindings (as well as computed effects)
// are excluded from top-down configure for now; to be revisited
if (x.kind === 'annotation' &&
x.effect.kind !== 'attribute' &&
!x.isCompound) {
var node = this._nodes[x.effect.index];
// seeding configuration only
if (node._configValue) {
var value = (p === x.effect.value) ? config[p] :
this.get(x.effect.value, config);
this._get(x.effect.value, config);
node._configValue(x.effect.name, value);
}
}
@ -174,10 +181,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// effects and side effects must not be processed before ready time,
// handling is queue/defered until then.
_notifyListener: function(fn, e) {
if (!this._clientsReadied) {
this._queueHandler([fn, e, e.target]);
} else {
return fn.call(this, e, e.target);
if (!Polymer.Bind._isEventBogus(e, e.target)) {
var value, path;
if (e.detail) {
value = e.detail.value;
path = e.detail.path;
}
if (!this._clientsReadied) {
this._queueHandler([fn, e.target, value, path]);
} else {
return fn.call(this, e.target, value, path);
}
}
},
@ -188,7 +202,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_flushHandlers: function() {
var h$ = this._handlers;
for (var i=0, l=h$.length, h; (i<l) && (h=h$[i]); i++) {
h[0].call(this, h[1], h[2]);
h[0].call(this, h[1], h[2], h[3]);
}
// reset handlers array
//

View File

@ -12,7 +12,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<link rel="import" href="../lib/bind/effects.html">
<script>
/**
* Support for property side effects.
*
@ -33,7 +32,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.Base._addFeature({
_addPropertyEffect: function(property, kind, effect) {
Polymer.Bind.addPropertyEffect(this, property, kind, effect);
var prop = Polymer.Bind.addPropertyEffect(this, property, kind, effect);
// memoize path function for faster lookup.
prop.pathFn = this['_' + prop.kind + 'PathEffect'];
},
// prototyping
@ -60,10 +61,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._addComputedEffect(p, prop.computed);
}
if (prop.notify) {
this._addPropertyEffect(p, 'notify');
this._addPropertyEffect(p, 'notify', {
event: Polymer.CaseMap.camelToDashCase(p) + '-changed'});
}
if (prop.reflectToAttribute) {
this._addPropertyEffect(p, 'reflect');
this._addPropertyEffect(p, 'reflect', {
attribute: Polymer.CaseMap.camelToDashCase(p)
});
}
if (prop.readOnly) {
// Ensure accessor is created
@ -75,14 +79,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_addComputedEffect: function(name, expression) {
var sig = this._parseMethod(expression);
sig.args.forEach(function(arg) {
for (var i=0, arg; (i<sig.args.length) && (arg=sig.args[i]); i++) {
this._addPropertyEffect(arg.model, 'compute', {
method: sig.method,
args: sig.args,
trigger: arg,
property: name
name: name
});
}, this);
}
},
_addObserverEffect: function(property, observer) {
@ -94,34 +98,37 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_addComplexObserverEffects: function(observers) {
if (observers) {
observers.forEach(function(observer) {
this._addComplexObserverEffect(observer);
}, this);
for (var i=0, o; (i<observers.length) && (o=observers[i]); i++) {
this._addComplexObserverEffect(o);
}
}
},
_addComplexObserverEffect: function(observer) {
var sig = this._parseMethod(observer);
sig.args.forEach(function(arg) {
if (!sig) {
throw new Error("Malformed observer expression '" + observer + "'");
}
for (var i=0, arg; (i<sig.args.length) && (arg=sig.args[i]); i++) {
this._addPropertyEffect(arg.model, 'complexObserver', {
method: sig.method,
args: sig.args,
trigger: arg
});
}, this);
}
},
_addAnnotationEffects: function(notes) {
// create a virtual annotation list, must be concretized at instance time
this._nodes = [];
// process annotations that have been parsed from template
notes.forEach(function(note) {
for (var i=0, note; (i<notes.length) && (note=notes[i]); i++) {
// where to find the node in the concretized list
var index = this._nodes.push(note) - 1;
note.bindings.forEach(function(binding) {
this._addAnnotationEffect(binding, index);
}, this);
}, this);
var b$ = note.bindings;
for (var j=0, binding; (j<b$.length) && (binding=b$[j]); j++) {
this._addAnnotationEffect(binding, i);
}
}
},
_addAnnotationEffect: function(note, index) {
@ -129,39 +136,53 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (Polymer.Bind._shouldAddListener(note)) {
// <node>.on.<dash-case-property>-changed: <path> = e.detail.value
Polymer.Bind._addAnnotatedListener(this, index,
note.name, note.value, note.event);
note.name, note.parts[0].value, note.parts[0].event);
}
if (note.signature) {
this._addAnnotatedComputationEffect(note, index);
} else {
// capture the node index
note.index = index;
// add 'annotation' binding effect for property 'model'
this._addPropertyEffect(note.model, 'annotation', note);
for (var i=0; i<note.parts.length; i++) {
var part = note.parts[i];
if (part.signature) {
this._addAnnotatedComputationEffect(note, part, index);
} else if (!part.literal) {
// add 'annotation' binding effect for property 'model'
this._addPropertyEffect(part.model, 'annotation', {
kind: note.kind,
index: index,
name: note.name,
value: part.value,
isCompound: note.isCompound,
compoundIndex: part.compoundIndex,
event: part.event,
customEvent: part.customEvent,
negate: part.negate
});
}
}
},
_addAnnotatedComputationEffect: function(note, index) {
var sig = note.signature;
_addAnnotatedComputationEffect: function(note, part, index) {
var sig = part.signature;
if (sig.static) {
this.__addAnnotatedComputationEffect('__static__', index, note, sig, null);
this.__addAnnotatedComputationEffect('__static__', index, note, part, null);
} else {
sig.args.forEach(function(arg) {
for (var i=0, arg; (i<sig.args.length) && (arg=sig.args[i]); i++) {
if (!arg.literal) {
this.__addAnnotatedComputationEffect(arg.model, index, note, sig, arg);
this.__addAnnotatedComputationEffect(arg.model, index, note, part,
arg);
}
}, this);
}
}
},
__addAnnotatedComputationEffect: function(property, index, note, sig, trigger) {
__addAnnotatedComputationEffect: function(property, index, note, part, trigger) {
this._addPropertyEffect(property, 'annotatedComputation', {
index: index,
isCompound: note.isCompound,
compoundIndex: part.compoundIndex,
kind: note.kind,
property: note.name,
negate: note.negate,
method: sig.method,
args: sig.args,
name: note.name,
negate: part.negate,
method: part.signature.method,
args: part.signature.args,
trigger: trigger
});
},
@ -169,7 +190,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// method expressions are of the form: `name([arg1, arg2, .... argn])`
_parseMethod: function(expression) {
// tries to match valid javascript property names
var m = expression.match(/([^\s]+)\(([\s\S]*)\)/);
var m = expression.match(/([^\s]+?)\((.*)\)/);
if (m) {
var sig = { method: m[1], static: true };
if (m[2].trim()) {
@ -206,8 +227,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
;
// basic argument descriptor
var a = {
name: arg,
model: this._modelForPath(arg)
name: arg
};
// detect literal value (must be String or Number)
var fc = arg[0];
@ -230,6 +250,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
// if not literal, look for structured path
if (!a.literal) {
a.model = this._modelForPath(arg);
// detect structured path (has dots)
a.structured = arg.indexOf('.') > 0;
if (a.structured) {
@ -243,17 +264,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
// instancing
_marshalInstanceEffects: function() {
Polymer.Bind.prepareInstance(this);
Polymer.Bind.setupBindListeners(this);
if (this._bindListeners) {
Polymer.Bind.setupBindListeners(this);
}
},
_applyEffectValue: function(value, info) {
_applyEffectValue: function(info, value) {
var node = this._nodes[info.index];
// TODO(sorvell): ideally, the info object is normalized for easy
// lookup here.
var property = info.property || info.name || 'textContent';
var property = info.name;
if (info.isCompound) {
var storage = node.__compoundStorage__[property];
storage[info.compoundIndex] = value;
value = storage.join('');
}
// special processing for 'class' and 'className'; 'class' handled
// when attr is serialized.
if (info.kind == 'attribute') {
@ -269,19 +294,22 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
(node.localName == 'input' && property == 'value')) {
value = value == undefined ? '' : value;
}
// TODO(kschaaf): Ideally we'd use `fromAbove: true`, but this
// breaks read-only properties
// this.__setProperty(property, value, true, node);
return node[property] = value;
// Ideally we would call setProperty using fromAbove: true to avoid
// spinning the wheel needlessly, but we found that users were listening
// for change events outside of bindings
var pinfo;
if (!node._propertyInfo || !(pinfo = node._propertyInfo[property]) ||
!pinfo.readOnly) {
this.__setProperty(property, value, false, node);
}
}
},
_executeStaticEffects: function() {
if (this._propertyEffects.__static__) {
if (this._propertyEffects && this._propertyEffects.__static__) {
this._effectEffects('__static__', null, this._propertyEffects.__static__);
}
}
});
</script>

View File

@ -55,18 +55,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/
listeners: {},
// TODO(sorvell): need to deprecate listening for a.b.
// In the interim, we need to keep a map of listeners by node name
// to avoid these string searches at instance time.
_listenListeners: function(listeners) {
var node, name, key;
for (key in listeners) {
if (key.indexOf('.') < 0) {
var node, name, eventName;
for (eventName in listeners) {
if (eventName.indexOf('.') < 0) {
node = this;
name = key;
name = eventName;
} else {
name = key.split('.');
name = eventName.split('.');
node = this.$[name[0]];
name = name[1];
}
this.listen(node, name, listeners[key]);
this.listen(node, name, listeners[eventName]);
}
},
@ -80,8 +83,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* @param {string} methodName Name of handler method on `this` to call.
*/
listen: function(node, eventName, methodName) {
this._listen(node, eventName,
this._createEventHandler(node, eventName, methodName));
var handler = this._recallEventHandler(this, eventName, node, methodName);
// reuse cache'd handler
if (!handler) {
handler = this._createEventHandler(node, eventName, methodName);
}
// don't call _listen if we are already listening
if (handler._listening) {
return;
}
this._listen(node, eventName, handler);
handler._listening = true;
},
_boundListenerKey: function(eventName, methodName) {
@ -125,6 +137,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
methodName + '` not defined'));
}
};
handler._listening = false;
this._recordEventHandler(host, eventName, node, methodName, handler);
return handler;
},
@ -140,9 +153,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
anymore.
*/
unlisten: function(node, eventName, methodName) {
// leave handler in map for cache purposes
var handler = this._recallEventHandler(this, eventName, node, methodName);
if (handler) {
this._unlisten(node, eventName, handler);
handler._listening = false;
}
},

View File

@ -12,6 +12,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
'use strict';
var wrap = Polymer.DomApi.wrap;
// detect native touch action support
var HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string';
var GESTURE_KEY = '__polymerGestures';
@ -166,6 +168,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
function untrackDocument(stateObj) {
document.removeEventListener('mousemove', stateObj.movefn);
document.removeEventListener('mouseup', stateObj.upfn);
stateObj.movefn = null;
stateObj.upfn = null;
}
var Gestures = {
@ -199,8 +203,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
handleNative: function(ev) {
var handled;
var type = ev.type;
var node = ev.currentTarget;
var node = wrap(ev.currentTarget);
var gobj = node[GESTURE_KEY];
if (!gobj) {
return;
}
var gs = gobj[type];
if (!gs) {
return;
@ -292,6 +299,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// automate the event listeners for the native events
add: function(node, evType, handler) {
// SD polyfill: handle case where `node` is unwrapped, like `document`
node = wrap(node);
var recognizer = this.gestures[evType];
var deps = recognizer.deps;
var name = recognizer.name;
@ -323,6 +332,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// automate event listener removal for native events
remove: function(node, evType, handler) {
// SD polyfill: handle case where `node` is unwrapped, like `document`
node = wrap(node);
var recognizer = this.gestures[evType];
var deps = recognizer.deps;
var name = recognizer.name;
@ -407,8 +418,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
emits: ['down', 'up'],
info: {
movefn: function(){},
upfn: function(){}
movefn: null,
upfn: null
},
reset: function() {
@ -443,12 +454,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this.fire('up', Gestures.findOriginalTarget(e), e.changedTouches[0]);
},
fire: function(type, target, event) {
var self = this;
Gestures.fire(target, type, {
x: event.clientX,
y: event.clientY,
sourceEvent: event,
prevent: Gestures.prevent.bind(Gestures)
prevent: function(e) {
return Gestures.prevent(e);
}
});
}
});
@ -475,8 +487,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
this.moves.push(move);
},
movefn: function(){},
upfn: function(){},
movefn: null,
upfn: null,
prevent: false
},
@ -664,6 +676,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
};
Polymer.Base._addFeature({
_setupGestures: function() {
this.__polymerGestures = null;
},
// override _listen to handle gestures
_listen: function(node, eventName, handler) {
if (Gestures.gestures[eventName]) {

View File

@ -65,10 +65,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.Base._addFeature({
/**
* Notify that a path has changed. For example:
* Notify that a path has changed.
*
* this.item.user.name = 'Bob';
* this.notifyPath('item.user.name', this.item.user.name);
* Example:
*
* this.item.user.name = 'Bob';
* this.notifyPath('item.user.name', this.item.user.name);
*
* @param {string} path Path that should be notified.
* @param {*} value Value of the specified path.
@ -79,6 +81,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* based on a dirty check of whether the new value was already known.
*/
notifyPath: function(path, value, fromAbove) {
// Convert any array indices to keys before notifying path
var info = {};
this._get(path, this, info);
// Notify change to key-based path
if (info.path) {
this._notifyPath(info.path, value, fromAbove);
}
},
// Note: this implemetation only accepts key-based array paths
_notifyPath: function(path, value, fromAbove) {
var old = this._propertySetter(path, value);
// manual dirty checking for now...
// NaN is always not equal to itself,
@ -94,7 +107,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// is coming from above already (avoid wasted event dispatch)
if (!fromAbove) {
// TODO(sorvell): should only notify if notify: true?
this._notifyPath(path, value);
this._notifyPathUp(path, value);
}
// console.groupEnd((this.localName || this.dataHost.id + '-' + this.dataHost.dataHost.index) + '#' + (this.id || this.index) + ' ' + path, value);
return true;
@ -131,11 +144,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*
* @method set
* @param {(string|Array<(string|number)>)} path Path to the value
* to read. The path may be specified as a string (e.g. `foo.bar.baz`)
* to write. The path may be specified as a string (e.g. `foo.bar.baz`)
* or an array of path parts (e.g. `['foo.bar', 'baz']`). Note that
* bracketed expressions are not supported; string-based path parts
* *must* be separated by dots. Note that when dereferencing array
* indicies, the index may be used as a dotted part directly
* indices, the index may be used as a dotted part directly
* (e.g. `users.12.name` or `['users', 12, 'name']`).
* @param {*} value Value to set at the specified path.
* @param {Object=} root Root object from which the path is evaluated.
@ -146,29 +159,56 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var array;
var last = parts[parts.length-1];
if (parts.length > 1) {
// Loop over path parts[0..n-2] and dereference
for (var i=0; i<parts.length-1; i++) {
var part = parts[i];
prop = prop[part];
if (array && (parseInt(part) == part)) {
parts[i] = Polymer.Collection.get(array).getKey(prop);
if (array && part[0] == '#') {
// Part was key; lookup item in collection
prop = Polymer.Collection.get(array).getItem(part);
} else {
// Get item from simple property dereference
prop = prop[part];
if (array && (parseInt(part, 10) == part)) {
// Translate array indices to collection keys for path notificaiton
parts[i] = Polymer.Collection.get(array).getKey(prop);
}
}
if (!prop) {
return;
}
// Cache previous part if it is an array
array = Array.isArray(prop) ? prop : null;
}
if (array && (parseInt(last) == last)) {
// Special handling when last part is a array item: need to replace
// item in collection associated with key for that item
if (array) {
var coll = Polymer.Collection.get(array);
var old = prop[last];
var key = coll.getKey(old);
parts[i] = key;
coll.setItem(key, value);
if (last[0] == '#') {
// Part was key; lookup item in collection
var key = last;
var old = coll.getItem(key);
// Update last part from key to index: O(n) lookup unavoidable
last = array.indexOf(old);
// Replace item associated with key in collection
coll.setItem(key, value);
} else if (parseInt(last, 10) == last) {
// Dereference index & lookup collection key
var old = prop[last];
var key = coll.getKey(old);
// Translate array indices to collection keys for path notificaiton
parts[i] = key;
// Replace item associated with key in collection
coll.setItem(key, value);
}
}
// Set value to object at end of path
prop[last] = value;
// Notify observers of path change
if (!root) {
this.notifyPath(parts.join('.'), value);
this._notifyPath(parts.join('.'), value);
}
} else {
// Simple property set
prop[path] = value;
}
},
@ -186,37 +226,62 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* or an array of path parts (e.g. `['foo.bar', 'baz']`). Note that
* bracketed expressions are not supported; string-based path parts
* *must* be separated by dots. Note that when dereferencing array
* indicies, the index may be used as a dotted part directly
* indices, the index may be used as a dotted part directly
* (e.g. `users.12.name` or `['users', 12, 'name']`).
* @param {Object=} root Root object from which the path is evaluated.
* @return {*} Value at the path, or `undefined` if any part of the path
* is undefined.
*/
get: function(path, root) {
return this._get(path, root);
},
// If `info` object is supplied, a `path` property will be added to it
// containing the path with array indices converted to keys, for use
// by the private _notifyPath / _notifySplice implementations
_get: function(path, root, info) {
var prop = root || this;
var parts = this._getPathParts(path);
var last = parts.pop();
while (parts.length) {
prop = prop[parts.shift()];
var array;
// Loop over path parts[0..n-1] and dereference
for (var i=0; i<parts.length; i++) {
if (!prop) {
return;
}
var part = parts[i];
if (array && part[0] == '#') {
// Part was key; lookup item in collection
prop = Polymer.Collection.get(array).getItem(part);
} else {
// Get item from simple property dereference
prop = prop[part];
if (info && array && (parseInt(part, 10) == part)) {
// Translate array indices to collection keys for path notificaiton
parts[i] = Polymer.Collection.get(array).getKey(prop);
}
}
// Cache previous part if it is an array
array = Array.isArray(prop) ? prop : null;
}
return prop[last];
if (info) {
info.path = parts.join('.');
}
return prop;
},
_pathEffector: function(path, value) {
// get root property
var model = this._modelForPath(path);
// search property effects of the root property for 'annotation' effects
var fx$ = this._propertyEffects[model];
var fx$ = this._propertyEffects && this._propertyEffects[model];
if (fx$) {
fx$.forEach(function(fx) {
var fxFn = this['_' + fx.kind + 'PathEffect'];
for (var i=0, fx; (i<fx$.length) && (fx=fx$[i]); i++) {
// use memoized path functions
var fxFn = fx.pathFn;
if (fxFn) {
fxFn.call(this, path, value, fx.effect);
}
}, this);
}
}
// notify runtime-bound paths
if (this._boundPaths) {
@ -232,9 +297,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
} else if ((path.indexOf(effect.value + '.') === 0) && !effect.negate) {
// locate the bound node
var node = this._nodes[effect.index];
if (node && node.notifyPath) {
if (node && node._notifyPath) {
var p = this._fixPath(effect.name , effect.value, path);
node.notifyPath(p, value, true);
node._notifyPath(p, value, true);
}
}
},
@ -276,7 +341,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._boundPaths = this._boundPaths || {};
if (from) {
this._boundPaths[to] = from;
// this.set(to, this.get(from));
// this.set(to, this._get(from));
} else {
this.unlinkPaths(to);
// this.set(to, from);
@ -302,9 +367,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
for (var a in this._boundPaths) {
var b = this._boundPaths[a];
if (path.indexOf(a + '.') == 0) {
this.notifyPath(this._fixPath(b, a, path), value);
this._notifyPath(this._fixPath(b, a, path), value);
} else if (path.indexOf(b + '.') == 0) {
this.notifyPath(this._fixPath(a, b, path), value);
this._notifyPath(this._fixPath(a, b, path), value);
}
}
},
@ -313,14 +378,15 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return property + path.slice(root.length);
},
_notifyPath: function(path, value) {
_notifyPathUp: function(path, value) {
var rootName = this._modelForPath(path);
var dashCaseName = Polymer.CaseMap.camelToDashCase(rootName);
var eventName = dashCaseName + this._EVENT_CHANGED;
// use a cached event here (_useCache: true) for efficiency
this.fire(eventName, {
path: path,
value: value
}, {bubbles: false});
}, {bubbles: false, _useCache: true});
},
_modelForPath: function(path) {
@ -330,25 +396,67 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_EVENT_CHANGED: '-changed',
/**
* Notify that an array has changed.
*
* Example:
*
* this.items = [ {name: 'Jim'}, {name: 'Todd'}, {name: 'Bill'} ];
* ...
* this.items.splice(1, 1, {name: 'Sam'});
* this.items.push({name: 'Bob'});
* this.notifySplices('items', [
* { index: 1, removed: [{name: 'Todd'}], addedCount: 1, obect: this.items, type: 'splice' },
* { index: 3, removed: [], addedCount: 1, object: this.items, type: 'splice'}
* ]);
*
* @param {string} path Path that should be notified.
* @param {Array} splices Array of splice records indicating ordered
* changes that occurred to the array. Each record should have the
* following fields:
* * index: index at which the change occurred
* * removed: array of items that were removed from this index
* * addedCount: number of new items added at this index
* * object: a reference to the array in question
* * type: the string literal 'splice'
*
* Note that splice records _must_ be normalized such that they are
* reported in index order (raw results from `Object.observe` are not
* ordered and must be normalized/merged before notifying).
*/
notifySplices: function(path, splices) {
var info = {};
var array = this._get(path, this, info);
// Notify change to key-based path
this._notifySplices(array, info.path, splices);
},
// Note: this implemetation only accepts key-based array paths
_notifySplices: function(array, path, splices) {
var change = {
keySplices: Polymer.Collection.applySplices(array, splices),
indexSplices: splices
};
if (!array.hasOwnProperty('splices')) {
Object.defineProperty(array, 'splices',
{configurable: true, writable: true});
}
array.splices = change;
this._notifyPath(path + '.splices', change);
this._notifyPath(path + '.length', array.length);
// Null here to allow potentially large splice records to be GC'ed
change.keySplices = null;
change.indexSplices = null;
},
_notifySplice: function(array, path, index, added, removed) {
var splices = [{
this._notifySplices(array, path, [{
index: index,
addedCount: added,
removed: removed,
object: array,
type: 'splice'
}];
var change = {
keySplices: Polymer.Collection.applySplices(array, splices),
indexSplices: splices
};
this.set(path + '.splices', change);
if (added != removed.length) {
this.notifyPath(path + '.length', array.length);
}
// Null here to allow potentially large splice records to be GC'ed
change.keySplices = null;
change.indexSplices = null;
}]);
},
/**
@ -366,12 +474,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* @return {number} New length of the array.
*/
push: function(path) {
var array = this.get(path);
var info = {};
var array = this._get(path, this, info);
var args = Array.prototype.slice.call(arguments, 1);
var len = array.length;
var ret = array.push.apply(array, args);
if (args.length) {
this._notifySplice(array, path, len, args.length, []);
this._notifySplice(array, info.path, len, args.length, []);
}
return ret;
},
@ -390,12 +499,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* @return {any} Item that was removed.
*/
pop: function(path) {
var array = this.get(path);
var info = {};
var array = this._get(path, this, info);
var hadLength = Boolean(array.length);
var args = Array.prototype.slice.call(arguments, 1);
var ret = array.pop.apply(array, args);
if (hadLength) {
this._notifySplice(array, path, array.length, 0, [ret]);
this._notifySplice(array, info.path, array.length, 0, [ret]);
}
return ret;
},
@ -418,7 +528,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* @return {Array} Array of removed items.
*/
splice: function(path, start, deleteCount) {
var array = this.get(path);
var info = {};
var array = this._get(path, this, info);
// Normalize fancy native splice handling of crazy start values
if (start < 0) {
start = array.length - Math.floor(-start);
@ -432,7 +543,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var ret = array.splice.apply(array, args);
var addedCount = Math.max(args.length - 2, 0);
if (addedCount || ret.length) {
this._notifySplice(array, path, start, addedCount, ret);
this._notifySplice(array, info.path, start, addedCount, ret);
}
return ret;
},
@ -451,12 +562,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* @return {any} Item that was removed.
*/
shift: function(path) {
var array = this.get(path);
var info = {};
var array = this._get(path, this, info);
var hadLength = Boolean(array.length);
var args = Array.prototype.slice.call(arguments, 1);
var ret = array.shift.apply(array, args);
if (hadLength) {
this._notifySplice(array, path, 0, 0, [ret]);
this._notifySplice(array, info.path, 0, 0, [ret]);
}
return ret;
},
@ -476,13 +588,40 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* @return {number} New length of the array.
*/
unshift: function(path) {
var array = this.get(path);
var info = {};
var array = this._get(path, this, info);
var args = Array.prototype.slice.call(arguments, 1);
var ret = array.unshift.apply(array, args);
if (args.length) {
this._notifySplice(array, path, 0, args.length, []);
this._notifySplice(array, info.path, 0, args.length, []);
}
return ret;
},
// TODO(kschaaf): This is the path analogue to Polymer.Bind.prepareModel,
// which provides API for path-based notification on elements with property
// effects; this should be re-factored along with the Bind lib, either all on
// Base or all in Bind (see issue https://github.com/Polymer/polymer/issues/2547).
prepareModelNotifyPath: function(model) {
this.mixin(model, {
fire: Polymer.Base.fire,
_getEvent: Polymer.Base._getEvent,
__eventCache: Polymer.Base.__eventCache,
notifyPath: Polymer.Base.notifyPath,
_get: Polymer.Base._get,
_EVENT_CHANGED: Polymer.Base._EVENT_CHANGED,
_notifyPath: Polymer.Base._notifyPath,
_notifyPathUp: Polymer.Base._notifyPathUp,
_pathEffector: Polymer.Base._pathEffector,
_annotationPathEffect: Polymer.Base._annotationPathEffect,
_complexObserverPathEffect: Polymer.Base._complexObserverPathEffect,
_annotatedComputationPathEffect: Polymer.Base._annotatedComputationPathEffect,
_computePathEffect: Polymer.Base._computePathEffect,
_modelForPath: Polymer.Base._modelForPath,
_pathMatchesEffect: Polymer.Base._pathMatchesEffect,
_notifyBoundPaths: Polymer.Base._notifyBoundPaths,
_getPathParts: Polymer.Base._getPathParts
});
}
});

View File

@ -40,16 +40,20 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._encapsulateStyle = !nativeShadow &&
Boolean(this._template);
}
this._styles = this._collectStyles();
var cssText = styleTransformer.elementStyles(this);
if (cssText && this._template) {
var style = styleUtil.applyCss(cssText, this.is,
nativeShadow ? this._template.content : null);
// keep track of style when in document scope (polyfill) so we can
// attach property styles after it.
if (!nativeShadow) {
this._scopeStyle = style;
if (this._template) {
this._styles = this._collectStyles();
var cssText = styleTransformer.elementStyles(this);
if (cssText) {
var style = styleUtil.applyCss(cssText, this.is,
nativeShadow ? this._template.content : null);
// keep track of style when in document scope (polyfill) so we can
// attach property styles after it.
if (!nativeShadow) {
this._scopeStyle = style;
}
}
} else {
this._styles = [];
}
},
@ -64,11 +68,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
}
cssText += styleUtil.cssFromModule(this.is);
// check if we have a disconnected template and add styles from that
// if so; if our template has no parent or is not in our dom-module...
var p = this._template && this._template.parentNode;
if (this._template && (!p || p.id.toLowerCase() !== this.is)) {
cssText += styleUtil.cssFromElement(this._template);
}
if (cssText) {
var style = document.createElement('style');
style.textContent = cssText;
// extends!!
if (styleExtends.hasExtends(style.textContent)) {
// TODO(sorvell): variable is not used, should it update `style.textContent`?
cssText = styleExtends.transform(style);
}
styles.push(style);
@ -99,7 +110,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
/**
* Apply style scoping to the specified `container` and all its
* descendants. If `shoudlObserve` is true, changes to the container are
* descendants. If `shouldObserve` is true, changes to the container are
* monitored via mutation observer and scoping is applied.
*
* This method is useful for ensuring proper local DOM CSS scoping
@ -119,23 +130,25 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var self = this;
var scopify = function(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
node.className = self._scopeElementClass(node, node.className);
var className = node.getAttribute('class');
node.setAttribute('class', self._scopeElementClass(node, className));
var n$ = node.querySelectorAll('*');
Array.prototype.forEach.call(n$, function(n) {
n.className = self._scopeElementClass(n, n.className);
});
for (var i=0, n; (i<n$.length) && (n=n$[i]); i++) {
className = n.getAttribute('class');
n.setAttribute('class', self._scopeElementClass(n, className));
}
}
};
scopify(container);
if (shouldObserve) {
var mo = new MutationObserver(function(mxns) {
mxns.forEach(function(m) {
for (var i=0, m; (i<mxns.length) && (m=mxns[i]); i++) {
if (m.addedNodes) {
for (var i=0; i < m.addedNodes.length; i++) {
scopify(m.addedNodes[i]);
for (var j=0; j < m.addedNodes.length; j++) {
scopify(m.addedNodes[j]);
}
}
});
}
});
mo.observe(container, {childList: true, subtree: true});
return mo;

View File

@ -104,10 +104,67 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
},
/**
* Returns a list of nodes that are the effective childNodes. The effective
* childNodes list is the same as the element's childNodes except that
* any `<content>` elements are replaced with the list of nodes distributed
* to the `<content>`, the result of its `getDistributedNodes` method.
*
* @method getEffectiveChildNodes
* @return {Array<Node>} List of effctive child nodes.
*/
getEffectiveChildNodes: function() {
return Polymer.dom(this).getEffectiveChildNodes();
},
/**
* Returns a list of elements that are the effective children. The effective
* children list is the same as the element's children except that
* any `<content>` elements are replaced with the list of elements
* distributed to the `<content>`.
*
* @method getEffectiveChildren
* @return {Array<Node>} List of effctive children.
*/
getEffectiveChildren: function() {
var list = Polymer.dom(this).getEffectiveChildNodes();
return list.filter(function(n) {
return (n.nodeType === Node.ELEMENT_NODE);
});
},
/**
* Returns a string of text content that is the concatenation of the
* text content's of the element's effective childNodes (the elements
* returned by <a href="#getEffectiveChildNodes>getEffectiveChildNodes</a>.
*
* @method getEffectiveTextContent
* @return {Array<Node>} List of effctive children.
*/
getEffectiveTextContent: function() {
var cn = this.getEffectiveChildNodes();
var tc = [];
for (var i=0, c; c = cn[i]; i++) {
if (c.nodeType !== Node.COMMENT_NODE) {
tc.push(Polymer.dom(c).textContent);
}
}
return tc.join('');
},
queryEffectiveChildren: function(slctr) {
var e$ = Polymer.dom(this).queryDistributedElements(slctr);
return e$ && e$[0];
},
queryAllEffectiveChildren: function(slctr) {
return Polymer.dom(this).queryDistributedElements(slctr);
},
/**
* Returns a list of nodes distributed to this element's `<content>`.
*
* If this element contans more than one `<content>` in its local DOM,
* If this element contains more than one `<content>` in its local DOM,
* an optional selector may be passed to choose the desired content.
*
* @method getContentChildNodes
@ -124,7 +181,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* Returns a list of element children distributed to this element's
* `<content>`.
*
* If this element contans more than one `<content>` in its
* If this element contains more than one `<content>` in its
* local DOM, an optional selector may be passed to choose the desired
* content. This method differs from `getContentChildNodes` in that only
* elements are returned.
@ -141,34 +198,59 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
},
/**
* Dispatches a custom event with an optional detail object.
* Dispatches a custom event with an optional detail value.
*
* @method fire
* @param {String} type Name of event type.
* @param {Object=} detail Detail object containing event-specific
* @param {*=} detail Detail value containing event-specific
* payload.
* @param {Object=} options Object specifying options. These may include:
* `bubbles` (boolean, defaults to `true`),
* `cancelable` (boolean, defaults to false), and
* `cancelable` (boolean, defaults to false), and
* `node` on which to fire the event (HTMLElement, defaults to `this`).
* @return {CustomEvent} The new event that was fired.
*/
fire: function(type, detail, options) {
options = options || Polymer.nob;
var node = options.node || this;
var detail = (detail === null || detail === undefined) ? Polymer.nob : detail;
var detail = (detail === null || detail === undefined) ? {} : detail;
var bubbles = options.bubbles === undefined ? true : options.bubbles;
var cancelable = Boolean(options.cancelable);
var event = new CustomEvent(type, {
bubbles: Boolean(bubbles),
cancelable: cancelable,
detail: detail
});
var useCache = options._useCache;
var event = this._getEvent(type, bubbles, cancelable, useCache);
event.detail = detail;
if (useCache) {
this.__eventCache[type] = null;
}
node.dispatchEvent(event);
if (useCache) {
this.__eventCache[type] = event;
}
return event;
},
__eventCache: {},
// NOTE: We optionally cache event objects for efficiency during high
// freq. opts. This option cannot be used for events which may have
// `stopPropagation` called on them. On Chrome and Safari (but not FF)
// if `stopPropagation` is called, the event cannot be reused. It does not
// dispatch again.
_getEvent: function(type, bubbles, cancelable, useCache) {
var event = useCache && this.__eventCache[type];
if (!event || ((event.bubbles != bubbles) ||
(event.cancelable != cancelable))) {
event = new Event(type, {
bubbles: Boolean(bubbles),
cancelable: cancelable
});
}
return event;
},
/**
* Runs a callback function asyncronously.
*
@ -183,7 +265,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* @return {number} Handle that may be used to cancel the async job.
*/
async: function(callback, waitTime) {
return Polymer.Async.run(callback.bind(this), waitTime);
var self = this;
return Polymer.Async.run(function() {
callback.call(self);
}, waitTime);
},
/**
@ -199,11 +284,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
/**
* Removes an item from an array, if it exists.
*
* If the array is specified by path, a change notification is
*
* If the array is specified by path, a change notification is
* generated, so that observers, data bindings and computed
* properties watching that path can update.
*
* properties watching that path can update.
*
* If the array is passed directly, **no change
* notification is generated**.
*
@ -221,7 +306,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return path.splice(index, 1);
}
} else {
var arr = this.get(path);
var arr = this._get(path);
index = arr.indexOf(item);
if (index >= 0) {
return this.splice(path, index, 1);
@ -273,17 +358,30 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
* loaded.
* @param {Function} onerror Callback to notify when an import
* unsuccessfully loaded.
* @param {boolean} optAsync True if the import should be loaded `async`.
* Defaults to `false`.
* @return {HTMLLinkElement} The link element for the URL to be loaded.
*/
importHref: function(href, onload, onerror) {
importHref: function(href, onload, onerror, optAsync) {
var l = document.createElement('link');
l.rel = 'import';
l.href = href;
optAsync = Boolean(optAsync);
if (optAsync) {
l.setAttribute('async', '');
}
var self = this;
if (onload) {
l.onload = onload.bind(this);
l.onload = function(e) {
return onload.call(self, e);
}
}
if (onerror) {
l.onerror = onerror.bind(this);
l.onerror = function(e) {
return onerror.call(self, e);
}
}
document.head.appendChild(l);
return l;
@ -306,6 +404,29 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
}
return elt;
},
/**
* Checks whether an element is in this element's light DOM tree.
*
* @method isLightDescendant
* @param {?Node} node The element to be checked.
* @return {Boolean} true if node is in this element's light DOM tree.
*/
isLightDescendant: function(node) {
return this !== node && this.contains(node) &&
Polymer.dom(this).getOwnerRoot() === Polymer.dom(node).getOwnerRoot();
},
/**
* Checks whether an element is in this element's local DOM tree.
*
* @method isLocalDescendant
* @param {HTMLElement=} node The element to be checked.
* @return {Boolean} true if node is in this element's local DOM tree.
*/
isLocalDescendant: function(node) {
return this.root === Polymer.dom(node).getOwnerRoot();
}
});

View File

@ -1,276 +1,292 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../lib/style-properties.html">
<link rel="import" href="../lib/settings.html">
<link rel="import" href="../lib/style-defaults.html">
<link rel="import" href="../lib/style-cache.html">
<script>
(function() {
'use strict';
var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute;
var propertyUtils = Polymer.StyleProperties;
var styleTransformer = Polymer.StyleTransformer;
var styleUtil = Polymer.StyleUtil;
var styleDefaults = Polymer.StyleDefaults;
var nativeShadow = Polymer.Settings.useNativeShadow;
Polymer.Base._addFeature({
_prepStyleProperties: function() {
// note: an element should produce an x-scope stylesheet
// if it has any _stylePropertyNames
this._ownStylePropertyNames = this._styles ?
propertyUtils.decorateStyles(this._styles) :
[];
},
/**
* An element's style properties can be directly modified by
* setting key-value pairs in `customStyle` on the element
* (analogous to setting `style`) and then calling `updateStyles()`.
*
*/
customStyle: {},
// here we have an instance time spot to put custom property data
_setupStyleProperties: function() {
this.customStyle = {};
},
_needsStyleProperties: function() {
return Boolean(this._ownStylePropertyNames &&
this._ownStylePropertyNames.length);
},
_beforeAttached: function() {
// note: do this once automatically,
// then requires calling `updateStyles`
if (!this._scopeSelector && this._needsStyleProperties()) {
this._updateStyleProperties();
}
},
_findStyleHost: function() {
var e = this, root;
while (root = Polymer.dom(e).getOwnerRoot()) {
if (Polymer.isInstance(root.host)) {
return root.host;
}
e = root.host;
}
return styleDefaults;
},
_updateStyleProperties: function() {
var info, scope = this._findStyleHost();
// install cache in host if it doesn't exist.
if (!scope._styleCache) {
scope._styleCache = new Polymer.StyleCache();
}
var scopeData = propertyUtils
.propertyDataFromStyles(scope._styles, this);
// look in scope cache
scopeData.key.customStyle = this.customStyle;
info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles);
// compute style properties (fast path, if cache hit)
var scopeCached = Boolean(info);
if (scopeCached) {
// when scope cached, we can safely take style propertis out of the
// scope cache because they are only for this scope.
this._styleProperties = info._styleProperties;
} else {
this._computeStyleProperties(scopeData.properties);
}
this._computeOwnStyleProperties();
// cache miss, do work!
if (!scopeCached) {
// and look in 2ndary global cache
info = styleCache.retrieve(this.is,
this._ownStyleProperties, this._styles);
}
var globalCached = Boolean(info) && !scopeCached;
// now we have properties and a cached style if one
// is available.
var style = this._applyStyleProperties(info);
// no cache so store in cache
//console.warn(this.is, scopeCached, globalCached, info && info._scopeSelector);
if (!scopeCached) {
// create an info object for caching
// TODO(sorvell): clone style node when using native Shadow DOM
// so a style used in a root does not itself get stored in the cache
// This can lead to incorrect sharing, but should be fixed
// in `Polymer.StyleProperties.applyElementStyle`
style = style && nativeShadow ? style.cloneNode(true) : style;
info = {
style: style,
_scopeSelector: this._scopeSelector,
_styleProperties: this._styleProperties
}
scopeData.key.customStyle = {};
this.mixin(scopeData.key.customStyle, this.customStyle);
scope._styleCache.store(this.is, info, scopeData.key, this._styles);
if (!globalCached) {
// save in global cache
styleCache.store(this.is, Object.create(info), this._ownStyleProperties,
this._styles);
}
}
},
_computeStyleProperties: function(scopeProps) {
// get scope and make sure it has properties
var scope = this._findStyleHost();
// force scope to compute properties if they don't exist or if forcing
// and it doesn't need properties
if (!scope._styleProperties) {
scope._computeStyleProperties();
}
// start with scope style properties
var props = Object.create(scope._styleProperties);
// mixin own host properties (lower specifity than scope props)
this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles));
// mixin properties matching this element in scope
scopeProps = scopeProps ||
propertyUtils.propertyDataFromStyles(scope._styles, this).properties;
this.mixin(props, scopeProps);
// finally mixin properties inherent to this element
this.mixin(props,
propertyUtils.scopePropertiesFromStyles(this._styles));
propertyUtils.mixinCustomStyle(props, this.customStyle);
// reify properties (note: only does own properties)
propertyUtils.reify(props);
this._styleProperties = props;
},
_computeOwnStyleProperties: function() {
var props = {};
for (var i=0, n; i < this._ownStylePropertyNames.length; i++) {
n = this._ownStylePropertyNames[i];
props[n] = this._styleProperties[n];
}
this._ownStyleProperties = props;
},
_scopeCount: 0,
_applyStyleProperties: function(info) {
// update scope selector (needed for style transformation)
var oldScopeSelector = this._scopeSelector;
// note, the scope selector is incremented per class counter
this._scopeSelector = info ? info._scopeSelector :
this.is + '-' + this.__proto__._scopeCount++;
var style = propertyUtils.applyElementStyle(this,
this._styleProperties, this._scopeSelector, info && info.style);
// apply scope selector
if (!nativeShadow) {
propertyUtils.applyElementScopeSelector(this, this._scopeSelector,
oldScopeSelector, this._scopeCssViaAttr);
}
return style;
},
serializeValueToAttribute: function(value, attribute, node) {
// override to ensure whenever classes are set, we need to shim them.
node = node || this;
if (attribute === 'class' && !nativeShadow) {
// host needed to scope styling.
// Under Shady DOM, domHost is safe to use here because we know it
// is a Polymer element
var host = node === this ? (this.domHost || this.dataHost) : this;
if (host) {
value = host._scopeElementClass(node, value);
}
}
// note: using Polymer.dom here ensures that any attribute sets
// will provoke distribution if necessary
node = Polymer.dom(node);
serializeValueToAttribute.call(this, value, attribute, node);
},
_scopeElementClass: function(element, selector) {
if (!nativeShadow && !this._scopeCssViaAttr) {
selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is +
(element._scopeSelector ? ' ' + XSCOPE_NAME + ' ' +
element._scopeSelector : '');
}
return selector;
},
/**
* Re-evaluates and applies custom CSS properties based on dynamic
* changes to this element's scope, such as adding or removing classes
* in this element's local DOM.
*
* For performance reasons, Polymer's custom CSS property shim relies
* on this explicit signal from the user to indicate when changes have
* been made that affect the values of custom properties.
*
* @method updateStyles
* @param {Object=} properties Properties object which is mixed into
* the element's `customStyle` property. This argument provides a shortcut
* for setting `customStyle` and then calling `updateStyles`.
*/
updateStyles: function(properties) {
if (this.isAttached) {
if (properties) {
this.mixin(this.customStyle, properties);
}
// skip applying properties to self if not used
if (this._needsStyleProperties()) {
this._updateStyleProperties();
// when an element doesn't use style properties, its own properties
// should be invalidated so elements down the tree update ok.
} else {
this._styleProperties = null;
}
if (this._styleCache) {
this._styleCache.clear();
}
// always apply properties to root
this._updateRootStyles();
}
},
_updateRootStyles: function(root) {
root = root || this.root;
var c$ = Polymer.dom(root)._query(function(e) {
return e.shadyRoot || e.shadowRoot;
});
for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) {
if (c.updateStyles) {
c.updateStyles();
}
}
}
});
/**
* Force all custom elements using cross scope custom properties,
* to update styling.
*/
Polymer.updateStyles = function(properties) {
// update default/custom styles
styleDefaults.updateStyles(properties);
// search the document for elements to update
Polymer.Base._updateRootStyles(document);
};
var styleCache = new Polymer.StyleCache();
Polymer.customStyleCache = styleCache;
var SCOPE_NAME = styleTransformer.SCOPE_NAME;
var XSCOPE_NAME = propertyUtils.XSCOPE_NAME;
})();
</script>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../lib/style-properties.html">
<link rel="import" href="../lib/settings.html">
<link rel="import" href="../lib/style-defaults.html">
<link rel="import" href="../lib/style-cache.html">
<script>
(function() {
'use strict';
var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute;
var propertyUtils = Polymer.StyleProperties;
var styleTransformer = Polymer.StyleTransformer;
var styleUtil = Polymer.StyleUtil;
var styleDefaults = Polymer.StyleDefaults;
var nativeShadow = Polymer.Settings.useNativeShadow;
Polymer.Base._addFeature({
_prepStyleProperties: function() {
// note: an element should produce an x-scope stylesheet
// if it has any _stylePropertyNames
this._ownStylePropertyNames = this._styles ?
propertyUtils.decorateStyles(this._styles) :
null;
},
/**
* An element's style properties can be directly modified by
* setting key-value pairs in `customStyle` on the element
* (analogous to setting `style`) and then calling `updateStyles()`.
*
*/
customStyle: null,
/**
* Returns the computed style value for the given property.
* @param {String} property
* @return {String} the computed value
*/
getComputedStyleValue: function(property) {
return this._styleProperties && this._styleProperties[property] ||
getComputedStyle(this).getPropertyValue(property);
},
// here we have an instance time spot to put custom property data
_setupStyleProperties: function() {
this.customStyle = {};
this._styleCache = null;
this._styleProperties = null;
this._scopeSelector = null;
this._ownStyleProperties = null;
this._customStyle = null;
},
_needsStyleProperties: function() {
return Boolean(this._ownStylePropertyNames &&
this._ownStylePropertyNames.length);
},
_beforeAttached: function() {
// note: do this once automatically,
// then requires calling `updateStyles`
if (!this._scopeSelector && this._needsStyleProperties()) {
this._updateStyleProperties();
}
},
_findStyleHost: function() {
var e = this, root;
while (root = Polymer.dom(e).getOwnerRoot()) {
if (Polymer.isInstance(root.host)) {
return root.host;
}
e = root.host;
}
return styleDefaults;
},
_updateStyleProperties: function() {
var info, scope = this._findStyleHost();
// install cache in host if it doesn't exist.
if (!scope._styleCache) {
scope._styleCache = new Polymer.StyleCache();
}
var scopeData = propertyUtils
.propertyDataFromStyles(scope._styles, this);
// look in scope cache
scopeData.key.customStyle = this.customStyle;
info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles);
// compute style properties (fast path, if cache hit)
var scopeCached = Boolean(info);
if (scopeCached) {
// when scope cached, we can safely take style propertis out of the
// scope cache because they are only for this scope.
this._styleProperties = info._styleProperties;
} else {
this._computeStyleProperties(scopeData.properties);
}
this._computeOwnStyleProperties();
// cache miss, do work!
if (!scopeCached) {
// and look in 2ndary global cache
info = styleCache.retrieve(this.is,
this._ownStyleProperties, this._styles);
}
var globalCached = Boolean(info) && !scopeCached;
// now we have properties and a cached style if one
// is available.
var style = this._applyStyleProperties(info);
// no cache so store in cache
//console.warn(this.is, scopeCached, globalCached, info && info._scopeSelector);
if (!scopeCached) {
// create an info object for caching
// TODO(sorvell): clone style node when using native Shadow DOM
// so a style used in a root does not itself get stored in the cache
// This can lead to incorrect sharing, but should be fixed
// in `Polymer.StyleProperties.applyElementStyle`
style = style && nativeShadow ? style.cloneNode(true) : style;
info = {
style: style,
_scopeSelector: this._scopeSelector,
_styleProperties: this._styleProperties
};
scopeData.key.customStyle = {};
this.mixin(scopeData.key.customStyle, this.customStyle);
scope._styleCache.store(this.is, info, scopeData.key, this._styles);
if (!globalCached) {
// save in global cache
styleCache.store(this.is, Object.create(info), this._ownStyleProperties,
this._styles);
}
}
},
_computeStyleProperties: function(scopeProps) {
// get scope and make sure it has properties
var scope = this._findStyleHost();
// force scope to compute properties if they don't exist or if forcing
// and it doesn't need properties
if (!scope._styleProperties) {
scope._computeStyleProperties();
}
// start with scope style properties
var props = Object.create(scope._styleProperties);
// mixin own host properties (lower specifity than scope props)
this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles));
// mixin properties matching this element in scope
scopeProps = scopeProps ||
propertyUtils.propertyDataFromStyles(scope._styles, this).properties;
this.mixin(props, scopeProps);
// finally mixin properties inherent to this element
this.mixin(props,
propertyUtils.scopePropertiesFromStyles(this._styles));
propertyUtils.mixinCustomStyle(props, this.customStyle);
// reify properties (note: only does own properties)
propertyUtils.reify(props);
this._styleProperties = props;
},
_computeOwnStyleProperties: function() {
var props = {};
for (var i=0, n; i < this._ownStylePropertyNames.length; i++) {
n = this._ownStylePropertyNames[i];
props[n] = this._styleProperties[n];
}
this._ownStyleProperties = props;
},
_scopeCount: 0,
_applyStyleProperties: function(info) {
// update scope selector (needed for style transformation)
var oldScopeSelector = this._scopeSelector;
// note, the scope selector is incremented per class counter
this._scopeSelector = info ? info._scopeSelector :
this.is + '-' + this.__proto__._scopeCount++;
var style = propertyUtils.applyElementStyle(this,
this._styleProperties, this._scopeSelector, info && info.style);
// apply scope selector
if (!nativeShadow) {
propertyUtils.applyElementScopeSelector(this, this._scopeSelector,
oldScopeSelector, this._scopeCssViaAttr);
}
return style;
},
serializeValueToAttribute: function(value, attribute, node) {
// override to ensure whenever classes are set, we need to shim them.
node = node || this;
if (attribute === 'class' && !nativeShadow) {
// host needed to scope styling.
// Under Shady DOM, domHost is safe to use here because we know it
// is a Polymer element
var host = node === this ? (this.domHost || this.dataHost) : this;
if (host) {
value = host._scopeElementClass(node, value);
}
}
// note: using Polymer.dom here ensures that any attribute sets
// will provoke distribution if necessary; do this iff necessary
node = (this.shadyRoot && this.shadyRoot._hasDistributed) ?
Polymer.dom(node) : node;
serializeValueToAttribute.call(this, value, attribute, node);
},
_scopeElementClass: function(element, selector) {
if (!nativeShadow && !this._scopeCssViaAttr) {
selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is +
(element._scopeSelector ? ' ' + XSCOPE_NAME + ' ' +
element._scopeSelector : '');
}
return selector;
},
/**
* Re-evaluates and applies custom CSS properties based on dynamic
* changes to this element's scope, such as adding or removing classes
* in this element's local DOM.
*
* For performance reasons, Polymer's custom CSS property shim relies
* on this explicit signal from the user to indicate when changes have
* been made that affect the values of custom properties.
*
* @method updateStyles
* @param {Object=} properties Properties object which is mixed into
* the element's `customStyle` property. This argument provides a shortcut
* for setting `customStyle` and then calling `updateStyles`.
*/
updateStyles: function(properties) {
if (this.isAttached) {
if (properties) {
this.mixin(this.customStyle, properties);
}
// skip applying properties to self if not used
if (this._needsStyleProperties()) {
this._updateStyleProperties();
// when an element doesn't use style properties, its own properties
// should be invalidated so elements down the tree update ok.
} else {
this._styleProperties = null;
}
if (this._styleCache) {
this._styleCache.clear();
}
// always apply properties to root
this._updateRootStyles();
}
},
_updateRootStyles: function(root) {
root = root || this.root;
var c$ = Polymer.dom(root)._query(function(e) {
return e.shadyRoot || e.shadowRoot;
});
for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) {
if (c.updateStyles) {
c.updateStyles();
}
}
}
});
/**
* Force all custom elements using cross scope custom properties,
* to update styling.
*/
Polymer.updateStyles = function(properties) {
// update default/custom styles
styleDefaults.updateStyles(properties);
// search the document for elements to update
Polymer.Base._updateRootStyles(document);
};
var styleCache = new Polymer.StyleCache();
Polymer.customStyleCache = styleCache;
var SCOPE_NAME = styleTransformer.SCOPE_NAME;
var XSCOPE_NAME = propertyUtils.XSCOPE_NAME;
})();
</script>

View File

@ -15,7 +15,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</head>
<body>
<script>
WCT.loadSuites([
var suites = [
'unit/base.html',
'unit/micro.html',
'unit/unresolved.html',
@ -25,6 +25,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
'unit/async.html',
'unit/behaviors.html',
'unit/template.html',
'unit/template-whitespace.html',
'unit/ready.html',
'unit/ready-shadow.html',
'unit/attached-style.html',
@ -33,12 +34,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
'unit/polymer-dom.html',
'unit/polymer-dom-shadow.html',
'unit/polymer-dom-content.html',
'unit/polymer-dom-content.html?dom=shadow',
'unit/polymer-dom-observeNodes.html',
'unit/polymer-dom-observeNodes.html?dom=shadow',
'unit/debounce.html',
'unit/bind.html',
'unit/bind.html?dom=shadow',
'unit/notify-path.html',
'unit/array-selector.html',
'unit/events.html',
'unit/gestures.html',
'unit/gestures.html?dom=shadow',
'unit/utils.html',
'unit/utils-content.html',
'unit/utils.html?dom=shadow',
@ -50,18 +56,26 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
'unit/styling-remote.html',
'unit/styling-cross-scope-var.html',
'unit/styling-cross-scope-apply.html',
// TODO(sorvell): disable until this Chrome bug is addressed:
// https://code.google.com/p/chromium/issues/detail?id=525280
// 'unit/styling-cross-scope-var.html?dom=shadow',
// 'unit/styling-cross-scope-apply.html?dom=shadow',
'unit/styling-cross-scope-var.html?dom=shadow',
'unit/styling-cross-scope-apply.html?dom=shadow',
'unit/styling-cross-scope-unknown-host.html',
'unit/custom-style.html',
'unit/custom-style.html?dom=shadow',
'unit/custom-style-late.html',
'unit/dynamic-import.html',
'unit/templatizer.html',
'unit/dom-repeat.html',
'unit/dom-if.html',
'unit/dom-bind.html',
'unit/script-after-import-in-head.html'
]);
'unit/script-after-import-in-head.html',
'unit/globals.html'
];
if (document.body.createShadowRoot) {
var idx = suites.indexOf('unit/polymer-dom-shadow.html');
suites.splice(idx, 0, 'unit/polymer-dom-native-shadow.html');
}
WCT.loadSuites(suites);
</script>
</body>
</html>

View File

@ -0,0 +1,62 @@
<!doctype html>
<html>
<head>
<title>distribute dom-repeat</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../polymer.html">
</head>
<body>
<dom-module id="x-container">
<template>
<content select="*"></content>
</template>
<script>
Polymer({
is: 'x-container'
})
</script>
</dom-module>
<dom-module id="x-test">
<template strip-whitespace>
<x-container id="container">
<template class="foo" id="nug" is="dom-repeat" items="{{model}}">
<div class="foo">{{item.id}}</div>
</template>
</x-container>
</template>
<script>
Polymer({
is: 'x-test',
ready: function() {
this.model = [
{id: '1', text: 'How much do you like food?'},
{id: '2', text: 'Where do you buy your groceries?'},
{id: '3', text: 'What is your favourite colour?'}
];
Polymer.dom.flush();
var self = this;
console.log(Polymer.dom(self.$.container).firstElementChild);
setTimeout(function() {
console.log('***');
console.log(Polymer.dom(self.$.container).firstElementChild);
}, 100);
}
})
</script>
</dom-module>
<x-test></x-test>
</body>
</html>

View File

@ -0,0 +1,42 @@
<!doctype html>
<html>
<head>
<title>annotations</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<script>//Polymer = {dom: 'shadow'}</script>
<link rel="import" href="../../polymer.html">
</head>
<body>
<script>
var ul = document.createElement("ul");
for (i=0; i < 1000; i++) {
var li = document.createElement("li");
ul.appendChild(li);
}
var start = window.performance.now();
var e = ul.firstElementChild;
for (i=0; i <999; i++)
e = e.nextSibling;
console.log(e);
console.log("Native performance: "+(window.performance.now()-start)+"ms");
var start = window.performance.now();
var e = ul.firstElementChild;
for (i=0; i <999; i++)
e = Polymer.dom(e).nextSibling;
console.log(e);
console.log("Polymer performance: "+(window.performance.now()-start)+"ms");
</script>
</body>
</html>

View File

@ -0,0 +1,84 @@
<!doctype html>
<html>
<head>
<title>observeContent</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../polymer.html">
</head>
<body>
<dom-module id='test-content'>
<template>
[<content id="ip"></content>]
</template>
<script>
(function() {
Polymer({
is:'test-content',
created: function() {
},
// _setupRoot: function() {
// Polymer.Base._setupRoot.call(this);
// Polymer.dom(this.$.ip).observeNodes(function(info) {
// console.log('internal', info.changes[0]);
// });
// Polymer.dom(this.$.ip).observer.includeChanges = true;
// }
});
})();
</script>
</dom-module>
<dom-module id='test-static'>
<template>
My children do not render.
</template>
<script>
Polymer({
is:'test-static',
created: function() {
}
});
</script>
</dom-module>
<test-content id="content">
<div>content A</div>
<div>content B</div>
</test-content>
<br><br>
<test-static id="stat"><div>static A</div><div>static B</div></test-static>
<script>
function obs(info) {
console.log('custom observer', info);
}
Polymer.dom(content.$.ip).observeNodes(function(info) {
console.log(info);
}, true);
Polymer.dom(content).flush();
Polymer.dom(stat).flush();
console.group('test dynamic');
var d = document.createElement('div');
d.id = 'foo';
d.innerHTML = 'Dynamic!';
Polymer.dom(content).flush();
Polymer.dom(content).appendChild(d);
Polymer.dom(content).flush();
// Polymer.dom(content).removeChild(d);
// Polymer.dom(content).flush();
console.groupEnd('test dynamic');
</script>
</body>
</html>

View File

@ -0,0 +1,108 @@
<!doctype html>
<html>
<head>
<title>observeNodes-repeat</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../polymer.html">
<body>
<script>
Polymer({
is: 'x-observes-nodes',
properties: {
children: {
type: Array,
readOnly: true,
value: function() {
return [];
}
}
},
_updateChildren: function(info) {
console.log('Updating children!', info);
var children = Polymer.dom(this).queryDistributedElements('div');
this._setChildren(children);
},
attached: function() {
Polymer.dom(this).observeNodes(function(info) {
this._updateChildren(info);
}.bind(this));
}
});
function randomObject() {
return {
name: 'foo'
};
}
function randomArray(size) {
var array = [];
for (var i = 0; i < size; ++i) {
array.push(randomObject());
}
return array;
}
function numberOfDivs() {
return Polymer.dom(document).querySelectorAll('div').length;
}
function assertDivs(number) {
console.log('Do we have (' + number + ') divs?', number === numberOfDivs(), 'Actual: ' + numberOfDivs());
}
function assertChildren() {
var observesNodes = Polymer.dom(document).querySelector('x-observes-nodes');
console.log('Same number of children as divs?', observesNodes.children.length === numberOfDivs(), 'Actual: ' + observesNodes.children.length);
}
window.addEventListener('load', function() {
var dom = document.querySelector('#x-dom');
console.log('Starting tests..');
dom.set('items', randomArray(3));
Polymer.Base.async(function() {
assertDivs(3);
assertChildren();
dom.set('items', randomArray(6));
Polymer.Base.async(function() {
assertDivs(6);
assertChildren();
dom.set('items', randomArray(4));
Polymer.Base.async(function() {
assertDivs(4);
assertChildren();
console.log('Tests done!');
}, 10);
}, 10);
}, 10);
});
</script>
<template id="x-dom" is="dom-bind">
<x-observes-nodes><template is="dom-repeat" items="[[items]]"><div>{{item.name}}</div></template></x-observes-nodes>
</template>
</body>
</html>

View File

@ -0,0 +1,82 @@
<!doctype html>
<html>
<head>
<title>observeNodes</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../polymer.html">
</head>
<body>
<dom-module id='test-content'>
<template>
[<content></content>]
</template>
<script>
Polymer({
is:'test-content',
created: function() {
Polymer.dom(this).observeNodes(function(info) {
console.log('test-content', info);
});
}
});
</script>
</dom-module>
<dom-module id='test-static'>
<template>
My children do not render.
</template>
<script>
Polymer({
is:'test-static',
created: function() {
Polymer.dom(this).observeNodes(function(info) {
console.log('test-static', info);
});
}
});
</script>
</dom-module>
<test-content id="content">
<div>content A</div>
<div>content B</div>
</test-content>
<br><br>
<test-static id="stat"><div>static A</div><div>static B</div></test-static>
<script>
Polymer.dom(content).flush();
Polymer.dom(stat).flush();
console.group('test dynamic');
function obs(info) {
console.log('custom observer', info);
}
var hc = Polymer.dom(content).observeNodes(obs);
var hs = Polymer.dom(stat).observeNodes(obs);
var d = document.createElement('div');
d.id = 'foo';
d.innerHTML = 'Dynamic!';
Polymer.dom(content).appendChild(d);
Polymer.dom(stat).appendChild(d);
Polymer.dom(content).flush();
Polymer.dom(stat).flush();
Polymer.dom(content).unobserveNodes(hc);
Polymer.dom(stat).unobserveNodes(hs);
Polymer.dom(stat).removeChild(d);
Polymer.dom(stat).flush();
console.groupEnd('test dynamic');
</script>
</body>
</html>

View File

@ -0,0 +1,158 @@
<!doctype html>
<html>
<head>
<title>observeReNodes</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../polymer.html">
</head>
<body>
<dom-module id='test-inner'>
<template>
~<content id="c" select=".a"></content>~
</template>
<script>
Polymer({
is:'test-inner',
ready: function() {
this.observe();
},
observe: function() {
console.warn('observeNodes', this.localName);
this._childObserver = Polymer.dom(this).observeNodes(function(info) {
info.addedNodes.forEach(function(n) {
if (n.nodeType === Node.ELEMENT_NODE) {
console.log('added:', n.localName, n.textContent);
}
});
info.removedNodes.forEach(function(n) {
if (n.nodeType === Node.ELEMENT_NODE) {
console.log('removed:', n.localName, n.textContent);
}
});
}, {changes: true, attributes: true});
this._contentObserver = Polymer.dom(this.$.c).observeNodes(function(info) {
info.addedNodes.forEach(function(n) {
if (n.nodeType === Node.ELEMENT_NODE) {
console.log('%c content added:', 'color: blue;', n.localName, n.textContent);
}
});
info.removedNodes.forEach(function(n) {
if (n.nodeType === Node.ELEMENT_NODE) {
console.log('%c content removed:', 'color: blue;', n.localName, n.textContent);
}
});
}, {changes: true, attributes: true});
},
unobserve: function() {
console.warn('unobserveNodes', this.localName);
Polymer.dom(this).unobserveNodes(this._childObserver);
Polymer.dom(this.$.c).unobserveNodes(this._contentObserver);
}
});
</script>
</dom-module>
<dom-module id='test-content'>
<template>
<test-inner id="inner">[<content id="ip" select=".b"></content>]</test-inner>
</template>
<script>
(function() {
Polymer({
is:'test-content'
});
})();
</script>
</dom-module>
<test-content id="content">
<div class="a b">content A</div>
<div class="a b">content B</div>
</test-content>
<br><br>
<script>
function makeNode(text) {
var d = document.createElement('div');
d.textContent = text;;
return d;
}
Polymer.dom.flush();
function test(done) {
console.group('test dynamic');
var d = makeNode('dynamic!');
Polymer.dom.flush();
Polymer.dom(content).appendChild(d);
Polymer.dom.flush();
Polymer.dom(content).removeChild(d);
Polymer.dom.flush();
d = makeNode('1');
Polymer.dom(d).classList.add('b');
Polymer.dom(content).appendChild(d);
Polymer.dom.flush();
Polymer.dom(d).classList.add('a');
//Polymer.dom(Polymer.dom(d).parentNode).notifyObservers();
Polymer.dom(content.$.inner).appendChild(makeNode('2'));
Polymer.dom.flush();
d = makeNode('-1');
Polymer.dom(d).classList.add('b');
Polymer.dom(d).classList.add('a');
Polymer.dom(content).insertBefore(d, Polymer.dom(content).firstChild);
Polymer.dom.flush();
Polymer.dom(content.$.inner).insertBefore(makeNode('-2'), Polymer.dom(content.$.inner).firstChild);
Polymer.dom.flush();
Polymer.dom(content.$.inner).removeChild(Polymer.dom(content.$.inner).firstChild);
Polymer.dom(content.$.inner).removeChild(Polymer.dom(content.$.inner).lastChild);
Polymer.dom(content).removeChild(Polymer.dom(content).firstChild);
Polymer.dom(content).removeChild(Polymer.dom(content).lastChild);
Polymer.dom.flush();
var d1 = makeNode('dynamic2!');
Polymer.dom(content).appendChild(d1);
setTimeout(function() {
Polymer.dom(d1).classList.add('a');
Polymer.dom(d1).classList.add('b');
setTimeout(function() {
//Polymer.dom(content).notifyObservers();
console.groupEnd('test dynamic');
if (done) {
done();
}
});
});
}
test(function() {
content.$.inner.unobserve();
test(function() {
content.$.inner.observe();
test(function() {
content.$.inner.unobserve();
test(function() {
content.$.inner.observe();
test();
});
});
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,119 @@
<!doctype html>
<html>
<head>
<title>observeReNodes</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../polymer.html">
</head>
<body>
<dom-module id='test-inner'>
<template>
~<content></content>~
</template>
<script>
Polymer({
is:'test-inner',
ready: function() {
this.observe();
},
observe: function() {
console.warn('observeNodes', this.localName);
this._childObserver = Polymer.dom(this).observeNodes(function(info) {
info.addedNodes.forEach(function(n) {
if (n.nodeType === Node.ELEMENT_NODE) {
console.log('added:', n.localName, n.textContent);
}
});
info.removedNodes.forEach(function(n) {
if (n.nodeType === Node.ELEMENT_NODE) {
console.log('removed:', n.localName, n.textContent);
}
});
});
},
unobserve: function() {
console.warn('unobserveNodes', this.localName);
Polymer.dom(this).unobserveNodes(this._childObserver);
}
});
</script>
</dom-module>
<dom-module id='test-content'>
<template>
<test-inner id="inner">[<content id="ip"></content>]</test-inner>
</template>
<script>
(function() {
Polymer({
is:'test-content'
});
})();
</script>
</dom-module>
<test-content id="content">
<div>content A</div>
<div>content B</div>
</test-content>
<br><br>
<script>
function makeNode(text) {
var d = document.createElement('div');
d.textContent = text;;
return d;
}
Polymer.dom.flush();
function test() {
console.group('test dynamic');
var d = makeNode('dynamic!');
Polymer.dom.flush();
Polymer.dom(content).appendChild(d);
Polymer.dom.flush();
Polymer.dom(content).removeChild(d);
Polymer.dom.flush();
Polymer.dom(content).appendChild(makeNode('1'));
Polymer.dom.flush();
Polymer.dom(content.$.inner).appendChild(makeNode('2'));
Polymer.dom.flush();
Polymer.dom(content).insertBefore(makeNode('-1'), Polymer.dom(content).firstChild);
Polymer.dom.flush();
Polymer.dom(content.$.inner).insertBefore(makeNode('-2'), Polymer.dom(content.$.inner).firstChild);
Polymer.dom.flush();
Polymer.dom(content.$.inner).removeChild(Polymer.dom(content.$.inner).firstChild);
Polymer.dom(content.$.inner).removeChild(Polymer.dom(content.$.inner).lastChild);
Polymer.dom(content).removeChild(Polymer.dom(content).firstChild);
Polymer.dom(content).removeChild(Polymer.dom(content).lastChild);
Polymer.dom.flush();
console.groupEnd('test dynamic');
}
test();
content.$.inner.unobserve();
test();
content.$.inner.observe();
test();
content.$.inner.unobserve();
test();
content.$.inner.observe();
test();
</script>
</body>
</html>

View File

@ -0,0 +1,59 @@
<!doctype html>
<html>
<head>
<title>early owner-root</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../polymer.html">
</head>
<body>
<dom-module id="x-owner-root">
<template>where am I?</template>
<script>
Polymer({
is: 'x-owner-root',
properties: {
nug: {type: String, observer: '_nugChanged'}
},
_nugChanged: function() {
console.log(this.localName, Polymer.dom(this).getOwnerRoot());
}
});
</script>
</dom-module>
<dom-module id="x-test">
<template>
<template is="dom-if" if="{{foo}}">
<x-owner-root nug="foo"></x-owner-root>
</template>
</template>
<script>
Polymer({
is: 'x-test',
attached: function() {
this.foo = true;
Polymer.dom.flush();
var e = Polymer.dom(this.root).querySelector('x-owner-root');
var r = e && Polymer.dom(e).getOwnerRoot();
console.log(e, r, r === this.root);
}
});
</script>
</dom-module>
<x-test></x-test>
</body>
</html>

View File

@ -247,25 +247,23 @@ suite('Polymer.dom (patch)', function() {
s.id = 'light';
s.textContent = 'Light';
rere.appendChild(s);
// TODO(sorvell); patch
Polymer.dom(s);
assert.equal(rere.querySelector('#light'), s);
assert.equal(s.parentNode, rere);
Polymer.dom.flush();
if (rere.shadyRoot) {
assert.notEqual(s._composedParent, rere);
assert.notEqual(s.__dom.$parentNode, rere);
}
Polymer.dom.flush();
if (rere.shadyRoot) {
assert.equal(s._composedParent, p);
assert.equal(s.__dom.$parentNode, p);
}
rere.removeChild(s);
if (rere.shadyRoot) {
assert.equal(s._composedParent, p);
assert.equal(s.__dom.$parentNode, p);
}
Polymer.dom.flush();
if (rere.shadyRoot) {
assert.equal(s._composedParent, null);
assert.equal(s.__dom.$parentNode, null);
}
});
@ -298,35 +296,6 @@ suite('Polymer.dom (patch)', function() {
assert.notOk(projected);
});
test('Polymer.dom event', function() {
var test = document.querySelector('x-test');
var rere = test.root.querySelector('x-rereproject');
var re = rere.root.querySelector('x-reproject');
var p = re.root.querySelector('x-project');
var eventHandled = 0;
test.addEventListener('test-event', function(e) {
eventHandled++;
assert.equal(Polymer.dom(e).rootTarget, p);
assert.equal(Polymer.dom(e).localTarget, test);
var path = Polymer.dom(e).path;
// path includes window only on more recent Shadow DOM implementations
// account for that here.
assert.ok(path.length >= 10);
assert.equal(path[0], p);
assert.equal(path[2], re);
assert.equal(path[4], rere);
assert.equal(path[6], test);
});
rere.addEventListener('test-event', function(e) {
eventHandled++;
assert.equal(Polymer.dom(e).localTarget, rere);
});
p.fire('test-event');
assert.equal(eventHandled, 2);
});
test('Polymer.dom.childNodes is an array', function() {
assert.isTrue(Array.isArray(Polymer.dom(document.body).childNodes));
});
@ -374,13 +343,13 @@ suite('Polymer.dom non-distributed elements', function() {
function testNoAttr() {
assert.equal(Polymer.dom(child).getDestinationInsertionPoints()[0], d.$.notTestContent, 'child not distributed logically');
if (shady) {
assert.equal(child._composedParent, d.$.notTestContainer, 'child not rendered in composed dom');
assert.equal(child.__dom.$parentNode, d.$.notTestContainer, 'child not rendered in composed dom');
}
}
function testWithAttr() {
assert.equal(Polymer.dom(child).getDestinationInsertionPoints()[0], d.$.testContent, 'child not distributed logically');
if (shady) {
assert.equal(child._composedParent, d.$.testContainer, 'child not rendered in composed dom');
assert.equal(child.__dom.$parentNode, d.$.testContainer, 'child not rendered in composed dom');
}
}
// test with x-distribute
@ -395,17 +364,12 @@ suite('Polymer.dom non-distributed elements', function() {
testNoAttr();
// set / unset `test` attr and see if it distributes properly
child.setAttribute('test', '');
d.distributeContent();
Polymer.dom.flush();
testWithAttr();
//
child.removeAttribute('test');
d.distributeContent();
Polymer.dom.flush();
testNoAttr();
//
child.setAttribute('test', '');
d.distributeContent();
Polymer.dom.flush();
testWithAttr();
});

View File

@ -0,0 +1,79 @@
<!doctype html>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<meta charset="utf-8">
<script src="../../../../webcomponentsjs/webcomponents-lite.js"></script>
<script src="../../../../web-component-tester/browser.js"></script>
<link rel="import" href="../../../polymer.html">
<link rel="import" href="../../../src/lib/experimental/patch-dom.html">
</head>
<body>
<x-test>
<header>header 1</header>
<footer>footer 1</footer>
</x-test>
<dom-module id="x-test">
<template>
<style>
:host {
display: block;
border: 2px solid black;
padding: 8px;
margin: 8px;
}
.header {
background: steelblue;
}
.content {
margin: 10px;
background: beige;
}
.footer {
background: tomato;
}
</style>
<div>User headers...</div>
<div class="header"><content select="header"></content></div>
<div class="content">Element content...</div>
<div>User footers...</div>
<div class="footer"><content select="footer"></content></div>
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({is: 'x-test'});
});
</script>
</dom-module>
<script>
addEventListener('WebComponentsReady', function() {
var test = document.querySelector('x-test');
console.log('should not find anything:',
document.querySelector('.content'),
test.querySelector('.content'));
var f = document.createElement('footer');
f.textContent = 'dynamic footer';
test.insertBefore(f, test.lastElementChild);
var h = document.createElement('header');
h.textContent = 'dynamic header';
test.appendChild(h);
})
</script>
</body>
</html>

View File

@ -188,7 +188,7 @@ suite('basic', function() {
assert.equal(info.path, 'multiSelected.splices');
assert.equal(info.value.keySplices.length, 1);
assert.equal(info.value.keySplices[0].added.length, 1);
assert.equal(info.value.keySplices[0].added[0], 0);
assert.equal(info.value.keySplices[0].added[0], '#0');
assert.equal(info.value.keySplices[0].removed.length, 0);
assert.sameMembers(bind.$.observer.multiSelected, [bind.items[2]]);
}
@ -203,7 +203,7 @@ suite('basic', function() {
assert.equal(info.path, 'multiSelected.splices');
assert.equal(info.value.keySplices.length, 1);
assert.equal(info.value.keySplices[0].added.length, 1);
assert.equal(info.value.keySplices[0].added[0], 1);
assert.equal(info.value.keySplices[0].added[0], '#1');
assert.equal(info.value.keySplices[0].removed.length, 0);
assert.sameMembers(bind.$.observer.multiSelected, [bind.items[2], bind.items[0]]);
}
@ -217,14 +217,14 @@ suite('basic', function() {
var called = false;
observer.multiChanged = function(info) {
called = true;
assert.equal(info.path, 'multiSelected.0.name');
assert.equal(info.path, 'multiSelected.#0.name');
assert.equal(info.value, 'test');
assert.equal(bind.$.observer.multiSelected[0].name, 'test');
};
bind.set(['items', 2, 'name'], 'test');
observer.multiChanged = function(info) {
called = true;
assert.equal(info.path, 'multiSelected.1.name');
assert.equal(info.path, 'multiSelected.#1.name');
assert.equal(info.value, 'test2');
assert.equal(bind.$.observer.multiSelected[1].name, 'test2');
};
@ -238,7 +238,7 @@ suite('basic', function() {
var called = false;
observer.multiChanged = function(info) {
called = true;
assert.equal(info.path, 'multiSelected.1.name');
assert.equal(info.path, 'multiSelected.#1.name');
assert.equal(info.value, 'test3');
assert.equal(bind.$.observer.multiSelected[1].name, 'test3');
};

View File

@ -128,13 +128,12 @@ suite('attributeChangedCallback', function() {
test('calls attributeChanged()', function() {
var args = null;
Child.attributeChanged = function() {args = arguments};
instance.attributeChangedCallback('attr', null, 1, 'stuff');
instance.attributeChangedCallback('attr', null, 1);
assert.equal(args.length, 4);
assert.equal(args.length, 3);
assert.equal(args[0], 'attr');
assert.equal(args[1], null);
assert.equal(args[2], 1);
assert.equal(args[3], 'stuff');
});
});

View File

@ -139,7 +139,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
behaviors: [
Polymer.BehaviorA,
null,
Polymer.BehaviorB
],
@ -260,4 +259,4 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
</script>
</script>

View File

@ -127,6 +127,27 @@ suite('multi-behaviors element', function() {
assert.equal(el.attributes.user.value, 'user', 'Behavior hostAttribute overrode user attribute');
});
test('behaviour is null generates warning', function() {
var warned = false, oldWarn = Polymer.Base._warn;
Polymer.Base._warn = function (message) {
assert.match(message, /behavior is null/);
warned = true;
};
Polymer({
behaviors: [
null
],
is: 'behavior-null'
});
assert.equal(warned, true, 'Null behaviour should generate warning');
Polymer.Base._warn = oldWarn;
});
});

View File

@ -17,6 +17,7 @@
neg-computed-inline="{{!computeInline(value,add,divide)}}"
computed-negative-number="{{computeNegativeNumber(-1)}}"
computed-negative-literal="{{computeNegativeNumber(-A)}}"
computed-wildcard="{{computeWildcard(a, b.*)}}"
style$="{{boundStyle}}"
data-id$="{{dataSetId}}"
custom-event-value="{{customEventValue::custom}}"
@ -30,13 +31,30 @@
"tricky\,&#39;zot&#39;" )}}'
computed-from-no-args="{{computeFromNoArgs( )}}"
no-computed="{{foobared(noInlineComputed)}}"
compoundAttr1$="{{cpnd1}}{{ cpnd2 }}{{cpnd3.prop}}{{ computeCompound(cpnd4, cpnd5, 'literal')}}"
compoundAttr2$="literal1 {{cpnd1}} literal2 {{cpnd2}}{{cpnd3.prop}} literal3 {{computeCompound(cpnd4, cpnd5, 'literal')}} literal4"
compoundAttr3$="[yes/no]: {{cpnd1}}, {{computeCompound('world', 'username ', 'Hello {0} ')}}"
>
Test
<!-- Malformed bindings to be ignored -->
{{really.long.identifier.in.malformed.binding.should.be.ignored]}
{{computeFromLiterals(3, 'really.long.literal.in.malformed.binding.should.be.ignored)]}
<!-- Should still parse -->
{{computeFromLiterals(3, 'foo', bool)}}
</div>
<x-prop id="boundProps"
prop1="{{cpnd1}}{{ cpnd2 }}{{cpnd3.prop}}{{ computeCompound(cpnd4, cpnd5, 'literal')}}"
prop2="literal1 {{cpnd1}} literal2 {{cpnd2}}{{cpnd3.prop}} literal3 {{computeCompound(cpnd4, cpnd5, 'literal')}} literal4"
></x-prop>
<span id="boundText">{{text}}</span>
<span idtest id="{{boundId}}"></span>
<s id="computedContent">{{computeFromTrickyLiterals(3, 'tricky\,\'zot\'')}}</s>
<s id="computedContent2">{{computeFromTrickyLiterals("(",3)}}</s>
<input id="boundInput" value="{{text::input}}">
<div id="compound1">{{cpnd1}}{{cpnd2}}{{cpnd3.prop}}{{computeCompound(cpnd4, cpnd5, 'literal')}}</div>
<div id="compound2">
literal1 {{cpnd1}} literal2 {{cpnd2}}{{cpnd3.prop}} literal3 {{computeCompound(cpnd4, cpnd5, 'literal')}} literal4
</div>
</template>
<script>
Polymer({
@ -243,6 +261,12 @@
},
computeNegativeNumber: function (num) {
return num;
},
computeCompound: function(a, b, c) {
return '' + c + b + a;
},
computeWildcard: function(a, bInfo) {
return a + (bInfo && bInfo.base ? bInfo.base.value : 0);
}
});
</script>
@ -359,6 +383,26 @@
});
</script>
<script>
Polymer({
is: 'x-prop',
properties: {
prop1: {
value: 'default',
observer: 'prop1Changed'
},
prop2: {
value: 'default',
observer: 'prop2Changed'
}
},
created: function() {
this.prop1Changed = sinon.spy();
this.prop2Changed = sinon.spy();
}
});
</script>
<script>
Polymer({
is: 'x-notifies1',
@ -407,20 +451,44 @@
</script>
<dom-module id="x-entity-and-binding">
<template>
<p>&copy;</p>
<p id="binding">{{myText}}</p>
</template>
</dom-module>
<template>
<p>&copy;</p>
<p id="binding">{{myText}}</p>
</template>
</dom-module>
<script>
Polymer({
is: "x-entity-and-binding",
properties: {
myText: {
type: String,
value: 'binding'
}
}
});
</script>
<dom-module id="x-input-value">
<template>
<input id="input" value$="{{inputValue}}">
</template>
</dom-module>
<dom-module id="x-bind-is-attached">
<template>
<div id="check">{{isAttached}}</div>
</template>
<script>
Polymer({
is: "x-entity-and-binding",
is: 'x-bind-is-attached',
properties: {
myText: {
type: String,
value: 'binding'
isAttached: {
observer: '_isAttachedChanged'
}
}
},
_isAttachedChanged: function() {}
});
</script>
</dom-module>

View File

@ -16,7 +16,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<link rel="import" href="../../polymer.html">
<link rel="import" href="bind-elements.html">
<body>
<script>
suite('single-element binding effects', function() {
@ -176,6 +175,7 @@ suite('single-element binding effects', function() {
assert.equal(el.$.boundChild.computedFromTrickyLiterals2, '3tricky,\'zot\'', 'Wrong result from tricky literal arg computation');
assert.equal(el.$.boundChild.computedFromTrickyLiterals3, '3tricky,\'zot\'', 'Wrong result from tricky literal arg computation');
assert.equal(el.$.computedContent.textContent, '3tricky,\'zot\'', 'Wrong textContent from tricky literal arg computation');
assert.equal(el.$.computedContent2.textContent, '(3', 'Wrong textContent from tricky literal arg computation');
});
test('computed annotation with no args', function() {
@ -260,7 +260,7 @@ suite('single-element binding effects', function() {
test('custom notification event to path', function() {
el.clearObserverCounts();
el.$.boundChild.customEventObjectValue = 84;
el.fire('change', null, {node: el.$.boundChild});
el.$.boundChild.dispatchEvent(new Event('change'));
assert.equal(el.customEventObject.value, 84, 'custom bound path incorrect');
assert.equal(el.observerCounts.customEventObjectValueChanged, 1, 'custom bound path observer not called');
});
@ -273,6 +273,12 @@ suite('single-element binding effects', function() {
assert.equal(el.$.boundChild.computedNegativeLiteral, undefined);
});
test('computed binding with wildcard', function() {
el.a = 5;
el.b = {value: 10};
assert.equal(el.$.boundChild.computedWildcard, 15);
});
});
</script>
@ -373,6 +379,15 @@ suite('2-way binding effects between elements', function() {
assert.equal(el.boundreadonlyvalue, 46, 'property bound to read-only property should change from change to bound value');
});
test('listener for value-changed fires when value changed from host', function() {
var listener = sinon.spy();
el.$.basic1.addEventListener('notifyingvalue-changed', listener);
el.boundnotifyingvalue = 678;
assert.equal(el.$.basic1.notifyingvalue, 678);
assert.isTrue(listener.calledOnce);
assert.equal(listener.getCalls()[0].args[0].detail.value, 678);
});
});
suite('1-way binding effects between elements', function() {
@ -598,6 +613,17 @@ suite('binding to attribute', function() {
assert(!el.$.boundChild.hasAttribute('attrvalue'));
});
test('bind to value attribute on input should not fail', function() {
Polymer({
is: 'x-input-value'
});
var el = document.createElement('x-input-value');
el.inputValue = "the value";
assert.equal(el.$.input.value, "the value", "The value of the input is not propagated");
assert.equal(el.$.input.value$, undefined, "value$ should be removed from input");
});
});
suite('avoid non-bubbling event gotchas', function() {
@ -653,7 +679,6 @@ suite('warnings', function() {
var warned = false;
el._warn = function() {
warned = true;
warn.apply(el, arguments);
};
el.noObserver = 42;
assert.equal(warned, true, 'no warning for undefined observer');
@ -663,7 +688,6 @@ suite('warnings', function() {
var warned = false;
el._warn = function() {
warned = true;
warn.apply(el, arguments);
};
el.noComplexObserver = {};
assert.equal(warned, true, 'no warning for undefined complex observer');
@ -673,7 +697,6 @@ suite('warnings', function() {
var warned = false;
el._warn = function() {
warned = true;
warn.apply(el, arguments);
};
el.noComputed = 99;
assert.equal(warned, true, 'no warning for undefined computed function');
@ -683,7 +706,6 @@ suite('warnings', function() {
var warned = false;
el._warn = function() {
warned = true;
warn.apply(el, arguments);
};
el.noInlineComputed = 99;
assert.equal(warned, true, 'no warning for undefined computed function');
@ -691,22 +713,153 @@ suite('warnings', function() {
});
</script>
<script>
suite('binding corner cases', function() {
// IE can create adjacent text nodes that split bindings; this test
// ensures the code that addresses this is functional
test('text binding after entity', function() {
var el = document.createElement('x-entity-and-binding');
assert.equal(el.$.binding.textContent, 'binding');
});
suite('binding corner cases', function() {
// IE can create adjacent text nodes that split bindings; this test
// ensures the code that addresses this is functional
test('text binding after entity', function() {
var el = document.createElement('x-entity-and-binding');
assert.equal(el.$.binding.textContent, 'binding');
});
test('bind to isAttached', function() {
var el = document.createElement('x-bind-is-attached');
sinon.spy(el, '_isAttachedChanged');
document.body.appendChild(el);
Polymer.dom.flush();
assert.equal(el.$.check.textContent, 'true');
assert.isTrue(el._isAttachedChanged.calledOnce);
document.body.removeChild(el);
});
});
suite('compound binding / string interpolation', function() {
test('compound adjacent property bindings', function() {
var el = document.createElement('x-basic');
// Adjacent compound binding with no literal do not override the default
assert.equal(el.$.boundProps.prop1, 'default');
assert.isTrue(el.$.boundProps.prop1Changed.calledOnce);
el.cpnd2 = 'cpnd2';
assert.equal(el.$.boundProps.prop1, 'cpnd2');
el.cpnd1 = 'cpnd1';
el.cpnd3 = {prop: 'cpnd3'};
assert.equal(el.$.boundProps.prop1, 'cpnd1cpnd2cpnd3');
el.cpnd4 = 'cpnd4';
assert.equal(el.$.boundProps.prop1, 'cpnd1cpnd2cpnd3');
el.cpnd5 = 'cpnd5';
assert.equal(el.$.boundProps.prop1, 'cpnd1cpnd2cpnd3literalcpnd5cpnd4');
});
test('compound property bindings with literals', function() {
var el = document.createElement('x-basic');
assert.equal(el.$.boundProps.prop2, 'literal1 literal2 literal3 literal4');
assert.isTrue(el.$.boundProps.prop2Changed.calledOnce);
el.cpnd1 = 'cpnd1';
el.cpnd2 = 'cpnd2';
el.cpnd3 = {prop: 'cpnd3'};
el.cpnd4 = 'cpnd4';
el.cpnd5 = 'cpnd5';
assert.equal(el.$.boundProps.prop2, 'literal1 cpnd1 literal2 cpnd2cpnd3 literal3 literalcpnd5cpnd4 literal4');
el.cpnd1 = null;
el.cpnd2 = undefined;
el.cpnd3 = {};
el.cpnd4 = '';
el.cpnd5 = '';
assert.equal(el.$.boundProps.prop2, 'literal1 literal2 literal3 literal literal4');
});
test('compound adjacent attribute bindings', function() {
var el = document.createElement('x-basic');
// Adjacent compound binding with no literal do not override the default
assert.equal(el.$.boundChild.getAttribute('compoundAttr1'), null);
el.cpnd2 = 'cpnd2';
assert.equal(el.$.boundChild.getAttribute('compoundAttr1'), 'cpnd2');
el.cpnd1 = 'cpnd1';
el.cpnd3 = {prop: 'cpnd3'};
assert.equal(el.$.boundChild.getAttribute('compoundAttr1'), 'cpnd1cpnd2cpnd3');
el.cpnd4 = 'cpnd4';
assert.equal(el.$.boundChild.getAttribute('compoundAttr1'), 'cpnd1cpnd2cpnd3');
el.cpnd5 = 'cpnd5';
assert.equal(el.$.boundChild.getAttribute('compoundAttr1'), 'cpnd1cpnd2cpnd3literalcpnd5cpnd4');
});
test('compound property attribute with literals', function() {
var el = document.createElement('x-basic');
assert.equal(el.$.boundChild.getAttribute('compoundAttr2'), 'literal1 literal2 literal3 literal4');
el.cpnd1 = 'cpnd1';
el.cpnd2 = 'cpnd2';
el.cpnd3 = {prop: 'cpnd3'};
el.cpnd4 = 'cpnd4';
el.cpnd5 = 'cpnd5';
assert.equal(el.$.boundChild.getAttribute('compoundAttr2'), 'literal1 cpnd1 literal2 cpnd2cpnd3 literal3 literalcpnd5cpnd4 literal4');
el.cpnd1 = null;
el.cpnd2 = undefined;
el.cpnd3 = {};
el.cpnd4 = '';
el.cpnd5 = '';
assert.equal(el.$.boundChild.getAttribute('compoundAttr2'), 'literal1 literal2 literal3 literal literal4');
});
test('compound property attribute with {} and [] in text', function() {
var el = document.createElement('x-basic');
el.cpnd1 = 'cpnd1';
assert.equal(el.$.boundChild.getAttribute('compoundAttr3'), '[yes/no]: cpnd1, Hello {0} username world');
});
test('compound adjacent textNode bindings', function() {
var el = document.createElement('x-basic');
// The single space is due to the gambit to prevent empty text nodes
// from being evacipated on IE during importNode from the template; it will
// only be there the when in the virgin state after cloning the template
assert.equal(el.$.compound1.textContent, ' ');
el.cpnd2 = 'cpnd2';
assert.equal(el.$.compound1.textContent, 'cpnd2');
el.cpnd1 = 'cpnd1';
el.cpnd3 = {prop: 'cpnd3'};
assert.equal(el.$.compound1.textContent, 'cpnd1cpnd2cpnd3');
el.cpnd4 = 'cpnd4';
assert.equal(el.$.compound1.textContent, 'cpnd1cpnd2cpnd3');
el.cpnd5 = 'cpnd5';
assert.equal(el.$.compound1.textContent, 'cpnd1cpnd2cpnd3literalcpnd5cpnd4');
// Once the binding evaluates back to '', it will in fact be ''
el.computeCompound = function() { return ''; };
el.cpnd1 = null;
el.cpnd2 = '';
el.cpnd3 = {prop: null};
el.cpnd4 = null;
el.cpnd5 = '';
assert.equal(el.$.compound1.textContent, '');
});
test('compound textNode bindings with literals', function() {
var el = document.createElement('x-basic');
assert.equal(el.$.compound2.textContent.trim(), 'literal1 literal2 literal3 literal4');
el.cpnd1 = 'cpnd1';
el.cpnd2 = 'cpnd2';
el.cpnd3 = {prop: 'cpnd3'};
el.cpnd4 = 'cpnd4';
el.cpnd5 = 'cpnd5';
assert.equal(el.$.compound2.textContent.trim(), 'literal1 cpnd1 literal2 cpnd2cpnd3 literal3 literalcpnd5cpnd4 literal4');
el.cpnd1 = null;
el.cpnd2 = undefined;
el.cpnd3 = {};
el.cpnd4 = '';
el.cpnd5 = '';
assert.equal(el.$.compound2.textContent.trim(), 'literal1 literal2 literal3 literal literal4');
});
test('malformed bindings ignored', function() {
var el = document.createElement('x-basic');
el.bool = true;
assert.isTrue(el.$.boundChild.textContent.indexOf('really.long.identifier.in.malformed.binding.should.be.ignored') >= 0, true);
assert.isTrue(el.$.boundChild.textContent.indexOf('really.long.literal.in.malformed.binding.should.be.ignored') >= 0, true);
assert.isTrue(el.$.boundChild.textContent.indexOf('3foo') >= 0, true);
});
});
</script>
</body>
</html>

View File

@ -128,7 +128,7 @@
<dom-module id="x-configure-host">
<template>
<x-configure-child id="child" content="{{content}}" object="{{object.goo}}"></x-configure-child>
<x-configure-child id="child" content="{{content}}" object="{{object.goo}}" attr$="{{attrValue}}"></x-configure-child>
</template>
<script>
Polymer({
@ -156,6 +156,9 @@
},
stomp: {
value: 5
},
attrValue: {
value: 'attrValue'
}
}

View File

@ -22,9 +22,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<x-configure-value content="attr" object='{"foo": "obj-attr"}'></x-configure-value>
<x-configure-host></x-configure-host>
<x-configure-host></x-configure-host>
<x-configure-host content="attr"></x-configure-host>
<x-configure-host content="attr"></x-configure-host>
<script>
@ -53,7 +53,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
test('value set in properties initializes correctly', function() {
var e = document.querySelector('x-configure-value');
testConfigure(e, 'default', 'obj-default');
testConfigure(e, 'default', 'obj-default');
});
test('attribute overrides value set in properties', function() {
@ -81,6 +81,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assert.equal(e.readOnly, 'default');
});
test('properties for attribute bindings not configured', function() {
var e = document.querySelector('x-configure-host');
assert.equal(e.$.child.getAttribute('attr'), 'attrValue');
assert.equal(e.$.child.attr, undefined);
});
});
</script>

View File

@ -59,6 +59,44 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
background: red;
}
/* comment */
/*
This is a multi-line comment
*/
/*.aclassThatShouldBeIgnored {
someProperty: thatMustNotShowUp
}*/
</style>
<style id="short-escape-sequence">
.\33 d-model {
border-top: 3px solid red;
}
.\a33 d-model {
border-top: 3px solid red;
}
.\b333 d-model {
border-top: 3px solid red;
}
.\c3333 d-model {
border-top: 3px solid red;
}
.\d33333 d-model {
border-top: 3px solid red;
}
.\e33333d-model {
border-top: 3px solid red;
}
</style>
<style id="multiple-spaces">
.foo .bar {}
.foo .bar {}
.foo
.bar {}
</style>
<script>
@ -78,7 +116,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assert.equal(tree.rules.length, 4, 'unexpected number of rules');
assert.equal(tree.rules[2].rules.length, 8, 'unexpected number of rules in keyframes');
assert.equal(tree.rules[3].rules.length, 1, 'unexpected number of rules in @media');
console.log('test');
});
test('rule selectors parse', function() {
@ -119,6 +156,25 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assert.equal(result, '.stuff { background: red; }', 'unexpected stringified output');
});
test('short escape sequences', function() {
var s3 = document.querySelector('#short-escape-sequence');
var t = css.parse(s3.textContent);
assert.equal(t.rules[0].selector, '.\\000033d-model');
assert.equal(t.rules[1].selector, '.\\000a33d-model');
assert.equal(t.rules[2].selector, '.\\00b333d-model');
assert.equal(t.rules[3].selector, '.\\0c3333d-model');
assert.equal(t.rules[4].selector, '.\\d33333d-model');
assert.equal(t.rules[5].selector, '.\\e33333d-model');
});
test('multiple consequent spaces in CSS selector', function() {
var s4 = document.querySelector('#multiple-spaces');
var t = css.parse(s4.textContent);
assert.equal(t.rules[0].selector, '.foo .bar');
assert.equal(t.rules[1].selector, '.foo .bar');
assert.equal(t.rules[2].selector, '.foo .bar');
});
});
</script>

View File

@ -0,0 +1,19 @@
<script>
Polymer({
is: 'x-input',
extends : 'input'
});
</script>
<style is="custom-style">
input[is=x-input] {
border : 4px solid red;
@apply (--cs-blue);
}
</style>
<style is="custom-style">
:root {
--cs-blue: {
border : 8px solid blue;
};
}
</style>

View File

@ -0,0 +1,42 @@
<!doctype html>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<meta charset="utf-8">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<script src="../../../web-component-tester/browser.js"></script>
<link rel="import" href="../../polymer.html">
<link rel="import" href="custom-style-late-import.html">
</head>
<body>
<input id="input" is="x-input">
<script>
suite('custom-style late property definition', function() {
test('late defined properties applied to custom-style', function() {
assertComputed(input, '8px');
});
});
function assertComputed(element, value, property, pseudo) {
var computed = getComputedStyle(element, pseudo);
property = property || 'border-top-width';
assert.equal(computed[property], value, 'computed style incorrect for ' + property);
}
</script>
</body>
</html>

View File

@ -47,6 +47,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
};
}
:root {
--red-text : {
color : red;
};
}
:root{--blue-text:{color:#0000ff};--dummy-mixin:{};--bold-text:{font-weight:700}}
:root {
@ -69,15 +77,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
x-foo {
--primary: 10px;
}
body /deep/ * {
--deeep: 6px solid orange;
}
</style>
<style is="custom-style">
.bag {
@ -108,7 +114,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<style is="custom-style" include="shared-style2">
.zazz {
border: 20px solid blue;
border: 20px solid blue;
}
</style>
<style>
.foo--bar {
border-top : 3px solid red;
}
</style>
<style is="custom-style">
@media (min-width: 1px) {
.foo--bar {
border-top : 20px solid blue;
}
}
</style>
</head>
@ -126,11 +144,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<x-foo></x-foo>
<x-red-text></x-red-text>
<x-blue-bold-text></x-blue-bold-text>
<parent-variable-with-var></parent-variable-with-var>
<br><br>
<div id="after"></div>
<div class="foo"></div>
<div class="foo--bar"></div>
<dom-module id="x-baz">
<style>
@ -180,12 +205,91 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</template>
</dom-module>
<dom-module id="x-red-text">
<style>
:host {
@apply(--red-text);
}
</style>
<template>
x-red-text
</template>
</dom-module>
<dom-module id="x-blue-bold-text">
<style>
:host {@apply(--blue-text);@apply(--bold-text);}
</style>
<template>
x-blue-bold-text
</template>
</dom-module>
<dom-module id="parent-variable-with-var">
<template>
<style>
child-variable-with-var {
--variable-property-own-line: 1px;
--variable-property-preceded-property: 2px;
--variable-property-before-property: yellow;
--variable-property-after-property: 3px;
--variable-property-after-assignment: 4px;
--variable-property-before-assignment: 5px;
--variable-into-first-variable: 9px;
--variable-into-second-variable: 10px;
--variable-into-third-variable: 11px;
}
</style>
<child-variable-with-var id="child"></child-variable-with-var>
</template>
</dom-module>
<dom-module id="child-variable-with-var">
<template>
<style>
child-of-child-with-var {
/* in certain browsers (e.g. Safari) `top`, `bottom`, `left`, `right` don't compute
when no explicit position is defined (`relative` / `absolute` / `fixed`) */
position: relative;
--variable-own-line: "Varela font";
margin-top: var(--variable-property-own-line);
margin-bottom: var(--variable-property-preceded-property);
--variable-between-properties: 6px;
background-color: var(--variable-property-before-property); padding-top: var(--variable-property-after-property);
--variable-assignment-before-property: 7px; padding-bottom: var(--variable-property-after-assignment);
padding-left: var(--variable-property-before-assignment);--variable-assignment-after-property: 8px;
top: 12px;--variable-from-other-variable: var(--variable-into-first-variable);--variable-from-another-variable: var(--variable-into-second-variable); --variable-from-last-variable: var(--variable-into-third-variable);
}
</style>
<child-of-child-with-var id="child"></child-of-child-with-var>
</template>
</dom-module>
<dom-module id="child-of-child-with-var">
<template>
<style>
:host {
font-family: var(--variable-own-line);
padding-right: var(--variable-between-properties);
margin-left: var(--variable-assignment-before-property);
margin-right: var(--variable-assignment-after-property);
bottom: var(--variable-from-other-variable);
left: var(--variable-from-another-variable);
right: var(--variable-from-last-variable);
}
</style>
Text
</template>
</dom-module>
<script>
suite('custom-style', function() {
var xBar, xFoo;
suiteSetup(function() {
Polymer({
is: 'x-baz'
});
@ -198,6 +302,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
is: 'x-foo'
});
Polymer({
is: 'x-red-text'
});
Polymer({
is: 'x-blue-bold-text'
});
xBar = document.querySelector('x-bar');
xFoo = document.querySelector('x-foo');
@ -227,6 +339,20 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assert.property(props, '--bag');
});
test('custom properties with space before semicolon', function() {
var red = document.querySelector('x-red-text');
assertComputed(red, 'rgb(255, 0, 0)', 'color');
});
test('custom properties in minified css', function() {
var blue = document.querySelector('x-blue-bold-text');
assertComputed(blue, 'rgb(0, 0, 255)', 'color');
var computed = getComputedStyle(blue);
// Firefox returns `700`, while Chrome returns original `bold`
assert.ok(computed.fontWeight == '700' || computed.fontWeight == 'bold', 'computed style incorrect for fontWeight');
});
test('custom-styles apply normal and property values to main document', function() {
var bag = document.querySelector('.bag');
var italic = document.querySelector('.italic');
@ -243,17 +369,15 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assertComputed(m, '4px');
});
test('dynamic custom-styles apply', function(done) {
test('dynamic custom-styles apply', function() {
var dynamic = document.querySelector('.dynamic');
assertComputed(dynamic, '0px');
var ds = document.createElement('style', 'custom-style');
ds.textContent = ':root { --dynamic: 11px solid orange; }';
document.head.appendChild(ds);
setTimeout(function() {
Polymer.updateStyles();
assertComputed(dynamic, '11px');
done();
}, 0);
CustomElements.takeRecords();
Polymer.updateStyles();
assertComputed(dynamic, '11px');
});
test('custom-styles apply normal and property values to elements and cannot be late bound via inheritance', function() {
@ -301,6 +425,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
document.body.appendChild(d);
document.body.appendChild(style);
CustomElements.takeRecords();
Polymer.updateStyles();
assertComputed(d, '16px');
document.body.removeChild(d);
document.body.removeChild(style);
@ -331,6 +456,42 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
document.body.removeChild(d);
});
test('variable name with assignment including var correctly applied', function() {
Polymer({
is: 'parent-variable-with-var'
});
Polymer({
is: 'child-variable-with-var'
});
Polymer({
is: 'child-of-child-with-var'
});
var d = document.querySelector('parent-variable-with-var');
var el = d.$.child.$.child;
assertComputed(el, '1px', 'margin-top');
assertComputed(el, '2px', 'margin-bottom');
assertComputed(el, '3px', 'padding-top');
assertComputed(el, '4px', 'padding-bottom');
assertComputed(el, '5px', 'padding-left');
assertComputed(el, '6px', 'padding-right');
assertComputed(el, '7px', 'margin-left');
assertComputed(el, '8px', 'margin-right');
assertComputed(el, 'rgb(255, 255, 0)', 'background-color');
assertComputed(el, '9px', 'bottom');
assertComputed(el, '10px', 'left');
assertComputed(el, '11px', 'right');
assertComputed(el, '12px', 'top');
// Because FireFox and Chrome parse font-family differently...
var computed = getComputedStyle(el);
assert.equal(computed['font-family'].replace(/['"]+/g, ''), 'Varela font');
});
test('BEM-like CSS selectors under media queries', function() {
assertComputed(document.querySelector('.foo--bar'), '20px');
});
});

83
test/unit/debounce.html Normal file
View File

@ -0,0 +1,83 @@
<!doctype html>
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
<meta charset="utf-8">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<script src="../../../web-component-tester/browser.js"></script>
<link rel="import" href="../../polymer-mini.html">
</head>
<body>
<script>
HTMLImports.whenReady(function() {
Polymer({is: 'my-element'});
});
setup(function() {
window.el1 = document.createElement('my-element');
document.body.appendChild(window.el1);
window.el2 = document.createElement('my-element');
window.el1.appendChild(window.el2);
});
teardown(function() {
document.body.removeChild(window.el1);
delete window.el1;
delete window.el2;
});
suite('debounce', function() {
test('debounce (no-wait)', function(done) {
var called = 0;
var cb = function() {
called++;
};
window.el1.debounce('foo', cb);
window.el1.debounce('foo', cb);
window.el1.debounce('foo', cb);
setTimeout(function() {
assert.equal(called, 1, 'debounce should be called exactly once');
done();
}, 50);
});
test('debounce (wait)', function(done) {
var called = 0;
var now = Date.now();
var cb = function() {
called++;
};
window.el1.debounce('foo', cb);
window.el1.debounce('foo', cb, 100);
window.el1.debounce('foo', cb, 100);
setTimeout(function() {
assert.equal(called, 1, 'debounce should be called exactly once');
assert(Date.now() - now > 100, 'debounce should be called after at least 100ms');
done();
}, 200);
});
});
</script>
</body>
</html>

View File

@ -1,5 +1,4 @@
<script>
console.log('x-needs-repeat loaded')
Polymer({
is: 'x-needs-host',
ready: function() {
@ -9,4 +8,4 @@ console.log('x-needs-repeat loaded')
this.config = this.dataHost.getAttribute('config');
}
});
</script>
</script>

View File

@ -226,6 +226,5 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</script>
<link rel="import" href="dom-bind-elements2.html">
<link rel="import" href="should-404.html">
</body>
</html>

View File

@ -165,13 +165,42 @@
<div>1</div>
<div>2</div>
<div>3</div>
Stuff
{{text}}
<div>4</div>
</template>
</template>
<script>
Polymer({
is: 'x-textcontent'
is: 'x-textcontent',
properties: {
text: {
value: 'Stuff'
}
}
});
</script>
</dom-module>
<dom-module id="x-host">
<template>
<template id="domif" is="dom-if" if>
<x-client></x-client>
<x-client></x-client>
<x-client></x-client>
</template>
</template>
<script>
Polymer({
is: 'x-host'
});
Polymer({
is: 'x-client',
statics: {
uid: 0
},
ready: function() {
this.uid = this.statics.uid++;
}
});
</script>
</dom-module>

View File

@ -56,6 +56,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</template>
</div>
<div id="outerContainer">
<template is="dom-if" id="simple">
<x-client></x-client>
<x-client></x-client>
<x-client></x-client>
</template>
<div id="innerContainer">
</div>
</div>
<div id="removalContainer">
<template is="dom-if" if id="toBeRemoved"><div id="shouldBeRemoved"></div></template>
</div>
<script>
suite('nested pre-configured dom-if', function() {
@ -491,8 +506,190 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
document.body.removeChild(x);
});
test('binding to text nodes changed while if=false', function() {
var x = document.createElement('x-textcontent');
document.body.appendChild(x);
x.$.domIf.render();
var stamped = Polymer.dom(x.root).childNodes;
assert.equal(stamped.length, 12);
assert.equal(stamped[7].textContent.trim(), 'Stuff');
x.$.domIf.if = false;
x.$.domIf.render();
x.text = 'Hollaaaaa!';
stamped = Polymer.dom(x.root).childNodes;
assert.equal(stamped.length, 12);
assert.equal(stamped[7].textContent.trim(), '');
x.$.domIf.if = true;
x.$.domIf.render();
stamped = Polymer.dom(x.root).childNodes;
assert.equal(stamped.length, 12);
assert.equal(stamped[7].textContent.trim(), 'Hollaaaaa!');
document.body.removeChild(x);
});
});
suite('attach/detach tests', function() {
test('remove, append domif', function(done) {
var domif = document.querySelector('#simple');
domif.if = true;
outerContainer.removeChild(domif);
setTimeout(function() {
var clients = outerContainer.querySelectorAll('x-client');
assert.equal(clients.length, 0);
outerContainer.appendChild(domif);
setTimeout(function() {
var clients = outerContainer.querySelectorAll('x-client');
assert.equal(clients[0].uid, 0);
assert.equal(clients[1].uid, 1);
assert.equal(clients[2].uid, 2);
assert.equal(clients[1].previousElementSibling, clients[0]);
assert.equal(clients[2].previousElementSibling, clients[1]);
assert.equal(domif.previousElementSibling, clients[2]);
done();
});
});
});
test('move domif (clients persist)', function(done) {
var domif = document.querySelector('#simple');
domif.if = true;
Polymer.dom(innerContainer).appendChild(domif);
setTimeout(function() {
var clients = innerContainer.querySelectorAll('x-client');
// Same clients as before since move happened in one turn
assert.equal(clients[0].uid, 0);
assert.equal(clients[1].uid, 1);
assert.equal(clients[2].uid, 2);
assert.equal(clients[1].previousElementSibling, clients[0]);
assert.equal(clients[2].previousElementSibling, clients[1]);
assert.equal(domif.previousElementSibling, clients[2]);
done();
});
});
test('remove, wait, append domif (clients recreated)', function(done) {
var domif = document.querySelector('#simple');
domif.if = true;
Polymer.dom(innerContainer).removeChild(domif);
setTimeout(function() {
var clients = innerContainer.querySelectorAll('x-client');
assert.equal(clients.length, 0);
Polymer.dom(innerContainer).appendChild(domif);
setTimeout(function() {
var clients = outerContainer.querySelectorAll('x-client');
// New clients since removed for a turn
assert.equal(clients[0].uid, 3);
assert.equal(clients[1].uid, 4);
assert.equal(clients[2].uid, 5);
assert.equal(clients[1].previousElementSibling, clients[0]);
assert.equal(clients[2].previousElementSibling, clients[1]);
assert.equal(domif.previousElementSibling, clients[2]);
done();
});
});
});
test('move host with domif (clients persist)', function(done) {
var host = document.createElement('x-host');
Polymer.dom(outerContainer).appendChild(host);
setTimeout(function() {
var clients = Polymer.dom(host.root).querySelectorAll('x-client');
// New clients created in host instance
assert.equal(clients[0].uid, 6);
assert.equal(clients[1].uid, 7);
assert.equal(clients[2].uid, 8);
assert.equal(clients[1].previousElementSibling, clients[0]);
assert.equal(clients[2].previousElementSibling, clients[1]);
assert.equal(host.$.domif.previousElementSibling, clients[2]);
Polymer.dom(innerContainer).appendChild(host);
setTimeout(function() {
var clients = Polymer.dom(host.root).querySelectorAll('x-client');
// Clients in removed host persist
assert.equal(clients[0].uid, 6);
assert.equal(clients[1].uid, 7);
assert.equal(clients[2].uid, 8);
assert.equal(clients[1].previousElementSibling, clients[0]);
assert.equal(clients[2].previousElementSibling, clients[1]);
assert.equal(host.$.domif.previousElementSibling, clients[2]);
done();
});
});
});
test('remove, wait, append host with domif (clients persist)', function(done) {
var host = document.createElement('x-host');
Polymer.dom(outerContainer).appendChild(host);
setTimeout(function() {
var clients = Polymer.dom(host.root).querySelectorAll('x-client');
// New clients created in host instance
assert.equal(clients[0].uid, 9);
assert.equal(clients[1].uid, 10);
assert.equal(clients[2].uid, 11);
assert.equal(clients[1].previousElementSibling, clients[0]);
assert.equal(clients[2].previousElementSibling, clients[1]);
assert.equal(host.$.domif.previousElementSibling, clients[2]);
Polymer.dom(outerContainer).removeChild(host);
setTimeout(function() {
// Clients in removed host persist
assert.equal(clients[0].uid, 9);
assert.equal(clients[1].uid, 10);
assert.equal(clients[2].uid, 11);
assert.equal(clients[1].previousElementSibling, clients[0]);
assert.equal(clients[2].previousElementSibling, clients[1]);
assert.equal(host.$.domif.previousElementSibling, clients[2]);
Polymer.dom(innerContainer).appendChild(host);
setTimeout(function() {
// Clients in removed host persist
var clients = Polymer.dom(host.root).querySelectorAll('x-client');
assert.equal(clients[0].uid, 9);
assert.equal(clients[1].uid, 10);
assert.equal(clients[2].uid, 11);
assert.equal(clients[1].previousElementSibling, clients[0]);
assert.equal(clients[2].previousElementSibling, clients[1]);
assert.equal(host.$.domif.previousElementSibling, clients[2]);
done();
});
});
});
});
test('move into doc fragment', function(done) {
var el = shouldBeRemoved;
assert.equal(el.parentNode, removalContainer);
var frag = document.createDocumentFragment();
Polymer.dom(frag).appendChild(toBeRemoved);
setTimeout(function() {
assert.equal(el.parentNode, null);
Polymer.dom(removalContainer).appendChild(frag);
setTimeout(function() {
assert.equal(shouldBeRemoved.parentNode, removalContainer);
done();
});
});
});
test('move into shadow root', function(done) {
if (Polymer.Settings.hasShadow) {
var el = shouldBeRemoved;
assert.equal(el.parentNode, removalContainer);
var div = document.createElement('div');
document.body.appendChild(div);
var frag = div.createShadowRoot();
Polymer.dom(frag).appendChild(toBeRemoved);
setTimeout(function() {
assert.equal(el.parentNode, frag);
done();
});
} else {
done();
}
});
});
</script>
</body>

View File

@ -23,13 +23,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var module;
suite('dom-module inline', function() {
var temp = document.getElementById('temp');
test('dom-module will work when inlined', function() {
// simulate an inline dom-module
// use a new script object to make document.currentScript work correctly
var e = document.createElement('script');
e.textContent = 'temp.innerHTML = \'<dom-module id="foo"></dom-module>\'; module = Polymer.DomModule.import("foo");';
e.textContent =
'temp.innerHTML = \'<dom-module id="foo"></dom-module>\';' +
'module = Polymer.DomModule.import("foo");';
document.body.appendChild(e);
assert.ok(module, 'found module');
});

View File

@ -411,3 +411,64 @@ window.data = [
});
</script>
</dom-module>
<dom-module id="x-repeat-limit">
<template>
<template id="repeater" is="dom-repeat" items="{{items}}">
<div prop="{{outerProp.prop}}">{{item.prop}}</div>
</template>
</template>
<script>
Polymer({
is: 'x-repeat-limit',
properties: {
preppedItems: {
value: function() {
var ar = [];
for (var i = 0; i < 20; i++) {
ar.push({prop: i});
}
return ar;
}
},
outerProp: {
value: function() {
return {prop: 'outer'};
}
}
}
});
</script>
</dom-module>
<dom-module id="x-repeat-chunked">
<template>
<template id="repeater" is="dom-repeat" items="{{items}}" initial-count="10">
<x-wait>{{item.prop}}</x-wait>
</template>
</template>
<script>
Polymer({
is: 'x-repeat-chunked',
properties: {
preppedItems: {
value: function() {
var ar = [];
for (var i = 0; i < 100; i++) {
ar.push({prop: i});
}
return ar;
}
}
}
});
Polymer({
is: 'x-wait',
created: function() {
var time = performance.now();
time += 4;
while (performance.now() < time) {}
}
});
</script>
</dom-module>

View File

@ -71,6 +71,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<h4>x-primitive-large</h4>
<x-primitive-large id="primitiveLarge"></x-primitive-large>
<h4>x-repeat-limit</h4>
<x-repeat-limit id="limited"></x-repeat-limit>
<h4>x-repeat-chunked</h4>
<x-repeat-chunked id="chunked"></x-repeat-chunked>
<div id="inDocumentContainer">
</div>
@ -93,12 +99,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
stamped[38] .. 3-3-3
*/
suite('errors', function() {
test('items must be array', function() {
assert.throws(function() {
inDocumentRepeater.items = {};
}, /expected array/, 'should warn when items is not array');
var warned = false;
inDocumentRepeater._error = function(message) {
assert.match(message, /expected array/)
warned = true;
}
inDocumentRepeater.items = {};
assert.equal(warned, true, 'should warn when items is not array');
});
});
@ -108,6 +119,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
test('basic rendering, downward item binding', function() {
var stamped = Polymer.dom(configured.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
assert.equal(configured.$.repeater.renderedItemCount, 3, 'rendered item count incorrect');
assert.equal(stamped[0].itemaProp, 'prop-1');
assert.equal(stamped[0].computeda, 'prop-1+itemForComputedA');
assert.equal(stamped[0].indexa, 0);
@ -275,6 +287,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
CustomElements.takeRecords();
var stamped = Polymer.dom(configured.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
assert.equal(configured.$.repeater.renderedItemCount, 2, 'rendered item count incorrect');
assert.equal(stamped[0].itemaProp, 'prop-1');
assert.equal(stamped[0].indexa, 0);
assert.equal(stamped[13].itemaProp, 'prop-3');
@ -1310,6 +1323,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
done();
});
});
} else {
done();
}
});
@ -3215,7 +3230,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
setTimeout(function() {
var stamped1 = Polymer.dom(primitive.$.container1).querySelectorAll('*:not(template)');
for (var i=0; i<items.length; i++) {
assert.equal(stamped1[i].textContent, items[i]);
assert.equal(stamped1[i].itemaProp, items[i]);
}
var prev = items.slice();
items.sort();
@ -3228,7 +3243,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
setTimeout(function() {
var stamped1 = Polymer.dom(primitive.$.container1).querySelectorAll('*:not(template)');
for (var i=0; i<items.length; i++) {
assert.equal(stamped1[i].textContent, items[i]);
assert.equal(stamped1[i].itemaProp, items[i]);
}
done();
});
@ -3300,6 +3315,23 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assert.equal(repeater3.keyForElement(stamped1[4]), coll3.getKey(items3[2]));
});
test('renderedItemCount', function() {
var repeater1 = primitive.$.repeater1;
primitive.items = [ 'a', 'b', 'c', 'd', 'e' ];
repeater1.render();
assert.equal(repeater1.renderedItemCount, 5, 'renderedItemCount is incorrect');
repeater1.renderedItemCount = 0;
assert.equal(repeater1.renderedItemCount, 5, 'renderedItemCount is writable');
repeater1.filter = function(item) {
return (item != 'a' && item != 'e');
}
repeater1.render();
assert.equal(repeater1.renderedItemCount, 3, 'renderedItemCount incorrect after filter');
// reset repeater
repeater1.filter = undefined;
repeater1.render();
});
test('__hideTemplateChildren__', function() {
// Initially all showing
var stamped1 = Polymer.dom(primitive.$.container1).querySelectorAll('*:not(template)');
@ -3348,6 +3380,494 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
suite('limit', function() {
var checkItemOrder = function(stamped) {
for (var i=0; i<stamped.length; i++) {
assert.equal(parseInt(stamped[i].textContent), i);
}
};
test('initial limit', function() {
limited.items = limited.preppedItems;
limited.$.repeater._limit = 2;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 2);
checkItemOrder(stamped);
});
test('change item paths in & out of limit', function() {
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
limited.outerProp = {prop: 'changed'};
assert.equal(stamped[0].prop, 'changed');
limited.set('items.0.prop', '0-changed');
limited.set('items.3.prop', '3-changed');
assert.equal(stamped[0].textContent, '0-changed');
limited.set('outerProp.prop', 'changed again');
assert.equal(stamped[0].prop, 'changed again');
});
test('increase limit', function() {
// Increase limit
limited.$.repeater._limit = 10;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 10);
checkItemOrder(stamped);
assert.equal(stamped[3].prop, 'changed again');
assert.equal(stamped[3].textContent, '3-changed');
limited.set('items.0.prop', 0);
limited.set('items.3.prop', 3);
// Increase limit
limited.$.repeater._limit = 20;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 20);
checkItemOrder(stamped);
});
test('increase limit above items.length', function() {
limited.$.repeater._limit = 30;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 20);
checkItemOrder(stamped);
});
test('decrease limit', function() {
// Decrease limit
limited.$.repeater._limit = 15;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 15);
checkItemOrder(stamped);
// Decrease limit
limited.$.repeater._limit = 0;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 0);
});
test('negative limit', function() {
limited.$.repeater._limit = -10;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 0);
});
});
suite('limit with sort', function() {
var checkItemOrder = function(stamped) {
for (var i=0; i<stamped.length; i++) {
assert.equal(stamped[i].textContent, 19 - i);
}
};
test('initial limit', function() {
limited.$.repeater._limit = 2;
limited.$.repeater.sort = function(a, b) {
return b.prop - a.prop;
};
limited.items = null;
limited.$.repeater.render();
limited.items = limited.preppedItems;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 2);
checkItemOrder(stamped);
});
test('increase limit', function() {
// Increase limit
limited.$.repeater._limit = 10;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 10);
checkItemOrder(stamped);
// Increase limit
limited.$.repeater._limit = 20;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 20);
checkItemOrder(stamped);
});
test('increase limit above items.length', function() {
limited.$.repeater._limit = 30;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 20);
checkItemOrder(stamped);
});
test('decrease limit', function() {
// Decrease limit
limited.$.repeater._limit = 15;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 15);
checkItemOrder(stamped);
// Decrease limit
limited.$.repeater._limit = 0;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 0);
});
test('negative limit', function() {
limited.$.repeater._limit = -10;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 0);
});
});
suite('limit with filter', function() {
var checkItemOrder = function(stamped) {
for (var i=0; i<stamped.length; i++) {
assert.equal(stamped[i].textContent, i * 2);
}
};
test('initial limit', function() {
var items = limited.items;
limited.$.repeater._limit = 2;
limited.$.repeater.sort = null;
limited.$.repeater.filter = function(a) {
return (a.prop % 2) === 0;
};
limited.items = null;
limited.$.repeater.render();
limited.items = items;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 2);
checkItemOrder(stamped);
});
test('increase limit', function() {
// Increase limit
limited.$.repeater._limit = 5;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 5);
checkItemOrder(stamped);
// Increase limit
limited.$.repeater._limit = 10;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 10);
checkItemOrder(stamped);
});
test('increase limit above items.length', function() {
limited.$.repeater._limit = 30;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 10);
checkItemOrder(stamped);
});
test('decrease limit', function() {
// Decrease limit
limited.$.repeater._limit = 5;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 5);
checkItemOrder(stamped);
// Decrease limit
limited.$.repeater._limit = 0;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 0);
});
test('negative limit', function() {
limited.$.repeater._limit = -10;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 0);
});
});
suite('limit with sort & filter', function() {
var checkItemOrder = function(stamped) {
for (var i=0; i<stamped.length; i++) {
assert.equal(stamped[i].textContent, (9 - i) * 2);
}
};
test('initial limit', function() {
var items = limited.items;
limited.$.repeater._limit = 2;
limited.$.repeater.sort = function(a, b) {
return b.prop - a.prop;
};
limited.$.repeater.filter = function(a) {
return (a.prop % 2) === 0;
};
limited.items = null;
limited.$.repeater.render();
limited.items = items;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 2);
checkItemOrder(stamped);
});
test('increase limit', function() {
// Increase limit
limited.$.repeater._limit = 5;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 5);
checkItemOrder(stamped);
// Increase limit
limited.$.repeater._limit = 10;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 10);
checkItemOrder(stamped);
});
test('increase limit above items.length', function() {
limited.$.repeater._limit = 30;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 10);
checkItemOrder(stamped);
});
test('decrease limit', function() {
// Decrease limit
limited.$.repeater._limit = 5;
limited.$.repeater.render();
var stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 5);
checkItemOrder(stamped);
// Decrease limit
limited.$.repeater._limit = 0;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 0);
});
test('negative limit', function() {
limited.$.repeater._limit = -10;
limited.$.repeater.render();
stamped = Polymer.dom(limited.root).querySelectorAll('*:not(template)');
assert.equal(stamped.length, 0);
});
});
// TODO(kschaaf): This test suite has proven to be flaky only on IE, only
// on CI (Sauce) presumably because of rAF handling in the CI environment
// disabling for IE for now to avoid Polymer tests being flaky
if (!/Trident/.test(navigator.userAgent)) {
suite('chunked rendering', function() {
test('basic chunked rendering', function(done) {
var checkItemOrder = function(stamped) {
for (var i=0; i<stamped.length; i++) {
assert.equal(stamped[i].textContent, i);
}
};
var lastLength = 0;
var checkCount = function() {
var stamped = Polymer.dom(chunked.root).querySelectorAll('*:not(template)');
checkItemOrder(stamped);
if (stamped.length && lastLength === 0) {
// Initial rendering of initial count
assert.equal(stamped.length, 10);
} else {
// Remaining rendering incremenets
assert.isTrue(stamped.length > lastLength);
}
if (stamped.length < 100) {
lastLength = stamped.length;
checkUntilComplete();
} else {
// Final rendering at exact item count
assert.equal(stamped.length, 100);
done();
}
};
var checkUntilComplete = function() {
// On polyfilled MO, need to wait one setTimeout before rAF
if (MutationObserver._isPolyfilled) {
setTimeout(function() {
requestAnimationFrame(checkCount);
});
} else {
requestAnimationFrame(checkCount);
}
};
chunked.items = chunked.preppedItems.slice();
checkUntilComplete();
});
test('mutations during chunked rendering', function(done) {
var checkItemOrder = function(stamped) {
var last = -1;
for (var i=0; i<stamped.length; i++) {
var curr = parseFloat(stamped[i].textContent);
assert.isTrue(curr > last);
last = curr;
}
};
var mutateArray = function(repeater, renderedCount) {
// The goal here is to remove & add some, and do it over
// the threshold of where we have currently rendered items, and
// ensure that the prop values of the newly inserted items are in
// ascending order so we can do a simple check in checkItemOrder
var overlap = 2;
var remove = 4;
var add = 6;
var start = renderedCount.length - overlap;
if (start + add < repeater.items.length) {
var end = start + remove;
var args = ['items', start, remove];
var startVal = repeater.items[start].prop;
var endVal = repeater.items[end].prop;
var delta = (endVal - startVal) / add;
for (var i=0; i<add; i++) {
args.push({prop: startVal + i*delta});
}
repeater.splice.apply(repeater, args);
}
};
var lastLength = 0;
var mutateCount = 5;
var checkCount = function() {
var stamped = Polymer.dom(chunked.root).querySelectorAll('*:not(template)');
checkItemOrder(stamped);
if (stamped.length && lastLength === 0) {
// Initial rendering of initial count
assert.equal(stamped.length, 10);
} else {
// Remaining rendering incremenets
assert.isTrue(stamped.length > lastLength);
}
if (stamped.length < chunked.items.length) {
if (mutateCount-- > 0) {
mutateArray(chunked, stamped);
}
lastLength = stamped.length;
checkUntilComplete();
} else {
// Final rendering at exact item count
assert.equal(stamped.length, chunked.items.length);
done();
}
};
var checkUntilComplete = function() {
// On polyfilled MO, need to wait one setTimeout before rAF
if (MutationObserver._isPolyfilled) {
setTimeout(function() {
requestAnimationFrame(checkCount);
});
} else {
requestAnimationFrame(checkCount);
}
};
chunked.items = chunked.preppedItems.slice();
checkUntilComplete();
});
test('mutations during chunked rendering, sort & filtered', function(done) {
var checkItemOrder = function(stamped) {
var last = Infinity;
for (var i=0; i<stamped.length; i++) {
var curr = parseFloat(stamped[i].textContent);
assert.isTrue(curr <= last);
assert.strictEqual(curr % 2, 0);
last = curr;
}
};
var mutateArray = function(repeater, stamped) {
var start = parseInt(stamped[0].textContent);
var end = parseInt(stamped[stamped.length-1].textContent);
var mid = (end-start)/2;
for (var i=0; i<5; i++) {
chunked.push('items', {prop: mid + 1});
}
chunked.splice('items', Math.round(stamped.length/2), 3);
};
var lastLength = 0;
var mutateCount = 5;
var checkCount = function() {
var stamped = Polymer.dom(chunked.root).querySelectorAll('*:not(template)');
checkItemOrder(stamped);
var filteredLength = chunked.items.filter(chunked.$.repeater.filter).length;
if (stamped.length && lastLength === 0) {
// Initial rendering of initial count
assert.equal(stamped.length, 10);
} else {
// Remaining rendering incremenets
if (stamped.length < filteredLength) {
assert.isTrue(stamped.length > lastLength);
}
}
if (stamped.length < filteredLength) {
if (mutateCount-- > 0) {
mutateArray(chunked, stamped);
}
lastLength = stamped.length;
checkUntilComplete();
} else {
assert.equal(stamped.length, filteredLength);
done();
}
};
var checkUntilComplete = function() {
// On polyfilled MO, need to wait one setTimeout before rAF
if (MutationObserver._isPolyfilled) {
setTimeout(function() {
requestAnimationFrame(checkCount);
});
} else {
requestAnimationFrame(checkCount);
}
};
chunked.$.repeater.sort = function(a, b) {
return b.prop - a.prop;
};
chunked.$.repeater.filter = function(a) {
return (a.prop % 2) === 0;
};
chunked.items = chunked.preppedItems.slice();
checkUntilComplete();
});
});
}
</script>
</body>

View File

@ -20,7 +20,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<dynamic-element></dynamic-element>
<script>
suite('dynamic imports', function() {
test('use importHref to load and create an element', function(done) {
@ -36,6 +36,27 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
});
suite('async/sync loading', function() {
var url = 'dynamic-imports/dynamic-element.html';
test('importHref sync loads by default', function(done) {
Polymer.Base.importHref(url, function(e) {
assert.isFalse(e.target.hasAttribute('async'),
'sync load is default');
done();
});
});
test('importHref sync loading', function(done) {
Polymer.Base.importHref(url, function(e) {
assert.isTrue(e.target.hasAttribute('async'), 'async load');
done();
}, null, true);
});
});
});
</script>

View File

@ -12,12 +12,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<dom-module id="dynamic-element">
<template>
<span id="content">dynamic-element</span> :
<span id="content">dynamic-element</span> :
</template>
</dom-module>
<script>
Polymer({
is: 'dynamic-element',
ready: function() {
@ -42,4 +42,4 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
});
</script>
</script>

View File

@ -93,3 +93,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
</script>
</dom-module>
<dom-module id="x-double">
<script>
Polymer({
is: 'x-double',
behaviors: [EventLoggerImpl],
setup: function() {
this.listen(this, 'foo', 'missing');
this.listen(this, 'foo', 'missing');
},
teardown: function() {
this.unlisten(this, 'foo', 'missing');
}
});
</script>
</dom-module>

Some files were not shown because too many files have changed in this diff Show More