Data being hidden when importing data from a website via script - google-apps-script

When the data is copied, in theory it should appear - or X:Y Half time results: Z:A, but some values as shown below are hidden, I would like to know what is wrong with the script that is doing this happen and how the script has to stay so that it doesn't happen anymore. Only the values appear when there is a V mark (in green color) together to the result of the game.
SITE:
CURRENT RESULTS:
EXPECTED RESULTS:
THE COLUMN THAT IS MISSING DATA TO COLLECT IS THIS:
function sample(placeOfUrl) {
// Retrieve URL.
var baseUrl = "http://old.statarea.com/predictions.php";
var res1 = UrlFetchApp.fetch(baseUrl);
if (res1.getResponseCode() != 200) throw new Erro("URL cannot be used.");
const from = '<td style="padding-top: 10px; text-align: center;">';
const to = ' </td>';
const htmlData1 = (from + Parser.data(res1.getContentText()).from(from).to(to).build() + to).replace(/\ /g, "");
const xmlRoot = XmlService.parse(htmlData1).getRootElement();
const c = xmlRoot.getChildren()[placeOfUrl - 1];
if (!c) return;
const url = c.getAttribute("href").getValue();
// Parse HTML data.
const res2 = UrlFetchApp.fetch(url);
if (res2.getResponseCode() != 200) throw new Erro("URL for retrieving data cannot be used.");
const htmlData2 = res2.getContentText();
const parsedData1 = Parser.data(htmlData2).from('<table class="style_1" cellspacing="0" cellpadding="0" width="918" border="0">').to('</table>').build();
const parsedData2 = Parser.data(parsedData1).from("<tr>").to("</tr>").iterate();
const data = parsedData2
.filter(function(e) {return /^<td width="35" align="center">/.test(e)})
.map(function(e) {return "<content>" + e.match(/<td.+?\/td>/g).map(function(f) {return f.replace(/\&nbsp\;|<div.+?>|<\/div>|<img.+?>|<input.+?>|\&team_guest|<\/h.+?>|\&/g, "")}).join("") + "</content>"})
.join("");
const xmlRootContent = XmlService.parse("<root>" + data + "</root>").getRootElement();
// Retrieve result values.
const content = xmlRootContent.getChildren();
const values = content.reduce((ar1, e) => {
const temp = e.getChildren().reduce((ar2, f, j) => {
if (f) {
if (f.getChild("a")) {
const t = f.getChild("a").getValue()
if (t) ar2.push(t);
} else {
if (f.getAttribute("style")) {
const v = f.getValue();
if (v && [5, 6, 7, 8, 15].includes(j)) {
ar2.push(v);
}
}
}
}
return ar2;
}, []);
ar1.push(temp);
return ar1;
}, []);
return values;
}
LINK TO SPREADSHEET:
https://docs.google.com/spreadsheets/d/1ZdU05slyWXHeOVnoRzhpUNCzb8lBuAsJlVys2bmdh20/edit?usp=sharing

In order to achieve your goal, for example, how about modifying values as follows?
From:
const values = content.reduce((ar1, e) => {
const temp = e.getChildren().reduce((ar2, f, j) => {
if (f) {
if (f.getChild("a")) {
const t = f.getChild("a").getValue()
if (t) ar2.push(t);
} else {
if (f.getAttribute("style")) {
const v = f.getValue();
if (v && [5, 6, 7, 8, 15].includes(j)) {
ar2.push(v);
}
}
}
}
return ar2;
}, []);
ar1.push(temp);
return ar1;
}, []);
To:
const values = content.reduce((ar1, e) => {
const temp = e.getChildren().reduce((ar2, f, j) => {
if (f) {
if (f.getChild("a")) {
const t = f.getChild("a").getValue();
if (t) ar2.push(t);
} else if (f.getChild("span")) {
ar2.push(f.getValue());
} else {
const v = f.getValue();
if (v && [5, 6, 7, 8, 15].includes(j)) {
ar2.push(v);
}
}
}
return ar2;
}, []);
ar1.push(temp);
return ar1;
}, []);

Related

How to create Calendar Events to generate notifications for taking medications

I like to create calendar events with notifications to assist me in taking medications sometimes when the medication regimen gets complicated. I found this application to be quite useful to me and thought perhaps others might like to use such a script. It uses an array of objects to process the medication title, the hours it must be taken and the notifications that you require.
Admittedly, I used hours only rather than hours and minutes. It seemed like a useful simplification to me.
Here's the code:
function createMyEvents() {
const cal = CalendarApp.getCalendarById("calendar id");
const tA = [{ title: "Take Med1", hours: [6, 12, 18, 23], nots: [10, 30] }, { title: "Take Med2", hours: [8, 20], nots: [10, 30] }];
//add to calendar for the following offset days
[2, 3, 4, 5, 6].forEach(offset => {
let dt = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() + offset);
tA.forEach(obj => {
//using the hours array to create events for each day
obj.hours.forEach(h => {
let st = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), h)
let et = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), h + 1);
let event = cal.createEvent(obj.title, st, et)
obj.nots.forEach(m => {
event.addPopupReminder(m);//notifications for each event
});
});
});
});
}
My Current Answer:
Uses a control sheet to set up the parameters for each event creation sequence which also stores the event id's created which makes it possible to also delete the events by iterating through the list.
Code:
function makeEventArray(start = 9, end = 12, offA = '') {
if (!offA) {
const arr = [...Array.from(new Array(end - start + 1).keys(), x => start + x)];
//Logger.log(arr);
return arr;
} else {
return offA;
}
}
function createMyEventsFromList() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('MakeEvents');
const shsr = 3;
let d = new Date();
sh.getRange("C1").setValue(d).setNumberFormat("MMM dd yyyy");
sh.getRange("E1").setValue(d).setNumberFormat("HH:mm:ss");
const vs = sh.getRange(shsr, 1, sh.getLastRow() - shsr + 1, sh.getLastColumn()).getValues().map((r,i) => {r[9] = i + shsr;return r; }).filter(r => r[0] == true).filter(e => e);
if (vs && vs.length > 0) {
ss.toast('Process Running','Creating Events',300)
vs.forEach(r => {
let idA = [];
let title = r[1];
let hours;
if (~r[2].toString().indexOf(',')) {
hours = r[2].toString().split(',').map(e => Number(e));
} else {
hours = [r[2]];
}
let nots;
if (~r[3].toString().indexOf(",")) {
nots = r[3].toString().split(',').map(e => Number(e));
} else {
nots = [r[3]];
}
let desc = r[4];
let start = r[5];
let end = r[6];
let offA;
if (r[7] && r[7].toString().length > 0) {
if (r[7].toString().includes(',')) {
offA = r[7].toString().split(',').map(e => Number(e));
} else {
offA = [r[7]];
}
}
const cal = CalendarApp.getCalendarById("q289qp9augtnshkn70dkibcc54#group.calendar.google.com");
const tob = { start: start, end: end, offA: offA, desc: desc, tA: [{ title: title, hours: hours, nots: nots }] };
makeEventArray(tob.start, tob.end, tob.offA).forEach(offset => {
let dt = new Date(d.getFullYear(), d.getMonth(), d.getDate() + offset);
tob.tA.forEach(obj => {
obj.hours.forEach(h => {
let st = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), h)
let et = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), h + 1);
let event = cal.createEvent(obj.title, st, et);;
if (event) {
event.setDescription(tob.desc);
idA.push(event.getId())
}
if (event && obj.nots && obj.nots.length > 0) {
obj.nots.forEach(m => {
event.addPopupReminder(m);
});
}
});
});
});
sh.getRange(r[9],9,1,2).setValues([[idA.join(', '),r[9]]])
});
ss.toast('Process Complete','Creating Events',15);
}
}
function deleteEventsFromList() {
const cal = CalendarApp.getCalendarById("q289qp9augtnshkn70dkibcc54#group.calendar.google.com");
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('MakeEvents');
const shsr = 3;
const vs = sh.getRange(shsr, 1, sh.getLastRow() - shsr + 1, sh.getLastColumn()).getValues().map((r,i) => {r[9] = i + shsr;return r; }).filter(r => r[0] == true).filter(e => e);
ss.toast('Process Running','Deleeting Events',180)
vs.forEach(r => {
r[8].toString().split(', ').forEach(id => {
cal.getEventById(id).deleteEvent();
});
sh.getRange(r[9],9,1,2).clearContent();
});
ss.toast('Process Complete','Deleeting Events');
}
Image of Sheet:
Changed
const shsr = 3;
let d = new Date();
to this to allow starting date selection:
const shsr = 3;
const r = SpreadsheetApp.getUi().prompt("Get StartDate","yyyy/M/d",SpreadsheetApp.getUi().ButtonSet.OK_CANCEL);
if(r.getSelectedButton() == SpreadsheetApp.getUi().Button.OK) {
var t = r.getResponseText().split('/');
} else {
ss.toast("Making Events","Cancelled Operation by User",5)
return;
}
let d = new Date(t[0],Number(t[1]) - 1,t[2]);

loop always returns 0 when comparing 2 arrays

My code should return a percentage of similaritys between two arrays but it just returns 0 all the time. what am i missing here? I want to compare the original array with the mutated array
const returnRandBase = () => {
const dnaBases = ['A', 'T', 'C', 'G']
return dnaBases[Math.floor(Math.random() * 4)]
}
// Returns a random single strand of DNA containing 15 bases
const mockUpStrand = () => {
const newStrand = []
for (let i = 0; i < 15; i++) {
newStrand.push(returnRandBase())
}
return newStrand
}
const pAequorFactory = (num, arr) => {
return {
specimen: num,
dna: arr,
mutate() {
let ranNum = Math.floor(Math.random() * this.dna.length)
let newBase = returnRandBase()
while (this.dna[ranNum] === newBase) {
newBase = returnRandBase();
}
this.dna[ranNum] = newBase
return this.dna
},
compareDNA(pAequor) {
let matches = 0;
let length = this.dna.length
for (let i = 0; i< pAequor.length; i++) {
if (length[i] === pAequor[i]){
matches++
}
} const simliaritys = matches / length * 100
console.log(simliaritys.toFixed(2) + "%")
}
}
}
const test1 = pAequorFactory(1, mockUpStrand())
console.log(test1)
const test2 = test1.mutate()
console.log(test1.dna);
(test1.compareDNA(test2))
You change the value of test1's DNA within mutate() with this.dna[randNum] = newBase, so mutate() returns test1.dna which is equal to test2.

Rooms with Forge

My goal is to see the Revit rooms in the Forge viewer. The application is in .NET Core. I have tried implementing GenerateMasterViews.
The code I am using to achieve this is:
[Route("api/forge/modelderivative/jobs")]
public async Task<dynamic> TranslateObject([FromBody]TranslateObjectModel objModel)
{
dynamic oauth = await OAuthController.GetInternalAsync();
// prepare the payload
var advOutputPayload = new JobSvf2OutputPayloadAdvanced();
advOutputPayload.GenerateMasterViews = true;
List<JobPayloadItem> outputs = new List<JobPayloadItem>()
{
new JobPayloadItem(
JobPayloadItem.TypeEnum.Svf2,
new List<JobPayloadItem.ViewsEnum>()
{
JobPayloadItem.ViewsEnum._2d,
JobPayloadItem.ViewsEnum._3d
},
advOutputPayload
)
};
JobPayload job;
job = new JobPayload(new JobPayloadInput(objModel.objectName), new JobPayloadOutput(outputs));
// start the translation
DerivativesApi derivative = new DerivativesApi();
derivative.Configuration.AccessToken = oauth.access_token;
dynamic jobPosted = await derivative.TranslateAsync(job);
return jobPosted;
}
Autodesk.Viewing.Initializer(options, () => {
viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('forgeViewer'));
viewer.start();
var documentId = 'urn:' + urn;
Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
}
function onDocumentLoadSuccess(doc) {
var viewables = doc.getRoot().getDefaultGeometry();
viewer.loadDocumentNode(doc, viewables).then(i => {
// documented loaded, any action?
});
}
But I can't get it to work.
I have looked for information, but this url: https://forge.autodesk.com/en/docs/model-derivative/v2/tutorials/prep-roominfo4viewer/option2/ and this url:
https://forge.autodesk.com/en/docs/model-derivative/v2/tutorials/prep-roominfo4viewer/option1/ they don't work and I couldn't see how to do it.
To check if the object is in the room, we can do the following:
Get bounds for each room and object
getBoundingBox(dbId, model) {
const it = model.getInstanceTree();
const fragList = model.getFragmentList();
let bounds = new THREE.Box3();
it.enumNodeFragments(dbId, (fragId) => {
let box = new THREE.Box3();
fragList.getWorldBounds(fragId, box);
bounds.union(box);
}, true);
return bounds;
}
Iterate rooms and objects and use containsBox or containsPoint to check if their bounding box has intersection.
If you want to do an acute collision check, you can take advantage of the ThreeCSG.js to do geometry intersection. Here is a blog post demonstrating how to integrate ThreeCSG.js with Forge Viewer.
https://forge.autodesk.com/blog/boolean-operations-forge-viewer
Note. This process would reduce the viewer performance since JavaScript is running on a single thread on the Web Browser, so you may use some technologies like the web worker to do the complex calculations on a separate thread.
Update:
Here is a working sample extension demonstrating the above idea:
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////
(function () {
const Utility = {
/**
* Rest an object
* #param {Object} obj An object to be reset.
* ref: https://stackoverflow.com/a/24090180
*/
resetObject: function (obj) {
for (let key in Object.getOwnPropertyNames(obj)) {
if (!obj.hasOwnProperty(key)) continue;
let val = obj[key];
switch (typeof val) {
case 'string':
obj[key] = ''; break;
case 'number':
obj[key] = 0; break;
case 'boolean':
obj[key] = false; break;
case 'object':
if (val === null) break;
if (val instanceof Array) {
while (obj[key].length > 0) {
obj[key].pop();
}
break;
}
val = {};
//Or recursively clear the sub-object
//resetObject(val);
break;
}
}
}
};
/**
* A Forge Viewer extension for loading and rendering Revit Grids by AEC Model Data
* #class
*/
class RoomLocatorExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this.roomCategoryName = options.roomCategoryName || 'Revit Rooms';//'Revit Habitaciones'
this.onContextMenu = this.onContextMenu.bind(this);
}
onContextMenu(menu, status) {
if (status.hasSelected) {
menu.push({
title: 'Find room',
target: async () => {
let selSet = this.viewer.getSelection();
this.viewer.clearSelection();
const roomDbIds = await this.locateElementByRoom(selSet[0]);
if (!roomDbIds || roomDbIds.length <= 0) return;
this.viewer.select(roomDbIds);
}
});
}
}
async getPropertiesAsync(dbId, model) {
return new Promise((resolve, reject) => {
model.getProperties2(
dbId,
(result) => resolve(result),
(error) => reject(error)
);
});
}
async getElementsByCategoryAsync(category) {
return new Promise((resolve, reject) => {
this.viewer.search(
category,
(dbIds) => resolve(dbIds),
(error) => reject(error),
['Category'],
{ searchHidden: true }
);
});
}
async getRoomDbIds() {
try {
const roomDbIds = await this.getElementsByCategoryAsync(this.roomCategoryName);
if (!roomDbIds || roomDbIds.length <= 0) {
throw new Error('No Rooms found in current model');
}
return roomDbIds;
} catch (ex) {
console.warn(`[RoomLocatorExtension]: ${ex}`);
throw new Error('No room found');
}
}
getBoundingBox(dbId, model) {
const it = model.getInstanceTree();
const fragList = model.getFragmentList();
let bounds = new THREE.Box3();
it.enumNodeFragments(dbId, (fragId) => {
let box = new THREE.Box3();
fragList.getWorldBounds(fragId, box);
bounds.union(box);
}, true);
return bounds;
}
getLeafFragIds(model, leafId) {
const instanceTree = model.getData().instanceTree;
const fragIds = [];
instanceTree.enumNodeFragments(leafId, function (fragId) {
fragIds.push(fragId);
});
return fragIds;
}
getComponentGeometryInfo(dbId, model) {
const viewer = this.viewer;
const viewerImpl = viewer.impl;
const fragIds = this.getLeafFragIds(model, dbId);
let matrixWorld = null;
const meshes = fragIds.map((fragId) => {
const renderProxy = viewerImpl.getRenderProxy(model, fragId);
const geometry = renderProxy.geometry;
const attributes = geometry.attributes;
const positions = geometry.vb ? geometry.vb : attributes.position.array;
const indices = attributes.index.array || geometry.ib;
const stride = geometry.vb ? geometry.vbstride : 3;
const offsets = geometry.offsets;
matrixWorld = matrixWorld || renderProxy.matrixWorld.elements;
return {
positions,
indices,
offsets,
stride
};
});
return {
matrixWorld,
meshes
};
}
getComponentGeometry(data, vertexArray) {
const offsets = [
{
count: data.indices.length,
index: 0,
start: 0
}
];
for (let oi = 0, ol = offsets.length; oi < ol; ++oi) {
let start = offsets[oi].start;
let count = offsets[oi].count;
let index = offsets[oi].index;
for (let i = start, il = start + count; i < il; i += 3) {
const a = index + data.indices[i];
const b = index + data.indices[i + 1];
const c = index + data.indices[i + 2];
const vA = new THREE.Vector3();
const vB = new THREE.Vector3();
const vC = new THREE.Vector3();
vA.fromArray(data.positions, a * data.stride);
vB.fromArray(data.positions, b * data.stride);
vC.fromArray(data.positions, c * data.stride);
vertexArray.push(vA);
vertexArray.push(vB);
vertexArray.push(vC);
}
}
}
buildComponentMesh(data) {
const vertexArray = [];
for (let idx = 0; idx < data.nbMeshes; ++idx) {
const meshData = {
positions: data['positions' + idx],
indices: data['indices' + idx],
stride: data['stride' + idx]
}
this.getComponentGeometry(meshData, vertexArray);
}
const geometry = new THREE.Geometry();
for (let i = 0; i < vertexArray.length; i += 3) {
geometry.vertices.push(vertexArray[i]);
geometry.vertices.push(vertexArray[i + 1]);
geometry.vertices.push(vertexArray[i + 2]);
const face = new THREE.Face3(i, i + 1, i + 2);
geometry.faces.push(face);
}
const matrixWorld = new THREE.Matrix4();
matrixWorld.fromArray(data.matrixWorld);
const mesh = new THREE.Mesh(geometry);
mesh.applyMatrix(matrixWorld);
mesh.boundingBox = data.boundingBox;
mesh.bsp = new ThreeBSP(mesh)
mesh.dbId = data.dbId;
return mesh;
}
buildCsgMesh(dbId, model) {
const geometry = this.getComponentGeometryInfo(dbId, model);
const data = {
boundingBox: this.getBoundingBox(dbId, model),
matrixWorld: geometry.matrixWorld,
nbMeshes: geometry.meshes.length,
dbId
};
geometry.meshes.forEach((mesh, idx) => {
data['positions' + idx] = mesh.positions;
data['indices' + idx] = mesh.indices;
data['stride' + idx] = mesh.stride;
});
return this.buildComponentMesh(data);
}
async buildBBoxes() {
try {
const model = this.viewer.model;
const roomBBoxes = {};
const roomDbIds = await this.getRoomDbIds();
for (let i = 0; i < roomDbIds.length; i++) {
let dbId = roomDbIds[i];
let bbox = await this.getBoundingBox(dbId, model);
roomBBoxes[dbId] = bbox;
}
this.cachedBBoxes['rooms'] = roomBBoxes;
} catch (ex) {
console.warn(`[RoomLocatorExtension]: ${ex}`);
throw new Error('Cannot build bounding boxes from rooms');
}
}
async locateElementByRoom(dbId) {
let bbox = await this.getBoundingBox(dbId, this.viewer.model);
const roomDbIds = Object.keys(this.cachedBBoxes['rooms']);
const roomBoxes = Object.values(this.cachedBBoxes['rooms']);
// Coarse Phase Collision
const coarseResult = [];
for (let i = 0; i < roomDbIds.length; i++) {
let roomDbId = roomDbIds[i];
let roomBox = roomBoxes[i];
if (roomBox.containsBox(bbox)) {
coarseResult.push(parseInt(roomDbId));
} else {
if (roomBox.containsPoint(bbox.min) || roomBox.containsPoint(bbox.max) || roomBox.containsPoint(bbox.center())) {
coarseResult.push(parseInt(roomDbId));
}
}
}
// Fine Phase Collision
const fineResult = [];
let elementCsgMesh = this.buildCsgMesh(dbId, this.viewer.model);
for (let i = 0; i < coarseResult.length; i++) {
let roomDbId = coarseResult[i];
let roomCsgMesh = this.buildCsgMesh(roomDbId, this.viewer.model);
let result = elementCsgMesh.bsp.intersect(roomCsgMesh.bsp);
if (result.tree.polygons.length <= 0) {
result = roomCsgMesh.bsp.intersect(elementCsgMesh.bsp);
// if (!this.viewer.overlays.hasScene('csg'))
// this.viewer.overlays.addScene('csg');
// else
// this.viewer.overlays.clearScene('csg');
// let mat = new THREE.MeshBasicMaterial({ color: 'red' })
// let mesh = result.toMesh(mat);
// this.viewer.overlays.addMesh(mesh, 'csg')
if (result.tree.polygons.length <= 0) continue;
}
fineResult.push(roomDbId);
}
return fineResult;
}
async load() {
await Autodesk.Viewing.Private.theResourceLoader.loadScript(
'https://cdn.jsdelivr.net/gh/Wilt/ThreeCSG#develop/ThreeCSG.js',
'ThreeBSP'
);
if (!window.ThreeBSP)
throw new Error('Cannot load ThreeCSG.js, please download a copy from https://github.com/Wilt/ThreeCSG/blob/develop/ThreeCSG.js')
await this.viewer.waitForLoadDone();
this.cachedBBoxes = {};
await this.buildBBoxes();
this.viewer.registerContextMenuCallback(
'RoomLocatorExtension',
this.onContextMenu
);
return true;
}
unload() {
Utility.resetObject(this.cachedBBoxes);
this.viewer.unregisterContextMenuCallback(
'RoomLocatorExtension',
this.onContextMenu
);
return true;
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('RoomLocatorExtension', RoomLocatorExtension);
})();
Snapshots:

ReactJS handle tab character in textarea

How can I handle tab key pressed events in ReactJS so I'm able to indent text inside a textarea?
onChange event does not get fired when tab is pressed on the textarea, so I guess there might be a higher level handler I can use to detect this event.
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Tab' && !e.shiftKey) {
e.preventDefault();
const value = this.textareaRef.current!.value;
const selectionStart = this.textareaRef.current!.selectionStart;
const selectionEnd = this.textareaRef.current!.selectionEnd;
this.textareaRef.current!.value =
value.substring(0, selectionStart) + ' ' + value.substring(selectionEnd);
this.textareaRef.current!.selectionStart = selectionEnd + 2 - (selectionEnd - selectionStart);
this.textareaRef.current!.selectionEnd = selectionEnd + 2 - (selectionEnd - selectionStart);
}
if (e.key === 'Tab' && e.shiftKey) {
e.preventDefault();
const value = this.textareaRef.current!.value;
const selectionStart = this.textareaRef.current!.selectionStart;
const selectionEnd = this.textareaRef.current!.selectionEnd;
const beforeStart = value
.substring(0, selectionStart)
.split('')
.reverse()
.join('');
const indexOfTab = beforeStart.indexOf(' ');
const indexOfNewline = beforeStart.indexOf('\n');
if (indexOfTab !== -1 && indexOfTab < indexOfNewline) {
this.textareaRef.current!.value =
beforeStart
.substring(indexOfTab + 2)
.split('')
.reverse()
.join('') +
beforeStart
.substring(0, indexOfTab)
.split('')
.reverse()
.join('') +
value.substring(selectionEnd);
this.textareaRef.current!.selectionStart = selectionStart - 2;
this.textareaRef.current!.selectionEnd = selectionEnd - 2;
}
}
}}
you can try onKeyDown and get the keycode for tab.
add: function(event){
console.log(event.keyCode); //press TAB and get the keyCode
},
render: function(){
return(
<div>
<input type="text" id="one" onKeyDown={this.add} />
</div>
);
}
Just in case someone wants a slightly updated and (in my opinion, enhanced) React Hooks version of vipe's solution in TypeScript:
Implementation Usage Example:
<EnhancedTextArea
ref={txtCodeInput} {/* reference used whenever required as seen below */}
className='code-input'
tabSize={2}
onTextChange={handleCodeChange} {/* Function accepting callback of type (string) -> void, called every time code is changed */}
/>
Getting Text:
const txtCodeInput = useRef<EnhancedTextAreaRefs>(null);
...
const codeContent = txtCodeInput.current?.getCodeContent();
EnhancedTextArea.tsx:
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
type EnhancedTextAreaProps = {
onTextChange?: (text: string) => void,
className?: string,
spellCheck?: boolean,
tabSize?: number,
};
export type EnhancedTextAreaRefs = {
getCodeContent: () => string;
}
const EnhancedTextArea = forwardRef<EnhancedTextAreaRefs, EnhancedTextAreaProps>(({
onTextChange = undefined,
className = undefined,
tabSize = 4,
spellCheck = false,
}: EnhancedTextAreaProps, ref) => {
const [text, setText] = useState('');
const [stateSelectionStart, setStateSelectionStart] = useState(0);
const [stateSelectionEnd, setStateSelectionEnd] = useState(0);
const txtInput = useRef<HTMLTextAreaElement>(null);
useImperativeHandle(ref, () => ({
getCodeContent: () => text,
}));
useEffect(() => {
const textArea = txtInput.current;
if (!textArea) {
return;
}
if (stateSelectionStart >= 0) {
textArea.selectionStart = stateSelectionStart;
}
if (stateSelectionEnd >= 0) {
textArea.selectionEnd = stateSelectionEnd;
}
}, [text, stateSelectionStart, stateSelectionEnd]);
async function handleCodeChange(e: React.ChangeEvent<HTMLTextAreaElement>): Promise<void> {
const text = e.target.value;
setText(text);
if (onTextChange) {
onTextChange(text);
}
}
async function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>): Promise<void> {
const textArea = e.target as HTMLTextAreaElement;
const tabString = ' '.repeat(tabSize);
const value = textArea.value;
const selectionStart = textArea.selectionStart;
const selectionEnd = textArea.selectionEnd;
if (e.key === 'Tab' && !e.shiftKey) {
e.preventDefault();
if (selectionStart !== selectionEnd) {
const slices1 = getNewLineSlices(value, selectionStart, selectionEnd);
const newValue1 = addTabs(value, slices1, tabString);
setText(newValue1);
setStateSelectionStart(selectionStart + tabSize);
setStateSelectionEnd(selectionEnd + (newValue1.length - value.length));
} else {
const newValue2 = value.substring(0, selectionStart) + tabString + value.substring(selectionEnd);
setText(newValue2);
setStateSelectionStart(selectionEnd + tabSize - (selectionEnd - selectionStart));
setStateSelectionEnd(selectionEnd + tabSize - (selectionEnd - selectionStart));
}
} else if (e.key === 'Tab' && e.shiftKey) {
e.preventDefault();
const slices2 = getNewLineSlices(value, selectionStart, selectionEnd);
const newValue3 = removeTabs(value, slices2, tabSize);
const diff = value.length - newValue3.length;
setText(newValue3);
setStateSelectionStart(Math.max(0, selectionStart - (diff ? tabSize : 0)));
setStateSelectionEnd(Math.max(0, selectionEnd - diff));
} else {
setStateSelectionStart(-1);
setStateSelectionEnd(-1);
}
}
function getNewLineSlices(value: string, selectionStart: number, selectionEnd: number): Array<string | null> {
const newLineLocations = getAllIndices(value, '\n');
const left = findRange(newLineLocations, selectionStart);
const split = value.split('\n');
const arr = [];
let count = 0;
for (let i = 0; i < split.length; i++) {
const line = split[i];
if (count > left && count <= selectionEnd) {
arr.push(line);
} else {
arr.push(null);
}
count += line.length + 1;
}
return arr;
}
function addTabs(value: string, arr: Array<string | null>, joiner: string): string {
const split = value.split('\n');
let ret = '';
for (let i = 0; i < split.length; i++) {
const val = split[i];
const newLineVal = arr[i];
if (newLineVal === val) {
ret += joiner;
}
ret += val;
if (i !== split.length - 1) {
ret += '\n';
}
}
return ret;
}
function removeTabs(value: string, arr: Array<string | null>, tabSize: number): string {
const split = value.split('\n');
let ret = '';
for (let i = 0; i < split.length; i++) {
const val = split[i];
const newLineVal = arr[i];
if (!val.startsWith(' ') || newLineVal !== val) {
ret += val;
if (i !== split.length - 1) {
ret += '\n';
}
continue;
}
let count = 1;
while (val[count] === ' ' && count < tabSize) {
count++;
}
ret += val.substring(count);
if (i !== split.length - 1) {
ret += '\n';
}
}
return ret;
}
function getAllIndices(arr: string, val: string): Array<number> {
const indices = [];
let i = -1;
while ((i = arr.indexOf(val, i + 1)) !== -1){
indices.push(i);
}
return indices;
}
function findRange(arr: Array<number>, min: number): number {
for (let i = 0; i < arr.length; i++) {
if (arr[i] >= min) {
return i === 0 ? -1 : arr[i - 1];
}
}
return arr[arr.length - 1];
}
return(
<textarea
ref={txtInput}
value={text}
onKeyDown={handleKeyDown}
onChange={handleCodeChange}
className={className}
spellCheck={spellCheck} />
);
});
EnhancedTextArea.displayName = 'EnhancedTextArea';
export default EnhancedTextArea;
My solution using useRef() for functional component :
const codeAreaRef = useRef();
const [code, setCode] = useState('');
//--------------
<textarea
name='code'
value={code}
ref={codeAreaRef}
onChange={(e) => {
setCode(e.target.value);
}}
onKeyDown={(e) => {
if (e.key == 'Tab') {
e.preventDefault();
const { selectionStart, selectionEnd } = e.target;
const newText =
code.substring(0, selectionStart) +
' ' + // Edit this for type tab you want
// Here it's 2 spaces per tab
code.substring(selectionEnd, code.length);
codeAreaRef.current.focus();
codeAreaRef.current.value = newText;
codeAreaRef.current.setSelectionRange(
selectionStart + 2,
selectionStart + 2
);
setCode(newText);
}
}}
/>

Node.js + bluebird + JSON. JSON is not valid after modification

Trying to get API data.
I have problem with creating valid JSON after modification.
Data should looks like this: [{"1"},{"2"},{"3"}, ... ,{201},{202},{203}, ...]
but now: [{"1"},{"2"},{"3"}, ...],[{"201"},{"202"},{"203"}, ...]
Where is my mistake?
var Promise = require("bluebird");
var request = require('bluebird').promisifyAll(require('request'));
var fs = Promise.promisifyAll(require('fs'));
var ladders = {"hardcore":"hardcore", "standard":"standard"};
function getJSONsync(urls) {
var ladder = [];
Promise.map(urls, function(url) {
return request
.getAsync(url)
.spread(function (res, body) {
if (res.statusCode != 200) {
throw new Error('Unsuccessful attempt. Code: '+ res.statusCode);
}
return JSON.stringify(ladder.concat(JSON.parse(body).entries), "", 4);
})
.catch(console.error);
},{ concurrency: 10 })
.then(function(arr) {
fs.writeFileAsync('file.json', arr);
})
}
function setUrls(ladderName, offset, limit) {
var arr = [];
while(offset < 15000 ) {
arr.push('http://api.pathofexile.com/ladders/'+ladderName+'?offset='+offset+'&limit='+limit);
offset = offset + 200;
}
return arr;
}
getJSONsync(setUrls(ladders.hardcore, 0, 200));
Thx for help.
Sorry for my Eng.
Finally:
var Promise = require("bluebird");
var request = require('bluebird').promisifyAll(require('request'));
var fs = Promise.promisifyAll(require('fs'));
var ladders = {"hardcore":"hardcore","standard":"standard"};
function getJSONsync(urls) {
Promise.map(urls, function(url) {
return request
.getAsync(url)
.spread(function (res, body) {
if (res.statusCode != 200) {
throw new Error('Unsuccessful attempt. Code: '+ res.statusCode);
}
return JSON.parse(body).entries;
})
.catch(console.error);
},{ concurrency: 10 })
.reduce(function(a, b) { return a.concat(b) })
.then(function(arr) {
fs.writeFileAsync('file.json', JSON.stringify(arr, "", 4));
console.log(arr.length);
})
}
function setUrls(ladder, offset, limit) {
var arr = [];
while(offset < 15000 ) {
arr.push('http://api.pathofexile.com/ladders/'+ladder+'?offset='+offset+'&limit='+limit);
offset = offset + 200;
}
return arr;
}
getJSONsync(setUrls(ladders.hardcore, 0, 200));
Promise.map returns an array, so when you do ladder.concat you return another array, so it becomes [[{"1"}], [{"1", "2"}]
You should just remove concat:
return JSON.stringify(JSON.parse(body).entries, "", 4);
But if you want to use variable ladder you may ladder.push(JSON.stringify(JSON.parse(body).entries, "", 4)) and use it instead of arr returned variable