/* RSence
* Copyright 2010 Riassence Inc.
* http://riassence.com/
*
* You should have received a copy of the GNU General Public License along
* with this software package. If not, contact licensing@riassence.com
*/
/*** = Description
** HPropertyList uses any JSON-compliant structure as its value and displays
** its content hierarchically in three columns:
** - Key
** - Type
** - Value
**
** Its current purpose is to view JSON data in a structured way. I'll be
** extended later as a full, generic JSON data editor.
**
** NOTE: HPropertyList does not work in any version of Internet Explorer yet.
**
***/
var//RSence.Lists
HPropertyList = HControl.extend({
defaultEvents: {
click: true
},
controlDefaults: HControlDefaults.extend({
keyColumnWidth: 100,
hideTypeColumn: false,
useEditor: false,
rowHeight: 15,
keyIndent: 8
}),
click: function(x,y){
if(this.options.useEditor){
var
clickY = y-this.contentView.pageY(),
itemNum = Math.floor(clickY/this.options.rowHeight);
if((clickY < 0) || (itemNum > this.valueTokens.length-1)){
this.editor.hide();
return;
}
this.editItem( itemNum );
}
},
keyColumnRight: function(){
return this.options.keyColumnWidth;
},
typeColumnLeft: function(){
return this.keyColumnRight();
},
typeColumnRight: function(){
return this.options.keyColumnWidth + 60;
},
valueColumnLeft: function(){
if(this.options.hideTypeColumn){
return this.keyColumnRight();
}
else {
return this.typeColumnRight();
}
},
drawSubviews: function(){
var borderAndBg = ELEM.make(this.elemId);
ELEM.setCSS(borderAndBg,'position:absolute;left:0;top:0;right:0;bottom:0;background-color:#e6e6e6;border:1px solid #999999;');
this.markupElemIds = {
bg: borderAndBg
};
this.contentView = HScrollView.extend({
click: function(x,y){
this.parent.click(x,y);
return true;
}
}).nu(
[ 1, 25, null, null, 1, 1 ],
this, {
scrollY: 'auto',
scrollX: false,
events: {
click: true
}
}
);
var separatorParentElemId = ELEM.make(this.contentView.elemId);
ELEM.setCSS( separatorParentElemId, 'position:absolute;left:0;top:0;right:0;' );
this.separatortParentElemId = separatorParentElemId;
this.propertyItems = [];
// Editor initialization
if(this.options.useEditor){
this.editorValue = HValue.nu(
false, {
top: 0,
left: 0,
type: 'h',
name: '--',
value: {}
}
);
this.editor = HPropertyListEditor.nu(
[0,0,null,this.options.rowHeight+2,0,null],
this.contentView, {
propertyItems: this.propertyItems,
visible: false,
valueObj: this.editorValue
}
);
}
// Set row style heights from options
var rowHeightStyle = 'height:'+this.options.rowHeight+'px;';
this.keyRowStyle += rowHeightStyle;
this.typeRowStyle += rowHeightStyle;
this.valueRowStyle += rowHeightStyle;
this.rowSeparatorStyle += rowHeightStyle;
// Style common font style
this.contentView.setStyle('font-size','11px');
// Create the key column
this.keyColumn = HView.nu(
[ 0, 0, this.keyColumnRight(), 24 ],
this.contentView, {
style: {
borderRight: '1px solid #999'
}
}
);
// Create the type column
if(!this.options.hideTypeColumn){
this.typeColumn = HView.nu(
[ this.typeColumnLeft(), 0, 60, 24 ],
this.contentView, {
style: {
borderRight: '1px solid #999'
}
}
);
}
// Create the value column
this.valueColumn = HView.nu(
[ this.valueColumnLeft(), 0, 0, 24, 0, null ],
this.contentView
);
// Create the column headers
this.header = HView.extend({
drawSubviews: function(){
var
keyColumnWidth = this.parent.options.keyColumnWidth;
this.keyLabel = HView.nu(
[ 0, 0, this.parent.keyColumnRight(), 24 ],
this, {
html: 'Key',
style: {
verticalAlign: 'middle',
textIndent: '16px',
lineHeight: '24px',
fontSize: '13px',
borderRight: '3px double #999999'
}
}
);
if(!this.parent.options.hideTypeColumn){
this.typeLabel = HView.nu(
[ this.parent.typeColumnLeft(), 0, 60, 24 ],
this, {
html: 'Type',
style: {
verticalAlign: 'middle',
textIndent: '8px',
lineHeight: '24px',
fontSize: '13px',
paddingRight: '1px',
borderRight: '1px solid #999999'
}
}
);
}
this.valueLabel = HView.nu(
[ this.parent.valueColumnLeft(), 0, 80, 24, 0, null ],
this, {
html: 'Value',
style: {
verticalAlign: 'middle',
textIndent: '8px',
lineHeight: '24px',
fontSize: '13px'
}
}
);
}
}).nu(
[ 0, 0, null, 24, 0, null ],
this, {
style: { borderBottom: '1px solid #999' }
}
);
// Create the resize control (invisible, just above the first column separator)
this.resizeColumns = HControl.extend({
drag: function(x,y){
var
parent = this.parent,
options = parent.options,
keyColumnWidth = x - parent.pageX(),
parentWidth = parent.rect.width;
if(keyColumnWidth < 80){
keyColumnWidth = 80;
}
else if ( keyColumnWidth > parentWidth-140 ){
keyColumnWidth = parentWidth - 140;
}
// Set the dragger itself
this.rect.offsetTo( keyColumnWidth-1, 0 ); this.drawRect();
// Resize the key column
options.keyColumnWidth = keyColumnWidth;
var
keyColumn = parent.keyColumn,
keyLabel = parent.header.keyLabel,
keyRight = parent.keyColumnRight();
keyColumn.rect.setRight( keyRight );
keyLabel.rect.setRight( keyRight );
var
valueColumn = parent.valueColumn,
valueLabel = parent.header.valueLabel,
valueLeft = parent.valueColumnLeft();
valueColumn.rect.setLeft( valueLeft );
valueLabel.rect.setLeft( valueLeft );
// Redraw the rects
keyColumn.drawRect();
keyLabel.drawRect();
valueColumn.drawRect();
valueLabel.drawRect();
// Resize the type column
if(!options.hideTypeColumn){
var
typeColumn = parent.typeColumn,
typeLabel = parent.header.typeLabel,
typeLeft = parent.typeColumnLeft(),
typeRight = parent.typeColumnRight();
typeColumn.rect.setLeft( typeLeft );
typeColumn.rect.setRight( typeRight );
typeLabel.rect.setLeft( typeLeft, 0 );
typeLabel.rect.setRight( typeRight, 0 );
typeColumn.drawRect();
typeLabel.drawRect();
}
if(options.useEditor){
parent.editor.resizeKeyColumn();
}
}
}).nu(
[ this.keyColumnRight(), 0, 5, 25 ],
this, {
events: { draggable: true },
style: { cursor: 'ew-resize' }
}
);
},
// Tokenize arrays
arrayTokens: function( arr, name ){
this.addToken( 'a', name, '('+arr.length+' items)' );
this.nodeProperties.left += this.options.keyIndent;
var i = 0, val, type;
for( ; i < arr.length; i++ ){
val = arr[i];
type = this.itemType( val );
if( type == 'h' ){
this.hashTokens( val, i );
}
else if ( type == 'a' ){
this.arrayTokens( val, i );
}
else {
this.addToken( type, i, val );
}
}
this.nodeProperties.left -= this.options.keyIndent;
},
// Get length of hash
hashLen: function( hash ){
var count = 0;
for( var item in hash ){
count += 1;
}
return count;
},
// Sort hash keys
hashSortedKeys: function( hash ){
var
keys = [],
key;
for( key in hash ){
keys.push( key );
}
return keys.sort();
},
// Tokenize hashes
hashTokens: function( hash, name ){
this.addToken( 'h', name, '('+this.hashLen( hash )+' items)' );
this.nodeProperties.left += this.options.keyIndent;
var key, val, type, i = 0, keys = this.hashSortedKeys( hash );
for( ; i < keys.length; i++ ){
key = keys[i];
val = hash[key];
type = this.itemType( val );
if( type == 'h' ){
this.hashTokens( val, key );
}
else if ( type == 'a' ){
this.arrayTokens( val, key );
}
else {
this.addToken( type, key, val );
}
}
this.nodeProperties.left -= this.options.keyIndent;
},
// Adds a taken
addToken: function( type, name, value ){
this.valueTokens.push( {
top: this.nodeProperties.top,
left: this.nodeProperties.left,
type: type,
name: name,
value: value
} );
this.nodeProperties.top += this.options.rowHeight;
},
// Returns type of item
itemType: function( item ){
return COMM.Values.type( item );
},
// Translation from type code to type name
typeNames: {
h: 'Hash',
a: 'Array',
s: 'String',
n: 'Number',
b: 'Boolean',
'~': 'Null',
'-': 'Undefined'
},
// Style for the key rows
keyRowStyle: "position:absolute;padding-top:2px;right:0px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;",
// Creates row in key column
addKeyColumnControl: function( token, i ){
var elemId;
if( i >= this.propertyItems.length ){
elemId = ELEM.make( this.keyColumn.elemId );
this.propertyItems.push( elemId );
ELEM.setCSS( elemId, 'top:'+token.top+'px;'+this.keyRowStyle );
}
else {
elemId = this.propertyItems[i];
}
ELEM.setStyle( elemId, 'left', (token.left+10)+'px' );
if( token.type === 'h' || token.type === 'a' ){
ELEM.setStyle( elemId, 'font-weight', 'bold' );
}
else {
ELEM.setStyle( elemId, 'font-weight', 'inherit' );
}
ELEM.setHTML( elemId, token.name );
},
// Style for the type rows
typeRowStyle: "position:absolute;padding-top:2px;left:8px;width:72px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;",
// Creates row in the type column
addTypeColumnControl: function( token, i ){
var elemId;
if( i >= this.propertyItems.length ){
elemId = ELEM.make( this.typeColumn.elemId );
this.propertyItems.push( elemId );
ELEM.setCSS( elemId, 'top:'+token.top+'px;'+this.typeRowStyle );
}
else {
elemId = this.propertyItems[i];
}
ELEM.setHTML( elemId, this.typeNames[token.type] );
},
// Style for the value rows
valueRowStyle: "position:absolute;padding-top:2px;left:8px;right:0px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;",
// Creates row in the value column
addValueColumnControl: function( token, i ){
var elemId, value;
if( i >= this.propertyItems.length ){
elemId = ELEM.make( this.valueColumn.elemId );
this.propertyItems.push( elemId );
ELEM.setCSS( elemId, 'top:'+token.top+'px;'+this.valueRowStyle );
}
else {
elemId = this.propertyItems[i];
}
if( token.type === 'h' || token.type === 'a' ){
ELEM.setStyle( elemId, 'font-style', 'italic' );
}
else {
ELEM.setStyle( elemId, 'font-style', 'inherit' );
}
value = token.value;
if(value===true){
value = 'true';
}
else if(value===false){
value = 'false';
}
else if(value===undefined){
value = 'undefined';
}
else if(value===null){
value = 'null';
}
ELEM.setHTML( elemId, value );
},
// Row separator style
rowSeparatorStyle: "position:absolute;left:1px;right:1px;font-size:0px;height:1px;overflow:hidden;border-bottom:1px solid #999999;",
// Adds row separator
addRowSeparator: function( token, i, even ){
if( i >= this.propertyItems.length ){
var elemId = ELEM.make( this.separatortParentElemId );
this.propertyItems.push( elemId );
ELEM.setCSS( elemId, 'top:'+token.top+'px;'+this.rowSeparatorStyle+'background-color:'+(even?'#f6f6f6':'#e6e6e6')+';' );
}
},
// Destructor, deletes extra elements created
die: function(){
var
i=0,
propLen = this.propertyItems.length,
elemId;
for(;ithis.valueTokens.length-1){
itemNum = this.valueTokens.length-1;
}
else if(itemNum < 0){
itemNum = 0;
}
var
targetY = (itemNum*this.options.rowHeight)-1,
elem = ELEM.get( this.contentView.elemId ),
scrollTop = elem.scrollTop,
contentHeight = this.contentView.rect.height;
if(targetY > (scrollTop+contentHeight-45)){
elem.scrollTop = scrollTop+45;
}
else if(targetY < scrollTop+45){
elem.scrollTop = scrollTop-45;
}
this.selectedItem = itemNum;
this.editorValue.set( COMM.Values.clone(this.valueTokens[itemNum]) );
this.editor.show();
EVENT.changeActiveControl(this.editor);
this.editor.offsetTo( 0, targetY );
this.editor.bringToFront();
},
// Starts tokenizing, when the value is changed.
refreshValue: function(){
if(this['propertyItems']===undefined){
return;
}
this.valueTokens = [];
this.nodeProperties = {
top: 0,
left: 8
};
var rootType = this.itemType( this.value );
if( rootType == 'h' ){
this.hashTokens( this.value, 'Root' );
}
else if( rootType == 'a' ){
this.arrayTokens( this.value, 'Root' );
}
else {
this.addToken( rootType, 'Root', this.value );
}
var i, token;
if(this['propertyItems'] === undefined){
this.propertyItems = [];
}
var colHeight = 0, colId = 0;
for( i = 0; i < this.valueTokens.length; i ++ ) {
token = this.valueTokens[i];
this.addRowSeparator( token, colId, (i%2===0) ); colId++;
this.addKeyColumnControl( token, colId ); colId++;
if(!this.options.hideTypeColumn){
this.addTypeColumnControl( token, colId ); colId++;
}
this.addValueColumnControl( token, colId ); colId++;
colHeight = token.top+this.options.rowHeight;
}
var propItemsLen = this.propertyItems.length, elemId;
for( i = colId; i < propItemsLen; i++ ){
elemId = this.propertyItems.pop();
ELEM.del( elemId );
}
this.keyColumn.bringToFront();
if(!this.options.hideTypeColumn){
this.typeColumn.bringToFront();
}
this.valueColumn.bringToFront();
this.resizeColumns.bringToFront();
this.keyColumn.rect.setHeight( colHeight );
this.keyColumn.drawRect();
if(!this.options.hideTypeColumn){
this.typeColumn.rect.setHeight( colHeight );
this.typeColumn.drawRect();
}
this.valueColumn.rect.setHeight( colHeight );
this.valueColumn.drawRect();
ELEM.setStyle(this.separatortParentElemId,'height',(colHeight+25)+'px');
}
});