Rails, turbolinks and inline javascript. How to avoid duplicate event handlers? - javascript

I have read many posts on the internet, but in the end I still haven't found a nice solution to deal with Turbolinks + inline scripts.
Turbolinks does give a nice feeling and I don't really want to stop using it, but in some cases I have a hard time debugging duplicate event handlers.
I have some views with inline javascript : I actually prefer having the (small) javascript codes near the element(s) that are interacted with, saving only big javascripts functions for app.js.
So imagine I have a search page with several filters, but only on that specific page. When the user changes the search filters, it is supposed to GET (using remote/AJAX) the new results. The easiest way was to introduce
$(document).on('ready turbolinks:load', function() {
// Submit on every change
$('#myform input').change(function() {
$('#myform').submit()
}
})
When navigating with turbolinks and going back to the search page, I naturally end up with several duplicate bindings on every select, causing multiple form submits.
I have tried adding
$('#sidebar-search input').unbind()
Just before the .change() line but it doesn't seem to work, and for some reason the form is still posted several times : once with AJAX, several other duplicates (that thankfully are cancelled) and another non-remote request (my server replies with HTML).
Using turbolinks 5

As far as I can tell, there is no way to avoid this problem when using inline JavaScript. The section of the Turbolinks README “Working with Script Elements” advises avoiding inline <script>s when installing event handlers:
Turbolinks evaluates <script> elements in a page’s <body> each time it renders the page. You can use inline body scripts to set up per-page JavaScript state or bootstrap client-side models. To install behavior, or to perform more complex operations when the page changes, avoid script elements and use the turbolinks:load event instead.
It sounds like the reason you prefer inline scripts is the convenience of having relevant JS and HTML next to each other. This is a matter of workflow. While I’m afraid I don’t know any way to make separate JS files as convenient as inline JS, you can make it close by putting a comment in the HTML where the JS used to be, saying “this template has JS that is only for it”. When you are editing a template, if you see that comment, open the corresponding JS file in a horizontal split in your editor, so you can view both files at once. If you are using an editor that supports opening paths in files, you could even write the path to the JS file in the comment so you can open it just by following the link.
You have various choices for organizing which files your single-page JS snippets go in. You could put them in per-controller JS files like products.js, or the global file application.js, or you could create a new global file single_page_js.js. Then you could put comments above each section, labeling which template that code affects. Alternatively, you could also use some sort of naming convention to match JS files to templates. For example, when writing event handlers for the template app/views/products/index.html.erb, you could put the JS in app/assets/javascripts/products_index.js.
The ideal for you would be some sort of plugin that preprocesses your HTML templates to remove <script> elements, copies their contents to the global JS file, and adds wrapping code that ensures that that bit of JS is only run once, when the page is first visited. But I don’t know of any such plugin.

I used todo like this, and never had any issue with turbolink except disabled for specific links.
function run_after_turbo(){
$('#myform input').change(function() {
$('#myform').submit()
}
}
$(run_after_turbo);
$(document).on("turbolinks:load", run_after_turbo);

I had a similar issue and solved switching to plain js
// Old
$(document).on('click', 'a[data-toggle="submenu"]', function(event){
$(this).next('ul.submenu').slideToggle();
return false;
});
// new
var subMenus = document.querySelectorAll('a[data-oggle="submenu"]');
subMenus.forEach(function(subMenu){
subMenu.addEventListener('click',function(event){
$(subMenu).next('ul.submenu').slideToggle();
event.preventDefault();
event.stopPropagation();
});
});
So in your case should be like this
var form = doucument.querySelector('#myform input');
form.addEventListener('change', function() {
$('#myform').submit();
});

Related

Render Scripts-Section after scripts were loaded in ASP.NET Core PartialView

In an ASP.NET Core app, I've a dashboard with widgets. Every widget has its own PartialViews, so the full page is generated in the following way:
-Layout.cshtml
--Dashboard.cshtml
--- Widget1.cshtml
--- Widget2.cshtml
Following best practices according to fast page load times, JavaScript is loaded before the closing </body> tag in Layout.cshtml. After that, there is a section for custom JS, which I commonly use to initiate objects on page load. So this section looks like this:
<script asp-append-version="true" type="text/javascript" src="~/clientscript/page.min.js"></script>
#RenderSection("Js", required: false)
In my Views, which are using the Layout.cshtml as layout (in this example, its Dashboard.cshtml), I can define a section like
#section Js {
// Js Code here
}
which is rendered after the script tag containing all script files. So I can be sure, that all dependencies like jQuery or custom classes are avaliable here.
But I also need to do this in widgets like Widget1.cshtml for example. The problem is: I load the PartialView Widget1.cshtml in Dashboard.cshtml. In the documentation is written, that this is not possible:
If you declare a Razor section in a partial view, it will not be visible to its parent(s); it will be limited to the partial view.
But that's exactly what I need. Is there a way to work around this limitation? Shortly, the goal is to inject JavaScript from a PartialView to the LayoutView, with an regular View between them.
The only way I know is the usage of setInterval() with a low interval like 50ms, and check there if jQuery or some of my custom class is defined in a loop until they are. Its a JS solution yes. But it makes it possible to include the script-block directly in the PartialView without making usage of sections. It fits well when you depend on a single variable like jQuery.
But I need to wait for custom classes to get loaded. They're included after jQuery. So writing a generic function like waitForTypeLoaded(type, callback) is not possible. It would cause me to write always the raw setInterval() code, which seems not a smart solution for me.
Something I did to get my scripts to run after Jquery was done loading was in my Partial Views and View Components I used the "DOMContentLoaded" event to load all my jQuery js script after the page was done loading. That way I could defer the Load of jQuery and Still Have jQuery code on my pages.
<script>
window.addEventListener('DOMContentLoaded',
function() {
$('body')....
});
</script>
Your problem can be solved as mentioned in my answer to this post:
How to render scripts, generated in TagHelper process method, to the bottom of the page rather than next to the tag element?
To sum up, you can create a pair of tag helpers, one that can be located in a partial view and just stores its content in a temporary dictionary, and the other that renders the content at the appropriate position (e.g. in the layout page). I use it extensively to render small dynamically created scripts as the final scripts of the body.
Hope it helps.
Honestly, I would make one step back and look at architecture once again if you have such dilemmas.
Why not add to required scripts which will be used on a couple of views/partial views to the main layout? In ASP.NET MVC you can use bundling mechanism (or you can write our own) - minify and bundle them with other required. It won't be heavy...
Your approach looks like unnecessary complicated.

JavaScript does not work well inside a loaded content in a DIV

I am creating an application with Symfony2, where I have a main menu of options depending on the option selected dynamically opens a tab at a lower div with the content for that option. Content is loaded with load() of Jquery in the container div.You can see in the picture below:
The first problem was that in the HTML loaded in each tab could not use the js file initially loaded in the index.html, as you can see in this example you should check out a notice when we click the content of each tab, but does nothing .
The solution to this problem was included in each HTML code to load the appropriate script, and it worked properly. But to do it this way, if two HTML carry the same js, when one of the contents some event runs is repetite many times as tabs we have created, that is, if I open two different options (each in its own tab both charge the same js) by clicking on the first event associated performed twice, whereas if I do it in the second only done once. In short, whenever a function of a js used, is repeated as many times as there are dynamically loaded on the tabs.
And I tried event.preventDefault();orevent.stopPropagation(); and it does not solve the problem.
Would it be okay that it js is included twice in the overall structure of HTML? (Included in the initial head and then the container div)
Dynamically loading HTML + JavaScript is not the best approach for this case. I suggest that you use some JavaScript SPA framework, like AngularJS or ReactJS. Both are very big and well supported projects, so you can find tons of documentation and tutorials. You'll most likely end up using Symfony only as a RESTful service and Angular/React taking care of the rest (template loading, sending request to server, etc). Also, js frameworks will take care of deep linking and in the end you'll have a better working, easier to maintain application.
It is a bit more work initially, especially until you bootstrap the application, but then it gets easier to maintain and implement new functionality, so it pays off in the end. With your current approach you soon will find yourself in a big mess full of 100s of templates, js callbacks, inclusions, etc. I'm saying this from a personal experience!
Well...
Jquery works like this: when you attach an event to html, if the html does not exist, the event is attached to nothing. If the element exists then the event is correctly attached. It attaches only to existing elements when the on function is execute. That is a correct behaviour. In the past it used to exist a .live method that did exactly what you want: you attached an event and if you create the element after the attachment, the new element also contained the event.
Adding the js twice is not the solution. As you said after a click the button will be executed twice.
Why do not attach the events after loading the content? If you load it in the page start you can do in the main file:
$(function(){ // will force to execute the on method after all the page is loaded.
$('.submenu .button').on ('click', function (){
...
});
});
If you load the menu by ajax, in the callback and after adding the html menu to the main you must use the code I wrote above.

Building a Windows 8 Metro app with jQuery

I am just starting out with Windows 8 development using HTML/JS. I've spent the last few months immersed in jQuery development for apps targeting vehicle head-units and televisions.
Jumping into this, I thought the transition would be simple. I have the design and structure of my site all figured out for the most part and was hoping to follow some of the practices I had been using for my previous work.
That is, I want to essentially create a single page app. The main default.html file will house the top navigation/title and one other div. The other div will be used to load in all the other pages, all separate HTML files within the project.
All of the global functions and major functionality will reside in a javascript file, application.js. Then any page-specific javascript will reside at the top of each HTML file.
I'm quickly realizing that this is a problem. Using jQuery.load() to load in my pages causes security errors in my app.
JavaScript runtime error: Unable to add dynamic content. A script attempted to inject dynamic content, or elements previously modified dynamically, that might be unsafe. For example, using the innerHTML property to add script or malformed HTML will generate this exception. Use the toStaticHTML method to filter dynamic content, or explicitly create elements and attributes with a method such as createElement.
I was really hoping to avoid having to learn a bunch of Microsoft-specific stuff. I think it's great that they've provided a lot of tools and what not, and maybe I just haven't used them enough, but everything just feels too rigid for me and for what I'm trying to do or can already be accomplished with jQuery. I'm one who likes to know EXACTLY what is happening and have full control over it.
Also looking through the templates and sample projects, I really don't like all the repeated code. For instance, every single HTML file declaring all the same references. I want to write my references and sections like my title bar just once, and not have to copy/paste that code all over my project.
Is there a way to do things the way I was hoping, and create a single page app? Do they have their own substitute for jQuery's .load()?
Any help pointing me in the right direction would be much appreciated!
EDIT 8/14/2012:
I have tried using the fix from this question:
Using jQuery with Windows 8 Metro JavaScript App causes security error
This gets rid of the security warning and I can load in HTML using jQuery.load(). However, looking at DOM explorer, my HTML is being stripped of my scripts.
I have also tried wrapping my .load() call inside of MSApp.execUnsafeLocalFunction(), but yet again my file still gets stripped of all scripts. What gives?
I fixed by simply changing the line of jQuery that was causing the error.
jQuery-1.8.0, line 5566:
append: function () {
return this.domManip(arguments, true, function (elem) {
if (this.nodeType === 1 || this.nodeType === 11) {
self.appendChild(elem); // problem line
}
});
},
Changed to:
append: function () {
return this.domManip(arguments, true, function (elem) {
if (this.nodeType === 1 || this.nodeType === 11) {
var self = this;
MSApp.execUnsafeLocalFunction(function () {
self.appendChild(elem);
});
}
});
},
There is a "formal" way to do what you are seeking.
WinJS.Navigation is provided to support "single page apps". For example, the default.html would contain a markup that would represent where the dynamically loaded page content would go:
<div id="contenthost"
data-win-control="Application.PageControlNavigator"
data-win-options="{home: '/pages/home/home.html'}">
</div>
In the example above, the actual content page loaded is at /pages/home/home.html
In event handlers, you can simply do the following to load or navigate to another page:
WinJS.Navigation.nav("/pages/other/page.html");
True, it is not jQuery, but it works great :)
Depending on your app, if you are not intending to access any WinRT components, you can navigate your page to ms-appx-web which will change the security policy around the page, but you can't specify this from start up. You would have to do a navigate, and leverage that new securyt context.
The other option you have it to wrap the calls to JQuery with msWWA.execUnsafeLocalFunction function, which will enable all that unsafe code be pushed into the DOM

a solution to include jquery & js in the end of the html while at the same time a control which is included in the middle requires $(document).ready

i am building a website using asp.net mvc and jquery.
as a best practice its known that including javascript should be done at the end of the html page.
so i basically include jquery.js and other js files in the end of the html before the /body tag.
now i have some controls which are included in the page as partials. and they need to add functionally to $(document).ready.
but i write the code as a script tag in the partial then the jquery library wont be even included at that time and i cant include this javascript at the end of the html from within the partial. since the partial is included in the middle of the html.
Move all javascript in single file(you may exclude jQuery file) and move it to the bottom.
If you are talking of good practice then, then writing inline javascript is not a good practice too.
So I would suggest move all your java script to single file,there are many tools available that merge multiple javascript files and crunch them, use those!!
Edit1
You may try this:
//define this at before body (or at the beginning of body)
var arrReadyCollection = [];
Inside controllers:
arrReadyCollection.push(site.module.Dialog_AcceptChanges);
arrReadyCollection.push(some_thing_Else);
At the end, after jQuery file
for (i=0;i<arrReadyCollection.length; i++)
{
var fn= arrReadyCollection[i];
$(document).ready(fn);
}
Note: this is not recommended way, its just way you can solve your problem
You should use a javascript loader like LABjs.
You can use it to run javascript when certain libraries are loaded and run.
<head>
<script src="lab.js"></script>
</head>
<body>
<script>
// $LAB uses async loading, no need to deferr to the end of body
// keeping a reference to the $LAB deferred is important, so we can
// use it from within partials (see below)
myPage.$LAB = $LAB
.script('someScript.js')
.script('someOtherScript.js')
.wait(function () {
// your scripts are loaded!
});
</script>
</body>
In your partials you can hook into LABjs, eg like this:
<script>
myPage.$LAB.script('jQuery.js').wait(function () {
// jQuery was loaded!
// if jQuery was loaded already by another partial or your main layout
// it will not be loaded again, but the callback will fire immediately!
});
</script>
That beeing said you really should follow the advice given by Praveen and tie your javascript files up as much as possible. Each request to your server will cost time and decrease the responsivness of your website.
Either fix your "control" files so that they don't require inline JavaScript, or else include jQuery at the top of the file.
You could also write a small "$" function of your own that you'd include before jQuery (and at the top). However, you'd be better off fixing those controls anyway.
Another possible solution would be to add a script tag dynamically from within the partial. This however could get messy if you want to run more than a few lines of code, eg:
<script>
var script = docuement.createElement('script');
script.innerHTML = "$(document).ready(function () { doSomething(); });";
document.body.appendChild(script);
</script>
This should run after the jQuery (which should be already somewhere at the end of the body) was loaded and run.
UPDATE:
This does not work, I assumed that inserting script elements in a DOM that wasn't ready yet wouldn't execute before the DOM is ready.
Use CDN's and quit obsessing about doubtful optimization. From the excellent jQuery in Action book, sec 1.22
NOTE For performance reasons, script blocks can also be placed at the bottom
of the document body, though modern browsers make the performance
difference rather moot. The important concept is to avoid embedding behavioral
elements within the structural elements.

How can I have multiple document.observe(dom:loaded ...) functions inside one javascript file? (prototype)

Basically what I'm after is, following squashing all my JavaScript into one file (or a few files) how can I make a document.observe(dom:loaded({...})) function call for the right page?
So, say I have 5 pages in my website, and each one has a separate JS file. Inside each file is a load of page related functions, and a special function which sets up eventhandlers etc when the DOM has loaded. Obviously if all 5 pages include their own JS files then that's fine. But I want to compact all my JS into one page for versioning and efficiency reasons.
Therefore, in doing this, I would end up with 5 "dom:loaded" functions waiting to be setup. This won't do as the stuff inside these functions is page specific. So, my question is how can I do what I want to do without causing a whole bunch of DOM errors, and false eventhandler setup requests?
Ive considered namespaces and stuff like that but don't really know anything about it/them.
Btw, I'm using the Prototype lib. Oh, and my sites are considerably larger than 5 pages! :)
Thanks,
Lee
Some ideas:
inspect the dom to identify the page. For example, attach an attribute to the body element.
put a script tag in the page that sets a variable.
inspect the url.
when the load event fires, the event handler identifies the page and hands control off to the proper code.
I would give different IDs to the <body>s of the separate HTML files and test in the dom:loaded event the name of the <body> element using $$('body')[0].id.
I have opted to use a php regexp to capture the URI, then use this as the body ID. On my sites, where the page names are static, it means each page will have a unique ID.
I then include a JS file in the HEAD which contains a switch block inside which the appropriate code/functions are loaded. The switch block is inside the document.observe(dom:loaded...) function.
Works a treat!
Thank you again for your help.
I tend to always write my .js with no self activation (so much as possible)...
psuedo-code
namespace('mysite.section.init');
mysite.section.init.pageName = function(){
//stuff to run here...
};
in your home page, at the bottom, or via dom:loaded event simply run mysite.section.init.pageName();

Categories