view does not change background image - css

I have a template in which I call a function and I have a directive element,
.container-wrapper{"ng-controller" => "MovieRowCtrl"}
%i.fa.fa-info-circle{"ng-click" => "updateSelectedMovie(movie)"}
#big-box-container{"ng-if" => "rowActive"}
%movie-details{:movie => "selectedMovie"}
The function is inside a directive,
app.directive('movieDetails', MovieDetailsDirectiveFn)
.controller('MovieRowCtrl', ['$scope', '$rootScope', MovieRowCtrlFn]);
function MovieDetailsDirectiveFn($animate) {
return {
restrict: 'E',
scope: {
movie: '=',
},
templateUrl: '../assets/angular-app/templates/_movie-info.html'
}
}
function MovieRowCtrlFn($scope, $rootScope) {
$scope.updateSelectedMovie = function updateVisibleMovieIndexFn(movie) {
$scope.selectedMovie = movie;
$rootScope.$broadcast('movieRowActivated', {targetScopeId: $scope.$id});
$scope.rowActive = true;
}
}
As you can see I set my MovieRowCtrl as controller for the .container-wrapper.
So when the updateSelectedMovie(movie) function fires it sets the $scope.rowActive to true (showing the element) and it inject the _movie-info.html template inside the %movie-details element.
This works fine.
In that _movie-info.html template I have this,
.movie-background{"ng-style" => "{'background-image':'url(https://image.tmdb.org/t/p/w1280{{movie.backdrop}})'}"}
Which results in,
<div class="movie-background" ng-style="{'background-image':'url(https://image.tmdb.org/t/p/w1280/lXOqBOcx1t5A3YhJEIfJZOkigwH.jpg)'}"
style="background-image: url("https://image.tmdb.org/t/p/w1280/nbIrDhOtUpdD9HKDBRy02a8VhpV.jpg");">
So it creates a ng-style, and a normale style attribute for the element.
The problem is that when I fire the updateSelectedMovie(movie) the ng-style attribute gets updated with a new url, but the style attribut does not get changed.
<div class="movie-background" ng-style="{'background-image':'url(https://image.tmdb.org/t/p/w1280/15PbZtjRJ4zgQA8XS0otL70piQi.jpg)'}"
style="background-image: url("https://image.tmdb.org/t/p/w1280/nbIrDhOtUpdD9HKDBRy02a8VhpV.jpg");">
Any ideas why the ng-style gets updated, but the other style does not?

There is not need to use {{}} inside ng-style, use + for string concatenation while concatenating scope variable inside ng-style expression.
.movie-background{"ng-style" =>
"{'background-image':'url(https://image.tmdb.org/t/p/w1280' + movie.backdrop +')'}"}

Related

How do I select my CSS module class using document.querySelector?

I want to be able to select styles.scrollValue using the document.querySelector()
import { useEffect } from "react";
import styles from "./Navbar.module.scss";
const Navbar = () => {
const handleScroll = () => {
document.querySelector(styles.scrollValue).innerHTML = scrollY;
};
useEffect(() => {
document.addEventListener("scroll", handleScroll);
return () => {
document.removeEventListener("scroll", handleScroll);
};
});
return (
<nav className={styles.navbar}>
<div className={styles.content}>
<h1 className={styles.scrollValue}>0</h1>
</div>
</nav>
);
};
Running this code I get an error:
TypeError: document.querySelector(...) is null
I know I can add a global class to that element doing this:
className={`${styles.scrollValue} scrollValue`}
But that would ruin the advantage of using CSS Modules.
Is there a way to select the CSS Module class without adding another one to it?
You need to select it as a class,
document.querySelector(`.${styles.scrollValue}`)
Calling styles.property returns a value such as the following,
scrollValue: '_src_styles_module__scrollValue'
So when you call document.querySelector it's looking for _src_styles_module__scrollValue which has no selector type such ., or # which makes it look for an element that would match <_src_styles_module__scrollValue> (in this case). Since there is no element with that name or the hashed name it will return null.
Working demo

Gutenberg add style attribute using block filter

I'm trying to add a style attribute in the block editor to a block's wrapper using a block filter:
const addStyle = createHigherOrderComponent( ( BlockListBlock ) => {
return ( props ) => {
return <BlockListBlock { ...props } className="my-class" style="color: red" />;
};
}, 'addStyle' );
wp.hooks.addFilter( 'editor.BlockListBlock', 'my-plugin/add-style', addStyle );
Only the my-class class name is added to the class attribute but no style attribute. Is it possible to add a style attribute as well? The documentation states:
It receives the original BlockListBlock component and returns a new
wrapped component.
but does not say you can add only class names.
For the rendering part it's:
wp.hooks.addFilter('blocks.getSaveContent.extraProps','my-plugin/add-style', function(props, name, atts){
return Object.assign(props, { 'style': 'color: red' });
});
Couldn't figure it out for the edit function tho.

AngularJS md-tab & ng-repeat: adding custom style to specific tabs

I'm trying to customize a couple tabs because they're different.
Here's what I have:
<md-tabs>
<md-tab ng-repeat="tab in tabs" ng-class="tab.customClass">
<md-tab-label ng-bind="tab.label"></md-tab-label>
</md-tab>
</md-tabs>
My issue: the custom class is not in the compiled md-tab-item
NOTE: the gets replaced because it's only needed to generate tab buttons and panes.
I don't know how many tabs I have, so I cannot write CSS based on position.
Any ideas?
If you are trying to customize how the tab itself looks at the top you can do this through the use of a decorator. This will allow you to alter how directives behave at run time.
For example if you are trying to style the tab itself, that directive would be "md-tab-item"
(function () {
'use strict';
MdTabItemDecorator.$inject = ['$provide'];
angular.module('common').config(MdTabItemDecorator);
function MdTabItemDecorator($provide) {
$provide.decorator('mdTabItemDirective', [
'$delegate',
'$controller',
function ($delegate) {
var directive = $delegate[0];
directive.compile = function () {
return function (scope, elem, attrs) {
directive.link.apply(this, arguments);
elem.attr('style', 'color:red');
scope.tabIndex = scope.$parent.$index;
};
};
return $delegate;
}
])
}
})();
The above example would change the color of the tab text to red.
What's happening here is that we are creating a decorator mdTabItem with
function MdTabItemDecorator($provide) {
$provide.decorator('mdTabItemDirective', [
function ($delegate) {
...
}
])
This gives you access to the $delegate object which is a representation of the directive object that is about to be instantiated.
This allows us to make some modifications and return the delegate object changing how the directive behaves.
In this case I am taking the existing link function and extending it's functionality to set the style of the directive element.
directive.compile = function () {
return function (scope, elem, attrs) {
directive.link.apply(this, arguments);
elem.attr('style', 'color:red');
scope.tabIndex = scope.$parent.$index;
};
};

Different body background for different UI-Router states

In a simplified scenario, I have two UI-Router states and want different body background colors for them.
Ideally, I would thus like to do the following:
<body ng-class="background" ui-view>
</body>
and then in the controller for the states set the background classes (which might be dynamic, meaning: calculated in the controller):
stateHelperProvider
.state({
name: 'a',
url: '/a',
templateUrl: 'views/a.html',
controller: function ($scope) {
$scope.background = 'bg1';
}
})
.state({
name: 'b',
url: '/b',
templateUrl: 'views/b.html',
controller: function ($scope) {
$scope.background = 'bg2';
}
});
But putting the ui-view attribute on the body deletes it. So I have to put the ui-view attribute on a div inside the body.
<body>
<div ui-view></div>
</body>
How can I now control the body background?
I am aware of various hacks (e.g. access the class DOM property of body in the onEnter function of UI-Router, ...), but is there a nice way to do that?
Or is it all about making the div[ui-view] full height (which is not trivial) and setting the background on this element to mimick how applying a background on the body takes up the full viewport?
So to sum up my learnings, there are two ways to do it. Both require the $state service to be in the $rootScope. The elegance of this can be disputed, but I will go for this solution.
If the background classes are depending on the state only, add them to the state's custom data:
Example code:
.state({
...,
data: {
bodyClass: 'bg1'
}
});
Then, on the body, put an ng-class and reference the current state's data:
<body ng-class="$state.current.data.bodyClass">
If the background classes are (besides the state) depending on something else (like a state parameter or a service, ...), use the state's resolve mechanism:
Example code:
.state({
...,
resolve: {
bodyClass: function($stateParams) {
// do some calculation
return ...
}
}
});
Then, on the body, put an ng-class and reference the resolved class name through $state.$current.locals.globals:
<body ng-class="$state.$current.locals.globals.bodyClass">
In both cases, bodyClass can anything that is valid for the ng-class directive.
Probably not accurate, but maybe could you just change your body background-color directly in the controller ?
stateHelperProvider
.state({
name: 'a',
url: '/a',
templateUrl: 'views/a.html',
controller: function ($scope) {
document.body.style.background = 'bg1';
}
})
.state({
name: 'b',
url: '/b',
templateUrl: 'views/b.html',
controller: function ($scope) {
document.body.style.background = 'bg2';
}
});
Or in this case just add/remove CSS classes to the body?
Good Luck'

How to include view/partial specific styling in AngularJS?

What is the proper/accepted way to use separate stylesheets for the various views my application uses?
Currently I'm placing a link element in the view/partial's html at the top but I've been told this is bad practice even though all modern browsers support it but I can see why it's frowned upon.
The other possibility is placing the separate stylesheets in my index.html's head but I would like it to only load the stylesheet if its view is being loaded in the name of performance.
Is this bad practice since styling won't take effect until after the css is loaded form the server, leading to a quick flash of unformatted content in a slow browser? I have yet to witness this although I'm testing it locally.
Is there a way to load the CSS through the object passed to Angular's $routeProvider.when?
I know this question is old now, but after doing a ton of research on various solutions to this problem, I think I may have come up with a better solution.
UPDATE 1: Since posting this answer, I have added all of this code to a simple service that I have posted to GitHub. The repo is located here. Feel free to check it out for more info.
UPDATE 2: This answer is great if all you need is a lightweight solution for pulling in stylesheets for your routes. If you want a more complete solution for managing on-demand stylesheets throughout your application, you may want to checkout Door3's AngularCSS project. It provides much more fine-grained functionality.
In case anyone in the future is interested, here's what I came up with:
1. Create a custom directive for the <head> element:
app.directive('head', ['$rootScope','$compile',
function($rootScope, $compile){
return {
restrict: 'E',
link: function(scope, elem){
var html = '<link rel="stylesheet" ng-repeat="(routeCtrl, cssUrl) in routeStyles" ng-href="{{cssUrl}}" />';
elem.append($compile(html)(scope));
scope.routeStyles = {};
$rootScope.$on('$routeChangeStart', function (e, next, current) {
if(current && current.$$route && current.$$route.css){
if(!angular.isArray(current.$$route.css)){
current.$$route.css = [current.$$route.css];
}
angular.forEach(current.$$route.css, function(sheet){
delete scope.routeStyles[sheet];
});
}
if(next && next.$$route && next.$$route.css){
if(!angular.isArray(next.$$route.css)){
next.$$route.css = [next.$$route.css];
}
angular.forEach(next.$$route.css, function(sheet){
scope.routeStyles[sheet] = sheet;
});
}
});
}
};
}
]);
This directive does the following things:
It compiles (using $compile) an html string that creates a set of <link /> tags for every item in the scope.routeStyles object using ng-repeat and ng-href.
It appends that compiled set of <link /> elements to the <head> tag.
It then uses the $rootScope to listen for '$routeChangeStart' events. For every '$routeChangeStart' event, it grabs the "current" $$route object (the route that the user is about to leave) and removes its partial-specific css file(s) from the <head> tag. It also grabs the "next" $$route object (the route that the user is about to go to) and adds any of its partial-specific css file(s) to the <head> tag.
And the ng-repeat part of the compiled <link /> tag handles all of the adding and removing of the page-specific stylesheets based on what gets added to or removed from the scope.routeStyles object.
Note: this requires that your ng-app attribute is on the <html> element, not on <body> or anything inside of <html>.
2. Specify which stylesheets belong to which routes using the $routeProvider:
app.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/some/route/1', {
templateUrl: 'partials/partial1.html',
controller: 'Partial1Ctrl',
css: 'css/partial1.css'
})
.when('/some/route/2', {
templateUrl: 'partials/partial2.html',
controller: 'Partial2Ctrl'
})
.when('/some/route/3', {
templateUrl: 'partials/partial3.html',
controller: 'Partial3Ctrl',
css: ['css/partial3_1.css','css/partial3_2.css']
})
}]);
This config adds a custom css property to the object that is used to setup each page's route. That object gets passed to each '$routeChangeStart' event as .$$route. So when listening to the '$routeChangeStart' event, we can grab the css property that we specified and append/remove those <link /> tags as needed. Note that specifying a css property on the route is completely optional, as it was omitted from the '/some/route/2' example. If the route doesn't have a css property, the <head> directive will simply do nothing for that route. Note also that you can even have multiple page-specific stylesheets per route, as in the '/some/route/3' example above, where the css property is an array of relative paths to the stylesheets needed for that route.
3. You're done
Those two things setup everything that was needed and it does it, in my opinion, with the cleanest code possible.
#tennisgent's solution is great. However, I think is a little limited.
Modularity and Encapsulation in Angular goes beyond routes. Based on the way the web is moving towards component-based development, it is important to apply this in directives as well.
As you already know, in Angular we can include templates (structure) and controllers (behavior) in pages and components. AngularCSS enables the last missing piece: attaching stylesheets (presentation).
For a full solution I suggest using AngularCSS.
Supports Angular's ngRoute, UI Router, directives, controllers and services.
Doesn't required to have ng-app in the <html> tag. This is important when you have multiple apps running on the same page
You can customize where the stylesheets are injected: head, body, custom selector, etc...
Supports preloading, persisting and cache busting
Supports media queries and optimizes page load via matchMedia API
https://github.com/door3/angular-css
Here are some examples:
Routes
$routeProvider
.when('/page1', {
templateUrl: 'page1/page1.html',
controller: 'page1Ctrl',
/* Now you can bind css to routes */
css: 'page1/page1.css'
})
.when('/page2', {
templateUrl: 'page2/page2.html',
controller: 'page2Ctrl',
/* You can also enable features like bust cache, persist and preload */
css: {
href: 'page2/page2.css',
bustCache: true
}
})
.when('/page3', {
templateUrl: 'page3/page3.html',
controller: 'page3Ctrl',
/* This is how you can include multiple stylesheets */
css: ['page3/page3.css','page3/page3-2.css']
})
.when('/page4', {
templateUrl: 'page4/page4.html',
controller: 'page4Ctrl',
css: [
{
href: 'page4/page4.css',
persist: true
}, {
href: 'page4/page4.mobile.css',
/* Media Query support via window.matchMedia API
* This will only add the stylesheet if the breakpoint matches */
media: 'screen and (max-width : 768px)'
}, {
href: 'page4/page4.print.css',
media: 'print'
}
]
});
Directives
myApp.directive('myDirective', function () {
return {
restrict: 'E',
templateUrl: 'my-directive/my-directive.html',
css: 'my-directive/my-directive.css'
}
});
Additionally, you can use the $css service for edge cases:
myApp.controller('pageCtrl', function ($scope, $css) {
// Binds stylesheet(s) to scope create/destroy events (recommended over add/remove)
$css.bind({
href: 'my-page/my-page.css'
}, $scope);
// Simply add stylesheet(s)
$css.add('my-page/my-page.css');
// Simply remove stylesheet(s)
$css.remove(['my-page/my-page.css','my-page/my-page2.css']);
// Remove all stylesheets
$css.removeAll();
});
You can read more about AngularCSS here:
http://door3.com/insights/introducing-angularcss-css-demand-angularjs
Could append a new stylesheet to head within $routeProvider. For simplicity am using a string but could create new link element also, or create a service for stylesheets
/* check if already exists first - note ID used on link element*/
/* could also track within scope object*/
if( !angular.element('link#myViewName').length){
angular.element('head').append('<link id="myViewName" href="myViewName.css" rel="stylesheet">');
}
Biggest benefit of prelaoding in page is any background images will already exist, and less lieklyhood of FOUC
#sz3, funny enough today I had to do exactly what you were trying to achieve: 'load a specific CSS file only when a user access' a specific page. So I used the solution above.
But I am here to answer your last question: 'where exactly should I put the code. Any ideas?'
You were right including the code into the resolve, but you need to change a bit the format.
Take a look at the code below:
.when('/home', {
title:'Home - ' + siteName,
bodyClass: 'home',
templateUrl: function(params) {
return 'views/home.html';
},
controler: 'homeCtrl',
resolve: {
style : function(){
/* check if already exists first - note ID used on link element*/
/* could also track within scope object*/
if( !angular.element('link#mobile').length){
angular.element('head').append('<link id="home" href="home.css" rel="stylesheet">');
}
}
}
})
I've just tested and it's working fine, it injects the html and it loads my 'home.css' only when I hit the '/home' route.
Full explanation can be found here, but basically resolve: should get an object in the format
{
'key' : string or function()
}
You can name the 'key' anything you like - in my case I called 'style'.
Then for the value you have two options:
If it's a string, then it is an alias for a service.
If it's function, then it is injected and the return value is treated
as the dependency.
The main point here is that the code inside the function is going to be executed before before the controller is instantiated and the $routeChangeSuccess event is fired.
Hope that helps.
Awesome, thank you!! Just had to make a few adjustments to get it working with ui-router:
var app = app || angular.module('app', []);
app.directive('head', ['$rootScope', '$compile', '$state', function ($rootScope, $compile, $state) {
return {
restrict: 'E',
link: function ($scope, elem, attrs, ctrls) {
var html = '<link rel="stylesheet" ng-repeat="(routeCtrl, cssUrl) in routeStyles" ng-href="{{cssUrl}}" />';
var el = $compile(html)($scope)
elem.append(el);
$scope.routeStyles = {};
function applyStyles(state, action) {
var sheets = state ? state.css : null;
if (state.parent) {
var parentState = $state.get(state.parent)
applyStyles(parentState, action);
}
if (sheets) {
if (!Array.isArray(sheets)) {
sheets = [sheets];
}
angular.forEach(sheets, function (sheet) {
action(sheet);
});
}
}
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
applyStyles(fromState, function(sheet) {
delete $scope.routeStyles[sheet];
console.log('>> remove >> ', sheet);
});
applyStyles(toState, function(sheet) {
$scope.routeStyles[sheet] = sheet;
console.log('>> add >> ', sheet);
});
});
}
}
}]);
If you only need your CSS to be applied to one specific view, I'm using this handy snippet inside my controller:
$("body").addClass("mystate");
$scope.$on("$destroy", function() {
$("body").removeClass("mystate");
});
This will add a class to my body tag when the state loads, and remove it when the state is destroyed (i.e. someone changes pages). This solves my related problem of only needing CSS to be applied to one state in my application.
'use strict';
angular.module('app')
.run(
[
'$rootScope', '$state', '$stateParams',
function($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
]
)
.config(
[
'$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider
.otherwise('/app/dashboard');
$stateProvider
.state('app', {
abstract: true,
url: '/app',
templateUrl: 'views/layout.html'
})
.state('app.dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard.html',
ncyBreadcrumb: {
label: 'Dashboard',
description: ''
},
resolve: {
deps: [
'$ocLazyLoad',
function($ocLazyLoad) {
return $ocLazyLoad.load({
serie: true,
files: [
'lib/jquery/charts/sparkline/jquery.sparkline.js',
'lib/jquery/charts/easypiechart/jquery.easypiechart.js',
'lib/jquery/charts/flot/jquery.flot.js',
'lib/jquery/charts/flot/jquery.flot.resize.js',
'lib/jquery/charts/flot/jquery.flot.pie.js',
'lib/jquery/charts/flot/jquery.flot.tooltip.js',
'lib/jquery/charts/flot/jquery.flot.orderBars.js',
'app/controllers/dashboard.js',
'app/directives/realtimechart.js'
]
});
}
]
}
})
.state('ram', {
abstract: true,
url: '/ram',
templateUrl: 'views/layout-ram.html'
})
.state('ram.dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard-ram.html',
ncyBreadcrumb: {
label: 'test'
},
resolve: {
deps: [
'$ocLazyLoad',
function($ocLazyLoad) {
return $ocLazyLoad.load({
serie: true,
files: [
'lib/jquery/charts/sparkline/jquery.sparkline.js',
'lib/jquery/charts/easypiechart/jquery.easypiechart.js',
'lib/jquery/charts/flot/jquery.flot.js',
'lib/jquery/charts/flot/jquery.flot.resize.js',
'lib/jquery/charts/flot/jquery.flot.pie.js',
'lib/jquery/charts/flot/jquery.flot.tooltip.js',
'lib/jquery/charts/flot/jquery.flot.orderBars.js',
'app/controllers/dashboard.js',
'app/directives/realtimechart.js'
]
});
}
]
}
})
);

Resources