<commit_before>import * as THREE from 'three/build/three.module';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { TransformControls } from './lib/TransformControls.js';
import { MarchingCubes } from './lib/MarchingCubes.js';
import * as dat from 'dat.gui';
import Stats from 'stats.js/src/Stats';
import MeshBVHVisualizer from '../src/MeshBVHVisualizer.js';
import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from '../src/index.js';
import SimplexNoise from 'simplex-noise';
import "@babel/polyfill";

THREE.Mesh.prototype.raycast = acceleratedRaycast;
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;

const params = {

	speed: 1,
	visualizeBounds: false,
	visualBoundsDepth: 10,
	distance: 1,

};

let stats;
let scene, camera, renderer, controls, boundsViz;
let terrain, target, transformControls;
let marchingCubes, marchingCubesMesh, marchingCubesMeshBack;

function init() {

	const bgColor = 0x263238 / 2;

	// renderer setup
	renderer = new THREE.WebGLRenderer( { antialias: true } );
	renderer.setPixelRatio( window.devicePixelRatio );
	renderer.setSize( window.innerWidth, window.innerHeight );
	renderer.setClearColor( bgColor, 1 );
	document.body.appendChild( renderer.domElement );

	// scene setup
	scene = new THREE.Scene();
	scene.fog = new THREE.Fog( 0x263238 / 2, 20, 60 );

	const light = new THREE.DirectionalLight( 0xffffff, 0.5 );
	light.position.set( 1, 1, 1 );
	scene.add( light );
	scene.add( new THREE.AmbientLight( 0xffffff, 0.4 ) );

	// geometry setup
	const size = 50;
	const dim = 250;
	const planeGeom = new THREE.PlaneBufferGeometry( size, size, dim - 1, dim - 1 );
	const posAttr = planeGeom.attributes.position;

	const noise = new SimplexNoise( Math.random() );
	for ( let i = 0; i < dim * dim; i ++ ) {

		const x = posAttr.getX( i ) / 15;
		const y = posAttr.getY( i ) / 15;
		posAttr.setZ( i, noise.noise2D( x, y ) * 2 );

	}
	planeGeom.computeVertexNormals();
	planeGeom.computeBoundsTree();

	terrain = new THREE.Mesh( planeGeom, new THREE.MeshStandardMaterial( { flatShading: true, metalness: 0.1, roughness: 0.9, side: THREE.DoubleSide } ) );
	scene.add( terrain );
	terrain.rotation.x = - Math.PI / 2;
	terrain.position.y = - 3;

	// camera setup
	camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 50 );
	camera.position.z = 5;
	camera.far = 100;
	camera.updateProjectionMatrix();

	// stats setup
	stats = new Stats();
	document.body.appendChild( stats.dom );

	const shapeMaterial = new THREE.MeshStandardMaterial( { roughness: 0.75, metalness: 0.1 } );
	target = new THREE.Mesh( new THREE.BoxBufferGeometry( 1, 1, 1 ), shapeMaterial );
	scene.add( target );

	controls = new OrbitControls( camera, renderer.domElement );
	transformControls = new TransformControls( camera, renderer.domElement );
	transformControls.attach( target );
	transformControls.addEventListener( 'dragging-changed', e => controls.enabled = ! e.value );
	transformControls.addEventListener( 'changed', e => controls.enabled = ! e.value );
	scene.add( transformControls );

	const cubeMat = new THREE.MeshStandardMaterial( {
		color: 0xff0000,
		metalness: 0.0,
		glossiness: 0.9
	} );
	marchingCubes = new MarchingCubes( 100, cubeMat, false, false );
	marchingCubes.isolation = 0;

	const meshMat = new THREE.MeshStandardMaterial( {
		flatShading: true,
		color: 0xff0000,
		metalness: 0.0,
		glossiness: 0.9,
		transparent: true,
		depthWrite: false,
		opacity: 0.25,
	} );
	marchingCubesMesh = new THREE.Mesh( undefined, meshMat );
	marchingCubesMesh.visible = false;

	const backMeshMat = meshMat.clone();
	backMeshMat.side = THREE.BackSide;
	marchingCubesMeshBack = new THREE.Mesh( undefined, backMeshMat );
	marchingCubesMeshBack.visible = false;

	const container = new THREE.Group();
	container.scale.multiplyScalar( 5 );
	container.add( marchingCubes );
	container.add( marchingCubesMeshBack );
	container.add( marchingCubesMesh );
	scene.add( container );

	window.marchingCubes = marchingCubes;

	scene.updateMatrixWorld( true );

	const gui = new dat.GUI();
	gui.add( params, 'speed' ).min( 0 ).max( 10 );
	gui.add( params, 'visualizeBounds' ).onChange( () => updateFromOptions() );
	gui.add( params, 'visualBoundsDepth' ).min( 1 ).max( 40 ).step( 1 ).onChange( () => updateFromOptions() );
	gui.add( params, 'distance' ).min( 0 ).max( 2 ).step( 0.01 ).onChange( () => updateFromOptions() );
	gui.add( transformControls, 'mode', [ 'translate', 'rotate', 'scale' ] );

	const posFolder = gui.addFolder( 'position' );
	posFolder.add( target.position, 'x' ).min( - 5 ).max( 5 ).step( 0.001 ).listen();
	posFolder.add( target.position, 'y' ).min( - 5 ).max( 5 ).step( 0.001 ).listen();
	posFolder.add( target.position, 'z' ).min( - 5 ).max( 5 ).step( 0.001 ).listen();
	posFolder.open();

	const rotFolder = gui.addFolder( 'rotation' );
	rotFolder.add( target.rotation, 'x' ).min( - Math.PI ).max( Math.PI ).step( 0.001 ).listen();
	rotFolder.add( target.rotation, 'y' ).min( - Math.PI ).max( Math.PI ).step( 0.001 ).listen();
	rotFolder.add( target.rotation, 'z' ).min( - Math.PI ).max( Math.PI ).step( 0.001 ).listen();
	rotFolder.open();

	gui.open();

}

function updateFromOptions() {

	// Update bounds viz
	if ( boundsViz && ! params.visualizeBounds ) {

		scene.remove( boundsViz );
		boundsViz = null;

	}
	if ( ! boundsViz && params.visualizeBounds ) {

		boundsViz = new MeshBVHVisualizer( terrain );
		scene.add( boundsViz );

	}

	if ( boundsViz ) {

		boundsViz.depth = params.visualBoundsDepth;

	}

}

function* updateMarchingCubes() {

	// marching cubes ranges from -1 to 1
	const dim = marchingCubes.matrixWorld.getMaxScaleOnAxis();
	const min = - dim;
	const max = dim;
	const size = marchingCubes.size;
	const cellWidth = 2 * dim / size;
	const cellWidth2 = cellWidth / 2;

	marchingCubes.isolation = 0.0000001;
	marchingCubes.position.x = 1 / size;
	marchingCubes.position.y = 1 / size;
	marchingCubes.position.z = 1 / size;

	marchingCubes.reset();
	marchingCubes.field.fill( - 1 );

	const pos = new THREE.Vector3();
	const scale = new THREE.Vector3();
	const quaternion = new THREE.Quaternion();

	target.matrixWorld.decompose( pos, quaternion, scale );

	const mat = new THREE.Matrix4();
	const targetToBvh = new THREE.Matrix4();
	const distance = params.distance;
	let count = 0;

	marchingCubesMesh.visible = false;
	marchingCubesMeshBack.visible = false;
	marchingCubes.visible = true;

	for ( let y = 0; y < size; y ++ ) {

		for ( let x = 0; x < size; x ++ ) {

			for ( let z = 0; z < size; z ++ ) {

				pos.x = min + cellWidth2 + x * cellWidth;
				pos.y = min + cellWidth2 + y * cellWidth;
				pos.z = min + cellWidth2 + z * cellWidth;

				mat.compose( pos, quaternion, scale );
				targetToBvh.getInverse( terrain.matrixWorld ).multiply( mat );

				if ( pos.length() < 3 ) {

					const result = terrain.geometry.boundsTree.distancecast( terrain, target.geometry, targetToBvh, distance );
					marchingCubes.setCell( x, y, z, result ? 0 : 1 );

				}

				count ++;

				yield count / ( size * size * size );

			}

		}

	}

	marchingCubes.blur();
	marchingCubes.isolation = 0.5;
	marchingCubesMesh.geometry = marchingCubes.generateBufferGeometry();
	marchingCubesMeshBack.geometry = marchingCubesMesh.geometry;
	marchingCubesMesh.visible = true;
	marchingCubesMeshBack.visible = true;
	marchingCubes.visible = false;

}

let currentTask = null;
let lastQuat = null;
let lastDist = null;
function render() {

	stats.begin();

	if ( boundsViz ) boundsViz.update();

	if ( ! lastQuat || ! lastQuat.equals( target.quaternion ) || lastDist !== params.distance ) {

		if ( ! lastQuat ) lastQuat = new THREE.Quaternion();
		currentTask = updateMarchingCubes();
		lastQuat.copy( target.quaternion );
		lastDist = params.distance;

	}

	let perc = 0;
	if ( currentTask ) {

		let startTime = window.performance.now();
		while ( window.performance.now() - startTime < 60 ) {

			const res = currentTask.next();
			perc = res.value;

			if ( res.done ) {

				currentTask = null;
				break;

			}

		}

	}

	document.getElementById( 'loader' ).setAttribute( 'style', `width: ${ perc * 100 }%` );

	// updateMarchingCubes();

	const transformMatrix =
		new THREE.Matrix4()
			.getInverse( terrain.matrixWorld )
			.multiply( target.matrixWorld );

	const hit = terrain.geometry.boundsTree.distancecast( terrain, target.geometry, transformMatrix, params.distance );
	target.material.color.set( hit ? 0xE91E63 : 0x666666 );
	target.material.emissive.set( 0xE91E63 ).multiplyScalar( hit ? 0.25 : 0 );

	renderer.render( scene, camera );
	stats.end();

	requestAnimationFrame( render );

}

window.addEventListener( 'resize', function () {

	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();

	renderer.setSize( window.innerWidth, window.innerHeight );

}, false );

init();
updateFromOptions();

// // const sphereMesh = new THREE.Mesh( new THREE.SphereBufferGeometry( 1, 20, 20 ) );
// // scene.add( sphereMesh );

// // const sphere = new THREE.Sphere( undefined, 0.5 );
// // sphere.center.y = -0.9;
// // window.sphere = sphere;

// const boxMesh = new THREE.Mesh( new THREE.BoxBufferGeometry( 1, 1, 1 ) );
// scene.add( boxMesh );
// // boxMesh.rotation.set( Math.PI / 4, Math.PI / 4, 0 );
// // boxMesh.position.y = 1.2;

// const box = new THREE.Box3();
// box.min.set( 1, 1, 1 ).multiplyScalar( - 0.5 );
// box.max.set( 1, 1, 1 ).multiplyScalar( 0.5 );
// window.box = boxMesh;

render();
<commit_msg>Update example<commit_after>import * as THREE from 'three/build/three.module';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { TransformControls } from './lib/TransformControls.js';
import { MarchingCubes } from './lib/MarchingCubes.js';
import * as dat from 'dat.gui';
import Stats from 'stats.js/src/Stats';
import MeshBVHVisualizer from '../src/MeshBVHVisualizer.js';
import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from '../src/index.js';
import SimplexNoise from 'simplex-noise';
import "@babel/polyfill";

THREE.Mesh.prototype.raycast = acceleratedRaycast;
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;

const params = {

	visualizeBounds: false,
	visualBoundsDepth: 10,
	distance: 1,

};

let stats;
let scene, camera, renderer, controls, boundsViz;
let terrain, target, transformControls;
let marchingCubes, marchingCubesMesh, marchingCubesMeshBack;

function init() {

	const bgColor = 0x263238 / 2;

	// renderer setup
	renderer = new THREE.WebGLRenderer( { antialias: true } );
	renderer.setPixelRatio( window.devicePixelRatio );
	renderer.setSize( window.innerWidth, window.innerHeight );
	renderer.setClearColor( bgColor, 1 );
	renderer.shadowMap.enabled = true;
	document.body.appendChild( renderer.domElement );

	// scene setup
	scene = new THREE.Scene();
	scene.fog = new THREE.Fog( 0x263238 / 2, 10, 45 );

	const light = new THREE.DirectionalLight( 0xffffff, 0.5 );
	light.position.set( 1, 1, 1 );
	light.castShadow = true;
	light.shadow.mapSize.set( 1024, 1024 );

	const shadowCam = light.shadow.camera;
	shadowCam.left = shadowCam.bottom = - 15;
	shadowCam.right = shadowCam.top = 15;
	scene.add( light );
	scene.add( new THREE.AmbientLight( 0xE0F7FA, 0.5 ) );

	// geometry setup
	const size = 50;
	const dim = 250;
	const planeGeom = new THREE.PlaneBufferGeometry( size, size, dim - 1, dim - 1 );
	const posAttr = planeGeom.attributes.position;

	const noise = new SimplexNoise( Math.random() );
	for ( let i = 0; i < dim * dim; i ++ ) {

		const x = posAttr.getX( i ) / 15;
		const y = posAttr.getY( i ) / 15;
		posAttr.setZ( i, noise.noise2D( x, y ) * 2 );

	}
	planeGeom.computeVertexNormals();
	planeGeom.computeBoundsTree();

	terrain = new THREE.Mesh( planeGeom, new THREE.MeshStandardMaterial( { color: 0xE0F7FA, metalness: 0.1, roughness: 0.9, side: THREE.DoubleSide } ) );
	terrain.rotation.x = - Math.PI / 2;
	terrain.position.y = - 3;
	terrain.receiveShadow = true;
	scene.add( terrain );

	// camera setup
	camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 50 );
	camera.position.z = 5;
	camera.far = 100;
	camera.updateProjectionMatrix();

	// stats setup
	stats = new Stats();
	document.body.appendChild( stats.dom );

	const shapeMaterial = new THREE.MeshStandardMaterial( { roughness: 0.75, metalness: 0.1 } );
	target = new THREE.Mesh( new THREE.BoxBufferGeometry( 1, 1, 1 ), shapeMaterial );
	target.castShadow = true;
	target.receiveShadow = true;
	scene.add( target );

	controls = new OrbitControls( camera, renderer.domElement );
	transformControls = new TransformControls( camera, renderer.domElement );
	transformControls.attach( target );
	transformControls.addEventListener( 'dragging-changed', e => controls.enabled = ! e.value );
	transformControls.addEventListener( 'changed', e => controls.enabled = ! e.value );
	scene.add( transformControls );

	const cubeMat = new THREE.MeshStandardMaterial( {
		color: 0xE91E63,
		metalness: 0.0,
		roughness: 0.9
	} );
	marchingCubes = new MarchingCubes( 100, cubeMat, false, false );
	marchingCubes.isolation = 0;

	const meshMat = new THREE.MeshStandardMaterial( {
		flatShading: true,
		color: 0xE91E63,
		metalness: 0.0,
		roughness: 0.9,
		transparent: true,
		depthWrite: false,
		opacity: 0.25,
	} );
	marchingCubesMesh = new THREE.Mesh( undefined, meshMat );
	marchingCubesMesh.visible = false;
	marchingCubesMesh.castShadow = true;

	const backMeshMat = meshMat.clone();
	backMeshMat.side = THREE.BackSide;
	marchingCubesMeshBack = new THREE.Mesh( undefined, backMeshMat );
	marchingCubesMeshBack.visible = false;

	const container = new THREE.Group();
	container.scale.multiplyScalar( 5 );
	container.add( marchingCubes );
	container.add( marchingCubesMeshBack );
	container.add( marchingCubesMesh );
	scene.add( container );

	scene.updateMatrixWorld( true );

	const gui = new dat.GUI();
	gui.add( params, 'visualizeBounds' ).onChange( () => updateFromOptions() );
	gui.add( params, 'visualBoundsDepth' ).min( 1 ).max( 40 ).step( 1 ).onChange( () => updateFromOptions() );
	gui.add( params, 'distance' ).min( 0 ).max( 2 ).step( 0.01 ).onChange( () => updateFromOptions() );
	gui.add( transformControls, 'mode', [ 'translate', 'rotate', 'scale' ] );

	const posFolder = gui.addFolder( 'position' );
	posFolder.add( target.position, 'x' ).min( - 5 ).max( 5 ).step( 0.001 ).listen();
	posFolder.add( target.position, 'y' ).min( - 5 ).max( 5 ).step( 0.001 ).listen();
	posFolder.add( target.position, 'z' ).min( - 5 ).max( 5 ).step( 0.001 ).listen();
	posFolder.open();

	const rotFolder = gui.addFolder( 'rotation' );
	rotFolder.add( target.rotation, 'x' ).min( - Math.PI ).max( Math.PI ).step( 0.001 ).listen();
	rotFolder.add( target.rotation, 'y' ).min( - Math.PI ).max( Math.PI ).step( 0.001 ).listen();
	rotFolder.add( target.rotation, 'z' ).min( - Math.PI ).max( Math.PI ).step( 0.001 ).listen();
	rotFolder.open();

	gui.open();

}

function updateFromOptions() {

	// Update bounds viz
	if ( boundsViz && ! params.visualizeBounds ) {

		scene.remove( boundsViz );
		boundsViz = null;

	}
	if ( ! boundsViz && params.visualizeBounds ) {

		boundsViz = new MeshBVHVisualizer( terrain );
		scene.add( boundsViz );

	}

	if ( boundsViz ) {

		boundsViz.depth = params.visualBoundsDepth;

	}

}

function* updateMarchingCubes() {

	// marching cubes ranges from -1 to 1
	const dim = marchingCubes.matrixWorld.getMaxScaleOnAxis();
	const min = - dim;
	const size = marchingCubes.size;
	const cellWidth = 2 * dim / size;
	const cellWidth2 = cellWidth / 2;

	marchingCubes.isolation = 0.0000001;
	marchingCubes.position.x = 1 / size;
	marchingCubes.position.y = 1 / size;
	marchingCubes.position.z = 1 / size;

	marchingCubes.reset();
	marchingCubes.field.fill( - 1 );

	const pos = new THREE.Vector3();
	const scale = new THREE.Vector3();
	const quaternion = new THREE.Quaternion();

	target.matrixWorld.decompose( pos, quaternion, scale );

	const mat = new THREE.Matrix4();
	const targetToBvh = new THREE.Matrix4();
	const distance = params.distance;
	let count = 0;

	marchingCubesMesh.visible = false;
	marchingCubesMeshBack.visible = false;
	marchingCubes.visible = true;

	for ( let y = 0; y < size; y ++ ) {

		for ( let x = 0; x < size; x ++ ) {

			for ( let z = 0; z < size; z ++ ) {

				pos.x = min + cellWidth2 + x * cellWidth;
				pos.y = min + cellWidth2 + y * cellWidth;
				pos.z = min + cellWidth2 + z * cellWidth;

				mat.compose( pos, quaternion, scale );
				targetToBvh.getInverse( terrain.matrixWorld ).multiply( mat );

				if ( pos.length() < 3 ) {

					const result = terrain.geometry.boundsTree.distancecast( terrain, target.geometry, targetToBvh, distance );
					marchingCubes.setCell( x, y, z, result ? 0 : 1 );

				}

				count ++;

				yield count / ( size * size * size );

			}

		}

	}

	marchingCubes.blur( 1 );
	marchingCubes.isolation = 0.5;
	marchingCubesMesh.geometry = marchingCubes.generateBufferGeometry();
	marchingCubesMeshBack.geometry = marchingCubesMesh.geometry;
	marchingCubesMesh.visible = true;
	marchingCubesMeshBack.visible = true;
	marchingCubes.visible = false;

}

let currentTask = null;
let lastQuat = null;
let lastDist = null;
function render() {

	stats.begin();

	if ( boundsViz ) boundsViz.update();

	if ( ! lastQuat || ! lastQuat.equals( target.quaternion ) || lastDist !== params.distance ) {

		if ( ! lastQuat ) lastQuat = new THREE.Quaternion();
		currentTask = updateMarchingCubes();
		lastQuat.copy( target.quaternion );
		lastDist = params.distance;

	}

	let perc = 0;
	if ( currentTask ) {

		let startTime = window.performance.now();
		while ( window.performance.now() - startTime < 60 ) {

			const res = currentTask.next();
			perc = res.value;

			if ( res.done ) {

				currentTask = null;
				break;

			}

		}

	}

	document.getElementById( 'loader' ).setAttribute( 'style', `width: ${ perc * 100 }%` );

	// updateMarchingCubes();

	const transformMatrix =
		new THREE.Matrix4()
			.getInverse( terrain.matrixWorld )
			.multiply( target.matrixWorld );

	const hit = terrain.geometry.boundsTree.distancecast( terrain, target.geometry, transformMatrix, params.distance );
	target.material.color.set( hit ? 0xE91E63 : 0x666666 );
	target.material.emissive.set( 0xE91E63 ).multiplyScalar( hit ? 0.25 : 0 );

	renderer.render( scene, camera );
	stats.end();

	requestAnimationFrame( render );

}

window.addEventListener( 'resize', function () {

	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();

	renderer.setSize( window.innerWidth, window.innerHeight );

}, false );

init();
updateFromOptions();

// // const sphereMesh = new THREE.Mesh( new THREE.SphereBufferGeometry( 1, 20, 20 ) );
// // scene.add( sphereMesh );

// // const sphere = new THREE.Sphere( undefined, 0.5 );
// // sphere.center.y = -0.9;
// // window.sphere = sphere;

// const boxMesh = new THREE.Mesh( new THREE.BoxBufferGeometry( 1, 1, 1 ) );
// scene.add( boxMesh );
// // boxMesh.rotation.set( Math.PI / 4, Math.PI / 4, 0 );
// // boxMesh.position.y = 1.2;

// const box = new THREE.Box3();
// box.min.set( 1, 1, 1 ).multiplyScalar( - 0.5 );
// box.max.set( 1, 1, 1 ).multiplyScalar( 0.5 );
// window.box = boxMesh;

render();
<|endoftext|>


<commit_before>'use strict';

const
  express = require('express'),
  fs = require('fs'),
  server = express();

  server.get('/medicineService', function (req, res) {
  fs.readFile('medicine.json', function(err, data){
    if (err) {
      let resp = {};
      resp.status = 500;
      resp.message = 'An error occured';

      res.json(resp);
    }

    var dataJson = JSON.parse(data.toString());
    console.log(dataJson);
    res.json(dataJson);
  });
});

server.listen(3000, function(){
  console.log('listening on port' + 3000);
});
<commit_msg>Remove logging<commit_after>'use strict';

const
  express = require('express'),
  fs = require('fs'),
  server = express();

  server.get('/medicineService', function (req, res) {
  fs.readFile('medicine.json', function(err, data){
    if (err) {
      let resp = {};
      resp.status = 500;
      resp.message = 'An error occured';

      res.json(resp);
    }

    var dataJson = JSON.parse(data.toString());
    console.log('SENDING RESPONSE FOR REQUEST : GET /medicineService');
    res.json(dataJson);
  });
});

server.listen(3000, function(){
  console.log('listening on port' + 3000);
});
<|endoftext|>


<commit_before>/*globals d3*/

var ICONS = {
    check: girder.staticRoot + '/built/plugins/isic_archive/extra/img/check.svg',
    ex: girder.staticRoot + '/built/plugins/isic_archive/extra/img/ex.svg',
    dash: girder.staticRoot + '/built/plugins/isic_archive/extra/img/dash.svg'
};

isic.views.ImagesFacetView = isic.View.extend({
    className: 'isic-images-facet',

    _getFieldLabel: function (fieldInfo) {
        if (fieldInfo.label === '__NaN__' || fieldInfo.label === '__undefined__') {
            return 'unknown';
        } else if (_.has(fieldInfo, 'lowBound')) {
            var formatter = d3.format('0.3s');
            return fieldInfo.label[0] +
                formatter(fieldInfo.lowBound) + ' - ' +
                formatter(fieldInfo.highBound) +
                fieldInfo.label[fieldInfo.label.length - 1];
        } else {
            return fieldInfo.label;
        }
    }
});

isic.views.ImagesFacetHistogramView = isic.views.ImagesFacetView.extend({
    events: {
        'click .toggle': function (evt) {
            this.$('.toggle').toggleClass('icon-down-open')
                .toggleClass('icon-right-open');

            this.$('svg.content').toggle();
        }
    },
    initialize: function (parameters) {
        this.attrName = parameters.facetName;

        this.title = isic.ENUMS.SCHEMA[this.attrName].humanName;
        this.scale = new isic.views.ImagesViewSubViews.HistogramScale();

        this.listenTo(this.model, 'change:overviewHistogram', this._renderHistogram);
        this.listenTo(this.model, 'change:filteredSetHistogram', this._renderHistogram);
    },
    render: function () {
        this.$el.html(isic.templates.imagesFacetHistogram({
            title: this.title,
            staticRoot: girder.staticRoot
        }));
        this._renderHistogram();
    },

    _renderHistogram: function () {
        var svg = d3.select(this.el).select('svg.content');
        if (svg.empty()) {
            // Do nothing until render() has been called
            return;
        }

        var parentWidth = this.el.getBoundingClientRect().width;
        var emSize = parseFloat(svg.style('font-size'));
        this.scale.update(
            this.model.get('overviewHistogram')[this.attrName],
            this.model.get('filteredSetHistogram')[this.attrName],
            emSize, parentWidth);

        var width = this.scale.width;
        var topPadding = 0.5 * emSize;
        var height = this.scale.height + topPadding;

        // Draw the y axis
        var yScale = d3.scale.linear()
            .domain([0, this.scale.yMax])
            .range([height, topPadding]);
        var yAxis = d3.svg.axis()
            .scale(yScale)
            .orient('left')
            .ticks(Math.min(4, this.scale.yMax))
            .tickFormat(d3.format('s'));
        var yAxisObj = svg.select('.yAxis')
            .attr('transform', 'translate(' + this.scale.leftAxisPadding + ',0)')
            .call(yAxis);

        // Move the special buttons into place and attach their events
        this.$('.selectAllBins').hide();
        svg.select('.selectAllBins')
            .attr('transform', 'translate(' +
                (this.scale.leftAxisPadding - 0.5 * emSize) + ',' +
                (height + emSize) + ')');
        svg.select('.selectAll')
            .on('click', function () {
                this.model.clearFilters(this.attrName);
            });

        // Draw the bin groups
        var bins = svg.select('.bins').selectAll('.bin')
            .data(this.scale.overviewHistogram, function (d) {
                return d.label;
            });
        var binsEnter = bins.enter().append('g')
            .attr('class', 'bin');
        bins.exit().remove();

        // Move the bins horizontally
        bins.attr('transform', _.bind(function (d) {
            var binNo = this.scale.labelToBin(d.label, 'overview');
            return 'translate(' + this.scale.binToPosition(binNo) + ',' + topPadding + ')';
        }, this));

        // Draw one bar for each bin
        binsEnter.append('rect')
            .attr('class', 'overview');
        binsEnter.append('rect')
            .attr('class', 'filteredSet');

        // This bar is the full height of the space allocated to the bars, and
        // marked almost transparent so that it still picks up mouse events - it
        // is used to trigger a tooltip on top of the overview bar showing how
        // many elements are represented.
        binsEnter.append('rect')
            .classed('target', true)
            .style('opacity', 1e-5);
        // Comment out this line to hide the page histogram (1/2):
        // binsEnter.append('rect')
        //     .attr('class', 'page');

        // Update each bar
        var drawBars = _.bind(function () {
            var self = this;
            bins.select('rect.overview')
                .each(function (d) {
                    // this refers to the DOM element
                    d3.select(this)
                        .attr(self.scale.getBinRect(d.label, 'overview'));

                    $(this).tooltip({
                        container: 'body',
                        title: function () {
                            var overviewCount = self.scale.labelToCount(d.label, 'overview');
                            var filteredCount = self.scale.labelToCount(d.label, 'filteredSet');

                            if (filteredCount === overviewCount) {
                                return String(filteredCount);
                            } else {
                                return filteredCount + ' (of ' + overviewCount + ')';
                            }
                        }
                    });
                });
            bins.select('rect.filteredSet')
                .each(function (d) {
                    // this refers to the DOM element
                    d3.select(this)
                      .attr(self.scale.getBinRect(d.label, 'filteredSet'));
                });
            bins.select('rect.target')
                .each(function (d) {
                    // this refers to the DOM element

                    var el = d3.select(this.parentElement)
                        .select('rect.overview')
                        .node();

                    // Delegate "mouseover" events to the tooltip on the
                    // overview bar (so that it appears on top of the bar
                    // itself, not at the top of the bar space).
                    d3.select(this)
                      .attr(self.scale.getFullRect())
                      .on('mouseenter', function () {
                          $(el).tooltip('show');
                      })
                      .on('mouseleave', function () {
                          $(el).tooltip('hide');
                      });
                });
            // Comment out these lines to hide the page histogram (2/2):
            // bins.select('rect.page')
            //     .each(function (d) {
            //         // this refers to the DOM element
            //         d3.select(this).attr(self.scale.getBinRect(d.label, 'page'));
            //     });
        }, this);
        drawBars();

        // Add the scale adjustment knob (needs a distinct scale instance)
        var knobScale = yScale.copy();
        var knob = svg.select('.yAxisKnob')
            .attr('transform', 'translate(' +
                this.scale.leftAxisPadding + ',' +
                knobScale(this.scale.yMax) + ')');
        knob.call(d3.behavior.drag()
            .origin(_.bind(function () {
                return { x: 0, y: knobScale(this.scale.yMax) };
            }, this)).on('drag', _.bind(function () {
                // the yMax setter automagically prevents bad values...
                this.scale.yMax = knobScale.invert(d3.event.y);

                // update everything that cares about the y this.scale:
                // the knob
                knob.attr('transform', 'translate(' +
                    this.scale.leftAxisPadding + ',' +
                    knobScale(this.scale.yMax) + ')');
                // the axis
                yScale.domain([0, this.scale.yMax]);
                yAxis.scale(yScale).ticks(Math.min(4, this.scale.yMax));
                yAxisObj.call(yAxis);
                // and the bars
                drawBars();
            }, this)).on('dragstart', function () {
                svg.style('cursor', 'ns-resize');
            }).on('dragend', function () {
                svg.style('cursor', null);
            }));

        // Add an include / exclude button for each bin
        binsEnter.append('image')
            .attr('class', 'button')
            .attr({
                x: -0.5 * emSize,
                y: height + 0.5 * emSize,
                width: emSize,
                height: emSize
            });
        var self = this;
        bins.select('image.button').each(function (d) {
            // this refers to the DOM element
            var bin = self.scale.labelToBin(d.label, 'overview');
            bin = self.model.get('overviewHistogram')[self.attrName][bin];
            var status = self.model.getBinStatus(self.attrName, bin);

            // To add / remove ranges, we might need to provide a comparison
            // function (undefined will just do default comparisons)
            var comparator;
            if (self.model.getAttributeType(self.attrName) === 'string') {
                comparator = function (a, b) {
                    return a.localeCompare(b);
                };
            }

            d3.select(this)
                .attr('xlink:href', function () {
                    if (status === isic.ENUMS.BIN_STATES.INCLUDED) {
                        return ICONS.check;
                    } else if (status === isic.ENUMS.BIN_STATES.EXCLUDED) {
                        return ICONS.ex;
                    } else {
                        return ICONS.dash;
                    }
                }).on('click', function (d) {
                    // TODO: Ideally, this should select and cancel only the old
                    // set of requests (and could be called in
                    // ImagesViewModel.updateFilters); until that's fixed,
                    // hopefully nobody will click histogram buttons too early
                    // in the page load
                    girder.cancelRestRequests();

                    if (status === isic.ENUMS.BIN_STATES.INCLUDED) {
                        // Remove this bin
                        if (_.has(bin, 'lowBound') && _.has(bin, 'highBound')) {
                            self.model.removeRange(self.attrName,
                                bin.lowBound, bin.highBound, comparator);
                        } else {
                            self.model.removeValue(self.attrName, bin.label);
                        }
                    } else {
                        // Add this bin
                        if (_.has(bin, 'lowBound') && _.has(bin, 'highBound')) {
                            self.model.includeRange(self.attrName,
                                bin.lowBound, bin.highBound, comparator);
                        } else {
                            self.model.includeValue(self.attrName, bin.label);
                        }
                    }

                    self._renderHistogram();
                });
        });
        height += 2 * emSize;

        // Add each bin label, and compute the total needed height
        var offsetY = 0.25 * emSize;
        var transformHeight = height + offsetY;
        var transformAngle = -45;
        var transformAngleRadians = transformAngle * (Math.PI / 180);
        var maxBoxHeight = svg.select('.selectAllBins').select('text')
            .node().getComputedTextLength();
        binsEnter.append('text');
        bins.select('text')
            .text(_.bind(this._getFieldLabel, this))
            .attr('text-anchor', 'end')
            .attr('transform', 'translate(0 ' + transformHeight + ') rotate(' + transformAngle + ')')
            .each(function (d) {
                // this refers to the DOM element
                var boxHeight = Math.abs(this.getComputedTextLength() * Math.sin(transformAngleRadians));
                maxBoxHeight = Math.max(boxHeight, maxBoxHeight);

                // Shorten any labels that are too long. Remove letters from the
                // end of the string one by one, and replace with an HTML
                // ellipsis, until the string is a manageable length.
                var me = d3.select(this);
                var text = me.text();
                var shortened = false;
                while (this.getComputedTextLength() > 95) {
                    shortened = true;

                    text = text.slice(0, -1);
                    me.html(text + '…');
                }

                // Add a tooltip to shortened labels, containing the full label.
                if (shortened) {
                    $(this).tooltip({
                        container: 'body',
                        title: function () {
                            return self._getFieldLabel(d);
                        }
                    });
                }
            });
        height += maxBoxHeight + topPadding + offsetY;

        svg.attr({
            width: width + 'px',
            height: height + 'px'
        });
        return this;
    },

    destroy: function () {
        // Since the tooltips are attached to the HTML <body> (way outside the
        // scope of this view's element, just destroy all tooltip elements
        // globally; this is overkill, but can be fixed in a future refactor
        $('.tooltip').remove();

        isic.views.ImagesFacetView.prototype.destroy.call(this);
    }
});

isic.views.ImagesFacetHistogramDatasetView = isic.views.ImagesFacetHistogramView.extend({
    _getFieldLabel: function (fieldInfo) {
        return this.model.datasetCollection.findWhere({
            _id: fieldInfo.label
        }).name();
    }
});

isic.views.ImagesFacetCategoricalView = isic.views.ImagesFacetHistogramDatasetView.extend({
    events: {
        'click .toggle': function (evt) {
            this.$('.toggle').toggleClass('icon-down-open')
                .toggleClass('icon-right-open');

            this.$('.content').toggle();
        }
    },
    initialize: function (parameters) {
        this.attrName = parameters.facetName;
        this.title = isic.ENUMS.SCHEMA[this.attrName].humanName;

        this.scale = new isic.views.ImagesViewSubViews.HistogramScale();

        this.listenTo(this.model, 'change:overviewHistogram', this.render);
        this.listenTo(this.model, 'change:filteredSetHistogram', this.render);
    },
    render: function () {
        this.scale.update(this.model.get('overviewHistogram')[this.attrName], this.model.get('filteredSetHistogram')[this.attrName], 0, 0);

        this.$el.html(isic.templates.imagesFacetCategorical({
            title: this.title
        }));

        var cats = d3.select(this.el)
            .select('ul.categories')
            .selectAll('li')
            .data(this.scale.overviewHistogram, function (d) {
                return d.label;
            });

        var labels = cats.enter()
            .append('li')
            .append('div')
            .style('display', 'inline')
            .classed('checkbox', true)
            .append('label');

        labels.append('input')
            .attr('type', 'checkbox');

        var self = this;
        labels.append('span')
            .text(function (d) {
                var name = self._getFieldLabel(d);
                var filteredSetCount = self.scale.labelToCount(d.label, 'filteredSet');
                var overviewCount = self.scale.labelToCount(d.label, 'overview');

                return name + ': ' + filteredSetCount + ' / ' + overviewCount;
            });

        cats.exit()
            .remove();
    }
});
<commit_msg>Implement checkbox toggling<commit_after>/*globals d3*/

var ICONS = {
    check: girder.staticRoot + '/built/plugins/isic_archive/extra/img/check.svg',
    ex: girder.staticRoot + '/built/plugins/isic_archive/extra/img/ex.svg',
    dash: girder.staticRoot + '/built/plugins/isic_archive/extra/img/dash.svg'
};

isic.views.ImagesFacetView = isic.View.extend({
    className: 'isic-images-facet',

    _getFieldLabel: function (fieldInfo) {
        if (fieldInfo.label === '__NaN__' || fieldInfo.label === '__undefined__') {
            return 'unknown';
        } else if (_.has(fieldInfo, 'lowBound')) {
            var formatter = d3.format('0.3s');
            return fieldInfo.label[0] +
                formatter(fieldInfo.lowBound) + ' - ' +
                formatter(fieldInfo.highBound) +
                fieldInfo.label[fieldInfo.label.length - 1];
        } else {
            return fieldInfo.label;
        }
    }
});

isic.views.ImagesFacetHistogramView = isic.views.ImagesFacetView.extend({
    events: {
        'click .toggle': function (evt) {
            this.$('.toggle').toggleClass('icon-down-open')
                .toggleClass('icon-right-open');

            this.$('svg.content').toggle();
        }
    },
    initialize: function (parameters) {
        this.attrName = parameters.facetName;

        this.title = isic.ENUMS.SCHEMA[this.attrName].humanName;
        this.scale = new isic.views.ImagesViewSubViews.HistogramScale();

        this.listenTo(this.model, 'change:overviewHistogram', this._renderHistogram);
        this.listenTo(this.model, 'change:filteredSetHistogram', this._renderHistogram);
    },
    render: function () {
        this.$el.html(isic.templates.imagesFacetHistogram({
            title: this.title,
            staticRoot: girder.staticRoot
        }));
        this._renderHistogram();
    },

    _renderHistogram: function () {
        var svg = d3.select(this.el).select('svg.content');
        if (svg.empty()) {
            // Do nothing until render() has been called
            return;
        }

        var parentWidth = this.el.getBoundingClientRect().width;
        var emSize = parseFloat(svg.style('font-size'));
        this.scale.update(
            this.model.get('overviewHistogram')[this.attrName],
            this.model.get('filteredSetHistogram')[this.attrName],
            emSize, parentWidth);

        var width = this.scale.width;
        var topPadding = 0.5 * emSize;
        var height = this.scale.height + topPadding;

        // Draw the y axis
        var yScale = d3.scale.linear()
            .domain([0, this.scale.yMax])
            .range([height, topPadding]);
        var yAxis = d3.svg.axis()
            .scale(yScale)
            .orient('left')
            .ticks(Math.min(4, this.scale.yMax))
            .tickFormat(d3.format('s'));
        var yAxisObj = svg.select('.yAxis')
            .attr('transform', 'translate(' + this.scale.leftAxisPadding + ',0)')
            .call(yAxis);

        // Move the special buttons into place and attach their events
        this.$('.selectAllBins').hide();
        svg.select('.selectAllBins')
            .attr('transform', 'translate(' +
                (this.scale.leftAxisPadding - 0.5 * emSize) + ',' +
                (height + emSize) + ')');
        svg.select('.selectAll')
            .on('click', function () {
                this.model.clearFilters(this.attrName);
            });

        // Draw the bin groups
        var bins = svg.select('.bins').selectAll('.bin')
            .data(this.scale.overviewHistogram, function (d) {
                return d.label;
            });
        var binsEnter = bins.enter().append('g')
            .attr('class', 'bin');
        bins.exit().remove();

        // Move the bins horizontally
        bins.attr('transform', _.bind(function (d) {
            var binNo = this.scale.labelToBin(d.label, 'overview');
            return 'translate(' + this.scale.binToPosition(binNo) + ',' + topPadding + ')';
        }, this));

        // Draw one bar for each bin
        binsEnter.append('rect')
            .attr('class', 'overview');
        binsEnter.append('rect')
            .attr('class', 'filteredSet');

        // This bar is the full height of the space allocated to the bars, and
        // marked almost transparent so that it still picks up mouse events - it
        // is used to trigger a tooltip on top of the overview bar showing how
        // many elements are represented.
        binsEnter.append('rect')
            .classed('target', true)
            .style('opacity', 1e-5);
        // Comment out this line to hide the page histogram (1/2):
        // binsEnter.append('rect')
        //     .attr('class', 'page');

        // Update each bar
        var drawBars = _.bind(function () {
            var self = this;
            bins.select('rect.overview')
                .each(function (d) {
                    // this refers to the DOM element
                    d3.select(this)
                        .attr(self.scale.getBinRect(d.label, 'overview'));

                    $(this).tooltip({
                        container: 'body',
                        title: function () {
                            var overviewCount = self.scale.labelToCount(d.label, 'overview');
                            var filteredCount = self.scale.labelToCount(d.label, 'filteredSet');

                            if (filteredCount === overviewCount) {
                                return String(filteredCount);
                            } else {
                                return filteredCount + ' (of ' + overviewCount + ')';
                            }
                        }
                    });
                });
            bins.select('rect.filteredSet')
                .each(function (d) {
                    // this refers to the DOM element
                    d3.select(this)
                      .attr(self.scale.getBinRect(d.label, 'filteredSet'));
                });
            bins.select('rect.target')
                .each(function (d) {
                    // this refers to the DOM element

                    var el = d3.select(this.parentElement)
                        .select('rect.overview')
                        .node();

                    // Delegate "mouseover" events to the tooltip on the
                    // overview bar (so that it appears on top of the bar
                    // itself, not at the top of the bar space).
                    d3.select(this)
                      .attr(self.scale.getFullRect())
                      .on('mouseenter', function () {
                          $(el).tooltip('show');
                      })
                      .on('mouseleave', function () {
                          $(el).tooltip('hide');
                      });
                });
            // Comment out these lines to hide the page histogram (2/2):
            // bins.select('rect.page')
            //     .each(function (d) {
            //         // this refers to the DOM element
            //         d3.select(this).attr(self.scale.getBinRect(d.label, 'page'));
            //     });
        }, this);
        drawBars();

        // Add the scale adjustment knob (needs a distinct scale instance)
        var knobScale = yScale.copy();
        var knob = svg.select('.yAxisKnob')
            .attr('transform', 'translate(' +
                this.scale.leftAxisPadding + ',' +
                knobScale(this.scale.yMax) + ')');
        knob.call(d3.behavior.drag()
            .origin(_.bind(function () {
                return { x: 0, y: knobScale(this.scale.yMax) };
            }, this)).on('drag', _.bind(function () {
                // the yMax setter automagically prevents bad values...
                this.scale.yMax = knobScale.invert(d3.event.y);

                // update everything that cares about the y this.scale:
                // the knob
                knob.attr('transform', 'translate(' +
                    this.scale.leftAxisPadding + ',' +
                    knobScale(this.scale.yMax) + ')');
                // the axis
                yScale.domain([0, this.scale.yMax]);
                yAxis.scale(yScale).ticks(Math.min(4, this.scale.yMax));
                yAxisObj.call(yAxis);
                // and the bars
                drawBars();
            }, this)).on('dragstart', function () {
                svg.style('cursor', 'ns-resize');
            }).on('dragend', function () {
                svg.style('cursor', null);
            }));

        // Add an include / exclude button for each bin
        binsEnter.append('image')
            .attr('class', 'button')
            .attr({
                x: -0.5 * emSize,
                y: height + 0.5 * emSize,
                width: emSize,
                height: emSize
            });
        var self = this;
        bins.select('image.button').each(function (d) {
            // this refers to the DOM element
            var bin = self.scale.labelToBin(d.label, 'overview');
            bin = self.model.get('overviewHistogram')[self.attrName][bin];
            var status = self.model.getBinStatus(self.attrName, bin);

            // To add / remove ranges, we might need to provide a comparison
            // function (undefined will just do default comparisons)
            var comparator;
            if (self.model.getAttributeType(self.attrName) === 'string') {
                comparator = function (a, b) {
                    return a.localeCompare(b);
                };
            }

            d3.select(this)
                .attr('xlink:href', function () {
                    if (status === isic.ENUMS.BIN_STATES.INCLUDED) {
                        return ICONS.check;
                    } else if (status === isic.ENUMS.BIN_STATES.EXCLUDED) {
                        return ICONS.ex;
                    } else {
                        return ICONS.dash;
                    }
                }).on('click', function (d) {
                    // TODO: Ideally, this should select and cancel only the old
                    // set of requests (and could be called in
                    // ImagesViewModel.updateFilters); until that's fixed,
                    // hopefully nobody will click histogram buttons too early
                    // in the page load
                    girder.cancelRestRequests();

                    if (status === isic.ENUMS.BIN_STATES.INCLUDED) {
                        // Remove this bin
                        if (_.has(bin, 'lowBound') && _.has(bin, 'highBound')) {
                            self.model.removeRange(self.attrName,
                                bin.lowBound, bin.highBound, comparator);
                        } else {
                            self.model.removeValue(self.attrName, bin.label);
                        }
                    } else {
                        // Add this bin
                        if (_.has(bin, 'lowBound') && _.has(bin, 'highBound')) {
                            self.model.includeRange(self.attrName,
                                bin.lowBound, bin.highBound, comparator);
                        } else {
                            self.model.includeValue(self.attrName, bin.label);
                        }
                    }

                    self._renderHistogram();
                });
        });
        height += 2 * emSize;

        // Add each bin label, and compute the total needed height
        var offsetY = 0.25 * emSize;
        var transformHeight = height + offsetY;
        var transformAngle = -45;
        var transformAngleRadians = transformAngle * (Math.PI / 180);
        var maxBoxHeight = svg.select('.selectAllBins').select('text')
            .node().getComputedTextLength();
        binsEnter.append('text');
        bins.select('text')
            .text(_.bind(this._getFieldLabel, this))
            .attr('text-anchor', 'end')
            .attr('transform', 'translate(0 ' + transformHeight + ') rotate(' + transformAngle + ')')
            .each(function (d) {
                // this refers to the DOM element
                var boxHeight = Math.abs(this.getComputedTextLength() * Math.sin(transformAngleRadians));
                maxBoxHeight = Math.max(boxHeight, maxBoxHeight);

                // Shorten any labels that are too long. Remove letters from the
                // end of the string one by one, and replace with an HTML
                // ellipsis, until the string is a manageable length.
                var me = d3.select(this);
                var text = me.text();
                var shortened = false;
                while (this.getComputedTextLength() > 95) {
                    shortened = true;

                    text = text.slice(0, -1);
                    me.html(text + '…');
                }

                // Add a tooltip to shortened labels, containing the full label.
                if (shortened) {
                    $(this).tooltip({
                        container: 'body',
                        title: function () {
                            return self._getFieldLabel(d);
                        }
                    });
                }
            });
        height += maxBoxHeight + topPadding + offsetY;

        svg.attr({
            width: width + 'px',
            height: height + 'px'
        });
        return this;
    },

    destroy: function () {
        // Since the tooltips are attached to the HTML <body> (way outside the
        // scope of this view's element, just destroy all tooltip elements
        // globally; this is overkill, but can be fixed in a future refactor
        $('.tooltip').remove();

        isic.views.ImagesFacetView.prototype.destroy.call(this);
    }
});

isic.views.ImagesFacetHistogramDatasetView = isic.views.ImagesFacetHistogramView.extend({
    _getFieldLabel: function (fieldInfo) {
        return this.model.datasetCollection.findWhere({
            _id: fieldInfo.label
        }).name();
    }
});

isic.views.ImagesFacetCategoricalView = isic.views.ImagesFacetHistogramDatasetView.extend({
    events: {
        'click .toggle': function (evt) {
            this.$('.toggle').toggleClass('icon-down-open')
                .toggleClass('icon-right-open');

            this.$('.content').toggle();
        }
    },
    initialize: function (parameters) {
        this.attrName = parameters.facetName;
        this.title = isic.ENUMS.SCHEMA[this.attrName].humanName;

        this.scale = new isic.views.ImagesViewSubViews.HistogramScale();

        this.listenTo(this.model, 'change:overviewHistogram', this._renderCategories);
        this.listenTo(this.model, 'change:filteredSetHistogram', this._renderCategories);
    },
    render: function () {
        this.$el.html(isic.templates.imagesFacetCategorical({
            title: this.title
        }));
        this._renderCategories();
    },
    _renderCategories: function () {
        this.scale.update(this.model.get('overviewHistogram')[this.attrName], this.model.get('filteredSetHistogram')[this.attrName], 0, 0);

        var cats = d3.select(this.el)
            .select('ul.categories')
            .selectAll('li')
            .data(this.scale.overviewHistogram, function (d) {
                return d.label;
            });

        var self = this;
        var labels = cats.enter()
            .append('li')
            .append('div')
            .style('display', 'inline')
            .classed('checkbox', true)
            .append('label');

        labels.append('input')
            .attr('type', 'checkbox')
            .property('checked', true)
            .each(function (d) {
                var bin = self.scale.labelToBin(d.label, 'overview');
                bin = self.model.get('overviewHistogram')[self.attrName][bin];

                // To add / remove ranges, we might need to provide a comparison
                // function (undefined will just do default comparisons)
                var comparator;
                if (self.model.getAttributeType(self.attrName) === 'string') {
                    comparator = function (a, b) {
                        return a.localeCompare(b);
                    };
                }

                var me = d3.select(this);

                // me.property('checked', true);

                me.on('click', function () {
                    var status = self.model.getBinStatus(self.attrName, bin);
                    girder.cancelRestRequests();

                    if (status === isic.ENUMS.BIN_STATES.INCLUDED) {
                        // Remove this bin
                        if (_.has(bin, 'lowBound') && _.has(bin, 'highBound')) {
                            self.model.removeRange(self.attrName,
                                bin.lowBound, bin.highBound, comparator);
                        } else {
                            self.model.removeValue(self.attrName, bin.label);
                        }
                    } else {
                        // Add this bin
                        if (_.has(bin, 'lowBound') && _.has(bin, 'highBound')) {
                            self.model.includeRange(self.attrName,
                                bin.lowBound, bin.highBound, comparator);
                        } else {
                            self.model.includeValue(self.attrName, bin.label);
                        }
                    }
                });
            });

        labels.append('span')
            .classed('isic-text', true);

        d3.select(this.el)
            .selectAll('span.isic-text')
            .text(function (d) {
                var name = self._getFieldLabel(d);
                var filteredSetCount = self.scale.labelToCount(d.label, 'filteredSet');
                var overviewCount = self.scale.labelToCount(d.label, 'overview');

                return name + ': ' + filteredSetCount + ' / ' + overviewCount;
            });

        cats.exit()
            .remove();
    }
});
<|endoftext|>


<commit_before>/*
Copyright (c) 2017 Google Inc. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt
Code distributed by Google as part of this project is also
subject to an additional IP rights grant found at
http://polymer.github.io/PATENTS.txt
*/

import '../../deps/@polymer/polymer/polymer-legacy.js';
import '../arcs-shared.js';
import {Polymer} from '../../deps/@polymer/polymer/lib/legacy/polymer-fn.js';
import {html} from '../../deps/@polymer/polymer/lib/utils/html-tag.js';

Polymer({
  _template: html`

    <template is="dom-if" if="{{shownRecipe}}">

{{strategyString}}

    </template>
`,

  is: 'se-recipe-view',

  properties: {
    recipe: {observer: 'recipeChanged'},
    strategyString: String,
    shownRecipe: {
      type: Object,
      observer: 'shownRecipeChanged'
    },
  },

  isDescriptionEmpty(description) {
    return !description;
  },

  recipeChanged: function(recipe) {
    if (!this.pinned) {
      this.shownRecipe = (({result, strategy, id, parent, score, description, hash}) => ({result, strategy, id, parent, score, description, hash}))(this.recipe);
    } else {
      if (recipe.id == this.shownRecipe.id) {
        this.set('shownRecipe.result', this.pinnedResult);
        this.set('shownRecipe.hash', this.pinnedHash);
        this.strategyString = '';
        return;
      }
      let left = recipe.result.replace(/</g, '<');
      let right = this.pinnedResult.replace(/</g, '<');
      let diff = this.over.strategyMap.has(this.to)
          ? JsDiff.diffWords(right, left)
          : JsDiff.diffWords(left, right);
      diff = diff.map(entry => {
        if (entry.added) {
          return `<span class='added'>${entry.value}</span>`;
        }
        if (entry.removed) {
          return `<span class='removed'>${entry.value}</span>`;
        }
        return entry.value;
      });
      this.set('shownRecipe.result', diff.join(''));
      let strategies = this.to.strategyMap.get(this.over) || this.over.strategyMap.get(this.to);
      if (strategies) {
        this.strategyString = 'Strategies: [' + strategies.join(']\n[') + ']\n';
      } else {
        this.strategyString = '';
      }
      this.set('shownRecipe.hash', `<span class='added'>${this.pinnedHash}</span> <span class='removed'>${this.recipe.hash}</span>`);
    }
  },

  shownRecipeChanged: function(shownRecipe) {
    let parts = shownRecipe.result.split(/(?=(?:\/\/ unresolved |[\n\r]+))/g);
    parts = parts.map(entry => {
      if (entry.startsWith('// unresolved ')) {
        return `<span class='unresolved'>${entry}</span>`;
      }
      return entry;
    });
    this.set('shownRecipe.result', parts.join(''));
  },

  pin: function() {
    this.pinned = true;
    this.pinnedResult = this.recipe.result;
    this.pinnedHash = this.recipe.hash;
    this.to = this.over;
  },

  resetToPinned: function() {
    if (this.pinned) {
      this.over = this.to;
      this.recipe = this.over.recipe;
      this.shownRecipeChanged(this.shownRecipe);
    }
  },

  unpin: function() {
    this.pinned = false;
    this.shownRecipe = (({result, strategy, id, parent, score, description, hash}) => ({result, strategy, id, parent, score, description, hash}))(this.recipe);
    this.strategyString = '';
    this.to = undefined;
  }
});
<commit_msg>Show diff of description in Strategy Explorer (#1876)<commit_after>/*
Copyright (c) 2017 Google Inc. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE.txt
Code distributed by Google as part of this project is also
subject to an additional IP rights grant found at
http://polymer.github.io/PATENTS.txt
*/

import '../../deps/@polymer/polymer/polymer-legacy.js';
import '../arcs-shared.js';
import {Polymer} from '../../deps/@polymer/polymer/lib/legacy/polymer-fn.js';
import {html} from '../../deps/@polymer/polymer/lib/utils/html-tag.js';

Polymer({
  _template: html`

    <template is="dom-if" if="{{shownRecipe}}">

{{strategyString}}

    </template>
`,

  is: 'se-recipe-view',

  properties: {
    recipe: {observer: 'recipeChanged'},
    strategyString: String,
    shownRecipe: {
      type: Object,
      observer: 'shownRecipeChanged'
    },
  },

  isDescriptionEmpty(description) {
    return !description;
  },

  recipeChanged: function(recipe) {
    if (!this.pinned) {
      this.shownRecipe = (({result, strategy, id, parent, score, description, hash}) => ({result, strategy, id, parent, score, description, hash}))(this.recipe);
    } else {
      if (recipe.id == this.shownRecipe.id) {
        this.set('shownRecipe.result', this.pinnedResult);
        this.set('shownRecipe.hash', this.pinnedHash);
        this.set('shownRecipe.description', this.pinnedDescription);
        this.strategyString = '';
        return;
      }

      this.set('shownRecipe.description', this.diffHtml(recipe.description, this.pinnedDescription));
      this.set('shownRecipe.hash', this.diffHtml(recipe.hash, this.pinnedHash));
      this.set('shownRecipe.result', this.diffHtml(
        recipe.result.replace(/</g, '<'),
        this.pinnedResult.replace(/</g, '<')));

      let strategies = this.to.strategyMap.get(this.over) || this.over.strategyMap.get(this.to);
      if (strategies) {
        this.strategyString = 'Strategies: [' + strategies.join(']\n[') + ']\n';
      } else {
        this.strategyString = '';
      }
    }
  },

  shownRecipeChanged: function(shownRecipe) {
    let parts = shownRecipe.result.split(/(?=(?:\/\/ unresolved |[\n\r]+))/g);
    parts = parts.map(entry => {
      if (entry.startsWith('// unresolved ')) {
        return `<span class='unresolved'>${entry}</span>`;
      }
      return entry;
    });
    this.set('shownRecipe.result', parts.join(''));
  },

  pin: function() {
    this.pinned = true;
    this.pinnedResult = this.recipe.result;
    this.pinnedHash = this.recipe.hash;
    this.pinnedDescription = this.recipe.description;
    this.to = this.over;
  },

  resetToPinned: function() {
    if (this.pinned) {
      this.over = this.to;
      this.recipe = this.over.recipe;
      this.shownRecipeChanged(this.shownRecipe);
    }
  },

  unpin: function() {
    this.pinned = false;
    this.shownRecipe = (({result, strategy, id, parent, score, description, hash}) => ({result, strategy, id, parent, score, description, hash}))(this.recipe);
    this.strategyString = '';
    this.to = undefined;
  },

  diffHtml: function(left, right) {
    let diff = this.over.strategyMap.has(this.to)
        ? JsDiff.diffWords(right, left)
        : JsDiff.diffWords(left, right);
    diff = diff.map(entry => {
      if (entry.added) {
        return `<span class='added'>${entry.value}</span>`;
      }
      if (entry.removed) {
        return `<span class='removed'>${entry.value}</span>`;
      }
      return entry.value;
    });
    return diff.join('');
  }
});
<|endoftext|>


<commit_before>var MassMediaCommand=function(){
    //create our own command, we dont want to use the FCKDialogCommand because it uses the default fck layout and not our own
};
MassMediaCommand.GetState=function() {
    return FCK_TRISTATE_OFF; //we dont want the button to be toggled
}
MassMediaCommand.Execute=function() {
    window.open('/admin/massmedia/flash/?t=id&pop=1', 'insertVariable', 'width=500,height=400,scrollbars=no,scrolling=no,location=no,toolbar=no');
}
FCKCommands.RegisterCommand('MassMedia', MassMediaCommand ); //otherwise our command will not be found
var oMassMedias = new FCKToolbarButton('MassMedia', 'insert media');
oMassMedias.IconPath = FCKConfig.PluginsPath + 'fckmassmedia/icon.png'; //specifies the image used in the toolbar
FCKToolbarItems.RegisterItem( 'MassMedia', oMassMedias );<commit_msg>audio?<commit_after>var MassMediaCommand=function(){
    //create our own command, we dont want to use the FCKDialogCommand because it uses the default fck layout and not our own
};
MassMediaCommand.GetState=function() {
    return FCK_TRISTATE_OFF; //we dont want the button to be toggled
}
MassMediaCommand.Execute=function() {
    window.open('/admin/massmedia/audio/?t=id&pop=1', 'insertVariable', 'width=700,height=400,scrollbars=yes,scrolling=yes,location=no,toolbar=no');
}
FCKCommands.RegisterCommand('MassMedia', MassMediaCommand ); //otherwise our command will not be found
var oMassMedias = new FCKToolbarButton('MassMedia', 'insert media');
oMassMedias.IconPath = FCKConfig.PluginsPath + 'fckmassmedia/icon.png'; //specifies the image used in the toolbar
FCKToolbarItems.RegisterItem( 'MassMedia', oMassMedias );<|endoftext|>


<commit_before>#! /usr/bin/env node

/**
 * asloc CLI
*/

var pkg = require('./package');
var program = require('commander');

var path = require('path');
var fs = require('fs');

// Split the filter list
var splitFilterList = function(val) {
  return val.split(/[ ,]+/).filter(Boolean);
};

var resolveAndNormalizePath = function(to) {
  return path.resolve(__dirname, to);
};

var dirExists = function(dir) {
  try {
    fs.accessSync(dir);
  }
  catch(error) {
    return false;
  }
  return fs.statSync(dir).isDirectory();
};

var processFiles = function(err, files) {
  var fileSLOC = {};
  var totalSLOC = 0;
  if(err) {

    if(err.message.indexOf('ENOENT') != -1) return console.error('Error: specified directory doesn\'t exist!');
    return console.error(err);
  }
  for(var i = 0; i < files.length; i++) {
    if(program.filter && program.filter.indexOf(path.extname(files[i]).substr(1)) == -1) continue;
    var source = fs.readFileSync(files[i], 'utf8');
    fileSLOC[files[i]] = countSourceSLOC(source, program.ignorecomments || false);
    totalSLOC += fileSLOC[files[i]];
  }
  // Now we can display it!
  displayResults(totalSLOC, fileSLOC);
};

var walkRecursive = function(dir, callback) {
  var list = [];
  var files = fs.readdirSync(dir);
  files.forEach(function(file) {
    file = path.join(dir, file);
    if(fs.statSync(file).isDirectory()) {
      walkRecursive(file, function(err, newFiles) {
        list = list.concat(newFiles);
      });
    }
    else {
      list.push(file);
    }
  });
  return callback(null, list);
};

var walkNotRecursive = function(dir, callback) {
  fs.readdir(dir, function(err, files) {
    if(err) return callback(err);
    var newFiles = [];
    for(var i in files) {
      files[i] = path.resolve(dir, files[i]);
      var stat = fs.statSync(files[i]);
      if(stat.isDirectory()) continue;
      newFiles.push(files[i]);
    }
    return callback(null, newFiles);
  });
};

var countSourceSLOC = function(source, ignoreComments) {
  source = source.split("\n");
  var sloc = 0;
  for(var n = 0; n < source.length; n++) {
    var multilineCommentOpen = false;
    source[n] = source[n].trim();
    // Don't count enpty lines
    if(source[n].length === 0) continue;
    // Remove single-line comments
    if(ignoreComments && source[n].substr(0, 2) == '//') continue;
    // Remove multi-line comments
    if(ignoreComments && source[n].substr(0, 2) == '/*' && !multilineCommentOpen) multilineCommentOpen = true;
    if(ignoreComments && source[n].substr(-2) == '*/' && multilineCommentOpen) multilineCommentOpen = false;
    if(multilineCommentOpen) continue;
    // Finally count the line
    sloc++;
  }
  return sloc;
};

var displayResults = function(totalSLOCs, fileSLOCs) {
  console.log('SLOC count per file:');
  for(var key in fileSLOCs) {
    console.log();
    console.log(' File: %s', path.relative(__dirname, key));
    console.log(' SLOC: %d', fileSLOCs[key].toLocaleString());
  }
  console.log();
  console.log('Total SLOC count: %s', totalSLOCs.toLocaleString());
};

program
    .version(pkg.version)
    .description('Simple Single Lines Of Code (SLOC) counter tool')
    .option('-d, --dir <dir>', 'Directory to walk (default is current directory)', resolveAndNormalizePath, __dirname)
    .option('-i, --ignorecomments', 'Ignore comments in SLOC count')
    .option('-r, --recurive', 'Enable recursive directory walking')
    .option('-f, --filter [filters]', 'Filter by file type (e.g. \'js,css\')', splitFilterList)
    .parse(process.argv);

// First make sure we selected a valid directory
if(!dirExists(program.dir)) {
  return console.error('Error: Specified directory is not a directory or doesn\'t exist!');
}

if(!program.recurive) walkNotRecursive(program.dir, processFiles);
else walkRecursive(program.dir, processFiles);
<commit_msg>Decapitalized option descriptions<commit_after>#! /usr/bin/env node

/**
 * asloc CLI
*/

var pkg = require('./package');
var program = require('commander');

var path = require('path');
var fs = require('fs');

// Split the filter list
var splitFilterList = function(val) {
  return val.split(/[ ,]+/).filter(Boolean);
};

var resolveAndNormalizePath = function(to) {
  return path.resolve(__dirname, to);
};

var dirExists = function(dir) {
  try {
    fs.accessSync(dir);
  }
  catch(error) {
    return false;
  }
  return fs.statSync(dir).isDirectory();
};

var processFiles = function(err, files) {
  var fileSLOC = {};
  var totalSLOC = 0;
  if(err) {

    if(err.message.indexOf('ENOENT') != -1) return console.error('Error: specified directory doesn\'t exist!');
    return console.error(err);
  }
  for(var i = 0; i < files.length; i++) {
    if(program.filter && program.filter.indexOf(path.extname(files[i]).substr(1)) == -1) continue;
    var source = fs.readFileSync(files[i], 'utf8');
    fileSLOC[files[i]] = countSourceSLOC(source, program.ignorecomments || false);
    totalSLOC += fileSLOC[files[i]];
  }
  // Now we can display it!
  displayResults(totalSLOC, fileSLOC);
};

var walkRecursive = function(dir, callback) {
  var list = [];
  var files = fs.readdirSync(dir);
  files.forEach(function(file) {
    file = path.join(dir, file);
    if(fs.statSync(file).isDirectory()) {
      walkRecursive(file, function(err, newFiles) {
        list = list.concat(newFiles);
      });
    }
    else {
      list.push(file);
    }
  });
  return callback(null, list);
};

var walkNotRecursive = function(dir, callback) {
  fs.readdir(dir, function(err, files) {
    if(err) return callback(err);
    var newFiles = [];
    for(var i in files) {
      files[i] = path.resolve(dir, files[i]);
      var stat = fs.statSync(files[i]);
      if(stat.isDirectory()) continue;
      newFiles.push(files[i]);
    }
    return callback(null, newFiles);
  });
};

var countSourceSLOC = function(source, ignoreComments) {
  source = source.split("\n");
  var sloc = 0;
  for(var n = 0; n < source.length; n++) {
    var multilineCommentOpen = false;
    source[n] = source[n].trim();
    // Don't count enpty lines
    if(source[n].length === 0) continue;
    // Remove single-line comments
    if(ignoreComments && source[n].substr(0, 2) == '//') continue;
    // Remove multi-line comments
    if(ignoreComments && source[n].substr(0, 2) == '/*' && !multilineCommentOpen) multilineCommentOpen = true;
    if(ignoreComments && source[n].substr(-2) == '*/' && multilineCommentOpen) multilineCommentOpen = false;
    if(multilineCommentOpen) continue;
    // Finally count the line
    sloc++;
  }
  return sloc;
};

var displayResults = function(totalSLOCs, fileSLOCs) {
  console.log('SLOC count per file:');
  for(var key in fileSLOCs) {
    console.log();
    console.log(' File: %s', path.relative(__dirname, key));
    console.log(' SLOC: %d', fileSLOCs[key].toLocaleString());
  }
  console.log();
  console.log('Total SLOC count: %s', totalSLOCs.toLocaleString());
};

program
    .version(pkg.version)
    .description('Simple Single Lines Of Code (SLOC) counter tool')
    .option('-d, --dir <dir>', 'directory to walk (default is current directory)', resolveAndNormalizePath, __dirname)
    .option('-i, --ignorecomments', 'ignore comments in SLOC count')
    .option('-r, --recurive', 'enable recursive directory walking')
    .option('-f, --filter [filters]', 'filter by file type (e.g. \'js,css\')', splitFilterList)
    .parse(process.argv);

// First make sure we selected a valid directory
if(!dirExists(program.dir)) {
  return console.error('Error: Specified directory is not a directory or doesn\'t exist!');
}

if(!program.recurive) walkNotRecursive(program.dir, processFiles);
else walkRecursive(program.dir, processFiles);
<|endoftext|>


<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you 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.
 */

/*global $, Config */

var sakai = sakai || {};

/**
 * @name sakai.creategroup
 *
 * @class creategroup
 *
 * @description
 * Creategroup widget
 *
 * @version 0.0.1
 * @param {String} tuid Unique id of the widget
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.creategroup = function(tuid, showSettings){

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    var MAX_LENGTH = 30;

    // - ID
    var createGroup = "#creategroup";

    // Container
    var createGroupContainer = createGroup + "_container";

    // template containers
    var createGroupAddTemplate = "#noncourse_template_container";

    // Non course
    var createGroupAdd = createGroup + "_add";
    var createGroupAddCancel = createGroupAdd + "_cancel";
    var createGroupAddDescription = createGroupAdd + "_description";
    var createGroupAddId = createGroupAdd + "_id";
    var createGroupAddName = createGroupAdd + "_name";
    var createGroupAddProcess = createGroupAdd + "_process";
    var createGroupAddSave = createGroupAdd + "_save";
    var createGroupAddUrl = createGroupAdd + "_url";
    var createGroupAddUrlLength = createGroupAddUrl + "_length";
    var createGroupAddUrlMaxLength = createGroupAddUrl + "_max_length";

    // Error fields
    var createGroupAddNameEmpty = createGroupAddName + "_empty";
    var createGroupAddNameShort = createGroupAddName + "_short";
    var createGroupAddIdEmpty = createGroupAddId + "_empty";
    var createGroupAddIdTaken = createGroupAddId + "_taken";
    var createGroupAddIdShort = createGroupAddId + "_short";
    var errorFields = ".creategroup_error_msg";

    // CSS Classes
    var invalidFieldClass = "invalid";

    // Pages to be added to the group
    var pagestemplate = "defaultgroup";

    ///////////////////////
    // Utility functions //
    ///////////////////////

    /**
     * Public function that can be called from elsewhere
     * (e.g. chat and sites widget)
     * It initializes the creategroup widget and shows the jqmodal (ligthbox)
     */
    sakai.creategroup.initialise = function(){
        $(".description_fields").show();
        $("#creategroup_add_container").show();
        $(createGroupAddTemplate).show();
        $(createGroupAddName).val("");
        $(createGroupAddDescription).val("");
        $(createGroupAddId).val("");
        $(createGroupContainer).jqmShow();
    };

    /**
     * Show or hide the process div and hide/shows the buttons
     * @param {Boolean} show
     *     true: show the process div and hide the buttons
     *     false: hide the process div and show the buttons
     */
    var showProcess = function(show){
        if(show){
            $(createGroupAddCancel).hide();
            $(createGroupAddSave).hide();
            $(createGroupAddProcess).show();
        } else {
            $(createGroupAddProcess).hide();
            $(createGroupAddCancel).show();
            $(createGroupAddSave).show();
        }
    };

    /**
     * Replace or remove malicious characters from the string
     * We use this function to modify the groupid
     * String to test against: test :?=&;\/?@+$<>#%'"''{}|\\^[]'
     * @param {Object} input The string where the characters need to be replaced
     */
    var replaceCharacters = function(input){

        input = $.trim(input); // Remove the spaces at the beginning and end of the id

        input = input.toLowerCase().replace(/ /g,"-");
        input = input.toLowerCase().replace(/'/g,"");
        input = input.toLowerCase().replace(/"/g,"");

        var regexp = new RegExp("[^a-z0-9-]", "gi");
        input = input.replace(regexp,"-");

        return input;
    };

    ////////////////////
    // Error handling //
    ////////////////////

    var resetErrorFields = function(){
        $("input").removeClass(invalidFieldClass);
        $("textarea").removeClass(invalidFieldClass);
        $(errorFields).hide();
    };

    /**
     * Function that will visually mark a form field as an
     * invalid field.
     * @param String field
     *  JQuery selector of the input box we want to show as invalid
     * @param String errorField
     *  JQuery selector of the error message that needs to be shown.
     * @param boolean noReset
     *  Parameter that specifies whether we need to make all of the
     *  fiels valid again first
     */
    var setError = function(field,errorField, noReset){
        if (!noReset) {
            resetErrorFields();
        }
        $(field).addClass(invalidFieldClass);
        $(errorField).show();
    };

    var myClose = function(hash) {
        resetErrorFields();
        hash.o.remove();
        hash.w.hide();
    };

    ///////////////////
    // Create a group //
    ///////////////////

    /**
     * Check if the group is created correctly and exists
     * @param {String} groupid
     */
    var doCheckGroup = function(groupid){
        // Check if the group exists.
        var groupExists = false;

        $.ajax({
            url: "/~" + groupid + ".json",
            type: "GET",
            async: false,
            success: function(data, textStatus){
                groupExists = true;
            }
        });
        return groupExists;
    };

    /**
     * Create the group.
     * @param {String} groupid the id of the group that's being created
     * @param {String} grouptitle the title of the group that's being created
     * @param {String} groupdescription the description of the group that's being created
     * @param {String} groupidManagers the id of the managers group for the group that's being created
    */
    var doSaveGroup = function(groupid, grouptitle, groupdescription){
    // Create a group with the managers group

        $.ajax({
            url: sakai.config.URL.GROUP_CREATE_SERVICE,
            data: {
                "_charset_":"utf-8",
                ":name": groupid,
                ":sakai:manager": sakai.data.me.user.userid,
                "sakai:group-title" : grouptitle,
                "sakai:group-description" : groupdescription,
                "sakai:group-id": groupid,
                ":sakai:pages-template": "/var/templates/site/" + pagestemplate,
                "sakai:pages-visible": sakai.config.Permissions.Groups.visible["public"]
            },
            type: "POST",
            success: function(data, textStatus){
                // check if the group exists
                if (doCheckGroup(groupid)) {
                    // set default permissions for this group
                    sakai.api.Groups.setPermissions(groupid,
                        sakai.config.Permissions.Groups.joinable.manager_add,
                        sakai.config.Permissions.Groups.visible["public"],
                        function (success, errorMessage) {
                            if(success) {
                                // show the group
                                document.location = "/~" + groupid;
                            } else {
                                fluid.log("creategroup.js doSaveGroup failed to set group permissions: " + errorMessage);
                            }
                        });
                }
            },
            error: function(xhr, textStatus, thrownError){
                var groupCheck = doCheckGroup(groupid);
                if (groupCheck){
                    setError(createGroupAddId,createGroupAddIdTaken,true);
                } else {
                    fluid.log("An error has occurred: " + xhr.status + " " + xhr.statusText);
                }
                showProcess(false);
            }
        });
    };

    var saveGroup = function(){
        resetErrorFields();

        // Get the values from the input text and radio fields
        var grouptitle = $(createGroupAddName).val() || "";
        var groupdescription = $(createGroupAddDescription).val() || "";
        var groupid = replaceCharacters($(createGroupAddId).val());
        var inputError = false;

        // Check if there is a group id or group title defined
        if (grouptitle === "")
        {
            setError(createGroupAddName,createGroupAddNameEmpty,true);
            inputError = true;
        } else if (grouptitle.length < 3) {
            setError(createGroupAddName,createGroupAddNameShort,true);
            inputError = true;
        }
        if (!groupid)
        {
            setError(createGroupAddId,createGroupAddIdEmpty,true);
            inputError = true;
        } else if (groupid.length < 3) {
            setError(createGroupAddId,createGroupAddIdShort,true);
            inputError = true;
        }

        if (inputError)
        {
            return;
        }
        else
        {
            // Hide the buttons and show the process status
            showProcess(true);
            doSaveGroup(groupid, grouptitle, groupdescription);
        }
    };

    ////////////////////
    // Event Handlers //
    ////////////////////

    /*
     * Add jqModal functionality to the container.
     * This makes use of the jqModal (jQuery Modal) plugin that provides support
     * for lightboxes
     */
    $(createGroupContainer).jqm({
        modal: true,
        overlay: 20,
        toTop: true,
        onHide: myClose
    });

    /*
     * Add binding to the save button (create the group when you click on it)
     */
    $(createGroupAddSave).click(function(){
        var entered = replaceCharacters($(createGroupAddId).val());
        $(createGroupAddId).val(entered);
        saveGroup();
    });

    /*
     * When you change something in the name of the group, it first removes the bad characters
     * and then it shows the edited url in the span
     */
    $(createGroupAddName + "," + createGroupAddId).bind("keyup", function(ev){
        var entered = replaceCharacters($(this).val());
        $(createGroupAddId).val(entered);
    });

    /////////////////////////////
    // Initialisation function //
    /////////////////////////////

    var doInit = function(){

        // Hide error fields at start
        $(errorFields).hide();

        // Set the text of the span containing the url of the current group
        // e.g. http://celestine.caret.local:8080/~
        var url = document.location.protocol + "//" + document.location.host;
        url += "/~";
        // get max length value
        var maxLength = parseInt(MAX_LENGTH,10);

        // get length

        // if url is too long greater than 30 character
        // show only first 15 characters +...+ last 15 characters
        // e.g.http://sakai3-demo.uits.indiana.edu:8080/~
        // it will change to shorter form:
        // http://sakai3-...diana.edu:8080/~
        if (url.length > maxLength) {
            url = url.substr(0,15)+ "..."+ url.substr(url.length-15,url.length-1);
        }

        $(createGroupAddUrl).text(sakai.api.Security.saneHTML(url));
    };

    doInit();
};

sakai.api.Widgets.widgetLoader.informOnLoad("creategroup");<commit_msg>SAKIII-916: Replacing - with _ for all replaced characters<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you 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.
 */

/*global $, Config */

var sakai = sakai || {};

/**
 * @name sakai.creategroup
 *
 * @class creategroup
 *
 * @description
 * Creategroup widget
 *
 * @version 0.0.1
 * @param {String} tuid Unique id of the widget
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.creategroup = function(tuid, showSettings){

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    var MAX_LENGTH = 30;

    // - ID
    var createGroup = "#creategroup";

    // Container
    var createGroupContainer = createGroup + "_container";

    // template containers
    var createGroupAddTemplate = "#noncourse_template_container";

    // Non course
    var createGroupAdd = createGroup + "_add";
    var createGroupAddCancel = createGroupAdd + "_cancel";
    var createGroupAddDescription = createGroupAdd + "_description";
    var createGroupAddId = createGroupAdd + "_id";
    var createGroupAddName = createGroupAdd + "_name";
    var createGroupAddProcess = createGroupAdd + "_process";
    var createGroupAddSave = createGroupAdd + "_save";
    var createGroupAddUrl = createGroupAdd + "_url";
    var createGroupAddUrlLength = createGroupAddUrl + "_length";
    var createGroupAddUrlMaxLength = createGroupAddUrl + "_max_length";

    // Error fields
    var createGroupAddNameEmpty = createGroupAddName + "_empty";
    var createGroupAddNameShort = createGroupAddName + "_short";
    var createGroupAddIdEmpty = createGroupAddId + "_empty";
    var createGroupAddIdTaken = createGroupAddId + "_taken";
    var createGroupAddIdShort = createGroupAddId + "_short";
    var errorFields = ".creategroup_error_msg";

    // CSS Classes
    var invalidFieldClass = "invalid";

    // Pages to be added to the group
    var pagestemplate = "defaultgroup";

    ///////////////////////
    // Utility functions //
    ///////////////////////

    /**
     * Public function that can be called from elsewhere
     * (e.g. chat and sites widget)
     * It initializes the creategroup widget and shows the jqmodal (ligthbox)
     */
    sakai.creategroup.initialise = function(){
        $(".description_fields").show();
        $("#creategroup_add_container").show();
        $(createGroupAddTemplate).show();
        $(createGroupAddName).val("");
        $(createGroupAddDescription).val("");
        $(createGroupAddId).val("");
        $(createGroupContainer).jqmShow();
    };

    /**
     * Show or hide the process div and hide/shows the buttons
     * @param {Boolean} show
     *     true: show the process div and hide the buttons
     *     false: hide the process div and show the buttons
     */
    var showProcess = function(show){
        if(show){
            $(createGroupAddCancel).hide();
            $(createGroupAddSave).hide();
            $(createGroupAddProcess).show();
        } else {
            $(createGroupAddProcess).hide();
            $(createGroupAddCancel).show();
            $(createGroupAddSave).show();
        }
    };

    /**
     * Replace or remove malicious characters from the string
     * We use this function to modify the groupid
     * String to test against: test :?=&;\/?@+$<>#%'"''{}|\\^[]'
     * @param {Object} input The string where the characters need to be replaced
     */
    var replaceCharacters = function(input){

        input = $.trim(input); // Remove the spaces at the beginning and end of the id

        input = input.toLowerCase().replace(/ /g,"-");
        input = input.toLowerCase().replace(/'/g,"");
        input = input.toLowerCase().replace(/"/g,"");

        var regexp = new RegExp("[^a-z0-9]", "gi");
        input = input.replace(regexp,"_");

        return input;
    };

    ////////////////////
    // Error handling //
    ////////////////////

    var resetErrorFields = function(){
        $("input").removeClass(invalidFieldClass);
        $("textarea").removeClass(invalidFieldClass);
        $(errorFields).hide();
    };

    /**
     * Function that will visually mark a form field as an
     * invalid field.
     * @param String field
     *  JQuery selector of the input box we want to show as invalid
     * @param String errorField
     *  JQuery selector of the error message that needs to be shown.
     * @param boolean noReset
     *  Parameter that specifies whether we need to make all of the
     *  fiels valid again first
     */
    var setError = function(field,errorField, noReset){
        if (!noReset) {
            resetErrorFields();
        }
        $(field).addClass(invalidFieldClass);
        $(errorField).show();
    };

    var myClose = function(hash) {
        resetErrorFields();
        hash.o.remove();
        hash.w.hide();
    };

    ///////////////////
    // Create a group //
    ///////////////////

    /**
     * Check if the group is created correctly and exists
     * @param {String} groupid
     */
    var doCheckGroup = function(groupid){
        // Check if the group exists.
        var groupExists = false;

        $.ajax({
            url: "/~" + groupid + ".json",
            type: "GET",
            async: false,
            success: function(data, textStatus){
                groupExists = true;
            }
        });
        return groupExists;
    };

    /**
     * Create the group.
     * @param {String} groupid the id of the group that's being created
     * @param {String} grouptitle the title of the group that's being created
     * @param {String} groupdescription the description of the group that's being created
     * @param {String} groupidManagers the id of the managers group for the group that's being created
    */
    var doSaveGroup = function(groupid, grouptitle, groupdescription){
    // Create a group with the managers group

        $.ajax({
            url: sakai.config.URL.GROUP_CREATE_SERVICE,
            data: {
                "_charset_":"utf-8",
                ":name": groupid,
                ":sakai:manager": sakai.data.me.user.userid,
                "sakai:group-title" : grouptitle,
                "sakai:group-description" : groupdescription,
                "sakai:group-id": groupid,
                ":sakai:pages-template": "/var/templates/site/" + pagestemplate,
                "sakai:pages-visible": sakai.config.Permissions.Groups.visible["public"]
            },
            type: "POST",
            success: function(data, textStatus){
                // check if the group exists
                if (doCheckGroup(groupid)) {
                    // set default permissions for this group
                    sakai.api.Groups.setPermissions(groupid,
                        sakai.config.Permissions.Groups.joinable.manager_add,
                        sakai.config.Permissions.Groups.visible["public"],
                        function (success, errorMessage) {
                            if(success) {
                                // show the group
                                document.location = "/~" + groupid;
                            } else {
                                fluid.log("creategroup.js doSaveGroup failed to set group permissions: " + errorMessage);
                            }
                        });
                }
            },
            error: function(xhr, textStatus, thrownError){
                var groupCheck = doCheckGroup(groupid);
                if (groupCheck){
                    setError(createGroupAddId,createGroupAddIdTaken,true);
                } else {
                    fluid.log("An error has occurred: " + xhr.status + " " + xhr.statusText);
                }
                showProcess(false);
            }
        });
    };

    var saveGroup = function(){
        resetErrorFields();

        // Get the values from the input text and radio fields
        var grouptitle = $(createGroupAddName).val() || "";
        var groupdescription = $(createGroupAddDescription).val() || "";
        var groupid = replaceCharacters($(createGroupAddId).val());
        var inputError = false;

        // Check if there is a group id or group title defined
        if (grouptitle === "")
        {
            setError(createGroupAddName,createGroupAddNameEmpty,true);
            inputError = true;
        } else if (grouptitle.length < 3) {
            setError(createGroupAddName,createGroupAddNameShort,true);
            inputError = true;
        }
        if (!groupid)
        {
            setError(createGroupAddId,createGroupAddIdEmpty,true);
            inputError = true;
        } else if (groupid.length < 3) {
            setError(createGroupAddId,createGroupAddIdShort,true);
            inputError = true;
        }

        if (inputError)
        {
            return;
        }
        else
        {
            // Hide the buttons and show the process status
            showProcess(true);
            doSaveGroup(groupid, grouptitle, groupdescription);
        }
    };

    ////////////////////
    // Event Handlers //
    ////////////////////

    /*
     * Add jqModal functionality to the container.
     * This makes use of the jqModal (jQuery Modal) plugin that provides support
     * for lightboxes
     */
    $(createGroupContainer).jqm({
        modal: true,
        overlay: 20,
        toTop: true,
        onHide: myClose
    });

    /*
     * Add binding to the save button (create the group when you click on it)
     */
    $(createGroupAddSave).click(function(){
        var entered = replaceCharacters($(createGroupAddId).val());
        $(createGroupAddId).val(entered);
        saveGroup();
    });

    /*
     * When you change something in the name of the group, it first removes the bad characters
     * and then it shows the edited url in the span
     */
    $(createGroupAddName + "," + createGroupAddId).bind("keyup", function(ev){
        var entered = replaceCharacters($(this).val());
        $(createGroupAddId).val(entered);
    });

    /////////////////////////////
    // Initialisation function //
    /////////////////////////////

    var doInit = function(){

        // Hide error fields at start
        $(errorFields).hide();

        // Set the text of the span containing the url of the current group
        // e.g. http://celestine.caret.local:8080/~
        var url = document.location.protocol + "//" + document.location.host;
        url += "/~";
        // get max length value
        var maxLength = parseInt(MAX_LENGTH,10);

        // get length

        // if url is too long greater than 30 character
        // show only first 15 characters +...+ last 15 characters
        // e.g.http://sakai3-demo.uits.indiana.edu:8080/~
        // it will change to shorter form:
        // http://sakai3-...diana.edu:8080/~
        if (url.length > maxLength) {
            url = url.substr(0,15)+ "..."+ url.substr(url.length-15,url.length-1);
        }

        $(createGroupAddUrl).text(sakai.api.Security.saneHTML(url));
    };

    doInit();
};

sakai.api.Widgets.widgetLoader.informOnLoad("creategroup");<|endoftext|>