<commit_before>// XSS Commit Checker
// If the file type if ejs and has <%- in it,
// We assume that it needs to be taken care for manual analysis
// Just report it using the event.

var events = require('events');

// report as many times as you want
exports.scan = function(diff, report){

  diff.forEach(function(file){
    // Only interested in files with .ejs as extension
    if(file.fileExtension !== 'ejs')
      return;
    file.additions.forEach(function(addition){
      if(addition.match(/<%-/gi) !== null || addition.match(/{{{/gi) !=== null){
        report( 'XSS in ' + addition);
      }
    });
  });

return;
}
<commit_msg>bug fixo<commit_after>// XSS Commit Checker
// If the file type if ejs and has <%- in it,
// We assume that it needs to be taken care for manual analysis
// Just report it using the event.

var events = require('events');

// report as many times as you want
exports.scan = function(diff, report){

  diff.forEach(function(file){
    // Only interested in files with .ejs as extension
    if(file.fileExtension !== 'ejs')
      return;
    file.additions.forEach(function(addition){
      if(addition.match(/<%-/gi) !== null || addition.match(/{{{/gi) !== null){
        report( 'XSS in ' + addition);
      }
    });
  });

return;
}
<|endoftext|>


<commit_before>
function CrocImageSimple(root, imgSrc) {

	var currentCrocImageSimple = this;

	CrocBase.call(this, root);

	this.imgSrc = imgSrc;
	this.currentImage = null;

	this.hitTestType = 'bounding';
	this.scaling = 'none';

	this.setImage(imgSrc);
};

CrocImageSimple.prototype = Object.create(CrocBase.prototype);
CrocImageSimple.prototype.constructor = CrocImageSimple;

CrocImageSimple.prototype.setImage = function(imgSrc) {
	var currentCrocImageSimple = this;

	this.getRoot().loadImage(imgSrc, function() {
		currentCrocImageSimple.currentImage = currentCrocImageSimple.getRoot().getImage(currentCrocImageSimple.imgSrc);
		currentCrocImageSimple.getRoot().repaint();
	});
};

CrocImageSimple.prototype.setScaling = function(scaling) {

	this.scaling = scaling || 'none';

	return;
};

CrocImageSimple.prototype.getWidth = function () {

	if(this.currentImage === null) {
		return 0;
	}

	if(this.scaling !== 'none') {
		return this.currentWidth;
	}

	return this.currentImage.width;
};

CrocImageSimple.prototype.getHeight = function() {

	if(this.currentImage === null) {
		return 0;
	}

	if(this.scaling !== 'none') {
		return this.currentHeight;
	}

	return this.currentImage.height;
};

CrocImageSimple.prototype.getImageWidth = function() {
	if(this.currentImage === null) {
		return 0;
	}

	return this.currentImage.width;
};

CrocImageSimple.prototype.getImageHeight = function() {
	if(this.currentImage === null) {
		return 0;
	}

	return this.currentImage.height;
};

CrocImageSimple.prototype.paint = function(context, width, height) {
	CrocBase.prototype.paint.call(this, context, width, height);

	if(!this.visible) {
		return;
	}

	if(this.currentImage === null) {
		this.currentImage = this.getRoot().getImage(this.imgSrc);

		if(this.currentImage === null) {
			return;
		}
	}

	context.save();

	if(this.scaling === 'stretch') {
		var w = width/this.currentImage.width;
		var h = height/this.currentImage.height;

		this.currentWidth = width;
		this.currentHeight = height;

		context.scale(w, h);
	}

	else if(this.scaling === 'target') {
		this.currentWidth = this.convertToPixels(this.targetWidth, width);
		this.currentHeight = this.convertToPixels(this.targetHeight, height);

		var w = 0;
		var h = 0;

		if(this.currentWidth !== 0) {
			w = this.currentWidth/this.currentImage.width;
		}

		if(this.currentHeight !== 0) {
			h = this.currentHeight/this.currentImage.height;
		}

		if(w >= 0 && h >= 0) {
			context.scale(w, h);
		}
	}

	else {
		this.currentWidth = this.currentImage.width;
		this.currentHeight = this.currentImage.height;
	}

	context.drawImage(this.currentImage, 0, 0);

	context.restore();
	return;

};
<commit_msg>When we set the image we have to change the imgSrc so the object knows which image data to grab from root.<commit_after>
function CrocImageSimple(root, imgSrc) {

	var currentCrocImageSimple = this;

	CrocBase.call(this, root);

	this.currentImage = null;

	this.hitTestType = 'bounding';
	this.scaling = 'none';

	this.setImage(imgSrc);
};

CrocImageSimple.prototype = Object.create(CrocBase.prototype);
CrocImageSimple.prototype.constructor = CrocImageSimple;

CrocImageSimple.prototype.setImage = function(imgSrc) {
	var currentCrocImageSimple = this;

	this.imgSrc = imgSrc;

	this.getRoot().loadImage(imgSrc, function() {
		currentCrocImageSimple.currentImage = currentCrocImageSimple.getRoot().getImage(currentCrocImageSimple.imgSrc);
		currentCrocImageSimple.getRoot().repaint();
	});
};

CrocImageSimple.prototype.setScaling = function(scaling) {

	this.scaling = scaling || 'none';

	return;
};

CrocImageSimple.prototype.getWidth = function () {

	if(this.currentImage === null) {
		return 0;
	}

	if(this.scaling !== 'none') {
		return this.currentWidth;
	}

	return this.currentImage.width;
};

CrocImageSimple.prototype.getHeight = function() {

	if(this.currentImage === null) {
		return 0;
	}

	if(this.scaling !== 'none') {
		return this.currentHeight;
	}

	return this.currentImage.height;
};

CrocImageSimple.prototype.getImageWidth = function() {
	if(this.currentImage === null) {
		return 0;
	}

	return this.currentImage.width;
};

CrocImageSimple.prototype.getImageHeight = function() {
	if(this.currentImage === null) {
		return 0;
	}

	return this.currentImage.height;
};

CrocImageSimple.prototype.paint = function(context, width, height) {
	CrocBase.prototype.paint.call(this, context, width, height);

	if(!this.visible) {
		return;
	}

	if(this.currentImage === null) {
		this.currentImage = this.getRoot().getImage(this.imgSrc);

		if(this.currentImage === null) {
			return;
		}
	}

	context.save();

	if(this.scaling === 'stretch') {
		var w = width/this.currentImage.width;
		var h = height/this.currentImage.height;

		this.currentWidth = width;
		this.currentHeight = height;

		context.scale(w, h);
	}

	else if(this.scaling === 'target') {
		this.currentWidth = this.convertToPixels(this.targetWidth, width);
		this.currentHeight = this.convertToPixels(this.targetHeight, height);

		var w = 0;
		var h = 0;

		if(this.currentWidth !== 0) {
			w = this.currentWidth/this.currentImage.width;
		}

		if(this.currentHeight !== 0) {
			h = this.currentHeight/this.currentImage.height;
		}

		if(w >= 0 && h >= 0) {
			context.scale(w, h);
		}
	}

	else {
		this.currentWidth = this.currentImage.width;
		this.currentHeight = this.currentImage.height;
	}

	context.drawImage(this.currentImage, 0, 0);

	context.restore();
	return;

};
<|endoftext|>


<commit_before>
/*
 * browser.js: Browser specific functionality for director.
 *
 * (C) 2011, Nodejitsu Inc.
 * MIT LICENSE
 *
 */

if (!Array.prototype.filter) {
  Array.prototype.filter = function(filter, that) {
    var other = [], v;
    for (var i = 0, n = this.length; i < n; i++) {
      if (i in this && filter.call(that, v = this[i], i, this)) {
        other.push(v);
      }
    }
    return other;
  };
}

if (!Array.isArray){
  Array.isArray = function(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
  };
}

var dloc = document.location;

var listener = {
  mode: 'modern',
  hash: dloc.hash,
  history: false,

  check: function () {
    var h = dloc.hash;
    if (h != this.hash) {
      this.hash = h;
      this.onHashChanged();
    }
  },

  fire: function () {
    if (this.mode === 'modern') {
      this.history === true ? window.onpopstate() : window.onhashchange();
    }
    else {
      this.onHashChanged();
    }
  },

  init: function (fn, history) {
    var self = this;
    this.history = history;

    if (!window.Router.listeners) {
      window.Router.listeners = [];
    }

    function onchange() {
      for (var i = 0, l = window.Router.listeners.length; i < l; i++) {
        window.Router.listeners[i]();
      }
    }

    //note IE8 is being counted as 'modern' because it has the hashchange event
    if ('onhashchange' in window && (document.documentMode === undefined
      || document.documentMode > 7)) {
      // At least for now HTML5 history is available for 'modern' browsers only
      if (this.history === true) {
        // There is an old bug in Chrome that causes onpopstate to fire even
        // upon initial page load. Since the handler is run manually in init(),
        // this would cause Chrome to run it twise. Currently the only
        // workaround seems to be to set the handler after the initial page load
        // http://code.google.com/p/chromium/issues/detail?id=63040
        setTimeout(function() {
          window.onpopstate = onchange;
        }, 500);
      }
      else {
        window.onhashchange = onchange;
      }
      this.mode = 'modern';
    }
    else {
      //
      // IE support, based on a concept by Erik Arvidson ...
      //
      var frame = document.createElement('iframe');
      frame.id = 'state-frame';
      frame.style.display = 'none';
      document.body.appendChild(frame);
      this.writeFrame('');

      if ('onpropertychange' in document && 'attachEvent' in document) {
        document.attachEvent('onpropertychange', function () {
          if (event.propertyName === 'location') {
            self.check();
          }
        });
      }

      window.setInterval(function () { self.check(); }, 50);

      this.onHashChanged = onchange;
      this.mode = 'legacy';
    }

    window.Router.listeners.push(fn);

    return this.mode;
  },

  destroy: function (fn) {
    if (!window.Router || !window.Router.listeners) {
      return;
    }

    var listeners = window.Router.listeners;

    for (var i = listeners.length - 1; i >= 0; i--) {
      if (listeners[i] === fn) {
        listeners.splice(i, 1);
      }
    }
  },

  setHash: function (s) {
    // Mozilla always adds an entry to the history
    if (this.mode === 'legacy') {
      this.writeFrame(s);
    }

    if (this.history === true) {
      window.history.pushState({}, document.title, s);
      // Fire an onpopstate event manually since pushing does not obviously
      // trigger the pop event.
      this.fire();
    } else {
      dloc.hash = (s[0] === '/') ? s : '/' + s;
    }
    return this;
  },

  writeFrame: function (s) {
    // IE support...
    var f = document.getElementById('state-frame');
    var d = f.contentDocument || f.contentWindow.document;
    d.open();
    d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>");
    d.close();
  },

  syncHash: function () {
    // IE support...
    var s = this._hash;
    if (s != dloc.hash) {
      dloc.hash = s;
    }
    return this;
  },

  onHashChanged: function () {}
};

var Router = exports.Router = function (routes) {
  if (!(this instanceof Router)) return new Router(routes);

  this.params   = {};
  this.routes   = {};
  this.methods  = ['on', 'once', 'after', 'before'];
  this._methods = {};

  this._insert = this.insert;
  this.insert = this.insertEx;

  this.historySupport = (window.history !== null ? window.history.pushState : null) !== null

  this.configure();
  this.mount(routes || {});
};

Router.prototype.init = function (r) {
  var self = this;
  this.handler = function(e) {
    var hash = self.history === true ? self.getPath() : dloc.hash.replace(/^#/, '');
    self.dispatch('on', hash);
  };

  if (this.history === false) {
    if (dloc.hash === '' && r) {
      dloc.hash = r;
    }

    if (dloc.hash.length > 0) {
      this.handler();
    }
  }
  else {
    // When using HTML5 History API, always run the handler. Unlike with
    // hash routing we cannot really know if we should execute or not.
    this.handler();
  }

  listener.init(this.handler, this.history);
  return this;
};

Router.prototype.explode = function () {
  var v = this.history === true ? this.getPath() : dloc.hash;
  if (v[1] === '/') { v=v.slice(1) }
  return v.slice(1, v.length).split("/");
};

Router.prototype.setRoute = function (i, v, val) {
  var url = this.explode();

  if (typeof i === 'number' && typeof v === 'string') {
    url[i] = v;
  }
  else if (typeof val === 'string') {
    url.splice(i, v, s);
  }
  else {
    url = [i];
  }

  listener.setHash(url.join('/'));
  return url;
};

//
// ### function insertEx(method, path, route, parent)
// #### @method {string} Method to insert the specific `route`.
// #### @path {Array} Parsed path to insert the `route` at.
// #### @route {Array|function} Route handlers to insert.
// #### @parent {Object} **Optional** Parent "routes" to insert into.
// insert a callback that will only occur once per the matched route.
//
Router.prototype.insertEx = function(method, path, route, parent) {
  if (method === "once") {
    method = "on";
    route = function(route) {
      var once = false;
      return function() {
        if (once) return;
        once = true;
        return route.apply(this, arguments);
      };
    }(route);
  }
  return this._insert(method, path, route, parent);
};

Router.prototype.getState = function () {
  return this.state;
};

Router.prototype.getRoute = function (v) {
  var ret = v;

  if (typeof v === "number") {
    ret = this.explode()[v];
  }
  else if (typeof v === "string"){
    var h = this.explode();
    ret = h.indexOf(v);
  }
  else {
    ret = this.explode();
  }

  return ret;
};

Router.prototype.destroy = function () {
  listener.destroy(this.handler);
  return this;
};

Router.prototype.getPath = function () {
  var path = window.location.pathname;
  if (path.substr(0, 1) !== '/') {
    path = '/' + path;
  }
  return path;
};
<commit_msg>Do not enforce type check when checking for history support<commit_after>
/*
 * browser.js: Browser specific functionality for director.
 *
 * (C) 2011, Nodejitsu Inc.
 * MIT LICENSE
 *
 */

if (!Array.prototype.filter) {
  Array.prototype.filter = function(filter, that) {
    var other = [], v;
    for (var i = 0, n = this.length; i < n; i++) {
      if (i in this && filter.call(that, v = this[i], i, this)) {
        other.push(v);
      }
    }
    return other;
  };
}

if (!Array.isArray){
  Array.isArray = function(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
  };
}

var dloc = document.location;

var listener = {
  mode: 'modern',
  hash: dloc.hash,
  history: false,

  check: function () {
    var h = dloc.hash;
    if (h != this.hash) {
      this.hash = h;
      this.onHashChanged();
    }
  },

  fire: function () {
    if (this.mode === 'modern') {
      this.history === true ? window.onpopstate() : window.onhashchange();
    }
    else {
      this.onHashChanged();
    }
  },

  init: function (fn, history) {
    var self = this;
    this.history = history;

    if (!window.Router.listeners) {
      window.Router.listeners = [];
    }

    function onchange() {
      for (var i = 0, l = window.Router.listeners.length; i < l; i++) {
        window.Router.listeners[i]();
      }
    }

    //note IE8 is being counted as 'modern' because it has the hashchange event
    if ('onhashchange' in window && (document.documentMode === undefined
      || document.documentMode > 7)) {
      // At least for now HTML5 history is available for 'modern' browsers only
      if (this.history === true) {
        // There is an old bug in Chrome that causes onpopstate to fire even
        // upon initial page load. Since the handler is run manually in init(),
        // this would cause Chrome to run it twise. Currently the only
        // workaround seems to be to set the handler after the initial page load
        // http://code.google.com/p/chromium/issues/detail?id=63040
        setTimeout(function() {
          window.onpopstate = onchange;
        }, 500);
      }
      else {
        window.onhashchange = onchange;
      }
      this.mode = 'modern';
    }
    else {
      //
      // IE support, based on a concept by Erik Arvidson ...
      //
      var frame = document.createElement('iframe');
      frame.id = 'state-frame';
      frame.style.display = 'none';
      document.body.appendChild(frame);
      this.writeFrame('');

      if ('onpropertychange' in document && 'attachEvent' in document) {
        document.attachEvent('onpropertychange', function () {
          if (event.propertyName === 'location') {
            self.check();
          }
        });
      }

      window.setInterval(function () { self.check(); }, 50);

      this.onHashChanged = onchange;
      this.mode = 'legacy';
    }

    window.Router.listeners.push(fn);

    return this.mode;
  },

  destroy: function (fn) {
    if (!window.Router || !window.Router.listeners) {
      return;
    }

    var listeners = window.Router.listeners;

    for (var i = listeners.length - 1; i >= 0; i--) {
      if (listeners[i] === fn) {
        listeners.splice(i, 1);
      }
    }
  },

  setHash: function (s) {
    // Mozilla always adds an entry to the history
    if (this.mode === 'legacy') {
      this.writeFrame(s);
    }

    if (this.history === true) {
      window.history.pushState({}, document.title, s);
      // Fire an onpopstate event manually since pushing does not obviously
      // trigger the pop event.
      this.fire();
    } else {
      dloc.hash = (s[0] === '/') ? s : '/' + s;
    }
    return this;
  },

  writeFrame: function (s) {
    // IE support...
    var f = document.getElementById('state-frame');
    var d = f.contentDocument || f.contentWindow.document;
    d.open();
    d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>");
    d.close();
  },

  syncHash: function () {
    // IE support...
    var s = this._hash;
    if (s != dloc.hash) {
      dloc.hash = s;
    }
    return this;
  },

  onHashChanged: function () {}
};

var Router = exports.Router = function (routes) {
  if (!(this instanceof Router)) return new Router(routes);

  this.params   = {};
  this.routes   = {};
  this.methods  = ['on', 'once', 'after', 'before'];
  this._methods = {};

  this._insert = this.insert;
  this.insert = this.insertEx;

  this.historySupport = (window.history != null ? window.history.pushState : null) != null

  this.configure();
  this.mount(routes || {});
};

Router.prototype.init = function (r) {
  var self = this;
  this.handler = function(e) {
    var hash = self.history === true ? self.getPath() : dloc.hash.replace(/^#/, '');
    self.dispatch('on', hash);
  };

  if (this.history === false) {
    if (dloc.hash === '' && r) {
      dloc.hash = r;
    }

    if (dloc.hash.length > 0) {
      this.handler();
    }
  }
  else {
    // When using HTML5 History API, always run the handler. Unlike with
    // hash routing we cannot really know if we should execute or not.
    this.handler();
  }

  listener.init(this.handler, this.history);
  return this;
};

Router.prototype.explode = function () {
  var v = this.history === true ? this.getPath() : dloc.hash;
  if (v[1] === '/') { v=v.slice(1) }
  return v.slice(1, v.length).split("/");
};

Router.prototype.setRoute = function (i, v, val) {
  var url = this.explode();

  if (typeof i === 'number' && typeof v === 'string') {
    url[i] = v;
  }
  else if (typeof val === 'string') {
    url.splice(i, v, s);
  }
  else {
    url = [i];
  }

  listener.setHash(url.join('/'));
  return url;
};

//
// ### function insertEx(method, path, route, parent)
// #### @method {string} Method to insert the specific `route`.
// #### @path {Array} Parsed path to insert the `route` at.
// #### @route {Array|function} Route handlers to insert.
// #### @parent {Object} **Optional** Parent "routes" to insert into.
// insert a callback that will only occur once per the matched route.
//
Router.prototype.insertEx = function(method, path, route, parent) {
  if (method === "once") {
    method = "on";
    route = function(route) {
      var once = false;
      return function() {
        if (once) return;
        once = true;
        return route.apply(this, arguments);
      };
    }(route);
  }
  return this._insert(method, path, route, parent);
};

Router.prototype.getState = function () {
  return this.state;
};

Router.prototype.getRoute = function (v) {
  var ret = v;

  if (typeof v === "number") {
    ret = this.explode()[v];
  }
  else if (typeof v === "string"){
    var h = this.explode();
    ret = h.indexOf(v);
  }
  else {
    ret = this.explode();
  }

  return ret;
};

Router.prototype.destroy = function () {
  listener.destroy(this.handler);
  return this;
};

Router.prototype.getPath = function () {
  var path = window.location.pathname;
  if (path.substr(0, 1) !== '/') {
    path = '/' + path;
  }
  return path;
};
<|endoftext|>


<commit_before>// jscs:disable
function draw_graph(json_data) {
	var data = json_data;
	var links = []; // dict = {source: , target: , value: ,}
	var target;
	for (var n = 0; n < data.length; n++) {
		var activation = data[n];
		var trigger_label = activation.trigger_label;
		var label = activation.label;
		var class_name = activation.class_name;
		links.push({"source": trigger_label, "target": label, "class": class_name});
	}

	var nodes = {};

	// Compute the distinct nodes from the links.
	links.forEach(function(link) {
		link.source = nodes[link.source] ||
			(nodes[link.source] = {name: link.source});
		link.target = nodes[link.target] ||
			(nodes[link.target] = {name: link.target, class: link.class});
	});

	console.log(nodes);
	console.log(links);

	var width = window.innerWidth;
	var	height = window.innerHeight;
	var height_panels = 150;
	var side_bar_height = document.getElementById('side_bar').clientHeight;
	var graph_width = document.getElementById('graph').clientWidth;

	var force = d3.layout.force()
		.nodes(d3.values(nodes))
		.links(links)
		.size([width / 2 , height/ 1.5])
		.linkDistance(300)
		.charge(-2000)
		.gravity(.1)
		.on("tick", tick)
		.start();

	// remove if anything was already drawn on the screen
	d3.select("body").select("#container").select("#parent").select("#graph").select("svg").remove();
	// draw new graph

	var svg = d3.select("body").select("#container").select("#parent").select("#graph").append("svg")
		.attr("width", graph_width)
		.attr("height", side_bar_height);

	// build the arrow.
	svg.append("svg:defs").selectAll("marker")
		.data(["end"])      // Different link/path types can be defined here
	.enter().append("svg:marker")    // This section adds in the arrows
		.attr("id", String)
		.attr("viewBox", "0 -5 10 10")
		.attr("refX", 23)
		.attr("refY", 0)
		.attr("fill", "black")
		.attr("markerWidth", 10)
		.attr("markerHeight", 6)
		.attr("orient", "auto")
	.append("svg:path")
		.attr("d", "M0,-5L10,0L0,5");
/*eslint-disable no-unused-vars*/
	// add the links and the arrows
	var path_index = 0;
	var path = svg.append("svg:g").selectAll("path")
		.data(force.links())
	.enter().append("svg:path")
		.attr("class", function(d) { return "link " + d.type; })
		.attr("class", "link")
		.attr("id", function(){
			path_index = path_index + 1;
			return path_index;
		} )
		/*.on("mouseover", function(d){
			var g = d3.select(this); // The node
			// The class is used to remove the additional text later
	//			if (d3.select(this).select('text.info')[0][0] == null){
			var info = g.append('text')
				.classed('info', true)
	//				.attr('x', 20)
	//				.attr('y', 10)
				.attr("font-size","30px")
				.append("textPath")
				.attr("xlink:href", function (d,i) {
					var path_id = g[0][0].id;
					return path_id; })
				.text(function(d) {
					return d.value; });
		})
		.on("mouseout", function() {
		// Remove the info text on mouse out.
			//d3.select(this).select('text.info').remove();
		})*/
		.attr("marker-end", function(d) {if (d.value === 0){
											return "";
										} else {
											return "url(#end)";}});

	// define the nodes
	var node = svg.selectAll(".node")
		.data(force.nodes())
	.enter().append("g")
		.attr("class", "node")
		.on("click", function(d) {
			var g = d3.select(this); // The node
			// The class is used to remove the additional text later
			if (d3.select(this).select('text.info')[0][0] === null){
				var info = g.append('text')
					.classed('info', true)
					.attr('x', 0)
					.attr('y', -10)
					.attr("font-size","30px")
					.text(function(d) { console.log(d); });
			} else {
				d3.select(this).select('text.info').remove();
			}
		})
	/*		.on("mouseout", function() {
			// Remove the info text on mouse out.
			d3.select(this).select('text.info').remove();
		})*/
		.call(force.drag);

	// add the nodes
	node.append("rect")
		.attr("x", -150)
		.attr("y", -10)
		.attr("width", function(d){
			return 300;})
		.attr("height", function(d){
			return 50;});
/*	node.append("circle")
		.attr("r", 5);*/
/*eslint-enable no-unused-vars*/
	// add the text
	node.append("foreignObject")
		.attr("x", -145)
		.attr("dy", ".35em")
		.attr("font-size", "10xp")
		.html(function(d) {
			var name;
			if (d.class){
				var first_line = "Agent:" + d.class;
				var second_line = "Label:" + d.name;
				name = first_line + "\n" + second_line;
			} else {
				name = "Agent:" + "\n" + "Label:" + d.name;
			}
			console.log(name);
			return name;
		});

	resize();
	d3.select(window).on("resize", resize);
	// add the curvy lines
	function tick() {
		path.attr("d", function(d) {
			var dx = d.target.x - d.source.x;
			var	dy = d.target.y - d.source.y;
			//var dr = Math.sqrt(dx * dx + dy * dy);
			var dr = 0;
			return "M" +
				d.source.x + "," +
				d.source.y + "A" +
				dr + "," + dr + " 0 0,1 " +
				d.target.x + "," +
				d.target.y;
		});

		node
			.attr("transform", function(d) {
			return "translate(" + d.x + "," + d.y + ")"; });
	}

	function resize(){
		var graph_width = document.getElementById('graph').clientWidth;
		svg.attr("width", graph_width).attr("height", height - height_panels);
		force.size([width / 2, height / 1.5]).resume();
	}

}

// Returns a list of all nodes under the root.
function flatten(root) {
  var nodes = [], i = 0;

  function recurse(node) {
    if (node.children) node.children.forEach(recurse);
    if (!node.id) node.id = ++i;
    nodes.push(node);
  }

  recurse(root);
  return nodes;
}
<commit_msg>Updated graph<commit_after>// jscs:disable
function draw_graph(json_data) {
	var data = json_data;
	var links = []; // dict = {source: , target: , value: ,}
	var target;
	for (var n = 0; n < data.length; n++) {
		var activation = data[n];
		var trigger_label = activation.trigger_label;
		var label = activation.label;
		var class_name = activation.class_name;
		links.push({"source": trigger_label, "target": label, "class": class_name});
	}

	var nodes = {};

	// Compute the distinct nodes from the links.
	links.forEach(function(link) {
		link.source = nodes[link.source] ||
			(nodes[link.source] = {name: link.source});
		link.target = nodes[link.target] ||
			(nodes[link.target] = {name: link.target, class: link.class});
	});

	console.log(nodes);
	console.log(links);

	var width = window.innerWidth;
	var	height = window.innerHeight;
	var height_panels = 150;
	var side_bar_height = document.getElementById('side_bar').clientHeight;
	var graph_width = document.getElementById('graph').clientWidth;

	var force = d3.layout.force()
		.nodes(d3.values(nodes))
		.links(links)
		.size([width / 2 , height/ 1.5])
		.linkDistance(300)
		.charge(-2000)
		.gravity(.1)
		.on("tick", tick)
		.start();

	// remove if anything was already drawn on the screen
	d3.select("body").select("#container").select("#parent").select("#graph").select("svg").remove();
	// draw new graph

	var svg = d3.select("body").select("#container").select("#parent").select("#graph").append("svg")
		.attr("width", graph_width)
		.attr("height", side_bar_height);

	// build the arrow.
	svg.append("svg:defs").selectAll("marker")
		.data(["end"])      // Different link/path types can be defined here
	.enter().append("svg:marker")    // This section adds in the arrows
		.attr("id", String)
		.attr("viewBox", "0 -5 10 10")
		.attr("refX", 23)
		.attr("refY", 0)
		.attr("fill", "black")
		.attr("markerWidth", 10)
		.attr("markerHeight", 6)
		.attr("orient", "auto")
	.append("svg:path")
		.attr("d", "M0,-5L10,0L0,5");
/*eslint-disable no-unused-vars*/
	// add the links and the arrows
	var path_index = 0;
	var path = svg.append("svg:g").selectAll("path")
		.data(force.links())
	.enter().append("svg:path")
		.attr("class", function(d) { return "link " + d.type; })
		.attr("class", "link")
		.attr("id", function(){
			path_index = path_index + 1;
			return path_index;
		} )
		/*.on("mouseover", function(d){
			var g = d3.select(this); // The node
			// The class is used to remove the additional text later
	//			if (d3.select(this).select('text.info')[0][0] == null){
			var info = g.append('text')
				.classed('info', true)
	//				.attr('x', 20)
	//				.attr('y', 10)
				.attr("font-size","30px")
				.append("textPath")
				.attr("xlink:href", function (d,i) {
					var path_id = g[0][0].id;
					return path_id; })
				.text(function(d) {
					return d.value; });
		})
		.on("mouseout", function() {
		// Remove the info text on mouse out.
			//d3.select(this).select('text.info').remove();
		})*/
		.attr("marker-end", function(d) {if (d.value === 0){
											return "";
										} else {
											return "url(#suit)";}});

	// define the nodes
	var node = svg.selectAll(".node")
		.data(force.nodes())
	.enter().append("g")
		.attr("class", "node")
		.on("click", function(d) {
			var g = d3.select(this); // The node
			// The class is used to remove the additional text later
			if (d3.select(this).select('text.info')[0][0] === null){
				var info = g.append('text')
					.classed('info', true)
					.attr('x', 0)
					.attr('y', -10)
					.attr("font-size","30px")
					.text(function(d) { console.log(d); });
			} else {
				d3.select(this).select('text.info').remove();
			}
		})
	/*		.on("mouseout", function() {
			// Remove the info text on mouse out.
			d3.select(this).select('text.info').remove();
		})*/
		.call(force.drag);

	// add the nodes
	node.append("rect")
		.attr("x", -150)
		.attr("y", -10)
		.attr("width", function(d){
			return 300;})
		.attr("height", function(d){
			return 50;});
/*	node.append("circle")
		.attr("r", 5);*/
/*eslint-enable no-unused-vars*/
	// add the text
	node.append("foreignObject")
		.attr("x", -145)
		.attr("dy", ".35em")
		.attr("font-size", "10xp")
		.html(function(d) {
			var name;
			if (d.class){
				var first_line = "Agent:" + d.class;
				var second_line = "Label:" + d.name;
				name = first_line + "\n" + second_line;
			} else {
				name = "Agent:" + "\n" + "Label:" + d.name;
			}
			console.log(name);
			return name;
		});

	resize();
	d3.select(window).on("resize", resize);
	// add the curvy lines
	function tick() {
		path.attr("d", function(d) {
			var dx = d.target.x - d.source.x;
			var	dy = d.target.y - d.source.y;
			//var dr = Math.sqrt(dx * dx + dy * dy);
			var dr = 0;
			return "M" +
				d.source.x + "," +
				d.source.y + "A" +
				dr + "," + dr + " 0 0,1 " +
				d.target.x + "," +
				d.target.y;
		});

		node
			.attr("transform", function(d) {
			return "translate(" + d.x + "," + d.y + ")"; });
	}

	function resize(){
		var graph_width = document.getElementById('graph').clientWidth;
		svg.attr("width", graph_width).attr("height", height - height_panels);
		force.size([width / 2, height / 1.5]).resume();
	}

}

// Returns a list of all nodes under the root.
function flatten(root) {
  var nodes = [], i = 0;

  function recurse(node) {
    if (node.children) node.children.forEach(recurse);
    if (!node.id) node.id = ++i;
    nodes.push(node);
  }

  recurse(root);
  return nodes;
}
<|endoftext|>


<commit_before>/*jslint browser: true, devel: true, node: true, ass: true, nomen: true, unparam: true, indent: 4 */

/**
 * @license
 * Copyright (c) 2015 The ExpandJS authors. All rights reserved.
 * This code may only be used under the BSD style license found at https://expandjs.github.io/LICENSE.txt
 * The complete set of authors may be found at https://expandjs.github.io/AUTHORS.txt
 * The complete set of contributors may be found at https://expandjs.github.io/CONTRIBUTORS.txt
 */
(function (global) {
    "use strict";

    var assertArgument        = require('../assert/assertArgument'),
        getBoundings          = require('../dom/getBoundings'),
        getHeight             = require('../dom/getHeight'),
        getMargin             = require('../dom/getMargin'),
        getWidth              = require('../dom/getWidth'),
        isElement             = require('../tester/isElement'),
        isString              = require('../tester/isString'),
        isVoid                = require('../tester/isVoid'),
        setStyles             = require('../dom/setStyles'),
        willBleedBottom       = require('../dom/willBleedBottom'),
        willBleedHorizontally = require('../dom/willBleedHorizontally'),
        willBleedLeft         = require('../dom/willBleedLeft'),
        willBleedRight        = require('../dom/willBleedRight'),
        willBleedTop          = require('../dom/willBleedTop'),
        willBleedVertically   = require('../dom/willBleedVertically');

    /**
     * Aligns an element relative to a target element. A third parameter can be passed
     * with the position's beharious, if the element need to be positioned
     * on the side, at the baseline of the target or over the target. A forth parameter
     * can be passed if the element needs to be centered on its axis.
     *
     * @function align
     * @param {Element} [element] The element to be positioned
     * @param {Element} [target] The relative target of the position
     * @param {string} [position = "over"] The position's behaviour
     * @param {boolean} [autoCenter = false] Flag for autocentering of the element
     * @returns {Element | undefined} Returns the new positioned element
     */
    module.exports = function alignElement(element, target, position, autoCenter) {

        // Asserting
        assertArgument(isVoid(element) || isElement(element), 1, 'Element');
        assertArgument(isVoid(target) || isElement(target), 2, 'Element');
        assertArgument(isVoid(position) || isString(position), 3, 'string');

        // Checking
        if (!element) { return; }

        // Styling
        setStyles(element, {position: 'fixed', bottom: null, left: 0, right: null, top: 0});

        // Vars
        var domHeight       = getHeight(),
            domWidth        = getWidth(),
            margin          = getMargin(element),
            boundings       = getBoundings(element),
            targetBoundings = getBoundings(target || global.document.body),
            newBoundings;

        // Calculating
        boundings.left = targetBoundings.left + (position === 'aside' ? targetBoundings.width : (autoCenter || !target ? (targetBoundings.width / 2) - (boundings.width / 2) : 0)) - margin.left;
        boundings.top  = targetBoundings.top + (position === 'baseline' ? targetBoundings.height : (!target ? (targetBoundings.height / 2) - (boundings.height / 2) : 0)) - margin.top;

        // Recalculating
        if (willBleedRight(boundings, margin)) { boundings.left = position === 'aside' ? boundings.left - (targetBoundings.width + boundings.width) : domWidth - (margin.left + boundings.width + margin.right); }
        if (willBleedLeft(boundings, margin)) { boundings.left = position === 'aside' && !willBleedHorizontally(boundings, margin) ? domWidth - (margin.left + boundings.width + margin.right) : 0; }
        if (willBleedBottom(boundings, margin)) { boundings.top = position === 'baseline' ? boundings.top - (boundings.height + targetBoundings.height) : domHeight - (margin.top + boundings.height + margin.bottom); }
        if (willBleedTop(boundings, margin)) { boundings.top = position === 'baseline' && !willBleedVertically(boundings, margin) ? domHeight - (margin.top + boundings.height + margin.bottom) : 0; }

        // Styling
        setStyles(element, {left: boundings.left + 'px', right: willBleedRight(boundings, margin) ? '0px' : null});
        setStyles(element, {top: boundings.top + 'px', bottom: willBleedBottom(boundings, margin) ? '0px' : null});

        // Getting
        newBoundings = getBoundings(element);

        // Fixing ("position: fixed" bug)
        if ((newBoundings.left -= margin.left) !== boundings.left) { setStyles(element, {left: ((boundings.left * 2) - newBoundings.left) + 'px', right: willBleedRight(boundings, margin) ? (boundings.left - newBoundings.left) + 'px' : null}); }
        if ((newBoundings.top -= margin.top) !== boundings.top) { setStyles(element, {top: ((boundings.top * 2) - newBoundings.top) + 'px', bottom: willBleedBottom(boundings, margin) ? (boundings.top - newBoundings.top) + 'px' : null}); }

        return element;
    };

}(typeof window !== "undefined" ? window : global));<commit_msg>First release<commit_after>/*jslint browser: true, devel: true, node: true, ass: true, nomen: true, unparam: true, indent: 4 */

/**
 * @license
 * Copyright (c) 2015 The ExpandJS authors. All rights reserved.
 * This code may only be used under the BSD style license found at https://expandjs.github.io/LICENSE.txt
 * The complete set of authors may be found at https://expandjs.github.io/AUTHORS.txt
 * The complete set of contributors may be found at https://expandjs.github.io/CONTRIBUTORS.txt
 */
(function (global) {
    "use strict";

    var assertArgument        = require('../assert/assertArgument'),
        getBoundings          = require('../dom/getBoundings'),
        getHeight             = require('../dom/getHeight'),
        getMargin             = require('../dom/getMargin'),
        getWidth              = require('../dom/getWidth'),
        isElement             = require('../tester/isElement'),
        isString              = require('../tester/isString'),
        isVoid                = require('../tester/isVoid'),
        setStyles             = require('../dom/setStyles'),
        willBleedBottom       = require('../dom/willBleedBottom'),
        willBleedHorizontally = require('../dom/willBleedHorizontally'),
        willBleedLeft         = require('../dom/willBleedLeft'),
        willBleedRight        = require('../dom/willBleedRight'),
        willBleedTop          = require('../dom/willBleedTop');

    /**
     * Aligns an element relative to a target element. A third parameter can be passed
     * with the position's beharious, if the element need to be positioned
     * on the side, at the baseline of the target or over the target. A forth parameter
     * can be passed if the element needs to be centered on its axis.
     *
     * @function align
     * @param {Element} [element] The element to be positioned
     * @param {Element} [target] The relative target of the position
     * @param {string} [position = "over"] The position's behaviour
     * @param {boolean} [autoCenter = false] Flag for autocentering of the element
     * @returns {Element | undefined} Returns the new positioned element
     */
    module.exports = function alignElement(element, target, position, autoCenter) {

        // Asserting
        assertArgument(isVoid(element) || isElement(element), 1, 'Element');
        assertArgument(isVoid(target) || isElement(target), 2, 'Element');
        assertArgument(isVoid(position) || isString(position), 3, 'string');

        // Checking
        if (!element) { return; }

        // Styling
        setStyles(element, {position: 'fixed', bottom: null, left: 0, right: null, top: 0});

        // Vars
        var domHeight       = getHeight(),
            domWidth        = getWidth(),
            margin          = getMargin(element),
            boundings       = getBoundings(element),
            targetBoundings = getBoundings(target || global.document.body),
            newBoundings;

        // Calculating
        boundings.left = targetBoundings.left + (position === 'aside' ? targetBoundings.width : (autoCenter || !target ? (targetBoundings.width / 2) - (boundings.width / 2) : 0)) - margin.left;
        boundings.top  = targetBoundings.top + (position === 'baseline' ? targetBoundings.height : (!target ? (targetBoundings.height / 2) - (boundings.height / 2) : 0)) - margin.top;

        // Recalculating
        if (willBleedRight(boundings, margin)) { boundings.left = position === 'aside' ? boundings.left - (targetBoundings.width + boundings.width) : domWidth - (margin.left + boundings.width + margin.right); }
        if (willBleedLeft(boundings, margin)) { boundings.left = position === 'aside' && !willBleedHorizontally(boundings, margin) ? domWidth - (margin.left + boundings.width + margin.right) : 0; }
        if (willBleedBottom(boundings, margin)) { boundings.top = domHeight - (margin.top + boundings.height + margin.bottom); }
        if (willBleedTop(boundings, margin)) { boundings.top = 0; }

        // Styling
        setStyles(element, {left: boundings.left + 'px', right: willBleedRight(boundings, margin) ? '0px' : null});
        setStyles(element, {top: boundings.top + 'px', bottom: willBleedBottom(boundings, margin) ? '0px' : null});

        // Getting
        newBoundings = getBoundings(element);

        // Fixing ("position: fixed" bug)
        if ((newBoundings.left -= margin.left) !== boundings.left) { setStyles(element, {left: ((boundings.left * 2) - newBoundings.left) + 'px', right: willBleedRight(boundings, margin) ? (boundings.left - newBoundings.left) + 'px' : null}); }
        if ((newBoundings.top -= margin.top) !== boundings.top) { setStyles(element, {top: ((boundings.top * 2) - newBoundings.top) + 'px', bottom: willBleedBottom(boundings, margin) ? (boundings.top - newBoundings.top) + 'px' : null}); }

        return element;
    };

}(typeof window !== "undefined" ? window : global));<|endoftext|>


<commit_before>/*eslint-disable */
//
// Maths Functions
//
// The name might be a bit close to JS's Math.whatever
// Feel free to think of a better one.
//

var _ = require('lodash');

var Maths = {
    extent: function (data) {
        var sorted = _.sortBy(data);

        // Sorts array and returns as [first, last]
        // var sorted = data.sort(function (a,b) {
        //     return a - b;
        // });

        var min = sorted[0];
        var max = sorted[sorted.length - 1];

        return [min, max];
    },
    normalize: function (rawValue, min, max) {
        // return 100 - Math.round((ammount / max) * 100);
        return (rawValue - min) / (max - min);
    },
    tickRange: function (ticks, min, max) {
        // Actual range of graph.
        var segment = max - min;

        // each tick is a proption of the segment. Must remove one as we are really
        // calculating the gaps between the ticks.
        // var tickSpace = segment / (ticks - 1);

        if (!ticks || ticks === 0) {
            throw new Error('Stampy.Maths.tickRange() - ticks must be > 0');
        }

        var tickSpace = segment / ticks;

        // return a range from base to the max at steps of the tickSpace.
        // Must add 1 becuase the last tick's height is really a zero value
        return _.range(min, max + tickSpace, tickSpace).reverse();
    },
    autoDecimalPlaces: function (value, fixed, cutoff) {
        cutoff = cutoff || 0.1;
        value = Number(value);
        return (value < cutoff) ? value : value.toFixed(fixed);
    },
    toDecimal: function (a, b) {
        return b * a / 100;
    },
    toPercentage: function (a, b) {
        return 100 * a / b;
    },
    cycle: function (length, index) {
        return (length + index) % length;
    }
};

module.exports = Maths;<commit_msg>Removed lodash from Maths<commit_after>/*eslint-disable */
//
// Maths Functions
//
// The name might be a bit close to JS's Math.whatever
// Feel free to think of a better one.
//

var Maths = {
    extent: function (data) {
        return [Math.min.apply(null, data), Math.max.apply(null, data)];
    },
    normalize: function (rawValue, min, max) {
        // return 100 - Math.round((ammount / max) * 100);
        return (rawValue - min) / (max - min);
    },
    tickRange: function (ticks, min, max) {
        // Actual range of graph.
        var segment = max - min;

        // each tick is a proption of the segment. Must remove one as we are really
        // calculating the gaps between the ticks.
        // var tickSpace = segment / (ticks - 1);

        if (!ticks || ticks === 0) {
            throw new Error('Stampy.Maths.tickRange() - ticks must be > 0');
        }

        var tickSpace = segment / ticks;

        // return a range from base to the max at steps of the tickSpace.
        // Must add 1 becuase the last tick's height is really a zero value
        return this.range(min, max + tickSpace, tickSpace).reverse();
    },
    range: function(start, end, step) {
        // from lodash 3.10.1
        start = +start || 0;
        step = step == null ? 1 : (+step || 0);
        if (end == null) {
            end = start;
            start = 0;
        } else {
            end = +end || 0;
        }
        var index = -1,
            length = Math.max(Math.ceil((end - start) / (step || 1)), 0),
            result = Array(length);
        while (++index < length) {
            result[index] = start;
            start += step;
        }
        return result;
    },
    autoDecimalPlaces: function (value, fixed, cutoff) {
        cutoff = cutoff || 0.1;
        value = Number(value);
        return (value < cutoff) ? value : value.toFixed(fixed);
    },
    toDecimal: function (a, b) {
        return b * a / 100;
    },
    toPercentage: function (a, b) {
        return 100 * a / b;
    },
    cycle: function (length, index) {
        return (length + index) % length;
    }
};

module.exports = Maths;<|endoftext|>


<commit_before>openerp.web.data_import = function(openerp) {
var QWeb = openerp.web.qweb;
/**
 * Safari does not deal well at all with raw JSON data being returned. As a
 * result, we're going to cheat by using a pseudo-jsonp: instead of getting
 * JSON data in the iframe, we're getting a ``script`` tag which consists of a
 * function call and the returned data (the json dump).
 *
 * The function is an auto-generated name bound to ``window``, which calls
 * back into the callback provided here.
 *
 * @param {Object} form the form element (DOM or jQuery) to use in the call
 * @param {Object} attributes jquery.form attributes object
 * @param {Function} callback function to call with the returned data
 */
function jsonp(form, attributes, callback) {
    attributes = attributes || {};
    var options = {jsonp: _.uniqueId('import_callback_')};
    window[options.jsonp] = function () {
        delete window[options.jsonp];
        callback.apply(null, arguments);
    };
    if ('data' in attributes) {
        _.extend(attributes.data, options);
    } else {
        _.extend(attributes, {data: options});
    }
    $(form).ajaxSubmit(attributes);
}

openerp.web.DataImport = openerp.web.Dialog.extend({
    template: 'ImportDataView',
    dialog_title: "Import Data",
    init: function(parent, dataset){
        var self = this;
        this._super(parent, {});
        this.model = parent.model;
        this.fields = [];
        this.all_fields = [];
        this.required_fields = null;

        var convert_fields = function (root, prefix) {
            prefix = prefix || '';
            _(root.fields).each(function (f) {
                self.all_fields.push(prefix + f.name);
                if (f.fields) {
                    convert_fields(f, prefix + f.id + '/');
                }
            });
        };
        this.ready  = $.Deferred.queue().then(function () {
            self.required_fields = _(self.fields).chain()
                .filter(function (field) { return field.required; })
                .pluck('name')
                .value();
            convert_fields(self);
            self.all_fields.sort();
        });
    },
    start: function() {
        var self = this;
        this._super();
        this.open({
            modal: true,
            width: '70%',
            height: 'auto',
            position: 'top',
            buttons: [
                {text: "Close", click: function() { self.stop(); }},
                {text: "Import File", click: function() { self.do_import(); }, 'class': 'oe-dialog-import-button'}
            ],
            close: function(event, ui) {
                self.stop();
            }
        });
        this.toggle_import_button(false);
        this.$element.find('#csvfile').change(this.on_autodetect_data);
        this.$element.find('fieldset').change(this.on_autodetect_data);
        this.$element.find('fieldset legend').click(function() {
            $(this).next().toggle();
        });
        this.ready.push(new openerp.web.DataSet(this, this.model).call(
            'fields_get', [], function (fields) {
                self.graft_fields(fields);
            }));
    },
    graft_fields: function (fields, parent, level) {
        parent = parent || this;
        level = level || 0;

        var self = this;
        _(fields).each(function (field, field_name) {
            var f = {
                id: field_name,
                name: field_name,
                string: field.string,
                required: field.required
            };

            switch (field.type) {
            case 'many2many':
            case 'many2one':
                f.name += '/id';
                break;
            case 'one2many':
                f.name += '/id';
                f.fields = [];
                // only fetch sub-fields to a depth of 2 levels
                if (level < 2) {
                    self.ready.push(new openerp.web.DataSet(self, field.relation).call(
                        'fields_get', [], function (fields) {
                            self.graft_fields(fields, f, level+1);
                    }));
                }
                break;
            }
            parent.fields.push(f);
        });
    },
    toggle_import_button: function (newstate) {
        this.$dialog.dialog('widget')
                .find('.oe-dialog-import-button')
                .button('option', 'disabled', !newstate);
    },
    do_import: function() {
        if(!this.$element.find('#csvfile').val()) { return; }
        var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10);
        var with_headers = this.$element.find('#file_has_headers').prop('checked');
        if (!lines_to_skip && with_headers) {
            lines_to_skip = 1;
        }
        var indices = [], fields = [];
        this.$element.find(".sel_fields").each(function(index, element) {
            var val = element.value;
            if (!val) {
                return;
            }
            indices.push(index);
            fields.push(val);
        });

        jsonp(this.$element.find('#import_data'), {
            url: '/web/import/import_data',
            data: {
                model: this.model,
                meta: JSON.stringify({
                    skip: lines_to_skip,
                    indices: indices,
                    fields: fields
                })
            }
        }, this.on_import_results);
    },
    on_autodetect_data: function() {
        if(!this.$element.find('#csvfile').val()) { return; }
        jsonp(this.$element.find('#import_data'), {
            url: '/web/import/detect_data'
        }, this.on_import_results);
    },
    on_import_results: function(results) {
        this.$element.find('#result').empty();
        var headers, result_node = this.$element.find("#result");

        if (results['records']) {
            var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10),
                with_headers = this.$element.find('#file_has_headers').prop('checked');
            headers = with_headers ? results.records[0] : null;

            result_node.append(QWeb.render('ImportView.result', {
                'headers': headers,
                'records': lines_to_skip ? results.records.slice(lines_to_skip)
                          : with_headers ? results.records.slice(1)
                          : results.records
            }));
        } else if (results['error']) {
            result_node.append(QWeb.render('ImportView.error', {
                'error': results['error']}));
        } else if (results['success']) {
            if (this.widget_parent.widget_parent.active_view == "list") {
                this.widget_parent.reload_content();
            }
            this.stop();
            return;
        }

        var self = this;
        this.ready.then(function () {
            var $fields = self.$element.find('.sel_fields').bind('blur', function () {
                if (this.value && !_(self.all_fields).contains(this.value)) {
                    this.value = '';
                }
            }).autocomplete({
                minLength: 0,
                source: self.all_fields,
                change: self.on_check_field_values
            }).focus(function () {
                $(this).autocomplete('search');
            });
            // Column auto-detection
            _(headers).each(function (header, index) {
                var f =_(self.fields).detect(function (field) {
                    // TODO: levenshtein between header and field.string
                    return field.name === header || field.string.toLowerCase() === header;
                });
                if (f) {
                    $fields.eq(index).val(f.name);
                }
            });
            self.on_check_field_values();
        });
    },
    /**
     * Looks through all the field selections, and tries to find if two
     * (or more) columns were matched to the same model field.
     *
     * Returns a map of the multiply-mapped fields to an array of offending
     * columns (not actually columns, but the inputs containing the same field
     * names).
     *
     * Also has the side-effect of marking the discovered inputs with the class
     * ``duplicate_fld``.
     *
     * @returns {Object<String, Array<String>>} map of duplicate field matches to same-valued inputs
     */
    find_duplicate_fields: function() {
        // Maps values to DOM nodes, in order to discover duplicates
        var values = {}, duplicates = {};
        this.$element.find(".sel_fields").each(function(index, element) {
            var value = element.value;
            var $element = $(element).removeClass('duplicate_fld');
            if (!value) { return; }

            if (!(value in values)) {
                values[value] = element;
            } else {
                var same_valued_field = values[value];
                if (value in duplicates) {
                    duplicates[value].push(element);
                } else {
                    duplicates[value] = [same_valued_field, element];
                }
                $element.add(same_valued_field).addClass('duplicate_fld');
            }
        });
        return duplicates;
    },
    on_check_field_values: function () {
        this.$element.find("#message, #msg").remove();

        var required_valid = this.check_required();

        var duplicates = this.find_duplicate_fields();
        if (_.isEmpty(duplicates)) {
            this.toggle_import_button(required_valid);
        } else {
            var $err = $('
Destination fields should only be selected once, some fields are selected more than once:
').insertBefore(this.$element.find('#result'));
            var $dupes = $('
').appendTo($err);
            _(duplicates).each(function(elements, value) {
                $('
').text(value).appendTo($dupes);
                _(elements).each(function(element) {
                    var cell = $(element).closest('td');
                    $('
').text(cell.parent().children().index(cell)).appendTo($dupes);
                });
            });
            this.toggle_import_button(false);
        }

    },
    check_required: function() {
        if (!this.required_fields.length) { return true; }

        var selected_fields = _(this.$element.find('.sel_fields').get()).chain()
            .pluck('value')
            .compact()
            .value();

        var missing_fields = _.difference(this.required_fields, selected_fields);
        if (missing_fields.length) {
            this.$element.find("#result").before('
*Required Fields are not selected : ' + missing_fields + '.
');
            return false;
        }
        return true;
    },
    stop: function() {
        $(this.$dialog).remove();
        this._super();
    }
});
};
<commit_msg>[FIX] columns with default values should not be marked as required during import<commit_after>openerp.web.data_import = function(openerp) {
var QWeb = openerp.web.qweb;
/**
 * Safari does not deal well at all with raw JSON data being returned. As a
 * result, we're going to cheat by using a pseudo-jsonp: instead of getting
 * JSON data in the iframe, we're getting a ``script`` tag which consists of a
 * function call and the returned data (the json dump).
 *
 * The function is an auto-generated name bound to ``window``, which calls
 * back into the callback provided here.
 *
 * @param {Object} form the form element (DOM or jQuery) to use in the call
 * @param {Object} attributes jquery.form attributes object
 * @param {Function} callback function to call with the returned data
 */
function jsonp(form, attributes, callback) {
    attributes = attributes || {};
    var options = {jsonp: _.uniqueId('import_callback_')};
    window[options.jsonp] = function () {
        delete window[options.jsonp];
        callback.apply(null, arguments);
    };
    if ('data' in attributes) {
        _.extend(attributes.data, options);
    } else {
        _.extend(attributes, {data: options});
    }
    $(form).ajaxSubmit(attributes);
}

openerp.web.DataImport = openerp.web.Dialog.extend({
    template: 'ImportDataView',
    dialog_title: "Import Data",
    init: function(parent, dataset){
        var self = this;
        this._super(parent, {});
        this.model = parent.model;
        this.fields = [];
        this.all_fields = [];
        this.fields_with_defaults = [];
        this.required_fields = null;

        var convert_fields = function (root, prefix) {
            prefix = prefix || '';
            _(root.fields).each(function (f) {
                self.all_fields.push(prefix + f.name);
                if (f.fields) {
                    convert_fields(f, prefix + f.id + '/');
                }
            });
        };
        this.ready  = $.Deferred.queue().then(function () {
            self.required_fields = _(self.fields).chain()
                .filter(function (field) {
                    return field.required &&
                           !_.include(self.fields_with_defaults, field.id); })
                .pluck('name')
                .value();
            convert_fields(self);
            self.all_fields.sort();
        });
    },
    start: function() {
        var self = this;
        this._super();
        this.open({
            modal: true,
            width: '70%',
            height: 'auto',
            position: 'top',
            buttons: [
                {text: "Close", click: function() { self.stop(); }},
                {text: "Import File", click: function() { self.do_import(); }, 'class': 'oe-dialog-import-button'}
            ],
            close: function(event, ui) {
                self.stop();
            }
        });
        this.toggle_import_button(false);
        this.$element.find('#csvfile').change(this.on_autodetect_data);
        this.$element.find('fieldset').change(this.on_autodetect_data);
        this.$element.find('fieldset legend').click(function() {
            $(this).next().toggle();
        });
        this.ready.push(new openerp.web.DataSet(this, this.model).call(
            'fields_get', [], function (fields) {
                self.graft_fields(fields);
                self.ready.push(new openerp.web.DataSet(self, self.model)
                        .default_get(_.pluck(self.fields, 'id'), function (fields) {
                    _.each(fields, function(val, key) {
                        if (val) {
                            self.fields_with_defaults.push(key);
                        }
                    });
                })
            )
        }));
    },
    graft_fields: function (fields, parent, level) {
        parent = parent || this;
        level = level || 0;

        var self = this;
        _(fields).each(function (field, field_name) {
            var f = {
                id: field_name,
                name: field_name,
                string: field.string,
                required: field.required
            };

            switch (field.type) {
            case 'many2many':
            case 'many2one':
                f.name += '/id';
                break;
            case 'one2many':
                f.name += '/id';
                f.fields = [];
                // only fetch sub-fields to a depth of 2 levels
                if (level < 2) {
                    self.ready.push(new openerp.web.DataSet(self, field.relation).call(
                        'fields_get', [], function (fields) {
                            self.graft_fields(fields, f, level+1);
                    }));
                }
                break;
            }
            parent.fields.push(f);
        });
    },
    toggle_import_button: function (newstate) {
        this.$dialog.dialog('widget')
                .find('.oe-dialog-import-button')
                .button('option', 'disabled', !newstate);
    },
    do_import: function() {
        if(!this.$element.find('#csvfile').val()) { return; }
        var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10);
        var with_headers = this.$element.find('#file_has_headers').prop('checked');
        if (!lines_to_skip && with_headers) {
            lines_to_skip = 1;
        }
        var indices = [], fields = [];
        this.$element.find(".sel_fields").each(function(index, element) {
            var val = element.value;
            if (!val) {
                return;
            }
            indices.push(index);
            fields.push(val);
        });

        jsonp(this.$element.find('#import_data'), {
            url: '/web/import/import_data',
            data: {
                model: this.model,
                meta: JSON.stringify({
                    skip: lines_to_skip,
                    indices: indices,
                    fields: fields
                })
            }
        }, this.on_import_results);
    },
    on_autodetect_data: function() {
        if(!this.$element.find('#csvfile').val()) { return; }
        jsonp(this.$element.find('#import_data'), {
            url: '/web/import/detect_data'
        }, this.on_import_results);
    },
    on_import_results: function(results) {
        this.$element.find('#result').empty();
        var headers, result_node = this.$element.find("#result");

        if (results['records']) {
            var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10),
                with_headers = this.$element.find('#file_has_headers').prop('checked');
            headers = with_headers ? results.records[0] : null;

            result_node.append(QWeb.render('ImportView.result', {
                'headers': headers,
                'records': lines_to_skip ? results.records.slice(lines_to_skip)
                          : with_headers ? results.records.slice(1)
                          : results.records
            }));
        } else if (results['error']) {
            result_node.append(QWeb.render('ImportView.error', {
                'error': results['error']}));
        } else if (results['success']) {
            if (this.widget_parent.widget_parent.active_view == "list") {
                this.widget_parent.reload_content();
            }
            this.stop();
            return;
        }

        var self = this;
        this.ready.then(function () {
            var $fields = self.$element.find('.sel_fields').bind('blur', function () {
                if (this.value && !_(self.all_fields).contains(this.value)) {
                    this.value = '';
                }
            }).autocomplete({
                minLength: 0,
                source: self.all_fields,
                change: self.on_check_field_values
            }).focus(function () {
                $(this).autocomplete('search');
            });
            // Column auto-detection
            _(headers).each(function (header, index) {
                var f =_(self.fields).detect(function (field) {
                    // TODO: levenshtein between header and field.string
                    return field.name === header || field.string.toLowerCase() === header;
                });
                if (f) {
                    $fields.eq(index).val(f.name);
                }
            });
            self.on_check_field_values();
        });
    },
    /**
     * Looks through all the field selections, and tries to find if two
     * (or more) columns were matched to the same model field.
     *
     * Returns a map of the multiply-mapped fields to an array of offending
     * columns (not actually columns, but the inputs containing the same field
     * names).
     *
     * Also has the side-effect of marking the discovered inputs with the class
     * ``duplicate_fld``.
     *
     * @returns {Object<String, Array<String>>} map of duplicate field matches to same-valued inputs
     */
    find_duplicate_fields: function() {
        // Maps values to DOM nodes, in order to discover duplicates
        var values = {}, duplicates = {};
        this.$element.find(".sel_fields").each(function(index, element) {
            var value = element.value;
            var $element = $(element).removeClass('duplicate_fld');
            if (!value) { return; }

            if (!(value in values)) {
                values[value] = element;
            } else {
                var same_valued_field = values[value];
                if (value in duplicates) {
                    duplicates[value].push(element);
                } else {
                    duplicates[value] = [same_valued_field, element];
                }
                $element.add(same_valued_field).addClass('duplicate_fld');
            }
        });
        return duplicates;
    },
    on_check_field_values: function () {
        this.$element.find("#message, #msg").remove();

        var required_valid = this.check_required();

        var duplicates = this.find_duplicate_fields();
        if (_.isEmpty(duplicates)) {
            this.toggle_import_button(required_valid);
        } else {
            var $err = $('
Destination fields should only be selected once, some fields are selected more than once:
').insertBefore(this.$element.find('#result'));
            var $dupes = $('
').appendTo($err);
            _(duplicates).each(function(elements, value) {
                $('
').text(value).appendTo($dupes);
                _(elements).each(function(element) {
                    var cell = $(element).closest('td');
                    $('
').text(cell.parent().children().index(cell)).appendTo($dupes);
                });
            });
            this.toggle_import_button(false);
        }

    },
    check_required: function() {
        if (!this.required_fields.length) { return true; }

        var selected_fields = _(this.$element.find('.sel_fields').get()).chain()
            .pluck('value')
            .compact()
            .value();

        var missing_fields = _.difference(this.required_fields, selected_fields);
        if (missing_fields.length) {
            this.$element.find("#result").before('
*Required Fields are not selected : ' + missing_fields + '.
');
            return false;
        }
        return true;
    },
    stop: function() {
        $(this.$dialog).remove();
        this._super();
    }
});
};
<|endoftext|>


<commit_before>/*---------------------------------------------------------
 * OpenERP web_graph
 *---------------------------------------------------------*/

openerp.web_graph = function (openerp) {
var COLOR_PALETTE = [
    '#cc99ff', '#ccccff', '#48D1CC', '#CFD784', '#8B7B8B', '#75507b',
    '#b0008c', '#ff0000', '#ff8e00', '#9000ff', '#0078ff', '#00ff00',
    '#e6ff00', '#ffff00', '#905000', '#9b0000', '#840067', '#9abe00',
    '#ffc900', '#510090', '#0000c9', '#009b00', '#75507b', '#3465a4',
    '#73d216', '#c17d11', '#edd400', '#fcaf3e', '#ef2929', '#ff00c9',
    '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f', '#f57900',
    '#cc0000', '#d400a8'];

var QWeb = openerp.web.qweb;
QWeb.add_template('/web_graph/static/src/xml/web_graph.xml');
openerp.web.views.add('graph', 'openerp.web_graph.GraphView');
openerp.web_graph.GraphView = openerp.web.View.extend({

    init: function(parent, dataset, view_id) {
        this._super(parent);
        this.dataset = dataset;
        this.view_id = view_id;

        this.first_field = null;
        this.abscissa = null;
        this.ordinate = null;
        this.columns = [];
        this.group_field = null;
    },
    do_show: function () {
        // TODO: re-trigger search
        this.$element.show();
    },
    do_hide: function () {
        this.$element.hide();
    },
    start: function() {
        var self = this;
        this._super();
        return $.when(
            this.dataset.call('fields_get', []),
            this.rpc('/web/view/load', {
                model: this.dataset.model,
                view_id: this.view_id,
                view_type: 'graph'
            })).then(function (fields_result, view_result) {
                self.fields = fields_result[0];
                self.fields_view = view_result[0];
                self.on_loaded();
        });
    },
    /**
     * Returns all object fields involved in the graph view
     */
    list_fields: function () {
        var fs = [this.abscissa];
        fs.push.apply(fs, _(this.columns).pluck('name'));
        if (this.group_field) {
            fs.push(this.group_field);
        }
        return fs;
    },
    on_loaded: function() {
        this.chart = this.fields_view.arch.attrs.type || 'pie';
        this.orientation = this.fields_view.arch.attrs.orientation || 'vertical';

        _.each(this.fields_view.arch.children, function (field) {
            var attrs = field.attrs;
            if (attrs.group) {
                this.group_field = attrs.name;
            } else if(!this.abscissa) {
                this.first_field = this.abscissa = attrs.name;
            } else {
                this.columns.push({
                    name: attrs.name,
                    operator: attrs.operator || '+'
                });
            }
        }, this);
        this.ordinate = this.columns[0].name;

        this.dataset.read_slice(
            this.list_fields(), {}, $.proxy(this, 'schedule_chart'));
    },
    schedule_chart: function(results) {
        var self = this;
        this.$element.html(QWeb.render("GraphView", {
            "fields_view": this.fields_view,
            "chart": this.chart,
            'element_id': this.element_id
        }));

        var fields = _(this.columns).pluck('name').concat([this.first_field]);
        if (this.group_field) { fields.push(this.group_field); }
        // transform search result into usable records (convert from OpenERP
        // value shapes to usable atomic types
        var records = _(results).map(function (result) {
            var point = {};
            _(result).each(function (value, field) {
                if (!_(fields).contains(field)) { return; }
                if (value === false) { point[field] = false; return; }
                switch (self.fields[field].type) {
                case 'selection':
                    point[field] = _(self.fields[field].selection).detect(function (choice) {
                        return choice[0] === value;
                    })[1];
                    break;
                case 'many2one':
                    point[field] = value[1];
                    break;
                case 'integer': case 'float': case 'char':
                case 'date': case 'datetime':
                    point[field] = value;
                    break;
                default:
                    throw new Error(
                        "Unknown field type " + self.fields[field].type
                        + "for field " + field + " (" + value + ")");
                }
            });
            return point;
        });
        // aggregate data, because dhtmlx is crap. Aggregate on abscissa field,
        // leave split on group field => max m*n records where m is the # of
        // values for the abscissa and n is the # of values for the group field
        var graph_data = [];
        _(records).each(function (record) {
            var abscissa = record[self.abscissa],
                group = record[self.group_field];
            var r = _(graph_data).detect(function (potential) {
                return potential[self.abscissa] === abscissa
                        && (!self.group_field
                            || potential[self.group_field] === group);
            });
            var datapoint = r || {};

            datapoint[self.abscissa] = abscissa;
            if (self.group_field) { datapoint[self.group_field] = group; }
            _(self.columns).each(function (column) {
                var val = record[column.name],
                    aggregate = datapoint[column.name];
                switch(column.operator) {
                case '+':
                    datapoint[column.name] = (aggregate || 0) + val;
                    return;
                case '*':
                    datapoint[column.name] = (aggregate || 1) * val;
                    return;
                case 'min':
                    datapoint[column.name] = (aggregate || Infinity) > val
                                           ? val
                                           : aggregate;
                    return;
                case 'max':
                    datapoint[column.name] = (aggregate || -Infinity) < val
                                           ? val
                                           : aggregate;
                }
            });

            if (!r) { graph_data.push(datapoint); }
        });
        graph_data = _(graph_data).sortBy(function (point) {
            return point[self.abscissa] + '[[--]]' + point[self.group_field];
        });

        if (this.chart == 'bar') {
            return this.schedule_bar(graph_data);
        } else if (this.chart == "pie") {
            return this.schedule_pie(graph_data);
        }
    },

    schedule_bar: function(results) {
        var self = this;

        var group_list, view_chart;
        if (!this.group_field) {
            view_chart = (this.orientation === 'horizontal') ? 'barH' : 'bar';
            group_list = _(this.columns).map(function (column, index) {
                return {
                    group: column.name,
                    text: self.fields[column.name].string,
                    color: COLOR_PALETTE[index % (COLOR_PALETTE.length)]
                }
            });
        } else {
            // dhtmlx handles clustered bar charts (> 1 column per abscissa
            // value) and stacked bar charts (basically the same but with the
            // columns on top of one another instead of side by side), but it
            // does not handle clustered stacked bar charts
            if (this.columns.length > 1) {
                this.$element.text(
                    'OpenERP Web does not support combining grouping and '
                  + 'multiple columns in graph at this time.');
                throw new Error(
                    'dhtmlx can not handle columns counts of that magnitude');
            }
            // transform series for clustered charts into series for stacked
            // charts
            view_chart = (this.orientation === 'horizontal')
                    ? 'stackedBarH' : 'stackedBar';
            group_list = _(results).chain()
                    .pluck(this.group_field)
                    .uniq()
                    .map(function (value, index) {
                        return {
                            group: self.ordinate + '_' +
                                    value.toLowerCase().replace(/\s/g, '_'),
                            text: value,
                            color: COLOR_PALETTE[index % COLOR_PALETTE.length]
                        };
                    }).value();

            results = _(results).chain()
                .groupBy(function (record) { return record[self.abscissa]; })
                .map(function (records) {
                    var r = {};
                    // second argument is coerced to a str, no good for boolean
                    r[self.abscissa] = records[0][self.abscissa];
                    _(records).each(function (record) {
                        var key = _.sprintf('%s_%s',
                            self.ordinate,
                            record[self.group_field].toLowerCase().replace(/\s/g, '_'));
                        r[key] = record[self.ordinate];
                    });
                    return r;
                })
                .value();
        }

        var abscissa_description = {
            title: "" + this.fields[this.abscissa].string + "",
            template: function (obj) {
                return obj[self.abscissa] || 'Undefined';
            }
        };

        var ordinate_description = {
            lines: true,
            title: "" + this.fields[this.ordinate].string + ""
        };

        var x_axis, y_axis;
        if (self.orientation == 'horizontal') {
            x_axis = ordinate_description;
            y_axis = abscissa_description;
        } else {
            x_axis = abscissa_description;
            y_axis = ordinate_description;
        }

        var bar_chart = new dhtmlXChart({
            view: view_chart,
            container: this.element_id+"-barchart",
            value:"#"+group_list[0].group+"#",
            gradient: "3d",
            border: false,
            width: 1024,
            tooltip:{
                template: _.sprintf("#%s#, %s=#%s#",
                    this.abscissa, group_list[0].text, group_list[0].group)
            },
            radius: 0,
            color:group_list[0].color,
            origin:0,
            xAxis: x_axis,
            yAxis: y_axis,
            padding: {
                left: 75
            },
            legend: {
                values: group_list,
                align:"left",
                valign:"top",
                layout: "x",
                marker: {
                    type:"round",
                    width:12
                }
            }
        });
        for (var m = 1; m<group_list.length;m++){
            var column = group_list[m];
            if (column.group === this.group_field) { continue; }
            bar_chart.addSeries({
                value: "#"+column.group+"#",
                tooltip:{
                    template: _.sprintf("#%s#, %s=#%s#",
                        this.abscissa, column.text, column.group)
                },
                color: column.color
            });
        }
        bar_chart.parse(results, "json");
        jQuery("#"+this.element_id+"-barchart").height(jQuery("#"+this.element_id+"-barchart").height()+50);
        bar_chart.attachEvent("onItemClick", function(id) {
            self.open_list_view(bar_chart.get(id));
        });
    },
    schedule_pie: function(result) {
        var self = this;
        var chart =  new dhtmlXChart({
            view:"pie3D",
            container:self.element_id+"-piechart",
            value:"#"+self.ordinate+"#",
            pieInnerText:function(obj) {
                var sum = chart.sum("#"+self.ordinate+"#");
                var val = obj[self.ordinate] / sum * 100 ;
                return val.toFixed(1) + "%";
            },
            tooltip:{
                template:"#"+self.abscissa+"#"+"="+"#"+self.ordinate+"#"
            },
            gradient:"3d",
            height: 20,
            radius: 200,
            legend: {
                width: 300,
                align:"left",
                valign:"top",
                layout: "x",
                marker:{
                    type:"round",
                    width:12
                },
                template:function(obj){
                    return obj[self.abscissa] || 'Undefined';
                }
            }
        });
        chart.parse(result,"json");
        chart.attachEvent("onItemClick", function(id) {
            self.open_list_view(chart.get(id));
        });
    },
    open_list_view : function (id){
        var self = this;
        if($(".dhx_tooltip").is(":visible")) {
            $(".dhx_tooltip").remove('div');
        }
        id = id[this.abscissa];
        if (typeof id == 'object'){
            id = id[0];
        }

        var record_id = this.abscissa;
        var modes = ["list", "form", "graph"];
        var views = [];
        _.each(modes, function(mode) {
            var view = [false, mode];
            if (self.fields.views && self.fields.views[mode]) {
                view.push(self.fields.views[mode]);
            }
            views.push(view);
        });
        this.do_action({
            "res_model" : this.dataset.model,
            "domain" : [[record_id, '=', id], ['id','in',this.dataset.ids]],
            "views" : views,
            "type" : "ir.actions.act_window",
            "auto_search" : true,
            "view_type" : "list",
            "view_mode" : "list"
        });
    },

    do_search: function(domains, contexts, groupbys) {
        var self = this;
        this.rpc('/web/session/eval_domain_and_context', {
            domains: domains,
            contexts: contexts,
            group_by_seq: groupbys
        }, function (results) {
            // TODO: handle non-empty results.group_by with read_group?
            if(!_(results.group_by).isEmpty()){
                self.abscissa = results.group_by[0];
            } else {
                self.abscissa = self.first_field;
            }
            self.dataset.read_slice(self.list_fields(), {
                context: results.context,
                domain: results.domain
            }, $.proxy(self, 'schedule_chart'));
        });
    }
});
};
// vim:et fdc=0 fdl=0:
<commit_msg>[FIX] code should use abscissa, not first_field<commit_after>/*---------------------------------------------------------
 * OpenERP web_graph
 *---------------------------------------------------------*/

openerp.web_graph = function (openerp) {
var COLOR_PALETTE = [
    '#cc99ff', '#ccccff', '#48D1CC', '#CFD784', '#8B7B8B', '#75507b',
    '#b0008c', '#ff0000', '#ff8e00', '#9000ff', '#0078ff', '#00ff00',
    '#e6ff00', '#ffff00', '#905000', '#9b0000', '#840067', '#9abe00',
    '#ffc900', '#510090', '#0000c9', '#009b00', '#75507b', '#3465a4',
    '#73d216', '#c17d11', '#edd400', '#fcaf3e', '#ef2929', '#ff00c9',
    '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f', '#f57900',
    '#cc0000', '#d400a8'];

var QWeb = openerp.web.qweb;
QWeb.add_template('/web_graph/static/src/xml/web_graph.xml');
openerp.web.views.add('graph', 'openerp.web_graph.GraphView');
openerp.web_graph.GraphView = openerp.web.View.extend({

    init: function(parent, dataset, view_id) {
        this._super(parent);
        this.dataset = dataset;
        this.view_id = view_id;

        this.first_field = null;
        this.abscissa = null;
        this.ordinate = null;
        this.columns = [];
        this.group_field = null;
    },
    do_show: function () {
        // TODO: re-trigger search
        this.$element.show();
    },
    do_hide: function () {
        this.$element.hide();
    },
    start: function() {
        var self = this;
        this._super();
        return $.when(
            this.dataset.call('fields_get', []),
            this.rpc('/web/view/load', {
                model: this.dataset.model,
                view_id: this.view_id,
                view_type: 'graph'
            })).then(function (fields_result, view_result) {
                self.fields = fields_result[0];
                self.fields_view = view_result[0];
                self.on_loaded();
        });
    },
    /**
     * Returns all object fields involved in the graph view
     */
    list_fields: function () {
        var fs = [this.abscissa];
        fs.push.apply(fs, _(this.columns).pluck('name'));
        if (this.group_field) {
            fs.push(this.group_field);
        }
        return fs;
    },
    on_loaded: function() {
        this.chart = this.fields_view.arch.attrs.type || 'pie';
        this.orientation = this.fields_view.arch.attrs.orientation || 'vertical';

        _.each(this.fields_view.arch.children, function (field) {
            var attrs = field.attrs;
            if (attrs.group) {
                this.group_field = attrs.name;
            } else if(!this.abscissa) {
                this.first_field = this.abscissa = attrs.name;
            } else {
                this.columns.push({
                    name: attrs.name,
                    operator: attrs.operator || '+'
                });
            }
        }, this);
        this.ordinate = this.columns[0].name;

        this.dataset.read_slice(
            this.list_fields(), {}, $.proxy(this, 'schedule_chart'));
    },
    schedule_chart: function(results) {
        var self = this;
        this.$element.html(QWeb.render("GraphView", {
            "fields_view": this.fields_view,
            "chart": this.chart,
            'element_id': this.element_id
        }));

        var fields = _(this.columns).pluck('name').concat([this.abscissa]);
        if (this.group_field) { fields.push(this.group_field); }
        // transform search result into usable records (convert from OpenERP
        // value shapes to usable atomic types
        var records = _(results).map(function (result) {
            var point = {};
            _(result).each(function (value, field) {
                if (!_(fields).contains(field)) { return; }
                if (value === false) { point[field] = false; return; }
                switch (self.fields[field].type) {
                case 'selection':
                    point[field] = _(self.fields[field].selection).detect(function (choice) {
                        return choice[0] === value;
                    })[1];
                    break;
                case 'many2one':
                    point[field] = value[1];
                    break;
                case 'integer': case 'float': case 'char':
                case 'date': case 'datetime':
                    point[field] = value;
                    break;
                default:
                    throw new Error(
                        "Unknown field type " + self.fields[field].type
                        + "for field " + field + " (" + value + ")");
                }
            });
            return point;
        });
        // aggregate data, because dhtmlx is crap. Aggregate on abscissa field,
        // leave split on group field => max m*n records where m is the # of
        // values for the abscissa and n is the # of values for the group field
        var graph_data = [];
        _(records).each(function (record) {
            var abscissa = record[self.abscissa],
                group = record[self.group_field];
            var r = _(graph_data).detect(function (potential) {
                return potential[self.abscissa] === abscissa
                        && (!self.group_field
                            || potential[self.group_field] === group);
            });
            var datapoint = r || {};

            datapoint[self.abscissa] = abscissa;
            if (self.group_field) { datapoint[self.group_field] = group; }
            _(self.columns).each(function (column) {
                var val = record[column.name],
                    aggregate = datapoint[column.name];
                switch(column.operator) {
                case '+':
                    datapoint[column.name] = (aggregate || 0) + val;
                    return;
                case '*':
                    datapoint[column.name] = (aggregate || 1) * val;
                    return;
                case 'min':
                    datapoint[column.name] = (aggregate || Infinity) > val
                                           ? val
                                           : aggregate;
                    return;
                case 'max':
                    datapoint[column.name] = (aggregate || -Infinity) < val
                                           ? val
                                           : aggregate;
                }
            });

            if (!r) { graph_data.push(datapoint); }
        });
        graph_data = _(graph_data).sortBy(function (point) {
            return point[self.abscissa] + '[[--]]' + point[self.group_field];
        });

        if (this.chart == 'bar') {
            return this.schedule_bar(graph_data);
        } else if (this.chart == "pie") {
            return this.schedule_pie(graph_data);
        }
    },

    schedule_bar: function(results) {
        var self = this;

        var group_list, view_chart;
        if (!this.group_field) {
            view_chart = (this.orientation === 'horizontal') ? 'barH' : 'bar';
            group_list = _(this.columns).map(function (column, index) {
                return {
                    group: column.name,
                    text: self.fields[column.name].string,
                    color: COLOR_PALETTE[index % (COLOR_PALETTE.length)]
                }
            });
        } else {
            // dhtmlx handles clustered bar charts (> 1 column per abscissa
            // value) and stacked bar charts (basically the same but with the
            // columns on top of one another instead of side by side), but it
            // does not handle clustered stacked bar charts
            if (this.columns.length > 1) {
                this.$element.text(
                    'OpenERP Web does not support combining grouping and '
                  + 'multiple columns in graph at this time.');
                throw new Error(
                    'dhtmlx can not handle columns counts of that magnitude');
            }
            // transform series for clustered charts into series for stacked
            // charts
            view_chart = (this.orientation === 'horizontal')
                    ? 'stackedBarH' : 'stackedBar';
            group_list = _(results).chain()
                    .pluck(this.group_field)
                    .uniq()
                    .map(function (value, index) {
                        return {
                            group: self.ordinate + '_' +
                                    value.toLowerCase().replace(/\s/g, '_'),
                            text: value,
                            color: COLOR_PALETTE[index % COLOR_PALETTE.length]
                        };
                    }).value();

            results = _(results).chain()
                .groupBy(function (record) { return record[self.abscissa]; })
                .map(function (records) {
                    var r = {};
                    // second argument is coerced to a str, no good for boolean
                    r[self.abscissa] = records[0][self.abscissa];
                    _(records).each(function (record) {
                        var key = _.sprintf('%s_%s',
                            self.ordinate,
                            record[self.group_field].toLowerCase().replace(/\s/g, '_'));
                        r[key] = record[self.ordinate];
                    });
                    return r;
                })
                .value();
        }

        var abscissa_description = {
            title: "" + this.fields[this.abscissa].string + "",
            template: function (obj) {
                return obj[self.abscissa] || 'Undefined';
            }
        };

        var ordinate_description = {
            lines: true,
            title: "" + this.fields[this.ordinate].string + ""
        };

        var x_axis, y_axis;
        if (self.orientation == 'horizontal') {
            x_axis = ordinate_description;
            y_axis = abscissa_description;
        } else {
            x_axis = abscissa_description;
            y_axis = ordinate_description;
        }

        var bar_chart = new dhtmlXChart({
            view: view_chart,
            container: this.element_id+"-barchart",
            value:"#"+group_list[0].group+"#",
            gradient: "3d",
            border: false,
            width: 1024,
            tooltip:{
                template: _.sprintf("#%s#, %s=#%s#",
                    this.abscissa, group_list[0].text, group_list[0].group)
            },
            radius: 0,
            color:group_list[0].color,
            origin:0,
            xAxis: x_axis,
            yAxis: y_axis,
            padding: {
                left: 75
            },
            legend: {
                values: group_list,
                align:"left",
                valign:"top",
                layout: "x",
                marker: {
                    type:"round",
                    width:12
                }
            }
        });
        for (var m = 1; m<group_list.length;m++){
            var column = group_list[m];
            if (column.group === this.group_field) { continue; }
            bar_chart.addSeries({
                value: "#"+column.group+"#",
                tooltip:{
                    template: _.sprintf("#%s#, %s=#%s#",
                        this.abscissa, column.text, column.group)
                },
                color: column.color
            });
        }
        bar_chart.parse(results, "json");
        jQuery("#"+this.element_id+"-barchart").height(jQuery("#"+this.element_id+"-barchart").height()+50);
        bar_chart.attachEvent("onItemClick", function(id) {
            self.open_list_view(bar_chart.get(id));
        });
    },
    schedule_pie: function(result) {
        var self = this;
        var chart =  new dhtmlXChart({
            view:"pie3D",
            container:self.element_id+"-piechart",
            value:"#"+self.ordinate+"#",
            pieInnerText:function(obj) {
                var sum = chart.sum("#"+self.ordinate+"#");
                var val = obj[self.ordinate] / sum * 100 ;
                return val.toFixed(1) + "%";
            },
            tooltip:{
                template:"#"+self.abscissa+"#"+"="+"#"+self.ordinate+"#"
            },
            gradient:"3d",
            height: 20,
            radius: 200,
            legend: {
                width: 300,
                align:"left",
                valign:"top",
                layout: "x",
                marker:{
                    type:"round",
                    width:12
                },
                template:function(obj){
                    return obj[self.abscissa] || 'Undefined';
                }
            }
        });
        chart.parse(result,"json");
        chart.attachEvent("onItemClick", function(id) {
            self.open_list_view(chart.get(id));
        });
    },
    open_list_view : function (id){
        var self = this;
        if($(".dhx_tooltip").is(":visible")) {
            $(".dhx_tooltip").remove('div');
        }
        id = id[this.abscissa];
        if (typeof id == 'object'){
            id = id[0];
        }

        var record_id = this.abscissa;
        var modes = ["list", "form", "graph"];
        var views = [];
        _.each(modes, function(mode) {
            var view = [false, mode];
            if (self.fields.views && self.fields.views[mode]) {
                view.push(self.fields.views[mode]);
            }
            views.push(view);
        });
        this.do_action({
            "res_model" : this.dataset.model,
            "domain" : [[record_id, '=', id], ['id','in',this.dataset.ids]],
            "views" : views,
            "type" : "ir.actions.act_window",
            "auto_search" : true,
            "view_type" : "list",
            "view_mode" : "list"
        });
    },

    do_search: function(domains, contexts, groupbys) {
        var self = this;
        this.rpc('/web/session/eval_domain_and_context', {
            domains: domains,
            contexts: contexts,
            group_by_seq: groupbys
        }, function (results) {
            // TODO: handle non-empty results.group_by with read_group?
            if(!_(results.group_by).isEmpty()){
                self.abscissa = results.group_by[0];
            } else {
                self.abscissa = self.first_field;
            }
            self.dataset.read_slice(self.list_fields(), {
                context: results.context,
                domain: results.domain
            }, $.proxy(self, 'schedule_chart'));
        });
    }
});
};
// vim:et fdc=0 fdl=0:
<|endoftext|>