<commit_before>// Some general purpose JS functions for the iTop application

//IE 8 compatibility, copied from: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/IndexOf
if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
    	"use strict";
        if (this == null) {
        	throw new TypeError();
        }
        var t = Object(this);
        var len = t.length >>> 0;
        if (len === 0) {
        	return -1;
        }
        var n = 0;
        if (arguments.length > 1) {
        	n = Number(arguments[1]);
            if (n != n) { // shortcut for verifying if it's NaN
            	n = 0;
            } else if (n != 0 && n != Infinity && n != -Infinity) {
            	n = (n > 0 || -1) * Math.floor(Math.abs(n));
            }
        }
        if (n >= len) {
        	return -1;
        }
        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
        for (; k < len; k++) {
        	if (k in t && t[k] === searchElement) {
            	return k;
            }
        }
        return -1;
    }
}
/**
 * Reload a truncated list
 */
aTruncatedLists = {}; // To keep track of the list being loaded, each member is an ajaxRequest object

function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams)
{
	$('#'+divId).block();
	//$('#'+divId).blockUI();
	if (aTruncatedLists[divId] != undefined)
	{
		try
		{
			aAjaxRequest = aTruncatedLists[divId];
			aAjaxRequest.abort();
		}
		catch(e)
		{
			// Do nothing special, just continue
			console.log('Uh,uh, exception !');
		}
	}
	aTruncatedLists[divId] = $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?style=list',
	   { operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams },
	     function(data)
	     {
			 aTruncatedLists[divId] = undefined;
			 if (data.length > 0)
			 {
				 $('#'+divId).html(data);
				 $('#'+divId+' .listResults').tableHover(); // hover tables
				 $('#'+divId+' .listResults').each( function()
					{
						var table = $(this);
						var id = $(this).parent();
						aTruncatedLists[divId] = undefined;
						var checkbox = (table.find('th:first :checkbox').length > 0);
						if (checkbox)
						{
							// There is a checkbox in the first column, don't make it sortable
							table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $("#pager")}); // sortable and zebra tables
						}
						else
						{
							// There is NO checkbox in the first column, all columns are considered sortable
							table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $("#pager"), totalRows:97, filter: sSerializedFilter, extra_params: sExtraParams }); // sortable and zebra tables
						}
					});
				 $('#'+divId).unblock();
			 }
		}
	 );
}
/**
 * Truncate a previously expanded list !
 */
function TruncateList(divId, iLimit, sNewLabel, sLinkLabel)
{
	$('#'+divId).block();
	var iCount = 0;
	$('#'+divId+' table.listResults tr:gt('+iLimit+')').each( function(){
			$(this).remove();
	});
	$('#lbl_'+divId).html(sNewLabel);
	$('#'+divId+' table.listResults tr:last td').addClass('truncated');
	$('#'+divId+' table.listResults').addClass('truncated');
	$('#trc_'+divId).html(sLinkLabel);
	$('#'+divId+' .listResults').trigger("update"); //  Reset the cache
	$('#'+divId).unblock();
}
/**
 * Reload any block -- used for periodic auto-reload
 */
function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams)
{
	// Check if the user is not editing the list properties right now
	var bDialogOpen = false;
	var oDataTable = $('#'+divId+' :itop-datatable');
	if (oDataTable.length > 0)
	{
		bDialogOpen = oDataTable.datatable('IsDialogOpen');
	}
	if (!bDialogOpen)
	{
		$('#'+divId).block();

		$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?style='+sStyle,
		   { operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams },
		   function(data){
			 $('#'+divId).empty();
			 $('#'+divId).append(data);
			 $('#'+divId).removeClass('loading');
			}
		 );
	}
}

/**
 * Update the display and value of a file input widget when the user picks a new file
 */
function UpdateFileName(id, sNewFileName)
{
	var aPath = sNewFileName.split('\\');
	var sNewFileName = aPath[aPath.length-1];

	$('#'+id).val(sNewFileName);
	$('#'+id).trigger('validate');
	$('#name_'+id).text(sNewFileName);
	return true;
}
/**
 * Reload a search form for the specified class
 */
function ReloadSearchForm(divId, sClassName, sBaseClass, sContext)
{
    var oDiv = $('#ds_'+divId);
	oDiv.block();
	// deprecated in jQuery 1.8
	//var oFormEvents = $('#ds_'+divId+' form').data('events');
	var oForm = $('#ds_'+divId+' form');
	var oFormEvents = $._data(oForm[0], "events");

	// Save the submit handlers
    aSubmit = new Array();
	if ( (oFormEvents != null) && (oFormEvents.submit != undefined))
	{
		for(var index = 0; index < oFormEvents.submit.length; index++)
		{
			aSubmit [index ] = { data:oFormEvents.submit[index].data, namespace:oFormEvents.submit[index].namespace, handler:  oFormEvents.submit[index].handler};
		}
	}
	sAction =  $('#ds_'+divId+' form').attr('action');

	// Save the current values in the form
	var oMap = {};
	$('#ds_'+divId+" form :input[name!='']").each(function() {
		oMap[this.name] = this.value;
	});
	oMap.operation = 'search_form';
	oMap.className = sClassName;
	oMap.baseClass = sBaseClass;
	oMap.currentId = divId;
	oMap.action = sAction;

	$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, oMap,
	   function(data) {
		   oDiv.empty();
		   oDiv.append(data);
		   if (aSubmit.length > 0)
		   {
			    var oForm = $('#ds_'+divId+' form'); // Form was reloaded, recompute it
				for(var index = 0; index < aSubmit.length; index++)
				{
					// Restore the previously bound submit handlers
					if (aSubmit[index].data != undefined)
					{
						oForm.bind('submit.'+aSubmit[index].namespace, aSubmit[index].data, aSubmit[index].handler)
					}
					else
					{
						oForm.bind('submit.'+aSubmit[index].namespace, aSubmit[index].handler)
					}
				}
		   }
		   oDiv.unblock();
		   oDiv.parent().resize(); // Inform the parent that the form has just been (potentially) resized
	   }
	 );
}

/**
 * Stores - in a persistent way - user specific preferences
 * depends on a global variable oUserPreferences created/filled by the iTopWebPage
 * that acts as a local -write through- cache
 */
function SetUserPreference(sPreferenceCode, sPrefValue, bPersistent)
{
	sPreviousValue = undefined;
	try
	{
		sPreviousValue = oUserPreferences[sPreferenceCode];
	}
	catch(err)
	{
		sPreviousValue = undefined;
	}
    oUserPreferences[sPreferenceCode] = sPrefValue;
    if (bPersistent && (sPrefValue != sPreviousValue))
    {
    	ajax_request = $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
    						  { operation: 'set_pref', code: sPreferenceCode, value: sPrefValue} ); // Make it persistent
    }
}

/**
 * Get user specific preferences
 * depends on a global variable oUserPreferences created/filled by the iTopWebPage
 * that acts as a local -write through- cache
 */
function GetUserPreference(sPreferenceCode, sDefaultValue)
{
	var value = sDefaultValue;
	if ( oUserPreferences[sPreferenceCode] != undefined)
	{
		value = oUserPreferences[sPreferenceCode];
	}
	return value;
}

/**
 * Check/uncheck a whole list of checkboxes
 */
function CheckAll(sSelector, bValue)
{
	var value = bValue;
	$(sSelector).each( function() {
		if (this.checked != value)
		{
			this.checked = value;
			$(this).trigger('change');
		}
	});
}

/**
 * Toggle (enabled/disabled) the specified field of a form
 */
function ToogleField(value, field_id)
{
	if (value)
	{
		$('#'+field_id).removeAttr('disabled');
	}
	else
	{
		$('#'+field_id).attr('disabled', 'disabled');
	}
	$('#'+field_id).trigger('update');
	$('#'+field_id).trigger('validate');
}

/**
 * For the fields that cannot be visually disabled, they can be blocked
 * @return
 */
function BlockField(field_id, bBlocked)
{
	if (bBlocked)
	{
		$('#'+field_id).block({ message: ' ** disabled ** '});
	}
	else
	{
		$('#'+field_id).unblock();
	}
}

/**
 * Updates (enables/disables) a "duration" field
 */
function ToggleDurationField(field_id)
{
	// Toggle all the subfields that compose the "duration" input
	aSubFields = new Array('d', 'h', 'm', 's');

	if ($('#'+field_id).attr('disabled'))
	{
		for(var i=0; i<aSubFields.length; i++)
		{
			$('#'+field_id+'_'+aSubFields[i]).attr('disabled', 'disabled');
		}
	}
	else
	{
		for(var i=0; i<aSubFields.length; i++)
		{
			$('#'+field_id+'_'+aSubFields[i]).removeAttr('disabled');
		}
	}
}

/**
 * PropagateCheckBox
 */
function PropagateCheckBox(bCurrValue, aFieldsList, bCheck)
{
	if (bCurrValue == bCheck)
	{
		for(var i=0;i<aFieldsList.length;i++)
		{
			$('#enable_'+aFieldsList[i]).attr('checked', bCheck);
			ToogleField(bCheck, aFieldsList[i]);
		}
	}
}

function FixTableSorter(table)
{
	if ($('th.header', table).length == 0)
	{
		// Table is not sort-able, let's fix it
		var checkbox = (table.find('th:first :checkbox').length > 0);
		if (checkbox)
		{
			// There is a checkbox in the first column, don't make it sort-able
			table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sort-able and zebra tables
		}
		else
		{
			// There is NO checkbox in the first column, all columns are considered sort-able
			table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sort-able and zebra tables
		}
	}
}

function DashletCreationDlg(sOQL)
{
	$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'dashlet_creation_dlg', oql: sOQL}, function(data){
		$('body').append(data);
	});
	return false;
}

function ShortcutListDlg(sOQL, sContext)
{
	$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, {operation: 'shortcut_list_dlg', oql: sOQL}, function(data){
		$('body').append(data);
	});
	return false;
}<commit_msg>Forget about IE 8 support ???<commit_after>// Some general purpose JS functions for the iTop application

//IE 8 compatibility, copied from: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/IndexOf
if (!Array.prototype.indexOf) {

	if (false) // deactivated since it causes troubles: for(k in aData) => returns the indexOf function as first element on empty arrays !
	{
	Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
    	"use strict";
        if (this == null) {
        	throw new TypeError();
        }
        var t = Object(this);
        var len = t.length >>> 0;
        if (len === 0) {
        	return -1;
        }
        var n = 0;
        if (arguments.length > 1) {
        	n = Number(arguments[1]);
            if (n != n) { // shortcut for verifying if it's NaN
            	n = 0;
            } else if (n != 0 && n != Infinity && n != -Infinity) {
            	n = (n > 0 || -1) * Math.floor(Math.abs(n));
            }
        }
        if (n >= len) {
        	return -1;
        }
        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
        for (; k < len; k++) {
        	if (k in t && t[k] === searchElement) {
            	return k;
            }
        }
        return -1;
    }
	}
}
/**
 * Reload a truncated list
 */
aTruncatedLists = {}; // To keep track of the list being loaded, each member is an ajaxRequest object

function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams)
{
	$('#'+divId).block();
	//$('#'+divId).blockUI();
	if (aTruncatedLists[divId] != undefined)
	{
		try
		{
			aAjaxRequest = aTruncatedLists[divId];
			aAjaxRequest.abort();
		}
		catch(e)
		{
			// Do nothing special, just continue
			console.log('Uh,uh, exception !');
		}
	}
	aTruncatedLists[divId] = $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?style=list',
	   { operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams },
	     function(data)
	     {
			 aTruncatedLists[divId] = undefined;
			 if (data.length > 0)
			 {
				 $('#'+divId).html(data);
				 $('#'+divId+' .listResults').tableHover(); // hover tables
				 $('#'+divId+' .listResults').each( function()
					{
						var table = $(this);
						var id = $(this).parent();
						aTruncatedLists[divId] = undefined;
						var checkbox = (table.find('th:first :checkbox').length > 0);
						if (checkbox)
						{
							// There is a checkbox in the first column, don't make it sortable
							table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $("#pager")}); // sortable and zebra tables
						}
						else
						{
							// There is NO checkbox in the first column, all columns are considered sortable
							table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $("#pager"), totalRows:97, filter: sSerializedFilter, extra_params: sExtraParams }); // sortable and zebra tables
						}
					});
				 $('#'+divId).unblock();
			 }
		}
	 );
}
/**
 * Truncate a previously expanded list !
 */
function TruncateList(divId, iLimit, sNewLabel, sLinkLabel)
{
	$('#'+divId).block();
	var iCount = 0;
	$('#'+divId+' table.listResults tr:gt('+iLimit+')').each( function(){
			$(this).remove();
	});
	$('#lbl_'+divId).html(sNewLabel);
	$('#'+divId+' table.listResults tr:last td').addClass('truncated');
	$('#'+divId+' table.listResults').addClass('truncated');
	$('#trc_'+divId).html(sLinkLabel);
	$('#'+divId+' .listResults').trigger("update"); //  Reset the cache
	$('#'+divId).unblock();
}
/**
 * Reload any block -- used for periodic auto-reload
 */
function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams)
{
	// Check if the user is not editing the list properties right now
	var bDialogOpen = false;
	var oDataTable = $('#'+divId+' :itop-datatable');
	if (oDataTable.length > 0)
	{
		bDialogOpen = oDataTable.datatable('IsDialogOpen');
	}
	if (!bDialogOpen)
	{
		$('#'+divId).block();

		$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?style='+sStyle,
		   { operation: 'ajax', filter: sSerializedFilter, extra_params: sExtraParams },
		   function(data){
			 $('#'+divId).empty();
			 $('#'+divId).append(data);
			 $('#'+divId).removeClass('loading');
			}
		 );
	}
}

/**
 * Update the display and value of a file input widget when the user picks a new file
 */
function UpdateFileName(id, sNewFileName)
{
	var aPath = sNewFileName.split('\\');
	var sNewFileName = aPath[aPath.length-1];

	$('#'+id).val(sNewFileName);
	$('#'+id).trigger('validate');
	$('#name_'+id).text(sNewFileName);
	return true;
}
/**
 * Reload a search form for the specified class
 */
function ReloadSearchForm(divId, sClassName, sBaseClass, sContext)
{
    var oDiv = $('#ds_'+divId);
	oDiv.block();
	// deprecated in jQuery 1.8
	//var oFormEvents = $('#ds_'+divId+' form').data('events');
	var oForm = $('#ds_'+divId+' form');
	var oFormEvents = $._data(oForm[0], "events");

	// Save the submit handlers
    aSubmit = new Array();
	if ( (oFormEvents != null) && (oFormEvents.submit != undefined))
	{
		for(var index = 0; index < oFormEvents.submit.length; index++)
		{
			aSubmit [index ] = { data:oFormEvents.submit[index].data, namespace:oFormEvents.submit[index].namespace, handler:  oFormEvents.submit[index].handler};
		}
	}
	sAction =  $('#ds_'+divId+' form').attr('action');

	// Save the current values in the form
	var oMap = {};
	$('#ds_'+divId+" form :input[name!='']").each(function() {
		oMap[this.name] = this.value;
	});
	oMap.operation = 'search_form';
	oMap.className = sClassName;
	oMap.baseClass = sBaseClass;
	oMap.currentId = divId;
	oMap.action = sAction;

	$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, oMap,
	   function(data) {
		   oDiv.empty();
		   oDiv.append(data);
		   if (aSubmit.length > 0)
		   {
			    var oForm = $('#ds_'+divId+' form'); // Form was reloaded, recompute it
				for(var index = 0; index < aSubmit.length; index++)
				{
					// Restore the previously bound submit handlers
					if (aSubmit[index].data != undefined)
					{
						oForm.bind('submit.'+aSubmit[index].namespace, aSubmit[index].data, aSubmit[index].handler)
					}
					else
					{
						oForm.bind('submit.'+aSubmit[index].namespace, aSubmit[index].handler)
					}
				}
		   }
		   oDiv.unblock();
		   oDiv.parent().resize(); // Inform the parent that the form has just been (potentially) resized
	   }
	 );
}

/**
 * Stores - in a persistent way - user specific preferences
 * depends on a global variable oUserPreferences created/filled by the iTopWebPage
 * that acts as a local -write through- cache
 */
function SetUserPreference(sPreferenceCode, sPrefValue, bPersistent)
{
	sPreviousValue = undefined;
	try
	{
		sPreviousValue = oUserPreferences[sPreferenceCode];
	}
	catch(err)
	{
		sPreviousValue = undefined;
	}
    oUserPreferences[sPreferenceCode] = sPrefValue;
    if (bPersistent && (sPrefValue != sPreviousValue))
    {
    	ajax_request = $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
    						  { operation: 'set_pref', code: sPreferenceCode, value: sPrefValue} ); // Make it persistent
    }
}

/**
 * Get user specific preferences
 * depends on a global variable oUserPreferences created/filled by the iTopWebPage
 * that acts as a local -write through- cache
 */
function GetUserPreference(sPreferenceCode, sDefaultValue)
{
	var value = sDefaultValue;
	if ( oUserPreferences[sPreferenceCode] != undefined)
	{
		value = oUserPreferences[sPreferenceCode];
	}
	return value;
}

/**
 * Check/uncheck a whole list of checkboxes
 */
function CheckAll(sSelector, bValue)
{
	var value = bValue;
	$(sSelector).each( function() {
		if (this.checked != value)
		{
			this.checked = value;
			$(this).trigger('change');
		}
	});
}

/**
 * Toggle (enabled/disabled) the specified field of a form
 */
function ToogleField(value, field_id)
{
	if (value)
	{
		$('#'+field_id).removeAttr('disabled');
	}
	else
	{
		$('#'+field_id).attr('disabled', 'disabled');
	}
	$('#'+field_id).trigger('update');
	$('#'+field_id).trigger('validate');
}

/**
 * For the fields that cannot be visually disabled, they can be blocked
 * @return
 */
function BlockField(field_id, bBlocked)
{
	if (bBlocked)
	{
		$('#'+field_id).block({ message: ' ** disabled ** '});
	}
	else
	{
		$('#'+field_id).unblock();
	}
}

/**
 * Updates (enables/disables) a "duration" field
 */
function ToggleDurationField(field_id)
{
	// Toggle all the subfields that compose the "duration" input
	aSubFields = new Array('d', 'h', 'm', 's');

	if ($('#'+field_id).attr('disabled'))
	{
		for(var i=0; i<aSubFields.length; i++)
		{
			$('#'+field_id+'_'+aSubFields[i]).attr('disabled', 'disabled');
		}
	}
	else
	{
		for(var i=0; i<aSubFields.length; i++)
		{
			$('#'+field_id+'_'+aSubFields[i]).removeAttr('disabled');
		}
	}
}

/**
 * PropagateCheckBox
 */
function PropagateCheckBox(bCurrValue, aFieldsList, bCheck)
{
	if (bCurrValue == bCheck)
	{
		for(var i=0;i<aFieldsList.length;i++)
		{
			$('#enable_'+aFieldsList[i]).attr('checked', bCheck);
			ToogleField(bCheck, aFieldsList[i]);
		}
	}
}

function FixTableSorter(table)
{
	if ($('th.header', table).length == 0)
	{
		// Table is not sort-able, let's fix it
		var checkbox = (table.find('th:first :checkbox').length > 0);
		if (checkbox)
		{
			// There is a checkbox in the first column, don't make it sort-able
			table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sort-able and zebra tables
		}
		else
		{
			// There is NO checkbox in the first column, all columns are considered sort-able
			table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sort-able and zebra tables
		}
	}
}

function DashletCreationDlg(sOQL)
{
	$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'dashlet_creation_dlg', oql: sOQL}, function(data){
		$('body').append(data);
	});
	return false;
}

function ShortcutListDlg(sOQL, sContext)
{
	$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?'+sContext, {operation: 'shortcut_list_dlg', oql: sOQL}, function(data){
		$('body').append(data);
	});
	return false;
}<|endoftext|>


<commit_before>'use strict';
/**
 * @memberof CollaborativeMap
 * @fileOverview Directive which to handle feature properties. Allows adding/editing/deleting properties
 * @exports CollaborativeMap.FeaturePropertiesDirective
 * @author Dennis Wilhelm
 */
angular.module('CollaborativeMap')
  .directive('featureproperties', ['$compile', 'MapHandler', '$http', '$q',
    function($compile, MapHandler, $http, $q) {

      return {
        restrict: 'E', // E = Element, A = Attribute, C = Class, M = Comment
        templateUrl: 'partials/featureproperties',
        replace: true,
        link: function postLink($scope) {

          /**
           * Toggles the visibility of the featureproprties view
           */

          function activateToolbox() {
            if ($scope.views.toolBarIn) {
              $scope.toggleToolbar('toolsView');
              $scope.$apply();
            } else if ($scope.views.toolsView) {
              $scope.toggleToolbar('toolsView');
              $scope.$apply();
            }

          }

          /**
           * Opens the featureproperties view, sets the feature as the selected feature within the scope
           * Pushes the properties to the scope array which is used in ng-repeat
           * Called from the feature onClick through the map services.
           * @param {Object} feature the leaflet layer
           */
          $scope.selectFeature = function(feature) {

            //TODO: filter simplestyle spec items: https://github.com/mapbox/simplestyle-spec/tree/master/1.1.0

            //jshint camelcase:false
            activateToolbox();

            cleanSelection();

            $scope.selectedFeature = {
              'properties': [],
              'fid': feature._leaflet_id
            };

            selectCategoriesForGeomType(feature);

            var tmpGeoJSON = $scope.selectedFeature.feature = feature.toGeoJSON();

            //Create an Array containing all properties. Has to be included in the feature again
            for (var prop in tmpGeoJSON.properties) {
              if (allowedProp(prop)) {
                $scope.selectedFeature.properties.push({
                  'key': prop,
                  'value': tmpGeoJSON.properties[prop]
                });
              }
            }

            //Preselect the selectboxes if a category/preset is available
            if (tmpGeoJSON.properties && tmpGeoJSON.properties.category) {
              $scope.selectedCategory = tmpGeoJSON.properties.category;
              setPresetsInScope($scope.selectedCategory);
            }
            if (tmpGeoJSON.properties && tmpGeoJSON.properties.preset) {
              var i = getPresetIndex(tmpGeoJSON.properties.preset);
              $scope.selectedPreset = i;
              //Wait to let the gui render first and set the selected index for the selectbox
              setTimeout(function() {
                $('#presetSelect')[0].selectedIndex = parseInt(i) + 1;
              }, 40);

            }

            //$apply has to be called manually, if the function is called from a different event (here leaflet click)
            if ($scope.$root.$$phase !== '$apply' && $scope.$root.$$phase !== '$digest') {
              $scope.$apply();
            }

          };

          /**
           * Opens a bootstrap modal to show the history of a single feature
           * @param {String} id the feature id
           */
          $scope.showFeatureHistory = function(id) {
            $scope.$root.$broadcast('showFeatureHistory', id);
          };

          /**
           * Checks if a property should be displayed or not
           * @param  {String} prop the property
           * @return {Boolean}      true if the property should be displayed, false if not
           */

          function allowedProp(prop) {
            var notAllowed = ['category', 'preset', 'stroke', 'stroke-width', 'stroke-dasharray', 'stroke-linecap', 'fill'];
            //var notAllowed = ['category', 'preset'];
            if (notAllowed.indexOf(prop) > -1) {
              return false;
            } else {
              return true;
            }
          }

          var lastChange = -1;
          /**
           * Gets called if a property changes. Only send a change every 1s if no further changes have been made in between to prevent submits on every keystroke.
           */
          $scope.propertyChanged = function() {
            lastChange = new Date().getTime();
            setTimeout(function() {
              var tmpDate = new Date().getTime();
              if ((tmpDate - lastChange) > 900) {
                console.log('update property');
                updateFeature();
              }
            }, 1000);
          };

          /**
           * Calls the MapHandler functions to revert/cancel the current editing
           */
          $scope.cancelEditMode = function() {
            MapHandler.removeEditHandler();
            hideStopEditingBtn();
          };

          /**
           * Deletes the currently selected feature
           */
          $scope.deleteFeature = function() {
            MapHandler.deleteFeature();
            $scope.selectedFeature = undefined;
            $scope.toggleToolbar('toolsView');

          };

          /**
           * Include the feature properties back into the layer and call the update function
           */

          function updateFeature() {
            $scope.selectedFeature.properties.forEach(function(prop) {
              $scope.selectedFeature.feature.properties[prop.key] = prop.value;
            });
            MapHandler.updateOnlyProperties($scope.selectedFeature);
          }

          /**
           * Adds a new property to the feature.
           * @param {Number} key key code of the ng-key event
           */
          $scope.newProperty = function(key) {
            var newProp = function() {
              if ($scope.newKey && $scope.newValue) {
                $scope.selectedFeature.properties.push({
                  'key': $scope.newKey,
                  'value': $scope.newValue
                });
                $scope.newKey = '';
                $scope.newValue = '';
                updateFeature();
              }
            };

            if (key && key.keyCode === 13) {
              newProp();
            } else if (!key) {
              newProp();
            }
          };

          /**
           * Adds a new property to the feature.
           * @param {String} type the property type
           */

          function addNewPropertyType(type) {
            $scope.selectedFeature.properties.push({
              'key': type,
              'value': ''
            });
            updateFeature();
          }

          function removePropertyType(type) {
            for (var i = $scope.selectedFeature.properties.length - 1; i >= 0; i--) {
              if ($scope.selectedFeature.properties[i].key === type) {
                $scope.selectedFeature.properties.splice(i, 1);
              }
            }
            delete $scope.selectedFeature.feature.properties[type];
          }

          //Variable used to controle the 'hide' class via ng-class
          $scope.hideNewProperty = true;

          /**
           * Show the GUI form to create new properties
           * @param {Object} e html button
           */
          $scope.addNewProperty = function(e) {
            var element = e.currentTarget;
            if (element.value.indexOf('Add') > -1) {
              element.value = 'Hide new Property';
            } else {
              element.value = 'Add new Property';
            }
            $scope.hideNewProperty = !$scope.hideNewProperty;
          };

          /**
           * Show the button to stop the edit mode
           */

          function showStopEditingBtn() {
            if ($('#stopEditBtn').length > 0) {
              var newClassName = replaceAll(' hidden', '', $('#stopEditBtn')[0].className);
              $('#stopEditBtn')[0].className = newClassName;

            }
          }

          function replaceAll(find, replace, str) {
            return str.replace(new RegExp(find, 'g'), replace);
          }

          /**
           * Hide the button to stop the edit mode
           */

          function hideStopEditingBtn() {
            $('#stopEditBtn')[0].className += ' hidden';
          }

          /**
           * Listen to the editHandler events to show or hide the "Stop Editing" button
           */
          $scope.$on('editHandler', function(e, eventValue) {
            if (!eventValue) {
              hideStopEditingBtn();
            } else {
              showStopEditingBtn();
            }
          });

          /**
           * Remove a given property from the feature. Updates the feature afterwards.
           * @param {Number} i index of the properties Array
           */
          $scope.removeProperty = function(i) {
            var remKey = $scope.selectedFeature.properties[i].key;
            delete $scope.selectedFeature.feature.properties[remKey];
            $scope.selectedFeature.properties.splice(i, 1);
            updateFeature();
          };

          /**
           * Cancel the edit mode if the toolbox window is closed.
           */
          $scope.$on('toolbox', function() {
            if ($scope.views.toolsView) {
              MapHandler.removeEditHandler();
            }
          });

          //NEW CATEGORIES SYSTEM
          var presets;
          var fields;
          var categories;

          /**
           * Select the suitable categories for the given feature based on the geometry type.
           * Put the selection in the scope variable for the GUI
           * @param  {Object} layer selected feature
           */

          function selectCategoriesForGeomType(layer) {
            var geomType = MapHandler.getLayerType(layer);
            $scope.categories = {};

            for (var key in categories) {
              if (categories[key].geometry.indexOf(geomType) > -1) {
                $scope.categories[key] = categories[key];
              }
            }
          }

          /**
           * Remove selected category and preset from the scope
           */

          function cleanSelection() {
            $scope.presets = undefined;
            $scope.selectedCategory = undefined;
            $scope.selectedPreset = undefined;
          }

          /**
           * GET request to load the category/preset and fields information from the server.
           * Stores the categories in the scope for the select box.
           * Fields and presets will be used as soon as a category has been chosen.
           */

          function getPresetData() {
            var categoriesPromise = $http.get('presets/categories'),
              fieldsPromise = $http.get('presets/fields'),
              presetsPromise = $http.get('presets/presets');

            $q.all([categoriesPromise, fieldsPromise, presetsPromise]).then(function(resultArray) {
              if (resultArray) {
                if (resultArray[0] && resultArray[0].data) {
                  categories = resultArray[0].data;
                }
                if (resultArray[1] && resultArray[1].data) {
                  fields = resultArray[1].data;
                }
                if (resultArray[2] && resultArray[2].data) {
                  presets = resultArray[2].data;
                }
              }
            });
          }

          /**
           * If a category is selected, append the sub categories (presets) to a second select box.
           * Saves the category in the feature and call the update function to sync the feature.
           */
          $scope.selectPresets = function() {
            $scope.cancelEditMode();
            $scope.fields = [];
            var selCategory = $scope.selectedCategory;

            if (selCategory) {
              //Update the feature
              $scope.selectedFeature.feature.properties.category = selCategory;
              setStyleFromCategory(selCategory);
              MapHandler.updateOnlyProperties($scope.selectedFeature);

              //Set to scope array
              setPresetsInScope(selCategory);

            }
          };

          /**
           * Removes existing simplestyle properties and sets the new ones
           * based on the configured category styles.
           * @param {Object} category the chosen osm category
           */

          function setStyleFromCategory(category) {
            var style = categories[category].style;
            var selFeature = $scope.selectedFeature.feature;
            removeExistingStyle(selFeature);
            for (var key in style) {
              selFeature.properties[key] = style[key];
            }
          }

          /**
           * Removes existing simplestyle properties from the given feature
           * @param  {Object} feature the GeoJSON feature
           */

          function removeExistingStyle(feature) {
            var simpleStyleKeys = [
              'marker-size',
              'marker-symbol',
              'marker-color',
              'stroke',
              'stroke-opacity',
              'stroke-width',
              'fill',
              'fill-opacity'
            ];
            simpleStyleKeys.forEach(function(styleKey) {
              delete feature.properties[styleKey];
            });
          }

          /**
           * Append the presets to the scope variable to fill the select box.
           */

          function setPresetsInScope(category) {
            $scope.presets = [];
            $scope.presets = [];
            //Get the member of the chosen category = presets
            var members = categories[category].members || [];
            members.forEach(function(member) {
              $scope.presets.push(presets[member]);
            });

          }

          /**
           * Returns the index of a preset in the categories member array
           * @param  {String} presetKey object key
           * @return {String}           Key of the categories member array
           */

          function getPresetIndex(presetKey) {
            var members = categories[$scope.selectedCategory].members;
            for (var key in members) {
              if (presetKey === members[key]) {
                return key;
              }
            }
          }

          /**
           * Called if the preset is selected.
           * Updates the feature and cally update to sync.
           *
           * Checks if the preset is associated with fields and adds new ones to the properties.
           */
          $scope.selectFields = function() {
            $scope.cancelEditMode();

            var members;
            $scope.fields = [];
            if ($scope.selectedPreset) {
              //Update the feature
              var oldPreset = $scope.selectedFeature.feature.properties.preset;
              $scope.selectedFeature.feature.properties.preset = getSelectedPresetName($scope.selectedPreset);
              MapHandler.updateOnlyProperties($scope.selectedFeature);

              if (oldPreset) {
                var oldMembers = presets[oldPreset].fields || [];
                if (oldMembers) {
                  oldMembers.forEach(function(member) {
                    var index = $scope.fields.indexOf(fields[member]);
                    if (index > -1) {
                      $scope.fields.splice(index, 1);
                    }
                    removePropertyType(fields[member].label);
                  });
                }
              }

              //Get the fields of the preset
              members = $scope.presets[$scope.selectedPreset].fields || [];
              members.forEach(function(member) {
                var newKey = fields[member].label;
                //Only append if not already existing
                if (!$scope.selectedFeature.feature.properties.hasOwnProperty(newKey)) {
                  addNewPropertyType(newKey);
                }
                //Scope array for the GUI
                $scope.fields.push(fields[member]);
              });

            }

          };

          /**
           * Returns the key of the selected preset (sub-category)
           * @param  {String} index the key of the categories member object
           * @return {String}       preset name
           */

          function getSelectedPresetName(index) {
            if (index && $scope.categories[$scope.selectedCategory] && $scope.categories[$scope.selectedCategory].members && $scope.categories[$scope.selectedCategory].members[index]) {
              return $scope.categories[$scope.selectedCategory].members[index];
            }
          }

          getPresetData();

        }
      };
    }
  ]);
<commit_msg>remove preset if category changes<commit_after>'use strict';
/**
 * @memberof CollaborativeMap
 * @fileOverview Directive which to handle feature properties. Allows adding/editing/deleting properties
 * @exports CollaborativeMap.FeaturePropertiesDirective
 * @author Dennis Wilhelm
 */
angular.module('CollaborativeMap')
  .directive('featureproperties', ['$compile', 'MapHandler', '$http', '$q',
    function($compile, MapHandler, $http, $q) {

      return {
        restrict: 'E', // E = Element, A = Attribute, C = Class, M = Comment
        templateUrl: 'partials/featureproperties',
        replace: true,
        link: function postLink($scope) {

          /**
           * Toggles the visibility of the featureproprties view
           */

          function activateToolbox() {
            if ($scope.views.toolBarIn) {
              $scope.toggleToolbar('toolsView');
              $scope.$apply();
            } else if ($scope.views.toolsView) {
              $scope.toggleToolbar('toolsView');
              $scope.$apply();
            }

          }

          /**
           * Opens the featureproperties view, sets the feature as the selected feature within the scope
           * Pushes the properties to the scope array which is used in ng-repeat
           * Called from the feature onClick through the map services.
           * @param {Object} feature the leaflet layer
           */
          $scope.selectFeature = function(feature) {

            //TODO: filter simplestyle spec items: https://github.com/mapbox/simplestyle-spec/tree/master/1.1.0

            //jshint camelcase:false
            activateToolbox();

            cleanSelection();

            $scope.selectedFeature = {
              'properties': [],
              'fid': feature._leaflet_id
            };

            selectCategoriesForGeomType(feature);

            var tmpGeoJSON = $scope.selectedFeature.feature = feature.toGeoJSON();

            //Create an Array containing all properties. Has to be included in the feature again
            for (var prop in tmpGeoJSON.properties) {
              if (allowedProp(prop)) {
                $scope.selectedFeature.properties.push({
                  'key': prop,
                  'value': tmpGeoJSON.properties[prop]
                });
              }
            }

            //Preselect the selectboxes if a category/preset is available
            if (tmpGeoJSON.properties && tmpGeoJSON.properties.category) {
              $scope.selectedCategory = tmpGeoJSON.properties.category;
              setPresetsInScope($scope.selectedCategory);
            }
            if (tmpGeoJSON.properties && tmpGeoJSON.properties.preset) {
              var i = getPresetIndex(tmpGeoJSON.properties.preset);
              $scope.selectedPreset = i;
              //Wait to let the gui render first and set the selected index for the selectbox
              setTimeout(function() {
                $('#presetSelect')[0].selectedIndex = parseInt(i) + 1;
              }, 40);

            }

            //$apply has to be called manually, if the function is called from a different event (here leaflet click)
            if ($scope.$root.$$phase !== '$apply' && $scope.$root.$$phase !== '$digest') {
              $scope.$apply();
            }

          };

          /**
           * Opens a bootstrap modal to show the history of a single feature
           * @param {String} id the feature id
           */
          $scope.showFeatureHistory = function(id) {
            $scope.$root.$broadcast('showFeatureHistory', id);
          };

          /**
           * Checks if a property should be displayed or not
           * @param  {String} prop the property
           * @return {Boolean}      true if the property should be displayed, false if not
           */

          function allowedProp(prop) {
            var notAllowed = ['category', 'preset', 'stroke', 'stroke-width', 'stroke-dasharray', 'stroke-linecap', 'fill'];
            //var notAllowed = ['category', 'preset'];
            if (notAllowed.indexOf(prop) > -1) {
              return false;
            } else {
              return true;
            }
          }

          var lastChange = -1;
          /**
           * Gets called if a property changes. Only send a change every 1s if no further changes have been made in between to prevent submits on every keystroke.
           */
          $scope.propertyChanged = function() {
            lastChange = new Date().getTime();
            setTimeout(function() {
              var tmpDate = new Date().getTime();
              if ((tmpDate - lastChange) > 900) {
                console.log('update property');
                updateFeature();
              }
            }, 1000);
          };

          /**
           * Calls the MapHandler functions to revert/cancel the current editing
           */
          $scope.cancelEditMode = function() {
            MapHandler.removeEditHandler();
            hideStopEditingBtn();
          };

          /**
           * Deletes the currently selected feature
           */
          $scope.deleteFeature = function() {
            MapHandler.deleteFeature();
            $scope.selectedFeature = undefined;
            $scope.toggleToolbar('toolsView');

          };

          /**
           * Include the feature properties back into the layer and call the update function
           */

          function updateFeature() {
            $scope.selectedFeature.properties.forEach(function(prop) {
              $scope.selectedFeature.feature.properties[prop.key] = prop.value;
            });
            MapHandler.updateOnlyProperties($scope.selectedFeature);
          }

          /**
           * Adds a new property to the feature.
           * @param {Number} key key code of the ng-key event
           */
          $scope.newProperty = function(key) {
            var newProp = function() {
              if ($scope.newKey && $scope.newValue) {
                $scope.selectedFeature.properties.push({
                  'key': $scope.newKey,
                  'value': $scope.newValue
                });
                $scope.newKey = '';
                $scope.newValue = '';
                updateFeature();
              }
            };

            if (key && key.keyCode === 13) {
              newProp();
            } else if (!key) {
              newProp();
            }
          };

          /**
           * Adds a new property to the feature.
           * @param {String} type the property type
           */

          function addNewPropertyType(type) {
            $scope.selectedFeature.properties.push({
              'key': type,
              'value': ''
            });
            updateFeature();
          }

          function removePropertyType(type) {
            for (var i = $scope.selectedFeature.properties.length - 1; i >= 0; i--) {
              if ($scope.selectedFeature.properties[i].key === type) {
                $scope.selectedFeature.properties.splice(i, 1);
              }
            }
            delete $scope.selectedFeature.feature.properties[type];
          }

          //Variable used to controle the 'hide' class via ng-class
          $scope.hideNewProperty = true;

          /**
           * Show the GUI form to create new properties
           * @param {Object} e html button
           */
          $scope.addNewProperty = function(e) {
            var element = e.currentTarget;
            if (element.value.indexOf('Add') > -1) {
              element.value = 'Hide new Property';
            } else {
              element.value = 'Add new Property';
            }
            $scope.hideNewProperty = !$scope.hideNewProperty;
          };

          /**
           * Show the button to stop the edit mode
           */

          function showStopEditingBtn() {
            if ($('#stopEditBtn').length > 0) {
              var newClassName = replaceAll(' hidden', '', $('#stopEditBtn')[0].className);
              $('#stopEditBtn')[0].className = newClassName;

            }
          }

          function replaceAll(find, replace, str) {
            return str.replace(new RegExp(find, 'g'), replace);
          }

          /**
           * Hide the button to stop the edit mode
           */

          function hideStopEditingBtn() {
            $('#stopEditBtn')[0].className += ' hidden';
          }

          /**
           * Listen to the editHandler events to show or hide the "Stop Editing" button
           */
          $scope.$on('editHandler', function(e, eventValue) {
            if (!eventValue) {
              hideStopEditingBtn();
            } else {
              showStopEditingBtn();
            }
          });

          /**
           * Remove a given property from the feature. Updates the feature afterwards.
           * @param {Number} i index of the properties Array
           */
          $scope.removeProperty = function(i) {
            var remKey = $scope.selectedFeature.properties[i].key;
            delete $scope.selectedFeature.feature.properties[remKey];
            $scope.selectedFeature.properties.splice(i, 1);
            updateFeature();
          };

          /**
           * Cancel the edit mode if the toolbox window is closed.
           */
          $scope.$on('toolbox', function() {
            if ($scope.views.toolsView) {
              MapHandler.removeEditHandler();
            }
          });

          //NEW CATEGORIES SYSTEM
          var presets;
          var fields;
          var categories;

          /**
           * Select the suitable categories for the given feature based on the geometry type.
           * Put the selection in the scope variable for the GUI
           * @param  {Object} layer selected feature
           */

          function selectCategoriesForGeomType(layer) {
            var geomType = MapHandler.getLayerType(layer);
            $scope.categories = {};

            for (var key in categories) {
              if (categories[key].geometry.indexOf(geomType) > -1) {
                $scope.categories[key] = categories[key];
              }
            }
          }

          /**
           * Remove selected category and preset from the scope
           */

          function cleanSelection() {
            $scope.presets = undefined;
            $scope.selectedCategory = undefined;
            $scope.selectedPreset = undefined;
          }

          /**
           * GET request to load the category/preset and fields information from the server.
           * Stores the categories in the scope for the select box.
           * Fields and presets will be used as soon as a category has been chosen.
           */

          function getPresetData() {
            var categoriesPromise = $http.get('presets/categories'),
              fieldsPromise = $http.get('presets/fields'),
              presetsPromise = $http.get('presets/presets');

            $q.all([categoriesPromise, fieldsPromise, presetsPromise]).then(function(resultArray) {
              if (resultArray) {
                if (resultArray[0] && resultArray[0].data) {
                  categories = resultArray[0].data;
                }
                if (resultArray[1] && resultArray[1].data) {
                  fields = resultArray[1].data;
                }
                if (resultArray[2] && resultArray[2].data) {
                  presets = resultArray[2].data;
                }
              }
            });
          }

          /**
           * If a category is selected, append the sub categories (presets) to a second select box.
           * Saves the category in the feature and call the update function to sync the feature.
           */
          $scope.selectPresets = function() {
            $scope.cancelEditMode();
            $scope.fields = [];
            $scope.selectedPreset = undefined;
            var selCategory = $scope.selectedCategory;

            if (selCategory) {
              //Update the feature
              $scope.selectedFeature.feature.properties.category = selCategory;
              setStyleFromCategory(selCategory);
              MapHandler.updateOnlyProperties($scope.selectedFeature);

              //Set to scope array
              setPresetsInScope(selCategory);

            }
          };

          /**
           * Removes existing simplestyle properties and sets the new ones
           * based on the configured category styles.
           * @param {Object} category the chosen osm category
           */

          function setStyleFromCategory(category) {
            var style = categories[category].style;
            var selFeature = $scope.selectedFeature.feature;
            removeExistingStyle(selFeature);
            for (var key in style) {
              selFeature.properties[key] = style[key];
            }
          }

          /**
           * Removes existing simplestyle properties from the given feature
           * @param  {Object} feature the GeoJSON feature
           */

          function removeExistingStyle(feature) {
            var simpleStyleKeys = [
              'marker-size',
              'marker-symbol',
              'marker-color',
              'stroke',
              'stroke-opacity',
              'stroke-width',
              'fill',
              'fill-opacity'
            ];
            simpleStyleKeys.forEach(function(styleKey) {
              delete feature.properties[styleKey];
            });
          }

          /**
           * Append the presets to the scope variable to fill the select box.
           */

          function setPresetsInScope(category) {
            $scope.presets = [];
            $scope.presets = [];
            //Get the member of the chosen category = presets
            var members = categories[category].members || [];
            members.forEach(function(member) {
              $scope.presets.push(presets[member]);
            });

          }

          /**
           * Returns the index of a preset in the categories member array
           * @param  {String} presetKey object key
           * @return {String}           Key of the categories member array
           */

          function getPresetIndex(presetKey) {
            var members = categories[$scope.selectedCategory].members;
            for (var key in members) {
              if (presetKey === members[key]) {
                return key;
              }
            }
          }

          /**
           * Called if the preset is selected.
           * Updates the feature and cally update to sync.
           *
           * Checks if the preset is associated with fields and adds new ones to the properties.
           */
          $scope.selectFields = function() {
            $scope.cancelEditMode();

            var members;
            $scope.fields = [];
            if ($scope.selectedPreset) {
              //Update the feature
              var oldPreset = $scope.selectedFeature.feature.properties.preset;
              $scope.selectedFeature.feature.properties.preset = getSelectedPresetName($scope.selectedPreset);
              MapHandler.updateOnlyProperties($scope.selectedFeature);

              members = $scope.presets[$scope.selectedPreset].fields || [];

              //Remove the fields of older presets from the feature
              if (oldPreset) {
                var oldMembers = presets[oldPreset].fields || [];
                if (oldMembers) {
                  oldMembers.forEach(function(member) {
                    //only delete members if they aren't used by the new preset
                    if (members.indexOf(member) === -1) {
                      var index = $scope.fields.indexOf(fields[member]);
                      if (index > -1) {
                        $scope.fields.splice(index, 1);
                      }
                      removePropertyType(fields[member].label);
                    }
                  });
                }
              }

              //Get the fields of the preset
              members = $scope.presets[$scope.selectedPreset].fields || [];
              members.forEach(function(member) {
                var newKey = fields[member].label;
                //Only append if not already existing
                if (!$scope.selectedFeature.feature.properties.hasOwnProperty(newKey)) {
                  addNewPropertyType(newKey);
                }
                //Scope array for the GUI
                $scope.fields.push(fields[member]);
              });

            }

          };

          /**
           * Returns the key of the selected preset (sub-category)
           * @param  {String} index the key of the categories member object
           * @return {String}       preset name
           */

          function getSelectedPresetName(index) {
            if (index && $scope.categories[$scope.selectedCategory] && $scope.categories[$scope.selectedCategory].members && $scope.categories[$scope.selectedCategory].members[index]) {
              return $scope.categories[$scope.selectedCategory].members[index];
            }
          }

          getPresetData();

        }
      };
    }
  ]);
<|endoftext|>


<commit_before>'use strict'
var express = require('express'),
    app = express(),
    config = require('./libs/config'),
    router = require('./routes'),
    SerialPort = require('serialport').SerialPort,
    serialPort = new SerialPort(config.serial.path, {
      baudrate: config.serial.buadRate
    }, false),
    cards = require('./routes/cards'),
    door = require('./libs/door'),
    bodyParser = require('body-parser');

app.use(bodyParser.json())

app.get('/', function(req, res){
  res.send({
    active: true,
    timestamp: new Date().getTime()
  })
})

app.use('/api/v1', router)

// Server Listener
app.listen(config.app.port, config.app.host, function(e) {
  console.log('Listening on http://%s:%s', config.app.host, config.app.port)
  if(process.env.PROD) {
  door.setup(function() {
    serialPort.open(function (error) {
      if ( error ) {
        console.log('failed to open: '+ error);
      } else {
        serialPort.on('data', function(data) {
          cards.isUIDAllowed(data, function(err, allowed) {
            if (err) throw err;
            if (allowed) door.open();
          });
        }
      });
    });
  }
})
<commit_msg>add missing '}'<commit_after>'use strict'
var express = require('express'),
    app = express(),
    config = require('./libs/config'),
    router = require('./routes'),
    SerialPort = require('serialport').SerialPort,
    serialPort = new SerialPort(config.serial.path, {
      baudrate: config.serial.buadRate
    }, false),
    cards = require('./routes/cards'),
    door = require('./libs/door'),
    bodyParser = require('body-parser');

app.use(bodyParser.json())

app.get('/', function(req, res){
  res.send({
    active: true,
    timestamp: new Date().getTime()
  })
})

app.use('/api/v1', router)

// Server Listener
app.listen(config.app.port, config.app.host, function(e) {
  console.log('Listening on http://%s:%s', config.app.host, config.app.port)
  if(process.env.PROD) {
    door.setup(function() {
      serialPort.open(function (error) {
        if ( error ) {
          console.log('failed to open: '+ error);
        } else {
          serialPort.on('data', function(data) {
            cards.isUIDAllowed(data, function(err, allowed) {
              if (err) throw err;
              if (allowed) door.open();
            });
          }
        });
      });
    }
  }
})
<|endoftext|>


<commit_before>// Some general purpose JS functions for the iTop application
/**
 * Reload a truncated list
 */
function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams)
{
	$('#'+divId).addClass('loading');
	//$('#'+divId).blockUI();
	$.get('ajax.render.php?filter='+sSerializedFilter+'&style=list',
	   { operation: 'ajax', extra_params: sExtraParams },
	   function(data){
		 $('#'+divId).empty();
		 $('#'+divId).append(data);
		 $('#'+divId).removeClass('loading');
		 $('#'+divId+' .listResults').tableHover(); // hover tables
		 $('#'+divId+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables
		 //$('#'+divId).unblockUI();
		}
	 );
}
/**
 * Reload any block -- used for periodic auto-reload
 */
function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams)
{
	$('#'+divId).addClass('loading');
	//$('#'+divId).blockUI();
	$.get('ajax.render.php?filter='+sSerializedFilter+'&style='+sStyle,
	   { operation: 'ajax', extra_params: sExtraParams },
	   function(data){
		 $('#'+divId).empty();
		 $('#'+divId).append(data);
		 $('#'+divId).removeClass('loading');
		 $('#'+divId+' .listResults').tableHover(); // hover tables
		 $('#'+divId+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables
		 //$('#'+divId).unblockUI();
		}
	 );
}

/**
 * Update the display and value of a file input widget when the user picks a new file
 */
function UpdateFileName(id, sNewFileName)
{
	var aPath = sNewFileName.split('\\');
	var sNewFileName = aPath[aPath.length-1];

	$('#'+id).val(sNewFileName);
	$('#name_'+id).text(sNewFileName);
}
<commit_msg>- Search forms now display the subclasses of the specified class...<commit_after>// Some general purpose JS functions for the iTop application
/**
 * Reload a truncated list
 */
function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams)
{
	$('#'+divId).addClass('loading');
	//$('#'+divId).blockUI();
	$.get('ajax.render.php?filter='+sSerializedFilter+'&style=list',
	   { operation: 'ajax', extra_params: sExtraParams },
	   function(data){
		 $('#'+divId).empty();
		 $('#'+divId).append(data);
		 $('#'+divId).removeClass('loading');
		 $('#'+divId+' .listResults').tableHover(); // hover tables
		 $('#'+divId+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables
		 //$('#'+divId).unblockUI();
		}
	 );
}
/**
 * Reload any block -- used for periodic auto-reload
 */
function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams)
{
	$('#'+divId).addClass('loading');
	//$('#'+divId).blockUI();
	$.get('ajax.render.php?filter='+sSerializedFilter+'&style='+sStyle,
	   { operation: 'ajax', extra_params: sExtraParams },
	   function(data){
		 $('#'+divId).empty();
		 $('#'+divId).append(data);
		 $('#'+divId).removeClass('loading');
		 $('#'+divId+' .listResults').tableHover(); // hover tables
		 $('#'+divId+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables
		 //$('#'+divId).unblockUI();
		}
	 );
}

/**
 * Update the display and value of a file input widget when the user picks a new file
 */
function UpdateFileName(id, sNewFileName)
{
	var aPath = sNewFileName.split('\\');
	var sNewFileName = aPath[aPath.length-1];

	$('#'+id).val(sNewFileName);
	$('#name_'+id).text(sNewFileName);
}
/**
 * Reload a search form for the specified class
 */
function ReloadSearchForm(divId, sClassName, sBaseClass)
{
	$('#'+divId).block();
	var formEvents = $('#'+divId+' form').data('events');
	var bSubmitHookIsUsed = false;
	if ( (formEvents != undefined) && (SubmitHook != undefined))
	{
		// Assume that we're using the function submit hook...
		bSubmitHookIsUsed = true;
	}
	$('#'+divId+' form').unbind('submit');
	$.get('ajax.render.php',
	   { operation: 'search_form', className: sClassName, baseClass: sBaseClass, currentId: divId },
	   function(data){
		   $('#'+divId).empty();
		   $('#'+divId).append(data);
		   if (bSubmitHookIsUsed)
		   {
			   $('#'+divId+' form').bind('submit', SubmitHook);
		   }
		   $('#'+divId).unblock();
	   }
	 );

}
<|endoftext|>


<commit_before>angular.module( 'sailng.post', [
])

.config(function config( $stateProvider ) {
	$stateProvider.state( 'post', {
		url: '/post',
		views: {
			"main": {
				controller: 'PostCtrl',
				templateUrl: 'post/index.tpl.html'
			}
		},
		resolve: {
            messages: function(MessageModel) {
                return MessageModel.getAll().then(function(models) {
                    return models;
                });
            }
        }
	});
})

.controller( 'PostCtrl', function PostController( $scope, $sailsSocket, lodash, titleService, config, PostModel, messages) {
	titleService.setTitle('post');
	$scope.newPost = {};
    //$scope.posts = posts;
    $scope.currentUser = config.currentUser;
	//$scope.master = {};

    $sailsSocket.subscribe('post', function (envelope) {
	    switch(envelope.verb) {
	        case 'created':
	            //$scope.posts.unshift(envelope.data);
	            break;
	        case 'destroyed':
	            //lodash.remove($scope.posts, {id: envelope.id});
	            break;
	    }
    });

	$scope.createPost = function(newPost) {
        newPost.user = config.currentUser.id;
        PostModel.create(newPost).then(function(model) {
            $scope.newPost = {};
        });
    };

});
<commit_msg>hello<commit_after>angular.module( 'sailng.post', [
])

.config(function config( $stateProvider ) {
	$stateProvider.state( 'post', {
		url: '/post',
		views: {
			"main": {
				controller: 'PostCtrl',
				templateUrl: 'post/index.tpl.html'
			}
		},
		resolve: {
            posts: function() {}
        }
	});
})

.controller( 'PostCtrl', function PostController( $scope, $sailsSocket, lodash, titleService, config, PostModel) {
	titleService.setTitle('post');
	$scope.newPost = {};
    //$scope.posts = posts;
    $scope.currentUser = config.currentUser;

    $sailsSocket.subscribe('post', function (envelope) {
	    switch(envelope.verb) {
	        case 'created':
	            //$scope.posts.unshift(envelope.data);
	            break;
	        case 'destroyed':
	            //lodash.remove($scope.posts, {id: envelope.id});
	            break;
	    }
    });

	$scope.createPost = function(newPost) {
        newPost.user = config.currentUser.id;
        PostModel.create(newPost).then(function(model) {
            $scope.newPost = {};
        });
    };

});
<|endoftext|>


<commit_before>// @see https://devcenter.heroku.com/articles/nodejs#write-your-app

var express = require('express')
  , fs      = require('fs') // node
  , path    = require('path') //node
  , gm      = require('gm')
  , http    = require('http') // node
  , https   = require('https') // node
  , mime    = require('mime') // express
  , url     = require('url') // node
  , md5     = require('MD5')
  , glob    = require('glob')
  // @see http://aaronheckmann.posterous.com/graphicsmagick-on-heroku-with-nodejs
  , imageMagick = gm.subClass({imageMagick: true})
  , app = express.createServer(express.logger())
  , mimeTypes = [
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/jpg'
  ];

app.get('/crossdomain.xml', function(req, res, next){
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Headers", "X-Requested-With");
    res.setHeader("Content-Type", "text/xml");
    var filePath = path.join(__dirname, "crossdomain.xml");
    var stream = fs.createReadStream(filePath);
    stream.pipe(res);
});

// Set default file extension for image/jpg
mime.define({
  'image/jpeg': ['jpg'],
  'image/jpg': ['jpg']
});

app.get('/:url/:width/:height', function (req, res, next) {

    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Headers", "X-Requested-With");
    res.setHeader("Cache-Control", "public, max-age: 300");
  var width = req.params.width
    , height = req.params.height
    , retrieve = function (remote) {
      // @see http://nodejs.org/api/url.html#url_url
      var parts = url.parse(remote);
      if (['https:', 'http:'].indexOf(parts.protocol) === -1) {
        return res.send('Expected URI scheme to be HTTP or HTTPS', 404);
      }
      if (!parts.hostname) {
        return res.send('Expected URI host to be non-empty', 404);
      }

      // see if its cached
      var hash = width+'x'+height+'-'+md5(remote);
      var cachedPath = '/tmp/'+hash;

      try {
        //var fd = fs.openSync(cachedPath, 'r');
        var files = glob.sync(cachedPath+'.*');
        //console.log(files);
        if(files.length){

          var fr = fs.readFileSync(files[0]);
          if(fr) {
            console.log('Cache hit: '+files[0]);
            res.setHeader('X-Cache', 'HIT');
            //res.writeHead(304, {
              //'Content-Type': mime.lookup(files[0]),
              //'Cache-Control': 'max-age=31536000, public', // 1 year
            //});
            res.setHeader('Content-Type', mime.lookup(files[0]));
            //var img = new Buffer(fr, 'base64');
            return res.status(304).send(fr);
          }
          console.log('File not read');
        }else {
          console.log('Cache miss: '+cachedPath);
        }
      } catch (e) {
        console.log(e);
        //console.log('Cached file '+cachedPath+' not found');
      }

      var agent = parts.protocol === 'http:' ? http : https
        // @see http://nodejs.org/api/http.html#http_http_get_options_callback
        , request = agent.get(remote, function (res2) {
          // @see http://nodejs.org/api/http.html#http_response_statuscode
          if ([301, 302].indexOf(res2.statusCode) !== -1 && res2.headers['location']) {
            return retrieve(res2.headers['location']);
          }

          // The remote image must return status code 200.
          if (res2.statusCode !== 200) {
            return res.status(404).send('Expected response code 200, got ' + res2.statusCode, 404);
          }

          // The remote image must be a valid content type.
          // @see http://nodejs.org/api/http.html#http_request_headers
          var mimeType = res2.headers['content-type'].replace(/;.+/, '');
          if (mimeTypes.indexOf(mimeType) === -1) {
            if (mimeType == 'video/mp4') {
              var writeStream = fs.createWriteStream(cachedPath + '.' + mime.extension(mimeType));
              res2.pipe(writeStream);
              return res2.pipe(res);
            }

            return res.send('Expected content type ' + mimeTypes.join(', ') + ', got ' + mimeType, 404);
          }

          // @see https://github.com/aheckmann/gm#constructor
          imageMagick(res2, 'image.' + mime.extension(mimeType))
          // @see http://www.imagemagick.org/Usage/thumbnails/#cut
          .resize(width, height + '^>')
          .gravity('Center') // faces are most often near the center
          .extent(width, height)
          .stream(function (err, stdout, stderr) {
            if (err) return next(err);
            stdout.setMaxListeners(500);
            // Log errors in production.
            stderr.pipe(process.stderr);
            // @see http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html
//            res.writeHead(200, {
//              'Content-Type': mimeType,
//              'Cache-Control': 'max-age=31536000, public', // 1 year
//            });
            console.log('Cache save: '+cachedPath+'.'+mime.extension(mimeType));
            var writeStream = fs.createWriteStream(cachedPath+'.'+mime.extension(mimeType));

            stdout.pipe(writeStream);
            stdout.pipe(res);
          });
        }).on('error', next);

      // Timeout after five seconds. Better luck next time.
      request.setTimeout(5000, function () {
        return res.send(504);
      });
    };

  // Validate query string parameters.
  if (whitelist.length) {
    var parts = url.parse(req.params.url)
      , any = false;
    for (var _i = 0, _len = whitelist.length; _i < _len; _i++) {
      if (whitelist[_i].test(parts.hostname)) {
        any = true;
        break;
      }
    }
    if (!any) { // if none
      return res.send('Expected URI host to be whitelisted', 404);
    }
  }
  if (isNaN(parseInt(width))) {
    return res.send('Expected width to be an integer', 404);
  }
  if (parseInt(width) > 1000) {
    return res.send('Expected width to be less than or equal to 1000', 404);
  }
  if (isNaN(parseInt(height))) {
    return res.send('Expected height to be an integer', 404);
  }
  if (parseInt(height) > 1000) {
    return res.send('Expected height to be less than or equal to 1000', 404);
  }

  retrieve(req.params.url);
});

var port = process.env.PORT || 5000;
var whitelist = process.env.WHITELIST || []; // [/\.gov$/, /google\.com$/]
app.listen(port, function () {
  console.log('Listening on ' + port);
});
<commit_msg>Revert "Send a 304 for cache hit"<commit_after>// @see https://devcenter.heroku.com/articles/nodejs#write-your-app

var express = require('express')
  , fs      = require('fs') // node
  , path    = require('path') //node
  , gm      = require('gm')
  , http    = require('http') // node
  , https   = require('https') // node
  , mime    = require('mime') // express
  , url     = require('url') // node
  , md5     = require('MD5')
  , glob    = require('glob')
  // @see http://aaronheckmann.posterous.com/graphicsmagick-on-heroku-with-nodejs
  , imageMagick = gm.subClass({imageMagick: true})
  , app = express.createServer(express.logger())
  , mimeTypes = [
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/jpg'
  ];

app.get('/crossdomain.xml', function(req, res, next){
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Headers", "X-Requested-With");
    res.setHeader("Content-Type", "text/xml");
    var filePath = path.join(__dirname, "crossdomain.xml");
    var stream = fs.createReadStream(filePath);
    stream.pipe(res);
});

// Set default file extension for image/jpg
mime.define({
  'image/jpeg': ['jpg'],
  'image/jpg': ['jpg']
});

app.get('/:url/:width/:height', function (req, res, next) {

    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Headers", "X-Requested-With");
    res.setHeader("Cache-Control", "public, max-age: 300");
  var width = req.params.width
    , height = req.params.height
    , retrieve = function (remote) {
      // @see http://nodejs.org/api/url.html#url_url
      var parts = url.parse(remote);
      if (['https:', 'http:'].indexOf(parts.protocol) === -1) {
        return res.send('Expected URI scheme to be HTTP or HTTPS', 404);
      }
      if (!parts.hostname) {
        return res.send('Expected URI host to be non-empty', 404);
      }

      // see if its cached
      var hash = width+'x'+height+'-'+md5(remote);
      var cachedPath = '/tmp/'+hash;

      try {
        //var fd = fs.openSync(cachedPath, 'r');
        var files = glob.sync(cachedPath+'.*');
        //console.log(files);
        if(files.length){

          var fr = fs.readFileSync(files[0]);
          if(fr) {
            console.log('Cache hit: '+files[0]);
            res.setHeader('X-Cache', 'HIT');
            //res.writeHead(304, {
              //'Content-Type': mime.lookup(files[0]),
              //'Cache-Control': 'max-age=31536000, public', // 1 year
            //});
            res.setHeader('Content-Type', mime.lookup(files[0]));
            //var img = new Buffer(fr, 'base64');
            return res.status(200).send(fr);
          }
          console.log('File not read');
        }else {
          console.log('Cache miss: '+cachedPath);
        }
      } catch (e) {
        console.log(e);
        //console.log('Cached file '+cachedPath+' not found');
      }

      var agent = parts.protocol === 'http:' ? http : https
        // @see http://nodejs.org/api/http.html#http_http_get_options_callback
        , request = agent.get(remote, function (res2) {
          // @see http://nodejs.org/api/http.html#http_response_statuscode
          if ([301, 302].indexOf(res2.statusCode) !== -1 && res2.headers['location']) {
            return retrieve(res2.headers['location']);
          }

          // The remote image must return status code 200.
          if (res2.statusCode !== 200) {
            return res.status(404).send('Expected response code 200, got ' + res2.statusCode, 404);
          }

          // The remote image must be a valid content type.
          // @see http://nodejs.org/api/http.html#http_request_headers
          var mimeType = res2.headers['content-type'].replace(/;.+/, '');
          if (mimeTypes.indexOf(mimeType) === -1) {
            if (mimeType == 'video/mp4') {
              var writeStream = fs.createWriteStream(cachedPath + '.' + mime.extension(mimeType));
              res2.pipe(writeStream);
              return res2.pipe(res);
            }

            return res.send('Expected content type ' + mimeTypes.join(', ') + ', got ' + mimeType, 404);
          }

          // @see https://github.com/aheckmann/gm#constructor
          imageMagick(res2, 'image.' + mime.extension(mimeType))
          // @see http://www.imagemagick.org/Usage/thumbnails/#cut
          .resize(width, height + '^>')
          .gravity('Center') // faces are most often near the center
          .extent(width, height)
          .stream(function (err, stdout, stderr) {
            if (err) return next(err);
            stdout.setMaxListeners(500);
            // Log errors in production.
            stderr.pipe(process.stderr);
            // @see http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html
//            res.writeHead(200, {
//              'Content-Type': mimeType,
//              'Cache-Control': 'max-age=31536000, public', // 1 year
//            });
            console.log('Cache save: '+cachedPath+'.'+mime.extension(mimeType));
            var writeStream = fs.createWriteStream(cachedPath+'.'+mime.extension(mimeType));

            stdout.pipe(writeStream);
            stdout.pipe(res);
          });
        }).on('error', next);

      // Timeout after five seconds. Better luck next time.
      request.setTimeout(5000, function () {
        return res.send(504);
      });
    };

  // Validate query string parameters.
  if (whitelist.length) {
    var parts = url.parse(req.params.url)
      , any = false;
    for (var _i = 0, _len = whitelist.length; _i < _len; _i++) {
      if (whitelist[_i].test(parts.hostname)) {
        any = true;
        break;
      }
    }
    if (!any) { // if none
      return res.send('Expected URI host to be whitelisted', 404);
    }
  }
  if (isNaN(parseInt(width))) {
    return res.send('Expected width to be an integer', 404);
  }
  if (parseInt(width) > 1000) {
    return res.send('Expected width to be less than or equal to 1000', 404);
  }
  if (isNaN(parseInt(height))) {
    return res.send('Expected height to be an integer', 404);
  }
  if (parseInt(height) > 1000) {
    return res.send('Expected height to be less than or equal to 1000', 404);
  }

  retrieve(req.params.url);
});

var port = process.env.PORT || 5000;
var whitelist = process.env.WHITELIST || []; // [/\.gov$/, /google\.com$/]
app.listen(port, function () {
  console.log('Listening on ' + port);
});
<|endoftext|>


<commit_before>/*global FormplayerFrontend */

hqDefine('cloudcare/js/util', function () {
    if (!String.prototype.startsWith) {
        String.prototype.startsWith = function (searchString, position) {
            position = position || 0;
            return this.indexOf(searchString, position) === position;
        };
    }

    NProgress.configure({
        showSpinner: false,
    });

    var getFormUrl = function (urlRoot, appId, moduleId, formId, instanceId) {
        // TODO: make this cleaner
        var url = urlRoot + "view/" + appId + "/modules-" + moduleId + "/forms-" + formId + "/context/";
        if (instanceId) {
            url += '?instance_id=' + instanceId;
        }
        return url;
    };

    var getSubmitUrl = function (urlRoot, appId) {
        // deprecated but still called from "touchforms-inline"
        // which is used to fill out forms from within case details view
        // use app.getSubmitUrl instead
        // todo: replace and remove
        return urlRoot + "/" + appId + "/";
    };

    var showError = function (message, $el) {
        if (message === undefined) {
            message = gettext("Sorry, an error occurred while processing that request.");
        }
        _show(message, $el, null, "alert alert-danger");
        reportFormplayerErrorToHQ({
            type: 'show_error_notification',
            message: message,
        });

    };

    var showWarning = function (message, $el) {
        if (message === undefined) {
            return;
        }
        _show(message, $el, 5000, "alert alert-warning");
    };

    var showHTMLError = function (message, $el, autoHideTime) {
        message = message || gettext("Sorry, an error occurred while processing that request.");
        var $container = _show(message, $el, autoHideTime, "", true);
        try {
            message = $container.text();  // pull out just the text the user sees
            message = message.replace(/\s+/g, ' ').trim();
        } catch (e) {
            // leave the message as at came in if there's an issue parsing text from the container
        }
        reportFormplayerErrorToHQ({
            type: 'show_error_notification',
            message: message,
        });
    };

    var showSuccess = function (message, $el, autoHideTime, isHTML) {
        if (message === undefined) {
            message = "Success";
        }
        return _show(message, $el, autoHideTime, "alert alert-success", isHTML);
    };

    var _show = function (message, $el, autoHideTime, classes, isHTML) {
        var $container = $("<div />"),
            $alertDialog;
        $container.addClass(classes);
        if (isHTML) {
            $container.html(message);
        } else {
            $container.text(message);
        }
        // HTML errors may already have an alert dialog
        $alertDialog = $container.hasClass("alert") ? $container : $container.find('.alert');
        $alertDialog
            .prepend(
                $("<a />")
                    .addClass("close")
                    .attr("data-dismiss", "alert")
                    .html("×")
            );
        $el.append($container);
        if (autoHideTime) {
            $container.delay(autoHideTime).fadeOut(500);
        }
        return $container;
    };

    var showLoading = function (selector) {
        NProgress.start();
    };

    var formplayerLoading = function (selector) {
        showLoading();
    };

    var formplayerLoadingComplete = function (isError, message) {
        hideLoading();
        if (isError) {
            showError(message || gettext('Error saving!'), $('#cloudcare-notifications'));
        }
    };

    var formplayerSyncComplete = function (isError) {
        hideLoading();
        if (isError) {
            showError(
                gettext('Could not sync user data. Please report an issue if this persists.'),
                $('#cloudcare-notifications')
            );
        } else {
            showSuccess(gettext('User Data successfully synced.'), $('#cloudcare-notifications'), 5000);
        }
    };

    var clearUserDataComplete = function (isError) {
        hideLoading();
        if (isError) {
            showError(
                gettext('Could not clear user data. Please report an issue if this persists.'),
                $('#cloudcare-notifications')
            );
        } else {
            showSuccess(gettext('User data successfully cleared.'), $('#cloudcare-notifications'), 5000);
        }
    };

    var hideLoading = function (selector) {
        NProgress.done();
    };

    var reportFormplayerErrorToHQ = function (data) {
        try {
            var reverse = hqImport("hqwebapp/js/initial_page_data").reverse;
            var cloudcareEnv = FormplayerFrontend.request('currentUser').environment;
            if (!data.cloudcareEnv) {
                data.cloudcareEnv = cloudcareEnv || 'unknown';
            }
            $.ajax({
                type: 'POST',
                url: reverse('report_formplayer_error'),
                data: JSON.stringify(data),
                contentType: "application/json",
                dataType: "json",
                success: function () {
                    window.console.info('Successfully reported error: ' + JSON.stringify(data));
                },
                error: function () {
                    window.console.error('Failed to report error: ' + JSON.stringify(data));
                },
            });
        } catch (e) {
            window.console.error(
                "reportFormplayerErrorToHQ failed hard and there is nowhere " +
                "else to report this error: " + JSON.stringify(data),
                e
            );
        }
    };

    return {
        getFormUrl: getFormUrl,
        getSubmitUrl: getSubmitUrl,
        showError: showError,
        showWarning: showWarning,
        showHTMLError: showHTMLError,
        showSuccess: showSuccess,
        clearUserDataComplete: clearUserDataComplete,
        formplayerLoading: formplayerLoading,
        formplayerLoadingComplete: formplayerLoadingComplete,
        formplayerSyncComplete: formplayerSyncComplete,
        reportFormplayerErrorToHQ: reportFormplayerErrorToHQ,
    };
});
<commit_msg>Add back original htmlMessage in cloudcare errors<commit_after>/*global FormplayerFrontend */

hqDefine('cloudcare/js/util', function () {
    if (!String.prototype.startsWith) {
        String.prototype.startsWith = function (searchString, position) {
            position = position || 0;
            return this.indexOf(searchString, position) === position;
        };
    }

    NProgress.configure({
        showSpinner: false,
    });

    var getFormUrl = function (urlRoot, appId, moduleId, formId, instanceId) {
        // TODO: make this cleaner
        var url = urlRoot + "view/" + appId + "/modules-" + moduleId + "/forms-" + formId + "/context/";
        if (instanceId) {
            url += '?instance_id=' + instanceId;
        }
        return url;
    };

    var getSubmitUrl = function (urlRoot, appId) {
        // deprecated but still called from "touchforms-inline"
        // which is used to fill out forms from within case details view
        // use app.getSubmitUrl instead
        // todo: replace and remove
        return urlRoot + "/" + appId + "/";
    };

    var showError = function (message, $el) {
        if (message === undefined) {
            message = gettext("Sorry, an error occurred while processing that request.");
        }
        _show(message, $el, null, "alert alert-danger");
        reportFormplayerErrorToHQ({
            type: 'show_error_notification',
            message: message,
        });

    };

    var showWarning = function (message, $el) {
        if (message === undefined) {
            return;
        }
        _show(message, $el, 5000, "alert alert-warning");
    };

    var showHTMLError = function (message, $el, autoHideTime) {
        var htmlMessage = message = message || gettext("Sorry, an error occurred while processing that request.");
        var $container = _show(message, $el, autoHideTime, "", true);
        try {
            message = $container.text();  // pull out just the text the user sees
            message = message.replace(/\s+/g, ' ').trim();
        } catch (e) {
            // leave the message as at came in if there's an issue parsing text from the container
        }
        reportFormplayerErrorToHQ({
            type: 'show_error_notification',
            message: message,
            htmlMessage: htmlMessage,
        });
    };

    var showSuccess = function (message, $el, autoHideTime, isHTML) {
        if (message === undefined) {
            message = "Success";
        }
        return _show(message, $el, autoHideTime, "alert alert-success", isHTML);
    };

    var _show = function (message, $el, autoHideTime, classes, isHTML) {
        var $container = $("<div />"),
            $alertDialog;
        $container.addClass(classes);
        if (isHTML) {
            $container.html(message);
        } else {
            $container.text(message);
        }
        // HTML errors may already have an alert dialog
        $alertDialog = $container.hasClass("alert") ? $container : $container.find('.alert');
        $alertDialog
            .prepend(
                $("<a />")
                    .addClass("close")
                    .attr("data-dismiss", "alert")
                    .html("×")
            );
        $el.append($container);
        if (autoHideTime) {
            $container.delay(autoHideTime).fadeOut(500);
        }
        return $container;
    };

    var showLoading = function (selector) {
        NProgress.start();
    };

    var formplayerLoading = function (selector) {
        showLoading();
    };

    var formplayerLoadingComplete = function (isError, message) {
        hideLoading();
        if (isError) {
            showError(message || gettext('Error saving!'), $('#cloudcare-notifications'));
        }
    };

    var formplayerSyncComplete = function (isError) {
        hideLoading();
        if (isError) {
            showError(
                gettext('Could not sync user data. Please report an issue if this persists.'),
                $('#cloudcare-notifications')
            );
        } else {
            showSuccess(gettext('User Data successfully synced.'), $('#cloudcare-notifications'), 5000);
        }
    };

    var clearUserDataComplete = function (isError) {
        hideLoading();
        if (isError) {
            showError(
                gettext('Could not clear user data. Please report an issue if this persists.'),
                $('#cloudcare-notifications')
            );
        } else {
            showSuccess(gettext('User data successfully cleared.'), $('#cloudcare-notifications'), 5000);
        }
    };

    var hideLoading = function (selector) {
        NProgress.done();
    };

    var reportFormplayerErrorToHQ = function (data) {
        try {
            var reverse = hqImport("hqwebapp/js/initial_page_data").reverse;
            var cloudcareEnv = FormplayerFrontend.request('currentUser').environment;
            if (!data.cloudcareEnv) {
                data.cloudcareEnv = cloudcareEnv || 'unknown';
            }
            $.ajax({
                type: 'POST',
                url: reverse('report_formplayer_error'),
                data: JSON.stringify(data),
                contentType: "application/json",
                dataType: "json",
                success: function () {
                    window.console.info('Successfully reported error: ' + JSON.stringify(data));
                },
                error: function () {
                    window.console.error('Failed to report error: ' + JSON.stringify(data));
                },
            });
        } catch (e) {
            window.console.error(
                "reportFormplayerErrorToHQ failed hard and there is nowhere " +
                "else to report this error: " + JSON.stringify(data),
                e
            );
        }
    };

    return {
        getFormUrl: getFormUrl,
        getSubmitUrl: getSubmitUrl,
        showError: showError,
        showWarning: showWarning,
        showHTMLError: showHTMLError,
        showSuccess: showSuccess,
        clearUserDataComplete: clearUserDataComplete,
        formplayerLoading: formplayerLoading,
        formplayerLoadingComplete: formplayerLoadingComplete,
        formplayerSyncComplete: formplayerSyncComplete,
        reportFormplayerErrorToHQ: reportFormplayerErrorToHQ,
    };
});
<|endoftext|>