PDA

View Full Version : Custom Components with HTML Encoding



Tom23
12 Dec 2009, 10:10 AM
After following the discussion on
http://www.extjs.com/forum/showthread.php?t=13913
I decied that the world needs*) some enhanced versions of GridView, DataView and TreePanel that can handle unencoded HTML characters in their source data.

Here we go:


// http://www.extjs.com/forum/showthread.php?p=418233

Ext.ns('Ext.ux.grid');

/**
* Just like Ext.grid.GridPanel, but raw text can be used as input from a store
* The panel takes care of encodings.
*/
Ext.ux.grid.EncodingGridPanel = Ext.extend(Ext.grid.GridPanel, {
initComponent: function () {
if(Ext.isArray(this.columns)) {
this.colModel = new Ext.ux.grid.EncodingColumnModel(this.columns);
delete this.columns;
}
Ext.ux.grid.EncodingGridPanel.superclass.initComponent.apply(this, arguments);
this.autoEncode = false;
}
});
Ext.reg('ux-encodinggrid', Ext.ux.grid.EncodingGridPanel);

/**
* Just like Ext.grid.EditorGridPanel, but raw text can be used as input from a store.
* The panel takes care of encodings.
*/
Ext.ux.grid.EncodingEditorGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
initComponent: function () {
if(Ext.isArray(this.columns)) {
this.colModel = new Ext.ux.grid.EncodingColumnModel(this.columns);
delete this.columns;
}
Ext.ux.grid.EncodingEditorGridPanel.superclass.initComponent.apply(this, arguments);
this.autoEncode = false;
}
});
Ext.reg('ux-encodingeditorgrid', Ext.ux.grid.EncodingEditorGridPanel);

/**
* This is the column model for use with {@link Ext.ux.grid.EncodingGridPanel} or {@link Ext.ux.grid.EncodingEditorGridPanel}
*/
Ext.ux.grid.EncodingColumnModel = Ext.extend(Ext.grid.ColumnModel, {
getRenderer: function(col) {
var renderer = this.config[col].renderer || Ext.grid.ColumnModel.defaultRenderer;
/**
* @param {Boolean} autoEncode Set this to <b>false</b> to disable encoding. Undefined by default
*/
if (this.config[col].autoEncode === false) {
return renderer;
} else {
return function (input) {
return renderer(Ext.util.Format.htmlEncode(input));
};
}
}
});


/**
* Just like Ext.DataView, but raw text can be used as input from a store.
* The view takes care of encodings.
*/
Ext.ux.EncodingDataView = Ext.extend(Ext.DataView, {
refresh: function () {
this.clearSelections(false, true);
var el = this.getTemplateTarget();
el.update("");
var records = this.store.getRange();
if(records.length < 1) {
if(!this.deferEmptyText || this.hasSkippedEmptyText) {
el.update(this.emptyText);
}
this.all.clear();
} else {
this.tpl.overwrite(el, this.collectAndEncodeData(records, 0));
this.all.fill(Ext.query(this.itemSelector, el.dom));
this.updateIndexes(0);
}
this.hasSkippedEmptyText = true;
},

bufferRender: function (records) {
var div = document.createElement('div');
this.tpl.overwrite(div, this.collectAndEncodeData(records));
return Ext.query(this.itemSelector, div);
},

collectAndEncodeData: function (records) {
var data = this.collectData(records);
var encodedData = [];
Ext.each(data, function (item) {
var encodedItem = {};
for (var prop in item) {
var unencodedVal = item[prop];
encodedItem[prop] = Ext.isString(unencodedVal) ? Ext.util.Format.htmlEncode(unencodedVal) : unencodedVal;
}
encodedData.push(encodedItem);
});
return encodedData;
}
});
Ext.reg('ux-encodingdataview', Ext.ux.EncodingDataView);

Ext.ns('Ext.ux.tree');

/**
* Just like Ext.tree.TreeNodeUI, but raw text can be used as a label.
* The tree takes care of encodings.
*/
Ext.ux.tree.EncodingTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
onTextChange : function (node, text, oldText){
if(this.rendered) {
this.textNode.innerHTML = Ext.util.Format.htmlEncode(text);
}
},

renderElements: function (node, a, targetNode, bulkRender) {
var convertedNode = Ext.apply({}, node);
convertedNode.text = Ext.util.Format.htmlEncode(node.text);
Ext.ux.tree.EncodingTreeNodeUI.superclass.renderElements.call(this, convertedNode, a, targetNode, bulkRender);
}
});

/**
* Just like Ext.tree.TreeNode, but raw text can be used as a label.
* The tree takes care of encodings.
*/
Ext.ux.tree.EncodingTreeNode = Ext.extend(Ext.tree.TreeNode, {
defaultUI: Ext.ux.tree.EncodingTreeNodeUI
});

/**
* Just like Ext.tree.AsyncTreeNode, but raw text can be used as a label.
* The tree takes care of encodings.
*/
Ext.ux.tree.EncodingAsyncTreeNode = Ext.extend(Ext.tree.AsyncTreeNode, {
defaultUI: Ext.ux.tree.EncodingTreeNodeUI
});

/**
* Just like Ext.tree.TreeLoader, but generates
* Ext.ux.tree.EncodingTreeNodes instead of Ext.tree.TreeNodes
* Ext.ux.tree.EncodingAsyncTreeNodes instead of Ext.tree.AsyncTreeNodes
*/
Ext.ux.tree.EncodingTreeLoader = Ext.extend(Ext.tree.TreeLoader, {
createNode: function (attr) {
if (this.baseAttrs) {
Ext.applyIf(attr, this.baseAttrs);
}
if (this.applyLoader !== false && !attr.loader) {
attr.loader = this;
}
if (Ext.isString(attr.uiProvider)) {
attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
}
if (attr.nodeType) {
return new Ext.ux.tree.EncodingTreePanel.nodeTypes[attr.nodeType](attr);
} else {
return attr.leaf ?
new Ext.ux.tree.EncodingTreeNode(attr) :
new Ext.ux.tree.EncodingAsyncTreeNode(attr);
}
}
});

/**
* Just like Ext.tree.TreePanel, but raw text can be used as node labels
* The panel takes care of encodings.
*/
Ext.ux.tree.EncodingTreePanel = Ext.extend(Ext.tree.TreePanel, {
initComponent: function () {
var l = this.loader;
if (!l) {
l = new Ext.ux.tree.EncodingTreeLoader({
dataUrl: this.dataUrl,
requestMethod: this.requestMethod
});
} else if (Ext.isObject(l) && !l.load) {
l = new Ext.ux.tree.EncodingTreeLoader(l);
}
this.loader = l;
Ext.ux.tree.EncodingTreePanel.superclass.initComponent.apply(this, arguments);
}
});
Ext.ux.tree.EncodingTreePanel.nodeTypes = {
node: Ext.ux.tree.EncodingTreeNode,
async: Ext.ux.tree.EncodingAsyncTreeNode
};
Ext.reg('ux-encodingtreepanel', Ext.ux.tree.EncodingTreePanel);

/**
* Just like Ext.form.ComboBox, but raw text can be used as input from a store
* The combobox takes care of encodings.
*/
Ext.ns('Ext.ux.form');
Ext.ux.form.EncodingComboBox = Ext.extend(Ext.form.ComboBox, {
initList: function() {
if (!this.list) {
var cls = 'x-combo-list';

this.list = new Ext.Layer({
parentEl: this.getListParent(),
shadow: this.shadow,
cls: [cls, this.listClass].join(' '),
constrain: false,
zindex: 12000
});

var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
this.list.setSize(lw, 0);
this.list.swallowEvent('mousewheel');
this.assetHeight = 0;
if (this.syncFont !== false) {
this.list.setStyle('font-size', this.el.getStyle('font-size'));
}
if (this.title) {
this.header = this.list.createChild({
cls: cls + '-hd',
html: this.title
});
this.assetHeight += this.header.getHeight();
}

this.innerList = this.list.createChild({
cls: cls + '-inner'
});
this.mon(this.innerList, 'mouseover', this.onViewOver, this);
this.mon(this.innerList, 'mousemove', this.onViewMove, this);
this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));

if (this.pageSize) {
this.footer = this.list.createChild({
cls: cls + '-ft'
});
this.pageTb = new Ext.PagingToolbar({
store: this.store,
pageSize: this.pageSize,
renderTo: this.footer
});
this.assetHeight += this.footer.getHeight();
}

if (!this.tpl) {

this.tpl = '<tpl for="."><div class="' + cls + '-item">{' + this.displayField + '}</div></tpl>';

}


// this is the only thing that changes, compared to the original implementation
this.view = new Ext.ux.EncodingDataView({
applyTo: this.innerList,
tpl: this.tpl,
singleSelect: true,
selectedClass: this.selectedClass,
itemSelector: this.itemSelector || '.' + cls + '-item',
emptyText: this.listEmptyText
});

this.mon(this.view, 'click', this.onViewClick, this);

this.bindStore(this.store, true);

if (this.resizable) {
this.resizer = new Ext.Resizable(this.list, {
pinned: true,
handles: 'se'
});
this.mon(this.resizer, 'resize', function (r, w, h) {
this.maxHeight = h - this.handleHeight - this.list.getFrameWidth('tb') - this.assetHeight;
this.listWidth = w;
this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
this.restrictHeight();
},
this);

this[this.pageSize ? 'footer' : 'innerList'].setStyle('margin-bottom', this.handleHeight + 'px');
}
}
}
});
And some sample usage, too:

Ext.onReady(function() {

var fakeData = [
['foo', 'bar'],
['<script>alert("XSS!");</script>', '<b>&amp;</b>'],
['baz', 'qux']
];

var store = new Ext.data.ArrayStore({
idIndex: 0,
fields: ['field1', 'field2']
});

store.loadData(fakeData);

var grid = new Ext.ux.grid.EncodingEditorGridPanel({
title: 'EditorGridPanel',
height: 150,
width: 420,
columns: [{
header: 'Column 1',
dataIndex: 'field1',
width: 200,
editor: Ext.form.TextField
}, {
header: 'Column 2 (custom renderer used)',
dataIndex: 'field2',
width: 200,
editor: Ext.form.TextField,
autoEncode: false, // load raw data into renderer
renderer: function (raw) { return raw.replace(/&/g, '+').replace(/</g, '[').replace(/>/g, ']'); }
}],
store: store
});

var dataView = new Ext.ux.EncodingDataView({
store: store,
tpl: new Ext.XTemplate('<tpl for="."><p><b>{field1}</b> <span>{field2}</span></p></tpl>'),
itemSelector:'p'
});
var dataViewPanel = new Ext.Panel({
title: 'DataView',
width: 420,
items: [dataView]
});

var treePanel = new Ext.ux.tree.EncodingTreePanel({
title: 'TreePanel',
width: 420,
heigth: 200,
root: new Ext.ux.tree.EncodingAsyncTreeNode({
expanded: true,
children: makeNodes(store)
})
});
new Ext.tree.TreeEditor(treePanel);

function makeNodes(store) {
var records = store.getRange();
var result = [];
Ext.each(records, function (record) {
result.push({
text: record.data.field1 + ' ' + record.data.field2,
leaf: true,
editable: true
});
});
return result;
}

grid.render(Ext.getBody());
dataViewPanel.render(Ext.getBody());
treePanel.render(Ext.getBody());
});*) at least I do need them, so why not share...

---

Edit 09-12-17: added EncodingComboBox