I have a HTML with a lot of divs. I have already generated divs that look like this.
static HTML (not dynamically generated) example of desired result using renderer2
<div class="time-rowss clearfixx" #timerowss>
<div><mat-icon>today</mat-icon> date </div>
</div>
<div class="time-rows clearfix" #timerows>
<div><mat-icon>brightness_3</mat-icon>00:00</div>
<div><mat-icon>brightness_3</mat-icon>01:00</div>
<div><mat-icon>brightness_3</mat-icon>02:00</div>
</div>
I want to achieve the same but dynamically generating the divs.
What I have done so far is add dynamically times and dates.
Here is my code:
for (let j = this.requestVehicle.startDateTime.getDate(); j < this.requestVehicle.endDateTime.getDate(); j++) {
const newTime = new Date(time.getTime() + 24 * 3600 * 1000);
time = newTime;
const date = this.renderer.createElement('div');
this.renderer.appendChild(date, this.renderer.createText(newTime.getDate() + '/' + newTime.getMonth() + '/' + newTime.getFullYear()));
this.renderer.appendChild(this.d7.nativeElement, date);
for (let i = 0; i < 24; i++) {
const b = this.renderer.createElement('div');
const icon = this.renderer.createElement('mat-icon');
if (i < 7 || i > 18) {
this.renderer.setAttribute(icon, 'svgIcon', '"brightness_3"');
} else {
this.renderer.setProperty(icon, 'svgIcon', '"wb_sunny"');
}
let text;
if (i >= 10) {
text = ' ' + i;
} else {
text = '0' + i;
}
this.renderer.appendChild(b, icon);
this.renderer.appendChild(b, this.renderer.createText(text + ':00'));
this.renderer.appendChild(this.d3.nativeElement, b);
}
}
I have tried several options:
this.renderer.setProperty(icon, 'svgIcon', '"wb_sunny"');
this.renderer.setProperty(icon, 'svgIcon', 'wb_sunny');
this.renderer.setAttribute(icon, 'svgIcon', '"brightness_3"');
this.renderer.setAttribute(icon, 'svgIcon', 'brightness_3');
this.renderer.appendChild(icon, this.renderer.createText('brightness'));
this.renderer.appendChild(icon, 'brightness_3');
none of these options work. I also tried iconName instead of svgIcon.
how should I add iconName or svgIcon with renderer2?
I figured it out. I what I was noticed when I tried to add mat-icon value with renderer createText. It was adding it correctly. The problem was that the IconName was appearing in html as name not as an icon. So I realized the css was missing. I looked into the dev tools and inspected the divs and mat-icons. I found out that they were missing classes.
So I added the classes manually.
In short
you need to create mat-icon element.
const dateIcon = this.renderer.createElement('mat-icon');
add value using createText.
this.renderer.appendChild(dateIcon, this.renderer.createText('today'));
give classes for css styling.
this.renderer.addClass(dateIcon, 'mat-icon');
this.renderer.addClass(dateIcon, 'material-icons');
Full code if curious. -->
for (let j = this.requestVehicle.startDateTime.getDate(); j < this.requestVehicle.endDateTime.getDate(); j++) {
const newTime = new Date(time.getTime() + 24 * 3600 * 1000);
time = newTime;
const date = this.renderer.createElement('div');
const dateIcon = this.renderer.createElement('mat-icon');
this.renderer.appendChild(dateIcon, this.renderer.createText('today'));
this.renderer.addClass(dateIcon, 'mat-icon');
this.renderer.addClass(dateIcon, 'material-icons');
this.renderer.appendChild(date, dateIcon);
this.renderer.appendChild(date, this.renderer.createText(newTime.getDate() + '/' + newTime.getMonth() + '/' + newTime.getFullYear()));
this.renderer.appendChild(this.d7.nativeElement, date);
for (let i = 0; i < 24; i++) {
const b = this.renderer.createElement('div');
const icon = this.renderer.createElement('mat-icon');
if (i < 7 || i > 18) {
this.renderer.appendChild(icon, this.renderer.createText('brightness_3'));
} else {
this.renderer.appendChild(icon, this.renderer.createText('wb_sunny'));
}
let text;
if (i >= 10) {
text = ' ' + i;
} else {
text = '0' + i;
}
this.renderer.appendChild(b, icon);
this.renderer.addClass(icon, 'mat-icon');
this.renderer.addClass(icon, 'material-icons');
this.renderer.appendChild(b, this.renderer.createText(text + ':00'));
this.renderer.appendChild(this.d3.nativeElement, b);
}
}
Related
Hello everyone I am a junior web dev. and I've been stuck on this task for weeks now :[]. I need an input field that will show default text coming from the backend. Restrictions I need for this input field are: The input line should break after reaching 30 characters and some parts of default text should be noneditable.
//Here's the closest solution that I came up
import React, { useEffect } from "react";
import styles from "./TextEditor.module.css";
const TextEditor = (props) => {
var limit = 80000; //height limit
let defaultvalueArr = props.data.variables.map((vari, index) => {
return vari.texts.map((p, index) => {
return p;
});
});
useEffect(() => {
const box = document.getElementById(props.title);
var charlimit = 30; // char limit per line
var space;
box.onkeyup = function () {
var lines = box.value.split("\n");
console.log(box.value, "box.value");
for (var i = 0; i < lines.length; i++) {
if (lines[i].length <= charlimit) continue;
var j = 0;
space = charlimit;
while (j++ <= charlimit) {
if (lines[i].charAt(j) === " ") space = j;
}
lines[i + 1] = lines[i].substring(space + 1) + (lines[i + 1] || "");
lines[i] = lines[i].substring(0, space);
}
box.value = lines.slice(0, 500).join("\n");
};
box.oninput = function() {
box.style.height = "";
box.style.height = Math.min(box.scrollHeight, limit) + "px";
};
box.value = defaultvalueArr.join("\n");
}, []);
return (
<div className={styles.TextEditor}>
<textarea
id={props.title}
className={styles.TextArea}
// rows={defaultvalueArr[0].length}
></textarea>
</div>
);
};
export default TextEditor;
output
the problem with this solution is that when the number of characters reaches 30 the cursor jumps at the end of text
I'm quite new to programming and using google apps scripts.
I wrote script that splits selected text in text box (google slides) in different text boxes (each line of initial textbox is a separated textbox). Code was just a modification of examples from developers.google.com.
function SelectedTextGrabber() {
var selection = SlidesApp.getActivePresentation().getSelection();
var selectionType = selection.getSelectionType();
var currentPage;
switch (selectionType) {
case SlidesApp.SelectionType.NONE:
Logger.log('Nothing selected');
break;
...
case SlidesApp.SelectionType.TEXT:
var tableCellRange = selection.getTableCellRange();
if (tableCellRange != null) {
var tableCell = tableCellRange.getTableCells()[0];
Logger.log('Selected text is in a table at row ' +
tableCell.getRowIndex() + ', column ' +
tableCell.getColumnIndex());
}
var textRange = selection.getTextRange();
if (textRange.getStartIndex() == textRange.getEndIndex()) {
Logger.log('Text cursor position: ' + textRange.getStartIndex());
} else {
Logger.log('Selection is a text range from: ' + textRange.getStartIndex() + ' to: ' +
textRange.getEndIndex() + ' is selected');
var s1 = textRange.asString();
var s2 = '';
var s3 = [];
for (var i = 0; i < s1.length; i++){
if (s1[i] === '\n' || i === s1.length -1) {
s3.push(s2);
s2='';
} else {
s2 += s1[i];
}
}
// textbox parameteres
var h4 = 0;
var left = 10;
var top = 10;
var textsize = 12;
var standnum = 37;
var width = 2 * textsize + (textsize - textsize % 2) / 2 * standnum;
Logger.log(width);
var slide = SlidesApp.getActivePresentation().getSlides()[1];
for (var i = 0; i < s3.length; i++){
//анализ размера текстового блока
var s4 = s3[i].length;
if (s4 <= standnum) {
h4 = textsize * 2;
} else {
h4 = textsize * 2 + (s4 - s4 % standnum) / standnum * textsize;
}
var shape = slide.insertShape(SlidesApp.ShapeType.TEXT_BOX, left, top, width, h4);
var textRange = shape.getText();
textRange.setText(s3[i]);
textRange.getTextStyle().setFontSize(textsize);
top += h4;
if (top > 370) {
top = 10;
left += width;
}
}
}
break;
case SlidesApp.SelectionType.TABLE_CELL:
var tableCells = selection.getTableCellRange().getTableCells();
var table = tableCells[0].getParentTable();
Logger.log('There are ' + tableCells.length + ' table cells selected.');
break;
case SlidesApp.SelectionType.PAGE:
var pages = selection.getPageRange().getPages();
Logger.log('There are ' + pages.length + ' pages selected.');
break;
default:
break;
}
}
It worked just fine, but when I renamed script and presentation, I get the TypeError: Cannot call method "getSelectionType" of null. (line 4, file "Code").
After 30 minutes of waiting this script started working again without errors.
I thought it may happen because it takes time to make some changes in google servers.
But when I modified initial text in text box to be splitted the script gave me the same result as I didn't change initial text (the result is separated lines in textboxes but for initial text).
Do u have any idea what should I do to fix it?
I am trying to implement a control, using either
<input type="time"/>
or just with
<input type="text"/>
and implement a duration picker control which can have hours format more than 24, something like 000:00:00 or hhh:mm:ss, and no am/pm option ( The default input type for time has formats in am/pm format, which is not useful in my case).
The requirement is to be able to increase decrease the duration using up and down keys much like the default input type time of HTML.
Is there any native HTML, angular, or material component for this?
Or is there a way to achieve this using regular expression/patterns or something?
One way I can think of is to write your custom control (as also mentioned by #Allabakash). For Native HTML, The control can be something like this:
window.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('[my-duration-picker]').forEach(picker => {
//prevent unsupported keys
const acceptedKeys = ['Backspace', 'ArrowLeft', 'ArrowRight', 'ArrowDown', 'ArrowUp'];
const selectFocus = event => {
//get cursor position and select nearest block;
const cursorPosition = event.target.selectionStart;
"000:00:00" //this is the format used to determine cursor location
const hourMarker = event.target.value.indexOf(":");
const minuteMarker = event.target.value.lastIndexOf(":");
if (hourMarker < 0 || minuteMarker < 0) {
//something wrong with the format. just return;
return;
}
if (cursorPosition < hourMarker) {
event.target.selectionStart = 0; //hours mode
event.target.selectionEnd = hourMarker;
}
if (cursorPosition > hourMarker && cursorPosition < minuteMarker) {
event.target.selectionStart = hourMarker + 1; //minutes mode
event.target.selectionEnd = minuteMarker;
}
if (cursorPosition > minuteMarker) {
event.target.selectionStart = minuteMarker + 1; //seconds mode
event.target.selectionEnd = minuteMarker + 3;
}
}
const insertFormatted = (inputBox, secondsValue) => {
let hours = Math.floor(secondsValue / 3600);
secondsValue %= 3600;
let minutes = Math.floor(secondsValue / 60);
let seconds = secondsValue % 60;
minutes = String(minutes).padStart(2, "0");
hours = String(hours).padStart(3, "0");
seconds = String(seconds).padStart(2, "0");
inputBox.value = hours + ":" + minutes + ":" + seconds;
}
const increaseValue = inputBox => {
const rawValue = inputBox.value;
sectioned = rawValue.split(':');
let secondsValue = 0
if (sectioned.length === 3) {
secondsValue = Number(sectioned[2]) + Number(sectioned[1] * 60) + Number(sectioned[0] * 60 * 60);
}
secondsValue += 1;
insertFormatted(inputBox, secondsValue);
}
const decreaseValue = inputBox => {
const rawValue = inputBox.value;
sectioned = rawValue.split(':');
let secondsValue = 0
if (sectioned.length === 3) {
secondsValue = Number(sectioned[2]) + Number(sectioned[1] * 60) + Number(sectioned[0] * 60 * 60);
}
secondsValue -= 1;
if (secondsValue < 0) {
secondsValue = 0;
}
insertFormatted(inputBox, secondsValue);
}
const validateInput = event => {
sectioned = event.target.value.split(':');
if (sectioned.length !== 3) {
event.target.value = "000:00:00"; //fallback to default
return;
}
if (isNaN(sectioned[0])) {
sectioned[0] = "000";
}
if (isNaN(sectioned[1]) || sectioned[1] < 0) {
sectioned[1] = "00";
}
if (sectioned[1] > 59 || sectioned[1].length > 2) {
sectioned[1] = "59";
}
if (isNaN(sectioned[2]) || sectioned[2] < 0) {
sectioned[2] = "00";
}
if (sectioned[2] > 59 || sectioned[2].length > 2) {
sectioned[2] = "59";
}
event.target.value = sectioned.join(":");
}
const controlsDiv = document.createElement("div");
const scrollUpBtn = document.createElement("button");
const scrollDownBtn = document.createElement("button");
scrollDownBtn.textContent = " - ";
scrollUpBtn.textContent = " + ";
scrollUpBtn.addEventListener('click', (e) => {
increaseValue(picker);
});
scrollDownBtn.addEventListener('click', (e) => {
decreaseValue(picker);
});
picker.parentNode.insertBefore(scrollDownBtn, picker.nextSibling);
picker.parentNode.insertBefore(scrollUpBtn, picker.nextSibling);
picker.value = "000:00:00";
picker.style.textAlign = "right"; //align the values to the right (optional)
picker.addEventListener('keydown', event => {
//use arrow keys to increase value;
if (event.key == 'ArrowDown' || event.key == 'ArrowUp') {
if(event.key == 'ArrowDown'){
decreaseValue(event.target);
}
if(event.key == 'ArrowUp'){
increaseValue(event.target);
}
event.preventDefault(); //prevent default
}
if (isNaN(event.key) && !acceptedKeys.includes(event.key)) {
event.preventDefault(); //prevent default
return false;
}
});
picker.addEventListener('focus', selectFocus); //selects a block of hours, minutes etc
picker.addEventListener('click', selectFocus); //selects a block of hours, minutes etc
picker.addEventListener('change', validateInput);
picker.addEventListener('blur', validateInput);
picker.addEventListener('keyup', validateInput);
});
});
<input type="text" my-duration-picker></input>
Tested and working on Google Chrome 78. I will do a Angular version later.
For the Angular version, you can write your own custom Directive and just import it to your app-module-ts declarations. See this example on stackblitz:
App Demo: https://angular-xbkeoc.stackblitz.io
Code: https://stackblitz.com/edit/angular-xbkeoc
UPDATE: I developed and improved this concept over time. You can checkout the picker here 👉 https://nadchif.github.io/html-duration-picker.js/
checkout this solution , https://github.com/FrancescoBorzi/ngx-duration-picker. which provides options you are looking for.
here is the demo - https://embed.plnkr.co/1dAIGrGqbcfrNVqs4WwW/.
Demo shows Y:M:W:D:H:M:S format. you can hide the parameters using flags defined in docs.
Since you are looking for duration picker with single input, creating your own component will be handy.
You can consider the concepts formatters and parsers.
checkout this topics which helps you in achieving that.
https://netbasal.com/angular-formatters-and-parsers-8388e2599a0e
https://stackoverflow.com/questions/39457941/parsers-and-formatters-in-angular2
here is the updated sample demo - https://stackblitz.com/edit/hello-angular-6-yuvffz
you can implement the increase/decrease functionalities using keyup/keydown event functions.
handle(event) {
let value = event.target.value; //hhh:mm:ss
if(event.key === 'ArrowUp') {
console.log('increase');
} else if (event.key === 'ArrowDown') {
console.log('decrease');
} else {
//dont allow user from entering more than two digits in seconds
}
}
Validations you need to consider ::
- If user enters wrong input, show error message / block from entering anything other than numbers
- allowing only unit specific digits - (Ex :: for hr - 3 digits, mm - 2 digits etc as per your requirement)
To do something more interesting or make it look like interactive you can use the
flipclock.js which is very cool in looking and to work with it is also feasible.
Here is the link :-
http://flipclockjs.com/
You can try with number as type :
<input type="min" min="0" max="60">
demo :
https://stackblitz.com/edit/angular-nz9hrn
For some reason my blog homepage starts loading half way down the page, and then slides to the top when the featured images above it have loaded. I'm not sure why it's doing this. Does anyone know how I can change the order so it loads from the first element at the top of the page?
Link to blog is: https://futuremag-demo.blogspot.com
The featured post section requires JavaScript to render but currently that Javascript is executed only after all the HTML is loaded on the page (likely to prevent render blocking issues).
Currently <span data-type="recent"></span> is the only HTML present in the HTML/Javascript widget which renders the featured post section. Behind the scenes, when JavaScript detects the above HTML, it makes an Ajax call to the specific Blogger feed (/feeds/posts/default?alt=json-in-script&max-results=4) to get the posts to be shown in that section.
One way to resolve this problem is by moving the JS snippet responsible for rendering this featured section just after the HTML <span data-type="recent"></span> present in the widget. The specific JS snippet responsible for this section is -
<script>
$('.featured .HTML .widget-content').each(function() {
var v = $(this).find("span").attr("data-label"),
box = $(this).find("span").attr("data-type");
if (box.match('recent')) {
$.ajax({
url: "/feeds/posts/default?alt=json-in-script&max-results=4",
type: 'get',
dataType: "jsonp",
success: function(e) {
var u = "";
var h = '<ul>';
for (var i = 0; i < e.feed.entry.length; i++) {
for (var j = 0; j < e.feed.entry[i].link.length; j++) {
if (e.feed.entry[i].link[j].rel == "alternate") {
u = e.feed.entry[i].link[j].href;
break
}
}
var g = e.feed.entry[i].title.$t;
var s = e.feed.entry[i].category[0].term;
var y = e.feed.entry[i].author[0].name.$t;
var d = e.feed.entry[i].published.$t,
t = d.substring(0, 4),
w = d.substring(5, 7),
f = d.substring(8, 10),
r = month_format[parseInt(w, 10)] + ' ' + f + ', ' + t;
var c = e.feed.entry[i].content.$t;
var $c = $('<div>').html(c);
if (c.indexOf("//www.youtube.com/embed/") > -1) {
var p = e.feed.entry[i].media$thumbnail.url;
var k = p
} else if (c.indexOf("<img") > -1) {
var q = $c.find('img:first').attr('src');
var k = q
} else {
var k = no_image
}
h += '<li><div class="featured-inner">' + s + '<a class="rcp-thumb" href="' + u + '" style="background:url(' + k + ') no-repeat center center;background-size: cover"><span class="featured-overlay"/></a><div class="post-panel"><h3 class="rcp-title">' + g + '</h3><div class="featured-meta"><span class="featured-author idel">' + y + '</span><span class="featured-date">' + r + '</span></div></div></div></li>'
}
h += '</ul>';
$(".featured .HTML .widget-content").each(function() {
$(this).html(h);
$(this).find('.rcp-thumb').each(function() {
$(this).attr('style', function(i, src) {
return src.replace('/default.jpg', '/mqdefault.jpg')
}).attr('style', function(i, src) {
return src.replace('s72-c', 's1600')
})
})
})
}
})
} else if (box.match('label')) {
$.ajax({
url: "/feeds/posts/default/-/" + v + "?alt=json-in-script&max-results=4",
type: 'get',
dataType: "jsonp",
success: function(e) {
var u = "";
var h = '<ul>';
for (var i = 0; i < e.feed.entry.length; i++) {
for (var j = 0; j < e.feed.entry[i].link.length; j++) {
if (e.feed.entry[i].link[j].rel == "alternate") {
u = e.feed.entry[i].link[j].href;
break
}
}
var g = e.feed.entry[i].title.$t;
var s = e.feed.entry[i].category[0].term;
var y = e.feed.entry[i].author[0].name.$t;
var d = e.feed.entry[i].published.$t,
t = d.substring(0, 4),
w = d.substring(5, 7),
f = d.substring(8, 10),
r = month_format[parseInt(w, 10)] + ' ' + f + ', ' + t;
var c = e.feed.entry[i].content.$t;
var $c = $('<div>').html(c);
if (c.indexOf("//www.youtube.com/embed/") > -1) {
var p = e.feed.entry[i].media$thumbnail.url;
var k = p
} else if (c.indexOf("<img") > -1) {
var q = $c.find('img:first').attr('src');
var k = q
} else {
var k = no_image
}
h += '<li><div class="featured-inner">' + s + '<a class="rcp-thumb" href="' + u + '" style="background:url(' + k + ') no-repeat center center;background-size: cover"><span class="featured-overlay"/></a><div class="post-panel"><h3 class="rcp-title">' + g + '</h3><div class="featured-meta"><span class="featured-author idel">' + y + '</span><span class="featured-date">' + r + '</span></div></div></div></li>'
}
h += '</ul>';
$(".featured .HTML .widget-content").each(function() {
$(this).html(h);
$(this).find('.rcp-thumb').each(function() {
$(this).attr('style', function(i, src) {
return src.replace('/default.jpg', '/mqdefault.jpg')
}).attr('style', function(i, src) {
return src.replace('s72-c', 's1600')
})
})
})
}
})
}
});
After analyzing the website, it's found that the said images have big sizes which makes them load slowly.
They can be compressed using a technique called lossless compression to minimize the size without distorting the resolution of the image.
Compress your images using (for example): https://compressjpeg.com/
and test again.
I'm trying to dynamically populate a select and call a truncate function in the loop... like below. I want to send the option text down to the function, truncate it if it's longer than 20 chars and send it back before it gets added to the option and appended to the select.
$(function() {
for (var i = 0; i < response.option.length; i++) {
var truncatedText = truncate();
var text = response.option[i].name;
truncate(text);
$("select").append("<option>" + truncatedText.text + "</option>");
}
});
function truncate(text) {
var textLength = text.length;
if (textLength > 20) {
text = text.substr(0, 20) + '...';
}
return text;
}
After jsfiddling for a while I landed on a working solution. Is there a more elegant way to do this?
https://jsfiddle.net/kirkbross/pcb0a3Lg/9/
var namesList = ['johnathan', 'tim', 'greggory', 'ashton', 'elizabeth'];
$(function() {
for (var i = 0; i < 5; i++) {
var name = namesList[i];
$('#names').append('<option>' + name + '</option>');
}
var selected_option = $('#names').find('option:selected').val();
var truncated = truncate(selected_option);
$('option:selected').text(truncated.new);
$('#names').change(function(){
var selected_option = $(this).find('option:selected').val();
var truncated = truncate(selected_option);
$('option:selected').text(truncated.new);
});
});
function truncate(selected_option) {
var nameLength = selected_option.length
if (nameLength > 4) {
selected_option = selected_option.substr(0, 4) + '...';
}
return {new: selected_option}
}