Am I reading this right? If Shady DOM doesn't have event retargeting, does this mean if you need retargeting, and you support any browsers that don't have ShadowDOM (aka iPhones), you'll always have to do it manually? https://www.polymer-project.org/1.0/docs/devguide/events#retargeting
The problem I'm trying to solve is event communication between Polymer customer elements. I want some elements to be consumers of events, and others to be producers. However, browsers such as Safari are on our supported list, which I believe means they'll be using shady DOM.
So a code example, would go something like this:
<wut-action async remote="false" file="/actions/saveInventory.js">
<wut-button class="saveInventory">
</wut-button>
</wut-action>
I want to create a <wut-button> custom element that produces a doAction event on tap. And I want <wut-action> to consume the doAction event (listener). I assume because of bubbling, I'll need to do the composition as shown above.
I'm concerned after reading the docs that this design won't work. Thoughts?
In my tests, listening to events from distributed children is supported in both Shady and Shadow DOM. Your design would work across browsers. I verified this on Chrome 54, Firefox 49, and Safari 10.
Assuming wut-button fires an event named "x-event", wut-action could listen to that event like this:
Polymer({
is: 'wut-action',
listeners: {
'x-event': '_onEvent'
},
_onEvent: function(e) {
console.log('event', e.detail);
}
});
codepen
I'm a bit amazed I haven't been able to find an explanation for how to do this as it seems like it's fairly elemental to debugging, but I can't find anywhere how to print the attributes of an object in Polymer.
I'm learning Polymer and I keep running into situations where I have an object, but I have no idea what the attributes are of the object. (Ex. I print to the window, and I get [object Object]. I've found some explanations for how to print a list of the keys/attributes of an object (I know how to print the values for those keys if I know what they are), but I have no idea how to get the keys if I don't already know the format of my data. Every example presumes you already know what the attributes are.
I've seen solutions recommending adding a script like:
getKeys : function(o){
return Object.keys(o);
}
And then they recommend something like this:
<template is="dom-repeat" items="{{ item in obj | getKeys}}">
{{item}}
</template>
But I think they must work off maybe an earlier version of polymer. Most are from 2014-ish and I know the library has changed a lot since then.
This is the closest thing I get to an error with this code:
Polymer::Attributes: couldn`t decode Array as JSON
Here's an example post recommending this strategy. I understand I could dig deeper into the documentation and try to understand what response is supposed to be coming back, but I'm more curious what the general strategy is for this situation - I've multiple times wanted to check to see how polymer was modeling something vs how I thought it was.
The post you mention recommends a method that is no longer possible with post-1.0 Polymer, which does not support that syntax of filtering/pipes (as of the current release, 1.5.0).
You could use DevTools to select the Polymer element and then run console.dir($0). This works in the following browsers (and maybe older versions):
Chrome 50
Firefox 45
Safari 9.1
Opera 39
Chrome and Opera display all keys (even inherited ones from HTMLElement) in sorted order, so it can be tedious to scan through the long list of keys for a Polymer-specific property. However, Firefox and Safari list Polymer-specific keys first and then the inherited ones.
One workaround for Chrome/Opera is to use this snippet:
((o) => {
let obj = {};
Object.keys(o).sort().forEach((x) => {
obj[x] = o[x];
});
console.dir(obj);
})($0);
Here's a codepen that logs the attributes of a paper-button. You don't need to click the button. Open the browser's console log (not the Codepen console) to see something like the screenshot below. You can expand the fields in the console log to see the attributes of the Polymer object.
The solution I have been using is the following:
Place a button somewhere on the visible page.
When that button is tapped, print the object to the console.
my-element.html
<button on-tap="show">Click here to see user</button>
...
show: function() {
console.log('user', this.user);
},
...
You can also use console.dir() as follows.
<my-element id="foo"></my-element>
...
bar: function() {
console.dir( this.$.foo );
}
In Polymer 1.0 you can drop {{localPropFoo.bar}} and bar will be observed for changes if you use this.set('localPropFoo.bar', 'new value'); to update its value.
But what to do if you want to bind template to an external object which is out of your control? E.g., this {{window.globalFoo.bar}} won't be bound to bar changes because external code doesn't depend on Polymer and doesn't call this.set.
Demo on codepen
Using Object.observe manually requires extra code and doesn't work in FireFox (dirty checks of observe.js to the rescue).
I want to know what is the idiomatic way of data binding to external objects out of your control.
Polymer doesn't do observation out of the box, because:
Object.observe support is not ubiquitous and dirty checks are expensive.
Object.observe may be expensive on itself.
Supposed Solution
Catch changes yourself, call notifyPath, this.fire('global-foo-changed', {path: 'globalFoo.bar', value:...}, this.set and this.push.
They all dispatch corresponding non-bubbling (capturing) globalFoo-changed custom events (only when needed).
Why my global-foo-changed events affect only this element?
global-foo-changed events are capturing (non-bubbling).
Polymer elements listen for bubbling events by default.
For some reason these capturing listeners capture bubble events dispatched from the same element (not from its children). Demo on codepen.
You may patch polymer with this behavior (I don't understand how it works):
SharedGlobalsBehavior = {
properties: {
globalFoo: {
type: Object,
notify: true,
value: globalFoo
}
},
created: function() {
window.addEventListener('global-foo-changed', () => {
if (!event.detail || !event.detail.path)
return; // Property initialization.
this.notifyPath(event.detail.path, event.detail.value);
},/* if capturing */ true);
}
};
Why No Observation Out of the Box
...sometimes imperative code needs to change an object’s sub- properties directly. As we avoid more sophisticated observation mechanisms such as Object.observe or dirty-checking in order to achieve the best startup and runtime performance cross-platform for the most common use cases, changing an object’s sub-properties directly requires cooperation from the user.
Specifically, Polymer provides two methods that allow such changes to be notified to the system: notifyPath(path, value) and set(path, value), where path is a string identifying the path (relative to the host element).
First of all, I know there's libraries that provide polyfills for location.pushState/popState (History.js, Hash.js, jQuery hashchange), so please don't just link to those.
I need a more powerful library to achieve the following in a RIA:
User clicks a link
library is notified and loads context via Ajax (no complete reload!)
All <a> elements are leveraged with a click handler that
prevents page reloads in 2. (preventDefault) and
calls location.pushState instead / sets location.hash for older browsers
loaded content is inserted in page and replaces current content
Continue with 1.
Also, previously loaded content should be restored as the user navigates back.
As an example, klick through Google+ in Internet Explorer <10 and any other browser.
Is there anything that comes even close? I need support for IE8, FF10, Safari 5 and Chrome 18. Also, it should have a permissive license like MIT or Apache.
I believe Sammy.js ( http://sammyjs.org) (MIT-licenced) has the best focus on what you want to do, with its 2 main pillars being:
Routes
Events
I could quote from the docs but it's pretty straightforward:
setup clientside routes that relate to stuff to be done, e.g: update the view through ajax
link events to call routes, e.g: call the route above when I click an link. (You would have to make sure e.preventDefault is called in the defined event I believe, since this is an app decision really, so that can't be abstracted away by any library that you're going to use imho)
Some relevant docs
http://sammyjs.org/docs
http://sammyjs.org/docs/routes
http://sammyjs.org/docs/events
Example for a route: (from http://sammyjs.org/docs/tutorials/json_store_1)
this.get('#/', function(context) {
$.ajax({
url: 'data/items.json',
dataType: 'json',
success: function(items) {
$.each(items, function(i, item) {
context.log(item.title, '-', item.artist);
});
}
});
});
Or something like
this.get('#/', function(context) {
context.app.swap(''); ///the 'swap' here indicates a cleaning of the view
//before partials are loaded, effectively rerendering the entire screen. NOt doing the swap enables you to do infinite-scrolling / appending style, etc.
// ...
});
Of course other clientside MVC-frameworks could be an option too, which take away even more plumbing, but might be overkill in this situation.
a pretty good (and still fairly recent) comparison:
http://codebrief.com/2012/01/the-top-10-javascript-mvc-frameworks-reviewed/
( I use Spine.js myself ) .
Lastly, I thought it might be useful to include an answer I've written a while ago that goes into detail to the whole best-practice (as I see it) in client-side refreshes, etc. Perhaps you find it useful:
Accessibility and all these JavaScript frameworks
I currently use PathJS in one of my applications.
It has been the best decision that i have made.
For your particular usecase take a look at HTML5 Example.
The piece of code that that makes the example work (from the source):
<script type="text/javascript">
// This example makes use of the jQuery library.
// You can use any methods as actions in PathJS. You can define them as I do below,
// assign them to variables, or use anonymous functions. The choice is yours.
function notFound(){
$("#output .content").html("404 Not Found");
$("#output .content").addClass("error");
}
function setPageBackground(){
$("#output .content").removeClass("error");
}
// Here we define our routes. You'll notice that I only define three routes, even
// though there are four links. Each route has an action assigned to it (via the
// `to` method, as well as an `enter` method. The `enter` method is called before
// the route is performed, which allows you to do any setup you need (changes classes,
// performing AJAX calls, adding animations, etc.
Path.map("/users").to(function(){
$("#output .content").html("Users");
}).enter(setPageBackground);
Path.map("/about").to(function(){
$("#output .content").html("About");
}).enter(setPageBackground);
Path.map("/contact").to(function(){
$("#output .content").html("Contact");
}).enter(setPageBackground);
// The `Path.rescue()` method takes a function as an argument, and will be called when
// a route is activated that you have not yet defined an action for. On this example
// page, you'll notice there is no defined route for the "Unicorns!?" link. Since no
// route is defined, it calls this method instead.
Path.rescue(notFound);
$(document).ready(function(){
// This line is used to start the HTML5 PathJS listener. This will modify the
// `window.onpopstate` method accordingly, check that HTML5 is supported, and
// fall back to hashtags if you tell it to. Calling it with no arguments will
// cause it to do nothing if HTML5 is not supported
Path.history.listen();
// If you would like it to gracefully fallback to Hashtags in the event that HTML5
// isn't supported, just pass `true` into the method.
// Path.history.listen(true);
$("a").click(function(event){
event.preventDefault();
// To make use of the HTML5 History API, you need to tell your click events to
// add to the history stack by calling the `Path.history.pushState` method. This
// method is analogous to the regular `window.history.pushState` method, but
// wraps calls to it around the PathJS dispatched. Conveniently, you'll still have
// access to any state data you assign to it as if you had manually set it via
// the standard methods.
Path.history.pushState({}, "", $(this).attr("href"));
});
});
</script>
PathJS has some of the most wanted features of a routing library:
Lightweight
Supports the HTML5 History API, the 'onhashchange' method, and graceful degredation
Supports root routes, rescue methods, paramaterized routes, optional route components (dynamic routes), and Aspect Oriented Programming
Well Tested (tests available in the ./tests directory)
Compatible with all major browsers (Tested on Firefox 3.6, Firefox 4.0, Firefox 5.0, Chrome 9, Opera 11, IE7, IE8, IE9)
Independant of all third party libraries, but plays nice with all of them
I found the last too points most attractive.
You can find them here
I hope you find this useful.
i'd like to suggest a combination of
crossroads.js as a router
http://millermedeiros.github.com/crossroads.js/
and hasher for handling browser history and hash urls (w/ plenty of fallback solutions):
https://github.com/millermedeiros/hasher/
(based on http://millermedeiros.github.com/js-signals/)
This will still require a few lines of code (to load ajax content etc.), but give you loads and loads of other possibilities when handling a route.
Here's an example using jQuery (none of the above libraries require jQuery, i'm just lazy...)
http://fiddle.jshell.net/Fe5Kz/2/show/light
HTML
<ul id="menu">
<li>
foo
</li>
<li>
bar/baz
</li>
</ul>
<div id="content"></div>
JS
//register routes
crossroads.addRoute('foo', function() {
$('#content').html('this could be ajax loaded content or whatever');
});
crossroads.addRoute('bar/{baz}', function(baz) {
//maybe do something with the parameter ...
//$('#content').load('ajax_url?baz='+baz, function(){
// $('#content').html('bar route called with parameter ' + baz);
//});
$('#content').html('bar route called with parameter ' + baz);
});
//setup hash handling
function parseHash(newHash, oldHash) {
crossroads.parse(newHash);
}
hasher.initialized.add(parseHash);
hasher.changed.add(parseHash);
hasher.init();
//add click listener to menu items
$('#menu li a').on('click', function(e) {
e.preventDefault();
$('#menu a').removeClass('active');
$(this).addClass('active');
hasher.setHash($(this).attr('href'));
});
Have you looked at the BigShelf sample SPA (Single Page Application) from Microsoft? It sounds like it covers how to achieve most of what you're asking.
It makes use of History.js, a custom wrapper object to easily control navigation called NavHistory and Knockout.js for click handling.
Here's an extremely abbreviated workflow of how this works: first you'll need to initialize a NavHistory object which wraps history.js and registers a callback which executes when there is a push state or hash change:
var nav = new NavHistory({
params: { page: 1, filter: "all", ... etc ... },
onNavigate: function (navEntry) {
// Respond to the incoming sort/page/filter parameters
// by updating booksDataSource and re-querying the server
}
});
Next, you'll define one or more Knockout.js view models with commands that can be bound to links buttons, etc:
var ViewModel = function (nav) {
this.search = function () {
nav.navigate({ page: 2, filter: '', ... }); // JSON object matching the NavHistory params
};
}
Finally, in your markup, you'll use Knockout.js to bind your commands to various elements:
<a data-bind="click: search">...</a>
The linked resources are much more detailed in explaining how all of this works. Unfortunately, it's not a single framework like you're seeking, but you'd be surprised how easy it is to get this working.
One more thing, following the BigShelf example, the site I'm building is fully cross-browser compatible, IE6+, Firefox, Safari (mobile and desktop) and Chrome (mobile and desktop).
The AjaxTCR Library seems to cover all bases and contains robust methods that I haven't seen before. It's released under a BSD License (Open Source Initiative).
For example, here are five AjaxTCR.history(); methods:
init(onStateChangeCallback, initState);
addToHistory(id, data, title, url, options);
getAll();
getPosition();
enableBackGuard(message, immediate);
The above addToHistory(); has enough parameters to allow for deep hash-linking in websites.
More eye-candy of .com.cookie(), .storage(), and .template() provides more than enough methods to handle any session data requirements.
The well documented AjaxTCR API webpage has a plethora of information with downloadable doc's to boot!
Status Update:
That website also has an Examples Webpage Section including downloadable .zip files with ready to use Front End(Client) and Back End(Server) project files.
Notably are the following ready-to-use examples:
One-way Cookie
HttpOnly Cookies
History Stealing
History Explorer
There are quite a bit other examples that rounds out the process to use many of their API methods, making any small learning curve faster to complete.
Several suggestions
ExtJs, see their History Example, and here are the docs.
YUI Browser History Manager.
jQuery BBQ seem to provide a more advanced feature-set over jQuery.hashcode.
ReallySimpleHistory may also be of help, though it's quite old and possibly outdated.
Note: ExtJs History has been extended to optimize duplicate (redundant) calls to add().
PJAX is the process you're describing.
The more advanced pjax techniques will even start to preload the content, when the user hovers over the link.
This is a good pjax library.
https://github.com/MoOx/pjax
You mark the containers which need will be updated on the subsequent requests:
new Pjax({ selectors: ["title", ".my-Header", ".my-Content", ".my-Sidebar"] })
So in the above, only the title, the .my-header, .my-content, and .my-sidebar will be replaced with the content from the ajax call.
Somethings to look out for
Pay attention to how your JS loads and detects when the page is ready. The javascript will not reload on new pages. Also pay attention to when any analytics calls get called, for the same reason.
I want to execute a chrome script,when the content of webpage is update (E.g like Facebook)
So what method I should use for that?
You can use
$("body").bind("DOMSubtreeModified", function() {
alert("something changed");
});
Note: DOMSubtreeModified is deprecated
You should try it with chrome.* API:
chrome.tabs.onUpdated.addListener(
function(integer tabId, object changeInfo, Tab tab) {
//your code
});
See here for documentation:
http://code.google.com/chrome/extensions/tabs.html#event-onUpdated
It should work for updates like (AJAX) requests etc...
As sachleen said, you can use DOMSubtreeModified listener
document.addEventListener("DOMSubtreeModified", methodToRun);
Note that the Mutation Events are performance hogs which can't really be tamed well (they fire too often and slow down the page a lot). Therefore, they have been deprecated over a year ago and should be used only when really needed. However, they work.
If you want this for a Chrome extension, you could use the new and shiny Mutation Observers from DOM Level 4 (follow the links there, they explain a lot!). Where DOMSubtreeModified fired a thousand times, MutationObserver fires only once with all the modifications contained and accessible.
Works for (as of 2012/06):
Chrome 18+ (prefixed, window.WebKitMutationObserver)
Firefox 14+ (unprefixed)
WebKit nightlies