Is there way to implement data validation programmatically using Office.js API ?
While you can absolutely implement your own data validation within an add-in, it would be distinct from the built-in data validation tool. There is currently no API for configuring Excel's data validation tool programmatically.
Here I have try to add data validation on excel cell within range.
Excel.run(function (context) {
var currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
var expensesTable = currentWorksheet.tables.add("A1:D1", true /*hasHeaders*/);
expensesTable.name = "ExpensesTable";
expensesTable.getHeaderRowRange().values = [["Date", "Merchant", "Category", "Amount"]];
expensesTable.rows.add(null /*add at the end*/, [
["1/1/2017", "The Phone Company", "Communications", "120"],
["1/2/2017", "Northwind Electric Cars", "Transportation", "142.33"],
["1/5/2017", "Best For You Organics Company", "Groceries", "27.9"],
["1/10/2017", "Coho Vineyard", "Restaurant", "33"],
["1/11/2017", "Bellows College", "Education", "350.1"],
["1/15/2017", "Trey Research", "Other", "135"],
["1/15/2017", "Best For You Organics Company", "Groceries", "97.88"]
]);
var range = currentWorksheet.getRange("C2:C200");
range.dataValidation.clear();
range.dataValidation.rule = {
list: {
inCellDropDown: true,
source: "Groceries, Education, Other,Transportation",
autofitColumns: true
}
};
//range.dataValidation.errorAlert = {
// message: "Sorry, only positive numbers are allowed",
// showAlert: true,
// style: "Stop",
// title: "Negative Number Entered"
//};
list.find();
return context.sync();
}).catch(function (error) {
console.log("Error: " + error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
Related
Hi to every one reading this. I am struggling with a PDF.js problem. I am using this library: https://github.com/edisonneza/jspdf-invoice-template to render a PDF with data that I am getting from an internal axios route that returns orders. This orders are listed with a checkbox, for the orders checked I generate a new array with the objects of the different orders checked.
Up to this I was doing fine, but the next thing I need is to generate this PDFs. I got it to work rendering one single pdf with the data order, but i would like to generete a PDF with (N) numbers of pages, using the checked orders to print with a button.
Below are the blocks of the code that i wrote.
printAllbtn.addEventListener('click',function(){
try {
if(ordersSelectedToPrint.length \> 1){
console.log(ordersSelectedToPrint);
for (let orderPrint of ordersSelectedToPrint) {
let pdfObject = jsPDFInvoiceTemplate.default(props);
console.log( 'object created' + pdfObject + orderPrint)
var props = {
outputType: jsPDFInvoiceTemplate.OutputType.Save,
returnJsPDFDocObject: true,
fileName: "remito",
orientationLandscape: false,
compress: true,
business: {
name: "Isidorito",
address: "San Pedro, Buenos",
phone: "(3329) 069 11 11 111",
email: "contacto#isidorito.com",
website: "www.isidorito.com.ar/nosotros",
},
contact: {
label: "Remito dirigito a:",
name: `${orderPrint.cliente.dueño}`,
address:`${orderPrint.cliente.direccion}`,
phone: `${orderPrint.cliente.telefono1}`,
email: `${orderPrint.cliente.correo}`,
},
invoice: {
label: "Pedido #",
num: 19,
invDate: `${orderPrint.createdAt}`,
invGenDate: `28/10/2022`,
headerBorder: false,
tableBodyBorder: false,
header: [
{
title: "#",
style: {
width: 10
}
},
{
title: "Productos descripción",
style: {
width: 70
}
},
{
title: "Cantidad ",
style: {
width:20
}
},
{ title: "Por Unidad",
style:{width:30}},
// { title: "Cantidad"},
{ title: "Por tot",
style:{
width:30
}},
{ title: "Total",
style:{
width:30
}}
],
table: Array.from(orderPrint.productosPedidosNombre).forEach(function(productoIm, index){[
index + 1,
`${productoIm.nombre} - ${productoIm.marca} - ${productoIm.presentacion}`,
`${productoIm.cantidad}`,
`${productoIm.cantidad}`,
`${productoIm.cantidad}`,
`${productoIm.cantidad}`,
]}),
invDescLabel: "Cliente:",
invDesc: `${orderPrint.cliente.nombreLocal}`,
},
footer: {
text: "Este remito generado digitalmente es valido para el pedido realizado",
},
pageEnable: true,
pageLabel: "Page ",
};
} } } catch (error) { console.log(error); }
});
The first note is that "orderPrint.productosPedidosNombre" is an array of products of the order. In the example from the documentation its represented in this way.
table: Array.from(Array(10), (item, index)=>([
index + 1,
"There are many variations ",
"Lorem Ipsum is simply dummy text dummy text ",
200.5,
4.5,
"m2",
400.5
])),
And the way I generate the PDF is:
let pdfObject = jsPDFInvoiceTemplate.default(props);
First I would like to know if it is possible the loops and iteration to generate this type of single file with N number of order pages, and each page with different data. It's possible with this library.
Thanks you. It's my second question I've asked in StackOverflow, so I welcome the feedback on the how to ask in stack overflow.
I've developed an app and connector for Microsoft teams, whenever I try to setup a connector for a specific channel and save the connector when the setup is finished there is an unexpected error message
If I then look in the developer tools I see that some request is send to https://outlook.office.com/connectors/ConnectToO365Inline/Manage/UpdateAsync with a http 500 response. I have followed the documentation provided by Microsoft at https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-creating I also stripped down our code to the bare minimum but still get the unexpected error while saving.
It's also very odd that the connector works perfectly on our Microsoft partner account, but whenever I try the connector on an other microsoft account it gives the unexpected error while saving.
Here you can see the manifest.json for the app:
{
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.11/MicrosoftTeams.schema.json",
"manifestVersion": "1.11",
"version": "1.0.0",
"id": "0fa0f150-fd7e-4c4e-822e-793951af07f6",
"packageName": "com.package",
"developer": {
"name": "App name",
"websiteUrl": "https://www.domain",
"privacyUrl": "https://www.domain",
"termsOfUseUrl": "https://www.domain"
},
"name": {
"short": "short",
"full": "full"
},
"description": {
"short": "short description",
"full": "long description"
},
"icons": {
"outline": "icon.png",
"color": "icon.png"
},
"accentColor": "#3F487F",
"connectors": [
{
"connectorId": "8b4fe7a3-a04d-4d60-b204-0519ca2f0d04",
"configurationUrl": "https://domain/teams/config",
"scopes": [
"team"
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"domain"
]
}
And this is our save handler as part of our file to setup the connector:
microsoftTeams.settings.registerOnSaveHandler(function (saveEvent) {
microsoftTeams.getContext(context => {
microsoftTeams.settings.getSettings(s => {
let data = {
url: s.webhookUrl,
name: context.teamName + ' - ' + context.channelName,
channel_id: context.channelId,
token: s.entityId,
origin: window.location.origin
};
let xhr = new XMLHttpRequest();
xhr.open('POST', '/teams/token/updateWebhook');
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let response = JSON.parse(xhr.response);
if (response.status === true) {
context.webhookUrl = response.value;
saveEvent.notifySuccess();
}
}
}
xhr.send(JSON.stringify(data));
});
});
});
I already contacted Microsoft support but they said their expertise to developed software and connectors are very little, so we should ask it on Stackoverflow.
What is going wrong, and what do i need to do to make it work?
I am facing an issue with an excel file. I receive some data from the DB and the user should be able to replace that data with a spreadsheet that looks like this:
This is how the data comes from the DB and how the excel file should be finally formatted:
"employers": [{
"id": "4147199311345513",
"shifts": [{
"url": "https://zoom.com/983493unsdkd/",
"days": "Mon,Tue,Wed,Thu,Fri",
"name": "Morning",
"endTime": "12:00",
"timezone": "CST",
"startTime": "8:00"
}, {
"url": "https://zoom.com/983493unsdkd/",
"days": "Mon,Tue,Wed,Thu,Fri",
"name": "Afternoon",
"endTime": "12:00",
"timezone": "CST",
"startTime": "8:00"
}],
"employerUrl": "http://www.google.com",
"employerName": "AT&T",
"employerUrlText": "URL Text",
"employerLogoSmall": "assets/images/att-logo.png",
"employerDescription": "AT&T is a world premier employer with a bunch of stuff here and there."
}, {
"id": "3763171269270198",
"shifts": [{
"url": "https://zoom.com/983493unsdkd/",
"days": "Mon,Tue,Wed,Thu,Fri",
"name": "Morning",
"endTime": "12:00",
"timezone": "CST",
"startTime": "8:00"
}, {
"url": "https://zoom.com/983493unsdkd/",
"days": "Mon,Tue,Wed,Thu,Fri",
"name": "Afternoon",
"endTime": "12:00",
"timezone": "CST",
"startTime": "8:00"
}],
"employerUrl": "http://www.google.com",
"employerName": "AT&T",
"employerUrlText": "URL Text",
"employerLogoSmall": "assets/images/att-logo.png",
"employerDescription": "AT&T is a world premier employer with a bunch of stuff here and there."
}]
So I need to take that spreadsheet and format it to look like that JSON above. All of this with Javascript/React.
This is what I have so far to format my excel file and render it:
const [excelData, setExcelData] = useState({ rows: [], fileName: "" });
const fileHandler = (event) => {
let fileObj = event.target.files[0];
ExcelRenderer(fileObj, (err, resp) => {
if (err) {
console.log(err);
} else {
let newRows = [];
let shiftRows = [];
console.log(resp.rows);
resp.rows.slice(1).map((row, index) => {
if (row && row !== "undefined") {
return newRows.push({
key: index,
employer: {
name: row[0],
description: row[1],
employerUrl: row[2],
employerUrlText: row[3],
shifts: shiftRows.push({ shift: row[2] }),
},
});
}
return false;
});
setExcelData({ rows: newRows, fileName: fileObj.name });
}
});
};
That console.log above (console.log(resp.rows)) returns this:
Where the first row are the headers of the excel file.
And the code above ends up like this and it should be exactly as the JSON I mentioned:
rows: [
{
key: 0,
employer: {
name: 'AT&T',
description: 'AT&T is a world premier employer with a bunch of stuff here and there.',
shifts: 1
}
},
{
key: 1,
employer: {
shifts: 2
}
},
{
key: 2,
employer: {
shifts: 3
}
},
{
key: 3,
employer: {
shifts: 4
}
},
{
key: 4,
employer: {
name: 'Verizon',
description: 'Verizon is a world premier employer with a bunch of stuff here and there.',
shifts: 5
}
},
{
key: 5,
employer: {
shifts: 6
}
},
{
key: 6,
employer: {
shifts: 7
}
},
{
key: 7,
employer: {
shifts: 8
}
}
],
fileName: 'EmployerChats.xlsx',
false: {
rows: [
{
url: 'https://www.youtube.com/kdfjkdjfieht/',
title: 'This is a video',
thumbnail: '/assets/images/pages/5/links/0/link.png',
description: 'This is some text'
},
{
url: 'https://www.youtube.com/kdfjkdjfieht/',
title: 'This is a video',
thumbnail: '/assets/images/pages/5/links/1/link.png',
description: 'This is some text'
}
]
},
I am using this plugin to help me render the excel file: https://www.npmjs.com/package/react-excel-renderer
Any ideas on what can I do to make format the spreadsheet data as the JSON?
Please notice those empty rows.
For example every time there is a new employer name, that's a new row or item in the array, then all of the columns and rows below and after Shift Name is a new nested array of objects. Hence, this file contains an array with a length of 2 and then it contains another array of items when it hits the Shift Name column.
Is it clear?
1st of all - you don't need to follow 'original', class based setState. In FC you can just use two separate useState.
const [rows, setRows] = useState([]);
const [fileName, setFileName] = useState("");
Data conversion
I know that you need a bit different workflow, but this can be usefull (common point - data structure), too - as conversion guide, read on.
You don't need to use ExcelRenderer to operate on data from db and render it as sheet. Converted data can be exported to file later.
You can just create array of array (aoa) that follows expected view (rows = array of row cells array). To do this you need very easy algorithm:
let newData = []
map over emplyers, for each (emp):
set flag let first = true;
map over shifts, for each (shift):
if( first ) { newData.push( [emp.name, emp.descr, shift.name, shift.timezone...]); first = false;
} else newData.push( [null, null, shift.name, shift.timezone...]);
setRows( newData );
Rendering
<OutTable/> operates on data and colums props - structures similar to internal state. 'datais ourrows, we only needcolumns` prop, just another state value:
const [columns, setColumns] = useState([
{ name: "Employer name", key: 0 },
{ name: "Employer description", key: 1 },
{ name: "Shift name", key: 2 },
// ...
]);
and finally we can render it
return (
<OutTable data={rows] columns />
Later
User can operate on sheet view - f.e. insert rows using setRows() or download this as file (XLSX.writeFile()) after simple conversion:
var ws = XLSX.utils.aoa_to_sheet( columns.concat( rows ) );
There is a lot of utils you can use for conversions - see samples.
Back to your needs
We have data loaded from db, data in aoa form, rendered as sheet. I don't fully understand format you need, but for your db format conversion is simple (opposite to above) - you can follow it and adjust to your needs.
let newEmployers = [];
let empCounter = -1;
// itarate on rows, on each (`row`):
rows.map( (row) => {
// new employer
if( row[0] ) {
newEmployers.push( {
// id should be here
"employerName": row[0],
"employerDescription": row[1],
"shifts": [
{
"shiftName": row[3],
"shiftDescription": row[4],
// ...
}
]
} );
empCounter ++;
} else {
// new shift for current employer
newEmployers[empCounter].shifts.push(
{
"shiftName": row[3],
"shiftDescription": row[4],
// ...
}
);
}
});
// newEmployers can be sent to backend (as json) to update DB
I am creating an API that gets Patients data(id and name), Physicians data(id and name) and Appointments(id, phyId, patId, app_date) and displays the Patients appointed to a particular physician. I need to create a remote method in physician.js in such a way that I get related Appointment that has phyId and print the details of the Patients using the patId obtained from appointment.
I'm using loopback 3.
Refer this link for clear idea:
https://loopback.io/doc/en/lb3/HasManyThrough-relations.html
I have related models (Physicians, Patients) that are related by "hasMany" with each other "through" Appointment(another model) and Appointment is related to each of these by belongsTo, in my loopback application and i need to print the Patients of a particular Physician.
Patient data:
[
{
"id": 1,
"name": "Anna Mull"
},
{
"id": 2,
"name": "Paige Trner"
}
]
Physician data:
[
{
"id": 1,
"name": "Cardiologist"
}
]
Appointment data:
[
{
"id": 1,
"physicianId": 1,
"patientId": 1,
"appointmentDate": "2019-01-28T10:06:33.530Z"
},
{
"id": 2,
"physicianId": 1,
"patientId": 2,
"appointmentDate": "2019-01-28T10:06:33.530Z"
}
]
I know there is a method already available to query the Patients of a Physician, but I want to code it myself to learn and also print it in the following format.
My idea is to get all the Appointments having the specific phyId in it and find the patId in those appointment and store it in an array. I then use that array to get the patients from the Patient model. I managed to get the Patient details in a function, but I can only console.log(Patients) but I am not able to display it in the API response.
The following is the format i need it in. (EXPECTED OUTPUT in API response)
Physician:
{
"id": 1,
"name": "Cardiologist"
}
Patients:
[
{
"id": 1,
"name": "Anna Mull"
},
{
"id": 2,
"name": "Paige Trner"
}
]
or any similar format.
I've tried to the same and here is my code.
common/models/physician.js
'use strict';
var app = require('../../server/server');
module.exports = function (Physician) {
Physician.getDetails = function (phyid, cb) {
var Appointments = app.models.Appointment;
var Patient = app.models.Patient;
Physician.findById(phyid, function (err, Physician) {
Appointments.find({ where: { physicianId: phyid } }, function (err, Appointment) {
if (err) {
cb(null, "Errorrrrrrrr", "Errorrrrrr");
}
else {
var patients = [], i = 0;
var patobj= [];
for (i in Appointment) {
patients[i] = Appointment[i].patientId;
//console.log(patients);
Patient.findById(patients[i], function(err, Patients){
if(err){
cb("Error in patients", "--");
}
else{
patobj[i]=Patients;//doesnt have any effect
console.log(Patients);//prints in console
}
});
}
cb(null, Physician, patobj);//only Physician is printed, patobj is empty.
}
});
});
}
Physician.remoteMethod('getDetails', {
http: {
path:
'/:phyid/getDetails',
verb: 'get'
},
accepts: {
arg: 'phyid',
type: 'number'
},
returns: [{
arg: 'Physician',
type: 'Object'
}, {
arg: 'Patient',
type: 'Object'
}]
});
};
I am actually getting this in the API response:
{
"Physician": {
"id": 1,
"name": "Cardiologist"
},
"Patient": []
}
and this in the console:
D:\Project\Project1>node .
Web server listening at: http://localhost:3000
Browse your REST API at http://localhost:3000/explorer
{ name: 'Anna Mull', id: 1 }
{ name: 'Paige Trner', id: 2 }
How am I supposed to get the patient data to be printed in the API response?
You patients are empty because, finding Patients by Id is an asynchronous operation. But the for loop is synchronous. The loop finishes and calls the following line before any of the Patients are found.
cb(null, Physician, patobj);//only Physician is printed, patobj is empty.
You need to wait for all the patients to be found by using either Promise.all or async.each.
I am trying to filter results using Typeahead.js. I can currently filter the results using a field called activity_title. This works fine.
How can I filter my results by a second value? In this case, I would like to select only the results that have a certain value for activity_level. I need to set this when the typeahead is initialised rather than hard coding it into the Bloodhound initialisation (e.g. I don't want to use url: 'api/activity/&range=1,3')
I have the following valid JSON that I access remotely:
{
"meta": [
{
"name": "activity_id",
"table": "table",
"max_length": 4
},
{
"name": "activity_title",
"table": "table",
"max_length": 91
},
{
"name": "activity_level",
"table": "table",
"max_length": 2
}
],
"detail": [
{
"activity_id": "57",
"activity_title": "Help old ladies to cross the road.",
"activity_level": "2"
},
{
"activity_id": "58",
"activity_title": "Help mum with the washing up.",
"activity_level": "3"
},
{
"activity_id": "59",
"activity_title": "Shine my shoes",
"activity_level": "1"
},
{
"activity_id": "60",
"activity_title": "Put the bins out",
"activity_level": "1"
}
]
}
I set up a Bloodhound instance like this:
var activities = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.activity_title);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: '/api/activity/',
filter: function(data) {
return $.map(data['detail'], function(detail) {
return {
activity_id: detail.activity_id,
activity_title: detail.activity_title,
objective_level: detail.objective_level
};
});
}
}
});
I use Typeahead.js to do a lookup on the data as I type.
$( document ).on( "focus", ".typeahead-init", function() {
// + '&range=' + minimum + ',' + maximum
var minimum = $('#group-level-min-1').val();
var maximum = $('#group-level-max-1').val();
$(this).typeahead({
highlight: true
},
{
name: 'activity_title',
displayKey: 'activity',
source: activities.ttAdapter(),
templates: {
header: '<div class="header-name">Activities</div>',
empty: [
'<div class="empty-message">',
'No activities match your search',
'</div>'
].join('\n'),
suggestion: Handlebars.compile('<div class="typeahead-activity" id="typeahead-activity-{{activity_id}}"><strong>{{objective_level}}</strong> - {{activity_title}}</div>')
}
})
//info on binding selection at https://github.com/twitter/typeahead.js/issues/300
.bind('typeahead:selected', function(obj, datum, name) {
var target = $(this).closest('.activity-container');
var activityId = datum['activity_id'];
var url = '/api/activity/id/'+activityId;
$(target).children('.activity-id').val(activityId);
//http://runnable.com/UllA9u8MD5wiAACj/how-to-combine-json-with-handlebars-js-for-javascript-ajax-and-jquery
var raw_template = $('#activity-output').html();
// Compile that into an handlebars template
var template = Handlebars.compile(raw_template);
// Fetch all data from server in JSON
$.get(url,function(data,status,xhr){
$.each(data,function(index,element){
// Generate the HTML for each post
var html = template(element);
// Render the posts into the page
target.append(html);
});
});
});
$(this).removeClass("typeahead-init");
$(this).focus();
});
This has been cobbled together from several answers on Stackoverflow and others. Any help greatly appreciated.