Ag-grid: clear cell when "agSelectCellEditor" is used - javascript

As stated in the ag-grid documentation:
The default editor will clear the contents of the cell if Backspace or
Delete are pressed.
But this doesn't work when the "agSelectCellEditor" is used. If you press Delete or Backspace the cell will enter in EDIT mode and you can choose only the values that are provided as options.
Any idea how can I achieve the same behavior?

I found an article that explain how to write the delete cells logic. This works also for multiple cells. Please check this article: https://blog.ag-grid.com/deleting-selected-rows-and-cell-ranges-via-key-press/
Basically you override the default behavior of the DELETE or BACKSPACE keys using suppressKeyboardEvent callback in our default column definition:
defaultColDef: {
suppressKeyboardEvent: params => {
if (!params.editing) {
let isBackspaceKey = params.event.keyCode === 8;
let isDeleteKey = params.event.keyCode === 46;
if (isBackspaceKey || isDeleteKey) {
params.api.getCellRanges().forEach(range => {
let colIds = range.columns.map(col => col.colId);
let startRowIndex = Math.min(
range.startRow.rowIndex,
range.endRow.rowIndex
);
let endRowIndex = Math.max(
range.startRow.rowIndex,
range.endRow.rowIndex
);
clearCells(startRowIndex, endRowIndex, colIds, params.api);
}
}
return false;
}
}
And the delete method:
function clearCells(start, end, columns, gridApi) {
let itemsToUpdate = [];
for (let i = start; i <= end; i++) {
let data = gridApi.rowModel.rowsToDisplay[i].data;
columns.forEach(column => {
data[column] = "";
});
itemsToUpdate.push(data);
}
gridApi.applyTransaction({ update: itemsToUpdate });
}
This works as expected.

This was fixed in 28.2.0, now delete key always clears the cell without entering the "editing mode".
Reference: https://github.com/ag-grid/ag-grid/releases/tag/v28.2.0, AG‑4801

Related

matter.js: collisionStart triggered many times for one collision

I am working on a game app using React native and Matter.js.
I am trying to implement a system that adds points every time a bullet hits a target.
In order to do this, I am trying to use collisionStart to detect the collision.
However, even though the bullet and target collide only once, the event seems to be triggered 41 times.
This is the code:
Matter.Events.on(engine, 'collisionStart', (event) => {
let pairs = event.pairs
for (const pair of pairs) {
if (pair.bodyA.label === 'bullet' && pair.bodyB.label === 'Worm') {
console.log("target hit");
}
}
})
In the end, I'm planning to replace console.log with something that adds points. At the current moment, one collision seems like it would trigger the add points 41 times, which is obviously not ideal.
Any ideas what is happening here and how I can get this to trigger only once for one collision?
Try next example. I take it from my own project [you need little adaptd from ts to js]:
Matter.Events.on(this.starter.getEngine(), "collisionStart", function (event) {
root.collisionCheck(event, true);
});
public collisionCheck(event, ground: boolean) {
const myInstance = this;
const pairs = event.pairs;
for (let i = 0, j = pairs.length; i !== j; ++i) {
const pair = pairs[i];
if (pair.activeContacts) {
if (pair.bodyA.label === "bullet" && pair.bodyB.label === "Worm") {
const collectitem = pair.bodyA;
this.playerDie(collectitem);
} else if (pair.bodyB.label === "bullet" && pair.bodyA.label === "Worm") {
const collectitem = pair.bodyB;
this.playerDie(collectitem);
}
// ....
}
}
}
public destroyBody = (destroyBody) => {
try {
Matter.Composite.remove(this.getWorld(), destroyBody);
} catch(err) {
console.log(err)
}
}
If you still have same problem , we can adapt also with flag PREVENT_DOUBLE_BY_1_SECOUND for example.

Value is not changing in real time -- VueJS

I am using a JS class, I have following code:
class Field {
public Value = null;
public Items = [];
public UniqueKey = null;
public getItems() {
let items = [...this.Items];
items = items.filter((item) => {
if (item.VisibleIf) {
const matched = item.VisibleIf.match(/\$\[input:(.*?)\]/g);
if (matched?.length) {
const srv = Service.getInstance();
for (let match of matched) {
match = match.slice(8, -1);
if (srv.Fields?.length) {
let found = srv.Fields.find((x) => x.UniqueKey === match);
if (found) {
item.VisibleIf = item.VisibleIf.replace(
`$[input:${match}]`,
found.Value ?? ''
);
return JSON.parse('' + eval(item.VisibleIf));
}
}
}
}
}
return true;
});
return items;
}
public getInputTitle() {
let title = this.Title;
const matched = title.match(/\$\[input:(.*?)\]/g);
if (matched?.length && title) {
const srv = Service.getInstance();
for (let match of matched) {
match = match.slice(8, -1);
if (srv.Fields?.length) {
let found = srv.Fields.find((x) => x.UniqueKey === match);
if (found) {
title = title.replace(`$[input:${match}]`, found.Value ?? '');
}
}
}
}
return title;
}
}
Now I have a Vue component:
<div v-for="Field in Fields" :key="Field.UniqueKey">
<v-select
v-if="Field.Type == 'Select'"
:label="Field.getInputTitle()"
v-model="Field.Value"
:items="Field.getItems()"
item-text="Value"
item-value="Id"
/>
<v-input
v-else-if="Field.Type == 'Input'"
v-model="Field.Value"
:label="Field.getInputTitle()"
/>
</div>
// JS
const srv = Service.getInstance();
Fields = srv.getFields(); // <- API call will be there.
So basically, data comes from an API, having Title as Input $[input:uniqueKey], in a component I am looping over the data and generating the fields. See getInputTitle function in Field class, it works very well. All the fields which are dependent on the $[input:uniqueKey] are changing when I start typing into that field on which other fields are dependent.
Now I have pretty much same concept in the getItems function, so basically, what I want to do is whenever I type into a field and that field exists in the VisibleIf on the Items, the VisibleIf will be like '$[input:uniqueKey] < 1', or any other valid JavaScript expression which can be solved by eval function. But the getItems function is called only 1st time when page gets loaded, on the other hand the getInputTitle function which is pretty much same, gets called every time when I type into the field.
I tried to explain at my best, I will provide any necessary information if needed.
Any solution will be appreciated. Thanks.
You are updating the Object itself in here:
item.VisibleIf = item.VisibleIf.replace( `$[input:${match}]`, found.Value ?? '' );
Even though you tried to copy the array, but you have done shallow copy of the object in here: let items = [...this.Config.Items];
I suggest the following solution:
const visibleIf = item.VisibleIf.replace(
`$[input:${match}]`,
found.Value ?? ''
);
const val = '' + helpers.evalExp('' + visibleIf);
if (helpers.isJSON(val)) {
return JSON.parse(val);
}
Means instead of changing the VisibleIf object, just store it into the variable and just use that.
I hope that it will fix your issue. Let me know if it works.

Excel JavaScript API - How to select cells until filled range end

What would be the JavaScript API version for VBA:
Range(ActiveCell, ActiveCell.End(xlDown)).Select
In essence, I want to do the same what ctrl+down arrow keycombo does. Extend current selection down to the last cell with value.
I've had to write a custom function. Here it is. But I was hoping for having a native API for such a frequently used piece of functionality. I'd argue, that it is top frequently used.
export const get_nonempty_range_down = async (startingCell, context) => {
const distanceLimit = 999;
const rangeToTest = startingCell.getResizedRange(distanceLimit, 0);
rangeToTest.load("values");
await context.sync();
const matrixValues = rangeToTest.values;
let finalCellPosition = null;
matrixValues.some((row, i) => {
if (row[0] === "") {
finalCellPosition = i - 1;
return true;
}
return false;
});
const result = startingCell.getResizedRange(finalCellPosition, 0);
// Debug
// result.load("address");
// await context.sync();
// console.log(result.address);
return result;
};
And it is still imperfect as it traverses only a thousand cells down. Ok for my needs, but not a complete solution.

React autocomplete in a textarea like in an IDE (e.g. VS Code, Atom)

I try to code an auto completion feature like in the gif in React.
So suggestions appear while writing text.
However all packages I could find so far work
a) only in the beginning of the input/textarea (e.g. react-autosuggest)
b) or need a trigger character (like # or #) to open (e.g. react-textarea-autocomplete)
Do I miss some React limitation? Any hints / packages?
We ended up using the fantastic editor Slate.js.
The Mentions example can easily be changed so that the suggestions are triggered by any character (not only '#'). There you go: perfect auto suggest.
I'm actually having the same problem in regards to needing a textarea implementation, but I can help with autocomplete triggering behavior.
We have an implementation of template variables that look like this {{person.name}} which get resolved into whatever the actual value is.
In regards to the autocompletion being triggered only on the first word, you can get around that with a couple modifications to the required functions.
For instance my required functions look like this. (not a completely working example, but all the important bits)
const templateVars = Object.values(TemplateVarMap);
const variables = templateVars.map((templateVar) => {
return {
name: templateVar,
};
});
//This func, onChange, and onSuggestionSelected/Highlight are the important
//parts. We essentially grab the full input string, then slice down to our
//autocomplete token and do the same for the search so it filters as you type
const getSuggestions = (value) => {
const sliceIndex = value
.trim()
.toLowerCase()
.lastIndexOf('{{'); //activate autocomplete token
const inputValue = value
.trim()
.toLowerCase()
.slice(sliceIndex + 2); //+2 to skip over the {{
const inputLength = inputValue.length;
//show every template variable option once '{{' is typed, then filter as
//they continue to type
return inputLength === 0
? variables
: variables.filter(
(variable) => variable.name.toLowerCase().slice(0, inputValue.length) === inputValue
);
};
const getSuggestionValue = (suggestion) => suggestion.name;
const renderSuggestion = (suggestion) => <div>{suggestion.name}</div>;
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value),
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: [],
});
};
onChange = (event, { newValue }) => {
//onChange fires on highlight / selection and tries to wipe
//the entire input to the suggested variable, so if our value
//is exactly a template variable, don't wipe it
if (templateVars.includes(newValue)) {
return;
}
this.setState({
value: newValue,
});
};
//These both need to do similar things because one is a click selection
//and the other is selection using the arrow keys + enter, we are essentially
//manually going through the input and only putting the variable into the
//string instead of replacing it outright.
onSuggestionHighlighted = ({ suggestion }) => {
if (!suggestion) {
return;
}
const { value } = this.state;
const sliceIndex = value.lastIndexOf('{{') + 2;
const currentVal = value.slice(0, sliceIndex);
const newValue = currentVal.concat(suggestion.name) + '}}';
this.setState({ value: newValue });
};
onSuggestionSelected = (event, { suggestionValue }) => {
const { value } = this.state;
const sliceIndex = value.lastIndexOf('{{') + 2;
const currentVal = value.slice(0, sliceIndex);
const newValue = currentVal.concat(suggestionValue) + '}}';
this.setState({ value: newValue });
};
const inputProps = {
value: this.state.value,
onChange: this.onChange,
};
render() {
return (
<Autosuggest
suggestions={this.state.suggestions}
onSuggestionSelected={this.onSubjectSuggestionSelected}
onSuggestionHighlighted={this.onSubjectSuggestionHighlighted}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
)
}
This lets me type something like This is some text with a {{ and have autocomplete pop up, upon choosing a selection it should go to This is some text with a {{person.name}}.
The only problem here is that it requires the final two characters in the input to be {{ (or whatever your token is) for the autocomplete box to come up. I'm still playing with cursor movement and slicing the string around in different ways so if I edit a template thats not at the end the box still pops up.
Hopefully this helps.
You can try react-predictive-text

How to customize ngTypeahead active class(navigating through keyUp and Down)?

This is the official ngTypeahead plunker
http://embed.plnkr.co/gV6kMSRlogjBKnh3JHU3/
This is the github link
https://github.com/orizens/ngx-typeahead
This is staticList in ngxTypeahead
While navigating with arrows keyUp and down , I have an issue.
If the number of typeahed results is 5, so when i navigate through options using keyDown, after reaching 5th , if i press keyDown it should highlight first result right? But resolveNextIndex (as added in the image) has topLimit as 9 and bottom limit as 0 it is calculated 10 times and hence adding activeRsult i.e highlighting the selected option doesnt work properly.
How can i resolve this?
Is there any way to customize this adding active button?
Because i need typeahead like below.
Initially no results should be highlighted.
Only if the user presses keyUp the hightLighting should happen from bottom to top since my inputbox is in the bottom of the screen.
After navigating by keys to the end of the typeahead results, if i press keyDown again it should navigate to top of the result. It shoould be according to the length of the results.
Is there any other library suiting my need or can i customize this ngxTypeahed?
Any answer would be appreciated. Thanks in advance!
I modified the source and and importing locally.
I added one more parameter for results length and dynamically changing the topLimit
ngx-typeahead.component
navigateWithArrows(elementObs: Observable<{}>) {
return elementObs
.filter((e: any) => validateArrowKeys(e.keyCode))
.map((e: any) => e.keyCode)
.subscribe((keyCode: number) => {
this.suggestionIndex = resolveNextIndex(
this.suggestionIndex,**this.resultsLength**,
keyCode === Key.ArrowUp
);
this.showSuggestions = true;
this.cdr.markForCheck();
});
}
listenAndSuggest() {
return Observable.fromEvent(this.element.nativeElement, 'keyup')
.filter((e: any) => validateNonCharKeyCode(e.keyCode))
.map((e: any) => e.target.value)
.debounceTime(300)
.concat()
.distinctUntilChanged()
.filter((query: string) => query.length > 0)
.switchMap((query: string) => this.suggest(query))
.subscribe((results: string[]) => {
this.results = results;
**this.resultsLength = results.length-1;**
this.showSuggestions = true;
this.suggestionIndex = 0;
this.cdr.markForCheck();
});
}
This is ngx-typeahead.util
export function resolveNextIndex(currentIndex: number, **resultsLength: number,** stepUp: boolean) {
const step = stepUp ? 1 : -1;
**const topLimit = resultsLength;**
const bottomLimit = 0;
const currentResultIndex = currentIndex + step;
let resultIndex = currentResultIndex;
if (currentResultIndex === topLimit + 1) {
resultIndex = bottomLimit;
}
if (currentResultIndex === bottomLimit - 1) {
resultIndex = topLimit;
}
return resultIndex;
}

Categories

Resources