Values and captions in Ace Editor autocomplete - javascript

I am using Ace Editor v.1.1.8 with ext-language_tools.
I want to achieve the following behavior with auto-complete:
User starts typing, presses Ctrl+Space and I show him the list of found captions, when he selects one of them the value is inserted.
My code:
var completions = [
{id: 'id1', 'value': 'value1'},
{id: 'id2', 'value': 'value2'}
];
var autoCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
if (prefix.length === 0) {
callback(null, []);
return;
}
callback(
null,
completions.map(function(c) {
return {value: c.id, caption: c.value};
})
);
}
};
editor.setOptions({
enableBasicAutocompletion: [autoCompleter],
enableLiveAutocompletion: false,
enableSnippets: false
});
So what I need from the above is that user enters 'val', sees the list with 'value1' and 'value2', selects one of them and 'id1' or 'id2' is inserted into editor.
At this point:
Editor always searches by value (and i need to search by caption)
If I set 'value' = c.value, then editor will search correctly but will insert c.value while I need c.id inserted.
Here's the working code: http://plnkr.co/edit/8zAZaLpZVhocHmI4GWhd?p=preview
UPD:
Was able to achieve this behavior by adding insertMatch method to data:
completions.map(function(c) {
return {
value: c.name,
meta: c.id,
completer: {
insertMatch: function(editor, data) {
if (editor.completer.completions.filterText) {
var ranges = editor.selection.getAllRanges();
for (var i = 0, range; range = ranges[i]; i++) {
range.start.column -= editor.completer.completions.filterText.length;
editor.session.remove(range);
}
}
editor.execCommand("insertstring", data.meta);
}
}
};
})

You can change
var caption = item.value || item.caption || item.snippet;
line at https://github.com/ajaxorg/ace/blob/v1.1.8/lib/ace/autocomplete.js#L449 to
var caption = item.caption || item.value || item.snippet;
You can also implement custom insertMatch method on completer https://github.com/ajaxorg/ace/blob/v1.1.8/lib/ace/autocomplete.js#L181, but making a pull request with first option is better

Related

javascript match defined values in all objects and return rest of values in variable

I need solution not using any frameworks, just plain javascript.
I have initial values that I need to search in array items:
var name = 'blah';
var click = 'double';
I have array like this:
var items =
[
{
name: 'blah',
click: 'single',
url: 'url1',
token: 'token1'
},
{
name: 'blah',
click: 'double',
url: 'url2',
token: 'token2'
},
{
name: 'bar',
click: 'double',
url: 'url3',
token: 'token3',
},
{
name: 'baz',
click: 'single',
url: 'url4',
token: 'token4'
}
];
When I find the object that contains both values, in this case:
{
name: 'blah',
click: 'double',
url: 'url2',
token: 'token2'
}
Than, I need to assign rest of values from that object to separate variables. In this case result should be:
var url = 'url2';
var token = 'token2'
The problem is that i don't know what the search values will be each time. For example on each button press, search values will be different. Different value for "name" and different value for "click".
I just want to check if it is the same across all objects in that array and assign rest of values to variables.
UPDATE:
I was thinking maybe to first create array of found objects that contain matched value from "var name = blah"
var names = items.filter(function(item) {return item.name === 'blah'});
and then search over "names" array to find what is matching to var click = "double".
var match = names.filter(function(matchitem) {return matchitem.click === "double"});
You can use find which is a method on the Array prototype. It would work like this
var match = items.find(function(item) {
return item.name === name && item.click === click;
}
Or written in a more modern way
const match = items.find(item => item.name === name && item.click === click);
Now if you have a match you can destructure it like below to get separate variables for the various properties.
const { url, token } = match;
The variable names need to match the keys of your object for this to work.
Get the first matching object which contains both name and click
const item = items.find(item => item.name === name && item.click === click);
if (item){
url = item.url;
token = item.token;
}
if something is desired maybe so.
var items = [
{
name: 'blah',
click: 'single',
url: 'url1',
token: 'token1'
},
{
name: 'blah',
click: 'double',
url: 'url2',
token: 'token2'
},
{
name: 'bar',
click: 'double',
url: 'url3',
token: 'token3',
},
{
name: 'baz',
click: 'single',
url: 'url4',
token: 'token4'
}
];
var name = 'blah';
var click = 'double';
items.forEach((item) => {
if(item.name == name && item.click == click){
console.log(item)
}
})
so you have access to the item and everything.

Can I disable completion filtering in Ace Editor?

This plunker uses a rhyming dictionary to do autocompletion.
var langTools = ace.require("ace/ext/language_tools");
var editor = ace.edit("editor");
editor.setOptions({enableBasicAutocompletion: true});
// uses http://rhymebrain.com/api.html
var rhymeCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
if (prefix.length === 0) { callback(null, []); return }
$.getJSON(
"http://rhymebrain.com/talk?function=getRhymes&word=" + prefix,
function(wordList) {
// wordList like [{"word":"flow","freq":24,"score":300,"flags":"bc","syllables":"1"}]
callback(null, wordList.map(function(ea) {
return {name: ea.word, value: ea.word, score: ea.score, meta: "rhyme"}
}));
})
}
}
langTools.addCompleter(rhymeCompleter);
For example, type "trace" and hit ctrl-space, and only a few suggestions pop up (retrace, interlace, interface).
The rhyming dictionary actually suggested many other matches, and they were passed in to the callback, but internally Ace filtered them out because they don't contain the letters "t", "r", "a", "c", and "e". Is there a way to bypass that filter so that it suggests all of the things that were passed to the callback?
The Autocompletion is filtered by ace in the autocompleter file. But you can set insertMatch to avoid this filter,
insertMatch: function(editor, data) {
editor.completer.insertMatch({value: data.value})
});
You can update the callback function in this way:
callback(null,
wordList.map(function (word) {
return {
caption: word,
value: word,
completer: {
insertMatch: function (editor, data) {
editor.completer.insertMatch({value: data.value})
}
}
}

Dynamically autocomplete in ace editor

I want to update autocomplete suggestions according to the string.
aceeditorObj.completers.push({
getCompletions: function(editor, session, pos, prefix, callback) {
obj = editor.getSession().getTokenAt(pos.row, pos.column-count);
if(obj.value === "student"){
var wordList = ["name", "age" , "surname"];
callback(null, wordList.map(function(word) {
return {
caption: word,
value: word,
meta: "static"
};
}));
}
}
});
Name, age and surname is added to the auto suggestion list. But the old ones are still there. How can I show only the new world list in the list of auto completion?
Try setting the language tools to blank after you call your completers function:
var langTools = ace.require("ace/ext/language_tools");
aceeditorObj.completers.push({
getCompletions: function(editor, session, pos, prefix, callback) {
obj = editor.getSession().getTokenAt(pos.row, pos.column-count);
if(obj.value === "student"){
var wordList = ["name", "age" , "surname"];
callback(null, wordList.map(function(word) {
return {
caption: word,
value: word,
meta: "static"
};
}));
}
}
});
langTools.setCompleters([]); // This function should clear them

Add editable dynamic objects to an array in Mithriljs

I have an array, that I need to populate with multiple 'properties', something like this:
[
{
name: 'Date',
value: '27 Oct'
},
{
name: 'Type',
value: 'Image'
},
{
name: 'Client',
value: 'Apple'
}
]
I want to list out all the properties in ul and I want to have a + button, that will add a new object to an array (initially name and value will be blank). After double clicking on each item (li in this case), I want to make these properties editable.
So I did something like this:
var Properties = {};
Properties.items = [
{
name: m.prop('Date'),
value: m.prop('21 Dec'),
editable: m.prop(false)
}
];
This is my initial array.
Then, I have a controller:
Properties.controller = function() {
this.addNewItem = function() {
Properties.items.push({
name: m.prop('Sample'),
value: m.prop('Test'),
editable: m.prop(false)
})
};
this.toggleEditable = function(item) {
item.editable(!item.editable());
console.log(item.editable());
};
};
My view:
Properties.view = function(ctrl) {
return m('ul.list-unstyled', [
Properties.items.map(function(item) {
return m('li', [
item.editable() ? m('input', {
value: item.name(),
onkeyup: function(e) {
if(e.keyCode == 13) {
ctrl.toggleEditable.bind(ctrl, item);
} else {
m.withAttr('value', item.name)
}
}
}) :
m('span', {
ondblclick: ctrl.toggleEditable.bind(ctrl, item)
}, item.name()),
m('span', ': '),
m('span', item.value())
]);
}),
m('div',{
onclick: ctrl.addNewItem.bind(ctrl)
}, '+')
]);
};
And finally:
m.mount(document.querySelector('.container'), Properties);
But when I start typing, it sort of overwrites what I wrote and doesn't save the new value..
Any ideas, anyone?
You should keep your original array as the data model, pass it to Properties and let Properties store only the editable state. That is a nice data separation and will simplify the code. It may fix the save problem as well, since it can just set the array value directly in the onkeyup event.
For the overwrite problem, maybe adding a key attribute will work? It seems related. Check out http://mithril.js.org/mithril.html#dealing-with-focus for more information.

Sencha Touch store filterBy function

Hi I am trying to work with the store filterBy function but i am unable to make it work as I want.
I have four buttons(A B C D) and a list with all data related to buttons
When I click on the button I should filter accordingly
Note:- when I click A ,I should get records of A
when I click B , I should get records of B appended to A's list of records
So actually when I click on the buttons I should have a array of button ids and filter the the list using these id's.
filterList:function (list, index, target, record){
var categories=[];
categories.push(record.get('id'));
for(c=0;c<categories.length;c++)
{
Ext.getStore('mystore').filterBy(function(record){
var id = record.get('id');
if(id==categories[c])
{
return true;
}
});
}
}
Any help is appreciated.
What is wrong with your code
The filterBy return function should always return a boolean value. You are only returning true, and no false. Besides that you are filtering the store in a loop, and the parameter record exists twice (as a parameter in the function filterList and in the return function of the filterBy).
Example
I created a Fiddle to show you where I came up with. I know it is ExtJs instead of Sencha Touch, but you will get the idea. I will step through the MainController, where all the logic is.
I didn't clean the code so there is some duplication, but it's just a concept.
Setting up some logic
First I create a set of buttons on my view. See that I'm setting an action property.
...
tbar: [{
text: 'Filter a',
action: 'a'
}, {
text: 'Filter b',
action: 'b'
}],
...
Then I bind an onClick event to the button. In this event I use the action property as a searchValue, but it could be anything of course.
...
onClick: function(button, event, eOpts) {
var me = this,
grid = me.getMainGrid(),
store = grid.getStore(),
filters = store.getFilters(),
searchValue = button.action,
regex = RegExp(searchValue, 'i'),
filter = new Ext.util.Filter({
id: button.id,
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
});
if (filters.containsKey(button.id)) {
store.removeFilter(filter);
} else {
store.addFilter(filter);
}
},
...
The magic
The magic is in the filter instance. By adding new filter instances each time you filter, you can filter with several filters. If you push button a and button b they will respect each other. In that filter I use a regex to search through all data of the current model. I config an id to recognize the filter so I can remove it afterwards.
filter = new Ext.util.Filter({
id: button.id,
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
});
If the filter doesn't exists it will be added, otherwise it will be removed.
if (filters.containsKey(button.id)) {
store.removeFilter(filter);
} else {
store.addFilter(filter);
}
Reset filters
I also created a textfield to search through all data. Instead of adding and removing filters I just call clearFilter and add a new filter with new searchvalue.
onKeyUp: function(textField, event, eOpts) {
this.filterStore(textField.getValue());
},
filterStore: function(searchValue) {
var me = this,
grid = me.getMainGrid(),
store = grid.getStore(),
regex = RegExp(searchValue, 'i');
store.clearFilter(true);
store.filter(new Ext.util.Filter({
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
}));
}
The complete maincontroller
Ext.define('Filtering.controller.MainController', {
extend: 'Ext.app.Controller',
config: {
refs: {
mainGrid: 'maingrid'
}
},
init: function() {
var me = this;
me.listen({
global: {},
controller: {},
component: {
'segmentedbutton': {
toggle: 'onToggle'
},
'toolbar > button': {
click: 'onClick'
},
'textfield': {
keyup: 'onKeyUp'
}
},
store: {
'*': {
metachange: 'onMetaChange',
filterchange: 'onFilterChange'
}
},
direct: {}
});
},
onLaunch: function() {
var store = Ext.StoreManager.lookup('Users') || Ext.getStore('Users');
store.load();
},
onMetaChange: function(store, metaData) {
var grid = this.getMainGrid(),
model = store.getModel(),
// metadata
fields = metaData.fields,
columns = metaData.columns,
gridTitle = metaData.gridTitle;
model.fields = fields;
grid.setTitle(gridTitle);
grid.reconfigure(store, columns);
},
onFilterChange: function(store, filters, eOpts) {
var me = this,
grid = me.getMainGrid();
grid.getSelectionModel().select(0);
},
/**
* Used for the segmented buttons
*/
onToggle: function(container, button, pressed) {
var me = this,
grid = me.getMainGrid(),
store = grid.getStore(),
//filters = store.getFilters(),
searchValue = button.action,
regex = RegExp(searchValue, 'i'),
filter = new Ext.util.Filter({
id: button.id,
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
});
if (pressed) {
store.addFilter(filter);
} else {
store.removeFilter(filter);
}
},
/**
* Used for the toolbar buttons
*/
onClick: function(button, event, eOpts) {
var me = this,
grid = me.getMainGrid(),
store = grid.getStore(),
filters = store.getFilters(),
searchValue = button.action,
regex = RegExp(searchValue, 'i'),
filter = new Ext.util.Filter({
id: button.id,
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
});
if (filters.containsKey(button.id)) {
store.removeFilter(filter);
} else {
store.addFilter(filter);
}
},
/**
* Used for the textfield
*/
onKeyUp: function(textField, event, eOpts) {
this.filterStore(textField.getValue());
},
filterStore: function(searchValue) {
var me = this,
grid = me.getMainGrid(),
store = grid.getStore(),
regex = RegExp(searchValue, 'i');
store.clearFilter(true);
store.filter(new Ext.util.Filter({
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
}));
}
});
The complete view
Ext.define('Filtering.view.MainGrid', {
extend: 'Ext.grid.Panel',
xtype: 'maingrid',
tbar: [{
xtype: 'segmentedbutton',
allowMultiple: true,
items: [{
text: 'Filter a',
action: 'a'
}, {
text: 'Filter b',
action: 'b'
}]
}, {
text: 'Filter a',
action: 'a'
}, {
text: 'Filter b',
action: 'b'
}, {
xtype: 'textfield',
emptyText: 'Search',
enableKeyEvents: true
}],
//title: 'Users', // Title is set through the metadata
//store: 'Users', // we reconfigure the store in the metachange event of the store
columns: [] // columns are set through the metadata of the store (but we must set an empty array to avoid problems)
});
Assuming you have two toggle buttons and you want to filter store on toggling of those buttons,
Controller
Ext.define('namespace',{
extend:'controller...',
config:{
refs:{
btnA:'#btnA',
btnB:'#btnB',
},
controls:{
btnA:{
change:'btnChange'
},
btnB:{
change:'btnChange'
}
}
},
btnChange:function(ths,val){
var btnA = this.getBtnA().getValue(),
btnB = this.getBtnB().getValue();
Ext.getStore('mystore').filterBy(function(r){
if(btnA && btnB){
return r.get('id') === btnA.getValue() || r.get('id') === btnB.getValue();
}else if(btnA){
return r.get('id') === btnA.getValue();
}else if(btnB){
return r.get('id') === btnB.getValue();
}
return false;
});
}
});

Categories

Resources