mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-11-04 12:22:47 +00:00 
			
		
		
		
	Merge branch 'javascript-rework' into development
This commit is contained in:
		@@ -30,46 +30,63 @@ def init_corpus_analysis(corpus_id):
 | 
			
		||||
                                   corpus_id, current_user.id, request.sid)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('corpus_analysis_get_meta_data')
 | 
			
		||||
@socketio.on('corpus_analysis_meta_data')
 | 
			
		||||
@socketio_login_required
 | 
			
		||||
def corpus_analysis_get_meta_data(corpus_id):
 | 
			
		||||
    # get meta data from db
 | 
			
		||||
    db_corpus = Corpus.query.get(corpus_id)
 | 
			
		||||
    # TODO: Check if current user is actually the creator of the corpus?
 | 
			
		||||
    metadata = {}
 | 
			
		||||
    metadata['corpus_name'] = db_corpus.title
 | 
			
		||||
    metadata['corpus_description'] = db_corpus.description
 | 
			
		||||
    metadata['corpus_creation_date'] = db_corpus.creation_date.isoformat()
 | 
			
		||||
    metadata['corpus_last_edited_date'] = db_corpus.last_edited_date.isoformat()
 | 
			
		||||
    # get meta data from corpus in cqp server
 | 
			
		||||
    client = corpus_analysis_clients.get(request.sid)
 | 
			
		||||
    client_corpus = client.corpora.get('CORPUS')
 | 
			
		||||
    metadata['corpus_properties'] = client_corpus.attrs['properties']
 | 
			
		||||
    metadata['corpus_size_tokens'] = client_corpus.attrs['size']
 | 
			
		||||
    if client is None:
 | 
			
		||||
        response = {'code': 424, 'desc': 'No client found for this session',
 | 
			
		||||
                    'msg': 'Failed Dependency'}
 | 
			
		||||
        socketio.emit('corpus_analysis_query', response, room=request.sid)
 | 
			
		||||
        return
 | 
			
		||||
    # check if client is busy or not
 | 
			
		||||
    if client.status == 'running':
 | 
			
		||||
        client.status = 'abort'
 | 
			
		||||
        while client.status != 'ready':
 | 
			
		||||
            socketio.sleep(0.1)
 | 
			
		||||
    # get meta data from corpus in cqp server
 | 
			
		||||
    client.status = 'running'
 | 
			
		||||
    try:
 | 
			
		||||
        client_corpus = client.corpora.get('CORPUS')
 | 
			
		||||
        metadata['corpus_properties'] = client_corpus.attrs['properties']
 | 
			
		||||
        metadata['corpus_size_tokens'] = client_corpus.attrs['size']
 | 
			
		||||
 | 
			
		||||
    text_attr = client_corpus.structural_attributes.get('text')
 | 
			
		||||
    struct_attrs = client_corpus.structural_attributes.list(filters={'part_of': text_attr})
 | 
			
		||||
    text_ids = range(0, (text_attr.attrs['size']))
 | 
			
		||||
    texts_metadata = {}
 | 
			
		||||
    for text_id in text_ids:
 | 
			
		||||
        texts_metadata[text_id] = {}
 | 
			
		||||
        for struct_attr in struct_attrs:
 | 
			
		||||
            texts_metadata[text_id][struct_attr.attrs['name'][(len(text_attr.attrs['name']) + 1):]] = struct_attr.values_by_ids(list(range(struct_attr.attrs['size'])))[text_id]
 | 
			
		||||
    metadata['corpus_all_texts'] = texts_metadata
 | 
			
		||||
    metadata['corpus_analysis_date'] = datetime.utcnow().isoformat()
 | 
			
		||||
    metadata['corpus_cqi_py_protocol_version'] = client.api.version
 | 
			
		||||
    metadata['corpus_cqi_py_package_version'] = cqi.__version__
 | 
			
		||||
    metadata['corpus_cqpserver_version'] = 'CQPserver v3.4.22'  # TODO: make this dynamically
 | 
			
		||||
        text_attr = client_corpus.structural_attributes.get('text')
 | 
			
		||||
        struct_attrs = client_corpus.structural_attributes.list(filters={'part_of': text_attr})
 | 
			
		||||
        text_ids = range(0, (text_attr.attrs['size']))
 | 
			
		||||
        texts_metadata = {}
 | 
			
		||||
        for text_id in text_ids:
 | 
			
		||||
            texts_metadata[text_id] = {}
 | 
			
		||||
            for struct_attr in struct_attrs:
 | 
			
		||||
                texts_metadata[text_id][struct_attr.attrs['name'][(len(text_attr.attrs['name']) + 1):]] = struct_attr.values_by_ids(list(range(struct_attr.attrs['size'])))[text_id]
 | 
			
		||||
        metadata['corpus_all_texts'] = texts_metadata
 | 
			
		||||
        metadata['corpus_analysis_date'] = datetime.utcnow().isoformat()
 | 
			
		||||
        metadata['corpus_cqi_py_protocol_version'] = client.api.version
 | 
			
		||||
        metadata['corpus_cqi_py_package_version'] = cqi.__version__
 | 
			
		||||
        metadata['corpus_cqpserver_version'] = 'CQPserver v3.4.22'  # TODO: make this dynamically
 | 
			
		||||
 | 
			
		||||
    # write some metadata to the db
 | 
			
		||||
    db_corpus.current_nr_of_tokens = metadata['corpus_size_tokens']
 | 
			
		||||
    db.session.commit()
 | 
			
		||||
        # write some metadata to the db
 | 
			
		||||
        db_corpus.current_nr_of_tokens = metadata['corpus_size_tokens']
 | 
			
		||||
        db.session.commit()
 | 
			
		||||
 | 
			
		||||
    # emit data
 | 
			
		||||
    payload = metadata
 | 
			
		||||
    response = {'code': 200, 'desc': 'Corpus meta data', 'msg': 'OK',
 | 
			
		||||
                'payload': payload}
 | 
			
		||||
    socketio.emit('corpus_analysis_send_meta_data', response, room=request.sid)
 | 
			
		||||
        # emit data
 | 
			
		||||
        payload = metadata
 | 
			
		||||
        response = {'code': 200, 'desc': 'Corpus meta data', 'msg': 'OK',
 | 
			
		||||
                    'payload': payload}
 | 
			
		||||
        socketio.emit('corpus_analysis_meta_data', response, room=request.sid)
 | 
			
		||||
    except cqi.errors.CQiException as e:
 | 
			
		||||
        payload = {'code': e.code, 'desc': e.description, 'msg': e.name}
 | 
			
		||||
        response = {'code': 500, 'desc': None, 'msg': 'Internal Server Error',
 | 
			
		||||
                    'payload': payload}
 | 
			
		||||
        socketio.emit('corpus_analysis_meta_data', response, room=request.sid)
 | 
			
		||||
    client.status = 'ready'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('corpus_analysis_query')
 | 
			
		||||
@@ -100,7 +117,6 @@ def corpus_analysis_query(query):
 | 
			
		||||
               'match_count': results.attrs['size']}
 | 
			
		||||
    response = {'code': 200, 'desc': None, 'msg': 'OK', 'payload': payload}
 | 
			
		||||
    socketio.emit('corpus_analysis_query', response, room=request.sid)
 | 
			
		||||
    # TODO: Stop here and add a new method for transmission
 | 
			
		||||
    chunk_size = 100
 | 
			
		||||
    chunk_start = 0
 | 
			
		||||
    context = 50
 | 
			
		||||
@@ -142,6 +158,11 @@ def corpus_analysis_inspect_match(payload):
 | 
			
		||||
        socketio.emit('corpus_analysis_inspect_match', response,
 | 
			
		||||
                      room=request.sid)
 | 
			
		||||
        return
 | 
			
		||||
    if client.status == 'running':
 | 
			
		||||
        client.status = 'abort'
 | 
			
		||||
        while client.status != 'ready':
 | 
			
		||||
            socketio.sleep(0.1)
 | 
			
		||||
    client.status = 'running'
 | 
			
		||||
    try:
 | 
			
		||||
        corpus = client.corpora.get('CORPUS')
 | 
			
		||||
        s = corpus.structural_attributes.get('s')
 | 
			
		||||
@@ -171,6 +192,7 @@ def corpus_analysis_inspect_match(payload):
 | 
			
		||||
                    'type': type,
 | 
			
		||||
                    'data_indexes': data_indexes}
 | 
			
		||||
    socketio.emit('corpus_analysis_inspect_match', response, room=request.sid)
 | 
			
		||||
    client.status = 'ready'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def corpus_analysis_session_handler(app, corpus_id, user_id, session_id):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										188
									
								
								web/app/static/js/modules/corpus_analysis/client/Client.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								web/app/static/js/modules/corpus_analysis/client/Client.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to create a Client object.
 | 
			
		||||
 * The client handels the client server communication.
 | 
			
		||||
 * It communicates with the server (e.g. connection or query)
 | 
			
		||||
 * and recieves data from it, if dynamicMode is true.
 | 
			
		||||
 * If dynamicMode is false, the client can also handle data that is already
 | 
			
		||||
 * loaded and is not coming in in chunks.
 | 
			
		||||
 */
 | 
			
		||||
class Client {
 | 
			
		||||
  constructor({corpusId = null,
 | 
			
		||||
               socket = null,
 | 
			
		||||
               logging = true,
 | 
			
		||||
               dynamicMode = true} = {}) {
 | 
			
		||||
    this.corpusId = corpusId;
 | 
			
		||||
    this.dynamicMode = dynamicMode;
 | 
			
		||||
    this.logging = logging;
 | 
			
		||||
    this.requestQueryProgress = 0;
 | 
			
		||||
    this.socket = socket;
 | 
			
		||||
    this.eventListeners = {};
 | 
			
		||||
    this.connected = false;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disable all console logging.
 | 
			
		||||
     * This is global. So every other log message in every other Class or
 | 
			
		||||
     * function used in conjunction with the client either logs or does not
 | 
			
		||||
     * log depending on the logging flag.
 | 
			
		||||
     * Credits to https://gist.github.com/kmonsoor/0244fdb4ad79a4826371e58a1a5fa984
 | 
			
		||||
     */
 | 
			
		||||
    if (!logging) {
 | 
			
		||||
      (() => {
 | 
			
		||||
        let console = (window.console = window.console || {});
 | 
			
		||||
        [
 | 
			
		||||
          'assert', 'clear', 'count', 'debug', 'dir', 'dirxml',
 | 
			
		||||
          'error', 'exception', 'group', 'groupCollapsed', 'groupEnd',
 | 
			
		||||
          'info', 'log', 'markTimeline', 'profile', 'profileEnd', 'table',
 | 
			
		||||
          'time', 'timeEnd', 'timeStamp', 'trace', 'warn'
 | 
			
		||||
        ].forEach((method) => {
 | 
			
		||||
          console[method] = () => {};
 | 
			
		||||
        });
 | 
			
		||||
      })();
 | 
			
		||||
    }
 | 
			
		||||
    console.info("Client initialized:", this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Registers one or more SocketEventListeners to the Client.
 | 
			
		||||
  setSocketEventListeners(eventListeners) {
 | 
			
		||||
    for (let eventListener of eventListeners) {
 | 
			
		||||
      this.eventListeners[eventListener.type] = eventListener;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads the SocketEventListeners so they will be triggered on their assigned
 | 
			
		||||
   * type strings because they double as the socket event event names.
 | 
			
		||||
   */
 | 
			
		||||
  loadSocketEventListeners() {
 | 
			
		||||
    for (let [type, listener] of Object.entries(this.eventListeners)) {
 | 
			
		||||
      listener.listenerFunction(type, this);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * This functions sends events to the View to trigger specific functions that
 | 
			
		||||
   * are handleing the representation of data stored in the model.
 | 
			
		||||
   */
 | 
			
		||||
  notifyView(caseIdentifier, detailObject={}) {
 | 
			
		||||
    detailObject.caseIdentifier = caseIdentifier;
 | 
			
		||||
    const event = new CustomEvent('notify-view', { detail: detailObject });
 | 
			
		||||
    console.info('Client dispatching Notification:', caseIdentifier);
 | 
			
		||||
    document.dispatchEvent(event);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Registers a CorpusAnalysisDisplay object to the Client.
 | 
			
		||||
  setDisplay(type, corpusAnalysisDisplay) {
 | 
			
		||||
    this.displays[type] = corpusAnalysisDisplay;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Connects to the corpus analysis session for the specified corpus via
 | 
			
		||||
   * socket.io.
 | 
			
		||||
   */
 | 
			
		||||
  connect() {
 | 
			
		||||
    console.info('corpus_analysis_init: Client connecting to session via',
 | 
			
		||||
                 'socket.emit');
 | 
			
		||||
    this.socket.emit('corpus_analysis_init', this.corpusId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getMetaData() {
 | 
			
		||||
    console.info('corpus_analysis_meta_data: Client getting meta data via',
 | 
			
		||||
                 'socket.emit.');
 | 
			
		||||
    this.socket.emit('corpus_analysis_meta_data', this.corpusId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Emits query to the server via socket.io. Server will send the results
 | 
			
		||||
   * back.
 | 
			
		||||
   */
 | 
			
		||||
  query(queryStr) {
 | 
			
		||||
    console.info('corpus_analysis_query: Client sending query via',
 | 
			
		||||
                 'socket.emit for the query', queryStr);
 | 
			
		||||
    this.socket.emit('corpus_analysis_query', queryStr);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Create results data either from all results or from all marked sub results
 | 
			
		||||
   * Triggers emit to get full match context from server for a number of
 | 
			
		||||
   * matches identified by their data_index.
 | 
			
		||||
   **/
 | 
			
		||||
  getResultsData(resultsType, dataIndexes, results) {
 | 
			
		||||
    let tmp_first_cpos = [];
 | 
			
		||||
    let tmp_last_cpos = [];
 | 
			
		||||
    for (let dataIndex of dataIndexes) {
 | 
			
		||||
      tmp_first_cpos.push(results.data.matches[dataIndex].c[0]);
 | 
			
		||||
      tmp_last_cpos.push(results.data.matches[dataIndex].c[1]);
 | 
			
		||||
    }
 | 
			
		||||
    nopaque.socket.emit("corpus_analysis_inspect_match",
 | 
			
		||||
                        {type: resultsType,
 | 
			
		||||
                         data_indexes: dataIndexes,
 | 
			
		||||
                         first_cpos: tmp_first_cpos,
 | 
			
		||||
                         last_cpos: tmp_last_cpos,});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to create an SocketEventListener.
 | 
			
		||||
 * Input are an identifying type string, the listener function and callbacks
 | 
			
		||||
 * which will be executed as part of the listener function. The identifying
 | 
			
		||||
 * type string is also used as the socket event event identifier.
 | 
			
		||||
 */
 | 
			
		||||
class ClientEventListener {
 | 
			
		||||
  constructor(type, listenerFunction) {
 | 
			
		||||
    this.listenerCallbacks = {};
 | 
			
		||||
    this.listenerFunction = listenerFunction;
 | 
			
		||||
    this.type = type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Registers callbacks to this SocketEventListener
 | 
			
		||||
  setCallbacks(listenerCallbacks) {
 | 
			
		||||
    for (let listenerCallback of listenerCallbacks) {
 | 
			
		||||
      this.listenerCallbacks[listenerCallback.type] = listenerCallback;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Shorthand to execute all registered callbacks with same defaultArgs
 | 
			
		||||
   * in insertion order.
 | 
			
		||||
   * NOTE:
 | 
			
		||||
   * Since ECMAScript 2015, objects do preserve creation order for
 | 
			
		||||
   * string and Symbol keys. In JavaScript engines that comply with the
 | 
			
		||||
   * ECMAScript 2015 spec, iterating over an object with only string keys will
 | 
			
		||||
   * yield the keys in order of insertion.
 | 
			
		||||
   * So all modern Browsers.
 | 
			
		||||
   */
 | 
			
		||||
  executeCallbacks(defaultArgs) {
 | 
			
		||||
    for (let [type, listenerCallback] of Object.entries(this.listenerCallbacks)) {
 | 
			
		||||
      listenerCallback.callbackFunction(...defaultArgs,
 | 
			
		||||
                                        ...listenerCallback.args);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // use this if you only want to execute a specific registered callback
 | 
			
		||||
  executeCallback(defaultArgs, type) {
 | 
			
		||||
    let listenerCallback = this.listenerCallbacks[type];
 | 
			
		||||
    listenerCallback.callbackFunction(...defaultArgs,
 | 
			
		||||
                                      ...listenerCallback.args);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to create an ListenerCallback which will be registered
 | 
			
		||||
 * to an SocketEventListener so the Listener can invoke the ListenerCallback
 | 
			
		||||
 * callback functions.
 | 
			
		||||
 */
 | 
			
		||||
class ListenerCallback {
 | 
			
		||||
  constructor(type, callbackFunction, argsList) {
 | 
			
		||||
    this.args = argsList;
 | 
			
		||||
    this.callbackFunction = callbackFunction;
 | 
			
		||||
    this.type = type;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// export Classes from this module
 | 
			
		||||
export {
 | 
			
		||||
  Client,
 | 
			
		||||
  ClientEventListener,
 | 
			
		||||
  ListenerCallback,
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,90 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This callback is called on a socket.on "corpus_analysis_send_meta_data".
 | 
			
		||||
 * Handels incoming corpus metadata
 | 
			
		||||
 */
 | 
			
		||||
function saveMetaData() {
 | 
			
		||||
  let [payload, client, results, rest] = arguments;
 | 
			
		||||
  results.metaData.init(payload)
 | 
			
		||||
  console.info('Metada saved:', results);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This callback should be registered to the SocketEventListener 'recieveQueryStatus'.
 | 
			
		||||
 * It just gets the incoming status data of the issued query
 | 
			
		||||
 * and does some preperation work like hiding or showing elements and deleting
 | 
			
		||||
 * the data from the last query.
 | 
			
		||||
 */
 | 
			
		||||
function prepareQueryData() {
 | 
			
		||||
  // deletes old data from query issued before this new query
 | 
			
		||||
  let [payload, client, results, rest] = arguments;
 | 
			
		||||
  // always initialize the results to delete data from the query issued before
 | 
			
		||||
  results.init();
 | 
			
		||||
  results.data.match_count = payload.match_count;
 | 
			
		||||
  client.requestQueryProgress = 0;
 | 
			
		||||
  client.notifyView('query-data-prepareing', { results: results });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function saveQueryData(args) {
 | 
			
		||||
  let [payload, client, results, rest] = arguments;
 | 
			
		||||
  // get data matches length before new chun kdata is being inserted
 | 
			
		||||
  let dataLength = results.data.matches.length;
 | 
			
		||||
  // incorporating new chunk data into full results
 | 
			
		||||
  results.data.matches.push(...payload.chunk.matches);
 | 
			
		||||
  results.data.addData(payload.chunk.cpos_lookup, 'cpos_lookup');
 | 
			
		||||
  results.data.addData(payload.chunk.text_lookup, 'text_lookup');
 | 
			
		||||
  results.data.cpos_ranges = payload.chunk.cpos_ranges;
 | 
			
		||||
  let queryFormElement = document.querySelector('#query-form');
 | 
			
		||||
  results.data.getQueryStr(queryFormElement);
 | 
			
		||||
  client.requestQueryProgress = payload.progress;
 | 
			
		||||
  client.notifyView('query-data-recieving',
 | 
			
		||||
                    { results: results,
 | 
			
		||||
                      client: client,
 | 
			
		||||
                      dataLength: dataLength });
 | 
			
		||||
  console.info('Query data chunk saved', results.data);
 | 
			
		||||
  if (client.requestQueryProgress === 100) {
 | 
			
		||||
    client.notifyView('query-data-recieved');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getResultsData(args) {
 | 
			
		||||
  let [resultsType, dataIndexes, client, results, rest] = arguments;
 | 
			
		||||
  client.notifyView('results-data-recieving');
 | 
			
		||||
  client.getResultsData(resultsType, dataIndexes, results);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function saveResultsData(args) {
 | 
			
		||||
  let [payload, type, client, results, rest] = arguments;
 | 
			
		||||
  let objectKey = '';
 | 
			
		||||
  if (type === 'full-results') {
 | 
			
		||||
    console.info('Saving full-results data.');
 | 
			
		||||
    objectKey = 'fullResultsData';
 | 
			
		||||
  } else if (type === 'sub-results') {
 | 
			
		||||
    console.info('Saving sub-results data.');
 | 
			
		||||
    objectKey = 'subResultsData';
 | 
			
		||||
  } else if (type = 'inspect-results') {
 | 
			
		||||
    objectKey = 'inspectResultsData'
 | 
			
		||||
    console.info('Saving inspect-results data');
 | 
			
		||||
  }
 | 
			
		||||
  // Save incoming data
 | 
			
		||||
  results[objectKey].init();
 | 
			
		||||
  results[objectKey].matches.push(...payload.matches);
 | 
			
		||||
  results[objectKey].addData(payload.cpos_lookup, "cpos_lookup");
 | 
			
		||||
  results[objectKey].addData(payload.text_lookup, "text_lookup");
 | 
			
		||||
  results[objectKey].addData(results.metaData);
 | 
			
		||||
  results[objectKey].query = results.data.query;
 | 
			
		||||
  results[objectKey].corpus_type = type;
 | 
			
		||||
  results[objectKey].match_count = [...payload.matches].length;
 | 
			
		||||
  results[objectKey].cpos_ranges = payload.cpos_ranges;
 | 
			
		||||
  console.info('Results data has been saved.', results);
 | 
			
		||||
  client.notifyView('results-data-recieved', {type: type,
 | 
			
		||||
                                              results: results});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// export callbacks
 | 
			
		||||
export {
 | 
			
		||||
  prepareQueryData,
 | 
			
		||||
  saveMetaData,
 | 
			
		||||
  saveQueryData,
 | 
			
		||||
  getResultsData,
 | 
			
		||||
  saveResultsData,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										202
									
								
								web/app/static/js/modules/corpus_analysis/client/listeners.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								web/app/static/js/modules/corpus_analysis/client/listeners.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,202 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This file contains the listener functions which can be assigned to the
 | 
			
		||||
 * coprus_analysis client. So that the incoming data/status informations will
 | 
			
		||||
 * be handled. There are several listeners listening for socket .io events.
 | 
			
		||||
 * Further below one javascript custom event listener is specified. This
 | 
			
		||||
 * listener listens for javascript custom events which are being dispatched by
 | 
			
		||||
 * the View (resultsList).
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// Listeners for socket io events
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Recieves a corpus analysis connected signal via socket.io.
 | 
			
		||||
 */
 | 
			
		||||
function recieveConnected(type, client) {
 | 
			
		||||
  client.socket.on(type, (response) => {
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if request for session was OK.
 | 
			
		||||
     * If OK execute registered callbacks and notify View.
 | 
			
		||||
     */
 | 
			
		||||
    if (response.code === 200) {
 | 
			
		||||
      console.group('Connected!')
 | 
			
		||||
      console.info('corpus_analysis_init: Client recieving connected codes',
 | 
			
		||||
                   'codes via socket.on');
 | 
			
		||||
      console.info(`corpus_analysis_init: ${response.code} - ${response.msg}`);
 | 
			
		||||
      console.info('corpus_analysis_init: Initialization succeeded');
 | 
			
		||||
      console.info(response);
 | 
			
		||||
      client.notifyView('connected');
 | 
			
		||||
      console.groupEnd();
 | 
			
		||||
      // get meta data immediately
 | 
			
		||||
      client.getMetaData();
 | 
			
		||||
    } else {
 | 
			
		||||
      let errorText = `Error ${response.code} - ${response.msg}`;
 | 
			
		||||
      console.group('Connection failed!')
 | 
			
		||||
      console.error(`corpus_analysis_init: ${errorText}`);
 | 
			
		||||
      client.notifyView('connecting-failed', { msg: errorText });
 | 
			
		||||
      console.groupEnd();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Recieves meta data from the server via socket.io.
 | 
			
		||||
 */
 | 
			
		||||
function recieveMetaData(type, client) {
 | 
			
		||||
  client.socket.on(type, (response) => {
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if request for session was OK.
 | 
			
		||||
     * If OK execute registered callbacks and notify View.
 | 
			
		||||
     */
 | 
			
		||||
    if (response.code === 200) {
 | 
			
		||||
      console.group('Client recieving meta data')
 | 
			
		||||
      console.info('corpus_analysis_meta_data: Client recieving meta data',
 | 
			
		||||
      'via socket.on');
 | 
			
		||||
      console.info(`corpus_analysis_meta_data: ${response.code} - ${response.msg}`);
 | 
			
		||||
      console.info(response);
 | 
			
		||||
      // executing the registered callbacks
 | 
			
		||||
      client.eventListeners[type].executeCallbacks([response.payload]);
 | 
			
		||||
      console.groupEnd();
 | 
			
		||||
    } else {
 | 
			
		||||
      console.group('Failed to recieve meta data.');
 | 
			
		||||
      console.error('corpus_analysis_meta_data: Client failed to recieve',
 | 
			
		||||
                    'meta data via socket.on');
 | 
			
		||||
      let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
 | 
			
		||||
      console.error(`corpus_analysis_meta_data: ${errorText}`);
 | 
			
		||||
      console.groupEnd();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Recieves the query process status before any actual results are being
 | 
			
		||||
 * transmitted. So it recieves error codes if a query failed or
 | 
			
		||||
 * was invalid etc.
 | 
			
		||||
 * Also prepares the result.jsList for the incoming data.
 | 
			
		||||
 */
 | 
			
		||||
function recieveQueryStatus(type, client) {
 | 
			
		||||
  client.socket.on(type, (response) => {
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if request for session was OK.
 | 
			
		||||
     * If OK execute registered callbacks and notify View.
 | 
			
		||||
     */
 | 
			
		||||
    if (response.code === 200) {
 | 
			
		||||
      console.group('corpus_analysis_query: Client recieving query process',
 | 
			
		||||
      'status via socket.on');
 | 
			
		||||
      console.info(`corpus_analysis_query: ${response.code} - ${response.msg}`);
 | 
			
		||||
      console.info(response);
 | 
			
		||||
      // executing the registered callbacks
 | 
			
		||||
      client.eventListeners[type].executeCallbacks([response.payload]);
 | 
			
		||||
      console.groupEnd();
 | 
			
		||||
    } else {
 | 
			
		||||
      console.group('corpus_analysis_query: Client failed recieving',
 | 
			
		||||
                    'query process status via socket.on');
 | 
			
		||||
      let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
 | 
			
		||||
      if (response.payload.code == 1281) {
 | 
			
		||||
        errorText += ' - Invalid Query';
 | 
			
		||||
      }
 | 
			
		||||
      console.error(`corpus_analysis_query: ${errorText}`);
 | 
			
		||||
      console.groupEnd();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Recieves the query data from the request and handles it.
 | 
			
		||||
 */
 | 
			
		||||
function recieveQueryData(type, client) {
 | 
			
		||||
  /**
 | 
			
		||||
   * Check if request for session was OK.
 | 
			
		||||
   * If OK execute registered callbacks and notify View.
 | 
			
		||||
   */
 | 
			
		||||
  if (client.dynamicMode) {
 | 
			
		||||
    client.socket.on(type, (response) => {
 | 
			
		||||
    if (response.code === 200) {
 | 
			
		||||
      console.group('corpus_analysis_query_results: Recieveing query data')
 | 
			
		||||
      console.info('Client recieving query data via socket.on');
 | 
			
		||||
      console.info('Recieved chunk', response);
 | 
			
		||||
      /**
 | 
			
		||||
       * Execute registered callbacks and notify View.
 | 
			
		||||
       */
 | 
			
		||||
      client.eventListeners[type].executeCallbacks([response.payload]);
 | 
			
		||||
      console.info('Added chunk data to results.data.');
 | 
			
		||||
      console.groupEnd();
 | 
			
		||||
    } else {
 | 
			
		||||
      console.group('corpus_analysis_query_results: Client failed recieving',
 | 
			
		||||
                    'the results via socket.on');
 | 
			
		||||
      let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
 | 
			
		||||
      console.error(`corpus_analysis_query: ${errorText}`);
 | 
			
		||||
      console.groupEnd();
 | 
			
		||||
    }
 | 
			
		||||
    });
 | 
			
		||||
  } else {
 | 
			
		||||
    console.group('corpus_analysis_query_results: Loading query data.');
 | 
			
		||||
    console.info('Client loading imported query data from database.');
 | 
			
		||||
    // executing the registered callbacks
 | 
			
		||||
    client.eventListeners[type].executeCallbacks();
 | 
			
		||||
    console.groupEnd();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Recieves the data requested by the create Results or sub results button
 | 
			
		||||
 */
 | 
			
		||||
function recieveResultsData(type, client) {
 | 
			
		||||
  client.socket.on(type, (response) => {
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if request for session was OK.
 | 
			
		||||
     * If OK execute registered callbacks and notify View.
 | 
			
		||||
     */
 | 
			
		||||
    if (response.code === 200) {
 | 
			
		||||
      console.group('Client recieving results data')
 | 
			
		||||
      console.info('corpus_analysis_inspect_match: Client recieving results data',
 | 
			
		||||
      'via socket.on');
 | 
			
		||||
      console.info(`corpus_analysis_inspect_match: ${response.code} - ${response.msg}`);
 | 
			
		||||
      console.info(response);
 | 
			
		||||
      // executing the registered callbacks
 | 
			
		||||
      client.eventListeners[type].executeCallbacks([response.payload,
 | 
			
		||||
                                                    response.type]);
 | 
			
		||||
      console.groupEnd();
 | 
			
		||||
    } else {
 | 
			
		||||
      console.group('Failed to recieve results data.');
 | 
			
		||||
      console.error('corpus_analysis_inspect_match: Client failed to recieve',
 | 
			
		||||
                    'results data via socket.on');
 | 
			
		||||
      let errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
 | 
			
		||||
      console.error(`corpus_analysis_inspect_match: ${errorText}`);
 | 
			
		||||
      console.groupEnd();
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * This is the javascript custom event listener, listening for events
 | 
			
		||||
 * dispatched by the View.
 | 
			
		||||
 */
 | 
			
		||||
function recieveViewNotification(type, client) {
 | 
			
		||||
  document.addEventListener(type, (event) => {
 | 
			
		||||
    let caseIdentifier = event.detail.caseIdentifier;
 | 
			
		||||
    switch(caseIdentifier) {
 | 
			
		||||
      case 'get-results':
 | 
			
		||||
        console.info('Client getting full results for export.');
 | 
			
		||||
        // execute callback or functions
 | 
			
		||||
        client.eventListeners[type].executeCallback([event.detail.resultsType,
 | 
			
		||||
                                                     event.detail.dataIndexes],
 | 
			
		||||
                                                     caseIdentifier);
 | 
			
		||||
        break
 | 
			
		||||
        default:
 | 
			
		||||
          console.error('Recieved unkown notification case identifier from View');
 | 
			
		||||
          // do something to not crash the analysis session?
 | 
			
		||||
          // maybe unnecessary
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// export listeners from this module
 | 
			
		||||
export {
 | 
			
		||||
  recieveConnected,
 | 
			
		||||
  recieveMetaData,
 | 
			
		||||
  recieveQueryStatus,
 | 
			
		||||
  recieveQueryData,
 | 
			
		||||
  recieveViewNotification,
 | 
			
		||||
  recieveResultsData,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										104
									
								
								web/app/static/js/modules/corpus_analysis/model/Results.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								web/app/static/js/modules/corpus_analysis/model/Results.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
/**
 | 
			
		||||
 * These classes are implementing the data store of the corpus_analysis
 | 
			
		||||
 * package. If we follow the idea of the Model View Controller Pattern these
 | 
			
		||||
 * classes combined in the Results class define the Model.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class Results {
 | 
			
		||||
  constructor() {
 | 
			
		||||
  this.data = new Data();
 | 
			
		||||
  this.metaData = new MetaData();
 | 
			
		||||
  this.fullResultsData = new Data();
 | 
			
		||||
  this.subResultsData = new Data();
 | 
			
		||||
  this.inspectResultsData = new Data();
 | 
			
		||||
  console.info('Initialized the Results object.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init() {
 | 
			
		||||
    this.data.init();
 | 
			
		||||
    this.metaData.init();
 | 
			
		||||
    this.fullResultsData.init();
 | 
			
		||||
    this.subResultsData.init();
 | 
			
		||||
    this.inspectResultsData.init();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Data {
 | 
			
		||||
  // Sets empty object structure. Also usefull to delete old results.
 | 
			
		||||
  // matchCount default is 0
 | 
			
		||||
  init(matchCount=0, type="results") {
 | 
			
		||||
    this.matches = [];  // list of all c with lc and rc
 | 
			
		||||
    this.cpos_lookup = {};  // object contains all this key value pair
 | 
			
		||||
    this.text_lookup = {};  // same as above for all text ids
 | 
			
		||||
    this.match_count = matchCount;
 | 
			
		||||
    this.corpus_type = 'results';
 | 
			
		||||
    this.cpos_ranges = null;
 | 
			
		||||
    this.query = '';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addData(jsonData, key=null) {
 | 
			
		||||
    if (key !== null) {
 | 
			
		||||
      Object.assign(this[key], jsonData);
 | 
			
		||||
    } else if (key === null) {
 | 
			
		||||
      Object.assign(this, jsonData)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // get query as string from form Element
 | 
			
		||||
  getQueryStr(queryFormElement) {
 | 
			
		||||
    // gets query
 | 
			
		||||
    let queryFormData;
 | 
			
		||||
    let queryStr;
 | 
			
		||||
    queryFormData = new FormData(queryFormElement);
 | 
			
		||||
    queryStr = queryFormData.get('query-form-query');
 | 
			
		||||
    this["query"] = queryStr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // function creates a unique and safe filename for the download
 | 
			
		||||
  createDownloadFilename(suffix) {
 | 
			
		||||
    let today = new Date();
 | 
			
		||||
    let currentDate = `${today.getUTCFullYear()}` +
 | 
			
		||||
                      `-${(today.getUTCMonth() + 1)}` +
 | 
			
		||||
                      `-${today.getUTCDate()}`;
 | 
			
		||||
    let currentTime = `${today.getUTCHours()}h` +
 | 
			
		||||
                      `${today.getUTCMinutes()}m` +
 | 
			
		||||
                      `${today.getUTCSeconds()}s`;
 | 
			
		||||
    let safeFilename = this.query.replace(/[^a-z0-9_-]/gi, "_");
 | 
			
		||||
    let resultFilename = `UTC-${currentDate}_${currentTime}_${safeFilename}_${suffix}`;
 | 
			
		||||
    return resultFilename
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * Function to download data as Blob created from string.
 | 
			
		||||
   * Should be private but that is not yet a feature of javascript 08.04.2020
 | 
			
		||||
   */
 | 
			
		||||
  download(downloadElement, dataStr, filename, type, filenameSlug) {
 | 
			
		||||
    filename += filenameSlug;
 | 
			
		||||
    let file = new Blob([dataStr], {type: type});
 | 
			
		||||
        var url = URL.createObjectURL(file);
 | 
			
		||||
        downloadElement.href = url;
 | 
			
		||||
        downloadElement.download = filename;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // function to download the results as JSON
 | 
			
		||||
  downloadJSONRessource(resultFilename, downloadData, downloadElement) {
 | 
			
		||||
    /**
 | 
			
		||||
     * Stringify JSON object for json download.
 | 
			
		||||
     * Use tabs to save some space.
 | 
			
		||||
     */
 | 
			
		||||
    let dataStr = JSON.stringify(downloadData, undefined, "\t");
 | 
			
		||||
    // Start actual download
 | 
			
		||||
    this.download(downloadElement, dataStr, resultFilename, "text/json", ".json")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MetaData {
 | 
			
		||||
  // Sets empty object structure when no input is given.
 | 
			
		||||
  // if json object like input is given class fields are created from this
 | 
			
		||||
  init(json={}) {
 | 
			
		||||
    Object.assign(this, json);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {Results, Data, MetaData};
 | 
			
		||||
							
								
								
									
										832
									
								
								web/app/static/js/modules/corpus_analysis/view/ResultsView.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										832
									
								
								web/app/static/js/modules/corpus_analysis/view/ResultsView.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,832 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This class implements a NotificationListener that is listening for the
 | 
			
		||||
 * specified
 | 
			
		||||
 */
 | 
			
		||||
class ViewEventListener {
 | 
			
		||||
  constructor(type, listenerFunction) {
 | 
			
		||||
    this.listenerFunction = listenerFunction;
 | 
			
		||||
    this.type = type;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is implements a View which handles the reprensentation of the
 | 
			
		||||
 * data that has been fetched by the Client of the corpus_analysis. This view
 | 
			
		||||
 * only handles how the data is shown to the user. View extends the list.js
 | 
			
		||||
 * List class.
 | 
			
		||||
 */
 | 
			
		||||
class ResultsList extends List {
 | 
			
		||||
  /**
 | 
			
		||||
   * If no options are given when a new instance of this class is created
 | 
			
		||||
   * the options below are used.
 | 
			
		||||
   */
 | 
			
		||||
  static options = {
 | 
			
		||||
    page: 30,
 | 
			
		||||
    pagination: [{
 | 
			
		||||
      name: "paginationTop",
 | 
			
		||||
      paginationClass: "paginationTop",
 | 
			
		||||
      innerWindow: 8,
 | 
			
		||||
      outerWindow: 1
 | 
			
		||||
    }, {
 | 
			
		||||
      paginationClass: "paginationBottom",
 | 
			
		||||
      innerWindow: 8,
 | 
			
		||||
      outerWindow: 1
 | 
			
		||||
    }],
 | 
			
		||||
    valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}],
 | 
			
		||||
    item: `<span></span>`
 | 
			
		||||
  };
 | 
			
		||||
  constructor(idOrElement, options) {
 | 
			
		||||
    super(idOrElement, options);
 | 
			
		||||
    /**
 | 
			
		||||
     * All span tokens which are holding events if expert
 | 
			
		||||
     * mode is on. Collected here to delete later on.
 | 
			
		||||
     */
 | 
			
		||||
    this.eventTokens = {};
 | 
			
		||||
    /**
 | 
			
		||||
    * all token elements which have added
 | 
			
		||||
    * classes like chip and hoverable for expert view. Collected
 | 
			
		||||
    * here to delete later on
 | 
			
		||||
    */
 | 
			
		||||
    this.currentExpertTokenElements = {};
 | 
			
		||||
    // TODO: Rename both variables to something more descreptive and clear
 | 
			
		||||
     // holds True/false for check buttons used to add matches tu sub-results. If checked, it is True. If unchecked, it is false. Buttons for this have the class add. Those little round check buttons.
 | 
			
		||||
    this.addToSubResultsStatus = {};
 | 
			
		||||
    this.addToSubResultsIdsToShow = new Set();  // If check button is pressed its corresponding data_index is saved in this set. The set is shown to the user.
 | 
			
		||||
    // notification listeners listening for client notifications (or other in the future?)
 | 
			
		||||
    this.notificationListeners = {};
 | 
			
		||||
    this.clientIsBusy = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * // TODO:
 | 
			
		||||
   * Init function that gets all needed HTML Elements. Implement this, or not?
 | 
			
		||||
   * Or build a check into the get HTMLElements function if element already exists.
 | 
			
		||||
   * Also think about saving alle elements in resultsList.es.nameOfElement
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Function to clear/reset some class field values. Usefull if a new query
 | 
			
		||||
   * hase been issued by the user.
 | 
			
		||||
   */
 | 
			
		||||
  resetFields() {
 | 
			
		||||
    this.addToSubResultsIdsToShow = new Set();
 | 
			
		||||
    this.addToSubResultsStatus = {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Function that takes one or more query selector
 | 
			
		||||
   * strings in an array as an input. The function then creates a
 | 
			
		||||
   * class field in the ResultsList object with the query selector
 | 
			
		||||
   * string as the key. The selector will be converted to a valid JavaScript
 | 
			
		||||
   * Field name i. e. #html-id-string -> this.htmlIdString
 | 
			
		||||
   * The value will be the identifed element or elements fetched with the querySelector
 | 
			
		||||
   * method.
 | 
			
		||||
   */
 | 
			
		||||
  getHTMLElements(arrayOfSelectors) {
 | 
			
		||||
    for (let selector of arrayOfSelectors) {
 | 
			
		||||
      let element;
 | 
			
		||||
      let elements;
 | 
			
		||||
      if (selector.startsWith('#')) {
 | 
			
		||||
        element = document.querySelector(selector);
 | 
			
		||||
      } else {
 | 
			
		||||
        elements = document.querySelectorAll(selector);
 | 
			
		||||
        elements = [...elements];
 | 
			
		||||
      }
 | 
			
		||||
      let cleanKey = [];
 | 
			
		||||
      selector = selector.replace(/_/g, '-');
 | 
			
		||||
      selector.match(/\w+/g).forEach((word) => {
 | 
			
		||||
        let tmp = word[0].toUpperCase() + word.slice(1);
 | 
			
		||||
        cleanKey.push(tmp);
 | 
			
		||||
      });
 | 
			
		||||
      cleanKey[0] = cleanKey[0].toLowerCase();
 | 
			
		||||
      cleanKey = cleanKey.join('');
 | 
			
		||||
      this[cleanKey] = element ? element: elements;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Register notificationListeners to the ResultsList. Which will listen for
 | 
			
		||||
   * the specified event.
 | 
			
		||||
   */
 | 
			
		||||
  setNotificationListeners(notificationListeners) {
 | 
			
		||||
    for (let notificationListener of notificationListeners) {
 | 
			
		||||
      this.notificationListeners[notificationListener.type] = notificationListener;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Loads the notificationListeners so that hey will be listening to their
 | 
			
		||||
   * assigned custom events.
 | 
			
		||||
   */
 | 
			
		||||
  loadNotificationListeners() {
 | 
			
		||||
    for (let [type, listener] of Object.entries(this.notificationListeners)) {
 | 
			
		||||
      listener.listenerFunction(type, this);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * This functions sends events to the Client to trigger specific functions to
 | 
			
		||||
   * trigger new data requests from the server.
 | 
			
		||||
   */
 | 
			
		||||
  notifyClient(caseIdentifier, detailObject={}) {
 | 
			
		||||
    detailObject.caseIdentifier = caseIdentifier;
 | 
			
		||||
    const event = new CustomEvent('notify-client', { detail: detailObject });
 | 
			
		||||
    console.info('Client dispatching Notification:', caseIdentifier);
 | 
			
		||||
    document.dispatchEvent(event);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates cpos either from ranges or not.
 | 
			
		||||
   */
 | 
			
		||||
  helperCreateCpos(cpos_ranges, cpos_values) {
 | 
			
		||||
    let lc;
 | 
			
		||||
    let c;
 | 
			
		||||
    let rc;
 | 
			
		||||
    if (cpos_ranges) {
 | 
			
		||||
      // python range like function from MDN
 | 
			
		||||
      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Sequence_generator_(range)
 | 
			
		||||
      const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));
 | 
			
		||||
      lc = range(cpos_values.lc[0], cpos_values.lc[1], 1)
 | 
			
		||||
      c = range(cpos_values.c[0], cpos_values.c[1], 1)
 | 
			
		||||
      rc = range(cpos_values.rc[0], cpos_values.rc[1], 1)
 | 
			
		||||
    } else {
 | 
			
		||||
      lc = cpos_values.lc;
 | 
			
		||||
      c = cpos_values.c;
 | 
			
		||||
      rc = cpos_values.rc;
 | 
			
		||||
    }
 | 
			
		||||
    return {lc: lc, c: c, rc: rc};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // handels interactionElements during a pagination navigation
 | 
			
		||||
  // loops over interactionElements and executes callback functions accordingly
 | 
			
		||||
  pageChangeEventInteractionHandler(interactionElements) {
 | 
			
		||||
    // get elements to check thier status
 | 
			
		||||
    for (let interaction of interactionElements.interactions) {
 | 
			
		||||
      if (interaction.checkStatus) {
 | 
			
		||||
        if (interaction.element.checked) {
 | 
			
		||||
          let f_on = interaction.bindThisToCallback("on");
 | 
			
		||||
          let args_on = interaction.callbacks.on.args;
 | 
			
		||||
          f_on(...args_on);
 | 
			
		||||
        } else {
 | 
			
		||||
          let f_off = interaction.bindThisToCallback("off");
 | 
			
		||||
          let args_off = interaction.callbacks.off.args;
 | 
			
		||||
          f_off(...args_off);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        let f = interaction.bindThisToCallback("noCheck");
 | 
			
		||||
        let args = interaction.callbacks.noCheck.args;
 | 
			
		||||
        f(...args);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // get display options from display options form element
 | 
			
		||||
  static getDisplayOptions(htmlId) {
 | 
			
		||||
    // gets display options parameters
 | 
			
		||||
    let displayOptionsFormElement = document.getElementById(htmlId);
 | 
			
		||||
    let displayOptionsFormData = new FormData(displayOptionsFormElement);
 | 
			
		||||
    let displayOptionsData =
 | 
			
		||||
      {
 | 
			
		||||
        "resultsPerPage": displayOptionsFormData.get("display-options-form-results_per_page"),
 | 
			
		||||
        "resultsContex": displayOptionsFormData.get("display-options-form-result_context"),
 | 
			
		||||
        "expertMode": displayOptionsFormData.get("display-options-form-expert_mode")
 | 
			
		||||
      };
 | 
			
		||||
    return displayOptionsData
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Used in addToSubResults and inspect to toggle the design of the check
 | 
			
		||||
  // buttons according to its checked unchecked status.
 | 
			
		||||
  helperActivateAddBtn(btn) {
 | 
			
		||||
    btn.classList.remove("grey");
 | 
			
		||||
    btn.classList.add("green");
 | 
			
		||||
    btn.textContent = "check";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Used in addToSubResults and inspect to toggle the design of the check
 | 
			
		||||
  // buttons according to its checked unchecked status.
 | 
			
		||||
  helperDeactivateAddBtn(btn) {
 | 
			
		||||
    btn.classList.remove("green");
 | 
			
		||||
    btn.classList.add("grey");
 | 
			
		||||
    btn.textContent = "add";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Either adds or removes a match to the sub-results. For this it checks
 | 
			
		||||
  // onclick if the current button has been checked or not. For this the
 | 
			
		||||
  // function checks if its status in addToSubResultsStatus is either flase or
 | 
			
		||||
  // true. Adds match to sub-results if status is false if status is true it
 | 
			
		||||
  // removes it.
 | 
			
		||||
  addToSubResults(dataIndex, tableCall=true) {
 | 
			
		||||
    if (!this.addToSubResultsStatus[dataIndex]
 | 
			
		||||
        || this.addToSubResultsStatus === undefined) {
 | 
			
		||||
      // add button is activated because status is either false or undefined
 | 
			
		||||
      this.helperActivateAddBtn(event.target);
 | 
			
		||||
      this.addToSubResultsStatus[dataIndex] = true;  // sets status to true
 | 
			
		||||
      this.addToSubResultsIdsToShow.add(dataIndex + 1);  // + 1 because user does not see zero indexd data indexes
 | 
			
		||||
      this.subResultsMatchIds.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", ");  // automaticalle sorts ids into the textarea in ascending order
 | 
			
		||||
      M.textareaAutoResize(this.subResultsMatchIds);  // after an insert textarea has to be resized manually
 | 
			
		||||
      this.nrMarkedMatches.textContent = [...this.addToSubResultsIdsToShow].length;
 | 
			
		||||
    } else if (this.addToSubResultsStatus[dataIndex]) {
 | 
			
		||||
      // add button is deactivated because status is true
 | 
			
		||||
      this.helperDeactivateAddBtn(event.target);
 | 
			
		||||
      this.addToSubResultsStatus[dataIndex] = false;  // sets status to false
 | 
			
		||||
      this.addToSubResultsIdsToShow.delete(dataIndex + 1);  // + 1 because user does not see zero indexd data indexes
 | 
			
		||||
      this.subResultsMatchIds.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", ");  // automaticalle sorts ids into the textarea in ascending order
 | 
			
		||||
      this.nrMarkedMatches.textContent = [...this.addToSubResultsIdsToShow].length;
 | 
			
		||||
      M.textareaAutoResize(this.subResultsMatchIds); // after an insert textarea has to be resized manually
 | 
			
		||||
    }
 | 
			
		||||
    // Toggles the create button according to the number of ids in addToSubResultsIdsToShow
 | 
			
		||||
    if ([...this.addToSubResultsIdsToShow].length > 0) {
 | 
			
		||||
      this.subResultsCreate.classList.toggle('disabled', false);
 | 
			
		||||
    } else if ([...this.addToSubResultsIdsToShow].length === 0) {
 | 
			
		||||
      this.subResultsCreate.classList.toggle('disabled', true);
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * After a match as been added or removed the export button will be
 | 
			
		||||
     * hidden because the sub-results have been altered and have to be built
 | 
			
		||||
     * again. Thus subResultsCreateElement has to be shown again.
 | 
			
		||||
     */
 | 
			
		||||
    this.subResultsExport.classList.add("hide");
 | 
			
		||||
    this.subResultsCreate.classList.remove("hide");
 | 
			
		||||
    /**
 | 
			
		||||
     * Also activate/deactivate buttons in the table/jsList results accordingly
 | 
			
		||||
     * if button in inspect was activated/deactivated.
 | 
			
		||||
     * This part only runs if tableCall is set to false when this function is
 | 
			
		||||
     * called.
 | 
			
		||||
     */
 | 
			
		||||
    if (!tableCall) {
 | 
			
		||||
      this.getHTMLElements(['#query-results-table']);
 | 
			
		||||
      let container = this.queryResultsTable.querySelector(`[data-index="${dataIndex}"]`);
 | 
			
		||||
      let tableAddBtn = container.querySelector('.add-btn'); // gets the add button from the list view
 | 
			
		||||
      if (this.addToSubResultsStatus[dataIndex]) {
 | 
			
		||||
        this.helperActivateAddBtn(tableAddBtn);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.helperDeactivateAddBtn(tableAddBtn);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // ###### Functions to inspect one match, to show more details ######
 | 
			
		||||
  // activate inspect buttons if progress is 100
 | 
			
		||||
  activateInspect() {
 | 
			
		||||
    if (!this.clientIsBusy) {
 | 
			
		||||
      let inspectBtnElements;
 | 
			
		||||
      inspectBtnElements = document.querySelectorAll('.inspect');
 | 
			
		||||
      for (let inspectBtn of inspectBtnElements) {
 | 
			
		||||
        inspectBtn.classList.toggle('disabled', false);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // deactivate inspect buttons
 | 
			
		||||
  deactivateInspect() {
 | 
			
		||||
    let inspectBtnElements;
 | 
			
		||||
    inspectBtnElements = document.querySelectorAll('.inspect');
 | 
			
		||||
    for (let inspectBtn of inspectBtnElements) {
 | 
			
		||||
      inspectBtn.classList.toggle('disabled', true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // ### functions to inspect imported Matches
 | 
			
		||||
  // This function creates an object that is similar to the object that is
 | 
			
		||||
  // being recieved as an answere to the getMatchWithContext Method, which is
 | 
			
		||||
  // triggering an socket.io event.
 | 
			
		||||
  // It is used as an input for show match context in the context of imported
 | 
			
		||||
  // results to be able to inspect matches.
 | 
			
		||||
  createFakeResponse() {
 | 
			
		||||
    contextModal.open();
 | 
			
		||||
    // match nr for user to display derived from data_index
 | 
			
		||||
    let contextMatchNrElement = document.getElementById("context-match-nr");
 | 
			
		||||
    contextMatchNrElement.textContent = this.contextId + 1;
 | 
			
		||||
    let cpos_lookup;
 | 
			
		||||
    let fake_response = {};
 | 
			
		||||
    let contextResultsElement;
 | 
			
		||||
    // function to create one match object from entire imported results
 | 
			
		||||
    // that is passed into the results.jsList.showMatchContext() function
 | 
			
		||||
    fake_response["payload"] = {};
 | 
			
		||||
    let dataIndex = event.target.closest("tr").dataset.index;
 | 
			
		||||
    this.contextId = dataIndex;
 | 
			
		||||
    fake_response.payload["matches"] = [results.data.matches[dataIndex]];
 | 
			
		||||
    contextResultsElement = document.getElementById("context-results");
 | 
			
		||||
    contextResultsElement.innerHTML = "";
 | 
			
		||||
    let {lc, c, rc} = this.helperCreateCpos(results.data.cpos_ranges,
 | 
			
		||||
                                            fake_response.payload.matches[0]);
 | 
			
		||||
    cpos_lookup = {};
 | 
			
		||||
    for (let cpos of lc) {
 | 
			
		||||
      cpos_lookup[cpos] = results.data.cpos_lookup[cpos];
 | 
			
		||||
    }
 | 
			
		||||
    for (let cpos of c) {
 | 
			
		||||
      cpos_lookup[cpos] = results.data.cpos_lookup[cpos];
 | 
			
		||||
    }
 | 
			
		||||
    for (let cpos of rc) {
 | 
			
		||||
      cpos_lookup[cpos] = results.data.cpos_lookup[cpos];
 | 
			
		||||
    }
 | 
			
		||||
    fake_response.payload["cpos_lookup"] = cpos_lookup
 | 
			
		||||
    fake_response.payload["cpos_ranges"] = results.data.cpos_ranges;
 | 
			
		||||
    fake_response.payload["query"] = results.data.query;
 | 
			
		||||
    fake_response.payload["context_id"] = dataIndex + 1;
 | 
			
		||||
    fake_response.payload["match_count"] = fake_response.payload.matches.length
 | 
			
		||||
    fake_response.payload["corpus_type"] = "inspect-result"
 | 
			
		||||
    return fake_response
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // gets result cpos infos for one dataIndex (list of length 1) to send back to
 | 
			
		||||
  // the server
 | 
			
		||||
  inspect(dataIndex, type) {
 | 
			
		||||
    // initialize context modal
 | 
			
		||||
    this.getHTMLElements([
 | 
			
		||||
      '#context-modal',
 | 
			
		||||
      '#context-results',
 | 
			
		||||
      '#create-inspect-menu',
 | 
			
		||||
    ]);
 | 
			
		||||
    this.contextModal = M.Modal.init(this.contextModal);
 | 
			
		||||
    // get result infos from server and show them in context modal
 | 
			
		||||
    this.contextId = dataIndex[0];
 | 
			
		||||
    this.contextResults.innerHTML = "";  // clear it from old inspects
 | 
			
		||||
    this.notifyClient('get-results', {resultsType: 'inspect-results',
 | 
			
		||||
                                      dataIndexes: [dataIndex]});
 | 
			
		||||
    // match nr for user to display derived from data_index
 | 
			
		||||
    let contextMatchNrElement = document.getElementById("context-match-nr");
 | 
			
		||||
    contextMatchNrElement.textContent = this.contextId + 1;
 | 
			
		||||
    this.contextModal.open();
 | 
			
		||||
    // add a button to add this match to sub results with onclick event
 | 
			
		||||
    let classes = `btn-floating btn waves-effect` +
 | 
			
		||||
                  ` waves-light grey right`
 | 
			
		||||
    let addToSubResultsIdsBtn = document.createElement("a");
 | 
			
		||||
    addToSubResultsIdsBtn.setAttribute("class", classes + ` add`);
 | 
			
		||||
    addToSubResultsIdsBtn.innerHTML = '<i class="material-icons">add</i>';
 | 
			
		||||
    addToSubResultsIdsBtn.onclick= () => {this.addToSubResults(dataIndex[0], false)};
 | 
			
		||||
    // checks if the match has or has not been added to sub results yet
 | 
			
		||||
    // sets the color and status of the button accordingly
 | 
			
		||||
    if (this.addToSubResultsStatus[dataIndex[0]]) {
 | 
			
		||||
      this.helperActivateAddBtn(addToSubResultsIdsBtn.firstElementChild);
 | 
			
		||||
    } else if (!this.addToSubResultsStatus[dataIndex[0]]) {
 | 
			
		||||
      this.helperDeactivateAddBtn(addToSubResultsIdsBtn.firstElementChild);
 | 
			
		||||
    }
 | 
			
		||||
    this.createInspectMenu.innerHTML = '';
 | 
			
		||||
    this.createInspectMenu.appendChild(addToSubResultsIdsBtn);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // create Element from HTML String helper function
 | 
			
		||||
  HTMLTStrToElement(htmlStr) {
 | 
			
		||||
    // https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
 | 
			
		||||
    let template = document.createElement("template");
 | 
			
		||||
    htmlStr = htmlStr.trim();
 | 
			
		||||
    template.innerHTML = htmlStr;
 | 
			
		||||
    return template.content.firstChild;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  // Used as a callback to handle incoming match context results when inspect
 | 
			
		||||
  // has been used.
 | 
			
		||||
  showMatchContext(results) {
 | 
			
		||||
 | 
			
		||||
    this.getHTMLElements([
 | 
			
		||||
      '#context-results',
 | 
			
		||||
      '#inspect-display-options-form-expert_mode_inspect',
 | 
			
		||||
      '#inspect-display-options-form-highlight_sentences',
 | 
			
		||||
      '#context-sentences'
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
    let uniqueS = new Set();
 | 
			
		||||
    let uniqueContextS = new Set();
 | 
			
		||||
    let {lc, c, rc} = this.helperCreateCpos(results.inspectResultsData.cpos_ranges,
 | 
			
		||||
                                            results.inspectResultsData.matches[0]);
 | 
			
		||||
    // create sentence strings as tokens
 | 
			
		||||
    let tokenHTMLArray = [];
 | 
			
		||||
    let htmlTokenStr = ``;
 | 
			
		||||
    let tokenHTMlElement;
 | 
			
		||||
    let token;
 | 
			
		||||
    for (let cpos of lc) {
 | 
			
		||||
      token = results.inspectResultsData.cpos_lookup[cpos];
 | 
			
		||||
      uniqueS.add(token.s)
 | 
			
		||||
      htmlTokenStr = `<span class="token"` +
 | 
			
		||||
                           `data-sid="${token.s}"` +
 | 
			
		||||
                           `data-cpos="${cpos}">` +
 | 
			
		||||
                       `${token.word}` +
 | 
			
		||||
                     `</span>`;
 | 
			
		||||
      tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
 | 
			
		||||
      tokenHTMLArray.push(tokenHTMlElement);
 | 
			
		||||
    }
 | 
			
		||||
    for (let cpos of c) {
 | 
			
		||||
      token = results.inspectResultsData.cpos_lookup[cpos];
 | 
			
		||||
      uniqueContextS.add(token.s);
 | 
			
		||||
      uniqueS.add(token.s);
 | 
			
		||||
      htmlTokenStr = `<span class="token bold light-green"` +
 | 
			
		||||
                           `data-sid="${token.s}"` +
 | 
			
		||||
                           `data-cpos="${cpos}"` +
 | 
			
		||||
                           `style="text-decoration-line: underline;">` +
 | 
			
		||||
                       `${token.word}` +
 | 
			
		||||
                     `</span>`;
 | 
			
		||||
      tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
 | 
			
		||||
      tokenHTMLArray.push(tokenHTMlElement);
 | 
			
		||||
    }
 | 
			
		||||
    results.inspectResultsData["context_s_ids"] = Array.from(uniqueContextS);
 | 
			
		||||
    for (let cpos of rc) {
 | 
			
		||||
      token = results.inspectResultsData.cpos_lookup[cpos];
 | 
			
		||||
      uniqueS.add(token.s)
 | 
			
		||||
      htmlTokenStr = `<span class="token"` +
 | 
			
		||||
                           `data-sid="${token.s}"` +
 | 
			
		||||
                           `data-cpos="${cpos}">` +
 | 
			
		||||
                       `${token.word}` +
 | 
			
		||||
                     `</span>`;
 | 
			
		||||
      tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
 | 
			
		||||
      tokenHTMLArray.push(tokenHTMlElement);
 | 
			
		||||
    }
 | 
			
		||||
    for (let sId of uniqueS) {
 | 
			
		||||
      let htmlSentence = `<span class="sentence" data-sid="${sId}"></span>`;
 | 
			
		||||
      let sentenceElement = this.HTMLTStrToElement(htmlSentence);
 | 
			
		||||
      for (let tokenElement of tokenHTMLArray) {
 | 
			
		||||
        if (tokenElement.dataset.sid == sId) {
 | 
			
		||||
          sentenceElement.appendChild(tokenElement);
 | 
			
		||||
          sentenceElement.insertAdjacentHTML("beforeend", ` `);
 | 
			
		||||
        } else {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this.contextResults.appendChild(sentenceElement);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // add inspect display options events
 | 
			
		||||
    this.inspectDisplayOptionsFormExpertModeInspect.onchange = (event) => {
 | 
			
		||||
      if (event.target.checked) {
 | 
			
		||||
        this.expertModeOn("context-results", results);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.expertModeOff("context-results")
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.inspectDisplayOptionsFormHighlightSentences.onchange = (event) => {
 | 
			
		||||
      if (event.target.checked) {
 | 
			
		||||
        this.higlightContextSentences();
 | 
			
		||||
      } else {
 | 
			
		||||
      this.unhighlightContextSentences();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.contextSentences.onchange = (event) => {
 | 
			
		||||
      // console.log(event.target.value);
 | 
			
		||||
      this.changeSentenceContext(event.target.value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // checks on new modal opening if switches are checked
 | 
			
		||||
    // if switches are checked functions are executed
 | 
			
		||||
    if (this.inspectDisplayOptionsFormExpertModeInspect.checked) {
 | 
			
		||||
      this.expertModeOn("context-results", results);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.inspectDisplayOptionsFormHighlightSentences.checked) {
 | 
			
		||||
      this.higlightContextSentences();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // checks the value of the number of sentences to show on modal opening
 | 
			
		||||
    // sets context sentences accordingly
 | 
			
		||||
    this.changeSentenceContext(this.contextSentences.value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // splits context text into sentences based on spacy sentence split
 | 
			
		||||
  higlightContextSentences() {
 | 
			
		||||
    let sentences;
 | 
			
		||||
    sentences = document.getElementById("context-results").getElementsByClassName("sentence");
 | 
			
		||||
      for (let s of sentences) {
 | 
			
		||||
        s.insertAdjacentHTML("beforeend", `<span><br><br></span>`)
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  unhighlightContextSentences() {
 | 
			
		||||
    let sentences;
 | 
			
		||||
    let br;
 | 
			
		||||
    sentences = document.getElementById("context-results").getElementsByClassName("sentence");
 | 
			
		||||
      for (let s of sentences) {
 | 
			
		||||
        br = s.lastChild;
 | 
			
		||||
        br.remove();
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // changes how many context sentences in inspect view are shown
 | 
			
		||||
  changeSentenceContext(sValue, maxSValue=10) {
 | 
			
		||||
    let array;
 | 
			
		||||
    let sentences;
 | 
			
		||||
    let toHideArray;
 | 
			
		||||
    let toShowArray;
 | 
			
		||||
    sValue = maxSValue - sValue;
 | 
			
		||||
    // console.log(sValue);
 | 
			
		||||
    sentences = document.getElementById("context-results").getElementsByClassName("sentence");
 | 
			
		||||
    array = Array.from(sentences);
 | 
			
		||||
    if (sValue != 0) {
 | 
			
		||||
      toHideArray = array.slice(0, sValue).concat(array.slice(-(sValue)));
 | 
			
		||||
      toShowArray = array.slice(sValue, 9).concat(array.slice(9, -(sValue)))
 | 
			
		||||
    } else {
 | 
			
		||||
      toHideArray = [];
 | 
			
		||||
      toShowArray = array;
 | 
			
		||||
    }
 | 
			
		||||
    // console.log(array);
 | 
			
		||||
    // console.log("#######");
 | 
			
		||||
    // console.log(toHideArray);
 | 
			
		||||
    for (let s of toHideArray) {
 | 
			
		||||
      s.classList.add("hide");
 | 
			
		||||
    }
 | 
			
		||||
    for (let s of toShowArray) {
 | 
			
		||||
      s.classList.remove("hide");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // ###### Display options changing live how the matches are being displayed ######
 | 
			
		||||
 | 
			
		||||
  // Event function that changes the shown hits per page.
 | 
			
		||||
  // Just alters the resultsList.page property
 | 
			
		||||
  changeHitsPerPage() {
 | 
			
		||||
    try {
 | 
			
		||||
      if (event.type === "change") {
 | 
			
		||||
          nopaque.flash("Updated matches per page.", "corpus")
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
 | 
			
		||||
    } finally {
 | 
			
		||||
      this.page = this.displayOptionsFormResultsPerPage.value;
 | 
			
		||||
      this.update();
 | 
			
		||||
    }
 | 
			
		||||
    this.activateInspect();
 | 
			
		||||
    if (this.displayOptionsFormExpertMode.checked) {
 | 
			
		||||
      this.expertModeOn("query-display");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Event function triggered on context select change
 | 
			
		||||
  // also if pagination is clicked
 | 
			
		||||
  changeContext() {
 | 
			
		||||
    try {
 | 
			
		||||
        if (event.type === "change") {
 | 
			
		||||
            nopaque.flash("Updated context per match!", "corpus");
 | 
			
		||||
        }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
 | 
			
		||||
    } finally {
 | 
			
		||||
        let newContextValue = this.displayOptionsFormResultContext.value;
 | 
			
		||||
        let lc = document.querySelectorAll(".left-context");
 | 
			
		||||
        let rc = document.querySelectorAll(".right-context");
 | 
			
		||||
        for (let element of lc) {
 | 
			
		||||
          let arrayLc = Array.from(element.childNodes);
 | 
			
		||||
          for (let element of arrayLc.reverse().slice(newContextValue)) {
 | 
			
		||||
            element.classList.add("hide");
 | 
			
		||||
          }
 | 
			
		||||
          for (let element of arrayLc.slice(0, newContextValue)) {
 | 
			
		||||
            element.classList.remove("hide");
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        for (let element of rc) {
 | 
			
		||||
          let arrayRc = Array.from(element.childNodes);
 | 
			
		||||
          for (let element of arrayRc.slice(newContextValue)) {
 | 
			
		||||
            element.classList.add("hide");
 | 
			
		||||
          }
 | 
			
		||||
          for (let element of arrayRc.slice(0, newContextValue)) {
 | 
			
		||||
            element.classList.remove("hide");
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  // ###### Expert view event functions ######
 | 
			
		||||
  // function to create a tooltip for the current hovered token
 | 
			
		||||
  tooltipEventCreate(event, results) {
 | 
			
		||||
    // console.log("Create Tooltip on mouseover.");
 | 
			
		||||
    let token;
 | 
			
		||||
    token = results.data.cpos_lookup[event.target.dataset.cpos];
 | 
			
		||||
    if (!token) {
 | 
			
		||||
      token = results.inspectResultsData.cpos_lookup[event.target.dataset.cpos];
 | 
			
		||||
    }
 | 
			
		||||
    this.addToolTipToTokenElement(event.target, token, results);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Function to destroy the current Tooltip for the current hovered tooltip
 | 
			
		||||
  // on mouse leave
 | 
			
		||||
  tooltipEventDestroy(event) {
 | 
			
		||||
    // console.log("Tooltip destroy on leave.");
 | 
			
		||||
    this.currentTooltipElement.destroy();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // turn the expert mode on for all tokens in the DOM element identified by its htmlID
 | 
			
		||||
  expertModeOn(htmlId, results) {
 | 
			
		||||
    if (!Array.isArray(this.currentExpertTokenElements[htmlId])) {
 | 
			
		||||
      this.currentExpertTokenElements[htmlId] = [];
 | 
			
		||||
    }
 | 
			
		||||
    let container = document.getElementById(htmlId);
 | 
			
		||||
    let tokens = container.querySelectorAll('span.token');
 | 
			
		||||
    this.currentExpertTokenElements[htmlId].push(...tokens);
 | 
			
		||||
    this.eventTokens[htmlId] = [];
 | 
			
		||||
    for (let tokenElement of this.currentExpertTokenElements[htmlId]) {
 | 
			
		||||
      tokenElement.classList.add("chip", "hoverable", "expert-view");
 | 
			
		||||
      const eventCreate = (event, arg) => this.tooltipEventCreate(event, arg);
 | 
			
		||||
      tokenElement.onmouseover = (event) => eventCreate(event, results);
 | 
			
		||||
      tokenElement.onmouseout = (event) => this.tooltipEventDestroy(event);
 | 
			
		||||
      this.eventTokens[htmlId].push(tokenElement);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // fuction that creates Tooltip for one token and extracts the corresponding
 | 
			
		||||
  // infos from the result JSON
 | 
			
		||||
  addToolTipToTokenElement(tokenElement, token, results) {
 | 
			
		||||
    this.currentTooltipElement;
 | 
			
		||||
    this.currentTooltipElement = M.Tooltip.init(tokenElement,
 | 
			
		||||
     {"html": `<table>
 | 
			
		||||
                 <tr>
 | 
			
		||||
                   <th>Token information</th>
 | 
			
		||||
                   <th>Source information</th>
 | 
			
		||||
                 </tr>
 | 
			
		||||
                 <tr>
 | 
			
		||||
                   <td class="left-align">
 | 
			
		||||
                     Word: ${token.word}<br>
 | 
			
		||||
                     Lemma: ${token.lemma}<br>
 | 
			
		||||
                     POS: ${token.pos}<br>
 | 
			
		||||
                     Simple POS: ${token.simple_pos}<br>
 | 
			
		||||
                     NER: ${token.ner}
 | 
			
		||||
                   </td>
 | 
			
		||||
                   <td class="left-align">
 | 
			
		||||
                     Title: ${results.data.text_lookup[token.text].title}
 | 
			
		||||
                     <br>
 | 
			
		||||
                     Author: ${results.data.text_lookup[token.text].author}
 | 
			
		||||
                     <br>
 | 
			
		||||
                     Publishing year: ${results.data.text_lookup[token.text].publishing_year}
 | 
			
		||||
                   </td>
 | 
			
		||||
                 </tr>
 | 
			
		||||
               </table>`}
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // function to remove extra informations and animations from tokens
 | 
			
		||||
  expertModeOff(htmlId) {
 | 
			
		||||
    // console.log("Expert mode is off.");
 | 
			
		||||
    if (!Array.isArray(this.currentExpertTokenElements[htmlId])) {
 | 
			
		||||
      this.currentExpertTokenElements[htmlId] = [];
 | 
			
		||||
    }
 | 
			
		||||
    if (!Array.isArray(this.eventTokens[htmlId])) {
 | 
			
		||||
      this.eventTokens[htmlId] = [];
 | 
			
		||||
    }
 | 
			
		||||
    for (let tokenElement of this.currentExpertTokenElements[htmlId]) {
 | 
			
		||||
      tokenElement.classList.remove("chip", "hoverable", "expert-view");
 | 
			
		||||
    }
 | 
			
		||||
    this.currentExpertTokenElements[htmlId] = [];
 | 
			
		||||
 | 
			
		||||
    for (let eventToken of this.eventTokens[htmlId]) {
 | 
			
		||||
      eventToken.onmouseover = "";
 | 
			
		||||
      eventToken.onmouseout = "";
 | 
			
		||||
    }
 | 
			
		||||
  this.eventTokens[htmlId] = [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createResultRowElement(item, chunk, imported=false) {
 | 
			
		||||
    let aCellElement;
 | 
			
		||||
    let addToSubResultsBtn;
 | 
			
		||||
    let cCellElement;
 | 
			
		||||
    let cpos;
 | 
			
		||||
    let fakeResponse;   // used if imported results are being created;
 | 
			
		||||
    let inspectBtn
 | 
			
		||||
    let lcCellElement;
 | 
			
		||||
    let matchNrElement;
 | 
			
		||||
    let matchRowElement;
 | 
			
		||||
    let rcCellElement;
 | 
			
		||||
    let textTitles;
 | 
			
		||||
    let textTitlesCellElement;
 | 
			
		||||
    let token;
 | 
			
		||||
    let values;
 | 
			
		||||
    // gather values from item
 | 
			
		||||
    values = item.values();
 | 
			
		||||
    let {lc, c, rc} = this.helperCreateCpos(chunk.cpos_ranges,
 | 
			
		||||
                                            values)
 | 
			
		||||
    // get infos for full match row
 | 
			
		||||
    matchRowElement = document.createElement("tr");
 | 
			
		||||
    matchRowElement.setAttribute("data-index", values.index)
 | 
			
		||||
    lcCellElement = document.createElement("td");
 | 
			
		||||
    lcCellElement.classList.add("left-context");
 | 
			
		||||
    matchRowElement.appendChild(lcCellElement);
 | 
			
		||||
    for (cpos of lc) {
 | 
			
		||||
      token = chunk.cpos_lookup[cpos];
 | 
			
		||||
      lcCellElement.insertAdjacentHTML("beforeend",
 | 
			
		||||
        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // get infos for hit of match and set actions
 | 
			
		||||
    textTitles = new Set();
 | 
			
		||||
    aCellElement = document.createElement("td");
 | 
			
		||||
    aCellElement.classList.add("actions");
 | 
			
		||||
    cCellElement = document.createElement("td");
 | 
			
		||||
    cCellElement.classList.add("match-hit");
 | 
			
		||||
    textTitlesCellElement = document.createElement("td");
 | 
			
		||||
    textTitlesCellElement.classList.add("titles");
 | 
			
		||||
    matchNrElement = document.createElement("td");
 | 
			
		||||
    matchNrElement.classList.add("match-nr");
 | 
			
		||||
    matchRowElement.appendChild(cCellElement);
 | 
			
		||||
    matchRowElement.appendChild(aCellElement);
 | 
			
		||||
    for (cpos of c) {
 | 
			
		||||
      token = chunk.cpos_lookup[cpos];
 | 
			
		||||
      cCellElement.insertAdjacentHTML("beforeend",
 | 
			
		||||
        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
 | 
			
		||||
      // get text titles of every hit cpos token
 | 
			
		||||
      textTitles.add(chunk.text_lookup[token.text].title);
 | 
			
		||||
    }
 | 
			
		||||
    // add some interaction buttons
 | 
			
		||||
    // # some btn css rules and classes
 | 
			
		||||
    let css = `margin-right: 5px; margin-bottom: 5px;`
 | 
			
		||||
    let classes = `btn-floating btn waves-effect` +
 | 
			
		||||
                  ` waves-light grey`
 | 
			
		||||
    // # add button to trigger more context to every match td
 | 
			
		||||
    inspectBtn = document.createElement("a");
 | 
			
		||||
    inspectBtn.setAttribute("style", css);
 | 
			
		||||
    inspectBtn.setAttribute("class", classes + ` disabled inspect`
 | 
			
		||||
                            );
 | 
			
		||||
    inspectBtn.innerHTML = '<i class="material-icons inspect-btn">search</i>';
 | 
			
		||||
    // # add btn to add matches to sub-results. hidden per default
 | 
			
		||||
    addToSubResultsBtn = document.createElement("a");
 | 
			
		||||
    addToSubResultsBtn.setAttribute("style", css);
 | 
			
		||||
    addToSubResultsBtn.setAttribute("class", classes + ` add`
 | 
			
		||||
                                );
 | 
			
		||||
    addToSubResultsBtn.innerHTML = '<i class="material-icons add-btn">add</i>';
 | 
			
		||||
    aCellElement.appendChild(inspectBtn);
 | 
			
		||||
    aCellElement.appendChild(addToSubResultsBtn);
 | 
			
		||||
    // add text titles at front as first td of one row
 | 
			
		||||
    textTitlesCellElement.textContent = [...textTitles].join(", ");
 | 
			
		||||
    matchRowElement.insertAdjacentHTML("afterbegin", textTitlesCellElement.outerHTML);
 | 
			
		||||
    matchNrElement.textContent = values.index + 1;
 | 
			
		||||
    matchRowElement.insertAdjacentHTML("afterbegin", matchNrElement.outerHTML);
 | 
			
		||||
 | 
			
		||||
    // get infos for right context of match
 | 
			
		||||
    rcCellElement = document.createElement("td");
 | 
			
		||||
    rcCellElement.classList.add("right-context");
 | 
			
		||||
    matchRowElement.appendChild(rcCellElement);
 | 
			
		||||
    for (cpos of rc) {
 | 
			
		||||
      token = chunk.cpos_lookup[cpos];
 | 
			
		||||
      rcCellElement.insertAdjacentHTML("beforeend",
 | 
			
		||||
        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
 | 
			
		||||
    }
 | 
			
		||||
    return matchRowElement
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // creates the HTML table code for the metadata view in the corpus analysis interface
 | 
			
		||||
  createMetaDataForModal(metaDataObject) {
 | 
			
		||||
    let html = `<div class="col s12">
 | 
			
		||||
                      <table class="highlight">
 | 
			
		||||
                        <thead>
 | 
			
		||||
                          <tr>
 | 
			
		||||
                            <th>Metadata Description</th>
 | 
			
		||||
                            <th>Value</th>
 | 
			
		||||
                          </tr>
 | 
			
		||||
                        </thead>
 | 
			
		||||
                        <tbody>`
 | 
			
		||||
    for (let [outerKey, outerValue] of Object.entries(metaDataObject)) {
 | 
			
		||||
      html += `<tr>
 | 
			
		||||
                  <td style="text-transform: uppercase;">${outerKey.replace(/_/g, " ")}</td>`
 | 
			
		||||
      if (outerKey === "corpus_all_texts" || outerKey === "text_lookup") {
 | 
			
		||||
        html += `<td>
 | 
			
		||||
                  <ul class="collapsible">`
 | 
			
		||||
        for (let [innerKey, innerValue] of Object.entries(outerValue)) {
 | 
			
		||||
          html += `<li class="text-metadata"
 | 
			
		||||
                        data-metadata-key="${outerKey}"
 | 
			
		||||
                        data-text-key="${innerKey}">
 | 
			
		||||
                      <div class="collapsible-header"
 | 
			
		||||
                           data-metadata-key="${outerKey}"
 | 
			
		||||
                           data-text-key="${innerKey}">
 | 
			
		||||
                        <i class="material-icons"
 | 
			
		||||
                           data-metadata-key="${outerKey}"
 | 
			
		||||
                           data-text-key="${innerKey}">info_outline</i>
 | 
			
		||||
                           ${innerValue['author']} - ${innerValue['publishing_year']} -
 | 
			
		||||
                           ${innerValue['title']}
 | 
			
		||||
                      </div>
 | 
			
		||||
                      <div class="collapsible-body">
 | 
			
		||||
                        <span>
 | 
			
		||||
                          <ul id="bibliographic-data-${outerKey}-${innerKey}"
 | 
			
		||||
                              style="column-count: 2;">
 | 
			
		||||
                          </ul>
 | 
			
		||||
                        </span>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </li>`
 | 
			
		||||
        }
 | 
			
		||||
        html += `</ul>
 | 
			
		||||
                  </td>`
 | 
			
		||||
      } else {
 | 
			
		||||
        html += `<td>${outerValue}</td>`
 | 
			
		||||
      }
 | 
			
		||||
      html += `</tr>`
 | 
			
		||||
    }
 | 
			
		||||
    html += `</tbody>
 | 
			
		||||
              </table>`
 | 
			
		||||
    return html
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Creates the text details for the texts shown in the corpus analysis metadata modal.
 | 
			
		||||
  createTextDetails(metaData) {
 | 
			
		||||
    let metadataKey = event.target.dataset.metadataKey;
 | 
			
		||||
    let textKey = event.target.dataset.textKey;
 | 
			
		||||
    let textData = metaData[metadataKey][textKey];
 | 
			
		||||
    let bibliographicData = document.querySelector(`#bibliographic-data-${metadataKey}-${textKey}`);
 | 
			
		||||
    bibliographicData.textContent = '';
 | 
			
		||||
    for (let [key, value] of Object.entries(textData)) {
 | 
			
		||||
      bibliographicData.insertAdjacentHTML("afterbegin",
 | 
			
		||||
      `
 | 
			
		||||
      <li><span style="text-transform: capitalize;">${key}:</span> ${value}</li>
 | 
			
		||||
      `);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// export classses
 | 
			
		||||
export { ViewEventListener, ResultsList };
 | 
			
		||||
							
								
								
									
										183
									
								
								web/app/static/js/modules/corpus_analysis/view/callbacks.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								web/app/static/js/modules/corpus_analysis/view/callbacks.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,183 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This file contains all the callbacks triggered by the notificationListener.
 | 
			
		||||
 * Also general callbacks are defined which are doing some hiding/disabling and
 | 
			
		||||
 * showing/enabling of common elements when data is being transmitted or not.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function disableElementsGeneralCallback(resultsList, detail) {
 | 
			
		||||
  if (detail.type === 'full-results') {
 | 
			
		||||
    resultsList.fullResultsCreate.classList.toggle('hide', false);
 | 
			
		||||
    resultsList.fullResultsExport.classList.toggle('hide', true);
 | 
			
		||||
  } else if (detail.type === 'sub-results') {
 | 
			
		||||
    resultsList.subResultsCreate.classList.toggle('hide', false);
 | 
			
		||||
    resultsList.subResultsExport.classList.toggle('hide', true);
 | 
			
		||||
  } else {
 | 
			
		||||
    resultsList.fullResultsCreate.classList.toggle('disabled', true);
 | 
			
		||||
    resultsList.subResultsCreate.classList.toggle('disabled', true);
 | 
			
		||||
  }
 | 
			
		||||
  resultsList.deactivateInspect();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function enableElementsGeneralCallback(resultsList, detail) {
 | 
			
		||||
  resultsList.fullResultsCreate.classList.toggle('disabled');
 | 
			
		||||
  resultsList.subResultsCreate.classList.toggle('disabled');
 | 
			
		||||
  resultsList.activateInspect();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function connectingCallback(resultsList, detail) {
 | 
			
		||||
  resultsList.getHTMLElements(['#analysis-init-modal']);
 | 
			
		||||
  resultsList.analysisInitModal = M.Modal.init(resultsList.analysisInitModal,
 | 
			
		||||
                                               {dismissible: false});
 | 
			
		||||
  resultsList.analysisInitModal.open();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function connectedCallback(resultsList, detail) {
 | 
			
		||||
  resultsList.analysisInitModal.close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function connectingFaildeCallback(resultsList, detail) {
 | 
			
		||||
  resultsList.getHTMLElements([
 | 
			
		||||
    '#analysis-init-progress',
 | 
			
		||||
    '#analysis-init-error'
 | 
			
		||||
  ]);
 | 
			
		||||
  resultsList.analysisInitProgress.classList.toggle('hide');
 | 
			
		||||
  resultsList.analysisInitError.classList.toggle('hide');
 | 
			
		||||
  resultsList.analysisInitError.textContent = detail.msg;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function queryDataPreparingCallback(resultsList, detail) {
 | 
			
		||||
  resultsList.clientIsBusy = true;
 | 
			
		||||
  // remove all items from resultsList, like from the query issued before
 | 
			
		||||
  resultsList.clear()
 | 
			
		||||
  // get needed HTML Elements
 | 
			
		||||
  let results = detail.results;
 | 
			
		||||
  resultsList.getHTMLElements([
 | 
			
		||||
    '#interactions-menu',
 | 
			
		||||
    '#recieved-match-count',
 | 
			
		||||
    '#total-match-count',
 | 
			
		||||
    '#text-lookup-count',
 | 
			
		||||
    '#text-lookup-titles',
 | 
			
		||||
    '#query-results-user-feedback',
 | 
			
		||||
    '#query-progress-bar',
 | 
			
		||||
    '#query-results-create',
 | 
			
		||||
    '#sub-results-match-ids',
 | 
			
		||||
    '#nr-marked-matches',
 | 
			
		||||
   ]);
 | 
			
		||||
  // show or enable some things for the user
 | 
			
		||||
  resultsList.interactionsMenu.classList.toggle('hide', false)
 | 
			
		||||
  resultsList.queryResultsUserFeedback.classList.toggle('hide', false);
 | 
			
		||||
  resultsList.queryProgressBar.classList.toggle('hide', false);
 | 
			
		||||
  /**
 | 
			
		||||
   * Set some initial values for the user feedback
 | 
			
		||||
   * or reset values for new issued query
 | 
			
		||||
   */
 | 
			
		||||
  resultsList.recievedMatchCount.textContent = 0;
 | 
			
		||||
  resultsList.totalMatchCount.textContent = results.data.match_count;
 | 
			
		||||
  resultsList.textLookupTitles.textContent = '';
 | 
			
		||||
  resultsList.textLookupCount.textContent = 0;
 | 
			
		||||
  resultsList.nrMarkedMatches.textContent = 0;
 | 
			
		||||
  resultsList.subResultsMatchIds.textContent = '';
 | 
			
		||||
  resultsList.resetFields();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function queryDataRecievingCallback(resultsList, detail) {
 | 
			
		||||
  // load the data into the resultsList and show them to the user
 | 
			
		||||
  let results = detail.results;
 | 
			
		||||
  let client = detail.client;
 | 
			
		||||
  let start = detail.dataLength;
 | 
			
		||||
  let resultItems = [];
 | 
			
		||||
  for (let [index, match] of Object.entries(results.data.matches).slice(start)) {
 | 
			
		||||
    resultItems.push({ ...match, ...{ 'index': parseInt(index) } });
 | 
			
		||||
  }
 | 
			
		||||
  if (client.dynamicMode) {
 | 
			
		||||
    resultsList.add(resultItems, (items) => {
 | 
			
		||||
      for (let item of items) {
 | 
			
		||||
        item.elm = resultsList.createResultRowElement(item, results.data);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  // update user feedback about query status
 | 
			
		||||
  resultsList.recievedMatchCount.textContent = results.data.matches.length;
 | 
			
		||||
  resultsList.queryProgressBar.firstElementChild.style.width = `${client.requestQueryProgress}%`;
 | 
			
		||||
  resultsList.textLookupCount.textContent = `${Object.keys(results.data.text_lookup).length}`;
 | 
			
		||||
  let titles = new Array();
 | 
			
		||||
  for (let [key, value] of Object.entries(results.data.text_lookup)) {
 | 
			
		||||
    titles.push(`${value.title} (${value.publishing_year})`);
 | 
			
		||||
  }
 | 
			
		||||
  resultsList.textLookupTitles.textContent = `${titles.join(', ')}`;
 | 
			
		||||
  // updating table on finished item creation callback via createResultRowElement
 | 
			
		||||
  resultsList.update();
 | 
			
		||||
  resultsList.changeHitsPerPage();
 | 
			
		||||
  resultsList.changeContext();
 | 
			
		||||
  //activate expertMode of switch is checked
 | 
			
		||||
  if (resultsList.displayOptionsFormExpertMode.checked) {
 | 
			
		||||
    resultsList.expertModeOn('query-display', results);
 | 
			
		||||
  }
 | 
			
		||||
  } else if (!client.dynamicMode) {
 | 
			
		||||
    results.jsList.add(resultItems, (items) => {
 | 
			
		||||
      for (let item of items) {
 | 
			
		||||
        item.elm = results.jsList.createResultRowElement(item, payload.chunk,
 | 
			
		||||
                                                         true);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function queryDataRecievedCallback(resultsList, detail) {
 | 
			
		||||
  // hide or disable some things for the user
 | 
			
		||||
  resultsList.queryResultsUserFeedback.classList.toggle('hide');
 | 
			
		||||
  resultsList.queryProgressBar.classList.toggle('hide');
 | 
			
		||||
  // reset bar progress for next query
 | 
			
		||||
  resultsList.queryProgressBar.firstElementChild.style.width = '0%';
 | 
			
		||||
  resultsList.clientIsBusy = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function resultsDataRecievingCallback(resultsList, detail) {
 | 
			
		||||
  resultsList.clientIsBusy = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function resultsDataRecievedCallback(resultsList, detail) {
 | 
			
		||||
  resultsList.clientIsBusy = false;
 | 
			
		||||
  // create strings for create buttons depending on type
 | 
			
		||||
  const handleType = (keyPrefix, text) => {
 | 
			
		||||
    // hides the create element after results have been recieved and reset it
 | 
			
		||||
    resultsList[`${keyPrefix}Create`].classList.toggle('hide');
 | 
			
		||||
    resultsList[`${keyPrefix}Create`].textContent = `Create ${text}`;
 | 
			
		||||
    resultsList[`${keyPrefix}Create`].insertAdjacentHTML('beforeend',
 | 
			
		||||
    `<i class="material-icons left">build</i>`);
 | 
			
		||||
    // show and highlight export button
 | 
			
		||||
    resultsList[`${keyPrefix}Export`].classList.toggle('hide', false);
 | 
			
		||||
    resultsList[`${keyPrefix}Export`].classList.toggle('pulse', true);
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      resultsList[`${keyPrefix}Export`].classList.toggle('pulse', false);
 | 
			
		||||
      clearTimeout();
 | 
			
		||||
    }, 3000)
 | 
			
		||||
  }
 | 
			
		||||
  if (detail.type === 'full-results') {
 | 
			
		||||
    handleType('fullResults', 'Results');
 | 
			
		||||
  } else if (detail.type ==='sub-results') {
 | 
			
		||||
    handleType('subResults', 'Sub-Results');
 | 
			
		||||
  } else if (detail.type ==='inspect-results') {
 | 
			
		||||
    if (resultsList.addToSubResultsIdsToShow.size === 0) {
 | 
			
		||||
      /**
 | 
			
		||||
      * Prevent create sub results button from being activated if it is disabled
 | 
			
		||||
      * and no matches have been marked by the user for sub results creation.
 | 
			
		||||
      */
 | 
			
		||||
      resultsList.subResultsCreate.classList.toggle('disabled', true);
 | 
			
		||||
    }
 | 
			
		||||
    resultsList.showMatchContext(detail.results);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// export the callbacks
 | 
			
		||||
export {
 | 
			
		||||
  connectingCallback,
 | 
			
		||||
  connectedCallback,
 | 
			
		||||
  connectingFaildeCallback,
 | 
			
		||||
  queryDataPreparingCallback,
 | 
			
		||||
  queryDataRecievingCallback,
 | 
			
		||||
  queryDataRecievedCallback,
 | 
			
		||||
  resultsDataRecievingCallback,
 | 
			
		||||
  resultsDataRecievedCallback,
 | 
			
		||||
  disableElementsGeneralCallback,
 | 
			
		||||
  enableElementsGeneralCallback,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										87
									
								
								web/app/static/js/modules/corpus_analysis/view/listeners.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								web/app/static/js/modules/corpus_analysis/view/listeners.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This file contains the listener function that will be assigned to the
 | 
			
		||||
 * corpus_analysis ResultsView. The listener is listening for the notification
 | 
			
		||||
 * event which is being dispatched by the corpus_analysis Client. The
 | 
			
		||||
 * notification Event triggers the listener whiche will call different
 | 
			
		||||
 * callback functions depending on the detail information of the notification
 | 
			
		||||
 * event.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  connectingCallback,
 | 
			
		||||
  connectedCallback,
 | 
			
		||||
  connectingFaildeCallback,
 | 
			
		||||
  queryDataPreparingCallback,
 | 
			
		||||
  queryDataRecievingCallback,
 | 
			
		||||
  queryDataRecievedCallback,
 | 
			
		||||
  resultsDataRecievingCallback,
 | 
			
		||||
  resultsDataRecievedCallback,
 | 
			
		||||
  disableElementsGeneralCallback,
 | 
			
		||||
  enableElementsGeneralCallback,
 | 
			
		||||
} from './callbacks.js';
 | 
			
		||||
 | 
			
		||||
function recieveClientNotification(eventType, resultsList) {
 | 
			
		||||
  document.addEventListener(eventType, (event) => {
 | 
			
		||||
    let caseIdentifier = event.detail.caseIdentifier;
 | 
			
		||||
    switch (caseIdentifier) {
 | 
			
		||||
      case 'connecting':
 | 
			
		||||
        console.info('View recieved notification:', caseIdentifier);
 | 
			
		||||
        connectingCallback(resultsList, event.detail);
 | 
			
		||||
        // execute callbacks
 | 
			
		||||
        break;
 | 
			
		||||
      case 'connected':
 | 
			
		||||
        console.info('View recieved notification:', caseIdentifier);
 | 
			
		||||
        connectedCallback(resultsList, event.detail);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'connecting-failed':
 | 
			
		||||
        console.info('View recieved notification:', caseIdentifier);
 | 
			
		||||
        // execute callbacks
 | 
			
		||||
        connectingFaildeCallback(resultsList, event.detail);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'query-data-prepareing':
 | 
			
		||||
        console.info('View recieved notification:', caseIdentifier);
 | 
			
		||||
        // some extra hiding and showing (this should be done less confusing)
 | 
			
		||||
        resultsList.fullResultsExport.classList.toggle('hide', true);
 | 
			
		||||
        resultsList.subResultsExport.classList.toggle('hide', true);
 | 
			
		||||
        resultsList.fullResultsCreate.classList.toggle('hide', false);
 | 
			
		||||
        resultsList.subResultsCreate.classList.toggle('hide', false);
 | 
			
		||||
        // execute callbacks
 | 
			
		||||
        disableElementsGeneralCallback(resultsList, event.detail);
 | 
			
		||||
        queryDataPreparingCallback(resultsList, event.detail);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'query-data-recieving':
 | 
			
		||||
        console.info('View recieved notification:', caseIdentifier);
 | 
			
		||||
        // execute callbacks
 | 
			
		||||
        queryDataRecievingCallback(resultsList, event.detail);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'query-data-recieved':
 | 
			
		||||
        console.info('View recieved notification:', caseIdentifier);
 | 
			
		||||
        // execute callbacks
 | 
			
		||||
        queryDataRecievedCallback(resultsList, event.detail);
 | 
			
		||||
        enableElementsGeneralCallback(resultsList, event.detail);
 | 
			
		||||
        // create sub-results is disabled per default until matches have been added
 | 
			
		||||
        resultsList.subResultsCreate.classList.toggle('disabled', true);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'results-data-recieving':
 | 
			
		||||
        console.info('View recieved notification:', caseIdentifier);
 | 
			
		||||
        // execute callbacks
 | 
			
		||||
        disableElementsGeneralCallback(resultsList, event.detail);
 | 
			
		||||
        resultsDataRecievedCallback(resultsList, event.detail);
 | 
			
		||||
        break;
 | 
			
		||||
      case 'results-data-recieved':
 | 
			
		||||
        console.info('View recieved notification:', caseIdentifier);
 | 
			
		||||
        // execute callbacks
 | 
			
		||||
        console.info(event.detail);
 | 
			
		||||
        enableElementsGeneralCallback(resultsList, event.detail);
 | 
			
		||||
        resultsDataRecievedCallback(resultsList, event.detail);
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        console.error('Recieved unkown notification case identifier from Client');
 | 
			
		||||
        // do something to not crash the analysis session?
 | 
			
		||||
        // maybe unnecessary
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// export listeners
 | 
			
		||||
export { recieveClientNotification };
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Function to show a scroll to top button if the user has scrolled down
 | 
			
		||||
 * 250 pixels from the headline element.
 | 
			
		||||
 */
 | 
			
		||||
function scrollToTop(scrollToElementSelector, triggerElementSelector) {
 | 
			
		||||
  let headline = document.querySelector(scrollToElementSelector);
 | 
			
		||||
  let scrollToTop = document.querySelector(triggerElementSelector);
 | 
			
		||||
  window.addEventListener('scroll', (event) => {
 | 
			
		||||
    if (pageYOffset > 250) {
 | 
			
		||||
      scrollToTop.classList.toggle('hide', false);
 | 
			
		||||
    } else {
 | 
			
		||||
      scrollToTop.classList.toggle('hide', true);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  scrollToTop.onclick = () => {
 | 
			
		||||
    headline.scrollIntoView({behavior: 'smooth', block: 'end', inline: 'nearest'});
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// export function
 | 
			
		||||
export { scrollToTop };
 | 
			
		||||
							
								
								
									
										17
									
								
								web/app/static/js/modules/corpus_analysis/view/spinner.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								web/app/static/js/modules/corpus_analysis/view/spinner.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
// loading spinner animation HTML
 | 
			
		||||
const loadingSpinnerHTML = `
 | 
			
		||||
            <div class="preloader-wrapper button-icon-spinner small active">
 | 
			
		||||
              <div class="spinner-layer spinner-green-only">
 | 
			
		||||
                <div class="circle-clipper left">
 | 
			
		||||
                  <div class="circle"></div>
 | 
			
		||||
                  </div><div class="gap-patch">
 | 
			
		||||
                  <div class="circle"></div>
 | 
			
		||||
                  </div><div class="circle-clipper right">
 | 
			
		||||
                  <div class="circle"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            `;
 | 
			
		||||
 | 
			
		||||
//export
 | 
			
		||||
export { loadingSpinnerHTML };
 | 
			
		||||
@@ -1,205 +0,0 @@
 | 
			
		||||
class CorpusAnalysisClient {
 | 
			
		||||
  constructor(corpusId, socket) {
 | 
			
		||||
    this.callbacks = {};
 | 
			
		||||
    this.corpusId = corpusId;
 | 
			
		||||
    this.displays = {};
 | 
			
		||||
    this.socket = socket;
 | 
			
		||||
 | 
			
		||||
    // socket on event for corpous analysis initialization
 | 
			
		||||
    socket.on("corpus_analysis_init", (response) => {
 | 
			
		||||
      let errorText;
 | 
			
		||||
 | 
			
		||||
      if (response.code === 200) {
 | 
			
		||||
        console.log(`corpus_analysis_init: ${response.code} - ${response.msg}`);
 | 
			
		||||
        if (this.callbacks.init != undefined) {
 | 
			
		||||
          this.callbacks.init(response.payload);
 | 
			
		||||
          this.callbacks.get_metadata();  // should hold the function getMetaData
 | 
			
		||||
        }
 | 
			
		||||
        if (this.displays.init != undefined) {
 | 
			
		||||
          this.displays.init.setVisibilityByStatus("success");
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        errorText = `Error ${response.code} - ${response.msg}`;
 | 
			
		||||
        if (this.displays.init.errorContainer != undefined)  {
 | 
			
		||||
          this.displays.init.errorContainer.innerHTML = `<p class="red-text">` +
 | 
			
		||||
                   `<i class="material-icons tiny">error</i> ${errorText}</p>`;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.displays.init != undefined)  {
 | 
			
		||||
          this.displays.init.setVisibilityByStatus("error");
 | 
			
		||||
        }
 | 
			
		||||
        console.error(`corpus_analysis_init: ${errorText}`);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // socket on event for recieving meta
 | 
			
		||||
    socket.on('corpus_analysis_send_meta_data', (response) => {
 | 
			
		||||
      let errorText;
 | 
			
		||||
 | 
			
		||||
      if (response.code === 200) {
 | 
			
		||||
        console.log(`corpus_analysis_send_meta_data: ${response.code} - ${response.msg} - ${response.desc}`);
 | 
			
		||||
        if (this.callbacks.recv_meta_data != undefined) {
 | 
			
		||||
          this.callbacks.recv_meta_data(response.payload);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        errorText = `Error ${response.code} - ${response.msg}`;
 | 
			
		||||
        if (this.displays.init.errorContainer != undefined)  {
 | 
			
		||||
          this.displays.init.errorContainer.innerHTML = `<p class="red-text">` +
 | 
			
		||||
                   `<i class="material-icons tiny">error</i> ${errorText}</p>`;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.displays.init != undefined)  {
 | 
			
		||||
          this.displays.init.setVisibilityByStatus("error");
 | 
			
		||||
        }
 | 
			
		||||
        console.error(`corpus_analysis_send_meta_data: ${errorText}`);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // socket on event for recieveing query results
 | 
			
		||||
    socket.on("corpus_analysis_query", (response) => {
 | 
			
		||||
      let errorText;
 | 
			
		||||
 | 
			
		||||
      if (response.code === 200) {
 | 
			
		||||
        console.log(`corpus_analysis_query: ${response.code} - ${response.msg}`);
 | 
			
		||||
        if (this.callbacks.query != undefined)  {
 | 
			
		||||
          this.callbacks.query(response.payload);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.displays.query != undefined)  {
 | 
			
		||||
          this.displays.query.setVisibilityByStatus("success");
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        errorText = `Error ${response.payload.code} - ${response.payload.msg}`;
 | 
			
		||||
        nopaque.flash(errorText, "error");
 | 
			
		||||
        if (this.displays.query.errorContainer != undefined)  {
 | 
			
		||||
          this.displays.query.errorContainer.innerHTML = `<p class="red-text">`+
 | 
			
		||||
                    `<i class="material-icons tiny">error</i> ${errorText}</p>`;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.displays.query != undefined)  {
 | 
			
		||||
          this.displays.query.setVisibilityByStatus("error");
 | 
			
		||||
        }
 | 
			
		||||
        console.error(`corpus_analysis_query: ${errorText}`);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    socket.on("corpus_analysis_query_results", (response) => {
 | 
			
		||||
        if (this.callbacks.query_results != undefined) {
 | 
			
		||||
          this.callbacks.query_results(response.payload);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // inspect callback handeling based on type
 | 
			
		||||
    socket.on("corpus_analysis_inspect_match", (response) => {
 | 
			
		||||
      console.log(response);
 | 
			
		||||
      if (response.type === "inspect") {
 | 
			
		||||
        if (this.callbacks.query_match_context != undefined) {
 | 
			
		||||
          this.callbacks.query_match_context(response);
 | 
			
		||||
        }
 | 
			
		||||
      } else if (response.type === "sub-results"
 | 
			
		||||
                 || response.type ==="results"){
 | 
			
		||||
        if (this.callbacks.save_sub_results_choices != undefined) {
 | 
			
		||||
          this.callbacks.save_sub_results_choices(response);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init() {
 | 
			
		||||
    if (this.displays.init.errorContainer != undefined)  {
 | 
			
		||||
      this.displays.init.errorContainer.innerHTML == "";
 | 
			
		||||
    }
 | 
			
		||||
    if (this.displays.init != undefined)  {
 | 
			
		||||
      this.displays.init.setVisibilityByStatus("waiting");
 | 
			
		||||
    }
 | 
			
		||||
    this.socket.emit("corpus_analysis_init", this.corpusId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getMetaData() {
 | 
			
		||||
    // just emits this to tell the server to gather all meta data infos and send
 | 
			
		||||
    // those back
 | 
			
		||||
    this.socket.emit("corpus_analysis_get_meta_data", this.corpusId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  query(queryStr) {
 | 
			
		||||
    let displayOptionsData;
 | 
			
		||||
    let resultListOptions;
 | 
			
		||||
 | 
			
		||||
    if (this.displays.query.errorContainer != undefined)  {
 | 
			
		||||
      this.displays.query.errorContainer.innerHTML == "";
 | 
			
		||||
    }
 | 
			
		||||
    if (this.displays.query != undefined)  {
 | 
			
		||||
      this.displays.query.setVisibilityByStatus("waiting");
 | 
			
		||||
    }
 | 
			
		||||
    nopaque.socket.emit("corpus_analysis_query", queryStr);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCallback(type, callback) {
 | 
			
		||||
    // saves callback functions into an object. Key is function type, callback
 | 
			
		||||
    // is the callback function
 | 
			
		||||
    this.callbacks[type] = callback;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDisplay(type, display) {
 | 
			
		||||
    this.displays[type] = display;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CorpusAnalysisDisplay {
 | 
			
		||||
  constructor(element) {
 | 
			
		||||
    this.element = element;
 | 
			
		||||
    this.errorContainer = element.querySelector(".error-container");
 | 
			
		||||
    this.showOnError = element.querySelectorAll(".show-on-error");
 | 
			
		||||
    this.showOnSuccess = element.querySelectorAll(".show-on-success");
 | 
			
		||||
    this.showWhileWaiting = element.querySelectorAll(".show-while-waiting");
 | 
			
		||||
    this.hideOnComplete = element.querySelectorAll(".hide-on-complete")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setVisibilityByStatus(status) {
 | 
			
		||||
    switch (status) {
 | 
			
		||||
      case "error":
 | 
			
		||||
        for (let element of this.showOnError) {
 | 
			
		||||
          element.classList.remove("hide");
 | 
			
		||||
        }
 | 
			
		||||
        for (let element of this.showOnSuccess) {
 | 
			
		||||
          element.classList.add("hide");
 | 
			
		||||
        }
 | 
			
		||||
        for (let element of this.showWhileWaiting) {
 | 
			
		||||
          element.classList.add("hide");
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case "success":
 | 
			
		||||
        for (let element of this.showOnError) {
 | 
			
		||||
          element.classList.add("hide");
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        for (let element of this.showOnSuccess) {
 | 
			
		||||
          element.classList.remove("hide");
 | 
			
		||||
        }
 | 
			
		||||
        for (let element of this.showWhileWaiting) {
 | 
			
		||||
          element.classList.add("hide");
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case "waiting":
 | 
			
		||||
        for (let element of this.showOnError) {
 | 
			
		||||
          element.classList.add("hide");
 | 
			
		||||
        }
 | 
			
		||||
        for (let element of this.showOnSuccess) {
 | 
			
		||||
          element.classList.add("hide");
 | 
			
		||||
        }
 | 
			
		||||
        for (let element of this.showWhileWaiting) {
 | 
			
		||||
          element.classList.remove("hide");
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        // Hide all
 | 
			
		||||
        for (let element of this.showOnError) {
 | 
			
		||||
          element.classList.add("hide");
 | 
			
		||||
        }
 | 
			
		||||
        for (let element of this.showOnSuccess) {
 | 
			
		||||
          element.classList.add("hide");
 | 
			
		||||
        }
 | 
			
		||||
        for (let element of this.showWhileWaiting) {
 | 
			
		||||
          element.classList.add("hide");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,64 +0,0 @@
 | 
			
		||||
class InteractionElement {
 | 
			
		||||
  constructor(htmlId="",
 | 
			
		||||
              checkStatus=true,
 | 
			
		||||
              disabledBefore=true,
 | 
			
		||||
              disabledAfter=false,
 | 
			
		||||
              hideBefore=true,
 | 
			
		||||
              hideAfter=false) {
 | 
			
		||||
    this.htmlId = htmlId;
 | 
			
		||||
    this.element = (htmlId) => {this.element = document.getElementById(htmlId);}
 | 
			
		||||
    this.checkStatus = checkStatus;
 | 
			
		||||
    this.callbacks = {};
 | 
			
		||||
    this.disabledBefore = disabledBefore;
 | 
			
		||||
    this.disabledAfter = disabledAfter;
 | 
			
		||||
    this.hideBefore = hideBefore;
 | 
			
		||||
    this.hideAfter = hideAfter;
 | 
			
		||||
    this.element(this.htmlId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCallback(trigger, callback, bindThis, args=[]) {
 | 
			
		||||
    this.callbacks[trigger] = {
 | 
			
		||||
                                 "function": callback,
 | 
			
		||||
                                 "bindThis": bindThis,
 | 
			
		||||
                                 "args": args
 | 
			
		||||
                              };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bindThisToCallback(trigger) {
 | 
			
		||||
    let callback = this.callbacks[trigger];
 | 
			
		||||
    let boundedCallback = callback["function"].bind(callback.bindThis);
 | 
			
		||||
    return boundedCallback;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class InteractionElements {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.interactions = [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addInteractions (interactionsArray) {
 | 
			
		||||
    this.interactions.push(...interactionsArray);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChangeExecute() {
 | 
			
		||||
    // checks if a change for every interactionElement happens and executes
 | 
			
		||||
    // the callbacks accordingly
 | 
			
		||||
    for (let interaction of this.interactions) {
 | 
			
		||||
      if (interaction.checkStatus) {
 | 
			
		||||
        interaction.element.addEventListener("change", (event) => {
 | 
			
		||||
          if (event.target.checked) {
 | 
			
		||||
            let f_on = interaction.bindThisToCallback("on");
 | 
			
		||||
            let args_on = interaction.callbacks.on.args;
 | 
			
		||||
            f_on(...args_on);
 | 
			
		||||
          } else if (!event.target.checked){
 | 
			
		||||
            let f_off = interaction.bindThisToCallback("off");
 | 
			
		||||
            let args_off = interaction.callbacks.off.args;
 | 
			
		||||
            f_off(...args_off);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,125 +0,0 @@
 | 
			
		||||
class Results {
 | 
			
		||||
  constructor(data, jsList , metaData) {
 | 
			
		||||
  this.data = data;
 | 
			
		||||
  this.jsList = jsList;
 | 
			
		||||
  this.metaData = metaData
 | 
			
		||||
  this.resultsData = new Data();
 | 
			
		||||
  this.subResultsData = new Data();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearAll() {
 | 
			
		||||
    this.jsList.clear();
 | 
			
		||||
    this.jsList.update();
 | 
			
		||||
    this.data.init();
 | 
			
		||||
    this.metaData.init();
 | 
			
		||||
    this.resultsData.init()
 | 
			
		||||
    this.subResultsData.init();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Data {
 | 
			
		||||
  // Sets empty object structure. Also usefull to delete old results.
 | 
			
		||||
  // matchCount default is 0
 | 
			
		||||
  init(matchCount = 0) {
 | 
			
		||||
    this["matches"] = [];  // list of all c with lc and rc
 | 
			
		||||
    this["cpos_lookup"] = {};  // object contains all this key value pair
 | 
			
		||||
    this["text_lookup"] = {};  // same as above for all text ids
 | 
			
		||||
    this["match_count"] = matchCount;
 | 
			
		||||
    this["corpus_type"] = "results";
 | 
			
		||||
    this["query"] = "";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addData(jsonData, key=null) {
 | 
			
		||||
    if (key !== null) {
 | 
			
		||||
      Object.assign(this[key], jsonData);
 | 
			
		||||
    } else if (key === null) {
 | 
			
		||||
      Object.assign(this, jsonData)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // get query as string from form Element
 | 
			
		||||
  getQueryStr(queryFormElement) {
 | 
			
		||||
    // gets query
 | 
			
		||||
    let queryFormData;
 | 
			
		||||
    let queryStr;
 | 
			
		||||
    queryFormData = new FormData(queryFormElement);
 | 
			
		||||
    queryStr = queryFormData.get("query-form-query");
 | 
			
		||||
    this["query"] = queryStr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // function creates a unique and safe filename for the download
 | 
			
		||||
  createDownloadFilename(suffix) {
 | 
			
		||||
    let today;
 | 
			
		||||
    let currentDate;
 | 
			
		||||
    let currentTime;
 | 
			
		||||
    let safeFilename;
 | 
			
		||||
    let resultFilename;
 | 
			
		||||
    // get and create metadata
 | 
			
		||||
    today = new Date();
 | 
			
		||||
    currentDate = `${today.getUTCFullYear()}` +
 | 
			
		||||
                  `-${(today.getUTCMonth() + 1)}` +
 | 
			
		||||
                  `-${today.getUTCDate()}`;
 | 
			
		||||
    currentTime = `${today.getUTCHours()}h` +
 | 
			
		||||
                  `${today.getUTCMinutes()}m` +
 | 
			
		||||
                  `${today.getUTCSeconds()}s`;
 | 
			
		||||
    safeFilename = this.query.replace(/[^a-z0-9_-]/gi, "_");
 | 
			
		||||
    resultFilename = `UTC-${currentDate}_${currentTime}_${safeFilename}_${suffix}`;
 | 
			
		||||
    return resultFilename
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Function to download data as Blob created from string
 | 
			
		||||
  // should be private but that is not yet a feature of javascript 08.04.2020
 | 
			
		||||
  download(downloadElement, dataStr, filename, type, filenameSlug) {
 | 
			
		||||
    console.log("Start Download!");
 | 
			
		||||
    let file;
 | 
			
		||||
    filename += filenameSlug;
 | 
			
		||||
    file = new Blob([dataStr], {type: type});
 | 
			
		||||
        var url = URL.createObjectURL(file);
 | 
			
		||||
        downloadElement.href = url;
 | 
			
		||||
        downloadElement.download = filename;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // function to download the results as JSON
 | 
			
		||||
  downloadJSONRessource(resultFilename, downloadData, downloadElement) {
 | 
			
		||||
    let dataStr;
 | 
			
		||||
    // stringify JSON object for json download
 | 
			
		||||
    // use tabs to save some space
 | 
			
		||||
    dataStr = JSON.stringify(downloadData, undefined, "\t");
 | 
			
		||||
    // start actual download
 | 
			
		||||
    this.download(downloadElement, dataStr, resultFilename, "text/json", ".json")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // create results data either from all results or from al lmarked sub results
 | 
			
		||||
  createResultsData(type) {
 | 
			
		||||
    // deactivate inspect, because cqp server cannot handle multiple requests
 | 
			
		||||
    results.jsList.deactivateInspect();
 | 
			
		||||
    activateInspectInteraction.setCallback("noCheck",
 | 
			
		||||
                                            results.jsList.deactivateInspect,
 | 
			
		||||
                                            results.jsList);
 | 
			
		||||
    // set flag that results are being created to avoid reactivation of
 | 
			
		||||
    // sub results creation if marked matches are changed
 | 
			
		||||
    resultCreationRunning = true;
 | 
			
		||||
    console.log(resultCreationRunning);
 | 
			
		||||
    if (type === "sub-results") {
 | 
			
		||||
      resultsCreateElement.classList.add("disabled");  // cqp server cannot handle more than one request at a time. Thus we deactivate the resultsCreateElement
 | 
			
		||||
      let tmp = [...results.jsList.addToSubResultsIdsToShow].sort(function(a, b){return a-b});
 | 
			
		||||
      let dataIndexes = [];
 | 
			
		||||
      tmp.forEach((index) => dataIndexes.push(index - 1));
 | 
			
		||||
      results.jsList.getMatchWithContext(dataIndexes, "sub-results");
 | 
			
		||||
    } else if (type === "results") {
 | 
			
		||||
      subResultsCreateElement.classList.add("disabled");  // cqp server cannot handle more than one request at a time. Thus we deactivate the subResultsCreateElement
 | 
			
		||||
      let dataIndexes = [...Array(results.data.match_count).keys()];
 | 
			
		||||
      results.jsList.getMatchWithContext(dataIndexes, "results");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MetaData {
 | 
			
		||||
  // Sets empty object structure when no input is given.
 | 
			
		||||
  // if json object like input is given class fields are created from this
 | 
			
		||||
  init(json = {}) {
 | 
			
		||||
    Object.assign(this, json);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -423,764 +423,4 @@ RessourceList.options = {
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResultsList extends List {
 | 
			
		||||
  constructor(idOrElement, options={}) {
 | 
			
		||||
  super(idOrElement, options);
 | 
			
		||||
  this.eventTokens = {};  // all span tokens which are holdeing events if expert
 | 
			
		||||
  // mode is on. Collected here to delete later on
 | 
			
		||||
  this.currentExpertTokenElements = {}; // all token elements which have added
 | 
			
		||||
  // classes like chip and hoverable for expert view. Collected
 | 
			
		||||
  //here to delete later on
 | 
			
		||||
  this.addToSubResultsStatus = {};  // holds True/false for check buttons used to add matches tu sub-results. If checked, it is True. If unchecked, it is false. Buttons for this have the class add. Those little round check buttons.
 | 
			
		||||
  this.addToSubResultsIdsToShow = new Set();  // If check button is pressed its corresponding data_index is saved in this set. The set is shown to the user.
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  helperCreateCpos(cpos_ranges, cpos_values) {
 | 
			
		||||
    let lc;
 | 
			
		||||
    let c;
 | 
			
		||||
    let rc;
 | 
			
		||||
    if (cpos_ranges) {
 | 
			
		||||
      // python range like function from MDN
 | 
			
		||||
      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Sequence_generator_(range)
 | 
			
		||||
      const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));
 | 
			
		||||
      lc = range(cpos_values.lc[0], cpos_values.lc[1], 1)
 | 
			
		||||
      c = range(cpos_values.c[0], cpos_values.c[1], 1)
 | 
			
		||||
      rc = range(cpos_values.rc[0], cpos_values.rc[1], 1)
 | 
			
		||||
    } else {
 | 
			
		||||
      lc = cpos_values.lc;
 | 
			
		||||
      c = cpos_values.c;
 | 
			
		||||
      rc = cpos_values.rc;
 | 
			
		||||
    }
 | 
			
		||||
    return {lc: lc, c: c, rc: rc};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // handels interactionElements during a pagination navigation
 | 
			
		||||
  // loops over interactionElements and executes callback functions accordingly
 | 
			
		||||
  pageChangeEventInteractionHandler(interactionElements) {
 | 
			
		||||
    // get elements to check thier status
 | 
			
		||||
    for (let interaction of interactionElements.interactions) {
 | 
			
		||||
      if (interaction.checkStatus) {
 | 
			
		||||
        if (interaction.element.checked) {
 | 
			
		||||
          let f_on = interaction.bindThisToCallback("on");
 | 
			
		||||
          let args_on = interaction.callbacks.on.args;
 | 
			
		||||
          f_on(...args_on);
 | 
			
		||||
        } else {
 | 
			
		||||
          let f_off = interaction.bindThisToCallback("off");
 | 
			
		||||
          let args_off = interaction.callbacks.off.args;
 | 
			
		||||
          f_off(...args_off);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        let f = interaction.bindThisToCallback("noCheck");
 | 
			
		||||
        let args = interaction.callbacks.noCheck.args;
 | 
			
		||||
        f(...args);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // get display options from display options form element
 | 
			
		||||
  static getDisplayOptions(displayOptionsFormElement) {
 | 
			
		||||
    // gets display options parameters
 | 
			
		||||
    let displayOptionsFormData
 | 
			
		||||
    let displayOptionsData;
 | 
			
		||||
    displayOptionsFormData = new FormData(displayOptionsFormElement);
 | 
			
		||||
    displayOptionsData =
 | 
			
		||||
      {
 | 
			
		||||
        "resultsPerPage": displayOptionsFormData.get("display-options-form-results_per_page"),
 | 
			
		||||
        "resultsContex": displayOptionsFormData.get("display-options-form-result_context"),
 | 
			
		||||
        "expertMode": displayOptionsFormData.get("display-options-form-expert_mode")
 | 
			
		||||
      };
 | 
			
		||||
    return displayOptionsData
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // ###### Functions to add one match to a sub-results ######
 | 
			
		||||
  // activate the add buttons
 | 
			
		||||
  activateAddToSubResults() {
 | 
			
		||||
    subResultsIdListElement.classList.remove("hide");
 | 
			
		||||
    if (subResultsExportElement.classList.contains("hide")) {
 | 
			
		||||
      subResultsCreateElement.classList.remove("hide");
 | 
			
		||||
    }
 | 
			
		||||
    let addToSubResultsBtnElements = document.getElementsByClassName("add");
 | 
			
		||||
    for (let addToSubResultsBtn of addToSubResultsBtnElements) {
 | 
			
		||||
      addToSubResultsBtn.classList.remove("hide");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // deactivate the add buttons
 | 
			
		||||
  deactivateAddToSubResults() {
 | 
			
		||||
    subResultsIdListElement.classList.add("hide");
 | 
			
		||||
    subResultsCreateElement.classList.add("hide");
 | 
			
		||||
    let addToSubResultsBtnElements = document.getElementsByClassName("add");
 | 
			
		||||
    for (let addToSubResultsBtn of addToSubResultsBtnElements) {
 | 
			
		||||
      addToSubResultsBtn.classList.add("hide");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Used in addToSubResults and inspect to toggle the design of the check
 | 
			
		||||
  // buttons according to its checked unchecked status.
 | 
			
		||||
  helperActivateBtn(btn) {
 | 
			
		||||
    btn.classList.remove("grey");
 | 
			
		||||
    btn.classList.add("green");
 | 
			
		||||
    btn.textContent = "check";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Used in addToSubResults and inspect to toggle the design of the check
 | 
			
		||||
  // buttons according to its checked unchecked status.
 | 
			
		||||
  helperDeactivateBtn(btn) {
 | 
			
		||||
    btn.classList.remove("green");
 | 
			
		||||
    btn.classList.add("grey");
 | 
			
		||||
    btn.textContent = "add";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Either adds or removes a match to the sub-results. For this it checks
 | 
			
		||||
  // onclick if the current button has been checked or not. For this the
 | 
			
		||||
  // function checks if its status in addToSubResultsStatus is either flase or
 | 
			
		||||
  // true. Adds match to sub-results if status is false if status is true it
 | 
			
		||||
  // removes it.
 | 
			
		||||
  addToSubResults(dataIndex, tableCall=true) {
 | 
			
		||||
    let textarea = subResultsIdListElement.getElementsByTagName("textarea")[0];
 | 
			
		||||
    if (!this.addToSubResultsStatus[dataIndex]
 | 
			
		||||
        || this.addToSubResultsStatus === undefined) {
 | 
			
		||||
      // add button is activated because status is either false or undefined
 | 
			
		||||
      this.helperActivateBtn(event.target);
 | 
			
		||||
      this.addToSubResultsStatus[dataIndex] = true;  // sets status to true
 | 
			
		||||
      this.addToSubResultsIdsToShow.add(dataIndex + 1);  // + 1 because user does not see zero indexd data indexes
 | 
			
		||||
      textarea.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", ");  // automaticalle sorts ids into the textarea in ascending order
 | 
			
		||||
      M.textareaAutoResize(textarea);  // after an insert textarea has to be resized manually
 | 
			
		||||
      nrMarkedMatches.textContent = [...this.addToSubResultsIdsToShow].length;
 | 
			
		||||
    } else if (this.addToSubResultsStatus[dataIndex]) {
 | 
			
		||||
      // add button is deactivated because status is true
 | 
			
		||||
      this.helperDeactivateBtn(event.target);
 | 
			
		||||
      this.addToSubResultsStatus[dataIndex] = false;  // sets status to false
 | 
			
		||||
      this.addToSubResultsIdsToShow.delete(dataIndex + 1);  // + 1 because user does not see zero indexd data indexes
 | 
			
		||||
      textarea.textContent = [...this.addToSubResultsIdsToShow].sort(function(a, b){return a-b}).join(", ");  // automaticalle sorts ids into the textarea in ascending order
 | 
			
		||||
      nrMarkedMatches.textContent = [...this.addToSubResultsIdsToShow].length;
 | 
			
		||||
      M.textareaAutoResize(textarea); // after an insert textarea has to be resized manually
 | 
			
		||||
    }
 | 
			
		||||
    // Toggles the create button according to the number of ids in addToSubResultsIdsToShow
 | 
			
		||||
    if ([...this.addToSubResultsIdsToShow].length > 0) {
 | 
			
		||||
      subResultsCreateElement.classList.remove("disabled");
 | 
			
		||||
    } else if ([...this.addToSubResultsIdsToShow].length === 0) {
 | 
			
		||||
      subResultsCreateElement.classList.add("disabled");
 | 
			
		||||
    }
 | 
			
		||||
    if (resultCreationRunning) {
 | 
			
		||||
      subResultsCreateElement.classList.add("disabled");
 | 
			
		||||
    }
 | 
			
		||||
    // After a match as been added or removed the export button will be
 | 
			
		||||
    // hidden because the sub-results have been altered and have to be built
 | 
			
		||||
    // again. Thus subResultsCreateElement has to be shown again.
 | 
			
		||||
    subResultsExportElement.classList.add("hide");
 | 
			
		||||
    subResultsCreateElement.classList.remove("hide");
 | 
			
		||||
    // Also activate/deactivate buttons in the table/jsList results accordingly
 | 
			
		||||
    //if button in inspect was activated/deactivated.
 | 
			
		||||
    // This part only runs if tableCall is false.
 | 
			
		||||
    if (!tableCall) {
 | 
			
		||||
      let tableAddBtn = document.getElementById("query-results").querySelectorAll(`[data-index="${dataIndex}"]`)[0].getElementsByClassName('add')[0].firstElementChild; // gets the add button from the list view
 | 
			
		||||
      if (this.addToSubResultsStatus[dataIndex]) {
 | 
			
		||||
        this.helperActivateBtn(tableAddBtn);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.helperDeactivateBtn(tableAddBtn);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Triggers emit to get full match context from server for a number of
 | 
			
		||||
  // matches identified by their data_index.
 | 
			
		||||
  getMatchWithContext(dataIndexes, type) {
 | 
			
		||||
    let tmp_first_cpos = [];
 | 
			
		||||
    let tmp_last_cpos = [];
 | 
			
		||||
    for (let dataIndex of dataIndexes) {
 | 
			
		||||
      tmp_first_cpos.push(results.data.matches[dataIndex].c[0]);
 | 
			
		||||
      tmp_last_cpos.push(results.data.matches[dataIndex].c[1]);
 | 
			
		||||
    }
 | 
			
		||||
    nopaque.socket.emit("corpus_analysis_inspect_match",
 | 
			
		||||
                      {
 | 
			
		||||
                        type: type,
 | 
			
		||||
                        data_indexes: dataIndexes,
 | 
			
		||||
                        first_cpos: tmp_first_cpos,
 | 
			
		||||
                        last_cpos: tmp_last_cpos,
 | 
			
		||||
                      }
 | 
			
		||||
          );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // ###### Functions to inspect one match, to show more details ######
 | 
			
		||||
  // activate inspect buttons if progress is 100
 | 
			
		||||
  activateInspect() {
 | 
			
		||||
    if (progress === 100) {
 | 
			
		||||
      let inspectBtnElements;
 | 
			
		||||
      inspectBtnElements = document.getElementsByClassName("inspect");
 | 
			
		||||
      for (let inspectBtn of inspectBtnElements) {
 | 
			
		||||
        inspectBtn.classList.remove("disabled");
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // deactivate inspect buttons
 | 
			
		||||
  deactivateInspect() {
 | 
			
		||||
    let inspectBtnElements;
 | 
			
		||||
    inspectBtnElements = document.getElementsByClassName("inspect");
 | 
			
		||||
    for (let inspectBtn of inspectBtnElements) {
 | 
			
		||||
      inspectBtn.classList.add("disabled");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // ### functions to inspect imported Matches
 | 
			
		||||
  // This function creates an object that is similar to the object that is
 | 
			
		||||
  // being recieved as an answere to the getMatchWithContext Method, which is
 | 
			
		||||
  // triggering an socket.io event.
 | 
			
		||||
  // It is used as an input for show match context in the context of imported
 | 
			
		||||
  // results to be able to inspect matches.
 | 
			
		||||
  createFakeResponse() {
 | 
			
		||||
    contextModal.open();
 | 
			
		||||
    // match nr for user to display derived from data_index
 | 
			
		||||
    let contextMatchNrElement = document.getElementById("context-match-nr");
 | 
			
		||||
    contextMatchNrElement.textContent = this.contextId + 1;
 | 
			
		||||
    let cpos_lookup;
 | 
			
		||||
    let fake_response = {};
 | 
			
		||||
    let contextResultsElement;
 | 
			
		||||
    // function to create one match object from entire imported results
 | 
			
		||||
    // that is passed into the results.jsList.showMatchContext() function
 | 
			
		||||
    fake_response["payload"] = {};
 | 
			
		||||
    let dataIndex = event.target.closest("tr").dataset.index;
 | 
			
		||||
    this.contextId = dataIndex;
 | 
			
		||||
    fake_response.payload["matches"] = [results.data.matches[dataIndex]];
 | 
			
		||||
    contextResultsElement = document.getElementById("context-results");
 | 
			
		||||
    contextResultsElement.innerHTML = "";
 | 
			
		||||
    let {lc, c, rc} = this.helperCreateCpos(results.data.cpos_ranges,
 | 
			
		||||
                                            fake_response.payload.matches[0]);
 | 
			
		||||
    cpos_lookup = {};
 | 
			
		||||
    for (let cpos of lc) {
 | 
			
		||||
      cpos_lookup[cpos] = results.data.cpos_lookup[cpos];
 | 
			
		||||
    }
 | 
			
		||||
    for (let cpos of c) {
 | 
			
		||||
      cpos_lookup[cpos] = results.data.cpos_lookup[cpos];
 | 
			
		||||
    }
 | 
			
		||||
    for (let cpos of rc) {
 | 
			
		||||
      cpos_lookup[cpos] = results.data.cpos_lookup[cpos];
 | 
			
		||||
    }
 | 
			
		||||
    fake_response.payload["cpos_lookup"] = cpos_lookup
 | 
			
		||||
    fake_response.payload["cpos_ranges"] = results.data.cpos_ranges;
 | 
			
		||||
    fake_response.payload["query"] = results.data.query;
 | 
			
		||||
    fake_response.payload["context_id"] = dataIndex + 1;
 | 
			
		||||
    fake_response.payload["match_count"] = fake_response.payload.matches.length
 | 
			
		||||
    fake_response.payload["corpus_type"] = "inspect-result"
 | 
			
		||||
    return fake_response
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // gets result cpos infos for one dataIndex (list of length 1) to send back to
 | 
			
		||||
  // the server
 | 
			
		||||
  inspect(dataIndex, type) {
 | 
			
		||||
    let contextResultsElement;
 | 
			
		||||
    // get result infos from server and show them in context modal
 | 
			
		||||
    this.contextId = dataIndex[0];
 | 
			
		||||
    contextResultsElement = document.getElementById("context-results");
 | 
			
		||||
    contextResultsElement.innerHTML = "";  // clear it from old inspects
 | 
			
		||||
    this.getMatchWithContext(dataIndex, type);
 | 
			
		||||
    // match nr for user to display derived from data_index
 | 
			
		||||
    let contextMatchNrElement = document.getElementById("context-match-nr");
 | 
			
		||||
    contextMatchNrElement.textContent = this.contextId + 1;
 | 
			
		||||
    contextModal.open();
 | 
			
		||||
    // add a button to add this match to sub results with onclick event
 | 
			
		||||
    let classes = `btn-floating btn waves-effect` +
 | 
			
		||||
                  `waves-light grey right`
 | 
			
		||||
    let addToSubResultsIdsBtn = document.createElement("a");
 | 
			
		||||
    addToSubResultsIdsBtn.setAttribute("class", classes + ` add`);
 | 
			
		||||
    addToSubResultsIdsBtn.innerHTML = '<i class="material-icons">add</i>';
 | 
			
		||||
    addToSubResultsIdsBtn.onclick= () => {this.addToSubResults(dataIndex[0], false)};
 | 
			
		||||
    // checks if a button has already been added to the inspect modal and removes it
 | 
			
		||||
    if (addToSubResultsFromInspectElement.children.length > 0) {
 | 
			
		||||
      addToSubResultsFromInspectElement.firstElementChild.remove();
 | 
			
		||||
    }
 | 
			
		||||
    // Changes the design of the add button according to its checked status
 | 
			
		||||
    // upon opening the inspect modal.
 | 
			
		||||
    if (this.addToSubResultsStatus[dataIndex[0]]) {
 | 
			
		||||
      this.helperActivateBtn(addToSubResultsIdsBtn.firstElementChild);
 | 
			
		||||
    } else if (!this.addToSubResultsStatus[dataIndex[0]]) {
 | 
			
		||||
      this.helperDeactivateBtn(addToSubResultsIdsBtn.firstElementChild);
 | 
			
		||||
    }
 | 
			
		||||
    addToSubResultsFromInspectElement.appendChild(addToSubResultsIdsBtn);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // create Element from HTML String helper function
 | 
			
		||||
  HTMLTStrToElement(htmlStr) {
 | 
			
		||||
    // https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
 | 
			
		||||
    let template = document.createElement("template");
 | 
			
		||||
    htmlStr = htmlStr.trim();
 | 
			
		||||
    template.innerHTML = htmlStr;
 | 
			
		||||
    return template.content.firstChild;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  // Used as a callback to handle incoming match context results when inspect
 | 
			
		||||
  // has been used.
 | 
			
		||||
  showMatchContext(response) {
 | 
			
		||||
    this.contextData;
 | 
			
		||||
    let contextModalLoading;
 | 
			
		||||
    let contextModalReady;
 | 
			
		||||
    let contextResultsElement;
 | 
			
		||||
    let highlightSentencesSwitchElement;
 | 
			
		||||
    let htmlTokenStr;
 | 
			
		||||
    let modalExpertModeSwitchElement;
 | 
			
		||||
    let modalTokenElements;
 | 
			
		||||
    let nrOfContextSentences;
 | 
			
		||||
    let partElement;
 | 
			
		||||
    let token;
 | 
			
		||||
    let tokenHTMLArray;
 | 
			
		||||
    let tokenHTMlElement;
 | 
			
		||||
    let uniqueContextS;
 | 
			
		||||
    let uniqueS;
 | 
			
		||||
 | 
			
		||||
    this.contextData = response.payload;
 | 
			
		||||
    console.log(this.contextData);
 | 
			
		||||
    this.contextData["cpos_ranges"] = response.payload.cpos_ranges;
 | 
			
		||||
    this.contextData["query"] = results.data.query;
 | 
			
		||||
    this.contextData["context_id"] = this.contextId;
 | 
			
		||||
    this.contextData["match_count"] = this.contextData.matches.length
 | 
			
		||||
    this.contextData["corpus_type"] = "inspect-result"
 | 
			
		||||
    Object.assign(this.contextData, results.metaData);
 | 
			
		||||
    contextResultsElement = document.getElementById("context-results");
 | 
			
		||||
    modalExpertModeSwitchElement = document.getElementById("inspect-display-options-form-expert_mode_inspect");
 | 
			
		||||
    highlightSentencesSwitchElement = document.getElementById("inspect-display-options-form-highlight_sentences");
 | 
			
		||||
    nrOfContextSentences = document.getElementById("context-sentences");
 | 
			
		||||
    uniqueS = new Set();
 | 
			
		||||
    uniqueContextS = new Set();
 | 
			
		||||
    let {lc, c, rc} = this.helperCreateCpos(this.contextData.cpos_ranges,
 | 
			
		||||
                                            this.contextData.matches[0])
 | 
			
		||||
    // create sentence strings as tokens
 | 
			
		||||
    tokenHTMLArray = [];
 | 
			
		||||
    for (let cpos of lc) {
 | 
			
		||||
      token = this.contextData.cpos_lookup[cpos];
 | 
			
		||||
      uniqueS.add(token.s)
 | 
			
		||||
      htmlTokenStr = `<span class="token"` +
 | 
			
		||||
                           `data-sid="${token.s}"` +
 | 
			
		||||
                           `data-cpos="${cpos}">` +
 | 
			
		||||
                       `${token.word}` +
 | 
			
		||||
                     `</span>`;
 | 
			
		||||
      tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
 | 
			
		||||
      tokenHTMLArray.push(tokenHTMlElement);
 | 
			
		||||
    }
 | 
			
		||||
    for (let cpos of c) {
 | 
			
		||||
      token = this.contextData.cpos_lookup[cpos];
 | 
			
		||||
      uniqueContextS.add(token.s);
 | 
			
		||||
      uniqueS.add(token.s);
 | 
			
		||||
      htmlTokenStr = `<span class="token bold light-green"` +
 | 
			
		||||
                           `data-sid="${token.s}"` +
 | 
			
		||||
                           `data-cpos="${cpos}"` +
 | 
			
		||||
                           `style="text-decoration-line: underline;">` +
 | 
			
		||||
                       `${token.word}` +
 | 
			
		||||
                     `</span>`;
 | 
			
		||||
      tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
 | 
			
		||||
      tokenHTMLArray.push(tokenHTMlElement);
 | 
			
		||||
    }
 | 
			
		||||
    this.contextData["context_s_ids"] = Array.from(uniqueContextS);
 | 
			
		||||
    for (let cpos of rc) {
 | 
			
		||||
      token = this.contextData.cpos_lookup[cpos];
 | 
			
		||||
      uniqueS.add(token.s)
 | 
			
		||||
      htmlTokenStr = `<span class="token"` +
 | 
			
		||||
                           `data-sid="${token.s}"` +
 | 
			
		||||
                           `data-cpos="${cpos}">` +
 | 
			
		||||
                       `${token.word}` +
 | 
			
		||||
                     `</span>`;
 | 
			
		||||
      tokenHTMlElement = this.HTMLTStrToElement(htmlTokenStr)
 | 
			
		||||
      tokenHTMLArray.push(tokenHTMlElement);
 | 
			
		||||
    }
 | 
			
		||||
    for (let sId of uniqueS) {
 | 
			
		||||
      let htmlSentence = `<span class="sentence" data-sid="${sId}"></span>`;
 | 
			
		||||
      let sentenceElement = this.HTMLTStrToElement(htmlSentence);
 | 
			
		||||
      for (let tokenElement of tokenHTMLArray) {
 | 
			
		||||
        if (tokenElement.dataset.sid == sId) {
 | 
			
		||||
          sentenceElement.appendChild(tokenElement);
 | 
			
		||||
          sentenceElement.insertAdjacentHTML("beforeend", ` `);
 | 
			
		||||
        } else {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      contextResultsElement.appendChild(sentenceElement);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // add inspect display options events
 | 
			
		||||
    modalExpertModeSwitchElement.onchange = (event) => {
 | 
			
		||||
      if (event.target.checked) {
 | 
			
		||||
        this.expertModeOn("context-results");
 | 
			
		||||
      } else {
 | 
			
		||||
        this.expertModeOff("context-results")
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    highlightSentencesSwitchElement.onchange = (event) => {
 | 
			
		||||
      if (event.target.checked) {
 | 
			
		||||
        this.higlightContextSentences();
 | 
			
		||||
      } else {
 | 
			
		||||
      this.unhighlightContextSentences();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    nrOfContextSentences.onchange = (event) => {
 | 
			
		||||
      // console.log(event.target.value);
 | 
			
		||||
      this.changeSentenceContext(event.target.value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // checks on new modal opening if switches are checked
 | 
			
		||||
    // if switches are checked functions are executed
 | 
			
		||||
    if (modalExpertModeSwitchElement.checked) {
 | 
			
		||||
      this.expertModeOn("context-results");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (highlightSentencesSwitchElement.checked) {
 | 
			
		||||
      this.higlightContextSentences();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // checks the value of the number of sentences to show on modal opening
 | 
			
		||||
    // sets context sentences accordingly
 | 
			
		||||
    this.changeSentenceContext(nrOfContextSentences.value)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // splits context text into sentences based on spacy sentence split
 | 
			
		||||
  higlightContextSentences() {
 | 
			
		||||
    let sentences;
 | 
			
		||||
    sentences = document.getElementById("context-results").getElementsByClassName("sentence");
 | 
			
		||||
      for (let s of sentences) {
 | 
			
		||||
        s.insertAdjacentHTML("beforeend", `<span><br><br></span>`)
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  unhighlightContextSentences() {
 | 
			
		||||
    let sentences;
 | 
			
		||||
    let br;
 | 
			
		||||
    sentences = document.getElementById("context-results").getElementsByClassName("sentence");
 | 
			
		||||
      for (let s of sentences) {
 | 
			
		||||
        br = s.lastChild;
 | 
			
		||||
        br.remove();
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // changes how many context sentences in inspect view are shown
 | 
			
		||||
  changeSentenceContext(sValue, maxSValue=10) {
 | 
			
		||||
    let array;
 | 
			
		||||
    let sentences;
 | 
			
		||||
    let toHideArray;
 | 
			
		||||
    let toShowArray;
 | 
			
		||||
    sValue = maxSValue - sValue;
 | 
			
		||||
    // console.log(sValue);
 | 
			
		||||
    sentences = document.getElementById("context-results").getElementsByClassName("sentence");
 | 
			
		||||
    array = Array.from(sentences);
 | 
			
		||||
    if (sValue != 0) {
 | 
			
		||||
      toHideArray = array.slice(0, sValue).concat(array.slice(-(sValue)));
 | 
			
		||||
      toShowArray = array.slice(sValue, 9).concat(array.slice(9, -(sValue)))
 | 
			
		||||
    } else {
 | 
			
		||||
      toHideArray = [];
 | 
			
		||||
      toShowArray = array;
 | 
			
		||||
    }
 | 
			
		||||
    // console.log(array);
 | 
			
		||||
    // console.log("#######");
 | 
			
		||||
    // console.log(toHideArray);
 | 
			
		||||
    for (let s of toHideArray) {
 | 
			
		||||
      s.classList.add("hide");
 | 
			
		||||
    }
 | 
			
		||||
    for (let s of toShowArray) {
 | 
			
		||||
      s.classList.remove("hide");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // ###### Display options changing live how the matches are being displayed ######
 | 
			
		||||
 | 
			
		||||
  // Event function that changes the shown hits per page.
 | 
			
		||||
  // Just alters the resultsList.page property
 | 
			
		||||
  changeHitsPerPage(event) {
 | 
			
		||||
    try {
 | 
			
		||||
      // console.log(this);
 | 
			
		||||
      this.page = event.target.value;
 | 
			
		||||
      this.update();
 | 
			
		||||
      this.activateInspect();
 | 
			
		||||
      this.pageChangeEventInteractionHandler(interactionElements);
 | 
			
		||||
      if (expertModeSwitchElement.checked) {
 | 
			
		||||
        this.expertModeOn("query-display");  // page holds new result rows, so add new tooltips
 | 
			
		||||
      }
 | 
			
		||||
      nopaque.flash("Updated matches per page.", "corpus")
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      // console.log(e);
 | 
			
		||||
      // console.log("resultsList has no results right now.");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Event function triggered on context select change
 | 
			
		||||
  // also if pagination is clicked
 | 
			
		||||
  changeContext(event) {
 | 
			
		||||
    let array;
 | 
			
		||||
    let lc;
 | 
			
		||||
    let newContextValue;
 | 
			
		||||
    let rc;
 | 
			
		||||
    try {
 | 
			
		||||
        if (event.type === "change") {
 | 
			
		||||
            nopaque.flash("Updated context per match!", "corpus");
 | 
			
		||||
        }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
    } finally {
 | 
			
		||||
        newContextValue = document.getElementById("display-options-form-result_context").value;
 | 
			
		||||
        lc = document.getElementsByClassName("left-context");
 | 
			
		||||
        rc = document.getElementsByClassName("right-context");
 | 
			
		||||
        for (let element of lc) {
 | 
			
		||||
          array = Array.from(element.childNodes);
 | 
			
		||||
          for (let element of array.reverse().slice(newContextValue)) {
 | 
			
		||||
            element.classList.add("hide");
 | 
			
		||||
          }
 | 
			
		||||
          for (let element of array.slice(0, newContextValue)) {
 | 
			
		||||
            element.classList.remove("hide");
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        for (let element of rc) {
 | 
			
		||||
          array = Array.from(element.childNodes);
 | 
			
		||||
          for (let element of array.slice(newContextValue)) {
 | 
			
		||||
            element.classList.add("hide");
 | 
			
		||||
          }
 | 
			
		||||
          for (let element of array.slice(0, newContextValue)) {
 | 
			
		||||
            element.classList.remove("hide");
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // ###### Expert view event functions ######
 | 
			
		||||
  // function to create a tooltip for the current hovered token
 | 
			
		||||
  tooltipEventCreate(event) {
 | 
			
		||||
    // console.log("Create Tooltip on mouseover.");
 | 
			
		||||
    let token;
 | 
			
		||||
    token = results.data.cpos_lookup[event.target.dataset.cpos];
 | 
			
		||||
    if (!token) {
 | 
			
		||||
      token = this.contextData.cpos_lookup[event.target.dataset.cpos];
 | 
			
		||||
    }
 | 
			
		||||
    this.addToolTipToTokenElement(event.target, token);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Function to destroy the current Tooltip for the current hovered tooltip
 | 
			
		||||
  // on mouse leave
 | 
			
		||||
  tooltipEventDestroy(event) {
 | 
			
		||||
    // console.log("Tooltip destroy on leave.");
 | 
			
		||||
    this.currentTooltipElement.destroy();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  expertModeOn(htmlId) {
 | 
			
		||||
    // turn the expert mode on for all tokens in the DOM element identified by its htmlID
 | 
			
		||||
    if (!Array.isArray(this.currentExpertTokenElements[htmlId])) {
 | 
			
		||||
      this.currentExpertTokenElements[htmlId] = [];
 | 
			
		||||
    }
 | 
			
		||||
    let container = document.getElementById(htmlId);
 | 
			
		||||
    let tokens = container.querySelectorAll("span.token");
 | 
			
		||||
    this.currentExpertTokenElements[htmlId].push(...tokens);
 | 
			
		||||
    this.tooltipEventCreateBind = this.tooltipEventCreate.bind(this);
 | 
			
		||||
    this.tooltipEventDestroyBind = this.tooltipEventDestroy.bind(this);
 | 
			
		||||
    this.eventTokens[htmlId] = [];
 | 
			
		||||
    for (let tokenElement of this.currentExpertTokenElements[htmlId]) {
 | 
			
		||||
      tokenElement.classList.add("chip", "hoverable", "expert-view");
 | 
			
		||||
      tokenElement.onmouseover = this.tooltipEventCreateBind;
 | 
			
		||||
      tokenElement.onmouseout = this.tooltipEventDestroyBind;
 | 
			
		||||
      this.eventTokens[htmlId].push(tokenElement);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // fuction that creates Tooltip for one token and extracts the corresponding
 | 
			
		||||
  // infos from the result JSON
 | 
			
		||||
  addToolTipToTokenElement(tokenElement, token) {
 | 
			
		||||
    this.currentTooltipElement;
 | 
			
		||||
    this.currentTooltipElement = M.Tooltip.init(tokenElement,
 | 
			
		||||
     {"html": `<table>
 | 
			
		||||
                 <tr>
 | 
			
		||||
                   <th>Token information</th>
 | 
			
		||||
                   <th>Source information</th>
 | 
			
		||||
                 </tr>
 | 
			
		||||
                 <tr>
 | 
			
		||||
                   <td class="left-align">
 | 
			
		||||
                     Word: ${token.word}<br>
 | 
			
		||||
                     Lemma: ${token.lemma}<br>
 | 
			
		||||
                     POS: ${token.pos}<br>
 | 
			
		||||
                     Simple POS: ${token.simple_pos}<br>
 | 
			
		||||
                     NER: ${token.ner}
 | 
			
		||||
                   </td>
 | 
			
		||||
                   <td class="left-align">
 | 
			
		||||
                     Title: ${results.data.text_lookup[token.text].title}
 | 
			
		||||
                     <br>
 | 
			
		||||
                     Author: ${results.data.text_lookup[token.text].author}
 | 
			
		||||
                     <br>
 | 
			
		||||
                     Publishing year: ${results.data.text_lookup[token.text].publishing_year}
 | 
			
		||||
                   </td>
 | 
			
		||||
                 </tr>
 | 
			
		||||
               </table>`}
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // function to remove extra informations and animations from tokens
 | 
			
		||||
  expertModeOff(htmlId) {
 | 
			
		||||
    // console.log("Expert mode is off.");
 | 
			
		||||
    if (!Array.isArray(this.currentExpertTokenElements[htmlId])) {
 | 
			
		||||
      this.currentExpertTokenElements[htmlId] = [];
 | 
			
		||||
    }
 | 
			
		||||
    if (!Array.isArray(this.eventTokens[htmlId])) {
 | 
			
		||||
      this.eventTokens[htmlId] = [];
 | 
			
		||||
    }
 | 
			
		||||
    for (let tokenElement of this.currentExpertTokenElements[htmlId]) {
 | 
			
		||||
      tokenElement.classList.remove("chip", "hoverable", "expert-view");
 | 
			
		||||
    }
 | 
			
		||||
    this.currentExpertTokenElements[htmlId] = [];
 | 
			
		||||
 | 
			
		||||
    for (let eventToken of this.eventTokens[htmlId]) {
 | 
			
		||||
      eventToken.onmouseover = "";
 | 
			
		||||
      eventToken.onmouseout = "";
 | 
			
		||||
    }
 | 
			
		||||
  this.eventTokens[htmlId] = [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createResultRowElement(item, chunk, imported=false) {
 | 
			
		||||
    let aCellElement;
 | 
			
		||||
    let addToSubResultsBtn;
 | 
			
		||||
    let cCellElement;
 | 
			
		||||
    let cpos;
 | 
			
		||||
    let fakeResponse;   // used if imported results are being created;
 | 
			
		||||
    let inspectBtn
 | 
			
		||||
    let lcCellElement;
 | 
			
		||||
    let matchNrElement;
 | 
			
		||||
    let matchRowElement;
 | 
			
		||||
    let rcCellElement;
 | 
			
		||||
    let textTitles;
 | 
			
		||||
    let textTitlesCellElement;
 | 
			
		||||
    let token;
 | 
			
		||||
    let values;
 | 
			
		||||
    // gather values from item
 | 
			
		||||
    values = item.values();
 | 
			
		||||
    let {lc, c, rc} = this.helperCreateCpos(chunk.cpos_ranges,
 | 
			
		||||
                                            values)
 | 
			
		||||
    // get infos for full match row
 | 
			
		||||
    matchRowElement = document.createElement("tr");
 | 
			
		||||
    matchRowElement.setAttribute("data-index", values.index)
 | 
			
		||||
    lcCellElement = document.createElement("td");
 | 
			
		||||
    lcCellElement.classList.add("left-context");
 | 
			
		||||
    matchRowElement.appendChild(lcCellElement);
 | 
			
		||||
    for (cpos of lc) {
 | 
			
		||||
      token = chunk.cpos_lookup[cpos];
 | 
			
		||||
      lcCellElement.insertAdjacentHTML("beforeend",
 | 
			
		||||
        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // get infos for hit of match and set actions
 | 
			
		||||
    textTitles = new Set();
 | 
			
		||||
    aCellElement = document.createElement("td");
 | 
			
		||||
    aCellElement.classList.add("actions");
 | 
			
		||||
    cCellElement = document.createElement("td");
 | 
			
		||||
    cCellElement.classList.add("match-hit");
 | 
			
		||||
    textTitlesCellElement = document.createElement("td");
 | 
			
		||||
    textTitlesCellElement.classList.add("titles");
 | 
			
		||||
    matchNrElement = document.createElement("td");
 | 
			
		||||
    matchNrElement.classList.add("match-nr");
 | 
			
		||||
    matchRowElement.appendChild(cCellElement);
 | 
			
		||||
    matchRowElement.appendChild(aCellElement);
 | 
			
		||||
    for (cpos of c) {
 | 
			
		||||
      token = chunk.cpos_lookup[cpos];
 | 
			
		||||
      cCellElement.insertAdjacentHTML("beforeend",
 | 
			
		||||
        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
 | 
			
		||||
      // get text titles of every hit cpos token
 | 
			
		||||
      textTitles.add(chunk.text_lookup[token.text].title);
 | 
			
		||||
    }
 | 
			
		||||
    // add some interaction buttons
 | 
			
		||||
    // # some btn css rules and classes
 | 
			
		||||
    let css = `margin-right: 5px; margin-bottom: 5px;`
 | 
			
		||||
    let classes = `btn-floating btn waves-effect` +
 | 
			
		||||
                  `waves-light grey`
 | 
			
		||||
    // # add button to trigger more context to every match td
 | 
			
		||||
    inspectBtn = document.createElement("a");
 | 
			
		||||
    inspectBtn.setAttribute("style", css);
 | 
			
		||||
    inspectBtn.setAttribute("class", classes + ` disabled inspect`
 | 
			
		||||
                            );
 | 
			
		||||
    inspectBtn.innerHTML = '<i class="material-icons inspect-btn">search</i>';
 | 
			
		||||
    // # add btn to add matches to sub-results. hidden per default
 | 
			
		||||
    addToSubResultsBtn = document.createElement("a");
 | 
			
		||||
    addToSubResultsBtn.setAttribute("style", css);
 | 
			
		||||
    addToSubResultsBtn.setAttribute("class", classes + ` hide add`
 | 
			
		||||
                                );
 | 
			
		||||
    addToSubResultsBtn.innerHTML = '<i class="material-icons add-btn">add</i>';
 | 
			
		||||
    aCellElement.appendChild(inspectBtn);
 | 
			
		||||
    aCellElement.appendChild(addToSubResultsBtn);
 | 
			
		||||
    // add text titles at front as first td of one row
 | 
			
		||||
    textTitlesCellElement.textContent = [...textTitles].join(", ");
 | 
			
		||||
    matchRowElement.insertAdjacentHTML("afterbegin", textTitlesCellElement.outerHTML);
 | 
			
		||||
    matchNrElement.textContent = values.index + 1;
 | 
			
		||||
    matchRowElement.insertAdjacentHTML("afterbegin", matchNrElement.outerHTML);
 | 
			
		||||
 | 
			
		||||
    // get infos for right context of match
 | 
			
		||||
    rcCellElement = document.createElement("td");
 | 
			
		||||
    rcCellElement.classList.add("right-context");
 | 
			
		||||
    matchRowElement.appendChild(rcCellElement);
 | 
			
		||||
    for (cpos of rc) {
 | 
			
		||||
      token = chunk.cpos_lookup[cpos];
 | 
			
		||||
      rcCellElement.insertAdjacentHTML("beforeend",
 | 
			
		||||
        `<span class="token" data-cpos="${cpos}">${token.word} </span>`);
 | 
			
		||||
    }
 | 
			
		||||
    return matchRowElement
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // creates the HTML table code for the metadata vie in the corpus analysis interface
 | 
			
		||||
  createMetaDataForModal(metaDataObject) {
 | 
			
		||||
    let html = `<div class="col s12">
 | 
			
		||||
                      <table class="highlight">
 | 
			
		||||
                        <thead>
 | 
			
		||||
                          <tr>
 | 
			
		||||
                            <th>Metadata Description</th>
 | 
			
		||||
                            <th>Value</th>
 | 
			
		||||
                          </tr>
 | 
			
		||||
                        </thead>
 | 
			
		||||
                        <tbody>`
 | 
			
		||||
    for (let [outerKey, outerValue] of Object.entries(metaDataObject)) {
 | 
			
		||||
      html += `<tr>
 | 
			
		||||
                  <td style="text-transform: uppercase;">${outerKey.replace(/_/g, " ")}</td>`
 | 
			
		||||
      if (outerKey === "corpus_all_texts" || outerKey === "text_lookup") {
 | 
			
		||||
        html += `<td>
 | 
			
		||||
                  <ul class="collapsible">`
 | 
			
		||||
        for (let [innerKey, innerValue] of Object.entries(outerValue)) {
 | 
			
		||||
          html += `<li class="text-metadata"
 | 
			
		||||
                        data-metadata-key="${outerKey}"
 | 
			
		||||
                        data-text-key="${innerKey}">
 | 
			
		||||
                      <div class="collapsible-header"
 | 
			
		||||
                           data-metadata-key="${outerKey}"
 | 
			
		||||
                           data-text-key="${innerKey}">
 | 
			
		||||
                        <i class="material-icons"
 | 
			
		||||
                           data-metadata-key="${outerKey}"
 | 
			
		||||
                           data-text-key="${innerKey}">info_outline</i>
 | 
			
		||||
                           ${innerValue['author']} - ${innerValue['publishing_year']} -
 | 
			
		||||
                           ${innerValue['title']}
 | 
			
		||||
                      </div>
 | 
			
		||||
                      <div class="collapsible-body">
 | 
			
		||||
                        <span>
 | 
			
		||||
                          <ul id="bibliographic-data-${outerKey}-${innerKey}"
 | 
			
		||||
                              style="column-count: 2;">
 | 
			
		||||
                          </ul>
 | 
			
		||||
                        </span>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </li>`
 | 
			
		||||
        }
 | 
			
		||||
        html += `</ul>
 | 
			
		||||
                  </td>`
 | 
			
		||||
      } else {
 | 
			
		||||
        html += `<td>${outerValue}</td>`
 | 
			
		||||
      }
 | 
			
		||||
      html += `</tr>`
 | 
			
		||||
    }
 | 
			
		||||
    html += `</tbody>
 | 
			
		||||
              </table>`
 | 
			
		||||
    return html
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Creates the text details for the texts shown in the corpus analysis metadata modal.
 | 
			
		||||
  createTextDetails(metaDataObject) {
 | 
			
		||||
    let metadataKey = event.target.dataset.metadataKey;
 | 
			
		||||
    let textKey = event.target.dataset.textKey;
 | 
			
		||||
    let textData = metaDataObject[metadataKey][textKey];
 | 
			
		||||
    let bibliographicData = document.getElementById(`bibliographic-data-${metadataKey}-${textKey}`);
 | 
			
		||||
    bibliographicData.innerHTML = "";
 | 
			
		||||
    for (let [key, value] of Object.entries(textData)) {
 | 
			
		||||
      bibliographicData.insertAdjacentHTML("afterbegin",
 | 
			
		||||
      `
 | 
			
		||||
      <li><span style="text-transform: capitalize;">${key}:</span> ${value}</li>
 | 
			
		||||
      `);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export { RessourceList, };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										83
									
								
								web/app/static/js/web-components/InfoMenu.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								web/app/static/js/web-components/InfoMenu.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
/**
 | 
			
		||||
 * HTML for showing infos about the current query or result. Also gives
 | 
			
		||||
 * the user the abiltiy to access the meta data for the current query or
 | 
			
		||||
 * result.
 | 
			
		||||
 */
 | 
			
		||||
const template = document.createElement('template');
 | 
			
		||||
template.innerHTML = `
 | 
			
		||||
 | 
			
		||||
  <link rel="stylesheet" href="../../static/fonts/Material_design_icons/material-icons.css">
 | 
			
		||||
  <link rel="stylesheet" href="../../static/css/Materialize/materialize.min.css">
 | 
			
		||||
  <link rel="stylesheet" href="../../static/css/nopaque.css">
 | 
			
		||||
 | 
			
		||||
  <div class="col">
 | 
			
		||||
    <h6 style="margin-top: 0px;">Infos</h6>
 | 
			
		||||
    <div class="divider" style="margin-bottom: 10px;"></div>
 | 
			
		||||
    <div class="row">
 | 
			
		||||
      <div class="col s12">
 | 
			
		||||
        <button id="show-metadata"
 | 
			
		||||
                class="waves-effect
 | 
			
		||||
                       waves-light
 | 
			
		||||
                       btn-flat
 | 
			
		||||
                       flat-interaction"
 | 
			
		||||
                type="submit">Corpus Metadata
 | 
			
		||||
          <i class="material-icons left">info_outline</i>
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col s12">
 | 
			
		||||
        <p>
 | 
			
		||||
          <slot name="received-match-count">
 | 
			
		||||
            0
 | 
			
		||||
          </slot> of
 | 
			
		||||
          <slot name="match-count">(to be determined)</slot>
 | 
			
		||||
          matches loaded.
 | 
			
		||||
          <br>
 | 
			
		||||
          Matches occured in
 | 
			
		||||
          <slot name="text-lookup-count">(to be determined)</slot>
 | 
			
		||||
          corpus files:
 | 
			
		||||
          <br>
 | 
			
		||||
          <slot name=text-titles>(to be determined)</slot>
 | 
			
		||||
        </p>
 | 
			
		||||
        <p id="query-results-user-feedback">
 | 
			
		||||
          <i class="material-icons">help</i>
 | 
			
		||||
          The Server is still sending your results.
 | 
			
		||||
          Functions like "Export Results" and "Match Inspect" will be
 | 
			
		||||
          available after all matches have been loaded.
 | 
			
		||||
        </p>
 | 
			
		||||
        <div class="progress" id="query-progress-bar">
 | 
			
		||||
          <div class="determinate"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
class InfoMenu extends HTMLElement {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
 | 
			
		||||
    this.appendChild(template.content.cloneNode(true));
 | 
			
		||||
    this.attachShadow({ mode: 'open' });
 | 
			
		||||
    this.shadowRoot.appendChild(template.content.cloneNode(true));
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // methods that will be used in connectedCallback on eventListeners
 | 
			
		||||
  showMetadata() {
 | 
			
		||||
    console.log('Show metadata somehow');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  connectedCallback() {
 | 
			
		||||
    const showMetadataBtn = this.querySelector('#show-metadata');
 | 
			
		||||
    showMetadataBtn.addEventListener('click', () => this.showMetadata());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  disconnectedCallback() {
 | 
			
		||||
    const showMetadataBtn = this.querySelector('#show-metadata');
 | 
			
		||||
    showMetadataBtn.removeEventListener();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
window.customElements.define('info-menu', InfoMenu);
 | 
			
		||||
 | 
			
		||||
export { InfoMenu };
 | 
			
		||||
@@ -41,8 +41,7 @@
 | 
			
		||||
<div class="col s12" id="query-display">
 | 
			
		||||
  <div class="card">
 | 
			
		||||
    <div class="card-content" id="result-list" style="overflow: hidden;">
 | 
			
		||||
      <div class="error-container hide show-on-error"></div>
 | 
			
		||||
      <div class=" row hide show-on-success">
 | 
			
		||||
      <div id="interactions-menu" class="row hide">
 | 
			
		||||
        {% include 'interactions/infos.html.j2' %}
 | 
			
		||||
        {% include 'interactions/export.html.j2' %}
 | 
			
		||||
        {% include 'interactions/create.html.j2' %}
 | 
			
		||||
@@ -65,376 +64,304 @@
 | 
			
		||||
{% include 'modals/export_query_results.html.j2' %}
 | 
			
		||||
{% include 'modals/context_modal.html.j2' %}
 | 
			
		||||
 | 
			
		||||
<!-- import modules -->
 | 
			
		||||
<script type="module">
 | 
			
		||||
/**
 | 
			
		||||
 * First Phase:
 | 
			
		||||
 * Document content is loaded and scripts are being imported and executed.
 | 
			
		||||
 */
 | 
			
		||||
 // import Client classes
 | 
			
		||||
import {
 | 
			
		||||
  Client,
 | 
			
		||||
  ClientEventListener,
 | 
			
		||||
  ListenerCallback,
 | 
			
		||||
} from '../../static/js/modules/corpus_analysis/client/Client.js';
 | 
			
		||||
// import client listener functions
 | 
			
		||||
import {
 | 
			
		||||
  recieveConnected,
 | 
			
		||||
  recieveMetaData,
 | 
			
		||||
  recieveQueryStatus,
 | 
			
		||||
  recieveQueryData,
 | 
			
		||||
  recieveViewNotification,
 | 
			
		||||
  recieveResultsData,
 | 
			
		||||
} from '../../static/js/modules/corpus_analysis/client/listeners.js';
 | 
			
		||||
// import client listener callbacks
 | 
			
		||||
import {
 | 
			
		||||
  prepareQueryData,
 | 
			
		||||
  saveQueryData,
 | 
			
		||||
  saveMetaData,
 | 
			
		||||
  getResultsData,
 | 
			
		||||
  saveResultsData,
 | 
			
		||||
} from '../../static/js/modules/corpus_analysis/client/callbacks.js';
 | 
			
		||||
import {
 | 
			
		||||
  Results,
 | 
			
		||||
} from '../../static/js/modules/corpus_analysis/model/Results.js';
 | 
			
		||||
import {
 | 
			
		||||
  ViewEventListener,
 | 
			
		||||
  ResultsList,
 | 
			
		||||
} from '../../static/js/modules/corpus_analysis/view/ResultsView.js';
 | 
			
		||||
import {
 | 
			
		||||
  recieveClientNotification,
 | 
			
		||||
} from '../../static/js/modules/corpus_analysis/view/listeners.js';
 | 
			
		||||
import {
 | 
			
		||||
  scrollToTop,
 | 
			
		||||
} from '../../static/js/modules/corpus_analysis/view/scrollToTop.js'
 | 
			
		||||
import {
 | 
			
		||||
  loadingSpinnerHTML,
 | 
			
		||||
} from '../../static/js/modules/corpus_analysis/view/spinner.js'
 | 
			
		||||
 | 
			
		||||
<script src="{{ url_for('static', filename='js/nopaque.CorpusAnalysisClient.js') }}">
 | 
			
		||||
</script>
 | 
			
		||||
<script src="{{ url_for('static', filename='js/nopaque.Results.js') }}">
 | 
			
		||||
</script>
 | 
			
		||||
<script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}">
 | 
			
		||||
</script>
 | 
			
		||||
<script src="{{ url_for('static', filename='js/nopaque.InteractionElement.js') }}">
 | 
			
		||||
</script>
 | 
			
		||||
<script>
 | 
			
		||||
// ###### Defining global variables used in other functions ######
 | 
			
		||||
  var addToSubResultsElement; // Button to start adding matches to sub-results
 | 
			
		||||
  var addToSubResultsFromInspectElement; // button in inspect mdoal to add this match to the sub results
 | 
			
		||||
  var client;  // CorpusAnalysisClient first undefined on DOMContentLoaded defined
 | 
			
		||||
  var collapsibleElements;  // All collapsibleElements on this page
 | 
			
		||||
  var contextModal;  // Modal to open on inspect for further match context
 | 
			
		||||
  var data;  // full JSON object holding match results
 | 
			
		||||
  var expertModeSwitchElement; // Expert mode switch Element
 | 
			
		||||
  var initDisplay;  // CorpusAnalysisDisplay object first undfined on DOMContentLoaded defined
 | 
			
		||||
  var interactionElements;  // Interaction elements and their parameters
 | 
			
		||||
  var matchCountElement;  // Total nr. of matches will be displayed in this element
 | 
			
		||||
  var progress;  // global progress value
 | 
			
		||||
  var queryDisplay; // CorpusAnalysisDisplay object first undfined on DOMContentLoaded defined
 | 
			
		||||
  var queryFormElement;  // the query form
 | 
			
		||||
  var queryResultsDeterminateElement;  // The progress bar for recieved results
 | 
			
		||||
  var resultsExportElement;  // Download button opens download modal
 | 
			
		||||
  var queryResultsProgressElement;  // Div element holding the progress bar
 | 
			
		||||
  var queryResultsUserFeedbackElement;  // Element showing match count|total etc
 | 
			
		||||
  var receivedMatchCountElement;  // Nr. of loaded matches will be displayed in this element
 | 
			
		||||
  var results;  // results object
 | 
			
		||||
  var resultsList;  // resultsList object
 | 
			
		||||
  var resultsListOptions;  // specifies ResultsList options
 | 
			
		||||
  var subResultsCreateElement; // if pressed sub results will be created from ids
 | 
			
		||||
  var resultsCreateElement; // if pressed results will pe created for all matches
 | 
			
		||||
  var subResultsExportElement;  // button to download sub results
 | 
			
		||||
  var subResultsIdListElement;  // list showing marked matches for sub corpus creation
 | 
			
		||||
  var textLookupCountElement  // Nr of texts the matches occured in will be shown in this element
 | 
			
		||||
  var textTitlesElement;  // matched text titles
 | 
			
		||||
  var nrMarkedMatches;  // count of matches marked for subresults
 | 
			
		||||
  var showMetaDataButton;  // Button to show corpus metadata
 | 
			
		||||
  var activateInspectInteraction;  // global interaction element
 | 
			
		||||
  var expertModeInteraction;  // global interaction element
 | 
			
		||||
  var subResultsInteraction;  // global interaction element
 | 
			
		||||
  var changeContextInteraction;  // global interaction element
 | 
			
		||||
  var resultCreationRunning;
 | 
			
		||||
 | 
			
		||||
  // ###### Defining local scope variables ######
 | 
			
		||||
  let contextPerItemElement;  // Form Element for display option
 | 
			
		||||
  let contextSentencesElement;  // Form Element for display option in inspect
 | 
			
		||||
  let displayOptionsData;  // Getting form data from display options
 | 
			
		||||
  let displayOptionsFormElement;  // Form holding the display informations
 | 
			
		||||
  let downloadResultsJSONElement;  // button for downloading results as JSON
 | 
			
		||||
  let downloadInspectContextElement;  // button for downloading inspect context
 | 
			
		||||
  let exportModal;  // Download options modal
 | 
			
		||||
  let firstPageElement;  // first page element of resultsList pagination
 | 
			
		||||
  let hitsPerPageInputElement;
 | 
			
		||||
  let initDisplayElement;  // Element for initialization using initDisplay
 | 
			
		||||
  let initModal;
 | 
			
		||||
  let paginationElements;
 | 
			
		||||
  let queryDisplayElement;  // Element for initialization using queryDisplay
 | 
			
		||||
  let xpath;  // xpath to grab first resultsList page pagination element
 | 
			
		||||
  let metaDataModal;  // modal showing corpus meta data
 | 
			
		||||
 | 
			
		||||
  // ###### Initialize variables ######
 | 
			
		||||
  addToSubResultsElement = document.getElementById("add-to-sub-results");
 | 
			
		||||
  addToSubResultsFromInspectElement = document.getElementById("add-to-sub-results-from-inspect");
 | 
			
		||||
  client = undefined;
 | 
			
		||||
  collapsibleElements = document.querySelector('.collapsible.expandable');
 | 
			
		||||
  contextModal = document.getElementById("context-modal");
 | 
			
		||||
  contextPerItemElement = document.getElementById("display-options-form-result_context");
 | 
			
		||||
  contextSentencesElement = document.getElementById("context-sentences");
 | 
			
		||||
  displayOptionsFormElement = document.getElementById("display-options-form");
 | 
			
		||||
  expertModeSwitchElement = document.getElementById("display-options-form-expert_mode");
 | 
			
		||||
  exportModal = document.getElementById("query-results-download-modal");
 | 
			
		||||
  hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page");
 | 
			
		||||
  initDisplay = undefined;
 | 
			
		||||
  initDisplayElement = document.getElementById("init-display");
 | 
			
		||||
  matchCountElement = document.getElementById("match-count");
 | 
			
		||||
  paginationElements = document.getElementsByClassName("pagination");
 | 
			
		||||
  queryDisplay = undefined;
 | 
			
		||||
  queryDisplayElement = document.getElementById("query-display");
 | 
			
		||||
  queryFormElement = document.getElementById("query-form");
 | 
			
		||||
  queryResultsDeterminateElement = document.getElementById("query-results-determinate");
 | 
			
		||||
  resultsExportElement = document.getElementById("query-results-export");
 | 
			
		||||
  queryResultsProgressElement = document.getElementById("query-results-progress");
 | 
			
		||||
  queryResultsUserFeedbackElement = document.getElementById("query-results-user-feedback");
 | 
			
		||||
  receivedMatchCountElement = document.getElementById("received-match-count");
 | 
			
		||||
  subResultsCreateElement = document.getElementById("sub-results-create");
 | 
			
		||||
  resultsCreateElement = document.getElementById("results-create");
 | 
			
		||||
  subResultsExportElement = document.getElementById("sub-results-export");
 | 
			
		||||
  subResultsIdListElement = document.getElementById("sub-results-match-ids-div");
 | 
			
		||||
  textLookupCountElement = document.getElementById("text-lookup-count");
 | 
			
		||||
  textTitlesElement = document.getElementById("text-titles");
 | 
			
		||||
  nrMarkedMatches = document.getElementById("nr-marked-matches");
 | 
			
		||||
  showMetaDataButton = document.getElementById("show-metadata");
 | 
			
		||||
  metaDataModal = document.getElementById("meta-data-modal");
 | 
			
		||||
 | 
			
		||||
  // ###### js list options and intialization ######
 | 
			
		||||
  displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement);
 | 
			
		||||
  resultsListOptions = {page: displayOptionsData["resultsPerPage"],
 | 
			
		||||
    pagination: [{
 | 
			
		||||
      name: "paginationTop",
 | 
			
		||||
      paginationClass: "paginationTop",
 | 
			
		||||
      innerWindow: 8,
 | 
			
		||||
      outerWindow: 1
 | 
			
		||||
    }, {
 | 
			
		||||
      paginationClass: "paginationBottom",
 | 
			
		||||
      innerWindow: 8,
 | 
			
		||||
      outerWindow: 1
 | 
			
		||||
    }],
 | 
			
		||||
    valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}],
 | 
			
		||||
    item: `<span></span>`};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  // ###### event on DOMContentLoaded ######
 | 
			
		||||
  document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
    // creates some modals on DOMContentLoaded
 | 
			
		||||
    let defaultOptions = {"dismissible": true,
 | 
			
		||||
                          "preventScrolling": false};
 | 
			
		||||
    contextModal = M.Modal.init(contextModal, defaultOptions);
 | 
			
		||||
    exportModal = M.Modal.init(exportModal, defaultOptions);
 | 
			
		||||
    initModal = M.Modal.init(initDisplayElement, {"dismissible": false});
 | 
			
		||||
    let deleteOverlay = () => {
 | 
			
		||||
      let overlay = document.getElementsByClassName("modal-overlay")[0];
 | 
			
		||||
      overlay.remove();
 | 
			
		||||
    };
 | 
			
		||||
    metaDataModal = M.Modal.init(metaDataModal, {"preventScrolling": false,
 | 
			
		||||
                                 "opacity": 0.0,
 | 
			
		||||
                                 "dismissible": false,
 | 
			
		||||
                                 "onOpenEnd": deleteOverlay});
 | 
			
		||||
    // Init corpus analysis components
 | 
			
		||||
    data = new Data();
 | 
			
		||||
    resultsList = new ResultsList("result-list", resultsListOptions);
 | 
			
		||||
    resultsMetaData = new MetaData();
 | 
			
		||||
    results = new Results(data, resultsList, resultsMetaData);
 | 
			
		||||
    initDisplay = new CorpusAnalysisDisplay(initDisplayElement);
 | 
			
		||||
    queryDisplay = new CorpusAnalysisDisplay(queryDisplayElement);
 | 
			
		||||
    client = new CorpusAnalysisClient({{ corpus_id }}, nopaque.socket);
 | 
			
		||||
    initModal.open();
 | 
			
		||||
 | 
			
		||||
    // set displays and callback functions
 | 
			
		||||
    client.setDisplay("init", initDisplay);
 | 
			
		||||
    client.setCallback("init", () => {
 | 
			
		||||
      initModal.close();
 | 
			
		||||
    });
 | 
			
		||||
    client.setCallback('get_metadata', () => {
 | 
			
		||||
      client.getMetaData();
 | 
			
		||||
    })
 | 
			
		||||
    client.setCallback('recv_meta_data', (response) => {
 | 
			
		||||
      recvMetaData(response);
 | 
			
		||||
    })
 | 
			
		||||
    client.setDisplay("query", queryResultsUserFeedbackElement);
 | 
			
		||||
    client.setDisplay("query", queryDisplay);
 | 
			
		||||
    client.setCallback("query", (payload) => {
 | 
			
		||||
      querySetup(payload);
 | 
			
		||||
    });
 | 
			
		||||
    client.setCallback("query_results", (payload) => {
 | 
			
		||||
      queryRenderResults(payload);
 | 
			
		||||
    });
 | 
			
		||||
    client.setCallback("query_match_context", (payload) => {
 | 
			
		||||
      results.jsList.showMatchContext(payload);
 | 
			
		||||
    });
 | 
			
		||||
    client.setCallback("save_sub_results_choices", (payload) => {
 | 
			
		||||
      saveSubResultsChoices(payload);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Trigger corpus analysis initialization on server side
 | 
			
		||||
    client.init();
 | 
			
		||||
    // start a query request on submit
 | 
			
		||||
    queryFormElement.addEventListener("submit", (event) => {
 | 
			
		||||
      try {
 | 
			
		||||
        // Selects first page of result list if pagination is already available
 | 
			
		||||
        // from an query submitted before.
 | 
			
		||||
        // This avoids confusion for the user eg: The user was on page 24
 | 
			
		||||
        // reviewing the results and issues a new query. He would not see any
 | 
			
		||||
        // results until the new results reach page 24 or he clicks on another
 | 
			
		||||
        // valid result page element from the new pagination.
 | 
			
		||||
        firstPageElement;
 | 
			
		||||
        xpath = '//a[@class="page" and text()=1]';
 | 
			
		||||
        firstPageElement = document.evaluate(xpath,
 | 
			
		||||
                                             document,
 | 
			
		||||
                                             null,
 | 
			
		||||
                                             XPathResult.FIRST_ORDERED_NODE_TYPE,
 | 
			
		||||
                                             null).singleNodeValue;
 | 
			
		||||
        firstPageElement.click();
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
      }
 | 
			
		||||
      // Prevent page from reloading on submit
 | 
			
		||||
      event.preventDefault();
 | 
			
		||||
      // Get query string and send query to server
 | 
			
		||||
      results.data.getQueryStr(queryFormElement);
 | 
			
		||||
      client.query(results.data.query);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // live update of hits per page if hits per page value is changed
 | 
			
		||||
    let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);
 | 
			
		||||
    hitsPerPageInputElement.onchange = changeHitsPerPageBind;
 | 
			
		||||
 | 
			
		||||
    // live update of lr context per item if context value is changed
 | 
			
		||||
    contextPerItemElement.onchange = results.jsList.changeContext;
 | 
			
		||||
 | 
			
		||||
    // Initialization of interactionElemnts
 | 
			
		||||
    // An interactionElement is an object identifing a switch or button via
 | 
			
		||||
    // htmlID. Callbacks are set for these elements which will be triggered on
 | 
			
		||||
    // a pagination interaction by the user or if the status of the element has
 | 
			
		||||
    // been altered. (Like the switche has ben turned on or off).
 | 
			
		||||
    interactionElements = new InteractionElements();
 | 
			
		||||
    expertModeInteraction = new InteractionElement("display-options-form-expert_mode");
 | 
			
		||||
    expertModeInteraction.setCallback("on",
 | 
			
		||||
                                      results.jsList.expertModeOn,
 | 
			
		||||
                                      results.jsList,
 | 
			
		||||
                                      ["query-display"])
 | 
			
		||||
    expertModeInteraction.setCallback("off",
 | 
			
		||||
                                      results.jsList.expertModeOff,
 | 
			
		||||
                                      results.jsList,
 | 
			
		||||
                                      ["query-display"])
 | 
			
		||||
 | 
			
		||||
    subResultsInteraction = new InteractionElement("add-to-sub-results");
 | 
			
		||||
    subResultsInteraction.setCallback("on",
 | 
			
		||||
                                        results.jsList.activateAddToSubResults,
 | 
			
		||||
                                        results.jsList);
 | 
			
		||||
    subResultsInteraction.setCallback("off",
 | 
			
		||||
                                        results.jsList.deactivateAddToSubResults,
 | 
			
		||||
                                        results.jsList);
 | 
			
		||||
 | 
			
		||||
    activateInspectInteraction = new InteractionElement("inspect",
 | 
			
		||||
                                                            false);
 | 
			
		||||
    activateInspectInteraction.setCallback("noCheck",
 | 
			
		||||
                                            results.jsList.activateInspect,
 | 
			
		||||
                                            results.jsList);
 | 
			
		||||
 | 
			
		||||
    changeContextInteraction = new InteractionElement("display-options-form-results_per_page",
 | 
			
		||||
                                                          false);
 | 
			
		||||
    changeContextInteraction.setCallback("noCheck",
 | 
			
		||||
                                        results.jsList.changeContext,
 | 
			
		||||
                                        results.jsList)
 | 
			
		||||
    interactionElements.addInteractions([expertModeInteraction, subResultsInteraction, activateInspectInteraction, changeContextInteraction]);
 | 
			
		||||
 | 
			
		||||
    // checks if a change for every interactionElement happens and executes
 | 
			
		||||
    // the callbacks accordingly
 | 
			
		||||
    interactionElements.onChangeExecute();
 | 
			
		||||
 | 
			
		||||
    // eventListener if pagination is used to apply new context size to new page
 | 
			
		||||
    // and also activate inspect match if progress is 100
 | 
			
		||||
    // also adds more interaction buttons like add to sub results
 | 
			
		||||
    for (let element of paginationElements) {
 | 
			
		||||
      element.addEventListener("click", (event) => {
 | 
			
		||||
        results.jsList.pageChangeEventInteractionHandler(interactionElements);
 | 
			
		||||
      });
 | 
			
		||||
/**
 | 
			
		||||
 * Second Phase:
 | 
			
		||||
 * Asynchronus and event driven code
 | 
			
		||||
 */
 | 
			
		||||
document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
  // Initialize the client for server client communication in dynamic mode
 | 
			
		||||
  let corpusId = {{ corpus_id }}
 | 
			
		||||
  const client = new Client({'corpusId': corpusId,
 | 
			
		||||
                             'socket': nopaque.socket,
 | 
			
		||||
                             'logging': true,
 | 
			
		||||
                             'dynamicMode': true});
 | 
			
		||||
  /**
 | 
			
		||||
  * Initializing the results object as a model holding all the data of a query.
 | 
			
		||||
  * Also holds the metadata of one query.
 | 
			
		||||
  * After that initialize the ResultsList object as the View handeling the
 | 
			
		||||
  * represnetation of the data for the user.
 | 
			
		||||
  */
 | 
			
		||||
  let results = new Results();
 | 
			
		||||
  let resultsList = new ResultsList('result-list', ResultsList.options);
 | 
			
		||||
  /**
 | 
			
		||||
  * Register listeners listening to socket.io events and their callbacks
 | 
			
		||||
  * Afterwards load them. Also registers listeners listening for custom
 | 
			
		||||
  * javascript events.
 | 
			
		||||
  */
 | 
			
		||||
  const listenForConnected = new ClientEventListener('corpus_analysis_init',
 | 
			
		||||
                                                     recieveConnected);
 | 
			
		||||
  const listenForMetaData = new ClientEventListener('corpus_analysis_meta_data',
 | 
			
		||||
                                                    recieveMetaData);
 | 
			
		||||
  const metaDataCallback = new ListenerCallback('corpus_analysis_meta_data',
 | 
			
		||||
                                                saveMetaData,
 | 
			
		||||
                                                [client, results]);
 | 
			
		||||
  listenForMetaData.setCallbacks([metaDataCallback]);
 | 
			
		||||
  const listenForQueryStatus = new ClientEventListener('corpus_analysis_query',
 | 
			
		||||
                                                       recieveQueryStatus);
 | 
			
		||||
  const queryStatusCallback = new ListenerCallback('corpus_analysis_query',
 | 
			
		||||
                                                   prepareQueryData,
 | 
			
		||||
                                                   [client, results]);
 | 
			
		||||
  listenForQueryStatus.setCallbacks([queryStatusCallback]);
 | 
			
		||||
  const listenForQueryData = new ClientEventListener('corpus_analysis_query_results',
 | 
			
		||||
                                                     recieveQueryData);
 | 
			
		||||
  const queryDataCallback = new ListenerCallback('corpus_analysis_query_results',
 | 
			
		||||
                                                 saveQueryData,
 | 
			
		||||
                                                 [client, results]);
 | 
			
		||||
  listenForQueryData.setCallbacks([queryDataCallback]);
 | 
			
		||||
  const listenForResults = new ClientEventListener('corpus_analysis_inspect_match',
 | 
			
		||||
                                                   recieveResultsData);
 | 
			
		||||
  const resultsDataCallback = new ListenerCallback('corpus_analysis_inspect_match',
 | 
			
		||||
                                                   saveResultsData,
 | 
			
		||||
                                                   [client, results]);
 | 
			
		||||
  listenForResults.setCallbacks([resultsDataCallback]);
 | 
			
		||||
  // listen for javascript custom notifications
 | 
			
		||||
  const listenForViewNotification = new ClientEventListener('notify-client',
 | 
			
		||||
                                                            recieveViewNotification);
 | 
			
		||||
  const getResultsCallback = new ListenerCallback('get-results',
 | 
			
		||||
                                                  getResultsData,
 | 
			
		||||
                                                  [client, results]);
 | 
			
		||||
  listenForViewNotification.setCallbacks([getResultsCallback]);
 | 
			
		||||
  client.setSocketEventListeners([listenForConnected,
 | 
			
		||||
                                  listenForQueryStatus,
 | 
			
		||||
                                  listenForQueryData,
 | 
			
		||||
                                  listenForMetaData,
 | 
			
		||||
                                  listenForViewNotification,
 | 
			
		||||
                                  listenForResults]);
 | 
			
		||||
  client.loadSocketEventListeners();
 | 
			
		||||
  /**
 | 
			
		||||
  * Register resultsList listeners listening to notification events.
 | 
			
		||||
  */
 | 
			
		||||
  const listenForClientNotification = new ViewEventListener('notify-view',
 | 
			
		||||
                                                            recieveClientNotification);
 | 
			
		||||
  resultsList.setNotificationListeners([listenForClientNotification]);
 | 
			
		||||
  resultsList.loadNotificationListeners();
 | 
			
		||||
  // Connect client to server
 | 
			
		||||
  client.notifyView('connecting');
 | 
			
		||||
  client.connect();
 | 
			
		||||
  // Send a query and recieve its answer data
 | 
			
		||||
  let queryFormElement = document.querySelector('#query-form');
 | 
			
		||||
  queryFormElement.addEventListener('submit', (event) => {
 | 
			
		||||
    try {
 | 
			
		||||
      /**
 | 
			
		||||
       * Selects first page of result list if pagination is already available
 | 
			
		||||
       * from an query submitted before.
 | 
			
		||||
       * This avoids confusion for the user e.g.: The user was on page 24
 | 
			
		||||
       * reviewing the results and issues a new query. He would not see any
 | 
			
		||||
       * results until the new results reach page 24 or he clicks on another
 | 
			
		||||
       * valid result page element from the new pagination.
 | 
			
		||||
       */
 | 
			
		||||
      let firstPageElement = document.querySelector('a.page');
 | 
			
		||||
      firstPageElement.click();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      // No page element is present if first query is submitted.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ### Show corpus Metadata
 | 
			
		||||
    showMetaDataButton.onclick = () => {
 | 
			
		||||
      let metaDataObject = {};
 | 
			
		||||
      Object.assign(metaDataObject, results.metaData);
 | 
			
		||||
      metaDataObject["query"] = results.data.query;
 | 
			
		||||
      metaDataObject["text_lookup"] = results.data.text_lookup;
 | 
			
		||||
      metaDataModalContent = document.getElementById("meta-data-modal-content");
 | 
			
		||||
      metaDataModalContent.innerHTML = "";
 | 
			
		||||
      let table = results.jsList.createMetaDataForModal(metaDataObject);
 | 
			
		||||
      metaDataModalContent.insertAdjacentHTML("afterbegin", table);
 | 
			
		||||
      metaDataModal.open();
 | 
			
		||||
      let collapsibles = document.getElementsByClassName("text-metadata");
 | 
			
		||||
      for (let collapsible of collapsibles) {
 | 
			
		||||
        collapsible.onclick = () => {
 | 
			
		||||
          let elems = document.querySelectorAll('.collapsible');
 | 
			
		||||
          let instances = M.Collapsible.init(elems, {accordion: false});
 | 
			
		||||
          results.jsList.createTextDetails(metaDataObject);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Prevent page from reloading on submit
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    // Get query string and send query to server
 | 
			
		||||
    results.data.getQueryStr(queryFormElement);
 | 
			
		||||
    client.query(results.data.query);
 | 
			
		||||
  });
 | 
			
		||||
  // Get all needed HTMLElements for the following event listeners
 | 
			
		||||
  resultsList.getHTMLElements([
 | 
			
		||||
    '#display-options-form-results_per_page',
 | 
			
		||||
    '#display-options-form-result_context',
 | 
			
		||||
    '#show-meta-data',
 | 
			
		||||
    '#meta-data-modal',
 | 
			
		||||
    '#meta-data-modal-content',
 | 
			
		||||
    '#full-results-create',
 | 
			
		||||
    '#sub-results-create',
 | 
			
		||||
    '#full-results-export',
 | 
			
		||||
    '#sub-results-export',
 | 
			
		||||
    '#download-results-json',
 | 
			
		||||
    '#query-results-download-modal',
 | 
			
		||||
    '#query-results-table',
 | 
			
		||||
    '#display-options-form-expert_mode',
 | 
			
		||||
    '.pagination',
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  // new insepct event listener makeing use of javascript bubbleing
 | 
			
		||||
  let resultsTable = document.getElementById("query-results");
 | 
			
		||||
  console.log(resultsTable);
 | 
			
		||||
  resultsTable.addEventListener("click", (event) => {
 | 
			
		||||
    let dataIndex;
 | 
			
		||||
    if (event.target.classList.contains("inspect-btn")) {
 | 
			
		||||
      dataIndex = parseInt(event.target.closest("tr").dataset.index);
 | 
			
		||||
      console.log(dataIndex);
 | 
			
		||||
      results.jsList.inspect([dataIndex], "inspect");
 | 
			
		||||
    } else if (event.target.classList.contains("add-btn")) {
 | 
			
		||||
      dataIndex = parseInt(event.target.closest("tr").dataset.index);
 | 
			
		||||
      results.jsList.addToSubResults(dataIndex);
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  // ### Download events and sub-results creation ###
 | 
			
		||||
  var loadingSpinnerHTML = `
 | 
			
		||||
              <div class="preloader-wrapper button-icon-spinner small active">
 | 
			
		||||
                <div class="spinner-layer spinner-green-only">
 | 
			
		||||
                  <div class="circle-clipper left">
 | 
			
		||||
                    <div class="circle"></div>
 | 
			
		||||
                    </div><div class="gap-patch">
 | 
			
		||||
                    <div class="circle"></div>
 | 
			
		||||
                    </div><div class="circle-clipper right">
 | 
			
		||||
                    <div class="circle"></div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              `
 | 
			
		||||
 | 
			
		||||
  // create results on click from all match ids
 | 
			
		||||
  resultsCreateElement.onclick = () => {
 | 
			
		||||
    resultsCreateElement.getElementsByTagName("i")[0].classList.add("hide");
 | 
			
		||||
    resultsCreateElement.innerText = "Creating...";
 | 
			
		||||
    resultsCreateElement.insertAdjacentHTML("afterbegin", loadingSpinnerHTML);
 | 
			
		||||
    results.data.createResultsData("results");
 | 
			
		||||
  /**
 | 
			
		||||
   * The following listener handles what functions are called when the user
 | 
			
		||||
   * does use the page navigation to navigate to a new page.
 | 
			
		||||
   */
 | 
			
		||||
  for (let element of resultsList.pagination) {
 | 
			
		||||
    element.addEventListener("click", (event) => {
 | 
			
		||||
      // shows match context according to the user picked value
 | 
			
		||||
      resultsList.changeContext();
 | 
			
		||||
      // activates or deactivates expertMode on new page depending switch value
 | 
			
		||||
      if (resultsList.displayOptionsFormExpertMode.checked) {
 | 
			
		||||
        resultsList.expertModeOn('query-display', results);
 | 
			
		||||
      } else {
 | 
			
		||||
        resultsList.expertModeOff('query-display');
 | 
			
		||||
      }
 | 
			
		||||
      // activates inspect buttons on new page if client is not busy
 | 
			
		||||
      resultsList.activateInspect();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Add onclick to open download modal when Export Results button is pressed
 | 
			
		||||
  resultsExportElement.onclick = () => {
 | 
			
		||||
    exportModal.open();
 | 
			
		||||
    // add onclick to download JSON button and download the file
 | 
			
		||||
    downloadResultsJSONElement = document.getElementById("download-results-json")
 | 
			
		||||
    downloadResultsJSONElement.onclick = () => {
 | 
			
		||||
      let filename = results.resultsData.createDownloadFilename("matches-results");
 | 
			
		||||
      results.resultsData.addData(results.metaData);
 | 
			
		||||
      results.resultsData.downloadJSONRessource(filename, results.resultsData,
 | 
			
		||||
                                         downloadResultsJSONElement
 | 
			
		||||
      )};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // create sub results on click from shown marked match ids
 | 
			
		||||
  subResultsCreateElement.onclick = () => {
 | 
			
		||||
    subResultsCreateElement.getElementsByTagName("i")[0].remove();
 | 
			
		||||
    subResultsCreateElement.innerText = "Creating...";
 | 
			
		||||
    subResultsCreateElement.insertAdjacentHTML("afterbegin", loadingSpinnerHTML);
 | 
			
		||||
    results.data.createResultsData("sub-results");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Add onclick to open download modal when sub-results button is pressed
 | 
			
		||||
  subResultsExportElement.onclick = () => {
 | 
			
		||||
    exportModal.open();
 | 
			
		||||
    console.log(results.subResultsData);
 | 
			
		||||
    // add onclick to download JSON button and download the file
 | 
			
		||||
    downloadResultsJSONElement = document.getElementById("download-results-json")
 | 
			
		||||
    downloadResultsJSONElement.onclick = () => {
 | 
			
		||||
      let filename = results.subResultsData.createDownloadFilename("matches-sub-results");
 | 
			
		||||
      results.subResultsData.downloadJSONRessource(filename,
 | 
			
		||||
                                                   results.subResultsData,
 | 
			
		||||
                                                   downloadResultsJSONElement
 | 
			
		||||
      )};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // add onclick to download JSON button and download the file
 | 
			
		||||
  downloadInspectContextElement = document.getElementById("inspect-download-context")
 | 
			
		||||
  downloadInspectContextElement.onclick = () => {
 | 
			
		||||
    let filename = results.data.createDownloadFilename(`context-id-${results.jsList.contextId}`);
 | 
			
		||||
    results.data.addData(results.metaData);
 | 
			
		||||
    results.data.downloadJSONRessource(filename,
 | 
			
		||||
                                       results.jsList.contextData,
 | 
			
		||||
                                       downloadInspectContextElement);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // scroll to top button if user scrolled down the list
 | 
			
		||||
  let headline = document.querySelector(".headline");
 | 
			
		||||
  let scrollToTop = document.querySelector("#menu-scroll-to-top-div");
 | 
			
		||||
  window.addEventListener("scroll", (event) => {
 | 
			
		||||
    if (pageYOffset > 250) {
 | 
			
		||||
      scrollToTop.classList.toggle("hide", false);
 | 
			
		||||
  /**
 | 
			
		||||
   * The following event Listener handles the expert mode switch for the list
 | 
			
		||||
   */
 | 
			
		||||
  resultsList.displayOptionsFormExpertMode.onchange = (event) => {
 | 
			
		||||
    if (event.target.checked) {
 | 
			
		||||
      resultsList.expertModeOn('query-display', results);
 | 
			
		||||
    } else {
 | 
			
		||||
      scrollToTop.classList.toggle("hide", true);
 | 
			
		||||
      resultsList.expertModeOff('query-display');
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  scrollToTop.onclick = () => {
 | 
			
		||||
    headline.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The following event Listener handles the add-btn and the inspect-btn
 | 
			
		||||
   * onclick events via bubbleing.
 | 
			
		||||
   */
 | 
			
		||||
   resultsList.queryResultsTable.addEventListener('click', (event) => {
 | 
			
		||||
     let dataIndex;
 | 
			
		||||
     if (event.target.classList.contains('inspect-btn')) {
 | 
			
		||||
       dataIndex = parseInt(event.target.closest('tr').dataset.index);
 | 
			
		||||
       resultsList.inspect([dataIndex], 'inspect');
 | 
			
		||||
     } else if (event.target.classList.contains('add-btn')) {
 | 
			
		||||
       dataIndex = parseInt(event.target.closest('tr').dataset.index);
 | 
			
		||||
       resultsList.addToSubResults(dataIndex);
 | 
			
		||||
     }
 | 
			
		||||
   })
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Display events: Following event listeners are handling the
 | 
			
		||||
   * live update of hits per page if hits per page value is changed and the
 | 
			
		||||
   * context size of every match.
 | 
			
		||||
   */
 | 
			
		||||
  resultsList.displayOptionsFormResultsPerPage.onchange = () => {
 | 
			
		||||
    resultsList.changeHitsPerPage();
 | 
			
		||||
  };
 | 
			
		||||
  resultsList.displayOptionsFormResultContext.onchange = () => {
 | 
			
		||||
    resultsList.changeContext();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The following event listener handel the Show metadata button and its
 | 
			
		||||
   * functionality. Before the needed modal is initialized.
 | 
			
		||||
   */
 | 
			
		||||
  resultsList.metaDataModal= M.Modal.init(resultsList.metaDataModal, {
 | 
			
		||||
    'preventScrolling': false,
 | 
			
		||||
    'opacity': 0.0,
 | 
			
		||||
    'dismissible': false,
 | 
			
		||||
    'onOpenEnd': (() => {document.querySelector(".modal-overlay").remove()})
 | 
			
		||||
  });
 | 
			
		||||
  resultsList.showMetaData.onclick = () => {
 | 
			
		||||
    resultsList.metaDataModalContent.textContent = '';
 | 
			
		||||
    let table = resultsList.createMetaDataForModal(results.metaData);
 | 
			
		||||
    resultsList.metaDataModalContent.insertAdjacentHTML('afterbegin', table);
 | 
			
		||||
    resultsList.metaDataModal.open();
 | 
			
		||||
    let collapsibles = resultsList.metaDataModalContent.querySelectorAll(".text-metadata");
 | 
			
		||||
    for (let collapsible of collapsibles) {
 | 
			
		||||
      collapsible.onclick = () => {
 | 
			
		||||
        let elems = resultsList.metaDataModalContent.querySelectorAll('.collapsible');
 | 
			
		||||
        let instances = M.Collapsible.init(elems, {accordion: false});
 | 
			
		||||
        resultsList.createTextDetails(results.metaData);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The following event listeners are handeling the data export.
 | 
			
		||||
   * 1. Create full-results
 | 
			
		||||
   * 2. Create sub-results
 | 
			
		||||
   * 3. Download full-results
 | 
			
		||||
   * 4. Download sub-results
 | 
			
		||||
   */
 | 
			
		||||
  resultsList.fullResultsCreate.onclick = () => {
 | 
			
		||||
    resultsList.fullResultsCreate.querySelector('i').classList.toggle('hide');
 | 
			
		||||
    resultsList.fullResultsCreate.innerText = 'Creating...';
 | 
			
		||||
    resultsList.fullResultsCreate.insertAdjacentHTML('afterbegin',
 | 
			
		||||
                                                      loadingSpinnerHTML);
 | 
			
		||||
    let dataIndexes = [...Array(results.data.match_count).keys()];
 | 
			
		||||
    resultsList.notifyClient('get-results', { resultsType: 'full-results',
 | 
			
		||||
                                              dataIndexes: dataIndexes});
 | 
			
		||||
  }
 | 
			
		||||
  resultsList.subResultsCreate.onclick = () => {
 | 
			
		||||
    let dataIndexes = [];
 | 
			
		||||
    resultsList.addToSubResultsIdsToShow.forEach((id) => {
 | 
			
		||||
      dataIndexes.push(id - 1);
 | 
			
		||||
    });
 | 
			
		||||
    resultsList.subResultsCreate.querySelector('i').classList.toggle('hide');
 | 
			
		||||
    resultsList.subResultsCreate.innerText = 'Creating...';
 | 
			
		||||
    resultsList.subResultsCreate.insertAdjacentHTML('afterbegin',
 | 
			
		||||
    loadingSpinnerHTML);
 | 
			
		||||
    resultsList.notifyClient('get-results', { resultsType: 'sub-results',
 | 
			
		||||
                                              dataIndexes: dataIndexes});
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
   * Before the downland events are added the needed modal is initialized.
 | 
			
		||||
   */
 | 
			
		||||
  resultsList.queryResultsDownloadModal = M.Modal.init(resultsList.queryResultsDownloadModal);
 | 
			
		||||
  // Open download modal when full results export button is pressed
 | 
			
		||||
  resultsList.fullResultsExport.onclick = () => {
 | 
			
		||||
    resultsList.queryResultsDownloadModal.open();
 | 
			
		||||
    // add onclick to download JSON button and download the file
 | 
			
		||||
    resultsList.downloadResultsJson.onclick = () => {
 | 
			
		||||
      let filename = results.fullResultsData.createDownloadFilename('full-results');
 | 
			
		||||
      results.fullResultsData.addData(results.metaData);
 | 
			
		||||
      results.fullResultsData.downloadJSONRessource(filename, results.fullResultsData,
 | 
			
		||||
                                                    resultsList.downloadResultsJson)};
 | 
			
		||||
  }
 | 
			
		||||
  // Open download modal when sub results export button is pressed
 | 
			
		||||
  resultsList.subResultsExport.onclick = () => {
 | 
			
		||||
    resultsList.queryResultsDownloadModal.open();
 | 
			
		||||
    // add onclick to download JSON button and download the file
 | 
			
		||||
    resultsList.downloadResultsJson.onclick = () => {
 | 
			
		||||
      let filename = results.subResultsData.createDownloadFilename('sub-results');
 | 
			
		||||
      results.subResultsData.addData(results.metaData);
 | 
			
		||||
      results.subResultsData.downloadJSONRessource(filename, results.subResultsData,
 | 
			
		||||
                                                   resultsList.downloadResultsJson)};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // enable scroll to Top
 | 
			
		||||
  scrollToTop('.headline', '#menu-scroll-to-top-div');
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -107,8 +107,8 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
<script type="module">
 | 
			
		||||
  import {RessourceList} from '../../static/js/nopaque.lists.js';
 | 
			
		||||
  class InformationUpdater {
 | 
			
		||||
    constructor(corpusId, foreignCorpusFlag) {
 | 
			
		||||
      this.corpusId = corpusId;
 | 
			
		||||
@@ -196,7 +196,8 @@
 | 
			
		||||
    nopaque.socket.emit("foreign_user_data_stream_init", {{ corpus.user_id }});
 | 
			
		||||
  });
 | 
			
		||||
  {% endif %}
 | 
			
		||||
  var corpusFilesList = new RessourceList("corpus-files", null, "CorpusFile");
 | 
			
		||||
 | 
			
		||||
  let corpusFilesList = new RessourceList("corpus-files", null, "CorpusFile");
 | 
			
		||||
  document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
    corpusFilesList._add({{ corpus_files|tojson|safe }});
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -6,22 +6,14 @@ results.-->
 | 
			
		||||
  <div class="divider" style="margin-bottom: 10px;"></div>
 | 
			
		||||
  <div class="row">
 | 
			
		||||
    <div class="col s12">
 | 
			
		||||
      <div class="switch">
 | 
			
		||||
        Sub-Results creation:
 | 
			
		||||
        <label>
 | 
			
		||||
          Off
 | 
			
		||||
          <input disabled
 | 
			
		||||
                 type="checkbox"
 | 
			
		||||
                 id="add-to-sub-results">
 | 
			
		||||
          <span class="lever"></span>
 | 
			
		||||
          On
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <p>Add matches to Sub-Results with the
 | 
			
		||||
        <i class="material-icons tiny">add</i>
 | 
			
		||||
        button in the list or inspect view.
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col s12 hide" id="sub-results-match-ids-div">
 | 
			
		||||
    <div class="col s12">
 | 
			
		||||
      <div class="input-field">
 | 
			
		||||
        <p><span id="nr-marked-matches"></span> matches marked
 | 
			
		||||
          for Sub-Results:</p>
 | 
			
		||||
        <p><span id="nr-marked-matches"></span> matches added for sub-results:</p>
 | 
			
		||||
        <textarea id="sub-results-match-ids"
 | 
			
		||||
                  class="materialize-textarea"
 | 
			
		||||
                  disabled>
 | 
			
		||||
 
 | 
			
		||||
@@ -12,10 +12,10 @@ the selected sub results.-->
 | 
			
		||||
              disabled
 | 
			
		||||
              flat-interaction"
 | 
			
		||||
              type="submit"
 | 
			
		||||
              id="results-create">Create Results
 | 
			
		||||
              id="full-results-create">Create Results
 | 
			
		||||
        <i class="material-icons left">build</i>
 | 
			
		||||
      </button>
 | 
			
		||||
      <button id="query-results-export"
 | 
			
		||||
      <button id="full-results-export"
 | 
			
		||||
              class="waves-effect
 | 
			
		||||
              waves-light
 | 
			
		||||
              btn-flat
 | 
			
		||||
@@ -29,7 +29,6 @@ the selected sub results.-->
 | 
			
		||||
      <button class="waves-effect
 | 
			
		||||
              waves-light
 | 
			
		||||
              btn-flat
 | 
			
		||||
              hide
 | 
			
		||||
              disabled
 | 
			
		||||
              flat-interaction"
 | 
			
		||||
              type="submit"
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ result.-->
 | 
			
		||||
  <div class="divider" style="margin-bottom: 10px;"></div>
 | 
			
		||||
  <div class="row">
 | 
			
		||||
    <div class="col s12">
 | 
			
		||||
      <button id="show-metadata"
 | 
			
		||||
      <button id="show-meta-data"
 | 
			
		||||
              class="waves-effect
 | 
			
		||||
                     waves-light
 | 
			
		||||
                     btn-flat
 | 
			
		||||
@@ -17,29 +17,29 @@ result.-->
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col s12">
 | 
			
		||||
      <div class="progress hide" id="query-progress-bar">
 | 
			
		||||
        <div class="determinate"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <p>
 | 
			
		||||
        <span id="received-match-count">
 | 
			
		||||
        <span id="recieved-match-count">
 | 
			
		||||
        </span> of
 | 
			
		||||
        <span id="match-count"></span>
 | 
			
		||||
        <span id="total-match-count"></span>
 | 
			
		||||
        matches loaded.
 | 
			
		||||
        <br>
 | 
			
		||||
        <br>
 | 
			
		||||
        Matches occured in
 | 
			
		||||
        <span id="text-lookup-count"></span>
 | 
			
		||||
        corpus files:
 | 
			
		||||
        <br>
 | 
			
		||||
        <span id=text-titles></span>
 | 
			
		||||
        <span id=text-lookup-titles></span>
 | 
			
		||||
      </p>
 | 
			
		||||
      {% if not imported %}
 | 
			
		||||
      <p id="query-results-user-feedback">
 | 
			
		||||
        <i class="material-icons">help</i>
 | 
			
		||||
        The Server is still sending your results.
 | 
			
		||||
      <br>
 | 
			
		||||
      <p class="hide" id="query-results-user-feedback">
 | 
			
		||||
        <i class="material-icons tiny">help</i>
 | 
			
		||||
        Server is sending your results.
 | 
			
		||||
        Functions like "Export Results" and "Match Inspect" will be
 | 
			
		||||
        available after all matches have been loaded.
 | 
			
		||||
      </p>
 | 
			
		||||
      <div class="progress" id="query-results-progress">
 | 
			
		||||
        <div class="determinate" id="query-results-determinate"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
      {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -162,11 +162,11 @@
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  var corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, "Corpus");
 | 
			
		||||
  var jobList = new RessourceList("jobs", nopaque.jobsSubscribers, "Job");
 | 
			
		||||
  var queryResultList = new RessourceList("query-results", nopaque.queryResultsSubscribers, "QueryResult");
 | 
			
		||||
<script type="module">
 | 
			
		||||
  import {RessourceList} from '../../static/js/nopaque.lists.js';
 | 
			
		||||
  let corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, "Corpus");
 | 
			
		||||
  let jobList = new RessourceList("jobs", nopaque.jobsSubscribers, "Job");
 | 
			
		||||
  let queryResultList = new RessourceList("query-results", nopaque.queryResultsSubscribers, "QueryResult");
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,17 @@
 | 
			
		||||
<!-- Analysis init modal. User feedback showing that the analysis session is
 | 
			
		||||
loading. -->
 | 
			
		||||
 | 
			
		||||
<div class="modal no-autoinit" id="init-display">
 | 
			
		||||
<div class="modal no-autoinit" id="analysis-init-modal">
 | 
			
		||||
  <div class="modal-content">
 | 
			
		||||
    <h4>Initializing your corpus analysis session...</h4>
 | 
			
		||||
    <div class="error-container hide show-on-error"></div>
 | 
			
		||||
    <div class="hide progress show-while-waiting">
 | 
			
		||||
    <p>If the loading takes to long or an error occured,
 | 
			
		||||
      <a onclick="window.location.reload()" href="#">click here</a>
 | 
			
		||||
      to refresh your session or
 | 
			
		||||
      <a href="{{ url_for('corpora.corpus', corpus_id=corpus_id) }}">go back</a>!
 | 
			
		||||
    </p>
 | 
			
		||||
    <div id="analysis-init-progress" class="progress">
 | 
			
		||||
      <div class="indeterminate"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <p id="analysis-init-error" class="hide red-text"></p>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
          <div class="section">
 | 
			
		||||
            <h6 style="margin-top: 0px;">Display</h6>
 | 
			
		||||
            <div class="divider" style="margin-bottom: 10px;"></div>
 | 
			
		||||
            <div class="col s12" style="margin-bottom: 10px;" id="display-inspect">
 | 
			
		||||
            <div class="col s12" style="margin-bottom: 10px;">
 | 
			
		||||
              {{ inspect_display_options_form.expert_mode_inspect.label.text }}
 | 
			
		||||
              <div class="switch right">
 | 
			
		||||
                <label>
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
                </label>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col s12" style="margin-bottom: 10px;" id="create-inspect">
 | 
			
		||||
            <div class="col s12" style="margin-bottom: 10px;">
 | 
			
		||||
              {{ inspect_display_options_form.highlight_sentences.label.text }}
 | 
			
		||||
              <div class="switch right">
 | 
			
		||||
                <label>
 | 
			
		||||
@@ -42,19 +42,18 @@
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% if not imported %}
 | 
			
		||||
        <div class="col s12 m6 l6">
 | 
			
		||||
          <div class="section">
 | 
			
		||||
            <h6 style="margin-top: 0px;">Create</h6>
 | 
			
		||||
            <div class="divider" style="margin-bottom: 10px;"></div>
 | 
			
		||||
            <div class="col s12">
 | 
			
		||||
              Add to Sub Results
 | 
			
		||||
              <div class="secondary-content right" id="add-to-sub-results-from-inspect">
 | 
			
		||||
              <div class="secondary-content right" id="create-inspect-menu">
 | 
			
		||||
                {# The needed button is created and added via javascript #}
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
      </div>
 | 
			
		||||
    </form>
 | 
			
		||||
    <div class="row section">
 | 
			
		||||
 
 | 
			
		||||
@@ -121,7 +121,9 @@
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/List.js/list.min.js') }}"></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/Socket.IO/socket.io.slim.js') }}"></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/nopaque.js') }}"></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/nopaque.lists.js') }}"></script>
 | 
			
		||||
    <script type="module">
 | 
			
		||||
      import {RessourceList} from '../../static/js/nopaque.lists.js'
 | 
			
		||||
    </script>
 | 
			
		||||
    <script>
 | 
			
		||||
      {% if current_user.is_authenticated %}
 | 
			
		||||
      {% if current_user.setting_dark_mode %}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
<div class="col s12" id="query-display">
 | 
			
		||||
  <div class="card">
 | 
			
		||||
    <div class="card-content" id="result-list" style="overflow: hidden;">
 | 
			
		||||
      <div class=" row show-on-success">
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        {% include 'interactions/infos.html.j2' %}
 | 
			
		||||
        {% include 'interactions/display.html.j2' %}
 | 
			
		||||
        {% include 'interactions/analysis.html.j2' %}
 | 
			
		||||
@@ -54,180 +54,169 @@
 | 
			
		||||
{% include 'modals/context_modal.html.j2' %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script src="{{ url_for('static', filename='js/nopaque.Results.js') }}">
 | 
			
		||||
</script>
 | 
			
		||||
<script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}">
 | 
			
		||||
</script>
 | 
			
		||||
<script src="{{ url_for('static', filename='js/nopaque.InteractionElement.js') }}">
 | 
			
		||||
</script>
 | 
			
		||||
<script>
 | 
			
		||||
  // ###### global variables ######
 | 
			
		||||
  var full_result_json;
 | 
			
		||||
  var result_json;
 | 
			
		||||
  var receivedMatchCountElement;  // Nr. of loaded matches will be displayed in this element
 | 
			
		||||
  var textLookupCountElement  // Nr of texts the matches occured in will be shown in this element
 | 
			
		||||
  var textTitlesElement;  // matched text titles
 | 
			
		||||
  var progress;  // global progress value
 | 
			
		||||
  var queryResultsProgressElement;  // Div element holding the progress bar
 | 
			
		||||
  var expertModeSwitchElement; // Expert mode switch Element
 | 
			
		||||
  var matchCountElement;  // Total nr. of matches will be displayed in this element
 | 
			
		||||
  var interactionElements;  // Interaction elements and their parameters
 | 
			
		||||
  var contextModal;  // Modal to open on inspect for further match context
 | 
			
		||||
<script type="module">
 | 
			
		||||
/**
 | 
			
		||||
 * First Phase:
 | 
			
		||||
 * document content is loaded and scripts are being imported and executed
 | 
			
		||||
 */
 | 
			
		||||
 import {
 | 
			
		||||
   CorpusAnalysisClient,
 | 
			
		||||
   CorpusAnalysisDisplay,
 | 
			
		||||
   SocketEventListener,
 | 
			
		||||
   ListenerCallback,
 | 
			
		||||
 } from '../../static/js/modules/nopaque.CorpusAnalysisClient.js';
 | 
			
		||||
 import {
 | 
			
		||||
   recieveSession,
 | 
			
		||||
   recieveQueryStatus,
 | 
			
		||||
   recieveQueryData,
 | 
			
		||||
 } from '../../static/js/modules/nopaque.listenerFunctions.js';
 | 
			
		||||
 import {
 | 
			
		||||
   querySetup,
 | 
			
		||||
   queryRenderResults,
 | 
			
		||||
 } from '../../static/js/modules/nopaque.listenerCallbacks.js'
 | 
			
		||||
 import {
 | 
			
		||||
   Results,
 | 
			
		||||
   Data,
 | 
			
		||||
   MetaData,
 | 
			
		||||
 } from '../../static/js/nopaque.Results.js';
 | 
			
		||||
 import {
 | 
			
		||||
   ResultsList,
 | 
			
		||||
 } from '../../static/js/nopaque.lists.js';
 | 
			
		||||
 import {
 | 
			
		||||
   scrollToTop,
 | 
			
		||||
 } from '../../static/js/modules/nopaque.scrollToTop.js';
 | 
			
		||||
 | 
			
		||||
  // ###### Defining local scope variables
 | 
			
		||||
  let displayOptionsFormElement;  // Form holding the display informations
 | 
			
		||||
  let resultItems;  // array of built html result items row element. This is called when results are transmitted and being recieved
 | 
			
		||||
  let hitsPerPageInputElement;let contextPerItemElement;  // Form Element for display option
 | 
			
		||||
  let paginationElements;
 | 
			
		||||
  let inspectBtnElements;
 | 
			
		||||
  let metaDataModal;
 | 
			
		||||
  let showMetaDataButton
 | 
			
		||||
/**
 | 
			
		||||
 * Second Phase:
 | 
			
		||||
 * Asynchronus and event driven code
 | 
			
		||||
 */
 | 
			
		||||
document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
  // Initialize the CorpusAnalysisClient with dynamicMode as false
 | 
			
		||||
  const client = new CorpusAnalysisClient({'logging': true,
 | 
			
		||||
                                           'dynamicMode': false});
 | 
			
		||||
  console.info("CorpusAnalysisClient created as client:", client);
 | 
			
		||||
  // Set up display elements which hare show depending on the client status
 | 
			
		||||
  client.getHTMLElements(['#query-display']);
 | 
			
		||||
  const queryDisplay = new CorpusAnalysisDisplay(client.queryDisplay);
 | 
			
		||||
  // Register those display elements to client
 | 
			
		||||
  client.setDisplay("query", queryDisplay);
 | 
			
		||||
  /**
 | 
			
		||||
   * Initializing the results object holding all the data of a query.
 | 
			
		||||
   * Also holds the metadata of one query.
 | 
			
		||||
   * Lastly it contains the object ResultsList which is a list.js
 | 
			
		||||
   * subclass which handles the visual representation of the query data.
 | 
			
		||||
   */
 | 
			
		||||
   let displayOptionsData = ResultsList.getDisplayOptions('display-options-form');
 | 
			
		||||
   ResultsList.options.page = displayOptionsData["resultsPerPage"];
 | 
			
		||||
   let data = new Data();
 | 
			
		||||
   let resultsList = new ResultsList("result-list", ResultsList.options);
 | 
			
		||||
   let resultsMetaData = new MetaData();
 | 
			
		||||
   let results = new Results(data, resultsList, resultsMetaData);
 | 
			
		||||
   // make results part of the client
 | 
			
		||||
   client.results = results;
 | 
			
		||||
   // inits some object keys and values
 | 
			
		||||
   client.results.clearAll();
 | 
			
		||||
   console.info('Initialized the Results object.')
 | 
			
		||||
   // init some modals
 | 
			
		||||
   let deleteOverlay = () => {
 | 
			
		||||
     let overlay = document.getElementsByClassName("modal-overlay")[0];
 | 
			
		||||
     overlay.remove();
 | 
			
		||||
   };
 | 
			
		||||
   client.getHTMLElements(['#meta-data-modal'])
 | 
			
		||||
   client.metaDataModal = M.Modal.init(client.metaDataModal,
 | 
			
		||||
                                {'preventScrolling': false,
 | 
			
		||||
                                 'opacity': 0.0,
 | 
			
		||||
                                 'dismissible': false,
 | 
			
		||||
                                 'onOpenEnd': deleteOverlay});
 | 
			
		||||
  // saving imported data into client object
 | 
			
		||||
  const payload = {{ query_result_file_content|tojson|safe }};
 | 
			
		||||
  /**
 | 
			
		||||
   * Register listeners and their callbacks. Because we are using the client
 | 
			
		||||
   * not in dynamic mode we will not load the listeners. We just call the
 | 
			
		||||
   * callbacks of the listeners manually. This is done to keep the setup of
 | 
			
		||||
   * the client in dynamic or not dynamic mode similarish.
 | 
			
		||||
   */
 | 
			
		||||
   const listenForQueryStatus = new SocketEventListener('corpus_analysis_query',
 | 
			
		||||
                                                        recieveQueryStatus);
 | 
			
		||||
   const queryStatusCallback = new ListenerCallback('corpus_analysis_query',
 | 
			
		||||
                                                    querySetup);
 | 
			
		||||
   listenForQueryStatus.setCallbacks([queryStatusCallback]);
 | 
			
		||||
   const listenForQueryData = new SocketEventListener('corpus_analysis_query_results',
 | 
			
		||||
                                                   recieveQueryData);
 | 
			
		||||
   const queryDataCallback = new ListenerCallback('corpus_analysis_query_results',
 | 
			
		||||
                                                  queryRenderResults);
 | 
			
		||||
   listenForQueryData.setCallbacks([queryDataCallback]);
 | 
			
		||||
 | 
			
		||||
  // ###### Initializing variables ######
 | 
			
		||||
  displayOptionsFormElement = document.getElementById("display-options-form");
 | 
			
		||||
  resultItems = [];
 | 
			
		||||
  receivedMatchCountElement = document.getElementById("received-match-count");
 | 
			
		||||
  textLookupCountElement = document.getElementById("text-lookup-count");
 | 
			
		||||
  textTitlesElement = document.getElementById("text-titles");
 | 
			
		||||
  queryResultsProgressElement = document.getElementById("query-results-progress");
 | 
			
		||||
  expertModeSwitchElement = document.getElementById("display-options-form-expert_mode");
 | 
			
		||||
  matchCountElement = document.getElementById("match-count");
 | 
			
		||||
  hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page");
 | 
			
		||||
  contextPerItemElement = document.getElementById("display-options-form-result_context");
 | 
			
		||||
  paginationElements = document.getElementsByClassName("pagination");
 | 
			
		||||
  contextModal = document.getElementById("context-modal");
 | 
			
		||||
  metaDataModal = document.getElementById("meta-data-modal");
 | 
			
		||||
  showMetaDataButton = document.getElementById("show-metadata");
 | 
			
		||||
 | 
			
		||||
  // js list options
 | 
			
		||||
  displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement);
 | 
			
		||||
  resultsListOptions = {page: displayOptionsData["resultsPerPage"],
 | 
			
		||||
  pagination: [{
 | 
			
		||||
      name: "paginationTop",
 | 
			
		||||
      paginationClass: "paginationTop",
 | 
			
		||||
      innerWindow: 8,
 | 
			
		||||
      outerWindow: 1
 | 
			
		||||
    }, {
 | 
			
		||||
      paginationClass: "paginationBottom",
 | 
			
		||||
      innerWindow: 8,
 | 
			
		||||
      outerWindow: 1
 | 
			
		||||
    }],
 | 
			
		||||
    valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}],
 | 
			
		||||
    item: `<span></span>`
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
    // Initialize some Modals
 | 
			
		||||
    contextModal = M.Modal.init(contextModal, {"dismissible": true});
 | 
			
		||||
 | 
			
		||||
    // ###### recreating chunk structure to reuse callback queryRenderResults()
 | 
			
		||||
    full_result_json = {{ query_result_file_content|tojson|safe }};
 | 
			
		||||
    result_json = {};
 | 
			
		||||
    result_json["chunk"] = {};
 | 
			
		||||
    result_json.chunk["cpos_lookup"] = full_result_json.cpos_lookup;
 | 
			
		||||
    result_json.chunk["cpos_ranges"] = full_result_json.cpos_ranges;
 | 
			
		||||
    result_json.chunk["matches"] = full_result_json.matches;
 | 
			
		||||
    result_json.chunk["text_lookup"] = full_result_json.text_lookup;
 | 
			
		||||
 | 
			
		||||
    // Init corpus analysis components
 | 
			
		||||
    data = new Data();
 | 
			
		||||
    resultsList = new ResultsList("result-list", resultsListOptions);
 | 
			
		||||
    resultsMetaData = new MetaData();
 | 
			
		||||
    results = new Results(data, resultsList, resultsMetaData);
 | 
			
		||||
    results.clearAll();  // inits some object keys and values
 | 
			
		||||
    // init some modals
 | 
			
		||||
    let deleteOverlay = () => {
 | 
			
		||||
      let overlay = document.getElementsByClassName("modal-overlay")[0];
 | 
			
		||||
      overlay.remove();
 | 
			
		||||
    };
 | 
			
		||||
    metaDataModal = M.Modal.init(metaDataModal, {"preventScrolling": false,
 | 
			
		||||
                                 "opacity": 0.0,
 | 
			
		||||
                                 "dismissible": false,
 | 
			
		||||
                                 "onOpenEnd": deleteOverlay});
 | 
			
		||||
 | 
			
		||||
    // setting some initial values for user feedback
 | 
			
		||||
    matchCountElement.innerText = full_result_json.match_count;
 | 
			
		||||
 | 
			
		||||
    // Initialization of interactionElemnts
 | 
			
		||||
    // An interactionElement is an object identifing a switch or button via
 | 
			
		||||
    // htmlID. Callbacks are set for these elements which will be triggered on
 | 
			
		||||
    // a pagination interaction by the user or if the status of the element has
 | 
			
		||||
    // been altered. (Like the switche has ben turned on or off).
 | 
			
		||||
    interactionElements = new InteractionElements();
 | 
			
		||||
    let expertModeInteraction = new InteractionElement("display-options-form-expert_mode");
 | 
			
		||||
    expertModeInteraction.setCallback("on",
 | 
			
		||||
                                      results.jsList.expertModeOn,
 | 
			
		||||
                                      results.jsList,
 | 
			
		||||
                                      ["query-display"])
 | 
			
		||||
    expertModeInteraction.setCallback("off",
 | 
			
		||||
                                      results.jsList.expertModeOff,
 | 
			
		||||
                                      results.jsList,
 | 
			
		||||
                                      ["query-display"])
 | 
			
		||||
 | 
			
		||||
    let activateInspectInteraction = new InteractionElement("inspect",
 | 
			
		||||
                                                            false);
 | 
			
		||||
    activateInspectInteraction.setCallback("noCheck",
 | 
			
		||||
                                            results.jsList.activateInspect,
 | 
			
		||||
                                            results.jsList);
 | 
			
		||||
 | 
			
		||||
    let changeContextInteraction = new InteractionElement("display-options-form-results_per_page",
 | 
			
		||||
                                                          false);
 | 
			
		||||
    changeContextInteraction.setCallback("noCheck",
 | 
			
		||||
                                        results.jsList.changeContext,
 | 
			
		||||
                                        results.jsList)
 | 
			
		||||
    interactionElements.addInteractions([expertModeInteraction, activateInspectInteraction, changeContextInteraction]);
 | 
			
		||||
 | 
			
		||||
    // checks if a change for every interactionElement happens and executes
 | 
			
		||||
    // the callbacks accordingly
 | 
			
		||||
    interactionElements.onChangeExecute();
 | 
			
		||||
 | 
			
		||||
    // eventListener if pagination is used to apply new context size to new page
 | 
			
		||||
    // and also activate inspect match if progress is 100
 | 
			
		||||
    // also adds more interaction buttons like add to sub results
 | 
			
		||||
    for (let element of paginationElements) {
 | 
			
		||||
      element.addEventListener("click", (event) => {
 | 
			
		||||
        results.jsList.pageChangeEventInteractionHandler(interactionElements);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // render results in table imported parameter is true
 | 
			
		||||
    queryRenderResults(result_json, true);
 | 
			
		||||
 | 
			
		||||
    // ### Show corpus Metadata
 | 
			
		||||
    showMetaDataButton.onclick = () => {
 | 
			
		||||
      metaDataModal.open();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // live update of hits per page if hits per page value is changed
 | 
			
		||||
    let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);
 | 
			
		||||
    hitsPerPageInputElement.onchange = changeHitsPerPageBind;
 | 
			
		||||
 | 
			
		||||
    // live update of lr context per item if context value is changed
 | 
			
		||||
    contextPerItemElement.onchange = results.jsList.changeContext;
 | 
			
		||||
 | 
			
		||||
    // new insepct event listener makeing use of javascript bubbleing
 | 
			
		||||
    let resultsTable = document.getElementById("query-results");
 | 
			
		||||
    resultsTable.addEventListener("click", (event) => {
 | 
			
		||||
      if (event.target.classList.contains("inspect-btn")) {
 | 
			
		||||
        const dataIndex = event.target.closest("tr").dataset.index;
 | 
			
		||||
        const fakeResponse = results.jsList.createFakeResponse();
 | 
			
		||||
        results.jsList.showMatchContext(fakeResponse);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // scroll to top button if user scrolled down the list
 | 
			
		||||
    let headline = document.querySelector(".headline");
 | 
			
		||||
    let scrollToTop = document.querySelector("#menu-scroll-to-top-div");
 | 
			
		||||
    window.addEventListener("scroll", (event) => {
 | 
			
		||||
      if (pageYOffset > 250) {
 | 
			
		||||
        scrollToTop.classList.toggle("hide", false);
 | 
			
		||||
      } else {
 | 
			
		||||
        scrollToTop.classList.toggle("hide", true);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    scrollToTop.onclick = () => {
 | 
			
		||||
      headline.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
//
 | 
			
		||||
//   // Initialization of interactionElemnts
 | 
			
		||||
//   // An interactionElement is an object identifing a switch or button via
 | 
			
		||||
//   // htmlID. Callbacks are set for these elements which will be triggered on
 | 
			
		||||
//   // a pagination interaction by the user or if the status of the element has
 | 
			
		||||
//   // been altered. (Like the switche has ben turned on or off).
 | 
			
		||||
//   interactionElements = new InteractionElements();
 | 
			
		||||
//   let expertModeInteraction = new InteractionElement("display-options-form-expert_mode");
 | 
			
		||||
//   expertModeInteraction.setCallback("on",
 | 
			
		||||
//                                     results.jsList.expertModeOn,
 | 
			
		||||
//                                     results.jsList,
 | 
			
		||||
//                                     ["query-display"])
 | 
			
		||||
//   expertModeInteraction.setCallback("off",
 | 
			
		||||
//                                     results.jsList.expertModeOff,
 | 
			
		||||
//                                     results.jsList,
 | 
			
		||||
//                                     ["query-display"])
 | 
			
		||||
//
 | 
			
		||||
//   let activateInspectInteraction = new InteractionElement("inspect",
 | 
			
		||||
//                                                           false);
 | 
			
		||||
//   activateInspectInteraction.setCallback("noCheck",
 | 
			
		||||
//                                           results.jsList.activateInspect,
 | 
			
		||||
//                                           results.jsList);
 | 
			
		||||
//
 | 
			
		||||
//   let changeContextInteraction = new InteractionElement("display-options-form-results_per_page",
 | 
			
		||||
//                                                         false);
 | 
			
		||||
//   changeContextInteraction.setCallback("noCheck",
 | 
			
		||||
//                                       results.jsList.changeContext,
 | 
			
		||||
//                                       results.jsList)
 | 
			
		||||
//   interactionElements.addInteractions([expertModeInteraction, activateInspectInteraction, changeContextInteraction]);
 | 
			
		||||
//
 | 
			
		||||
//   // checks if a change for every interactionElement happens and executes
 | 
			
		||||
//   // the callbacks accordingly
 | 
			
		||||
//   interactionElements.onChangeExecute();
 | 
			
		||||
//
 | 
			
		||||
//   // eventListener if pagination is used to apply new context size to new page
 | 
			
		||||
//   // and also activate inspect match if progress is 100
 | 
			
		||||
//   // also adds more interaction buttons like add to sub results
 | 
			
		||||
//   for (let element of paginationElements) {
 | 
			
		||||
//     element.addEventListener("click", (event) => {
 | 
			
		||||
//       results.jsList.pageChangeEventInteractionHandler(interactionElements);
 | 
			
		||||
//     });
 | 
			
		||||
//   }
 | 
			
		||||
//
 | 
			
		||||
  // render results directly with callbacks because we are not in dynamic mode
 | 
			
		||||
  listenForQueryStatus.listenerCallbacks['corpus_analysis_query'].callbackFunction(payload, client);
 | 
			
		||||
  listenForQueryData.listenerCallbacks['corpus_analysis_query_results'].callbackFunction(payload, client);
 | 
			
		||||
//   // ### Show corpus Metadata
 | 
			
		||||
//   showMetaDataButton.onclick = () => {
 | 
			
		||||
//     metaDataModal.open();
 | 
			
		||||
//   };
 | 
			
		||||
//
 | 
			
		||||
//   // live update of hits per page if hits per page value is changed
 | 
			
		||||
//   let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);
 | 
			
		||||
//   hitsPerPageInputElement.onchange = changeHitsPerPageBind;
 | 
			
		||||
//
 | 
			
		||||
//   // live update of lr context per item if context value is changed
 | 
			
		||||
//   contextPerItemElement.onchange = results.jsList.changeContext;
 | 
			
		||||
//
 | 
			
		||||
//   // new insepct event listener makeing use of javascript bubbleing
 | 
			
		||||
//   let resultsTable = document.getElementById("query-results");
 | 
			
		||||
//   resultsTable.addEventListener("click", (event) => {
 | 
			
		||||
//     if (event.target.classList.contains("inspect-btn")) {
 | 
			
		||||
//       const dataIndex = event.target.closest("tr").dataset.index;
 | 
			
		||||
//       const fakeResponse = results.jsList.createFakeResponse();
 | 
			
		||||
//       results.jsList.showMatchContext(fakeResponse);
 | 
			
		||||
//     }
 | 
			
		||||
//   });
 | 
			
		||||
//
 | 
			
		||||
  // Add scrollToTop functionality
 | 
			
		||||
  scrollToTop();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										233
									
								
								web/app/templates/query_results/inspect.html.j2.bak
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								web/app/templates/query_results/inspect.html.j2.bak
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,233 @@
 | 
			
		||||
{% extends "nopaque.html.j2" %}
 | 
			
		||||
 | 
			
		||||
{% set headline = ' ' %}
 | 
			
		||||
 | 
			
		||||
{% set full_width = True %}
 | 
			
		||||
{% set imported = True %}
 | 
			
		||||
 | 
			
		||||
{% block page_content %}
 | 
			
		||||
 | 
			
		||||
<div class="col s12">
 | 
			
		||||
  <div class="card">
 | 
			
		||||
    <div class="card-content" style="padding-top: 5px;
 | 
			
		||||
                             padding-bottom: 0px;">
 | 
			
		||||
      <!-- Query form -->
 | 
			
		||||
      <div class="row">
 | 
			
		||||
      <form id="query-form">
 | 
			
		||||
          <div class="col s12 m10">
 | 
			
		||||
            <div class="input-field">
 | 
			
		||||
              <i class="material-icons prefix">search</i>
 | 
			
		||||
              <input disabled value="{{ query_metadata.query|escape }}" id="disabled" type="text" class="validate">
 | 
			
		||||
          <label for="disabled">Query</label>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col s12 m2 right-align">
 | 
			
		||||
            <br class="hide-on-small-only">
 | 
			
		||||
          </div>
 | 
			
		||||
      </form>
 | 
			
		||||
    </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- entire results div/card -->
 | 
			
		||||
<div class="col s12" id="query-display">
 | 
			
		||||
  <div class="card">
 | 
			
		||||
    <div class="card-content" id="result-list" style="overflow: hidden;">
 | 
			
		||||
      <div class=" row show-on-success">
 | 
			
		||||
        {% include 'interactions/infos.html.j2' %}
 | 
			
		||||
        {% include 'interactions/display.html.j2' %}
 | 
			
		||||
        {% include 'interactions/analysis.html.j2' %}
 | 
			
		||||
        {% include 'interactions/cite.html.j2' %}
 | 
			
		||||
      </div>
 | 
			
		||||
      {% include 'tables/query_results.html.j2' %}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Scroll to top element -->
 | 
			
		||||
{% include 'interactions/scroll_to_top.html.j2' %}
 | 
			
		||||
 | 
			
		||||
<!-- Modals -->
 | 
			
		||||
{% include 'modals/show_metadata.html.j2' %}
 | 
			
		||||
{% include 'modals/show_text_details.html.j2' %}
 | 
			
		||||
{% include 'modals/context_modal.html.j2' %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script src="{{ url_for('static', filename='js/nopaque.Results.js') }}">
 | 
			
		||||
</script>
 | 
			
		||||
<script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}">
 | 
			
		||||
</script>
 | 
			
		||||
<script src="{{ url_for('static', filename='js/nopaque.InteractionElement.js') }}">
 | 
			
		||||
</script>
 | 
			
		||||
<script>
 | 
			
		||||
  // ###### global variables ######
 | 
			
		||||
  var full_result_json;
 | 
			
		||||
  var result_json;
 | 
			
		||||
  var receivedMatchCountElement;  // Nr. of loaded matches will be displayed in this element
 | 
			
		||||
  var textLookupCountElement  // Nr of texts the matches occured in will be shown in this element
 | 
			
		||||
  var textTitlesElement;  // matched text titles
 | 
			
		||||
  var progress;  // global progress value
 | 
			
		||||
  var queryResultsProgressElement;  // Div element holding the progress bar
 | 
			
		||||
  var expertModeSwitchElement; // Expert mode switch Element
 | 
			
		||||
  var matchCountElement;  // Total nr. of matches will be displayed in this element
 | 
			
		||||
  var interactionElements;  // Interaction elements and their parameters
 | 
			
		||||
  var contextModal;  // Modal to open on inspect for further match context
 | 
			
		||||
 | 
			
		||||
  // ###### Defining local scope variables
 | 
			
		||||
  let displayOptionsFormElement;  // Form holding the display informations
 | 
			
		||||
  let resultItems;  // array of built html result items row element. This is called when results are transmitted and being recieved
 | 
			
		||||
  let hitsPerPageInputElement;let contextPerItemElement;  // Form Element for display option
 | 
			
		||||
  let paginationElements;
 | 
			
		||||
  let inspectBtnElements;
 | 
			
		||||
  let metaDataModal;
 | 
			
		||||
  let showMetaDataButton
 | 
			
		||||
 | 
			
		||||
  // ###### Initializing variables ######
 | 
			
		||||
  displayOptionsFormElement = document.getElementById("display-options-form");
 | 
			
		||||
  resultItems = [];
 | 
			
		||||
  receivedMatchCountElement = document.getElementById("received-match-count");
 | 
			
		||||
  textLookupCountElement = document.getElementById("text-lookup-count");
 | 
			
		||||
  textTitlesElement = document.getElementById("text-titles");
 | 
			
		||||
  queryResultsProgressElement = document.getElementById("query-results-progress");
 | 
			
		||||
  expertModeSwitchElement = document.getElementById("display-options-form-expert_mode");
 | 
			
		||||
  matchCountElement = document.getElementById("match-count");
 | 
			
		||||
  hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page");
 | 
			
		||||
  contextPerItemElement = document.getElementById("display-options-form-result_context");
 | 
			
		||||
  paginationElements = document.getElementsByClassName("pagination");
 | 
			
		||||
  contextModal = document.getElementById("context-modal");
 | 
			
		||||
  metaDataModal = document.getElementById("meta-data-modal");
 | 
			
		||||
  showMetaDataButton = document.getElementById("show-metadata");
 | 
			
		||||
 | 
			
		||||
  // js list options
 | 
			
		||||
  displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement);
 | 
			
		||||
  resultsListOptions = {page: displayOptionsData["resultsPerPage"],
 | 
			
		||||
  pagination: [{
 | 
			
		||||
      name: "paginationTop",
 | 
			
		||||
      paginationClass: "paginationTop",
 | 
			
		||||
      innerWindow: 8,
 | 
			
		||||
      outerWindow: 1
 | 
			
		||||
    }, {
 | 
			
		||||
      paginationClass: "paginationBottom",
 | 
			
		||||
      innerWindow: 8,
 | 
			
		||||
      outerWindow: 1
 | 
			
		||||
    }],
 | 
			
		||||
    valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}],
 | 
			
		||||
    item: `<span></span>`
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
    // Initialize some Modals
 | 
			
		||||
    contextModal = M.Modal.init(contextModal, {"dismissible": true});
 | 
			
		||||
 | 
			
		||||
    // ###### recreating chunk structure to reuse callback queryRenderResults()
 | 
			
		||||
    full_result_json = {{ query_result_file_content|tojson|safe }};
 | 
			
		||||
    result_json = {};
 | 
			
		||||
    result_json["chunk"] = {};
 | 
			
		||||
    result_json.chunk["cpos_lookup"] = full_result_json.cpos_lookup;
 | 
			
		||||
    result_json.chunk["cpos_ranges"] = full_result_json.cpos_ranges;
 | 
			
		||||
    result_json.chunk["matches"] = full_result_json.matches;
 | 
			
		||||
    result_json.chunk["text_lookup"] = full_result_json.text_lookup;
 | 
			
		||||
 | 
			
		||||
    // Init corpus analysis components
 | 
			
		||||
    data = new Data();
 | 
			
		||||
    resultsList = new ResultsList("result-list", resultsListOptions);
 | 
			
		||||
    resultsMetaData = new MetaData();
 | 
			
		||||
    results = new Results(data, resultsList, resultsMetaData);
 | 
			
		||||
    results.clearAll();  // inits some object keys and values
 | 
			
		||||
    // init some modals
 | 
			
		||||
    let deleteOverlay = () => {
 | 
			
		||||
      let overlay = document.getElementsByClassName("modal-overlay")[0];
 | 
			
		||||
      overlay.remove();
 | 
			
		||||
    };
 | 
			
		||||
    metaDataModal = M.Modal.init(metaDataModal, {"preventScrolling": false,
 | 
			
		||||
                                 "opacity": 0.0,
 | 
			
		||||
                                 "dismissible": false,
 | 
			
		||||
                                 "onOpenEnd": deleteOverlay});
 | 
			
		||||
 | 
			
		||||
    // setting some initial values for user feedback
 | 
			
		||||
    matchCountElement.innerText = full_result_json.match_count;
 | 
			
		||||
 | 
			
		||||
    // Initialization of interactionElemnts
 | 
			
		||||
    // An interactionElement is an object identifing a switch or button via
 | 
			
		||||
    // htmlID. Callbacks are set for these elements which will be triggered on
 | 
			
		||||
    // a pagination interaction by the user or if the status of the element has
 | 
			
		||||
    // been altered. (Like the switche has ben turned on or off).
 | 
			
		||||
    interactionElements = new InteractionElements();
 | 
			
		||||
    let expertModeInteraction = new InteractionElement("display-options-form-expert_mode");
 | 
			
		||||
    expertModeInteraction.setCallback("on",
 | 
			
		||||
                                      results.jsList.expertModeOn,
 | 
			
		||||
                                      results.jsList,
 | 
			
		||||
                                      ["query-display"])
 | 
			
		||||
    expertModeInteraction.setCallback("off",
 | 
			
		||||
                                      results.jsList.expertModeOff,
 | 
			
		||||
                                      results.jsList,
 | 
			
		||||
                                      ["query-display"])
 | 
			
		||||
 | 
			
		||||
    let activateInspectInteraction = new InteractionElement("inspect",
 | 
			
		||||
                                                            false);
 | 
			
		||||
    activateInspectInteraction.setCallback("noCheck",
 | 
			
		||||
                                            results.jsList.activateInspect,
 | 
			
		||||
                                            results.jsList);
 | 
			
		||||
 | 
			
		||||
    let changeContextInteraction = new InteractionElement("display-options-form-results_per_page",
 | 
			
		||||
                                                          false);
 | 
			
		||||
    changeContextInteraction.setCallback("noCheck",
 | 
			
		||||
                                        results.jsList.changeContext,
 | 
			
		||||
                                        results.jsList)
 | 
			
		||||
    interactionElements.addInteractions([expertModeInteraction, activateInspectInteraction, changeContextInteraction]);
 | 
			
		||||
 | 
			
		||||
    // checks if a change for every interactionElement happens and executes
 | 
			
		||||
    // the callbacks accordingly
 | 
			
		||||
    interactionElements.onChangeExecute();
 | 
			
		||||
 | 
			
		||||
    // eventListener if pagination is used to apply new context size to new page
 | 
			
		||||
    // and also activate inspect match if progress is 100
 | 
			
		||||
    // also adds more interaction buttons like add to sub results
 | 
			
		||||
    for (let element of paginationElements) {
 | 
			
		||||
      element.addEventListener("click", (event) => {
 | 
			
		||||
        results.jsList.pageChangeEventInteractionHandler(interactionElements);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // render results in table imported parameter is true
 | 
			
		||||
    queryRenderResults(result_json, true);
 | 
			
		||||
 | 
			
		||||
    // ### Show corpus Metadata
 | 
			
		||||
    showMetaDataButton.onclick = () => {
 | 
			
		||||
      metaDataModal.open();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // live update of hits per page if hits per page value is changed
 | 
			
		||||
    let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);
 | 
			
		||||
    hitsPerPageInputElement.onchange = changeHitsPerPageBind;
 | 
			
		||||
 | 
			
		||||
    // live update of lr context per item if context value is changed
 | 
			
		||||
    contextPerItemElement.onchange = results.jsList.changeContext;
 | 
			
		||||
 | 
			
		||||
    // new insepct event listener makeing use of javascript bubbleing
 | 
			
		||||
    let resultsTable = document.getElementById("query-results");
 | 
			
		||||
    resultsTable.addEventListener("click", (event) => {
 | 
			
		||||
      if (event.target.classList.contains("inspect-btn")) {
 | 
			
		||||
        const dataIndex = event.target.closest("tr").dataset.index;
 | 
			
		||||
        const fakeResponse = results.jsList.createFakeResponse();
 | 
			
		||||
        results.jsList.showMatchContext(fakeResponse);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // scroll to top button if user scrolled down the list
 | 
			
		||||
    let headline = document.querySelector(".headline");
 | 
			
		||||
    let scrollToTop = document.querySelector("#menu-scroll-to-top-div");
 | 
			
		||||
    window.addEventListener("scroll", (event) => {
 | 
			
		||||
      if (pageYOffset > 250) {
 | 
			
		||||
        scrollToTop.classList.toggle("hide", false);
 | 
			
		||||
      } else {
 | 
			
		||||
        scrollToTop.classList.toggle("hide", true);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    scrollToTop.onclick = () => {
 | 
			
		||||
      headline.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -90,8 +90,9 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  var corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, "Corpus");
 | 
			
		||||
  var queryResultList = new RessourceList("query-results", nopaque.queryResultsSubscribers, "QueryResult");
 | 
			
		||||
<script type="module">
 | 
			
		||||
  import {RessourceList} from '../../static/js/nopaque.lists.js';
 | 
			
		||||
  let corpusList = new RessourceList("corpora", nopaque.corporaSubscribers, "Corpus");
 | 
			
		||||
  let queryResultList = new RessourceList("query-results", nopaque.queryResultsSubscribers, "QueryResult");
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ results. -->
 | 
			
		||||
        <th style="width: 25%">Right Context</th>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody class="list" id="query-results">
 | 
			
		||||
    <tbody class="list" id="query-results-table">
 | 
			
		||||
    </tbody>
 | 
			
		||||
  </table>
 | 
			
		||||
  <ul class="pagination paginationBottom"></ul>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user