Why can't Knockout custom components be named and registered in camelCase? - javascript

I have started working on Knockout recently, and have been assigned to create custom components that can be used in various application. While creating a component, I used camel case to name it, e.g: "datePicker".
Component code
ko.components.register("datePicker",{
viewModel: require('./components/date-picker-widget'),
template: require('raw!./components/date-picker-widget.html')
});
HTML Code
<datePicker params="{value:returnValue, initialValue:returnValue.initialValue}"></datePicker>
But this is what was rendered:
So its clear that Knockout expects component names in lower case (JSFiddle reference). The question remains is why?
I also saw similar constraints on naming of components in React, where you have to start the name with capital character only.

The key is here in the documentation:
By default, Knockout assumes that your custom element tag names correspond exactly to the names of components registered using ko.components.register. This convention-over-configuration strategy is ideal for most applications.
If you want to have different custom element tag names, you can override getComponentNameForNode to control this. For example,
ko.components.getComponentNameForNode = function(node) {
var tagNameLower = node.tagName && node.tagName.toLowerCase();
if (ko.components.isRegistered(tagNameLower)) {
// If the element's name exactly matches a preregistered
// component, use that component
return tagNameLower;
} else if (tagNameLower === "special-element") {
// For the element <special-element>, use the component
// "MySpecialComponent" (whether or not it was preregistered)
return "MySpecialComponent";
} else {
// Treat anything else as not representing a component
return null;
}
}
You can use this technique if, for example, you want to control which subset of registered components may be used as custom elements.
In fact, the documentation is not entirely up to par with the code, because it claims "correspond exactly", whereas in fact in the code only the tag name is lowercased, and the registration isn't.
In any case, because browsers return tagName in upper case, the above code will only work if your registration is in lowercase.
So, you can either use datePicker camelCased in your view and register it lower case:
<datePicker></datePicker>
ko.components.register("datepicker",{
viewModel: function() { },
template: '<strong>DatePicker stub is showing!</strong>'
});
$(function() { ko.applyBindings({}); });
Or you can monkey patch the register function:
<datePicker></datePicker>
var originalFn = ko.components.register;
ko.components.register = function(name, options) {
originalFn(name.toLowerCase(), options);
};
ko.components.register("datePicker",{
viewModel: function() { },
template: '<strong>DatePicker stub is showing!</strong>'
});
$(function() { ko.applyBindings({}); });
Though I'm not sure how stable and cross-browser compatible that would be.
In any case that shouldn't matter much, since you merely asked "why" this was like it was.
My suggestion: stick to the docs' style of dash-cased-element-names.
PS. The screenshot you posted is how the debugger rendered your elements, not how Knockout or the browser would have it. In fact, most likely if you use "view source" you'll get a camelCased element name; to be entirely sure you could inspect the raw body of the request.

Its not just the case with knockoutjs or react components, if you are writing xHTML then according to the specification tag names and attributes must be in lower case. (Ref: http://www.w3.org/TR/xhtml1/#h-4.2).
In most browsers, although the rendered html will have case insensitive tags (View Source), but the DOM built by browser(debugger tools, inspect element) will usually have all lowercase for tag names and attributes.
Although there is no wrong with case insensitive tags and attribute names, the web developers have, IMO, just adopted the convention of using lowercase names for XHTML or HTML.

Related

Does a backdraft component have any visibility into the dom element where it's being inserted?

Using backdraftjs I want to do a customized fancy select widget similar to chosen.js.
My thought was that I could have markup like this:
<div id="bobSelect">
<select name="bob">
<option>Bob Crane</option>
<option>Bob Hope</option>
<option>Bob Melvin</option>
<option>Bob Dobbs</option>
</select>
</div>
Then I'd replace it via something like
render(FancySelectComponent, {}, 'bobSelect', 'replace');
...and in FancySelectComponent I would grab things like the options from bobSelect.
However it looks like there's no way for FancySelectComponent to have visibility into the dom element where it will eventually be inserted. Is that right?
So instead maybe in render I need to do something like
render(FancySelectComponent, {basedOn: 'bobSelect'}, 'bobSelect', 'replace');
Does that make sense?
You are correct; as written,
render(FancySelectComponent, {}, 'bobSelect', 'replace');
FancySelectComponent's constructor is not provided any information about the existing DOM tree rooted at div#bobSelect. In fact, the render application above is just syntax sugar for...
let result = new FancySelectComponent({});
let replacedNode = document.getElementById('bobSelect');
let parentNode = replacedNode.parentNode;
parentNode.removeChild(replacedNode);
parentNode.appendChild(result.render());
return result;
(note: in your example, both the replaced and replacing node are single nodes, but render can handle cases when either or both are a forrest of nodes)
You are also correct that, if you want access to the existing div#bobSelect, then you can pass it directly to the constructor via the second argument to render:
render(
FancySelectComponent,
{replacingDiv:document.getElementById('bobSelect')},
'bobSelect',
'replace'
);

Display react component name in attribute

While working with React, i would like to display component name in an attribute of the component. E.g. if I have a component <LoginBox /> I would like it to be rendered as
<div data-react-name="LoginBox">...</div>
But I want this to be done automatically for each transpiled component. Reason for this is automated testing when I'd check for rendered elements in HTML/DOM, currently a component is not differentiated by the name in rendered HTML.
I thought I'd write a babel plugin, but I have no idea what visitors I'd use and how to make it robust enough. I tried google for such a plugin but I have no idea how it would be called and found nothing useful.
So is there any plugin or any way to achieve this?
Thanks
Now after a year, as I'm rethinking, it should be quite easy.
For more details on writing plugins see handbook.
Use ASTexplorer to inspect what AST would your code result in. And then, for generated tree, prepare visitors. So e.g. with code:
<div><Custom some-prop="prop">Some text</Custom></div>
we would infer, that we need to use visitor JSXOpeningElement and alter node's property attribute. To this property - array we would add a new element that we would create by Babel.types.jsxAttribute(name, value). We will get the name of tag from node's property .name.name (the name string is nested inside name object). We also need to use appropriate types. So it would look like this:
module.exports = function(Babel) {
return {
visitor: {
JSXOpeningElement(path) {
const name = Babel.types.jsxIdentifier('data-testname');
const value = Babel.types.stringLiteral(path.node.name.name);
path.node.attributes.push(Babel.types.jsxAttribute(name, value));
}
}
};
};
The code is tested with the ASTExplorer.

Using hasClass with vue.js

I know this setup I have isn't ideal however I need to check the class of an input element. I have just started using Vue.js on a new project however we have a form validation library that has to be used that uses jQuery.
The validation library I am using adds a class to an input if it doesn't fit some validation requirements.
If I was using jQuery I could just use hasClass to check if the input element had a specific class.
In Vue I am unsure how I can check if an element has a class though?
Here is an example method of what I'd like to achieve:
methods: {
nextStep: function() {
const element = this.$el.querySelector("#conversation__tram-1 input");
if (element has the class of is-valid) {
Do something
}
},
},
As you can see I'd like to check whether or not the input field has a class of is-valid. Can you think of any way I can do this with Vue.js? Thanks.
You can use the element's classList property:
if (element.classList.contains('is-valid')) {
// Do something
}
Most of the native vue.js functions involve manipulating the data behind the DOM, not the DOM itself.
There are multiple ways you can achieve this,
You can use Vue class binding, which is two-way. So you once you initialize binding then you can build an API to expose that.
Have you tried this?
if (this.$els.elementNameHere.className.match(/\bmyclass\b/)) {
//
}
If jQuery is already involved, just use its hasClass the way you already know how.
nextStep: function() {
const element = this.$el.querySelector("#conversation__tram-1 input");
if ($(element).hasClass('is-valid')) {
Do something
}
},
Vue does not provide any special DOM manipulation routines, because in Vue, you aren't generally supposed to be manipulating the DOM. There are some carved-out exceptions, but this isn't really one. :) The key to mixing jQuery and Vue is that you must be careful to separate what jQuery controls from what Vue controls so they don't step on each other's toes. Not much harm here in simply reading the DOM as long as you don't expect Vue to be updating it.

Refreshing i18n translated string interpolated values in Aurelia

If i eg. use a select drop down input field in my header (more precisely in my sitewide nav-bar which is custom element), and have that value set globally in a shared state object. If I - on languageChanged(value) (inside the nav-bar custom element) also change the this.i18n.setLocale('de-DE')
How would I then refresh the i18n translated string interpolated values (eg. ${'status_deceased' | t} ) inside my templates without having to navigate to a new route and back as I do right now?
I found this issue on github about the problem https://github.com/aurelia/i18n/issues/6 but since I need this to work, I'm hoping that some clever workaround exists, except from having to use window.location to reload the page :/
Edit
If I'm understanding this correctly it seems it might just be my lucky day, and such a feature has just been added 8 days ago, although still undocumented: https://github.com/aurelia/templating-resources/pull/126 - Can anyone figure out and tell me how to implement this, using this new feature perhaps? If I figure this out myself, I'll update this thread with the solution :-)
When the next release goes out you'll be able to assign a "signal name" to a binding using the signal binding behavior like this:
<h1>${'title_key' | t & signal:'i18n'}</h1>
<p>${'content_key' | t & signal:'i18n'}</p>
The & symbol denotes a "Binding Behavior" (as opposed to | for value-converters). Binding behaviors are resources that add "behavior" to a binding. They have full access to the binding instance and are notified prior to the binding's bind and unbind lifecycle events.
Aurelia will ship with several built-in binding behaviors: "throttle", "debounce", "one-time", "signal" etc. You also have the option of creating your own binding behaviors.
In the example above we've given the title and content interpolation bindings a "signal" name of "i18n". The name is arbitrary, we just need to know what it is so we can "signal" the bindings to refresh using the BindingSignaler like this:
import {BindingSignaler} from 'aurelia-templating-resources';
import {inject} from 'aurelia-framework';
#inject(BindingSignaler)
export class App {
constructor(signaler) {
this.signaler = signaler;
}
// invoking this method will refresh all bindings in the application
// with the "signal name" of "i18n"
refreshBindings() {
this.signaler.signal('i18n');
}
}
I imagine once the binding behavior feature drops there will be additional work in the i18n plugin to combine the t value converter with some version of the signal binding behavior to enable terse binding expressions that take care of both translation and refreshing the bindings when the language changes so you might want to sit tight for the time being.
EDIT
If you need something today you could take advantage of an existing Aurelia feature: bindings are re-evaluated when converter parameters change.
Create a new class:
export class LanguageChangedNotifier {
signal = 0;
notify() {
this.signal++;
}
}
inject this class into all view-models and add the instance as a property:
#inject(LanguageChangedNotifier)
export class App {
constructor(notifier) {
this.notifier = notifier;
}
}
Use the notifier in your t bindings (it won't impact the behavior of the t value converter):
${'status_deceased' | t:notifier.signal}
When you change the locale, use the notifier to refresh the bindings:
this.notifier.notify();

Get element an observable is bound to with Knockout?

This isn't an ideal situation, but due to another knockout binding I am using I am in a situation where I am needing to get the element an observable is bound to, if it is indeed bound to anything.
So is there a way to do this?
== Update ==
I didn't want to add any extra context incase it confuses the question, but as it may get an answer more in line with expectations here is the scenario.
I am using the knockout validation binding, which exposes all the errors using the ko.validation.group(model) method. However the problem is that only gives you the textual errors, it does not give you any context as to what part of the model gave you those errors. So I have made a small change to the source to now pass back the observable tied to each error, as this may be useful for a few scenarios, but from here I need to be able to tie this to an element so I can display some in-line validation of some kind.
Knockout Validation provides a very basic in-line validation where it creates a span after your element and you can give it a class, but this is too basic for my needs as currently we are using Qtip and other notification systems to display validation errors, and because of this I need to be able to have a Dom element and an error. So far I have an observable and an error, but I need to tie that observable object (which could be any ko.observable() property from the model) to its given DOM element, if it does have an element binding.
As all I have is an object and I am using validation driven from the model not the UI, the problem is not really going to be solved via a custom binding. Ideally I need to be able to crack open the marry up the observable object (an unknown ko.observable()) to an element.
Not to go too project specific, but my current project abstracts validation where events are fired off (i.e EventSystem.SendEvent(ValidationEvents.ValidationFailed, <element>, <error>)) then a validation system listens for these events and ties the error to the element, be it a tooltip, a growl style notification, an alert box etc. So I am trying to find the best way to keep this abstraction when driving the validation from the models observables not the ui's DOM elements (i.e jquery-ui)
== Edit 2 ==
I was a bit thrown by the way Knockout Validation knows the elements for observables to put in its own validation elements, however they just piggy back off the existing value binding, so I am just going to change that to add the elements for any validation elements based on their isValidatable() method, at least that way for each error I can tie it to an observable, and for any observables with element bindings I can tie them to the elements, and if there are none then it is fine they would just be form wide validation errors. I will give this a try as this should be something like (not tested yet):
if(utils.isValidatable(valueAccessor())) {
valueAccessor().extend({hasElementalBinding: true, elementalBinding: element});
}
else {
valueAccessor().extend({hasElementalBinding: false});
}
At around line 250 in the registerValueBindingHandler, I will leave this question open for a while longer incase someone else has a better solution.
I have done something similar to what you mentioned above. My data-bind tag includes a custom binding:
data-bind="... myvalidationbinding: myobservable"
Then in my binding handler I extend the observable
ko.bindingHandlers.myvalidationbinding = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
valueAccessor().extend({element: element });
}
};
And finally my extension is
ko.extenders.element = function (target, element) {
target.DOMElement = element;
}
Now I can subscribe to isValid() given by knockout.validation and if not valid, go get the element the observable is bound to and then manipulate it with jQuery.
This won't be very fast, so I would definitely cache the results, but something using jQuery's attribute selectors:
$('[data-bind*="Property"]')
*= is the attribute contains selector: http://api.jquery.com/attribute-contains-selector/
Obviously this won't catch anything that subscribed manually using the .subscribe method, but I'm not sure how you would extract element's from the functions anyway.
Disclaimer: while this solution will probably work, this sounds like a horrible idea to me, I would instead write a custom binding (as mentioned in the comments) or find some other solution.

Categories

Resources