can
Version:
MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.
352 lines (350 loc) • 10.4 kB
JavaScript
steal('can/map', 'can/control', 'can/list', function () {
/* global EditOver */
window.Observer = can.Control({
defaults: {
observeName: 'observe',
end: function () {
return ' ';
}
}
}, {
init: function () {
// draw
var data = this.options.observe.attr();
this.options.obs = new can.Map(data);
this.on();
this.render();
},
render: function () {
var frag = this.draw(0, undefined, this.options.obs);
this.element.html(frag)
.find('.end')
.eq(-1)
.replaceWith('<span class=\'end\'>})' + this.options.end.call(this) + '</span>');
},
draw: function (indent, name, value) {
indent = indent || 0;
var space = new Array(indent * 4)
.join(' '),
frag = document.createDocumentFragment(),
propName;
var self = this,
namePart;
if (!name && indent === 0) {
if (this.options.fullName) {
namePart = this.options.fullName;
} else {
namePart = this.options.observeName + '.attr(';
}
} else if (typeof name === 'string') {
namePart = '<span class=\'name\'>' + name + '</span>' + ' : ';
} else {
namePart = '<span class=\'name\'>' + '</span>' + '';
}
if (namePart) {
propName = $('<span>')
.html((!name && indent === 0 ? '' : '\n') + '<span class=\'remove\'>' + space + '</span>' + namePart);
if (name !== undefined) {
propName.addClass('propName')
.data('propName', name);
}
}
frag.appendChild(propName[0]);
if (value instanceof can.Map) {
var isList = value instanceof can.List,
obs = $('<span><span class=\'start\'>' + (isList ? '[' : '{') + '</span></span>')
.addClass('observe')
.data('observe', value)
.data('indent', indent + 1);
var children = $();
value.each(function (val, name) {
var section = self.draw(indent + 1, name, val);
children.push(section);
});
// remove the last ","
if (!children.length) {
obs.append('<span class=\'end\'>' + (isList ? ']' : '}') + ', </span>');
} else {
// remove the last ","
if (children.eq(-1)[0].lastChild.className === 'comma') {
$(children.eq(-1)[0].lastChild)
.remove();
} else if (children.eq(-1)[0].lastChild.className === 'observe') {
var end = $(children.eq(-1)[0].lastChild)
.children('.end');
end[0].firstChild.nodeValue = end[0].firstChild.nodeValue.replace(',', '');
}
obs.append(children);
obs.append('\n' + space + '<span class=\'end\'>' + (isList ? ']' : '}') + ', </span>');
}
frag.appendChild(obs[0]);
} else {
var val = $('<span>')
.addClass('value')
.data('propValue', value)
.text(JSON.stringify(value));
frag.appendChild(val[0]);
frag.appendChild($('<span class="comma">,</span>')[0]);
}
return frag;
},
'.name mouseenter': function (el, ev) {
if (ev.relatedTarget && ev.relatedTarget.nodeName === 'INPUT') {
return;
}
this.newEditInput(el);
ev.stopImmediatePropagation();
this.editing = this.getObserveAndName(el);
},
'{editOverInput} destroyed': function () {
if (this.editing.inserting && !this.dontRemoveTemp) {
this.element.find('.temp')
.remove();
}
this.revert();
},
'{editOverInput} keypress': function (el, ev) {
if (ev.keyCode === 9) {
ev.preventDefault();
if (this.editing.inserting) {
this.editing.name = this.options.editOverInput.val();
this.dontRemoveTemp = true;
this.options.editOverInput.remove();
this.editing.naming = $('<span class=\'naming\'> </span>');
this.editing.inserting.parent()
.append(this.editing.naming);
this.newEditInput(this.editing.naming);
this.dontRemoveTemp = false;
}
} else if (ev.keyCode === 13) {
if (this.editing.inserting) {
if (this.editing.naming && this.options.editOverInput.val()) {
try {
var value = JSON.parse(this.options.editOverInput.val());
this.editing.observe.attr(this.editing.name, value);
this.commit();
this.options.editOverInput.remove();
} catch (e) {
console.log('bad formatted', e);
}
return;
}
}
this.commit();
this.options.editOverInput.remove();
}
},
'{editOverInput} keyup': function (el, ev) {
var value;
//submitting
if (!this.options.editOverInput.val() || ev.keyCode === 9) {
return;
}
if (this.editing.inserting) {
// do nothing ...
if (this.editing.isList) {
try {
value = can.evalJSON(this.options.editOverInput.val());
console.log(this.editing.index, value);
this.editing.observe.attr(this.editing.index, value);
} catch (e) {
console.log('bad formatted');
}
} else if (!this.editing.naming) {
this.editing.inserting.text(this.options.editOverInput.val());
} else {
// make naming right ...
this.editing.naming.text(this.options.editOverInput.val());
try {
value = can.evalJSON(this.options.editOverInput.val());
this.editing.observe.attr(this.editing.name, value);
} catch (e) {
console.log('bad formatted');
}
}
} else if (this.editing.value) {
try {
value = can.evalJSON(this.options.editOverInput.val());
this.editing.observe.attr(this.editing.name, value);
} catch (e) {
console.log('bad formatted');
}
} else {
var data = this.editing.observe.attr();
var ordered = {};
for (var name in data) {
if (name === this.editing.name) {
ordered[this.options.editOverInput.val()] = data[name];
} else {
ordered[name] = data[name];
}
}
this.editing.name = this.options.editOverInput.val();
this.editing.observe.attr({}, true);
this.editing.observe.attr(ordered);
}
},
newEditInput: function (el) {
if (this.options.editOverInput) {
this.options.editOverInput.remove();
}
this.options.editOverInput = $('<input type=\'text\'>');
new EditOver(this.options.editOverInput, {
el: el
});
this.on();
},
'.value mouseenter': function (el, ev) {
if (ev.relatedTarget && ev.relatedTarget.nodeName === 'INPUT') {
return;
}
this.newEditInput(el);
this.editing = this.getObserveAndName(el.prev()
.children()
.eq(0));
this.editing.value = true;
ev.stopImmediatePropagation();
},
getObserveAndName: function (el) {
var propParent = el.closest('.propName');
var propName = propParent.data('propName');
//get the observe
var observe = propParent.closest('.observe')
.data('observe') || this.options.obs;
return {
observe: observe,
name: propName,
oldName: propName
};
},
'.remove click': function (el, ev) {
// get the propName
var propParent = el.closest('.propName');
var propName = propParent.data('propName');
//get the observe
var observe = propParent.parent('.observe')
.data('observe') || this.options.obs;
// remove the property
observe.removeAttr(propName);
if (this.options.editOverInput) {
this.options.editOverInput.remove();
}
ev.stopImmediatePropagation();
},
'.end click': function (el, ev) {
//this.options.editOverInput && this.options.editOverInput.remove();
var obsEl = el.closest('.observe');
var indent = obsEl.data('indent') || 0,
space = new Array(indent * 4)
.join(' '),
observe = obsEl.data('observe') || this.options.obs,
isList = observe instanceof can.List;
// get the element before end ... ad an input
el.prev()
.after('<span class=\'temp\'>\n' + space + '<span class=\'insert\'> </span>' + (isList ? '' : ' : ') + '</span>');
var insert = el.prev('.temp')
.find('.insert');
this.newEditInput(insert);
this.editing = {
observe: observe,
inserting: insert,
isList: isList
};
if (isList) {
this.editing.index = observe.length;
}
ev.stopImmediatePropagation();
},
'{obs} change': function () {
// redraw
var self = this;
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(function () {
self.render();
}, 1);
},
'{observe} change': function () {
var data = this.options.observe.attr();
this.options.obs.attr(data, true);
},
commit: function () {
var data = this.options.obs.attr();
this.options.observe.attr(data, true);
},
revert: function () {
var data = this.options.observe.attr();
this.options.obs.attr(data, true);
}
});
function setEnd(txt) {
if (txt.createTextRange) {
//IE
var fieldRange = txt.createTextRange();
fieldRange.moveStart('character', txt.value.length);
fieldRange.collapse();
fieldRange.select();
} else {
//Firefox and Opera
txt.focus();
var length = txt.value.length;
txt.setSelectionRange(length, length);
}
}
// draws an input element over some other element
window.EditOver = can.Control({
init: function () {
this.element.addClass('edit-over');
this.element.val(this.options.el.text());
this.element.appendTo(document.body);
var offset = this.options.el.offset();
offset.top--;
offset.left -= 3;
this.element.width(this.options.el.width() + 6);
this.element.height(this.options.el.height() + 2);
var css = {
fontSize: this.options.el.css('fontSize'),
fontFamily: this.options.el.css('fontFamily'),
position: 'absolute'
};
this.element.css(css);
this.element.offset(offset);
if (this.options.focus) {
this.element[0].focus();
setEnd(this.element[0]);
} else {
this.element[0].select();
}
var self = this;
setTimeout(function () {
self.ready = true;
}, 10);
this.measure = $('<span>')
.css(css)
.text(this.element.val());
this.measure.appendTo(document.body);
this.measure.css({
left: '0px',
top: '-1000px'
});
},
'{window} click': function (el, ev) {
if (this.ready && ev.target !== this.element[0]) {
this.element.remove();
}
},
'keypress': function (el, ev) {
if (ev.charCode) {
this.measure.text(this.element.val() + 'W');
this.element.width(this.measure.width() + 6);
}
},
'keyup': function (el, ev) {
this.measure.text(this.element.val());
this.element.width(this.measure.width() + 6);
},
destroy: function () {
this.measure.remove();
can.Control.prototype.destroy.apply(this, arguments);
}
});
});