What is closing the EntityManagerFactory in Worklight? - jquery-mobile

I'm experimenting with form based authentication on the IBM Worklight platform, and I'm currently following a tutorial located here.
I have some wierd behaviour whereby the first time I attempt a login it will just reload the login page, but if I try a second time with the exact same details, it works. This behaviour is consistent and I'm able to reproduce it every time.
Looking closer at the JS console, I can see this being printed :
openjpa-1.2.2-r422266:898935 nonfatal user error> org.apache.openjpa.persistence.InvalidStateException: The factory has been closed. The stack trace at which the factory was closed is available if Runtime=TRACE logging is enabled.
at org.apache.openjpa.kernel.AbstractBrokerFactory.assertOpen(AbstractBrokerFactory.java:673)
at org.apache.openjpa.kernel.AbstractBrokerFactory.newBroker(AbstractBrokerFactory.java:182)
at org.apache.openjpa.kernel.DelegatingBrokerFactory.newBroker(DelegatingBrokerFactory.java:142)
at org.apache.openjpa.persistence.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:192)
at org.apache.openjpa.persistence.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:145)
at org.apache.openjpa.persistence.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:56)
at sun.reflect.GeneratedMethodAccessor70.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean$ManagedEntityManagerFactoryInvocationHandler.invoke(AbstractEntityManagerFactoryBean.java:434)
at $Proxy36.createEntityManager(Unknown Source)
at org.springframework.orm.jpa.EntityManagerFactoryUtils.doGetTransactionalEntityManager(EntityManagerFactoryUtils.java:195)
at org.springframework.orm.jpa.EntityManagerFactoryUtils.getTransactionalEntityManager(EntityManagerFactoryUtils.java:142)
at org.springframework.orm.jpa.EntityManagerFactoryAccessor.getTransactionalEntityManager(EntityManagerFactoryAccessor.java:129)
at org.springframework.orm.jpa.JpaTemplate.execute(JpaTemplate.java:174)
at org.springframework.orm.jpa.JpaTemplate.executeFind(JpaTemplate.java:151)
at org.springframework.orm.jpa.JpaTemplate.findByNamedQuery(JpaTemplate.java:343)
at com.worklight.core.auth.impl.AuthenticationDAO.getAssociatedIdentities(AuthenticationDAO.java:93)
at com.worklight.core.auth.impl.AuthenticationContext$1.run(AuthenticationContext.java:513)
at com.worklight.core.auth.impl.AuthenticationContext$1.run(AuthenticationContext.java:492)
at com.worklight.core.util.RssBrokerUtils.doInTransaction(RssBrokerUtils.java:123)
at com.worklight.core.auth.impl.AuthenticationContext.saveAuthenticationResult(AuthenticationContext.java:492)
at com.worklight.core.auth.impl.AuthenticationContext.processRequest(AuthenticationContext.java:244)
at com.worklight.core.auth.impl.AuthenticationFilter.doFilter(AuthenticationFilter.java:99)
at org.eclipse.equinox.http.servlet.internal.FilterRegistration.doFilter(FilterRegistration.java:81)
at org.eclipse.equinox.http.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:35)
at com.worklight.gadgets.serving.filters.AuthenticityFilter.doFilter(AuthenticityFilter.java:74)
at org.eclipse.equinox.http.servlet.internal.FilterRegistration.doFilter(FilterRegistration.java:81)
at org.eclipse.equinox.http.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:35)
at com.worklight.gadgets.serving.filters.InstanceAuthenticationFilter.doFilter(InstanceAuthenticationFilter.java:65)
at org.eclipse.equinox.http.servlet.internal.FilterRegistration.doFilter(FilterRegistration.java:81)
at org.eclipse.equinox.http.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:35)
at org.eclipse.equinox.http.servlet.internal.ProxyServlet.processAlias(ProxyServlet.java:130)
at org.eclipse.equinox.http.servlet.internal.ProxyServlet.service(ProxyServlet.java:68)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
at org.eclipse.equinox.http.jetty.internal.HttpServerManager$InternalHttpServiceServlet.service(HttpServerManager.java:317)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:390)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:326)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:939)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Searching for that stacktrace reveals a previous question SO, whereby it has been suggested that something is calling close() on the EntityManagerFactory. I'm not sure where/why this is happening, since all of this would be managed under Worklight.
This is the JS I have for the authentication process, under js/auth.js
var Authenticator = function() {
var LOGIN_PAGE_SECURITY_INDICATOR = 'j_security_check';
var USERNAME_INPUT_ID = '#usernameInputField';
var PASSWORD_INPUT_ID = '#passwordInputField';
var LOGIN_BUTTON_ID = '#loginButton';
function onFormSubmit() {
WL.Logger.debug("Entering auth.js.onFormSubmit()");
var reqURL = './' + LOGIN_PAGE_SECURITY_INDICATOR;
var params = {
j_username : $(USERNAME_INPUT_ID).val(),
j_password : $(PASSWORD_INPUT_ID).val()
};
onSubmitCallback(reqURL, {
parameters : params
});
}
return {
init : function() {
WL.Logger.debug("Inside auth.js.init");
$(LOGIN_BUTTON_ID).bind('click', onFormSubmit);
},
isLoginFormResponse : function(response) {
WL.Logger.debug("Inside auth.js.isLoginFormResponse " + response.responseText);
if (!response || response.responseText == null) {
WL.Logger.debug("Entering auth.js.isLoginFormResponse (), return false");
return false;
}
var indicatorIdx = response.responseText.search(LOGIN_PAGE_SECURITY_INDICATOR);
WL.Logger.debug("Entering auth.js.isLoginFormResponse (), return " + (indicatorIdx >= 0));
return (indicatorIdx >= 0);
},
onBeforeLogin : function(response, username, onSubmit, onCancel) {
WL.Logger.debug("Inside auth.js.onBeforeLogin");
onSubmitCallback = onSubmit;
onCancelCallback = onCancel;
if (typeof (username) != 'undefined' && username != null) {
$(USERNAME_INPUT_ID).val(username);
} else {
$(USERNAME_INPUT_ID).val('');
}
$(PASSWORD_INPUT_ID).val('');
},
onShowLogin : function() {
WL.Logger.debug("Inside auth.js.onShowLogin");
$.mobile.changePage("#loginPage");
},
onHideLogin : function() {
WL.Logger.debug("Inside auth.js.onHideLogin");
$.mobile.changePage("#page3");
}
};
}();
Like I mentioned, the login fails on the first time, it just reloads the login form and I can see the above stack trace in the JS console, but the second time it works fine. I'm wondering if something is not being initialised correctly on the first attempt, but is fine for the second. Can anyone suggest what is wrong?
These are the page elements that I'm using :
<div data-role="page" id="page3">
<div data-theme="a" data-role="header">
<h3>Autenticated Page - Page3</h3>
</div>
<div data-role="content">
<h3>You are logged in -Page3</h3>
Go to page1
</div>
<div data-theme="a" data-role="footer">
<input type="button" value="Logout"
onclick="WL.Client.logout('SampleAppRealm', {onSuccess: WL.Client.reloadApp});" />
</div>
</div>
<div data-role="page" id="loginPage">
<div data-theme="a" data-role="header">
<h3>Hello JQuery Mobile</h3>
</div>
<div data-role="content">
<div id="loginForm">
Username:<br/>
<input type="text" id="usernameInputField" autocorrect="off" autocapitalize="off" /><br />
Password:<br/>
<input type="password" id="passwordInputField" autocorrect="off" autocapitalize="off"/><br/>
<input type="button" id="loginButton" value="Login" />
</div>
</div>
<div data-theme="a" data-role="footer">
<h3>Copyright stuff</h3>
</div>
</div>

I don't know about the EntityManagerFactory bit, but it doesn't sound related.
From reading the scenario in the question, it sounds like a login attempt is taking place prior to having the app successfully connect to the Worklight Server. Make sure your application first connects connects to the Worklight Server, and only after doing so, the login will take place.
Use either connectOnStartup:true in initOptions.js or utilize WL.Client.Connect as soon as the app has finished loading.
Also, read these IBM Worklight Getting Started training modules.

Related

ionic contact select not working correctly

I have a problem in ionic when I want to open the contacts.
The funny thing is that contacts only open when before clicking to get_contacts() I tab into/activate the textarea. After that the function get_contacts() is working fine - contacts popup as it should.
But only clicking the button (I added two in my container) does not open the contacts - nothing happens. Why????
Here is my code of the container:
<div ng-repeat="message in messages" class="message-wrapper rlt"
on-hold="onMessageHold($event, $index, message)">
....
</div>
<form name="sendMessageForm" ng-submit="sendMessage(sendMessageForm)" style="background-color: black;" novalidate>
<ion-footer-bar class="bar-stable message-footer" style="background-color: black;" keyboard-attach>
<div class="footer-btn-wrap">
<button class="button button-icon icon ion-android-attach" ng-click="choosePhoto()"></button>
</div>
<label class="item-input-wrapper">
<textarea ng-model="input.message" value="" placeholder="Leave a message..."
required minlength="1" maxlength="1500" msd-elastic autofocus></textarea>
</label>
<div class="footer-btn-wrap">
<button class="button button-icon icon ion-android-send footer-btn" type="submit"
ng-disabled="!input.message || input.message === ''">
</button>
<button class="button button-icon icon ion-ios-search-strong" ng-click="get_contacts()">
</button>
</div>
</ion-footer-bar>
</form>
And this is my js......
document.addEventListener('deviceready', function () {
$scope.get_contacts = function (){
navigator.contacts.pickContact(function(contact){
console.log('The following contact has been selected:' + JSON.stringify(contact));
var mobilenr = " no mobile nr ";
var jsonObj = JSON.parse(JSON.stringify(contact));
for (i = 0; i < jsonObj.phoneNumbers.length; i++) {
var phoneNumber = contact.phoneNumbers[i];
if (phoneNumber.type == 'mobile')
{
mobilenr = phoneNumber.value;
}
}
$scope.itemsList.push({"name":contact.name.formatted, "room":mobilenr});
localStorage.setItem("phonecounter",+localStorage.getItem("phonecounter")+1);
},function(err){
console.log('Error: ' + err);
});
}
}, false)
I'm not able to get this thing working, googled and tried almost everything.
Does anybody have a hint?
I tested on iOS - but this should work in any environment, as I understand. Or not?
Thank you in advance - very much

select2 4.0 - TypeError: $(...).select2(...).select2(...) is undefined

Trying to finally change over to the 4.0 version of select2 and running into a problem.
All of this works perfectly fine on 3.5. On button click I open a bootstrap modal and load a remote page into it. I have tested all everything on a normal page (not in a modal) and it works correctly. When loaded in a modal as below the modal opens and everything like it always had for 3.5, but select2 is returning an error. I believe the issue here is the select2 is being loaded from a remote page... thing is this same remote loading method always worked flawlessly with 3.5.
TypeError: $(...).select2(...).select2(...) is undefined
js:
// show edit modal
$('#datatable2').on('click', '.dtEdit', function () {
var data = {
'filter_id': $(this).parents('tr').attr('id').replace('dtrow_', '')
};
$('#modal-ajax').load(
'/modals/m_filtering_applications_filters.php',
data,
function() {
$(this).modal('show');
changeSettings();
hourSelection();
}
);
});
// change filter modal confirmation
var changeSettings = function() {
// get the default filter
var default_filter = $("#filter_default").val();
//app list
$("#vedit-filter").select2({
placeholder: {
id: default_filter, // or whatever the placeholder value is
text: default_filter // the text to display as the placeholder
},
allowClear: true,
multiple: false,
tags: true,
createTag: function (query) {
return {
id: query.term,
text: query.term,
tag: true
}
},
ajax: {
dataType: 'json',
delay: 500,
type: 'post',
url: '/process/get_application_list.php',
data: function (params) {
return {
term: params.term, // search term
page: params.page, //page number
page_limit: 25, // page size
};
},
results: function (data, page) {
var more = (page * 25) < data.total; // whether or not there are more results available
return {
results: data.results,
more: more
};
}
}
}).select2('val', [default_filter]).on('change', function() {
$(this).valid();
});
}
m_filtering_applications_filters.php :
Just the contents of the modal which is loaded in :
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"></button>
<h3 class="modal-title">Change these settings?</h3>
</div>
<form id="application-filters-edit">
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-12 margin-bottom-30 form-group">
<div class="input-modal-group">
<label for="vedit-filter" class="f-14"><b>Application to filter :</b></label>
<select id="vedit-filter" name="settings[filter]" class="form-control select2">
<option value="<?php echo htmlspecialchars($result['filter'], ENT_QUOTES, 'UTF-8'); ?>" selected=""><?php echo htmlspecialchars($result['filter'], ENT_QUOTES, 'UTF-8'); ?></option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<input type="hidden" name="settings[users][]" value="<?php echo $result['user_id']; ?>"/>
<input id="filter_default" type="hidden" name="settings[original]" value="<?php echo htmlspecialchars($result['filter'], ENT_QUOTES, 'UTF-8'); ?>"/>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<?php
if (!$result)
{
//disable the button
echo '<button type="button" class="btn btn-primary disabled" data-dismiss="modal"><i class="fa fa-check-circle"></i> Save Settings</button>';
}
else
{
// show the button
echo '<button class="btn btn-primary" type="submit"><i class="fa fa-check-circle"></i> Save Settings</button>';
}
?>
</div>
</form>
</div>
</div>
UPDATE:
Okay, the error is coming from :
}).select2('val', [default_filter]).on('change', function() {
$(this).valid();
});
attached to the end... this is for using the jquery validation script (I did not include this in the jS here), and again, worked fine in 3.5. Can you not add on to the select2() anymore with 4.0 or something?
When removing this line the error goes away, but the display of the select2 is very small in width and I cannot gain focus on the search box to enter any values so it is still unusable within the modal.
UPDATE2:
Realized the events changed with 4.0 so this seems to work :
}).on("change", function (e) {
$(this).valid();
});
Still cannot enter anything into the search box though. Also, I notice if I click on anything other than the arrow in the input box to show the dropdown it acts as if the mouse is being held down - it highlights all the text/content within the modal.
Solution : All the issues I was having with the select2 in my bs3 modal were solved by adding the following in my js :
$.fn.modal.Constructor.prototype.enforceFocus = $.noop;
The highlighting of content/text is gone. The focus on the search box is gone. For now everything seems to work great. I do not have any information as to what this line actually does at the moment, but will be researching this to find out and will add it back here for future reference for anyone.

jquerymobile - navigation in multi-page template

I have a multi-page template set up. The first page has a set of links to the inner pages - however all but one link to the same #template page which loads data dynamically. The other link is to a form which I have built within the page.
Problem I'm having is when you navigate to one of the links, continue through the pages and then use the back button to get back to the first page, if you then click the link to the form it doesn't navigate to the #page, it goes to the last page you were on in the other links.
Hope that makes sense - maybe a diagram will make it clearer
home > product list > product detail
product detail > product list > home (back button)
home > contact form
This last step shows the product detail page again, instead of the contact form. It's as if it's not clearing the page out:
My code (truncated for clarity):
<script type="text/javascript">
$(document).bind("mobileinit", function () {
$.mobile.allowCrossDomainPages = true;
$.support.cors = true;
});
$(document).delegate("#pageDetail", "pagecreate", function () {
$(this).css('background', '#ECF2FE');//`this` refers to `#pageDetail`
});
$(document).bind("pagebeforechange", function (e, data) {
// We only want to handle changePage() calls where the caller is
// asking us to load a page by URL.
if (typeof data.toPage === "string") {
$.mobile.pageData = (data && data.options && data.options.pageData) ? data.options.pageData : null;
// We are being asked to load a page by URL, but we only
// want to handle URLs that request the data for a specific
// category.
var page;
var type;
var cat;
if ($.mobile.pageData && $.mobile.pageData.type) {
type = $.mobile.pageData.type;
}
if ($.mobile.pageData && $.mobile.pageData.cat) {
cat = $.mobile.pageData.cat;
}
if ($.mobile.pageData && $.mobile.pageData.page) {
page = $.mobile.pageData.page;
}
var url = $.url(data.toPage); // page url
var hash = url.fsegment(1).replace(/&.*/, ""); // nav hash for page holder
switch (hash) {
case "pageList":
$.ajax({
url: "http://localhost/myapp/" + type + ".aspx?type=" + type,
datatype: "html",
success: function (data) {
$('.submenu').html(data);
$('.title').html(type);
$('.submenu').trigger('create');
}
});
break;
case "pageDetail":
$('.detail').load('http://localhost/myapp/' + type + '.aspx?page=' + page + '&type=' + type + ' #contentdiv', function () {
$(this).trigger('create');
});
break;
default:
break;
}
}
});
</script>
</head>
HTML bits
<body>
<div data-role="page" id="home">
<div data-role="content">
<img src="images/Icon-Courses.png" alt="courses" /><br />
<img src="images/Icon-Contact.png" alt="contact" />
</div>
</div>
<div data-role="page" data-theme="a" id="pageList" data-add-back-btn="true" style="background-color:#F0F0F0 !important;">
<div data-role="header" data-position="fixed">
<h1 class="title">Products</h1>
</div>
<div data-role="content" class="submenu">
<!-- content gets put in here -->
</div>
</div>
<div data-role="page" data-theme="a" id="pageDetail" data-add-back-btn="true" style="background-color:#F0F0F0 !important;">
<div data-role="header" data-position="fixed">
<h1 class="title">Products</h1>
</div>
<div data-role="content" class="submenu">
<!-- content gets put in here -->
</div>
</div>
<div data-role="page" data-theme="a" id="contactForm" data-add-back-btn="true" class="detailpage" style="background-color:#F0F0F0 !important;">
<div data-role="header" data-position="fixed">
<h1 class="title">Contact Us</h1>
</div>
<div data-role="content" class="detail">
<div id="contentdiv" style="margin:10px auto;width:90%;">
<label for="name">Your Name</label>
<input type="text" id="name" style="width:90%;" data-mini="true" />
<label for="email">Email Address</label>
<input type="text" id="email" style="width:90%;" data-mini="true" />
<label for="phone">Phone Number</label>
<input type="text" id="phone" style="width:90%;" data-mini="true" />
<label for="comments">Enquiry / Comments</label>
<textarea id="comments" rows="80" style="width:90%;" data-mini="true"></textarea>
<br />
<input type="submit" id="submit" value="Send Enquiry Now" />
</div>
</div>
</div>
</body>
</html>
I'm using the jqm.page.params.js for handling the querystring parameters.
Is there anything obvious there? The links to the #pageDetail page are returned within the content shown on #pageList.
Don't understand why I can't seem to clear the cache and it doesn't navigate correctly to the contact form if you've been anywhere else first. if you go straight there it works fine.
Anyone shed any light? BTW this needs to work using PhoneGap as a standalone
Thanks
Actually feel a bit dim about this, but it's because I had the same class names on multiple divs and it was using those to import data - so when it was loading data remotely it was putting it in more than one place, overwriting the form at the same time...

Change position of input filed for jQuery Mobile Filter List

Im using the jQuery Mobile Filter List:
http://jquerymobile.com/test/docs/lists/lists-search-with-dividers.html
Is it possible to move the position of the input field so I can put it into a div already on my page?
Using jQuery's appendTo seems to work fine but its kind of a hacky solution. Thanks
this is what I found while searching for solution to a similar problem.
HTML code example:
<div data-role="page" id="page-id">
<div data-role="content">
<div data-role="fieldcontain">
<input type="search" name="password" id="search" value="" />
</div>
<div class="filter-div" data-filter="one">1</div>
<div class="filter-div" data-filter="two">2</div>
<div class="filter-div" data-filter="three">3</div>
<div class="filter-div" data-filter="four">4</div>
<div class="filter-div" data-filter="five">5</div>
</div>
</div>
JS code example:
$(document).delegate('#page-id', 'pageinit', function () {
var $filterDivs = $('.filter-div');
$('#search').bind('keyup change', function () {
if (this.value == '') {
$filterDivs.slideDown(500);
} else {
var regxp = new RegExp(this.value),
$show = $filterDivs.filter(function () {
return ($(this).attr('data-filter').search(regxp) > -1);
});
$filterDivs.not($show).slideUp(500);
$show.slideDown(500);
}
});
});
This way you can place your input text box anywhere on your page and it should properly filter a list of items you want.
JSFiddle DEMO: http://jsfiddle.net/cZW5r/30/

jQuery Mobile: Injected content appears then disappears immediately

I have a login page using jQuery Mobile which contains the following code:
<div id="loginPage" data-role="page" data-theme="a">
<div data-role="content">
<div id="alerts"></div>
<form id="login-form">
<input type="text" id="username" name="username" value="" placeholder="username or email" />
<input type="password" id="password" name="password" value="" placeholder="password" />
<button id="login-button" onClick="userLogin()">Login</button>
</form>
</div><!-- /content -->
</div><!-- /page -->
Here is a part of my javascript that is called when the user clicks the 'Login' button. If one of the fields is left blank, I see the following text injected into the #alerts div, but then within a fraction of a second the content has disappeared again.
if (username.length == 0 || password.length == 0) {
//alert('Please enter your username or email and your password');
$('#alerts').html('Please enter your username or email and your password.').trigger('create');
}
I also tried this using .append() instead of .html(). Same result with both. I've commented out my test alert(), which works when one of the fields is left blank.
What can I do to make sure the content remains on the page once it is injected?
Thank you for any help or insight you can offer! -Mark
Per Jasper's request, here is all of the javascript that is executed when the 'Login' button is clicked:
function userLogin() {
var username = $("#username").val();
var password = $("#password").val();
if (username.length == 0 || password.length == 0) {
$('#alerts').append('Please enter your username or email and your password.').trigger('create');
}
else {
$.post("services/user-status.php", { type: 'login', username: username, password: password },
function(data) {
var response = data.item;
console.log(response);
if (response.loggedIn == false) {
$('#alerts').html('The username/email and password you used did not work. Please try again.').trigger('create');
}
else {
localStorage.userID = response.userID;
localStorage.username = response.username;
localStorage.userStatus = 'loggedIn';
$.mobile.changePage('profile.html');
}
},'json');
}
}
It looks like you need to stop the propagation of the click event from firing for your button. You can do that by returning false in the click event handler:
HTML --
<button id="login-button" onClick="return userLogin()">Login</button>
JS --
function userLogin() {
...
return false;
}​
Here is a demo: http://jsfiddle.net/BkMEB/3/
Also, since you are using jQuery, you can bind to the <button> element like this:
$('#login-button').bind('click', userLogin);
This is the same as putting onClick="return userLogin()" as an attribute of the button but allows you to remove your inline JS.
Here is a demo: http://jsfiddle.net/BkMEB/4/

Resources