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.
Related
I updated from Angular 2 to Angular 4 and in the docs it's written to use the Renderer2 instead of Renderer which is deprecated.
Now am looking into the Renderer source, but cannot find a way to invoke the focus() method as I used to.
Old method:
this.renderer.invokeElementMethod(element, 'focus', []);
What is the new aproach?
EDIT
What if the element i am focusing onto is obtained via QuerySelector?
For instance:
let inputField = document.querySelectorAll('.dialog input');
if ( inputField[0] ) {
inputField[0].focus();
}
Since its obtained via QuerySelector, the focus() method doesn't exist on it.
The invokeElementMethod is deprecated, and will not find its way back into the new renderer. To set focus to an element, you can simply use this now:
element.nativeElement.focus();
If you are using document.querySelectorAll, you are doing something not the angular way, and you should find another way to do it. If there is no other way to do it, then the same principle applies. The focus() method is plain javascript, so you can use it within angular2/typescript. Be sure to do the querySelectorAll inside the ngAfterViewInit hook of the component though:
ngAfterViewInit() {
let inputField: HTMLElement = <HTMLElement>document.querySelectorAll('.dialog input')[0];
inputField && inputField.focus();
}
Also you can use selectRootElement method of Renderer2.
For example:
constructor(private renderer: Renderer2) {}
this.renderer.selectRootElement('#domElementId').focus()
, where domElementId is id='domElementId' in your html
template reference variable :#inlineEditControl
<input #inlineEditControl class="form-control form-control-lg" [placeholder]="label">
#ViewChild('inlineEditControl') inlineEditControl: ElementRef; // input DOM element
this.inlineEditControl.nativeElement.focus();
If you are using Angular CDK, you can set focus using FocusMonitor.focusVia method that is part of #angular/cdk/a11y Accessibility module (A11yModule).
This way you can avoid any DOM manipulation and avoid referencing nativeElement altogether.
import { FocusMonitor } from '#angular/cdk/a11y';
export class ExampleComponent implements AfterViewInit {
#ViewChild('elem', {static: false}) elemRef;
constructor(private focusMonitor: FocusMonitor) {
}
ngAfterViewInit() {
// Programmatically set focus. Focus source is 'program'.
this.focusMonitor.focusVia(this.elemRef, 'program');
}
}
At this time, it appears that this task is not achievable without directly modifying the DOM. As others have said, the invokeElementMethod() is deprecated. However, you will find issues with using selectRootElement as well, as this is not its intended purpose and you will end up losing the children inside of your DIV. Here is a link to a SO question that explains selectRootElement in more detail (since the angular docs are atrocious):
Renderer multiple selectRootElement Issue (with plnkr provided)
As for now, it appears the only way to easily achieve your goal is to use a template reference variable, a view child, and the yourViewChild.nativeElement.focus() (though this practice is also not recommended)
There is also a suggested workaround using a FocusService found on this GitHub issue: https://github.com/angular/angular/issues/15674
In this issue, Tytskyi mentions that you can implement a FocusService and provide different implementations of focus() based on AppModuleBrowser and AppModuleServer. I'm not sure of the fine details of how that would work but it may be the only way to achieve this without using nativeElement at this time.
If you declare the correct type of your inputField var, you can use any JS query selector.
For instances declaring:
let inputField: HTMLInputElement = document.querySelector('.dialog input');
enables you to call
inputField.focus();
that it will work and no TS error will be thrown.
I'm seeking some advice how I should handle elements when working with Angular2.
I have stored some elements id's in the localstorage, and want to set a selected class name on some specific elements.
For now I'm using this way:
ngAfterViewChecked() {
// Check if seats has been selected before
var selectedSeats: elementInterface = JSON.parse(localStorage.getItem('SelectedSeats'));
if (selectedSeats != null) {
var outboundElement = document.getElementById(selectedSeats.outboundSelectedElement);
var returnElement = document.getElementById(selectedSeats.returnSelectedElement);
this.autoSelectSeats(outboundElement);
this.autoSelectSeats(returnElement);
}
}
Method:
private autoSelectSeats(element: Element) {
// Set selected class on element
element.classList.add('selected');
}
I see two issues here. The first is the ngAfterViewChecked hook that fires more than once after the view is created. Is there something I can do so it only fires once?
Second: Is there a better way to get the element when you know the id and set a class attribute on it after the content has loaded?
I can't seem to find the Angular2 way of doing it, besides using this hook.
Any idea? Also, please don't post Jquery posts, as I don't want to implement that when using Angular :)
How about adding a custom directive to each of your "seat" elements and let that directive add the CSS class?
In your template, the directive would be used as follows (I'm guessing, since you didn't show your template):
<div *ngFor="let seat of seats" [highlight]="seat.id">
...
</div>
You need to pass some information to the directive to identify which seat it is working on. It seems better to pass an id directly (e.g. seat.id) rather than to rely on HTML ids (although in your case they might be one and the same).
Now the code for the directive:
#Directive({
selector: '[highlight]'
})
export class HighlightDirective {
#Input() highlight: string; // This will contain a seat.id
constructor(el: ElementRef, ss: SeatService) {
const selectedSeats = ss.getSelectedSeats();
// If current seat found in selectedSeats, mark it as selected.
if (selectedSeats.indexOf(this.highlight) !== -1) {
this.el.nativeElement.classList.add('selected');
}
}
}
The reason I'm using an external service SeatService to get the data from localStorage is that Angular will create an instance of HighlightDirective for every match it finds in your template. You don't want to refetch the selected seats in every instance (the service lets you cache the seats and return the same data).
Angular way has pretty good documentation, classes are toggled using the following syntax: [class.selected]="selected"
Assuming that there are some conditions on the vuejs side that I keep as data. So if the condition changes, then I want to trigger the corresponding function or initialise a plugin on jquery side.
var vm = new Vue({
el: '#tracking',
data: {
condition:'condition1'
//condition:'condition2'
}
});
jQuery('#tracking-picker-condition1').periodpicker({
});
jQuery('#tracking-picker-condition2').periodpicker({
});
Yes it is totally possible :)
Although custom components are the preferred choice https://github.com/hilongjw/vue-datepicker , you can call jQuery stuff anywhere you want.
You can even find in the official vue 2.0 docs such an example for the select2 jquery plugin.
https://jsfiddle.net/yyx990803/fruqrvdL/?utm_source=website&utm_medium=embed&utm_campaign=fruqrvdL
It is used as $(this.$el) where this.$el is the component identifier, but you can use any id of your choise.
I think what you're looking for is watchers.
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.
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.