<commit_before>/*

The endpoints have been implemented in a minimal way, to allow
the widgets to function - this is NOT a full P4M implementation

*/

var Express = require('express');

var Cookies     	= require('cookies');
var request 		= require('request');
var fs				= require('fs');

exports.getP4MAccessToken = function(req, res) {

	var cookies = new Cookies( req, res );

	if (cookies.get('p4mState') != req.query.state) {
	    res.status(500).send('Authentication error (p4mState)');
	}

	var url = "https://dev-ids.parcelfor.me/connect/token";

	var data = {
		grant_type		: "authorization_code",
		code			: req.query.code,
		redirect_uri	: "http://localhost:8080/p4m/getP4MAccessToken",
		client_id		: "10006"
	}

	var options = {
		url: url,
		headers: {
			"Authorization": "Basic " + new Buffer("10006:secret").toString('base64'),
			"Content-Type": "application/x-www-form-urlencoded"
		},
		port: 443,

		form : data,

		rejectUnauthorized : false

	}

	function isEmptyObject(obj) {
		return !Object.keys(obj).length;
	}

	request.post(options, function(error, response, body) {

		if (error && !isEmptyObject(error)) {
			res.status(500).send(error);
		}

		var now = new Date();
		var cookieConf = { path : '/', expires: new Date(now.setFullYear(now.getFullYear() + 1)) , httpOnly: false };

		cookies.set('p4mToken', JSON.parse(body).access_token, cookieConf);

		res.status(200).send('<script>window.close();</script>');
		res.end(); // needed for cookies to save !
	});

};

exports.localLogin = function(req, res) {

	var cookies = new Cookies( req, res );

	// see these cookies that are used by the other p4m widgets
	var now = new Date();
	var cookieConf = { path : '/', expires: new Date(now.setFullYear(now.getFullYear() + 1)) , httpOnly: false };
	cookies.set('p4mAvatarUrl', 		'http://localhost:8080/profile.png', cookieConf);
	cookies.set('p4mGivenName', 		'Hugo', cookieConf);
	cookies.set('p4mDefaultPostCode', 	'4000', cookieConf);
	cookies.set('p4mDefaultCountryCode', 'AU', cookieConf);
	cookies.set('p4mOfferCartRestore', 	'true', cookieConf);
	cookies.set('p4mLocalLogin', 		'true', cookieConf);

	res.status(200).json({ "RedirectUrl": null, "Success": true, "Error": null});
	res.end(); // needed for cookies to save !
};

exports.checkout = function(req, res) {

	var cookies = new Cookies( req, res );

	if (true)
	//if ( (cookies.get('gfsCheckoutToken')==null) || (cookies.get('gfsCheckoutToken')=='') )
	{

		var url = "https://identity.justshoutgfs.com/connect/token";

		var data = {
			grant_type	: "client_credentials",
			scope 		: "read checkout-api"
		}

		var options = {
			url: url,
			headers: {
				"Authorization": "Basic " + Buffer.from("parcel_4_me:needmoreparcels").toString('base64'),
				//"Authorization": "Basic " + Buffer.from("michael.strong@justshoutgfs.com:MScheckout!").toString('base64'),
				"Content-Type": "application/x-www-form-urlencoded"
			},
			form : data
		}

		request.post(options, function(error, response, body) {
			console.log(body);
			var data = JSON.parse(body);
			var now = new Date();
			var cookieConf = { path : '/', expires: new Date(now.setFullYear(now.getFullYear() + 1)) , httpOnly: false };
			var base64Token = Buffer.from(data.access_token).toString('base64');
			cookies.set('gfsCheckoutToken', base64Token, cookieConf);

			returnTemplateFile('checkout.html', '[gfs-access-token]', base64Token, res);

		});
	}
	else {
		console.log('THIS NEVER HAPPENS CURRENTLY.. but if i stored the gfs-access-token somewhere then I could do this logic ..');
		returnTemplateFile('checkout.html', '[gfs-access-token]', base64Token, res);
	}
}

exports.itemQtyChanged = function(req, res) {
    var result = {
        success: true
    };
    res.status(200).json(result);
}

function returnTemplateFile(file, find, replace, res) {

	fs.readFile('static_api/templates/'+file, function read(err, file_contents) {
		if (err) {
			res.status(500).send(err);
		}
		// IMPLEMENTING ONLY THE MOST BASIC FIND REPLACE, OF ONE SHORT CODE AND ONLY ONE OCCURANCE
		file_contents = file_contents.toString('utf8').replace(find, replace);

		res.set('Content-Type', 'text/html');
		res.status(200).send(file_contents);
		res.end(); // needed for cookies to save !
	});
}

exports.updShippingService = function(req, res) {
    var result = {
        discount: 0,
        error: null,
        shipping: req.body.Amount,
        success: true
    };
    try {
		result.tax = Math.floor(result.shipping / 10);
		result.total = result.tax + result.shipping;
    }
    catch(e) {
    	console.error(e);
    	result.tax = 0;
    	result.total = result.shipping;
    }
    res.status(200).json(result);
}

exports.applyDiscountCode = function(req, res) {
	if (req.body.discountCode == "AAA") {
		res.status(200).json(
			{
				"code": req.body.discountCode,
				"valid": true,
				"amount": 10.0,
				"description": "Special discount",
				"note": "Special discount"
			}
		);
	}
	else {
		res.status(200).json(
			{
				"code": req.body.discountCode,
				"valid": false,
				"error": "Not a valid code"
			}
		);

	}
}<commit_msg>Any coupon starting with "f" will be considered valid.<commit_after>/*

The endpoints have been implemented in a minimal way, to allow
the widgets to function - this is NOT a full P4M implementation

*/

var Express = require('express');

var Cookies     	= require('cookies');
var request 		= require('request');
var fs				= require('fs');

exports.getP4MAccessToken = function(req, res) {

	var cookies = new Cookies( req, res );

	if (cookies.get('p4mState') != req.query.state) {
	    res.status(500).send('Authentication error (p4mState)');
	}

	var url = "https://dev-ids.parcelfor.me/connect/token";

	var data = {
		grant_type		: "authorization_code",
		code			: req.query.code,
		redirect_uri	: "http://localhost:8080/p4m/getP4MAccessToken",
		client_id		: "10006"
	}

	var options = {
		url: url,
		headers: {
			"Authorization": "Basic " + new Buffer("10006:secret").toString('base64'),
			"Content-Type": "application/x-www-form-urlencoded"
		},
		port: 443,

		form : data,

		rejectUnauthorized : false

	}

	function isEmptyObject(obj) {
		return !Object.keys(obj).length;
	}

	request.post(options, function(error, response, body) {

		if (error && !isEmptyObject(error)) {
			res.status(500).send(error);
		}

		var now = new Date();
		var cookieConf = { path : '/', expires: new Date(now.setFullYear(now.getFullYear() + 1)) , httpOnly: false };

		cookies.set('p4mToken', JSON.parse(body).access_token, cookieConf);

		res.status(200).send('<script>window.close();</script>');
		res.end(); // needed for cookies to save !
	});

};

exports.localLogin = function(req, res) {

	var cookies = new Cookies( req, res );

	// see these cookies that are used by the other p4m widgets
	var now = new Date();
	var cookieConf = { path : '/', expires: new Date(now.setFullYear(now.getFullYear() + 1)) , httpOnly: false };
	cookies.set('p4mAvatarUrl', 		'http://localhost:8080/profile.png', cookieConf);
	cookies.set('p4mGivenName', 		'Hugo', cookieConf);
	cookies.set('p4mDefaultPostCode', 	'4000', cookieConf);
	cookies.set('p4mDefaultCountryCode', 'AU', cookieConf);
	cookies.set('p4mOfferCartRestore', 	'true', cookieConf);
	cookies.set('p4mLocalLogin', 		'true', cookieConf);

	res.status(200).json({ "RedirectUrl": null, "Success": true, "Error": null});
	res.end(); // needed for cookies to save !
};

exports.checkout = function(req, res) {

	var cookies = new Cookies( req, res );

	if (true)
	//if ( (cookies.get('gfsCheckoutToken')==null) || (cookies.get('gfsCheckoutToken')=='') )
	{

		var url = "https://identity.justshoutgfs.com/connect/token";

		var data = {
			grant_type	: "client_credentials",
			scope 		: "read checkout-api"
		}

		var options = {
			url: url,
			headers: {
				"Authorization": "Basic " + Buffer.from("parcel_4_me:needmoreparcels").toString('base64'),
				//"Authorization": "Basic " + Buffer.from("michael.strong@justshoutgfs.com:MScheckout!").toString('base64'),
				"Content-Type": "application/x-www-form-urlencoded"
			},
			form : data
		}

		request.post(options, function(error, response, body) {
			console.log(body);
			var data = JSON.parse(body);
			var now = new Date();
			var cookieConf = { path : '/', expires: new Date(now.setFullYear(now.getFullYear() + 1)) , httpOnly: false };
			var base64Token = Buffer.from(data.access_token).toString('base64');
			cookies.set('gfsCheckoutToken', base64Token, cookieConf);

			returnTemplateFile('checkout.html', '[gfs-access-token]', base64Token, res);

		});
	}
	else {
		console.log('THIS NEVER HAPPENS CURRENTLY.. but if i stored the gfs-access-token somewhere then I could do this logic ..');
		returnTemplateFile('checkout.html', '[gfs-access-token]', base64Token, res);
	}
}

exports.itemQtyChanged = function(req, res) {
    var result = {
        success: true
    };
    res.status(200).json(result);
}

function returnTemplateFile(file, find, replace, res) {

	fs.readFile('static_api/templates/'+file, function read(err, file_contents) {
		if (err) {
			res.status(500).send(err);
		}
		// IMPLEMENTING ONLY THE MOST BASIC FIND REPLACE, OF ONE SHORT CODE AND ONLY ONE OCCURANCE
		file_contents = file_contents.toString('utf8').replace(find, replace);

		res.set('Content-Type', 'text/html');
		res.status(200).send(file_contents);
		res.end(); // needed for cookies to save !
	});
}

exports.updShippingService = function(req, res) {
    var result = {
        discount: 0,
        error: null,
        shipping: req.body.Amount,
        success: true
    };
    try {
		result.tax = Math.floor(result.shipping / 10);
		result.total = result.tax + result.shipping;
    }
    catch(e) {
    	console.error(e);
    	result.tax = 0;
    	result.total = result.shipping;
    }
    res.status(200).json(result);
}

exports.applyDiscountCode = function(req, res) {
	if (req.body.discountCode && req.body.discountCode.length && req.body.discountCode[0].toLowerCase() == "f") {
		res.status(200).json(
			{
				"code": req.body.discountCode,
				"valid": true,
				"amount": 10.0,
				"description": "Special discount",
				"note": "Special discount"
			}
		);
	}
	else {
		res.status(200).json(
			{
				"code": req.body.discountCode,
				"valid": false,
				"error": "Not a valid code"
			}
		);

	}
}<|endoftext|>


<commit_before>//Image Resolution/Dimentions overlay - shows on each image on the page within the main content area, as well
//  as the pop-up/overlay images in the gallery the actual resolution of the image being displayed
//  this is so that you do not have to inspect element to show the resolution of an image. *@
function ShowResolution(img, offset, $imgResolution) {
    var pos = $(img).offset();
    $imgResolution.text(img.naturalWidth + "x" + img.naturalHeight)
        .css({
            top: (pos.top + $(img).outerHeight() - $imgResolution.outerHeight() - offset) + "px",
            left: (pos.left + $(img).outerWidth() - $imgResolution.outerWidth()) + "px"
        }).show();
}

function ShowVideoResolution(container, offset, $imgResolution) {
    var pos = $(container).offset();
    $imgResolution.text($(container).attr("data-resolution"))
        .css({
            top: (pos.top + $(container).outerHeight() - $imgResolution.outerHeight() - offset) + "px",
            left: (pos.left + $(container).outerWidth() - $imgResolution.outerWidth()) + "px"
        }).show();
}

$(function () {
    var $imgResolution = $('

').css({
        display: 'none',
        position: 'absolute',
        'z-index': 2043,
        padding: '1px 2px 0px 2px',
        border: '1px solid #999',
        'background-color': 'white',
        filter: 'alpha(opacity=80)',
        '-moz-opacity': 0.8,
        '-khtml-opacity': 0.8,
        opacity: 0.5,
        'text-shadow': '1px 0 0 #fff, -1px 0 0 #fff, 0 1px 0 #fff, 0 -1px 0 #fff, 1px 1px #fff, -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff'
    });

    var hideImgResolutionIfVisible = function () {
        if (imgResolutionVisible()) {
            $imgResolution.hide();
        }
    };

    var onHover = function (_this, offset, callback) {
        if (!imgResolutionVisible()) {
            callback(_this, offset, $imgResolution);
        }
    };

    $("body").append($imgResolution).bind("DOMSubtreeModified", function () {
        $("img.mfp-img").mouseenter(function () {
            onHover(this, 40, ShowResolution);
        });
        $("video").mouseenter(function () {
            onHover(this, 35, ShowVideoResolution);
        });
        $("video, img").mouseout(hideImgResolutionIfVisible);
    });

    var imgResolutionVisible = function () {
        $imgResolution.is(":visible");
    };

    $("main img").mouseenter(function () {
        onHover(this, 0, ShowResolution);
    });
    $("main video").mouseenter(function () {
        onHover(this, 0, ShowVideoResolution);
    });
    $("main video, main img").mouseout(hideImgResolutionIfVisible);
});<commit_msg>TSI-1606 resolution overlay live events binding<commit_after>//Image Resolution/Dimensions overlay - shows on each image on the page within the main content area, as well
//  as the pop-up/overlay images in the gallery the actual resolution of the image being displayed
//  this is so that you do not have to inspect element to show the resolution of an image. *@
$(function () {
    var $imgResolution = $('

').css({
        display: 'none',
        position: 'absolute',
        'z-index': 2043,
        padding: '1px 2px 0px 2px',
        border: '1px solid #999',
        'background-color': 'white',
        filter: 'alpha(opacity=80)',
        '-moz-opacity': 0.8,
        '-khtml-opacity': 0.8,
        opacity: 0.5,
        'text-shadow': '1px 0 0 #fff, -1px 0 0 #fff, 0 1px 0 #fff, 0 -1px 0 #fff, 1px 1px #fff, -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff'
    });

    var ShowResolution = function (img, offset, $imgResolution) {
        var $img = $(img);
        var pos = $img.offset();
        $imgResolution.text(img.naturalWidth + "x" + img.naturalHeight)
            .css({
                top: (pos.top + $img.outerHeight() - $imgResolution.outerHeight() - offset) + "px",
                left: (pos.left + $img.outerWidth() - $imgResolution.outerWidth()) + "px"
            }).show();
    };

    var ShowVideoResolution = function (video, offset, $imgResolution) {
        var $video = $(video);
        var pos = $video.offset();
        $imgResolution.text($video.attr("data-resolution"))
            .css({
                top: (pos.top + $video.outerHeight() - $imgResolution.outerHeight() - offset) + "px",
                left: (pos.left + $video.outerWidth() - $imgResolution.outerWidth()) + "px"
            }).show();
    };

    var hideImgResolutionIfVisible = function () {
        if (imgResolutionVisible()) {
            $imgResolution.hide();
        }
    };

    var onHover = function (_this, offset, callback) {
        if (!imgResolutionVisible()) {
            callback(_this, offset, $imgResolution);
        }
    };

    var imgResolutionVisible = function () {
        return $imgResolution.is(":visible");
    };

    $("body").append($imgResolution)
        .on('mouseenter', 'main img', function () {
            onHover(this, 0, ShowResolution);
        })
        .on('mouseenter', 'main video', function () {
            onHover(this, 0, ShowVideoResolution);
        })
        .on('mouseleave', 'main img, main video', hideImgResolutionIfVisible);
});<|endoftext|>


<commit_before>function toGeoJSON(data){
    var outPut = { "type": "FeatureCollection",
  "features": []};
    var fl = data.features.length;
    var i = 0;
    while(fl>i){
     var ft = data.features[i];
     var outFT = {
            "type": "Feature",
            "properties":ft.attributes
        };
        if(ft.geometry.x){
          outFT.geometry=point(ft.geometry);
        }else if(ft.geometry.points){
            outFT.geometry=points(ft.geometry);
            }else if(ft.geometry.paths){
         outFT.geometry=line(ft.geometry);
        }else if(ft.geometry.rings){
           outFT.geometry=poly(ft.geometry);
        }

     outPut.features.push(outFT);
     i++;
    }
function point(geometry){
    return {"type": "Point","coordinates": [geometry.x,geometry.y]};
}
function points(geometry){
    if(geometry.points.length===1){
        return {"type": "Point","coordinates": geometry.points[0]};
    }else{
        return { "type": "MultiPoint","coordinates":geometry.points};
    }
}
function line(geometry){
    if(geometry.paths.length===1){
        return {"type": "LineString","coordinates": geometry.paths[0]};
    }else{
        return { "type": "MultiLineString","coordinates":geometry.paths};
    }
}
function poly(geometry){
    if(geometry.rings.length===1){
        return {"type": "Polygon","coordinates": geometry.rings};
    }else{
        var ccc= dP(geometry.rings);
        var d = ccc[0];
        var dd = ccc[1];
        var r=[];
        if(dd.length===0){
            var l2 = d.length;
            var i3 = 0;
            while(l2>i3){
             r.push([d[i3]]);
            }
            return { "type": "MultiPolygon","coordinates":r};
        }else if(d.length===1){
            dd.unshift(d[0]);
            return {"type": "Polygon","coordinates": dd};

        }else{
            return { "type": "MultiPolygon","coordinates":d, "holes":dd};
        }
    }
}
function dP(a){
    var d = [];
        var dd =[];
        var l = a.length;
        var ii = 0;
        while(l>ii){
            if(c(a[ii])){
                d.push(a[ii]);
            }else{
             dd.push(a[ii]);
            }
         ii++;
        }
    return [d,dd];
}
function c(a){
 var l = a.length-1;
 var i = 0;
 var o=0;

 while(l>i){
 o+=(a[i][0]*a[i+1][1]-a[i+1][0]*a[i][1]);

     i++;
 }
    return o<=0;
}
return outPut;
}

<commit_msg>added comments<commit_after>function toGeoJSON(data){
    var outPut = { "type": "FeatureCollection",
  "features": []};
    var fl = data.features.length;
    var i = 0;
    while(fl>i){
     var ft = data.features[i];
/* as only ESRI based products care if all the features are the same type of geometry, check for geometry type at a feature level*/
     var outFT = {
            "type": "Feature",
            "properties":ft.attributes
        };
        if(ft.geometry.x){
//check if it's a point
          outFT.geometry=point(ft.geometry);
        }else if(ft.geometry.points){
//check if it is a multipoint
            outFT.geometry=points(ft.geometry);
            }else if(ft.geometry.paths){
//check if a line (or "ARC" in ESRI terms)
         outFT.geometry=line(ft.geometry);
        }else if(ft.geometry.rings){
//check if a poly.
           outFT.geometry=poly(ft.geometry);
        }

     outPut.features.push(outFT);
     i++;
    }
function point(geometry){
//this one is easy
    return {"type": "Point","coordinates": [geometry.x,geometry.y]};
}
function points(geometry){
//checks if the multipoint only has one point, if so exports as point instead
    if(geometry.points.length===1){
        return {"type": "Point","coordinates": geometry.points[0]};
    }else{
        return { "type": "MultiPoint","coordinates":geometry.points};
    }
}
function line(geometry){
//checks if their are multiple paths or just one
    if(geometry.paths.length===1){
        return {"type": "LineString","coordinates": geometry.paths[0]};
    }else{
        return { "type": "MultiLineString","coordinates":geometry.paths};
    }
}
function poly(geometry){
//first we check for some easy cases, like if their is only one ring
    if(geometry.rings.length===1){
        return {"type": "Polygon","coordinates": geometry.rings};
    }else{
/*if it isn't that easy then we have to start checking ring direction, basically the ring goes clockwise its part of the polygon, if it goes counterclockwise it is a hole in the polygon, but geojson does it by haveing an array with the first element be the polygons and the next elements being holes in it*/
        var ccc= dP(geometry.rings);
        var d = ccc[0];
        var dd = ccc[1];
        var r=[];
        if(dd.length===0){
/*if their are no holes we don't need to worry about this, but do need to stuck each ring inside its own array*/
            var l2 = d.length;
            var i3 = 0;
            while(l2>i3){
             r.push([d[i3]]);
            }
            return { "type": "MultiPolygon","coordinates":r};
        }else if(d.length===1){
/*if their is only one clockwise ring then we know all holes are in that poly*/
            dd.unshift(d[0]);
            return {"type": "Polygon","coordinates": dd};

        }else{
/*if their are multiple rings and holes we have no way of knowing which belong to which without looking at it specially, so just dump the coordinates and add  a hole field, this may cause errors*/
            return { "type": "MultiPolygon","coordinates":d, "holes":dd};
        }
    }
}
function dP(a){
//returns an array of 2 arrays, the first being all the clockwise ones, the second counter clockwise
    var d = [];
        var dd =[];
        var l = a.length;
        var ii = 0;
        while(l>ii){
            if(c(a[ii])){
                d.push(a[ii]);
            }else{
             dd.push(a[ii]);
            }
         ii++;
        }
    return [d,dd];
}
function c(a){
//return true if clockwise
 var l = a.length-1;
 var i = 0;
 var o=0;

 while(l>i){
 o+=(a[i][0]*a[i+1][1]-a[i+1][0]*a[i][1]);

     i++;
 }
    return o<=0;
}
return outPut;
}<|endoftext|>


<commit_before>function validate_enable ( object )
{
	// This function is called from the code to enable validation for this object
  // Create target div for the tooltip - it's named error-msg-<object-id> and inserted after <object-id>_div (INPUT-DIV) which must exists in the HTML page
	$( '
'+$(object).attr('data-validation-error-msg')+'
' ).insertAfter( $( object+'_div' ) );
	// Prevent return key
	$(object+"_div").keypress(function(e){ return e.which != 13; });

	// Handling screen resizing...
	$( window ).resize(function()
	{
		// Get position of the related INPUT-DIV
		var offset = $(object+'_div').position();
		// Set position of tooltip below the related INPUT-DIV
		$('#error-msg-'+object.substring(1)).css({'width': $(object+'_div').width(), 'top': offset.top + 40 , 'left': offset.left});
	});
	// The global variable window.obj_to_validate holds all objects which prevent submitting the form,
	// if the content doesn't match the rule in parameter data-validation-rule of the INPUT-DIV
  // Checking here if window.obj_to_validate is set and valid, otherwise create an empty array
	window.obj_to_validate = ( typeof window.obj_to_validate != 'undefined' && window.obj_to_validate instanceof Array ) ? window.obj_to_validate : [];
	// Find the form which is related to the object to validate.
	$( $(object).closest('form') ).submit(function(e)
	{
		// Call function validate_all() and it is NOT true, prevent submitting the form.
		if (!validate_all())
		{
			// Prevent submitting the form.
			e.preventDefault();
		}
		else
		{
			console.log("Submitting form");
		}
	});
	// Adding an event handler if someone pastes a value into the INPUT-DIV
	$(object+'_div').on('paste', function(e)
	{
		// In case of pasting values, remove any formatting
		validate_OnPaste_StripFormatting(this, event);
	});
	// Adding an event handler if someone leaves, enters the INPUT-DIV or
	// lift the finger from a key or input something into the INPUT-DIV
	$(object+'_div').on('blur keyup input focusin', function(e)
	{
		// In case of leaving the INPUT-DIV (blur)
		if (e.type == "blur" )
		{
			// Remove the focus CSS
			$(this).removeClass("ui-focus").addClass("ui-shadow-inset");
		}
		else
		{
			// For all other cases (keyup input focusin) add the focus CSS
			$(this).removeClass("ui-shadow-inset").addClass("ui-focus");
		}
		// If the object has CSS param_ok enabled ...
		if ( $(this).hasClass('param_ok') )
		{
			// ...remove the CSS param_ok and add it at the end to prevent overwriting by other CSS rules
			$(this).removeClass('param_ok').addClass('param_ok');
			// Hide the tooltip
			$('#error-msg-'+object.substring(1)).fadeOut(400);
		}
		else
		{
			// ...otherwise remove the CSS param_error and add it at the end to prevent overwriting by other CSS rules
			$(this).removeClass('param_error').addClass('param_error');
		}
		// Check the current value of the INPUT-DIV
		validate_chk_value( object,e );
	});
	// Put the value in the INPUT-DIV into the hidden INPUT field of the HTML page.
	// This is the value, which is really sent to the server when submitting the form.
	$(object).attr('value',$(object+"_div").text());
}

function validate_all()
{
	// This function validates all INPUT-DIV which were added to the array window.obj_to_validate
	// by calling the validate_chk_object(['<OBJECT>']); from the HTML page
	// The function validate_all() returns true only, if all fields are correct filled.
	// As soon as one field is wrong, false is returned. If the array window.obj_to_validate is
	// empty, true is returned.
	var trueCount			=-1;
  var falseCount		=-1;
  // Put the array window.obj_to_validate in to the local variable what_to_test
  var what_to_test 	= window.obj_to_validate;
	// If what_to_test is empty, abort and retrun true...
	if (what_to_test.length === 0 )
	{
		return true;
	}
	// If what_to_test is NOT empty, work...
	else
	{
		// Get each object to validate...
		$.each(what_to_test, function (i,v)
		  {
		  	// Write the value from hidden input field into the INPUT-DIV
		  	// to have both fields consistent before starting the check
		  	// and show the user the real value which is validated as the
		  	// hidden INPUT field is not visible
			  $(v+'_div').text($(v).val());
		    // Check the value...
		    if (validate_chk_value.call(this,v))
		    {
		    	// If the value matches the rule in attribute data-validation-rule
		    	// of the INPUT-DIV, increase the true counter
		      trueCount++;
		    }
		    else
		    {
	 	    	// If the value doesn't match the rule in attribute data-validation-rule
		    	// of the INPUT-DIV, increase the false counter
		      falseCount++;
		    }
	    });
	}
	// If falseCount is equal or greater than 0 something was wrong (initial value is -1)
  if (falseCount >= 0)
  {
    return false;
  }
	// If trueCount is equal or greater than 0 something was ok - so I expect all was ok
	// as nothing was wrong before with falseCount (initial value is -1)
  if (trueCount >= 0)
  {
    return true;
  }
  // Never should arrive here but if, return false to be sure nothin bad is submitted
  return false;
}

// Function block to remove formatting
var _onPaste_StripFormatting_IEPaste = false;
function validate_OnPaste_StripFormatting(elem, e)
{
    if (e.originalEvent && e.originalEvent.clipboardData && e.originalEvent.clipboardData.getData)
    {
        e.preventDefault();
        var text = e.originalEvent.clipboardData.getData('text/plain');
        window.document.execCommand('insertText', false, text);
    }
    else if (e.clipboardData && e.clipboardData.getData)
    {
        e.preventDefault();
        var text = e.clipboardData.getData('text/plain');
        window.document.execCommand('insertText', false, text);
    }
    else if (window.clipboardData && window.clipboardData.getData)
    {
        // Stop stack overflow
        if (!_onPaste_StripFormatting_IEPaste) {
            _onPaste_StripFormatting_IEPaste = true;
            e.preventDefault();
            window.document.execCommand('ms-pasteTextOnly', false);
        }
        _onPaste_StripFormatting_IEPaste = false;
    }
}

function validate_chk_value( object,evt,rule )
{
	// This function does the checking against the regular expression rule
	// If the event is not defined, set it to 0
	// (This happens if called from the code itself)
	evt = evt || 0;
	// If the rule is not given when calling the function, get the value
	// in attribute data-validation-rule of the INPUT-DIV
	var rule = rule || $(object).attr("data-validation-rule");
	// If the rule is neither given when calling the function, not in the value
	// of attribute data-validation-rule of the INPUT-DIV define a rule which
	// returns always false to be sure nothing bad is validated by mistake
	rule = rule || "(?=a)b";
	// Convert the rule string into a regular expression
	rule = new RegExp(rule);
	// Check, if the INPUT-DIV value matches the rule
	if( !rule.test( $(object+'_div').text() )  )
	{
		// The rule doesn't match => That's bad
		// If the validate_chk_value was called when leaving a field,
		// or no event is given, apply the following:
		if (evt.type == "blur"  || evt === 0 )
		{
			// Get the position of the INPUT-DIV on the page
			var offset = $(object+'_div').position();
			// Set the position of the tooltip below INPUT-DIV
			$('#error-msg-'+object.substring(1)).css({'padding': '5px', 'border': '1px solid #FF0000', 'border-radius': '5px', 'color': '#FF0000', 'background-color': '#FFFFC0', 'z-index': 1000, 'width': $(object+'_div').width(), 'top': offset.top + 40, 'left': offset.left, 'position':'absolute'});
			// Show the tooltip
			$('#error-msg-'+object.substring(1)).fadeIn(400);
		}
		// Remove the CSS for param_ok and add param_error instead
		$(object+'_div').removeClass('param_ok').addClass('param_error');
		// Remove the (invalid) value from the hidden input box
		$(object).val('');
		// Return false to the caller
		return false
	}
	else
	{
		// The rule matches => That's good
		// Remove the CSS for param_error and add param_ok instead
		$(object+'_div').removeClass('param_error').addClass('param_ok');
		// Put the (validated) value into the hidden input box
		$(object).val($(object+'_div').text());
		// Hide the tooltip
  	$('#error-msg-'+object.substring(1)).fadeOut(400);
		// Return false to the caller
		return true
	}
}

function validate_chk_object( obj_to_validate )
{
	// Function to define (add) which objects must have
	// correct values to be able to submit the form
	// Check the given variable. If not okay, create an empty array
	var obj_to_validate = ( typeof obj_to_validate != 'undefined' && obj_to_validate instanceof Array ) ? obj_to_validate : [];
	// Go through each object ...
	$.each(obj_to_validate, function(i, obj)
	{
			// Put the object into the global array window.obj_to_validate
	    window.obj_to_validate.push(obj);
	});
	// Remove duplicates from the global array window.obj_to_validate
	window.obj_to_validate = jQuery.unique( window.obj_to_validate );
	// Reorganize the tooltips
	validate_place_tooltips ();
}
function validate_clean_objects( to_clean )
{
	// Function to define (remove) which objects must NOT have
	// correct values any longer to be able to submit the form
	// Check the given variable. If not okay, create an empty array
	var to_clean = ( typeof to_clean != 'undefined' && to_clean instanceof Array ) ? to_clean : [];
	// Go through each object ...
	$.each(to_clean, function(i, object)
	{
		// Delete the value in the hidden input field
		$(object).val('');
		// Delete the value in the INPUT-DIV
		$(object+"_div").text('');
		// Hide the tooltip
		$('#error-msg-'+object.substring(1)).fadeOut(100);
		// Remove the CSS for param_error and add param_ok
		$(object+"_div").removeClass('param_error').addClass('param_ok');
		// Remove the object from the global array window.obj_to_validate
	  window.obj_to_validate.splice($.inArray(object, obj_to_validate),1);
	});
	// Reorganize the tooltips
	validate_place_tooltips ();
}

function validate_place_tooltips ()
{
	// Function to reorganize the tooltips
	// Remove duplicates from window.obj_to_validate
	window.obj_to_validate = jQuery.unique( window.obj_to_validate );
	// Go through each object ...
	$.each(window.obj_to_validate, function(i, single_object)
	{
		// Correct the position of the tooltip after 450 ms from now
		setTimeout( function()
		{
			// Get the INPUT-DIV position
			var offset = $(single_object+'_div').position();
			// Place the tooltip below the INPUT-DIV
			$('#error-msg-'+single_object.substring(1)).css({'padding': '5px', 'border': '1px solid #FF0000', 'border-radius': '5px', 'color': '#FF0000', 'background-color': '#FFFFC0', 'z-index': 1000, 'width': $(single_object+'_div').width(), 'top': offset.top + 40, 'left': offset.left, 'position':'absolute'});
		}, 450);
	});
	return;
}
<commit_msg>Validate: do not delete invalid values<commit_after>function validate_enable ( object )
{
	// This function is called from the code to enable validation for this object
  // Create target div for the tooltip - it's named error-msg-<object-id> and inserted after <object-id>_div (INPUT-DIV) which must exists in the HTML page
	$( '
'+$(object).attr('data-validation-error-msg')+'
' ).insertAfter( $( object+'_div' ) );
	// Prevent return key
	$(object+"_div").keypress(function(e){ return e.which != 13; });

	// Handling screen resizing...
	$( window ).resize(function()
	{
		// Get position of the related INPUT-DIV
		var offset = $(object+'_div').position();
		// Set position of tooltip below the related INPUT-DIV
		$('#error-msg-'+object.substring(1)).css({'width': $(object+'_div').width(), 'top': offset.top + 40 , 'left': offset.left});
	});
	// The global variable window.obj_to_validate holds all objects which prevent submitting the form,
	// if the content doesn't match the rule in parameter data-validation-rule of the INPUT-DIV
  // Checking here if window.obj_to_validate is set and valid, otherwise create an empty array
	window.obj_to_validate = ( typeof window.obj_to_validate != 'undefined' && window.obj_to_validate instanceof Array ) ? window.obj_to_validate : [];
	// Find the form which is related to the object to validate.
	$( $(object).closest('form') ).submit(function(e)
	{
		// Call function validate_all() and it is NOT true, prevent submitting the form.
		if (!validate_all())
		{
			// Prevent submitting the form.
			e.preventDefault();
		}
		else
		{
			console.log("Submitting form");
		}
	});
	// Adding an event handler if someone pastes a value into the INPUT-DIV
	$(object+'_div').on('paste', function(e)
	{
		// In case of pasting values, remove any formatting
		validate_OnPaste_StripFormatting(this, event);
	});
	// Adding an event handler if someone leaves, enters the INPUT-DIV or
	// lift the finger from a key or input something into the INPUT-DIV
	$(object+'_div').on('blur keyup input focusin', function(e)
	{
		// In case of leaving the INPUT-DIV (blur)
		if (e.type == "blur" )
		{
			// Remove the focus CSS
			$(this).removeClass("ui-focus").addClass("ui-shadow-inset");
		}
		else
		{
			// For all other cases (keyup input focusin) add the focus CSS
			$(this).removeClass("ui-shadow-inset").addClass("ui-focus");
		}
		// If the object has CSS param_ok enabled ...
		if ( $(this).hasClass('param_ok') )
		{
			// ...remove the CSS param_ok and add it at the end to prevent overwriting by other CSS rules
			$(this).removeClass('param_ok').addClass('param_ok');
			// Hide the tooltip
			$('#error-msg-'+object.substring(1)).fadeOut(400);
		}
		else
		{
			// ...otherwise remove the CSS param_error and add it at the end to prevent overwriting by other CSS rules
			$(this).removeClass('param_error').addClass('param_error');
		}
		// Check the current value of the INPUT-DIV
		validate_chk_value( object,e );
	});
	// Put the value in the INPUT-DIV into the hidden INPUT field of the HTML page.
	// This is the value, which is really sent to the server when submitting the form.
	$(object).attr('value',$(object+"_div").text());
}

function validate_all()
{
	// This function validates all INPUT-DIV which were added to the array window.obj_to_validate
	// by calling the validate_chk_object(['<OBJECT>']); from the HTML page
	// The function validate_all() returns true only, if all fields are correct filled.
	// As soon as one field is wrong, false is returned. If the array window.obj_to_validate is
	// empty, true is returned.
	var trueCount			=-1;
  var falseCount		=-1;
  // Put the array window.obj_to_validate in to the local variable what_to_test
  var what_to_test 	= window.obj_to_validate;
	// If what_to_test is empty, abort and retrun true...
	if (what_to_test.length === 0 )
	{
		return true;
	}
	// If what_to_test is NOT empty, work...
	else
	{
		// Get each object to validate...
		$.each(what_to_test, function (i,v)
		  {
		  	// Write the value from hidden input field into the INPUT-DIV
		  	// to have both fields consistent before starting the check
		  	// and show the user the real value which is validated as the
		  	// hidden INPUT field is not visible
			  $(v+'_div').text($(v).val());
		    // Check the value...
		    if (validate_chk_value.call(this,v))
		    {
		    	// If the value matches the rule in attribute data-validation-rule
		    	// of the INPUT-DIV, increase the true counter
		      trueCount++;
		    }
		    else
		    {
	 	    	// If the value doesn't match the rule in attribute data-validation-rule
		    	// of the INPUT-DIV, increase the false counter
		      falseCount++;
		    }
	    });
	}
	// If falseCount is equal or greater than 0 something was wrong (initial value is -1)
  if (falseCount >= 0)
  {
    return false;
  }
	// If trueCount is equal or greater than 0 something was ok - so I expect all was ok
	// as nothing was wrong before with falseCount (initial value is -1)
  if (trueCount >= 0)
  {
    return true;
  }
  // Never should arrive here but if, return false to be sure nothin bad is submitted
  return false;
}

// Function block to remove formatting
var _onPaste_StripFormatting_IEPaste = false;
function validate_OnPaste_StripFormatting(elem, e)
{
    if (e.originalEvent && e.originalEvent.clipboardData && e.originalEvent.clipboardData.getData)
    {
        e.preventDefault();
        var text = e.originalEvent.clipboardData.getData('text/plain');
        window.document.execCommand('insertText', false, text);
    }
    else if (e.clipboardData && e.clipboardData.getData)
    {
        e.preventDefault();
        var text = e.clipboardData.getData('text/plain');
        window.document.execCommand('insertText', false, text);
    }
    else if (window.clipboardData && window.clipboardData.getData)
    {
        // Stop stack overflow
        if (!_onPaste_StripFormatting_IEPaste) {
            _onPaste_StripFormatting_IEPaste = true;
            e.preventDefault();
            window.document.execCommand('ms-pasteTextOnly', false);
        }
        _onPaste_StripFormatting_IEPaste = false;
    }
}

function validate_chk_value( object,evt,rule )
{
	// This function does the checking against the regular expression rule
	// If the event is not defined, set it to 0
	// (This happens if called from the code itself)
	evt = evt || 0;
	// If the rule is not given when calling the function, get the value
	// in attribute data-validation-rule of the INPUT-DIV
	var rule = rule || $(object).attr("data-validation-rule");
	// If the rule is neither given when calling the function, not in the value
	// of attribute data-validation-rule of the INPUT-DIV define a rule which
	// returns always false to be sure nothing bad is validated by mistake
	rule = rule || "(?=a)b";
	// Convert the rule string into a regular expression
	rule = new RegExp(rule);
	// Check, if the INPUT-DIV value matches the rule
	if( !rule.test( $(object+'_div').text() )  )
	{
		// The rule doesn't match => That's bad
		// If the validate_chk_value was called when leaving a field,
		// or no event is given, apply the following:
		if (evt.type == "blur"  || evt === 0 )
		{
			// Get the position of the INPUT-DIV on the page
			var offset = $(object+'_div').position();
			// Set the position of the tooltip below INPUT-DIV
			$('#error-msg-'+object.substring(1)).css({'padding': '5px', 'border': '1px solid #FF0000', 'border-radius': '5px', 'color': '#FF0000', 'background-color': '#FFFFC0', 'z-index': 1000, 'width': $(object+'_div').width(), 'top': offset.top + 40, 'left': offset.left, 'position':'absolute'});
			// Show the tooltip
			$('#error-msg-'+object.substring(1)).fadeIn(400);
		}
		// Remove the CSS for param_ok and add param_error instead
		$(object+'_div').removeClass('param_ok').addClass('param_error');
		// Put the (unvalidated) value into the hidden input box
		$(object).val($(object+'_div').text());
		// Return false to the caller
		return false
	}
	else
	{
		// The rule matches => That's good
		// Remove the CSS for param_error and add param_ok instead
		$(object+'_div').removeClass('param_error').addClass('param_ok');
		// Put the (validated) value into the hidden input box
		$(object).val($(object+'_div').text());
		// Hide the tooltip
  	$('#error-msg-'+object.substring(1)).fadeOut(400);
		// Return false to the caller
		return true
	}
}

function validate_chk_object( obj_to_validate )
{
	// Function to define (add) which objects must have
	// correct values to be able to submit the form
	// Check the given variable. If not okay, create an empty array
	var obj_to_validate = ( typeof obj_to_validate != 'undefined' && obj_to_validate instanceof Array ) ? obj_to_validate : [];
	// Go through each object ...
	$.each(obj_to_validate, function(i, obj)
	{
			// Put the object into the global array window.obj_to_validate
	    window.obj_to_validate.push(obj);
	});
	// Remove duplicates from the global array window.obj_to_validate
	window.obj_to_validate = jQuery.unique( window.obj_to_validate );
	// Reorganize the tooltips
	validate_place_tooltips ();
}
function validate_clean_objects( to_clean )
{
	// Function to define (remove) which objects must NOT have
	// correct values any longer to be able to submit the form
	// Check the given variable. If not okay, create an empty array
	var to_clean = ( typeof to_clean != 'undefined' && to_clean instanceof Array ) ? to_clean : [];
	// Go through each object ...
	$.each(to_clean, function(i, object)
	{
		// Delete the value in the hidden input field
		$(object).val('');
		// Delete the value in the INPUT-DIV
		$(object+"_div").text('');
		// Hide the tooltip
		$('#error-msg-'+object.substring(1)).fadeOut(100);
		// Remove the CSS for param_error and add param_ok
		$(object+"_div").removeClass('param_error').addClass('param_ok');
		// Remove the object from the global array window.obj_to_validate
	  window.obj_to_validate.splice($.inArray(object, obj_to_validate),1);
	});
	// Reorganize the tooltips
	validate_place_tooltips ();
}

function validate_place_tooltips ()
{
	// Function to reorganize the tooltips
	// Remove duplicates from window.obj_to_validate
	window.obj_to_validate = jQuery.unique( window.obj_to_validate );
	// Go through each object ...
	$.each(window.obj_to_validate, function(i, single_object)
	{
		// Correct the position of the tooltip after 450 ms from now
		setTimeout( function()
		{
			// Get the INPUT-DIV position
			var offset = $(single_object+'_div').position();
			// Place the tooltip below the INPUT-DIV
			$('#error-msg-'+single_object.substring(1)).css({'padding': '5px', 'border': '1px solid #FF0000', 'border-radius': '5px', 'color': '#FF0000', 'background-color': '#FFFFC0', 'z-index': 1000, 'width': $(single_object+'_div').width(), 'top': offset.top + 40, 'left': offset.left, 'position':'absolute'});
		}, 450);
	});
	return;
}
<|endoftext|>


<commit_before>import {Platform} from 'react-native';
import FCM from 'react-native-fcm';

const API_KEY = 'AAAA1i1g05s:APA91bEm3cI3lCflqY74PSH3O-3RGUk4H9kGqXKB1NfhT9igNntvDSSqWDBxajEK-rsbFovPVJzTJojx4Q-SlgFs-7D2fT2dmdw_0ii_5jodpn__jahPlKE1UL-HibRkSO8_6WL88B3D';
var icube = [];
var loaded = false;

module.exports = {
    //gets subscription data
    get_iCube: function(callback) {
        if(!loaded) {
            //process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
            //var url = Platform.OS === 'ios' ? 'https://mobile-api.innovate.fresnostate.edu/icube' : 'http://mobile-api.innovate.fresnostate.edu/icube';
            //var url = 'https://mobile-api.innovate.fresnostate.edu/icube';
            var url = 'http://mobile-api.innovate.fresnostate.edu/icube';
            fetch(url)
            .then(function (response) {
                return response.json();
            }).then(function (json) {
                loaded = true;
                icube = json.icube;
                callback(icube);
            }).catch(function (err) {
                console.log("GET TOPICS ERR", err);
            });
        }
        else{
            callback(icube);
        }

    },
    //gets array of topic keys of currently subscribed subjects
    getSubscribed: function(callback){
        FCM.getFCMToken().then((token) => {
            fetch('https://iid.googleapis.com/iid/info/'+token+'?details=true', {
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'key='+API_KEY
                }
            }).then(function(response) {
                return response.json();
            }).then(function(subscribed) {
                if(subscribed.rel && subscribed.rel.topics){
                    callback(Object.keys(subscribed.rel.topics));
                }
                else{
                    callback([]);
                }
            }).catch(function(err){
                console.log("GET SUBS ERR", err);
            });
        });
    },
    //updates subject data to have a boolean field for whether a subject is currently subscribed to
    mergeSubData: function(subjects, subscribed, callback){
        subjects.forEach(function (subject) {
            subject["subscribed"] = subscribed.indexOf(subject.topic_key) >=0;
        });
        callback(subjects);
    },
    //takes subject data and returns only those currently subscribed to
    mergeMySubData: function(subjects, subscribed, callback){
        var mySubjects = [];
        subjects.forEach(function (subject) {
            if(subscribed.indexOf(subject.topic_key) >=0){
                subject["subscribed"] = true;
                mySubjects.push(subject);
            }
        });
        callback(mySubjects);
    },
    //returns boolean in cb for whether a subject is currently subscribed to
    checkIfSubscribed(topic_key, callback){
      this.getSubscribed((subjects)=>{
          var subscribed = false;
          for(var i=0; i<subjects.length; ++i){
              if(subjects[i].indexOf(topic_key) >=0){
                  subscribed = true;
                  break;
              }
          }
          callback(subscribed);
      })
    },
    //gets subscription info for a single subject by looking up the topic_key
    getSubscriptionInfo(topic_key, callback){
      this.get_iCube((channels)=>{
          let found = false;
          var subInfo = {
              channel: 'Channel',
              area: 'Area',
              subject: 'Subject',
              subscribed: false
          };
          //parse topic key to get subscription IDs
          var channel_id = topic_key.split("-")[0];
          var area_id = topic_key.split("-")[1];
          var subject_id = topic_key.split("-")[2];
          //get subscription/icube names from IDs
          for(var i=0; i<channels.length; ++i){
              if(channels[i]._id === channel_id){
                  for(var j=0; j<channels[i].areas.length; ++j){
                      if(channels[i].areas[j].id === area_id){
                          for(var k=0; k<channels[i].areas[j].subjects.length; ++k){
                              if(channels[i].areas[j].subjects[k].id === subject_id){
                                 found = true;
                                 subInfo.channel = channels[i].name;
                                 subInfo.area = channels[i].areas[j].name;
                                 subInfo.subject = channels[i].areas[j].subjects[k].name;
                                 subInfo.level = channels[i].areas[j].subjects[k].opt.level;
                                 break;
                              }
                          }
                      }
                  }
              }
          }
          if(found){
              this.checkIfSubscribed(topic_key, (subscribed)=> {
                  subInfo.subscribed = subscribed;
                  callback(subInfo);
              });
          }
          else{
              callback(subInfo);
          }
      });
    },
    subscribe: function(topic_key){
        FCM.subscribeToTopic('/topics/'+topic_key);
    },
    unsubscribe: function(topic_key){
        //add check here for safeguarding against removing required/forced topics
        FCM.unsubscribeFromTopic('/topics/'+topic_key);
    }
};
<commit_msg>Update subscription.js<commit_after>import {Platform} from 'react-native';
import FCM from 'react-native-fcm';

const API_KEY = 'api-key';
var icube = [];
var loaded = false;

module.exports = {
    //gets subscription data
    get_iCube: function(callback) {
        if(!loaded) {
            //process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
            //var url = Platform.OS === 'ios' ? 'https://mobile-api.innovate.fresnostate.edu/icube' : 'http://mobile-api.innovate.fresnostate.edu/icube';
            //var url = 'https://mobile-api.innovate.fresnostate.edu/icube';
            var url = 'http://mobile-api.innovate.fresnostate.edu/icube';
            fetch(url)
            .then(function (response) {
                return response.json();
            }).then(function (json) {
                loaded = true;
                icube = json.icube;
                callback(icube);
            }).catch(function (err) {
                console.log("GET TOPICS ERR", err);
            });
        }
        else{
            callback(icube);
        }

    },
    //gets array of topic keys of currently subscribed subjects
    getSubscribed: function(callback){
        FCM.getFCMToken().then((token) => {
            fetch('https://iid.googleapis.com/iid/info/'+token+'?details=true', {
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'key='+API_KEY
                }
            }).then(function(response) {
                return response.json();
            }).then(function(subscribed) {
                if(subscribed.rel && subscribed.rel.topics){
                    callback(Object.keys(subscribed.rel.topics));
                }
                else{
                    callback([]);
                }
            }).catch(function(err){
                console.log("GET SUBS ERR", err);
            });
        });
    },
    //updates subject data to have a boolean field for whether a subject is currently subscribed to
    mergeSubData: function(subjects, subscribed, callback){
        subjects.forEach(function (subject) {
            subject["subscribed"] = subscribed.indexOf(subject.topic_key) >=0;
        });
        callback(subjects);
    },
    //takes subject data and returns only those currently subscribed to
    mergeMySubData: function(subjects, subscribed, callback){
        var mySubjects = [];
        subjects.forEach(function (subject) {
            if(subscribed.indexOf(subject.topic_key) >=0){
                subject["subscribed"] = true;
                mySubjects.push(subject);
            }
        });
        callback(mySubjects);
    },
    //returns boolean in cb for whether a subject is currently subscribed to
    checkIfSubscribed(topic_key, callback){
      this.getSubscribed((subjects)=>{
          var subscribed = false;
          for(var i=0; i<subjects.length; ++i){
              if(subjects[i].indexOf(topic_key) >=0){
                  subscribed = true;
                  break;
              }
          }
          callback(subscribed);
      })
    },
    //gets subscription info for a single subject by looking up the topic_key
    getSubscriptionInfo(topic_key, callback){
      this.get_iCube((channels)=>{
          let found = false;
          var subInfo = {
              channel: 'Channel',
              area: 'Area',
              subject: 'Subject',
              subscribed: false
          };
          //parse topic key to get subscription IDs
          var channel_id = topic_key.split("-")[0];
          var area_id = topic_key.split("-")[1];
          var subject_id = topic_key.split("-")[2];
          //get subscription/icube names from IDs
          for(var i=0; i<channels.length; ++i){
              if(channels[i]._id === channel_id){
                  for(var j=0; j<channels[i].areas.length; ++j){
                      if(channels[i].areas[j].id === area_id){
                          for(var k=0; k<channels[i].areas[j].subjects.length; ++k){
                              if(channels[i].areas[j].subjects[k].id === subject_id){
                                 found = true;
                                 subInfo.channel = channels[i].name;
                                 subInfo.area = channels[i].areas[j].name;
                                 subInfo.subject = channels[i].areas[j].subjects[k].name;
                                 subInfo.level = channels[i].areas[j].subjects[k].opt.level;
                                 break;
                              }
                          }
                      }
                  }
              }
          }
          if(found){
              this.checkIfSubscribed(topic_key, (subscribed)=> {
                  subInfo.subscribed = subscribed;
                  callback(subInfo);
              });
          }
          else{
              callback(subInfo);
          }
      });
    },
    subscribe: function(topic_key){
        FCM.subscribeToTopic('/topics/'+topic_key);
    },
    unsubscribe: function(topic_key){
        //add check here for safeguarding against removing required/forced topics
        FCM.unsubscribeFromTopic('/topics/'+topic_key);
    }
};
<|endoftext|>


<commit_before>/*#ifndef(UMD)*/
(function () { /* Begin of privacy scope */
    "use strict";
/*#endif*/

    var
        gpfI = gpf.interfaces;

    /**
     * The Readable stream interface is the abstraction for a source of data
     * that you are reading from. In other words, data comes out of a Readable
     * stream.
     *
     * @class gpf.interfaces.IReadableStream
     * @extends gpf.interfaces.Interface
     */
    gpf._defIntrf("IReadableStream", {

        /**
         * Triggers the reading of data.
         * The expected behavior is:
         * - The callback is asynchronous
         * - One of the following callback must be called after a read
         *   - EVENT_ERROR: an error occurred.
         *     the stream can't be used after this.
         *   - EVENT_END_OF_STREAM: stream ended.
         *     the stream can't be used after this.
         *   - EVENT_DATA: a buffer is provided, it can't be empty.
         *
         * @param {Number} [size=0] size Number of bytes to read. Read
         * as much as possible if 0
         * @param {gpf.events.Handler} eventsHandler
         *
         * @event data Some data is ready to be ready
         * @eventParam {gpf.IReadOnlyArray} buffer Bytes buffer
         *
         * @event eos No more data can be read from the stream
         *
         */
        read: function (size, eventsHandler) {
            gpf.interfaces.ignoreParameter(size);
            gpf.interfaces.ignoreParameter(eventsHandler);
        },

        static: {
            EVENT_ERROR: "error",
            EVENT_DATA: "data",
            EVENT_END_OF_STREAM: "eos",

            EXCEPTION_READ_IN_PROGRESS: {
                message: "Read in progress"
            }
        }

    });

    /**
     * The Writable stream interface is an abstraction for a destination that
     * you are writing data to.
     * The expected behavior is:
     * - The callback is asynchronous
     * - One of the following callback must be called after a read
     *   - EVENT_ERROR: an error occurred.
     *     the stream can't be used after this.
     *   - EVENT_READY: the write operation succeeded, the provided buffer has
     *     been fully written (otherwise an error is thrown)
     *
     * @class gpf.interfaces.IReadableStream
     * @extends gpf.interfaces.Interface
     */
    gpf._defIntrf("IWritableStream", {

        /**
         * Triggers the writing of data
         *
         * @param {IReadOnlyArray} int8buffer Buffer to write
         * @param {gpf.events.Handler} eventsHandler
         *
         * @event ready it is appropriate to begin writing more data to the
         * stream
         *
         */
        write: function (int8buffer, eventsHandler) {
            gpf.interfaces.ignoreParameter(int8buffer);
            gpf.interfaces.ignoreParameter(eventsHandler);
        },

        static: {
            EVENT_ERROR: "error",
            EVENT_READY: "ready",

            EXCEPTION_WRITE_IN_PROGRESS: {
                message: "Read in progress"
            }
        }

    });

    /**
     * The stream combines both IReadableStream and IWritableStream
     */
    gpf._defIntrf("IStream", {

        /**
         * @inheritDoc gpf.interfaces.IReadableStream:read
         */
        read: function (size, eventsHandler) {
            gpf.interfaces.ignoreParameter(size);
            gpf.interfaces.ignoreParameter(eventsHandler);
        },

        /**
         * @inheritDoc gpf.interfaces.IWritableStream:write
         */
        write: function (int8buffer, eventsHandler) {
            gpf.interfaces.ignoreParameter(int8buffer);
            gpf.interfaces.ignoreParameter(eventsHandler);
        },

    });

    /**
     * Text stream: instead of an int8 buffer, the interface handles strings
     *
     * @class gpf.interfaces.ITextStream
     * @extends gpf.interfaces.IStream
     *
     * @event data Some data is ready to be ready
     * @eventParam {String} buffer
     */
    gpf._defIntrf("ITextStream", gpfI.IStream, {
    });

    /**
     * Internal helper to implement the expected write behavior in all streams
     * @inheritDoc gpf.interfaces.ITextStream:write
     */
    gpfI.ITextStream._write = function () {
        var argIdx, arg;
        for (argIdx = 0; argIdx < arguments.length; ++argIdx) {
            arg = arguments[argIdx];
            if (null !== arg && "string" !== typeof arg) {
                arg = arg.toString();
            }
            this.write_(arg);
        }
        if (0 === argIdx) { // No parameter at all
            this.write_(null);
        }
    };

/*#ifndef(UMD)*/
}()); /* End of privacy scope */
/*#endif*/<commit_msg>Created gpf.BufferedOnReadStream<commit_after>/*#ifndef(UMD)*/
(function () { /* Begin of privacy scope */
    "use strict";
/*#endif*/

    var
        gpfI = gpf.interfaces,
        _BUFREADSTREAM_READ_SIZE        = 256,
        _BUFREADSTREAM_ISTATE_INIT        = 0,
        _BUFREADSTREAM_ISTATE_INPROGRESS  = 1,
        _BUFREADSTREAM_ISTATE_WAITING     = 2,
        _BUFREADSTREAM_ISTATE_EOS         = 3;

    /**
     * The Readable stream interface is the abstraction for a source of data
     * that you are reading from. In other words, data comes out of a Readable
     * stream.
     *
     * @class gpf.interfaces.IReadableStream
     * @extends gpf.interfaces.Interface
     */
    gpf._defIntrf("IReadableStream", {

        /**
         * Triggers the reading of data.
         * The expected behavior is:
         * - The callback is asynchronous
         * - One of the following callback must be called after a read
         *   - EVENT_ERROR: an error occurred.
         *     the stream can't be used after this.
         *   - EVENT_END_OF_STREAM: stream ended.
         *     the stream can't be used after this.
         *   - EVENT_DATA: a buffer is provided, it can't be empty.
         *
         * @param {Number} [size=0] size Number of bytes to read. Read
         * as much as possible if 0
         * @param {gpf.events.Handler} eventsHandler
         *
         * @event data Some data is ready to be ready
         * @eventParam {gpf.IReadOnlyArray} buffer Bytes buffer
         *
         * @event eos No more data can be read from the stream
         *
         */
        read: function (size, eventsHandler) {
            gpf.interfaces.ignoreParameter(size);
            gpf.interfaces.ignoreParameter(eventsHandler);
        },

        static: {
            EVENT_ERROR: "error",
            EVENT_DATA: "data",
            EVENT_END_OF_STREAM: "eos",

            EXCEPTION_READ_IN_PROGRESS: {
                message: "Read in progress"
            }
        }

    });

    /**
     * The Writable stream interface is an abstraction for a destination that
     * you are writing data to.
     * The expected behavior is:
     * - The callback is asynchronous
     * - One of the following callback must be called after a read
     *   - EVENT_ERROR: an error occurred.
     *     the stream can't be used after this.
     *   - EVENT_READY: the write operation succeeded, the provided buffer has
     *     been fully written (otherwise an error is thrown)
     *
     * @class gpf.interfaces.IReadableStream
     * @extends gpf.interfaces.Interface
     */
    gpf._defIntrf("IWritableStream", {

        /**
         * Triggers the writing of data
         *
         * @param {IReadOnlyArray} int8buffer Buffer to write
         * @param {gpf.events.Handler} eventsHandler
         *
         * @event ready it is appropriate to begin writing more data to the
         * stream
         *
         */
        write: function (int8buffer, eventsHandler) {
            gpf.interfaces.ignoreParameter(int8buffer);
            gpf.interfaces.ignoreParameter(eventsHandler);
        },

        static: {
            EVENT_ERROR: "error",
            EVENT_READY: "ready",

            EXCEPTION_WRITE_IN_PROGRESS: {
                message: "Read in progress"
            }
        }

    });

    /**
     * The stream combines both IReadableStream and IWritableStream
     */
    gpf._defIntrf("IStream", {

        /**
         * @inheritDoc gpf.interfaces.IReadableStream:read
         */
        read: function (size, eventsHandler) {
            gpf.interfaces.ignoreParameter(size);
            gpf.interfaces.ignoreParameter(eventsHandler);
        },

        /**
         * @inheritDoc gpf.interfaces.IWritableStream:write
         */
        write: function (int8buffer, eventsHandler) {
            gpf.interfaces.ignoreParameter(int8buffer);
            gpf.interfaces.ignoreParameter(eventsHandler);
        }

    });

    /**
     * Text stream: instead of an int8 buffer, the interface handles strings
     *
     * @class gpf.interfaces.ITextStream
     * @extends gpf.interfaces.IStream
     *
     * @event data Some data is ready to be ready
     * @eventParam {String} buffer
     */
    gpf._defIntrf("ITextStream", gpfI.IStream, {
    });

    /**
     * Internal helper to implement the expected write behavior in all streams
     * @inheritDoc gpf.interfaces.ITextStream:write
     */
    gpfI.ITextStream._write = function () {
        var argIdx, arg;
        for (argIdx = 0; argIdx < arguments.length; ++argIdx) {
            arg = arguments[argIdx];
            if (null !== arg && "string" !== typeof arg) {
                arg = arg.toString();
            }
            this.write_(arg);
        }
        if (0 === argIdx) { // No parameter at all
            this.write_(null);
        }
    };

    /**
     * Handles a buffered stream that depends on another stream
     *
     * @class gpf.BufferedOnReadStream
     * @implements gpf.interfaces.IReadableStream
     */
    gpf.define("gpf.BufferedOnReadStream", {

        "[Class]": [gpf.$InterfaceImplement(gpfI.IReadableStream)],

        protected: {

            /**
             * Underlying stream default read size
             *
             * @type {Number}
             * @protected
             */
            _readSize: _BUFREADSTREAM_READ_SIZE,

            /**
             * Process underlying stream buffer
             *
             * @param {Array} buffer
             * @protected
             */
            _addToBuffer: function (buffer) {
                gpf.interfaces.ignoreParameter(buffer);
            },

            /**
             * Underlying stream reached its end
             *
             * @protected
             */
            _endOfInputStream: function () {
            },

            /**
             * Send buffer part to the caller of this stream.
             * This default implementation checks the buffer type to switch
             * between string and byte array.
             *
             * @param {Array} buffer
             * @param {Number} size
             * @returns {Array}
             * @protected
             */
            _extractFromBuffer: function (buffer, size) {
                gpf.ASSERT(buffer.length, "Buffer is not empty");
                if ("string" === typeof buffer[0]) {
                    return gpf.stringExtractFromStringArray(buffer, size);
                } else {
                    if (size > buffer.length) {
                        size = buffer.length;
                    }
                    return buffer.splice(0, size);
                }
            }

        },

        public: {

            /**
             * @param {gpf.interfaces.IReadableStream} input
             * @constructor
             */
            constructor: function (input) {
                this._iStream = gpfI.query(input, gpfI.IReadableStream, true);
                this._outputBuffer = [];
            },

            //region gpf.interfaces.IReadableStream

            /**
             * @implements gpf.interfaces.IReadableStream:read
             */
            read: function (size, eventsHandler) {
                var
                    iState = this._iState,
                    buffer,
                    length = this._outputBufferLength;
                if (_BUFREADSTREAM_ISTATE_INPROGRESS === iState) {
                    // A read call is already in progress
                    throw gpfI.IReadableStream.EXCEPTION_READ_IN_PROGRESS;

                } else if (size < length
                    || length && _BUFREADSTREAM_ISTATE_EOS === iState) {
                    // Enough chars in the output buffer to do the read
                    // OR there won't be any more chars
                    buffer = this._extractFromBuffer(
                        this._outputBuffer, size
                    );
                    this._outputBufferLength -= buffer.length;
                    // Can output something
                    gpf.events.fire.apply(this, [
                        gpfI.IReadableStream.EVENT_DATA,
                        {
                            buffer: buffer
                        },
                        eventsHandler
                    ]);

                } else if (_BUFREADSTREAM_ISTATE_EOS === iState) {
                    // No more input and output buffer is empty
                    gpf.events.fire.apply(this, [
                        gpfI.IReadableStream.EVENT_END_OF_STREAM,
                        eventsHandler
                    ]);

                } else {
                    // Read input
                    if (_BUFREADSTREAM_ISTATE_INIT === this._iState) {
                        // Very first call, create callback for input reads
                        this._cbRead = new gpf.Callback(this._onRead, this);
                    }
                    this._iState = _BUFREADSTREAM_ISTATE_INPROGRESS;
                    // Backup parameters
                    this._size = size;
                    this._eventsHandler = eventsHandler;
                    this._iStream.read(this._readSize, this._cbRead);
                }
            }

            //endregion
        },

        //region Implementation

        private: {

            /**
             * Input stream
             * @type {gpf.interfaces.IReadableStream}
             */
            _iStream: null,

            /**
             * Input stream read callback (pointing to this:_onRead)
             * @type {gpf.Callback}
             */
            _cbRead: null,

            /**
             * Output buffer contains items to be read
             * @type {*[]}
             */
            _outputBuffer: [],

            /**
             * Size of the output buffer (number of bytes)
             * @type {Number}
             */
            _outputBufferLength: 0,

            /**
             * Input state
             * @type {Number} see _BUFREADSTREAM_ISTATE_xxx
             */
            _iState: _BUFREADSTREAM_ISTATE_INIT,

            /**
             * Pending read call size
             * @type {Number}
             */
            _size: 0,

            /**
             * Pending read call event handlers
             * @type {gpf.events.Handler}
             */
            _eventsHandler: null,

            /**
             * Handles input stream read event
             *
             * @param {gpf.events.Event} event
             * @private
             */
            _onRead: function (event) {
                var
                    type = event.type();
                if (type === gpfI.IReadableStream.EVENT_END_OF_STREAM) {
                    this._iState = _BUFREADSTREAM_ISTATE_EOS;
                    this._endOfInputStream();
                    // Redirect to read with backed parameters
                    return this.read(this._size, this._eventsHandler);

                } else if (type === gpfI.IReadableStream.EVENT_ERROR) {
                    // Forward the event
                    gpf.events.fire.apply(this, [
                        event,
                        this._eventsHandler
                    ]);

                } else {
                    this._iState = _BUFREADSTREAM_ISTATE_WAITING;
                    this._addToBuffer(event.get("buffer"));
                    if (0 < this._outputBufferLength) {
                        // Redirect to read with backed parameters
                        return this.read(this._size, this._eventsHandler);
                    } else {
                        // Try to read source again
                        this._iStream.read(_BUFREADSTREAM_READ_SIZE,
                            this._cbRead);
                    }
                }
            }

        }

        //endregion
    });

    //endregion

/*#ifndef(UMD)*/
}()); /* End of privacy scope */
/*#endif*/<|endoftext|>