Autocomplete using values from AJAX - tabulator

I have a table using tabulator.
Everything works great, but I am trying to get autocomplete working with Ajax
What I am trying is:
var customerNumbers = [];
var table = new Tabulator("#edi-table",
ajaxURL: baseUrl + '/PaginatedEndPoint',
pagination: "remote",
paginationSize: 30,
paginationSizeSelector: [30, 60, 100, 200],
ajaxSorting: true,
ajaxFiltering: true,
selectable: true,
cellEdited: function (cell) {
cell.getElement().style.backgroundColor = "#32CD32";
},
dataLoading: function () {
customerNumbers = ["11", "12", "13"];
},
columns: [
{
title: "CustomerNumber", field: "CustomerNumber", headerFilter: "input", editor: "autocomplete", editorParams: {
searchFunc: function (term, values) {
var matches = [];
values.forEach(function (item) {
if (item.value === term) {
matches.push(item);
}
});
console.log(matches);
return matches;
},
listItemFormatter: function (value, title) {
return "Mr " + title;
},
values: customerNumbers
}
}
]
However, this does not show any predictions value predictions for me, it seems that autocomplete is built before "dataLoading" or any other Callback (I have tried many) is called.
I have tried to make an auxilary array in the style of values like {Title: "Mr + title", value: "title"} and then assign it in the searchFunc, and it didn't work despite being returned in matches.
Is it even possible to dynamically create autofill?

It seems like the current autocomplete functionality does not allow for the editorParams to take a function as an argument to set the dropdown values. You can set it with an object of key/values if you can send that via AJAX, but as far as dynamically setting, altering, or searching the data, it seems like that's impossible to do at the moment.
The other option would be use the editor:"select", which can take a function to set its editorParams. It's not the best solution, but it's the one I had to go with at the moment.
There is an open issue on the Tabulator docs, but so far no response from the developers.
I wish I had a better answer for you!

Related

select2 remove tag_list item

Trying to get Select2 to remove an item and figure out why it keeps adding a new tag on update.
Using Select2.JS V3.5
Followed ActiveAdmin / Select2 example
When I try to delete an item, it does not remove it. It also ends up combining the tag list to a new STRING creating a new tag. i.e. If I have ["play", "doe"] and I update another field, it creates a new tag called "play doe" resulting in ["play", "doe", "play doe"]. This continues every time I update.
Here's my JS code
// Generated by CoffeeScript 1.9.3
$(document).ready(function() {
$('.tagselect').each(function() {
var placeholder, saved, url;
placeholder = $(this).data('placeholder');
url = $(this).data('url');
saved = $(this).data('saved');
$(this).select2({
tags: true,
placeholder: placeholder,
minimumInputLength: 3,
allowClear: true,
multiple: true,
debug: true,
initSelection: function(element, callback) {
saved && callback(saved);
},
ajax: {
url: url,
dataType: 'json',
data: function(term) {
return {
q: term
};
},
results: function(data) {
return {
results: data
};
}
},
createSearchChoice: function(term, data) {
if ($(data).filter((function() {
return this.text.localeCompare(term) === 0;
})).length === 0) {
return {
id: term,
text: term
};
}
}
});
});
});
Saw https://github.com/mbleigh/acts-as-taggable-on/issues/676
adding the block in my model, fixes the formtastic bug where all tags where coming in as a flat string vs a comma separated string. Monkey Patch for now.
class MyModel < ActiveRecord::Base
...
def tag_list
super.to_s
end
end

Kendo UI Grid search as type example

I would like to search datagrid in Kendo UI during typing into input field above the grid.
How can I do it?
Thanks for any advice.
Here is example of columns:
$("#grid").kendoGrid({
dataSource: dataPacket,
filterable: true,
pageSize: 10,
pageable: true,
sortable: true,
reorderable: true,
resizable: true,
columnMenu: true,
height: 550,
toolbar: ["create", "save", "cancel"],
columns: ["id",
"username",
"name",
"surname",
"email",
{
field :"created",
title : "Created at",
format: "{0:M/d/yyyy}",
parseFormats: ["dd-MM-yyyy"],
type: "date"
},
Kendo make this thing really easy for you, what is needed is to create a filter and pass it to the DataSource.
http://docs.telerik.com/kendo-ui/api/framework/datasource#methods-filter
However, this problem must be divided into two different tasks:
a) Capture the key events in the search box, throttle it and start the search "operation".
b) Build a filter and pass it to the DataSource.
So for throttling the keyboard events, we need a timeout. Or use the throttle function from underscorejs. Why? We don't wanna trigger a search operation on each key press. Only 250 milliseconds (this number is up to you) after the last keystroke.
Here is your sample HTML
<input type="text" id="search" />
Here is your sample script. I wrap everything as a self calling function as you don't wanna create a mess declaring global variables.
(function($, kendo){
// ID of the timeout "timer" created in the last key-press
var timeout = 0;
// Our search function
var performSearch = function(){
// Our filter, an empty array mean "no filter"
var filter = [];
// Get the DataSource
var dataSource = $('#grid').data('kendoGrid').dataSource;
// Get and clean the search text.
var searchText = $.trim($('#search').val());
// Build the filter in case the user actually enter some text in the search field
if(searchText){
// In this case I wanna make a multiple column search so the filter that I want to apply will be an array of filters, with an OR logic.
filter.push({
logic: 'or',
filters:[
{ field: 'username', operator: 'contains', value: searchText },
{ field: 'name', operator: 'contains', value: searchText },
{ field: 'surname', operator: 'contains', value: searchText },
{ field: 'email', operator: 'contains', value: searchText }
]
});
}
// Apply the filter.
dataSource.filter(filter);
};
// Bind all the keyboard events that we wanna listen to the search field.
$('#search').on('keyup, keypress, change, blur', function(){
clearTimeout(timeout);
timeout = setTimeout(performSearch, 250);
});
})(window.jQuery, window.kendo);
Bottom-line: Make sure you are using the right DataSource configuration.
If you configured serverFiltering = true, this filtering logic will be part of your Ajax request, so your server will have to interpret and perform the filtering on server-side.
In case you configured serverFiltering = false all this filtering logic will be evaluated on client side using JavaScript (damn fast!). And in this case, the schema (what data-type is expected on each column) must be also well-configured.

Ext JS: TabPanel rendering Infinite Grid too quickly

Let's say I have a TabPanel that gets two components added to it. Each component contains an Infinite Grid. Each Infinite Grid loads its data from a service call, and each set of data contains 2,000 records. After the components are added to the TabPanel, we set each one to be the active tab, using setActiveTab. We first set the 2nd tab as the active tab and then set the first tab. When the page loads, the first tab is selected, as we expected.
When looking at the first tab, everything looks fine... we can infinitely scroll, sort, hide columns, etc. However, if we switch to the second tab, we see that it has partially loaded, and we can't scroll past the x records that have loaded, hide columns, sort, etc. It's almost as if using setActiveTab was a bit premature with rendering the grid... as if the store wasn't completely loaded, but the tab was rendered anyway. (this is what I'm assuming the issue is)
I do have code, but it takes a little work on your end to reproduce (because you need a service call). I'm using CompoundJS within a Node.js application, so it was very easy for me to create the test case. If you have access to a database, and can make a quick service call, just modify my Ext JS code, but if you want to use Node.js, you can try this:
Ext JS 4.2.1
Ext.onReady(function() {
var tabPanel = Ext.create('Ext.tab.Panel', {
width: 400,
height: 400,
renderTo: Ext.getBody()
});
Ext.define('myGrid', {
extend: 'Ext.grid.Panel',
constructor: function(config) {
this.columns = config.columns;
this.store = Ext.create('Ext.data.Store', {
fields: config.fields,
buffered: true,
leadingBufferZone: 20,
pageSize: 50,
proxy: {
type: 'ajax',
url: '/getData?id=' + config.id,
reader: {
totalProperty: 'totalCount',
type: 'json',
root: 'root'
}
},
autoLoad: true
});
this.id = config.id;
this.callParent();
}
});
var grid1 = Ext.create('myGrid', {
id: 'blah',
columns: [{
text: 'one',
dataIndex: 'one'
}, {
text: 'two',
dataIndex: 'two'
}, {
text: 'three',
dataIndex: 'three'
}],
fields: ['one', 'two', 'three'],
title: 'grid1'
});
var grid2 = Ext.create('myGrid', {
id: 'bleh',
columns: [{
text: 'one',
dataIndex: 'one'
}, {
text: 'two',
dataIndex: 'two'
}, {
text: 'three',
dataIndex: 'three'
}],
fields: ['one', 'two', 'three'],
title: 'grid2'
});
var c1 = [];
c1.items = [grid1];
c1.title = "BLAH";
c1.layout = 'fit';
var c2 = [];
c2.items = [grid2];
c2.title = "BLEH";
c2.layout = "fit";
tabPanel.add([c1, c2]);
tabPanel.setActiveTab(1);
tabPanel.setActiveTab(0);
});
Node.js code
compound init test && cd test
npm install
compound g s testcontroller
Replace app/controllers/testcontrollers_controller.js with:
load('application');
action('getData', function(data) {
var query = data.req.query;
var id = query.id;
var page = query.page;
var pushObj;
if (id === 'blah') {
pushObj = {
one: 'bye',
two: 'goodbye',
three: 'auf wiedersehen'
};
}
else {
pushObj = {
one: 'hi',
two: 'hello',
three: 'guten tag'
};
}
var obj = [];
for (var i = 0; i < 50; i++) {
obj.push(pushObj);
}
send({
totalCount: 2000,
root: obj,
page: page
});
});
In config/routes.js, remove testcontroller's map.resources line, and add this:
map.get('getData', 'testcontrollers#getData');
In public/index.html, make it a generic HTML file, and add in your links to Ext JS and my above Ext JS code.
Now, once you've done all of that, you should be able to reproduce my issue. The first tab will open and be just fine, but the second tab only loads the first x amount of records and doesn't function properly. I believe this is due to the store not loading completely when setActiveTab is fired, which makes the rendering load an impartial store.
What I want to know is, how do I get this to work properly? I've tried waiting for the store to load, and then adding it to the tab, but that still gives me inconsistent results, as well as tried waiting for the grid to stop rendering, but still, I get inconsistent results... inconsistent meaning, sometimes the grid loads all the way, and the tab is fine, but other times, I get a cannot read property 'length' of undefined in ext-all-dev.js:135,786... which makes it seem like the store hasn't completely loaded, as that line contains a reference to records.length.
If anyone has any ideas, I'd love to hear them! Cross-posted from the Sencha forums.
EDIT: Thanks to #rixo, I was able to reproduce the problem in this example. If you enable Firebug, you'll see the error about property length of undefined, as I stated above.
I tracked the issue down to the plugin caching incorrect size values when it is hidden.
Here's an override that would fix this behavior:
/**
* Prevents BufferedRenderer plugin to break when buffered views are
* rendered or refreshed while hidden, like in a card layout.
*
* Tested with Ext 4.2.1
*/
Ext.define('Ext.ux.Ext.grid.plugin.BufferedRenderer.HiddenRenderingSupport', {
override: 'Ext.grid.plugin.BufferedRenderer'
/**
* Refreshes the view and row size caches if they have a value of 0
* (meaning they have probably been cached when the view was not visible).
*/
,onViewResize: function() {
if (this.rowHeight === 0) {
if (this.view.body.getHeight() > 0) {
this.view.refresh();
}
} else {
this.callParent(arguments);
}
}
});
That being said, I advice against using this and unnecessarily bloating your code base.
You should rather organise your code in a way that avoid this situation. The problem is created by the fact that you're calling setActiveTab multiple time in the same execution frame. If you avoid that, Ext will handle the situation correctly all by itself.
As a general principle, you should try to make your Ext code more declarative... Ext architecture rather expects you to build big configuration objects to create your component at once (allowing for atomic DOM updates, etc.), and use methods that will update the state only later, to drive the components behaviour.
In your precise case, you should use the activeTab option to set the initial tab, and only call setActiveTab later, when you actually need to change the tab. That will save some unnecessary processing at initialization, and will also resolve your rendering issue...
Here's how your code should look like:
Ext.create('Ext.tab.Panel', {
renderTo: Ext.getBody(),
width: 400,
height: 400,
// Use this option to set the initial tab.
activeTab: 2,
// You don't need to overnest your grids into another Panel, you can add
// them directly as children of the tab panel.
items: [
// Ideally, you should give an xtype to your class, and add this to
// avoid instantiating the object yourself... Thus giving Ext more
// control on the rendering process, etc.
Ext.create('myGrid', {
id: 'blah',
title: 'Bleh',
columns: [{
text: 'one',
dataIndex: 'one'
}, {
text: 'two',
dataIndex: 'two'
}, {
text: 'three',
dataIndex: 'three'
}],
fields: ['one', 'two', 'three'],
title: 'grid1'
}),
Ext.create('myGrid', {
id: 'bleh',
title: 'Bleh',
columns: [{
text: 'one',
dataIndex: 'one'
}, {
text: 'two',
dataIndex: 'two'
}, {
text: 'three',
dataIndex: 'three'
}],
fields: ['one', 'two', 'three'],
title: 'grid2'
})
]
});
As you can see, the tab panel child items are set at creation time, avoiding useless processing with the add method. Likewise, the activeTab option avoid to initialize the panel in a given state and change it right away... Later (in another execution frame, for example in a button handler), you can use setActiveTab without triggering the bug demonstrated in your original example.

I can not get the value of a text field in extJS

Faced with a problem at work with ExtJS
There is such a code - a new class (view)
Ext.define('FormApp.view.ElementContainer', {
extend: 'Ext.Container',
alias: 'widget.elemcontainer',
initComponent: function() {
this.items = [{
layout: {
type: 'hbox',
align: 'middle'
},
items: [
{ xtype: 'component',
html: '&nbspПоиск&nbsp&nbsp'},
{ xtype: 'textfield',
width: 495,
name: 'search'
},
{ xtype:'component',
html:'&nbsp&nbsp'},
{ xtype: 'button',
text: 'Найти',
width: 80,
action: 'searchTreeData'}
]}
];
this.callParent();
}
});
Then in the controller I write code like this to get the value of the textfield
Ext.define('FormApp.controller.ControlOne', {
extend: 'Ext.app.Controller',
views: ['ElementContainer', 'TreeView'],
init: function() {
this.control({
'elemcontainer button[action=searchTreeData]': {
click: this.searchTree
},
'elemcontainer textfield[name=search]':{change: this.str}
});
},
searchTree: function(searchStr) {
var dat = Ext.widget('elemcontainer');
var str = dat.down('textfield').getValue();
alert (str.getValue());
},
str: function()
{alert('OK');}
});
I can not get the value of a text field in extJS
How to access the elements to get their values​​?
Thanks in advance and sorry for my clumsy English
The problem is that by using Ext.widget(...) in searchTree(), you're creating a new instance of the class (be sure to check the docs), rather than getting the of the component that already exists.
Another issue is that str is already the "value" of the textfield, so calling getValue() on str.getValue() won't get you very far either.
So a few suggestions:
Update you searchTree method to pass the correct arguments. Since this method is getting called on the click event of a button, the arguments will be those of the click event for Ext.button.Button : searchTree( btn, e, opts ) {...}
Once you have the correct arguments being passed to searchTree(), you can then use the component selector methods to get the existing instance of the container. For example, since the button is already a descendant of the container, you can do the following to get the correct instance of the component:
var ctr = btn.up( "elemcontainer" )
And now that you have the correct instance of the container, you can again use one of the component selector methods to find the textfield:
var str = ctr.down( 'textfield' ).getValue()

extjs4 MVC Scope Issue

I am trying to call resetCombo method once i click on link from tooltip which is rendered on combo
But i am not able to access it because of scope issue not sure what i am missing. Please help me on this.
Ext.define('test.BasicForm', {
extend: 'Ext.form.Panel',
renderTo:Ext.getBody(),
initComponent :function(){
this.items=[
{
fieldLabel: 'Test',
xtype: 'combo',
displayField: 'name',
width: 320,
labelWidth: 130,
store: [
[1, 'Value 1'],
[2, 'Value 2'],
[3, 'Value 3'],
[4, 'Value 4']
],
listeners:{
afterrender: function(combo) {
Ext.create('Ext.tip.ToolTip', {
target: combo.getEl(),
autoHide: false,
name:'tool-tip',
scope:this,
html: 'Old value was '+ combo.getValue()+ ' test',
listeners: {
beforeshow: function() {
return combo.isDirty();
}
}
});
}
},
value:'1'
}];
this.callParent(arguments);
},
resetCombo:function(){
alert('called');
}
});
First this has nothing to do with ExtJS4's MVC features which are generally associated with a controller's control method:
http://docs.sencha.com/ext-js/4-1/#!/api/Ext.app.Controller-method-control
Second, you may be able to instead get the effect you want by switching to the following, fully qualified path to reset combo:
onclick="javascript:test.BasicForm.resetCombo();" //etcetera
Lastly, though you may get the above to work, it is far from best practice. I don't have time to give the complete answer, but essentially what you want to do consists of:
Adding a click event handler to the tooltip's underlying Element
Then inside the element use the arguments to Ext.dom.Element.click (see http://docs.sencha.com/ext-js/4-1/#!/api/Ext.dom.Element-event-click) to ensure it was an <A> tag that got clicked
Then invoke the desired function without having to use Javascript pseudo-URL's and a fully qualified path to the function
Here is my working re-write of the afterrender listener following the above guidelines, with some tweaks to scope, in particular storing a reference to the form in a variable of the same name.
listeners:{
afterrender: function(combo) {
var form = this;
var tooltip = Ext.create('Ext.tip.ToolTip', {
target: combo.getEl(),
autoHide: false,
name:'tool-tip',
html: 'Old value was '+ combo.getValue()+ ' <a class="tooltipHref" href="#">test</a>',
listeners: {
beforeshow: function() {
return combo.isDirty();
},
afterrender: function() {
tooltip.el.on('click', function(event, element) {
if (element.className == 'tooltipHref') {
form.resetCombo();
}
});
}
}
});
},
scope: this
}
test
this code is attempting to call a function named resetCombo which is stored inside the top-level object (the window object).

Resources