Micropost character countdown (Rails Tutorial, 2nd

2020-02-06 09:53发布

I attempted the micropost character countdown in The Rails Tutorial (Chapter 10, Exercise 7) using the information here as a base and with some help from StackOverflow answers here and here.

On screen, it looks like this, and as you get closer to the character limit, the text gradually turns redder, and once the micropost is overlimit, the Post button disables, finishing like so.

The current implementation looks like:

views/shared/_micropost_form.html.haml

= form_for @micropost do |f|
  = render 'shared/error_messages', object: f.object
  .field= f.text_area :content, placeholder: t('.compose_micropost')
  %span
    .remaining= t('.characters_remaining').html_safe
    .countdown
  = f.submit t('.post'), class: "btn btn-large btn-primary"

assets/javascripts/microposts.js.coffee

updateCountdownAttributes = (toRemove, toAdd = null) ->
  for attr in toRemove
    $(".remaining, .countdown").removeClass attr
  if toAdd
    $(".remaining, .countdown").addClass toAdd
    if toAdd is "overlimit"
      $("input.btn.btn-large.btn-primary").attr("disabled", "true")
    else
      $("input.btn.btn-large.btn-primary").removeAttr("disabled")

updateCountdown = ->
  remaining = 140 - $("#micropost_content").val().length
  toRemove = ["nearlimit", "almostlimit", "overlimit"]
  if remaining > 19
    updateCountdownAttributes(toRemove)
  if remaining < 20
    toAdd = (toRemove.filter (attr) -> attr is "nearlimit").toString()
    updateCountdownAttributes(toRemove, toAdd)
  if remaining < 11
    toAdd = (toRemove.filter (attr) -> attr is "almostlimit").toString()
    updateCountdownAttributes(toRemove, toAdd)
  if remaining < 0
    toAdd = (toRemove.filter (attr) -> attr is "overlimit").toString()
    updateCountdownAttributes(toRemove, toAdd)
  $(".countdown").text remaining

$(document).ready ->
  $(".countdown").text 140
  $("#micropost_content").change updateCountdown
  $("#micropost_content").keyup updateCountdown
  $("#micropost_content").keydown updateCountdown
  $("#micropost_content").keypress updateCountdown

assets/stylesheets/custom.css.scss

...
/* Micropost character countdown */

.remaining, .countdown {
  display: inline;
  color: $grayLight;
  float: right;
}

.overlimit {
  color: $red;
}

.almostlimit {
  color: hsl(360, 57%, 21%);
}

.nearlimit {
  color: $gray;
}

config/locales/en.yml

en:
  ...
  shared:
    ...
    micropost_form:
      compose_micropost: "Compose new micropost..."
      post: "Post"
      characters_remaining: "&nbsp;characters remaining."

From here, I have two questions/problems:

The first is, if possible, I want to be able to do proper pluralization of the "characters remaining" string. Perhaps something like:

views/shared/_micropost_form.html.haml

...
%span
  .remaining= t('.characters_remaining', count: [[.countdown value]]).html_safe
  .countdown
...

config/locales/en.yml

...
micropost_form:
  ...
  characters_remaining: 
    one: "&nbsp;character remaining."
    other: "&nbsp;characters remaining."

However, I don't know how to retrieve the value within the .countdown div in a way that I can pass it over to the count parameter. How can I do this?

Assuming the first problem can be solved, I also want to get rid of the minus number of characters and instead change "-2 characters remaining" to "2 characters over". Perhaps using some kind of branching logic in the view and some javascript to change the negative number to a positive number...? I'm not really sure here, so any help would be appreciated.

views/shared/_micropost_form.html.haml

...
%span
  - [[ if .countdown value < 0 ]]
    .remaining= t('.characters_over', 
                  count: [[positive .countdown value]]).html_safe
  - [[ else ]]
    .remaining= t('.characters_remaining', count: [[.countdown value]]).html_safe
  .countdown
...

config/locales/en.yml

...
micropost_form:
  ...
  characters_remaining: 
    one: "&nbsp;character remaining."
    other: "&nbsp;characters remaining."
  characters_over: 
    one: "&nbsp;character over."
    other: "&nbsp;characters over."

2条回答
小情绪 Triste *
2楼-- · 2020-02-06 10:29

I am also going through this tutorial and found this post and while I like the css you added to make this look uniform (which I have taken to use as my own :)) I think your solution for this is over-complicated. For me it was simply two changes: the js script and adding the script to my view.

My JS file: character_countdown.js

function updateCountdown() {
  // 140 characters max
  var left = 140 - jQuery('.micropost_text_area').val().length;
  if(left == 1) {
    var charactersLeft = ' character left.'
  }
  else if(left < 0){
    var charactersLeft = ' characters too many.'
  }
  else{
    var charactersLeft = ' characters left.'
  }
  jQuery('.countdown').text(Math.abs(left) + charactersLeft);
}

jQuery(document).ready(function($) {
  updateCountdown();
  $('.micropost_text_area').change(updateCountdown);
  $('.micropost_text_area').keyup(updateCountdown);
});

and here is where I added it into the view

<script src="app/assets/javascripts/character_countdown.js"></script>
<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>

Please let me know what you think :)

查看更多
Bombasti
3楼-- · 2020-02-06 10:42

I've found a solution to both my questions (pluralization and getting rid of the minus numbers in all locales) that I think is pretty good, so I'll explain it in detail here and hopefully someone will find it useful.

If you want to see what it looks like before delving into the details, you can try it out for yourself at my Sample App deployment at Heroku.

Config

This solution uses the i18n-js gem, which is "a small library to provide the Rails I18n translations on the Javascript." The gem is great, but unfortunately doesn't play nice with Heroku as I would like, and doesn't seem like it will for the foreseeable future. So, the following configurations will need to be changed:

config/application.rb

# ...
config.assets.initialize_on_precompile = true

This means that before every deploy to Heroku, rake assets:precompile will need to be run, and once you've confirmed a successful deployment, run rake assets:clean to begin developing assets again. If this is too annoying, you'll need another solution.

Update:

If you enable user-env-compile in your Heroku environment, you can get Heroku to precompile your assets and still use the i18n-js gem. Instructions on how to do so are here, and I think it's worth doing for as long as Heroku will support the functionality.

The solution

Gemfile

# ...
gem 'i18n-js', '2.1.2'

app/assets/javascripts/application.js

// ...
//= require i18n
//= require i18n/translations

Due to the Heroku settings above, at this point I needed to run

$ rake i18n:js:setup

to copy i18n-js.yml to the config folder.

app/views/layouts/application.html.haml

%html
  %head
    # ...
    = render 'layouts/i18n_js'

app/views/layouts/_i18n_js.html.haml

:javascript
  I18n.defaultLocale = "#{I18n.default_locale}";
  I18n.locale = "#{I18n.locale}";

app/views/shared/_micropost_form.html.haml

# ...
.field= f.text_area :content, placeholder: t('.compose_micropost')
%span.countdown
= f.submit t('.post'), class: "btn btn-large btn-primary"

app/assets/stylesheets/custom.css.scss

/* Micropost character countdown */

.countdown {
  display: inline;
  color: $grayLight;
  float: right;
}
// ...

app/assets/javascripts/microposts.js.coffee
(I'm not that good with javascript/coffeescript, so there's likely room for improvement/refactoring here)

updateCountdownString = (remaining) ->
  if remaining > 1 or remaining is 0
  $(".countdown").text I18n.t('shared.micropost_form.characters_remaining.other',
                              count: remaining)
  else if remaining is 1
    $(".countdown").text I18n.t('shared.micropost_form.characters_remaining.one',
                                count: remaining)
  else if remaining is -1
    $(".countdown").text I18n.t('shared.micropost_form.characters_over.one',
                                count: -remaining)
  else
    $(".countdown").text I18n.t('shared.micropost_form.characters_over.other',
                                count: -remaining)

takeFromCollection = (collection, className) ->
  (collection.filter (attr) -> attr is className).toString()

updateCountdownAttributes = (remaining) ->
  toRemove = ["nearlimit", "almostlimit", "overlimit"]
  if remaining < 20
    toAdd = takeFromCollection(toRemove, "nearlimit")
  if remaining < 11
    toAdd = takeFromCollection(toRemove, "almostlimit")
  if remaining < 0
    toAdd = takeFromCollection(toRemove, "overlimit")

  if toAdd isnt null
    for attr in toRemove
      $(".countdown").removeClass attr
    $(".countdown").addClass toAdd
  if toAdd is "overlimit"
    $("input.btn.btn-large.btn-primary").attr("disabled", "true")
  else
    $("input.btn.btn-large.btn-primary").removeAttr("disabled")

updateCountdown = ->
  remaining = 140 - $("#micropost_content").val().length
  updateCountdownString(remaining)
  updateCountdownAttributes(remaining)

$(document).ready ->
  $(".countdown").text I18n.t('shared.micropost_form.characters_remaining.other',
                              count: 140)
  $("#micropost_content").on("change keyup keydown keypress paste drop",
                             updateCountdown)

config/locales/en.yml (other locales have the same keys in the same style)

shared:
  # ...
  micropost_form:
    characters_remaining:
      one: "%{count} character remaining."
      other: "%{count} characters remaining."
    characters_over:
      one: "%{count} character over limit."
      other: "%{count} characters over limit."
查看更多
登录 后发表回答