Rails Turbolinks - remove style before caching - ajax

I have this div:
<div class="hidden" id="more-items">
that I unhide/hide if click event happens:
<button type="button" onclick="showHide()">
Via:
#showHide = () ->
$('div#more-items').toggleClass('hidden')
return
But problem is that div supposed to be closed after user comes back to the page. If he leaves it open, it remains open for 1 or 2 seconds after content loaded. Not sure at what stage it closes.
This doesn't help:
document.addEventListener 'turbolinks:before-cache', (e) ->
$('div#more-items')[0].style.display = 'none'
return
as well as this:
document.addEventListener 'turbolinks:before-render', (e) ->
e.data.newBody.getElementById('more-items').style.display = 'none'
return

I think the issue is with the indentation of the returns. The Rails asset pipeline wraps each coffeescript file in an immediately invoked function expression (IIFE), so your compiled JavaScript would look like:
(function() {
this.showHide = function() {
return $('div#more-items').toggleClass('hidden');
};
return;
document.addEventListener('turbolinks:before-cache', function(e) {
return $('div#more-items')[0].style.display = 'none';
});
return;
}).call(this);
The outer function will return early and your turbolinks:before-cache code will never get run. CoffeeScript automatically returns the last line. If you want to return undefined, make sure your return is indented at the same level as your function's code.
Changing your coffeescript to the following should fix this:
#showHide = () ->
$('div#more-items').toggleClass('hidden')
document.addEventListener 'turbolinks:before-cache', (e) ->
$('div#more-items')[0].style.display = 'none'
To tidy things up a bit, you could do also the following:
HTML:
<button type="button" id="toggle-more">
CoffeeScript:
$(document).on 'click', '#toggle-more', ->
$('#more-items').toggleClass('hidden')
$(document).on 'turbolinks:before-cache', ->
$('#more-items').addClass('hidden')

Related

How to show a spinner in Meteor for the duration of an event

In a Meteor app, one function that runs on a click takes a while to run on some slower devices. As a result, on these slow devices, the app doesn't seem to do anything until after the function has completed.
The function in question loops through a largish array. It does not do any external stuff, or method calls.
To visually clue in the user, I want to show a spinner to the user (on slow devices). Ideally, I would show the spinner at the start of the event, and then remove the spinner at the end of the event. However, if my understanding of Meteor is correct (and based on what seems to be happening when I try it out), all template updates specified during the event are only propagated at the end of the event. So, as a result, the spinner never shows.
How can I make this work as intended?
Current setup (edit with actual code):
In categories.html:
{{#if hasSubCategories}}
<a href='#_' id='categorySelect-{{id}}' class='btn categorySelect pull-right
{{#if allSubsSelected}}
btn-danger
{{else}}
btn-success
{{/if}}
'>{{#if isFlipping}}<span id='spinner-{{id}}'>flip</span>{{/if}}<span class="glyphicon
{{#if allSubsSelected}}
glyphicon-remove
{{else}}
glyphicon-ok
{{/if}}
" aria-hidden="true"></span></a>
{{/if}}
In categories.js:
Template.categories.events({
"click .categorySelect": function (event) {
Session.set('categorySpinner', this.id);
categoryFlipper(this.id, function() {
Session.set('categorySpinner', "");
});
return false;
},
});
Template.categories.helpers({
allSubsSelected: function() {
var finder = Categories.find({parentId: this.id});
var allSelected = true;
finder.forEach(function(item) {
if (!($.inArray(item.id, Session.get("categoriesSelected")) !== -1)) {
allSelected = false;
}
});
return allSelected;
},
isFlipping: function() {
if (Session.get("categorySpinner") == this.id)
return true;
else
return false;
}
});
In main.js:
categoryFlipper = function (id, callback) {
var finder = Categories.find({parentId: id});
var allSelected = true;
finder.forEach(function(item) {
if (!($.inArray(item.id, Session.get("categoriesSelected")) !== -1)) {
allSelected = false;
}
});
var t = Session.get("categoriesSelected");
if (allSelected) {
finder.forEach(function(item) {
t.splice($.inArray(item.id, t), 1);
});
}
else {
finder.forEach(function(item) {
if (!($.inArray(item.id, t) !== -1)) {
t.push(item.id);
}
});
}
Session.set("categoriesSelected", t);
callback();
}
Looking at your sample code (though I still don't have a complete picture since I don't see the helper definitions for allSubsSelected and isFlipping), I'm pretty sure that your categoryFlipper function executes so quickly that you never really see the spinner. There's nothing in that code that would really take any significant amount of time. You have a find() call, but that's not what really takes the most amount of time. It's your subscribe() call that you usually need to figure in some delay for as the data is pulled from the database to the mini-mongo client in the browser.
Something like this is fairly common:
{{#unless Template.subscriptionsReady}}
spinner here...
{{else}}
Content here.
{{/unless}}
That way, while your subscription is triggering the publication and pulling data down, Template.subscriptionsReady returns false, and the spinner is shown. Try that approach instead.

How to resolve jQuery conflict

I have a few lines of jQuery codes that load external pages when the links are clicked.
$(document).ready(function() {
$(".link").click(function (e) {
e.preventDefault();
var $urlToLoad = $(this).attr('href');
$("#loadarea").load($urlToLoad, function(data){
$("#loading").fadeIn('fast').fadeOut('fast');
$("#loadarea").hide().fadeIn('slow');
return false;
});
});
});
This works fine. However, when I add this one single line of additional code, which is essential on this page, "$ is undefined" error shows up.
I've tried every single technique at http://api.jquery.com/jQuery.noConflict/ but, I can't resolve the conflict.
function goto(id, t){
$(".contentbox-wrapper").animate({"left": -($(id).position().left)}, 600);
$('#slide a').removeClass('active');
$(t).addClass('active');
}
I've tried var jq=jQuery.noConflict(); to replace $ but this doesn't solve the problem.
I guess I do not understand enough of jQuery to resolve this conflict and I would really appreciate anyone who can explain what is going on so that I can learn from this.
So all together, it looks like this:
<script>
$(document).ready(function() {
$(".link").click(function (e) {
e.preventDefault();
var $urlToLoad = $(this).attr('href');
$("#loadarea").load($urlToLoad, function(data){
$("#loading").fadeIn('fast').fadeOut('fast');
$("#loadarea").hide().fadeIn('slow');
return false;
});
});
});
function goto(id, t){
$(".contentbox-wrapper").animate({"left": -($(id).position().left)}, 600);
$('#slide a').removeClass('active');
$(t).addClass('active');
}
</script>
Then I have one inline code to fire the script.
(a class="active" href="#" onClick="goto('#kr', this); return false">test
Strange thing is, that even with the error, it fires on second click.
/////////////////////////////////////
The conflict/error was resolved by converting the inline javascript.
Thanks to Huangism below.
I would just rewrite the anchor from
<a class="active" href="#" onClick="goto('#kr', this); return false">test</a>
To
<a class="active" href="#kr">test</a>
For the jquery
$('.active').on('click', function() {
var $this = $(this);
var id = $this.attr('href');
$(".contentbox-wrapper").animate({"left": -($(id).position().left)}, 600);
$('#slide a').removeClass('active');
$this.addClass('active');
return false;
});
$(document).ready(function() {
});
Should be rewritten as:
jQuery(function($) {
});
That might help you out here.
It's probably to do with the order that you're including your js files in, make sure that jquery is the first loaded file.

Prototype.js event observe click intercept and stop propagation

I have a page that is built around a wrapper with some very defined logic. There is a Save button on the bottom of the wrapped form that looks like this:
<form>
... my page goes here...
<input id="submitBtnSaveId" type="button" onclick="submitPage('save', 'auto', event)" value="Save">
</form>
This cannot change...
Now, I'm writing some javascript into the page that gets loaded in "...my page goes here...". The code loads great and runs as expected. It does some work around the form elements and I've even injected some on-page validation. This is where I'm stuck. I'm trying to "intercept" the onclick and stop the page from calling "submitPage()" if the validation fails. I'm using prototype.js, so I've tried all variations and combinations like this:
document.observe("dom:loaded", function() {
Element.observe('submitBtnSaveId', 'click', function (e) {
console.log('Noticed a submit taking place... please make it stop!');
//validateForm(e);
Event.stop(e);
e.stopPropagation();
e.cancelBubble = true;
console.log(e);
alert('Stop the default submit!');
return false;
}, false);
});
Nothing stops the "submitPage()" from being called! The observe actually works and triggers the console message and shows the alert for a second. Then the "submitPage()" kicks in and everything goes bye-bye. I've removed the onclick attached to the button in Firebug, and my validation and alert all work as intended, so it leads me to think that the propagation isn't really being stopped for the onclick?
What am I missing?
So based on the fact that you can't change the HTML - here's an idea.
leave your current javascript as is to catch the click event - but add this to the dom:loaded event
$('submitBtnSaveId').writeAttribute('onclick',null);
this will remove the onclick attribute so hopefully the event wont be called
so your javascript will look like this
document.observe("dom:loaded", function() {
$('submitBtnSaveId').writeAttribute('onclick',null);
Element.observe('submitBtnSaveId', 'click', function (e) {
console.log('Noticed a submit taking place... please make it stop!');
//validateForm(e);
Event.stop(e);
e.stopPropagation();
e.cancelBubble = true;
console.log(e);
alert('Stop the default submit!');
return false;
submitPage('save', 'auto', e);
//run submitPage() if all is good
}, false);
});
I took the idea presented by Geek Num 88 and extended it to fully meet my need. I didn't know about the ability to overwrite the attribute, which was great! The problem continued to be that I needed to run submitPage() if all is good, and that method's parameters and call could be different per page. That ended up being trickier than just a simple call on success. Here's my final code:
document.observe("dom:loaded", function() {
var allButtons = $$('input[type=button]');
allButtons.each(function (oneButton) {
if (oneButton.value === 'Save') {
var originalSubmit = oneButton.readAttribute('onclick');
var originalMethod = getMethodName(originalSubmit);
var originalParameters = getMethodParameters(originalSubmit);
oneButton.writeAttribute('onclick', null);
Element.observe(oneButton, 'click', function (e) {
if (validateForm(e)) {
return window[originalMethod].apply(this, originalParameters || []);
}
}, false);
}
});
});
function getMethodName(theMethod) {
return theMethod.substring(0, theMethod.indexOf('('))
}
function getMethodParameters(theMethod) {
var parameterCommaDelimited = theMethod.substring(theMethod.indexOf('(') + 1, theMethod.indexOf(')'));
var parameterArray = parameterCommaDelimited.split(",");
var finalParamArray = [];
parameterArray.forEach(function(oneParam) {
finalParamArray.push(oneParam.trim().replace("'","", 'g'));
});
return finalParamArray;
}

Angular ng-click on inputed later on dom

i'm changing a innter html of a dom element with a button. And when button is clicked i want to fire another controller function. Something like that. ... But is not working :).
$scope.addBtn = function() {
$('domtarget').html('<button ng-click="removeButton();"></button>');
}
$scope.removeBtn = function() {
$('domtarget').html('');
}
Please suggest fix :)
Do not modify DOM inside your controller, ever.
<div ng-show="showMe"></div>
<button ng-click="showMe = !showMe;anotherAction()">Switch</button>
<button ng-click="someOtherAction()">Switch2</button>
.
function SomeCtrl($scope) {
$scope.showMe=true;
$scope.anotherAction = function () {
alert("gotcha");
};
$scope.someOtherAction = function () {
$scope.showMe = !$scope.showMe;
$scope.anotherAction();
};
}
For hiding/showing an element conditionally, use ng-show or ng-hide.
For firing an event on click, use ng-click

How to check if a button is clicked in JavaScript

How to check if a button is clicked or not in prototype JavaScript?
$('activateButton').observe('click', function(event) {
alert(hi);
});
The code above is not working.
With this button:
<button id="mybutton">Click Me</button>
Use this:
$('mybutton').observe('click', function () {
alert('Hi');
});
Tested and works, here.
You might want to encase it in a document.observe('dom:loaded', function () { }) thingy, to prevent it executing before your page loads.
Also, just an explanation:
The single dollar sign in Prototype selects an element by its id. The .observe function is very similar to jQuery's .on function, in that it is for binding an event handler to an element.
Also, if you need it to be a permanent 'button already clicked' thingy, try this:
$('mybutton').observe('click', function () {
var clicked = true;
window.clicked = clicked;
});
And then, if you want to test if the button has been clicked, then you can do this:
if (clicked) {
// Button clicked
} else {
// Button not clicked
}
This may help if you are trying to make a form, in which you don't want the user clicking multiple times.
How one may do it in jQuery, just for a reference:
$('#mybutton').on('click', function () {
alert('Hi');
});
Note that, the jQuery code mentioned above could also be shortened to:
$('#mybutton').click(function () {
alert('Hi');
});
jQuery is better in Prototype, in that it combines the usage of Prototype's $ and $$ functions into a single function, $. That is not just able to select elements via their id, but also by other possible css selection methods.
How one may do it with plain JavaScript:
document.getElementById('mybutton').onclick = function () {
alert('Hi');
}
Just for a complete reference, in case you need it.
$('body').delegate('.activateButton', 'click', function(e){
alert('HI');
});

Resources