In CodeMirror, bind `%%` to autocomplete and add `%%` after the chosen option - javascript

I have a project that want to have CodeMirror implemented. Basically, one of the requirements is that you can type double % (for example: %% keyword %%) to display a list (a hint). I have seen the official example with Ctrl+Space, but I'm wondering how I can make typing the second percent character of the start double percent be a trigger to show the hint list, and how I can display the keyword with the end double percent after choosing an option from the list. I need help or any demo or sample code.

I was having similar problems with CodeMirror, I'll share what I have learned.
type double % (for example: %% keyword %%) to display a list (a hint).
To achieve this, first you need to handle the 'change' event:
var cm = CodeMirror.fromTextArea(document.getElementById('textArea'),
{
mode: 'your_custom_language',
lineNumbers: true,
extraKeys: {'Ctrl-Space': 'autocomplete'}
});
var onChange = function(instance, object)
{
// do stuff...
}
CodeMirror.on(cm, 'change', onChange);
Then, on the onChange function you must do the following:
Check if the last inserted character is %.
Check if the previous character is %.
Summon the hint list.
I did it this way:
var onChange = function(instance, object)
{
// Check if the last inserted character is `%`.
if (object.text[0] === '%' &&
// Check if the previous character is `%`.
instance.getRange({ch: object.to.ch - 1, line: object.to.line}, object.to) === '%')
{
// Summon the hint list.
CodeMirror.showHint(cm, CodeMirror.hint.your_custom_language);
}
}
Note that I use the cm object previously declared, you must change this to meet your requirements. And also, the deduced context for your keyword will not match what you're expecting; in order to fix this you need to modify your own codemirror/addon/hint/your_custom_language-hint.js; in my case, I based my custom language on JavaScript (refactoring the javascript-hint.js) modifying the function called maybeAdd from:
function maybeAdd(str) {
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
}
to:
function maybeAdd(str)
{
if (isValidHint(str))
{
found.push({text: '%%' + str + '%%', displayText: str});
}
}
Note that the array found of getCompletions is no longer storing strings, it is storing objects with this format:
{
// this will be written when the hint is selected.
"text": "%%text%%",
// this will be shown on the hint list.
"displayText": "text",
// You can custom each hint match with a CSS class.
"className": "CSSClass"
}
Please, note that all the things I've writed above are refactoring of my CodeMirror custom language, it is untested on your own custom language but I hope it helps.
BTW; I think that CodeMirror documentation doesn't look clear and lacks on examples of many demanded features.

Related

What, how and when to use static formats, formats and format on custom blot?

DESCRIPTION
I am trying to create an inline blot for text highlighting. I know this feature is already present in quill. But in my implementation I would like to assign a different value to the html element depending on the type of highlighting that was assigned. Here's what I got:
let Inline = Quill.import('blots/inline');
class TextHighlight extends Inline {
static create(value) {
let node = super.create();
if(!value || value < 1) return node;
if(value == 5){
node.style.color = 'rgb(225, 225, 225)';
}
else {
node.style.borderRadius = '2px';
node.style.padding = '2px';
if(value == 1){ node.style.background = 'rgb(254, 255, 171)'; }
if(value == 2){ node.style.background = 'rgb(255, 171, 171)'; }
if(value == 3){ node.style.background = 'rgb(171, 207, 255)'; }
if(value == 4){ node.style.background = 'rgb(178, 255, 171)'; }
}
node.setAttribute('data-value' , value);
return node;
}
formats() {
console.log('#formats()');
let result = this.domNode.getAttribute('data-value');
return result ? result : 0;
}
}
TextHighlight.blotName = 'texthighlight';
TextHighlight.tagName = 'span';
My remove/add function:
function highlightSelectedText(value) {
if (value < 0 || value > 5) return false;
var range = quill.getSelection();
if (range && range.length > 0) {
if (value > 0) {
quill.formatText(
range.index,
range.length,
'texthighlight',
value,
true);
}
else {
quill.formatText(range.index, range.length, 'texthighlight', false, false);
}
}
}
And finally the creation of the Quill instance:
var toolbarOptions = {
container: '#toolbar-container',
handlers: {
'texthighlight': function (value) {
highlightSelectedText(value);
}
}
};
var quill = new Quill('#editor', {
theme: 'bubble',
modules: {
toolbar: toolbarOptions
}
});
The problems
Highlighted text snippets have the following Delta:
...
{
"attributes": {
"0": "3"
},
"insert": "highlighted text"
},
...
"texthighlight" should appear instead of 0, like:
...
{
"attributes": {
"texthighlight": "3"
},
"insert": "highlighted text"
},
...
If I apply formatting more than once, it starts accumulating, putting markup inside markup. For example:
<span class="texthighlight"><span class="texthighlight"><span class="texthighlight"><span class="texthighlight"></span></span></span></span>
The expected behavior is that only one highlight is present.
I cannot remove the formatting.
CONCLUSION
There is no doubt that I lack knowledge about how to properly implement this. I was able to create simpler blots in other situations, but now I'm really getting confused about overriding certain blot methods. For example, the following list shows which methods I mean, and what I understand about each:
static formats(node): Returns formats represented by domNode. Called on selection events when index is within formatted range.
formats(): Returns formats represented by this Blot. It is used for Delta generation. Called on selection events when index is within formatted range.
format(format , value): Applies formatting to this blot.
In the highlight implementation demonstrated, only formats() and create(value) are being called. I know there is an example of how each of these methods is implemented, but I am not getting the desired behavior. I think it's because I don't know how to exactly implement them. Could someone answer me what they really do, when they are called, and how they should behave (be implemented)?
Can somebody help me, please? :(
EDIT (Dec 18, 2019)
So... After hours of research, I was finally able to make the editor a little more correct. In this whole story I ended up creating a blot that is capable of formatting text with different styles depending on the value passed to formatting. You can even remove formatting by providing or omitting a value.
I was finally able to get an answer for item 2 and, as I thought, found it within the toolbar module. Regarding the formats methods and their differences, I think I could better understand it. I still don't understand why formats are called so often. I think I'll find the answer looking at quill core, but... The quill source code is very large as it involves multiple files.
I am putting here a more updated version of the code shown earlier. It is all commented, favoring anyone who wants to learn more about how to:
Define custom blots/formats,
Define blots that accept values, and behave in different ways
depending on the configured value.
Define custom toolbar buttons that respond and reflect the state of the
editor content.
I hope that this will enable people to better understand how this tool can be used, and to create greater interest and contribution with it.
Full Example on GitHub
ORIGINAL ANSWER
It seems that I somehow managed to get the desired result. I am putting an answer here, but I do not consider it correct yet, as it is not complete. The desired result is obtained, but I'm still unable to understood how or why things work. Understanding the process becomes something fundamental, especially if and when the code needs to be changed in the future. Below you can check the code of the whole project. To test, just run it.
My remaining questions are:
What are the differences between formats and static formats(domNode)? If you
observe code execution, they are called a few times, and static formats(domNode) are called twice. Is this normal? I do not know, that's why I am asking.
Within function highlightSelectedText(hl), why does hl appear with
a false value? How does this happen?
When formatting is applied, the create is used. When
it is removed, format(format, value) is called. There are snippets of code (inside format) that are never reached. Shouldn't applying
and removing formats be a format-only job? Why do I have to change
the DOM element in two different locations?
I think I can find the answer to number 2 by looking at the toolbar module source code. But I'm not quite sure. If I can figure it all out, I'll be back here to edit this answer, okay? I know others may also have the same questions.

How to hide/remove field in Blockly?

How to hide a field based on the dropdown value change.
I added a input field called 'A'. I have a drop-down field. If I select a value in the drop down, say 'Remove field A', then the input field should be removed.
I tried removeField. But it did not work. Any other methods? or how to use remove-field correctly?
this.appendDummyInput()
.setAlign(Blockly.ALIGN_RIGHT)
.appendField('Type ')
.appendField(new Blockly.FieldDropdown(typeOptions), 'columnType');
// if columnType = Card, show the following:
this.appendDummyInput()
.setAlign(Blockly.ALIGN_RIGHT)
.appendField(' Card: ')
.appendField(new Blockly.FieldDropdown(cardsList), 'cardValue');
// if columnType = view, show the following:
this.appendDummyInput()
.setAlign(Blockly.ALIGN_RIGHT)
.appendField(' View ')
.appendField(new Blockly.FieldDropdown(viewsList), 'viewValue');
Okay, so this doesn't have your full code, but I think I see your problem here.
The short answer is that, in your provided code, I don't see you doing anything in the callback functions when selecting a new value in your block, nor do I see you saving it from/reading it from the XML. It's possible some of this was left out, but in the interest of not making you play "include more code" tag in the comments, I'll just do a rundown here.
Let me show you some sample code and walk through what all I do in order to make this case work:
Blockly.Blocks['mySampleBlock'] = {
/**
* Initiate the block. This runs before domToMutation.
*/
init: function () {
var typeOptions = [['Card', 'card'], ['View', 'view']];
this.appendDummyInput()
.setAlign(Blockly.ALIGN_RIGHT)
.appendField('Type ')
.appendField(new Blockly.FieldDropdown(typeOptions, this.handleTypeSelection.bind(this)), 'typeSelector');
// Initialize the value of this.columnType (used in updateShape)
this.columnType = this.getFieldValue('typeSelector');
// Avoid duplicating code by running updateShape to append your appropriate input
this.updateShape();
//#TODO: Do other block configuration stuff like colors, additional inputs, etc. here
},
/**
* This function runs each time you select a new value in your type selection dropdown field.
* #param {string} newType This is the new value that the field will be set to.
*
* Important note: this function will run BEFORE the field's value is updated. This means that if you call
* this.getFieldValue('typeSelector') within here, it will reflect the OLD value.
*
*/
handleTypeSelection: function (newType) {
// Avoid unnecessary updates if someone clicks the same field twice
if(this.columnType !== newType) {
// Update this.columnType to the new value
this.columnType = newType;
// Add or remove fields as appropriate
this.updateShape();
}
},
/**
* This will remove old inputs and add new inputs as you need, based on the columnType value selected
*/
updateShape: function () {
// Remove the old input (so that you don't have inputs stack repeatedly)
if (this.getInput('appendToMe')) {
this.removeInput('appendToMe');
}
// Append the new input based on the value of this.columnType
if(this.columnType === 'card') {
// if columnType = Card, show the following:
//#TODO: define values in cardsList here
var cardsList = [['Dummy Option','option']];
this.appendDummyInput('appendToMe')
.setAlign(Blockly.ALIGN_RIGHT)
.appendField(' Card: ')
.appendField(new Blockly.FieldDropdown(cardsList), 'cardValue');
} else if (this.columnType === 'view') {
// if columnType = view, show the following:
//#TODO: define values in viewsList here
var viewsList = [['Dummy Option','option']];
this.appendDummyInput()
.setAlign(Blockly.ALIGN_RIGHT)
.appendField(' View ')
.appendField(new Blockly.FieldDropdown(viewsList), 'viewValue');
}
},
/**
* This function runs when saving your block to XML. This is important if you need to save your block to XML at any point and then either
* generate code from that XML or repopulate your workspace from that XML
*/
mutationToDom: function () {
var container = document.createElement('mutation');
// Do not use camelCase values for attribute names.
container.setAttribute('column_type', this.columnType);
// ALWAYS return container; this will be the input for domToMutation.
return container;
},
/**
* This function runs when loading your block from XML, after running init.
* It's very important for updating your block in response to values selected in a field.
*/
domToMutation: function (xmlElement) {
// This attribute should match the one you used in mutationToDom
var columnType = xmlElement.getAttribute('column_type');
// If, for whatever reason, you try to save an undefined value in column_type, it will actually be saved as the string 'undefined'
// If this is not an acceptable value, filter it out
if(columnType && columnType !== 'undefined') {
this.columnType = columnType;
}
// Run updateShape to append block values as needed
this.updateShape();
}
};
A few things to note about this situation, in addition to my explanatory comments:
You don't strictly have to use my this.columnType construction. Instead, you can pass a columnType value into updateShape and use this.getFieldValue('typeSelector') or the input of your 'callback' function (handleTypeSelection). I tend to prefer this because I often make much more complicated blocks where it's hard or inefficient to get the appropriate value every time, and this.whateverMyValueNameIs is easier.
Likewise, instead of this.removeInput and this.appendDummyInput in updateShape, you can use removeField and appendField, as was your first instinct. However, if you do this, you will need to make sure that you have named the input you intend to append your field to/remove it from. I tend to prefer just adding/removing the entire input in most cases because it also lets me change out labels, etc.
Any time you're making any changes in response to the value of a dropdown, you should probably be adding domToMutation and mutationToDom to save that value to the mutation attribute and then read it out and update your block accordingly. This applies even if you don't have an actual mutator on your block.
Pay attention to the TODO comments in here; since I didn't know the values for viewsList and cardsList, I didn't provide them, nor did I provide any other block configuration for you.
This can be a bit confusing, so please ask any follow-up questions if you have them. It took me a while to get the hang of it, myself. (I may request additional code samples from you if I'm not clear on what you're trying to do, though.)
Good luck!

Auto-complete with CodeMirrror

I am trying to implement auto-complete using CodeMirror show-hint addon, specifically with sql-hint. I want it auto-complete as I type.
What I am doing right now is,
codemirror_editor.on("change", function(instance) {
CodeMirror.commands.autocomplete(instance);
});
But the problem is, it completes words before I even type a single letter for a word. For example after space, it gives a long list of all possible tokens. I want it to show up only if some characters are typed. Can someone please help with that?
Before firing the autocomplete command, check whether the cursor is actually after 'some' (whatever 'some' means) letter characters. Also, do nothing when instance.state.completionActive is true, since that means there's already a completion popup open. Finally, you probably want to listen for the "inputRead" event instead of "change", so that you don't trigger when a change is made to the document in a way that didn't involve the user typing.
This is how I solved this, after Marijn's answer.
codemirror_editor.on("inputRead", function(instance) {
if (instance.state.completionActive) {
return;
}
var cur = instance.getCursor();
var token = instance.getTokenAt(cur);
var string = '';
if (token.string.match(/^[.`\w#]\w*$/)) {
string = token.string;
}
if (string.length > 0) {
CodeMirror.commands.autocomplete(instance);
}
});
This may be specific to SQL.
I am answering my own question to share the actual solution for the question.

jquery autocomplete, multiple values, minLength ignored

I'm trying to make this: http://jsfiddle.net/F9tMx/ work in a way that i can propose to the user who is typing separate suggestions from the availableTags Array.
Basically I'd like to add the same function that is present here on SO when someone is adding a comment to a questions and types the '#' character (a list of user appears if the first character[s] match someone who made activity on the page).
Anyway, I've seen that the minLength parameter is ignored. While the plugin is indeed able to separate words and match against the last word typed, once that the last word is actually empty (last == '') the plugin will propose ALL the elements in the availableTags Array, even if minLength is setted to a number greater than 1.
is there a way I could fix this on your opinion?
here's how I'd like it to behave, but of course it's raising an error
http://jsfiddle.net/F9tMx/3/
Hiya cool using # working demo here: http://jsfiddle.net/67dxH/
so: here is the answer which I replied and helped to someone else: jquery autocomplete #mention
Code segment
this statement will do the trick if (request.term.indexOf("#") >= 0) { rest code is in jdfiddle
.autocomplete({
source: function(request, response) {
if (request.term.indexOf("#") >= 0) {
$("#loading").show();
getTags(extractLast(request.term), function(data) {
response($.map(data.tags, function(el) {
return {
value: el.name,
count: el.count
}
}));
$("#loading").hide();
});
}
},
Have a nice one!
cheers!

Optional character in input mask

How do I specify an optional character in an input mask?
I found this masked input plugin http://digitalbush.com/projects/masked-input-plugin/ and these mask definitions.
$.mask.definitions['g']="[ ]";
$.mask.definitions['h']="[aApP]";
$.mask.definitions['i']="[mM]";
$.mask.definitions['2']="[0-1]";
$.mask.definitions['6']="[0-5]";
new_mask = "29:69";
$("#txtTime").mask(new_mask);
This defines an input mask for a 12 hour time format, e.g. 11:00. I'd like to allow the user to specify only one digit for the "hour". Instead of having to type 01:00, the user should be able to type 1:00. How do I do that?
You need to use ? for optional characters so in your case you should use "2?9:69".
Quote and example from the page you linked to in your question:
You can have part of your mask be optional. Anything listed after '?'
within the mask is considered optional user input. The common example
for this is phone number + optional extension.
jQuery(function($){
$("#phone").mask("(999) 999-9999? x99999");
});
You can use $.mask.definitions['g']="[0-9 ]";
I have a similar problem I am trying to solve for variable currency amounts ($0.00 - $10000.00), and I don't really understand how the plugin is working.
On the one hand, the $.mask.definitions['symbol'] = '???' bit looks like it's employing a regex fragment, based on the examples in the API reference, and somewhat confirmed by this part in the source, within the mask function's code:
$.each(mask.split(""), function (i, c) {
if (c == '?') {
len--;
partialPosition = i;
} else if (defs[c]) {
tests.push(new RegExp(defs[c]));
if (firstNonMaskPos == null)
firstNonMaskPos = tests.length - 1;
} else {
tests.push(null);
}
});
...On the other, a mask definition of []|[0-9] does not work (I am attempting to do empty string or 0-9, for those not literate in regex.) For reference, I am attempting to build a mask to fulfill the 0.00-10000.00 condition like oooo9.99, where o is []|[0-9].
Since the code confirms that this is based upon regexes, the null or range pattern should be working but aren't; this is a more obscure bug in the mask framework. Unless I am the one trying to do something incorrectly, which is also possible...

Categories