<commit_before>/* *
 * The MIT License
 *
 * Copyright (c) 2015, Patrick Fitzgerald
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

angular.module('ngm.widget.calHeatmap', ['ngm.provider'])
  .config(function(dashboardProvider){
    dashboardProvider
      .widget('calHeatmap', {
        title: 'Calendar Heatmap Panel',
        description: 'Displays a Calendar of Indicators',
        controller: 'calHeatmapCtrl',
        templateUrl: '/scripts/widgets/ngm-calHeatmap/view.html',
        resolve: {
          data: function(ngmData, config){
            if (config.request){
              return ngmData.get(config.request);
            }
          }
        }
      });
  }).controller('calHeatmapCtrl', [
    '$scope',
    '$element',
    '$timeout',
    'data',
    'config',
    function($scope, $element, $timeout, data, config){

      // unique id
      var id = 'ngm-cal-heatmap-' + Math.floor((Math.random()*1000000));
			$scope.sum= function(data){
				var getValue = (Object.values(data));
				temp = 0;
				getValue.forEach(element => {
					temp = element + temp
				});
				return temp;
			}
      // statistics widget default config
      $scope.calHeatmap = {
        id: id,
        options: {
          itemSelector: '#' + id,
          itemName: 'case',
          start: new Date(moment().subtract(1, 'years')),
          range: 12,
          domain: 'month',
          subDomain: 'day',
          cellPadding: 2,
          cellSize: 10,
          tooltip: true,
          weekStartOnMonday: false,
          legend: [20, 40, 60, 80],
          label: {
            position: 'top'
          },
          data: data && data.data ? data.data : {},
          subDomainTitleFormat: {
            filled: "
{count}
 {name} on {date}"
          },
          domainLabelFormat: function(label){
            // format month names
            var display = moment(label).format('MMM');
            return display;
          },
          onComplete: function() {

            // Set 'day' labels for calendar heatmap
            if($scope.calHeatmap.display.dayLabel) {
              $scope.calHeatmap.display.setDayLabel();
            }
            // Set 'year' labels for calendar heatmap
            if($scope.calHeatmap.display.yearLabel) {
              $scope.calHeatmap.display.setYearLabel();
            }
          }
        },
        display: {

          dayLabel: true,

          yearLabel: true,

          setDayLabel: function(){

            // select svg
            var svg = d3.select($scope.calHeatmap.options.itemSelector);

            // offset to cater for year labels
            var yOffset = parseFloat(svg.select('.graph').select('.graph-subdomain-group').attr('y')) + 5;
            var offset = $scope.calHeatmap.options.cellSize + $scope.calHeatmap.options.cellPadding;

            // enables rendering
            $timeout(function(){

              // Update container width
              svg
                .select('.cal-heatmap-container')
                .attr('width', parseFloat(svg.select('.cal-heatmap-container').attr('width')) + offset);

              // Monday
              svg
                .select('.graph')
                .append('text')
                  .attr('class', 'graph-secondary-label')
                  .attr('y', yOffset + offset)
                  .attr('x', 0)
                  .attr('dominant-baseline', 'middle')
                  .text('M');

              // Wednesday
              svg
                .select('.graph')
                .append('text')
                  .attr('class', 'graph-secondary-label')
                  .attr('y', yOffset + (offset)*3)
                  .attr('x', 0)
                  .attr('dominant-baseline', 'middle')
                  .text('W');

              // Friday
              svg
                .select('.graph')
                .append('text')
                  .attr('class', 'graph-secondary-label')
                  .attr('y', yOffset + (offset)*5)
                  .attr('x', 0)
                  .attr('dominant-baseline', 'middle')
                  .text('F');

              // Select and update all calendar blocks to allow for day labels
              svg
                .select('.graph')
                .selectAll('.graph-domain').each(function(d,i) {

                  // select svg calendar block
                  var graph = d3.select(this);
                      graph.attr('x', parseFloat(graph.attr('x')) +  offset);
                      graph.attr('width', parseFloat(graph.attr('width')) +  offset);
                });

              // update cal graph legend
              svg
                .select('.graph-legend')
                .attr('x', parseInt(svg.select('.graph-legend').attr('x')) + offset);

            }, 500);

          },

          // adds a year label on month calendar
          setYearLabel: function(){

            // offset to cater for year labels
            var svg = d3.select($scope.calHeatmap.options.itemSelector);
            var offset = $scope.calHeatmap.options.cellSize + $scope.calHeatmap.options.cellPadding;

            // enable rendering
            $timeout(function(){

              // Select all the calendar elements
              svg
                .select('.graph').selectAll('.graph-domain').each(function(d,i) {

                // select svg calendar block
                var graph = d3.select(this);

                // if its the first month of the year append year label
                if( graph.attr('class').search(' m_1 ') !=-1 ){

                  // Highlight month of Jan label
                  graph.select('.graph-label').attr('class', 'graph-title');

                  // update cal graph position
                  svg
                    .select('.graph')
                    .attr('y', offset);

                  // update cal graph legend
                  svg
                    .select('.graph-legend')
                    .attr('y', parseInt(d3.select('.graph-legend').attr('y')) + offset);

                  // update year label
                  svg
                    .select('.cal-heatmap-container')
                    .attr('height', parseInt(d3.select('.cal-heatmap-container').attr('height')) + offset)
                      .append('text')
                      .attr('class', 'graph-primary-label')
                      .attr('width', 100)
                      .attr('height', 100)
                      .attr('y', 10)
                      .attr('x', parseFloat(graph.attr('x')) + (parseFloat(graph.attr('width')/4)) )
                      .attr('dominant-baseline', 'middle')
                      .text(graph.attr('class').substr(-4));

                }

              });

            }, 600)

          }

		},
		dataLength: data && data.data ? $scope.sum(data.data) : 0,
      };

      // Merge defaults with config
      $scope.calHeatmap = angular.merge({}, $scope.calHeatmap, config);

      // Assign data to calHeatmap
      var cal = new CalHeatMap();
      $timeout(function() {
        cal.init($scope.calHeatmap.options);
      }, 10);

    }
]);

<commit_msg>update ngm-calHeatmap<commit_after>/* *
 * The MIT License
 *
 * Copyright (c) 2015, Patrick Fitzgerald
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

angular.module('ngm.widget.calHeatmap', ['ngm.provider'])
  .config(function(dashboardProvider){
    dashboardProvider
      .widget('calHeatmap', {
        title: 'Calendar Heatmap Panel',
        description: 'Displays a Calendar of Indicators',
        controller: 'calHeatmapCtrl',
        templateUrl: '/scripts/widgets/ngm-calHeatmap/view.html',
        resolve: {
          data: function(ngmData, config){
            if (config.request){
              return ngmData.get(config.request);
            }
          }
        }
      });
  }).controller('calHeatmapCtrl', [
    '$scope',
    '$element',
    '$timeout',
    'data',
    'config',
    function($scope, $element, $timeout, data, config){

      // unique id
      var id = 'ngm-cal-heatmap-' + Math.floor((Math.random()*1000000));
			$scope.sum= function(data){
				var getValue = (Object.values(data));
				temp = 0;
				getValue.forEach(element => {
					temp = element + temp
				});
				return temp;
			}
      // statistics widget default config
      $scope.calHeatmap = {
        id: id,
        options: {
          itemSelector: '#' + id,
          itemName: 'case',
          start: new Date(moment().subtract(1, 'years')),
          range: 12,
          domain: 'month',
          subDomain: 'day',
          cellPadding: 2,
          cellSize: 10,
          tooltip: true,
          weekStartOnMonday: false,
          legend: [20, 40, 60, 80],
          label: {
            position: 'top'
          },
          data: data && data.data ? data.data : {},
          subDomainTitleFormat: {
            filled: "
{count}
 {name} on {date}"
          },
          domainLabelFormat: function(label){
            // format month names
            var display = moment(label).format('MMM');
            return display;
          },
          onComplete: function() {

            // Set 'day' labels for calendar heatmap
            if($scope.calHeatmap.display.dayLabel) {
              $scope.calHeatmap.display.setDayLabel();
            }
            // Set 'year' labels for calendar heatmap
            if($scope.calHeatmap.display.yearLabel) {
              $scope.calHeatmap.display.setYearLabel();
            }
          }
        },
        display: {

          dayLabel: true,

          yearLabel: true,

          setDayLabel: function(){

            // select svg
            var svg = d3.select($scope.calHeatmap.options.itemSelector);

            // offset to cater for year labels
            var yOffset = parseFloat(svg.select('.graph').select('.graph-subdomain-group').attr('y')) + 5;
            var offset = $scope.calHeatmap.options.cellSize + $scope.calHeatmap.options.cellPadding;

            // enables rendering
            $timeout(function(){

              // Update container width
              svg
                .select('.cal-heatmap-container')
                .attr('width', parseFloat(svg.select('.cal-heatmap-container').attr('width')) + offset);

              // Monday
              svg
                .select('.graph')
                .append('text')
                  .attr('class', 'graph-secondary-label')
                  .attr('y', yOffset + offset)
                  .attr('x', 0)
                  .attr('dominant-baseline', 'middle')
                  .text('M');

              // Wednesday
              svg
                .select('.graph')
                .append('text')
                  .attr('class', 'graph-secondary-label')
                  .attr('y', yOffset + (offset)*3)
                  .attr('x', 0)
                  .attr('dominant-baseline', 'middle')
                  .text('W');

              // Friday
              svg
                .select('.graph')
                .append('text')
                  .attr('class', 'graph-secondary-label')
                  .attr('y', yOffset + (offset)*5)
                  .attr('x', 0)
                  .attr('dominant-baseline', 'middle')
                  .text('F');

              // Select and update all calendar blocks to allow for day labels
              svg
                .select('.graph')
                .selectAll('.graph-domain').each(function(d,i) {

                  // select svg calendar block
                  var graph = d3.select(this);
                      graph.attr('x', parseFloat(graph.attr('x')) +  offset);
                      graph.attr('width', parseFloat(graph.attr('width')) +  offset);
                });

              // update cal graph legend
              svg
                .select('.graph-legend')
                .attr('x', parseInt(svg.select('.graph-legend').attr('x')) + offset);

            }, 500);

          },

          // adds a year label on month calendar
          setYearLabel: function(){

            // offset to cater for year labels
            var svg = d3.select($scope.calHeatmap.options.itemSelector);
            var offset = $scope.calHeatmap.options.cellSize + $scope.calHeatmap.options.cellPadding;

            // enable rendering
            $timeout(function(){

              // Select all the calendar elements
              svg
                .select('.graph').selectAll('.graph-domain').each(function(d,i) {

                // select svg calendar block
                var graph = d3.select(this);

                // if its the first month of the year append year label
                if( graph.attr('class').search(' m_1 ') !=-1 ){

                  // Highlight month of Jan label
                  graph.select('.graph-label').attr('class', 'graph-title');

                  // update cal graph position
                  svg
                    .select('.graph')
                    .attr('y', offset);

                  // update cal graph legend
                  svg
                    .select('.graph-legend')
                    .attr('y', parseInt(d3.select('.graph-legend').attr('y')) + offset);

                  // update year label
                  svg
                    .select('.cal-heatmap-container')
                    .attr('height', parseInt(d3.select('.cal-heatmap-container').attr('height')) + offset)
                      .append('text')
                      .attr('class', 'graph-primary-label')
                      .attr('width', 100)
                      .attr('height', 100)
                      .attr('y', 10)
                      .attr('x', parseFloat(graph.attr('x')) + (parseFloat(graph.attr('width')/4)) )
                      .attr('dominant-baseline', 'middle')
                      .text(graph.attr('class').substr(-4));

                }

              });

            }, 600)

          }

		},
		dataLength: data && data.data ? $scope.sum(data.data) : 0,
      };

      // Merge defaults with config
      $scope.calHeatmap = angular.merge({}, $scope.calHeatmap, config);

      // Assign data to calHeatmap
      var cal = new CalHeatMap();
      $timeout(function() {
        cal.init($scope.calHeatmap.options);
      }, 50);

    }
]);

<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.
 */
dojo.provide("goat.elements.RelationshipElement");
dojo.require("dojox.gfx");
dojo.require("dojox.gfx.Moveable");
dojo.require("goat.Component");

//Relationship
// represents a line between two components.
//
//TODO:
// - add methods for pulse & glow.
// - add methods for line offset adjust & reset
//   - this will allow lines to move to rows of a twistie, then snap back
dojo.declare("goat.elements.RelationshipElement", [], {

	//relationship properties.
	fromComponent: null,
	toComponent: null,
	name: null,
	type: null,

	//object properties
	surface: null,
	visible: null,
	typeOffset: 0,

	//gfx objects
	line: null,

	// helper elements
	decorators: null,

	//internals
	stroke: null,

	//am I deleted?
	removed: false,

	//for the up and coming relationship aspect info..
	aspects: null,

	subs: null,

constructor: function(surface, name, type, fromComponent, toComponent, aspects) {
	this.surface=surface;

	//console.log("Building relationship elt for "+name+" "+type+" from "+fromComponent.id+" to "+toComponent.id+" ");

	this.name=name;
    this.type=type;
	this.fromComponent=fromComponent;
	this.toComponent=toComponent;
	this.aspects=aspects;

	this.stroke = '#000000';
    this.setStroke();
	this.updateVisibility();

	this.subs=new Array();
	this.subs.push(dojo.subscribe("goat.component.move."+fromComponent.id, this, this.onComponentMove));
	this.subs.push(dojo.subscribe("goat.component.move."+toComponent.id, this, this.onComponentMove));
	this.subs.push(dojo.subscribe("goat.component.hidden."+fromComponent.id, this, this.onComponentHidden));
	this.subs.push(dojo.subscribe("goat.component.hidden."+toComponent.id, this, this.onComponentHidden));
	this.subs.push(dojo.subscribe("goat.component.onclick."+toComponent.id, this, this.onComponentClick));
	this.subs.push(dojo.subscribe("goat.component.onclick."+fromComponent.id, this, this.onComponentClick));

    // When a component is resized teh relationship line needs to be re-drawn.
    this.subs.push(dojo.subscribe("goat.component.resize."+fromComponent.id, this, this.onComponentResize));
    this.subs.push(dojo.subscribe("goat.component.resize."+toComponent.id, this, this.onComponentResize));

	this.decorators = new Array();

	console.log("Publishing relationship create to event", fromComponent.id, toComponent.id);
	dojo.publish("goat.relationship.create."+fromComponent.id,[this]);
	dojo.publish("goat.relationship.create."+toComponent.id,[this]);
},
addDecorator: function(decorator) {
	decorator.setStroke(this.stroke);
	this.decorators.push(decorator);
},
setStroke: function(){

	//right idea.. wrong approach.. this needs the registry to provide the renderer for this relationship type.

	if(this.type=="packageImport"){
		this.typeOffset=5;
		this.stroke = '#F08080';
	}else if(this.type=="packageExport"){
		this.stroke = '#80F080';
		this.typeOffset=-5;
	}else if(this.type=="serviceExport"){
		this.stroke = '#F080F0';
		this.typeOffset=10;
	}else if(this.type=="serviceImport"){
		this.stroke = '#8080F0';
		this.typeOffset=-10;
	}
},
updateVisibility: function(){
	//if(this.removed){
		//console.log("uv EEK.. this line should be dead.. and its aliiiiiive "+this.type+" from "+this.fromComponent.id+" to "+this.toComponent.id);
		//console.log(this);
	//}

	// Visible will be 'true' only if both componenets are not hidden (ie visible)
	this.visible = (!this.fromComponent.hidden) && (!this.toComponent.hidden);

	if(!this.visible){
		if(this.line!=null){
		//No need to hide a line that doesn't exist.
			this.line.setShape({x1: -1000, y1: -1000, x2: -1000, y2: -1000});

            //console.log("Hiding decorators..");
			dojo.forEach(this.decorators, function(decorator){
                //console.log("Hiding decorator..");
				decorator.makeInvisible();
			},this);

		}
	}else{
		this.updateLine();
	}
},
updateLine: function(){
	if(this.removed){
		console.log("ul EEK.. this line should be dead.. and its aliiiiiive "+this.type+" from "+this.fromComponent.id+" to "+this.toComponent.id);
		console.log(this);
	}

	if(this.visible){
        //console.log("Updating VISIBLE line from "+this.fromComponent.id+" to "+this.toComponent.id);
		var fromx = this.fromComponent.x + (this.fromComponent.width / 2) + this.typeOffset;
		var fromy = this.fromComponent.y + (this.fromComponent.height / 2)+ this.typeOffset;
		var tox = this.toComponent.x + (this.toComponent.width / 2)+ this.typeOffset;
		var toy = this.toComponent.y + (this.toComponent.height / 2)+ this.typeOffset;

		if(this.line==null){
			this.line = this.surface.createLine({x1: fromx, y1: fromy, x2: tox, y2: toy})
		            .setStroke(this.stroke);
		}else{
			this.line.setShape({x1: fromx, y1: fromy, x2: tox, y2: toy});
		}

        if (this.decorators != null) {
            dojo.forEach(this.decorators, function(decorator){
                decorator.lineUpdated(this.line);
            },this);
        }

        // Our line should be underneath any decorations
        this.line.moveToBack();
	}

},
removeSelf: function(){
	//console.log("Line from "+this.fromComponent.id+" to "+this.toComponent.id+" being removed");
    //console.log(this);
	if(!this.removed){
		this.removed = true;

		//console.log("Line from "+this.fromComponent.id+" to "+this.toComponent.id+" being removed from surface");
        //console.log(this);
        if(this.line!=null) {
            this.surface.remove(this.line);
        }

        //console.log("Removing decorators..");
		dojo.forEach(this.decorators, function(decorator){
            //console.log("Asking...");
            //console.log(decorator);
            //console.log("..to remove itself");
			decorator.removeSelf();
		});
        this.decorators = new Array();
		//console.log("Line from "+this.fromComponent.id+" to "+this.toComponent.id+" being marked as deleted");

		//console.log("Removing line subscriptions to components.");
		dojo.forEach(this.subs, function(sub){
			//console.log("unsubscribing.. ");
			//console.log(sub);
			dojo.unsubscribe(sub);
		});

		this.subs = new Array();

		dojo.publish("goat.relationship.remove."+this.fromComponent.id,[this]);
	}else{
		console.log("Line from "+this.fromComponent.id+" to "+this.toComponent.id+" already marked as deleted");
	}
},
getKey: function(){
	var key = ""+this.fromComponent.id+"!"+this.toComponent.id+"!"+this.type+"!"+this.name;
},
onComponentMove: function(component){
	this.updateLine();
},
onComponentResize: function(component){
	this.updateLine();
},
onComponentHidden: function(component){
	this.updateVisibility();
},
onComponentClick: function(component){
    //console.log("OnClick "+component.id);
	if(this.removed){
		console.log("occ EEK.. this line should be dead.. and its aliiiiiive "+this.type+" from "+this.fromComponent.id+" to "+this.toComponent.id);
	}

    if(this.line!=null) {
        dojox.gfx.fx.animateStroke({
            shape: this.line,
            duration: 500,
            color: {start: "#FF3030", end: this.stroke},
            width: {start: 3, end: 2},
            join:  {values: ["miter", "bevel", "round"]}
        }).play();
    }
}
});
<commit_msg>ARIES-319 Step one towards moving element appearance to a configuration file<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.
 */
dojo.provide("goat.elements.RelationshipElement");
dojo.require("dojox.gfx");
dojo.require("dojox.gfx.Moveable");
dojo.require("goat.Component");

//Relationship
// represents a line between two components.
//
//TODO:
// - add methods for pulse & glow.
// - add methods for line offset adjust & reset
//   - this will allow lines to move to rows of a twistie, then snap back
dojo.declare("goat.elements.RelationshipElement", [], {

	//relationship properties.
	fromComponent: null,
	toComponent: null,
	name: null,
	type: null,

	//object properties
	surface: null,
	visible: null,
	typeOffset: 0,

	//gfx objects
	line: null,

	// helper elements
	decorators: null,

	//internals
	stroke: null,

	//am I deleted?
	removed: false,

	//for the up and coming relationship aspect info..
	aspects: null,

	subs: null,

constructor: function(surface, name, type, fromComponent, toComponent, aspects) {
	this.surface=surface;

	//console.log("Building relationship elt for "+name+" "+type+" from "+fromComponent.id+" to "+toComponent.id+" ");

	this.name=name;
    this.type=type;
	this.fromComponent=fromComponent;
	this.toComponent=toComponent;
	this.aspects=aspects;

	//This need to be replaced with a call to a configuration/theme
	this.stroke = '#6D7B8D';

	this.updateVisibility();

	this.subs=new Array();
	this.subs.push(dojo.subscribe("goat.component.move."+fromComponent.id, this, this.onComponentMove));
	this.subs.push(dojo.subscribe("goat.component.move."+toComponent.id, this, this.onComponentMove));
	this.subs.push(dojo.subscribe("goat.component.hidden."+fromComponent.id, this, this.onComponentHidden));
	this.subs.push(dojo.subscribe("goat.component.hidden."+toComponent.id, this, this.onComponentHidden));
	this.subs.push(dojo.subscribe("goat.component.onclick."+toComponent.id, this, this.onComponentClick));
	this.subs.push(dojo.subscribe("goat.component.onclick."+fromComponent.id, this, this.onComponentClick));

    // When a component is resized teh relationship line needs to be re-drawn.
    this.subs.push(dojo.subscribe("goat.component.resize."+fromComponent.id, this, this.onComponentResize));
    this.subs.push(dojo.subscribe("goat.component.resize."+toComponent.id, this, this.onComponentResize));

	this.decorators = new Array();

	console.log("Publishing relationship create to event", fromComponent.id, toComponent.id);
	dojo.publish("goat.relationship.create."+fromComponent.id,[this]);
	dojo.publish("goat.relationship.create."+toComponent.id,[this]);

},
addDecorator: function(decorator) {
	decorator.setStroke(this.stroke);
	this.decorators.push(decorator);
},
updateVisibility: function(){
	//if(this.removed){
		//console.log("uv EEK.. this line should be dead.. and its aliiiiiive "+this.type+" from "+this.fromComponent.id+" to "+this.toComponent.id);
		//console.log(this);
	//}

	// Visible will be 'true' only if both componenets are not hidden (ie visible)
	this.visible = (!this.fromComponent.hidden) && (!this.toComponent.hidden);

	if(!this.visible){
		if(this.line!=null){
		//No need to hide a line that doesn't exist.
			this.line.setShape({x1: -1000, y1: -1000, x2: -1000, y2: -1000});

            //console.log("Hiding decorators..");
			dojo.forEach(this.decorators, function(decorator){
                //console.log("Hiding decorator..");
				decorator.makeInvisible();
			},this);

		}
	}else{
		this.updateLine();
	}
},
updateLine: function(){
	if(this.removed){
		console.log("ul EEK.. this line should be dead.. and its aliiiiiive "+this.type+" from "+this.fromComponent.id+" to "+this.toComponent.id);
		console.log(this);
	}

	if(this.visible){
        //console.log("Updating VISIBLE line from "+this.fromComponent.id+" to "+this.toComponent.id);
		var fromx = this.fromComponent.x + (this.fromComponent.width / 2);
		var fromy = this.fromComponent.y + (this.fromComponent.height / 2);
		var tox = this.toComponent.x + (this.toComponent.width / 2);
		var toy = this.toComponent.y + (this.toComponent.height / 2);

		if(this.line==null){
			this.line = this.surface.createLine({x1: fromx, y1: fromy, x2: tox, y2: toy})
		            .setStroke(this.stroke);
		}else{
			this.line.setShape({x1: fromx, y1: fromy, x2: tox, y2: toy});
		}

        if (this.decorators != null) {
            dojo.forEach(this.decorators, function(decorator){
                decorator.lineUpdated(this.line);
            },this);
        }

        // Our line should be underneath any decorations
        this.line.moveToBack();
	}

},
removeSelf: function(){
	//console.log("Line from "+this.fromComponent.id+" to "+this.toComponent.id+" being removed");
    //console.log(this);
	if(!this.removed){
		this.removed = true;

		//console.log("Line from "+this.fromComponent.id+" to "+this.toComponent.id+" being removed from surface");
        //console.log(this);
        if(this.line!=null) {
            this.surface.remove(this.line);
        }

        //console.log("Removing decorators..");
		dojo.forEach(this.decorators, function(decorator){
            //console.log("Asking...");
            //console.log(decorator);
            //console.log("..to remove itself");
			decorator.removeSelf();
		});
        this.decorators = new Array();
		//console.log("Line from "+this.fromComponent.id+" to "+this.toComponent.id+" being marked as deleted");

		//console.log("Removing line subscriptions to components.");
		dojo.forEach(this.subs, function(sub){
			//console.log("unsubscribing.. ");
			//console.log(sub);
			dojo.unsubscribe(sub);
		});

		this.subs = new Array();

		dojo.publish("goat.relationship.remove."+this.fromComponent.id,[this]);
	}else{
		console.log("Line from "+this.fromComponent.id+" to "+this.toComponent.id+" already marked as deleted");
	}
},
getKey: function(){
	var key = ""+this.fromComponent.id+"!"+this.toComponent.id+"!"+this.type+"!"+this.name;
},
onComponentMove: function(component){
	this.updateLine();
},
onComponentResize: function(component){
	this.updateLine();
},
onComponentHidden: function(component){
	this.updateVisibility();
},
onComponentClick: function(component){
    //console.log("OnClick "+component.id);
	if(this.removed){
		console.log("occ EEK.. this line should be dead.. and its aliiiiiive "+this.type+" from "+this.fromComponent.id+" to "+this.toComponent.id);
	}

    if(this.line!=null) {
        dojox.gfx.fx.animateStroke({
            shape: this.line,
            duration: 500,
            color: {start: "#FF3030", end: this.stroke},
            width: {start: 3, end: 2},
            join:  {values: ["miter", "bevel", "round"]}
        }).play();
    }
}
});
<|endoftext|>


<commit_before>'use strict';

let angular = require('angular');

module.exports = angular
  .module('spinnaker.netflix.alert.handler', [
    require('../../core/config/settings.js'),
    require('../../core/authentication/authentication.service.js'),
  ])
  .config(function ($provide) {
    $provide.decorator('$exceptionHandler', function($delegate, settings, authenticationService, $, $log) {
      return function(exception, cause) {
        $delegate(exception, cause);
        if (!settings.alert) {
          return;
        }
        let message = exception.message;
        if (!message) {
          try {
            message = JSON.stringify(exception);
          } catch (e) {
            message = '[No message available - could not convert exception to JSON string]';
          }
        }
        let payload = {
          alertName: 'Spinnaker',
          details: {
            url: location.href,
            user: authenticationService.getAuthenticatedUser().name,
          },
          exception: {
            classes: [exception.name || '[no name on exception]'],
            messages: [message],
            stackTraces: [exception.stack || '[no stacktrace available]'],
            callerClass: 'Spinnaker',
            callerMethod: '[see stack trace]',
          },
          actions: [
            {
              action: 'email',
              suppressTimeSecs: settings.alert.throttleInSeconds,
              to: settings.alert.recipients,
              subject: settings.alert.subject || '[Spinnaker] Error in Deck',
              htmlTemplate: settings.alert.template || 'spinnaker_deck_error',
              incidentKey: exception.message,
            }
          ],
        };
        if (navigator.sendBeacon) {
          navigator.sendBeacon(settings.alert.url, JSON.stringify(payload));
        } else {
          $log.warn('no beacon support :(');
          $.post(settings.alert.url, JSON.stringify(payload));
        }
      };
    });
  });
<commit_msg>include version in error payload (#2314)<commit_after>'use strict';

let angular = require('angular');

module.exports = angular
  .module('spinnaker.netflix.alert.handler', [
    require('../../core/config/settings.js'),
    require('../../core/authentication/authentication.service.js'),
  ])
  .config(function ($provide) {
    $provide.decorator('$exceptionHandler', function($delegate, settings, authenticationService, $, $log) {
      let currentVersion = require('../../../../../version.json');

      return function(exception, cause) {
        $delegate(exception, cause);
        if (!settings.alert) {
          return;
        }
        let message = exception.message;
        if (!message) {
          try {
            message = JSON.stringify(exception);
          } catch (e) {
            message = '[No message available - could not convert exception to JSON string]';
          }
        }
        let payload = {
          alertName: 'Spinnaker',
          details: {
            url: location.href,
            user: authenticationService.getAuthenticatedUser().name,
            version: currentVersion.version,
          },
          exception: {
            classes: [exception.name || '[no name on exception]'],
            messages: [message],
            stackTraces: [exception.stack || '[no stacktrace available]'],
            callerClass: 'Spinnaker',
            callerMethod: '[see stack trace]',
          },
          actions: [
            {
              action: 'email',
              suppressTimeSecs: settings.alert.throttleInSeconds,
              to: settings.alert.recipients,
              subject: settings.alert.subject || '[Spinnaker] Error in Deck',
              htmlTemplate: settings.alert.template || 'spinnaker_deck_error',
              incidentKey: exception.message,
            }
          ],
        };
        if (navigator.sendBeacon) {
          navigator.sendBeacon(settings.alert.url, JSON.stringify(payload));
        } else {
          $log.warn('no beacon support :(');
          $.post(settings.alert.url, JSON.stringify(payload));
        }
      };
    });
  });
<|endoftext|>


<commit_before>// Add table of contents in English here
function addContents(context) {
    addArticle(context, "../../../", "Home");
    addArticle(context, "software-overview/", "Software Overview");
    addArticle(context, "software-overview/software-purpose.html", "Software Purpose and Characteristics", 1);
    addArticle(context, "software-overview/software-architecture.html", "Software Architecture", 1);
    addArticle(context, "software-overview/applications/", "Description of Applications", 1);
    addArticle(context, "software-overview/applications/server-application.html", "Server Application", 2);
    addArticle(context, "software-overview/applications/communicator-application.html", "Communicator Application", 2);
    addArticle(context, "software-overview/applications/webstation-application.html", "Webstation Application", 2);
    addArticle(context, "software-overview/applications/administrator-application.html", "Administrator Application", 2);
    addArticle(context, "software-overview/applications/table-editor-application.html", "Table Editor Application", 2);
    addArticle(context, "software-overview/applications/scheme-editor-application.html", "Scheme Editor Application", 2);
    addArticle(context, "software-overview/roadmap.html", "Roadmap", 1);

    //addArticle(context, "installation-and-run/", "Installation and Run");
    //addArticle(context, "installation-and-run/system-requirements.html", "System Requirements", 1);
    //addArticle(context, "installation-and-run/software-installation.html", "Software Installation", 1);
    //addArticle(context, "installation-and-run/manual-installation.html", "Manual Installation", 1);
    //addArticle(context, "installation-and-run/module-installation.html", "Installation of Additional Modules", 1);
    //addArticle(context, "installation-and-run/run-applications.html", "Run Applications", 1);
    //addArticle(context, "installation-and-run/migrate-configuration.html", "Migrate Configuration to New Server", 1);
    //addArticle(context, "installation-and-run/software-update.html", "Software Update", 1);

    addArticle(context, "software-configuration/", "Configuración de software");
    addArticle(context, "software-configuration/configuration-basics.html", "Conceptos básicos de configuración", 1);
    addArticle(context, "software-configuration/tune-database.html", "Sincronización de la base de datos de configuración", 1);
    addArticle(context, "software-configuration/using-formulas.html", "Uso de fórmulas", 1);
    addArticle(context, "software-configuration/user-authentication.html", "Configuración de autenticación del usuario", 1);
    addArticle(context, "software-configuration/communication-with-devices.html", "Configuración de comunicación con dispositivos", 1);
    addArticle(context, "software-configuration/creating-views.html", "Creación de vistas", 1);

    addArticle(context, "modules/", "Modules");
    addArticle(context, "modules/kp-telegram.html", "Telegram Driver", 1);
    addArticle(context, "modules/mod-auto-control.html", "Automatic Control Module", 1);
    addArticle(context, "modules/mod-db-export.html", "Export to Database Module", 1);
    addArticle(context, "modules/mod-rapid-gate.html", "Rapid Gate Module", 1);
    addArticle(context, "modules/plg-chart-pro.html", "Chart Pro Plugin", 1);
    addArticle(context, "modules/plg-dashboard.html", "Dashboard Plugin", 1);
    addArticle(context, "modules/plg-elastic-report.html", "Elastic Report Plugin", 1);
    addArticle(context, "modules/plg-map.html", "Map Plugin", 1);

    //addArticle(context, "use-cases/", "Use Cases");
    //addArticle(context, "use-cases/modbus-protocol.html", "Connecting Devices Using Modbus Protocol", 1);
    //addArticle(context, "use-cases/opc-standard.html", "Connecting Devices Using OPC Standard", 1);
    //addArticle(context, "use-cases/remote-server-management.html", "Managing Remote Server using Agent", 1);

    //addArticle(context, "version-history/", "Version History");
    //addArticle(context, "version-history/scada-history.html", "History of Rapid SCADA", 1);
    //addArticle(context, "version-history/server-history.html", "Server History", 1);
    //addArticle(context, "version-history/server-modules-history.html", "History of Server Modules", 1);
    //addArticle(context, "version-history/communicator-history.html", "Communicator History", 1);
    //addArticle(context, "version-history/communicator-drivers-history.html", "History of Communicator Drivers", 1);
    //addArticle(context, "version-history/administrator-history.html", "Administrator History", 1);
    //addArticle(context, "version-history/webstation-history.html", "Webstation History", 1);
    //addArticle(context, "version-history/webstation-plugins-history.html", "History of Webstation Plugins", 1);
    //addArticle(context, "version-history/table-editor-history.html", "Table Editor History", 1);
    //addArticle(context, "version-history/scheme-editor-history.html", "Scheme Editor History", 1);
}
<commit_msg>Update contents.js<commit_after>// Add table of contents in English here
function addContents(context) {
    addArticle(context, "../../../", "Home");
    addArticle(context, "software-overview/", "Software Overview");
    addArticle(context, "software-overview/software-purpose.html", "Software Purpose and Characteristics", 1);
    addArticle(context, "software-overview/software-architecture.html", "Software Architecture", 1);
    addArticle(context, "software-overview/applications/", "Description of Applications", 1);
    addArticle(context, "software-overview/applications/server-application.html", "Server Application", 2);
    addArticle(context, "software-overview/applications/communicator-application.html", "Communicator Application", 2);
    addArticle(context, "software-overview/applications/webstation-application.html", "Webstation Application", 2);
    addArticle(context, "software-overview/applications/administrator-application.html", "Administrator Application", 2);
    addArticle(context, "software-overview/applications/table-editor-application.html", "Table Editor Application", 2);
    addArticle(context, "software-overview/applications/scheme-editor-application.html", "Scheme Editor Application", 2);
    addArticle(context, "software-overview/roadmap.html", "Roadmap", 1);

    //addArticle(context, "installation-and-run/", "Installation and Run");
    //addArticle(context, "installation-and-run/system-requirements.html", "System Requirements", 1);
    //addArticle(context, "installation-and-run/software-installation.html", "Software Installation", 1);
    //addArticle(context, "installation-and-run/manual-installation.html", "Manual Installation", 1);
    //addArticle(context, "installation-and-run/module-installation.html", "Installation of Additional Modules", 1);
    //addArticle(context, "installation-and-run/run-applications.html", "Run Applications", 1);
    //addArticle(context, "installation-and-run/migrate-configuration.html", "Migrate Configuration to New Server", 1);
    //addArticle(context, "installation-and-run/software-update.html", "Software Update", 1);

    addArticle(context, "software-configuration/", "Configuración de software");
    addArticle(context, "software-configuration/configuration-basics.html", "Conceptos básicos de configuración", 1);
    addArticle(context, "software-configuration/tune-database.html", "Afinación de la base de datos de configuración", 1);
    addArticle(context, "software-configuration/using-formulas.html", "Uso de fórmulas", 1);
    addArticle(context, "software-configuration/user-authentication.html", "Configuración de autenticación del usuario", 1);
    addArticle(context, "software-configuration/communication-with-devices.html", "Configuración de comunicación con dispositivos", 1);
    addArticle(context, "software-configuration/creating-views.html", "Creación de vistas", 1);

    addArticle(context, "modules/", "Modules");
    addArticle(context, "modules/kp-telegram.html", "Telegram Driver", 1);
    addArticle(context, "modules/mod-auto-control.html", "Automatic Control Module", 1);
    addArticle(context, "modules/mod-db-export.html", "Export to Database Module", 1);
    addArticle(context, "modules/mod-rapid-gate.html", "Rapid Gate Module", 1);
    addArticle(context, "modules/plg-chart-pro.html", "Chart Pro Plugin", 1);
    addArticle(context, "modules/plg-dashboard.html", "Dashboard Plugin", 1);
    addArticle(context, "modules/plg-elastic-report.html", "Elastic Report Plugin", 1);
    addArticle(context, "modules/plg-map.html", "Map Plugin", 1);

    //addArticle(context, "use-cases/", "Use Cases");
    //addArticle(context, "use-cases/modbus-protocol.html", "Connecting Devices Using Modbus Protocol", 1);
    //addArticle(context, "use-cases/opc-standard.html", "Connecting Devices Using OPC Standard", 1);
    //addArticle(context, "use-cases/remote-server-management.html", "Managing Remote Server using Agent", 1);

    //addArticle(context, "version-history/", "Version History");
    //addArticle(context, "version-history/scada-history.html", "History of Rapid SCADA", 1);
    //addArticle(context, "version-history/server-history.html", "Server History", 1);
    //addArticle(context, "version-history/server-modules-history.html", "History of Server Modules", 1);
    //addArticle(context, "version-history/communicator-history.html", "Communicator History", 1);
    //addArticle(context, "version-history/communicator-drivers-history.html", "History of Communicator Drivers", 1);
    //addArticle(context, "version-history/administrator-history.html", "Administrator History", 1);
    //addArticle(context, "version-history/webstation-history.html", "Webstation History", 1);
    //addArticle(context, "version-history/webstation-plugins-history.html", "History of Webstation Plugins", 1);
    //addArticle(context, "version-history/table-editor-history.html", "Table Editor History", 1);
    //addArticle(context, "version-history/scheme-editor-history.html", "Scheme Editor History", 1);
}
<|endoftext|>


<commit_before>/**
 * @overview new ccm component for submits
 * @author André Kless <andre.kless@web.de> 2017
 * @author Manfred Kaul <manfred.kaul@h-brs.de> 2017
 * @license The MIT License (MIT)
 */

( function () {

  let component = {

    name: 'submit',

    ccm: '//akless.github.io/ccm/version/ccm-10.0.0.min.js',
    // ccm: '//akless.github.io/ccm/ccm.js',

    config: {

      "data": { "store": [ "ccm.store" ] },

    //  "onfinish": { "log": true }

    },

    Instance: function () {

      // collect all ccm input elements in the array called inputs
      let inputs = [];

      this.ready = ( callback ) => {

        // iterate all input elements
        [...this.inner.querySelectorAll( 'input' )].map( ( input ) => {

          let type = input.getAttribute( 'type' );
          switch ( type ) {
            case 'button':
            case 'checkbox':
            case 'color':
            case 'date':
            case 'datetime-local':
            case 'email':
            case 'file':
            case 'hidden':
            case 'image':
            case 'month':
            case 'number':
            case 'password':
            case 'radio':
            case 'range':
            case 'reset':
            case 'search':
            case 'submit':
            case 'tel':
            case 'text':
            case 'time':
            case 'url':
            case 'week':
              break; // do not touch standard HTML input elements

            default: // manage ccm subbcomponents

              // check whether there is a dependent subcomponent in this config
              if ( !this[ type ] ) return;

              // create a loading symbol
              let loading = this.ccm.helper.loading( this );

              // remember this loading element, the type und the name of this input element
              inputs.push( {
                elem: loading,
                type: type,
                name: input.name
              } );

              // replace
              input.parentNode.replaceChild( loading, input );

          }

        } );

        callback();
      };

      this.start = ( callback ) => {

        // make lightDOM the shadowDOM
        this.ccm.helper.setContent( this.element, this.inner );

        let submit = this.element.querySelector( '#submit' );

        // submit button is disabled until all subcomponents are ready
        submit.disabled = true;
        submit.addEventListener( 'click', () => {

          // fetch values from input elements
          // let record = this.ccm.helper.formData( this.element );
          let record = formData( this.element );

          // fetch values from ccm subcomponents
          // getValue ist the standard API for ccm subcomponents
          inputs.map( ( input ) => {
            record[ input.name ] = input.instance.getValue();
          } );

          // submit collected form data to database
          if ( this.onfinish ) this.ccm.helper.onFinish( this, record );

        } );

        // fetch data from database and write values into the input elements of this form
        this.ccm.helper.dataset( this.data, ( dataset ) => {

          // fill all input elements with values from dataset identified by name
          for ( key in dataset ) {
            //
            let input = this.element.querySelector( '[name="'+key+'"]' );
            if ( input ) input.value = dataset[ key ];
          }

          // count all started instances of ccm subbcomponents
          let counter = 1;

          // iterate over all ccm subbcomponents
          inputs.map( ( input ) => {
            counter++;

            // fork all ccm subbcomponents in parallel
            this[ input.type ].start( { 'data.key': input.name }, ( instance ) => {
              input.instance = instance;
              input.elem.parentNode.replaceChild( instance.root, input.elem );
              check();
            } );
          } );
          check();

          // join all started ccm subbcomponents in parallel
          function check() {

            counter--;
            if ( counter > 0 ) return;

            // submit button is enabled when all subcomponents are ready
            submit.disabled = false;

            // callback after join all parallel subcomponents start
            if ( callback ) callback();
          }

        } );

        // ccm.helper.formData
        function formData( form ) {

          let data = {};

          [ ...form.querySelectorAll( '*[name]' ) ].map( function ( input ) {

            if ( input.type === 'radio' ) {

              if ( input.checked ) data[ input.name ] = input.value;

            } else if ( input.type === 'checkbox' ){

              if ( ! data[ input.name ] ) data[ input.name ] = [];
              if ( input.checked ) data[ input.name ].push( input.value );

            } else if ( input.type.startsWith('select') ){

              data[ input.name ] = [ ...input.options ]
                .filter( option => option.selected )
                .map( option => option.value || option.text );

            } else if ( input.type === 'number' ) {

              let value = parseInt( input.value );
              if ( isNaN( value ) ) value = '';
              data[ input.name ] = value;

            }

            else if ( !input.value )
              data[ input.name ] = '';
            else
              data[ input.name ] = input.value;
          } );
          return data;
        }

      };

    }

  };

  function p(){window.ccm[v].component(component)}var f="ccm."+component.name+(component.version?"-"+component.version.join("."):"")+".js";if(window.ccm&&null===window.ccm.files[f])window.ccm.files[f]=component;else{var n=window.ccm&&window.ccm.components[component.name];n&&n.ccm&&(component.ccm=n.ccm),"string"==typeof component.ccm&&(component.ccm={url:component.ccm});var v=component.ccm.url.split("/").pop().split("-");if(v.length>1?(v=v[1].split("."),v.pop(),"min"===v[v.length-1]&&v.pop(),v=v.join(".")):v="latest",window.ccm&&window.ccm[v])p();else{var e=document.createElement("script");document.head.appendChild(e),component.ccm.integrity&&e.setAttribute("integrity",component.ccm.integrity),component.ccm.crossorigin&&e.setAttribute("crossorigin",component.ccm.crossorigin),e.onload=function(){p(),document.head.removeChild(e)},e.src=component.ccm.url}}
}() );<commit_msg>const instead of let<commit_after>/**
 * @overview new ccm component for submits
 * @author André Kless <andre.kless@web.de> 2017
 * @author Manfred Kaul <manfred.kaul@h-brs.de> 2017
 * @license The MIT License (MIT)
 */

( function () {

  const component = {

    name: 'submit',

    ccm: '//akless.github.io/ccm/version/ccm-10.0.0.min.js',
    // ccm: '//akless.github.io/ccm/ccm.js',

    config: {

      "data": { "store": [ "ccm.store" ] },

    //  "onfinish": { "log": true }

    },

    Instance: function () {

      // collect all ccm input elements in the array called inputs
      const inputs = [];

      this.ready = ( callback ) => {

        // iterate all input elements
        [...this.inner.querySelectorAll( 'input' )].map( ( input ) => {

          const type = input.getAttribute( 'type' );
          switch ( type ) {
            case 'button':
            case 'checkbox':
            case 'color':
            case 'date':
            case 'datetime-local':
            case 'email':
            case 'file':
            case 'hidden':
            case 'image':
            case 'month':
            case 'number':
            case 'password':
            case 'radio':
            case 'range':
            case 'reset':
            case 'search':
            case 'submit':
            case 'tel':
            case 'text':
            case 'time':
            case 'url':
            case 'week':
              break; // do not touch standard HTML input elements

            default: // manage ccm subbcomponents

              // check whether there is a dependent subcomponent in this config
              if ( !this[ type ] ) return;

              // create a loading symbol
              let loading = this.ccm.helper.loading( this );

              // remember this loading element, the type und the name of this input element
              inputs.push( {
                elem: loading,
                type: type,
                name: input.name
              } );

              // replace
              input.parentNode.replaceChild( loading, input );

          }

        } );

        callback();
      };

      this.start = ( callback ) => {

        // put LightDOM into ShadowDOM
        this.ccm.helper.setContent( this.element, this.inner );

        const submit = this.element.querySelector( '#submit' );

        // submit button is disabled until all subcomponents are ready
        submit.disabled = true;
        submit.addEventListener( 'click', () => {

          // fetch values from input elements
          // let record = this.ccm.helper.formData( this.element );
          const record = formData( this.element );

          // fetch values from ccm subcomponents
          // getValue ist the standard API for ccm subcomponents
          inputs.map( ( input ) => {
            record[ input.name ] = input.instance.getValue();
          } );

          // submit collected form data to database
          if ( this.onfinish ) this.ccm.helper.onFinish( this, record );

        } );

        // fetch data from database and write values into the input elements of this form
        this.ccm.helper.dataset( this.data, ( dataset ) => {

          // fill all input elements with values from dataset identified by name
          for ( key in dataset ) {
            //
            const input = this.element.querySelector( '[name="'+key+'"]' );
            if ( input ) input.value = dataset[ key ];
          }

          // count all started instances of ccm subbcomponents
          let counter = 1;

          // iterate over all ccm subbcomponents
          inputs.map( ( input ) => {
            counter++;

            // fork all ccm subbcomponents in parallel
            this[ input.type ].start( { 'data.key': input.name }, ( instance ) => {
              input.instance = instance;
              input.elem.parentNode.replaceChild( instance.root, input.elem );
              check();
            } );
          } );
          check();

          // join all started ccm subbcomponents in parallel
          function check() {

            counter--;
            if ( counter > 0 ) return;

            // submit button is enabled when all subcomponents are ready
            submit.disabled = false;

            // callback after join all parallel subcomponents start
            if ( callback ) callback();
          }

        } );

        // ccm.helper.formData
        function formData( form ) {

          const data = {};

          [ ...form.querySelectorAll( '*[name]' ) ].map( function ( input ) {

            if ( input.type === 'radio' ) {

              if ( input.checked ) data[ input.name ] = input.value;

            } else if ( input.type === 'checkbox' ){

              if ( ! data[ input.name ] ) data[ input.name ] = [];
              if ( input.checked ) data[ input.name ].push( input.value );

            } else if ( input.type.startsWith('select') ){

              data[ input.name ] = [ ...input.options ]
                .filter( option => option.selected )
                .map( option => option.value || option.text );

            } else if ( input.type === 'number' ) {

              let value = parseInt( input.value );
              if ( isNaN( value ) ) value = '';
              data[ input.name ] = value;

            }

            else if ( !input.value )
              data[ input.name ] = '';
            else
              data[ input.name ] = input.value;
          } );
          return data;
        }

      };

    }

  };

  function p(){window.ccm[v].component(component)}var f="ccm."+component.name+(component.version?"-"+component.version.join("."):"")+".js";if(window.ccm&&null===window.ccm.files[f])window.ccm.files[f]=component;else{var n=window.ccm&&window.ccm.components[component.name];n&&n.ccm&&(component.ccm=n.ccm),"string"==typeof component.ccm&&(component.ccm={url:component.ccm});var v=component.ccm.url.split("/").pop().split("-");if(v.length>1?(v=v[1].split("."),v.pop(),"min"===v[v.length-1]&&v.pop(),v=v.join(".")):v="latest",window.ccm&&window.ccm[v])p();else{var e=document.createElement("script");document.head.appendChild(e),component.ccm.integrity&&e.setAttribute("integrity",component.ccm.integrity),component.ccm.crossorigin&&e.setAttribute("crossorigin",component.ccm.crossorigin),e.onload=function(){p(),document.head.removeChild(e)},e.src=component.ccm.url}}
}() );<|endoftext|>


<commit_before>/**
 * Editor Image plugin.
 */

module.exports = {

    plugin: true,

    created: function () {

        var vm = this, editor = this.$parent.editor;

        if (!editor || !editor.htmleditor) {
            return;
        }

        this.images = [];

        editor
            .off('action.image')
            .on('action.image', function (e, editor) {
                vm.openModal(_.find(vm.images, function (img) {
                    return img.inRange(editor.getCursor());
                }));
            })
            .on('render', function () {
                var regexp = editor.getMode() != 'gfm' ? /<img(.+?)>/gi : /(?:<img(.+?)>|!(?:\[([^\n\]]*)])(?:\(([^\n\]]*?)\))?)/gi;
                vm.images = editor.replaceInPreview(regexp, vm.replaceInPreview);
            })
            .on('renderLate', function () {

                while (vm.$children.length) {
                    vm.$children[0].$destroy();
                }

                Vue.nextTick(function () {
                    editor.preview.find('image-preview').each(function () {
                        vm.$compile(this);
                    });
                });
            });
    },

    methods: {

        openModal: function (image) {

            var editor = this.$parent.editor, cursor = editor.editor.getCursor();

            if (!image) {
                image = {
                    replace: function (value) {
                        editor.editor.replaceRange(value, cursor);
                    }
                };
            }

            new this.$parent.$options.utils['image-picker']({
                parent: this,
                data: {
                    image: image
                }
            }).$mount()
                .$appendTo('body')
                .$on('select', function (image) {

                    var content;

                    if ((image.tag || editor.getCursorMode()) == 'html') {

                        if (!image.anchor) {
                            image.anchor = $('<img>');
                        }

                        image.anchor.attr('src', image.data.src);
                        image.anchor.attr('alt', image.data.alt);

                        content = image.anchor[0].outerHTML;

                    } else {
                        content = '![' + image.data.alt + '](' + image.data.src + ')';
                    }

                    image.replace(content);
                });
        },

        replaceInPreview: function (data, index) {

            data.data = {};
            if (data.matches[0][0] == '<') {
                data.anchor = $(data.matches[0]);
                data.data.src = data.anchor.attr('src');
                data.data.alt = data.anchor.attr('alt');
                data.tag = 'html';
            } else {
                data.data.src = data.matches[3];
                data.data.alt = data.matches[2];
                data.tag = 'gfm';
            }

            return '<image-preview index="' + index + '"></image-preview>';
        }

    },

    components: {

        'image-preview': require('./image-preview.vue')

    }

};
<commit_msg>image picker - use DOMParser in order to prevent the browser from loading resources<commit_after>/**
 * Editor Image plugin.
 */

module.exports = {

    plugin: true,

    created: function () {

        var vm = this, editor = this.$parent.editor;

        if (!editor || !editor.htmleditor) {
            return;
        }

        this.images = [];

        editor
            .off('action.image')
            .on('action.image', function (e, editor) {
                vm.openModal(_.find(vm.images, function (img) {
                    return img.inRange(editor.getCursor());
                }));
            })
            .on('render', function () {
                var regexp = editor.getMode() != 'gfm' ? /<img(.+?)>/gi : /(?:<img(.+?)>|!(?:\[([^\n\]]*)])(?:\(([^\n\]]*?)\))?)/gi;
                vm.images = editor.replaceInPreview(regexp, vm.replaceInPreview);
            })
            .on('renderLate', function () {

                while (vm.$children.length) {
                    vm.$children[0].$destroy();
                }

                Vue.nextTick(function () {
                    editor.preview.find('image-preview').each(function () {
                        vm.$compile(this);
                    });
                });
            });
    },

    methods: {

        openModal: function (image) {

            var parser = new DOMParser(), editor = this.$parent.editor, cursor = editor.editor.getCursor();

            if (!image) {
                image = {
                    replace: function (value) {
                        editor.editor.replaceRange(value, cursor);
                    }
                };
            }

            new this.$parent.$options.utils['image-picker']({
                parent: this,
                data: {
                    image: image
                }
            }).$mount()
                .$appendTo('body')
                .$on('select', function (image) {

                    var content;

                    if ((image.tag || editor.getCursorMode()) == 'html') {

                        if (!image.anchor) {
                            image.anchor = parser.parseFromString(data.matches[0], "text/html").body.childNodes[0];;
                        }

                        image.anchor.setAttribute('src', image.data.src);
                        image.anchor.setAttribute('alt', image.data.alt);

                        content = image.anchor.outerHTML;

                    } else {
                        content = '![' + image.data.alt + '](' + image.data.src + ')';
                    }

                    image.replace(content);
                });
        },

        replaceInPreview: function (data, index) {
            var parser = new DOMParser();

            data.data = {};
            if (data.matches[0][0] == '<') {
                data.anchor = parser.parseFromString(data.matches[0], "text/html").body.childNodes[0];
                data.data.src = data.anchor.attributes.src ? data.anchor.attributes.src.nodeValue : '';
                data.data.alt = data.anchor.attributes.alt ? data.anchor.attributes.alt.nodeValue : '';
                data.tag = 'html';
            } else {
                data.data.src = data.matches[3];
                data.data.alt = data.matches[2];
                data.tag = 'gfm';
            }

            return '<image-preview index="' + index + '"></image-preview>';
        }

    },

    components: {

        'image-preview': require('./image-preview.vue')

    }

};
<|endoftext|>


<commit_before><commit_msg>optimized to handle edge cases<commit_after><|endoftext|>


<commit_before>//======================================================================================================================
// Controllers for Basic Table for Webble World v3.0 (2013)
// Created By: truemrwalker
//======================================================================================================================

//=======================================================================================
// WEBBLE CONTROLLER
// This is the Main controller for this Webble Template
//=======================================================================================
wblwrld3App.controller('tableCtrl', function($scope, $log, Slot, Enum) {

    //=== PROPERTIES ====================================================================
    $scope.stylesToSlots = {
        tableContainer: ['background-color'],
        tableroot: ['color', 'font-family'],//, 'border', 'background-color'],
        tableheader: ['background-color', 'color', 'font-family', 'font-size']
    };

    $scope.customMenu = [
        {itemId: 'addRow', itemTxt: 'Add Row'},
        {itemId: 'removeRow', itemTxt: 'Delete Row'},
        {itemId: 'addCol', itemTxt: 'Add Column'},
        {itemId: 'removeCol', itemTxt: 'Delete Column'}
    ];

    $scope.headersOrder = ["Name", "Surname", "City"];

    $scope.initHeadersOrder = "Name,Surname,City";
    $scope.initTableData = {
        tabledata: [
            {Name: "Hans", Surname: "Mueller", City: "Leipzig"},
            {Name: "Dieter", Surname: "Zumpe", City: "Berlin"},
            {Name: "Bernd", Surname: "Danau", City: "Muenchen"}
        ]
    };
    var blankTable = {tabledata: []};

    var latestClick = {
        rowIndex: undefined,
        colName: undefined
    };

    $scope.cellEdit = {
        cellName: "",
        newVal: "",
        colIndex: undefined
    };

    //=== EVENT HANDLERS ================================================================

    //========================================================================================
    // Change Sorting
    //========================================================================================
    $scope.changeSorting = function(column, index) {
        if($scope.cellEdit.cellName != index + '_-1'){
            var rowOrderBy = $scope.gimme('rowOrderBy');
            var rowOrderIsReversed = $scope.gimme('rowOrderIsReversed');
            if (rowOrderBy == column) {
                $scope.set('rowOrderIsReversed', !rowOrderIsReversed);
            } else {
                $scope.set('rowOrderBy', column);
                $scope.set('rowOrderIsReversed', false);
            }
        }
    };
    //========================================================================================

    //========================================================================================
    // Remember Click
    //========================================================================================
    $scope.rememberClick = function(rowIndex, colName){
        if($scope.ctrlKeyIsDown){
            var newCellName = colName + '_' + rowIndex;
            if($scope.cellEdit.cellName == newCellName){
                $scope.cellEdit.cellName = "";
            }
            else{
                $scope.cellEdit.cellName = newCellName;
            }
			$scope.set('selectedCellContent', '');
        }
		else{
			if($scope.cellEdit.cellName == ""){
				var thePrevCell = $scope.theView.parent().find("#" + latestClick.colName + "_" + latestClick.rowIndex);
				var theCurrentCell = $scope.theView.parent().find("#" + colName + "_" + rowIndex);
				thePrevCell.css('border', $scope.gimme('tableBorder'));
				theCurrentCell.css('border', $scope.gimme('selectedCellBorder'));
				$scope.set('selectedCellContent', theCurrentCell.find("span").text());
			}
		}

        latestClick.rowIndex = rowIndex;
        latestClick.colName = colName;
    };
    //========================================================================================

    //========================================================================================
    // Close Edit
    //========================================================================================
    $scope.closeEdit = function(){
        $scope.cellEdit.cellName = "";
    };
    //========================================================================================

    //=== METHODS & FUNCTIONS ===========================================================

    //===================================================================================
    // Webble template Initialization
    //===================================================================================
    $scope.coreCall_Init = function(theInitWblDef){

		$scope.registerWWEventListener(Enum.availableWWEvents.slotChanged, function(eventData){
			var newVal = eventData.slotValue;
			if(eventData.slotName == 'headersOrder'){
				if(newVal.search){
					var workList = [];
					if(newVal.search(';') != -1){
						workList = newVal.split(';');
					}
					else if(newVal.search(',') != -1){
						workList = newVal.split(',');
					}

					else if(newVal.search(' ') != -1){
						workList = newVal.split(' ');
					}

					if(workList.length > 0){
						$scope.headersOrder = [];
						for(var i = 0; i < workList.length; i++){
							$scope.headersOrder.push(workList[i]);
						}
					}
					else{
						$scope.headersOrder = [];
					}
				}
			}
			else if(eventData.slotName == 'tableData'){
				if(!newVal.tabledata){
					$scope.set('tableData', blankTable);
				}
			}
		});

        $scope.addSlot(new Slot('tableData',
            $scope.initTableData,
            'Table Data',
            'A json string that describes the data to be displayed in the table. Example: {tabledata: [{COLUMN-NAME: "ROW-VALUE"}]}',
            $scope.theWblMetadata['templateid'],
            {inputType: Enum.aopInputTypes.TextArea},
            undefined
        ));

        $scope.addSlot(new Slot('headersOrder',
            $scope.initHeadersOrder,
            'Headers Order',
            'An ordered list of the table headers inside hard brackets [] and divide with comma',
            $scope.theWblMetadata['templateid'],
            {inputType: Enum.aopInputTypes.TextBox},
            undefined
        ));

        $scope.addSlot(new Slot('rowOrderBy',
            $scope.headersOrder[0],
            'Row Order By',
            'Which column to order the rows in the table by',
            $scope.theWblMetadata['templateid'],
            undefined,
            undefined
        ));

        $scope.addSlot(new Slot('rowOrderIsReversed',
            false,
            'Row Order Is Reversed',
            'If checked the order of the rows sorted by the selected column is reversed',
            $scope.theWblMetadata['templateid'],
            undefined,
            undefined
        ));

        $scope.addSlot(new Slot('evenRowBkgColor',
            '#EAF2D3',
            'Even Row Background Color',
            'the background color of every even row',
            $scope.theWblMetadata['templateid'],
            {inputType: Enum.aopInputTypes.ColorPick},
            undefined
        ));

        $scope.addSlot(new Slot('oddRowBkgColor',
            '#ffffff',
            'Odd Row Background Color',
            'the background color of every odd row',
            $scope.theWblMetadata['templateid'],
            {inputType: Enum.aopInputTypes.ColorPick},
            undefined
        ));

        $scope.addSlot(new Slot('tableBorder',
            '1px solid #98bf21',
            'Table Border',
            'the border of the table',
            $scope.theWblMetadata['templateid'],
            undefined,
            undefined
        ));

        $scope.addSlot(new Slot('cellFontSize',
            '1em',
            'Cell Fontsize',
            'the font size of the text in the table cells',
            $scope.theWblMetadata['templateid'],
            undefined,
            undefined
        ));

        $scope.addSlot(new Slot('cellPadding',
            '3px 15px 2px 7px',
            'Cell Padding',
            'the top - left - bottom - right padding of the cell in relation to the content',
            $scope.theWblMetadata['templateid'],
            undefined,
            undefined
        ));

		$scope.addSlot(new Slot('selectedCellBorder',
			'2px solid orange',
			'Selected Cell Border',
			'The border for a selected cell (clicked on)',
			$scope.theWblMetadata['templateid'],
			undefined,
			undefined
		));

		$scope.addSlot(new Slot('selectedCellContent',
			'',
			'Selected Cell Content',
			'The content of the current selected cell',
			$scope.theWblMetadata['templateid'],
			undefined,
			undefined
		));

        $scope.$watch(function(){return $scope.cellEdit.cellName;}, function(newVal, oldVal) {
            if($scope.cellEdit.newVal != $scope.headersOrder[$scope.cellEdit.colIndex]){
                var hoSlot = angular.copy($scope.gimme('headersOrder'));
                var workList = [];
                if(hoSlot.search){
                    if(hoSlot.search(';') != -1){
                        workList = hoSlot.split(';');
                    }
                    else if(hoSlot.search(',') != -1){
                        workList = hoSlot.split(',');
                    }
                    else if(hoSlot.search(' ') != -1){
                        workList = hoSlot.split(' ');
                    }
                }

                var changed = {old: workList[$scope.cellEdit.colIndex], new: $scope.cellEdit.newVal};
                workList[$scope.cellEdit.colIndex] = $scope.cellEdit.newVal;
                $scope.set('headersOrder', workList.toString());

                var tData = angular.copy($scope.gimme('tableData')) ;
                for(var i = 0; i < tData.tabledata.length; i++){
                    var val = tData.tabledata[i][changed.old];
                    tData.tabledata[i][changed.new] = val;
                    delete tData.tabledata[i][changed.old];
                }
                $scope.set('tableData', tData);
            }

            var s = parseInt(newVal.substr(0, newVal.indexOf('_')));
            if(!isNaN(s)){
                $scope.cellEdit.newVal = $scope.headersOrder[s];
                $scope.cellEdit.colIndex = s;
            }
            else{
                $scope.cellEdit.newVal = "";
                $scope.cellEdit.colIndex = undefined;
            }
        }, true);
    };
    //===================================================================================

    //===================================================================================
    // Webble template Menu Item Activity Reaction
    // If this template has its own custom menu items that needs to be taken care of,
    // then it is here where that should be executed.
    // If this function is empty and unused it can safely be deleted.
    //===================================================================================
    $scope.coreCall_Event_WblMenuActivityReaction = function(itemName){

        if(itemName == $scope.customMenu[0].itemId){  //add row
            var newRow = {};
            for(var i = 0; i < $scope.headersOrder.length; i++){
                newRow[$scope.headersOrder[i]] = "---";
            }

            var tData = angular.copy($scope.gimme('tableData')) ;
            tData.tabledata.push(newRow);
            $scope.set('tableData', tData);
        }
        if(itemName == $scope.customMenu[1].itemId){  //delete row
            var tData = angular.copy($scope.gimme('tableData')) ;
            tData.tabledata.splice(latestClick.rowIndex, 1);
            $scope.set('tableData', tData);
        }
        if(itemName == $scope.customMenu[2].itemId){  //add col
            var hData = angular.copy($scope.headersOrder);
            var colName = 'COL_' + (hData.length + 1);
            hData.push(colName);
            $scope.set('headersOrder', hData.toString());

            var tData = angular.copy($scope.gimme('tableData')) ;
            for(var i = 0; i < tData.tabledata.length; i++){
                tData.tabledata[i][colName] = "---";
            }
            $scope.set('tableData', tData);
        }
        if(itemName == $scope.customMenu[3].itemId){  //delete col
            var colIndex;
            for(var i = 0; i < $scope.headersOrder.length; i++){
                if($scope.headersOrder[i] == latestClick.colName){
                    colIndex = i;
                    break;
                }
            }

            if(colIndex){
                var hData = angular.copy($scope.headersOrder) ;
                hData.splice(colIndex, 1);
                $scope.set('headersOrder', hData.toString());

                var tData = angular.copy($scope.gimme('tableData')) ;
                for(var i = 0; i < tData.tabledata.length; i++){
                    delete tData.tabledata[i][latestClick.colName];
                }
                $scope.set('tableData', tData);
            }
        }
    };
    //===================================================================================

    //========================================================================================
    // Get Row Background Color
    //========================================================================================
    $scope.getRowBkgColor = function(rowIndex){
        if((rowIndex+1)%2 == 0){
            return $scope.gimme('evenRowBkgColor');
        }
        else{
            return $scope.gimme('oddRowBkgColor');
        }
    };
    //========================================================================================

    //=== CTRL MAIN CODE ======================================================================

});
//=======================================================================================
<commit_msg>Fixed a bug when deleting a row in the BasicTable Webble so that it is now deleting the correctly selected row.<commit_after>//======================================================================================================================
// Controllers for Basic Table for Webble World v3.0 (2013)
// Created By: truemrwalker
//======================================================================================================================

//=======================================================================================
// WEBBLE CONTROLLER
// This is the Main controller for this Webble Template
//=======================================================================================
wblwrld3App.controller('tableCtrl', function($scope, $log, Slot, Enum) {

    //=== PROPERTIES ====================================================================
    $scope.stylesToSlots = {
        tableContainer: ['background-color'],
        tableroot: ['color', 'font-family'],//, 'border', 'background-color'],
        tableheader: ['background-color', 'color', 'font-family', 'font-size']
    };

    $scope.customMenu = [
        {itemId: 'addRow', itemTxt: 'Add Row'},
        {itemId: 'removeRow', itemTxt: 'Delete Row'},
        {itemId: 'addCol', itemTxt: 'Add Column'},
        {itemId: 'removeCol', itemTxt: 'Delete Column'}
    ];

    $scope.headersOrder = ["Name", "Surname", "City"];

    $scope.initHeadersOrder = "Name,Surname,City";
    $scope.initTableData = {
        tabledata: [
            {Name: "Hans", Surname: "Mueller", City: "Leipzig"},
            {Name: "Dieter", Surname: "Zumpe", City: "Berlin"},
            {Name: "Bernd", Surname: "Danau", City: "Muenchen"}
        ]
    };
    var blankTable = {tabledata: []};

    var latestClick = {
        rowIndex: undefined,
        colName: undefined
    };

    $scope.cellEdit = {
        cellName: "",
        newVal: "",
        colIndex: undefined
    };

    //=== EVENT HANDLERS ================================================================

    //========================================================================================
    // Change Sorting
    //========================================================================================
    $scope.changeSorting = function(column, index) {
        if($scope.cellEdit.cellName != index + '_-1'){
            var rowOrderBy = $scope.gimme('rowOrderBy');
            var rowOrderIsReversed = $scope.gimme('rowOrderIsReversed');
            if (rowOrderBy == column) {
                $scope.set('rowOrderIsReversed', !rowOrderIsReversed);
            } else {
                $scope.set('rowOrderBy', column);
                $scope.set('rowOrderIsReversed', false);
            }
        }
    };
    //========================================================================================

    //========================================================================================
    // Remember Click
    //========================================================================================
    $scope.rememberClick = function(rowIndex, colName){
        if($scope.ctrlKeyIsDown){
            var newCellName = colName + '_' + rowIndex;
            if($scope.cellEdit.cellName == newCellName){
                $scope.cellEdit.cellName = "";
            }
            else{
                $scope.cellEdit.cellName = newCellName;
            }
			$scope.set('selectedCellContent', '');
        }
		else{
			if($scope.cellEdit.cellName == ""){
				var thePrevCell = $scope.theView.parent().find("#" + latestClick.colName + "_" + latestClick.rowIndex);
				var theCurrentCell = $scope.theView.parent().find("#" + colName + "_" + rowIndex);
				thePrevCell.css('border', $scope.gimme('tableBorder'));
				theCurrentCell.css('border', $scope.gimme('selectedCellBorder'));
				$scope.set('selectedCellContent', theCurrentCell.find("span").text());
			}
		}

        latestClick.rowIndex = rowIndex;
        latestClick.colName = colName;
    };
    //========================================================================================

    //========================================================================================
    // Close Edit
    //========================================================================================
    $scope.closeEdit = function(){
        $scope.cellEdit.cellName = "";
    };
    //========================================================================================

    //=== METHODS & FUNCTIONS ===========================================================

    //===================================================================================
    // Webble template Initialization
    //===================================================================================
    $scope.coreCall_Init = function(theInitWblDef){

		$scope.registerWWEventListener(Enum.availableWWEvents.slotChanged, function(eventData){
			var newVal = eventData.slotValue;
			if(eventData.slotName == 'headersOrder'){
				if(newVal.search){
					var workList = [];
					if(newVal.search(';') != -1){
						workList = newVal.split(';');
					}
					else if(newVal.search(',') != -1){
						workList = newVal.split(',');
					}

					else if(newVal.search(' ') != -1){
						workList = newVal.split(' ');
					}

					if(workList.length > 0){
						$scope.headersOrder = [];
						for(var i = 0; i < workList.length; i++){
							$scope.headersOrder.push(workList[i]);
						}
					}
					else{
						$scope.headersOrder = [];
					}
				}
			}
			else if(eventData.slotName == 'tableData'){
				if(!newVal.tabledata){
					$scope.set('tableData', blankTable);
				}
			}
		});

        $scope.addSlot(new Slot('tableData',
            $scope.initTableData,
            'Table Data',
            'A json string that describes the data to be displayed in the table. Example: {tabledata: [{COLUMN-NAME: "ROW-VALUE"}]}',
            $scope.theWblMetadata['templateid'],
            {inputType: Enum.aopInputTypes.TextArea},
            undefined
        ));

        $scope.addSlot(new Slot('headersOrder',
            $scope.initHeadersOrder,
            'Headers Order',
            'An ordered list of the table headers inside hard brackets [] and divide with comma',
            $scope.theWblMetadata['templateid'],
            {inputType: Enum.aopInputTypes.TextBox},
            undefined
        ));

        $scope.addSlot(new Slot('rowOrderBy',
            $scope.headersOrder[0],
            'Row Order By',
            'Which column to order the rows in the table by',
            $scope.theWblMetadata['templateid'],
            undefined,
            undefined
        ));

        $scope.addSlot(new Slot('rowOrderIsReversed',
            false,
            'Row Order Is Reversed',
            'If checked the order of the rows sorted by the selected column is reversed',
            $scope.theWblMetadata['templateid'],
            undefined,
            undefined
        ));

        $scope.addSlot(new Slot('evenRowBkgColor',
            '#EAF2D3',
            'Even Row Background Color',
            'the background color of every even row',
            $scope.theWblMetadata['templateid'],
            {inputType: Enum.aopInputTypes.ColorPick},
            undefined
        ));

        $scope.addSlot(new Slot('oddRowBkgColor',
            '#ffffff',
            'Odd Row Background Color',
            'the background color of every odd row',
            $scope.theWblMetadata['templateid'],
            {inputType: Enum.aopInputTypes.ColorPick},
            undefined
        ));

        $scope.addSlot(new Slot('tableBorder',
            '1px solid #98bf21',
            'Table Border',
            'the border of the table',
            $scope.theWblMetadata['templateid'],
            undefined,
            undefined
        ));

        $scope.addSlot(new Slot('cellFontSize',
            '1em',
            'Cell Fontsize',
            'the font size of the text in the table cells',
            $scope.theWblMetadata['templateid'],
            undefined,
            undefined
        ));

        $scope.addSlot(new Slot('cellPadding',
            '3px 15px 2px 7px',
            'Cell Padding',
            'the top - left - bottom - right padding of the cell in relation to the content',
            $scope.theWblMetadata['templateid'],
            undefined,
            undefined
        ));

		$scope.addSlot(new Slot('selectedCellBorder',
			'2px solid orange',
			'Selected Cell Border',
			'The border for a selected cell (clicked on)',
			$scope.theWblMetadata['templateid'],
			undefined,
			undefined
		));

		$scope.addSlot(new Slot('selectedCellContent',
			'',
			'Selected Cell Content',
			'The content of the current selected cell',
			$scope.theWblMetadata['templateid'],
			undefined,
			undefined
		));

        $scope.$watch(function(){return $scope.cellEdit.cellName;}, function(newVal, oldVal) {
            if($scope.cellEdit.newVal != $scope.headersOrder[$scope.cellEdit.colIndex]){
                var hoSlot = angular.copy($scope.gimme('headersOrder'));
                var workList = [];
                if(hoSlot.search){
                    if(hoSlot.search(';') != -1){
                        workList = hoSlot.split(';');
                    }
                    else if(hoSlot.search(',') != -1){
                        workList = hoSlot.split(',');
                    }
                    else if(hoSlot.search(' ') != -1){
                        workList = hoSlot.split(' ');
                    }
                }

                var changed = {old: workList[$scope.cellEdit.colIndex], new: $scope.cellEdit.newVal};
                workList[$scope.cellEdit.colIndex] = $scope.cellEdit.newVal;
                $scope.set('headersOrder', workList.toString());

                var tData = angular.copy($scope.gimme('tableData')) ;
                for(var i = 0; i < tData.tabledata.length; i++){
                    var val = tData.tabledata[i][changed.old];
                    tData.tabledata[i][changed.new] = val;
                    delete tData.tabledata[i][changed.old];
                }
                $scope.set('tableData', tData);
            }

            var s = parseInt(newVal.substr(0, newVal.indexOf('_')));
            if(!isNaN(s)){
                $scope.cellEdit.newVal = $scope.headersOrder[s];
                $scope.cellEdit.colIndex = s;
            }
            else{
                $scope.cellEdit.newVal = "";
                $scope.cellEdit.colIndex = undefined;
            }
        }, true);
    };
    //===================================================================================

	//===================================================================================
	// Compare Values
	// Method that compares the table data object array rows by the current sortBy key
	// ether in ascending or descending order and return the result to the Array Sort
	// method that used it.
	//===================================================================================
	function compareValues(key, order = false) {
		return function innerSort(a, b) {
			if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) {
				// property doesn't exist on either object
				return 0;
			}

			const varA = (typeof a[key] === 'string')
				? a[key].toUpperCase() : a[key];
			const varB = (typeof b[key] === 'string')
				? b[key].toUpperCase() : b[key];

			let comparison = 0;
			if (varA > varB) {
				comparison = 1;
			} else if (varA < varB) {
				comparison = -1;
			}
			return (
				(order === true) ? (comparison * -1) : comparison
			);
		};
	}
	//===================================================================================

    //===================================================================================
    // Webble template Menu Item Activity Reaction
    // If this template has its own custom menu items that needs to be taken care of,
    // then it is here where that should be executed.
    // If this function is empty and unused it can safely be deleted.
    //===================================================================================
    $scope.coreCall_Event_WblMenuActivityReaction = function(itemName){

        if(itemName == $scope.customMenu[0].itemId){  //add row
            var newRow = {};
            for(var i = 0; i < $scope.headersOrder.length; i++){
                newRow[$scope.headersOrder[i]] = "---";
            }

            var tData = angular.copy($scope.gimme('tableData')) ;
            tData.tabledata.push(newRow);
            $scope.set('tableData', tData);
        }
        if(itemName == $scope.customMenu[1].itemId){  //delete row
            var tData = angular.copy($scope.gimme('tableData')) ;
			tData.tabledata.sort(compareValues($scope.gimme('rowOrderBy'), $scope.gimme('rowOrderIsReversed')));
			tData.tabledata.splice(latestClick.rowIndex, 1);
            $scope.set('tableData', tData);
        }
        if(itemName == $scope.customMenu[2].itemId){  //add col
            var hData = angular.copy($scope.headersOrder);
            var colName = 'COL_' + (hData.length + 1);
            hData.push(colName);
            $scope.set('headersOrder', hData.toString());

            var tData = angular.copy($scope.gimme('tableData')) ;
            for(var i = 0; i < tData.tabledata.length; i++){
                tData.tabledata[i][colName] = "---";
            }
            $scope.set('tableData', tData);
        }
        if(itemName == $scope.customMenu[3].itemId){  //delete col
            var colIndex;
            for(var i = 0; i < $scope.headersOrder.length; i++){
                if($scope.headersOrder[i] == latestClick.colName){
                    colIndex = i;
                    break;
                }
            }

            if(colIndex){
                var hData = angular.copy($scope.headersOrder) ;
                hData.splice(colIndex, 1);
                $scope.set('headersOrder', hData.toString());

                var tData = angular.copy($scope.gimme('tableData')) ;
                for(var i = 0; i < tData.tabledata.length; i++){
                    delete tData.tabledata[i][latestClick.colName];
                }
                $scope.set('tableData', tData);
            }
        }
    };
    //===================================================================================

    //========================================================================================
    // Get Row Background Color
    //========================================================================================
    $scope.getRowBkgColor = function(rowIndex){
        if((rowIndex+1)%2 == 0){
            return $scope.gimme('evenRowBkgColor');
        }
        else{
            return $scope.gimme('oddRowBkgColor');
        }
    };
    //========================================================================================

    //=== CTRL MAIN CODE ======================================================================

});
//=======================================================================================
<|endoftext|>


<commit_before>/*
---

script: MooEditable.UI.ButtonOverlay.js

description: UI Class to create a button element with a popup overlay.

license: MIT-style license

authors:
- Lim Chee Aun

requires:
# - MooEditable
# - MooEditable.UI

provides: [MooEditable.UI.ButtonOverlay]

...
*/

MooEditable.UI.ButtonOverlay = new Class({

	Extends: MooEditable.UI.Button,

	options: {
		/*
		onOpenOverlay: $empty,
		onCloseOverlay: $empty,
		*/
		overlayHTML: '',
		overlayClass: '',
		overlaySize: {x: 150, y: 'auto'},
		overlayContentClass: ''
	},

	initialize: function(options){
		this.parent(options);
		this.render();
		this.el.addClass('mooeditable-ui-buttonOverlay');
		this.renderOverlay(this.options.overlayHTML);
	},

	renderOverlay: function(html){
		var self = this;
		this.overlay = new Element('div', {
			'class': 'mooeditable-ui-button-overlay ' + self.name + '-overlay ' + self.options.overlayClass,
			html: '
' + html + '
',
			tabindex: 0,
			styles: {
				left: '-999em',
				position: 'absolute',
				width: self.options.overlaySize.x,
				height: self.options.overlaySize.y
			},
			events: {
				mousedown: self.clickOverlay.bind(self),
				focus: self.openOverlay.bind(self),
				blur: self.closeOverlay.bind(self)
			}
		}).inject(document.body).store('MooEditable.UI.ButtonOverlay', this);
		this.overlayVisible = false;
	},

	openOverlay: function(){
		if (this.overlayVisible) return;
		this.overlayVisible = true;
		this.activate();
		this.fireEvent('openOverlay', this);
		return this;
	},

	closeOverlay: function(){
		if (!this.overlayVisible) return;
		this.overlay.setStyle('left', '-999em');
		this.overlayVisible = false;
		this.deactivate();
		this.fireEvent('closeOverlay', this);
		return this;
	},

	clickOverlay: function(e){
		if (e.target == this.overlay || e.target.parentNode == this.overlay) return;
		this.overlay.blur();
		e.preventDefault();
		this.action(e);
	},

	click: function(e){
		e.preventDefault();
		if (this.disabled) return;
		if (this.overlayVisible){
			this.overlay.blur();
			return;
		} else {
			var coords = this.el.getCoordinates();
			this.overlay.setStyles({
				top: coords.bottom,
				left: coords.left
			});
			this.overlay.focus();
		}
	}

});<commit_msg>Reposition button overlay when window resize.<commit_after>/*
---

script: MooEditable.UI.ButtonOverlay.js

description: UI Class to create a button element with a popup overlay.

license: MIT-style license

authors:
- Lim Chee Aun

requires:
# - MooEditable
# - MooEditable.UI

provides: [MooEditable.UI.ButtonOverlay]

...
*/

MooEditable.UI.ButtonOverlay = new Class({

	Extends: MooEditable.UI.Button,

	options: {
		/*
		onOpenOverlay: $empty,
		onCloseOverlay: $empty,
		*/
		overlayHTML: '',
		overlayClass: '',
		overlaySize: {x: 150, y: 'auto'},
		overlayContentClass: ''
	},

	initialize: function(options){
		this.parent(options);
		this.render();
		this.el.addClass('mooeditable-ui-buttonOverlay');
		this.renderOverlay(this.options.overlayHTML);
	},

	renderOverlay: function(html){
		var self = this;
		this.overlay = new Element('div', {
			'class': 'mooeditable-ui-button-overlay ' + self.name + '-overlay ' + self.options.overlayClass,
			html: '
' + html + '
',
			tabindex: 0,
			styles: {
				left: '-999em',
				position: 'absolute',
				width: self.options.overlaySize.x,
				height: self.options.overlaySize.y
			},
			events: {
				mousedown: self.clickOverlay.bind(self),
				focus: self.openOverlay.bind(self),
				blur: self.closeOverlay.bind(self)
			}
		}).inject(document.body).store('MooEditable.UI.ButtonOverlay', this);
		this.overlayVisible = false;

		window.addEvent('resize', function(){
			if (self.overlayVisible) self.positionOverlay();
		});
	},

	openOverlay: function(){
		if (this.overlayVisible) return;
		this.overlayVisible = true;
		this.activate();
		this.fireEvent('openOverlay', this);
		return this;
	},

	closeOverlay: function(){
		if (!this.overlayVisible) return;
		this.overlay.setStyle('left', '-999em');
		this.overlayVisible = false;
		this.deactivate();
		this.fireEvent('closeOverlay', this);
		return this;
	},

	clickOverlay: function(e){
		if (e.target == this.overlay || e.target.parentNode == this.overlay) return;
		this.overlay.blur();
		e.preventDefault();
		this.action(e);
	},

	click: function(e){
		e.preventDefault();
		if (this.disabled) return;
		if (this.overlayVisible){
			this.overlay.blur();
			return;
		} else {
			this.positionOverlay();
			this.overlay.focus();
		}
	},

	positionOverlay: function(){
		var coords = this.el.getCoordinates();
		this.overlay.setStyles({
			top: coords.bottom,
			left: coords.left
		});
		return this;
	}

});<|endoftext|>