I'm using handsontable js plugin. I want to use getCellMeta function in afterChange hook but not working.
I when use function out afterChange hook, function is working. But not working in afterChange hook.
var container = document.getElementById('t1'),
options = document.querySelectorAll('.options input'),
table,
hot;
hot = new Handsontable(container, {
autoWrapRow: true,
startRows: 81,
startCols: 206,
autoColumnSize : true,
stretchH: 'all',
afterChange : function(change,source) {
if (source === 'loadData') {
return;
}
var test = this.getCellMeta(change[0],change[1]); // not working, not return "id" meta
console.log(test);
}
});
$.ajax({
url: 'path',
type: 'GET',
dataType: 'json',
success: function (res) {
var data = [], row, pc = 0;
for (var i = 0, ilen = hot.countRows(); i < ilen; i++)
{
row = [];
for (var ii = 0; ii<hot.countCols(); ii++)
{
hot.setCellMeta(i,ii,'id',res[pc].id);
row[ii] = res[pc].price;
if(pc < (res.length-1)) {
pc++;
}
}
data[i] = row;
}
hot.loadData(data);
}
});
var test = this.getCellMeta(0,0); // is working, return "id" meta
console.log(test);
Output console log i tried out afterChange;
Output console log use in afterChange;
How to get cell meta after change?
Thanks.
You're almost there, there's just a small mistake in your callback: the doc for afterChange specifies that first argument (changes) of the callback is:
a 2D array containing information about each of the edited cells
[[row, prop, oldVal, newVal], ...].
So, 2 important details:
To get the "meta" of row/col of affected cell (assuming there is just one), you need to call hot.getCellMeta(change[0][0],change[0][1]) for example
On hot and not this because the afterChange callback function is invoked from a different context (ie on a different object), so this is not the right target for the call, see How does the "this" keyword work?
Example that reads the whole array of changes:
var hot = new Handsontable(container, {
/* rest of init... */
afterChange : function(changes,source) {
console.log("Changes:", changes, source);
if (changes) {
changes.forEach(function(change) {
var test = hot.getCellMeta(change[0],change[1]);
console.log(test.id, test); // 'id' is the property you've added earlier with setMeta
});
}
}
});
See demo fiddle, open JS console, make any change(s) in the table.
Related
As title, yesterday I learn how to retrieve the All-suggestion-accepted content of a document with API in this post, and I refer to the other post and know how to retrieve the insertion and deletion of the suggestion. Also, with the method above, I can retrieve the start-index(The position where insertion or deletion starts in a document) and end-index(The position where insertion or deletion ends in a document) of the insertion and deletion.
So, can I make changes, such as underlining the insertion or deletion parts, to the All-suggestion-accepted content referring to the indexes as positions? Is there going to be an error if I do this?
This is the array Google Doc API returned to me, and its format is
[suggestedDeletionIds,"delete",content,startIndex,endIndex]
This is the insertion and the deletion I made to the document, and I want to underline the deletion and insertion part within the All-suggestion-accepted content of a document based on the index.
Below is the snippet and the result of the snippet, last two figures are the start and the end index of the insertion or deletion.
function get_all_insertion_deletion() {
var documentId = "MYDOCUMENTID";
var doc = Docs.Documents.get(documentId);
remove_all(documentId);
doc.body.content.forEach(function (content){
if (content.paragraph) {
var elements = content.paragraph.elements;
elements.forEach(function (element){
if(element.textRun.suggestedDeletionIds)
{
var d_length= element.endIndex-element.startIndex;
var d= [element.textRun.suggestedDeletionIds,"delete",element.textRun.content,element.startIndex,element.endIndex];
Logger.log(d);
deletion++;
}
if(element.textRun.suggestedInsertionIds)
{
var i_length= element.endIndex-element.startIndex;
var i= [element.textRun.suggestedInsertionIds,"insert",element.textRun.content,element.startIndex,element.endIndex];
Logger.log(i);
insertion++; } }); }
});
}
I believe your goal is as follows.
You want to add the underline to "delete" and "insert" parts of your script in Google Document.
In order to achieve your goal, when your script is modified it becomes as follows.
Modified script:
function get_all_insertion_deletion() {
var documentId = "MYDOCUMENTID";
var doc = Docs.Documents.get(documentId);
var requests = [];
doc.body.content.forEach(function (content) {
if (content.paragraph) {
var elements = content.paragraph.elements;
elements.forEach(function (element) {
if (element.textRun.suggestedDeletionIds) {
// var d_length = element.endIndex - element.startIndex; // This is not used.
var d = [element.textRun.suggestedDeletionIds, "delete", element.textRun.content, element.startIndex, element.endIndex];
Logger.log(d);
requests.push({ updateTextStyle: { range: { startIndex: element.startIndex, endIndex: element.endIndex }, textStyle: { underline: true }, fields: "underline" } });
// deletion++; // This is not used.
}
if (element.textRun.suggestedInsertionIds) {
// var i_length = element.endIndex - element.startIndex; // This is not used.
var i = [element.textRun.suggestedInsertionIds, "insert", element.textRun.content, element.startIndex, element.endIndex];
requests.push({ updateTextStyle: { range: { startIndex: element.startIndex, endIndex: element.endIndex }, textStyle: { underline: true }, fields: "underline" } });
Logger.log(i);
// insertion++; // This is not used.
}
});
}
});
Docs.Documents.batchUpdate({requests}, documentId);
}
or, I thought that you can also the following modified script.
function get_all_insertion_deletion() {
var documentId = "MYDOCUMENTID";
var doc = Docs.Documents.get(documentId);
var requests = doc.body.content.flatMap(content => {
if (content.paragraph) {
var elements = content.paragraph.elements;
return elements.flatMap(element => element.textRun.suggestedDeletionIds || element.textRun.suggestedInsertionIds ? { updateTextStyle: { range: { startIndex: element.startIndex, endIndex: element.endIndex }, textStyle: { underline: true }, fields: "underline" } } : []);
}
return [];
});
Docs.Documents.batchUpdate({requests}, documentId);
}
Reference:
UpdateTextStyleRequest
I'm trying to delete a node from a vis.js graph. When the function is applied, instead of the node and its outgoing edges being deleted the entire graph gets erased. I want the changes to be kept intact when the page gets refreshed. The calls related to this are deleteNode (in index.html) and app.delete (in index.js).
index.html:
var deleteNode = function(data, callback){
$.ajax({
method: "delete",
url: "/api/node",
data: {node: data.nodes[0] },
success: function(result){
callback(result);
}
})
};
index.js:
app.delete("/api/node", function(req, res){
var deleteNode = req.body.node;
var deleteResult = {
nodes:[],
edges: []
};
var updatedNodes = _.filter(data.nodes, function(node){
var keep = (node.id !== deleteNode);
if(!keep){
deleteResult.nodes.push(node);
}
return keep;
});
var updatedEdges = _.filter(data.edges, function(edge){
var keep = (edge.from !== deleteNode) || (edge.to !== deleteNode);
if(!keep){
deleteResult.edges.push(node);
}
return keep;
});
data.nodes = updatedNodes;
data.edges = updatedEdges;
res.send(deleteResult).end();
});
Are you sure that your logic is correct? That is the first thing I would check here. In particular, I see that your 'delete' method seems to be returning the 'deleteResult' variable, which is going to have probably all the nodes that you deleted as a consequence of the API call. Perhaps you wanted to return the 'data' variable instead?
I'm using handsontable on Meteor 1.4.1 through the plugin awsp:handsontable#0.16.1
The problem I have is that the matrix gets re-rendered every time I change a value, which creates two issues. The first is that the focus of the edited cell gets lost and the scroll goes back to the top. The second is that sometimes the values are not saved because the data of the matrix get reloaded with each change.
The way I'm subscribing to the data and rendering the table is as follows:
Template.connectivityMatrix.onCreated(function () {
this.activeScenario = () => Session.get('active_scenario');
this.autorun(() => {
this.subscribe('connectivityMatrixUser', this.activeScenario());
});
});
Template.connectivityMatrix.onRendered(function () {
this.autorun(() => {
if (this.subscriptionsReady()) {
const activeScenario = Session.get('active_scenario');
const currentScenario = Scenarios.findOne({_id: activeScenario});
const currentTurn = currentScenario.turn;
const numObj = ConnectivityMatrix.find({scenario_id: activeScenario, user_id: Meteor.userId(), turn: currentTurn}).count();
var myData = []; // Need this to create instance
var container = document.getElementById('connectivity-matrix');
var hot = new Handsontable(container, { // Create Handsontable instance
data: myData,
startRows: numObj,
startCols: numObj,
afterChange: function (change, source) { // 'change' is an array of arrays.
if (source !== 'loadData') { // Don't need to run this when data is loaded
for (i = 0; i < change.length; i++) { // For each change, get the change info and update the record
var rowNum = change[i][0]; // Which row it appears on Handsontable
var row = myData[rowNum]; // Now we have the whole row of data, including _id
var key = change[i][1]; // Handsontable docs calls this 'prop'
var oldVal = change[i][2];
var newVal = change[i][3];
var setModifier = {$set: {}}; // Need to build $set object
setModifier.$set[key] = newVal; // So that we can assign 'key' dynamically using bracket notation of JavaScript object
ConnectivityMatrix.update(row._id, setModifier);
}
}
}
});
myData = ConnectivityMatrix.find({scenario_id: activeScenario, turn: currentTurn, user_id: Meteor.userId()}, {sort: {created_at: 1}}).fetch(); // Tie in our data
hot.loadData(myData);
}
});
});
What I want to achieve is to create the matrix only once instead of recreate it with each data change so the focus stays and the data gets always saved.
So I've tried leaving only the last two lines inside the block of this.autorun() as suggested in this question
Template.connectivityMatrix.onRendered(function () {
const activeScenario = Session.get('active_scenario');
const currentScenario = Scenarios.findOne({_id: activeScenario});
const currentTurn = currentScenario.turn;
const numObj = ConnectivityMatrix.find({scenario_id: activeScenario, user_id: Meteor.userId(), turn: currentTurn}).count();
var hot = new Handsontable(container, { // Create Handsontable instance
...
});
this.autorun(() => {
if (this.subscriptionsReady()) {
myData = ConnectivityMatrix.find({scenario_id: activeScenario, turn: currentTurn, user_id: Meteor.userId()}, {sort: {created_at: 1}}).fetch(); // Tie in our data
hot.loadData(myData);
}
});
});
but then the first time I load the page, the data is not available so I get the error
Cannot read property 'turn' of undefined
Therefore, how can I properly get all the data needed to create the table without re-rendering it when a cell value changes?
Thanks in advance for any help.
You are trying to query the Scenarios and ConnectivityMatrix collections before they are ready. Move all mongo queries inside your this.subscriptionsReady() conditional block.
The way I managed to do what I needed is by stopping the computation after the matrix gets rendered. The code is as follows:
Template.connectivityMatrix.onRendered(function () {
this.autorun((computation) => {
if (this.subscriptionsReady()) {
const currentScenario = Scenarios.findOne({_id: activeScenario});
const currentTurn = currentScenario.turn;
const numObj = ConnectivityMatrix.find({scenario_id: activeScenario, user_id: Meteor.userId(), turn: currentTurn}).count();
var myData = []; // Need this to create instance
var container = document.getElementById('connectivity-matrix');
var hot = new Handsontable(container, { // Create Handsontable instance
data: myData,
colHeaders: arrayRowsCols,
rowHeaders: arrayRowsCols,
height: '450',
maxRows: numObj,
maxCols: numObj,
columns: columns,
afterChange: function (change, source) { // 'change' is an array of arrays.
if (source !== 'loadData') { // Don't need to run this when data is loaded
for (i = 0; i < change.length; i++) { // For each change, get the change info and update the record
var rowNum = change[i][0]; // Which row it appears on Handsontable
var row = myData[rowNum]; // Now we have the whole row of data, including _id
var key = change[i][1]; // Handsontable docs calls this 'prop'
var oldVal = change[i][2];
var newVal = change[i][3];
var setModifier = {$set: {}}; // Need to build $set object
setModifier.$set[key] = newVal; // So that we can assign 'key' dynamically using bracket notation of JavaScript object
ConnectivityMatrix.update(row._id, setModifier);
}
}
}
});
myData = ConnectivityMatrix.find({scenario_id: activeScenario, turn: currentTurn, user_id: Meteor.userId()}, {sort: {created_at: 1}}).fetch(); // Tie in our data
hot.loadData(myData);
computation.stop();
}
});
});
I have a very strange issue that I am running into. I am using jsTree from JQueryUI on one of my sites, and I have different implementations of it used in different .js files. One of them seems to work, which is very confusing as it uses almost identical code (only the variable names are different) to the implementation that is broken. The problem comes from the contextmenu function. The code I am using is as follows:
$(document).ready(function () {
if(typeof dryerList == 'undefined' || dryerList.length == 0) {
var dryerList = [];
$.ajax({
url:'../TrackingApp/getGrainBins.php?t=234.23423452353',
async: false,
success: function(text) {
try {
dryerList = $.parseJSON(text);
} catch (e) {
alert('ERROR: ' + e);
}
if(dryerList.length == 0) {
alert('ERROR: No fleet data received.')
}
}
});
}
$("#dryerListTree").jstree({
plugins : ['json_data', 'ui', 'themes', 'contextmenu'],
contextmenu: {items: customBinMenu},
json_data : { data: binNodes }
});
$('#dryerListTree').bind("dblclick.jstree", function (event) {
var node = $(event.target).closest("li");
var id = node[0].id;
for(i=0; i < dryerList.length; i++) {
if(id == dryerList[i].id) {
centerMap(dryerList[i].y, dryerList[i].x);
break;
}
}
});
});
function customBinMenu(node) {
if ($(node).hasClass("folder")) {
return;
}
var items = {
centerItem: {
label: "Locate",
action: function () {
// Centers map on selected bin
var id = node[0].id;
for(i=0; i < dryerList.length; i++) {
if(id == dryerList[i].id) {
centerMap(dryerList[i].y, dryerList[i].x);
break;
}
}
}
},
dashboardItem: {
label: "Dashboard",
action: function () {
// Opens dryer info window over map
var id = node[0].id;
var dryerIndex = -1;
for(i=0; i < dryerList.length; i++) {
if(id == dryerList[i].id) {
dryerIndex = i;
break;
}
}
}
}
};
return items;
}
The strange bit is, the double-click handler works just fine. When I get to the customBinMenu() function, the dryerList array is there, and dryerList[0] contains 4 of the 5 values that it should- but somehow the 'id' element has been dropped from that object. I have been looking at this for quite some time, and I can't figure out how it can drop a single element from the object without losing any other data, especially when identical code is working for a similar list. Any suggestions?
Ok, I read in your question: 'and dryerList[0] contains 4 of the 5 values that it should- but somehow the 'id' element has been dropped from that object'
So by 'element' and 'value' I assume you mean 'attribute': the node's 'id'-attribute to be precise ??
I see in your code: var id = node[0].id;
That should be: var id = node[0].getAttribute("id");
Good luck!
UPDATE 1:
Ok, if (as per your comment) var id = node[0].id; (getting id from node[0]) is ok, then if(id == dryerList[i].id) looks wrong, since you just (re-)defined id to be the value of node[0]'s id.
Actually I would not use 'id' as a var-name (in this case).
So what if you did: var idc = node[0].getAttribute("id");
and then: if(idc === dryerList[i].getAttribute("id"))
UPDATE 5: You still have some errors by the way:
You forgot a semi-colon to close the alert in:
if(dryerList.length == 0) {
alert('ERROR: No fleet data received.')
}
You should use '===' to compare with '0' on line 2 and 14
naturally in real life you would define function customBinMenu(node) before it was used in your document.ready function.
Fixed by swapping code order.
The same goes for this document.ready function where you used var dryerList before it was defined.
Fixed by: var dryerList = dryerList || []; if(dryerList.length === 0){//ajax & json code}
Could you please confirm if this fiddle, which is now valid javascript, represents your intended baseline-code that still results in your problem of the 'id'-attribute being 'undefined' in dryerList's node-collection (since the code you posted contained some simple errors that are fixed in this jsfiddle, excluding the things mentioned in update 1, since you commented that this is not the problem) ?
May I ask (since you start at document.ready), why do you (still) check if dryerList already exists?
May I ask if you could update that corrected fiddle with some demo-data for us to toy around with?
I'm trying to figure out how to rollback only a folder node that wasn't successfully moved. The code below is an example of what I'm trying to do. The problem comes when you have selected a couple of folders and moved them into another folder. If one of the directories fails to be moved I want to be able to roll it back to it's original parent.
Unfortunately $.jstree.rollback(data.rlbk); rollsback all of the folders that were selected to their previous locations.
$("#tree").jstree({...}).bind("move_node.jstree", function (e, data) {
// process all selected nodes directory
data.rslt.o.each(function (i) {
// Send request.
var move = $.parseJSON($.ajax({
url: "./jstree.php",
type: 'post',
async: false,
data: {
operation: "move_dir",
....
}
}).responseText);
// When everything's ok, the reponseText will be {success: true}
// In all other cases it won't exist at all.
if(move.success == undefined){
// Here I want to rollback the CURRENT failed node.
// $.jstree.rollback(data.rlbk); will rollback all
// of the directories that have been moved.
}
}
});
Is there a way for this to be done?
I've looked at using jstree before, but haven't used it in my code. As a result, the code may not be correct, but the concepts should be.
Based on your code, it appears that you're performing the move operation on the server side and you want the tree to be updated to reflect the results.
Based on the jsTree documentation, it looks as though you cannot commit node updates and roll back to the last commit.
Instead of rolling back only the changes that you don't want, you can roll back the tree (all changes) and perform the moves afterward.
In order to better understand the code below, you may want to read it (or create a copy) without the lines where "wasTriggeredByCode" is set or referenced in the condition for an "if" statement.
$("#tree").jstree({...}).bind("move_node.jstree", function (e, data) {
var jsTree = $(this);
var successes = [];
// Becomes true when function was triggered by code that updates jsTree to
// reflect nodes that were successfully moved on the server
var wasTriggeredByCode = false;
// process all selected nodes directory
data.rslt.o.each(function (i) {
// I'm not certain that this is how the node is referenced
var node = $(this);
wasTriggeredByCode = (wasTriggeredByCode || node.data('redoing'));
// Don't perform server changes when event was triggered from code
if (wasTriggeredByCode) {
return;
}
// Send request.
var move = $.parseJSON($.ajax({
url: "./jstree.php",
type: 'post',
async: false,
data: {
operation: "move_dir",
....
}
}).responseText);
if(move.success){
successes.push(node);
}
});
// Don't continue when event was triggered from code
if (wasTriggeredByCode) {
return;
}
// Roll back the tree here
jsTree.rollback(data.rlbk);
// Move the nodes
for (var i=0; i < successes.length; i++) {
var node = successes[i];
// According to the documentation this will trigger the move event,
// which will result in infinite recursion. To avoid this you'll need
// to set a flag or indicate that you're redoing the move.
node.data('redoing', true);
jsTree.move_node(node, ...);
// Remove the flag so that additional moves aren't ignored
node.removeData('redoing');
}
});
I thought about having something like "onbeforenodemove" event in jstree, something like this:
$("#tree").jstree({...}).bind("before_move_node.jstree", function (e, data) {...}
So I looked inside jstree.js file (version jsTree 3.1.1) and searched for declaration of original "move_node.jstree" handler. It found it declared starting line 3689:
move_node: function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {...}
This function contains the following line at the end of its body:
this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_pos, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
The above line actually calls your callback declared using .bind("move_node.jstree").
So at the beginning of this function body, I added this:
var before_data = { "node": obj, "parent": new_par.id, "position": pos, "old_parent": old_par, "old_position": old_pos, 'is_multi': (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign': (!old_ins || !old_ins._id), 'old_instance': old_ins, 'new_instance': this, cancelled: false };
this.trigger('before_move_node', before_data);
if (before_data.cancelled) {
return false;
}
Mind "cancelled": false at the end of before_data assigned value.
Also mind inserting the above after new_par, etc. values are assigned.
Code (jsTree instantiation) on my page looks now like this:
$('#tree')
.jstree({
core: {...},
plugins: [...]
})
.bind('before_move_node.jstree', function (e, data) {
if (...) {
data.cancelled = true;
}
})
data object passed to 'before_move_node.jstree' contains the same values that you receive in standard 'move_node.jstree' data argument so you have everything to decide whether you want to cancel the move or let it go. If you decide to cancel, just set the additional 'cancelled' property to true. The entire move will then not happen.
As the documentation says https://github.com/vakata/jstree/wiki#more-on-configuration, you can check more.core property
Example
$('#jstree1').jstree({
core: {
check_callback: async (operation, node, node_parent, node_position, more) => {
switch (true) {
case operation === 'move_node':
let canmove = true
const dropped = more.core === true // not dragging anymore...
if (dropped) {
// before move..
const success = await yourHttpRequest()
if (!success) {
canmove = false
}
} else {
canmove = yourCheckHere()
}
return canmove
}
}
}
})
Example 2
document.addEventListener("DOMContentLoaded", function () {
const bootstrap = (() => {
myTree.mySetup()
})
const myTree = {
mySetup: () => {
$('#jstree1').jstree({
core: {
check_callback: (operation, node, node_parent, node_position, more) => {
switch (true) {
case operation === 'move_node':
return myTree.myGates.canMove(node, node_parent, node_position, more)
}
// deny by default
return false
}
},
plugins: ['dnd']
})
.on('move_node.jstree', (node, parent, position, old_parent, old_position, is_multi, old_instance, new_instance) => {
myTree.myHandlers.onMove({
node, parent, position, old_parent, old_position, is_multi, old_instance, new_instance
})
})
},
myGates: {
canMove: (node, node_parent, node_position, more) => {
const canmove = true
const dropped = more.core === true
if (dropped) {
const success = alberoSx.myHandlers.onBeforeMove({
node, node_parent, node_position, more
})
if (!success) {
canmove = false
}
} else {
canmove = yourCheckHere()
}
return canmove
}
},
myHandlers: {
onBeforeMove: async () => {
// try to update the node in database
const success = await yourHttpRequestHere()
return success
},
onMove: () => {
// node moved in the ui
// do other stuff...
},
}
}
bootstrap()
})