How can we style a word in css with special character? - javascript

I made an application in angular 2(5.0). The application is all about getting the trending tweets.
I faced one problem while styling. I am getting one paragraph though API
Eg:#_NAN_DINI #InTheWordsOfK #maidros78 Self immolation is still a form of social protest in India. Remember the guy w…
My need is, i want to give additional style to the words which having characters like # and #
Eg:
#_NAN_DINI
#InTheWordsOfK
#maidros78
#ereaad
So is it possible to add some basic style to these kind of word without heavy JavaScript ??
Can we solve it with CSS ? How ?
Note: I am using SCSS.

This is not something that can be done with only CSS. You will need JavaScript and the example below in raw JS without the need for any framework, but it can be use in any framework.
Try this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Twitter CSS 1</title>
<style>
#input {
font: 14px/1.2em Tahoma;
height: 12em;
width: 500px;
}
.class-at {
color: blue;
}
.class-hash {
color: green;
}
</style>
</head>
<body>
<h3>Enter Text Here</h3>
<textarea id="input">#_NAN_DINI #InTheWordsOfK #maidros78 Self immolation is still a form of social protest in India. Remember the guy w…</textarea>
<hr/>
<div id="output"></div>
<script>
var inEl = document.getElementById('input');
var outEl = document.getElementById('output');
function encodeStr(str) {
return str.replace(/(\#\S+)/g, (key) => `<span class="class-at">${key}</span>`).replace(/(\#\S+)/g, (key) => `<span class="class-hash">${key}</span>`);
}
function inputHandler() {
outEl.innerHTML = encodeStr(inEl.value);
}
inEl.addEventListener('input', inputHandler);
inputHandler();
</script>
</body>
</html>
See: https://jsfiddle.net/intervalia/bu3rxq8q/
The function encodeStr contains two calls to replace. And those add either a <span class="class-at"> or a <span class="class-hash"> around the words you are looking for.
All that is needed
The function encodeStr is all the JS that is really needed to do the conversion. It is up to you to get the string into the function and use the result. You will also need the CSS that colors your fields the color you want. .class-at and .class-hash. Of course you can change those to whatever you want them to be called.
function encodeStr(str) {
return str.replace(/(\#\S+)/g, (key) => `<span class="class-at">${key}</span>`).replace(/(\#\S+)/g, (key) => `<span class="class-hash">${key}</span>`);
}

Related

How to add/update scoped CSS variables via JavaScript

Is there a native API which can update the CSS variables scoped under a particular CSS class(or even any other complex CSS selector)predefined in a stylesheet? The question can be generalized for not just CSS variables but other CSS properties as well, i.e whether class specific CSS properties can be updated without targeting a specific HTML element, but by targeting the class definition itself.
Please find below the code snippets which demonstrates an example scenario. You can also find comments in the code to as to what I believe is happening/I am doing on specific lines.
var toggle = true;
function changeColor() {
document.documentElement.style.setProperty('--bg-color', toggle ? 'green' : 'red');
// this works for the "outer" div since there we receive global value(value defined in :root) of --bg-color
toggle = !toggle;
// here I want to also change the scoped value of --bg-color for "inner-primary" and "inner-secondary"
// currently I can do this by doing:
document.querySelectorAll('.inner-primary').forEach(ele => ele.style.setProperty('--bg-color', toggle ? 'blue' : 'yellow'))
document.querySelectorAll('.inner-secondary').forEach(ele => ele.style.setProperty('--bg-color', toggle ? 'yellow' : 'blue' ))
// another way I can see is: we dynamically insert a style tag, but this feels very awkward and can quickly get out of hand on multiple iterations
}
:root {
--bg-color: red;
}
body {
margin: 0;
}
.outer {
width: 100vw;
height: auto;
min-height: 100vh;
text-align: center;
background-color: var(--bg-color); /* receives value from :root */
}
.inner-primary,
.inner-secondary {
width: 100px;
height: 100px;
/* received scoped value from .inner-primary or .inner-secondary defined below*/
background-color: var(--bg-color);
border: 1px solid black;
margin: auto;
margin-bottom: 10px;
}
.inner-secondary {
--bg-color: yellow;
}
.inner-primary {
--bg-color: blue;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Testing</title>
</head>
<body>
<div class="outer">
<div class="inner-primary"></div>
<div class="inner-secondary"></div>
<div class="inner-primary"></div>
<div class="inner-secondary"></div>
<button onclick="changeColor()">Change Color</button>
</body>
</html>
Please try running this to get a full idea of intended effect. You can click "Change Color" button at the bottom to see the effects in action.
To get the intended overriding for CSS variable --bg-color for classes inner-primary and inner-secondary, I had to use querySelectorAll with the required CSS selector(in this case just a class name) and iteratively set the CSS variable for each individual element found.
By nature of how CSS gets read by the browser, feels like the other solution to this is to dynamically insert a style element tag into the DOM, with the required CSS variable update, scoped under the required class name(or any other required selector)..
But this feels awkward and can quickly get out of hand if we don't implement some system to reuse the same style tag and not insert new ones during each toggle.
Is there any other way to do this? Any native API which can solve this without having to access individual elements or without inserting style tags dynamically..?
As suggested by A Haworth and referring Change CSS of class in Javascript? I was able update changeColor function to use CSSStyleSheet(MDN link) instead. Please find the updated function below, which uses this API:
var toggle = true;
function changeColor() {
document.documentElement.style.setProperty('--bg-color', toggle ? 'green' : 'red');
// solution using document stylesheets
const styleSheet = document.styleSheets[0]
const cssRules = Array.from(styleSheet.cssRules);
const primaryClassIndex = cssRules.findIndex(cssRule => cssRule.selectorText === '.inner-primary');
const secondaryClassIndex = cssRules.findIndex(cssRule => cssRule.selectorText === '.inner-secondary');
//update primary:
styleSheet.deleteRule(primaryClassIndex);
styleSheet.insertRule(`.inner-primary {--bg-color: ${toggle ? 'yellow' : 'blue'};}`, primaryClassIndex)
//update secondary:
styleSheet.deleteRule(secondaryClassIndex);
styleSheet.insertRule(`.inner-secondary {--bg-color: ${toggle ? 'blue' : 'yellow'};}`, secondaryClassIndex)
//toggle
toggle = !toggle;
}
This is still some concern here since it seems like we can only overwrite the entire cssRule(which may also include other CSS properties) for a particular selector, and not just one required property. But this may arguably be better than updating each individual element style or inserting style tags as mentioned in the question.
Can check the full working codepen at => https://codepen.io/yadus/pen/mdWZmXX

How I add an item to localStorage JS?

I have a function that sets the color theme of my site when clicked, but when I reload the page or go to a new page, it reverts back to the default theme. There's a line of my code that is supposed to get the previous theme from localStorage and apply it to the new one, but I think why it isn't working is that there isn't actually any code that saves the theme to Storage. Either that, or localStorage resets when the page is reloaded/changed. Either way, I'm not sure how to fix it.
This is my code:
<a class="switch-light-purple color-palette" id="color-palette" onclick="setTheme('theme-light-purple')">
<script>
// function to set a given theme/color-scheme
function setTheme(themeName) {
localStorage.setItem('theme', themeName);
document.documentElement.className = themeName;
}
// Immediately invoked function to set the theme on initial load
(function () {
if (localStorage.getItem('theme') === 'theme-light-purple') {
setTheme('theme-light-purple');
}
})();
</script>
</a>
<a class="switch-dark-blue color-palette" id="color-palette" onclick="setTheme('theme-dark-blue')">
<script>
// function to set a given theme/color-scheme
function setTheme(themeName) {
localStorage.setItem('theme', themeName);
document.documentElement.className = themeName;
}
// Immediately invoked function to set the theme on initial load
(function () {
if (localStorage.getItem('theme') === 'theme-dark-blue') {
setTheme('theme-dark-blue');
}
})();
</script>
</a>
Separation of concerns:
Keep your html, css, and js in their respective files ALWAYS, there's no substitution for this.
So your index.html should look something like this (assuming index.html, style.css, and main.js are all in the same folder).
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="parent-container">
<a data-theme="theme-dark-blue" class="switch-dark-blue color-palette" id="color-palette">Dark Blue </a>
<a data-theme="theme-light-purple" class="switch-light-purple color-palette" id="color-palette2">Light Purple</a>
</div>
<script src="main.js"></script>
</body>
</html>
Your CSS will be in a file called style.css,
And your JS,
// This waits for the DOM to complete loading
document.addEventListener('DOMContentLoaded', function() {
// all your code goes inside this.
});
Also, as #Sebastian Simon pointed out in the comments,
Repeating the same id is invalid in HTML. Inline event handlers like onclick are not recommended. They are an obsolete, hard-to-maintain and unintuitive way of registering events. Always use addEventListener instead.
Unique IDs:
You could change the id of one of your anchor tags to color-palette2 for instance, but since you have a lot of different themes you could pick names that are more self-explanatory. But, as long as your ids are unique (1 unique id per page), it's fine.
Event Listeners:
Use .addEventListener to add events to your DOM nodes. I've done this in the code snippet I've shared. It's very easy once you get used to it.
I've also used data attributes to pass your theme in a more "dynamic" way to your logic. StackOverflow snippets don't support localStorage hence I've commented that line out.
function setTheme() {
let theme;
let savedTheme = localStorage.getItem('theme')
// check if localstorage already has a theme defined
// if it has, then set theme variable to it
// otherwise, get the theme from the data-theme attribute
// of the current anchor tag clicked represented by this.
theme = savedTheme || this.dataset.theme
let div = document.querySelector('div.parent-container')
// I'm adding this to a div
// you can add it to the body tag.
div.classList.add(theme)
// this code can be better but the idea is
// to remove all other themes and keep only the intended one.
if (theme === 'theme-dark-blue') {
div.classList.remove('theme-light-purple')
} else {
div.classList.remove('theme-dark-blue')
}
console.log(`Set theme to ${theme}`)
}
let palettes = document.querySelectorAll('.color-palette')
palettes.forEach(function(p) {
p.addEventListener('click', setTheme)
})
a {
cursor: pointer;
text-decoration: underline;
color: blue;
}
.switch-light-purple {
color: purple;
margin: 0 15px;
}
<div class="parent-container">
<a data-theme="theme-dark-blue" class="switch-dark-blue color-palette" id="color-palette">Dark Blue </a>
<a data-theme="theme-light-purple" class="switch-light-purple color-palette" id="color-palette2">Light Purple</a>
</div>
PS: the code snippet won't work since StackOverflow does not support localStorage.

binding textContent and overriding dom with svelte

I have a div with contenteditable=true and bind:textContent={value} so it behaves pretty much like a textarea.
The only issue I have with it is that I want to override the content of the div by processing the value, but seems like it is not possible.
To test I wrote this
<div contenteditable="true" bind:textContent={value}>testVal</div>
where value is an exported property of the component.
I kind of expected value to be set to testVal, but instead the div contains the value property.
I sort of understand why this is happening and that what I am doing is sort of an edge case, but is it at all possible to change this behaviour to kind of get a one way binding to value?
and I have tried my "normal" way of creating a one way binding (with some hacks to demonstrate issues):
<div contenteditable="true" on:input={e => value = e.target.textContent}>
{#each (value || "").split("") as part}
{part}
{/each}
</div>
this looks fine, but whenever I change type in the div my input gets multiplied, i.e. if I type e the div gets updated with ee. If I add another e I get eeee
I think the way to go is to use your "normal" way of creating a one way binding. Otherwise, using multiple ways of binding on the same element will conflict.
I used a combination of on:input like you described and, inside of the div, {#html html}
The following example formats each other word in bold as you type (there's some glitch when starting with an empty field):
<script>
import {tick} from "svelte";
let html = '<p>Write some text!</p>';
// for the implementation of the two functions below, see
// https://stackoverflow.com/a/13950376/4262276
let saveSelection = (containerEl) => { /**/ };
let restoreSelection = (containerEl, savedSel) => { /**/ };
let editor;
function handleInput(e){
const savedSelection = saveSelection(editor);
html = e.target.textContent
.split(" ")
.map((t, i) => i % 2 === 0
? `<span style="font-weight:bold">${t}</span>`
: t
)
.join(" ");
tick().then(() => {
restoreSelection(editor, savedSelection);
})
}
</script>
<div
bind:this={editor}
contenteditable="true"
on:input={handleInput}
>{#html html}</div>
<style>
[contenteditable] {
padding: 0.5em;
border: 1px solid #eee;
border-radius: 4px;
}
</style>

Function Return HTML

I have function who return html
renderSuggestion(suggestion) {
const query = this.query;
if (suggestion.name === "hotels") {
const image = suggestion.item;
return this.$createElement('div', image.title);
} else {
let str = suggestion.item.name;
let substr = query;
return this.$createElement('div', str.replace(substr, `<b>${substr}</b>`));
}
},
But<b> element not render in browser as html element. Its display like string...
How I display this <b> element?
Tnx
That is because when you provide a string as the second argument of createElement, VueJS actually inserts the string as a text node (hence your HTML tags will appear as-is). What you want is actually to use a data object as the second argument, which give you finer control over the properties of the created element:
this.$createElement('div', {
domProps: {
innerHHTML: str.replace(substr, `<b>${substr}</b>`)
}
});
Of course, when you are using innerHTML, use it with caution and never insert user-provided HTML, to avoid XSS attacks.
You can also create a component and use v-html to render the output.
Declare props for your inputs:
export default {
props: {
suggestion: Object,
query: String
}
};
And use a template that uses your logic in the template part
<template>
<div class="hello">
<div v-if="suggestion.name === 'hotels'">{{suggestion.item.title}}</div>
<div v-else>
<div v-html="suggestion.item.name.replace(this.query, `<b>${this.query}</b>`)"/>
</div>
</div>
</template>
This allows for greater flexibility when using more complex layouts.
A working example here
Provide more detail(possibly a picture) of how it's not showing. Consider using a custom CSS class to see the div and what's happening to it.
bold {
border-style: black;
font-weight: bold;
}
then just use the "bold" class instead of "b".

Animating light dom elements using neon-elements

So i've been playing around with the neon element examples. Specifically the load example.
I'm able to accomplish what I want but I feel there should be a more elegant way to do it. The way the example loads content is by creating an array inside of a config object and then uses a dom-repeat to stamp them out all from within animated-grid.html
This seems to force very tight coupling between the animations and the content that is being animating. I don't want to encapsulate content in side of my grid I basically want to say Hey I want an animated grid here are the cells/content to be put inside of it and then having the animated grid take care of laying out and animating those children
I set out to create an API that was more declarative like this.
<animated-grid id="grid">
<div style="background-color: #9C27B0"> <span>1</span> </div>
<div style="background-color: #4CAF50"> <span>2</span> </div>
<div style="background-color: #2196F3"> <span>3</span> </div>
<div style="background-color: #673AB7"> <span>4</span> </div>
<div style="background-color: #FF9800"> <span>5</span> </div>
<div style="background-color: #049688"> <span>6</span> </div>
</animated-grid>
I figured I could change the css selectors from .tile to :host::content div.
Then the template to
<template>
<content id='content'></content>
</template>
I then took a stab at rewritting the attached method to get the children in the lightdom instead of queryselecting for .tile
attached: function() {
this.async(function() {
var nodeList = Polymer.dom(this.$.content).getDistributedNodes();
// I only want to animate divs
nodelist = nodeList.filter(function(node){
return (node.nodeName === "DIV");
});
this.animationConfig['entry'][0].nodes = nodeList;
});
},
However, I keep getting a nasty error in my console.
The final code for animated-grid looked like this:
<!--
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../../../polymer/polymer.html">
<link rel="import" href="../../../paper-styles/paper-styles.html">
<link rel="import" href="../../neon-shared-element-animatable-behavior.html">
<dom-module id="animated-grid">
<link rel="import" type="css" href="../shared.css">
<style>
:host {
display: block;
}
:host::content div {
display: inline-block;
float: left;
color: white;
vertical-align: top;
width: calc(100% / 3);
height: calc(100% / 2);
#apply(--paper-font-title);
#apply(--layout-vertical);
#apply(--layout-center-center);
}
:host::content div:hover {
#apply(--shadow-elevation-2dp);
position:relative;
}
</style>
<template>
<content id='content'></content>
</template>
</dom-module>
<script>
Polymer({
is: 'animated-grid',
behaviors: [
Polymer.NeonSharedElementAnimatableBehavior
],
properties: {
animationConfig: {
type: Object,
value: function() {
return {
'entry': [{
name: 'cascaded-animation',
animation: 'transform-animation',
transformFrom: 'translateY(100%)',
transformTo: 'none',
timing: {
delay: 50
}
}]
}
}
}
},
attached: function() {
this.async(function() {
var nodeList = Polymer.dom(this.$.content).getDistributedNodes();
// I only want to animate divs
nodelist = nodeList.filter(function(node){
return (node.nodeName === "DIV");
});
this.animationConfig['entry'][0].nodes = nodeList;
});
},
_computeTileClass: function(color) {
return 'background-color: ' + color + '';
}
});
</script>
UPDATE: A good friend pointed out that while JavaScript is still insane, it's insane for different reasons than erroneously listed here. The error was that the variable being assigned into, nodelist, from the filter, did not have a capital "L", like the var nodeList does. In JS, this means nodelist is now in the global variable object, global.nodelist, which is why nodeList seemingly wasn't filtered or assigned correctly into. So, another solution is to simply capitalize nodelist to nodeList in the filter return, and it should be fine.
Everything you're doing is fine and good (the async in attached is the right way to setup your animationConfig), with the exception of one thing.
The error you're getting is because you're attempting to animate text nodes with the cascade-animation you setup.
You're attempting to animate text nodes because your variable nodeList contains all the original 13 light dom children, which includes the text nodes, instead of just divs.
The reason your filtered array wasn't filtered, is because JavaScript is insane, and assigning into the array doesn't work as expected, like in every other language.
A solution is to use a new variable for the filter results:
var divNodes = nodeList.filter(function(node){
return (node.nodeName === 'DIV');
});
this.animationConfig.entry[0].nodes = divNodes;

Categories

Resources