<commit_before>(function () {
   "use strict";

  var rp = require("request-promise"),
    cheerio = require("cheerio"),
    moment = require("moment"),
    pdfText = require('pdf-text'),
    fs = require ("fs"),
    _ = require("lodash"),
    q = require("q");

  require("stringformat").extendString("format");

  var geocoder = require("./geocoder");

  var service = (function (baseUrl) {
    if (typeof baseUrl !== "string") throw("baseUrl is not defined");

    var initUrl = "{0}/{1}".format(baseUrl, "dailybulletin.aspx"),
    	searchUrl = "{0}/{1}".format(baseUrl, "summary.aspx"),
    	jsonUrl = "{0}/{1}".format(baseUrl, "jqHandler.ashx?op=s");

    var cookieJar = rp.jar(),
      request = rp.defaults({
      proxy: process.env.CRHME_PROXY || null,
      jar: cookieJar,
      rejectUnauthorized: false,
      followRedirect: true,
      followAllRedirects: true,
      headers: {
        "accept": "application/json, text/javascript, */*",
        "user-agent": "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)",
        "dnt": "1",
        "accept-language": "en-US,en;q=0.8",
        "x-requested-with": "XMLHttpRequest",
        "content-type": "application/x-www-form-urlencoded",
        "connection": "keep-alive",
        "origin": baseUrl,
        "referer": initUrl
      }
    });

    var parameters = {};

    var ready = false;

    var getFormParams = function (html) {
      var $ = cheerio.load(html),
        params = {};

      _.forEach($("form input"), function (element) {
        var key = element.attribs["name"];
        (typeof key === "string") && (params[key] = element.attribs["value"]);
      });

      return params;
    };

    var connect = function () {
      var deferred = q.defer();
      request({
        method: "GET",
          uri: initUrl
        })
        .then(function (html) {
          parameters[initUrl] = getFormParams(html);
          parameters[initUrl]["MasterPage$mainContent$ddlType2"] = "AL";
          parameters[initUrl]["__EVENTTARGET"] = "MasterPage$mainContent$lbUpdate";
          parameters[initUrl]["__EVENTARGUMENT"] = "";
          deferred.resolve();
        }, deferred.reject)
        .catch(deferred.reject);
      return deferred.promise;
    },
    setDate = function (date) {
      var deferred = q.defer();
      parameters[initUrl]["MasterPage$mainContent$txtDate2"] = moment(date).format("MM/DD/YYYY");
      request({
        method: "POST",
        uri: initUrl,
        form: parameters[initUrl]
      }).then(deferred.resolve, deferred.reject);
      return deferred.promise;
    },
    fetch = function (date) {
      var deferred = q.defer();

      if (typeof date === "string") {
        date = Date.parse(date + " ");
      }

      var query = {
        "t": "db",
        "_search": true,
        "nd": date.valueOf(),
        "rows": 10000,
        "page": 1,
        "sidx": "case",
        "sord": "asc"
      };

      setDate(date).then(function () {
	      request({
	        method: "POST",
	        uri: jsonUrl,
	        form: query
	      })
	      .then(deferred.resolve, deferred.reject)
	      .catch(deferred.reject);
	  }, deferred.reject);

      return deferred.promise;
    },
    getPdf = function (caseNumber, date) {
      var deferred = q.defer();
      var cancel = _.bind(deferred.reject, deferred, [ null ]);
      request({
        method: "GET",
          uri: searchUrl
        })
        .then(function (html) {
          parameters[searchUrl] = getFormParams(html);
          parameters[searchUrl]["__EVENTTARGET"] = "MasterPage$mainContent$cmdSubmit2";
          parameters[searchUrl]["__EVENTARGUMENT"] = "";
          parameters[searchUrl]["MasterPage$mainContent$txtCase2"] = caseNumber;
          delete(parameters[searchUrl]["MasterPage$mainContent$chkTA2"]); // = "on";
          delete(parameters[searchUrl]["MasterPage$mainContent$chkAR2"]); // = "on";
          parameters[searchUrl]["MasterPage$mainContent$chkLW2"] = "on";
          parameters[searchUrl]["MasterPage$mainContent$txtDateFrom2"] = moment(Date.parse(date + " ")).format("MM/DD/YYYY");
          parameters[searchUrl]["MasterPage$mainContent$txtDateTo2"] = moment(Date.parse(date + " ") + (86400000 * 2)).format("MM/DD/YYYY");
          request({
	        method: "POST",
	        uri: searchUrl,
	        form: parameters[searchUrl]
	      }).then(function (html) {
            var $ = cheerio.load(html),
            	reportElementId = "mainContent_gvSummary_lbGetReport_0";
            if ($("#" + reportElementId).length) {
	            parameters[searchUrl] = getFormParams(html);
	            parameters[searchUrl]["__EVENTTARGET"] = "MasterPage$mainContent$gvSummary$ctl02$lbGetReport";
	            parameters[searchUrl]["__EVENTARGUMENT"] = "";
	            request({
		          method: "POST",
		          uri: searchUrl,
		          form: parameters[searchUrl],
		          encoding: null
		        }).then(function (buffer) {
		        	if (buffer) {
		        		deferred.resolve({
		        			id: caseNumber,
		        			buffer: buffer
		        		});
		        	}
		        	else cancel();
		        }, cancel)
	            .catch(cancel);
        	}
        	else {
        		cancel();
        	}
	      },
	      function (reason) {
	      	console.log("REPORT FAILED: " + reason);
		    cancel();
		  })
          .catch(cancel);
        }, cancel)
        .catch(cancel);
      return deferred.promise;
    },
    readPdf = function (caseNumber, buffer) {
    	var deferred = q.defer();
    	pdfText(buffer, function (err, chunks) {
		  	if (!err && chunks) {
		  		//fs.writeFileSync("./pdf/" + caseNumber + ".pdf.json", JSON.stringify(chunks, null, 2));
		  		var caseNumberIndex = _.findIndex((chunks || []), function (chunk) {
		  			var isCaseNumber = (chunk || "").replace(/\D/g, "").substring(0, 6) === String(caseNumber).substring(0, 6);
		  			return isCaseNumber;
		  		});
		  		if (caseNumberIndex === -1) {
		  			throw(new Error("No chunk for: " + String(caseNumber)));
		  		}
		  		if (caseNumberIndex >= 0) {
			  		deferred.resolve({
			  		  id: caseNumber,
		              location: (function () {
		              	var range = (chunks || []).slice(caseNumberIndex + 12, caseNumberIndex + 20);
		              	return _.find(range, function (chunk) {
		              		return chunk.indexOf("NC 275") !== -1;
		              	});
		              })(),
		              race: chunks[215],
		              sex: chunks[216],
		              property: chunks[203]
		            });
			  	}
		    	else {
		    		deferred.reject("Case Number " + caseNumber + " not found in JSON response.");
		    	}
	    	}
	    	else {
	    		deferred.reject(err);
	    	}
		});

    	return deferred.promise;
    },
    geocodeAddress = function (caseNumber, address) {
    	var deferred = q.defer();
    	geocoder.getCoordinates(address).then(function (result) {
    		if (result && result.lat && result.lng) {
	    		deferred.resolve({
		  		  id: caseNumber,
	              coordinates: [result.lat, result.lng]
	            });
	    	}
	    	else {
	    		deferred.reject("No coordinates returned");
	    	}
    	},
    	deferred.reject);
    	return deferred.promise;
    };

    return {
      getPdf: getPdf,
      fetch: function (date) {
        if (ready) {
          return fetch(date);
        }
        else {
          var deferred = q.defer();
          connect(date).then(function () {
            ready = true;
            fetch(date).then(function (response) {
              try {
                var text = response
                  .replace(/\\u0026nbsp\;/gi, "")
                  .replace(/\:\s?\"\,\s{0,1}/gi, ":\"")
                  .replace(/\:\s?\"\s{0,1}\"/gi, ":null");
                var json = JSON.parse(text);
                var incidents = _.filter(json.rows, function (row) {
                	return row.id && row.charge && row.key === "LW";
                });
                var reportPromises = _.map(incidents, function (incident) {
                	return getPdf(incident.id, date);
                });
                q.allSettled(reportPromises).then(function (results) {
                	var readPromises = _.map(_.where(results, { state: "fulfilled" }), function (result) {
                		return readPdf(result.value.id, result.value.buffer);
                	});
                	q.allSettled(readPromises).then(function (results) {
                		_.forEach(results, function (result) {
                			var data = result.value;
                			if (data) {
							  	_.forEach(_.omit(Object.keys(data), "id"), function (key) {
							  		_.forEach(_.where(incidents, { id: data.id }), function (incident) {
						          		incident[key] = data[key];
						          	});
						    	});
							}
						});

						var geocodePromises = _.map(incidents, function (incident) {
							return geocodeAddress(incident.id, incident.location);
						});
						q.allSettled(geocodePromises).then(function (results) {
							_.forEach(results, function (result) {
								if (result && result.value) {
									_.forEach(_.where(incidents, { id: result.value.id }), function (incident) {
						          		incident.coordinates = result.value.coordinates;
						          	});
								}
							});

							deferred.resolve({ rows: incidents });
						},
						function (reason) {
							console.log("ERROR5! " + ex.message);
							deferred.reject(reason);
						})
		                .catch(function (ex) {
		                	console.log("ERROR6! " + ex.message);
		                	deferred.reject(ex.message);
		                });
                	});
                },
                function (reason) {
					console.log("ERROR11! " + ex.message);
					deferred.reject(reason);
				})
                .catch(function (ex) {
                	console.log("ERROR4! " + ex.message);
                	deferred.reject();
                });
              }
              catch (ex) {
                deferred.reject("Cannot parse response as JSON: " + ex.message);
              }
            }, deferred.reject);
          });
          return deferred.promise;
        }
      }
    };
  })("http://policereports.townofcary.org");

  module.exports = service;
})();<commit_msg>Added more debugging and error handling<commit_after>(function() {
  "use strict";

  var DEBUG = true,
    DO_GEOCODE = false;

  var rp = require("request-promise"),
    cheerio = require("cheerio"),
    moment = require("moment"),
    pdfText = require('pdf-text'),
    fs = require("fs"),
    _ = require("lodash"),
    q = require("q");

  require("stringformat").extendString("format");

  var geocoder = require("./geocoder");

  var service = (function(baseUrl) {
    if (typeof baseUrl !== "string") throw ("baseUrl is not defined");

    var initUrl = "{0}/{1}".format(baseUrl, "dailybulletin.aspx"),
      searchUrl = "{0}/{1}".format(baseUrl, "summary.aspx"),
      jsonUrl = "{0}/{1}".format(baseUrl, "jqHandler.ashx?op=s");

    var cookieJar = rp.jar(),
      request = rp.defaults({
        proxy: process.env.CRHME_PROXY || null,
        jar: cookieJar,
        rejectUnauthorized: false,
        followRedirect: true,
        followAllRedirects: true,
        headers: {
          "accept": "application/json, text/javascript, */*",
          "user-agent": "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)",
          "dnt": "1",
          "accept-language": "en-US,en;q=0.8",
          "x-requested-with": "XMLHttpRequest",
          "content-type": "application/x-www-form-urlencoded",
          "connection": "keep-alive",
          "origin": baseUrl,
          "referer": initUrl
        }
      });

    var parameters = {};

    var ready = false;

    var getFormParams = function(html) {
      var $ = cheerio.load(html),
        params = {};

      _.forEach($("form input"), function(element) {
        var key = element.attribs["name"];
        (typeof key === "string") && (params[key] = element.attribs["value"]);
      });

      return params;
    };

    var connect = function() {
        var deferred = q.defer();
        request({
            method: "GET",
            uri: initUrl
          })
          .then(function(html) {
            parameters[initUrl] = getFormParams(html);
            parameters[initUrl]["MasterPage$mainContent$ddlType2"] = "AL";
            parameters[initUrl]["__EVENTTARGET"] = "MasterPage$mainContent$lbUpdate";
            parameters[initUrl]["__EVENTARGUMENT"] = "";
            deferred.resolve();
          }, deferred.reject)
          .catch(deferred.reject);
        return deferred.promise;
      },
      setDate = function(date) {
        var deferred = q.defer();
        parameters[initUrl]["MasterPage$mainContent$txtDate2"] = moment(date).format("MM/DD/YYYY");
        request({
          method: "POST",
          uri: initUrl,
          form: parameters[initUrl]
        }).then(deferred.resolve, deferred.reject);
        return deferred.promise;
      },
      fetch = function(date) {
        var deferred = q.defer();

        if (typeof date === "string") {
          date = Date.parse(date + " ");
        }

        var query = {
          "t": "db",
          "_search": true,
          "nd": date.valueOf(),
          "rows": 10000,
          "page": 1,
          "sidx": "case",
          "sord": "asc"
        };

        setDate(date).then(function() {
          request({
              method: "POST",
              uri: jsonUrl,
              form: query
            })
            .then(deferred.resolve, deferred.reject)
            .catch(deferred.reject);
        }, deferred.reject);

        return deferred.promise;
      },
      getPdf = function(caseNumber, date) {
        var deferred = q.defer();
        var cancel = _.bind(deferred.reject, deferred, [null]);
        request({
            method: "GET",
            uri: searchUrl
          })
          .then(function(html) {
            parameters[searchUrl] = getFormParams(html);
            parameters[searchUrl]["__EVENTTARGET"] = "MasterPage$mainContent$cmdSubmit2";
            parameters[searchUrl]["__EVENTARGUMENT"] = "";
            parameters[searchUrl]["MasterPage$mainContent$txtCase2"] = caseNumber;
            delete(parameters[searchUrl]["MasterPage$mainContent$chkTA2"]); // = "on";
            delete(parameters[searchUrl]["MasterPage$mainContent$chkAR2"]); // = "on";
            parameters[searchUrl]["MasterPage$mainContent$chkLW2"] = "on";
            parameters[searchUrl]["MasterPage$mainContent$txtDateFrom2"] = moment(Date.parse(date + " ")).format("MM/DD/YYYY");
            parameters[searchUrl]["MasterPage$mainContent$txtDateTo2"] = moment(Date.parse(date + " ") + (86400000 * 2)).format("MM/DD/YYYY");
            request({
                method: "POST",
                uri: searchUrl,
                form: parameters[searchUrl]
              }).then(function(html) {
                  var $ = cheerio.load(html),
                    reportElementId = "mainContent_gvSummary_lbGetReport_0";
                  if ($("#" + reportElementId).length) {
                    parameters[searchUrl] = getFormParams(html);
                    parameters[searchUrl]["__EVENTTARGET"] = "MasterPage$mainContent$gvSummary$ctl02$lbGetReport";
                    parameters[searchUrl]["__EVENTARGUMENT"] = "";
                    request({
                        method: "POST",
                        uri: searchUrl,
                        form: parameters[searchUrl],
                        encoding: null
                      }).then(function(buffer) {
                        if (buffer) {
                          deferred.resolve({
                            id: caseNumber,
                            buffer: buffer
                          });
                        } else cancel();
                      }, cancel)
                      .catch(cancel);
                  } else {
                    cancel();
                  }
                },
                function(reason) {
                  console.log("REPORT FAILED: " + reason);
                  cancel();
                })
              .catch(cancel);
          }, cancel)
          .catch(cancel);
        return deferred.promise;
      },
      readPdf = function(caseNumber, buffer) {
        var deferred = q.defer();
        pdfText(buffer, function(err, chunks) {
          if (!err && chunks) {
            //fs.writeFileSync("./pdf/" + caseNumber + ".pdf.json", JSON.stringify(chunks, null, 2));
            var caseNumberIndex = _.findIndex((chunks || []), function(chunk) {
              var isCaseNumber = (chunk || "").replace(/\D/g, "").substring(0, 6) === String(caseNumber).substring(0, 6);
              return isCaseNumber;
            });
            if (caseNumberIndex === -1) {
              DEBUG && console.log(JSON.stringify(chunks, null, 2));
              throw (new Error("No chunk for: " + String(caseNumber)));
            }
            if (caseNumberIndex >= 0) {
              deferred.resolve({
                id: caseNumber,
                location: (function() {
                  var range = (chunks || []).slice(caseNumberIndex + 12, caseNumberIndex + 20);
                  return _.find(range, function(chunk) {
                    return chunk.indexOf("NC 275") !== -1;
                  });
                })(),
                race: chunks[215],
                sex: chunks[216],
                property: chunks[203]
              });
            } else {
              deferred.reject("Case Number " + caseNumber + " not found in JSON response.");
            }
          } else {
            deferred.reject(err);
          }
        });

        return deferred.promise;
      },
      geocodeAddress = function(caseNumber, address) {
        var deferred = q.defer();
        geocoder.getCoordinates(address).then(function(result) {
            if (result && result.lat && result.lng) {
              deferred.resolve({
                id: caseNumber,
                coordinates: [result.lat, result.lng]
              });
            } else {
              deferred.reject("No coordinates returned");
            }
          },
          deferred.reject);
        return deferred.promise;
      };

    return {
      getPdf: getPdf,
      fetch: function(date) {
        if (ready) {
          return fetch(date);
        } else {
          var deferred = q.defer();
          connect(date).then(function() {
            ready = true;
            fetch(date).then(function(response) {
              try {
                var text = response
                  .replace(/\\u0026nbsp\;/gi, "")
                  .replace(/\:\s?\"\,\s{0,1}/gi, ":\"")
                  .replace(/\:\s?\"\s{0,1}\"/gi, ":null");
                var json = JSON.parse(text);
                var incidents = _.filter(json.rows, function(row) {
                  return row.id && row.charge && row.key === "LW";
                });
                var reportPromises = _.map(incidents, function(incident) {
                  return getPdf(incident.id, date);
                });
                q.allSettled(reportPromises).then(function(results) {
                      var readPromises = _.map(_.where(results, {
                        state: "fulfilled"
                      }), function(result) {
                        try {
                          var parsed = readPdf(result.value.id, result.value.buffer);
                          DEBUG && console.log("Parsed PDF for " + result.value.id);
                          return parsed;
                        }
                        catch (ex) {
                          DEBUG && console.log("FAILED to parse PDF for " + result.value.id);
                          return null;
                        }
                      });
                      q.allSettled(readPromises).then(function(results) {
                        _.forEach(results, function(result) {
                          try {
                            var data = result.value;
                            if (data) {
                              _.forEach(_.omit(Object.keys(data), "id"), function(key) {
                                _.forEach(_.where(incidents, {
                                  id: data.id
                                }), function(incident) {
                                  incident[key] = data[key];
                                });
                              });
                            }
                          }
                          catch (ex) {
                            DEBUG && console.log("FAILED to assign PDF values for " + result.value.id);
                          }
                        });

                        if (DO_GEOCODE) {
                          var geocodePromises = _.map(incidents, function(incident) {
                            return geocodeAddress(incident.id, incident.location);
                          });
                          q.allSettled(geocodePromises).then(function(results) {
                            _.forEach(results, function(result) {
                              if (result && result.value) {
                                _.forEach(_.where(incidents, {
                                  id: result.value.id
                                }), function(incident) {
                                  incident.coordinates = result.value.coordinates;
                                });
                              }
                            });

                            DEBUG && console.log("Resolving with geocoding for " + incidents.length + " incidents");

                            deferred.resolve({
                              rows: incidents
                            });
                          },
                          function(reason) {
                            console.log("ERROR5! " + ex.message);
                            deferred.reject(reason);
                          })
                          .catch(function(ex) {
                            console.log("ERROR6! " + ex.message);
                            deferred.reject(ex.message);
                          });
                        }
                        else {
                          DEBUG && console.log("Resolving WITHOUT geocoding for " + incidents.length + " incidents");
                          deferred.resolve({
                            rows: incidents
                          });
                        }
                      });
                    },
                    function(reason) {
                      console.log("ERROR11! " + reason);
                      deferred.reject(reason);
                    })
                  .catch(function(ex) {
                    console.log("ERROR4! " + ex.message);
                    deferred.reject();
                  });
              } catch (ex) {
                console.log("ERROR12! " + ex.message);
                deferred.reject("Cannot parse response as JSON: " + ex.message);
              }
            },
            function(reason) {
              console.log("ERROR13! " + reason);
              deferred.reject(reason);
            });
          });
          return deferred.promise;
        }
      }
    };
  })("http://policereports.townofcary.org");

  module.exports = service;
})();<|endoftext|>


<commit_before>var _ = require('lodash');
var async = require('async');

module.exports = {

  improve: 'apostrophe-pages',

  construct: function(self, options) {

    var superGetPathIndexParams = self.getPathIndexParams;
    self.getPathIndexParams = function() {
      var params = superGetPathIndexParams();
      params.workflowLocaleForPathIndex = 1;
      return params;
    };

    var superRemoveTrailingSlugSlashes = self.removeTrailingSlugSlashes;
    self.removeTrailingSlugSlashes = function(req, slug) {
      if (arguments.length === 1) {
        // bc workaround
        slug = req;
        req = self.apos.tasks.getAnonReq();
      }
      var workflow = self.apos.modules['apostrophe-workflow'];
      if (!workflow.prefixes) {
        return superRemoveTrailingSlugSlashes(slug);
      }
      var locale = workflow.liveify(req.locale);
      if (_.has(workflow.prefixes, locale)) {
        var matches = slug.match(/^(\/[^\/]+)(\/?)$/);
        if (matches && (workflow.prefixes[locale] === matches[1])) {
          // Something like /en/, leave it alone,
          // it's a localized homepage. However if the
          // trailing slash *after* the locale is missing,
          // add it and redirect
          if (matches[2] === '') {
            return slug + '/';
          } else {
            return slug;
          }
        }
      }
      return superRemoveTrailingSlugSlashes(slug);
    };

    var superPruneCurrentPageForBrowser = self.pruneCurrentPageForBrowser;
    self.pruneCurrentPageForBrowser = function(page) {
      var pruned = superPruneCurrentPageForBrowser(page);
      pruned.workflowLocale = page.workflowLocale;
      pruned.workflowGuid = page.workflowGuid;
      return pruned;
    };

    var superGetEditControls = self.getEditControls;
    self.getEditControls = function(req) {
      return upgradeControls(req, superGetEditControls(req), 'edit');
    };

    var superGetCreateControls = self.getCreateControls;
    self.getCreateControls = function(req) {
      return upgradeControls(req, superGetCreateControls(req), 'create');
    };

    function upgradeControls(req, controls, verb) {
      var workflow = self.apos.modules['apostrophe-workflow'];
      if (!workflow.includeType(self.name)) {
        // Not subject to workflow
        return controls;
      }
      var save = _.find(controls, { action: 'save' });
      if (save) {
        save.label = 'Save Draft';
      }
      controls.push({
        type: 'dropdown',
        label: 'Workflow',
        dropdownOptions: {
          direction: 'down'
        },
        items: [
          {
            label: 'Submit',
            action: 'workflow-submit'
          },
          {
            label: 'Commit',
            action: 'workflow-commit'
          }
        ].concat(
          (workflow.localized && (verb === 'edit')) ?
          [
            {
              label: 'History',
              action: 'workflow-history'
            }
          ] : []
        ).concat(workflow.localized ?
          [
            {
              label: 'Force Export',
              action: 'workflow-force-export'
            }
          ] : []
        )
      });
      return controls;
    }

    var superMovePermissions = self.movePermissions;

    // "Based on `req`, `moved`, `data.oldParent` and `data.parent`, decide whether
    // this move should be permitted. If it should not be, report an error." The `apostrophe-pages`
    // module did this already, but we must consider the impact on all locales.

    self.movePermissions = function(req, moved, data, options, callback) {
      return superMovePermissions(req, moved, data, options, function(err) {
        if (err) {
          return callback(err);
        }
        if (!moved.workflowGuid) {
          // No localization for pages. That's unusual but allowed
          return callback(null);
        }
        // Grab the pages of interest across all locales other than the original (already checked).
        return self.apos.docs.find(req, {
          workflowGuid: { $in: [ moved.workflowGuid, data.oldParent.workflowGuid, data.parent.workflowGuid ] },
          workflowLocale: { $ne: moved.workflowLocale }
        }).joins(false).areas(false).workflowLocale(null).permission(false).published(null).trash(null).toArray(function(err, pages) {
          if (err) {
            return callback(err);
          }
          var error = null;
          var locales = {};
          _.each(pages, function(page) {
            if (!locales[page.workflowLocale]) {
              locales[page.workflowLocale] = {};
            }
            if (page.workflowGuid === moved.workflowGuid) {
              locales[page.workflowLocale].moved = page;
            }
            // Parent does not always change, else statement is not appropriate here
            if (page.workflowGuid === data.parent.workflowGuid) {
              locales[page.workflowLocale].parent = page;
            }
            if (page.workflowGuid === data.oldParent.workflowGuid) {
              locales[page.workflowLocale].oldParent = page;
            }
          });
          _.each(locales, function(locale, name) {
          // Repeat the permissions check for every locale
            if (!locale.moved._publish) {
              error = new Error('forbidden');
              return false;
            }
            if ((locale.oldParent._id !== locale.parent._id) && (!locale.parent._edit)) {
              error = new Error('forbidden');
              return false;
            }
          });
          return callback(error);
        });
      });
    };

    // On the initial invocation of `apos.pages.move`, modify the criteria and filters
    // to ensure only the relevant locale is in play

    var superBeforeMove = self.beforeMove;
    self.beforeMove = function(req, moved, target, position, options, callback) {
      return superBeforeMove(req, moved, target, position, options, function(err) {
        if (err) {
          return callback(err);
        }
        if (options.workflowRecursing) {
          return callback(null);
        }
        if (moved.workflowLocale) {
          options.criteria = _.assign({}, options.criteria || {}, { workflowLocale: moved.workflowLocale });
          options.filters = _.assign({}, options.filters || {}, { workflowLocale: moved.workflowLocale });
        }
        return callback(null);
      });
    };

    // After a page is moved in one locale, with all of the ripple effects that go with it,
    // make the same move in all other locales. Note that we already verified we have permissions
    // across all locales.

    var superAfterMove = self.afterMove;
    self.afterMove = function(req, moved, info, callback) {
      return superAfterMove(req, moved, info, function(err) {
        if (err) {
          return callback(err);
        }
        if (info.options.workflowRecursing) {
          return callback(null);
        }
        var ids = _.pluck(info.changed || [], '_id');
        if (!moved.workflowGuid) {
          return callback(null);
        }
        var locales = {};
        return async.series([
          get,
          invoke
        ], function(err) {
          return callback(err);
        });
        function get(callback) {
          // Locate the doc moved and the doc it is moved relative to (target) in all locales other than
          // the original one
          return self.apos.docs.db.find({ workflowGuid: { $in: [ moved.workflowGuid, info.target.workflowGuid ] }, workflowLocale: { $ne: moved.workflowLocale } }, { workflowGuid: 1, _id: 1, workflowLocale: 1 }).toArray(function(err, docs) {
            if (err) {
              return callback(err);
            }
            _.each(docs, function(doc) {
              locales[doc.workflowLocale] = locales[doc.workflowLocale] || {};
              if (doc.workflowGuid === moved.workflowGuid) {
                locales[doc.workflowLocale].movedId = doc._id;
              }
              if (doc.workflowGuid === info.target.workflowGuid) {
                locales[doc.workflowLocale].targetId = doc._id;
              }
            });
            return callback(null);
          });
        }
        function invoke(callback) {
          // Reinvoke apos.pages.move
          return async.eachSeries(_.keys(locales), function(locale, callback) {
            var _options = _.clone(info.options);
            _options.criteria = _.assign({}, info.options.criteria || {}, { workflowLocale: locale });
            _options.filters = _.assign({}, info.options.filters || {}, { workflowLocale: locale });
            _options.workflowRecursing = true;
            return self.move(req, locales[locale].movedId, locales[locale].targetId, info.position, _options, callback);
          }, callback);
        }
      });
    };

    // `implementParkAll` must be reimplemented to cover all of
    // the locales and to adjust the slugs if the prefixes option
    // is in use
    self.implementParkAll = function(callback) {
      if ((self.apos.argv._[0] === 'apostrophe-workflow:add-missing-locales') ||
        (self.apos.argv._[0] === 'apostrophe-workflow:add-locale-prefixes')) {
        // If we park pages in this scenario, we'll wind up with
        // duplicate pages in the new locales after the task
        // fills them in
        return setImmediate(callback);
      }
      var workflow = self.apos.modules['apostrophe-workflow'];
      return async.eachSeries(_.keys(workflow.locales), function(locale, callback) {
        if (locale.match(/\-draft/)) {
          // Skip the draft locales, the doc will automatically get created
          // in both draft and live locales
          return setImmediate(callback);
        }
        var parked = _.cloneDeep(self.parked);
        if (workflow.prefixes) {
          fixPrefixes(parked, locale);
        }

        var req = self.apos.tasks.getReq();
        req.locale = locale;

        return async.eachSeries(parked, function(item, callback) {
          return self.implementParkOne(req, item, callback);
        }, callback);

        function fixPrefixes(parked, locale) {
          var prefix = workflow.prefixes[locale];
          if (!prefix) {
            return;
          }
          _.each(parked, function(item) {
            if (item.slug === '/') {
              // Hint to the implementParkOne implementation that
              // this is still a homepage even if its slug is
              // no longer / due to locale prefixes
              item.level = 0;
            }
            if (item.parent) {
              item.parent = prefix + item.parent;
            } else if (item.level !== 0) {
              item.parent = prefix + '/';
            }
            item.slug = prefix + item.slug;
            fixPrefixes(item._children || [], locale);
          });
        }
      }, callback);
    };

    var superGetBaseUrl = self.getBaseUrl;
    // Return the appropriate base URL for constructing absolute
    // URLs in the relevant locale, if the `hostnames` option is
    // in play for this locale, otherwise fall back to the
    // standard behavior
    self.getBaseUrl = function(req) {
      var workflow = self.apos.modules['apostrophe-workflow'];
      var live = workflow.liveify(req.locale || workflow.defaultLocale);
      if (!(workflow.hostnames && workflow.hostnames[live])) {
        return superGetBaseUrl(req);
      }
      return req.protocol + '://' + workflow.hostnames[live];
    };

  }

};
<commit_msg>Facilitate local testing of hostnames feature by accommodating the port number of the original request when generating cross-locale links<commit_after>var _ = require('lodash');
var async = require('async');

module.exports = {

  improve: 'apostrophe-pages',

  construct: function(self, options) {

    var superGetPathIndexParams = self.getPathIndexParams;
    self.getPathIndexParams = function() {
      var params = superGetPathIndexParams();
      params.workflowLocaleForPathIndex = 1;
      return params;
    };

    var superRemoveTrailingSlugSlashes = self.removeTrailingSlugSlashes;
    self.removeTrailingSlugSlashes = function(req, slug) {
      if (arguments.length === 1) {
        // bc workaround
        slug = req;
        req = self.apos.tasks.getAnonReq();
      }
      var workflow = self.apos.modules['apostrophe-workflow'];
      if (!workflow.prefixes) {
        return superRemoveTrailingSlugSlashes(slug);
      }
      var locale = workflow.liveify(req.locale);
      if (_.has(workflow.prefixes, locale)) {
        var matches = slug.match(/^(\/[^\/]+)(\/?)$/);
        if (matches && (workflow.prefixes[locale] === matches[1])) {
          // Something like /en/, leave it alone,
          // it's a localized homepage. However if the
          // trailing slash *after* the locale is missing,
          // add it and redirect
          if (matches[2] === '') {
            return slug + '/';
          } else {
            return slug;
          }
        }
      }
      return superRemoveTrailingSlugSlashes(slug);
    };

    var superPruneCurrentPageForBrowser = self.pruneCurrentPageForBrowser;
    self.pruneCurrentPageForBrowser = function(page) {
      var pruned = superPruneCurrentPageForBrowser(page);
      pruned.workflowLocale = page.workflowLocale;
      pruned.workflowGuid = page.workflowGuid;
      return pruned;
    };

    var superGetEditControls = self.getEditControls;
    self.getEditControls = function(req) {
      return upgradeControls(req, superGetEditControls(req), 'edit');
    };

    var superGetCreateControls = self.getCreateControls;
    self.getCreateControls = function(req) {
      return upgradeControls(req, superGetCreateControls(req), 'create');
    };

    function upgradeControls(req, controls, verb) {
      var workflow = self.apos.modules['apostrophe-workflow'];
      if (!workflow.includeType(self.name)) {
        // Not subject to workflow
        return controls;
      }
      var save = _.find(controls, { action: 'save' });
      if (save) {
        save.label = 'Save Draft';
      }
      controls.push({
        type: 'dropdown',
        label: 'Workflow',
        dropdownOptions: {
          direction: 'down'
        },
        items: [
          {
            label: 'Submit',
            action: 'workflow-submit'
          },
          {
            label: 'Commit',
            action: 'workflow-commit'
          }
        ].concat(
          (workflow.localized && (verb === 'edit')) ?
          [
            {
              label: 'History',
              action: 'workflow-history'
            }
          ] : []
        ).concat(workflow.localized ?
          [
            {
              label: 'Force Export',
              action: 'workflow-force-export'
            }
          ] : []
        )
      });
      return controls;
    }

    var superMovePermissions = self.movePermissions;

    // "Based on `req`, `moved`, `data.oldParent` and `data.parent`, decide whether
    // this move should be permitted. If it should not be, report an error." The `apostrophe-pages`
    // module did this already, but we must consider the impact on all locales.

    self.movePermissions = function(req, moved, data, options, callback) {
      return superMovePermissions(req, moved, data, options, function(err) {
        if (err) {
          return callback(err);
        }
        if (!moved.workflowGuid) {
          // No localization for pages. That's unusual but allowed
          return callback(null);
        }
        // Grab the pages of interest across all locales other than the original (already checked).
        return self.apos.docs.find(req, {
          workflowGuid: { $in: [ moved.workflowGuid, data.oldParent.workflowGuid, data.parent.workflowGuid ] },
          workflowLocale: { $ne: moved.workflowLocale }
        }).joins(false).areas(false).workflowLocale(null).permission(false).published(null).trash(null).toArray(function(err, pages) {
          if (err) {
            return callback(err);
          }
          var error = null;
          var locales = {};
          _.each(pages, function(page) {
            if (!locales[page.workflowLocale]) {
              locales[page.workflowLocale] = {};
            }
            if (page.workflowGuid === moved.workflowGuid) {
              locales[page.workflowLocale].moved = page;
            }
            // Parent does not always change, else statement is not appropriate here
            if (page.workflowGuid === data.parent.workflowGuid) {
              locales[page.workflowLocale].parent = page;
            }
            if (page.workflowGuid === data.oldParent.workflowGuid) {
              locales[page.workflowLocale].oldParent = page;
            }
          });
          _.each(locales, function(locale, name) {
          // Repeat the permissions check for every locale
            if (!locale.moved._publish) {
              error = new Error('forbidden');
              return false;
            }
            if ((locale.oldParent._id !== locale.parent._id) && (!locale.parent._edit)) {
              error = new Error('forbidden');
              return false;
            }
          });
          return callback(error);
        });
      });
    };

    // On the initial invocation of `apos.pages.move`, modify the criteria and filters
    // to ensure only the relevant locale is in play

    var superBeforeMove = self.beforeMove;
    self.beforeMove = function(req, moved, target, position, options, callback) {
      return superBeforeMove(req, moved, target, position, options, function(err) {
        if (err) {
          return callback(err);
        }
        if (options.workflowRecursing) {
          return callback(null);
        }
        if (moved.workflowLocale) {
          options.criteria = _.assign({}, options.criteria || {}, { workflowLocale: moved.workflowLocale });
          options.filters = _.assign({}, options.filters || {}, { workflowLocale: moved.workflowLocale });
        }
        return callback(null);
      });
    };

    // After a page is moved in one locale, with all of the ripple effects that go with it,
    // make the same move in all other locales. Note that we already verified we have permissions
    // across all locales.

    var superAfterMove = self.afterMove;
    self.afterMove = function(req, moved, info, callback) {
      return superAfterMove(req, moved, info, function(err) {
        if (err) {
          return callback(err);
        }
        if (info.options.workflowRecursing) {
          return callback(null);
        }
        var ids = _.pluck(info.changed || [], '_id');
        if (!moved.workflowGuid) {
          return callback(null);
        }
        var locales = {};
        return async.series([
          get,
          invoke
        ], function(err) {
          return callback(err);
        });
        function get(callback) {
          // Locate the doc moved and the doc it is moved relative to (target) in all locales other than
          // the original one
          return self.apos.docs.db.find({ workflowGuid: { $in: [ moved.workflowGuid, info.target.workflowGuid ] }, workflowLocale: { $ne: moved.workflowLocale } }, { workflowGuid: 1, _id: 1, workflowLocale: 1 }).toArray(function(err, docs) {
            if (err) {
              return callback(err);
            }
            _.each(docs, function(doc) {
              locales[doc.workflowLocale] = locales[doc.workflowLocale] || {};
              if (doc.workflowGuid === moved.workflowGuid) {
                locales[doc.workflowLocale].movedId = doc._id;
              }
              if (doc.workflowGuid === info.target.workflowGuid) {
                locales[doc.workflowLocale].targetId = doc._id;
              }
            });
            return callback(null);
          });
        }
        function invoke(callback) {
          // Reinvoke apos.pages.move
          return async.eachSeries(_.keys(locales), function(locale, callback) {
            var _options = _.clone(info.options);
            _options.criteria = _.assign({}, info.options.criteria || {}, { workflowLocale: locale });
            _options.filters = _.assign({}, info.options.filters || {}, { workflowLocale: locale });
            _options.workflowRecursing = true;
            return self.move(req, locales[locale].movedId, locales[locale].targetId, info.position, _options, callback);
          }, callback);
        }
      });
    };

    // `implementParkAll` must be reimplemented to cover all of
    // the locales and to adjust the slugs if the prefixes option
    // is in use
    self.implementParkAll = function(callback) {
      if ((self.apos.argv._[0] === 'apostrophe-workflow:add-missing-locales') ||
        (self.apos.argv._[0] === 'apostrophe-workflow:add-locale-prefixes')) {
        // If we park pages in this scenario, we'll wind up with
        // duplicate pages in the new locales after the task
        // fills them in
        return setImmediate(callback);
      }
      var workflow = self.apos.modules['apostrophe-workflow'];
      return async.eachSeries(_.keys(workflow.locales), function(locale, callback) {
        if (locale.match(/\-draft/)) {
          // Skip the draft locales, the doc will automatically get created
          // in both draft and live locales
          return setImmediate(callback);
        }
        var parked = _.cloneDeep(self.parked);
        if (workflow.prefixes) {
          fixPrefixes(parked, locale);
        }

        var req = self.apos.tasks.getReq();
        req.locale = locale;

        return async.eachSeries(parked, function(item, callback) {
          return self.implementParkOne(req, item, callback);
        }, callback);

        function fixPrefixes(parked, locale) {
          var prefix = workflow.prefixes[locale];
          if (!prefix) {
            return;
          }
          _.each(parked, function(item) {
            if (item.slug === '/') {
              // Hint to the implementParkOne implementation that
              // this is still a homepage even if its slug is
              // no longer / due to locale prefixes
              item.level = 0;
            }
            if (item.parent) {
              item.parent = prefix + item.parent;
            } else if (item.level !== 0) {
              item.parent = prefix + '/';
            }
            item.slug = prefix + item.slug;
            fixPrefixes(item._children || [], locale);
          });
        }
      }, callback);
    };

    var superGetBaseUrl = self.getBaseUrl;
    // Return the appropriate base URL for constructing absolute
    // URLs in the relevant locale, if the `hostnames` option is
    // in play for this locale, otherwise fall back to the
    // standard behavior
    self.getBaseUrl = function(req) {
      var workflow = self.apos.modules['apostrophe-workflow'];
      var live = workflow.liveify(req.locale || workflow.defaultLocale);
      if (!(workflow.hostnames && workflow.hostnames[live])) {
        return superGetBaseUrl(req);
      }
      var url = req.protocol + '://' + workflow.hostnames[live];
      // If the request URL was on a particular port, the
      // new URL should be on that port too. Helpful for
      // debugging locally
      var matches = (req.get('host') || '').match(/\:(\d+$)/);
      var port;
      if (matches) {
        port = matches[1];
        if (((req.protocol === 'http') && (port !== '80')) ||
          ((req.protocol === 'https') && (port !== '443'))) {
          url += ':' + port;
        }
      }
      return url;
    };

  }

};
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
blnLocRunning = false;
blnInBackground = false;
strHost = "";
intCompNo = 0;
strTechID = "";

var app = {
    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicitly call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
        document.addEventListener("pause", onPause, false);
        document.addEventListener("resume", onResume, false);
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        //alert("In receivedEvent");
        console.log("In receivedEvent 01");
        fillvars();
        checkvalues(false);
        console.log('Received Event: ' + id);
    }
};

function onResume() {
    console.log("app resumed");
    blnInBackground = false;
    fillvars();
    checkvalues(false);
    return;
}

function onPause() {
    blnInBackground = true;
    return;
}

function alertDismissed() {
    return;
}

function fillvars() {
    if (localStorage.LocHost)
    {
        strHost = localStorage.LocHost;
        document.getElementById('txtHost').value = strHost;
    }
    if (localStorage.LocCompNo)
    {
        intval = parseInt(localStorage.LocCompNo, 10);
        intCompNo = 0;
        if (intval > 0 && intval < 1000)
        {
            intCompNo = intval;
        }
        if (intCompNo > 0)
        {
            document.getElementById('txtCompNo').value = intCompNo;
        }

    }
    if (localStorage.LocTechID)
    {
        strTechID = localStorage.LocTechID;
        document.getElementById('txtTechID').value = strTechID;
    }
}

function checkvalues(ShowError) {
    blnError = false;
    strError = "";
    lenHost = document.getElementById('txtHost').value.length;
    if (lenHost < 1)
    {
        strError += "Invalid Host or IP address\r\n";
        blnErrro = true;
    }
    intVal = parseInt(document.getElementById('txtCompNo').value, 10);
    if (isNaN(intVal))
    {
        intVal = 0;
    }
    if (intVal < 1 || intVal > 999)
    {
        strError += "Invalid Company number\r\n";
        blnError = true;
    }
    lenTechID = document.getElementById('txtTechID').value.length;
    if (lenTechID < 1)
    {
        strError += "Invalid TechID\r\n";
        blnError = true;
    }
    if (ShowError && blnError)
    {
        navigator.notification.alert(
            strError,
            alertDismissed,
            'Error in input fields',
            'OK'
        );
    }
    if (blnLocRunning)
    {
        document.getElementById('btnStart').disabled = true;
        document.getElementById('btnStop').disabled = false;
        document.getElementById('backstatus').innerHTML = "Broadcasting locations";
    } else {
        document.getElementById('btnStart').disabled = false;
        document.getElementById('btnStop').disabled = true;
        document.getElementById('backstatus').innerHTML = "Not broadcasting location";
    }
    return blnError;
}

function start() {
//    if (blnLocRunning)
//    {
//        return;
//    }
    /* If checkvalues returns true then errors were found */
    if (checkvalues(true))
    {
        return;
    }
    strHost = document.getElementById('txtHost').value;
    strCompNo = document.getElementById('txtCompNo').value;
    intCompNo = parseInt(strCompNo, 10);
    strTechID = document.getElementById('txtTechID').value;
    localStorage.setItem("LocHost", strHost);
    localStorage.setItem("LocCompNo", intCompNo);
    localStorage.setItem("LocTechID", strTechID);

    backgroundGeolocation = window.backgroundGeolocation || window.backgroundGeoLocation || window.universalGeolocation;

    /**
    * This callback will be executed every time a geolocation is recorded in the background.
    */
    var callbackFn = function(location,blnBackground) {
        var d = new Date();
        if (!blnBackground)
        {
            document.getElementById('backstatus').innerHTML = "Sending broadcasts" + " " + d.toLocaleString();
        }

        console.log('[js] backgroundGeolocation callback:  ' + location.latitude + ',' + location.longitude + " " + d.toLocaleString());
        sendupdate(location);
        // Do your HTTP request here to POST location to your server.
        // jQuery.post(url, JSON.stringify(location));

        /*
        IMPORTANT:  You must execute the finish method here to inform the native plugin that you're finished,
        and the background-task may be completed.  You must do this regardless if your HTTP request is successful or not.
        IF YOU DON'T, ios will CRASH YOUR APP for spending too much time in the background.
        */
        backgroundGeolocation.finish();
    };

    function sendupdate(location) {
        xmlhttp = new XMLHttpRequest();
        if (!xmlhttp)
        {
            console.log("Could not transmit - no xmlhttp");
            return;
        }
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
            {
                var xmlDoc = xmlhttp.responseText;
                console.log("Got post response: " + xmlDoc);
            }
        };

        var d = new Date();
        var strUpdTime = formatdate(d);
        var locupdate = "request-cd=XLOC&comp-no=" + localStorage.LocCompNo +
                       "&tech-id=" + localStorage.LocTechID + "&longitude=" +
                       location.longitude + "&latitude=" + location.latitude +
                       "&accuracy=" + location.accuracy + "&speed=" +
                       location.speed + "&timestamp=" + strUpdTime +
                       "&resp-page=locupdresult.htm&error-page=" +
                       "locupderr.html";
        var url="https://" + localStorage.LocHost + "/cgi-bin/facshtml.cgi";
        console.log(url);
        console.log(locupdate);
        xmlhttp.open("POST",url,true);
        xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
        xmlhttp.responseType = "text";
        xmlhttp.send(locupdate);
    }

    var failureFn = function(error) {
        console.log('BackgroundGeolocation error');
    };

    // BackgroundGeolocation is highly configurable. See platform specific configuration options
    backgroundGeolocation.configure(callbackFn, failureFn, {
        desiredAccuracy: 10,
        stationaryRadius: 5,
        distanceFilter: 5,
        pauseLocationUpdates: false,
        interval: 1000
    });

    console.log("Start Tracking");
    // Turn ON the background-geolocation system.  The user will be tracked whenever they suspend the app.
    backgroundGeolocation.start();
    console.log("Tracking Started");

    blnLocRunning = true;

    checkvalues(false);
    // If you wish to turn OFF background-tracking, call the #stop method.
    // backgroundGeolocation.stop();

//    alert("Start Tracking");
}

function formatdate(dt) {
	strYear = dt.getFullYear().toString();
	var mth = dt.getMonth() + 1;
	var str = mth.toString();
	strMonth = pad(str, 2, '0', STR_PAD_LEFT);
	str = dt.getDate().toString();
	strDay = pad(str, 2, '0', STR_PAD_LEFT);
	var hrs = dt.getHours().toString();
	var mins = dt.getMinutes().toString();
	var strTime = pad(hrs, 2, '0', STR_PAD_LEFT) + ":" +
	              pad(mins,2, '0', STR_PAD_LEFT);
	return strYear + "-" + strMonth + "-" + strDay + " " + strTime;
}

/**
 *
 * Javascript string pad http://www.webtoolkit.info/
 *
 */

var STR_PAD_LEFT = 1;
var STR_PAD_RIGHT = 2;
var STR_PAD_BOTH = 3;

function pad(str, len, pad, dir) {
	if (str == undefined) {
		return;
	}
	if (typeof (len) == "undefined") {
		var len = 0;
	}
	if (typeof (pad) == "undefined") {
		var pad = ' ';
	}
	if (typeof (dir) == "undefined") {
		var dir = STR_PAD_RIGHT;
	}

	if (len + 1 >= str.length) {

		switch (dir) {

		case STR_PAD_LEFT:
			str = Array(len + 1 - str.length).join(pad) + str;
			break;

		case STR_PAD_BOTH:
			var right = Math.ceil((padlen = len - str.length) / 2);
			var left = padlen - right;
			str = Array(left + 1).join(pad) + str + Array(right + 1).join(pad);
			break;

		default:
			str = str + Array(len + 1 - str.length).join(pad);
			break;

		} // switch

	}

	return str;

}

function stop() {
    if (!blnLocRunning)
    {
        return;
    }
    backgroundGeolocation.stop();
    blnLocRunning = false;
    checkvalues(false);
}
<commit_msg>Changed stationaryRadius from 5 to 20<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
blnLocRunning = false;
blnInBackground = false;
strHost = "";
intCompNo = 0;
strTechID = "";

var app = {
    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicitly call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
        document.addEventListener("pause", onPause, false);
        document.addEventListener("resume", onResume, false);
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        //alert("In receivedEvent");
        console.log("In receivedEvent 01");
        fillvars();
        checkvalues(false);
        console.log('Received Event: ' + id);
    }
};

function onResume() {
    console.log("app resumed");
    blnInBackground = false;
    fillvars();
    checkvalues(false);
    return;
}

function onPause() {
    blnInBackground = true;
    return;
}

function alertDismissed() {
    return;
}

function fillvars() {
    if (localStorage.LocHost)
    {
        strHost = localStorage.LocHost;
        document.getElementById('txtHost').value = strHost;
    }
    if (localStorage.LocCompNo)
    {
        intval = parseInt(localStorage.LocCompNo, 10);
        intCompNo = 0;
        if (intval > 0 && intval < 1000)
        {
            intCompNo = intval;
        }
        if (intCompNo > 0)
        {
            document.getElementById('txtCompNo').value = intCompNo;
        }

    }
    if (localStorage.LocTechID)
    {
        strTechID = localStorage.LocTechID;
        document.getElementById('txtTechID').value = strTechID;
    }
}

function checkvalues(ShowError) {
    blnError = false;
    strError = "";
    lenHost = document.getElementById('txtHost').value.length;
    if (lenHost < 1)
    {
        strError += "Invalid Host or IP address\r\n";
        blnErrro = true;
    }
    intVal = parseInt(document.getElementById('txtCompNo').value, 10);
    if (isNaN(intVal))
    {
        intVal = 0;
    }
    if (intVal < 1 || intVal > 999)
    {
        strError += "Invalid Company number\r\n";
        blnError = true;
    }
    lenTechID = document.getElementById('txtTechID').value.length;
    if (lenTechID < 1)
    {
        strError += "Invalid TechID\r\n";
        blnError = true;
    }
    if (ShowError && blnError)
    {
        navigator.notification.alert(
            strError,
            alertDismissed,
            'Error in input fields',
            'OK'
        );
    }
    if (blnLocRunning)
    {
        document.getElementById('btnStart').disabled = true;
        document.getElementById('btnStop').disabled = false;
        document.getElementById('backstatus').innerHTML = "Broadcasting locations";
    } else {
        document.getElementById('btnStart').disabled = false;
        document.getElementById('btnStop').disabled = true;
        document.getElementById('backstatus').innerHTML = "Not broadcasting location";
    }
    return blnError;
}

function start() {
//    if (blnLocRunning)
//    {
//        return;
//    }
    /* If checkvalues returns true then errors were found */
    if (checkvalues(true))
    {
        return;
    }
    strHost = document.getElementById('txtHost').value;
    strCompNo = document.getElementById('txtCompNo').value;
    intCompNo = parseInt(strCompNo, 10);
    strTechID = document.getElementById('txtTechID').value;
    localStorage.setItem("LocHost", strHost);
    localStorage.setItem("LocCompNo", intCompNo);
    localStorage.setItem("LocTechID", strTechID);

    backgroundGeolocation = window.backgroundGeolocation || window.backgroundGeoLocation || window.universalGeolocation;

    /**
    * This callback will be executed every time a geolocation is recorded in the background.
    */
    var callbackFn = function(location,blnBackground) {
        var d = new Date();
        if (!blnBackground)
        {
            document.getElementById('backstatus').innerHTML = "Sending broadcasts" + " " + d.toLocaleString();
        }

        console.log('[js] backgroundGeolocation callback:  ' + location.latitude + ',' + location.longitude + " " + d.toLocaleString());
        sendupdate(location);
        // Do your HTTP request here to POST location to your server.
        // jQuery.post(url, JSON.stringify(location));

        /*
        IMPORTANT:  You must execute the finish method here to inform the native plugin that you're finished,
        and the background-task may be completed.  You must do this regardless if your HTTP request is successful or not.
        IF YOU DON'T, ios will CRASH YOUR APP for spending too much time in the background.
        */
        backgroundGeolocation.finish();
    };

    function sendupdate(location) {
        xmlhttp = new XMLHttpRequest();
        if (!xmlhttp)
        {
            console.log("Could not transmit - no xmlhttp");
            return;
        }
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
            {
                var xmlDoc = xmlhttp.responseText;
                console.log("Got post response: " + xmlDoc);
            }
        };

        var d = new Date();
        var strUpdTime = formatdate(d);
        var locupdate = "request-cd=XLOC&comp-no=" + localStorage.LocCompNo +
                       "&tech-id=" + localStorage.LocTechID + "&longitude=" +
                       location.longitude + "&latitude=" + location.latitude +
                       "&accuracy=" + location.accuracy + "&speed=" +
                       location.speed + "&timestamp=" + strUpdTime +
                       "&resp-page=locupdresult.htm&error-page=" +
                       "locupderr.html";
        var url="https://" + localStorage.LocHost + "/cgi-bin/facshtml.cgi";
        console.log(url);
        console.log(locupdate);
        xmlhttp.open("POST",url,true);
        xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
        xmlhttp.responseType = "text";
        xmlhttp.send(locupdate);
    }

    var failureFn = function(error) {
        console.log('BackgroundGeolocation error');
    };

    // BackgroundGeolocation is highly configurable. See platform specific configuration options
    backgroundGeolocation.configure(callbackFn, failureFn, {
        desiredAccuracy: 10,
        stationaryRadius: 20,
        distanceFilter: 5,
        pauseLocationUpdates: false,
        interval: 1000
    });

    console.log("Start Tracking");
    // Turn ON the background-geolocation system.  The user will be tracked whenever they suspend the app.
    backgroundGeolocation.start();
    console.log("Tracking Started");

    blnLocRunning = true;

    checkvalues(false);
    // If you wish to turn OFF background-tracking, call the #stop method.
    // backgroundGeolocation.stop();

//    alert("Start Tracking");
}

function formatdate(dt) {
	strYear = dt.getFullYear().toString();
	var mth = dt.getMonth() + 1;
	var str = mth.toString();
	strMonth = pad(str, 2, '0', STR_PAD_LEFT);
	str = dt.getDate().toString();
	strDay = pad(str, 2, '0', STR_PAD_LEFT);
	var hrs = dt.getHours().toString();
	var mins = dt.getMinutes().toString();
	var strTime = pad(hrs, 2, '0', STR_PAD_LEFT) + ":" +
	              pad(mins,2, '0', STR_PAD_LEFT);
	return strYear + "-" + strMonth + "-" + strDay + " " + strTime;
}

/**
 *
 * Javascript string pad http://www.webtoolkit.info/
 *
 */

var STR_PAD_LEFT = 1;
var STR_PAD_RIGHT = 2;
var STR_PAD_BOTH = 3;

function pad(str, len, pad, dir) {
	if (str == undefined) {
		return;
	}
	if (typeof (len) == "undefined") {
		var len = 0;
	}
	if (typeof (pad) == "undefined") {
		var pad = ' ';
	}
	if (typeof (dir) == "undefined") {
		var dir = STR_PAD_RIGHT;
	}

	if (len + 1 >= str.length) {

		switch (dir) {

		case STR_PAD_LEFT:
			str = Array(len + 1 - str.length).join(pad) + str;
			break;

		case STR_PAD_BOTH:
			var right = Math.ceil((padlen = len - str.length) / 2);
			var left = padlen - right;
			str = Array(left + 1).join(pad) + str + Array(right + 1).join(pad);
			break;

		default:
			str = str + Array(len + 1 - str.length).join(pad);
			break;

		} // switch

	}

	return str;

}

function stop() {
    if (!blnLocRunning)
    {
        return;
    }
    backgroundGeolocation.stop();
    blnLocRunning = false;
    checkvalues(false);
}
<|endoftext|>


<commit_before>/* @flow */

/**
 * main menu
 */

import React, {
  Component,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  View,
} from 'react-native';

import commonStyles from './common-styles';
import IconButton from './IconButton';
import Modal from 'react-native-modalbox';
const CalendarPicker = require('react-native-calendar-picker');

let styles = StyleSheet.create({
  container: {
    flex: 1,
    width: commonStyles.deviceWidth,
    height: commonStyles.deviceHeight,
    backgroundColor: '#eaf4be',
    justifyContent: 'space-between',
  },
  modal: {
    justifyContent: 'center',
    alignItems: 'flex-start',
    height: 400,
    backgroundColor: 'white',
  },
  spacer: {
    paddingBottom: 30,
    height:10,
    width: 1,
  },
  buttonStyle: {
    width: 50,
    height: 50,
    borderRadius: 50/2,
    paddingBottom: 10,
    marginRight: 10,
    marginTop: -10,
    paddingTop: 12,
  },
  heading: {
    fontSize: 24,
    marginBottom: 5,
  },
  inputSection: {
    flexDirection: 'column',
    marginBottom: 10,
  },
  inputLabel: {
    fontSize: 18,
    marginBottom: 5,
    paddingBottom: 10,
  },
})

export default class AddReservations extends Component {
  constructor(props: any) {
    super(props);
    this.state = {
      date: new Date(),
      modalOpen: false,
    };
  }

  open() {
    this.refs.modal1.open()
    this.setState({modalOpen: true})
  }

  close() {
    this.refs.modal1.close()
    this.setState({modalOpen: false})
  }

  onDateChange(date: any) {
    this.setState({date})
  }

  onClose() {
    console.log('closed')
  }

  onOpen() {
    console.log('open')
  }

  niceDate(): string {
    return this.state.date.toString().split(' ').splice(0,3).join(' ')
  }

  render() {
    let iconButton = null
    if (!this.state.modalOpen) {
      iconButton = (
        <IconButton
          active={true}
          color={'red'}
          iconName={'clear'}
          iconFamily={'material'}
          buttonStyle={styles.buttonStyle}
          onPress={this.open.bind(this)}
        />
      )
    }
    return (
      <View>
        <View style={styles.container}>
          <Text style={styles.heading}>New reservation</Text>
          <View style={styles.inputSection}>
            <TouchableOpacity
                onPress={this.open.bind(this)}
            >
              <Text style={styles.inputLabel}>Start
                Date:  {this.niceDate()}
              </Text>
            </TouchableOpacity>
            {iconButton}
          </View>
        </View>
        <Modal
          style={styles.modal}
          ref={"modal1"}
          onClosed={this.onClose}
          onOpened={this.onOpen}
        >
          <CalendarPicker
            selectedDate={this.state.date}
            onDateChange={this.onDateChange.bind(this)}
          />
          <IconButton
            active={true}
            color={'green'}
            iconName={'check'}
            iconFamily={'material'}
            buttonStyle={styles.buttonStyle}
            onPress={this.close.bind(this)}
          />
        </Modal>
      </View>
    )
  }
}

AddReservations.displayName = 'AddReservations';
AddReservations.propTypes = {
}
<commit_msg>Working date-pickers<commit_after>/* @flow */

/**
 * main menu
 */

import React, {
  Component,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  View,
} from 'react-native';

import commonStyles from './common-styles';
import IconButton from './IconButton';
import Modal from 'react-native-modalbox';
const CalendarPicker = require('react-native-calendar-picker');

let styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#eaf4be',
  },
  modal: {
    justifyContent: 'center',
    alignItems: 'flex-start',
    height: 400,
    backgroundColor: 'white',
  },
  spacer: {
    paddingBottom: 30,
    height:10,
    width: 1,
  },
  buttonStyle: {
    width: 50,
    height: 50,
    borderRadius: 50/2,
    paddingBottom: 10,
    marginRight: 10,
    marginTop: -10,
    paddingTop: 12,
  },
  heading: {
    fontSize: 24,
    marginBottom: 5,
  },
  inputSection: {
    flexDirection: 'column',
    marginBottom: 10,
  },
  inputLabel: {
    fontSize: 18,
    marginBottom: 5,
    paddingBottom: 10,
  },
})

export default class AddReservations extends Component {
  constructor(props: any) {
    super(props);
    let tomorrow = new Date();
    tomorrow.setDate(tomorrow.getDate() + 1);
    this.state = {
      startDate: new Date(),
      endDate: tomorrow,
      modalOpen: false,
    };
  }

  open(varName: string) {
    this.refs[`${varName}-modal`].open()
    this.setState({modalOpen: true})
  }

  close(varName: string) {
    this.refs[`${varName}-modal`].close()
    this.setState({modalOpen: false})
  }

  onDateChange(varName: string, date: any) {
    this.setState({[varName]: date})
  }

  onClose() {
    console.log('closed')
  }

  onOpen() {
    console.log('open')
  }

  niceDate(varName: string): string {
    return this.state[varName].toString().split(' ').splice(0,3).join(' ')
  }

  getCalendarModal(varName: string) {
    return (
      <Modal
        style={styles.modal}
        ref={`${varName}-modal`}
        onClosed={this.onClose}
        onOpened={this.onOpen}
      >
        <CalendarPicker
          selectedDate={this.state[varName]}
          onDateChange={this.onDateChange.bind(this, varName)}
        />
        <IconButton
          active={true}
          color={'green'}
          iconName={'check'}
          iconFamily={'material'}
          buttonStyle={styles.buttonStyle}
          onPress={this.close.bind(this, varName)}
        />
      </Modal>
    )
  }

  render() {
    let iconButton = null
    if (!this.state.modalOpen) {
      iconButton = (
        <IconButton
          active={true}
          color={'red'}
          iconName={'clear'}
          iconFamily={'material'}
          buttonStyle={styles.buttonStyle}
          onPress={this.open.bind(this)}
        />
      )
    }
    return (
      <View style={styles.container}>
        {this.getCalendarModal('startDate')}
        {this.getCalendarModal('endDate')}
        <Text style={styles.heading}>New reservation</Text>
        <View style={styles.inputSection}>
          <TouchableOpacity onPress={this.open.bind(this, 'startDate')}>
            <Text style={styles.inputLabel}>Start
              Date:  {this.niceDate('startDate')}
            </Text>
          </TouchableOpacity>

          <TouchableOpacity onPress={this.open.bind(this, 'endDate')}>
            <Text style={styles.inputLabel}>End
              Date:  {this.niceDate('endDate')}
            </Text>
          </TouchableOpacity>
          {iconButton}
        </View>
      </View>
    )
  }
}

AddReservations.displayName = 'AddReservations';
AddReservations.propTypes = {
}
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';

import {
  StyleSheet,
  PanResponder,
  View,
  TouchableHighlight,
  Platform,
  ViewPropTypes,
} from 'react-native';

import DefaultMarker from './DefaultMarker';
import { createArray, valueToPosition, positionToValue } from './converters';

export default class MultiSlider extends React.Component {
  static propTypes = {
    values: PropTypes.arrayOf(PropTypes.number),

    onValuesChangeStart: PropTypes.func,
    onValuesChange: PropTypes.func,
    onValuesChangeFinish: PropTypes.func,

    sliderLength: PropTypes.number,
    touchDimensions: PropTypes.object,

    customMarker: PropTypes.func,

    min: PropTypes.number,
    max: PropTypes.number,
    step: PropTypes.number,

    optionsArray: PropTypes.array,

    containerStyle: ViewPropTypes.style,
    trackStyle: ViewPropTypes.style,
    selectedStyle: ViewPropTypes.style,
    unselectedStyle: ViewPropTypes.style,
    markerStyle: ViewPropTypes.style,
    pressedMarkerStyle: ViewPropTypes.style,
    enabledOne: PropTypes.bool,
    enabledTwo: PropTypes.bool,
    onToggleOne: PropTypes.number,
    onToggleTwo: PropTypes.number,
  };

  static defaultProps = {
    values: [0],
    onValuesChangeStart: () => {
    },
    onValuesChange: values => {
    },
    onValuesChangeFinish: values => {
    },
    step: 1,
    min: 0,
    max: 10,
    touchDimensions: {
      height: 50,
      width: 50,
      borderRadius: 15,
      slipDisplacement: 200,
    },
    customMarker: DefaultMarker,
    markerOffsetX: 0,
    markerOffsetY: 0,
    sliderLength: 280,
    onToggleOne: undefined,
    onToggleTwo: undefined,
    enabledOne: true,
    enabledTwo: true,
  };

  constructor(props) {
    super(props);

    this.optionsArray = this.props.optionsArray ||
      createArray(this.props.min, this.props.max, this.props.step);
    this.stepLength = this.props.sliderLength / this.optionsArray.length;

    var initialValues = this.props.values.map(value =>
      valueToPosition(value, this.optionsArray, this.props.sliderLength));

    this.state = {
      pressedOne: true,
      valueOne: this.props.values[0],
      valueTwo: this.props.values[1],
      pastOne: initialValues[0],
      pastTwo: initialValues[1],
      positionOne: initialValues[0],
      positionTwo: initialValues[1],
    };
  }

  componentWillMount() {
    var customPanResponder = (start, move, end) => {
      return PanResponder.create({
        onStartShouldSetPanResponder: (evt, gestureState) => true,
        onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
        onMoveShouldSetPanResponder: (evt, gestureState) => true,
        onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
        onPanResponderGrant: (evt, gestureState) => start(),
        onPanResponderMove: (evt, gestureState) => move(gestureState),
        onPanResponderTerminationRequest: (evt, gestureState) => true,
        onPanResponderRelease: (evt, gestureState) => end(gestureState),
        onPanResponderTerminate: (evt, gestureState) => end(gestureState),
        onShouldBlockNativeResponder: (evt, gestureState) => true,
      });
    };

    this._panResponderOne = customPanResponder(
      this.startOne,
      this.moveOne,
      this.endOne,
    );
    this._panResponderTwo = customPanResponder(
      this.startTwo,
      this.moveTwo,
      this.endTwo,
    );
  }

  componentWillReceiveProps(nextProps) {
    if (this.state.onePressed || this.state.twoPressed) {
      return;
    }

    let position, nextState = {};
    if (
      nextProps.values[0] !== this.state.valueOne ||
      nextProps.sliderLength !== this.props.sliderLength
    ) {
      position = valueToPosition(
        nextProps.values[0],
        this.optionsArray,
        nextProps.sliderLength,
      );
      nextState.valueOne = nextProps.values[0];
      nextState.pastOne = position;
      nextState.positionOne = position;
    }
    if (
      nextProps.values[1] !== this.state.valueTwo ||
      (nextProps.sliderLength !== this.props.sliderLength &&
      nextProps.values[1])
    ) {
      position = valueToPosition(
        nextProps.values[1],
        this.optionsArray,
        nextProps.sliderLength,
      );
      nextState.valueTwo = nextProps.values[1];
      nextState.pastTwo = position;
      nextState.positionTwo = position;
    }

    if (nextState != {}) {
      this.setState(nextState);
    }
  }

  startOne = () => {
    if (this.state.enabledOne) {
      this.props.onValuesChangeStart();
      this.setState({
        onePressed: !this.state.onePressed,
      });
    }
  };

  startTwo = () => {
    if (this.state.enabledTwo) {
      this.props.onValuesChangeStart();
      this.setState({
        twoPressed: !this.state.twoPressed,
      });
    }
  };

  moveOne = gestureState => {
    if (!this.props.enabledOne) {
      return;
    }

    var unconfined = gestureState.dx + this.state.pastOne;
    var bottom = 0;
    var trueTop = this.state.positionTwo - this.stepLength;
    var top = trueTop === 0 ? 0 : trueTop || this.props.sliderLength;
    var confined = unconfined < bottom
      ? bottom
      : unconfined > top ? top : unconfined;
    var value = positionToValue(
      this.state.positionOne,
      this.optionsArray,
      this.props.sliderLength,
    );

    var slipDisplacement = this.props.touchDimensions.slipDisplacement;

    if (Math.abs(gestureState.dy) < slipDisplacement || !slipDisplacement) {
      this.setState({
        positionOne: confined,
      });
    }

    if (value !== this.state.valueOne) {
      this.setState(
        {
          valueOne: value,
        },
        () => {
          var change = [this.state.valueOne];
          if (this.state.valueTwo) {
            change.push(this.state.valueTwo);
          }
          this.props.onValuesChange(change);
        },
      );
    }
  };

  moveTwo = gestureState => {
    if (!this.props.enabledTwo) {
      return;
    }

    var unconfined = gestureState.dx + this.state.pastTwo;
    var bottom = this.state.positionOne + this.stepLength;
    var top = this.props.sliderLength;
    var confined = unconfined < bottom
      ? bottom
      : unconfined > top ? top : unconfined;
    var value = positionToValue(
      this.state.positionTwo,
      this.optionsArray,
      this.props.sliderLength,
    );
    var slipDisplacement = this.props.touchDimensions.slipDisplacement;

    if (Math.abs(gestureState.dy) < slipDisplacement || !slipDisplacement) {
      this.setState({
        positionTwo: confined,
      });
    }
    if (value !== this.state.valueTwo) {
      this.setState(
        {
          valueTwo: value,
        },
        () => {
          this.props.onValuesChange([this.state.valueOne, this.state.valueTwo]);
        },
      );
    }
  };

  endOne = gestureState => {
    if (gestureState.moveX === 0 && this.props.onToggleOne) {
      this.props.onToggleOne();
      return;
    }

    this.setState(
      {
        pastOne: this.state.positionOne,
        onePressed: !this.state.onePressed,
      },
      () => {
        var change = [this.state.valueOne];
        if (this.state.valueTwo) {
          change.push(this.state.valueTwo);
        }
        this.props.onValuesChangeFinish(change);
      },
    );
  };

  endTwo = gestureState => {
    if (gestureState.moveX === 0 && this.props.onToggleTwo) {
      this.props.onToggleTwo();
      return;
    }

    this.setState(
      {
        twoPressed: !this.state.twoPressed,
        pastTwo: this.state.positionTwo,
      },
      () => {
        this.props.onValuesChangeFinish([
          this.state.valueOne,
          this.state.valueTwo,
        ]);
      },
    );
  };

  render() {
    const { positionOne, positionTwo } = this.state;
    const { selectedStyle, unselectedStyle, sliderLength, markerOffsetX, markerOffsetY } = this.props;
    const twoMarkers = positionTwo;

    const trackOneLength = positionOne;
    const trackOneStyle = twoMarkers
      ? unselectedStyle
      : selectedStyle || styles.selectedTrack;
    const trackThreeLength = twoMarkers ? sliderLength - positionTwo : 0;
    const trackThreeStyle = unselectedStyle;
    const trackTwoLength = sliderLength - trackOneLength - trackThreeLength;
    const trackTwoStyle = twoMarkers
      ? selectedStyle || styles.selectedTrack
      : unselectedStyle;
    const Marker = this.props.customMarker;
    const {
      slipDisplacement,
      height,
      width,
      borderRadius,
    } = this.props.touchDimensions;
    const touchStyle = {
      borderRadius: borderRadius || 0,
    };

    const markerContainerOne = { top: markerOffsetY - 24, left : trackOneLength + markerOffsetX - 24 }

    const markerContainerTwo = { top: markerOffsetY - 24, right: trackThreeLength + markerOffsetX - 24 };

    return (
      <View style={[styles.container, this.props.containerStyle]}>
        <View style={[styles.fullTrack, { width: sliderLength }]}>
          <View
            style={[
              styles.track,
              this.props.trackStyle,
              trackOneStyle,
              { width: trackOneLength },
            ]}
          />
          <View
            style={[
              styles.track,
              this.props.trackStyle,
              trackTwoStyle,
              { width: trackTwoLength },
            ]}
          />
          {twoMarkers &&
          <View
            style={[
              styles.track,
              this.props.trackStyle,
              trackThreeStyle,
              { width: trackThreeLength },
            ]}
          />}
          <View
            style={[
              styles.markerContainer,
              markerContainerOne,
              positionOne > sliderLength / 2 && styles.topMarkerContainer,
            ]}
          >
            <View
              style={[styles.touch, touchStyle]}
              ref={component => this._markerOne = component}
              {...this._panResponderOne.panHandlers}
            >
              <Marker
                enabled={this.props.enabledOne}
                pressed={this.state.onePressed}
                markerStyle={[styles.marker, this.props.markerStyle]}
                pressedMarkerStyle={this.props.pressedMarkerStyle}
                currentValue={this.state.valueOne}
              />
            </View>
          </View>
          {twoMarkers &&
          positionOne !== this.props.sliderLength &&
          <View style={[styles.markerContainer, markerContainerTwo]}>
            <View
              style={[styles.touch, touchStyle]}
              ref={component => this._markerTwo = component}
              {...this._panResponderTwo.panHandlers}
            >
              <Marker
                pressed={this.state.twoPressed}
                markerStyle={this.props.markerStyle}
                pressedMarkerStyle={this.props.pressedMarkerStyle}
                currentValue={this.state.valueTwo}
                enabled={this.props.enabledTwo}
              />
            </View>
          </View>}
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    position: 'relative',
    height: 50,
  },
  fullTrack: {
    flexDirection: 'row',
  },
  track: {
    ...Platform.select({
      ios: {
        height: 2,
        borderRadius: 2,
        backgroundColor: '#A7A7A7',
      },
      android: {
        height: 2,
        backgroundColor: '#CECECE',
      },
    }),
  },
  selectedTrack: {
    ...Platform.select({
      ios: {
        backgroundColor: '#095FFF',
      },
      android: {
        backgroundColor: '#0D8675',
      },
    }),
  },
  markerContainer: {
    position: 'absolute',
    width: 48,
    height: 48,
    backgroundColor: 'transparent',
    justifyContent: 'center',
    alignItems: 'center',
  },
  topMarkerContainer: {
    zIndex: 1,
  },
  touch: {
    backgroundColor: 'transparent',
    justifyContent: 'center',
    alignItems: 'center',
    alignSelf: 'stretch',
  },
});
<commit_msg>Fixed onToggle PropTypes accidentally modified.<commit_after>import React, { PropTypes } from 'react';

import {
  StyleSheet,
  PanResponder,
  View,
  TouchableHighlight,
  Platform,
  ViewPropTypes,
} from 'react-native';

import DefaultMarker from './DefaultMarker';
import { createArray, valueToPosition, positionToValue } from './converters';

export default class MultiSlider extends React.Component {
  static propTypes = {
    values: PropTypes.arrayOf(PropTypes.number),

    onValuesChangeStart: PropTypes.func,
    onValuesChange: PropTypes.func,
    onValuesChangeFinish: PropTypes.func,

    sliderLength: PropTypes.number,
    touchDimensions: PropTypes.object,

    customMarker: PropTypes.func,

    min: PropTypes.number,
    max: PropTypes.number,
    step: PropTypes.number,

    optionsArray: PropTypes.array,

    containerStyle: ViewPropTypes.style,
    trackStyle: ViewPropTypes.style,
    selectedStyle: ViewPropTypes.style,
    unselectedStyle: ViewPropTypes.style,
    markerStyle: ViewPropTypes.style,
    pressedMarkerStyle: ViewPropTypes.style,
    enabledOne: PropTypes.bool,
    enabledTwo: PropTypes.bool,
    onToggleOne: PropTypes.func,
    onToggleTwo: PropTypes.func,
  };

  static defaultProps = {
    values: [0],
    onValuesChangeStart: () => {
    },
    onValuesChange: values => {
    },
    onValuesChangeFinish: values => {
    },
    step: 1,
    min: 0,
    max: 10,
    touchDimensions: {
      height: 50,
      width: 50,
      borderRadius: 15,
      slipDisplacement: 200,
    },
    customMarker: DefaultMarker,
    markerOffsetX: 0,
    markerOffsetY: 0,
    sliderLength: 280,
    onToggleOne: undefined,
    onToggleTwo: undefined,
    enabledOne: true,
    enabledTwo: true,
  };

  constructor(props) {
    super(props);

    this.optionsArray = this.props.optionsArray ||
      createArray(this.props.min, this.props.max, this.props.step);
    this.stepLength = this.props.sliderLength / this.optionsArray.length;

    var initialValues = this.props.values.map(value =>
      valueToPosition(value, this.optionsArray, this.props.sliderLength));

    this.state = {
      pressedOne: true,
      valueOne: this.props.values[0],
      valueTwo: this.props.values[1],
      pastOne: initialValues[0],
      pastTwo: initialValues[1],
      positionOne: initialValues[0],
      positionTwo: initialValues[1],
    };
  }

  componentWillMount() {
    var customPanResponder = (start, move, end) => {
      return PanResponder.create({
        onStartShouldSetPanResponder: (evt, gestureState) => true,
        onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
        onMoveShouldSetPanResponder: (evt, gestureState) => true,
        onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
        onPanResponderGrant: (evt, gestureState) => start(),
        onPanResponderMove: (evt, gestureState) => move(gestureState),
        onPanResponderTerminationRequest: (evt, gestureState) => true,
        onPanResponderRelease: (evt, gestureState) => end(gestureState),
        onPanResponderTerminate: (evt, gestureState) => end(gestureState),
        onShouldBlockNativeResponder: (evt, gestureState) => true,
      });
    };

    this._panResponderOne = customPanResponder(
      this.startOne,
      this.moveOne,
      this.endOne,
    );
    this._panResponderTwo = customPanResponder(
      this.startTwo,
      this.moveTwo,
      this.endTwo,
    );
  }

  componentWillReceiveProps(nextProps) {
    if (this.state.onePressed || this.state.twoPressed) {
      return;
    }

    let position, nextState = {};
    if (
      nextProps.values[0] !== this.state.valueOne ||
      nextProps.sliderLength !== this.props.sliderLength
    ) {
      position = valueToPosition(
        nextProps.values[0],
        this.optionsArray,
        nextProps.sliderLength,
      );
      nextState.valueOne = nextProps.values[0];
      nextState.pastOne = position;
      nextState.positionOne = position;
    }
    if (
      nextProps.values[1] !== this.state.valueTwo ||
      (nextProps.sliderLength !== this.props.sliderLength &&
      nextProps.values[1])
    ) {
      position = valueToPosition(
        nextProps.values[1],
        this.optionsArray,
        nextProps.sliderLength,
      );
      nextState.valueTwo = nextProps.values[1];
      nextState.pastTwo = position;
      nextState.positionTwo = position;
    }

    if (nextState != {}) {
      this.setState(nextState);
    }
  }

  startOne = () => {
    if (this.state.enabledOne) {
      this.props.onValuesChangeStart();
      this.setState({
        onePressed: !this.state.onePressed,
      });
    }
  };

  startTwo = () => {
    if (this.state.enabledTwo) {
      this.props.onValuesChangeStart();
      this.setState({
        twoPressed: !this.state.twoPressed,
      });
    }
  };

  moveOne = gestureState => {
    if (!this.props.enabledOne) {
      return;
    }

    var unconfined = gestureState.dx + this.state.pastOne;
    var bottom = 0;
    var trueTop = this.state.positionTwo - this.stepLength;
    var top = trueTop === 0 ? 0 : trueTop || this.props.sliderLength;
    var confined = unconfined < bottom
      ? bottom
      : unconfined > top ? top : unconfined;
    var value = positionToValue(
      this.state.positionOne,
      this.optionsArray,
      this.props.sliderLength,
    );

    var slipDisplacement = this.props.touchDimensions.slipDisplacement;

    if (Math.abs(gestureState.dy) < slipDisplacement || !slipDisplacement) {
      this.setState({
        positionOne: confined,
      });
    }

    if (value !== this.state.valueOne) {
      this.setState(
        {
          valueOne: value,
        },
        () => {
          var change = [this.state.valueOne];
          if (this.state.valueTwo) {
            change.push(this.state.valueTwo);
          }
          this.props.onValuesChange(change);
        },
      );
    }
  };

  moveTwo = gestureState => {
    if (!this.props.enabledTwo) {
      return;
    }

    var unconfined = gestureState.dx + this.state.pastTwo;
    var bottom = this.state.positionOne + this.stepLength;
    var top = this.props.sliderLength;
    var confined = unconfined < bottom
      ? bottom
      : unconfined > top ? top : unconfined;
    var value = positionToValue(
      this.state.positionTwo,
      this.optionsArray,
      this.props.sliderLength,
    );
    var slipDisplacement = this.props.touchDimensions.slipDisplacement;

    if (Math.abs(gestureState.dy) < slipDisplacement || !slipDisplacement) {
      this.setState({
        positionTwo: confined,
      });
    }
    if (value !== this.state.valueTwo) {
      this.setState(
        {
          valueTwo: value,
        },
        () => {
          this.props.onValuesChange([this.state.valueOne, this.state.valueTwo]);
        },
      );
    }
  };

  endOne = gestureState => {
    if (gestureState.moveX === 0 && this.props.onToggleOne) {
      this.props.onToggleOne();
      return;
    }

    this.setState(
      {
        pastOne: this.state.positionOne,
        onePressed: !this.state.onePressed,
      },
      () => {
        var change = [this.state.valueOne];
        if (this.state.valueTwo) {
          change.push(this.state.valueTwo);
        }
        this.props.onValuesChangeFinish(change);
      },
    );
  };

  endTwo = gestureState => {
    if (gestureState.moveX === 0 && this.props.onToggleTwo) {
      this.props.onToggleTwo();
      return;
    }

    this.setState(
      {
        twoPressed: !this.state.twoPressed,
        pastTwo: this.state.positionTwo,
      },
      () => {
        this.props.onValuesChangeFinish([
          this.state.valueOne,
          this.state.valueTwo,
        ]);
      },
    );
  };

  render() {
    const { positionOne, positionTwo } = this.state;
    const { selectedStyle, unselectedStyle, sliderLength, markerOffsetX, markerOffsetY } = this.props;
    const twoMarkers = positionTwo;

    const trackOneLength = positionOne;
    const trackOneStyle = twoMarkers
      ? unselectedStyle
      : selectedStyle || styles.selectedTrack;
    const trackThreeLength = twoMarkers ? sliderLength - positionTwo : 0;
    const trackThreeStyle = unselectedStyle;
    const trackTwoLength = sliderLength - trackOneLength - trackThreeLength;
    const trackTwoStyle = twoMarkers
      ? selectedStyle || styles.selectedTrack
      : unselectedStyle;
    const Marker = this.props.customMarker;
    const {
      slipDisplacement,
      height,
      width,
      borderRadius,
    } = this.props.touchDimensions;
    const touchStyle = {
      borderRadius: borderRadius || 0,
    };

    const markerContainerOne = { top: markerOffsetY - 24, left : trackOneLength + markerOffsetX - 24 }

    const markerContainerTwo = { top: markerOffsetY - 24, right: trackThreeLength + markerOffsetX - 24 };

    return (
      <View style={[styles.container, this.props.containerStyle]}>
        <View style={[styles.fullTrack, { width: sliderLength }]}>
          <View
            style={[
              styles.track,
              this.props.trackStyle,
              trackOneStyle,
              { width: trackOneLength },
            ]}
          />
          <View
            style={[
              styles.track,
              this.props.trackStyle,
              trackTwoStyle,
              { width: trackTwoLength },
            ]}
          />
          {twoMarkers &&
          <View
            style={[
              styles.track,
              this.props.trackStyle,
              trackThreeStyle,
              { width: trackThreeLength },
            ]}
          />}
          <View
            style={[
              styles.markerContainer,
              markerContainerOne,
              positionOne > sliderLength / 2 && styles.topMarkerContainer,
            ]}
          >
            <View
              style={[styles.touch, touchStyle]}
              ref={component => this._markerOne = component}
              {...this._panResponderOne.panHandlers}
            >
              <Marker
                enabled={this.props.enabledOne}
                pressed={this.state.onePressed}
                markerStyle={[styles.marker, this.props.markerStyle]}
                pressedMarkerStyle={this.props.pressedMarkerStyle}
                currentValue={this.state.valueOne}
              />
            </View>
          </View>
          {twoMarkers &&
          positionOne !== this.props.sliderLength &&
          <View style={[styles.markerContainer, markerContainerTwo]}>
            <View
              style={[styles.touch, touchStyle]}
              ref={component => this._markerTwo = component}
              {...this._panResponderTwo.panHandlers}
            >
              <Marker
                pressed={this.state.twoPressed}
                markerStyle={this.props.markerStyle}
                pressedMarkerStyle={this.props.pressedMarkerStyle}
                currentValue={this.state.valueTwo}
                enabled={this.props.enabledTwo}
              />
            </View>
          </View>}
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    position: 'relative',
    height: 50,
  },
  fullTrack: {
    flexDirection: 'row',
  },
  track: {
    ...Platform.select({
      ios: {
        height: 2,
        borderRadius: 2,
        backgroundColor: '#A7A7A7',
      },
      android: {
        height: 2,
        backgroundColor: '#CECECE',
      },
    }),
  },
  selectedTrack: {
    ...Platform.select({
      ios: {
        backgroundColor: '#095FFF',
      },
      android: {
        backgroundColor: '#0D8675',
      },
    }),
  },
  markerContainer: {
    position: 'absolute',
    width: 48,
    height: 48,
    backgroundColor: 'transparent',
    justifyContent: 'center',
    alignItems: 'center',
  },
  topMarkerContainer: {
    zIndex: 1,
  },
  touch: {
    backgroundColor: 'transparent',
    justifyContent: 'center',
    alignItems: 'center',
    alignSelf: 'stretch',
  },
});
<|endoftext|>