Related
This is the desired string rendered
Suppose you have an Array of Objects representing different attributes of the string:
[{
"isBold": false,
"isCode": true,
"isItalic": false,
"isStrikethrough": false,
"range": [
42,
8
]
},
{
"isBold": false,
"isCode": false,
"isItalic": true,
"isStrikethrough": false,
"range": [
19,
10
]
},
{
"isBold": false,
"isCode": false,
"isItalic": false,
"isStrikethrough": true,
"range": [
46,
11
]
},
{
"isBold": true,
"isCode": false,
"isItalic": false,
"isStrikethrough": false,
"range": [
27,
12
],
"nested": [
{
"isBold": false,
"isCode": true,
"isItalic": false,
"isStrikethrough": false,
"range": [
28,
7
]
}
]
},
{
"highlightColor": "#fff100 #fff432",
"isBold": false,
"isCode": false,
"isItalic": false,
"isStrikethrough": false,
"range": [
1,
15
]
},
{
"isBold": false,
"isCode": false,
"isItalic": false,
"isStrikethrough": false,
"linkURL": "www.google.com",
"range": [
5,
29
],
"nested": [
{
"isBold": false,
"isCode": false,
"isItalic": true,
"isStrikethrough": false,
"range": [
19,
10
]
}
]
}]
Any suggestions on how you would go about rendering something like this?
The nested ranges and different attributes has me completely stuck... I tried going through the string char by char and collecting which attributes belong to each char – I'm close, but I'm not rendering links correctly and the code attributes get split up instead of wrapping their entire text:
Render of my attempt to parse
function renderTextAttributes(content, textAttributes)
let attributes = []
let currentAttributes, start, end
for (let i = 0; i < content.length; i++) {
if (i === content.length - 1) {
attributes.push({
attributes: currentAttributes,
content:
currentAttributes.length > 0
? `<span class="${currentAttributes.join(' ')}">${content.slice(
Math.max(0, start)
)}</span>`
: content.slice(Math.max(0, start)),
end,
start
})
} else if (currentAttributes) {
let charAttributes = []
for (const attribute of textAttributes) {
if (
i >= attribute.range[0] &&
i <= attribute.range[0] + attribute.range[1]
) {
charAttributes = [
...new Set([...charAttributes, ...findAttributes(attribute)])
]
}
}
if (
JSON.stringify(charAttributes) === JSON.stringify(currentAttributes)
) {
end = i
} else {
attributes.push({
attributes: currentAttributes,
content:
currentAttributes.length > 0
? `<span class="${currentAttributes.join(
' '
)}">${content.substring(start, end)}</span>`
: content.substring(start, end),
end,
start
})
currentAttributes = null
i--
}
} else {
currentAttributes = []
start = i
end = i + 1
for (const attribute of textAttributes) {
if (
i >= attribute.range[0] &&
i <= attribute.range[0] + attribute.range[1]
) {
currentAttributes = [
...new Set([...currentAttributes, ...findAttributes(attribute)])
]
}
}
}
}
let newContent = ''
for (const attribute of attributes) {
newContent += attribute.content
}
return newContent
}
I did something that may be what you are looking for.
**(update) Sometimes when inserting the tags with function replaceWithTags we get some closing tags (e.g.: </code> between <del> tag) in the middle of the string and that mess up the final HTML, the string result may be ok, but when printing it on the DOM because HTML doesn't throw errors, instead, it tries to add the missing/closing tags, so, to solve that, I'm adding a validation where it checks if we have a single closing tag and we add the current opening tag right after the found single closing tag, that way if we have for example:
for Strikethrough case:
'<del> be </code>rendere</del>'
we will add a <del> tag after </code> like this:
<del> be </code><del>rendere</del>
as you can see it is still not a valid HTML code, but the browser will take care of it, adding the missing code:
Final HTML screenshot
let attributes = [
{
"isBold": false,
"isCode": true,
"isItalic": false,
"isStrikethrough": false,
"range": [
42,
8
]
},
{
"isBold": false,
"isCode": false,
"isItalic": true,
"isStrikethrough": false,
"range": [
19,
10
]
},
{
"isBold": false,
"isCode": false,
"isItalic": false,
"isStrikethrough": true,
"range": [
46,
11
]
},
{
"isBold": true,
"isCode": false,
"isItalic": false,
"isStrikethrough": false,
"range": [
27,
12
],
"nested": [
{
"isBold": false,
"isCode": true,
"isItalic": false,
"isStrikethrough": false,
"range": [
28,
7
]
}
]
},
{
"highlightColor": "#fff100 #fff432",
"isBold": false,
"isCode": false,
"isItalic": false,
"isStrikethrough": false,
"range": [
1,
15
]
},
{
"isBold": false,
"isCode": false,
"isItalic": false,
"isStrikethrough": false,
"linkURL": "www.google.com",
"range": [
5,
29
],
"nested": [
{
"isBold": false,
"isCode": false,
"isItalic": true,
"isStrikethrough": false,
"range": [
19,
10
]
}
]
}
];
const replaceWithTags = (content, ar, start, end, tag, attr) => {
const startTag = `<${tag}${attr ? ' ' + attr : ''}>`;
let str = content.join('');
let match = str.match(/<\/.*?>/g);
if (match && match.length === 1) {
let idx = content.findIndex(x => x.match(/<\/.*?>/g))
content[idx] = content[idx] + startTag;
}
ar[start] = startTag + content.slice(0, 1);
ar[end] = content.slice(content.length - 1, content.length) + `</${tag}>`;
ar = [...ar.slice(0, start + 1),
...content.slice(1, content.length - 1),
...ar.slice(end, ar.length)];
return ar;
}
const tags = {
isCode: (content, ar, start, end) => {
return replaceWithTags(content, ar, start, end, 'code');
},
isBold: (content, ar, start, end)=> {
return replaceWithTags(content, ar, start, end, 'b');
},
isStrikethrough: (content, ar, start, end) => {
return replaceWithTags(content, ar, start, end, 'del');
},
isItalic: (content, ar, start, end) => {
return replaceWithTags(content, ar, start, end, 'span', 'style="font-style: italic;"');
},
linkURL: (content, ar, start, end, link) => {
return replaceWithTags(content, ar, start, end, 'a', `href="${link}"`);
},
highlightColor: (content, ar, start, end, color) => {
return replaceWithTags(content, ar, start, end, 'span', `style="background: ${color};"`);
},
};
const renderTextAttributes = (content, textAttributes) => {
let arr = content.split('');
let checlAttrs = (attrs) => {
for (let itm of attrs) {
let start = itm.range[0];
let end = itm.range[1];
const slice = arr.slice(start, (start + (end)));
for (let prop in itm) {
if (typeof itm[prop] === 'boolean' && itm[prop]) {
arr = tags[prop](slice, arr, start, (start + (end - 1)));
} else if (prop === 'linkURL') {
arr = tags[prop](slice, arr, start, (start + (end - 1)), itm[prop]);
} else if (prop === 'highlightColor') {
arr = tags[prop](slice, arr, start, (start + (end - 1)), itm[prop].split(' ')[1]);
} else if (prop === 'nested') {
arr = checlAttrs(itm[prop]);
}
}
}
return arr;
}
arr = checlAttrs(textAttributes);
return arr.join('');
};
const text = 'String with different attributes and a link to be rendered.';
document.write(renderTextAttributes(text, attributes));
console.log(renderTextAttributes(text, attributes));
How to create a new spreadsheet within a specific folder?
I am using the following function to take data from one sheet, validate it and save it in another sheet if it isn't already there.
Additionally I want to create a completely new Spreadsheet in a specific folder, use the data from B8 as its name and fill it with the same data. How do I do that?
function validateEntry()
{
var myGooglSheet = SpreadsheetApp.getActiveSpreadsheet();
var shUserForm = myGooglSheet.getSheetByName("Fahrzeug anlegen");
var ui = SpreadsheetApp.getUi();
shUserForm.getRange("B4").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("B8").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("B11").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("B14").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("B17").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("B20").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("D8").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("D11").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("D14").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("D17").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("D20").setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
if(shUserForm.getRange("B8").isBlank() == true)
{
ui.alert("Fahrzeugnummer eingeben!");
shUserForm.getRange("B8").activate();
shUserForm.getRange("B8").setBackground('#FF0000');
return false;
}
else if(shUserForm.getRange("B11").isBlank() == true)
{
ui.alert("Fahrgestellnummer eingeben!");
shUserForm.getRange("B11").activate();
shUserForm.getRange("B11").setBackground('#FF0000');
return false;
}
else if(shUserForm.getRange("B14").isBlank() == true)
{
ui.alert("Marke eingeben!");
shUserForm.getRange("B14").activate();
shUserForm.getRange("B14").setBackground('#FF0000');
return false;
}
else if(shUserForm.getRange("B17").isBlank() == true)
{
ui.alert("Modell eingeben!");
shUserForm.getRange("B17").activate();
shUserForm.getRange("B17").setBackground('#FF0000');
return false;
}
else if(shUserForm.getRange("B20").isBlank() == true)
{
ui.alert("Kennzeichen eingeben!");
shUserForm.getRange("B20").activate();
shUserForm.getRange("B20").setBackground('#FF0000');
return false;
}
else if(shUserForm.getRange("D8").isBlank() == true)
{
ui.alert("EK-Datum eingeben!");
shUserForm.getRange("D8").activate();
shUserForm.getRange("D8").setBackground('#FF0000');
return false;
}
else if(shUserForm.getRange("D11").isBlank() == true)
{
ui.alert("EK-Preis eingeben!");
shUserForm.getRange("D11").activate();
shUserForm.getRange("D11").setBackground('#FF0000');
return false;
}
else if(shUserForm.getRange("D14").isBlank() == true)
{
ui.alert("Steuer eingeben!");
shUserForm.getRange("D14").activate();
shUserForm.getRange("D14").setBackground('#FF0000');
return false;
}
else if(shUserForm.getRange("D17").isBlank() == true)
{
ui.alert("Inzahlung eingeben!");
shUserForm.getRange("D17").activate();
shUserForm.getRange("D17").setBackground('#FF0000');
return false;
}
else if(shUserForm.getRange("D20").isBlank() == true)
{
ui.alert("Art eingeben!");
shUserForm.getRange("D20").activate();
shUserForm.getRange("D20").setBackground('#FF0000');
return false;
}
return true;
}
function submitData()
{
var myGooglSheet = SpreadsheetApp.getActiveSpreadsheet();
var shUserForm = myGooglSheet.getSheetByName("Fahrzeug anlegen");
var datasheet = myGooglSheet.getSheetByName("Masterliste");
var str = shUserForm.getRange("B8").getValue();
var values = datasheet.getDataRange().getValues();
var valuesFound = false;
for (var i = 0; i < values.length; i++)
{
var rowValue = values[i];
if (rowValue[0] == str)
{
shUserForm.getRange("B8").setValue(rowValue[0]).setFontFamily("Courier New").setFontSize(12).setHorizontalAlignment('left');
shUserForm.getRange("B11").setValue(rowValue[1]).setFontFamily("Courier New").setFontSize(12).setHorizontalAlignment('left');
shUserForm.getRange("B14").setValue(rowValue[2]).setFontFamily("Courier New").setFontSize(12).setHorizontalAlignment('left');
shUserForm.getRange("B17").setValue(rowValue[3]).setFontFamily("Courier New").setFontSize(12).setHorizontalAlignment('left');
shUserForm.getRange("B20").setValue(rowValue[4]).setFontFamily("Courier New").setFontSize(12).setHorizontalAlignment('left');
shUserForm.getRange("D8").setValue(rowValue[5]).setFontFamily("Courier New").setFontSize(12).setHorizontalAlignment('left');
shUserForm.getRange("D11").setValue(rowValue[7]).setFontFamily("Courier New").setFontSize(12).setHorizontalAlignment('left');
shUserForm.getRange("D14").setValue(rowValue[8]).setFontFamily("Courier New").setFontSize(12).setHorizontalAlignment('left');
shUserForm.getRange("D17").setValue(rowValue[9]).setFontFamily("Courier New").setFontSize(12).setHorizontalAlignment('left');
shUserForm.getRange("D20").setValue(rowValue[10]).setFontFamily("Courier New").setFontSize(12).setHorizontalAlignment('left');
var ui = SpreadsheetApp.getUi();
ui.alert("Eintrag schon vorhanden!");
return;
}
}
if(valuesFound == false)
{
var ui = SpreadsheetApp.getUi();
var response = ui.alert("Speichern", 'Daten speichern?',ui.ButtonSet.YES_NO);
if (response == ui.Button.NO)
{
return;
}
if (validateEntry() == true)
{
var blankRow = datasheet.getLastRow()+1; // nächste leere Zeile finden
datasheet.getRange(blankRow, 1).setValue(shUserForm.getRange("B8").getValue());
datasheet.getRange(blankRow, 2).setValue(shUserForm.getRange("B11").getValue());
datasheet.getRange(blankRow, 3).setValue(shUserForm.getRange("B14").getValue());
datasheet.getRange(blankRow, 4).setValue(shUserForm.getRange("B17").getValue());
datasheet.getRange(blankRow, 5).setValue(shUserForm.getRange("B20").getValue());
datasheet.getRange(blankRow, 6).setValue(shUserForm.getRange("D8").getValue());
datasheet.getRange(blankRow, 8).setValue(shUserForm.getRange("D11").getValue());
datasheet.getRange(blankRow, 9).setValue(shUserForm.getRange("D14").getValue());
datasheet.getRange(blankRow, 10).setValue(shUserForm.getRange("D17").getValue());
datasheet.getRange(blankRow, 11).setValue(shUserForm.getRange("D20").getValue());
ui.alert(' "Neues Fahrzeug gespeichert: #'+ shUserForm.getRange("B8").getValue() +'" ');
shUserForm.getRange("B4").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("B8").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("B11").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("B14").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("B17").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("B20").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("D8").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("D11").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("D14").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("D17").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
shUserForm.getRange("D20").clear().setBackground('#FFFFFF').setBorder(true, true, true, true, false, false, '#d9d9d9', SpreadsheetApp.BorderStyle.SOLID);
}
}
else
{
ui.alert("Eintrag schon vorhanden!");
ui.alert("Kein Eintrag gefunden!");
}
}
EDITED: You asked about creating a new spreadsheet in a specific folder, named with data from specific cell.
This function does that. It takes the desired filename as an input, so you can just call it from within one of your other functions and pass the value of cell B8 as the desired name.
function newSpreadsheet(name){
var destination = DriveApp.getFolderById('[FOLDER ID]')
var newFile = SpreadsheetApp.create(name).getId()
destination.addFile(DriveApp.getFileById(newFile))
}
Breakdown
var destination sets the target folder for the new file. You will need to put the desired folder location into the script, which you can get from the URL of the folder.
var newFile creates a new Google Sheet, naming it with the name you pass to this function. It returns the ID of the new file.
destination.addFile moves the new file into the destination folder.
I have the following data structure held in state by my React application.
{"assetNum": "SP234",
"eventDate": "2021-08-15 08:30:00Z",
"manufacturers": {
"part1":"mfg1",
"part2":"mfg2",
"part3":"mfg3"},
"part1":{
"inlet":[true, true, true, true, true],
"outlet":[false, false, false, false, false]}
"part2":{
"inlet":[false, false true, true, true],
"outlet":[false, false, false, false, false]}
"part3":{
"suction":[true, true, true, true, true],
"discharge":[false, false, false, false, false]}
}
On the other end I have a .NET API expecting the following data structure:
{"assetNum": "SP234",
"eventDate": "2021-08-15 08:30:00Z",
"details":[
{"part":"part1",
"label":"inlet",
"manufacturer":"mfg1",
"locations":[true, true, true, true, true]},
{"part":"part1",
"label":"outlet",
"manufacturer":"mfg1",
"locations":[false, false, false, false, false]},
{"part":"part2",
"label":"inlet",
"manufacturer":"mfg2",
"locations":[false, false, true, true, true]},
{"part":"part2",
"label":"outlet",
"manufacturer":"mfg2",
"locations":[false, false, false, false, false]},
{"part":"part3",
"label":"suction",
"manufacturer":"mfg3",
"locations":[true, true, true, true, true},
{"part":"part3",
"label":"fischarge",
"manufacturer":"mfg3",
"locations":[false, false, false, false, false]},
]
}
I'm trying to avoid changing the state on the front-end since a lot of other things already depend on it. I'm also trying to avoid changing the API.
How can I go about transforming one object into the other structure anytime the user clicks "SUBMIT"? I was thinking of creating a class with a constructor and a method to generate the other JSON but that might be an overkill? I'm hoping there's a straightforward way to do this in Javascript that I haven't learned yet.
Thanks to all in advance,
Easily achieved using ...
2 x Object.entries
Array#map
and
Array#flatMap
const input = {
"assetNum": "SP234",
"eventDate": "2021-08-15 08:30:00Z",
"manufacturers": {
"part1": "mfg1",
"part2": "mfg2",
"part3": "mfg3"
},
"part1": {
"inlet": [true, true, true, true, true],
"outlet": [false, false, false, false, false]
},
"part2": {
"inlet": [false, false, true, true, true],
"outlet": [false, false, false, false, false]
},
"part3": {
"suction": [true, true, true, true, true],
"discharge": [false, false, false, false, false]
}
};
const output = {
assetNum: input.assetNum,
eventDate: input.eventDate,
details: Object.entries(input.manufacturers).flatMap(([part, manufacturer]) => Object.entries(input[part]).map(([label, locations]) => ({ part, label, manufacturer, locations})))
};
console.log(output);
You can use Object.keys and map your objects
code:
const data = {
"assetNum": "SP234",
"eventDate": "2021-08-15 08:30:00Z",
"manufacturers": {
"part1": "mfg1",
"part2": "mfg2",
"part3": "mfg3"
},
"part1": {
"inlet": [true, true, true, true, true],
"outlet": [false, false, false, false, false]
},
"part2": {
"inlet": [false, false, true, true, true],
"outlet": [false, false, false, false, false]
},
"part3": {
"suction": [true, true, true, true, true],
"discharge": [false, false, false, false, false]
}
};
let model = {
"assetNum": data.assetNum,
"eventDate": data.eventDate,
"details": []
}
Object.keys(data.manufacturers).forEach(key=>{
const row = data[key]
Object.keys(row).forEach(innerKey=>{
let item = {
"manufacturer":data.manufacturers[key],
"part": key,
"label": innerKey,
"locations": row[innerKey]
}
model.details.push(item)
})
})
console.log(model)
Hi i 'm trying to hide all nodes in jstree when the search has no results but I am getting the following error.
Uncaught TypeError: $(...).jstree(...).hide_all is not a function
Here is the code I use:
$("#divtreeComponentes").jstree("destroy");
$("#divtreeComponentes").jstree({
"core": {
// so that create works
"check_callback": true,
"data": data2
},
"checkbox": {
"keep_selected_style": false
},
"search": {
"show_only_matches": true,//filtering
"show_only_matches_children": true
},
"types": {
"types": {
"disabled": {
"check_node": false,
"uncheck_node": false
}
}
},
"plugins": ["checkbox", "search", "sort"]
}).on('search.jstree', function (nodes, str, res) {
if (str.nodes.length===0) {
$('#divtreeComponentes').jstree(true).hide_all();
}
})
$('#Filtrar_Usuarios').keyup(function(){
$('#divtreeComponentes').jstree(true).show_all();
$('#divtreeComponentes').jstree('search', $(this).val());
});
are any ideas that may be happening ?
thanks for helping!
I solved the problem with this.
$("#divtreeComponentes").jstree({
"core": {
// so that create works
"check_callback": true,
"data": data2
},
"checkbox": {
"keep_selected_style": false
},
"search": {
"show_only_matches": true, //filtering
"show_only_matches_children": true
},
"types": {
"types": {
"disabled": {
"check_node": false,
"uncheck_node": false
}
}
},
"plugins": ["checkbox", "search", "sort"]
}).on('search.jstree', function(nodes, str, res) {
if (str.nodes.length === 0) {
$('#divtreeComponentes').hide();
}
})
$('#Filtrar_Usuarios').keyup(function() {
$('#divtreeComponentes').show();
$('#divtreeComponentes').jstree('search', $(this).val());
});
$('#divtreeComponentes').hide(); Works for me!
Good luck!
I want to disable all keyboard keys in wysihtml5 text editor .
I am using this editor https://github.com/bassjobsen/wysihtml5-image-upload
can anyone help me to do this. I have try "load" event and its works perfectly but i cannot use "onkeypress" or "onkeyup"\
var defaultOptions = $.fn.wysihtml5.defaultOptions = {
"font-styles": false,
"color": false,
"emphasis": false,
"lists": false,
"html": false,
"link": false,
"image": true,
events: {
"load": function() {
console.log('loaded!');
}
},
I think that is the answer below
var defaultOptions = $.fn.wysihtml5.defaultOptions = {
"font-styles": false,
"color": false,
"emphasis": false,
"lists": false,
"html": false,
"link": false,
"image": true,
events: {
"load": function() {
$('.wysihtml5-sandbox').contents().find('body').on("keydown",function(event) {
return false;
});
}
},