WordPress.org

Make WordPress Core

Opened 4 years ago

Last modified 21 months ago

#30505 new defect (bug)

Backbone subviews cannot be attached directly to DOM elements

Reported by: rmccue Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: Administration Keywords: needs-patch
Focuses: javascript Cc:

Description

Right now, you need selectors to attach subviews. Even if you have a direct reference to an element in the DOM, it's not possible to attach to this directly.

This is an issue when you're working with an existing DOM structure to attach views, and the elements don't have unique identifiers for a selector.

Backbone itself is happy to let you do this in a lot of places; this is why you can pass in el when constructing a view, for example. However, wp.Backbone.Views uses ! _.isString to overload some of the function parameters, which means you can never pass it through.

Change History (9)

#1 follow-up: @adamsilverstein
4 years ago

Can you give an example of how you are trying to "attach subviews directly to DOM elements" - maybe a code snippet?

I don't understand the use case and also: I'm confused - are we talking wp.Backbone.Subviews: https://core.trac.wordpress.org/browser/tags/4.0.1/src/wp-includes/js/wp-backbone.js#L12 or wp.Bckbone.View: https://core.trac.wordpress.org/browser/tags/4.0.1/src/wp-includes/js/wp-backbone.js#L339

#2 in reply to: ↑ 1 @rmccue
4 years ago

Replying to adamsilverstein:

Can you give an example of how you are trying to "attach subviews directly to DOM elements" - maybe a code snippet?

Say for example that you have a list of elements already on the page:

<div id="component-list">
	<div class="component"></div>
	<div class="component"></div>
	<div class="component"></div>
</div>

Typically with Backbone, you can attach these existing elements to views:

jQuery('#component-list .component').each( function () {
	var myview = new Backbone.View( { el: this } );
} );

However, if #component-list is itself a view, and you want to attach each subelement as a subview, you can't do the same. Selector needs a unique string selector for each of the .component elements that you want to attach.

If each of the elements has an ID, this isn't as hard:

	initialize: function () {
		var subviews = this.views;
		this.$('.component').each(function () {
			// If each of the elements has an ID, you can use:
			var selector = '#' . this.id;
			subviews.add( selector, new Backbone.View( { el: this } ) );
		});
	}

I don't want (or otherwise need) IDs on each of these though. Instead of selector, it'd be nice to be able to pass the element itself (or just a string selector).

I don't understand the use case and also: I'm confused - are we talking wp.Backbone.Subviews: https://core.trac.wordpress.org/browser/tags/4.0.1/src/wp-includes/js/wp-backbone.js#L12 or wp.Bckbone.View: https://core.trac.wordpress.org/browser/tags/4.0.1/src/wp-includes/js/wp-backbone.js#L339

This is an issue in wp.Backbone.Subviews, which is used by wp.Backbone.View. The original description was a little unclear here, sorry!

#3 @adamsilverstein
4 years ago

Thanks for clarifying!

I think this is an edge use case :)

maybe what you could do is extend add and if the element doesn't have an id, generate and add one on the fly? would that fix the issue?

This ticket was mentioned in Slack in #core by rmccue. View the logs.


4 years ago

#5 @chriscct7
3 years ago

  • Keywords needs-patch added

#6 follow-up: @georgestephanis
2 years ago

Isn't the reason that this requires selectors and not elements so that it can "re-find" the subviews after a render of the parent view? If it was a reference to the prior element itself, wouldn't that reference then be broken upon a redraw of the parent?

#7 in reply to: ↑ 6 @adamsilverstein
2 years ago

Replying to georgestephanis:

Isn't the reason that this requires selectors and not elements so that it can "re-find" the subviews after a render of the parent view? If it was a reference to the prior element itself, wouldn't that reference then be broken upon a redraw of the parent?

Indeed - internally wp.backbone keeps track of the subviews that have been added to a view - these can be added, removed and replaced etc. independently. I think the reference as a string (for the 'selector') makes sense.

Note that you CAN add subviews without a selector, in which case the subview is 'attached' to the root element of the parent view.

@rmccue can you try your code without a selector at all? just pass the view(s) and options to wp.Backbone.View.add (the selector is optional) - does that do what you need?

#8 @georgestephanis
2 years ago

Actually, I've been doing more reading, and it seems 'finding' is probably why we have detach() available for subviews -- https://core.trac.wordpress.org/browser/trunk/src/wp-includes/js/wp-backbone.js#L190 -- so they can be detached and then dropped back in without overwriting them.

I think?

I'm still very much learning this, so expect half of everything I say to be wrong, and the other half to be suspect. :P

#9 @nerrad
21 months ago

So I ran into this while working on a project so just thought I'd post what I'm finding so far here. Basically my use case is I've got an already loaded wp list table. I have a main parent view that is the list table tbody element. And then I want each existing row in the table to be its own subview. For this initial implementation backbone along with the wp-api client js library is just being used to do quick edit like tasks in the rows. The initial models data in the subview isn't fetched until an event is fired in the row.

Anyways, where I ran into issues is similar as Ryan first described in here where in my case I couldn't add the tr selectors as subviews in the parent view via a loop. I then tried what Adam suggested (leaving the selector blank on creating the subview) and then added my row element as a selector in the el property for the subview and so far that seems to be working. Something like this:

//setup all rows as their own 
this.$el.find( 'tr' ).each( function() {
     var row = $(this);
     self.views.add( new ev.SourceView( { el: row, model:new wp.api.models.Source( {id: 0} ) } ) )
});

I'm really new to backbone so still learning as I go, so you can reuse the disclaimer @georgestephanis gave here as well :)

Last edited 21 months ago by nerrad (previous) (diff)
Note: See TracTickets for help on using tickets.