Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return something on change callback #136

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -25,13 +25,15 @@
"jsonlint": "josdejong/jsonlint#85a19d7"
},
"devDependencies": {
"chai": "^3.0.0",
"chai-spies": "^0.6.0",
"gulp": "^3.8.11",
"gulp-concat-css": "^2.0.0",
"gulp-minify-css": "^0.4.5",
"gulp-shell": "^0.3.0",
"gulp-util": "^3.0.3",
"mkdirp": "^0.5.0",
"mocha": "^2.1.0",
"mocha": "^2.2.5",
"uglify-js": "^2.4.16",
"webpack": "^1.5.3"
}
Expand Down
4 changes: 4 additions & 0 deletions src/js/History.js
Expand Up @@ -170,6 +170,7 @@ History.prototype.canRedo = function () {

/**
* Undo the last action
* @returns applied history entry see {@link #add}
*/
History.prototype.undo = function () {
if (this.canUndo()) {
Expand All @@ -191,10 +192,12 @@ History.prototype.undo = function () {
// fire onchange event
this.onChange();
}
return obj || null;
};

/**
* Redo the last action
* @returns applied history entry see {@link #add}
*/
History.prototype.redo = function () {
if (this.canRedo()) {
Expand All @@ -217,6 +220,7 @@ History.prototype.redo = function () {
// fire onchange event
this.onChange();
}
return obj || null;
};

module.exports = History;
14 changes: 11 additions & 3 deletions src/js/textmode.js
Expand Up @@ -154,7 +154,7 @@ textmode.create = function (container, options) {
if (options.change) {
// register onchange event
editor.on('change', function () {
options.change();
options.change(replaceRootJSONPatch(me));
});
}
}
Expand All @@ -170,13 +170,13 @@ textmode.create = function (container, options) {
// register onchange event
if (this.textarea.oninput === null) {
this.textarea.oninput = function () {
options.change();
options.change(replaceRootJSONPatch(me));
}
}
else {
// oninput is undefined. For IE8-
this.textarea.onchange = function () {
options.change();
options.change(replaceRootJSONPatch(me));
}
}
}
Expand Down Expand Up @@ -337,6 +337,14 @@ textmode.setText = function(jsonText) {
}
};

function replaceRootJSONPatch(textmode){
return {
op: "replace",
path: "",
value: textmode.get()
};
}

// define modes
module.exports = [
{
Expand Down
128 changes: 117 additions & 11 deletions src/js/treemode.js
Expand Up @@ -301,7 +301,7 @@ treemode._onAction = function (action, params) {
// trigger the onChange callback
if (this.options.change) {
try {
this.options.change();
this.options.change(translateChangeToJSONPatch(action, params));
}
catch (err) {
util.log('Error in change callback: ', err);
Expand Down Expand Up @@ -580,11 +580,13 @@ treemode._createFrame = function () {
treemode._onUndo = function () {
if (this.history) {
// undo last action
this.history.undo();
var historyEntry = this.history.undo();

// trigger change callback
if (this.options.change) {
this.options.change();
// trigger change callback if anything have changed
if (this.options.change && historyEntry) {
this.options.change(
translateChangeToJSONPatch(historyEntry.action, historyEntry.params)
);
}
}
};
Expand All @@ -596,11 +598,12 @@ treemode._onUndo = function () {
treemode._onRedo = function () {
if (this.history) {
// redo last action
this.history.redo();

// trigger change callback
if (this.options.change) {
this.options.change();
var historyEntry = this.history.redo();
// trigger change callback if anything have changed
if (this.options.change && historyEntry) {
this.options.change(
translateChangeToJSONPatch(historyEntry.action, historyEntry.params)
);
}
}
};
Expand Down Expand Up @@ -724,6 +727,109 @@ treemode._createTable = function () {
this.frame.appendChild(contentOuter);
};

/**
* Translate our internal change info into JSON-Patch format
* @see http://tools.ietf.org/html/rfc6902
* @param {String} action JSONEditor action
* @param {Object} params JSONEditor params
* @return {Object} single JSON-Patch entry
*/
function translateChangeToJSONPatch(action, params){
/**
* Get path to node in JSON Pointer format
* (http://tools.ietf.org/html/rfc6901)
* _Almost_ like params.node.path().join("/")
* @param {Node} node jsoneditor node in question
* @returns {String} path
*/
function JSONPointer(node){
var path = "";
while (node.parent) {
var field = node.field != undefined ? node.field : node.index;
switch(typeof field){
case "string":
path = "/" + escapePathComponent(field) + path;
break;
case "number":
path = "/" + field + path;
break;
}
node = node.parent;
}
return path;

}
/**
* Escape `/` and `~`, according to JSON-Pointer rules.
* @param {String} str string to escape
* @returns {String} escaped string
*/
function escapePathComponent(str) {
if (str.indexOf('/') === -1 && str.indexOf('~') === -1)
return str;
return str.replace(/~/g, '~0').replace(/\//g, '~1');
}
var patch;
switch(action){
case "duplicateNode":
console.warn("duplicateNode->copy Is not supported yet, as currently new node with same name is created, what violates JSON-Patch");
break;
case "changeType":
(params.node.type == "array" || params.node.type == "object") && console.warn("changeType->replace may behave strange, as even if new node is created with specified type, its `node.value==\"\"`")
patch = {
op: "replace",
path: JSONPointer(params.node),
value: params.node.value
}
break;
case "editValue":
patch = {
op: "replace",
path: JSONPointer(params.node),
value: params.newValue
}
break;
case "removeNode":
patch = {
op: "remove",
path: JSONPointer(params.node)
}
break;
case "insertAfterNode":
case "insertBeforeNode":
case "appendNode":
console.warn("insertBeforeNode,appendNode->add may behave strange, as even if new node is created with specified type, its `node.value==\"\"`",
"also when inserting item into an array, new path is given, and there is no info about previous indexes, so it's hard to distinguish whether to use `-`, old index, or if it is not an array item, so we should stick to the given path");
patch = {
op: "add",
path: JSONPointer(params.node),
value: params.node.value
}
break;
case "moveNode":
console.warn("moveNode->move Still does not cover moving array items, as their name was already changed to `\"\"` which is fully valid object key, so we cannot distinguish it");
if(params.startParent !== params.endParent){
patch = {
op: "move",
from: JSONPointer(params.startParent) + "/" + params.node.field,
path: JSONPointer(params.node)
}
}
break;
case "editField":
patch = {
op: "move",
from: JSONPointer(params.node.parent) + "/" + params.oldValue,
path: JSONPointer(params.node)
}
break;
case "sort":
// no changes to the JSON itself
break;
}
return patch;
}

// define modes
module.exports = [
{
Expand All @@ -741,4 +847,4 @@ module.exports = [
mixin: treemode,
data: 'json'
}
];
];