LinuxQuestions.org
Welcome to the most active Linux Forum on the web.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 01-24-2020, 05:10 AM   #1
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,006

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Rails, Javascript and Turbolinks cached pages


I am obviously missing a key point here, so this is what I (think I) know so far:

1. Rails app uses Turbolinks

2. Turbolinks takes over the loading of the page and caches it once done (I think)

So I have 3 javascript files.

1. Allows the user to click a link and makes a form appear, which then adds the form data
to the end of a list of entries in a div
Code:
#code to show either link or form - app/javascript/packs/new_task.js
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const changeLinkForm = element => {
	let new_tasks = document.querySelectorAll(element);
	const [div, link, form] = new_tasks;

	div.replaceChild(link, form);

	link.addEventListener('click', e => {
		div.replaceChild(form, link);
	},false);

	form.addEventListener('submit',async e => {
		div.replaceChild(link, form);
		await sleep(1000);
		form.reset();
	},false);
}

window.addEventListener('turbolinks:load', event => {
	changeLinkForm('#new_task');
}, false);

#code called once form is submitted to create entry and add to list - app/views/tasks/create.js.erb
var task = document.querySelector('#incomplete_tasks');

task.insertAdjacentHTML("beforeend", '<%= j render(@task) %>');
Now this is where I think the first part of the caching shows as after I add a new entry and then look at the source for the page,
the new entry does not appear in the source but is on the page, hence the DOM has been updated but not the cached page

2. I have javascript to remove an entry from the list
Code:
# code called when 'remove' link is clicked - app/javascript/packs/remove_task.js
const remove_line = name => {
	let elements = document.querySelectorAll(name);

	elements.forEach(element => {
		element.lastElementChild.addEventListener('click', event => {
			element.remove();
		}, false);
	});
}

window.addEventListener('turbolinks:load', event => {
	remove_line('form');
}, false);
Now this code works just fine on all entries that were on the page when it first loaded.
However, the newly added entries from code above do not disappear, but the destroy method within rails constructor does remove the entry from database, so on a page refresh the entry is now gone

So my question is, how do I get my remove code to work on the newly added entries from the create/show_hide code?

Please let me know if I have missed any vital details to help solve the problem/educate me?
 
Old 01-24-2020, 06:18 AM   #2
phantom_cyph
Senior Member
 
Registered: Feb 2007
Location: The Tropics
Distribution: Slackware & Derivatives
Posts: 2,472
Blog Entries: 1

Rep: Reputation: 128Reputation: 128
Code:
form.addEventListener('submit',async e => {
		div.replaceChild(link, form);
		await sleep(1000);
		form.reset();
	},false);
Now.. I'm not sure but something here doesn't look right.

Normally you define the top level function as "async", then put anything synchronous into another function which then returns a promise. I.e. "await renderElement()".

JS is naturally asynchronous, so you're actually stating inside an async function that it's async, rather than having an async function which waits for the synchronous function to finish and return it's "resolution".

This itself could play a big role in what is causing your issue.

Honestly if you want automatic rendering on something like this, I'd go React.js.. as you could just render based on an array/list that is populated in state, and upon any change to that array, it would automatically re-render.
 
Old 01-24-2020, 07:27 AM   #3
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,006

Original Poster
Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
@phantom_cyph - thanks for the feedback

The reason for that little cludge was to get a pause to happen so the create.js.erb could do its thing and the reset the form so it is blank when user next clicks the link.
I can remove the entire thing as once the div.replaceChild is finished the other is nice to have and I probably need to learn a better way

So, if i change it to:
Code:
form.addEventListener('submit',e => {
  div.replaceChild(link, form);
},false);
My original issue still persists.

I have read of other gems and things I can install to change functionality, but I am starting out at a slow/low level as javascript is new to me
and I wanted to get it working with rails to then see how I can extend my apps/pages

If on the other hand there is no natural way to do what I want outside installing something else I am happy to look at that too?
 
Old 01-24-2020, 07:54 AM   #4
SoftSprocket
Member
 
Registered: Nov 2014
Posts: 399

Rep: Reputation: Disabled
Quote:
Originally Posted by grail View Post
Now this is where I think the first part of the caching shows as after I add a new entry and then look at the source for the page,
the new entry does not appear in the source but is on the page, hence the DOM has been updated but not the cached page
AFAIK there is no way to update the source. Javascript doesn't act on the source it acts on the DOM. Are you sure you are adding the listener on the correct element?
 
Old 01-24-2020, 08:31 AM   #5
dugan
LQ Guru
 
Registered: Nov 2003
Location: Canada
Distribution: distro hopper
Posts: 11,220

Rep: Reputation: 5319Reputation: 5319Reputation: 5319Reputation: 5319Reputation: 5319Reputation: 5319Reputation: 5319Reputation: 5319Reputation: 5319Reputation: 5319Reputation: 5319
I was going ask this last time but didn't for some reason.

Why are you passing false as the third argument to addEventListener? It's false by default.
 
Old 01-24-2020, 08:59 AM   #6
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,006

Original Poster
Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Quote:
Originally Posted by SoftSprocket
Are you sure you are adding the listener on the correct element?
I am learning, but as far as I can tell, I would say yes as all javascript files are working when the page is first loaded and works after any refresh to the page,
just not on the items added using javascript.
Quote:
Originally Posted by SoftSprocket
Javascript doesn't act on the source it acts on the DOM.
I am assuming the DOM is updated correctly as when I click the remove link in the page the rails side, ie the firing of the constructor, seems to work just fine

Quote:
Originally Posted by dugan
Why are you passing false as the third argument to addEventListener? It's false by default.
As above, I am learning and so at the moment I am trying not to leave anything to chance or misinterpretation (namely, by me).
I was aware it is the default, but for now it is there as a reminder/learning tool

I thought as additional information I would include the current source from when the page is first loaded:
Code:
<!DOCTYPE html>
<html>
  <head>
    <title>Rc136</title>
    <meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="xyLSXKQZJgtK0amvmG1k8MQ/HZNnPmNkhPS2E5jzIcbjNiW1z3lFt/CnIchqbOKtmqFP+w0cDVjsJYcrkrpt3g==" />
    

    <link rel="stylesheet" media="all" href="/assets/application.debug-3afd09bff0c2914d389ec26e5756550d546c1051aaedbc73714107ffbe8bb3bd.css" data-turbolinks-track="reload" />
    <script src="/packs/js/application-0b67a3cc2fcde6055024.js" data-turbolinks-track="reload"></script>
  </head>

  <body>
    <script src="/packs/js/new_task-0f218e4c845d787adbd8.js"></script>
<script src="/packs/js/remove_task-9851f625eb8db320a754.js"></script>

<h1>Check List</h1>

<div id="new_task">
  <a id="new_task" data-remote="true" href="/tasks/new">New Task</a>
  <form class="new_task" id="new_task" action="/tasks" accept-charset="UTF-8" data-remote="true" method="post">
  <input type="text" name="task[name]" id="task_name" />
  <input type="submit" name="commit" value="Create Task" data-disable-with="Create Task" />
</form>
</div>

<h2>Incomplete Tasks</h2>
<div class="tasks" id="incomplete_tasks">
  <form class="edit_task" id="edit_task_2" action="/tasks/2" accept-charset="UTF-8" method="post"><input type="hidden" name="_method" value="patch" /><input type="hidden" name="authenticity_token" value="gRBab/7SAjtIbS6DBGz4waL8XHDPb3DBDl3yGQ+43Kuqil7gBvDGUF0WTpezqCPSTJN4ti+i125GxrJapote1A==" />
  <input name="task[complete]" type="hidden" value="0" /><input type="checkbox" value="1" name="task[complete]" id="task_complete" />
  <input type="submit" name="commit" value="Update" data-disable-with="Update" />
  <label for="task_complete">Wax the car</label>
  <a data-confirm="Are you sure?" data-remote="true" rel="nofollow" data-method="delete" href="/tasks/2">(remove)</a>
</form><form class="edit_task" id="edit_task_157" action="/tasks/157" accept-charset="UTF-8" method="post"><input type="hidden" name="_method" value="patch" /><input type="hidden" name="authenticity_token" value="4fLoZz8vruBbGo3VcWhPsL/NAiFvsK6MU96z5bzZra81ORQ19t0aCnvTJSEaR81oZTmBpaCDia7fuHhoISfkew==" />
  <input name="task[complete]" type="hidden" value="0" /><input type="checkbox" value="1" name="task[complete]" id="task_complete" />
  <input type="submit" name="commit" value="Update" data-disable-with="Update" />
  <label for="task_complete">Sand the deck</label>
  <a data-confirm="Are you sure?" data-remote="true" rel="nofollow" data-method="delete" href="/tasks/157">(remove)</a>
</form>
</div>

  </body>
</html>
The 'Incomplete Tasks' is the section where my code adds an additional entry.
If I click either "(remove)" link, tasks 2 and 157, each of those items will be successfully removed both from the database by the rails constructor action and from the visible page
by the remove_task javascript.

Once the link, 'New Task', is clicked the link will disappear and be replaced with the new_task form.
On clicking the 'Create Task' button the new entry will appear as a new entry for incomplete tasks and the form will switch back to the link

It is at this point that if I do a dump as above the new entry will not be displayed in the source and the '(remove)' link will not fire the associated remove_task javascript,
but it will however fire the rails constructor to remove the task from the database.
 
Old 01-24-2020, 09:42 AM   #7
boughtonp
Senior Member
 
Registered: Feb 2007
Location: UK
Distribution: Debian
Posts: 3,596

Rep: Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545
Rails runs on the server.

JavaScript runs in the browser (on the client's computer), and has no control over your server.

Both HTML forms and JavaScript can send HTTP requests to your server, but it is up to Rails to process those requests and perform whatever logic is necessary (e.g. validate correct input, update the database, clear any caches, etc).

In short: forget about JavaScript for the moment - go to the browser network tab and deal with the HTTP requests and responses (either there, or by crafting a suitable curl command) and make sure you're getting the correct responses & behaviour when you make requests manually.

Once you have that working, you can return to the JavaScript side and ensure JS is generating the correct HTTP requests.
 
Old 01-24-2020, 10:27 AM   #8
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,006

Original Poster
Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Quote:
Originally Posted by boughtonp View Post
Rails runs on the server.

JavaScript runs in the browser (on the client's computer), and has no control over your server.

Both HTML forms and JavaScript can send HTTP requests to your server, but it is up to Rails to process those requests and perform whatever logic is necessary (e.g. validate correct input, update the database, clear any caches, etc).

In short: forget about JavaScript for the moment - go to the browser network tab and deal with the HTTP requests and responses (either there, or by crafting a suitable curl command) and make sure you're getting the correct responses & behaviour when you make requests manually.

Once you have that working, you can return to the JavaScript side and ensure JS is generating the correct HTTP requests.
If I am understanding you correctly, please let me know if not, but I started this app with zero javascript and all features were working perfectly
via html/rails.
The obvious difference being that each time the items were added or removed from the page it was associated with a return to the page, ie so the page was refreshed
each time and all changes were observed to have worked.

I initially added the link/form exchange and create javascript first and again (after some learning, see here) the items were created and added but now without the use of the page refresh as performed by javascript.

On adding the remove javascript it was only then did I notice the lack of updating in the source (which I understand is not required as the DOM is being altered correctly)
and the knock on effect of not allowing the remove option to work on newly created entries.

Please advise if this is equivalent to what you have mentioned and if not please advise what I have missed?
 
Old 01-24-2020, 05:32 PM   #9
boughtonp
Senior Member
 
Registered: Feb 2007
Location: UK
Distribution: Debian
Posts: 3,596

Rep: Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545Reputation: 2545
Ok, I understand the issue now - it's different to what I thought before.

Currently you're attaching remove_line on turbolinks:load event (and thus only attaching clicks to the entries around when that event fires, which is on navigation), so when you add a new entry, there is no click event on its remove button.

One option would be to use dispatchEvent to trigger the event manually after you create your new entry - whilst you could just re-trigger turbolinks:load event itself, that may involve other side-effects so it's probably better to create your own separate event and tie remove_line function to both events (i.e. two calls to addEventListener).

Another option would be to not add individual click event listeners to each remove button, but just have a single instance attached to the containing element, then have logic to check event.target to determine which button was clicked, and thus which entry to remove (or indeed any other actions).
 
Old 01-27-2020, 11:35 AM   #10
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,006

Original Poster
Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
@boughtonp - Thank you for this information, turns out, after some head scratching about why a form couldn't dispatch an event and that querySelector had to be on a tag and not a class or id to be able to
catch the event, that this was the way forward

For others who may find this thread useful, here is my updated solution:
Code:
$ cat app/javascript/packs/new_task.js 
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const changeLinkForm = element => {
	let new_tasks = document.querySelectorAll(element);
	const new_event = new Event('task_added', { bubbles: true });
	const [div, link, form] = new_tasks;

	div.replaceChild(link, form);

	link.addEventListener('click', e => {
		div.replaceChild(form, link);
	},false);

	form.addEventListener('submit',async e => {
		div.replaceChild(link, form);
		await sleep(1000);
		form.reset();
	},false);

	form.addEventListener('reset', e => link.dispatchEvent(new_event));
}

window.addEventListener('turbolinks:load', event => {
	changeLinkForm('.new_task');
}, false);
Code:
$ cat app/javascript/packs/remove_task.js 
const remove_line = name => {
	let elements = document.querySelectorAll(name);

	elements.forEach(element => {
		element.lastElementChild.addEventListener('click', event => {
			element.remove();
		}, false);
	});
}

window.addEventListener('turbolinks:load', event => {
	remove_line('form');
	
	document.addEventListener('task_added', event => remove_line('form'));
}, false);
Now, not sure if this is just being greedy, but it appears to be another issue I had missed.

When the rails side creates each entry for the tasks, the '(remove)' link also has a confirmation assigned in case you wish to cancel the removal.
In the resulting HTML a single entry looks like:
Code:
<form class="edit_task" id="edit_task_157" action="/tasks/157" accept-charset="UTF-8" method="post"><input type="hidden" name="_method" value="patch" /><input type="hidden" name="authenticity_token" value="3ki5PDrWw17b5DhEJ0/zJ38vs9GWoT+0nzVSL1MOqbYpU4AITgeq6WNsX5ch8h1ookZphDFxpiLzRLJmO/OALg==" />
  <input name="task[complete]" type="hidden" value="0" /><input type="checkbox" value="1" name="task[complete]" id="task_complete" />
  <input type="submit" name="commit" value="Update" data-disable-with="Update" />
  <label for="task_complete">Sand the deck</label>
  <a data-confirm="Are you sure?" data-remote="true" rel="nofollow" data-method="delete" href="/tasks/157">(remove)</a>
</form>
On pressing OK everything works as expected, however, on Cancel, rails rightly ignores calling the delete/destroy method so the entry stays in the database.
But my javascript is predicated on the click of the '(remove)' link and not the following conmfirmation dialogue box.

Is anyone able to tell me how I might catch which option is selected?

If moderator thinks this should be a new question I can raise?

Last edited by grail; 01-27-2020 at 11:36 AM.
 
Old 02-03-2020, 01:31 AM   #11
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,006

Original Poster
Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
I was not able to find a solution to catching the confirmation, but will go that a bit further on my own.

For now I have also changed to the regular .js.erb scenarios and have all point working.

Thanks for all the help
 
  


Reply

Tags
javascript, rails, turbolinks



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] Javascript ES6 and Rails grail Programming 13 01-13-2020 09:27 AM
LXer: Rails::API strips the fat off Ruby on Rails LXer Syndicated Linux News 0 11-23-2012 09:50 PM
getting rails console to work on Ubuntu 11.04 with RVM and rails 3.0.9 murankar Linux - Software 0 07-17-2011 02:28 PM
[SOLVED] [Ruby on Rails] backticks don't work under rails. bartonski Programming 1 09-08-2009 04:19 PM
LXer: Real world Rails: Caching in Rails LXer Syndicated Linux News 0 05-19-2007 09:16 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 07:19 AM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration