Override Marionette.Region's getEl method in Backbone.Marionette - javascript

I've set up a project where I've extended a Backbone.Marionette.Layout that contains two different regions. This layout can be used as a component throughout the application. In particular, the regions are set up like the following.
regions : {
masterRegion : { selector: '[data-region=master]' },
slaveRegion: { selector: '[data-region=slave]' }
},
In particular, I'm using a data-region selector to inject the view I'm interested in.
When such a layout is used in a tree structure views are duplicated since getEl function adresses the wrong region to inject the view. Obviously it's my fault and within Marionette (v1.1.0) doc the following is written.
override the getEl function if we have a parentEl this must be
overridden to ensure the selector is found on the first use of the
region. if we try to assign the region's el to parentEl.find(selector)
in the object literal to build the region, the element will not be
guaranteed to be in the DOM already, and will cause problems
where getEl is defined as
getEl: function(selector){
return Marionette.$(selector);
}
So, my question is the following. What does this mean? How can I override this method? Where is the correct to perform such an override?
Hope it's clear.

Here's my understanding of this:
the points below apply to the case where the layout is contained within another element ("if we have a parentEl")
the first time you use a region, Marionette needs to select the proper DOM element to populate, according to the selector string ("ensure the selector is found on the first use of the region")
you can't simply look for the selector in the parentEl ("if we try to assign the region's el to parentEl.find(selector) in the object literal"), because the DOM element we want isn't necessarily in the DOM yet ("the element will not be guaranteed to be in the DOM already")
In other words, the first time you use a region (e.g. with a call to the show method), Marionette needs to build a region instance and associate it with the correct DOM element (specified by the selectorattribute).
However, before Marionette can look for the DOM element within the containing parent element, it must ensure that all required DOM elements (most importantly the one we're looking for) have loaded.
Does that make more sense to you?
Edit based on flexaddicted's comment.
Could you suggest me a the correct way to achieve this? Is there any
manner to override the method below?
I don't think you need to override this method. The comment indicates why the DOM element is fetched that way instead of by direct assignment when the region is built, but it should still work properly with a tree structure (since parents can still be determined properly).
I think the problem might be with your region selector: as it is "generic", it can potentially match multiple elements (as opposed to selecting with an id attribute that should match only 1 element), and could be matching a DOM element you're not expecting such as a child view. (This of course depends on when Marionette looks at the DOM to fetch the selector.)
Also, I'd consider using a composite view for your tree structure needs if possible. See http://davidsulc.com/blog/2013/02/03/tutorial-nested-views-using-backbone-marionettes-compositeview/ and http://lostechies.com/derickbailey/2012/04/05/composite-views-tree-structures-tables-and-more/

Related

How to get the component that rendered a dom element with Vue.js

How to get the component that rendered a dom element with Vue.js ?
For example, suppose you want to retrieve which component "owns" a dom element that the user has selected, how would you do ? (it seems to be implemented in the dev tools, but I can't find a way neither in the documentation, neither on SO)
(I mean, given the DOM element, I know how to retrieve what element is selected)
DISCLAIMER : This may not be the right solution for common use cases. Always prefer handling event & co. in the root component using direct sub-component reference if you can
I do not know if this is safe or officially supported, but assuming you're trying to access the component from some external-to-Vue code, this will return the VueComponent object for a given DOM element (substitute your own DOM selector as needed):
document.getElementById('foo').__vue__
If used on the app's root element, it will instead return the Vue constructor object.
(This appears to work in both Vue 1.x and 2.x.)
This is possibly not the most elegant solution, but you can use mixins to achieve this:
var elemOwner = {
mounted: function() {
this.$el.setAttribute("isVueComponent", this.$options.name);
}
};
As long as you set the mixin to the components you need it in, when you click an element you can test the attributes to see if there's a component name in there.
See this codepen for a fuller example: https://codepen.io/huntleth/pen/EpEWjJ
Clicking the smaller blue square will return the component name of the component that rendered it.
EDIT - It should be noted though that this obviously would only work if the element is actually inside that components root element. I think that would be the case for almost all uses.
Getting this.$parent refers to the parent of a component.

Changing inner text value of tab through javascript

I'm learning Javascript right now, and attempting to change the text title of a particular tab. It's actually part of a larger Shiny dashboard project, but I want to add some custom functionality to a few tabs. Below are the tabs in question:
Simple enough. I first access my tabs in my Javascript file:
var tabScrub2 = $(document).find('[data-value="scrubTab2"]');
console.log(tabScrub2);
When I use Firefox's developer console, I see that the tab is an object:
Moreover, it looks like I need to change the innerText property of 0, whatever this is, since that corresponds to the title of my tab (the innerText of 1 corresponds to the text inside scrubTab2). However, I'm not familiar with the actual object type being returned here:
Simply put, how the heck do I access and manipulate properties from this? And am I actually accessing an array? When I type in
var scrub2 = tabScrub2["1"];
console.log(scrub2);
I get an HTML element. I'm seen the a element in CSS and jQuery, but am not super familiar with how to manipulate its properties programmatically? How do I go about accessing and manipulating the innerText properties of this via Javascript? For instance, how would I hide scrubTab2, or change its title to something else?
The first object you're seeing is jQuery's wrapper around the real DOM elements. It's not an actual array, but it does contain all of the elements that matched your query under zero-indexed properties (e.g. "0" and "1") which allows you to access to them via an array-like API (e.g. tabScrub[1]).
Your method of grabbing a node using tabScrub2["1"] is correct (see this question in the jQuery FAQ). It's more likely to see that done with a numeric key though (i.e. tabScrub[1]) because that matches the way you would access an element in a normal array.
As far as manipulating properties of the DOM node, the DOM's API is notoriously inconsistent and quirky (hence the need for things like jQuery in the first place). However, for your use case you can just assign a string to the innerText property directly (e.g. tagScrub2[1].innerText = "Tab title"). MDN is a great resource if you're looking for reference material on other parts of the DOM.
A side note: if you're looking for a specific element you should use a query that will only match that element. It's generally a bad sign if you're grabbing extra elements and then accessing the element you want at a key other than 0. If you're doing this then your code depends on other (potentially unrelated) nodes in the DOM existing before your node, and if/when you change those nodes your original code will break.
Just use jQuery eq method to get the relevant object index from the array.
For an example
//Query and get first element.
var tabScrub2 = $(document).find('[data-value="scrubTab2"]:eq(0)');
//Hide
tabScrub2.hide();
//Change title
tabScrub2.attr("title", "New Title Text");
Lean more about jQuery eq here.
https://api.jquery.com/eq/
Since you use jquery selectors tabScrub2[0] returns the native DOM element instead of another jQuery object. Therefore the hide function won't work in that object since the native DOM element doesn't implement such type of functionality for an element. That's why you have to use jQuery pseudo selector as above. Because hide will only work with a jQuery object.

The right way to create custom sub-elements with X-Tag

So I know the x-tag web component library enables you create custom elements that appear in HTML like this:
<x-my-custom-element>my content</x-my-custom-element>
However, what if I wanted to create multiple custom sub-elements, like this:
<x-my-custom-element>
<x-my-custom-element-child>
<x-my-custom-element-grandchild></x-my-custom-element-grandchild>
</x-my-custom-element-child>
</x-my-custom-element>
Is the right way to simply call xtag.register() three times, like so:
xtag.register('x-my-custom-element', {...});
xtag.register('x-my-custom-element-child', {...});
xtag.register('x-my-custom-element-grandchild', {...});
Also, is there any way to force a sub-element to always be a child of another element? In other words, this would work:
<x-my-custom-element-parent>
<x-my-custom-element-child></x-my-custom-element-child>
</x-my-custom-element-parent>
but this wouldn't:
<x-my-custom-element-child>
<x-my-custom-element-parent></x-my-custom-element-parent>
</x-my-custom-element-child>
Because your custom element names are valid (contain a "dash" - character), you would only need to register them with xtag.register() if you need to add functionality, attributes, default content, shadow DOM, etc. Elements with unrecognized but valid names will simply be HTMLElement objects. Elements with unrecognized and invalid names will be HTMLUnknownElement objects.
// valid custom element name
document.createElement('foo-bar') instanceof HTMLElement; // true
// invalid custom element name
document.createElement('foobar') instanceof HTMLUnknownElement; // true
You can read the WHATWG Spec for HTMLUnknownElement here.
I don't know of any way to force element hierarchy. Standard HTML elements don't enforce this. For example, you can do <li><ul></ul></li> and <source><video></source>. The elements simply don't function when used improperly like this.
You cannot register 3 different custom elements with the same prototype.
So you'll need to create 3 different prototypes, for example with Object.create():
protoChild = Object.create( protoParent )
protoGrandchild = Object.create( protoChild )
Then call regsiter() method.
Regarding your second question, you'll need to control yourself the content of your custom element, when the element is created of attached.

How does this backbone view get it's EL property set?

I'm looking at this Backbone app:
https://github.com/ccoenraets/nodecellar/blob/master/public/js/main.js
and trying to understand how it works. I see that in the main.js file he calls a WineView like this:
wineList.fetch({success: function(){
$("#content").html(new WineListView({model: wineList, page: p}).el);
I have a few questions about this:
1) Why call $("#content").... from this point? Isn't one of the points of creating a view object to let that new objects "Render" method handle the HTML injection? In fact his Wine View Object DOES have a render method (here: /public/js/views/winelist.js) so what's this call here good for?
2) Why add the EL property at the end? I thought EL was simply a single tag that the View was "attached" to. If it's just a single tag how does it then generate all the new HTML he's looking for?
3) How does the EL tag even get set in the new view object in th first place? I thought if you didn't explicitly state it then EL defaulted to an empty DIV and I can see nowhere EL defined for this View in his code.
Hope someone can clear this up!
How does the EL tag even get set here in the first place?
The Backbone code itself creates el when you don't specify it. As you noted, it defaults to an empty div:
this.el is created from the view's tagName, className, id and attributes properties, if specified. If not, el is an empty div.
Note that, if the el gets created in this way, then it will not be attached to the DOM. Hence, the code above has to take the el property (the view's root tag), and attach it to the DOM under "#content".
Isn't one of the points of calling creating a view object to let that new objects "Render" method handle the HTML injection
Maybe strictly speaking, but not necessarily. Backbone.js is agnostic about how you structure applications, and does not impose strict requirements on its models/views. You'll see lots of different approaches like this in Backbone apps.

Does jQuery cache elements internally?

I know jQuery doesn’t cache collections of element, f.ex calling:
$('.myclass').html('hello');
$('.myclass').html('bye');
Will make jQuery climb the DOM twice.
But how about cached DOM nodes?
var elems = document.querySelectorAll('.myclass');
$(elems).html('hello');
$(elems).html('bye');
Will jQuery cache those internally, or will they be equally slow as the first example?
To clarify: will jQuery keep a reference to elems and cache $(elems) internally so it won’t have to apply the same $() wrapper every time?
Something like:
cache = {}
constructor = function(collection)
if collection in cache
return cache[collection]
else construct(collection)
Assuming I've understood your question correctly, then no, jQuery won't keep a reference to the selected nodes beyond the statement that uses them:
$('.myclass').html('hello'); //Select all .myclass elements, then change their HTML
$('.myclass').html('bye'); //Select all .myclass elements, then change their HTML again
If you maintain a reference to those selected nodes separately, it will be faster:
var elems = document.querySelectorAll('.myclass'); //Select all .myclass elements
$(elems).html('hello'); //Change their HTML (no need to select them)
$(elems).html('bye'); //Change their HTML (no need to select them)
The difference won't be massive (unless your DOM is very complicated) but there will be a difference:
Update
will jQuery keep a reference to elems and cache $(elems) internally so
it won’t have to apply the same $() wrapper every time?
No, it won't. As stated above, the reference to the matched set of elements will not be maintained beyond the statement to which it applies. You can improve the performance of your code by keeping a reference to jQuery objects that are used throughout, rather than selecting them again each time, or even wrapping a stored set of native DOM nodes in a jQuery object each time.
If "cache" means "keep the DOM elements in the internal collection for that jQuery object" .. then yes.
Imagine
jq = $(elementListOrSelector)
where jq[0..jq.length-1] evaluate to the respectively DOM element. For instance, jq[0] evaluates to the first DOM element represented by the jQuery object, if any. Note that this collection is not magically changed once it has been built (and the way in which it was built does not matter).
However there is no "cache" outside of simply keeping the immediate results for that particular jQuery object.

Categories

Resources