- 1 :
/**
- 2 :
* The Contexts (or Keywords in Context) tool shows each occurrence of a keyword with a bit of surrounding text (the context).
- 3 :
*
- 4 :
* @example
- 5 :
*
- 6 :
* let config = {
- 7 :
* columns: null,
- 8 :
* context: 5,
- 9 :
* dir: null,
- 10 :
* docId: null,
- 11 :
* docIndex: null,
- 12 :
* expand: null,
- 13 :
* query: null,
- 14 :
* sort: null,
- 15 :
* stopList: null,
- 16 :
* termColors: null
- 17 :
* };
- 18 :
*
- 19 :
* loadCorpus("austen").tool("Contexts", config);
- 20 :
*
- 21 :
* @class Contexts
- 22 :
* @tutorial contexts
- 23 :
* @memberof Tools
- 24 :
*/
- 25 :
Ext.define('Voyant.panel.Contexts', {
- 26 :
extend: 'Ext.grid.Panel',
- 27 :
mixins: ['Voyant.panel.Panel'],
- 28 :
requires: ['Voyant.data.store.Contexts'],
- 29 :
alias: 'widget.contexts',
- 30 :
isConsumptive: true,
- 31 :
statics: {
- 32 :
i18n: {
- 33 :
},
- 34 :
api: {
- 35 :
/**
- 36 :
* @memberof Tools.Contexts
- 37 :
* @instance
- 38 :
* @property {query}
- 39 :
*/
- 40 :
query: undefined,
- 41 :
- 42 :
/**
- 43 :
* @memberof Tools.Contexts
- 44 :
* @instance
- 45 :
* @property {docId}
- 46 :
*/
- 47 :
docId: undefined,
- 48 :
- 49 :
/**
- 50 :
* @memberof Tools.Contexts
- 51 :
* @instance
- 52 :
* @property {docIndex}
- 53 :
*/
- 54 :
docIndex: undefined,
- 55 :
- 56 :
/**
- 57 :
* @memberof Tools.Contexts
- 58 :
* @instance
- 59 :
* @property {stopList}
- 60 :
* @default
- 61 :
*/
- 62 :
stopList: 'auto',
- 63 :
- 64 :
/**
- 65 :
* @memberof Tools.Contexts
- 66 :
* @instance
- 67 :
* @property {context}
- 68 :
* @default
- 69 :
*/
- 70 :
context: 5,
- 71 :
- 72 :
/**
- 73 :
* @memberof Tools.Contexts
- 74 :
* @instance
- 75 :
* @property {Number} expand How many terms to show when you expand any given row
- 76 :
* @default
- 77 :
*/
- 78 :
expand: 50,
- 79 :
- 80 :
/**
- 81 :
* @memberof Tools.Contexts
- 82 :
* @instance
- 83 :
* @property {columns} columns 'docIndex', 'left', 'term', 'right', 'position'
- 84 :
*/
- 85 :
columns: undefined,
- 86 :
- 87 :
/**
- 88 :
* @memberof Tools.Contexts
- 89 :
* @instance
- 90 :
* @property {sort}
- 91 :
*/
- 92 :
sort: undefined,
- 93 :
- 94 :
/**
- 95 :
* @memberof Tools.Contexts
- 96 :
* @instance
- 97 :
* @property {dir}
- 98 :
*/
- 99 :
dir: undefined,
- 100 :
- 101 :
/**
- 102 :
* @memberof Tools.Contexts
- 103 :
* @instance
- 104 :
* @property {termColors}
- 105 :
* @default
- 106 :
*/
- 107 :
termColors: 'categories'
- 108 :
},
- 109 :
glyph: 'xf0ce@FontAwesome'
- 110 :
},
- 111 :
config: {
- 112 :
options: [{xtype: 'stoplistoption'},{xtype: 'categoriesoption'},{xtype: 'termcolorsoption'}]
- 113 :
},
- 114 :
constructor: function() {
- 115 :
this.mixins['Voyant.util.Api'].constructor.apply(this, arguments);
- 116 :
this.callParent(arguments);
- 117 :
this.mixins['Voyant.panel.Panel'].constructor.apply(this, arguments);
- 118 :
},
- 119 :
- 120 :
initComponent: function() {
- 121 :
var me = this;
- 122 :
- 123 :
Ext.apply(me, {
- 124 :
title: this.localize('title'),
- 125 :
emptyText: this.localize("emptyText"),
- 126 :
store : Ext.create("Voyant.data.store.ContextsBuffered", {
- 127 :
parentPanel: this,
- 128 :
proxy: {
- 129 :
extraParams: {
- 130 :
stripTags: "all"
- 131 :
}
- 132 :
}
- 133 :
// sortOnLoad: true,
- 134 :
// sorters: {
- 135 :
// property: 'position',
- 136 :
// direction: 'ASC'
- 137 :
// }
- 138 :
}),
- 139 :
selModel: {
- 140 :
type: 'rowmodel',
- 141 :
listeners: {
- 142 :
selectionchange: {
- 143 :
fn: function(sm, selections) {
- 144 :
this.getApplication().dispatchEvent('termLocationClicked', this, selections);
- 145 :
},
- 146 :
scope: this
- 147 :
}
- 148 :
}
- 149 :
},
- 150 :
plugins: [{ // the expander slider assumes there's only one plugin, needs to be updated if changed
- 151 :
ptype: 'rowexpander',
- 152 :
rowBodyTpl : new Ext.XTemplate('')
- 153 :
}],
- 154 :
dockedItems: [{
- 155 :
dock: 'bottom',
- 156 :
xtype: 'toolbar',
- 157 :
overflowHandler: 'scroller',
- 158 :
items: [{
- 159 :
xtype: 'querysearchfield'
- 160 :
}, {
- 161 :
xtype: 'totalpropertystatus'
- 162 :
}, this.localize('context'), {
- 163 :
xtype: 'slider',
- 164 :
minValue: 5,
- 165 :
value: 5,
- 166 :
maxValue: 50,
- 167 :
increment: 5,
- 168 :
width: 50,
- 169 :
listeners: {
- 170 :
render: function(slider) {
- 171 :
slider.setValue(me.getApiParam('context'));
- 172 :
},
- 173 :
changecomplete: function(slider, newValue) {
- 174 :
me.setApiParam("context", slider.getValue());
- 175 :
me.getStore().clearAndLoad({params: me.getApiParams()});
- 176 :
}
- 177 :
}
- 178 :
}, this.localize('expand'), {
- 179 :
xtype: 'slider',
- 180 :
minValue: 5,
- 181 :
value: 5,
- 182 :
maxValue: 500,
- 183 :
increment: 10,
- 184 :
width: 50,
- 185 :
listeners: {
- 186 :
render: function(slider) {
- 187 :
slider.setValue(me.getApiParam('expand'));
- 188 :
},
- 189 :
changecomplete: function(slider, newValue) {
- 190 :
me.setApiParam('expand', newValue);
- 191 :
var view = me.getView();
- 192 :
var recordsExpanded = me.plugins[0].recordsExpanded;
- 193 :
var store = view.getStore();
- 194 :
for (var id in recordsExpanded) {
- 195 :
var record = store.getByInternalId(id);
- 196 :
var row = view.getRow(record);
- 197 :
var expandRow = row.parentNode.childNodes[1];
- 198 :
if (recordsExpanded[id]) {
- 199 :
view.fireEvent("expandbody", row, record, expandRow, {force: true});
- 200 :
} else {
- 201 :
Ext.fly(expandRow).down('.x-grid-rowbody').setHtml('');
- 202 :
}
- 203 :
}
- 204 :
}
- 205 :
}
- 206 :
},{
- 207 :
xtype: 'corpusdocumentselector'
- 208 :
}]
- 209 :
}],
- 210 :
columns: [{
- 211 :
text: this.localize("document"),
- 212 :
tooltip: this.localize("documentTip"),
- 213 :
width: 'autoSize',
- 214 :
dataIndex: 'docIndex',
- 215 :
sortable: true,
- 216 :
renderer: function (value, metaData, record, rowIndex, colIndex, store) {
- 217 :
return store.getCorpus().getDocument(value).getTitle();
- 218 :
}
- 219 :
},{
- 220 :
text: this.localize("left"),
- 221 :
tooltip: this.localize("leftTip"),
- 222 :
align: 'right',
- 223 :
dataIndex: 'left',
- 224 :
sortable: true,
- 225 :
flex: 1
- 226 :
},{
- 227 :
text: this.localize("term"),
- 228 :
tooltip: this.localize("termTip"),
- 229 :
dataIndex: 'term',
- 230 :
sortable: true,
- 231 :
width: 'autoSize',
- 232 :
xtype: 'coloredtermfield'
- 233 :
},{
- 234 :
text: this.localize("right"),
- 235 :
tooltip: this.localize("rightTip"),
- 236 :
dataIndex: 'right',
- 237 :
sortable: true,
- 238 :
flex: 1
- 239 :
},{
- 240 :
text: this.localize("position"),
- 241 :
tooltip: this.localize("positionTip"),
- 242 :
dataIndex: 'position',
- 243 :
sortable: true,
- 244 :
hidden: true,
- 245 :
flex: 1
- 246 :
}],
- 247 :
listeners: {
- 248 :
scope: this,
- 249 :
corpusSelected: function() {
- 250 :
if (this.getStore().getCorpus()) {
- 251 :
this.setApiParams({docId: undefined, docIndex: undefined})
- 252 :
this.getStore().clearAndLoad()
- 253 :
}
- 254 :
},
- 255 :
- 256 :
documentsSelected: function(src, docs) {
- 257 :
var docIds = [];
- 258 :
var corpus = this.getStore().getCorpus();
- 259 :
docs.forEach(function(doc) {
- 260 :
docIds.push(corpus.getDocument(doc).getId())
- 261 :
}, this);
- 262 :
this.setApiParams({docId: docIds, docIndex: undefined})
- 263 :
this.getStore().clearAndLoad()
- 264 :
},
- 265 :
- 266 :
documentSegmentTermClicked: {
- 267 :
fn: function(src, documentSegmentTerm) {
- 268 :
if (!documentSegmentTerm.term) {return;}
- 269 :
params = {query: documentSegmentTerm.term};
- 270 :
if (documentSegmentTerm.docId) {
- 271 :
params.docId = documentSegmentTerm.docId;
- 272 :
}
- 273 :
else {
- 274 :
// default to first document
- 275 :
params.docIndex = documentSegmentTerm.docIndex ? documentSegmentTerm.docIndex : 0;
- 276 :
}
- 277 :
this.setApiParams(params);
- 278 :
if (this.isVisible()) {
- 279 :
this.getStore().clearAndLoad()
- 280 :
}
- 281 :
},
- 282 :
scope: this
- 283 :
},
- 284 :
documentIndexTermsClicked: {
- 285 :
fn: function(src, documentIndexTerms) {
- 286 :
// this isn't quite right, since we want every term associated with a docIndex, but for now it will do
- 287 :
var queriesHash = {};
- 288 :
var queries = [];
- 289 :
var docIndexHash = {};
- 290 :
var docIndex = [];
- 291 :
documentIndexTerms.forEach(function(item) {
- 292 :
if (!queriesHash[item.term]) {
- 293 :
queries.push(item.term);
- 294 :
queriesHash[item.term]=true;
- 295 :
}
- 296 :
if (!docIndexHash[item.docIndex]) {
- 297 :
docIndex.push(item.docIndex);
- 298 :
docIndexHash[item.docIndex]=true;
- 299 :
}
- 300 :
});
- 301 :
this.setApiParams({
- 302 :
docId: undefined,
- 303 :
docIndex: docIndex,
- 304 :
query: queries
- 305 :
});
- 306 :
if (this.isVisible()) {
- 307 :
this.getStore().clearAndLoad({params: this.getApiParams()});
- 308 :
}
- 309 :
},
- 310 :
scope: this
- 311 :
},
- 312 :
afterrender: function(me) {
- 313 :
me.getView().on('expandbody', function( rowNode, record, expandRow, eOpts ) {
- 314 :
if (expandRow.textContent==="" || (eOpts && eOpts.force)) {
- 315 :
var store = Ext.create("Voyant.data.store.Contexts", {
- 316 :
stripTags: "all",
- 317 :
corpus: me.getStore().getCorpus()
- 318 :
});
- 319 :
var data = record.getData();
- 320 :
var query = data.query;
- 321 :
if (query.match(/^[\^@]/) !== null) {
- 322 :
query = data.term; // if it's a category query then use term instead
- 323 :
}
- 324 :
store.load({
- 325 :
params: {
- 326 :
query: query,
- 327 :
docIndex: data.docIndex,
- 328 :
position: data.position,
- 329 :
limit: 1,
- 330 :
context: me.getApiParam('expand')
- 331 :
},
- 332 :
callback: function(records, operation, success) {
- 333 :
if (success && records.length==1) {
- 334 :
data = records[0].getData();
- 335 :
Ext.fly(operation.expandRow).down('.x-grid-rowbody').setHtml(data.left + " <span class='word keyword'>" + data.middle + "</span> " + data.right);
- 336 :
}
- 337 :
},
- 338 :
expandRow: expandRow
- 339 :
});
- 340 :
- 341 :
}
- 342 :
});
- 343 :
}
- 344 :
- 345 :
}
- 346 :
});
- 347 :
- 348 :
me.on("loadedCorpus", function(src, corpus) {
- 349 :
if (this.hasCorpusAccess(corpus)==false) {
- 350 :
this.mask(this.localize('limitedAccess'), 'mask-no-spinner');
- 351 :
}
- 352 :
else {
- 353 :
var query = Ext.Array.from(this.getApiParam("query"));
- 354 :
if (query.length > 0 && query[0].match(/^[\^@]/) !== null) {
- 355 :
// query is a category so just load
- 356 :
this.getStore().clearAndLoad({params: this.getApiParams()});
- 357 :
} else {
- 358 :
var corpusTerms = corpus.getCorpusTerms({autoLoad: false});
- 359 :
corpusTerms.load({
- 360 :
callback: function(records, operation, success) {
- 361 :
if (success && records.length>0) {
- 362 :
this.setApiParam("query", [records[0].getTerm()]);
- 363 :
this.getStore().clearAndLoad({params: this.getApiParams()});
- 364 :
}
- 365 :
},
- 366 :
scope: me,
- 367 :
params: {
- 368 :
limit: 1,
- 369 :
query: query,
- 370 :
stopList: this.getApiParam("stopList"),
- 371 :
forTool: 'contexts'
- 372 :
}
- 373 :
});
- 374 :
}
- 375 :
}
- 376 :
});
- 377 :
- 378 :
me.on("query", function(src, query) {
- 379 :
this.setApiParam('query', query);
- 380 :
this.getStore().clearAndLoad({params: this.getApiParams()});
- 381 :
}, me);
- 382 :
- 383 :
me.on("documentTermsClicked", function(src, documentTerms) {
- 384 :
var documentIndexTerms = [];
- 385 :
documentTerms.forEach(function(documentTerm) {
- 386 :
documentIndexTerms.push({
- 387 :
term: documentTerm.getTerm(),
- 388 :
docIndex: documentTerm.getDocIndex()
- 389 :
});
- 390 :
});
- 391 :
this.fireEvent("documentIndexTermsClicked", this, documentIndexTerms);
- 392 :
});
- 393 :
- 394 :
me.on("termsClicked", function(src, terms) {
- 395 :
var documentIndexTerms = [];
- 396 :
if (Ext.isString(terms)) {terms = [terms];}
- 397 :
terms.forEach(function(term) {
- 398 :
if (term.docIndex !== undefined) {
- 399 :
documentIndexTerms.push({
- 400 :
term: term.term,
- 401 :
docIndex: term.docIndex
- 402 :
});
- 403 :
}
- 404 :
});
- 405 :
if (documentIndexTerms.length > 0) {
- 406 :
this.fireEvent("documentIndexTermsClicked", this, documentIndexTerms);
- 407 :
}
- 408 :
});
- 409 :
- 410 :
me.callParent(arguments);
- 411 :
}
- 412 :
- 413 :
});