Using sortable.js and turbolinks in Rails 5 - javascript

I have a Rails 5.1 app in which I am using the sortable.js library from https://www.kryogenix.org/code/browser/sorttable/ in order to do some simple client-side sorting of tables.
I am running into an issue where, when the page first loads, the table sorting does not work. (It just doesn't respond at all.) If you then manually reload the page (cmd-R or equivalent), then it does work.
After some searching, I believe that the problem is being caused by turbolinks. Posts like this one, and others suggest how to modify the JS code to solve it: Rails javascript only works after reload
To quote from the answer on that page you have to tell turbolinks to load by doing this:
document.addEventListener("turbolinks:load", function() {
my_func();
})
I, however, am not a JS expert, and so am a bit loathe to monkey around in the sortable.js code too much. I'm looking for help as to the least invasive way to solve the problem.

Posting a working solution, for the sake of future Google-ers.
I had to do two things to get this to work:
1: Comment out to lines at the top of the init function which prevent it from running more than once. So the top of my init function in sorttable.js looks like this:
sorttable = {
init: function() {
// Had to comment these top to out to get things to work with turbolinks
// quit if this function has already been called
//if (arguments.callee.done) return;
// flag this function so we don't do the same thing twice
//arguments.callee.done = true
Add this code somewhere else in the file. (I personally added it after the line that says "window.onload = sorttable.init;"
document.addEventListener("turbolinks:load", function() {
sorttable.init();
})

Related

Can content be updated via Ajax to end user without loosing scroll position from update/refresh?

This question is being asked to check if it is POSSIBLE to do what I’d like it to do, not so much as a HOW to do it question (although if someone wants to pen something up is always good too). As I am more in the infancy of my web-dev learning I figured it prudent to determine if the hopeful result is even possible before investing the substantial time into it to achieve it. I have read conflicting things from various references on the web and figured this is the place to ask others' opinions to be sure one way or the other.
I’m running Drupal 8 (8.9.13 at time of writing). The page uses Views. To keep it simple the page has 3 sections; a top, middle, and bottom (this is the page/content itself, this is NOT a header, body, footer). The bottom section/View consists of multiple div’s/sections. The content of all 3 sections is primarily links.
I’m using/trying the module ‘Views Auto-Refresh D8’. After install and setup it updates the page perfectly EXCEPT that not only after a content update, but ALSO after each Ajax check/interval the page returns to the top of the page, causing the user experience to be unacceptable. In other words, if reading the bottom of the page, once the page automatically checks for updates via Ajax the page suddenly returns to top of page.
Researching this I got the impression that is normal behavior for Ajax. I also got the impression that this behavior can be overridden, however, other sources made it sound as though it can’t. Hence the purpose of this question here.
The Javascript for the ‘Views Auto-Refresh D8’ module looks like this:
(function ($, Drupal, drupalSettings) {
// START jQuery
Drupal.behaviors.views_autorefresh = {
attach: function(context, settings) {
for(var view_name in settings.views_autorefresh) {
for(var view_display in settings.views_autorefresh[view_name]) {
var interval = settings.views_autorefresh[view_name][view_display];
var execution_setting = '.view-'+view_name.replace(new RegExp('_','g'),'-')+'.view-display-id-'+view_display;
if($(context).find(execution_setting).once(execution_setting).length > 0) {
// Delete timeOut before reset it
if(settings.views_autorefresh[view_name][view_display].timer) {
clearTimeout(settings.views_autorefresh[view_name][view_display].timer);
}
settings.views_autorefresh[view_name][view_display].timer = setInterval(
function() {
Drupal.behaviors.views_autorefresh.refresh(execution_setting)
}, interval
);
}
}
}
},
refresh: function(execution_setting) {
$(execution_setting).trigger('RefreshView');
}
}
Through researching this question I found closely related questions saying the page refresh can be bypassed/prevented by adding a javascript function such as:
preventDefault() / event.preventDefault()
However I’m not entirely sure if this event method can be used to achieve the result I’m pursuing? Or if the following is a better avenue to explore?
The other possible workaround/idea I came up with and am curious about is that I use a module called ‘Geysir’ that allows me to update my content directly on the page from the front-end. The content can be moved, deleted directly on the page, or even modified within a modal overlay that then updates the content without the page refresh/scroll back to top that the ‘Views Auto-Refresh D8’ is doing. I’m wondering if the logic from the Geysir module can be applied to the Views Auto-Refresh D8 module? With my moderate javascript knowledge it looks like the relevant js/Ajax logic in the Geysir module is:
$.each(Drupal.ajax.instances, function (index, event) {
var element = $(event.element);
if (element.hasClass('geysir-paste')) {
if (href === event.element_settings.url) {
event.options.url = event.options.url.replace('/' + paragraph_id + '/', '/' + parent_id + '/');
}
}
});
});
});
return false;
});
}
};
Drupal.AjaxCommands.prototype.geysirReattachBehaviors = function() {
Drupal.ajax.instances = Drupal.ajax.instances.filter(function(el) {
return el;
});
Drupal.attachBehaviors();
};
})(jQuery, Drupal, drupalSettings);
(lines 40-64)
That’s pretty much where I’m at before moving forward. So:
Is it even possible to maintain end-users scroll/page position while the Ajax call is being made and when content is updated via Ajax?
and if so,
Which avenue above is best to pursue, OR, is there yet another avenue I haven’t yet run across yet?

How to deal with DOM elements?

I am learning about writing custom JavaScript for my Odoo 10 addons.
I've written the following piece of code:
odoo.define('ioio.io', function(require) {
'use strict'
const e = $('div.o_sub_menu_footer')
console.log('--testing--'.repeat(7))
console.log(e)
// the "Powered by Odoo" down the secondary menu
e.remove()
})
The code is well loaded and I can see my testing string in the console.
However when this code is being loaded before the target div, so e empty/not yet filled and thus its content is not removed.
Doing it manually from the console works.
My question is what is the right way to do that? And how to know exactly when the code gets executed?
You can
put your html code before the script tag in your file
use jQuery $(document).ready(...);
Place your script at the bottom of the <body> tag to make sure the DOM renders before trying to manipulate it.
This is an Odoo specific question, so you should use the Odoo standard way, which is via its base JS class. That class contains a ready() method which does exactly what you need.
In your case, to use that function, you need to require the class first. Then you can use ready().
Updating your code, it should look like this:
odoo.define('ioio.io', function(require) {
'use strict'
// require base class
var base = require('web_editor.base');
//use its ready method
base.ready().done(function () {
// put all the code you want to get loaded
// once the DOM is loaded within this block
const e = $('div.o_sub_menu_footer')
console.log('--testing--'.repeat(7))
console.log(e)
// the "Powered by Odoo" down the secondary menu
e.remove()
});
})
While your accepted answer leads to the same outcome, you might want to update it to this one since this is the Odoo way. It's generally advised to work within the Odoo framework as much as possible and customise only if really needed. (Though it can be tough to learn what features Odoo already provides because of its poor documentation.)

Running client side javascript without loading a new (blank) view on Odoo 8

I need to run some client-side javascript from a button in a form view in Odoo 8. This button runs a python method which returns this dictionary:
{"type": "ir.actions.client",
"tag": "my_module.do_something",}
do_something is defined in a .js file as follows:
openerp.my_module = function (instance) {
instance.web.client_actions.add("my_module.do_something", "instance.my_module.Action");
instance.my_module.Action = instance.web.Widget.extend({
init: function() {
// Do a lot of nice things here
}
});
};
Now, the javascript is loaded and executed properly, but even before launching the init function, Odoo loads a brand new, blank view, and once the javascript is over I can't get browse any other menu entry. In fact, wherever I click I get this error:
Uncaught TypeError: Cannot read property 'callbackList' of undefined
What I need instead is to run the javascript from the form view where the button belongs, without loading a new view, so both getting the javascript stuff done and leaving all callbacks and the whole environment in a good state. My gut feeling is that I shouldn't override the init funcion (or maybe the whole thing is broken, I'm quite new to Odoo client-side js) , but I couldn't find docs neither a good example to call js the way I want. Any idea to get that?
Sorry, I don't work on v8 since a lot of time and I don't remember how to add that, but this might help you: https://github.com/odoo/odoo/blob/8.0/doc/howtos/web.rst
Plus, if you search into v8 code base you can find some occurence of client actions in web module docs https://github.com/odoo/odoo/search?utf8=%E2%9C%93&q=instance.web.client_actions.add
Thanks to the pointers simahawk posted in another answer, I have been able to fix my js, which is now doing exactly what I needed. For your reference, the code is as follows:
openerp.my_module = function (instance) {
instance.web.client_actions.add("my_module.do_something", "instance.my_module.action");
instance.my_module.action = function (parent, action) {
// Do a lot of nice things here
}
};

Include a javascript file only once in Joomla

now, this question has been asked and answered successfully many times, yet none of the things i try work.
I have tried head.js & require.js libraries
I have also tried
if (!window.unique_name) {
unique_name = true;
//code here..
}
none of which I can get to work (the global variable is always undefined)
the script I am trying to include runs something like this:
//clock.js
clockyTick = function() {
//my code here
}
setInterval(clockyTick, 1000);
the apps that call this script, standalone, work fine.
only when both apps are included on the same page (via calls to PHP require()) they break.
Here is the cause of the problems (I think):
I am building custom web apps on a (Joomla) site and have the requirement of displaying two of my apps on the same page.
Both apps need the same .js file to operate correctly, which works fine when they run standalone, but as soon as both apps are running on the same page (in the admin section) the scripts conflict and stop each other from working
(the script in question is a dynamic clock script that grabs the specialised contents of a div and modifies it to something else)
I think the reason I cannot get aforementioned libraries to work, is the fact that they also are being included twice on the admin page.
is there any way around this, or do I have to bite the bullet and integrate a library into the main Joomla template? (meaning the library is uselessly loaded on every single page, yet only used on 3 of hundreds)
jQuery is also required, separately, on each app..but thankfully I am able to use noConflict to avoid problems there (not ideal)
The joomla way would be to instantiate the document inside your module and unset only the conflicting script as described in this question here just before you load the module's script:
1) get an instance if the document object and remove the js files (you
could do that in a plugin) :
<?php
//get the array containing all the script declarations
$document = JFactory::getDocument();
$headData = $document->getHeadData();
$scripts = $headData['scripts'];
//remove your script, i.e. mootools
unset($scripts['/media/system/js/mootools-core.js']);
unset($scripts['/media/system/js/mootools-more.js']);
$headData['scripts'] = $scripts;
$document->setHeadData($headData);
?>
Or in your case, I think you could try the dirty solution below inside your js files:
//1st module script
var unique_name;
if (unique_name == false || unique_name == null) {
unique_name = true;
//code here..
alert("Included 1st script");
}else{
//do nothing
alert("Not included 1st script")
}
//2nd module script
var unique_name;
if (unique_name == false || unique_name == null) {
unique_name = true;
//code here..
alert("Included 2nd script");
}else{
//do nothing
alert("Not included 2nd script")
}
Here is a DEMO
If you are having conflicts with PHP require(), you can try require_once(). However, as mentioned, that’s not the Joomla way of doing things.

How to inject some JS code after every remote call in Rails 3

So I'm using timeago plugin in my Rails 3 app, wrapping it within this function (note than the timeout just keeps the timeago strings updated to the minute at every moment):
function doTimeago() {
$('.timeago').each(function() {
var $this = $(this);
if ($this.data('active')!='yes') {
$this.timeago().data('active','yes');
}
});
}
And then in my application.js
$(function() {
doTimeago();
}
This works great until I load some elements using remote calls. I researched a bit and found no working solution. I'm not happy adding livequery plugin as suggested in this question since it seems deprecated.
I was thinking of adding this JS code to the end of every js.erb file in my app, but it feels really duplicated and nasty.
doTimeago();
Question part 1: ¿Is there an easy way to inject that code after every js.erb execution?
Question part 2: ¿How do I achieve my primary goal of having ajaxy loaded elements work with timeago?
You can bind it to ajaxComplete event, like this:
$(document).on('ajaxComplete', function(){
do_timeago();
});
BTW, I didn't understand your timeout in do_timeago function. Also, JS best practices are a bit different than ruby ones, consider rename your function to something like doTimeago.
Hope it helps.

Categories

Resources