[#124,refactor][s]: addendum to last commit (forgot to split out filtereditor and delete from multiview).
This commit is contained in:
@@ -66,6 +66,7 @@
|
|||||||
<script type="text/javascript" src="../src/view-map.js"></script>
|
<script type="text/javascript" src="../src/view-map.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-timeline.js"></script>
|
<script type="text/javascript" src="../src/view-timeline.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.queryeditor.js"></script>
|
<script type="text/javascript" src="../src/widget.queryeditor.js"></script>
|
||||||
|
<script type="text/javascript" src="../src/widget.filtereditor.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.facetviewer.js"></script>
|
<script type="text/javascript" src="../src/widget.facetviewer.js"></script>
|
||||||
<script type="text/javascript" src="../src/view.multiview.js"></script>
|
<script type="text/javascript" src="../src/view.multiview.js"></script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,98 +1,5 @@
|
|||||||
/*jshint multistr:true */
|
/*jshint multistr:true */
|
||||||
|
|
||||||
// # Recline Views
|
|
||||||
//
|
|
||||||
// Recline Views are instances of Backbone Views and they act as 'WUI' (web
|
|
||||||
// user interface) component displaying some model object in the DOM. Like all
|
|
||||||
// Backbone views they have a pointer to a model (or a collection) and have an
|
|
||||||
// associated DOM-style element (usually this element will be bound into the
|
|
||||||
// page at some point).
|
|
||||||
//
|
|
||||||
// Views provided by core Recline are crudely divided into two types:
|
|
||||||
//
|
|
||||||
// * Dataset Views: a View intended for displaying a recline.Model.Dataset
|
|
||||||
// in some fashion. Examples are the Grid, Graph and Map views.
|
|
||||||
// * Widget Views: a widget used for displaying some specific (and
|
|
||||||
// smaller) aspect of a dataset or the application. Examples are
|
|
||||||
// QueryEditor and FilterEditor which both provide a way for editing (a
|
|
||||||
// part of) a `recline.Model.Query` associated to a Dataset.
|
|
||||||
//
|
|
||||||
// ## Dataset View
|
|
||||||
//
|
|
||||||
// These views are just Backbone views with a few additional conventions:
|
|
||||||
//
|
|
||||||
// 1. The model passed to the View should always be a recline.Model.Dataset instance
|
|
||||||
// 2. Views should generate their own root element rather than having it passed
|
|
||||||
// in.
|
|
||||||
// 3. Views should apply a css class named 'recline-{view-name-lower-cased} to
|
|
||||||
// the root element (and for all CSS for this view to be qualified using this
|
|
||||||
// CSS class)
|
|
||||||
// 4. Read-only mode: CSS for this view should respect/utilize
|
|
||||||
// recline-read-only class to trigger read-only behaviour (this class will
|
|
||||||
// usually be set on some parent element of the view's root element.
|
|
||||||
// 5. State: state (configuration) information for the view should be stored on
|
|
||||||
// an attribute named state that is an instance of a Backbone Model (or, more
|
|
||||||
// speficially, be an instance of `recline.Model.ObjectState`). In addition,
|
|
||||||
// a state attribute may be specified in the Hash passed to a View on
|
|
||||||
// iniitialization and this information should be used to set the initial
|
|
||||||
// state of the view.
|
|
||||||
//
|
|
||||||
// Example of state would be the set of fields being plotted in a graph
|
|
||||||
// view.
|
|
||||||
//
|
|
||||||
// More information about State can be found below.
|
|
||||||
//
|
|
||||||
// To summarize some of this, the initialize function for a Dataset View should
|
|
||||||
// look like:
|
|
||||||
//
|
|
||||||
// <pre>
|
|
||||||
// initialize: {
|
|
||||||
// model: {a recline.Model.Dataset instance}
|
|
||||||
// // el: {do not specify - instead view should create}
|
|
||||||
// state: {(optional) Object / Hash specifying initial state}
|
|
||||||
// ...
|
|
||||||
// }
|
|
||||||
// </pre>
|
|
||||||
//
|
|
||||||
// Note: Dataset Views in core Recline have a common layout on disk as
|
|
||||||
// follows, where ViewName is the named of View class:
|
|
||||||
//
|
|
||||||
// <pre>
|
|
||||||
// src/view-{lower-case-ViewName}.js
|
|
||||||
// css/{lower-case-ViewName}.css
|
|
||||||
// test/view-{lower-case-ViewName}.js
|
|
||||||
// </pre>
|
|
||||||
//
|
|
||||||
// ### State
|
|
||||||
//
|
|
||||||
// State information exists in order to support state serialization into the
|
|
||||||
// url or elsewhere and reloading of application from a stored state.
|
|
||||||
//
|
|
||||||
// State is available not only for individual views (as described above) but
|
|
||||||
// for the dataset (e.g. the current query). For an example of pulling together
|
|
||||||
// state from across multiple components see `recline.View.MultiView`.
|
|
||||||
//
|
|
||||||
// ### Flash Messages / Notifications
|
|
||||||
//
|
|
||||||
// To send 'flash messages' or notifications the convention is that views
|
|
||||||
// should fire an event named `recline:flash` with a payload that is a
|
|
||||||
// flash object with the following attributes (all optional):
|
|
||||||
//
|
|
||||||
// * message: message to show.
|
|
||||||
// * category: warning (default), success, error
|
|
||||||
// * persist: if true alert is persistent, o/w hidden after 3s (default=false)
|
|
||||||
// * loader: if true show a loading message
|
|
||||||
//
|
|
||||||
// Objects or views wishing to bind to flash messages may then subscribe to
|
|
||||||
// these events and take some action such as displaying them to the user. For
|
|
||||||
// an example of such behaviour see the MultiView view.
|
|
||||||
//
|
|
||||||
// ### Writing your own Views
|
|
||||||
//
|
|
||||||
// See the existing Views.
|
|
||||||
//
|
|
||||||
// ----
|
|
||||||
|
|
||||||
// Standard JS module setup
|
// Standard JS module setup
|
||||||
this.recline = this.recline || {};
|
this.recline = this.recline || {};
|
||||||
this.recline.View = this.recline.View || {};
|
this.recline.View = this.recline.View || {};
|
||||||
@@ -292,16 +199,16 @@ my.MultiView = Backbone.View.extend({
|
|||||||
_.each(this.pageViews, function(view, pageName) {
|
_.each(this.pageViews, function(view, pageName) {
|
||||||
$dataViewContainer.append(view.view.el);
|
$dataViewContainer.append(view.view.el);
|
||||||
});
|
});
|
||||||
var queryEditor = new my.QueryEditor({
|
var queryEditor = new recline.View.QueryEditor({
|
||||||
model: this.model.queryState
|
model: this.model.queryState
|
||||||
});
|
});
|
||||||
this.el.find('.query-editor-here').append(queryEditor.el);
|
this.el.find('.query-editor-here').append(queryEditor.el);
|
||||||
var filterEditor = new my.FilterEditor({
|
var filterEditor = new recline.View.FilterEditor({
|
||||||
model: this.model.queryState
|
model: this.model.queryState
|
||||||
});
|
});
|
||||||
this.$filterEditor = filterEditor.el;
|
this.$filterEditor = filterEditor.el;
|
||||||
this.el.find('.header').append(filterEditor.el);
|
this.el.find('.header').append(filterEditor.el);
|
||||||
var facetViewer = new my.FacetViewer({
|
var facetViewer = new recline.View.FacetViewer({
|
||||||
model: this.model
|
model: this.model
|
||||||
});
|
});
|
||||||
this.$facetViewer = facetViewer.el;
|
this.$facetViewer = facetViewer.el;
|
||||||
@@ -465,231 +372,5 @@ my.MultiView.restore = function(state) {
|
|||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
my.QueryEditor = Backbone.View.extend({
|
|
||||||
className: 'recline-query-editor',
|
|
||||||
template: ' \
|
|
||||||
<form action="" method="GET" class="form-inline"> \
|
|
||||||
<div class="input-prepend text-query"> \
|
|
||||||
<span class="add-on"><i class="icon-search"></i></span> \
|
|
||||||
<input type="text" name="q" value="{{q}}" class="span2" placeholder="Search data ..." class="search-query" /> \
|
|
||||||
</div> \
|
|
||||||
<div class="pagination"> \
|
|
||||||
<ul> \
|
|
||||||
<li class="prev action-pagination-update"><a href="">«</a></li> \
|
|
||||||
<li class="active"><a><input name="from" type="text" value="{{from}}" /> – <input name="to" type="text" value="{{to}}" /> </a></li> \
|
|
||||||
<li class="next action-pagination-update"><a href="">»</a></li> \
|
|
||||||
</ul> \
|
|
||||||
</div> \
|
|
||||||
<button type="submit" class="btn">Go »</button> \
|
|
||||||
</form> \
|
|
||||||
',
|
|
||||||
|
|
||||||
events: {
|
|
||||||
'submit form': 'onFormSubmit',
|
|
||||||
'click .action-pagination-update': 'onPaginationUpdate'
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function() {
|
|
||||||
_.bindAll(this, 'render');
|
|
||||||
this.el = $(this.el);
|
|
||||||
this.model.bind('change', this.render);
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
onFormSubmit: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var query = this.el.find('.text-query input').val();
|
|
||||||
var newFrom = parseInt(this.el.find('input[name="from"]').val());
|
|
||||||
var newSize = parseInt(this.el.find('input[name="to"]').val()) - newFrom;
|
|
||||||
this.model.set({size: newSize, from: newFrom, q: query});
|
|
||||||
},
|
|
||||||
onPaginationUpdate: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var $el = $(e.target);
|
|
||||||
var newFrom = 0;
|
|
||||||
if ($el.parent().hasClass('prev')) {
|
|
||||||
newFrom = this.model.get('from') - Math.max(0, this.model.get('size'));
|
|
||||||
} else {
|
|
||||||
newFrom = this.model.get('from') + this.model.get('size');
|
|
||||||
}
|
|
||||||
this.model.set({from: newFrom});
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var tmplData = this.model.toJSON();
|
|
||||||
tmplData.to = this.model.get('from') + this.model.get('size');
|
|
||||||
var templated = Mustache.render(this.template, tmplData);
|
|
||||||
this.el.html(templated);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
my.FilterEditor = Backbone.View.extend({
|
|
||||||
className: 'recline-filter-editor well',
|
|
||||||
template: ' \
|
|
||||||
<a class="close js-hide" href="#">×</a> \
|
|
||||||
<div class="row filters"> \
|
|
||||||
<div class="span1"> \
|
|
||||||
<h3>Filters</h3> \
|
|
||||||
</div> \
|
|
||||||
<div class="span11"> \
|
|
||||||
<form class="form-horizontal"> \
|
|
||||||
<div class="row"> \
|
|
||||||
<div class="span6"> \
|
|
||||||
{{#termFilters}} \
|
|
||||||
<div class="control-group filter-term filter" data-filter-id={{id}}> \
|
|
||||||
<label class="control-label" for="">{{label}}</label> \
|
|
||||||
<div class="controls"> \
|
|
||||||
<div class="input-append"> \
|
|
||||||
<input type="text" value="{{value}}" name="{{fieldId}}" class="span4" data-filter-field="{{fieldId}}" data-filter-id="{{id}}" data-filter-type="term" /> \
|
|
||||||
<a class="btn js-remove-filter"><i class="icon-remove"></i></a> \
|
|
||||||
</div> \
|
|
||||||
</div> \
|
|
||||||
</div> \
|
|
||||||
{{/termFilters}} \
|
|
||||||
</div> \
|
|
||||||
<div class="span4"> \
|
|
||||||
<p>To add a filter use the column menu in the grid view.</p> \
|
|
||||||
<button type="submit" class="btn">Update</button> \
|
|
||||||
</div> \
|
|
||||||
</form> \
|
|
||||||
</div> \
|
|
||||||
</div> \
|
|
||||||
',
|
|
||||||
events: {
|
|
||||||
'click .js-hide': 'onHide',
|
|
||||||
'click .js-remove-filter': 'onRemoveFilter',
|
|
||||||
'submit form': 'onTermFiltersUpdate'
|
|
||||||
},
|
|
||||||
initialize: function() {
|
|
||||||
this.el = $(this.el);
|
|
||||||
_.bindAll(this, 'render');
|
|
||||||
this.model.bind('change', this.render);
|
|
||||||
this.model.bind('change:filters:new-blank', this.render);
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var tmplData = $.extend(true, {}, this.model.toJSON());
|
|
||||||
// we will use idx in list as there id ...
|
|
||||||
tmplData.filters = _.map(tmplData.filters, function(filter, idx) {
|
|
||||||
filter.id = idx;
|
|
||||||
return filter;
|
|
||||||
});
|
|
||||||
tmplData.termFilters = _.filter(tmplData.filters, function(filter) {
|
|
||||||
return filter.term !== undefined;
|
|
||||||
});
|
|
||||||
tmplData.termFilters = _.map(tmplData.termFilters, function(filter) {
|
|
||||||
var fieldId = _.keys(filter.term)[0];
|
|
||||||
return {
|
|
||||||
id: filter.id,
|
|
||||||
fieldId: fieldId,
|
|
||||||
label: fieldId,
|
|
||||||
value: filter.term[fieldId]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
var out = Mustache.render(this.template, tmplData);
|
|
||||||
this.el.html(out);
|
|
||||||
// are there actually any facets to show?
|
|
||||||
if (this.model.get('filters').length > 0) {
|
|
||||||
this.el.show();
|
|
||||||
} else {
|
|
||||||
this.el.hide();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onHide: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.el.hide();
|
|
||||||
},
|
|
||||||
onRemoveFilter: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var $target = $(e.target);
|
|
||||||
var filterId = $target.closest('.filter').attr('data-filter-id');
|
|
||||||
this.model.removeFilter(filterId);
|
|
||||||
},
|
|
||||||
onTermFiltersUpdate: function(e) {
|
|
||||||
var self = this;
|
|
||||||
e.preventDefault();
|
|
||||||
var filters = self.model.get('filters');
|
|
||||||
var $form = $(e.target);
|
|
||||||
_.each($form.find('input'), function(input) {
|
|
||||||
var $input = $(input);
|
|
||||||
var filterIndex = parseInt($input.attr('data-filter-id'));
|
|
||||||
var value = $input.val();
|
|
||||||
var fieldId = $input.attr('data-filter-field');
|
|
||||||
filters[filterIndex].term[fieldId] = value;
|
|
||||||
});
|
|
||||||
self.model.set({filters: filters});
|
|
||||||
self.model.trigger('change');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
my.FacetViewer = Backbone.View.extend({
|
|
||||||
className: 'recline-facet-viewer well',
|
|
||||||
template: ' \
|
|
||||||
<a class="close js-hide" href="#">×</a> \
|
|
||||||
<div class="facets row"> \
|
|
||||||
<div class="span1"> \
|
|
||||||
<h3>Facets</h3> \
|
|
||||||
</div> \
|
|
||||||
{{#facets}} \
|
|
||||||
<div class="facet-summary span2 dropdown" data-facet="{{id}}"> \
|
|
||||||
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#"><i class="icon-chevron-down"></i> {{id}} {{label}}</a> \
|
|
||||||
<ul class="facet-items dropdown-menu"> \
|
|
||||||
{{#terms}} \
|
|
||||||
<li><a class="facet-choice js-facet-filter" data-value="{{term}}">{{term}} ({{count}})</a></li> \
|
|
||||||
{{/terms}} \
|
|
||||||
{{#entries}} \
|
|
||||||
<li><a class="facet-choice js-facet-filter" data-value="{{time}}">{{term}} ({{count}})</a></li> \
|
|
||||||
{{/entries}} \
|
|
||||||
</ul> \
|
|
||||||
</div> \
|
|
||||||
{{/facets}} \
|
|
||||||
</div> \
|
|
||||||
',
|
|
||||||
|
|
||||||
events: {
|
|
||||||
'click .js-hide': 'onHide',
|
|
||||||
'click .js-facet-filter': 'onFacetFilter'
|
|
||||||
},
|
|
||||||
initialize: function(model) {
|
|
||||||
_.bindAll(this, 'render');
|
|
||||||
this.el = $(this.el);
|
|
||||||
this.model.facets.bind('all', this.render);
|
|
||||||
this.model.fields.bind('all', this.render);
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var tmplData = {
|
|
||||||
facets: this.model.facets.toJSON(),
|
|
||||||
fields: this.model.fields.toJSON()
|
|
||||||
};
|
|
||||||
tmplData.facets = _.map(tmplData.facets, function(facet) {
|
|
||||||
if (facet._type === 'date_histogram') {
|
|
||||||
facet.entries = _.map(facet.entries, function(entry) {
|
|
||||||
entry.term = new Date(entry.time).toDateString();
|
|
||||||
return entry;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return facet;
|
|
||||||
});
|
|
||||||
var templated = Mustache.render(this.template, tmplData);
|
|
||||||
this.el.html(templated);
|
|
||||||
// are there actually any facets to show?
|
|
||||||
if (this.model.facets.length > 0) {
|
|
||||||
this.el.show();
|
|
||||||
} else {
|
|
||||||
this.el.hide();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onHide: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.el.hide();
|
|
||||||
},
|
|
||||||
onFacetFilter: function(e) {
|
|
||||||
var $target= $(e.target);
|
|
||||||
var fieldId = $target.closest('.facet-summary').attr('data-facet');
|
|
||||||
var value = $target.attr('data-value');
|
|
||||||
this.model.queryState.addTermFilter(fieldId, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
})(jQuery, recline.View);
|
})(jQuery, recline.View);
|
||||||
|
|
||||||
|
|||||||
109
src/widget.filtereditor.js
Normal file
109
src/widget.filtereditor.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*jshint multistr:true */
|
||||||
|
|
||||||
|
this.recline = this.recline || {};
|
||||||
|
this.recline.View = this.recline.View || {};
|
||||||
|
|
||||||
|
(function($, my) {
|
||||||
|
|
||||||
|
my.FilterEditor = Backbone.View.extend({
|
||||||
|
className: 'recline-filter-editor well',
|
||||||
|
template: ' \
|
||||||
|
<a class="close js-hide" href="#">×</a> \
|
||||||
|
<div class="row filters"> \
|
||||||
|
<div class="span1"> \
|
||||||
|
<h3>Filters</h3> \
|
||||||
|
</div> \
|
||||||
|
<div class="span11"> \
|
||||||
|
<form class="form-horizontal"> \
|
||||||
|
<div class="row"> \
|
||||||
|
<div class="span6"> \
|
||||||
|
{{#termFilters}} \
|
||||||
|
<div class="control-group filter-term filter" data-filter-id={{id}}> \
|
||||||
|
<label class="control-label" for="">{{label}}</label> \
|
||||||
|
<div class="controls"> \
|
||||||
|
<div class="input-append"> \
|
||||||
|
<input type="text" value="{{value}}" name="{{fieldId}}" class="span4" data-filter-field="{{fieldId}}" data-filter-id="{{id}}" data-filter-type="term" /> \
|
||||||
|
<a class="btn js-remove-filter"><i class="icon-remove"></i></a> \
|
||||||
|
</div> \
|
||||||
|
</div> \
|
||||||
|
</div> \
|
||||||
|
{{/termFilters}} \
|
||||||
|
</div> \
|
||||||
|
<div class="span4"> \
|
||||||
|
<p>To add a filter use the column menu in the grid view.</p> \
|
||||||
|
<button type="submit" class="btn">Update</button> \
|
||||||
|
</div> \
|
||||||
|
</form> \
|
||||||
|
</div> \
|
||||||
|
</div> \
|
||||||
|
',
|
||||||
|
events: {
|
||||||
|
'click .js-hide': 'onHide',
|
||||||
|
'click .js-remove-filter': 'onRemoveFilter',
|
||||||
|
'submit form': 'onTermFiltersUpdate'
|
||||||
|
},
|
||||||
|
initialize: function() {
|
||||||
|
this.el = $(this.el);
|
||||||
|
_.bindAll(this, 'render');
|
||||||
|
this.model.bind('change', this.render);
|
||||||
|
this.model.bind('change:filters:new-blank', this.render);
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
var tmplData = $.extend(true, {}, this.model.toJSON());
|
||||||
|
// we will use idx in list as there id ...
|
||||||
|
tmplData.filters = _.map(tmplData.filters, function(filter, idx) {
|
||||||
|
filter.id = idx;
|
||||||
|
return filter;
|
||||||
|
});
|
||||||
|
tmplData.termFilters = _.filter(tmplData.filters, function(filter) {
|
||||||
|
return filter.term !== undefined;
|
||||||
|
});
|
||||||
|
tmplData.termFilters = _.map(tmplData.termFilters, function(filter) {
|
||||||
|
var fieldId = _.keys(filter.term)[0];
|
||||||
|
return {
|
||||||
|
id: filter.id,
|
||||||
|
fieldId: fieldId,
|
||||||
|
label: fieldId,
|
||||||
|
value: filter.term[fieldId]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
var out = Mustache.render(this.template, tmplData);
|
||||||
|
this.el.html(out);
|
||||||
|
// are there actually any facets to show?
|
||||||
|
if (this.model.get('filters').length > 0) {
|
||||||
|
this.el.show();
|
||||||
|
} else {
|
||||||
|
this.el.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onHide: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.el.hide();
|
||||||
|
},
|
||||||
|
onRemoveFilter: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var $target = $(e.target);
|
||||||
|
var filterId = $target.closest('.filter').attr('data-filter-id');
|
||||||
|
this.model.removeFilter(filterId);
|
||||||
|
},
|
||||||
|
onTermFiltersUpdate: function(e) {
|
||||||
|
var self = this;
|
||||||
|
e.preventDefault();
|
||||||
|
var filters = self.model.get('filters');
|
||||||
|
var $form = $(e.target);
|
||||||
|
_.each($form.find('input'), function(input) {
|
||||||
|
var $input = $(input);
|
||||||
|
var filterIndex = parseInt($input.attr('data-filter-id'));
|
||||||
|
var value = $input.val();
|
||||||
|
var fieldId = $input.attr('data-filter-field');
|
||||||
|
filters[filterIndex].term[fieldId] = value;
|
||||||
|
});
|
||||||
|
self.model.set({filters: filters});
|
||||||
|
self.model.trigger('change');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
})(jQuery, recline.View);
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
<script type="text/javascript" src="../src/view-map.js"></script>
|
<script type="text/javascript" src="../src/view-map.js"></script>
|
||||||
<script type="text/javascript" src="../src/view-timeline.js"></script>
|
<script type="text/javascript" src="../src/view-timeline.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.queryeditor.js"></script>
|
<script type="text/javascript" src="../src/widget.queryeditor.js"></script>
|
||||||
|
<script type="text/javascript" src="../src/widget.filtereditor.js"></script>
|
||||||
<script type="text/javascript" src="../src/widget.facetviewer.js"></script>
|
<script type="text/javascript" src="../src/widget.facetviewer.js"></script>
|
||||||
<script type="text/javascript" src="../src/view.multiview.js"></script>
|
<script type="text/javascript" src="../src/view.multiview.js"></script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user