I created an editable grid using ag-grid and I need to highlight the cells that were changed. I added the following code:
(cellValueChanged)="onDemandsCellValueChanged($event)"
And the method:
onDemandsCellValueChanged(params): void {
if (params.oldValue === params.newValue) {
return;
}
params.data.modified = true; // add modified property so it can be filtered on save
const column = params.column.colDef.field;
params.column.colDef.cellStyle = { 'background-color': '#e1f9e8' }; // highlight modified cells
params.api.refreshCells({
force: true,
columns: [column],
rowNodes: [params.node],
});
}
The cell is highlighted but when I scroll up and down all the cell from that column are highlighted.
You can check the behavior on stackblitz.
Also if you have a better way of doing this I'm open to new solutions.
I understand your problem You can achieve what you want like this - you should use cellStyle in your column definition as showing in docs and for this code is below
cellStyle: params => {
if (
params.data["modifiedRow" +
params.node.rowIndex +"andCellKey"+
params.column.colDef.field]
) {
return { backgroundColor: "#e1f9e8" };
} else {
return null;
}
},
and after that in this function onDemandsCellValueChanged please add and modify this property modified as true and change your function like this as shown below
onDemandsCellValueChanged(params): void {
if (params.oldValue === params.newValue) {
return;
}
const column = params.column.colDef.field;
params.data["modifiedRow" + params.rowIndex + "andCellKey" + column] = true;
params.api.refreshCells({
force: true,
columns: [column],
rowNodes: [params.node]
});
}
Now it should work even when you scroll. Here is complete working Example on stackblitz
I have a custom TableViewRow with a title and switch like below:
rowFilter.xml
<TableViewRow id="rowFilter">
<View id="vwItemHeader">
<Label id="lblItemHeader"></Label>
</View>
<View id="vwFilterStatus">
<Switch id="swtFilterStatus" onChange="swtFilterStatusChange"></Switch>
</View>
</TableViewRow>
rowFilter.js
var args = arguments[0] || {};
var swtFilterStatusChange_callback;
initialize();
function initialize() {
// Initialize filter row UI
$.lblItemHeader.text = args.title;
$.swtFilterStatus.value = args.value;
// Callback
swtFilterStatusChange_callback = args.callback;
};
In a view I call Browse, I programmatically add these custom rows as follows:
var args = { title: item.title, value: item.value, callback: swtFilterStatusChanged, };
var newRow = Alloy.createController('rowFilter', args).getView('rowFilter');
This works just as intended. However now I want to add a check/uncheck all row. How do I programmatically toggle a switch within my custom rows?
I've tried creating the following function in rowFilter.js (and a similar one for toggle off):
exports.toggleOn = function() {
if ($.swtFilterStatus.value == false) {
$.swtFilterStatus.value = true;
swtFilterStatusChange();
}
};
And I've also tried this:
$.toggleOn = function() {
}
::EDIT:: Here is how I handle the check/uncheck all switch.
function allSwitch_Change(value) {
$.tblFilters.data[0].rows.forEach(function(row) {
if (value) {
row.toggleOn();
}
else {
row.toggleOf();
}
}
}
Then changing the Browse.js row above that does Alloy.createController with the following:
var newRow = require('rowFilter');
newRow.initialize(args);
But I just get an exception on the line with the require statement saying "Couldn't find module:rowFilter for architecture: x86_64".
What am I doing wrong and how do I implement a check/uncheck all row?
You can do something like this:
rowFilter.js
var args = arguments[0] || {};
var swtFilterStatusChange;
initialize();
function initialize() {
// Initialize filter row UI
$.lblItemHeader.text = args.title;
$.swtFilterStatus.value = args.value;
// Callback
swtFilterStatusChange = args.callback;
};
$.on('toggleOn',function(){
if ($.swtFilterStatus.value == false) {
$.swtFilterStatus.value = true;
swtFilterStatusChange();
}
});
browser.js
var args = { title: item.title, value: item.value, callback: swtFilterStatusChanged, };
var newRow = Alloy.createController('rowFilter', args);
newRow.trigger('toggleOn');
Other options to achieve your goal:
Global Events - Ti.App.addEventListener and Ti.App.fireEvent. Easy to implement, but has potential memory leak. Make sure to call Ti.App.removeEventListener after table row removed.
Get the switch view from the table row's children. Manually fire the event on it.
Store the reference to the callback swtFilterStatusChanged somewhere and call it later.
When a combobox is used in ExtJS (tested in 4.2, but likely other versions as well), and the "typeAhead: true" option is set, if you the user types in values very quickly and then hits the "tab" on their keyboard, the focus will move to the next field on the screen and the wrong value is set. Because of the tricky nature of this bug, I have created a JSFiddle for it here: http://jsfiddle.net/59AVC/2/
To replicate the bug, very quickly type "975" and then "tab" in the first combobox field. If you hit tab very quickly after you enter the "5" in "975", you will see that the combobox is set to the "970" option instead. I believe this is happening because the "Tab" is causing whatever option is highlighted in the list to be the value that is set, but what is strange is that the "970" is highlighted still after the "5" in "975" is entered, when it should process that event first before the "tab" and it should have changed the selection to be the correct "975".
I have tried adjusting the typeAheadDelay (set to 0 in the example), as well as the queryDelay and everything else I can think of. It looks like ExtJS is simply canceling the lookup that is somehow still running and not finished when the tab is pressed.
Any suggestions on how to work around this bug? Do I need to write my own "typeAhead" auto-complete function to handle this correctly by single threading the events?
Here is the sample JSFiddle code that shows this:
// The data store containing the list of values
var states = Ext.create('Ext.data.Store', {
fields: ['val_number', 'val_name'],
data : [
{"val_number":"970", "val_name":"970 - Name"},
{"val_number":"971", "val_name":"971 - Name"},
{"val_number":"972", "val_name":"972 - Name"},
{"val_number":"973", "val_name":"973 - Name"},
{"val_number":"974", "val_name":"974 - Name"},
{"val_number":"975", "val_name":"975 - Name"}
//...
]
});
Ext.create('Ext.form.ComboBox', {
fieldLabel: 'Choose 1st Value',
store: states,
queryMode: 'local',
displayField: 'val_name',
valueField: 'val_number',
renderTo: Ext.getBody(),
typeAhead: true,
typeAheadDelay: 0,
minChars: 1,
forceSelection: true,
autoSelect: false,
triggerAction: 'all',
queryDelay: 0,
queryCaching: false
});
Ext.create('Ext.form.ComboBox', {
fieldLabel: 'Choose 2nd Value',
store: states,
queryMode: 'local',
displayField: 'val_name',
valueField: 'val_number',
renderTo: Ext.getBody(),
typeAhead: true,
typeAheadDelay: 0,
minChars: 1,
forceSelection: true,
autoSelect: false,
triggerAction: 'all',
queryDelay: 0,
queryCaching: false
});
UPDATED:
Tried this code as suggested, no change in result - still doesn't select correctly:
Ext.define('App.CustomComboBox', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.CustomCombobox',
initComponent:function() {
// call parent init component
this.callParent(arguments);
},
onTypeAhead: function() {
console.log('onTypeAhead...');
var me = this,
displayField = me.displayField,
record = me.store.findRecord(displayField, me.getRawValue()),
boundList = me.getPicker(),
newValue, len, selStart;
if (record) {
newValue = record.get(displayField);
len = newValue.length;
selStart = me.getRawValue().length;
//boundList.highlightItem(boundList.getNode(record));
if (selStart !== 0 && selStart !== len) {
me.setRawValue(newValue);
me.selectText(selStart, newValue.length);
}
}
}
});
found the problematic code snippet:
beforeBlur: function() {
this.doQueryTask.cancel();
this.assertValue();
},
the problem is not typeAhead its selectOnTab together with autoSelect which will be set to true from typeahead.
so this happens:
you type "97" a query will fire and the first value (970) will be selected
you type "5" a query will start, but
you press "tab" and the beforeBlur function gets executed.
the query gets canceled and the currently highlighted value becomes the field value
so what can you do?
it is not possible to give the task a callback so assertValue is called after the query finishes :(
you need to disable autoselect again and the only way i found is to override onTypeAhead and to comment out the highlighting:
.
onTypeAhead: function() {
var me = this,
displayField = me.displayField,
record = me.store.findRecord(displayField, me.getRawValue()),
boundList = me.getPicker(),
newValue, len, selStart;
if (record) {
newValue = record.get(displayField);
len = newValue.length;
selStart = me.getRawValue().length;
//boundList.highlightItem(boundList.getNode(record));
if (selStart !== 0 && selStart !== len) {
me.setRawValue(newValue);
me.selectText(selStart, newValue.length);
}
}
}
Thanks to Jandalf, I have some good news. I was able to work out a solution for my needs by extending the combobox and introducing a few fixes. The first was to do as Jandalf suggested (a good starting point) and the next set of fixes was to stop using a DelayedTask if the delay was 0 or less (my config sets "typeAheadDelay" and "queryDelay" to 0). Finally, I had to also do a check in the "assertValue" that is the equivalent of what happens when someone types a regular key to catch the problem where the tab is blurring things before the keyUp is done. Because of this last part, it may not be the perfect solution for everyone, but it was the only thing that could solve my problem. So, here is the code that makes it work for me. I hope someone else will find it useful.
Ext.define('App.CustomComboBox', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.CustomCombobox',
initComponent:function() {
// call parent init component
this.callParent(arguments);
},
onTypeAhead: function() {
var me = this,
displayField = me.displayField,
record = me.store.findRecord(displayField, me.getRawValue()),
boundList = me.getPicker(),
newValue, len, selStart;
if (record) {
newValue = record.get(displayField);
len = newValue.length;
selStart = me.getRawValue().length;
//Removed to prevent onBlur/Tab causing invalid selections
//boundList.highlightItem(boundList.getNode(record));
if (selStart !== 0 && selStart !== len) {
me.setRawValue(newValue);
me.selectText(selStart, newValue.length);
}
}
},
onPaste: function(){
var me = this;
if (!me.readOnly && !me.disabled && me.editable) {
if (me.queryDelay > 0) {
//Delay it
me.doQueryTask.delay(me.queryDelay);
} else {
//Changed to do immediately instead of in the delayed task
me.doRawQuery();
}
}
},
// store the last key and doQuery if relevant
onKeyUp: function(e, t) {
var me = this,
key = e.getKey();
if (!me.readOnly && !me.disabled && me.editable) {
me.lastKey = key;
// we put this in a task so that we can cancel it if a user is
// in and out before the queryDelay elapses
// perform query w/ any normal key or backspace or delete
if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
if (me.queryDelay > 0) {
//Delay it
me.doQueryTask.delay(me.queryDelay);
} else {
//Changed to do immediately instead of in the delayed task
me.doRawQuery();
}
}
}
if (me.enableKeyEvents) {
me.callParent(arguments);
}
},
// private
assertValue: function() {
var me = this,
value = me.getRawValue(),
rec, currentValue;
if (me.forceSelection) {
if (me.multiSelect) {
// For multiselect, check that the current displayed value matches the current
// selection, if it does not then revert to the most recent selection.
if (value !== me.getDisplayValue()) {
me.setValue(me.lastSelection);
}
} else {
// For single-select, match the displayed value to a record and select it,
// if it does not match a record then revert to the most recent selection.
rec = me.findRecordByDisplay(value);
if (rec) {
currentValue = me.value;
// Prevent an issue where we have duplicate display values with
// different underlying values.
if (!me.findRecordByValue(currentValue)) {
me.select(rec, true);
}
} else {
//Try and query the value to find it as a "catch" for the blur happening before the last keyed value was entered
me.doRawQuery();
//Get the new value to use
value = me.getRawValue();
//Copy of the above/same assert value check
rec = me.findRecordByDisplay(value);
if (rec) {
currentValue = me.value;
// Prevent an issue where we have duplicate display values with
// different underlying values.
if (!me.findRecordByValue(currentValue)) {
me.select(rec, true);
}
} else {
//This is the original "else" condition
me.setValue(me.lastSelection);
}
}
}
}
me.collapse();
},
doTypeAhead: function() {
var me = this;
if (!me.typeAheadTask) {
me.typeAheadTask = new Ext.util.DelayedTask(me.onTypeAhead, me);
}
if (me.lastKey != Ext.EventObject.BACKSPACE && me.lastKey != Ext.EventObject.DELETE) {
//Changed to not use the delayed task if 0 or less
if (me.typeAheadDelay > 0) {
me.typeAheadTask.delay(me.typeAheadDelay);
} else {
me.onTypeAhead();
}
}
}
});
we not like to add three word again in selected item.
we like to validate on drag and drop event and need to show message that u already added this record.
i try with below code but not able to fine relevant event for validate
listeners: {
added:function(obj,event){
console.log("added");
},change:function(obj,event){
console.log("change");
},removed:function(obj,event){
console.log("removed");
}, blur:function(obj,event){
console.log("blur");
}, click: function( obj) {
console.log('click');
}, select: function( obj) {
console.log('select');
}
}
please see attached image bellow.
I am Using Extjs 3.4
The change event fires when an item is selected or deselected... But at this stage, you won't be able to prevent it anymore. So, apparently, your best move is to override the onAddBtnClick method:
{
xtype: 'itemselector'
// ... config
,onAddBtnClick: function() {
var me = this,
selected = me.getSelections(me.fromField.boundList),
i, l, record;
var toStore = this.toField.boundList.getStore(),
idField = 'value', // or 'id', or whatever you want
selectedIds = Ext.pluck(Ext.pluck(toStore.getRange(), 'data'), idField),
accepted = [], rejected = [];
for (i=0, l=selected.length; i<l; i++) {
record = selected[i];
if (selectedIds.indexOf(record.get(idField)) === -1) {
accepted.push(record);
} else {
rejected.push(record);
}
}
if (rejected.length) {
// warning msg
}
me.moveRec(true, accepted);
me.toField.boundList.getSelectionModel().select(accepted);
}
}
How do I dynamically update the items in a drop down?
I have a custom plugin for CKEditor that populates a drop down menu with a list of items which I can inject into my textarea.
This list of items comes from a Javascript array called maptags, which is updated dynamically for each page.
var maptags = []
This list of tags gets added to the drop down when you first click on it by the init: function. My problem is what if the items in that array change as the client changes things on the page, how can I reload that list to the updated array?
Here is my CKEditor Plugin code:
CKEDITOR.plugins.add('mapitems', {
requires: ['richcombo'], //, 'styles' ],
init: function (editor) {
var config = editor.config,
lang = editor.lang.format;
editor.ui.addRichCombo('mapitems',
{
label: "Map Items",
title: "Map Items",
voiceLabel: "Map Items",
className: 'cke_format',
multiSelect: false,
panel:
{
css: [config.contentsCss, CKEDITOR.getUrl(editor.skinPath + 'editor.css')],
voiceLabel: lang.panelVoiceLabel
},
init: function () {
this.startGroup("Map Items");
//this.add('value', 'drop_text', 'drop_label');
for (var this_tag in maptags) {
this.add(maptags[this_tag][0], maptags[this_tag][1], maptags[this_tag][2]);
}
},
onClick: function (value) {
editor.focus();
editor.fire('saveSnapshot');
editor.insertHtml(value);
editor.fire('saveSnapshot');
}
});
}
});
I think I just solved this actually.
Change your init like this:
init: function () {
var rebuildList = CKEDITOR.tools.bind(buildList, this);
rebuildList();
$(editor).bind('rebuildList', rebuildList);
},
And define the buildList function outside that scope.
var buildListHasRunOnce = 0;
var buildList = function () {
if (buildListHasRunOnce) {
// Remove the old unordered list from the dom.
// This is just to cleanup the old list within the iframe
$(this._.panel._.iframe.$).contents().find("ul").remove();
// reset list
this._.items = {};
this._.list._.items = {};
}
for (var i in yourListOfItems) {
var item = yourListOfItems[i];
// do your add calls
this.add(item.id, 'something here as html', item.text);
}
if (buildListHasRunOnce) {
// Force CKEditor to commit the html it generates through this.add
this._.committed = 0; // We have to set to false in order to trigger a complete commit()
this.commit();
}
buildListHasRunOnce = 1;
};
The clever thing about the CKEDITOR.tools.bind function is that we supply "this" when we bind it, so whenever the rebuildList is triggered, this refer to the richcombo object itself which I was not able to get any other way.
Hope this helps, it works fine for me!
ElChe
I could not find any helpful documenatation around richcombo, i took a look to the source code and got an idea of the events i needed.
#El Che solution helped me to get through this issue but i had another approach to the problem because i had a more complex combobox structure (search,groups)
var _this = this;
populateCombo.call(_this, data);
function populateCombo(data) {
/* I have a search workaround added here */
this.startGroup('Default'); /* create default group */
/* add items with your logic */
for (var i = 0; i < data.length; i++) {
var dataitem = data[i];
this.add(dataitem.name, dataitem.description, dataitem.name);
}
/* other groups .... */
}
var buildListHasRunOnce = 0;
/* triggered when combo is shown */
editor.on("panelShow", function(){
if (buildListHasRunOnce) {
// reset list
populateCombo.call(_this, data);
}
buildListHasRunOnce = 1;
});
/* triggered when combo is hidden */
editor.on("panelHide", function(){
$(_this._.list.element.$).empty();
_this._.items = {};
_this._.list._.items = {};
});
NOTE
All above code is inside addRichCombo init callback
I remove combobox content on "panelHide" event
I repopulate combobox on "panelShow" event
Hope this helps