I am writing a simple grid layout using Sass. I am trying to use calc() to determine the width in relative units %. To test the styles, I am using a simple HTML file.
Problem: Inspecting the results with dev tools on Chrome, it shows that the width declaration with the calc() call is dropped as Invalid property value. Here is the code:
src.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="X-UA Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="./../css/12grid_rwd.css">
</head>
<body>
<main class="grid_1" role="main">
<!--<header class="grid_12" role="banner">Header</header>
<nav class="grid_3" role="navigation">Nav</nav>
<section class="grid_9">Content</section>
<footer class="grid_12" role="contentinfo">Footer</footer> -->
</main>
</body>
</html>
src.scss
$context-width: 1200;
// Grid >> 12 Columns
.grid {
&_1 { width: calc(calc(80/#{$context-width})*100); }
}
generated css:
.grid_1 {
width: calc(calc(80/1200)*100); }
calc() call's can't be nested, it expects expression to calculate, it is the reason why your property is dropped by browser.
Moreover since your expression contains plain math - it can be calculated by Sass itself. Moreover from your expression it looks like you want resulted value to be percent from container's width, in this case you can use percentage() function from Sass:
$context-width: 1200;
$column-width: 80;
// Grid >> 12 Columns
.grid {
#for $n from 1 through 12 {
&_#{$n} {
width: percentage($column-width * $n/$context-width);
}
}
}
You can play with this example at Sassmeister.
Following the suggestions from #Flying, I was able to implement the logic I wanted. The code is much simpler to understand and debug.
Goal: To have a 1280 pixels grid system with 20px margin on container and 20px padding on the grids. Note that these margin and padding properties are for left and right only (10px each side). Starting with a first grid, class grid_1, at 80px, grid_2 will be 80*2 + 20*(2-1) where 80 is the fixed column width, 2 is the grid number, 20 is the paddingand so on. The scss code is as follows:
src.scss
$context-width: 1200;
$column-width: 80;
$fixed-gutter: 20;
// Grid >> 12 Columns
#mixin width-calc($n) {
#if $n == 1{
width: percentage(($column-width * $n)/$context-width);
}
#else {
$c: $column-width * $n + ($fixed-gutter * ($n - 1));
width: percentage(($c)/$context-width);
}
}
.grid {
#for $n from 1 through 12 {
&_#{$n} {
#include width-calc($n);
}
}
}
The generated css:
.grid_1 {
width: 6.66667%;
}
.grid_2 {
width: 15%;
}
.grid_3 {
width: 23.33333%;
}
.grid_4 {
width: 31.66667%;
}
.grid_5 {
width: 40%;
}
.grid_6 {
width: 48.33333%;
}
.grid_7 {
width: 56.66667%;
}
.grid_8 {
width: 65%;
}
.grid_9 {
width: 73.33333%;
}
.grid_10 {
width: 81.66667%;
}
.grid_11 {
width: 90%;
}
.grid_12 {
width: 98.33333%;
}
Related
I am doing a simple example using ESPAsyncWebServer on ESP32. In this context I wrote a html file (there are a slider and a button) and tested it on a browser until the content look well. Then I integrate it in the C++ source code for ESP32. It works and look as expected until I don't add a callback to read the value of the slider.
This is the complete source code:
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
// Replace with your network credentials
const char ssid[] = "Vodafone-A40881218";
const char pswd[] = "rJbFMktHCcqN67Ye";
const int output = 2;
String sliderValue = "0";
// setting PWM properties
const int freq = 5000;
const int ledChannel = 0;
const int resolution = 8;
const char* PARAM_INPUT = "value";
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
#if 0
const char old_index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ESP Web Server</title>
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 2.3rem;}
p {font-size: 1.9rem;}
body {max-width: 400px; margin:0px auto; padding-bottom: 25px;}
.slider { -webkit-appearance: none; margin: 14px; width: 360px; height: 25px; background: #FFD65C; outline: none; -webkit-transition: .2s; transition: opacity .2s;}
.slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 35px; height: 35px; background: #003249; cursor: pointer;}
.slider::-moz-range-thumb { width: 35px; height: 35px; background: #003249; cursor: pointer; }
button { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:20%;color:white;font-size:130%; }
.buttons { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:15%;color:white;font-size:80%; }
.buttonsm { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:9%; color:white;font-size:70%; }
.buttonm { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:15%;color:white;font-size:70%; }
.buttonw { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:40%;color:white;font-size:70%; }
.buttong { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:40%;color:white;font-size:130%; }
</style>
</head>
<body>
<h2>ESP Web Server</h2>
<p><span id="textSliderValue">%SLIDERVALUE%</span></p>
<p><input type="range" onchange="updateSliderPWM(this)" id="pwmSlider" min="0" max="21" value="%SLIDERVALUE%" step="1" class="slider"></p>
<a href='/setup'><button class='button'>SETUP</button></a>
<script>
function updateSliderPWM(element) {
var sliderValue = document.getElementById("pwmSlider").value;
document.getElementById("textSliderValue").innerHTML = sliderValue;
console.log(sliderValue);
var xhr = new XMLHttpRequest();
xhr.open("GET", "/slider?value="+sliderValue, true);
xhr.send();
}
</script>
</body>
</html>
)rawliteral";
#endif
const char index_html[] = R"rawliteral(
<!DOCTYPE HTML>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ESP Web Server</title>
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 2.3rem;}
p {font-size: 1.9rem;}
body {max-width: 400px; margin:0px auto; padding-bottom: 25px;}
.slider { width: 360px; }
.slider::-webkit-slider-thumb { width: 50px; height: 50px; }
.slider::-moz-range-thumb { width: 50px; height: 50px; }
button { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:40%;color:white;font-size:130%; }
.buttons { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:15%;color:white;font-size:80%; }
.buttonx { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:9%; color:white;font-size:70%; }
.buttonm { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:15%;color:white;font-size:70%; }
.buttonw { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:40%;color:white;font-size:70%; }
.buttong { border-radius:0.5em;background:#C20000;padding:0.3em 0.3em;width:40%;color:white;font-size:130%; }
</style>
</head>
<body>
<h2>ESP Web Server</h2>
<p><span id="textSliderValue">%SLIDERVALUE%</span></p>
<p><input type="range" onchange="updateSliderPWM(this)" id="pwmSlider" min="0" max="21" value="%SLIDERVALUE%" step="1" class="slider"></p>
<a href='/setup'><button class='button'>SETUP</button></a>
<script>
function updateSliderPWM(element) {
var sliderValue = document.getElementById("pwmSlider").value;
document.getElementById("textSliderValue").innerHTML = sliderValue;
console.log(sliderValue);
var xhr = new XMLHttpRequest();
xhr.open("GET", "/slider?value="+sliderValue, true);
xhr.send();
}
</script>
</body>
</html>
)rawliteral";
// Replaces placeholder with button section in your web page
String processor(const String& var)
{
//Serial.println(var);
if (var == "SLIDERVALUE"){
return sliderValue;
}
return String();
}
#include <Preferences.h>
Preferences Pref;
int32_t g_iVolume = 0;
void setup()
{
// Serial port for debugging purposes
Serial.begin(115200);
Pref.begin("datasetup", false);
g_iVolume = Pref.getInt("volume", 5);
Serial.print("volume="); Serial.println(g_iVolume);
sliderValue = String(g_iVolume);
Pref.end();
// Connect to Wi-Fi
WiFi.begin(ssid, pswd);
Serial.println("Connecting ...");
while (WiFi.status() != WL_CONNECTED)
{ // Wait for the Wi-Fi to connect: scan for Wi-Fi networks, and connect to the strongest of the networks above
delay(250);
Serial.print('.');
}
// Print ESP Local IP Address
Serial.println(WiFi.localIP());
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
// Send a GET request to <ESP_IP>/slider?value=<inputMessage>
server.on("/slider", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage;
// GET input1 value on <ESP_IP>/slider?value=<inputMessage>
if (request->hasParam(PARAM_INPUT)) {
inputMessage = request->getParam(PARAM_INPUT)->value();
sliderValue = inputMessage;
int ival = sliderValue.toInt();
Serial.print("ival="); Serial.println(ival);
Pref.begin("datasetup", false);
size_t st = Pref.putInt("volume", ival);
Pref.end();
Serial.print("st="); Serial.println(st);
Pref.begin("datasetup", false);
int vol = Pref.getInt("volume", -1);
Serial.print("volume="); Serial.println(vol);
Pref.end();
}
else {
inputMessage = "No message sent";
}
Serial.println(inputMessage);
request->send(200, "text/plain", "OK");
});
// Start server
server.begin();
}
//------------------------------------------------------------------------
void loop()
{
// put your main code here, to run repeatedly:
}
//------------------------------------------------------------------------
the 1st image is with nullptr instead of processor and the 2nd with processor callback.
I found the problem. The author of ESPAsyncWebServer decided to use the % character as the delimiter for placeholders for the template processor. Unfortunately, the % is quite common in CSS and JavaScript so, writing CSS and JavaScript in HTML file(s) or Strings does not work as expected because the wrong interpretation of % as delimiters of chuncks of text that are not placeholders but CSS or JavaScript code. At the moment there is not a workaround, just do not use % in CSS and JavaScript.
I had the same problem and I fixed it by changing the delimiter character (%) to a ($).
To do this you have to modify the definition of "TEMPLATE_PLACEHOLDER" which is in the file "ESPAsyncWebServer\src\WebResponseImpl.h"
#ifndef TEMPLATE_PLACEHOLDER
#define TEMPLATE_PLACEHOLDER '$' <<<<
#endif
Is there a simple way to create pdfs with multiple pages per sheet using PuppeteerSharp (as per the option available when you print a pdf from Chrome) e.g. 1 / 2 / 4 / 6 / 9 / 16 pages per sheet?
There is a trick that I created where the viewport is set to a certain width and height, and then the container div of the page is set to the "contained" height after trial and error.
pdf generation function
public async Task<byte[]> GeneratePDF(string html)
{
var browser = await GetBrowserAsync();
var page = await browser.NewPageAsync();
// Here, we sit the viewport height to match a div height in html
await page.SetViewportAsync(new ViewPortOptions{Width = 720, Height = 1280 });
await page.SetContentAsync(html);
var data = await page.PdfDataAsync(new PdfOptions
{
PrintBackground = true,
Format = PaperFormat.A4,
DisplayHeaderFooter = false,
MarginOptions = new MarginOptions
{
Top = "60px",
Right = "40px",
Bottom = "60px",
Left = "40px"
}
});
return data;
}
private async Task<IBrowser> GetBrowserAsync()
{
var browserOptions = new BrowserFetcherOptions{ Path = AppContext.BaseDirectory };
using var browserFetcher = new BrowserFetcher(browserOptions);
await browserFetcher.DownloadAsync(BrowserFetcher.DefaultChromiumRevision);
string[] args = {"--no-sandbox"};
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = args
});
return browser;
}
Razor page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Test Page</title>
<style>
:root {
--primary-color: #ffffff;
--accent-color: #f2f2f2;
--secondary-color: #5f7cb3;
--text-color: #000;
}
body {
font-family: Helvetica, sans-serif;
}
h1, h2, h3, h4, h5, h6 {
color: var(--secondary-color);
}
.page-height {
min-height: 870px; /*trial and error*/
height: 870px;
max-height: 870px;
padding: 30px 0;
}
</style>
</head>
<body>
<div class="page-height" style="display: flex; flex-direction: column; justify-content: center; align-items: center;">
<img src="data:image/png;base64,BASE64_HERE" alt="logo.png" style="width: auto; height: 80px; padding: 20px 0;"/>
<h2 style="color: var(--text-color);">Title Here/h2>
<h3>Subtitle Here</h3>
</div>
<div class="page-height">
<p>Content of the first page.</p>
</div>
<div class="page-height">
<p>Content of the second page. Etc...</p>
</div>
</body>
</html>
This should do the trick.
I am using the latest KendoUI, and try to set a gauge like the kendoUI's offical web site AD image shows.
you could find the AD image just at: http://www.telerik.com/kendo-ui
please see the image, it shows an application named "northwind-dash", and there is a green gauge on it, there is a white color text "63%" inside the gauge.
I try the code below:
<!DOCTYPE html>
<html>
<head>
<title></title>
<link href="styles/kendo.common.min.css" rel="stylesheet" />
<link href="styles/kendo.default.min.css" rel="stylesheet" />
<link href="styles/kendo.dataviz.min.css" rel="stylesheet" />
<link href="styles/kendo.dataviz.default.min.css" rel="stylesheet" />
<script src="js/jquery.min.js"></script>
<script src="js/angular.min.js"></script>
<script src="js/kendo.all.min.js"></script>
</head>
<body>
<div id="example" class="k-content">
<div id="gauge-container">
<div id="gauge"></div>
<input id="gauge-value" value="65">
</div>
<script>
function createGauge() {
$("#gauge").kendoRadialGauge({
pointer: {
value: $("#gauge-value").val()
},
scale: {
minorUnit: 5,
startAngle: -30,
endAngle: 210,
max: 180
}
});
}
$(document).ready(function() {
createGauge();
function updateValue() {
$("#gauge").data("kendoRadialGauge").value($("#gauge-value").val());
}
if (kendo.ui.Slider) {
$("#gauge-value").kendoSlider({
min: 0,
max: 180,
showButtons: false,
change: updateValue
});
} else {
$("#gauge-value").change(updateValue);
}
$(document).bind("kendo:skinChange", function(e) {
createGauge();
});
});
</script>
<style scoped>
#gauge-container {
background: transparent url("../content/dataviz/gauge/gauge-container-partial.png") no-repeat 50% 50%;
width: 386px;
height: 386px;
text-align: center;
margin: 0 auto 30px auto;
}
#gauge {
width: 350px;
height: 300px;
margin: 0 auto;
}
#gauge-container .k-slider {
margin-top: -11px;
width: 140px;
}
</style>
</div>
</body>
</html>
But, I could only get the normal Radial Gauge.
I look everywhere in the KendoUI's document, but can't find anything about the northwind-dash demo or example.
Who knows how to change the style of the Gauge to make it just like the image shows.
yours,
Ivan
You can find the sources for KendoUI + Northwind application in GitHub and you can see a version running in Telerik ASPNET-MVC demo site
But no, it does not look like the capture that they have, not sure if that's an older version of this project or an internal one.
Nevertheless, there is no option for placing that percentage and you have to do it manually playing with HTML and CSS.
What you might do is define the Chart using a DataSource like this:
$("#value").kendoChart({
// Define Data Source, the first element is the value to represent,
// the second is 100 - first element.
dataSource: {
data: [
{ "value": 63 },
// The remaining part of the Chart is transparent so we actually
// only see the first value
{ "value": 37, color: "transparent" }
]
},
// Define a DataBound event handler used when we change the data and will
// print the value inside the Chart.
dataBound: function () {
// Get current value and compute percentage.
var percentage = (this.dataSource.at(0).value / 100);
// Convert percentage to text and place it in inside the chart
$("#value-label").text(kendo.toString(percentage, "p0"));
},
// No legend
legend: {
visible: false
},
seriesDefaults: {
// Type of series: Donut
type: "donut",
// Size of the hole of the Donut
holeSize: 60,
// Thickness of the Donut
size: 20
},
series: [
// The value of the series is in "value" field while the color is
// a field called color.
{ field: "value", colorField: "color" }
],
// No tooltip
tooltip: { visible: false }
});
Now, the HTML and CSS definition for placing the label on top of the Chart:
<div class="value-container">
<div id="value"></div>
<div id="value-label" class="overlay"></div>
</div>
A container that includes two DIV the first for the Chart and the second for the label. In order to display the label on top of the Chart, we define a CSS class overlay as follow:
.overlay {
font-size: 24px;
width: 100%;
height: 100%;
position: absolute;
top: 220px;
left: 0;
text-align: center;
}
Where we specify the size of the label and its position from top and centered horizontally (here I hardcoded the vertical position (220px) but you can find for methods for centering a text in a DIV.
You can see it in action here: http://jsfiddle.net/OnaBai/egfrohtx/
Which looks like:
im trying to do a css background animation with the Body tag. Here's the code
body
{
animation-name:mymove;
animation-duration:5s;
animation-direction:alternate;
animation-iteration-count:infinite;
-webkit-animation-name:mymove;
-webkit-animation-duration:5s;
-webkit-animation-direction:alternate;
-webkit-animation-iteration-count:infinite;
}
#keyframes mymove
{
from {background-image:url('Space.png');}
to {background:blue;}
}
#-webkit-keyframes mymove
{
from {background-image:url('Space.png');}
to {background:blue;}
}
But when i use it the background just fades from white to blue. Is there a reason why the image wont display? And by the way the image directory is correct and works if i just set it as the background image.
Background image isn't a property that can be animated - you can't tween the property. Thus you cant use it with keyframe animations. Instead try this code-
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body
{background:url('Space.png');}
#frame
{
position: absolute;
z-index: -1;
top: 0;
left: 0;
height: 100%; /* or the height of webpage is px */
width: 100%; /* or the width of webpage is px */
animation-name:mymove;
animation-duration:5s;
animation-direction:alternate;
animation-iteration-count:infinite;
-webkit-animation-name:mymove;
-webkit-animation-duration:5s;
-webkit-animation-direction:alternate;
-webkit-animation-iteration-count:infinite;
}
#keyframes mymove
{
from {background:transparent;}
to {background:blue;}
}
#-webkit-keyframes mymove
{
from {background:transparent;}
to {background:blue;}
}
</style>
</head>
<body>
<div id="frame">
<--- Webpage contents --->
</div>
</body>
</html>
I am working on autocompleter box based on Scriptaculous Ajax.Autocompleter.
It works in general, but I need to have list of choices wider (see image -- green line -- 320px) then input box (see image -- red line -- 155px).
My first try was to set various width with CSS classes (like above), but it didn't work -- list of choices became as wide as input box.
According to Firebug width defined by my CSS class was overwritten by width set by element.style CSS class, which seems to be defined by Ajax.Autocompleter.
My second try was to set width for list of choices after creating Ajax.Autocompleter
$("isearch_choices").setStyle({width: "320px"});
but it didn't work too :(.
No more ideas :(.
How to set different width for list of choices for Scriptaculous Ajax.Autocompleter?
Below there is complete example of code:
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.2/scriptaculous.js"></script>
<style>
input.iSearchInput {
width: 155px;
height: 26px;
margin-top: 7px;
line-height: 20px;
}
div.iSearchChoices {
position:absolute;
background-color:white;
border:1px solid #888;
margin:0;
padding:0;
width: 320px;
}
div.iSearchChoices ul {
list-style-type:none;
margin:0;
padding:0;
}
div.iSearchChoices ul li.selected { background-color: #ffb;}
div.iSearchChoices ul li {
list-style-type:none;
display:block;
margin:0;
padding:2px;
height:32px;
cursor:pointer;
border-bottom: 1px dotted #929292;
}
</style>
</head>
<body>
<input type="text" maxlength="255" class="input iSearchInput" name="isearch_value" id="isearch" value="Search" onfocus="this.select()">
<br>
<div id='isearch_choices' class='iSearchChoices'></div>
</script>
</body>
<script>
function iSearchGetSelectedId(text, li) {
console.log([text, li.innerHTML].join("\n"));
document.location.href = li.getAttribute("url");
}
document.observe('dom:loaded', function() {
try {
new Ajax.Autocompleter("isearch", "isearch_choices", "/url", {
paramName: "phrase",
minChars: 1,
afterUpdateElement : iSearchGetSelectedId
});
}
catch (ex) {
alert(ex);
}
$("isearch_choices").setStyle({width: "320px"});
});
</script>
</html>
And link to image with its result.
The width is reset in the autocompletion list automatically by the Base.Autocompleter implementation. The Base.Autocompleter will set the list to be the same width as the search input field. There are a couple of ways to go around this:
1. Patch Autocompleter.Base by yourself
You can patch the Autocompleter.Base script by yourself as described by this post. For script.aculo.us version 1.8.1 in controls.js at line 68 you have the following:
Position.clone(element, update, {
setHeight: false,
offsetTop: element.offsetHeight
});
Add a setWidth: false into that options object like this:
Position.clone(element, update, {
setWidth: false,
setHeight: false,
offsetTop: element.offsetHeight
});
2. Extend very your Autocompleter
The example below is for extending the Ajax.Autocompleter. The idea is to replace the onShow function that the base class will call in order to show the autocompleter and resize it with Position.clone().
/**
* Extension of Ajax.Autocompleter that disables width reset.
* #class
*/
var MyAutocompleter = Class.create(Ajax.Autocompleter, {
/**
* #constructor
* #param $super reference to the base class (provided by prototype.js)
* #param id_for_search the id for the search input element
* #param id_for_list the id for autocompletion list element
* #param url the ajax url to be used
*/
initialize: function($super, id_for_search, id_for_list, url) {
$super(id_for_search, id_for_list, url, {
onShow: function(element, update) {
if(!update.style.position || update.style.position=='absolute') {
update.style.position = 'absolute';
Position.clone(element, update, {
setHeight: false,
setWidth: false,
offsetTop: element.offsetHeight
});
}
Effect.Appear(update,{duration:0.15});
}
});
}
});
And you create it as usual with new just as with the other Autocompleter classes.
document.observe("dom:loaded", function() {
new MyAutocompleter('search', 'autoList', 'url/for/ajaxcall');
});
The benefit of the latter is that you can update the script.aculo.us version without repatching the files, and you can continue to overload and extend the Autocompleter to your liking (given you know how the base class behaves).
Seems to be fixed. I modified CSS stylesheets and seems (visually) to work:
Removed border from DIV element and moved it UL element.
Add width for UL element.
Here are my changes:
div.iSearchChoices {
position:absolute;
background-color:white;
/* border:1px solid #888; */
margin:0;
padding:0;
/* width: 320px; */
}
div.iSearchChoices ul {
list-style-type:none;
margin:0;
padding:0;
/* moved from div.iSearchChoices class */
border:1px solid #888;
width: 320px;
}