Avoid writing same function multiple times - json

I'm using ajax to load json data into several accordion tables on the same html page. So far, I have the same function written twice to output 2 tables, but I need 16 more tables. Having the same function written out 18 times doesn't seem dry at all, so I'm thinking there must be better way.
let myArray = [];
$.ajax({
method: "GET",
url: "https://chrisjschreiner.github.io/data/verbs1.json",
success: function (response) {
myArray = response;
buildTable1(myArray[0]);
buildTable2(myArray[1]);
},
});
let buildTable1 = (data) => {
let table = document.getElementById("myTable1");
for (const [key, value] of Object.entries(data)) {
let row = `<tr>
<td>${key}</td>
<td>${value}</td>
</tr>`;
table.innerHTML += row;
}
};
let buildTable2 = (data) => {
let table = document.getElementById("myTable2");
for (const [key, value] of Object.entries(data)) {
let row = `<tr>
<td>${key}</td>
<td>${value}</td>
</tr>`;
table.innerHTML += row;
}
};
Any suggestions?

In order to avoid rewriting functions, pass in variables:
buildTable(selector, response, ...indices)
Parameters
selector: string - selector of the element that'll contain all of the <table>s
response: array of objects - the parsed JSON from the server
...indices: '*' or comma delimited list of numbers - the spread operator will gather all of the numbers or '*' into an array
If '*' is passed, it will be an array of numbers that represent all of the indices within response.
'*' passed as [0 to data.length-1]
If number(s) are passed, those will represent the index of each object of response. The following example would be for the first, third and seventh object of response
0, 2, 6 passed as [0, 2, 6]
The following example will create a <table> for each applicable object within the array
$.ajax({
method: "GET",
url: "https://chrisjschreiner.github.io/data/verbs1.json",
success: function(response) {
buildTable1('main', response, '*');
},
});
const buildTable1 = (selector, response, ...indices) => {
const node = document.querySelector(selector);
const data = JSON.parse(JSON.stringify(response));
const idcs = indices.includes('*') ? data.map((o, i) => i) : indices;
const table = document.createElement('table');
data.forEach((obj, idx) => {
if (idcs.includes(idx)) {
let tClone = table.cloneNode(true);
node.appendChild(tClone);
Object.entries(obj).forEach(([key, val]) => {
let row = tClone.insertRow();
row.innerHTML = `<td>${key}</td><td>${val}</td>`;
});
}
});
};
table {
width: 80%;
margin: 10px;
table-layout: fixed;
border: 1px solid black;
border-collapse: collapse;
}
td {
padding: 0 5px;
border-top: 1px solid red;
vertical-align: top;
}
tr:first-child td {
border-top: 0px solid transparent;
}
td:first-child {
width: 35%;
border-right: 3px dotted red;
}
<main>
<!--Tables will be added here -- selector = 'main'-->
</main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Related

How To Put A Number In Front Of Every Suggestion Corretcly?

Detail Of The Problem
As title, I am using Google App Script and Google Docs API's Batchupdate, trying to put number in front of every suggestion. However, I can place it correctly at the very first one, but it starts to deviate after the first one.
Result I Currently Have
Please refer to the image below.
What I have Tried
Below is the snippet I currently have
function markNumberInFrontOfMark(fileID) {
fileID = "MYFILEID";
let doc = Docs.Documents.get(fileID);
let requests = doc.body.content.flatMap(content => {
if (content.paragraph) {
let elements = content.paragraph.elements;
return elements.flatMap(element => element.textRun.suggestedDeletionIds ? {
insertText: {
text: "(1)",
location: {
index: element.startIndex
}
}
} : []);
}
return [];
});
Docs.Documents.batchUpdate({requests}, fileID);
return true;
}
Result I Want To Have
Please refer to the image below
Post I Refer to
How to change the text based on suggestions through GAS and Google DOC API
Here is an example of how to insert text. In this case I am adding 3 characters "(1)" for example. If the number of additions exceeds 9 you will have to adjust the number of characters added.
function markNumberInFrontOfMark() {
try {
let doc = DocumentApp.getActiveDocument();
let id = doc.getId();
doc = Docs.Documents.get(id);
let contents = doc.body.content;
let requests = [];
let num = 0;
contents.forEach( content => {
if( content.paragraph ) {
let elements = content.paragraph.elements;
elements.forEach( element => {
if( element.textRun.suggestedDeletionIds ) {
num++;
let text = "("+num+")"
let request = { insertText: { text, location: { index: element.startIndex+3*(num-1) } } };
requests.push(request);
}
}
);
}
}
);
if( requests.length > 0 ) {
Docs.Documents.batchUpdate({requests}, id);
}
}
catch(err) {
console.log(err)
}
}
And the resulting updated document.

Rendering columns of buttons within div

I have a list of languages, and I want to render them each as buttons within a column, within a div. I want to be able to render them based on a variable I set, buttonsPerColumn.
For example, if I have 40 languages, and four columns, I will render 10 buttons per column. If I have 36 languages, I will render 10 buttons for the first three, and 6 for the remainder. However, I am at a loss for how to do this. I have console logged my desired output, however, I would like this in button form. How can I create all of the column and button divs I need within my method, and then output them all at once?
css
.languageList {
position: absolute;
height: 35%;
width: 25%;
left: 43%;
top: 15%;
background-color: #b6bbf4;
display: flex;
justify-content: space-between;
}
.languageColumn {
position: relative;
width: 25%;
height: 100%;
background-color: red;
}
languagelist.jsx
class LanguageList extends Component {
render() {
this.renderButtons();
return (
<React.Fragment>
<div className="languageList">
<div className="languageColumn" />
</div>
</React.Fragment>
);
}
renderButtons = () => {
let buttonsPerColumn = 6;
const languages = LanguageList.getLanguages();
const myArray = LanguageList.arrayFromObject(languages);
var i, language;
let column = 0;
for (i = 0; i < myArray.length; i++) {
language = myArray[i];
console.log("Render " + language + " in column " + column);
if (i == buttonsPerColumn) {
column++;
buttonsPerColumn += buttonsPerColumn;
}
}
};
static arrayFromObject = object => {
var myArray = [];
var key;
for (key in object) {
myArray.push(key);
}
return myArray;
};
static getLanguages = () => {
return {
Azerbaijan: "az",
Albanian: "sq",
Amharic: "am",
English: "en",
Arabic: "ar",
Armenian: "hy",
Afrikaans: "af",
Basque: "eu",
German: "de",
Bashkir: "ba",
Nepali: "ne"
};
};
}
Link to code sandbox:
https://codesandbox.io/s/practical-chatelet-bq589
Try this:
import React, { Component } from "react";
class LanguageList extends Component {
render() {
return (
<React.Fragment>
<div className="languageList">{this.renderButtons()}</div>
</React.Fragment>
);
}
renderButtons = () => {
const buttonsPerColumn = 6;
const languages = LanguageList.getLanguages();
const myArray = LanguageList.arrayFromObject(languages);
const columns = [];
for (let i = 0; i < myArray.length; i++) {
const columnIndex = Math.floor(i / buttonsPerColumn);
if (!columns[columnIndex]) columns[columnIndex] = [];
columns[columnIndex].push(
<button className="languageButton">{myArray[i]}</button>
);
}
return columns.map((singleColumn, index) => (
<div key={index} className="languageColumn">
{singleColumn}
</div>
));
};
static arrayFromObject = object => {
var myArray = [];
var key;
for (key in object) {
myArray.push(key);
}
return myArray;
};
static getLanguages = () => {
return {
Azerbaijan: "az",
Albanian: "sq",
Amharic: "am",
English: "en",
Arabic: "ar",
Armenian: "hy",
Afrikaans: "af",
Basque: "eu",
German: "de",
Bashkir: "ba",
Nepali: "ne"
};
};
}
export default LanguageList;

Fetch URI from Post Data through Get Data

Show 1 textfield with 2 buttons - Post, Get. Take a number as input in a text field. On clicking Post, create an array of the numbers from 1 to that number. Post this array at the URL. Display the response from the Post.On clicking Get, fetch data from the URL returned by the Post and display it.
urlPost = 'https://api.myjson.com/bins';
clist: number[] = [];
strData: string = '';
S1: String = '';
ntext: number;
constructor(private netService: NetService) {}
postData() {
for (var i = 1; i <= this.ntext; i++) {
this.clist.push(i);
}
this.netService.postData(this.urlPost, this.clist)
.subscribe(postresp => {
this.strData = JSON.stringify(postresp);
});
}
getData() {
this.netService.getData(this.strData.Uri)
.subscribe(resp => {
this.strData = JSON.stringify(resp);
});
}
this line need to be improved.
this.netService.getData(this.strData.Uri)
As I understand your question, you simply have a problem with parsing a response from your postData(). So, just refer to the following -
postData() {
for (var i = 1; i <= this.ntext; i++) {
this.clist.push(i);
}
this.netService.postData(this.urlPost, this.clist)
.subscribe(postresp => {
this.S1 = postresp['uri']; // get URL here
});
}
getData() {
this.netService.getData(this.S1) // use it here
.subscribe(resp => {
this.strData = JSON.stringify(resp);
});
}
See it working here.

Transform Request to Autoquery friendly

We are working with a 3rd party grid (telerik kendo) that has paging/sorting/filtering built in. It will send the requests in a certain way when making the GET call and I'm trying to determine if there is a way to translate these requests to AutoQuery friendly requests.
Query string params
Sort Pattern:
sort[{0}][field] and sort[{0}][dir]
Filtering:
filter[filters][{0}][field]
filter[filters][{0}][operator]
filter[filters][{0}][value]
So this which is populated in the querystring:
filter[filters][0][field]
filter[filters][0][operator]
filter[filters][0][value]
would need to be translated to.
FieldName=1 // filter[filters][0][field]+filter[filters][0][operator]+filter[filters][0][value] in a nutshell (not exactly true)
Should I manipulate the querystring object in a plugin by removing the filters (or just adding the ones I need) ? Is there a better option here?
I'm not sure there is a clean way to do this on the kendo side either.
I will explain the two routes I'm going down, I hope to see a better answer.
First, I tried to modify the querystring in a request filter, but could not. I ended up having to run the autoqueries manually by getting the params and modifying them before calling AutoQuery.Execute. Something like this:
var requestparams = Request.ToAutoQueryParams();
var q = AutoQueryDb.CreateQuery(requestobject, requestparams);
AutoQueryDb.Execute(requestobject, q);
I wish there was a more global way to do this. The extension method just loops over all the querystring params and adds the ones that I need.
After doing the above work, I wasn't very happy with the result so I investigated doing it differently and ended up with the following:
Register the Kendo grid filter operations to their equivalent Service Stack auto query ones:
var aq = new AutoQueryFeature { MaxLimit = 100, EnableAutoQueryViewer=true };
aq.ImplicitConventions.Add("%neq", aq.ImplicitConventions["%NotEqualTo"]);
aq.ImplicitConventions.Add("%eq", "{Field} = {Value}");
Next, on the grid's read operation, we need to reformat the the querystring:
read: {
url: "/api/stuff?format=json&isGrid=true",
data: function (options) {
if (options.sort && options.sort.length > 0) {
options.OrderBy = (options.sort[0].dir == "desc" ? "-" : "") + options.sort[0].field;
}
if (options.filter && options.filter.filters.length > 0) {
for (var i = 0; i < options.filter.filters.length; i++) {
var f = options.filter.filters[i];
console.log(f);
options[f.field + f.operator] = f.value;
}
}
}
Now, the grid will send the operations in a Autoquery friendly manner.
I created an AutoQueryDataSource ts class that you may or may not find useful.
It's usage is along the lines of:
this.gridDataSource = AutoQueryKendoDataSource.getDefaultInstance<dtos.QueryDbSubclass, dtos.ListDefinition>('/api/autoQueryRoute', { orderByDesc: 'createdOn' });
export default class AutoQueryKendoDataSource<queryT extends dtos.QueryDb_1<T>, T> extends kendo.data.DataSource {
private constructor(options: kendo.data.DataSourceOptions = {}, public route?: string, public request?: queryT) {
super(options)
}
defer: ng.IDeferred<any>;
static exportToExcel(columns: kendo.ui.GridColumn[], dataSource: kendo.data.DataSource, filename: string) {
let rows = [{ cells: columns.map(d => { return { value: d.field }; }) }];
dataSource.fetch(function () {
var data = this.data();
for (var i = 0; i < data.length; i++) {
//push single row for every record
rows.push({
cells: _.map(columns, d => { return { value: data[i][d.field] } })
})
}
var workbook = new kendo.ooxml.Workbook({
sheets: [
{
columns: _.map(columns, d => { return { autoWidth: true } }),
// Title of the sheet
title: filename,
// Rows of the sheet
rows: rows
}
]
});
//save the file as Excel file with extension xlsx
kendo.saveAs({ dataURI: workbook.toDataURL(), fileName: filename });
})
}
static getDefaultInstance<queryT extends dtos.QueryDb_1<T>, T>(route: string, request: queryT, $q?: ng.IQService, model?: any) {
let sortInfo: {
orderBy?: string,
orderByDesc?: string,
skip?: number
} = {
};
let opts = {
transport: {
read: {
url: route,
dataType: 'json',
data: request
},
parameterMap: (data, type) => {
if (type == 'read') {
if (data.sort) {
data.sort.forEach((s: any) => {
if (s.field.indexOf('.') > -1) {
var arr = _.split(s.field, '.')
s.field = arr[arr.length - 1];
}
})
}//for autoquery to work, need only field names not entity names.
sortInfo = {
orderByDesc: _.join(_.map(_.filter(data.sort, (s: any) => s.dir == 'desc'), 'field'), ','),
orderBy: _.join(_.map(_.filter(data.sort, (s: any) => s.dir == 'asc'), 'field'), ','),
skip: 0
}
if (data.page)
sortInfo.skip = (data.page - 1) * data.pageSize,
_.extend(data, request);
//override sorting if done via grid
if (sortInfo.orderByDesc) {
(<any>data).orderByDesc = sortInfo.orderByDesc;
(<any>data).orderBy = null;
}
if (sortInfo.orderBy) {
(<any>data).orderBy = sortInfo.orderBy;
(<any>data).orderByDesc = null;
}
(<any>data).skip = sortInfo.skip;
return data;
}
return data;
},
},
requestStart: (e: kendo.data.DataSourceRequestStartEvent) => {
let ds = <AutoQueryKendoDataSource<queryT, T>>e.sender;
if ($q)
ds.defer = $q.defer();
},
requestEnd: (e: kendo.data.DataSourceRequestEndEvent) => {
new DatesToStringsService().convert(e.response);
let ds = <AutoQueryKendoDataSource<queryT, T>>e.sender;
if (ds.defer)
ds.defer.resolve();
},
schema: {
data: (response: dtos.QueryResponse<T>) => {
return response.results;
},
type: 'json',
total: 'total',
model: model
},
pageSize: request.take || 40,
page: 1,
serverPaging: true,
serverSorting: true
}
let ds = new AutoQueryKendoDataSource<queryT, T>(opts, route, request);
return ds;
}
}

ionic: I have a scope variable empty from a stringified json

I'm trying to filter a JSON result from a SQLite query. The filter works when I use JSON directly, but it doesn't when I use the query from the service. Then, the $scope.arrayme just appears as empty.
Where is the error? Thank you!
This is the service:
getSubtipos: function() {
var query = "SELECT subtipos.idsubtipo, subtipos.tipos_idtipo, subtipos.nombre, subtipos.icon, subtipos.val FROM subtipos";
var arraySubtipos = [];
$cordovaSQLite.execute(db, query, []).then(function(res) {
if(res.rows.length > 0) {
for(var i = 0; i < res.rows.length; i++) {
arraySubtipos.push(res.rows.item(i));
}
} else {
console.log("No results found");
}
}, function (err) {
console.error("ERROR: " + err.message);
}).finally(function() {
arraySubtipos = JSON.stringify(arraySubtipos);
});
return arraySubtipos;
}
This is the controller:
.controller('MenuSubtiposCtrl', function($scope, $filter, miJson, $stateParams, $cordovaSQLite){
var arrayme = JSON.stringify(miJson.getSubtipos());
$scope.arrayme = $filter("filter")(JSON.parse(arrayme), {tipos_idtipo: $stateParams.foo});
})
And this is the state:
.state('app.menusubtipos', {
url: "/menusubtipos/:foo",
views: {
'menuContent': {
templateUrl: "templates/menuSubtipos.html",
controller: "MenuSubtiposCtrl"
}
}
})
There may be more problems than what I've immediately noticed, but I have have noticed that you're returning a variable within your getSubtipos function before it's set.
The $cordovaSQL.execute() function is an asyncronous function. As a result, you are returning arraySubtipos before it's set.
A better way to do this would be within getSubtipos to do the following:
var arraySubtipos = [];
return $q.when($cordovaSQLite.execute(db, query, [])
.then(function(res) {
if(res.rows.length > 0) {
for(var i = 0; i < res.rows.length; i++) {
arraySubtipos.push(res.rows.item(i));
}
} else {
console.log("No results found");
}
return JSON.stringify(arraySubtipos);
}));
// Then, within your controller, do the following:
.controller('MenuSubtiposCtrl', function($scope, $filter, miJson, $stateParams, $cordovaSQLite){
miJson.getSubtipos()
.then(function(arrayMe) {
// No need to stringify it again
$scope.arrayme = $filter("filter")(JSON.parse(arrayme), {tipos_idtipo: $stateParams.foo});
})
.catch(function(error) {
// Handle the error here
});
var arrayme = JSON.stringify(miJson.getSubtipos());
});
I'm also a little suspicious about your use of JSON.stringify and JSON.parse. It's likely that they're not needed, but without knowing the format of your data, I've left that as is.