Add empty option to List Validator - spreadjs

I'm trying to add the option to my users that, on a List Validator, to allow select any of the options or a blank option. Spreadjs has the IgnoreBlanks setting, which I use, so when the user uses the delete key or the backspace and deletes the cell it validates correctly.
However, I would love to use the same functionality as in Excel, which allows blank options in the list validator, in part of the list.
I've tried to target the <select> element that holds the list and programmatically add the empty element, however, it crashes after the user selects the empty option.
I've also tried to add different escaped characters to the list. If I select a character that represents an empty string or a tab, it won't add a new option to the list. If I use any strange character, or even the null character \0 you get a new option to select, but the content is that typical rectangle you see when your font doesn't have the character you're trying to display.
I've also tested using a regular ListValidator like in the example pages, not our custom functionality and doesn't work either.
https://www.grapecity.com/demos/spread/JS/TutorialSample/#/demos/basicDataValidator
I have also tried creating a FormulaListValidator, and if my range has empty cells I could then get an empty option on my list, however, because the range may have duplicates, I get duplicated options.

After researching a little bit I found a workaround in a different language which I adapted to Typescript (Angular 6)
export const getListValidatorFromArray = (spread: GC.Spread.Sheets.Workbook, data: any[]) => {
// saving validation list values in a hidden sheet
spread.addSheet(spread.getSheetCount());
const sheet = spread.getSheet(spread.getSheetCount() - 1);
sheet.visible(false);
for (let i = 0; i < data.length; i++) {
sheet.setValue(i, 0, data[i]);
}
// create validator based on the values
const dv = GC.Spread.Sheets.DataValidation.createFormulaListValidator(
'=' + sheet.name() + '!$A$1:' + sheet.name() + '!$A$' + data.length
);
return dv;
};
Note: This creates an extra sheet for each validator you create. Makes sure you reuse them as much as possible (i.e. assigning it to a variable when it's created, and reusing the variable for other columns/rows that use the same one).

Related

Google Sheets: How can I extract partial text from a string based on a column of different options?

Goal: I have a bunch of keywords I'd like to categorise automatically based on topic parameters I set. Categories that match must be in the same column so the keyword data can be filtered.
e.g. If I have "Puppies" as a first topic, it shouldn't appear as a secondary or third topic otherwise the data cannot be filtered as needed.
Example Data: https://docs.google.com/spreadsheets/d/1TWYepApOtWDlwoTP8zkaflD7AoxD_LZ4PxssSpFlrWQ/edit?usp=sharing
Video: https://drive.google.com/file/d/11T5hhyestKRY4GpuwC7RF6tx-xQudNok/view?usp=sharing
Parameters Tab: I will add words in columns D-F that change based on the keyword data set and there will often be hundreds, if not thousands, of options for larger data sets.
Categories Tab: I'd like to have a formula or script that goes down the columns D-F in Parameters and fills in a corresponding value (in Categories! columns D-F respectively) based on partial match with column B or C (makes no difference to me if there's a delimiter like a space or not. Final data sheet should only have one of these columns though).
Things I've Tried:
I've tried a bunch of things. Nested IF formula with regexmatch works but seems clunky.
e.g. this formula in Categories! column D
=IF(REGEXMATCH($B2,LOWER(Parameters!$D$3)),Parameters!$D$3,IF(REGEXMATCH($B2,LOWER(Parameters!$D$4)),Parameters!$D$4,""))
I nested more statements changing out to the next cell in Parameters!D column (as in , manually adding $D$5, $D$6 etc) but this seems inefficient for a list thousands of words long. e.g. third topic will get very long once all dog breed types are added.
Any tips?
Functionality I haven't worked out:
if a string in Categories B or C contains more than one topic in the parameters I set out, is there a way I can have the first 2 to show instead of just the first one?
e.g. Cell A14 in Categories, how can I get a formula/automation to add both "Akita" & "German Shepherd" into the third topic? Concatenation with a CHAR(10) to add to new line is ideal format here. There will be other keywords that won't have both in there in which case these values will just show up individually.
Since this data set has a bunch of mixed breeds and all breeds are added as a third topic, it would be great to differentiate interest in mixes vs pure breeds without confusion.
Any ideas will be greatly appreciated! Also, I'm open to variations in layout and functionality of the spreadsheet in case you have a more creative solution. I just care about efficiently automating a tedious task!!
Try using custom function:
To create custom function:
1.Create or open a spreadsheet in Google Sheets.
2.Select the menu item Tools > Script editor.
3.Delete any code in the script editor and copy and paste the code below into the script editor.
4.At the top, click Save save.
To use custom function:
1.Click the cell where you want to use the function.
2.Type an equals sign (=) followed by the function name and any input value — for example, =DOUBLE(A1) — and press Enter.
3.The cell will momentarily display Loading..., then return the result.
Code:
function matchTopic(p, str) {
var params = p.flat(); //Convert 2d array into 1d
var buildRegex = params.map(i => '(' + i + ')').join('|'); //convert array into series of capturing groups. Example (Dog)|(Puppies)
var regex = new RegExp(buildRegex,"gi");
var results = str.match(regex);
if(results){
// The for loops below will convert the first character of each word to Uppercase
for(var i = 0 ; i < results.length ; i++){
var words = results[i].split(" ");
for (let j = 0; j < words.length; j++) {
words[j] = words[j][0].toUpperCase() + words[j].substr(1);
}
results[i] = words.join(" ");
}
return results.join(","); //return with comma separator
}else{
return ""; //return blank if result is null
}
}
Example Usage:
Parameters:
First Topic:
Second Topic:
Third Topic:
Reference:
Custom Functions
I've added a new sheet ("Erik Help") with separate formulas (highlighted in green currently) for each of your keyword columns. They are each essentially the same except for specific column references, so I'll include only the "First Topic" formula here:
=ArrayFormula({"First Topic";IF(A2:A="",,IFERROR(REGEXEXTRACT(LOWER(B2:B&C2:C),JOIN("|",LOWER(FILTER(Parameters!D3:D,Parameters!D3:D<>""))))) & IFERROR(CHAR(10)&REGEXEXTRACT(REGEXREPLACE(LOWER(B2:B&C2:C),IFERROR(REGEXEXTRACT(LOWER(B2:B&C2:C),JOIN("|",LOWER(FILTER(Parameters!D3:D,Parameters!D3:D<>""))))),""),JOIN("|",LOWER(FILTER(Parameters!D3:D,Parameters!D3:D<>""))))))})
This formula first creates the header (which can be changed within the formula itself as you like).
The opening IF condition leaves any row in the results column blank if the corresponding cell in Column A of that row is also blank.
JOIN is used to form a concatenated string of all keywords separated by the pipe symbol, which REGEXEXTRACT interprets as OR.
IFERROR(REGEXEXTRACT(LOWER(B2:B&C2:C),JOIN("|",LOWER(FILTER(Parameters!D3:D,Parameters!D3:D<>""))))) will attempt to extract any of the keywords from each concatenated string in Columns B and C. If none is found, IFERROR will return null.
Then a second-round attempt is made:
& IFERROR(CHAR(10)&REGEXEXTRACT(REGEXREPLACE(LOWER(B2:B&C2:C),IFERROR(REGEXEXTRACT(LOWER(B2:B&C2:C),JOIN("|",LOWER(FILTER(Parameters!D3:D,Parameters!D3:D<>""))))),""),JOIN("|",LOWER(FILTER(Parameters!D3:D,Parameters!D3:D<>"")))))
Only this time, REGEXREPLACE is used to replace the results of the first round with null, thus eliminating them from being found in round two. This will cause any second listing from the JOIN clause to be found, if one exists. Otherwise, IFERROR again returns null for round two.
CHAR(10) is the new-line character.
I've written each of the three formulas to return up to two results for each keyword column. If that is not your intention for "First Topic" and "Second Topic" (i.e., if you only wanted a maximum of one result for each of those columns), just select and delete the entire round-two portion of the formula shown above from the formula in each of those columns.

AppleScript : keep word only from list

How can I only keep words / numbers from list?
I use the follow script and I want to count unique payment methods, but I have unwanted values on my list
I also tried to use "word of item" but this is not working
I try different version of the script but always having an issue
set PaymentMethods to {}
set UniquePaymentMethods to {}
set walletPayement to {}
set mobilePayemnt to {}
tell application "Safari"
set PaymentMethods to do JavaScript "var outPut=[]; var arr=document.getElementsByClassName('sortable Summary');for (var i in arr) {outPut.push(arr[i].innerHTML)};outPut;" in current tab of window 1
end tell
repeat with x from 1 to count of items of PaymentMethods
set n to item x of PaymentMethods
if n is in PaymentMethods and n is not in UniquePaymentMethods and n does not contain "<a href=\"/finance/Support.ok" and n does not contain "Safari" and n does not contain "None" then set end of UniquePaymentMethods to n
end repeat
e.g of the result :
{"
PayPal
", "
MasterCard (999)
", "
MasterCard (888)
", application "Safari"}
It's good that you're generally mindful about supplying a sample of the input, as you've done here with the list that I presume relates to PaymentMethods. But don't forget to also provide the output you want to get back at the end. It took me a while to realise "keep words / numbers from list" was actually referring to items of class text (or string), which was about my fifth interpretation of what was being asked.
Your solution appears to do what you want, but it doesn't really because you've had to use that very case-specific set of conditionals, i.e.:
if n is in PaymentMethods ¬
and n is not in UniquePaymentMethods ¬
and n does not contain ("<a href=\"/finance/Support.ok") ¬
and n does not contain ("Safari") ¬
and n does not contain "None" then ¬
set end of UniquePaymentMethods to n
Without that, the item application "Safari", for instance, would simply be coerced into class text, which would return "Safari" in your resulting list.
So, while your request was to filter a list's data types to contain only text class items, your proposed fix, i.e.:
set n to item x of PaymentMethods as text
doesn't isolate text items; it coerces items of any class into text.
The way to filter a list by class type is to use this syntax:
get every [class] in [list]
where [class] is the AppleScript class you want to keep (discarding any items not of this class), and [list] is the list (or variable that references a list), e.g.:
every text in {1, "Hello", true, missing value, "2", text, number, pi}
--> {"Hello", "2"}
every number in {1, "Hello", true, missing value, "2", text, number, pi}
--> {1, 3.14159265359}
every application in {"PayPal", "MasterCard (999)", "MasterCard (888)", application "Safari"}
--> {application "Safari"}
every text in {"PayPal", "MasterCard (999)", "MasterCard (888)", application "Safari"}
--> {"PayPal", "MasterCard (999)", "MasterCard (888)"}
But, there's a lot you can do to simplify your script by having the initial JavaScript code do the heavy lifting so it returns a cleaner list of items that will be simpler for AppleScript to process, or eliminate any need for additional processing.
Your list items have a lot of whitespace in them. In JS, use trim().
Your AppleScript conditionals reference "innerHTML property without ever wanting to use any actual HTML. You've been consistently interested only in the text that gets printed on your webpage. Therefore, in JS, experiment with innerText applied to the right HTML element and you'll probably be able to isolate the payment methods text straight away.
You appear to want your list to contain unique items. In JS, use sets, which are basically arrays with unique items.
Implementing these three recommendations, together with the above method of filtering by text class, would look something like:
tell application "Safari" to tell document 1 to tell (do JavaScript "
[...new Set( document.getElementsByClassName('sortable Summary') )]
.map( x => x.innerText.trim() );") ¬
to set PaymentMethods to every text
As I don't know what your webpage source looks like, you may need to make slight adjustments to how you want to transform innerText besides trim(), and/or consider whether getElementsByClassName('sortable Summary') is the best way to get access to the parts of the HTML DOM tree you require. querySelectorAll() is a much more powerful method to use, which I have mentioned before.
Found it, need to add the kind for each item
set n to item x of PaymentMethods as text

Tkinter: Adding a entry at index 3 without there being one at index 1 or 2

The problem im getting is if type something in my entry box it first fills index 1 and 2 of the listbox before finally typing into the 3rd index.
def country_get(event):
listbox.delete(3)
listbox.insert(3, country_label.cget('text') + event.widget.get() + '\n')
title_text=StringVar()
entry_country=Entry(master, bg="wheat3", fg="dark slate gray", textvariable=title_text)
entry_country.bind('<KeyRelease>', country_get)
entry_country.grid(row=4, column=1)
I want to be able to type at any index of the listbox whether it be the 3rd or 5th, without having anything at the previous index's.
You can't do what you want. The listbox isn't designed to have empty rows. If you want empty rows, you will need to insert empty strings.
Depending on how many entries I'm using or need, i just insert empty strings like so:
listbox.insert(0, "")
listbox.insert(1, "")
listbox.insert(2, "")
listbox.insert(3, "")
listbox.insert(4, "")
It appears that the listbox is empty when it fact it is just filled with empty strings. There are most likely other ways to get around this, but for what I need my program to do, this is what worked for me.

1-indexed Model in QCombobox

I have a 1-indexed (readonly-)model and want to use it for a combobox.
I parse the data (comes from a file-parser) and have for example:
1: Variable Number 1 and that will be my first item, next
2: Variable Number 2 and so on.
When I click on an item the currentIndex()-method from QCombobox will give me a 0-indexed int, so my problem is:
I don't want to write everytime I parse a file +1 respectively -1 when writing back to the file (although the model is readonly, I can alter the data in the file). (I have nearly 30 UIs where I need the model, and for every UI I have to parse other data)
I currently use something like:
virtual int currentIndex() const { return QComboBox::currentIndex() + 1; }
virtual void setCurrentIndex(int index) { QComboBox::setCurrentIndex(index-1); }
I know that this is not ideal, because (set-)currentIndex is not virtual. But to avoid +/-1 I used this for now.
Does anybode have a good suggestion for this problem?
If you have a custom model you could add a role that returns the "real" index value.
If you just use strings to fill the combobox, you could use the setItemData() and itemData() methods to associated your reference value.
E.g.
comboBox->addItem("Number 1", 1);
and
int refValue = comboBox->itemData(comboIndex).toInt();
The associated data can be anything that can be stored in a QVariant.

Ultragrid Export Sort Order / Indicator

To make a long story short, we have a legacy application which displays an infragistics grid where users can export the grid display. The issue I'm having is that there's a particular order in which they want the export to occur, and if I set the order within the grid view prior to export, it retains this order, however if I try to force it "on export", it doesn't seem to work despite trying to set it. Here's my code (VB), as you can see just prior to import I try to set the "sortindicator", but I suspect I'm missing something.
Dim FileName As String
Dim I As Integer
I = 1
FileName = "C:\ReconciliationReport.xls"
While System.IO.File.Exists(FileName)
FileName = "C:\ReconciliationReport_" & I & ".xls"
I = I + 1
End While
grdReconciliationReport.DisplayLayout.Bands(0).Columns("ReconciliationOrder").SortIndicator = Infragistics.Win.UltraWinGrid.SortIndicator.Ascending
UltraGridExcelExporter.Export(grdReconciliationReport, FileName)
During the export of the grid UltraGridExcelExporter creates its own copy of the Layout. This is done exactly to allow you to sort, hide, delete and any other action in the layout without changing the actual grid. To sort the grid by any column you need to handle ExportStarted event. The event argument contains reference to the clonned layout. You can use code like this:
Private Sub UltraGridExcelExporter_ExportStarted(sender As Object, e As ExcelExport.ExportStartedEventArgs) Handles UltraGridExcelExporter1.ExportStarted
Dim sortedCol As UltraGridColumn = e.Layout.Bands(0).Columns(1)
e.Layout.Bands(0).SortedColumns.Add(sortedCol, False, False)
End Sub