<commit_before>var jsonWork;
var zoom = 1.8;
var estilo = "label{position:absolute;}";
var proced = new Object();

$( document ).ready(function() {
    logger("ready!");

	$("#desHtml").hide();
	$("#desCSS").hide();

    $("#zoom").val(zoom);
    $("#ghtml").on('click',function(){
    	zoom = $("#zoom").val();
    	$("#divhtml").empty();
    	$.get("xml/" + $("#forma").val()+".xml",function(xml){
    		var json = $.xml2json(xml)
    		jsonWork = json.FormModule;
    		crearHTML(json.FormModule);
    		$("#respjson").text(JSON.stringify(json));
	    });
    	$("#desHtml").show();
		$("#desCSS").show();

	});
	$("#gpkg").on('click',function(){
		$("#divhtml").empty();
		$.get("xml/" + $("#forma").val()+".xml",function(xml){
    		var json = $.xml2json(xml)
    		jsonWork = json.FormModule;
    		extraerUnidades(json.FormModule);
    		$("#respjson").text(JSON.stringify(json));
	    });
	});

});
function logger(msj){
	console.log(msj);
}

function crearHTML(json){
	jsonWork = json;
	//Pinta los atributos Window
	if(!$.isArray(json.Window)){
		pintarVentana(json.Window);
	}else{
		for (var i = 0; i < json.Window.length; i++) {
			pintarVentana(json.Window[i]);

		};
	}

	//Pinta los atributos Canvas
	if(!$.isArray(json.Canvas)){
		pintarCanvas(json.Canvas);
	}else{
		for (var i = 0; i < json.Canvas.length; i++) {
			pintarCanvas(json.Canvas[i]);

		};
	}

	//Pinta los bloques
	if(!$.isArray(json.Block)){
		pintarBloque(json.Block);
	}else{
		for (var i = 0; i < json.Block.length; i++) {
			pintarBloque(json.Block[i]);

		};
	}

	downloadDiv();
}

function pintarVentana(window){
	$( "
",{
		id:"w"+window.Name,
		title:window.Title,
		class:"window"
	}).appendTo("#divhtml");

	estilo += "#w"+window.Name+"{"+
		"width:" + Math.round(zoom * window.Width) + "px;" +
		"height:" + Math.round(zoom * window.Height) + "px;"+
		"}";
}

function pintarCanvas(canvas){
	$( "
",{
		id:"c"+canvas.Name,
		title:canvas.Title,
		class:"canvas"
	}).appendTo("#w" + canvas.WindowName);

	estilo += "#c"+canvas.Name+"{"+
		"width:" + Math.round(zoom * canvas.Width) + "px;" +
		"height:" + Math.round(zoom * canvas.Height) + "px;"+
		"position:relative;"+
		"}";

	leerGraphics(canvas.Graphics,canvas.Name);
}

function leerGraphics(Graphics,canvas){
	//Pinta los atributos Graphics
	if(!$.isArray(Graphics)){
		pintarGraphics(Graphics,canvas);
	}else{
		for (var i = 0; i < Graphics.length; i++) {
			pintarGraphics(Graphics[i],canvas);
		};
	}
	if(Graphics ){
		var contenedora;
		var areaMayor = 0;
		var area = 0;
		for (var i = 0; i <Graphics.length;i++) {
			area = (Graphics[i].Width - Graphics[i].XPosition) *(Graphics[i].Height - Graphics[i].YPosition);
			if(area > areaMayor){
				areaMayor = area;
				contenedora = Graphics[i].Name+"_"+canvas;
			}
		}
		$("#"+contenedora).removeClass("bloque");
		$("#"+contenedora).addClass("mayor");
	}

}

function pintarGraphics(graphics,canvas){
	try{
		if(graphics.Width > 0){

			var grp = $( "
",{
				id:graphics.Name+"_"+canvas,
				class:"graphics"
			}).appendTo("#c" + canvas);

			estilo += "#"+graphics.Name+"_"+canvas+"{"+
				"width:" + Math.round(zoom * graphics.Width) + "px;" +
				"height:" + Math.round(zoom * graphics.Height) + "px;"+
				"position:absolute;"+
				"Top:" + Math.round(zoom * graphics.YPosition) + "px;" +
				"Left:" + Math.round(zoom * graphics.XPosition) + "px;"+
				"color: " + graphics.GraphicsFontColor + ";"+
				"}";

			if(graphics.GraphicsText){
				var texto = graphics.GraphicsText;
				texto = texto.replace("
","");
				grp.text(texto);
			}

			if(graphics.CornerRadiusY > 0){
				grp.css('border-radius',graphics.CornerRadiusY + "px")	;
				grp.css('background-color',"#A6F6F6");
				grp.css('border',"1px solid white");
				grp.addClass('bloque');
			}
		}
	}catch(e){
		logger("Error en graphics canvas:"+canvas);
	}
}

function pintarBloque(block){

	if(!$.isArray(block.Item)){
		pintarItems(block.Item,block.Name);
	}else{
		for (var i = 0; i < block.Item.length; i++) {
			pintarItems(block.Item[i],block);

		};
	}
}

function pintarItems(item,block){
	try{

		var divCont =$('
',{
			id:"d"+block.Name+"_"+item.Name
		});

		try{
			var label = $("",{
				id:"l"+block.Name+"_"+item.Name,
				'for':block.Name+"_"+item.Name,
				text:item.Prompt
			}).appendTo(divCont);

			var lTop,lLeft;

		/*	if(item.PromptAttachmentEdge === "Arriba"){
				lTop = (zoom * item.YPosition - 20) + "px;";
				lLeft = zoom * item.XPosition + "px;";
			}else{
				lTop = (zoom * item.YPosition) + "px;";
				lLeft = (zoom * item.XPosition - (item.Prompt.length) * 8) + "px;";
			}
			if(!isNaN(item.YPosition) && !isNaN(item.XPosition) && !isNaN(item.Prompt.length)){
				estilo +="#l"+block.Name+"_"+item.Name+"{"+
					"position:absolute;" +
					"Top:" + lTop   +
					"Left:" + lLeft +
					"}";
			}*/

		}catch(e){
			logger(item.Name + " error en label")
			item.Prompt = "";
		}

		var input = $('<input/>',{
			id:block.Name+"_"+item.Name,
			placeholder:item.Prompt,
			title:item.Prompt
		}).appendTo(divCont);

		if(!isNaN(item.Width) && !isNaN(item.Height) && !isNaN(item.YPosition) && !isNaN(item.XPosition)){
			var lTop = Math.round(zoom * item.YPosition);
			var lLeft = Math.round(zoom * item.XPosition);
			var contenedor, contName;
			var divGraph = $("#c"+item.CanvasName);

			try{
				salida:
				for(var i=0; i < jsonWork.Canvas.length;i++){
					if(jsonWork.Canvas[i].Graphics && jsonWork.Canvas[i].Name === item.CanvasName){
						if($.isArray(jsonWork.Canvas[i].Graphics)){
							for (var j = 0; j < jsonWork.Canvas[i].Graphics.length; j++) {
								contenedor = jsonWork.Canvas[i].Graphics[j];
								contName = contenedor.Name + "_" + jsonWork.Canvas[i].Name;
								if($('#'+contName).hasClass('bloque') ){
									var pos = {"top": Math.round(zoom*contenedor.YPosition), "left": Math.round(zoom*contenedor.XPosition),
										"width": Math.round(zoom*contenedor.Width), "height": Math.round(zoom*contenedor.Height)};
									var it = {"top": Math.round(zoom * item.YPosition),"left": Math.round(zoom * item.XPosition)};

									if((pos.left < it.left && it.left < pos.width + pos.left ) && (pos.top < it.top && it.top < pos.height + pos.top)){
										var msj = block.Name+"_"+item.Name + " dentro " + contenedor.Name ;
										lTop = it.top - pos.top;
										lLeft = it.left - pos.left
										divGraph = $('#'+contenedor.Name+"_"+item.CanvasName);
										break salida;
									}
								}//Cierre validacion si es bloque
							}//Cierre for interno
						}else{//Cierre Graphics es array
							divGraph = $('#'+jsonWork.Canvas[i].Graphics.Name+"_"+item.CanvasName);
						}//Cierre si Graphics es objeto
					}//Cierre canvas.graphics esta definido y nombre canvas es el canvas del item
				}//Cierre for Externo
				divCont.appendTo(divGraph);
			}catch(e){
				logger("Error mirando contenedores" + JSON.stringify(contenedor)+e);
			}

			estilo +="#"+block.Name+"_"+item.Name+"{"+
				"width:" + Math.round(zoom * item.Width) + "px;" +
				"height:" + Math.round(zoom * item.Height) + "px;" +
				"position:absolute;" +
				"Top:" + lTop + "px;" +
				"Left:" + lLeft + "px;"+
				"}";

			if(item.Prompt){
				if(item.PromptAttachmentEdge === "Arriba"){
					lTop = Math.round(lTop - 20) + "px;";
					lLeft = lLeft + "px;";
				}else{
					lTop = lTop + "px;";
					lLeft = Math.round(lLeft - (item.Prompt.length) * 8) + "px;";
				}
				if(!isNaN(item.YPosition) && !isNaN(item.XPosition) && !isNaN(item.Prompt.length)){
					estilo +="#l"+block.Name+"_"+item.Name+"{"+
						"position:absolute;" +
						"Top:" + lTop   +
						"Left:" + lLeft +
						"}";
				}
			}

		}

		if(item.ItemType === "Botón"){
			input.attr('type','button');
			input.attr('value',item.Label);
			input.attr('class','btn btn-primary');
		}else if(item.ItemType === "Elemento de Gráfico"){
			input.attr('type','checkbox');
		}else{
			input.attr('type','text');
			input.attr('class','form-control');
		}

	}catch(e){
		logger("Error con el item " + item.Name)
	}
}

//Agregado para descargar
function downloadDiv() {

	var contenido = '<!DOCTYPE html>'+
	'<meta charset="UTF-8"/> '+
	'' +
	'' +
	'<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">' +
	'<link rel="stylesheet" type="text/css" href="css/roboto.css">' +
	'<link rel="stylesheet" type="text/css" href="css/font-awesome.min.css">' +
	'<link rel="stylesheet" type="text/css" href="css/'+ $("#forma").val() +'.css"/>' +
	'' +
	$("#divhtml").html() + '';

	$("#desHtml").attr({
		download:$("#forma").val()+".html",
		href:'data:text/html;charset=utf-8,' + encodeURIComponent(contenido)
	});

	$("#desCSS").attr({
		download:$("#forma").val()+".css",
		href:'data:text/css;charset=utf-8,' + encodeURIComponent(estilo)
	});
}

/*---------------------------------------------------------------
* Arranque con la logica XD
*---------------------------------------------------------------*/
function extraerUnidades(json){
	var texto = "";
	var jst = "";
	for(var i = 0; i < json.ProgramUnit.length ; i++  ){
	    if(json.ProgramUnit[i]){
	    	var nombre = json.ProgramUnit[i].Name;
	    	var procedimiento = json.ProgramUnit[i].ProgramUnitText.replace('
','\n','ig');

	        proced[nombre] = procedimiento;

	        texto += "----------------------------\n" + procedimiento + "\n\n";
	        jst += "----------------------------\n" + traducirProc(procedimiento) + "\n\n";
	    }
	}

	$('<textarea/>',{
		text:texto,
		cols:100,
		rows:20
	}).appendTo("#divhtml");
	$('<textarea/>',{
		text:jst,
		cols:100,
		rows:20
	}).appendTo("#divhtml");
}

function traducirProc(proc){
	var traducido ="";
	proc = proc.replace('PROCEDURE','function');
	proc = proc.replace('IS','(/*');
	proc = proc.replace('BEGIN','*/){');
	proc = proc.replace("null;","return 0;")
	proc = proc.replace("END;","};")
	proc = reemplazarGoItem(proc);
	traducido = proc;

	return traducido;
}

function buscarLinea(proce,inicio,finalL){
	var linea = "";

	var posI = proce.search(inicio);
	var lAux = proce.substr(posI,proce.length);
	var posF = lAux.search(finalL);

	linea = proce.substr(posI, posF);
	return linea;
}

function reemplazarGoItem(proce){

	var linea = buscarLinea(proce,'go_item',';')
	var lAux = linea.toUpperCase();

	lAux = lAux.replace('.','_');
	lAux = lAux.replace("GO_ITEM('",'$("#');
	lAux = lAux.replace("')",'").focus()');
	proce = proce.replace(linea,lAux);

	return proce;
}<commit_msg>cambios<commit_after>var jsonWork;
var zoom = 1.8;
var estilo = "label{position:absolute;}";
var proced = new Object();

$( document ).ready(function() {
    logger("ready!");

	$("#desHtml").hide();
	$("#desCSS").hide();

    $("#zoom").val(zoom);
    $("#ghtml").on('click',function(){
    	zoom = $("#zoom").val();
    	$("#divhtml").empty();
    	$.get("xml/" + $("#forma").val()+".xml",function(xml){
    		var json = $.xml2json(xml)
    		jsonWork = json.FormModule;
    		crearHTML(json.FormModule);
    		$("#respjson").text(JSON.stringify(json));
	    });
    	$("#desHtml").show();
		$("#desCSS").show();

	});
	$("#gpkg").on('click',function(){
		$("#divhtml").empty();
		$.get("xml/" + $("#forma").val()+".xml",function(xml){
    		var json = $.xml2json(xml)
    		jsonWork = json.FormModule;
    		extraerUnidades(json.FormModule);
    		$("#respjson").text(JSON.stringify(json));
	    });
	});

});
function logger(msj){
	console.log(msj);
}

function crearHTML(json){
	jsonWork = json;
	//Pinta los atributos Window
	if(!$.isArray(json.Window)){
		pintarVentana(json.Window);
	}else{
		for (var i = 0; i < json.Window.length; i++) {
			pintarVentana(json.Window[i]);

		};
	}

	//Pinta los atributos Canvas
	if(!$.isArray(json.Canvas)){
		pintarCanvas(json.Canvas);
	}else{
		for (var i = 0; i < json.Canvas.length; i++) {
			pintarCanvas(json.Canvas[i]);

		};
	}

	//Pinta los bloques
	if(!$.isArray(json.Block)){
		pintarBloque(json.Block);
	}else{
		for (var i = 0; i < json.Block.length; i++) {
			pintarBloque(json.Block[i]);

		};
	}

	downloadDiv();
}

function pintarVentana(window){
	$( "
",{
		id:"w"+window.Name,
		title:window.Title,
		class:"window"
	}).appendTo("#divhtml");

	estilo += "#w"+window.Name+"{"+
		"width:" + Math.round(zoom * window.Width) + "px;" +
		"height:" + Math.round(zoom * window.Height) + "px;"+
		"}";
}

function pintarCanvas(canvas){
	$( "
",{
		id:"c"+canvas.Name,
		title:canvas.Title,
		class:"canvas"
	}).appendTo("#w" + canvas.WindowName);

	estilo += "#c"+canvas.Name+"{"+
		"width:" + Math.round(zoom * canvas.Width) + "px;" +
		"height:" + Math.round(zoom * canvas.Height) + "px;"+
		"position:relative;"+
		"}";

	leerGraphics(canvas.Graphics,canvas.Name);
}

function leerGraphics(Graphics,canvas){
	//Pinta los atributos Graphics
	if(!$.isArray(Graphics)){
		pintarGraphics(Graphics,canvas);
	}else{
		for (var i = 0; i < Graphics.length; i++) {
			pintarGraphics(Graphics[i],canvas);
		};
	}
	if(Graphics ){
		var contenedora;
		var areaMayor = 0;
		var area = 0;
		for (var i = 0; i <Graphics.length;i++) {
			area = (Graphics[i].Width - Graphics[i].XPosition) *(Graphics[i].Height - Graphics[i].YPosition);
			if(area > areaMayor){
				areaMayor = area;
				contenedora = Graphics[i].Name+"_"+canvas;
			}
		}
		$("#"+contenedora).removeClass("bloque");
		$("#"+contenedora).addClass("mayor");
	}

}

function pintarGraphics(graphics,canvas){
	try{
		if(graphics.Width > 0){

			var grp = $( "
",{
				id:graphics.Name+"_"+canvas,
				class:"graphics"
			}).appendTo("#c" + canvas);

			estilo += "#"+graphics.Name+"_"+canvas+"{"+
				"width:" + Math.round(zoom * graphics.Width) + "px;" +
				"height:" + Math.round(zoom * graphics.Height) + "px;"+
				"position:absolute;"+
				"Top:" + Math.round(zoom * graphics.YPosition) + "px;" +
				"Left:" + Math.round(zoom * graphics.XPosition) + "px;"+
				"color: " + graphics.GraphicsFontColor + ";"+
				"}";

			if(graphics.GraphicsText){
				var texto = graphics.GraphicsText;
				texto = texto.replace("
","");
				grp.text(texto);
			}

			if(graphics.CornerRadiusY > 0){
				grp.css('border-radius',graphics.CornerRadiusY + "px")	;
				grp.css('background-color',"#A6F6F6");
				grp.css('border',"1px solid white");
				grp.addClass('bloque');
			}
		}
	}catch(e){
		logger("Error en graphics canvas:"+canvas);
	}
}

function pintarBloque(block){

	if(!$.isArray(block.Item)){
		pintarItems(block.Item,block.Name);
	}else{
		for (var i = 0; i < block.Item.length; i++) {
			pintarItems(block.Item[i],block);
		};
	}
}

function pintarItems(item,block){
	try{

		var divCont =$('
',{
			id:"d"+block.Name+"_"+item.Name
		});

		try{
			var label = $("",{
				id:"l"+block.Name+"_"+item.Name,
				'for':block.Name+"_"+item.Name,
				text:item.Prompt.replace('
','\n')
			}).appendTo(divCont);

			var lTop,lLeft;

		}catch(e){
			logger(item.Name + " error en label")
			item.Prompt = "";
		}

		var input = $('<input/>',{
			id:block.Name+"_"+item.Name,
			placeholder:item.Prompt.replace('
',' '),
			title:item.Prompt.replace('
',' ')
		}).appendTo(divCont);

		if(!isNaN(item.Width) && !isNaN(item.Height) && !isNaN(item.YPosition) && !isNaN(item.XPosition)){
			var lTop = Math.round(zoom * item.YPosition);
			var lLeft = Math.round(zoom * item.XPosition);
			var contenedor, contName;
			var divGraph = $("#c"+item.CanvasName);

			try{
				salida:
				for(var i=0; i < jsonWork.Canvas.length;i++){
					if(jsonWork.Canvas[i].Graphics && jsonWork.Canvas[i].Name === item.CanvasName){
						if($.isArray(jsonWork.Canvas[i].Graphics)){
							for (var j = 0; j < jsonWork.Canvas[i].Graphics.length; j++) {
								contenedor = jsonWork.Canvas[i].Graphics[j];
								contName = contenedor.Name + "_" + jsonWork.Canvas[i].Name;
								if($('#'+contName).hasClass('bloque') ){
									var pos = {"top": Math.round(zoom*contenedor.YPosition), "left": Math.round(zoom*contenedor.XPosition),
										"width": Math.round(zoom*contenedor.Width), "height": Math.round(zoom*contenedor.Height)};
									var it = {"top": Math.round(zoom * item.YPosition),"left": Math.round(zoom * item.XPosition)};

									if((pos.left < it.left && it.left < pos.width + pos.left ) && (pos.top < it.top && it.top < pos.height + pos.top)){
										var msj = block.Name+"_"+item.Name + " dentro " + contenedor.Name ;
										lTop = it.top - pos.top;
										lLeft = it.left - pos.left
										divGraph = $('#'+contenedor.Name+"_"+item.CanvasName);
										break salida;
									}
								}//Cierre validacion si es bloque
							}//Cierre for interno
						}else{//Cierre Graphics es array
							divGraph = $('#'+jsonWork.Canvas[i].Graphics.Name+"_"+item.CanvasName);
						}//Cierre si Graphics es objeto
					}//Cierre canvas.graphics esta definido y nombre canvas es el canvas del item
				}//Cierre for Externo
				divCont.appendTo(divGraph);
			}catch(e){
				logger("Error mirando contenedores" + JSON.stringify(contenedor)+e);
			}

			estilo +="#"+block.Name+"_"+item.Name+"{"+
				"width:" + Math.round(zoom * item.Width) + "px;" +
				"height:" + Math.round(zoom * item.Height) + "px;" +
				"position:absolute;" +
				"Top:" + lTop + "px;" +
				"Left:" + lLeft + "px;"+
				"}";

			if(item.Prompt){
				if(item.PromptAttachmentEdge === "Arriba"){
					lTop = Math.round(lTop - 20) + "px;";
					lLeft = lLeft + "px;";
				}else{
					lTop = lTop + "px;";
					lLeft = Math.round(lLeft - (item.Prompt.length) * 8) + "px;";
				}
				if(!isNaN(item.YPosition) && !isNaN(item.XPosition) && !isNaN(item.Prompt.length)){
					estilo +="#l"+block.Name+"_"+item.Name+"{"+
						"position:absolute;" +
						"Top:" + lTop   +
						"Left:" + lLeft +
						"}";
				}
			}

		}

		if(item.ItemType === "Botón"){
			input.attr('type','button');
			input.attr('value',item.Label);
			input.attr('class','btn btn-primary');
		}else if(item.ItemType === "Elemento de Gráfico"){
			input.attr('type','checkbox');
		}else{
			input.attr('type','text');
			input.attr('class','form-control');
		}

	}catch(e){
		logger("Error con el item " + item.Name)
	}
}

//Agregado para descargar
function downloadDiv() {

	var contenido = '<!DOCTYPE html>'+
	'<meta charset="UTF-8"/> '+
	'' +
	'' +
	'<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">' +
	'<link rel="stylesheet" type="text/css" href="css/roboto.css">' +
	'<link rel="stylesheet" type="text/css" href="css/font-awesome.min.css">' +
	'<link rel="stylesheet" type="text/css" href="css/'+ $("#forma").val() +'.css"/>' +
	'' +
	$("#divhtml").html() + '';

	$("#desHtml").attr({
		download:$("#forma").val()+".html",
		href:'data:text/html;charset=utf-8,' + encodeURIComponent(contenido)
	});

	$("#desCSS").attr({
		download:$("#forma").val()+".css",
		href:'data:text/css;charset=utf-8,' + encodeURIComponent(estilo)
	});
}

/*---------------------------------------------------------------
* Arranque con la logica XD
*---------------------------------------------------------------*/
function extraerUnidades(json){
	var texto = "";
	var jst = "";
	for(var i = 0; i < json.ProgramUnit.length ; i++  ){
	    if(json.ProgramUnit[i]){
	    	var nombre = json.ProgramUnit[i].Name;
	    	var procedimiento = json.ProgramUnit[i].ProgramUnitText.replace('
','\n','ig');

	        proced[nombre] = procedimiento;

	        texto += "----------------------------\n" + procedimiento + "\n\n";
	        jst += "----------------------------\n" + traducirProc(procedimiento) + "\n\n";
	    }
	}

	$('<textarea/>',{
		text:texto,
		cols:100,
		rows:20
	}).appendTo("#divhtml");
	$('<textarea/>',{
		text:jst,
		cols:100,
		rows:20
	}).appendTo("#divhtml");
}

function traducirProc(proc){
	var traducido ="";
	proc = proc.replace('PROCEDURE','function');
	proc = proc.replace('IS','(/*');
	proc = proc.replace('BEGIN','*/){');
	proc = proc.replace("null;","return 0;")
	proc = proc.replace("END;","};")
	proc = reemplazarGoItem(proc);
	traducido = proc;

	return traducido;
}

function buscarLinea(proce,inicio,finalL){
	var linea = "";

	var posI = proce.search(inicio);
	var lAux = proce.substr(posI,proce.length);
	var posF = lAux.search(finalL);

	linea = proce.substr(posI, posF);
	return linea;
}

function reemplazarGoItem(proce){

	var linea = buscarLinea(proce,'go_item',';')
	var lAux = linea.toUpperCase();

	lAux = lAux.replace('.','_');
	lAux = lAux.replace("GO_ITEM('",'$("#');
	lAux = lAux.replace("')",'").focus()');
	proce = proce.replace(linea,lAux);

	return proce;
}<|endoftext|>


<commit_before>/* ========================================================================
 * Bootstrap: offcanvas.js v3.1.3
 * http://jasny.github.io/bootstrap/javascript/#offcanvas
 * ========================================================================
 * Copyright 2013-2014 Arnold Daniels
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ======================================================================== */

+function ($) { "use strict";

  // OFFCANVAS PUBLIC CLASS DEFINITION
  // =================================

  var OffCanvas = function (element, options) {
    this.$element = $(element)
    this.options  = $.extend({}, OffCanvas.DEFAULTS, options)
    this.state    = null
    this.placement = null

    if (this.options.recalc) {
      this.calcClone()
      $(window).on('resize', $.proxy(this.recalc, this))
    }

    if (this.options.autohide)
      $(document).on('click', $.proxy(this.autohide, this))

    if (this.options.toggle) this.toggle()

    if (this.options.disablescrolling) {
        this.options.disableScrolling = this.options.disablescrolling
        delete this.options.disablescrolling
    }
  }

  OffCanvas.DEFAULTS = {
    toggle: true,
    placement: 'auto',
    autohide: true,
    recalc: true,
    disableScrolling: true
  }

  OffCanvas.prototype.offset = function () {
    switch (this.placement) {
      case 'left':
      case 'right':  return this.$element.outerWidth()
      case 'top':
      case 'bottom': return this.$element.outerHeight()
    }
  }

  OffCanvas.prototype.calcPlacement = function () {
    if (this.options.placement !== 'auto') {
        this.placement = this.options.placement
        return
    }

    if (!this.$element.hasClass('in')) {
      this.$element.css('visiblity', 'hidden !important').addClass('in')
    }

    var horizontal = $(window).width() / this.$element.width()
    var vertical = $(window).height() / this.$element.height()

    var element = this.$element
    function ab(a, b) {
      if (element.css(b) === 'auto') return a
      if (element.css(a) === 'auto') return b

      var size_a = parseInt(element.css(a), 10)
      var size_b = parseInt(element.css(b), 10)

      return size_a > size_b ? b : a
    }

    this.placement = horizontal >= vertical ? ab('left', 'right') : ab('top', 'bottom')

    if (this.$element.css('visibility') === 'hidden !important') {
      this.$element.removeClass('in').css('visiblity', '')
    }
  }

  OffCanvas.prototype.opposite = function (placement) {
    switch (placement) {
      case 'top':    return 'bottom'
      case 'left':   return 'right'
      case 'bottom': return 'top'
      case 'right':  return 'left'
    }
  }

  OffCanvas.prototype.getCanvasElements = function() {
    // Return a set containing the canvas plus all fixed elements
    var canvas = this.options.canvas ? $(this.options.canvas) : this.$element

    var fixed_elements = canvas.find('*').filter(function() {
      return $(this).css('position') === 'fixed'
    }).not(this.options.exclude)

    return canvas.add(fixed_elements)
  }

  OffCanvas.prototype.slide = function (elements, offset, callback) {
    // Use jQuery animation if CSS transitions aren't supported
    if (!$.support.transition) {
      var anim = {}
      anim[this.placement] = "+=" + offset
      return elements.animate(anim, 350, callback)
    }

    var placement = this.placement
    var opposite = this.opposite(placement)

    elements.each(function() {
      if ($(this).css(placement) !== 'auto')
        $(this).css(placement, (parseInt($(this).css(placement), 10) || 0) + offset)

      if ($(this).css(opposite) !== 'auto')
        $(this).css(opposite, (parseInt($(this).css(opposite), 10) || 0) - offset)
    })

    this.$element
      .one($.support.transition.end, callback)
      .emulateTransitionEnd(350)
  }

  OffCanvas.prototype.disableScrolling = function() {
    var bodyWidth = $('body').width()
    var prop = 'padding-' + this.opposite(this.placement)

    if ($('body').data('offcanvas-style') === undefined) {
      $('body').data('offcanvas-style', $('body').attr('style') || '')
    }

    $('body').css('overflow', 'hidden')

    if ($('body').width() > bodyWidth) {
      var padding = parseInt($('body').css(prop), 10) + $('body').width() - bodyWidth

      setTimeout(function() {
        $('body').css(prop, padding)
      }, 1)
    }
  }

  OffCanvas.prototype.show = function () {
    if (this.state) return

    var startEvent = $.Event('show.bs.offcanvas')
    this.$element.trigger(startEvent)
    if (startEvent.isDefaultPrevented()) return

    this.state = 'slide-in'
    this.calcPlacement();

    var elements = this.getCanvasElements()
    var placement = this.placement
    var opposite = this.opposite(placement)
    var offset = this.offset()

    if (elements.index(this.$element) !== -1) {
      $(this.$element).data('offcanvas-style', $(this.$element).attr('style') || '')
      this.$element.css(placement, -1 * offset)
      this.$element.css(placement); // Workaround: Need to get the CSS property for it to be applied before the next line of code
    }

    elements.addClass('canvas-sliding').each(function() {
      if ($(this).data('offcanvas-style') === undefined) $(this).data('offcanvas-style', $(this).attr('style') || '')
      if ($(this).css('position') === 'static') $(this).css('position', 'relative')
      if (($(this).css(placement) === 'auto' || $(this).css(placement) === '0px') &&
          ($(this).css(opposite) === 'auto' || $(this).css(opposite) === '0px')) {
        $(this).css(placement, 0)
      }
    })

    if (this.options.disableScrolling) this.disableScrolling()

    var complete = function () {
      if (this.state != 'slide-in') return

      this.state = 'slid'

      elements.removeClass('canvas-sliding').addClass('canvas-slid')
      this.$element.trigger('shown.bs.offcanvas')
    }

    setTimeout($.proxy(function() {
      this.$element.addClass('in')
      this.slide(elements, offset, $.proxy(complete, this))
    }, this), 1)
  }

  OffCanvas.prototype.hide = function (fast) {
    if (this.state !== 'slid') return

    var startEvent = $.Event('hide.bs.offcanvas')
    this.$element.trigger(startEvent)
    if (startEvent.isDefaultPrevented()) return

    this.state = 'slide-out'

    var elements = $('.canvas-slid')
    var placement = this.placement
    var offset = -1 * this.offset()

    var complete = function () {
      if (this.state != 'slide-out') return

      this.state = null
      this.placement = null

      this.$element.removeClass('in')

      elements.removeClass('canvas-sliding')
      elements.add(this.$element).add('body').each(function() {
        $(this).attr('style', $(this).data('offcanvas-style')).removeData('offcanvas-style')
      })

      this.$element.trigger('hidden.bs.offcanvas')
    }

    elements.removeClass('canvas-slid').addClass('canvas-sliding')

    setTimeout($.proxy(function() {
      this.slide(elements, offset, $.proxy(complete, this))
    }, this), 1)
  }

  OffCanvas.prototype.toggle = function () {
    if (this.state === 'slide-in' || this.state === 'slide-out') return
    this[this.state === 'slid' ? 'hide' : 'show']()
  }

  OffCanvas.prototype.calcClone = function() {
    this.$calcClone = this.$element.clone()
      .html('')
      .addClass('offcanvas-clone').removeClass('in')
      .appendTo($('body'))
  }

  OffCanvas.prototype.recalc = function () {
    if (this.$calcClone.css('display') === 'none' || (this.state !== 'slid' && this.state !== 'slide-in')) return

    this.state = null
    this.placement = null
    var elements = this.getCanvasElements()

    this.$element.removeClass('in')

    elements.removeClass('canvas-slid')
    elements.add(this.$element).add('body').each(function() {
      $(this).attr('style', $(this).data('offcanvas-style')).removeData('offcanvas-style')
    })
  }

  OffCanvas.prototype.autohide = function (e) {
    if ($(e.target).closest(this.$element).length === 0) this.hide()
  }

  // OFFCANVAS PLUGIN DEFINITION
  // ==========================

  var old = $.fn.offcanvas

  $.fn.offcanvas = function (option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.offcanvas')
      var options = $.extend({}, OffCanvas.DEFAULTS, $this.data(), typeof option === 'object' && option)

      if (!data) $this.data('bs.offcanvas', (data = new OffCanvas(this, options)))
      if (typeof option === 'string') data[option]()
    })
  }

  $.fn.offcanvas.Constructor = OffCanvas

  // OFFCANVAS NO CONFLICT
  // ====================

  $.fn.offcanvas.noConflict = function () {
    $.fn.offcanvas = old
    return this
  }

  // OFFCANVAS DATA-API
  // =================

  $(document).on('click.bs.offcanvas.data-api', '[data-toggle=offcanvas]', function (e) {
    var $this   = $(this), href
    var target  = $this.attr('data-target')
        || e.preventDefault()
        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
    var $canvas = $(target)
    var data    = $canvas.data('bs.offcanvas')
    var option  = data ? 'toggle' : $this.data()

    e.stopPropagation()

    if (data) data.toggle()
      else $canvas.offcanvas(option)
  })

}(window.jQuery);
<commit_msg>Always add padding to the right for body offcanvas.disableScrolling <commit_after>/* ========================================================================
 * Bootstrap: offcanvas.js v3.1.3
 * http://jasny.github.io/bootstrap/javascript/#offcanvas
 * ========================================================================
 * Copyright 2013-2014 Arnold Daniels
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ======================================================================== */

+function ($) { "use strict";

  // OFFCANVAS PUBLIC CLASS DEFINITION
  // =================================

  var OffCanvas = function (element, options) {
    this.$element = $(element)
    this.options  = $.extend({}, OffCanvas.DEFAULTS, options)
    this.state    = null
    this.placement = null

    if (this.options.recalc) {
      this.calcClone()
      $(window).on('resize', $.proxy(this.recalc, this))
    }

    if (this.options.autohide)
      $(document).on('click', $.proxy(this.autohide, this))

    if (this.options.toggle) this.toggle()

    if (this.options.disablescrolling) {
        this.options.disableScrolling = this.options.disablescrolling
        delete this.options.disablescrolling
    }
  }

  OffCanvas.DEFAULTS = {
    toggle: true,
    placement: 'auto',
    autohide: true,
    recalc: true,
    disableScrolling: true
  }

  OffCanvas.prototype.offset = function () {
    switch (this.placement) {
      case 'left':
      case 'right':  return this.$element.outerWidth()
      case 'top':
      case 'bottom': return this.$element.outerHeight()
    }
  }

  OffCanvas.prototype.calcPlacement = function () {
    if (this.options.placement !== 'auto') {
        this.placement = this.options.placement
        return
    }

    if (!this.$element.hasClass('in')) {
      this.$element.css('visiblity', 'hidden !important').addClass('in')
    }

    var horizontal = $(window).width() / this.$element.width()
    var vertical = $(window).height() / this.$element.height()

    var element = this.$element
    function ab(a, b) {
      if (element.css(b) === 'auto') return a
      if (element.css(a) === 'auto') return b

      var size_a = parseInt(element.css(a), 10)
      var size_b = parseInt(element.css(b), 10)

      return size_a > size_b ? b : a
    }

    this.placement = horizontal >= vertical ? ab('left', 'right') : ab('top', 'bottom')

    if (this.$element.css('visibility') === 'hidden !important') {
      this.$element.removeClass('in').css('visiblity', '')
    }
  }

  OffCanvas.prototype.opposite = function (placement) {
    switch (placement) {
      case 'top':    return 'bottom'
      case 'left':   return 'right'
      case 'bottom': return 'top'
      case 'right':  return 'left'
    }
  }

  OffCanvas.prototype.getCanvasElements = function() {
    // Return a set containing the canvas plus all fixed elements
    var canvas = this.options.canvas ? $(this.options.canvas) : this.$element

    var fixed_elements = canvas.find('*').filter(function() {
      return $(this).css('position') === 'fixed'
    }).not(this.options.exclude)

    return canvas.add(fixed_elements)
  }

  OffCanvas.prototype.slide = function (elements, offset, callback) {
    // Use jQuery animation if CSS transitions aren't supported
    if (!$.support.transition) {
      var anim = {}
      anim[this.placement] = "+=" + offset
      return elements.animate(anim, 350, callback)
    }

    var placement = this.placement
    var opposite = this.opposite(placement)

    elements.each(function() {
      if ($(this).css(placement) !== 'auto')
        $(this).css(placement, (parseInt($(this).css(placement), 10) || 0) + offset)

      if ($(this).css(opposite) !== 'auto')
        $(this).css(opposite, (parseInt($(this).css(opposite), 10) || 0) - offset)
    })

    this.$element
      .one($.support.transition.end, callback)
      .emulateTransitionEnd(350)
  }

  OffCanvas.prototype.disableScrolling = function() {
    var bodyWidth = $('body').width()
    var prop = 'padding-right'

    if ($('body').data('offcanvas-style') === undefined) {
      $('body').data('offcanvas-style', $('body').attr('style') || '')
    }

    $('body').css('overflow', 'hidden')

    if ($('body').width() > bodyWidth) {
      var padding = parseInt($('body').css(prop), 10) + $('body').width() - bodyWidth

      setTimeout(function() {
        $('body').css(prop, padding)
      }, 1)
    }
  }

  OffCanvas.prototype.show = function () {
    if (this.state) return

    var startEvent = $.Event('show.bs.offcanvas')
    this.$element.trigger(startEvent)
    if (startEvent.isDefaultPrevented()) return

    this.state = 'slide-in'
    this.calcPlacement();

    var elements = this.getCanvasElements()
    var placement = this.placement
    var opposite = this.opposite(placement)
    var offset = this.offset()

    if (elements.index(this.$element) !== -1) {
      $(this.$element).data('offcanvas-style', $(this.$element).attr('style') || '')
      this.$element.css(placement, -1 * offset)
      this.$element.css(placement); // Workaround: Need to get the CSS property for it to be applied before the next line of code
    }

    elements.addClass('canvas-sliding').each(function() {
      if ($(this).data('offcanvas-style') === undefined) $(this).data('offcanvas-style', $(this).attr('style') || '')
      if ($(this).css('position') === 'static') $(this).css('position', 'relative')
      if (($(this).css(placement) === 'auto' || $(this).css(placement) === '0px') &&
          ($(this).css(opposite) === 'auto' || $(this).css(opposite) === '0px')) {
        $(this).css(placement, 0)
      }
    })

    if (this.options.disableScrolling) this.disableScrolling()

    var complete = function () {
      if (this.state != 'slide-in') return

      this.state = 'slid'

      elements.removeClass('canvas-sliding').addClass('canvas-slid')
      this.$element.trigger('shown.bs.offcanvas')
    }

    setTimeout($.proxy(function() {
      this.$element.addClass('in')
      this.slide(elements, offset, $.proxy(complete, this))
    }, this), 1)
  }

  OffCanvas.prototype.hide = function (fast) {
    if (this.state !== 'slid') return

    var startEvent = $.Event('hide.bs.offcanvas')
    this.$element.trigger(startEvent)
    if (startEvent.isDefaultPrevented()) return

    this.state = 'slide-out'

    var elements = $('.canvas-slid')
    var placement = this.placement
    var offset = -1 * this.offset()

    var complete = function () {
      if (this.state != 'slide-out') return

      this.state = null
      this.placement = null

      this.$element.removeClass('in')

      elements.removeClass('canvas-sliding')
      elements.add(this.$element).add('body').each(function() {
        $(this).attr('style', $(this).data('offcanvas-style')).removeData('offcanvas-style')
      })

      this.$element.trigger('hidden.bs.offcanvas')
    }

    elements.removeClass('canvas-slid').addClass('canvas-sliding')

    setTimeout($.proxy(function() {
      this.slide(elements, offset, $.proxy(complete, this))
    }, this), 1)
  }

  OffCanvas.prototype.toggle = function () {
    if (this.state === 'slide-in' || this.state === 'slide-out') return
    this[this.state === 'slid' ? 'hide' : 'show']()
  }

  OffCanvas.prototype.calcClone = function() {
    this.$calcClone = this.$element.clone()
      .html('')
      .addClass('offcanvas-clone').removeClass('in')
      .appendTo($('body'))
  }

  OffCanvas.prototype.recalc = function () {
    if (this.$calcClone.css('display') === 'none' || (this.state !== 'slid' && this.state !== 'slide-in')) return

    this.state = null
    this.placement = null
    var elements = this.getCanvasElements()

    this.$element.removeClass('in')

    elements.removeClass('canvas-slid')
    elements.add(this.$element).add('body').each(function() {
      $(this).attr('style', $(this).data('offcanvas-style')).removeData('offcanvas-style')
    })
  }

  OffCanvas.prototype.autohide = function (e) {
    if ($(e.target).closest(this.$element).length === 0) this.hide()
  }

  // OFFCANVAS PLUGIN DEFINITION
  // ==========================

  var old = $.fn.offcanvas

  $.fn.offcanvas = function (option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.offcanvas')
      var options = $.extend({}, OffCanvas.DEFAULTS, $this.data(), typeof option === 'object' && option)

      if (!data) $this.data('bs.offcanvas', (data = new OffCanvas(this, options)))
      if (typeof option === 'string') data[option]()
    })
  }

  $.fn.offcanvas.Constructor = OffCanvas

  // OFFCANVAS NO CONFLICT
  // ====================

  $.fn.offcanvas.noConflict = function () {
    $.fn.offcanvas = old
    return this
  }

  // OFFCANVAS DATA-API
  // =================

  $(document).on('click.bs.offcanvas.data-api', '[data-toggle=offcanvas]', function (e) {
    var $this   = $(this), href
    var target  = $this.attr('data-target')
        || e.preventDefault()
        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
    var $canvas = $(target)
    var data    = $canvas.data('bs.offcanvas')
    var option  = data ? 'toggle' : $this.data()

    e.stopPropagation()

    if (data) data.toggle()
      else $canvas.offcanvas(option)
  })

}(window.jQuery);
<|endoftext|>


<commit_before>/*
 * Spotmop
 * By James Barnsley (http://jamesbarnsley.co.nz
 *
 * Playlists
 *
 */

$(document).ready( function(evt){

	setupNewPlaylistButton();
	setupRefreshPlaylistButton();

    var readyToLoad = true;

    // listen for scroll to bottom, then we can load the additional tracks
    $('#pages').scroll(function( evt ){
        if( readyToLoad && ( $('#pages').scrollTop() + 40 >= ( $('#pages > .liner').innerHeight() - $('#pages').innerHeight() ) ) ){

            // first, check the token
            $.when( checkToken() ).done( function(){

                // if the next link is not null (like it would be if there were no more tracks)
                if( coreArray['playlistNextTracksLink'] ){

                    readyToLoad = false;

                    updateLoader('start');

                    getFromSpotify( coreArray['playlistNextTracksLink'] ).success(function( tracks ){
                        updateLoader('stop');
                        renderTracksTable( $('.page#playlist .tracks'), tracks.items, null, null, true );
                        coreArray['playlistNextTracksLink'] = tracks.next;
                        readyToLoad = true;
                    });

                }

            });
        }
    });

});

/*
 * Setup functionality on the 'new playlist' button
 * Fires request to API and injects result into sidebar
*/

function setupNewPlaylistButton(){

	$('.new-playlist-button').on('click', function(evt){

		var playlistName = prompt("New playlist name");

		if( playlistName != null ){

			createPlaylist( playlistName ).success( function( playlist ) {

				var lists = $('.menu-item-wrapper.playlists .playlist-list');
				coreArray['playlists'] += playlist;

				lists.prepend('
'+playlist.name+'
');

				// draggable to drop them onto playlists
				$(document).find('#menu .playlist-list .playlist-item[data-uri="'+playlist.uri+'"]').droppable({
					drop: function(evt, ui){
						addTrackToPlaylist( getIdFromUri( $(evt.target).data('uri') ), $(ui.helper).data('uri') ).success(function(evt){
							notifyUser('good','Track added to playlist');
						});
					}
				});
			});
		};

	});

};

/*
 * Setup functionality on the 'refresh playlist' button
 * Retries command to fetch from spotify api
*/

function setupRefreshPlaylistButton(){

	$(document).on('click', '.refresh-playlist-button', function(evt){
		updatePlaylists();
	});

};

/*
 * Update the playlists list on the sidebar
 * Also updates coreArray reference
*/

function updatePlaylists(){

	// Get the users playlists and place them in the client
	if( checkToken() ){

		// get "my" playlists from Spotify (excludes collaborative due to API limitations)
		updateLoader('start');
		getMyPlaylists().success( function( playlists ){

			var lists = $('.menu-item-wrapper.playlists .playlist-list');
			localStorage.playlists = JSON.stringify( playlists.items );

			// clear out the previous playlists
			lists.html('');
			updateLoader('stop');

			// loop each playlist
			for( var i = 0; i < playlists.items.length; i++ ){

				var playlist = playlists.items[i];
				var playlistButtonHTML = '
'+playlist.name+'
';

				// add list to the playlists bar
                if( playlist.owner['id'] == localStorage.userID ){
				    lists.filter('.owned').append(playlistButtonHTML);
                }else{
				    lists.filter('.following').append(playlistButtonHTML);
                }
			}

		    // let's now load the custom playlists
		    // TODO: This is under development and isn't fully supported by Spotify API
		    /*
			if( typeof( localStorage.customPlaylists ) !== 'undefined' && localStorage.customPlaylists !== 'null' ){

				var lists = $('.menu-item-wrapper.playlists .playlist-list');
				var customPlaylists = JSON.parse(localStorage.customPlaylists);

				$.each( customPlaylists, function(key, playlist){

					// add list to the playlists bar
					lists.append('
'+playlist.name+'
');
				});

			}
			*/

		}).fail( function( response ){

			updateLoader('stop');
        	notifyUser('error', 'Error fetching playlists: '+response.responseJSON.error.message);
        	$('#menu .playlist-list').html('

');

	    });

	};

};

/*
 * Add an existing playlist
 * Spotify API doesn't permit collaborative playlists, so this is a workaround
 * Uses localStorage to retain playlist info
 *
 * CURRENTLY IN DEVELOPMENT, POSSIBLY WON'T WORK WITH SPOTIFY API
function AddCustomPlaylist( userID, playlistID ){

	updateLoader('start');

	// go get the playlist as a spotify object
	getPlaylist( userID, playlistID )
		.success( function( response ){
			updateLoader('stop');

			var customPlaylists = [];

			if( typeof( localStorage.customPlaylists ) !== 'undefined' && localStorage.customPlaylists !== 'null' ){
				customPlaylists = JSON.parse(localStorage.customPlaylists);
			}

			customPlaylists.push( response );

			localStorage.customPlaylists = JSON.stringify(customPlaylists);

        	notifyUser('good', 'Custom playlist added');
		})
		.fail( function( response ){
			updateLoader('stop');
        	notifyUser('error', 'Error adding custom playlist: '+response.responseJSON.error.message);
		});

};
*/

<commit_msg>Implementing base level custom playlists<commit_after>/*
 * Spotmop
 * By James Barnsley (http://jamesbarnsley.co.nz
 *
 * Playlists
 *
 */

$(document).ready( function(evt){

	setupNewPlaylistButton();
	setupRefreshPlaylistButton();

    var readyToLoad = true;

    // listen for scroll to bottom, then we can load the additional tracks
    $('#pages').scroll(function( evt ){
        if( readyToLoad && ( $('#pages').scrollTop() + 40 >= ( $('#pages > .liner').innerHeight() - $('#pages').innerHeight() ) ) ){

            // first, check the token
            $.when( checkToken() ).done( function(){

                // if the next link is not null (like it would be if there were no more tracks)
                if( coreArray['playlistNextTracksLink'] ){

                    readyToLoad = false;

                    updateLoader('start');

                    getFromSpotify( coreArray['playlistNextTracksLink'] ).success(function( tracks ){
                        updateLoader('stop');
                        renderTracksTable( $('.page#playlist .tracks'), tracks.items, null, null, true );
                        coreArray['playlistNextTracksLink'] = tracks.next;
                        readyToLoad = true;
                    });

                }

            });
        }
    });

});

/*
 * Setup functionality on the 'new playlist' button
 * Fires request to API and injects result into sidebar
*/

function setupNewPlaylistButton(){

	$('.new-playlist-button').on('click', function(evt){

		var playlistName = prompt("New playlist name");

		if( playlistName != null ){

			createPlaylist( playlistName ).success( function( playlist ) {

				var lists = $('.menu-item-wrapper.playlists .playlist-list');
				coreArray['playlists'] += playlist;

				lists.prepend('
'+playlist.name+'
');

				// draggable to drop them onto playlists
				$(document).find('#menu .playlist-list .playlist-item[data-uri="'+playlist.uri+'"]').droppable({
					drop: function(evt, ui){
						addTrackToPlaylist( getIdFromUri( $(evt.target).data('uri') ), $(ui.helper).data('uri') ).success(function(evt){
							notifyUser('good','Track added to playlist');
						});
					}
				});
			});
		};

	});

};

/*
 * Setup functionality on the 'refresh playlist' button
 * Retries command to fetch from spotify api
*/

function setupRefreshPlaylistButton(){

	$(document).on('click', '.refresh-playlist-button', function(evt){
		updatePlaylists();
	});

};

/*
 * Update the playlists list on the sidebar
 * Also updates coreArray reference
*/

function updatePlaylists(){

	// Get the users playlists and place them in the client
	if( checkToken() ){

		// get "my" playlists from Spotify (excludes collaborative due to API limitations)
		updateLoader('start');
		getMyPlaylists().success( function( playlists ){

			var lists = $('.menu-item-wrapper.playlists .playlist-list');
			localStorage.playlists = JSON.stringify( playlists.items );

			// clear out the previous playlists
			lists.html('');
			updateLoader('stop');

			// loop each playlist
			for( var i = 0; i < playlists.items.length; i++ ){

				var playlist = playlists.items[i];
				var playlistButtonHTML = '
'+playlist.name+'
';

				// add list to the playlists bar
                if( playlist.owner['id'] == localStorage.userID ){
				    lists.filter('.owned').append(playlistButtonHTML);
                }else{
				    lists.filter('.following').append(playlistButtonHTML);
                }
			}

		    // let's now load the custom playlists
		    // TODO: This is under development and isn't fully supported by Spotify API
			if( typeof( localStorage.customPlaylists ) !== 'undefined' && localStorage.customPlaylists !== 'null' ){

				var lists = $('.menu-item-wrapper.playlists .playlist-list.owned');
				var customPlaylists = JSON.parse(localStorage.customPlaylists);

				$.each( customPlaylists, function(key, playlist){
					console.log(playlist);
					// add list to the playlists bar
					lists.append('
'+playlist.name+'
');
				});

			}

		}).fail( function( response ){

			updateLoader('stop');
        	notifyUser('error', 'Error fetching playlists: '+response.responseJSON.error.message);
        	$('#menu .playlist-list').html('

');

	    });

	};

};

/**
 * Add an existing playlist
 * Spotify API doesn't permit fetching of collaborative playlists, so this is a workaround
 * Uses localStorage to retain playlist info
 *
 * CURRENTLY IN DEVELOPMENT, POSSIBLY WON'T WORK WITH SPOTIFY API
 **/
function AddCustomPlaylist( userID, playlistID ){

	updateLoader('start');

	// go get the playlist as a spotify object
	getPlaylist( userID, playlistID )
		.success( function( response ){
			updateLoader('stop');

			var customPlaylists = [];

			if( typeof( localStorage.customPlaylists ) !== 'undefined' && localStorage.customPlaylists !== 'null' ){
				customPlaylists = JSON.parse(localStorage.customPlaylists);
			}

			customPlaylists.push( response );

			localStorage.customPlaylists = JSON.stringify(customPlaylists);

			// add the new playlist to the DOM
			$(document)
				.find('.menu-item-wrapper.playlists .playlist-list.owned')
				.append('
'+response.name+'
');

        	notifyUser('good', 'Custom playlist added');
		})
		.fail( function( response ){
			updateLoader('stop');
        	notifyUser('error', 'Error adding custom playlist: '+response.responseJSON.error.message);
		});

};

<|endoftext|>


<commit_before>app.controller("TestQuestions", function($scope)
	{
		$scope.questions =
		[
				{
					"id": 1,
					"question": "Каков уровень Вашей организации?",
						"answers": [
									{
										"id" : 1,
										"answer":	"Федеральные органы исполнительной власти",
										"isSelected" : 0
									},
									{	"id" : 2,
										 "answer": "Региональные органы исполнительной власти",
										 "isSelected" : 0
									},
									{	 "id" : 3,
										 "answer": "Местные административные органы управления",
										 "isSelected" : 0
									},
									{	 "id" : 4,
										 "answer": "Некоммерческие организации",
										"isSelected" : 0
									},
									{	 "id" : 5,
										 "answer": "Частное лицо",
										 "isSelected" : 0
									},
									{	 "id" : 6,
										 "answer": "Другое",
										 "isSelected" : 0
									}
								 ],
						"MaxAllowedChoice" : 1,
						"isEnabled" : true,
						"NowSelected" : 0
				},

				{
					"id": 2,
					"question": "Какая главная область использования Вами данных ДЗЗ?", "answers": [
																										{
																											"id" : 1,
																											"answer": "Сельское хозяйство",
																											"subanswers":
																													[
																														{
																															"id":1,
																															"subanswer": "Растеневодство",
																															"subisSelected" : 0
																														},
																														{
																															"id":2,
																															"subanswer": "Животноводство",
																															"subisSelected" : 0
																														},
																													],
																											"isSelected" : 0
																										},
																										{
																											"id" : 2,
																											"answer": "Лесное хозяйство",
																											"subanswers":
																													[
																														{
																															"id":1,
																															"subanswer": "Вырубки",
																															"subisSelected" : 0
																														},
																														{
																															"id":2,
																															"subanswer": "Состояние лесонасаждений",
																															"subisSelected" : 0
																														},
																													],
																											"isSelected" : 0
																										},

																										{
																											"id": 3,
																											"answer": "Землепользование",
																											"isSelected" : 0
																										}
																									  ],
					"MaxAllowedChoice" : 3,
					"isEnabled" : true,
					"NowSelected" : 0
				},

				{
					"id": 3, "question": "What is your name?", "answers": [
																			{"id": 1, "answer" : "Dima", "isSelected" : 0},
																			{"id": 2, "answer": "Roma", "isSelected" : 0},
																			{"id": 3, "answer": "Masha", "isSelected" : 0}],
					"MaxAllowedChoice" : 2,
					"isEnabled" : true,
					"NowSelected" : 0,
					"foo":"TESTTEST"
				},
				{
					"id": 4, "question": "Where do you live?", "answers": ["Moscow", "London", "Minsk"],
					"isEnabled" : true,
					"MaxAllowedChoice" : 2,
					"NowSelected" : 0
				}
		],

    // При нажатии кнопки мы передадим в функцию answer и содержащий его question
    $scope.setChoice = function(answer, question) {

            // Меняем состояние кнопки на противоположное
            answer.isSelected = !answer.isSelected;

            // Обьявим счетчик отмеченных кнопок
            var _select_count = 0;

            // Запустим цикл для подсчета отмеченных кнопок
            angular.forEach(question.answers, function(_answer) {
                if (_answer.isSelected) {
                    _select_count++;
                }
            });

            if (_select_count >= question.MaxAllowedChoice) {
                // Если количество отмеченных кнопок достигло максимально возможного значения

                // Запустим цикл по всем ответам текущего вопроса
                angular.forEach(question.answers, function(_answer) {
                    if (!_answer.isSelected) {
                        // Все оставшиеся неотмеченными кнопки задисейблим
                        _answer.isDisabled = true;
                    }
                });
            } else {
                // Если количество отмеченных кнопок меньше максимально возможного значения
                angular.forEach(question.answers, function(_answer) {
                    if (_answer.isDisabled) {
                        // Уберем дисейбл с кнопки если он у нее был
                        _answer.isDisabled = false;
                    }
                });
            }

     $scope.setSubChoice = function(question, answer, suba) {

    //         // Меняем состояние кнопки на противоположное
            suba.isSelected = !suba.isSelected;

            // Запустим цикл для подсчета отмеченных кнопок
            angular.forEach(question.answers, function(_answer) {
                if (_answer.isSelected) // только для выделенных кнопок идем и смотрим их subanswers и subisSelected (0 или 1)
                {
                    _select_count++;

	                 angular.forEach(_answer, function(_subanswer)
	                 {
	               		// до этого бегали по ансверам, теперь нужно по массиву сабансверов

	               		angular.forEach(_subanswer, function(_item)
	                	{
	                 		//console.log(_item.subanswer);
	                 		// suba - это данные из кнопки которые мы сюда передаем
	                 		suba.subisSelected = !suba.subisSelected; // меняем состояние
							//console.log(suba);
	                 	});

	                 });
        		}

            });

            }

       	$scope.calculateResult = function()
       	{
       		var answers_result = [];
       		var answers_sub_puncts = []; //collect of selected sub-items
       		angular.forEach($scope.questions, function(question) {
          		angular.forEach(question.answers, function(_answer) {

          			if (_answer.isSelected == 1)
          			{
          				//Запишем ответы в массив ответов
          				var answers_string = ("\"QID\": question.id, \"AID\": _answer.id, \"SubAID\":[SubAID_val]").replace("question.id", question.id).replace("_answer.id", _answer.id);
          				//console.log(answers_result.push(("QID: question.id, AID: _answer.id").replace("question.id", question.id).replace("_answer.id", _answer.id)));

          				// проверить есть ли подпункты
	               		// копи-паста обхода написанная выше, ТОЛЬКО СОСТОЯНИЕ НЕ МЕНЯЕМ УЖЕ
	               		angular.forEach(_answer, function(_subanswer)
		                 {
		               		// до этого бегали по ансверам, теперь нужно по массиву сабансверов
		               		//САБАНСВЕРЫ
		               		angular.forEach(_subanswer, function(_item)
		                	{

		                 		if (_item.isSelected == 1)
          						{
		                 			//Запишем собранные подответы (_item.id) в answers_sub_puncts
		                 			answers_sub_puncts.push(_item.id);

		                 			// console.log(answers_result.push("Subanswer id: ", _item.id));
		                 			console.log(_item.id);
		                 		}
					//			console.log(_subanswer);
		                 	});

		                 });

   			        //Запишем подответы в массив ответов --> [""QID": 2, "AID": 1, "SubAID":1,2"]
         			answers_string = answers_string.replace("SubAID_val", answers_sub_puncts.join());
         			console.log(answers_result.push(answers_string));

          			}
            	});

            });

       	// 	console.log(answers.push("QID: ", question.id, ", AID: ", _answer.id));
      		// console.log("pushed!");
      		console.log(answers_result);

       	}

    };

});

<commit_msg>fixed issues with multiple sub-answers<commit_after>app.controller("TestQuestions", function($scope)
	{
		$scope.questions =
		[
				{
					"id": 1,
					"question": "Каков уровень Вашей организации?",
						"answers": [
									{
										"id" : 1,
										"answer":	"Федеральные органы исполнительной власти",
										"isSelected" : 0
									},
									{	"id" : 2,
										 "answer": "Региональные органы исполнительной власти",
										 "isSelected" : 0
									},
									{	 "id" : 3,
										 "answer": "Местные административные органы управления",
										 "isSelected" : 0
									},
									{	 "id" : 4,
										 "answer": "Некоммерческие организации",
										"isSelected" : 0
									},
									{	 "id" : 5,
										 "answer": "Частное лицо",
										 "isSelected" : 0
									},
									{	 "id" : 6,
										 "answer": "Другое",
										 "isSelected" : 0
									}
								 ],
						"MaxAllowedChoice" : 1,
						"isEnabled" : true,
						"NowSelected" : 0
				},

				{
					"id": 2,
					"question": "Какая главная область использования Вами данных ДЗЗ?", "answers": [
																										{
																											"id" : 1,
																											"answer": "Сельское хозяйство",
																											"subanswers":
																													[
																														{
																															"id":1,
																															"subanswer": "Растеневодство",
																															"subisSelected" : 0
																														},
																														{
																															"id":2,
																															"subanswer": "Животноводство",
																															"subisSelected" : 0
																														},
																													],
																											"isSelected" : 0
																										},
																										{
																											"id" : 2,
																											"answer": "Лесное хозяйство",
																											"subanswers":
																													[
																														{
																															"id":1,
																															"subanswer": "Вырубки",
																															"subisSelected" : 0
																														},
																														{
																															"id":2,
																															"subanswer": "Состояние лесонасаждений",
																															"subisSelected" : 0
																														},
																													],
																											"isSelected" : 0
																										},

																										{
																											"id": 3,
																											"answer": "Землепользование",
																											"isSelected" : 0
																										}
																									  ],
					"MaxAllowedChoice" : 3,
					"isEnabled" : true,
					"NowSelected" : 0
				},

				{
					"id": 3, "question": "What is your name?", "answers": [
																			{"id": 1, "answer" : "Dima", "isSelected" : 0},
																			{"id": 2, "answer": "Roma", "isSelected" : 0},
																			{"id": 3, "answer": "Masha", "isSelected" : 0}],
					"MaxAllowedChoice" : 2,
					"isEnabled" : true,
					"NowSelected" : 0,
					"foo":"TESTTEST"
				},
				{
					"id": 4, "question": "Where do you live?", "answers": ["Moscow", "London", "Minsk"],
					"isEnabled" : true,
					"MaxAllowedChoice" : 2,
					"NowSelected" : 0
				}
		],

    // При нажатии кнопки мы передадим в функцию answer и содержащий его question
    $scope.setChoice = function(answer, question) {

            // Меняем состояние кнопки на противоположное
            answer.isSelected = !answer.isSelected;

            // Обьявим счетчик отмеченных кнопок
            var _select_count = 0;

            // Запустим цикл для подсчета отмеченных кнопок
            angular.forEach(question.answers, function(_answer) {
                if (_answer.isSelected) {
                    _select_count++;
                }
            });

            if (_select_count >= question.MaxAllowedChoice) {
                // Если количество отмеченных кнопок достигло максимально возможного значения

                // Запустим цикл по всем ответам текущего вопроса
                angular.forEach(question.answers, function(_answer) {
                    if (!_answer.isSelected) {
                        // Все оставшиеся неотмеченными кнопки задисейблим
                        _answer.isDisabled = true;
                    }
                });
            } else {
                // Если количество отмеченных кнопок меньше максимально возможного значения
                angular.forEach(question.answers, function(_answer) {
                    if (_answer.isDisabled) {
                        // Уберем дисейбл с кнопки если он у нее был
                        _answer.isDisabled = false;
                    }
                });
            }

     $scope.setSubChoice = function(question, answer, suba) {

    //         // Меняем состояние кнопки на противоположное
            suba.isSelected = !suba.isSelected;

            // Запустим цикл для подсчета отмеченных кнопок
            angular.forEach(question.answers, function(_answer) {
                if (_answer.isSelected) // только для выделенных кнопок идем и смотрим их subanswers и subisSelected (0 или 1)
                {
                    _select_count++;

	                 angular.forEach(_answer, function(_subanswer)
	                 {
	               		// до этого бегали по ансверам, теперь нужно по массиву сабансверов

	               		angular.forEach(_subanswer, function(_item)
	                	{
	                 		//console.log(_item.subanswer);
	                 		// suba - это данные из кнопки которые мы сюда передаем
	                 		suba.subisSelected = !suba.subisSelected; // меняем состояние
							//console.log(suba);
	                 	});

	                 });
        		}

            });

            }

       	$scope.calculateResult = function()
       	{
       		var answers_result = [];
       		angular.forEach($scope.questions, function(question) {
          		angular.forEach(question.answers, function(_answer) {

          			if (_answer.isSelected == 1)
          			{
          				var answers_sub_puncts = []; //collect of selected sub-items
          				//Запишем ответы в массив ответов
          				var answers_string = ("{\"QID\": question.id, \"AID\": _answer.id, \"SubAID\":[SubAID_val]},").replace("question.id", question.id).replace("_answer.id", _answer.id);
          				//console.log(answers_result.push(("QID: question.id, AID: _answer.id").replace("question.id", question.id).replace("_answer.id", _answer.id)));

          				// проверить есть ли подпункты
	               		// копи-паста обхода написанная выше, ТОЛЬКО СОСТОЯНИЕ НЕ МЕНЯЕМ УЖЕ
	               		angular.forEach(_answer, function(_subanswer)
		                 {
		               		// до этого бегали по ансверам, теперь нужно по массиву сабансверов
		               		//САБАНСВЕРЫ
		               		angular.forEach(_subanswer, function(_item)
		                	{

		                 		if (_item.isSelected == 1)
          						{
		                 			//Запишем собранные подответы (_item.id) в answers_sub_puncts
		                 			answers_sub_puncts.push(_item.id);

		                 			// console.log(answers_result.push("Subanswer id: ", _item.id));
		                 			console.log(_item.id);
		                 		}
					//			console.log(_subanswer);
		                 	});

		                 });

   			        //Запишем подответы в массив ответов --> [""QID": 2, "AID": 1, "SubAID":1,2"]
         			answers_string = answers_string.replace("SubAID_val", answers_sub_puncts.join());
         			console.log(answers_result.push(answers_string));

         			//FIX ME
         			//ПРИ ВЫБОРЕ нескольких пунктов вот такая херь вылазит:
         			// [""QID": 2, "AID": 1, "SubAID":[1,2]", ""QID": 3, "AID": 1, "SubAID":[1,2]"]

          			}
            	});

            });

       	// 	console.log(answers.push("QID: ", question.id, ", AID: ", _answer.id));
      		// console.log("pushed!");
      		console.log(answers_result);

       	}

    };

});

<|endoftext|>


<commit_before>var randomDot = function(canvasWidth, canvasHeight){
	this.x = getRandomIntInclusive(20,canvasWidth-20);
	this.y = getRandomIntInclusive(20,canvasHeight-20);
	this.color = randomColor();
	this.radius = randomRadius();
}

function getRandomIntInclusive(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is inclusive and the minimum is inclusive
}

var randomColor = function(){
	var randomNumber = getRandomIntInclusive(0,5);
	switch(randomNumber){
		case 0:
			return 'white';
		case 1:
			return 'red';
		case 2:
			return 'yellow';
		case 3:
			return 'blue';
		case 4:
			return 'green';
		case 5:
			return 'purple';
	}
}

var randomRadius = function(){
	var randomNumber = getRandomIntInclusive(0,5);
	switch(randomNumber){
		case 0:
			return 20;
		case 1:
			return 30;
		case 2:
			return 40;
		case 3:
			return 45;
		case 4:
			return 50;
		case 5:
			return 55;
	}
}<commit_msg>Shows a rectangle randomly and deducts points if it is clicked<commit_after>var randomDot = function(canvasWidth, canvasHeight){
	this.x = getRandomIntInclusive(50,canvasWidth-50);
	this.y = getRandomIntInclusive(50,canvasHeight-50);
	this.color = randomColor();
	this.radius = randomRadius();
}

function getRandomIntInclusive(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is inclusive and the minimum is inclusive
}

var randomColor = function(){
	var randomNumber = getRandomIntInclusive(0,5);
	switch(randomNumber){
		case 0:
			return 'white';
		case 1:
			return 'red';
		case 2:
			return 'yellow';
		case 3:
			return 'blue';
		case 4:
			return 'green';
		case 5:
			return 'purple';
	}
}

var randomRadius = function(){
	var randomNumber = getRandomIntInclusive(0,7);
	switch(randomNumber){
		case 0:
			return 20;
		case 1:
			return 30;
		case 2:
			return 40;
		case 3:
			return 45;
		case 4:
			return 50;
		case 5:
			return 55;
		case 6:
			return 100;
		case 7:
			return 100;
	}
}<|endoftext|>


<commit_before>'use strict';

/* jasmine specs for controllers go here */

describe('JobsCtrl', function(){
    var $httpBackend, createJobsCtrl, createResultSetCtrl, jobScope, resultsetScope;

    beforeEach(module('treeherder'));

    beforeEach(inject(function ($injector, $rootScope, $controller, thUrl) {

        $httpBackend = $injector.get('$httpBackend');
        jasmine.getJSONFixtures().fixturesPath='base/test/mock';

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/repository/').respond(
            getJSONFixture('repositories.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/resultset/?count=10&format=json&full=false&offset=0').respond(
            getJSONFixture('resultset_list.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/jobs/1235/').respond(
            getJSONFixture('job_1235.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/artifact/403/').respond(
            getJSONFixture('artifact_403.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/artifact/403').respond(
            getJSONFixture('artifact_403.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/note?job_id=1235').respond(
            getJSONFixture('notes_job_1235.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/note/?job_id=1235').respond(
            getJSONFixture('notes_job_1235.json')
        );

        $httpBackend.whenGET('resources/job_groups.json').respond(
            getJSONFixture('job_groups.json')
        );

        jobScope = $rootScope.$new();

        //setting attributes derived from the parent controller
        jobScope.mru_repos = [];
        $rootScope.new_failures = [];

        $controller('JobsCtrl', {'$scope': jobScope});

        resultsetScope = jobScope.$new();
        createResultSetCtrl = function(resultset) {
            resultsetScope.resultset = resultset;
            var ctrl = $controller('ResultSetCtrl', {'$scope': resultsetScope});
            return  ctrl;
        };
        $httpBackend.flush();
    }));

    /*
        Tests JobsCtrl
     */

    it('should have 10 resultsets', function() {
        expect(jobScope.result_sets.length).toBe(10);
    });

    /*
        Tests ResultSetCtrl
     */

    it('should have 139 jobs in resultset 2', function() {
        createResultSetCtrl(jobScope.result_sets[8]);
        expect(resultsetScope.resultset.platforms.length).toBe(14);
    });

    it('should default to revisions collapsed', function() {
        createResultSetCtrl(jobScope.result_sets[8]);
        expect(resultsetScope.isCollapsedRevisions).toBe(true);
    });

    it('should set the selectedJob in scope when calling viewJob()', function() {
        createResultSetCtrl(jobScope.result_sets[8]);
        var job = resultsetScope.resultset.platforms[0].groups[0].jobs[0];
        resultsetScope.viewJob(job);
        expect(resultsetScope.selectedJob).toBe(job);
    });

});
<commit_msg>test fix<commit_after>'use strict';

/* jasmine specs for controllers go here */

describe('JobsCtrl', function(){
    var $httpBackend, createJobsCtrl, createResultSetCtrl, jobScope, resultsetScope;

    beforeEach(module('treeherder'));

    beforeEach(inject(function ($injector, $rootScope, $controller, thUrl) {

        $httpBackend = $injector.get('$httpBackend');
        jasmine.getJSONFixtures().fixturesPath='base/test/mock';

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/repository/').respond(
            getJSONFixture('repositories.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/resultset/?count=10&format=json&full=false').respond(
            getJSONFixture('resultset_list.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/jobs/1235/').respond(
            getJSONFixture('job_1235.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/artifact/403/').respond(
            getJSONFixture('artifact_403.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/artifact/403').respond(
            getJSONFixture('artifact_403.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/note?job_id=1235').respond(
            getJSONFixture('notes_job_1235.json')
        );

        $httpBackend.whenGET('http://local.treeherder.mozilla.org/api/project/mozilla-inbound/note/?job_id=1235').respond(
            getJSONFixture('notes_job_1235.json')
        );

        $httpBackend.whenGET('resources/job_groups.json').respond(
            getJSONFixture('job_groups.json')
        );

        jobScope = $rootScope.$new();

        //setting attributes derived from the parent controller
        jobScope.mru_repos = [];
        $rootScope.new_failures = [];

        $controller('JobsCtrl', {'$scope': jobScope});

        resultsetScope = jobScope.$new();
        createResultSetCtrl = function(resultset) {
            resultsetScope.resultset = resultset;
            var ctrl = $controller('ResultSetCtrl', {'$scope': resultsetScope});
            return  ctrl;
        };
        $httpBackend.flush();
    }));

    /*
        Tests JobsCtrl
     */

    it('should have 10 resultsets', function() {
        expect(jobScope.result_sets.length).toBe(10);
    });

    /*
        Tests ResultSetCtrl
     */

    it('should have 139 jobs in resultset 2', function() {
        createResultSetCtrl(jobScope.result_sets[8]);
        expect(resultsetScope.resultset.platforms.length).toBe(14);
    });

    it('should default to revisions collapsed', function() {
        createResultSetCtrl(jobScope.result_sets[8]);
        expect(resultsetScope.isCollapsedRevisions).toBe(true);
    });

    it('should set the selectedJob in scope when calling viewJob()', function() {
        createResultSetCtrl(jobScope.result_sets[8]);
        var job = resultsetScope.resultset.platforms[0].groups[0].jobs[0];
        resultsetScope.viewJob(job);
        expect(resultsetScope.selectedJob).toBe(job);
    });

});
<|endoftext|>


<commit_before>/* Core rebalance algorithms, no UI. */

function rebalance(ctx, req) {
  return run(ctx, req,
             validatePartitionSettings,
             allocNextMap,
             planNextRebalanceMap,
             actualizeNextMap);
}
function failOver(ctx, req) {
  return run(ctx, req,
             validatePartitionSettings,
             allocNextMap,
             planNextFailOverMap,
             actualizeNextMap);
}
function restoreBack(ctx, req) {
  return run(ctx, req,
             validatePartitionSettings,
             allocNextMap,
             planNextRestoreBackMap,
             actualizeNextMap);
}
function cancelRebalance(ctx, req) {
  return run(ctx, req,
             cancelTakeOverSteps,
             takeCurrentMapAsNextMap,
             actualizeNextMap);
}
function actualizeNextMap(ctx, req) {
  return run(ctx, req,
             validateNextMap,
             scheduleSteps,
             executeSteps,
             checkHealth);
}

// --------------------------------------------------------

function validatePartitionSettings(ctx, req) {
  req.nextBucketEvents = _.clone(req.prevBucketEvents);
  req.nextBucketEvents.events = sortDesc(req.nextBucketEvents.events || [],
                                         "when");

  req.lastPartitionMap = _.findWhere(req.nextBucketEvents.events,
                                     { class: "partitionMap" });
  if (req.lastPartitionMap) {
    req.lastPartitions = partitionsWithNodeNames(req.lastPartitionMap.partitions,
                                                 req.lastPartitionMap.nodes);
  }

  req.arrNodes = { want: req.wantPartitionParams.nodes };
  req.mapNodes = {};

  req.lastPartitionParams = _.findWhere(req.nextBucketEvents.events,
                                        { class: "partitionParams" });
  if (req.lastPartitionParams) {
    req.err =
      _.reduce(["keyFunc", "assignment", "numPartitions"], function(r, k) {
          if (req.lastPartitionParams[k] != req.wantPartitionParams[k]) {
            return "partitionParams." + k + " not equal: " +
              req.lastPartitionParams[k] + " vs " + req.wantPartitionParams[k];
          }
          return r;
        }, null);
    if (req.err) {
      return;
    }

    req.arrNodes.added =
      _.difference(req.wantPartitionParams.nodes, req.lastPartitionParams.nodes);
    req.arrNodes.removed =
      _.difference(req.lastPartitionParams.nodes, req.wantPartitionParams.nodes);
    req.arrNodes.same =
      _.intersection(req.lastPartitionParams.nodes, req.wantPartitionParams.nodes);
  } else {
    req.arrNodes.added   = req.wantPartitionParams.nodes;
    req.arrNodes.removed = [];
    req.arrNodes.same    = [];
  }
  _.each(req.arrNodes, function(a, k) { req.mapNodes[k] = arrToMap(a); });
  function arrToMap(a) {
    return _.reduce(a, function(m, k) { m[k] = {}; return m; }, {});
  }

  req.partitionModel =
    ctx.getObj("partitionModel-" + req.wantPartitionParams.assignment).result;
  if (!req.partitionModel) {
    req.err = "error: missing partitionModel-" + req.wantPartitionParams.assignment;
    return;
  }
  req.partitionModelStates =
    sortDesc(_.reduce(req.partitionModel.states,
                      function(a, v, k) {
                        a.push(_.defaults(_.clone(v), { name: k }));
                        return a;
                      }, []),
             "priority");
  req.mapStatePriority = // Key is state name (e.g., "master"), val is priority int.
    _.reduce(req.partitionModel.states,
             function(m, partitionModel, name) {
               m[name] = partitionModel.priority;
               return m;
             }, {});
}

function allocNextMap(ctx, req) {
  req.nextPartitionMap =
    ctx.newObj("partitionMap",
               _.omit(req.wantPartitionParams, "class")).result;
  req.nextPartitionMap.partitions =
    keyFunc[req.wantPartitionParams.keyFunc].allocPartitions(req);
}

function planNextRebalanceMap(ctx, req) {
  return planNextMap(ctx, req);
}

function planNextFailOverMap(ctx, req) {
  return planNextMap(ctx, req);
}

function planNextRestoreBackMap(ctx, req) {
  return planNextMap(ctx, req);
}

function planNextMap(ctx, req) {
  // TODO: maintenance mode & swap rebalance detected as part of rebalance.

  // Remove nodes that user wants to be removed.
  var lastPartitions = req.lastPartitions || {};
  var nextPartitions =
    _.object(_.map(req.nextPartitionMap.partitions,
                   function(partition, partitionId) {
                     var lastPartition = lastPartitions[partitionId] || {};
                     var nextPartition = removeNodesFromPartition(lastPartition,
                                                                  req.arrNodes.removed);
                     return [partitionId, nextPartition];
                   }));

  req.stateNodeCountsBeg = countStateNodes(nextPartitions);
  req.stateNodeCountsCur = countStateNodes(nextPartitions);

  // Run through the sorted partition states (master, slave, etc) and
  // invoke assignStateToPartitions().
  _.each(req.partitionModelStates,
         function(s) {
           var constraints =
             parseInt((req.wantPartitionParams.constraints || {})[s.name]) ||
             parseInt(s.constraints);
           if (constraints >= 0) {
             assignStateToPartitions(s.name, constraints);
           }
         });

  // Given a state and its constraints, for every partition, find and
  // assign the best nodes to that state.
  function assignStateToPartitions(state, constraints) {
    nextPartitions =
      _.object(_.map(nextPartitions,
                     function(partition, partitionId) {
                       if ((partition[state] || []).length < constraints) {
                         var nodesToAssign =
                           findBestNodes(partitionId, partition, state, constraints);
                         partition = removeNodesFromPartition(partition,
                                                              nodesToAssign);
                         partition[state] =
                           (partition[state] || []).concat(nodesToAssign);
                       }
                       return [partitionId, partition];
                     }));
  }

  function findBestNodes(partitionId, partition, state, constraints) {
    var statePriority = req.mapStatePriority[state];
    var candidateNodes = req.nextPartitionMap.nodes;
    _.each(partition,
           function(sNodes, s) {
             // Filter out or don't touch nodes at a higher priority state.
             if (req.mapStatePriority[s] > statePriority) {
               candidateNodes = _.difference(candidateNodes, sNodes);
             }
           });
    var sNodes = partition[state] || [];
    candidateNodes = _.difference(candidateNodes, sNodes);
    candidateNodes =
      candidateNodes.slice(0, constraints - sNodes.length);
    return candidateNodes;
  }

  req.stateNodeCountsCur = countStateNodes(nextPartitions);

  req.nextPartitionMap.partitions = nextPartitions;
  req.nextPartitionMap.partitions =
    partitionsWithNodeIndexes(req.nextPartitionMap.partitions,
                              req.nextPartitionMap.nodes);

  // TODO: mark partitions on removed node as dead.
}

function validateNextMap(ctx, req) {
  // TODO: do real validation here.
  req.nextBucketEvents.events.unshift(req.wantPartitionParams);
  req.nextBucketEvents.events.unshift(req.nextPartitionMap);
}

function scheduleSteps(ctx, req) {
}

function executeSteps(ctx, req) {
}

function cancelTakeOverSteps(ctx, req) {
}

function takeCurrentMapAsNextMap(ctx, req) {
}

function checkHealth(ctx, req) {
}

// --------------------------------------------------------

// Returns partition with nodes removed.  Example, when removeNodes == ["a"],
//   before - partition: {"0": { "master": ["a"], "slave": ["b"] } }
//   after  - partition: {"0": { "master": [], "slave": ["b"] } }
function removeNodesFromPartition(partition, removeNodes) {
  return _.object(_.map(partition,
                        function(partitionNodes, state) {
                          return [state, _.difference(partitionNodes, removeNodes)];
                        }));
}

// Converts node indexes to node names.  Example, with "nodes": ["a", "b"]:
//   before - "partitions": { "0": { "master": [0], "slave": [1] }, ... }
//   after  - "partitions": { "0": { "master": ["a"], "slave": ["b"] }, ... }
// Reverse of partitionsWithNodeIndexes().
function partitionsWithNodeNames(partitions, nodes) {
  return partitionsMap(partitions,
                       function(nodeIdx) { return nodes[nodeIdx]; });
}

// Converts node names to indexes.  Example, with node" == ["a", "b"]:
//   before - partitions: { "0": { "master": ["a"], "slave": ["b"] }, ... }
//   after  - partitions: { "0": { "master": [0], "slave": [1] }, ... }
// Reverse of partitionsWithNodeNames().
function partitionsWithNodeIndexes(partitions, nodes) {
  return partitionsMap(partitions,
                       function(nodeName) { return _.indexOf(nodes, nodeName); });
}

// Like map(), but runs f() on every nodes array in the partition.
// Example, with partitions == { "0": { "master": ["a"], "slave": ["b", "c"] } }
// then you'll see f(["a"]) and f(["b", "c"]).
function partitionsMap(partitions, f) {
  return _.object(_.map(partitions,
                        function(partition, partitionId) {
                          return [partitionId,
                                  _.object(_.map(partition,
                                                 function(arr, state) {
                                                   return [state, _.map(arr, f)];
                                                 }))];
                        }));
}

// Example, with partitions of...
//   { "0": { "master": ["a"], "slave": ["b", "c"] } },
//   { "1": { "master": ["b"], "slave": ["c"] } }
// then return value will be...
//   { "master": { "a": 1, "b": 1 } },
//   { "slave": { "b": 1, "c": 2 } }
function countStateNodes(partitions) {
  return _.reduce(partitions, function(r, partition, partitionId) {
      return _.reduce(partition, function(r, nodes, state) {
          _.each(nodes, function(node) {
              var s = r[state] = r[state] || {};
              s[node] = (s[node] || 0) + 1;
            });
          return r;
        }, r);
    }, {});
}

// --------------------------------------------------------

function run(ctx, req) { // Varargs are steps to apply to req as long as no req.err.
  return _.reduce(_.rest(arguments, 2),
                  function(req, step) {
                    return req.err ? req : step(ctx, req) || req;
                  }, req);
}

function sortDesc(a, field) { return _.sortBy(a, field).reverse(); }
<commit_msg>Sorting candidate nodes by their stateNodeCounts.<commit_after>/* Core rebalance algorithms, no UI. */

function rebalance(ctx, req) {
  return run(ctx, req,
             validatePartitionSettings,
             allocNextMap,
             planNextRebalanceMap,
             actualizeNextMap);
}
function failOver(ctx, req) {
  return run(ctx, req,
             validatePartitionSettings,
             allocNextMap,
             planNextFailOverMap,
             actualizeNextMap);
}
function restoreBack(ctx, req) {
  return run(ctx, req,
             validatePartitionSettings,
             allocNextMap,
             planNextRestoreBackMap,
             actualizeNextMap);
}
function cancelRebalance(ctx, req) {
  return run(ctx, req,
             cancelTakeOverSteps,
             takeCurrentMapAsNextMap,
             actualizeNextMap);
}
function actualizeNextMap(ctx, req) {
  return run(ctx, req,
             validateNextMap,
             scheduleSteps,
             executeSteps,
             checkHealth);
}

// --------------------------------------------------------

function validatePartitionSettings(ctx, req) {
  req.nextBucketEvents = _.clone(req.prevBucketEvents);
  req.nextBucketEvents.events = sortDesc(req.nextBucketEvents.events || [],
                                         "when");

  req.lastPartitionMap = _.findWhere(req.nextBucketEvents.events,
                                     { class: "partitionMap" });
  if (req.lastPartitionMap) {
    req.lastPartitions = partitionsWithNodeNames(req.lastPartitionMap.partitions,
                                                 req.lastPartitionMap.nodes);
  }

  req.arrNodes = { want: req.wantPartitionParams.nodes };
  req.mapNodes = {};

  req.lastPartitionParams = _.findWhere(req.nextBucketEvents.events,
                                        { class: "partitionParams" });
  if (req.lastPartitionParams) {
    req.err =
      _.reduce(["keyFunc", "assignment", "numPartitions"], function(r, k) {
          if (req.lastPartitionParams[k] != req.wantPartitionParams[k]) {
            return "partitionParams." + k + " not equal: " +
              req.lastPartitionParams[k] + " vs " + req.wantPartitionParams[k];
          }
          return r;
        }, null);
    if (req.err) {
      return;
    }

    req.arrNodes.added =
      _.difference(req.wantPartitionParams.nodes, req.lastPartitionParams.nodes);
    req.arrNodes.removed =
      _.difference(req.lastPartitionParams.nodes, req.wantPartitionParams.nodes);
    req.arrNodes.same =
      _.intersection(req.lastPartitionParams.nodes, req.wantPartitionParams.nodes);
  } else {
    req.arrNodes.added   = req.wantPartitionParams.nodes;
    req.arrNodes.removed = [];
    req.arrNodes.same    = [];
  }
  _.each(req.arrNodes, function(a, k) { req.mapNodes[k] = arrToMap(a); });
  function arrToMap(a) {
    return _.reduce(a, function(m, k) { m[k] = {}; return m; }, {});
  }

  req.partitionModel =
    ctx.getObj("partitionModel-" + req.wantPartitionParams.assignment).result;
  if (!req.partitionModel) {
    req.err = "error: missing partitionModel-" + req.wantPartitionParams.assignment;
    return;
  }
  req.partitionModelStates =
    sortDesc(_.reduce(req.partitionModel.states,
                      function(a, v, k) {
                        a.push(_.defaults(_.clone(v), { name: k }));
                        return a;
                      }, []),
             "priority");
  req.mapStatePriority = // Key is state name (e.g., "master"), val is priority int.
    _.reduce(req.partitionModel.states,
             function(m, partitionModel, name) {
               m[name] = partitionModel.priority;
               return m;
             }, {});
}

function allocNextMap(ctx, req) {
  req.nextPartitionMap =
    ctx.newObj("partitionMap", _.omit(req.wantPartitionParams, "class")).result;
  req.nextPartitionMap.partitions =
    keyFunc[req.wantPartitionParams.keyFunc].allocPartitions(req);
}

function planNextRebalanceMap(ctx, req) {
  return planNextMap(ctx, req);
}

function planNextFailOverMap(ctx, req) {
  return planNextMap(ctx, req);
}

function planNextRestoreBackMap(ctx, req) {
  return planNextMap(ctx, req);
}

function planNextMap(ctx, req) {
  // TODO: maintenance mode & swap rebalance detected as part of rebalance.

  // Remove nodes that user wants to be removed.
  var lastPartitions = req.lastPartitions || {};
  var nextPartitions =
    _.object(_.map(req.nextPartitionMap.partitions,
                   function(partition, partitionId) {
                     var lastPartition = lastPartitions[partitionId] || {};
                     var nextPartition = removeNodesFromPartition(lastPartition,
                                                                  req.arrNodes.removed);
                     return [partitionId, nextPartition];
                   }));

  req.stateNodeCountsBeg = countStateNodes(nextPartitions);
  req.stateNodeCountsCur = countStateNodes(nextPartitions);

  // Run through the sorted partition states (master, slave, etc) that
  // have constraints and invoke assignStateToPartitions().
  _.each(req.partitionModelStates,
         function(s) {
           var constraints =
             parseInt((req.wantPartitionParams.constraints || {})[s.name]) ||
             parseInt(s.constraints);
           if (constraints >= 0) {
             assignStateToPartitions(s.name, constraints);
           }
         });

  // Given a state and its constraints, for every partition, find and
  // assign the best nodes to that state.
  function assignStateToPartitions(state, constraints) {
    nextPartitions =
      _.object(_.map(nextPartitions,
                     function(partition, partitionId) {
                       var nodesToAssign =
                         findBestNodes(partitionId, partition, state, constraints);
                       partition = removeNodesFromPartition(partition, nodesToAssign);
                       partition[state] = nodesToAssign;
                       return [partitionId, partition];
                     }));
  }

  function findBestNodes(partitionId, partition, state, constraints) {
    var stateNodeCounts = req.stateNodeCountsCur[state] || {}
    var statePriority = req.mapStatePriority[state];
    var candidateNodes = req.nextPartitionMap.nodes;
    _.each(partition,
           function(sNodes, s) {
             // Filter out or don't touch nodes at a higher priority state.
             if (req.mapStatePriority[s] > statePriority) {
               candidateNodes = _.difference(candidateNodes, sNodes);
             }
           });
    candidateNodes = _.sortBy(candidateNodes, function(n) {
        return stateNodeCounts[n] || 0;
      });
    return candidateNodes.slice(0, constraints);
  }

  req.stateNodeCountsCur = countStateNodes(nextPartitions);

  req.nextPartitionMap.partitions = nextPartitions;
  req.nextPartitionMap.partitions =
    partitionsWithNodeIndexes(req.nextPartitionMap.partitions,
                              req.nextPartitionMap.nodes);

  // TODO: mark partitions on removed node as dead.
}

function validateNextMap(ctx, req) {
  // TODO: do real validation here.
  req.nextBucketEvents.events.unshift(req.wantPartitionParams);
  req.nextBucketEvents.events.unshift(req.nextPartitionMap);
}

function scheduleSteps(ctx, req) {
}

function executeSteps(ctx, req) {
}

function cancelTakeOverSteps(ctx, req) {
}

function takeCurrentMapAsNextMap(ctx, req) {
}

function checkHealth(ctx, req) {
}

// --------------------------------------------------------

// Returns partition with nodes removed.  Example, when removeNodes == ["a"],
//   before - partition: {"0": { "master": ["a"], "slave": ["b"] } }
//   after  - partition: {"0": { "master": [], "slave": ["b"] } }
function removeNodesFromPartition(partition, removeNodes) {
  return _.object(_.map(partition,
                        function(partitionNodes, state) {
                          return [state, _.difference(partitionNodes, removeNodes)];
                        }));
}

// Converts node indexes to node names.  Example, with "nodes": ["a", "b"]:
//   before - "partitions": { "0": { "master": [0], "slave": [1] }, ... }
//   after  - "partitions": { "0": { "master": ["a"], "slave": ["b"] }, ... }
// Reverse of partitionsWithNodeIndexes().
function partitionsWithNodeNames(partitions, nodes) {
  return partitionsMap(partitions,
                       function(nodeIdx) { return nodes[nodeIdx]; });
}

// Converts node names to indexes.  Example, with node" == ["a", "b"]:
//   before - partitions: { "0": { "master": ["a"], "slave": ["b"] }, ... }
//   after  - partitions: { "0": { "master": [0], "slave": [1] }, ... }
// Reverse of partitionsWithNodeNames().
function partitionsWithNodeIndexes(partitions, nodes) {
  return partitionsMap(partitions,
                       function(nodeName) { return _.indexOf(nodes, nodeName); });
}

// Like map(), but runs f() on every nodes array in the partition.
// Example, with partitions == { "0": { "master": ["a"], "slave": ["b", "c"] } }
// then you'll see f(["a"]) and f(["b", "c"]).
function partitionsMap(partitions, f) {
  return _.object(_.map(partitions,
                        function(partition, partitionId) {
                          return [partitionId,
                                  _.object(_.map(partition,
                                                 function(arr, state) {
                                                   return [state, _.map(arr, f)];
                                                 }))];
                        }));
}

// Example, with partitions of...
//   { "0": { "master": ["a"], "slave": ["b", "c"] } },
//   { "1": { "master": ["b"], "slave": ["c"] } }
// then return value will be...
//   { "master": { "a": 1, "b": 1 } },
//   { "slave": { "b": 1, "c": 2 } }
function countStateNodes(partitions) {
  return _.reduce(partitions, function(r, partition, partitionId) {
      return _.reduce(partition, function(r, nodes, state) {
          _.each(nodes, function(node) {
              var s = r[state] = r[state] || {};
              s[node] = (s[node] || 0) + 1;
            });
          return r;
        }, r);
    }, {});
}

// --------------------------------------------------------

function run(ctx, req) { // Varargs are steps to apply to req as long as no req.err.
  return _.reduce(_.rest(arguments, 2),
                  function(req, step) {
                    return req.err ? req : step(ctx, req) || req;
                  }, req);
}

function sortDesc(a, field) { return _.sortBy(a, field).reverse(); }
<|endoftext|>