admin管理员组

文章数量:1300048

I have a posts.js file that looks like this:

var ready;
ready = function() {

    var toggleSidebar = $(".togglesidebar");
    var primary = $("#primary");
    var secondary = $("#secondary");

    toggleSidebar.on("click", function(){

        if(primary.hasClass("col-sm-9")){
            primary.removeClass("col-sm-9");
            primary.addClass("col-sm-12");
            secondary.css('display', 'none');
        }
        else {
            primary.removeClass("col-sm-12");
            primary.addClass("col-sm-9");
            secondary.css('display', 'inline-block');
        }
    }); 
};

var counter = function(event) {
    var fieldValue = $(this).val();
    var wc = fieldValue.trim().replace(regex, ' ').split(' ').length;
    var regex = /\s+/gi;
    var $wcField;
    var maxWc;

    if ($(this)[0] === $('#post_title')[0]) {
      $wcField = $('#wordCountTitle');
      maxWc = 7;
    } else {
      $wcField = $('#wordCountBody');
      maxWc = 150;
    }

    $wcField.html(wc);
    $wcField.toggleClass('over-limit', wc > maxWc);
};

$(document).ready(ready);
$(document).on('ready page:load', function () {
    $('#post_title, #body-field').on('change keyup paste', counter);
});

In my application.html.erb page, I have this:

<div id="secondary" class="col-sm-3 sidebar" style="display: none;">
    <aside>
      <div class="about">
        <h4>Submit Report</h4>
            <%= render "posts/form" %>
        </div>
    </aside> 
</div>  <!-- /#secondary --> 

And when I toggle this div, and the _form partial is displayed, the JS works fine in those fields.

But if I go to posts/new or /posts/:id/edit, it doesn't.

Even though if I check the source of that page, I see the post.js included there.

What could be causing this?

Edit 1

I am using Turbolinks, if that matters.

Edit 2

I tried this, per suggestions in the Answers:

var ready;
ready = function() {

    // This is the Sidebar toggle functionality
    var toggleSidebar = $(".togglesidebar");
    var primary = $("#primary");
    var secondary = $("#secondary");

    $(document).on("click", ".togglesidebar", function(){       

        if(primary.hasClass("col-sm-9")){
            primary.removeClass("col-sm-9");
            primary.addClass("col-sm-12");
            secondary.css('display', 'none');
        }
        else {
            primary.removeClass("col-sm-12");
            primary.addClass("col-sm-9");
            secondary.css('display', 'inline-block');
        }
    }); 
};

But that hasn't worked.

The key issue I am having a problem with is the 'counters', i.e. #wordCountTitle and #wordCountBody don't work on my /posts/new even though they work on the posts/index when .toggleSidebar is activated.

I have a posts.js file that looks like this:

var ready;
ready = function() {

    var toggleSidebar = $(".togglesidebar");
    var primary = $("#primary");
    var secondary = $("#secondary");

    toggleSidebar.on("click", function(){

        if(primary.hasClass("col-sm-9")){
            primary.removeClass("col-sm-9");
            primary.addClass("col-sm-12");
            secondary.css('display', 'none');
        }
        else {
            primary.removeClass("col-sm-12");
            primary.addClass("col-sm-9");
            secondary.css('display', 'inline-block');
        }
    }); 
};

var counter = function(event) {
    var fieldValue = $(this).val();
    var wc = fieldValue.trim().replace(regex, ' ').split(' ').length;
    var regex = /\s+/gi;
    var $wcField;
    var maxWc;

    if ($(this)[0] === $('#post_title')[0]) {
      $wcField = $('#wordCountTitle');
      maxWc = 7;
    } else {
      $wcField = $('#wordCountBody');
      maxWc = 150;
    }

    $wcField.html(wc);
    $wcField.toggleClass('over-limit', wc > maxWc);
};

$(document).ready(ready);
$(document).on('ready page:load', function () {
    $('#post_title, #body-field').on('change keyup paste', counter);
});

In my application.html.erb page, I have this:

<div id="secondary" class="col-sm-3 sidebar" style="display: none;">
    <aside>
      <div class="about">
        <h4>Submit Report</h4>
            <%= render "posts/form" %>
        </div>
    </aside> 
</div>  <!-- /#secondary --> 

And when I toggle this div, and the _form partial is displayed, the JS works fine in those fields.

But if I go to posts/new or /posts/:id/edit, it doesn't.

Even though if I check the source of that page, I see the post.js included there.

What could be causing this?

Edit 1

I am using Turbolinks, if that matters.

Edit 2

I tried this, per suggestions in the Answers:

var ready;
ready = function() {

    // This is the Sidebar toggle functionality
    var toggleSidebar = $(".togglesidebar");
    var primary = $("#primary");
    var secondary = $("#secondary");

    $(document).on("click", ".togglesidebar", function(){       

        if(primary.hasClass("col-sm-9")){
            primary.removeClass("col-sm-9");
            primary.addClass("col-sm-12");
            secondary.css('display', 'none');
        }
        else {
            primary.removeClass("col-sm-12");
            primary.addClass("col-sm-9");
            secondary.css('display', 'inline-block');
        }
    }); 
};

But that hasn't worked.

The key issue I am having a problem with is the 'counters', i.e. #wordCountTitle and #wordCountBody don't work on my /posts/new even though they work on the posts/index when .toggleSidebar is activated.

Share Improve this question edited Mar 5, 2015 at 19:33 marcamillion asked Feb 20, 2015 at 0:34 marcamillionmarcamillion 33.8k57 gold badges199 silver badges393 bronze badges 12
  • Have you included the file in your application.js file under app/assets/javascripts? Sounds like an asset pipeline problem. – ChrisBarthol Commented Feb 20, 2015 at 1:39
  • I have //= require_tree . in my application.js. That should cover everything in my JS directory no? – marcamillion Commented Feb 20, 2015 at 2:03
  • Are you using turbolinks? – johnsorrentino Commented Feb 20, 2015 at 3:20
  • @John Yes. I am for sure. – marcamillion Commented Feb 20, 2015 at 8:38
  • 2 Turbolinks is known to cause issues with loading JavaScript between pages. Which portion of the JavaScript isn't getting triggered? Try adding $(document).on('page:load', ready); after $(document).ready(ready); – johnsorrentino Commented Feb 20, 2015 at 17:06
 |  Show 7 more ments

10 Answers 10

Reset to default 7 +50

Instead of binding directly to the element's onclick which needs careful handling of Turbolinks events, you can use an event handler on the document, try changing the direct event

toggleSidebar.on("click", function(){

to the delegated event

$(document).on("click", ".togglesidebar", function(){

When you modify the DOM dynamically (as when Turbolinks replaces it) if you use a direct event then you would need to re-assign it.

For a detailed explanation see http://api.jquery./on/#direct-and-delegated-events


The same that goes for the first function stands for the second. Also, with delegated events the "ready" check bees unnecessary. With this in mind, your code would bee:

$(document).on("click", ".togglesidebar", function(){

    var primary = $("#primary");
    var secondary = $("#secondary");

    if(primary.hasClass("col-sm-9")){
        primary.removeClass("col-sm-9");
        primary.addClass("col-sm-12");
        secondary.css('display', 'none');
    }
    else {
        primary.removeClass("col-sm-12");
        primary.addClass("col-sm-9");
        secondary.css('display', 'inline-block');
    }

}); 

$(document).on('change keyup paste', '#post_title, #body-field', function () {
    var fieldValue = $(this).val();
    var wc = fieldValue.trim().replace(regex, ' ').split(' ').length;
    var regex = /\s+/gi;
    var $wcField;
    var maxWc;

    if ($(this)[0] === $('#post_title')[0]) {
      $wcField = $('#wordCountTitle');
      maxWc = 7;
    } else {
      $wcField = $('#wordCountBody');
      maxWc = 150;
    }

    $wcField.html(wc);
    $wcField.toggleClass('over-limit', wc > maxWc);
});

I am using something like this on one of my projects, had the same problem hope it helps:

window.onLoad = function(callback) {
  $(document).ready(callback);
  $(document).on('page:load', callback);
};

and then wrap up my functions with onLoad, something like

onLoad(function() {
  counter()
});

The onLoad function binds the ready event and the turbolink page:load event

when you do

$("selector").on("click", function(){});

you actually bind the selector to the click event.

White if you use

$(document).on("click", "selector", function(){});

You bind the click event to the document, which after the click checks if the clicked element was the selector you used. if yes, it executes the function. So you should use the second approach whenever binding events on dynamic elements.

I hope that answers the question of "why"

Long time I don't work with jQuery, but since I got here: If you have more than one element with the selector ".sidebar", I believe you'll need to use ".each" to bind the function to all elements that match that selector on the dom.

For the reference go here http://api.jquery./each/

Hope this helps, good luck.

I took a look on Turbolinks, and it does what I thought it did: It loads the HTML content of a link inside a container on the main page. Problem is, anything it loads is agnostic of whatever events and functions you have declared when the main page loaded, so indeed, after the first click, the selector on the HTML it has just loaded won't have the click event attributed to it (it was not on the DOM when you did the binding).

Possible solution: I did a little research on the .live() method, but is has been deprecated, so I remend doing something like this:

$('body').on('click','a.saveButton', saveHandler)

Binding closer to the element you need will, it seems, will assure that whatever Turbolinks loads inside the body will get the bindings you have declared.

There is a more detailed answer here: Javascript event binding persistence

The documentation for the .live hook is here: http://api.jquery./live/#typefn

I used to have the same architecture on my web pages back in the day, and I did run on a problem similar to yours.

Hope it helps.

Should it work.. make sure:

  1. Nothing id conflict.
  2. Your html structure, maybe you forgot put value in your post.
  3. Maybe wrong path posts.js, open with CTRL+U and then click post.js what is showing your code, if there so it's ok.

I try like this, it's ok(dummy):

$(document).on("click", ".togglesidebar", function(){

    var primary = $("#primary");
    var secondary = $("#secondary");

    if(primary.hasClass("col-sm-9")){
        primary.removeClass("col-sm-9");
        primary.addClass("col-sm-12");
        secondary.css('display', 'none');
    }
    else {
        primary.removeClass("col-sm-12");
        primary.addClass("col-sm-9");
        secondary.css('display', 'inline-block');
    }
	
	

}); 

$(document).on('change keyup paste', '#post_title, #body-field', function () {
    
    var fieldValue = $(this).val();
    var wc = fieldValue.trim().replace(regex, ' ').split(' ').length;
	
	console.log(wc);
	
    var regex = /\s+/gi;
    var $wcField;
    var maxWc;

    if ($(this)[0] === $('#post_title')[0]) {
      $wcField = $('#wordCountTitle');
      maxWc = 7;
    } else {
      $wcField = $('#wordCountBody');
      maxWc = 150;
    }

    $wcField.html(wc);
    $wcField.toggleClass('over-limit', wc > maxWc);
});
<script src="https://ajax.googleapis./ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="togglesidebar">Toogle</div>
<div id="wordCountTitle"></div>
<div id="wordCountBody"></div>
<div id="primary">
<div id="secondary" class="col-sm-3 sidebar" style="display: none;">
    <aside>
      <div class="about">
        <h4>Submit Report</h4>
            <input type="text" id="body-field"/>
        </div>
    </aside> 
</div>  <!-- /#secondary --> 
</div>

Honestly with Js problems involving Turbolinks the best way to have it work efficiently is to install jquery rails gem, add // require jquery-Turbolinks and then remove //require turbolinks in your Js file

Make sure that the ids you are passing on the jQuery selectors are unique. If that page/partial is loaded without a postback, you should use

//document or selector that does not get removed
var primary = $(document).find('#primary'); 
var secondary = $(document).find('#secondary');

this, with

$(document).click('eventName', 'contextSelector', function)

should help resolve the issue.

If #post_title and #body-field are created dynamically you'll need to change:

$('#post_title, #body-field').on('change keyup paste', counter);

To this:

$(document).on('change keyup paste', '#post_title, #body-field', counter);

You'll need to delegate your events to elements that exist on page load (the document itself, in this case) when targeting elements that don't exist when the page is loaded (probably #post_title and #body-field).

Regarding $('#wordCountTitle') and $('#wordCountBody') on /posts/new, have you tried just typing in either of them at the console? It's possible that the view for /posts/new is different to your index and is missing those ids (or you made them classes or some other transposition happened).

I was having all kinds of issues with Turbolinks and jquery so a few suggestions on how I would go about trying to fix your problems:

  1. Use gem 'turbolinks' to include it. Follow the instructions and do it the Ruby (gems) way rather than the PHP way.

  2. Using Turbolinks means that$(document).ready doesn't fire when a new 'page' loads. The solution is to use: $(document).on('page:load', ready) along with $(document).ready(ready). The page:load event is the Turbolinks version of the ready event.

  3. Others have suggested it but I've found it incredibly valuable in my rails app: Rather than binding events directly to selectors $("a").click(/* function */), binding to the document means that when Turbolinks loads a new page, the document binding survives the page load: $(document).on('click', 'a.particularAnchor', particularAnchorClicked)

With your specific code in mind, I would change this:

$('#post_title, #body-field').on('change keyup paste', counter);

To

$(document).on('change keyup paste', '#post_title, #body-field', counter);

It also seems to me that in your counter function you have mixed the following two lines up (regex needs to be defined first)

var wc = fieldValue.trim().replace(regex, ' ').split(' ').length;
var regex = /\s+/gi;

本文标签: javascriptWhy would this JS fire on a form partial rendered on 1 page but not anotherStack Overflow