How to check if cell if empty in Office.js - excel

I have just started with Office Addins and I'm experimenting with the functionalities. I have several VBA Userforms that I would want to replace with popups from the Office add-in.
I am using the following code to enter a string into a cell(nothing fancy, I know) but I would want to check if the cell if empty before passing the value. If it is, enter (arg.message).
the problem I have encountered:
with if (range.value == "") the value is being set in "A4" even if "A3" if empty;
with if (range.value == " ") the value is not being entered in any cells.
Can anyone give me an example of how to check if a cell is empty?
I know it seems trivial but I have only found examples of how to check with col and row numbers for conditional formatting. I am trying to test all these functionalities to be able to start moving stuff from VBA to OfficeJS.
Thanks,
Mike
function processMessage(arg) {
console.log(arg.message);
$('#user-name').text(arg.message);
dialog.close();
Excel.run(function (context) {
var sheet = context.workbook.worksheets.getItem("Sheet1");
var range = sheet.getRange("A3");
if (range.value == "") {
range.values = (arg.message);
range.format.autofitColumns();
return context.sync();
} else {
range.getOffsetRange(1, 0).values = (arg.message)
return context.sync();
}
}).catch(errorHandler);
}
PS: the whole code in case there is something wrong somewhere else
(function () {
"use strict";
// The initialize function must be run each time a new page is loaded.
Office.initialize = function (reason) {
$(document).ready(function () {
// Add a click event handler for the button.
$('#popup-button').click(opensesame);
$('#simple-button').click(function () {
Office.context.document.getSelectedDataAsync(Office.CoercionType.Text,
function (result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
$("#banner-text").text('The selected text is: "' + result.value + '"');
$("#banner").show(result.value);
console.log()
} else {
$("#banner-text").text('Error: ' + result.error.message);
$("#banner").show();
}
});
});
$("#banner-close").click(function () { $("#banner").hide(); });
$("#banner").hide();
});
}
let dialog = null;
function opensesame() {
Office.context.ui.displayDialogAsync(
'https://localhost:3000/popup.html',
{ height: 35, width: 25 },
function (result) {
dialog = result.value;
dialog.addEventHandler(Microsoft.Office.WebExtension.EventType.DialogMessageReceived, processMessage);
}
);
}
function processMessage(arg) {
console.log(arg.message);
$('#user-name').text(arg.message);
dialog.close();
Excel.run(function (context) {
var sheet = context.workbook.worksheets.getItem("Sheet1");
var range = sheet.getRange("A3");
if (range.value == "") {
range.values = (arg.message);
range.format.autofitColumns();
return context.sync();
} else {
range.getOffsetRange(1, 0).values = (arg.message)
return context.sync();
}
}).catch(errorHandler);
}
})();

The Range object has a values property, but not a value property. So range.value in your condition test is undefined which does not match an empty string; hence the else clause runs.
A couple of other things:
Your condition tries to read a property of the range object. You have to load the property and call context.sync before you can read the property.
The value of the range.values property is a two-dimensional array (although it may have a single value in it if the range is a single cell). It is not a string, so comparing it with an empty string will always be false.
If I understand your goal, I think you should be testing with whether range.values (after you load it and sync) has an empty string in it's only cell. For example, if (range.values[0][0] === ""). Even better from a performance standpoint is to load the range.valueTypes property (and sync) and then compare like this: if (range.valueTypes[0][0] === Excel.RangeValueType.empty).

Related

Tabulator - How to set value inside cellEdited function

I am using the Tabulator plugin and am using the editorParams function to select from a list of options. If a value isn't selected (eg: Cancelled) I want it to revert to the old (previous) cell value and do nothing, but calling cell.setValue() keeps retriggering the cellEdit function and it gets stuck in a loop.
table.on('cellEdited', function(cell) {
var cellOldValue = cell.getOldValue();
var cellNewValue = cell.getValue();
var row = cell.getRow();
var index = row.getIndex();
if (cellNewValue == 'none-selected') {
cell.setValue(cellOldValue);
} else {
if (confirm('Are you sure?')) {
// ok, do something
} else {
cell.setValue(cellOldValue);
}
}
});
This just keeps triggering the prompt. Any solutions, thank you?

Excel.js custom function caller cell number format

I'm trying to number format the caller cell for a custom function, specifically to replace the scientific notation with a numeric format for big numbers and then auto fit the column width.
An idea is to check the cell text for presence of "E", but the issue is that the formatting code seems to run before the result is written to the cell (which kind of makes sense, honestly), so I'm doing a comparison and set the cell format accordingly. Setting the cell format works fine (it doesn't need the result written to the cell), but auto fitting the column width doesn't.
Here is the custom function code:
getData returns a number (or an error string) from an API call
formatNumber should set the cell number format and autofit the column width, based on the returned number.
async function Test(symbol, metric, date, invocation) {
const address = invocation.address;
return await getData(symbol, metric, date)
.then(async (result) => {
if (!isNaN(result) && result > 99999999999) {
await formatNumber(address);
}
return result;
})
.catch((error) => {
console.log("error: " + error);
return error;
});
}
Here is the formatNumber code.
range.text returns #BUSY, which means the data is still retrieved from the API when the function runs. Due to this, autofitColumns will set the column size based on "#BUSY" string length.
async function formatNumber(address) {
await Excel.run(async (context) => {
const formats = [["#,##0"]];
const range = context.workbook.worksheets.getActiveWorksheet().getRange(address);
range.load("text");
await context.sync();
console.log("range.text: " + range.text);
range.load("numberFormat");
await context.sync();
range.numberFormat = formats;
range.format.autofitColumns();
await context.sync();
});
}
Any ideas?
Thank you for your time,
Adrian
The custom functions return value will be set to the cell after the function is returned.
I suggest your add-in register an onChanged event handler on the worksheet, and call format.autofitColumns() to handle the event;

Fabric js Image Filters

Hello i am trying to create img filter there are Range input sliders from which user select filter value and then applied to selected image object its working but every time input changes it added a new filter and multiply how can i resolve this issue below is my code:
$('.img-fillter-controller .range-field').on("change", "input", function () {
t = $(this).data("filter");
v = $(this).val();
o = activeCanvas.getActiveObject();
switch(t) {
case 'brightness':
f = new fabric.Image.filters.Brightness({brightness: v/100 });
applyImgFilter(f);
return;
case 'saturation':
return;
case 'contrast':
return;
case 'blur':
return;
case 'exposure':
return;
case 'colorify':
return;
case 'hue':
}
});
function applyImgFilter(f) {
o.filters.push(f);
o.applyFilters();
activeCanvas.renderAll();
}
For apply filter properly on image, we need to fix filter index or can say that assign filter by static index.
You should replace This funciton :
function applyImgFilter(f) {
o.filters.push(f);
o.applyFilters();
activeCanvas.renderAll();
}
With New funciton :
function applyImgFilter(f) {
o.filters[0] = f
o.applyFilters();
activeCanvas.renderAll();
}

Append the formula of the selected cell

I want to realise the following scenario: a user selects a cell holding a formula, clicks on the test button of my add-in, then my test function reads the formula of the selected cell, append +RAND() to it, and write it back to the workbook.
The following code reads well the formula of the selected cell, but it does not write back well. I am not sure if (the second) return ctx.sync() is correctly used.
Additionally, I don't know if I should use getSelectedDataAsync and setSelectedDataAsync (rather than getSelectedRange) in the whole scenario.
Could anyone help?
(function() {
"use strict";
Office.initialize = function(reason) {
$(document).ready(function() {
app.initialize();
$('#test').click(test);
});
}
;
function test() {
Excel.run(function(ctx) {
var selectedRange = ctx.workbook.getSelectedRange();
selectedRange.load(["formulas"]);
return ctx.sync().then(function() {
console.log(selectedRange.formulas[0][0]);
var x = selectedRange.formulas[0][0] + "+RAND()";
selectedRange.formulas[0][0] = x;
return ctx.sync();
})
}).then(function() {
console.log("done");
}).catch(function(error) {
console.log("Error: " + error);
});
}
})();
The bug is that you are trying to assign to an individual element in the formula context object. Instead use:
selectedRange.formulas = x;
or
selectedRange.formulas = [[x]];

Handsontable numeric cell globalization

I'm relatively new to js and now have to implement a handsontable into our project.
This worked well so far, but I am hitting a roadblock with globalization.
Basically, we use comma as a decimal seperator, but when I try and copy something like "100,2" into a cell designated as 'numeric,' it will show as 1002.
If the same value is entered in a cell designated as 'text' and the type is changed to numeric afterwards, the value will be shown correctly.
For this I already had to add 'de' culture to the table sourcecode.(basically copying 'en' and changing the values currently relevant to me.)
numeral.language('de', {
delimiters: {
thousands: '.',
decimal: ','
},//other non-relevant stuff here
When I copy the values directly from the table and insert them to np++ they show as 100.2 etc. However, when inserting them into handsontable the arguments-array looks as follows:
[Array[1], "paste", undefined, undefined, undefined, undefined]
0: Array[4]
0: 1 //row
1: 1 //column
2: "100.2" //previous value
3: 1002 //new value
Here's what I have tried currently:
hot.addHook("beforeChange", function () {
if (arguments[1] === "paste") {
hot.updateSettings({
cells: function (row, col, prop) {
var cellProperties = {
type: 'numeric',
language: 'en'
};
return cellProperties;
}
});
//hot.updateSettings({
// cells: function (row, col, prop) {
// var cellProperties = {
// type: 'text',
// };
// return cellProperties;
// }
//});
}
}, hot);
hot.addHook("afterChange", function () {
if (arguments[1] === "paste") {
ChangeMatrixSettings(); //reset cell properties of whole table
}
}, hot);
I hope I've made my problem clear enough, not sure if I missed something.
Are there any other ways to get the correct values back into the table? Is this currently not possible?
Thanks in advance.
You asked more than one thing, but let me see if I can help you.
As explained in handsontable numeric documentation, you can define a format of the cell. If you want '100,2' to be shown you would format as follows
format: '0.,'
You can change that to what you really need, like if you are looking for money value you could do something like
format: '0,0.00 $'
The other thing you asked about is not on the latest release, but you can check it out how it would work here
I have since implemented my own validation of input, due to other requirements we have for the table mainly in regards to showing invalid input to user.
function validateInputForNumeric(parameter) {
var value = parameter[3];
var row = parameter[0];
var col = parameter[1];
if (decimalSeperator === '') {
var tmpculture = getCurrCulture();
}
if (value !== null && value !== "") {
if (!value.match('([a-zA-Z])')) {
if (value.indexOf(thousandSeperator) !== -1) {
value = removeAndReplaceLast(value, thousandSeperator, ''); //Thousandseperators will be ignored
}
if (value.indexOf('.') !== -1 && decimalSeperator !== '.') {
//Since numeric variables are handled as '12.3' this will customize the variables to fit with the current culture
value = removeAndReplaceLast(value, '.', decimalSeperator);
}
//Add decimalseperator if string does not contain one
if (numDecimalPlaces > 0 && value.indexOf(decimalSeperator) === -1) {
value += decimalSeperator;
}
var index = value.indexOf(decimalSeperator)
var zerosToAdd = numDecimalPlaces - (value.length - index - 1);
for (var j = 0; j < zerosToAdd; j++) {
//Add zeros until numberOfDecimalPlaces is matched for uniformity in display values
value += '0';
}
if (index !== -1) {
if (numDecimalPlaces === 0) {
//Remove decimalseperator when there are no decimal places
value = value.substring(0, index)
} else {
//Cut values that have to many decimalplaces
value = value.substring(0, index + 1 + numDecimalPlaces);
}
}
if (ErrorsInTable.indexOf([row, col]) !== -1) {
RemoveCellFromErrorList(row, col);
}
} else {
AddCellToErrorList(row, col);
}
}
//console.log("r:" + row + " c:" + col + " v:" + value);
return value;
}
The inputParameter is an array, due to handsontable hooks using arrays for edit-events. parameter[2] is the old value, should this be needed at any point.
This code works reasonably fast even when copying 2k records from Excel (2s-4s).
One of my main hindrances regarding execution speed was me using the handsontable .getDataAtCell and .setDataAtCell methods to check. These don't seem to handle large tables very well ( not a critique, just an observation ). This was fixed by iterating through the data via .getData method.

Resources