<commit_before>const {Transform} = require("prosemirror-transform")
const {Node} = require("prosemirror-model")
const {Selection} = require("./selection")

let warnedAboutReplaceSelection = false

// ::- A selection-aware extension of `Transform`. Use
// [`EditorState.tr`](#state.EditorState.tr) to create an instance.
class EditorTransform extends Transform {
  constructor(state) {
    super(state.doc)
    this.state = state
    this.curSelection = state.selection
    this.curSelectionAt = 0
    this.selectionSet = false
  }

  // :: Selection
  // The transform's current selection. This defaults to the
  // editor selection [mapped](#state.Selection.map) through the steps in
  // this transform, but can be overwritten with
  // [`setSelection`](#state.EditorTransform.setSelection).
  get selection() {
    if (this.curSelectionAt < this.steps.length) {
      this.curSelection = this.curSelection.map(this.doc, this.mapping.slice(this.curSelectionAt))
      this.curSelectionAt = this.steps.length
    }
    return this.curSelection
  }

  // :: (Selection) → EditorTransform
  // Update the transform's current selection. This will determine the
  // selection that the editor gets when the transform is applied.
  setSelection(selection) {
    this.curSelection = selection
    this.curSelectionAt = this.steps.length
    this.selectionSet = true
    return this
  }

  // :: (Slice) → EditorTransform
  replaceSelection(slice) {
    if (slice instanceof Node) { // FIXME backwards compat hack, drop in next release
      if (!warnedAboutReplaceSelection) {
        if (typeof console != "undefined" && console.warn)
          console.warn("Calling EditorTransform.replaceSelection with a node is deprecated. Use replaceSelectionWith")
        warnedAboutReplaceSelection = true
      }
      return this.replaceSelectionWith(slice)
    }
    let {from, to} = this.selection, startLen = this.steps.length
    this.replaceRange(from, to, slice)
    // Move the selection to the position after the inserted content.
    // When that ended in an inline node, search backwards, to get the
    // position after that node. If not, search forward.
    let lastNode = slice.content.lastChild, lastParent = null
    for (let i = 0; i < slice.openRight; i++) {
      lastParent = lastNode
      lastNode = lastNode.lastChild
    }
    selectionToInsertionEnd(this, startLen, (lastNode ? lastNode.isInline : lastParent && lastParent.isTextblock) ? -1 : 1)
    return this
  }

  // :: (Node, ?bool) → EditorTransform
  // Replace the selection with the given node or slice, or delete it
  // if `content` is null. When `inheritMarks` is true and the content
  // is inline, it inherits the marks from the place where it is
  // inserted.
  replaceSelectionWith(node, inheritMarks) {
    let {from, to} = this.selection, startLen = this.steps.length
    if (inheritMarks !== false)
      node = node.mark(this.state.storedMarks || this.doc.marksAt(from, to > from))
    this.replaceRangeWith(from, to, node)
    selectionToInsertionEnd(this, startLen, node.isInline ? -1 : 1)
    return this
  }

  // :: () → EditorTransform
  // Delete the selection.
  deleteSelection() {
    let {from, to} = this.selection
    return this.deleteRange(from, to)
  }

  // :: (string, from: ?number, to: ?number) → EditorTransform
  // Replace the given range, or the selection if no range is given,
  // with a text node containing the given string.
  insertText(text, from, to = from) {
    if (from == null) {
      if (!text) return this.deleteSelection()
      return this.replaceSelectionWith(this.state.schema.text(text), true)
    } else {
      if (!text) return this.deleteRange(from, to)
      let node = this.state.schema.text(text, this.state.storedMarks || this.doc.marksAt(from, to > from))
      return this.replaceRangeWith(from, to, node)
    }
  }

  // :: (?Object) → TransformAction
  // Create a transform action. `options` can be given to add extra
  // properties to the action object.
  action(options) {
    let action = {type: "transform",
                  transform: this,
                  selection: this.selectionSet ? this.selection : null,
                  time: Date.now()}
    if (options) for (let prop in options) action[prop] = options[prop]
    return action
  }

  // :: () → TransformAction
  // Create a transform action with the `scrollIntoView` property set
  // to true (this is common enough to warrant a shortcut method).
  scrollAction() {
    return this.action({scrollIntoView: true})
  }
}
exports.EditorTransform = EditorTransform

function selectionToInsertionEnd(tr, startLen, bias) {
  if (tr.steps.length == startLen) return
  let map = tr.mapping.maps[tr.mapping.maps.length - 1], end
  map.forEach((_from, _to, _newFrom, newTo) => end = newTo)
  if (end != null) tr.setSelection(Selection.near(tr.doc.resolve(end), bias))
}

// :: (Action, (transform: Transform)) → Action
// If, when dispatching actions, you need to extend a transform action
// with additional steps, you can use this helper. It takes an action
// and a function that extends a transform, and will update the action
// to reflect any additional steps. It won't call the function if the
// action is not a transform action or a
// [sealed](#state.TransformAction.sealed) transform action.
function extendTransformAction(action, f) {
  if (action.type != "transform" || action.sealed) return action
  let tr = action.transform, steps = tr.steps.length, set = tr.selectionSet
  f(tr)
  if (!set && tr.selectionSet)
    action.selection = tr.selection
  else if (action.selection && tr.steps.length > steps)
    action.selection = action.selection.map(tr.doc, tr.mapping.slice(steps))
  return action
}
exports.extendTransformAction = extendTransformAction
<commit_msg>Drop backwards-compat kludge<commit_after>const {Transform} = require("prosemirror-transform")
const {Selection} = require("./selection")

// ::- A selection-aware extension of `Transform`. Use
// [`EditorState.tr`](#state.EditorState.tr) to create an instance.
class EditorTransform extends Transform {
  constructor(state) {
    super(state.doc)
    this.state = state
    this.curSelection = state.selection
    this.curSelectionAt = 0
    this.selectionSet = false
  }

  // :: Selection
  // The transform's current selection. This defaults to the
  // editor selection [mapped](#state.Selection.map) through the steps in
  // this transform, but can be overwritten with
  // [`setSelection`](#state.EditorTransform.setSelection).
  get selection() {
    if (this.curSelectionAt < this.steps.length) {
      this.curSelection = this.curSelection.map(this.doc, this.mapping.slice(this.curSelectionAt))
      this.curSelectionAt = this.steps.length
    }
    return this.curSelection
  }

  // :: (Selection) → EditorTransform
  // Update the transform's current selection. This will determine the
  // selection that the editor gets when the transform is applied.
  setSelection(selection) {
    this.curSelection = selection
    this.curSelectionAt = this.steps.length
    this.selectionSet = true
    return this
  }

  // :: (Slice) → EditorTransform
  replaceSelection(slice) {
    let {from, to} = this.selection, startLen = this.steps.length
    this.replaceRange(from, to, slice)
    // Move the selection to the position after the inserted content.
    // When that ended in an inline node, search backwards, to get the
    // position after that node. If not, search forward.
    let lastNode = slice.content.lastChild, lastParent = null
    for (let i = 0; i < slice.openRight; i++) {
      lastParent = lastNode
      lastNode = lastNode.lastChild
    }
    selectionToInsertionEnd(this, startLen, (lastNode ? lastNode.isInline : lastParent && lastParent.isTextblock) ? -1 : 1)
    return this
  }

  // :: (Node, ?bool) → EditorTransform
  // Replace the selection with the given node or slice, or delete it
  // if `content` is null. When `inheritMarks` is true and the content
  // is inline, it inherits the marks from the place where it is
  // inserted.
  replaceSelectionWith(node, inheritMarks) {
    let {from, to} = this.selection, startLen = this.steps.length
    if (inheritMarks !== false)
      node = node.mark(this.state.storedMarks || this.doc.marksAt(from, to > from))
    this.replaceRangeWith(from, to, node)
    selectionToInsertionEnd(this, startLen, node.isInline ? -1 : 1)
    return this
  }

  // :: () → EditorTransform
  // Delete the selection.
  deleteSelection() {
    let {from, to} = this.selection
    return this.deleteRange(from, to)
  }

  // :: (string, from: ?number, to: ?number) → EditorTransform
  // Replace the given range, or the selection if no range is given,
  // with a text node containing the given string.
  insertText(text, from, to = from) {
    if (from == null) {
      if (!text) return this.deleteSelection()
      return this.replaceSelectionWith(this.state.schema.text(text), true)
    } else {
      if (!text) return this.deleteRange(from, to)
      let node = this.state.schema.text(text, this.state.storedMarks || this.doc.marksAt(from, to > from))
      return this.replaceRangeWith(from, to, node)
    }
  }

  // :: (?Object) → TransformAction
  // Create a transform action. `options` can be given to add extra
  // properties to the action object.
  action(options) {
    let action = {type: "transform",
                  transform: this,
                  selection: this.selectionSet ? this.selection : null,
                  time: Date.now()}
    if (options) for (let prop in options) action[prop] = options[prop]
    return action
  }

  // :: () → TransformAction
  // Create a transform action with the `scrollIntoView` property set
  // to true (this is common enough to warrant a shortcut method).
  scrollAction() {
    return this.action({scrollIntoView: true})
  }
}
exports.EditorTransform = EditorTransform

function selectionToInsertionEnd(tr, startLen, bias) {
  if (tr.steps.length == startLen) return
  let map = tr.mapping.maps[tr.mapping.maps.length - 1], end
  map.forEach((_from, _to, _newFrom, newTo) => end = newTo)
  if (end != null) tr.setSelection(Selection.near(tr.doc.resolve(end), bias))
}

// :: (Action, (transform: Transform)) → Action
// If, when dispatching actions, you need to extend a transform action
// with additional steps, you can use this helper. It takes an action
// and a function that extends a transform, and will update the action
// to reflect any additional steps. It won't call the function if the
// action is not a transform action or a
// [sealed](#state.TransformAction.sealed) transform action.
function extendTransformAction(action, f) {
  if (action.type != "transform" || action.sealed) return action
  let tr = action.transform, steps = tr.steps.length, set = tr.selectionSet
  f(tr)
  if (!set && tr.selectionSet)
    action.selection = tr.selection
  else if (action.selection && tr.steps.length > steps)
    action.selection = action.selection.map(tr.doc, tr.mapping.slice(steps))
  return action
}
exports.extendTransformAction = extendTransformAction
<|endoftext|>


<commit_before>/**
 * Graph is a mathematical graph library for javascript. It is designed to be a functional
 * stand-alone library. It is a bare-bones library designed to be light-weight and scalable.
 *
 * Definitions are taken from the Wikipedia glossary of mathematical graph theory. That's a
 * great place to start to get a basic understanding of abstract graph theory without
 * the implemetation details.
 *
 * http://en.wikipedia.org/wiki/Glossary_of_graph_theory
 *
 * Note: In its current form, the library supports only undirected graphs.
 *
 * @author yoni ben-meshulam
 */
;(function(){
/**
 * A vertex (basic element) is simply drawn as a node or a dot. The vertex set of G
 * is usually denoted by V(G), or V when there is no danger of confusion.
 */
Vertex = function(obj){
  // a Vertex is nothing more than an object
  // this allows you to extend vertices in any way desirable
  for(var k in obj) {
    this[k]=obj[k];
  }
};
/**
 * An edge (a set of two elements) is drawn as a line connecting two vertices, called
 * endvertices, or endpoints. An edge with endvertices x and y is denoted by xy
 * (without any symbol in between). The edge set of G is usually denoted by E(G), or
 * E when there is no danger of confusion.
 */
Edge = function(v1, v2){
  if((!v1) || (!v2)) {
    throw "Illegal arguments for constructing an Edge. Expected two vertices.";
  }
  this.v1 = v1;
  this.v2 = v2;

  this.directed = false;
  // A loop is an edge whose endvertices are the same vertex.
  this.isLoop = function() {
    if(v1===v2) return true;
    return false;
  };

  // A link has two distinct endvertices.
  this.isLink = function() {
    return !this.isLoop();
  };

  // some functions have different implementations, depending on if the graph is directed
  if(this.directed) {
    // The multiplicity of an edge is the number of multiple edges sharing the same endvertices
    this.multiplicity = function(G) {
      var m = 0;
      for(var i in G.E) {
        var edge = G.E[i];
        if((edge.v1===this.v1 && edge.v2===this.v2)
        || (edge.v1===this.v2 && edge.v2===this.v1)) {
          m++;
        }
      }
      return m;
    };
  } else {
    // The multiplicity of an edge is the number of multiple edges sharing the same endvertices
    this.multiplicity = function(G) {
      var m = 0;
      for(var i in G.E) {
        var edge = G.E[i];
        if(edge.v1===this.v1 && edge.v2===this.v2) {
          m++;
        }
      }
      return m;
    };
  }

  // An edge is multiple if there is another edge with the same endvertices; otherwise it is simple.
  this.isMultiple = function(G) {
    return this.multiplicity(G) > 1;
  };
  this.isSimple = function(e) {
    return this.multiplicity(G) === 1;
  };
};
/**
 * A graph G consists of two types of elements, namely vertices and edges. Every edge
 * has two endpoints in the set of vertices, and is said to connect or join the two
 * endpoints. An edge can thus be defined as a set of two vertices (or an ordered pair,
 * in the case of a directed graph - see Section Direction).
 *
 * Uniqueness of vertices and edges is not guaranteed.
 */
Graph = function(){
  this.V = []; // the set of vertices in this graph
  this.E = []; // the set of edges in this graph

  this.directed = false;

  // adds the given vertex to the graph
  this.addVertex = function(v) {
    if(typeof(v)!='object') throw "A vertex must be an object.";
    this.V.push(v);
    return true; // success
  };
  // adds the given vertices to the graph
  this.addVertices = function(V) {
    for(var i in V) {
      this.addVertex(V[i]);
    }
    return true; // success
  };
  // adds the given edge to the graph
  this.addEdge = function(e) {
    this.E.push(e);
    return true; // success
  };
  // adds the given edges to the graph
  this.addEdges = function(E) {
    for(var i in E) {
      this.addEdge(E[i]);
    }
    return true; // success
  };
  // The order of a graph is the number of vertices, i.e. |V(G)|.
  this.order = function() {
    return this.V.length;
  };
  // The size of a graph is the number of its edges, i.e. |E(G)|.
  this.size = function() {
    return this.E.length;
  };
  // The multiplicity of a graph is the maximum multiplicity of its edges.
  this.multiplicity = function(){
    var max = 0;
    for (var i in this.E) {
      var eMultiplicity = this.E[i].multiplicity(this);
      if(max < eMultiplicity) max = eMultiplicity;
    }
    return max;
  };
  // a graph has a loop if any of its edges is a loop
  this.hasLoops = function() {
    var E = this.E;
    for (var i in E) {
      if(E[i].isLoop()) return true;
    }
    return false;
  };
  // A graph is a simple graph if it has no multiple edges or loops
  this.isSimple = function() {
    return !(this.hasLoops()||this.isMulti());
  };
  // A graph is a multigraph if it has multiple edges, but no loops
  this.isMulti = function() {
    return this.multiplicity()!=1;
  };
  // A graph is a pseudograph if it contains both multiple edges and loops
  this.isPseudo = function() {
    return this.hasLoops()&&(this.multiplicity()!==1);
  };
  // An anti-edge is an edge that "is not there". More formally, for two vertices u and v, {u,v} is an anti-edge in a graph G whenever {u,v} is not an edge in G. This means that there is either no edge between the two vertices or (for directed graphs) at most one of (u,v) and (v,u) from v is an arc in G.
  this.isAntiEdge = function(e) {
    return !this.edgeExists(e);
  };

  // some functions have different implementations, depending on if the graph is directed
  if (this.directed) {
    // Returns true if the edge exists in the graph
    this.edgeExists = function(e){
      for (var i in this.E) {
        var edge = this.E[i];
        if (edge.v1 === e.v1 && edge.v2 === e.v2) {
          return true;
        }
        return false;
      }
    };
  } else {
    // Returns true if the edge exists in the graph
    this.edgeExists = function(e){
      for (var i in this.E) {
        var edge = this.E[i];
        if ((edge.v1 === e.v1 && edge.v2 === e.v2) ||
        (edge.v1 === e.v2 && edge.v2 === e.v1)) {
          return true;
        }
      }
      return false;
    };
  }
  //The complement of a graph G is a graph with the same vertex set as G but with an edge set such that xy is an edge in complement(G) if and only if xy is not an edge in G.
  this.complement = function() {
    var C = new Graph();
    C.addVertices(this.V);
    for(var i in this.V) {
      for(var j in this.V) {
        var e = new Edge(this.V[i], this.V[j]);
        if(this.isAntiEdge(e)) {
          C.addEdge(e);
        }
      }
    }
    return C;
  };
};
})();<commit_msg> * Added return statement to Graph#complement.  * Added explicit 'return false' to a few methods.<commit_after>/**
 * Graph is a mathematical graph library for javascript. It is designed to be a functional
 * stand-alone library. It is a bare-bones library designed to be light-weight and scalable.
 *
 * Definitions are taken from the Wikipedia glossary of mathematical graph theory. That's a
 * great place to start to get a basic understanding of abstract graph theory without
 * the implemetation details.
 *
 * http://en.wikipedia.org/wiki/Glossary_of_graph_theory
 *
 * Note: In its current form, the library supports only undirected graphs.
 *
 * @author yoni ben-meshulam
 */
;(function(){
/**
 * A vertex (basic element) is simply drawn as a node or a dot. The vertex set of G
 * is usually denoted by V(G), or V when there is no danger of confusion.
 */
Vertex = function(obj){
  // a Vertex is nothing more than an object
  // this allows you to extend vertices in any way desirable
  for(var k in obj) {
    this[k]=obj[k];
  }
};
/**
 * An edge (a set of two elements) is drawn as a line connecting two vertices, called
 * endvertices, or endpoints. An edge with endvertices x and y is denoted by xy
 * (without any symbol in between). The edge set of G is usually denoted by E(G), or
 * E when there is no danger of confusion.
 */
Edge = function(v1, v2){
  if((!v1) || (!v2)) {
    throw "Illegal arguments for constructing an Edge. Expected two vertices.";
  }
  this.v1 = v1;
  this.v2 = v2;

  this.directed = false;
  // A loop is an edge whose endvertices are the same vertex.
  this.isLoop = function() {
    if(v1===v2) return true;
    return false;
  };

  // A link has two distinct endvertices.
  this.isLink = function() {
    return !this.isLoop();
  };

  // some functions have different implementations, depending on if the graph is directed
  if(this.directed) {
    // The multiplicity of an edge is the number of multiple edges sharing the same endvertices
    this.multiplicity = function(G) {
      var m = 0;
      for(var i in G.E) {
        var edge = G.E[i];
        if((edge.v1===this.v1 && edge.v2===this.v2)
        || (edge.v1===this.v2 && edge.v2===this.v1)) {
          m++;
        }
      }
      return m;
    };
  } else {
    // The multiplicity of an edge is the number of multiple edges sharing the same endvertices
    this.multiplicity = function(G) {
      var m = 0;
      for(var i in G.E) {
        var edge = G.E[i];
        if(edge.v1===this.v1 && edge.v2===this.v2) {
          m++;
        }
      }
      return m;
    };
  }

  // An edge is multiple if there is another edge with the same endvertices; otherwise it is simple.
  this.isMultiple = function(G) {
    return this.multiplicity(G) > 1;
  };
  this.isSimple = function(e) {
    return this.multiplicity(G) === 1;
  };
};
/**
 * A graph G consists of two types of elements, namely vertices and edges. Every edge
 * has two endpoints in the set of vertices, and is said to connect or join the two
 * endpoints. An edge can thus be defined as a set of two vertices (or an ordered pair,
 * in the case of a directed graph - see Section Direction).
 *
 * Uniqueness of vertices and edges is not guaranteed.
 */
Graph = function(){
  this.V = []; // the set of vertices in this graph
  this.E = []; // the set of edges in this graph

  this.directed = false;

  // adds the given vertex to the graph
  this.addVertex = function(v) {
    if(typeof(v)!='object') throw "A vertex must be an object.";
    this.V.push(v);
    return true; // success
  };
  // adds the given vertices to the graph
  this.addVertices = function(V) {
    for(var i in V) {
      this.addVertex(V[i]);
    }
    return true; // success
  };
  // adds the given edge to the graph
  this.addEdge = function(e) {
    this.E.push(e);
    return true; // success
  };
  // adds the given edges to the graph
  this.addEdges = function(E) {
    for(var i in E) {
      this.addEdge(E[i]);
    }
    return true; // success
  };
  // The order of a graph is the number of vertices, i.e. |V(G)|.
  this.order = function() {
    return this.V.length;
  };
  // The size of a graph is the number of its edges, i.e. |E(G)|.
  this.size = function() {
    return this.E.length;
  };
  // The multiplicity of a graph is the maximum multiplicity of its edges.
  this.multiplicity = function(){
    var max = 0;
    for (var i in this.E) {
      var eMultiplicity = this.E[i].multiplicity(this);
      if(max < eMultiplicity) max = eMultiplicity;
    }
    return max;
  };
  // a graph has a loop if any of its edges is a loop
  this.hasLoops = function() {
    var E = this.E;
    for (var i in E) {
      if(E[i].isLoop()) return true;
    }
    return false;
  };
  // A graph is a simple graph if it has no multiple edges or loops
  this.isSimple = function() {
    return !(this.hasLoops()||this.isMulti());
  };
  // A graph is a multigraph if it has multiple edges, but no loops
  this.isMulti = function() {
    return this.multiplicity()!=1;
  };
  // A graph is a pseudograph if it contains both multiple edges and loops
  this.isPseudo = function() {
    return this.hasLoops()&&(this.multiplicity()!==1);
  };
  // An anti-edge is an edge that "is not there". More formally, for two vertices u and v, {u,v} is an anti-edge in a graph G whenever {u,v} is not an edge in G. This means that there is either no edge between the two vertices or (for directed graphs) at most one of (u,v) and (v,u) from v is an arc in G.
  this.isAntiEdge = function(e) {
    return !this.edgeExists(e);
  };

  // some functions have different implementations, depending on if the graph is directed
  if (this.directed) {
    // Returns true if the edge exists in the graph
    this.edgeExists = function(e){
      for (var i in this.E) {
        var edge = this.E[i];
        if (edge.v1 === e.v1 && edge.v2 === e.v2) {
          return true;
        }
        return false;
      }
    };
  } else {
    // Returns true if the edge exists in the graph
    this.edgeExists = function(e){
      for (var i in this.E) {
        var edge = this.E[i];
        if ((edge.v1 === e.v1 && edge.v2 === e.v2) ||
        (edge.v1 === e.v2 && edge.v2 === e.v1)) {
          return true;
        }
      }
      return false;
    };
  }

  //The complement of a graph G is a graph with the same vertex set as G but with an edge set such that xy is an edge in complement(G) if and only if xy is not an edge in G.
  this.complement = function() {
    var C = new Graph();
    C.addVertices(this.V);
    for(var i in this.V) {
      for(var j in this.V) {
        var e = new Edge(this.V[i], this.V[j]);
        if(this.isAntiEdge(e)) {
          C.addEdge(e);
        }
      }
    }
    return C;
  };

};
})();<|endoftext|>


<commit_before>// ======== DB-Model: ========
// "_id"           -> ID
// "region"        -> ID_region
// "title"         -> String
// "description"   -> String
//
// start:      Time the events starts
//
// end:        Time the event ends
//
// location:   Object with location info
//             _id:     Optional reference to a document in the Locations collection
//                      If this is set, the fields name, loc, and address are synchronized
//             name:    Descriptive name for the location
//             loc:     Event location in GeoJSON format
//             address: Address string where the event will take place
//
// room:       Where inside the building the event will take place
//
// "createdby"     -> userId
// "time_created"  -> Date
// "time_lastedit" -> Date
// "course_id"     -> ID_course  (maybe list in Future)
// ===========================

Events = new Meteor.Collection("Events");
if (Meteor.isServer) Events._ensureIndex({loc : "2dsphere"});

mayEditEvent = function(user, event) {
	if (!user) return false;
	if (event.createdBy == user._id) return true;
	if (privileged(user, 'admin')) return true;
	if (event.course_id) {
		var course = Courses.findOne({_id: event.course_id, members: {$elemMatch: { user: user._id, roles: 'team' }}});
		if (course) return true;
	}
	return false;
};

affectedReplicaSelectors = function(event) {
	// If the event itself is not in the DB, we don't expect it to have replicas
	if (!event._id) return { _id: -1 }; // Finds nothing

	// Only replicas future from the edited event are updated
	// replicas in the past are never updated
	var futureDate = event.start;
	if (futureDate < new Date()) futureDate = new Date();

	var selector = {
		_id: { $ne: event._id }, // so the event is not considered to be its own replica
		start: { $gte: futureDate }
	};

	if (event.replicaOf) {
		selector.$or = [
			{ replicaOf: event.replicaOf },
			{ _id: event.replicaOf }
		];
	} else {
		selector.replicaOf = event._id;
	}

	return selector;
};

// Sync location fields of the event document
updateEventLocation = function(eventId) {
	untilClean(function() {
		var event = Events.findOne(eventId);
		if (!event) return true; // Nothing was successfully updated, we're done.

		if (typeof event.location != 'object') {
			// This happens only at creation when the field was not initialized correctly
			Events.update(event._id, { $set:{ location: {} }});
			return false;
		}

		var location = false;
		if (event.location._id) {
			location = Locations.findOne(event.location._id);
		}

		var update;
		if (location) {
			// Do not update location for historical events
			if (event.start < new Date()) return true;

			// Sync values to the values set in the location document
			update = { $set: {
				'location.name': location.name,
				'location.address': location.address,
				'location.loc': location.loc
			}};
		} else {
			// If the location vanished we delete the locationId but let the cached fields live on
			update = { $unset: { 'location._id': 1 }};
		}

		// We have to use the Mongo collection API because Meteor does not
		// expose the modification counter
		var r = Events.rawCollection();
		var result = Meteor.wrapAsync(r.update, r)(
			{ _id: event._id },
			update,
			{ fullResult: true }
		);

		return result.nModified === 0;
	});
};

Meteor.methods({
	saveEvent: function(eventId, changes, updateReplicas) {
		check(eventId, String);

		var expectedFields = {
			title:       String,
			description: String,
			location:    Object,
			room:        Match.Optional(String),
			start:       Match.Optional(Date),
			end:         Match.Optional(Date),
			files:       Match.Optional(Array),
			mentors:     Match.Optional(Array),
			host:        Match.Optional(Array),
			replicaOf:   Match.Optional(String),
			course_id:   Match.Optional(String),
			internal:    Match.Optional(Boolean),
			groups:      Match.Optional([String]),
		};

		var isNew = eventId === '';
		if (isNew) {
			expectedFields.region = String;
		}

		check(changes, expectedFields);

		var user = Meteor.user();
		if (!user) {
			if (Meteor.isClient) {
				pleaseLogin();
				return;
			} else {
				throw new Meteor.Error(401, "please log in");
			}
		}

		var now = new Date();

		changes.time_lastedit = now;

		var event = false;
		if (isNew) {
			changes.time_created = now;
			if (changes.course_id && !mayEditEvent(user, changes)) {
				throw new Meteor.Error(401, "not permitted");
			}

			if (!changes.start || changes.start < now) {
				throw new Meteor.Error(400, "Event date in the past or not provided");
			}

			if (changes.course_id) {
				// Inherit groups from the course
				var course = Courses.findOne(changes.course_id);
				changes.groups = course.groups;
			} else {
				var tested_groups = [];
				if (changes.groups) {
					tested_groups = _.map(changes.groups, function(groupId) {
						var group = Groups.findOne(groupId);
						if (!group) throw new Meteor.Error(404, "no group with id "+groupId);
						return group._id;
					});
				}
				changes.groups = tested_groups;
			}

			// Coerce faulty end dates
			if (!changes.end || changes.end < changes.start) {
				changes.end = changes.start;
			}

			// Synthesize event document because the code below relies on it
			event = { course_id: changes.course_id };

		} else {

			event = Events.findOne(eventId);
			if (!event) throw new Meteor.Error(404, "No such event");
			if (!mayEditEvent(user, event)) throw new Meteor.Error(401, "not permitted");

			// Not allowed to update
			delete changes.replicaOf;
			delete changes.groups;
		}

		// Don't allow moving past events or moving events into the past
		if (!changes.start || changes.start < now) {
			changes.start = event.start;
		}

		if (changes.end && changes.end < changes.start) {
			throw new Meteor.Error(400, "End before start");
		}

		if (Meteor.isServer) {
			changes.description = saneHtml(changes.description);
		}

		if (changes.title) {
			changes.title = saneText(changes.title).substring(0, 1000);
			changes.slug = getSlug(changes.title);
		}

		if (isNew) {
			changes.createdBy = user._id;
			eventId = Events.insert(changes);
		} else {
			Events.update(eventId, { $set: changes });

			if (updateReplicas) {
				delete changes.start;
				delete changes.end;

				Events.update(affectedReplicaSelectors(event), { $set: changes }, { multi: true });
			}
		}

		// the assumption is that all replicas have the same course if any
		if (event.course_id) Meteor.call('updateNextEvent', event.course_id);

		return eventId;
	},

	removeEvent: function(eventId) {
		check(eventId, String);

		var user = Meteor.user();
		if (!user) throw new Meteor.Error(401, "please log in");
		var event = Events.findOne(eventId);
		if (!event) throw new Meteor.Error(404, "No such event");
		if (!mayEditEvent(user, event)) throw new Meteor.Error(401, "not permitted");

		Events.remove(eventId);

		if (event.course_id) Meteor.call('updateNextEvent', event.course_id);

		return Events.findOne({id:eventId}) === undefined;
	},

	removeFile: function(eventId,fileId) {
		check(eventId, String);

		var user = Meteor.user();
		if (!user) throw new Meteor.Error(401, "please log in");
		var event = Events.findOne(eventId);
		if (!event) throw new Meteor.Error(404, "No such event");
		if (!mayEditEvent(user, event)) throw new Meteor.Error(401, "not permitted");

		var tmp = [];

		for(var i = 0; i < event.files.length; i++ ){
			var fileObj = event.files[i];
			if( fileObj._id != fileId){
				tmp.push(fileObj);
			}
		}

		var edits = {
			files: tmp,
		};
		var upd = Events.update(eventId, { $set: edits });
		return upd;
	},

	// Update the location fields for all events matching the selector
	updateEventLocation: function(selector) {
		var idOnly = { fields: { _id: 1 } };
		Events.find(selector, idOnly).forEach(function(event) {
			updateEventLocation(event._id);
		});
	}
});

/* Find events for given filters
 *
 * filter: dictionary with filter options
 *   search: string of words to search for
 *   period: include only events that overlap the given period (list of start and end date)
 *   start: only events that end after this date
 *   before: only events that ended before this date
 *   ongoing: only events that are ongoing during this date
 *   end: only events that started before this date
 *   after: only events starting after this date
 *   location: only events at this location (string match)
 *   room: only events in this room (string match)
 *   standalone: only events that are not attached to a course
 *   region: restrict to given region
 *   categories: list of category ID the event must be in
 *   group: the event must be in that group (ID)
 *   course: only events for this course (ID)
 * limit: how many to find
 *
 * The events are sorted by start date (ascending, before-filter causes descending order)
 *
 */
eventsFind = function(filter, limit) {
	var find = {};
	var and = [];
	var options = {
		sort: { start: 1 }
	};

	if (limit > 0) {
		options.limit = limit;
	}

	if (filter.period) {
		find.start = { $lt: filter.period[1] }; // Start date before end of period
		find.end = { $gte: filter.period[0] }; // End date after start of period
	}

	if (filter.start) {
		and.push({ end: { $gte: filter.start } });
	}

	if (filter.end) {
		and.push({ start: { $lte: filter.end } });
	}

	if (filter.after) {
		find.start = { $gt: filter.after };
	}

	if (filter.ongoing) {
		find.start = { $lte: filter.ongoing };
		find.end = { $gte: filter.ongoing };
	}

	if (filter.before) {
		find.end = { $lt: filter.before };
		if (!filter.after) options.sort = { start: -1 };
	}

	if (filter.location) {
		find['location._id'] = filter.location;
	}

	if (filter.room) {
		find.room = filter.room;
	}

	if (filter.standalone) {
		find.course_id = { $exists: false };
	}

	if (filter.region) {
		find.region = filter.region;
	}

	if (filter.categories) {
		find.categories = { $all: filter.categories };
	}

	if (filter.group) {
		find.groups = filter.group;
	}

	if (filter.course) {
		find.course_id = filter.course;
	}

	if (filter.search) {
		var searchTerms = filter.search.split(/\s+/);
		searchTerms.forEach(function(searchTerm) {
			and.push({ $or: [
				{ title: { $regex: escapeRegex(searchTerm), $options: 'i' } },
				{ description: { $regex: escapeRegex(searchTerm), $options: 'i' } }
			] });
		});
	}

	if (and.length > 0) {
		find.$and = and;
	}

	return Events.find(find, options);
};
<commit_msg>update events-documentation<commit_after>// ======== DB-Model: ========
// _id             -> ID
// region          -> ID_region
// title           -> String
// description     -> String
// start:          -> Date      (Time the events starts)
// end:            -> Date      (Time the event ends)
//
// location {
//       _id:          Optional reference to a document in the Locations collection
//                         If this is set, the fields name, loc, and address are synchronized
//       name:         Descriptive name for the location
//       loc:          Event location in GeoJSON format
//       address:      Address string where the event will take place
// }
// room            -> String    (Where inside the building the event will take place)
// createdby       -> userId
// time_created    -> Date
// time_lastedit   -> Date
// course_id       -> ID_course  (Maybe [list] of courses in future)
// internal        -> Boolean    (Events are only displayed when group or location-filter is active)
// ===========================

Events = new Meteor.Collection("Events");
if (Meteor.isServer) Events._ensureIndex({loc : "2dsphere"});

mayEditEvent = function(user, event) {
	if (!user) return false;
	if (event.createdBy == user._id) return true;
	if (privileged(user, 'admin')) return true;
	if (event.course_id) {
		var course = Courses.findOne({_id: event.course_id, members: {$elemMatch: { user: user._id, roles: 'team' }}});
		if (course) return true;
	}
	return false;
};

affectedReplicaSelectors = function(event) {
	// If the event itself is not in the DB, we don't expect it to have replicas
	if (!event._id) return { _id: -1 }; // Finds nothing

	// Only replicas future from the edited event are updated
	// replicas in the past are never updated
	var futureDate = event.start;
	if (futureDate < new Date()) futureDate = new Date();

	var selector = {
		_id: { $ne: event._id }, // so the event is not considered to be its own replica
		start: { $gte: futureDate }
	};

	if (event.replicaOf) {
		selector.$or = [
			{ replicaOf: event.replicaOf },
			{ _id: event.replicaOf }
		];
	} else {
		selector.replicaOf = event._id;
	}

	return selector;
};

// Sync location fields of the event document
updateEventLocation = function(eventId) {
	untilClean(function() {
		var event = Events.findOne(eventId);
		if (!event) return true; // Nothing was successfully updated, we're done.

		if (typeof event.location != 'object') {
			// This happens only at creation when the field was not initialized correctly
			Events.update(event._id, { $set:{ location: {} }});
			return false;
		}

		var location = false;
		if (event.location._id) {
			location = Locations.findOne(event.location._id);
		}

		var update;
		if (location) {
			// Do not update location for historical events
			if (event.start < new Date()) return true;

			// Sync values to the values set in the location document
			update = { $set: {
				'location.name': location.name,
				'location.address': location.address,
				'location.loc': location.loc
			}};
		} else {
			// If the location vanished we delete the locationId but let the cached fields live on
			update = { $unset: { 'location._id': 1 }};
		}

		// We have to use the Mongo collection API because Meteor does not
		// expose the modification counter
		var r = Events.rawCollection();
		var result = Meteor.wrapAsync(r.update, r)(
			{ _id: event._id },
			update,
			{ fullResult: true }
		);

		return result.nModified === 0;
	});
};

Meteor.methods({
	saveEvent: function(eventId, changes, updateReplicas) {
		check(eventId, String);

		var expectedFields = {
			title:       String,
			description: String,
			location:    Object,
			room:        Match.Optional(String),
			start:       Match.Optional(Date),
			end:         Match.Optional(Date),
			files:       Match.Optional(Array),
			mentors:     Match.Optional(Array),
			host:        Match.Optional(Array),
			replicaOf:   Match.Optional(String),
			course_id:   Match.Optional(String),
			internal:    Match.Optional(Boolean),
			groups:      Match.Optional([String]),
		};

		var isNew = eventId === '';
		if (isNew) {
			expectedFields.region = String;
		}

		check(changes, expectedFields);

		var user = Meteor.user();
		if (!user) {
			if (Meteor.isClient) {
				pleaseLogin();
				return;
			} else {
				throw new Meteor.Error(401, "please log in");
			}
		}

		var now = new Date();

		changes.time_lastedit = now;

		var event = false;
		if (isNew) {
			changes.time_created = now;
			if (changes.course_id && !mayEditEvent(user, changes)) {
				throw new Meteor.Error(401, "not permitted");
			}

			if (!changes.start || changes.start < now) {
				throw new Meteor.Error(400, "Event date in the past or not provided");
			}

			if (changes.course_id) {
				// Inherit groups from the course
				var course = Courses.findOne(changes.course_id);
				changes.groups = course.groups;
			} else {
				var tested_groups = [];
				if (changes.groups) {
					tested_groups = _.map(changes.groups, function(groupId) {
						var group = Groups.findOne(groupId);
						if (!group) throw new Meteor.Error(404, "no group with id "+groupId);
						return group._id;
					});
				}
				changes.groups = tested_groups;
			}

			// Coerce faulty end dates
			if (!changes.end || changes.end < changes.start) {
				changes.end = changes.start;
			}

			// Synthesize event document because the code below relies on it
			event = { course_id: changes.course_id };

		} else {

			event = Events.findOne(eventId);
			if (!event) throw new Meteor.Error(404, "No such event");
			if (!mayEditEvent(user, event)) throw new Meteor.Error(401, "not permitted");

			// Not allowed to update
			delete changes.replicaOf;
			delete changes.groups;
		}

		// Don't allow moving past events or moving events into the past
		if (!changes.start || changes.start < now) {
			changes.start = event.start;
		}

		if (changes.end && changes.end < changes.start) {
			throw new Meteor.Error(400, "End before start");
		}

		if (Meteor.isServer) {
			changes.description = saneHtml(changes.description);
		}

		if (changes.title) {
			changes.title = saneText(changes.title).substring(0, 1000);
			changes.slug = getSlug(changes.title);
		}

		if (isNew) {
			changes.createdBy = user._id;
			eventId = Events.insert(changes);
		} else {
			Events.update(eventId, { $set: changes });

			if (updateReplicas) {
				delete changes.start;
				delete changes.end;

				Events.update(affectedReplicaSelectors(event), { $set: changes }, { multi: true });
			}
		}

		// the assumption is that all replicas have the same course if any
		if (event.course_id) Meteor.call('updateNextEvent', event.course_id);

		return eventId;
	},

	removeEvent: function(eventId) {
		check(eventId, String);

		var user = Meteor.user();
		if (!user) throw new Meteor.Error(401, "please log in");
		var event = Events.findOne(eventId);
		if (!event) throw new Meteor.Error(404, "No such event");
		if (!mayEditEvent(user, event)) throw new Meteor.Error(401, "not permitted");

		Events.remove(eventId);

		if (event.course_id) Meteor.call('updateNextEvent', event.course_id);

		return Events.findOne({id:eventId}) === undefined;
	},

	removeFile: function(eventId,fileId) {
		check(eventId, String);

		var user = Meteor.user();
		if (!user) throw new Meteor.Error(401, "please log in");
		var event = Events.findOne(eventId);
		if (!event) throw new Meteor.Error(404, "No such event");
		if (!mayEditEvent(user, event)) throw new Meteor.Error(401, "not permitted");

		var tmp = [];

		for(var i = 0; i < event.files.length; i++ ){
			var fileObj = event.files[i];
			if( fileObj._id != fileId){
				tmp.push(fileObj);
			}
		}

		var edits = {
			files: tmp,
		};
		var upd = Events.update(eventId, { $set: edits });
		return upd;
	},

	// Update the location fields for all events matching the selector
	updateEventLocation: function(selector) {
		var idOnly = { fields: { _id: 1 } };
		Events.find(selector, idOnly).forEach(function(event) {
			updateEventLocation(event._id);
		});
	}
});

/* Find events for given filters
 *
 * filter: dictionary with filter options
 *   search: string of words to search for
 *   period: include only events that overlap the given period (list of start and end date)
 *   start: only events that end after this date
 *   before: only events that ended before this date
 *   ongoing: only events that are ongoing during this date
 *   end: only events that started before this date
 *   after: only events starting after this date
 *   location: only events at this location (string match)
 *   room: only events in this room (string match)
 *   standalone: only events that are not attached to a course
 *   region: restrict to given region
 *   categories: list of category ID the event must be in
 *   group: the event must be in that group (ID)
 *   course: only events for this course (ID)
 * limit: how many to find
 *
 * The events are sorted by start date (ascending, before-filter causes descending order)
 *
 */
eventsFind = function(filter, limit) {
	var find = {};
	var and = [];
	var options = {
		sort: { start: 1 }
	};

	if (limit > 0) {
		options.limit = limit;
	}

	if (filter.period) {
		find.start = { $lt: filter.period[1] }; // Start date before end of period
		find.end = { $gte: filter.period[0] }; // End date after start of period
	}

	if (filter.start) {
		and.push({ end: { $gte: filter.start } });
	}

	if (filter.end) {
		and.push({ start: { $lte: filter.end } });
	}

	if (filter.after) {
		find.start = { $gt: filter.after };
	}

	if (filter.ongoing) {
		find.start = { $lte: filter.ongoing };
		find.end = { $gte: filter.ongoing };
	}

	if (filter.before) {
		find.end = { $lt: filter.before };
		if (!filter.after) options.sort = { start: -1 };
	}

	if (filter.location) {
		find['location._id'] = filter.location;
	}

	if (filter.room) {
		find.room = filter.room;
	}

	if (filter.standalone) {
		find.course_id = { $exists: false };
	}

	if (filter.region) {
		find.region = filter.region;
	}

	if (filter.categories) {
		find.categories = { $all: filter.categories };
	}

	if (filter.group) {
		find.groups = filter.group;
	}

	if (filter.course) {
		find.course_id = filter.course;
	}

	if (filter.search) {
		var searchTerms = filter.search.split(/\s+/);
		searchTerms.forEach(function(searchTerm) {
			and.push({ $or: [
				{ title: { $regex: escapeRegex(searchTerm), $options: 'i' } },
				{ description: { $regex: escapeRegex(searchTerm), $options: 'i' } }
			] });
		});
	}

	if (and.length > 0) {
		find.$and = and;
	}

	return Events.find(find, options);
};
<|endoftext|>


<commit_before>

// Create xmlhttprequest object - separated to make it possible to implement fallback, primarily for IE
Util.createRequestObject = u.createRequestObject = function() {
	return new XMLHttpRequest();
}

// Request object
Util.request = u.request = function(node, url, settings) {
//	u.bug("request")

	var request_id = u.randomString(6);

	node[request_id] = {};
	node[request_id].request_url = url;

	// set default values
	node[request_id].request_method = "GET";
	node[request_id].request_async = true;
	node[request_id].request_params = "";
	node[request_id].request_headers = false;
	node[request_id].response_callback = "response";

	// additional info passed to function as JSON object
	if(typeof(settings) == "object") {
		var argument;
		for(argument in settings) {

			switch(argument) {
				case "method"		: node[request_id].request_method		= settings[argument]; break;
				case "params"		: node[request_id].request_params		= settings[argument]; break;
				case "async"		: node[request_id].request_async		= settings[argument]; break;
				case "headers"		: node[request_id].request_headers		= settings[argument]; break;
				case "callback"		: node[request_id].response_callback	= settings[argument]; break;
			}

		}
	}

//	u.bug("request:" + node.request_url + ", " + node.request_method + ", " + node.request_params + ", " + node.request_async + ", " + node.request_headers);

	// regular HTTP request
	if(node[request_id].request_method.match(/GET|POST|PUT|PATCH/i)) {

		node[request_id].HTTPRequest = this.createRequestObject();
		node[request_id].HTTPRequest.node = node;
		node[request_id].HTTPRequest.request_id = request_id;

		// listen for async request state change
		if(node[request_id].request_async) {
			node[request_id].HTTPRequest.onreadystatechange = function() {
				if(this.readyState == 4) {
					// process async response
					u.validateResponse(this);
				}
			}
		}

		// perform request
		try {
			// perform GET request
			if(node[request_id].request_method.match(/GET/i)) {
//				u.bug("GET request");

				// convert JSON params to regular params, JSON cannot be sent as GET
				var params = u.JSONtoParams(node[request_id].request_params);

				// add params to url
				node[request_id].request_url += params ? ((!node[request_id].request_url.match(/\?/g) ? "?" : "&") + params) : "";

				node[request_id].HTTPRequest.open(node[request_id].request_method, node[request_id].request_url, node[request_id].request_async);
				node[request_id].HTTPRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

				// rails form proofing
				var csfr_field = u.qs('meta[name="csrf-token"]');
				if(csfr_field && csfr_field.content) {
					node[request_id].HTTPRequest.setRequestHeader("X-CSRF-Token", csfr_field.content);
				}

				// add additional headers
				if(typeof(node[request_id].request_headers) == "object") {
					var header;
					for(header in node[request_id].request_headers) {
						node[request_id].HTTPRequest.setRequestHeader(header, node[request_id].request_headers[header]);
					}
				}

				// send info
				// some older browser (Firefox 3 in paticular) requires a parameter for send - an empty string is enough
				node[request_id].HTTPRequest.send("");

			}
			// perform POST, PUT or PATCH request
			else if(node[request_id].request_method.match(/POST|PUT|PATCH/i)) {
//				u.bug("POST|PUT|PATCH request");

				// stringify possible JSON object
				var params;
//				u.bug("params typeof:" + typeof(node.request_params) + ", " +node.request_params.constructor.toString().match(/FormData/i));
				if(typeof(node[request_id].request_params) == "object" && !node[request_id].request_params.constructor.toString().match(/FormData/i)) {
					params = JSON.stringify(node[request_id].request_params);
				}
				else {
					params = node[request_id].request_params;
				}

				node[request_id].HTTPRequest.open(node[request_id].request_method, node[request_id].request_url, node[request_id].request_async);
				node[request_id].HTTPRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

				// rails form proofing
				var csfr_field = u.qs('meta[name="csrf-token"]');
				if(csfr_field && csfr_field.content) {
					node[request_id].HTTPRequest.setRequestHeader("X-CSRF-Token", csfr_field.content);
				}

				// add additional headers
				if(typeof(node[request_id].request_headers) == "object") {
					var header;
					for(header in node[request_id].request_headers) {
						node[request_id].HTTPRequest.setRequestHeader(header, node[request_id].request_headers[header]);
					}
				}

				// send params
				node[request_id].HTTPRequest.send(params);

			}
		}
		// catch security exceptions and other exeptions
		catch(exception) {
//			u.bug("Request exc:" + exception)
			node[request_id].HTTPRequest.exception = exception;
			u.validateResponse(node[request_id].HTTPRequest);
			return;
		}

		// process synchronous response
		if(!node[request_id].request_async) {
			u.validateResponse(node[request_id].HTTPRequest);
		}
	}
	// request by script injection
	else if(node[request_id].request_method.match(/SCRIPT/i)) {

		// generate callback key
		var key = u.randomString();

		// create global reference
		document[key] = new Object();
		document[key].node = node;
		document[key].request_id = request_id;
		document[key].responder = function(response) {

			// make object to map node
			var response_object = new Object();
			response_object.node = this.node;
			response_object.request_id = this.request_id;
			response_object.responseText = response;
			u.validateResponse(response_object);
		}

		// convert JSON params to regular params, JSON cannot be sent as GET
		var params = u.JSONtoParams(node[request_id].request_params);

		// add params to url
		node[request_id].request_url += params ? ((!node[request_id].request_url.match(/\?/g) ? "?" : "&") + params) : "";
		// add callback to url
		node[request_id].request_url += (!node[request_id].request_url.match(/\?/g) ? "?" : "&") + "callback=document."+key+".responder";

		// add JSON Request to HTML head
		u.ae(u.qs("head"), "script", ({"type":"text/javascript", "src":node[request_id].request_url}));
	}

}

// convert simple (first level only) JSON to parameter string
Util.JSONtoParams = function(json) {
	if(typeof(json) == "object") {
		var params = "", param;
		for(param in json) {
			params += (params ? "&" : "") + param + "=" + json[param];
		}
		return params
	}

	var object = u.isStringJSON(json);
	if(object) {
		return u.JSONtoParams(object);
	}

	return json;
}

// is string valid JSON
Util.isStringJSON = function(string) {

	// JSON hints
	// ( | { - json
	if(string.trim().substr(0, 1).match(/[\{\[]/i) && string.trim().substr(-1, 1).match(/[\}\]]/i)) {
//		u.bug("guessing JSON:" + string, "green");

		try {
			// test for json object()
			var test = JSON.parse(string);
			if(typeof(test) == "object") {
				test.isJSON = true;
				return test;
			}
		}
		// ignore exception
		catch(exception) {}
	}

	// unknown response
	return false;
}

// is string valid HTML
Util.isStringHTML = function(string) {

	// HTML hints
	// < - HTML
	if(string.trim().substr(0, 1).match(/[\<]/i) && string.trim().substr(-1, 1).match(/[\>]/i)) {
//		u.bug("guessing HTML" + string, "green");

		// test for DOM
		try {
			var test = document.createElement("div");
			test.innerHTML = string;

			// seems to be a valid test for now
			if(test.childNodes.length) {

				// sometimes if a head/body tag is actually sent from the server, we may need some of its information
				// getting head/body info with regular expression on responseText
				var body_class = string.match(/<body class="([a-z0-9A-Z_: ]+)"/);
				test.body_class = body_class ? body_class[1] : "";
				var head_title = string.match(/<title>([^$]+)<\/title>/);
				test.head_title = head_title ? head_title[1] : "";

				test.isHTML = true;
				return test;
			}
		}
		// ignore exception
		catch(exception) {}
	}

	// unknown response
	return false;
}

// evaluate responseText string
// see what response contains
Util.evaluateResponseText = function(responseText) {

	var object;

	// already a JSON object (could be the response from a SCRIPT)
	if(typeof(responseText) == "object") {
		// u.bug("guessing object:" + responseText, "green");

		responseText.isJSON = true;
		return responseText;
	}
	else {

		var response_string;

		// quoted string (could be the response from SCRIPT, POST or GET)
		if(responseText.trim().substr(0, 1).match(/[\"\']/i) && responseText.trim().substr(-1, 1).match(/[\"\']/i)) {
			// u.bug("guessing quoted string:" + responseText, "red");

			// remove quotes before testing content
			response_string = responseText.trim().substr(1, responseText.trim().length-2);
		}
		else {
			response_string = responseText;
		}

		// check for JSON
		var json = u.isStringJSON(response_string);
		if(json) {
			return json;
		}

		// check for HTML
		var html = u.isStringHTML(response_string);
		if(html) {
			return html;
		}

		// neither JSON or HTML, return original responseText
		return responseText;

	}

}

// Simple validation of responseText
// Makes callback to appropriate notifier
Util.validateResponse = function(response){
//	u.bug("validateResponse:" + response);

	var object = false;

	if(response) {

//		u.bug("response:" + response + ":" + u.nodeId(response.node) + ":" + response.status)

		// u.bug("status:" + response.status + ":" + u.nodeId(response.node));
		// u.bug("responseText:" + response.responseText);

		try {
			// valid response status
			if(response.status && !response.status.toString().match(/403|404|500/)) {
				object = u.evaluateResponseText(response.responseText);
			}
			// SCRIPT has no response.status
			// is responseText available for evaluation
			else if(response.responseText) {
				object = u.evaluateResponseText(response.responseText);
			}
		}
		catch(exception) {
			response.exception = exception;
//			u.bug("HTTPRequest exection:" + exception);
		}
	}
//	u.bug("object:" + object);

	// did validation yield usable object
	if(object) {

		// callback to Response handler
//		u.bug("response:" + typeof(response.node[response.node.response_callback]))
		if(typeof(response.node[response.node[response.request_id].response_callback]) == "function") {
			response.node[response.node[response.request_id].response_callback](object);
		}

		// // callback to Response handler
		// if(typeof(response.node.Response) == "function") {
		// 	response.node.Response(object);
		// }
		// if(typeof(response.node.response) == "function") {
		// 	response.node.response(object);
		// }
	}
	else {

		// callback to ResponseError handler
		if(typeof(response.node.ResponseError) == "function") {
			response.node.ResponseError(response);
		}
		if(typeof(response.node.responseError) == "function") {
			response.node.responseError(response);
		}
	}

}

<commit_msg>returning request_id on request and on response<commit_after>

// Create xmlhttprequest object - separated to make it possible to implement fallback, primarily for IE
Util.createRequestObject = u.createRequestObject = function() {
	return new XMLHttpRequest();
}

// Request object
Util.request = u.request = function(node, url, settings) {
//	u.bug("request")

	var request_id = u.randomString(6);

	node[request_id] = {};
	node[request_id].request_url = url;

	// set default values
	node[request_id].request_method = "GET";
	node[request_id].request_async = true;
	node[request_id].request_params = "";
	node[request_id].request_headers = false;
	node[request_id].response_callback = "response";

	// additional info passed to function as JSON object
	if(typeof(settings) == "object") {
		var argument;
		for(argument in settings) {

			switch(argument) {
				case "method"		: node[request_id].request_method		= settings[argument]; break;
				case "params"		: node[request_id].request_params		= settings[argument]; break;
				case "async"		: node[request_id].request_async		= settings[argument]; break;
				case "headers"		: node[request_id].request_headers		= settings[argument]; break;
				case "callback"		: node[request_id].response_callback	= settings[argument]; break;
			}

		}
	}

//	u.bug("request:" + node.request_url + ", " + node.request_method + ", " + node.request_params + ", " + node.request_async + ", " + node.request_headers);

	// regular HTTP request
	if(node[request_id].request_method.match(/GET|POST|PUT|PATCH/i)) {

		node[request_id].HTTPRequest = this.createRequestObject();
		node[request_id].HTTPRequest.node = node;
		node[request_id].HTTPRequest.request_id = request_id;

		// listen for async request state change
		if(node[request_id].request_async) {
			node[request_id].HTTPRequest.onreadystatechange = function() {
				if(this.readyState == 4) {
					// process async response
					u.validateResponse(this);
				}
			}
		}

		// perform request
		try {
			// perform GET request
			if(node[request_id].request_method.match(/GET/i)) {
//				u.bug("GET request");

				// convert JSON params to regular params, JSON cannot be sent as GET
				var params = u.JSONtoParams(node[request_id].request_params);

				// add params to url
				node[request_id].request_url += params ? ((!node[request_id].request_url.match(/\?/g) ? "?" : "&") + params) : "";

				node[request_id].HTTPRequest.open(node[request_id].request_method, node[request_id].request_url, node[request_id].request_async);
				node[request_id].HTTPRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

				// rails form proofing
				var csfr_field = u.qs('meta[name="csrf-token"]');
				if(csfr_field && csfr_field.content) {
					node[request_id].HTTPRequest.setRequestHeader("X-CSRF-Token", csfr_field.content);
				}

				// add additional headers
				if(typeof(node[request_id].request_headers) == "object") {
					var header;
					for(header in node[request_id].request_headers) {
						node[request_id].HTTPRequest.setRequestHeader(header, node[request_id].request_headers[header]);
					}
				}

				// send info
				// some older browser (Firefox 3 in paticular) requires a parameter for send - an empty string is enough
				node[request_id].HTTPRequest.send("");

			}
			// perform POST, PUT or PATCH request
			else if(node[request_id].request_method.match(/POST|PUT|PATCH/i)) {
//				u.bug("POST|PUT|PATCH request");

				// stringify possible JSON object
				var params;
//				u.bug("params typeof:" + typeof(node.request_params) + ", " +node.request_params.constructor.toString().match(/FormData/i));
				if(typeof(node[request_id].request_params) == "object" && !node[request_id].request_params.constructor.toString().match(/FormData/i)) {
					params = JSON.stringify(node[request_id].request_params);
				}
				else {
					params = node[request_id].request_params;
				}

				node[request_id].HTTPRequest.open(node[request_id].request_method, node[request_id].request_url, node[request_id].request_async);
				node[request_id].HTTPRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

				// rails form proofing
				var csfr_field = u.qs('meta[name="csrf-token"]');
				if(csfr_field && csfr_field.content) {
					node[request_id].HTTPRequest.setRequestHeader("X-CSRF-Token", csfr_field.content);
				}

				// add additional headers
				if(typeof(node[request_id].request_headers) == "object") {
					var header;
					for(header in node[request_id].request_headers) {
						node[request_id].HTTPRequest.setRequestHeader(header, node[request_id].request_headers[header]);
					}
				}

				// send params
				node[request_id].HTTPRequest.send(params);

			}
		}
		// catch security exceptions and other exeptions
		catch(exception) {
//			u.bug("Request exc:" + exception)
			node[request_id].HTTPRequest.exception = exception;
			u.validateResponse(node[request_id].HTTPRequest);
			return;
		}

		// process synchronous response
		if(!node[request_id].request_async) {
			u.validateResponse(node[request_id].HTTPRequest);
		}
	}
	// request by script injection
	else if(node[request_id].request_method.match(/SCRIPT/i)) {

		// generate callback key
		var key = u.randomString();

		// create global reference
		document[key] = new Object();
		document[key].node = node;
		document[key].request_id = request_id;
		document[key].responder = function(response) {

			// make object to map node
			var response_object = new Object();
			response_object.node = this.node;
			response_object.request_id = this.request_id;
			response_object.responseText = response;
			u.validateResponse(response_object);
		}

		// convert JSON params to regular params, JSON cannot be sent as GET
		var params = u.JSONtoParams(node[request_id].request_params);

		// add params to url
		node[request_id].request_url += params ? ((!node[request_id].request_url.match(/\?/g) ? "?" : "&") + params) : "";
		// add callback to url
		node[request_id].request_url += (!node[request_id].request_url.match(/\?/g) ? "?" : "&") + "callback=document."+key+".responder";

		// add JSON Request to HTML head
		u.ae(u.qs("head"), "script", ({"type":"text/javascript", "src":node[request_id].request_url}));
	}

	return request_id;
}

// convert simple (first level only) JSON to parameter string
Util.JSONtoParams = function(json) {
	if(typeof(json) == "object") {
		var params = "", param;
		for(param in json) {
			params += (params ? "&" : "") + param + "=" + json[param];
		}
		return params
	}

	var object = u.isStringJSON(json);
	if(object) {
		return u.JSONtoParams(object);
	}

	return json;
}

// is string valid JSON
Util.isStringJSON = function(string) {

	// JSON hints
	// ( | { - json
	if(string.trim().substr(0, 1).match(/[\{\[]/i) && string.trim().substr(-1, 1).match(/[\}\]]/i)) {
//		u.bug("guessing JSON:" + string, "green");

		try {
			// test for json object()
			var test = JSON.parse(string);
			if(typeof(test) == "object") {
				test.isJSON = true;
				return test;
			}
		}
		// ignore exception
		catch(exception) {}
	}

	// unknown response
	return false;
}

// is string valid HTML
Util.isStringHTML = function(string) {

	// HTML hints
	// < - HTML
	if(string.trim().substr(0, 1).match(/[\<]/i) && string.trim().substr(-1, 1).match(/[\>]/i)) {
//		u.bug("guessing HTML" + string, "green");

		// test for DOM
		try {
			var test = document.createElement("div");
			test.innerHTML = string;

			// seems to be a valid test for now
			if(test.childNodes.length) {

				// sometimes if a head/body tag is actually sent from the server, we may need some of its information
				// getting head/body info with regular expression on responseText
				var body_class = string.match(/<body class="([a-z0-9A-Z_: ]+)"/);
				test.body_class = body_class ? body_class[1] : "";
				var head_title = string.match(/<title>([^$]+)<\/title>/);
				test.head_title = head_title ? head_title[1] : "";

				test.isHTML = true;
				return test;
			}
		}
		// ignore exception
		catch(exception) {}
	}

	// unknown response
	return false;
}

// evaluate responseText string
// see what response contains
Util.evaluateResponseText = function(responseText) {

	var object;

	// already a JSON object (could be the response from a SCRIPT)
	if(typeof(responseText) == "object") {
		// u.bug("guessing object:" + responseText, "green");

		responseText.isJSON = true;
		return responseText;
	}
	else {

		var response_string;

		// quoted string (could be the response from SCRIPT, POST or GET)
		if(responseText.trim().substr(0, 1).match(/[\"\']/i) && responseText.trim().substr(-1, 1).match(/[\"\']/i)) {
			// u.bug("guessing quoted string:" + responseText, "red");

			// remove quotes before testing content
			response_string = responseText.trim().substr(1, responseText.trim().length-2);
		}
		else {
			response_string = responseText;
		}

		// check for JSON
		var json = u.isStringJSON(response_string);
		if(json) {
			return json;
		}

		// check for HTML
		var html = u.isStringHTML(response_string);
		if(html) {
			return html;
		}

		// neither JSON or HTML, return original responseText
		return responseText;

	}

}

// Simple validation of responseText
// Makes callback to appropriate notifier
Util.validateResponse = function(response){
//	u.bug("validateResponse:" + response);

	var object = false;

	if(response) {

//		u.bug("response:" + response + ":" + u.nodeId(response.node) + ":" + response.status)

		// u.bug("status:" + response.status + ":" + u.nodeId(response.node));
		// u.bug("responseText:" + response.responseText);

		try {
			// valid response status
			if(response.status && !response.status.toString().match(/403|404|500/)) {
				object = u.evaluateResponseText(response.responseText);
			}
			// SCRIPT has no response.status
			// is responseText available for evaluation
			else if(response.responseText) {
				object = u.evaluateResponseText(response.responseText);
			}
		}
		catch(exception) {
			response.exception = exception;
//			u.bug("HTTPRequest exection:" + exception);
		}
	}
//	u.bug("object:" + object);

	// did validation yield usable object
	if(object) {

		// callback to Response handler
//		u.bug("response:" + typeof(response.node[response.node.response_callback]))
		if(typeof(response.node[response.node[response.request_id].response_callback]) == "function") {
			response.node[response.node[response.request_id].response_callback](object, response.request_id);
		}

		// // callback to Response handler
		// if(typeof(response.node.Response) == "function") {
		// 	response.node.Response(object);
		// }
		// if(typeof(response.node.response) == "function") {
		// 	response.node.response(object);
		// }
	}
	else {

		// callback to ResponseError handler
		if(typeof(response.node.ResponseError) == "function") {
			response.node.ResponseError(response);
		}
		if(typeof(response.node.responseError) == "function") {
			response.node.responseError(response);
		}
	}

}

<|endoftext|>


<commit_before>
RegExp.escape = function(text) {
  if (!arguments.callee.sRE) {
    var specials = [
      '/', '.', '*', '+', '?', '|',
      '(', ')', '[', ']', '{', '}', '\\'
    ];
    arguments.callee.sRE = new RegExp(
      '(\\' + specials.join('|\\') + ')', 'g'
    );
  }
  return text.replace(arguments.callee.sRE, '\\$1');
}

function isUndefined(x) {
    return x == null && x !== null;
}

OCRJS = OCRJS || {};
OCRJS.countProperties = function(obj) {
    var count = 0;
    for (var k in obj) {
        if (obj.hasOwnProperty(k)) {
            ++count;
        }
    }
    return count;
};

OCRJS.ParameterBuilder = OCRJS.OcrBase.extend({
    constructor: function(parent, valuedata) {
        this.base(parent);
        this.parent = parent;

        this._valuedata = valuedata || null;

        this._cache = null;
        this._waiting = {};
        console.log("Called constructor");
    },

    init: function() {
        var self = this;
        self.setupEvents();
        self.queryOptions(null, null);
    },

    isReady: function() {
        return OCRJS.countProperties(this._waiting) > 0 ? false : true;
    },

    setupEvents: function() {
        var self = this;
        $(".multiple").live("mouseenter", function(event) {
            var select = $(this).children("select").first();
            var ctrl = $("

")
                .addClass("control_manip");
            var plus = $("

")
                .addClass("ui-icon ui-icon-plus")
                .addClass("control_manip_add")
                .data("ctrl", this)
                .appendTo(ctrl);
            var minus = $("

")
                .addClass("ui-icon ui-icon-minus")
                .addClass("control_manip_remove")
                .appendTo(ctrl)
                .data("ctrl", this)
                .toggle($(this).siblings().length > 0);
            ctrl.css({
                top: select.position().top,
                left: select.position().left + select.width(),
            }).appendTo(this);
        }).live("mouseleave", function(event) {
            $(".control_manip", this).remove();
        });

        $(".control_manip_remove").live("click", function(event) {
            var elem = $(this).data("ctrl");
            if ($(elem).siblings().length > 0) {
                var id = $(elem).find("select").first().attr("id");
                $(elem).remove();
                self.renumberMultiples(id);
            }
        });

        $(".control_manip_add").live("click", function(event) {
            var elem = $(this).data("ctrl");
            // clone the element with both data and events,
            // then remove any existing hover controls and
            // trigger a rebuild of the section
            var clone = $(elem).clone(true)
                .find(".control_manip")
                .remove()
                .end()
                .insertAfter(elem)
                .find("select")
                .change();
        });
    },

    renumberMultiples: function(baseid) {
        var base = baseid.replace(/\[\d+\]_ctrl$/, "");
        var re = new RegExp(RegExp.escape(base) + "\\[\\d+\\]", "g");
        $(".multiple[id^='" + base + "']").each(function(index, elem) {
            var newid = base + "[" + index + "]";
            $(elem).attr("id", $(elem).attr("id").replace(re, newid))
                .find("*").each(function(i, e) {
                $.each(e.attributes, function(j, attrib) {
                    if (attrib.value.match(re)) {
                        attrib.value = attrib.value.replace(re, newid);
                    }
                });
            });
        });
    },

    queryOptions: function(parent) {
        var self = this;
        var parent = parent || self.parent;
        var url = "/ocrplugins/query/";
        self._waiting[parent.id] = true;
        $.ajax({
            url: url,
            type: "GET",
            error: OCRJS.ajaxErrorHandler,
            success: function(data) {
                self._cache = data;
                $(parent).append(
                    self.buildSection(parent, data)
                );
                self._queryDone(parent.id);
            },
        });
    },

    getIdent: function(parent, data, index, multiindex) {
        var ident;
        if (index !== undefined)
            ident = parent.id + "[" + index + "]" + "." + data.name;
        else
            ident = parent.id + "." + data.name;
        if (multiindex !== undefined)
            ident = ident + "[" + multiindex + "]";
        return ident;
    },

    getKeyName: function(ident, data, index) {
        var iname = ident.replace(/\[(\d+)\]$/, "[" + index + "]", ident);
        return (data.type == "list"
                ? "@"
                : (data.type == "object" ? "%" : "$")) + iname;
    },

    buildSection: function(parent, data, index) {
        // build a section for one parameter and its
        // children
        if (!data)
            return;
        var self = this;
        var ident = self.getIdent(parent, data, index);
        var container = $("

")
            .attr("id", ident)
            .addClass("param_section");
        if (data.choices) {
            if (data.multiple) {
                var mtemp = container.clone();
                mcount = 0;
                while (true) {
                    var mident = self.getIdent(parent, data, index, mcount);
                    var stored = self._storedValue(mident);
                    if ((stored === null || stored === undefined) && mcount > 0)
                        break;
                    var mcontainer = mtemp.clone()
                        .attr("id", mident);
                    container.append(mcontainer);
                    self.buildChoicesSection(mcontainer, mident, data, mcount);
                    mcount++;
                }
            } else
                self.buildChoicesSection(container, ident, data);
        } else if (data.parameters) {
            self.buildParameterList(parent, container, ident, data);
        } else {
            self.buildParameterSection(container, ident, data);
        }
        return container;
    },

    buildChoicesSection: function(container, ident, data, multiindex) {
        var self = this;
        // fetch more data if we need to...
        if (data.choices === true) {
            var parts = ident.replace(/\[\d+\]/g, "").split(".").splice(2);
            //parts.push(data.name);
            self._waiting[ident] = true;
            $.getJSON("/ocrplugins/query/" + parts.join("/") + "/", function(data) {
                self.buildChoicesSection(container, ident, data);
                self._queryDone(ident);
            });
            return;
        }

        var ctrlname = self.getKeyName(ident, data, multiindex);
        var label = $("")
            .attr("for", ident + "_ctrl")
            .attr("title", data.description)
            .text(data.name)
            .appendTo(container);
        var control = $("<select></select>")
            .attr("id", ident + "_ctrl")
            .addClass("option_select")
            .attr("name", ctrlname)
            .appendTo(container);

        if (data.multiple) {
            container.addClass("multiple");
        }
        var option = $("<option></option>");
        $.each(data.choices, function(i, choice) {
            control.data(choice.name, choice);
            option.clone()
                .attr("value", choice.value == null ? choice.name : choice.value)
                .text(choice.name)
                .appendTo(control);
        });
        control.change(function(event) {
            var newdata = $(this).data($(this).val());
            var section = self.buildSection(container.get(0), newdata);
            if ($(this).next("div").length)
                $(this).next("div").replaceWith(section);
            else
                container.append(section);
            self.renumberMultiples(this.id);
        });
        if (data.choices.length) {
            self.loadOptionState(control, data.choices[0].name);
            control.change();
        }
    },

    buildParameterList: function(parent, container, ident, data) {
        var self = this;

        // fetch more data if we need to...
        if (data.parameters === true) {
            var parts = ident.replace(/\[\d+\]/g, "").split(".").splice(2);
            self._waiting[ident] = true;
            $.getJSON("/ocrplugins/query/" + parts.join("/") + "/", function(data) {
                var section = self.buildSection(parent, data);
                if ($(parent).children("div").length)
                    $(parent).children("div").replaceWith(section);
                else
                    $(parent).append(section);
                self._queryDone(ident);
            });
            return;
        }

        $.each(data.parameters, function(i, param) {
            //console.log("Building param for: " + i + " " + param.name + " ", param);
            container.append(
                self.buildSection(container.get(0), param, i)
            );
        });
    },

    buildParameterSection: function(container, ident, data) {
        var self = this;
        if (!(data.value || data.parameters || data.choices))
            return;
        var label = $("")
            .attr("for", ident + "_ctrl")
            .text(data.name).attr("title", data.description);
        var control = $("<input></input>")
            .attr("id", ident + "_ctrl")
            .attr("name", self.getKeyName(ident, data));
        if (data.type == "bool")
            control.attr("type", "checkbox");
        self.loadOptionState(control, data.value);
        container.append(label).append(control);
    },

    saveState: function() {
        if (this._valuedata !== null)
            return;
        // Delete all existing state cookies
        var val;
        var cookies = document.cookie.split(";");
        for (var i in cookies) {
            val = cookies[i].split("=")[0];
            if ($.trim(val).match("^" + this.parent.id)) {
                $.cookie(val, null);
            }
        }
        $("select, input", this.parent).each(function(index, item) {
            var key = $(item).attr("name").substr(1);
            if ($(item).attr("type") == "checkbox")
                $.cookie(key, $(item).attr("checked"));
            else
                $.cookie(key, $(item).val());
        });
    },

    loadOptionState: function(item, defaultoption) {
        // check for a cookie with the given 'name'
        // otherwise, select the first available option
        var key = item.attr("name").substr(1);
        var val = this._storedValue(key);
        var setval = val ? val : defaultoption;
        item.val(setval);
        if (item.attr("type") == "checkbox") {
            item.attr("checked", !(parseInt(setval) == 0 || setval == "false"
                        || setval == "off"));
        }
        return val !== null ? true : false;
    },

    serializedData: function() {
        return $("input, select", this.parent).serialize();
    },

    setFromData: function(data) {
        $("input, select", this.parent).each(function(index, elem) {
            // get the element name and cut off the object type
            // prefix
            var key = $(elem).attr("name").substr(1);
            if (data[key]) {
                console.log("Restoring parameter: ", key);
                $(elem).val(data[key]);
            }
        });
    },

    _queryDone: function(key) {
        delete this._waiting[key];
        if (this.isReady()) {
            this.onReadyState();
        }
    },

    _storedValue: function(key) {
        if (this._valuedata !== null) {
            return this._valuedata[key];
        } else {
            return $.cookie(key);
        }
    },

    _deleteStoredValue: function(key) {
        if (this._valuedata !== null) {
            delete this._valuedata[key];
        } else {
            $.cookie(key, null);
        }
    },

    // overrideable functions
    onReadyState: function() {
        console.log("READY");
    },
});

<commit_msg>Inadequately-extensive cleanup<commit_after>
RegExp.escape = function(text) {
  if (!arguments.callee.sRE) {
    var specials = [
      '/', '.', '*', '+', '?', '|',
      '(', ')', '[', ']', '{', '}', '\\'
    ];
    arguments.callee.sRE = new RegExp(
      '(\\' + specials.join('|\\') + ')', 'g'
    );
  }
  return text.replace(arguments.callee.sRE, '\\$1');
}

function isUndefined(x) {
    return x == null && x !== null;
}

OCRJS = OCRJS || {};
OCRJS.countProperties = function(obj) {
    var count = 0;
    for (var k in obj) {
        if (obj.hasOwnProperty(k)) {
            ++count;
        }
    }
    return count;
};

OCRJS.ParameterBuilder = OCRJS.OcrBase.extend({
    constructor: function(parent, valuedata) {
        this.base(parent);
        this.parent = parent;

        this._valuedata = valuedata || null;
        this._temp = null;
        this._waiting = {};
    },

    init: function() {
        var self = this;
        self.setupEvents();
        self.queryOptions(null, null);
    },

    isReady: function() {
        return OCRJS.countProperties(this._waiting) > 0 ? false : true;
    },

    setupEvents: function() {
        var self = this;
        $(".multiple").live("mouseenter", function(event) {
            var select = $(this).children("select").first();
            var ctrl = $("

")
                .addClass("control_manip");
            var plus = $("

")
                .addClass("ui-icon ui-icon-plus")
                .addClass("control_manip_add")
                .data("ctrl", this)
                .appendTo(ctrl);
            var minus = $("

")
                .addClass("ui-icon ui-icon-minus")
                .addClass("control_manip_remove")
                .appendTo(ctrl)
                .data("ctrl", this)
                .toggle($(this).siblings().length > 0);
            ctrl.css({
                top: select.position().top,
                left: select.position().left + select.width(),
            }).appendTo(this);
        }).live("mouseleave", function(event) {
            $(".control_manip", this).remove();
        });

        $(".control_manip_remove").live("click", function(event) {
            var elem = $(this).data("ctrl");
            if ($(elem).siblings().length > 0) {
                var id = $(elem).find("select").first().attr("id");
                $(elem).remove();
                self.renumberMultiples(id);
            }
        });

        $(".control_manip_add").live("click", function(event) {
            var elem = $(this).data("ctrl");
            // clone the element with both data and events,
            // then remove any existing hover controls and
            // trigger a rebuild of the section
            var clone = $(elem).clone(true)
                .find(".control_manip")
                .remove()
                .end()
                .insertAfter(elem)
                .find("select")
                .change();
        });
    },

    renumberMultiples: function(baseid) {
        var base = baseid.replace(/\[\d+\]_ctrl$/, "");
        var re = new RegExp(RegExp.escape(base) + "\\[\\d+\\]", "g");
        $(".multiple[id^='" + base + "']").each(function(index, elem) {
            var newid = base + "[" + index + "]";
            $(elem).attr("id", $(elem).attr("id").replace(re, newid))
                .find("*").each(function(i, e) {
                $.each(e.attributes, function(j, attrib) {
                    if (attrib.value.match(re)) {
                        attrib.value = attrib.value.replace(re, newid);
                    }
                });
            });
        });
    },

    queryOptions: function(parent) {
        var self = this;
        var parent = parent || self.parent;
        var url = "/ocrplugins/query/";
        self._waiting[parent.id] = true;
        $.ajax({
            url: url,
            type: "GET",
            error: OCRJS.ajaxErrorHandler,
            success: function(data) {
                self._temp = self.buildSection(parent, data)
                self._queryDone(parent.id);
            },
        });
    },

    getIdent: function(parent, data, index, multiindex) {
        var ident;
        if (index !== undefined)
            ident = parent.id + "[" + index + "]" + "." + data.name;
        else
            ident = parent.id + "." + data.name;
        if (multiindex !== undefined)
            ident = ident + "[" + multiindex + "]";
        return ident;
    },

    getKeyName: function(ident, data, index) {
        var iname = ident.replace(/\[(\d+)\]$/, "[" + index + "]", ident);
        return (data.type == "list"
                ? "@"
                : (data.type == "object" ? "%" : "$")) + iname;
    },

    buildSection: function(parent, data, index) {
        // build a section for one parameter and its
        // children
        if (!data)
            return;
        var self = this;
        var ident = self.getIdent(parent, data, index);
        var container = $("

")
            .attr("id", ident)
            .addClass("param_section");
        if (data.choices) {
            if (data.multiple) {
                var mtemp = container.clone();
                mcount = 0;
                while (true) {
                    var mident = self.getIdent(parent, data, index, mcount);
                    var stored = self._storedValue(mident);
                    if ((stored === null || stored === undefined) && mcount > 0)
                        break;
                    var mcontainer = mtemp.clone()
                        .attr("id", mident);
                    container.append(mcontainer);
                    self.buildChoicesSection(mcontainer, mident, data, mcount);
                    mcount++;
                }
            } else
                self.buildChoicesSection(container, ident, data);
        } else if (data.parameters) {
            self.buildParameterList(parent, container, ident, data);
        } else {
            self.buildParameterSection(container, ident, data);
        }
        return container;
    },

    fetchData: function(ident, callback) {
        var self = this;
        var parts = ident.replace(/\[\d+\]/g, "").split(".").splice(2);
        self._waiting[ident] = true;
        $.getJSON("/ocrplugins/query/" + parts.join("/") + "/", function(data) {
            callback.call(self, data);
            self._queryDone(ident);
        });
    },

    buildChoicesSection: function(container, ident, data, multiindex) {
        var self = this;
        // fetch more data if we need to...
        if (data.choices === true) {
            self.fetchData(ident, function(data) {
                self.buildChoicesSection(container, ident, data, multiindex);
            });
            return;
        }

        var ctrlname = self.getKeyName(ident, data, multiindex);
        var label = $("")
            .attr("for", ident + "_ctrl")
            .attr("title", data.description)
            .text(data.name)
            .appendTo(container);
        var control = $("<select></select>")
            .attr("id", ident + "_ctrl")
            .addClass("option_select")
            .attr("name", ctrlname)
            .appendTo(container);

        if (data.multiple)
            container.addClass("multiple");

        var option = $("<option></option>");
        $.each(data.choices, function(i, choice) {
            control.data(choice.name, choice);
            option.clone()
                .attr("value", choice.value == null ? choice.name : choice.value)
                .text(choice.name)
                .appendTo(control);
        });
        control.change(function(event) {
            var newdata = $(this).data($(this).val());
            var section = self.buildSection(container.get(0), newdata);
            if ($(this).next("div").length)
                $(this).next("div").replaceWith(section);
            else
                container.append(section);
            self.renumberMultiples(this.id);
        });

        // if we have some choices, select the last cached
        // and then trigger a change event to build any children
        if (data.choices.length) {
            self.loadOptionState(control, data.choices[0].name);
            control.change();
        }
    },

    buildParameterList: function(parent, container, ident, data) {
        var self = this;

        // fetch more data if we need to...
        if (data.parameters === true) {
            self.fetchData(ident, function(data) {
                var section = self.buildSection(parent, data);
                if ($(parent).children("div").length)
                    $(parent).children("div").replaceWith(section);
                else
                    $(parent).append(section);
            });
            return;
        }

        $.each(data.parameters, function(i, param) {
            container.append(
                self.buildSection(container.get(0), param, i)
            );
        });
    },

    buildParameterSection: function(container, ident, data) {
        var self = this;
        if (!(data.value || data.parameters || data.choices))
            return;
        var label = $("")
            .attr("for", ident + "_ctrl")
            .text(data.name).attr("title", data.description);
        var control = $("<input></input>")
            .attr("id", ident + "_ctrl")
            .attr("name", self.getKeyName(ident, data));
        if (data.type == "bool")
            control.attr("type", "checkbox");
        self.loadOptionState(control, data.value);
        container.append(label).append(control);
    },

    saveState: function() {
        if (this._valuedata !== null)
            return;
        // Delete all existing state cookies
        var val;
        var cookies = document.cookie.split(";");
        for (var i in cookies) {
            val = cookies[i].split("=")[0];
            if ($.trim(val).match("^" + this.parent.id)) {
                $.cookie(val, null);
            }
        }
        $("select, input", this.parent).each(function(index, item) {
            var key = $(item).attr("name").substr(1);
            if ($(item).attr("type") == "checkbox")
                $.cookie(key, $(item).attr("checked"));
            else
                $.cookie(key, $(item).val());
        });
    },

    loadOptionState: function(item, defaultoption) {
        // check for a cookie with the given 'name'
        // otherwise, select the first available option
        var key = item.attr("name").substr(1);
        var val = this._storedValue(key);
        var setval = val ? val : defaultoption;
        item.val(setval);
        if (item.attr("type") == "checkbox") {
            item.attr("checked", !(parseInt(setval) == 0 || setval == "false"
                        || setval == "off"));
        }
        return val !== null ? true : false;
    },

    serializedData: function() {
        return $("input, select", this.parent).serialize();
    },

    setFromData: function(data) {
        $("input, select", this.parent).each(function(index, elem) {
            // get the element name and cut off the object type
            // prefix
            var key = $(elem).attr("name").substr(1);
            if (data[key]) {
                console.log("Restoring parameter: ", key);
                $(elem).val(data[key]);
            }
        });
    },

    _queryDone: function(key) {
        delete this._waiting[key];
        if (this.isReady()) {
            $(this.parent).append(this._temp);
            this.onReadyState();
        }
    },

    _storedValue: function(key) {
        if (this._valuedata !== null) {
            return this._valuedata[key];
        } else {
            return $.cookie(key);
        }
    },

    _deleteStoredValue: function(key) {
        if (this._valuedata !== null) {
            delete this._valuedata[key];
        } else {
            $.cookie(key, null);
        }
    },

    // overrideable functions
    onReadyState: function() {
        console.log("READY");
    },
});

<|endoftext|>