Anchor link to open jquery mobile popups just redirects - ruby-on-rails

I previously used javascript dialogs for confirmation on a mobile web app, but am now trying to switch over to using the new popups feature in JQM 1.2. My initial test doesn't work - no popup appears and I'm simply redirected to the anchor I'm trying to call.
My test code is simple, albeit a bit obfuscated because I'm using haml:
%a{:href => "#popupBasic", :"data-rel" => "popup"} Show popup
%div{:id => "popupBasic", :"data-role" => "popup"} Basic popup div
That said, I don't believe the haml is causing the issue based on reading the final HTML output. Both elements are at equal depth and contained within the element.
In addition, the div does "popup" without issue when I use the following at the console:
$( "#popupBasic" ).popup( "open" )
That makes me believe that the issue lies somewhere in the link or the URL handling. When I do click the link, it instead takes me straight to
http://localhost:3000/#popupBasic
Any ideas on how I should be handling the URL differently so that it shows the popup as intended?

After realizing the problem was probably some part of my Javascript, I went through and tried turning off each bit of javascript individually, until I figured out that the problem was with this in my application.js file:
$(document).bind("mobileinit", function(){
$.mobile.linkBindingEnabled = false;
});
which prevents all anchor click handling. Obviously now that I've removed this code the anchor links are working properly. Of course, that means I'm now left with trying to figure out why I added that in the first place and what I just broke by removing it...

Related

React Component not rendered properly with Turbolinks in Rails 5.1

I have a very simple Rails app with a react component that just displays "Hello" in an existing div element in a particular page (let's say the show page).
When I load the related page using its URL, it works. I see Hello on the page.
However, when I'm previously on another page (let's say the index page and then I go to the show page using Turbolinks, well, the component is not rendered, unless I go back and forth again. (going back to the index Page and coming back to the show page)
From here every time I go back and forth, I can say that the view is rendered twice more time.Not only twice but twice more time! (i.e. 2 times then 4, then 6 etc..)
I know that since in the same time I set the content of the div I output a message to the console.
In fact I guess that going back to the index page should still run the component code without the display since the div element is not on the index page. But why in a cumulative manner?
The problems I want to solve are:
To get the code run on the first request of the show page
To block the code from running in other pages (including the index page)
To get the code run once on subsequent requests of the show page
Here the exact steps and code I used (I'll try to be as concise as possible.)
I have a Rails 5.1 app with react installed with:
rails new myapp --webpack=react
I then create a simple Item scaffold to get some pages to play with:
rails generate scaffold Item name
I just add the following div element in the Show page (app/views/items/show.html.erb):
<div id=hello></div>
Webpacker already generated a Hello component (hello_react.jsx) that I modified as following in ordered to use the above div element. I changed the original 'DOMContentLoaded' event:
document.addEventListener('turbolinks:load', () => {
console.log("DOM loaded..");
var element = document.getElementById("hello");
if(element) {
ReactDOM.render(<Hello name="React" />, element)
}
})
I then added the following webpack script tag at the bottom of the previous view (app/views/items/show.html.erb):
<%= javascript_pack_tag("hello_react") %>
I then run the rails server and the webpack-dev-server using foreman start (installed by adding gem 'foreman' in the Gemfile) . Here is the content of the Procfile I used:
web: bin/rails server -b 0.0.0.0 -p 3000
webpack: bin/webpack-dev-server --port 8080 --hot
And here are the steps to follow to reproduce the described behavior:
Load the index page using the URL http://localhost:3000/items
Click New Item to add a new item. Rails redirects to the item's show page at the URL localhost:3000/items/1. Here we can see the Hello React! message. It works well!
Reload the index page using the URL http://localhost:3000/items. The item is displayed as expected.
Reload the show page using the URL http://localhost:3000/items/1. The Hello message is displayed as expected with one console message.
Reload the index page using the URL http://localhost:3000/items
Click to the Show link (should be performed via turbolink). The message is not shown neither the console message.
Click the Back link (should be performed via turbolink) to go to the index page.
Click again to the Show link (should be performed via turbolink). This time the message is well displayed. The console message for its part is shown twice.
From there each time I go back to the index and come back again to the show page displays two more messages at the console each time.
Note: Instead of using (and replacing) a particular div element, if I let the original hello_react file that append a div element, this behavior is even more noticeable.
Edit: Also, if I change the link_to links by including data: {turbolinks: false}. It works well. Just as we loaded the pages using the URLs in the browser address bar.
I don't know what I'm doing wrong..
Any ideas?
Edit: I put the code in the following repo if interested to try this:
https://github.com/sanjibukai/react-turbolinks-test
This is quite a complex issue, and I am afraid I don't think it has a straightforward answer. I will explain as best I can!
To get the code run on the first request of the show page
Your turbolinks:load event handler is not running because your code is run after the turbolinks:load event is triggered. Here is the flow:
User navigates to show page
turbolinks:load triggered
Script in body evaluated
So the turbolinks:load event handler won't be called (and therefore your React component won't be rendered) until the next page load.
To (partly) solve this you could remove the turbolinks:load event listener, and call render directly:
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(document.createElement('div'))
)
Alternatively you could use <%= content_for … %>/<%= yield %> to insert the script tag in the head. e.g. in your application.html.erb layout
…
<head>
…
<%= yield :javascript_pack %>
…
</head>
…
then in your show.html.erb:
<%= content_for :javascript_pack, javascript_pack_tag('hello_react') %>
In both cases, it is worth nothing that for any HTML you add to the page with JavaScript in a turbolinks:load block, you should remove it on turbolinks:before-cache to prevent duplication issues when revisiting pages. In your case, you might do something like:
var div = document.createElement('div')
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(div)
)
document.addEventListener('turbolinks:before-cache', function () {
ReactDOM.unmountComponentAtNode(div)
})
Even with all this, you may still encounter duplication issues when revisiting pages. I believe this is to do with the way in which previews are rendered, but I have not been able to fix it without disabling previews.
To get the code run once on subsequent requests of the show page
To block the code from running in other pages (including the index page)
As I have mentioned above, including page-specific scripts dynamically can create difficulties when using Turbolinks. Event listeners in a Turbolinks app behave very differently to that without Turbolinks, where each page gets a new document and therefore the event listeners are removed automatically. Unless you manually remove the event listener (e.g. on turbolinks:before-cache), every visit to that page will add yet another listener. What's more, if Turbolinks has cached that page, a turbolinks:load event will fire twice: once for the cached version, and another for the fresh copy. This is probably why you were seeing it rendered 2, 4, 6 times.
With this in mind, my best advice is to avoid adding page-specific scripts to run page-specific code. Instead, include all your scripts in your application.js manifest file, and use the elements on your page to determine whether a component gets mounted. Your example does something like this in the comments:
document.addEventListener('turbolinks:load', () => {
var element = document.getElementById("hello");
if(element) {
ReactDOM.render(<Hello name="React" />, element)
}
})
If this is included in your application.js, then any page with a #hello element will get the component.
Hope that helps!
I was struggling with similar problem (link_to helper method was changing URL but react content was not loaded; had to refresh page manually to load it properly). After some googling I've found simple workaround on this page.
<%= link_to "Foo", new_rabbit_path(#rabbit), data: { turbolinks: false } %>
Since this causes a full page refresh when the link is clicked, now my react pages are loaded properly. Maybe you will find it useful in your project as well :)
Upon what you said I tested some code.
First, I simply pull out the ReactDOM.render method from the listener as you suggested in your first snippet.
This provide a big step forward since the message is no longer displayed elsewhere (like in the index page) but only in the show page as wanted.
But something interesting happen in the show page. There is no more accumulation of the message as appended div element, which is good. In fact it's even displayed once as wanted. But.. The console message is displayed twice!?
I guess that something related to the caching mechanism is going on here, but since the message is supposed to be appended why it isn't displayed twice as the console message?
Putting aside this issue, this seems to work and I wonder why it's necessary in the first place to put the React rendering after the page is loaded (without Turbolinks there was the DOMContentLoaded event listener)?
I guess that this has do with unexpected rendering by javascript code executed when some DOM elements are yet to be loaded.
Then, I tried your alternative way using <%= content_for … %>/<%= yield %>.
And as you expected this give mitigate results ans some weird behavior.
When I load via the URL the index page and then go to the show page using the Turbolink, it works!
The div message as well as the console message are shown once.
Then if I go back (using Turbolink), the div message is gone and I got the ".. unmounted.." console message as wanted.
But from then on, whenever I go back to the show page, the div and the console message are both never displayed at all.
The only message that's displayed is the ".. unmounted.." console message whenever I go back to the index page.
Worse, if I load the show page using the URL, the div message is not displayed anymore!? The console message is displayed but I got an error regarding the div element (Cannot read property 'appenChild' of null).
I will not deny that I completely ignore what's happening here..
Lastly, I tried your last best advice and simply put the last code snippet in the HTML head.
Since this is jsx code, I don't know how to handle it within the Rails asset pipeline / file structure, so I put my javascript_pack_tag in the html head.
And indeed, this works well.
This time the code is executed everywhere so it makes sense to use page-specific element (as previously intended in the commented code).
The downside, is that this time the code could be messy unless I put all page-specific code inside if statements that test for the presence of the page-specific element.
However since Rails/Webpack has a good code structure, it should be easily manageable to put page-specific code into page-specific jsxfiles.
Nevertheless the benefit is that this time all the page-specific parts are rendered at the same time as the whole page, thus avoiding a display glitch that occurs otherwise.
I didn't address this issue at the first place, but indeed, I would like to know how to get page specific contents rendered at the same time as the whole page.
I don't know if this is possible when combining Turbolink with React (or any other framework).
But in conclusion I leave this question for later on.
Thank you for your contribution Dom..

jQuery Mobile breaks my site

I load jQuery Mobile on my site when I am only on a mobile touchscreen device. When I do though. It messes up everything. For example, select menus don't work quite right, as well, the words "loading, loading, undefined" appear at the bottom of the page. I know I am missing something but do not know what.
Any ideas on what I could be missing?
Thanks
EDIT: Okay, So I took out all scripts that I am running except for jQuery and jQuery Mobile. I call jQuery first, then jQuery Mobile. It still breaks aspects of the site.
What it breaks:
- I cannot navigate to any other page via the navbar, if I click on a nav item, and look in the url, the correct url appears (with a # in it) like: /#/about-us/ Then, it just redirects to the home page and the page goes white
Select menus have weird results. It prints out whatever is in the select right beside it. And if you in landscape mode on the ipad and you click on the select, it sends you to the bottom of the page (weird).
it prints out 'loading' twice and 'undefined' once at the bottom of the page
All I have for scripts are jQuery and jQuery Mobile. I should also mention that I am using wordpress so it might have enqueued some other scripts (I have deregistered Wordpress' version of jquery and enqueued my own)
Anyone else experiencing these problems?
jQueryMobile replace your normal links with Ajax one, so every page can be loaded by the ajax, text on docs page:
(..) Ajax is used to load the contents of each page into the DOM as you navigate, and the DOM ready handler only executes for the first page. To execute code whenever a new page is loaded and created, you can bind to the pageinit event. This event is explained in detail at the bottom of this page.
If you want to disable single link to be loaded by the ajax you should write something like this:
<a href="/some_page" data-ajax="false" >link</a>
or do it globally:
$(document).bind("mobileinit", function() {
$.mobile.ajaxEnabled = false;
});
jm also does replacement on other elements so you should try using data-role attribute, for example:
<select id="test" data-role="none">
to disable replacing this element.
For those like me where
$.mobile.ajaxEnabled = false;
did not work and the whole page layout seems still broken:
For me this one works (- set it inline before loading the jquery mobile file):
<script>
// Preload configuration
$( document ).on( "mobileinit", function() {
$.mobile.autoInitializePage = false; // This one does the job
});
</script>
Furthermore if you want to disable jQuery mobile automatic link and form handling via ajax, set (as dvk3 said) ajaxEnabled to false and pushStateEnabled to false as recommended:
$.mobile.ajaxEnabled = false;
$.mobile.pushStateEnabled = false; // Recommended is false, when ajax is disabled
For further information see: http://api.jquerymobile.com/global-config/
I'm using v1.4.5
Same happened to me by mixing mobile with other frameworks. Fixed issue but getting custom build of jQuery.mobile. My case was that I needed swipe for touch devices only so used custom min file and nothing was broken after that.
It really depends if you need jQuery.mobile or you need just a certain functionality, Widgets, events? Use custom version that you can build yourself.
You can make and download yours here : http://jquerymobile.com/download-builder/
I hope it worked for you too guys!

Refresh the browser once on load or clear DOM

I have a dynamic MVC4, jQuery Mobile application that works for the most part quite well. I have an auto posting dropdown list that selects a list from the database via the following code.
<script type="text/javascript">
$(function () {
$("#TownID").live('change', function () {
//$("#TownID").change(function () {
var actionUrl = $('#TheForm1').attr('action') + '/' + $('#TownID').val();
$('#TheForm1').attr('action', actionUrl);
$('#TheForm1').submit();
});
});
</script>
<p>
#using (Html.BeginForm("SearchTown", "Home", FormMethod.Post, new { id = "TheForm1" }))
{
#Html.DropDownList("TownID", (SelectList)ViewBag.TownId, "Select a Town")
}
</p>
The problem is it only works properly the first time a search is performed unless I click refresh. I don’t think this has anything to do with MVC, I think the problem is with AJAX and jQuery Mobile.
Edit:
The first time I search www.mysite.com/Home/Search/2 yields a result and woks fine, but the second time something seems to be left behind in the DOM??? and it looks for:
www.mysite.com/Home/Search/2/2 also
I get 404 errors in my log and “Error Loading Page” but it still finds the results and displays the page correctly!
Then with a third search I get the error 404’s in my log and “Error Loading Page” but it has grown and now looks for:
www.mysite.com/Home/Search/2/2
www.mysite.com/Home/Search/2/2/2 also
This then continues to grow after every search until at some seemingly random point on each test, it seems to give up and I get error 505
Additional Edit:
The code works perfectly if I take jQuery Mobile out of the question
Can anyone tell me what might be going on here?
Get rid of: $(function () {
And replace it with: $(document).delegate('[data-role="page"]', 'pageinit', function () {
Please read the big yellow sections at the top of this page: http://jquerymobile.com/demos/1.1.0/docs/api/events.html
You can't rely on document.ready or any other event that only fires once per page. Instead you have to get used to using jQuery Mobile's custom page events like pageinit so your code will work no-matter when the page is added to the DOM (which you don't know when this will happen in a jQuery Mobile website). There are a ton of events, so again, please read the documentation I linked-to above.
Firstly, dynamically generated html using a server side templating engine blows. I really don't understand what value people see in it.
My guess is that it used to make sense 10 years ago before AJAX became popular, and has just hung in there ever since because people have this feeling that it is "the right way to do it". It isn't. ESPECIALLY for mobile web apps.
Secondly, it looks like you are trying to do pretty simple search. All this MVC4 garbage makes it difficult for you to see what is really happening though. You don't need to append parameters to your URL for a simple form submission like this. In fact your TownId should already be part of the POST data when you submit, so you can just remove the URL modification bit.
Alternatively, don't use a form submission, but just a GET and AJAX. I don't know what your app is doing here, but I imagine you want to display the results on the page dynamically somehow, so a GET is more than enough.
Use your developer browser tools (F12) to see what exactly is getting submitted when you do the submit - it really helps. And for your next project, abandon MVC4! "Well established design patterns" my foot.
I have been bothered by this problem for a long time
There are same select element in the DOM I think so...
and I used $('.SelectCSS:last').val()
It seen work well.
I come from China , English is poor...
I guess this is one for the future, MVC and jQuery Mobile don't seem to blend completely right now. Maybe MS's response to the issue is Single Page Applications!
SPA may satisfy Danial also?

recaptcha within modal window (fancybox)

I am trying to load a form through ajax within fancybox. It works great, everything works fine.
I used the recaptcha (rails) plugin and got the captcha on the form. Now when the fancybox loads, its getting redirected to an empty page with only captcha on it.
I assume this is some problem with iframe and modal window?
Has anyone loaded recaptcha on a form within fancybox? would help me out if you could point me to an example..
appreciate your help...
I had the same problem with fancybox. I solved it using the recaptcha ajax API:
https://developers.google.com/recaptcha/docs/display#AJAX
I have the same problem, though only in Safari (presumably Chrome too). It works fine in Firefox. It has nothing to do with Ruby (I use Grails), and the problem occurs with all jQuery popups (I've tried blockUI, SimpelModal and ThickBox).
As far as I can tell, for some reason when jQuery moves an iframe (which is what ReCaptcha is) in the domtree, Webkit decides to load the iframe content as a new page. Or maybe replaces the page content with the iframe content.
I'm pretty sure it's a Webkit bug, and I have no idea if there is a fix possible.
Edit: turns out there's a fix possible:
$('#captcha-form script').remove();
'captcha-form' is the id of the form containing the captcha. Remove the script tags so the scripts don't get executed a second time when Safari re-renders them after jQuery moves them. The event handlers created by the script aren't in the script tags, so they survive.
I think that Safari re-executes javascript when it re-renders the tags. And anything in a modal dialog gets re-rendered when the dialog opens. Furthermore, I suspect that after having been re-rendered, the document.write used by recaptcha gets confused about where it is and messes up.
if anyone is still searching for an easy solution to this, i've managed to get a recaptcha working in fancybox by calling it in as an iframe
if calling it in from a class selector:
<script>
$(document).ready(function() {
$(".example").fancybox({
fitToView : true,
autoSize : false,
openEffect : 'elastic',
closeEffect : 'elastic'
});
});
</script>
Then in your HTML
<a href="http://url.com/iframe" class="example fancybox.iframe>Captcha Fancybox</a>
Or, if you are triggering the fancybox programatically, do something like:
$.fancybox({
width : '520px',
height : '230px',
autoSize : false,
openEffect : 'elastic',
closeEffect : 'elastic',
'href' : 'http://www.url.cc',
type : 'iframe'
});
The easy solution to load a form with recaptcha through ajax within fancybox.
<%= recaptcha_tags ajax: true %>

Changing the hash but not moving the page using jquery ui tabs

I added the following code to change the hash to the tab name:
$("#tabs > ul").tabs({
select: function(event, ui){
window.location.hash = ui.tab.hash;
}
} );
This works fine in FF3 but in IE7 it moves down the page (depending on the tab selected anywhere from somewhere near the top of the page all the way down to the very end of the page).
I tried changing it to:
$("#tabs > ul").tabs();
$("#tabs > ul").bind("tabsshow", function(event, ui) {
window.location = ui.tab.hash;
})
This leads to identical behavior in both IE7 and FF3, which moves the page down to the top of the selected tab.
I would like the tab to be changed, the hash to be updated, but the page not moved at all, which is how it works in FF3 in my first example, but not in IE7.
Thanks.
Notes: JQuery 1.3.1 / JQuery-UI 1.6rc6
If there's an element on the page that has the same id as what you're setting the hash to, for instance you're trying to set the browser hash to #cars and there's already a div#cars on the page, the browser will scroll you down to where that div is.
To my knowledge, there are 3 possible workarounds
1) Change the browser hash to something else such as #thecars.
2) Change your existing markup in some similar manner.
3) On some event, changing the id of your similarly named markup, then changing the browser hash, then rechanging the name of markup back to it's original value should also theoretically work. This is obviously a bad and slow workaround, just thought I'd mention it.
You could try having a "return false;" after you set the window location but I can't be sure.
Unfortunately, your problems won't end there. There are other issues with navigating back and forth across multiple browsers--nothing may change, page may reload, page state might be mangled, javascript may get reinitialized etc.
You may want to have a look at Tabs v2 which uses the History/Remote plugin though it has not been updated for jQuery 1.3+.
This demo is easier to understand. If you look at the javascript source, you'll notice the use of iframes to handle states.
There is also the History Event plugin and the jHistory plugin to achieve what you want.
Would like to hear back how things turns out and what solution you went with.
What Chris suggested worked for me, had no clue even a div could link via the #. So my solution is quite simple, in the show: event handler, I do the following, it's not perfect in that back button won't be in history, but that's another job for BBQ history plugin. All my divs simply have id="tab-cars", id="tab-trucks"... strip out the 'tab-' part and put it into the url hash.
var name = ui.panel.id.substr(4);
location.hash = '#'+name;

Resources