How to hide/remove field in Blockly? - javascript

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!

Related

Can't access JavaScript variable inside function

I'm making an extension for selected text search in different search engines. One of the menu items changes if the selected string is a particular one. I have this done so far and it does what I want, except I can not make the "title" of the menu change. How do I give value to the variable "myTitle" in if statement inside the function?
Thanks in advance.
var myTitle; // if I give value here it does work, but I need the value based on the if statement bellow.
function myFunction(selectedText) {
if(selectedText.match (/someString|SomeOtherString/)) {
var myURL1 = 'https://someURL' + selectedText;
chrome.tabs.create({url: myURL1});
myTitle = "title1"; //I can not make the variable to get this value here
}else{
var myURL2 = 'https://someOtherURL' + selectedText;
chrome.tabs.create({url: topicCall});
myTitle = "title2"; //I can not make the variable to get this value here
}
}
chrome.contextMenus.create({
**"title": myTitle,** // this should change based on the selection
contexts:["selection"],
onclick: function (info)myFunction(info.selectionText);}
});
You have "cause and effect" mixed up. When chrome.contextMenus.create is called, myFunction has not executed yet and the value of myTitle has not been assigned.
Perhaps, you can use chrome.contextMenus.update, after the page is loaded. You could create the menu, with default titles, but with unique IDs. Then use the above method, based on whatever the "selectedText" is, and use the IDs to replace the titles.

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

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.

How to validate a change in a cell and determine whether that change should be comited?

I have something like so:
CELL #0 CELL #1
------------------------
|start_date | end_date |
|----------------------|
|1/1/2012 |1/1/2013 | ROW#0
|----------------------|
I would like to assert that if the user clicks on (Row0,Cell0) and tries to change the start date, then the start date has to be less than the end date else the changes are not put through. Is this possible?
Currently when creating the columns, I add a 'validator' property and pass in the function below
startDateValidator(value){
/* value is new value, need to reference the end_date to make sure
that value < end_date but not possible in this column validator */
}
but as the comment points out, we are given the new value but no reference to the old data-item. Is there a way to get around this? I can change the source, but would want to avoid if possible.
It's possible to do it on the onCellChange event of the grid, I stripped up some code from a previous implementation I have so don't be too hard on me if it doesn't work first time... Assuming the ID of both your dates are start_date and end_date, then here is a working concept:
grid.onCellChange.subscribe(function(e,args) {
// do a proper validation of date
if (args.item.start_date < args.item.end_date) {
// post/save result to DB and re-render the grid
$.post('yourAjaxFile.php?action=update', args.item, function(ServerResponseUpt) {
// success, update the datagrid view on screen
grid.updateRow(args.row);
grid.updateRowCount();
grid.render();
});
}else {
// invalid, undo the changes from grid, this will put back orginal value
var command = commandUndoQueue.pop();
if (command && Slick.GlobalEditorLock.cancelCurrentEdit()) {
command.undo();
grid.gotoCell(command.row, command.cell, false);
}
}
});
Please note that the dates validation is obviously incorrect here, though the main answer is more focused on how to achieve the rest of the event
Use OnBeforeEdit instead of onCellChange
grid.onBeforeEditCell.subscribe(function (e, args) {
//returning true will update the cell and returning false will not update it
}

Alert once when user is outside geofence

I have the bellow code checking if a map marker is inside or outside of a geofence.
i am going to make it alert detected out of the bounds.
My problem is the map markers a refreshing constantly and i do not want the alert to be repeated over an over.
I need to set something when the alarm is played. Then only do the alert if that thing is unset.
When the user is detected back inside the bounds it will unset it also.
if (name === f.contact) {
var fence = new google.maps.LatLng(f.lat, f.lng);
var dist = google.maps.geometry.spherical.computeDistanceBetween(posi, fence);
// check if in/out of fence
if (dist > f.radius) {
console.log(f.contact+" : "+dist+" meters - outside the fence");
// OMG outside the fence play an alarm
} else {
console.log(f.contact+" : "+dist+" meters - inside the fence");
// Back inside the fence, reset the alarm
}
}
i was thinking possibly making an array like this
var alertSent = [];
and then if outside the geofence adding the users name to it
alertSent.push(name);
how would i check if the name exists in the array?
and how would i delete the name from the array when back inside the fence?
You could use an Object as an asociative array and use the names as keys and a boolean value for sent / not sent. alertSent[name] also evaluates to a falsy value if it doesn't contain name at all.
var alertSent = {};
// if user outside: check
if (!alertSent[name]) {
// show alert
// remember that alert was shown
alertSent[name] = true;
}
// remove name from alertSent:
alertSent[name] = false;
If you end up using array, then you have search it for every index till you find the string like
How do I check if an array includes an object in JavaScript?
or
Best way to find if an item is in a JavaScript array?
You can also think about, registering events and eventlisteners for handling this problem, it would be a better design.
Or you can also use hashmap kind of javascript implementation like using
alertSent["driver1"]=true;,
Lookup will be simple in this case just use ,
alertSent["driver1"]
to get the boolean value. But take care of array space in this case.

How to get the long/lat returned after a text search

example can be found at:
http://projects.snowshtechnologies.com/findhrr/_admin/_geolocate/content.php
view the source for the full idea.
I am trying to get the JS to populate the long/lat fields as soon as a text search result has re-positioned the map.
The linked Map script has a submit feature which returns the long lat back to a parent form (not included in this link - so don't worry about that!)
There are two main methods here:
First is the text search - which is used to determine the map location buy entering an address. The required fix lies here, after the user clicks search, the map returns the position - all good. But I also now need the long/lat to appear in the text areas and enable the submit button.. you can see that functionality in the next method:
The second method simply allows the user to drag the map, the last clicked position will populate the lon/lat fields.
The txt search function:
function FindLoc(numResults)
{
try
{
results = map.Find(document.getElementById('txtWhat').value,
document.getElementById('txtWhere').value,
null,
null,
index,
numResults,
true,
true,
true,
true,
MoreResults);
index = parseInt(index)+9;
}
catch(e)
{
alert(e.message);
}
map.AttachEvent("onchange", DisplayCoords);
}
I have a function displayCoords that will output the clicked long/lat position to the page, I have included it in the text search function but no joy.
map.AttachEvent("onchange", DisplayCoords);
Any ideas?
I can't change your sources so I can't test this to be sure, however, I believe you want to do the following:
1) Get rid of the "map.AttachEvent" in FindLoc, because this will attach a new event each time the user performs a find, and anyway it's not a correct event name for the map type so it won't be triggered. The list of valid events is here.
2) Change your MoreResults callback so that it reads the resultsArray parameter, which is an array of find results, and reads the LatLong property from an array element. This class is documented here. For example, something like:
var pos;
if (resultsArray && resultsArray.length > 0) {
pos = resultsArray[0].LatLong;
if (pos) {
document.getElementById("mapLatitude").value = pos.Latitude;
document.getElementById("mapLongitude").value = pos.Longitude;
document.getElementById('getLongLat').style.display='block';
}
}
Edit: The resultsArray always seems to be null, so you need to use the places array instead. Add this to the beginning of MoreResults:
if (places && places.length > 0) {
document.getElementById("mapLatitude").value = places[0].LatLong.Latitude;
document.getElementById("mapLongitude").value = places[0].LatLong.Longitude;
document.getElementById('getLongLat').style.display='block';
}
I tested this alternative, and it does work for me.

Categories