Choose between different style for an Ember.JS application - css

The issue I have is that I can't find a way to 'change' the css style within my application.
The thing that I want to access is for example: I have a red theme, but I want that the user can choose an other predefined theme, like a green, or a blue theme.
The idea is that I have different app.css, how can I change between one another, I can't find method to do so. Maybe I can do it in my environnement.js?
Any tips is apreciated.
tl;dr: How to set multiple css style in our Ember.JS app?

You can achieve this by generating multiple stylesheets, see: https://ember-cli.com/asset-compilation#configuring-output-paths
I suggest using ember-cli-head to add a specific link element with the additional theme stylesheet. You could set the stylesheet in your head.hbs using the headData service.
Full Example:
ember-cli-build.js
var app = new EmberApp({
outputPaths: {
app: {
css: {
// app/styles/red.css
'red': '/assets/themes/red.css'
}
}
},
// Exclude theme css files from fingerprinting,
// otherwise your file is named `red-somehash.css`
// which we don't (easily) now at runtime.
fingerprint: {
exclude: [ 'red.css' ]
}
})
head.hbs
{{#if theme}}
<link rel="stylesheet" href="/assets/{{theme}}.css">
{{/if}}
some-component.js (or Route or whatever)
export default Ember.Component.extend({
headData: Ember.inject.service(),
actions: {
setTheme(themeName) {
this.set('headData.theme')
}
}
})

Related

Is there a way to fetch colors stored from the Back End and place them in SCSS file

At the moment i'am currently re-factoring my code . So that means more clearness and higher maintance . I have an idea in my head , but unfortunately i don't know how this can be realeased.
In order to be more clean and effecient i want to create an isolated _colors.scss file that will contain all of my colors for the app . In other words i want it to look something like this
$colorPrimary: branding.colorPrimary,
$colorSecondary: branding.colorSecondary
...
At the moment is pretty messy because the color styling is maintened at the component level , but i want to do this seperately .
Ex:
<button style={
color: branding.colorPrimary,
background: branding.colorSecondary>
Hello
</button>
The colors are stored on the BE side so if i want to react them i have to do a call . I will be glad if someone can give me a hand with this mindf****. Thank you .
to acheive this behavior you have two main way to do it. the first one describe are not compatible with Internet Explorer.
On last CSS version describe on W3C we are able to have variable directly on CSS.
Like this backend can do something like this :
<head>
<style type="text/css">
:root {
--primary-color: #cecece;
--secondary-color: #fefefe;
}
</style>
</head>
then on your scss (or css) you can do something like :
.mySelector {
color: var(--primary-color, black);
}
which will be basically interpret as :
.mySelector {
color: #cecece;
}
Or fallback in color black if --primary-color is not defined.
As you can imagine, is very easy for backend to prepare the configuration for frontend. And from your side (as front end) you can simply use what is already available on CSS API tools.
But if you are looking for something which are compatible with IE. You probably need more complex infrastructure.
Goal is to spawn webpack sass compilation, on each User color change,
to build css output with relevant variable configuration.
for that you will need SaSS ressource loader which automatically inject sass file on all other file. Is like adding #import "_colors" automatically.
then backend server will have to :
Write _color.scss file somewhere (let say /user/123/_color.scss)
Ask for compilation like webpack client 123
read the output webpack folder for client 123 and detect if specific CSS exist
Inject on your head HTML.
For webpack configuration
on your webpack you will have something like :
const argv = require("yargs").argv;
entry: {
[...]
},
output: {
// Take the argument as clientId and craft dedicated output folder.
path: helpers.root(`public/themes/front/${argv.client}`),
filename: "[name].[contenthash].js",
chunkFilename: "[name].[contenthash].js"
}
Like this base on the client id you will store the outputed CSS on specific folder.
Finally the SaSS rules will looks like :
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader',
{
loader: 'sass-resources-loader',
options: {
resources: (argv.client) ? `/user/${ argv.client }/_color.scss` : `/user/default/_color.scss`,
},
},
],
}

Vuejs SPA need to load css for ltr and rtl based on language

I'm building a website using Vue but I have 2 separate css files, 1 for normal ltr and one for rtl.
Now according to the URL xyz.com/en or xyz.com/ar I can serve the appropriate file, based on server-side logic. However, I want this to be done on the client via Vue Router.
So that the URLs can look like this xyz.com/#/en or xyz.com/#/ar
You could have language specific CSS inclusions <link href="..." /> from the respective components for EN and AR as shown below.
routes: [
{
path: '/en',
name: 'Welcome EN...',
component: WelcomeEN
},
{
path: '/ar',
name: 'Welcome AR...',
component: WelcomeAR
}
]
Do you think that would work for you ?
this is an example of conditional css loading inside a component
// change x to change color
const x = true;
if(x)
require("./assets/style.css");
else
require("./assets/stylegreen.css");
code snippet of conditional css loading in vue

Make webpack conditionally load additional or alternative css file

I'm trying to implement a kind of css theming in an angular 4 project. We use webpack 3 for bundling. The product is intended to be used by several companies and has to look according to their brandbooks. So we need themes.
We gonna have several builds, but we don't want to have several versions of code. All themes should remain in the same codebase. The differences are minimal: colors, icons, fonts — everything may be changed in css.
I have thought of several ways to do it, the most obvious would be to implement theming via :host-context for components and change the class of body by changing environment variable for webpack. With such method we will heve every theme inside our bundle, which is not good. Maybe there's another way?
I wonder if it is possible to have webpack load not the css file it is asked for. Instead it could look for another file by pattern, and if it exists, use that file instead of original one. Or load both files.
For example, we have a button.component.ts which imports button.component.css. If we don't tell webpack to use any theme, it works as usual. But if we do, it tries to read button.component.theme-name.css in the same directory. If that file exists, webpack imports it instead (or altogether with) the default file.
That's basically what I'm trying to do. I guess, the same mechanism would be useful for html templates in angular.
Is there a plugin to do such magic? Or maybe some sophisticated loader option? If you have another way to solve my task — feel free to drop a comment!
I created a loader which can append or replace the content of a loaded file with the content of its sibling which has a chosen theme's title in its name.
TL;DR
Create a file with loader.
Use it in webpack config.
Run webpack in THEME=<themeName> evironment.
theme-loader.js
const fs = require('fs');
const loaderUtils = require('loader-utils');
module.exports = function (mainData) {
const options = loaderUtils.getOptions(this);
let themeName = options.theme;
let mode = options.mode;
if (themeName) {
// default mode
if (!Object.keys(transform).includes(mode)) {
mode = 'replace';
}
// fileName.suffix.ext -> fileName.suffix.themeName.ext
const themeAssetPath = this.resourcePath.replace(/\.([^\.]*)$/, `.${themeName}.$1`);
const callback = this.async();
// for HMR to work
this.addDependency(themeAssetPath);
fs.readFile(themeAssetPath, 'utf8', (err, themeData) => {
if (!err) {
callback(null, transform[mode](mainData, themeData));
} else if (err.code === 'ENOENT') {
// don't worry! if it's not here then it's not needed
callback(null, mainData);
} else {
callback(err);
}
});
} else {
return mainData;
}
};
const transform = {
// concat theme file with main file
concat: (mainData, themeData) => mainData + '\n' + themeData,
// replace main file with theme file
replace: (mainData, themeData) => themeData
};
A piece of sample webpack.config.js to use this handmade loader:
resolveLoader: {
modules: [
paths.libs, // ./node_modules
paths.config // this is where our custom loader sits
]
},
module: {
rules: [
// component styles
{
test: /\.css$/,
include: path.join(paths.src, 'app'),
use: [
'raw-loader',
// search for a themed one and append it to main file if found
{
loader: 'theme-loader',
options: {
theme: process.env.THEME,
mode: 'concat'
}
}
]
},
// angular templates — search for a themed one and use it if found
{
test: /\.html$/,
use: ['raw-loader',
{
loader: 'theme-loader',
options: {
theme: process.env.THEME,
mode: 'replace'
}
}
]
}
]
}
For example, an app.component.css:
:host {
background: #f0f0f0;
color: #333333;
padding: 1rem 2rem;
display: flex;
flex-direction: column;
flex: 1;
justify-content: center;
}
nav {
/* ... */
/* something about nav element */
/* ... */
}
header {
/* ... */
/* pile of styles for header */
/* ... */
}
To implement dark theme we don't need to change all that flex and padding staff and maybe nav and header don't have their own background and font color settings. So we'll just have to override host element style. We create app.component.dark.css:
:host {
background: #222222;
color: #e0e0e0;
}
The we run webpack with environment variable THEME set to dark. The loader takes a request to process app.component.css, tries to load app.component.dark.css and voila! Themed css is appended to the end of resulting file. Because of cascade,
if multiple competing selectors have the same importance and specificity, … later rules will win over earlier rules (MDN).
For HTML we don't have such method. So we'll have to rewrite our template completely. Hopefully, you won't need to do it too often. I my case, I wanted to change like header and footer to fit the cutomer's branding demand.
This was my first attempt to create a webpack loader, please leave a comment if you see a problem with it.

How to switch css file when using Webpack to load css?

I use gulp to compile my sass file to css files, and reference the css file in my html. The project support theme switch. For example, I have 3 css theme files:
red.css
yellow.css
blue.css
I can currently switch the theme css like this:
var styleDom = $('#theme-style');
var newHref = 'styles/themes/' + themeName + '.css';
if (styleDom.attr('href') !== newHref) {
styleDom.attr('href', newHref);
}
Now I want to use webpack to load the css file.
require('styles/themes/red.css');
It seems work well, but I cannot find a way to switch the theme css file now, does anyone have a solution?
Your approach doesn’t need to change. Just use Extract Text plugin to save out the CSS files. You’ll need to make multiple entry points to create multiple CSS files.
OR
More ideally, (the approach I would take) make your CSS switch based on a different html or body class and just change the class. It won’t add much overhead, and it will be a more ideal UX when changing themes.
You'll need to use a combination of webpacks style-loader and file-loader (second example ) and use require.ensure (second example "dynamic imports") to accomplish this:
function switchTheme(name) {
// Remove the current theme stylesheet
// Note: it is important that your theme css always is the last
// <link/> tag within the <head/>
$('head link[rel="stylesheet"]').last().remove();
// Since webpack needs all filePaths at build-time
// you can't dynamically build the filePath
switch(name) {
case 'red':
// With require.ensure, it is possible to tell webpack
// to only load the module (css) when require is actually called
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/red.css');
});
case 'yellow':
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/yellow.css');
});
case 'blue':
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/blue.css');
});
default:
throw new Error('Unknown theme "' + name + '"');
}
}
Then a call like switchTheme('blue') should do the trick.
And you might have to check your current webpack.config.js, in case you already have configured a loader for .css files.

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