What's the better way of manipulating the DOM to change the background of a specific div, rather than using document.getElementById('id').style.backgroundImage.
I'm trying to change backgrounds as I change my Url, but the only way I could think and easy is using document.getElementById()
changeBg() {
var urlPath = window.location.pathname.split('/');
switch (urlPath[4]) {
case "Refreshments%20North":
document.getElementById('homeBg').style.backgroundImage = "url('./assets/imgs/spur-2.jpg')";
break;
... more cases
default:
document.getElementById('homeBg').style.backgroundImage = "url('./assets/imgs/background.jpg')";
}
}
I also tried Renderer dependency but how do I target homeBg using this?
this.renderer.setElementStyle(this.elRef.nativeElement, 'background-image', "url(./assets/imgs/spur-2.jpg)");
Template -- is basically just a div
<nav></nav>
<div id="homeBg"></div>
Edit --
Moved my changeBg() to my sharedService
public changeBg() {
var urlPath = window.location.pathname.split('/');
switch (urlPath[4]) {
case "Refreshments%20North":
this.homeBg = this.sanitizer.bypassSecurityTrustStyle("url('./assets/imgs/spur-2.jpg')");
break;
default:
this.homeBg = this.sanitizer.bypassSecurityTrustStyle("url('./assets/imgs/background.jpg')");
}
}
Calling changeBg() service in my profile component
ngOnInit() {
this.sharedService.changeBg(); // is this correct?
}
Profile template -- like this gives me an error Cannot read property 'homeBg' of undefined
<div class="home" id="homeBg" [style.background-image]="changeBg?.homeBg"></div>
Change background with route.param.subscribe()
this.routeSub = this.route.params.subscribe(params => {
this.sharedService.changeBg();
}
Using binding and directives is the preferred way in Angular2 instead of imperative DOM manipulation:
<div [style.background-image]="myService.homeBg"
You need to sanitize the URL for Angular to accept it.
See In RC.1 some styles can't be added using binding syntax for more details.
changeBg() {
var urlPath = window.location.pathname.split('/');
switch (urlPath[4]) {
case "Refreshments%20North":
this.homeBg = this.sanitizer.bypassSecurityTrustStyle("url('./assets/imgs/spur-2.jpg')");
break;
... more cases
default:
this.homeBg = this.sanitizer.bypassSecurityTrustStyle( "url('./assets/imgs/background.jpg')");
}
}
See also How to add background-image using ngStyle (angular2)?
You can use template references and #ViewChild decorator:
template :
<div #myDiv id="homeBg"></div>
component :
class MyComponent implements AfterViewInit{
#ViewChild("myDiv")
elRef:ElementRef
ngAfterViewInit(){
this.renderer.setElementStyle(this.elRef.nativeElement, 'background-image', "url(./assets/imgs/spur-2.jpg)");
}
}
Related
The content of my Vue app is fetched from Prismic (an API CMS). I have a rich text block, some parts of which are wrapped inside span tags with a specific class. I want to get those span nodes with Vue and add to them an event listener.
With JS, this code would work:
var selectedSpanElements = document.querySelectorAll('.className');
selectedSpanElements[0].style.color = "red"
But when I use this code in Vue, I can see that it works just a fraction of a second before Vue updates the DOM. I've tried using this code on mounted, beforeupdate, updated, ready hooks... Nothing has worked.
Update: Some hours later, I found that with the HTMLSerializer I can add HTML code to the span tag. But this is regular HTML, I cannot access to Vue methods.
#Bruja
I was able to find a solution using a closure. The folks at Prismic reminded/showed me.
Of note, per Phil Snow's comment above: If you are using Nuxt you won't have access to Vue's functionality and will have to go old-school JS.
Here is an example where you can pass in component-level props, data, methods, etc... to the prismic htmlSerializer:
<template>
<div>
<prismic-rich-text
:field="data"
:htmlSerializer="anotherHtmlSerializer((startNumber = list.start_number))"
/>
</div>
</template>
import prismicDOM from 'prismic-dom';
export default {
methods: {
anotherHtmlSerializer(startNumber = 1) {
const Elements = prismicDOM.RichText.Elements;
const that = this;
return function(type, element, content, children) {
// To add more elements and customizations use this as a reference:
// https://prismic.io/docs/vuejs/beyond-the-api/html-serializer
that.testMethod(startNumber);
switch (type) {
case Elements.oList:
return `<ol start=${startNumber}>${children.join('')}</ol>`;
}
// Return null to stick with the default behavior for everything else
return null;
};
},
testMethod(startNumber) {
console.log('test method here');
console.log(startNumber);
}
}
};
I believe you are on the right track looking into the HTML Serializer. If you want all your .specialClass <span> elements to trigger a click event that calls specialmethod() this should work for you:
import prismicDOM from 'prismic-dom';
const Elements = prismicDOM.RichText.Elements;
export default function (type, element, content, children) {
// I'm not 100% sure if element.className is correct, investigate with your devTools if it doesn't work
if (type === Elements.span && element.className === "specialClass") {
return `<span #click="specialMethod">${content}</span>`;
}
// Return null to stick with the default behavior for everything else
return null;
};
I have a component ResultPill with a tooltip (implemented via vuikit) for the main container. The tooltip text is calculated by a getter function tooltip (I use vue-property-decorator) so the relevant bits are:
<template>
<div class="pill"
v-vk-tooltip="{ title: tooltip, duration: 0, cls: 'some-custom-class uk-active' }"
ref="container"
>
..some content goes here..
</div>
</template>
<script lang="ts">
#Component({ props: ... })
export default class ResultPill extends Vue {
...
get tooltip (): string { ..calcing tooltip here.. }
isContainerSqueezed (): boolean {
const container = this.$refs.container as HTMLElement | undefined;
if(!container) return false;
return container.scrollWidth != container.clientWidth;
}
...
</script>
<style lang="stylus" scoped>
.pill
white-space pre
overflow hidden
text-overflow ellipsis
...
</style>
Now I'm trying to add some content to the tooltip when the component is squeezed by the container's width and hence the overflow styles are applied. Using console, I can roughly check this using $0.scrollWidth == $0.clientWidth (where $0 is the selected element), but when I start tooltip implementation with
get tooltip (): string {
if(this.isContainerSqueezed())
return 'aha!'
I find that for many instances of my component this.$refs.container is undefined so isContainerSqueezed doesn't help really. Do I have to somehow set unique ref per component instance? Are there other problems with this approach? How can I check whether the element is overflown?
PS to check if the non-uniqueness of refs may affect the case, I've tried to add to the class a random id property:
containerId = 'ref' + Math.random();
and use it like this:
:ref="containerId"
>
....
const container = this.$refs[this.containerId] as HTMLElement | undefined;
but it didn't help: still tooltip isn't altered.
And even better, there's the $el property which I can use instead of refs, but that still doesn't help. Looks like the cause is this:
An important note about the ref registration timing: because the refs themselves are created as a result of the render function, you cannot access them on the initial render - they don’t exist yet! $refs is also non-reactive, therefore you should not attempt to use it in templates for data-binding.
(presumably the same is applicable to $el) So I have to somehow recalc tooltip on mount. This question looks like what I need, but the answer is not applicable for my case.
So, like I've mentioned in one of the edits, docs warn that $refs shouldn't be used for initial rendering since they are not defined at that time. So, I've made tooltip a property instead of a getter and calcuate it in mounted:
export default class ResultPill extends Vue {
...
tooltip = '';
calcTooltip () {
// specific logic here is not important, the important bit is this.isContainerSqueezed()
// works correctly at this point
this.tooltip = !this.isContainerSqueezed() ? this.mainTooltip :
this.label + (this.mainTooltip ? '\n\n' + this.mainTooltip : '');
}
get mainTooltip (): string { ..previously used calculation.. }
...
mounted () {
this.calcTooltip()
}
}
I made a directive for this loader thing. I want to do something like below but all styles are undefined. Is there a way to access the "computed styles" of the element in the directive?
export const ElementLoader = {
componentUpdated(el, binding) {
if (binding.value.isLoading) {
if (el.style.position !== '' || el.style.position !== 'static') {
el.style.position = 'relative'
}
el.classList.add('is-loading')
} else {
el.classList.remove('is-loading')
}
}
}
Vue.js doesn't provide anything out of the box for this. You must use core JavaScript API for this:
componentUpdated(el, binding) {
const styleObj = window.getComputedStyle(el);
// Other code...
}
If you really need it in a directive style well this is not the solution, but you can always v-bind a property dynamically in this case a CSS Class.
See: Class and style bindings
Is there a way to add stylesheet url or <style></style> dynamically in Angular2 ?
For example, if my variable is isModalOpened is true, I would like to add some CSS to few elements outside my root component. Like the body or html.
It's possible to do it with the DOM or jQuery but I would like to do this with Angular 2.
Possible ?
Thanks
You can create a <style> tag dynamically like this:
ngOnInit() {
const css = 'a {color: pink;}';
const head = document.getElementsByTagName('head')[0];
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
head.appendChild(style);
}
I am not sure you can do it to body or html, but you can do it to root component.
Create a service injected to root component
Let the service have a state ( may be BehaviorSubject )
Access that service and change the state when isModalOpened is changed
In root component , you will be watching this and change component parameter values
Inside root component html , you can change class values based on the component param values
Update : Setting background color from an inner component .
app.component.css
.red{
background: red;
}
.white{
background: white;
}
.green{
background: green;
}
app.component.html
<div [ngClass]="backgroundColor" ></div>
app.component.ts
constructor(private statusService: StatusService) {
this.subscription = this.statusService.getColor()
.subscribe(color => { this.backgroundColor = color; });
}
status.service.ts
private color = new Subject<any>();
public setColor(newColor){
this.color.next(newColor);
}
public getColor(){
return this.color.asObservable();
}
child.component.ts
export class ChildComponent {
constructor(private statusService: StatusService) {}
setColor(color:string){
this.statusService.setColor(color);
}
}
So whenever we call setColor and pass a color variable such as 'red', 'green' or 'white' the background of root component changes accordingly.
Put all your html code in a custom directive - let's call it ngstyle...
Add your ngstyle to your page using the directive tags, in our case:
<ngstyle><ngstyle>
but let's also append the logic to your directive using ng-if so you can toggle it on or off...
<ngstyle ng-if="!isModalOpened"><ngstyle>
Now if your 'isModalOpened' is set to a scope in your controller like this:
$scope.isModalOpened = false; //or true, depends on what you need
...you can toggle it true or false many different ways which should toggle your directive on and off.
I'm currently working on an Angular 2 Project where I have a menu that should be closable by a click on a button. Since this is not heavy at all, I would like to put it outside of Angular (without using a component for the menu).
But I'm not sure of how to do it, actually I've just put a simple javascript in my html header, but shouldn't I put it somewhere else?
Also, what the code should be? Using class, export something? Currently this is my code:
var toggleMenuButton = document.getElementById('open-close-sidebar');
var contentHolder = document.getElementById('main-content');
var menuHolder = document.getElementById('sidebar');
var menuIsVisible = true;
var updateVisibility = function() {
contentHolder.className = menuIsVisible ? "minimised" : "extended";
menuHolder.className = menuIsVisible ? "open" : "closed";
}
toggleMenuButton.addEventListener('click', function() {
menuIsVisible = !menuIsVisible;
updateVisibility();
});
Finally moved to something with MenuComponent and a service, but I'm still encountering an issue.
MenuService.ts
#Injectable()
export class MenuService {
isAvailable: boolean = true;
isOpen: boolean = true;
mainClass: string = "minimised";
sidebarClass: string = "open";
updateClassName() {
this.mainClass = this.isOpen ? "minimised" : "extended";
this.sidebarClass = this.isOpen ? "open" : "closed";
}
toggleMenu(newState: boolean = !this.isOpen) {
this.isOpen = newState;
this.updateClassName();
}
}
MenuComponent.ts
export class MenuComponent {
constructor(private _menuService: MenuService) { }
public isAvailable: boolean = this._menuService.isAvailable;
public sidebarClass: string = this._menuService.sidebarClass;
toggleMenu() {
this._menuService.toggleMenu();
}
}
MenuComponent.html
<div id="sidebar" [class]="sidebarClass" *ngIf="isAvailable">
...
<div id="open-close-sidebar"><a (click)="toggleMenu()"></a></div>
The action are rightly triggered, if I debug the value with console.log, the class name are right but it didn't change the value of the class. I thought the binding was automatic. And I still do not really understand how to change it. Do I have to use Emmit like AMagyar suggested?
The advantage of using angular2 above your own implementation, greatly outweigh the marginal benefit in performance you will get from using plane JavaSccript. I suggest not going on this path.
If you however do want to continue with this, you should export a function and import and call this function inside the ngAfterViewInit of your AppComponent. The exported function should add the click EventListener and (important) set the document.getElementById variables. Because your script possibly won't be able to find those elements yet when it's loaded.
But let me emphasise once more, that angular2 is optimised for exactly these tasks, and once you get more familiar with it, it will also be a lot easier to code it.
update
For inter component communication you should immediately think about a service. Just create a service which stores the menu state and add this to your global ngModule providers array. For instance:
export class MenuService {
public get menuOpen(): boolean {
return this._menuOpen;
}
private _menuOpen: boolean;
public openMenu() : void {
this._menuOpen = true;
}
public closeMenu() : void {
this._menuOpen = false;
}
public toggleMenu() : void {
this._menuOpen = !this._menuOpen;
}
}
You can then inject this service into your menu component and bind the classes open/closed and minimized/extended to the MenuService.menuOpen.
#Component({
selector : 'menu'
template : `
<button (click)="menuService.toggleMenu()">click</button>
<div id="open-close-sidebar" [class.open]="menuService.menuOpen"></div>
`
})
export class MenuComponent {
constructor(public menuService: MenuService){}
}
For other component you can use the same logic to see if the menu is open or closed
update #2
You have to use a getter to get the value from menuService. There is only one way binding:
export class MenuComponent {
constructor(private _menuService: MenuService) { }
public get isAvailable(): boolean {
return this._menuService.isAvailable;
}
public get sidebarClass(): string {
return this._menuService.sidebarClass;
}
toggleMenu() {
this._menuService.toggleMenu();
}
}
FYI, it's better practice to use [class.open] instead of a string class name. If you want to do it like that, it will only require minimal change in your current css.
The main reason of why I want to avoid using Angular component is the
fact that my manipulation should be done over all the website and not
just the "menu" component.
You can create many components in Angular 2, it's easy and very practical.
The action will change the class on my menu (located in my menu
component) and on my main content (located outside of the component).
I don't know how to do it, and I'm not sure that this is the best
way... Maybe by binding the service value directly... –
The main content can have a child that is the Menu itself.
Take a look in this link. There are many solutions, one of them is to "emit" the child changes to the parent.
If you need an example I can provide one quickly.