turbolinks / turbolinks-classic

Classic version of Turbolinks. Now deprecated in favor of Turbolinks 5.
MIT License
3.54k stars 431 forks source link

Selector-based replace #616

Open firedev opened 9 years ago

firedev commented 9 years ago

Hi,

After working a bit with Cells and Trailblazer I have adopted some concepts from React.js and built view-less system where updates are happening from the controller. It looks a bit like Turbolinks3:

class CalendarController
    def update
     ...
      update_cells(
        concept('worktime/form', @worktime),
        concept('calendar/day', day),
        concept('calendar/stat', day.month, user: current_user)
      )
    end
end

update_cells just calls update method on each concept and glues the resulting JavaScripts together:

class ApplicationController
  def update_cells(*cells)
    render text: cells.flatten.join
  end
end

Each cell has it's unique-ish document identifier, but instead of id I use unique CSS class.

Why?

That allows me to do sweeping updates on the page. When one cell is changed, it is being re-rendered everywhere in the document. And the only thing that stops me from jumping on Turbolinks3 train is that Turbolinks uses id.

Is there a way to replace document fragments based on CSS class? Was it even considered? Why not?

Thanks.

tortuetorche commented 9 years ago

Hi @firedev,

In the Turbolinks docs, there is a concept of namespaced ids, maybe you can use it instead of CSS class?

If the id contains a colon, the key before the colon can also be targeted to replace many nodes with a similar prefix.

<div id="comments:1"></div>
<div id="comments:2"></div>
<!-- ... -->
<div id="comments:12"></div>

Cheers, Tortue Torche

firedev commented 9 years ago

Yep, that is the problem. I don't have the luxury of React that keeps track of it's components and can't do cycle in rails for obvious reasons. All in all id-based approach seems like a dead-end to me. I use "semi-unique" identifiers made from cell name and dom_id of the main object so it looks kind of like this:

<div class="calendar-day_1234">

This way I don't have to worry about updating the wrong fragment and if there are more than one - no problem.

tortuetorche commented 9 years ago

Sorry, I don't really understand what you said. My English knowledge isn't as good as I hope.

If you use dom_id() helper, you can prefix it with comments:.

In a Rails console:

comment = Comment.first
helper.dom_id(comment)
=> "comment_2"
helper.dom_id(comment, 'comments:')
=> "comments:_comment_2"

The generated id is a bit weird, but it should work.

Then, when you run this Javascript command:

Turbolinks.replace('some HTML tags', {change: 'comments'});

Turbolinks will replace all the HTML nodes with an id beginning with this string: comments:.

firedev commented 9 years ago

I render the same fragment in a few places on the page, I can't generate #comments:comment_1:1 #comments:comment_1:2 #comments:comment_1:3. But if using css selectors, or even data-id one is good enough: .comments-comment_1.

Rails is going backwards here. Imagine each fragment is very expensive to compute, pre-rendederd and cached and I can't change id for each instance. What about russian-doll caching?

tortuetorche commented 9 years ago

Why do you render the same fragment in a few places of the page?

firedev commented 9 years ago

I am building a worktime calendar app, there are few stats cells on each end, there are day cells you can edit in a modal window and they are being updated on-the-fly.

Just remembers that this came up before and I was asking on StackOverflow: http://stackoverflow.com/questions/25418379/storing-object-id-in-html-document-id-vs-data-id-vs

Apparently there are cases when you want more than one kind of the same object on the page.

tortuetorche commented 9 years ago

Well, maybe you can do something like that:

class Comment < ActiveRecord::Base
  attr_accessor :context_id

  def to_key
    super << @context_id
  end
end

Where @context_id is the context of you record in the page.

tortuetorche commented 9 years ago

Or monkey patching the dom_id() helper to add a third argument, who appends a suffix.

comment = Comment.first
dom_id(comment, 'comments:', 1)
=> "comments:_comment_2_1"
firedev commented 9 years ago

I'll just rename the title to a more general 'selector-based' replacement, so it allows more freedom of speech.