Skip to content

Instantly share code, notes, and snippets.

@shadeglare
Created November 18, 2014 18:34
Show Gist options
  • Save shadeglare/f7f571557015c97fb23a to your computer and use it in GitHub Desktop.
Save shadeglare/f7f571557015c97fb23a to your computer and use it in GitHub Desktop.
var Frame = Frame || {};
(function($, _, Backbone, Frame) {
var renderTemplate = function(template) {
var templateHtml = template.html();
return _.template(templateHtml, {});
};
var renderTemplateWithModel = function(template, model) {
var templateHtml = template.html();
return _.template(templateHtml, model.toJSON());
};
$(function() {
var EntityType = {
Dictionary: 0,
Array: 1
};
var JsonScalar = Backbone.Model.extend({
defaults: function() {
return {
data: null
};
}
});
var JsonEntity = Backbone.Model.extend({
defaults: function() {
return { };
},
initialize: function() {
this._index = 0;
this._jsonSerializers[EntityType.Array] = this._arrayToJson;
this._jsonSerializers[EntityType.Dictionary] = this._objectToJson;
this.setEntityType(EntityType.Dictionary);
},
setPair: function(key, value) {
if (!key && this.getEntityType() === EntityType.Dictionary) {
throw new Error("key must be set for the dictionary entity");
} else {
if (key !== 0) {
key = key || this._index++;
}
this.set(key, value);
}
return key;
},
unsetPair: function(key) {
switch(this.getEntityType()) {
case EntityType.Dictionary:
break;
case EntityType.Array:
break;
}
this.unset(key);
},
setEntityType: function(entityType) {
this._entityType = entityType;
},
getEntityType: function() {
return this._entityType;
},
toJSON: function() {
return this._jsonSerializers[this._entityType].apply(this);
},
_arrayToJson: function() {
var self = this;
var result = [];
var keys = Object.getOwnPropertyNames(this.attributes);
keys.forEach(function(key) {
var value = self.get(key);
var isJsonValue = value instanceof JsonScalar;
value = isJsonValue ? value.get("data") : value;
result.push(value);
});
return result;
},
_objectToJson: function() {
var self = this;
var result = {};
var keys = Object.getOwnPropertyNames(this.attributes);
keys.forEach(function(key) {
var value = self.get(key);
var isJsonValue = value instanceof JsonScalar;
value = isJsonValue ? value.get("data") : value;
result[key] = value;
});
return result;
},
_jsonSerializers: {}
}, {
checkType: function(data) {
var type = "";
if (_.isArray(data)) {
type = EntityType.Array;
} else if (_.isObject(data)) {
type = EntityType.Dictionary;
} else {
throw new Error("Invalid entity type");
}
return type;
},
fromJSON: function(json) {
var jsonEntity = new JsonEntity();
if (_.isArray(json)) {
jsonEntity.setEntityType(EntityType.Array);
_.each(json, function(value) {
JsonEntity._setPair(jsonEntity, null, value);
});
} else if (_.isObject(json)) {
jsonEntity.setEntityType(EntityType.Dictionary);
_.each(json, function(value, key) {
JsonEntity._setPair(jsonEntity, key, value)
});
} else {
throw new Error("Invalid object type");
}
return jsonEntity;
},
_setPair: function(rootEntity, key, value) {
var isScalar =
_.isNumber(value) ||
_.isBoolean(value) ||
_.isString(value) ||
_.isNull(value);
if (isScalar) {
var jsonScalar = new JsonScalar({ data: value });
rootEntity.setPair(key, jsonScalar);
} else {
var jsonEntity = JsonEntity.fromJSON(value);
rootEntity.setPair(key, jsonEntity);
}
}
});
var Pair = Backbone.Model.extend({
defaults: function() {
return {
key: "",
value: null
};
},
initialize: function() {
this.isReference = true;
}
});
var JsonStringView = Backbone.View.extend({
className: "json-string",
template: $("#json-string-template"),
initialize: function() {
this._valueInputChanged = _.bind(this._valueInputChanged, this);
},
render: function() {
var $renderedTemplate = $(renderTemplateWithModel(this.template, this.model));
this.$el.append($renderedTemplate);
this.$valueInput = this.$el.find(".value");
this.$valueInput.on("input", this._valueInputChanged);
},
dispose: function() {
this.$valueInput.off("input");
this.remove();
this.unbind();
},
_valueInputChanged: function(event) {
this.model.set("data", event.target.value);
}
});
var JsonNumberView = Backbone.View.extend({
className: "json-number",
template: $("#json-number-template"),
initialize: function() {
this._valueInputChanged = _.bind(this._valueInputChanged, this);
},
render: function() {
var $renderedTemplate = $(renderTemplateWithModel(this.template, this.model));
this.$el.append($renderedTemplate);
this.$valueInput = this.$el.find(".value");
this.$valueInput.on("input", this._valueInputChanged);
this.$valueInput.on("keypress", this._valueInputKeypressed)
},
dispose: function() {
this.$valueInput.off("input");
this.$valueInput.off("keypress");
this.remove();
this.unbind();
},
_valueInputChanged: function(event) {
this.model.set("data", parseFloat(event.target.value));
},
_valueInputKeypressed: function(event) {
var notDelimiter = !(event.which === 46 && event.target.value.indexOf('.') === -1);
var notDigit = event.which < 48 || event.which > 57;
if (notDelimiter && notDigit) {
event.preventDefault();
}
}
});
var JsonBooleanView = Backbone.View.extend({
className: "json-boolean",
template: $("#json-boolean-template"),
initialize: function() {
this._valueSelectChanged = _.bind(this._valueSelectChanged, this);
},
render: function() {
var $renderedTemplate = $(renderTemplateWithModel(this.template, this.model));
this.$el.append($renderedTemplate);
this.$valueInput = this.$el.find(".value");
this.$valueInput.on("change", this._valueSelectChanged);
this.$valueInput
.find("option[value=" + this.model.get("data") +"]")
.prop("selected", true);
},
dispose: function() {
this.$valueInput.off("change");
this.remove();
this.unbind();
},
_valueSelectChanged: function(event) {
this.model.set("data", event.target.value === "true");
}
});
var JsonReferenceView = Backbone.View.extend({
className: "json-reference",
template: $("#json-reference-template"),
initialize: function() {
},
render: function() {
var $renderedTemplate = $(renderTemplateWithModel(this.template, this.model));
this.$el.append($renderedTemplate);
this.$href = this.$el.find(".value");
this.$editReferenceButton = this.$el.find(".edit-reference-button");
this.$editReferenceButton.click(function() {
alert("test");
});
},
dispose: function() {
this.$editReferenceButton.off("click");
this.remove();
this.unbind();
}
});
var JsonEntityView = Backbone.View.extend({
tagName: "div",
className: "json-entity",
template: $("#json-entity-template"),
initialize: function() {
this.key = null;
this._pairViewCollection = {};
this._itemNameChanged = _.bind(this._itemNameChanged, this);
this._addItemButtonClick = _.bind(this._addItemButtonClick, this);
this._collectJsonButtonClick = _.bind(this._collectJsonButtonClick, this);
this._addPairView = _.bind(this._addPairView, this);
this._removePairView = _.bind(this._removePairView, this);
},
render: function() {
var $renderedTemplate = $(renderTemplateWithModel(this.template, this.model));
this.$el.append($renderedTemplate);
this.$content = this.$el.find(".content-holder");
this.$dictionary = this.$el.find(".dictionary");
this.$itemName = this.$el.find(".item-name");
this.$addItemButton = this.$el.find(".add-item-button");
this.$collectJsonButton = this.$el.find(".collect-json-button");
this.$itemName.on("input", this._itemNameChanged);
this.$addItemButton.on("click", this._addItemButtonClick);
this.$collectJsonButton.on("click", this._collectJsonButtonClick);
this.entityType = this.model.getEntityType();
if (this.entityType !== EntityType.Dictionary) {
this.$dictionary.css("display", "none");
}
},
renderJson: function(data) {
var self = this;
var type = JsonEntity.checkType(data);
this.model = new JsonEntity();
this.model.setEntityType(type);
this.render();
_.each(data, function(value, key) {
self._renderPair(new Pair(), key, value);
});
},
dispose: function() {
this.trigger("dispose");
this.$itemName.off("input");
this.$addItemButton.off("click");
this.$collectJsonButton.off("click");
this.remove();
this.unbind();
},
_itemNameChanged: function(event) {
this.key = event.target.value;
},
_checkItemExists: function() {
return this.entityType === EntityType.Dictionary &&
(!this.key || this._pairViewCollection.hasOwnProperty(this.key));
},
_addItemButtonClick: function() {
this._renderPair();
},
_collectJsonButtonClick: function() {
alert(JSON.stringify(this.model, null, 4));
},
_renderPair: function(pair, key, data) {
pair = pair || new Pair();
this.key = key || this.key;
if (this._checkItemExists()) {
alert("Could not add duplicate or null");
return;
}
this._attachPairToModel(pair);
this._addPairView(pair, data);
},
_attachPairToModel: function(pair) {
var self = this;
if (this.entityType === EntityType.Array) {
this.key = this.model.setPair(null || pair.get("key"), pair.get("value"));
} else {
this.model.setPair(this.key, pair.get("value"));
}
pair.set("key", this.key);
pair.on("change", function() {
self.model.setPair(pair.get("key"), pair.get("value"));
});
pair.listenToOnce(this, "dispose", function() {
pair.off("change");
});
},
_addPairView: function(pair, data) {
var self = this;
var pairView = new PairView({ model: pair });
pairView.render();
pairView.loadData(data);
pairView.on("dispose", function() {
var key = pair.get("key");
self.model.unsetPair(key);
self._removePairView(key);
});
this.$content.append(pairView.$el);
this._pairViewCollection[pair.get("key")] = pairView;
},
_removePairView: function(key) {
delete this._pairViewCollection[key];
}
});
var PairView = Backbone.View.extend({
tagName: "div",
className: "pair",
template: $("#pair-template"),
initialize: function() {
this._valueEditorView = null;
this._foldingButtonClick = _.bind(this._foldingButtonClick, this);
this._deletePairButtonClick = _.bind(this._deletePairButtonClick, this);
this._typeSelectChanged = _.bind(this._typeSelectChanged, this);
},
render: function() {
var $renderedTemplate = $(renderTemplateWithModel(this.template, this.model));
this.$el.append($renderedTemplate);
this.$settings = this.$el.find(".settings");
this.$foldingHolder = this.$el.find(".folding-holder");
this.$foldingButton = this.$el.find(".folding-button");
this.$jsonScalarHolder = this.$el.find(".json-scalar-holder");
this.$jsonEntityHolder = this.$el.find(".json-entity-holder");
this.$deletePairButton = this.$el.find(".delete-pair-button");
this.$typeSelect = this.$el.find("select.type");
this.$foldingButton.on("click", this._foldingButtonClick);
this.$deletePairButton.on("click", this._deletePairButtonClick);
this.$typeSelect.on("change", this._typeSelectChanged);
this._updateTypeSelectOptions();
},
loadData: function(data) {
if (typeof(data) !== typeof(undefined)) {
var type = this._getDataType(data);
this.$typeSelect
.find("option[value=" + type +"]")
.prop("selected", true);
this._switchValueEditorView(type, data);
}
},
dispose: function() {
this.trigger("dispose");
this.$foldingButton.off("click");
this.$deletePairButton.off("click");
this.$typeSelect.off("change");
this.model.off();
this.model.stopListening();
this.remove();
this.unbind();
},
_updateTypeSelectOptions: function() {
if (this.model.isReference) {
this.$typeSelect.find("option[value='String']").remove();
this.$typeSelect.find("option[value='Number']").remove();
this.$typeSelect.find("option[value='Boolean']").remove();
} else {
this.$typeSelect.find("option[value='Reference']").remove();
}
},
_getDataType: function(data) {
var type = "";
if (_.isArray(data)) {
type = "Array";
} else if (_.isObject(data)) {
type = "Dictionary";
} else if (_.isNumber(data)) {
type = "Number";
} else if (_.isString(data)) {
type = "String";
} else if(_.isBoolean(data)) {
type = "Boolean";
} else {
type = "Null";
}
return type;
},
_foldingButtonClick: function() {
if (this.$jsonEntityHolder.css("display") === "none") {
this.$jsonEntityHolder.css("display", "block");
this.$foldingButton.text("-");
this.$settings.css("margin-bottom", "0");
} else {
this.$jsonEntityHolder.css("display", "none");
this.$foldingButton.text("+");
this.$settings.css("margin-bottom", "0.5em");
}
},
_deletePairButtonClick: function() {
this.dispose();
},
_typeSelectChanged: function(event) {
var $selectType = $(event.target);
var type = $selectType.find("option:selected").val();
this._switchValueEditorView(type);
},
_switchValueEditorView: function(type, data) {
this._removeValueEditorView();
switch(type) {
case "Array":
case "Dictionary":
this.$foldingHolder.css("display", "table-cell");
this._addJsonEntityView(type, data);
break;
case "Reference":
this.$foldingHolder.css("display", "none");
this._addJsonReferenceView(data);
break;
case "String":
this.$foldingHolder.css("display", "none");
this._addJsonStringView(data);
break;
case "Number":
this.$foldingHolder.css("display", "none");
this._addJsonNumberView(data);
break;
case "Boolean":
this.$foldingHolder.css("display", "none");
this._addJsonBooleanView(data);
break;
default:
break;
}
},
_removeValueEditorView: function() {
if (this._valueEditorView) {
this.model.set("value", null);
this._valueEditorView.dispose();
this._valueEditorView = null;
}
},
_addJsonEntityView: function(type, data) {
var jsonEntityView = new JsonEntityView();
var jsonEntity;
if (data) {
jsonEntityView = new JsonEntityView();
jsonEntityView.renderJson(data);
jsonEntity = jsonEntityView.model;
} else {
jsonEntity = new JsonEntity();
jsonEntity.setEntityType(EntityType[type]);
jsonEntityView.model = jsonEntity;
jsonEntityView.render();
}
this.model.set("value", jsonEntity);
this.$jsonEntityHolder.append(jsonEntityView.$el);
this._valueEditorView = jsonEntityView;
},
_addJsonStringView: function(data) {
var jsonScalar = new JsonScalar();
jsonScalar.set("data", data || "");
this.model.set("value", jsonScalar);
var jsonStringView = new JsonStringView({ model: jsonScalar });
jsonStringView.render();
this.$jsonScalarHolder.append(jsonStringView.$el);
this._valueEditorView = jsonStringView;
},
_addJsonNumberView: function(data) {
var jsonScalar = new JsonScalar();
jsonScalar.set("data", data || 0);
this.model.set("value", jsonScalar);
var jsonNumberView = new JsonNumberView({ model: jsonScalar });
jsonNumberView.render();
this.$jsonScalarHolder.append(jsonNumberView.$el);
this._valueEditorView = jsonNumberView;
},
_addJsonBooleanView: function(data) {
var jsonScalar = new JsonScalar();
jsonScalar.set("data", data || false);
this.model.set("value", jsonScalar);
var jsonBooleanView = new JsonBooleanView({ model: jsonScalar });
jsonBooleanView.render();
this.$jsonScalarHolder.append(jsonBooleanView.$el);
this._valueEditorView = jsonBooleanView;
},
_addJsonReferenceView: function(data) {
var jsonScalar = new JsonScalar();
jsonScalar.set("data", data || "");
this.model.set("value", jsonScalar);
var jsonReferenceView = new JsonReferenceView({ model: jsonScalar });
jsonReferenceView.render();
this.$jsonScalarHolder.append(jsonReferenceView.$el);
this._valueEditorView = jsonReferenceView;
}
});
Frame.JsonBuilder = {
JsonEntity: JsonEntity,
JsonScalar: JsonScalar,
EntityType: EntityType,
Pair: Pair,
JsonStringView: JsonStringView,
JsonNumberView: JsonNumberView,
JsonBooleanView: JsonBooleanView,
JsonEntityView: JsonEntityView,
PairView: PairView
};
});
})($, _, Backbone, Frame);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment