Control OnChangeAction default and revert to default when dynamically adding Widget to Google Add-on card - google-apps-script

Starting a new question to extend Dynamically Add Widget on user click in Gmail Add-on using CardService Appscript (GAS)
I was able to get this to work on a fairly complex UI card I am designing.
There are two things that I can't seem to control:
The selector after OnChangeAction reverts to the default item, so the user looses the context of their selection. Is there a way to persist the selection when rebuilding the card?
There is no way to setup that the value of the default selected item to force an OnChangeAction event to push its value to the UI on the cards initial load.
Anyone able to use this method to accomplish the above?

I came up with a hack for this that seems to work.
I added a default item to the top of the item list with a label of item0
.addItem("Click here to select an existing item to edit","item0", true)
I also created a cache to cache this label. CacheService.getUserCache().put('itemSelected','item0')
When the item is selected from the dropdown I update the Cache with that items value, e.g. item(n).
I then set up two Widgets within a single function itemManagerCard(e, item) that takes the item I want to change as a parameter.
WidgetOne is the base state of the card where the parameters are undefined
if(item === undefined)
WidgetTwo responds when onModeChange(e) returns the target card with the item parameters I want to change
return itemManagerCard(e, item);
if(item != undefined)
I can then populate WidgetTwo with whatever UI data I want to collect through the function's parameters. Such as:
var editItemNumText = 'You are currently editing ${itemNum}'
function itemManagerCard(e, item) {
var selectItemBodyWidget = CardService.newSelectionInput()
.setType(CardService.SelectionInputType.DROPDOWN)
.setTitle('Which item do you want to edit?')
.setFieldName('editItem');
.setOnChangeAction(CardService.newAction().setFunctionName('onModeChange'))
.addItem("Click here to select an existing Item to edit","item0", true)
.addItem("Item 1","item1", false);
//
if (item != undefined)
let itemNum = CacheService.getUserCache().get('itemNum');
var itemText = `You are currently editing ${itemNum}`;
var itemWidget = CardService.newTextParagraph()
.setText(itemText);
}
function onModeChange(e) {
let itemNum = (e.formInput.editItem);
CacheService.getUserCache().put('itemNum', itemNum);
var item = itemArr[itemNum]
return itemManagerCard(e, item);
}
Major drawback is needing to build and maintain the two separate virtually identical Widgets, which can get messy the more complex it gets.

Related

Slate Foundry : Modify parameter of a Dropdown programmatically

In Slate foundry, I would modify parameter of a Dropdown programmatically.
Change the display value or disable value. But it doesn't work.
My code
const select ={{w_selectEndMonthYear}}
select.selectedValue = "202201"
select.selectedDisplayValue = "January 2022"
select.disabled = true
console.log({{w_selectEndMonthYear}})
return {{w_selectEndMonthYear.selectedDisplayValue}}
in my console, I have
{selectedDisplayValue: 'January 2022', selectedValue: '202201', disabled: 'true'}
It seems good, but neither the return nor the display of the widget changed
Thanks for your help
You're on the right track - to template the selected value of a dropdown list programmatically you need to change to the </> tab of the dropdown widget configuration and use Handlebars to provide a valid default selection for both the the selectedValue and selectedDisplayValue parameter.
Note that you cannot set widget properties from inside a Slate Function. You need to return the value (or a json object with multiple values to reference) and then use a Handlebar statement in the widget configuration to template the appropriate values into the widget configuration.
You can read a bit about this in the context of resetting widget selection state to default values in this section of the documentation.
You can also search your Foundry instance for some helpful Slate tutorials - find the Foundry Training and Resources project and navigate to the Reference Examples/App Building In Slate/1. Tutorials folder to find a series of interactive Slate apps demonstrating the many patterns for using Handlebars, Functions, and other Slate components.
The easiest way i via Functions, also to deliver the the content of dropdown.
Just a hint, to sort the content you need 'transformColumnSchemaToRowSchema' and 'transformRowSchemaToColumnSchema'
Here is a Slate JavaScript snippet for enabling/disabling a button:
Similar you could do this for drop-downs i think.
// Collect Inputs for Validation
// -----------------------------
var inputs = {
UserID: _.trim(_.toUpper({{w_pD_AdmUserAdd_UserID.text}})),
Foundry: _.trim({{w_pD_AdmUserAdd_Foundry.placeholder}}),
NameFirst: _.trim(_.toUpper({{w_pD_AdmUserAdd_NameFirst.text}})),
NameLast: _.trim(_.toUpper({{w_pD_AdmUserAdd_NameLast.text}})),
Email: _.trim(_.toUpper({{w_pD_AdmUserAdd_Email.text}})),
Team: {{w_pD_AdmUserAdd_Team.selectedValue}},
Company: {{w_pD_AdmUserAdd_Company.selectedValue}}
}
// Initialize Variables
// --------------------
var disable = false;
var messages = [];
// Implement Form Validation Checks
// --------------------------------
// Check if all the required fields have a value
if (!(inputs.UserID &&
inputs.Foundry &&
inputs.NameFirst &&
inputs.NameLast &&
inputs.Email &&
inputs.Team &&
inputs.Company
)){
disable = true;
messages.push("Please complete all required fields.");
}
var email_regex = /^(([^<>()[\]\\.,;:\s#\"]+(\.[^<>()[\]\\.,;:\s#\"]+)*)|(\".+\"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (inputs.Email && !email_regex.test(inputs.Email)){
messages.push('Please enter a valid email for "Email"')
disable = true;
}
var data_users = {{s_buyers.data}}
var index_email = data_users.str_email.indexOf(inputs.Email)
if (inputs.Email && index_email !== -1){
disable = true;
messages.push('This Email already exist')
}
var index_userid = data_users.str_buyer_id.indexOf(inputs.UserID)
if (inputs.UserID && index_userid !== -1){
disable = true;
messages.push('This User ID already exist')
}
return {
inputs,
disable,
messages
}

ngOnChanges only works when it's not the same value

So basically I have a modal component with an input field that tells it which modal should be opened (coz I didn't want to make a component for each modal):
#Input() type!:string
ngOnChanges(changes: SimpleChanges): void {
this.type = changes["type"].currentValue;
this.openModal();
}
that field is binded to one in the app component:
modalType = "auth";
HTML:
<app-modal [type] = modalType></app-modal>
In the beginning it's got the type "auth" (to login or register), but when I click on an icon I want to open a different modal, I do it like so:
<h1 id="options-route"
(click) ="modalType = 'settings'"
>⚙</h1>
but this only works the first time, when modalType already has the value "settings" the event doesn't trigger even though the value has technically changed
I think the problem is that it's the same value because i tried putting a button that does the exact same thing but with the value "auth" again and with that it was clear that the settings button only worked when tha last modal opened was auth and viceversa
any ideas? I want to be able to open the settings modal more than once consecutively possibly keeping onChange because ngDoCheck gets called a whole lot of times and it slows down the app
You need to include the changeDetectorRef, in order to continue in this way.
More about it https://angular.io/api/core/ChangeDetectorRef
Although, a better and a faster alternative is the use of a behavior Subject.
All you have to do is create a service that makes use of a behavior subject to cycle through each and every value exposed and then retrieve that value in as many components as you want. To do that just check for data changes in the ngOnInit of target component.
You may modify this for implementation,
private headerData = new BehaviorSubject(new HeaderData());
headerDataCurrent = this.headerData.asObservable();
changeHeaderData(headerDataNext : HeaderData) {
this.headerData.next(headerDataNext)
console.log("subscription - changeUserData - "+headerDataNext);
}
Explanation:
HeaderData is a class that includes the various values that can be shared with respective data types.
changeHeaderData({obj: value}), is used to update the subject with multiple values.
headerDataCurrent, an observable has to be subscribed to in the target component and data can be retrieved easily.
I mean i'm too l-a-z-y to use your slightly-not-so-much-tbh complicated answers so I just did this:
I added a counter that tops to 9 then gets resetted to 0 and I add it to the value
screwYouOnChangesImTheMasterAndYouShallDoMyBidding = 0;
//gets called onClick
openSettings(){
if(this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding === 9){
this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding = 0;
}
this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding = this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding + 1;
this.modalType = "settings"+this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding;
}
then in the child component I just cut that last character out:
ngOnChanges(changes: SimpleChanges): void {
let change = changes["type"].currentValue as string;
change = change.substring(0, change.length - 1);
this.type = change;
this.openModal();
}
works like a charm 😂

Tabulator - Cell.setValue creating recursive loop for custom formatter, table not saving data

I'll try to be as brief as possible as my situation is unique and I am a fairly new programmer.
TL:DR - Cannot get the row to update, table does not remember initial values when editing, cell.setValue() creates recursive loop, cell.getRow().update() seems to do nothing, row does not resize in the event of editing working (still does not hold on to previous values)), unsure if mutator or cellEdit() callback are potential solutions.
I have JSON data populating my table from an AJAX call to a Java backend. One of the columns can either be one item, a list of multiple items, or none at all. I am using a custom formatter to populate that cell with the list of items, followed by icons to remove those items...
Some text here X
Some other text X
And some more X
When clicking the X icon, it should remove the item, and when clicking the text, it will drop down a list to choose another option.
Some text here X
Some other text X
Added another line X <----removed the previous item and selected a new
The problem I am having is that the table does not continuously hold onto previous values nor concatenates them with new selections. I have written some code to take care of the concatenation issue, but it still does not save that info within the table. I have tried to use cell.setValue() but this creates a recursive loop as all my code is within the custom formatter. I have also tried cell.getRow().update() but this seems to do nothing.
Also, clicking into the drop down and clicking away without selecting a value will still send the previous value as though it was clicked again. I was able to get it to function once, but it only changed the most recent values and did not include the original ones and it did not resize the row to be smaller.
I am unsure if a custom mutator would solve my issue, or even how to get the mutator to interact with the formatter to update the cell every time a change has been made. I also have a cellEdited: function(){} callback that is going to be designed to take the entire row of information as a JSON object to save it within a DAO and considered that I might be able to solve the issue there?
Here is my code for that column. This currently will load the page with the initial design yet does not work with editing. "cell.setValue()" and "cell.getRow().update()" are currently not included because they weren't working properly...
title: "Groups",
field: "associatedGroups",
variableHeight: true,
headerFilter: true,
headerFilterPlaceholder: "Search by Group...",
formatter: function (cell, formatterParams, onRendered) {
var data = [];
var newValue = cell.getValue();
var oldValue = cell.getOldValue();
var newValueIsArray = $.isArray(newValue);
var oldValueIsArray = $.isArray(oldValue);
if (oldValue !== null && newValue !== null) {
if (!oldValueIsArray && newValueIsArray) {
data = data.concat(newValue);
data.unshift(oldValue);
} else if (oldValueIsArray && !newValueIsArray) {
data = data.concat(oldValue);
data.push(newValue);
} else {
data.push(oldValue);
data.push(newValue);
}
} else {
data = newValue;
}
<!-------------------Value of "data" used here to determine visual layout----------------->
},
editor: "select",
responsive:
0,
editorParams:
{
values: groupNames
}
,
sorter: "string",
width:
212
},

Kendo Asp.net MVC Grid Batch Mode Calculated Column Display does not update

Using Kendo Asp.net MVC Grid in Ajax Batch Mode.
Having three columns - Qty, Rate, Total. Need to achieve real-time calculation on change. Written following function to update data.
function grid_change(e) {
if (e.action === "itemchange") {
var item = e.items[0];
item.Total = item.Qty * item.Rate;
}
}
But the column does not reflect the calculated value until focus is moved over it. How to update / refresh the cell display as soon as the change event is completed?
Changed the calculation statement (see below) and all the related columns started reflecting changes immediately after the focus was moved out.
function grid_change(e) {
if (e.action === "itemchange") {
var item = e.items[0];
item.set("Total", item.Qty * item.Rate); // Changed to this
}
}
Note: The columns that you are going to update at real-time must be editable.

angularjs save rendered values in html in a variable

I hope someone can help me with this, It's a strange question maybe as I didn't find an answer online.
I call the database and retrieve a list (in json) of items.
Then in angularjs,I render this list by extracting relevant pieces of data(name,age,etc) and show it properly in a table as a list of rows.
I have then an edit button that takes me to another page where I want to put a dropdown list.
What I want to know if is possible to add to that dropdown list the rendered list I previously created in my previous page.
is it possible to save the previously rendered list in a variable and then use that variable in the dropdown?
thank you
You could store the list within a controller and make this data availablte to this dropdown, I think.
Instead of trying to query for the list, add the list to the template, get the list from the template and render somewhere else, I'd suggest query for the list, save the list in a service , and then when you want to use that list again, get it from the service. Something like:
service:
var services = angular.module('services');
services.factory('getListService',['$http',function($http){
var getListOfStuff = function(){
//call to database
return //your json
};
var extractNameAgeEtc = function(){
var myListOfStuff = //get list of stuff from $http or database
var myListOfNameAgeEtc = //make a list of tuples or {name,age,etc} objects
return myListOfNameAgeEtc;
};
return {
extractNameAgeEtc : extractNameAgeEtc
};
}]);
controllers:
angular.module('controllers',['services']);
var controllersModule = angular.module('controllers');
controllersModule.controller('tableRenderController',['getListService','$scope',function(getListService,$scope){
//use this with your table rendering template, probably with ng-repeat
$scope.MyTableValue = getListService.extractNameAgeEtc();
}]);
controllersModule.controller('dropdownRenderController',['getListService','$scope',function(getListService,$scope){
//use this with your dropdown rendering template, probably with ng-repeat
$scope.MyDropDownValue = getListService.extractNameAgeEtc();
}]);