<commit_before>L.Util.extend(L.LineUtil, {

	/**
	 * Snap to all layers
	 *
	 * @param <Latlng> latlng - original position
	 * @param <Number> id - leaflet unique id
	 * @param <Object> opts - snapping options
	 *
	 * @return <Latlng> closest point
	*/
	snapToLayers: function (latlng, id, opts) {
		var i, j, keys, feature, res, sensitivity, vertexonly, layers, minDist, minPoint, map;

		sensitivity = opts.sensitivity || 10;
		vertexonly = opts.vertexonly || false;
		layers = opts.layers || [];
		minDist = Infinity;
		minPoint = latlng;
		minPoint._feature = null; // containing layer
		map = opts.layers[0]._map; // @todo check for undef

		for (i = 0; i < opts.layers.length; i++) {
			keys = Object.keys(opts.layers[i]._layers);
			for (j = 0; j < keys.length; j++) {
				feature = opts.layers[i]._layers[keys[j]];

				// Don't even try snapping to itself!
				if (id === feature._leaflet_id) { continue; }

				// GeometryCollection
				if (feature._layers) {
					var newLatlng = this.snapToLayers(latlng, id, {
						'sensitivity': sensitivity,
						'vertexonly': vertexonly,
						'layers': [feature]
					});
					// What if this is the same?
					res = {'minDist': latlng.distanceTo(newLatlng), 'minPoint': newLatlng};

				// Marker
        } else if (feature instanceof L.Marker) {
					res = this._snapToLatlngs(latlng, [feature.getLatLng()], map, sensitivity, vertexonly, minDist);

				// Polyline
				} else if (feature instanceof L.Polyline) {
					res = this._snapToLatlngs(latlng, feature.getLatLngs(), map, sensitivity, vertexonly, minDist);

				// MultiPolyline
				} else if (feature instanceof L.MultiPolyline) {
					console.error('Snapping to MultiPolyline is currently unsupported', feature);
					res = {'minDist': minDist, 'minPoint': minPoint};

				// Polygon
				} else if (feature instanceof L.Polygon) {
					res = this._snapToPolygon(latlng, feature, map, sensitivity, vertexonly, minDist);

				// MultiPolygon
				} else if (feature instanceof L.MultiPolygon) {
					res = this._snapToMultiPolygon(latlng, feature, map, sensitivity, vertexonly, minDist);

				// Unknown
				} else {
					console.error('Unsupported snapping feature', feature);
					res = {'minDist': minDist, 'minPoint': minPoint};
				}

				if (res.minDist < minDist) {
					minDist = res.minDist;
					minPoint = res.minPoint;
					minPoint._feature = feature;
				}

			}
		}

		return minPoint;
	},

	/**
	 * Snap to Polygon
	 *
	 * @param <Latlng> latlng - original position
	 * @param <L.Polygon> feature -
	 * @param <L.Map> map -
	 * @param <Number> sensitivity -
	 * @param <Boolean> vertexonly -
	 * @param <Number> minDist -
	 *
	 * @return <Object> minDist and minPoint
	*/
	_snapToPolygon: function (latlng, polygon, map, sensitivity, vertexonly, minDist) {
		var res, keys, latlngs, i, minPoint;

		minPoint = null;

		latlngs = polygon.getLatLngs();
		latlngs.push(latlngs[0]);
		res = this._snapToLatlngs(latlng, polygon.getLatLngs(), map, sensitivity, vertexonly, minDist);
		if (res.minDist < minDist) {
			minDist = res.minDist;
			minPoint = res.minPoint;
		}

		keys = Object.keys(polygon._holes);
		for (i = 0; i < keys.length; i++) {
			latlngs = polygon._holes[keys[i]];
			latlngs.push(latlngs[0]);
			res = this._snapToLatlngs(latlng, polygon._holes[keys[i]], map, sensitivity, vertexonly, minDist);
			if (res.minDist < minDist) {
				minDist = res.minDist;
				minPoint = res.minPoint;
			}
		}

		return {'minDist': minDist, 'minPoint': minPoint};
	},

	/**
	 * Snap to MultiPolygon
	 *
	 * @param <Latlng> latlng - original position
	 * @param <L.Polygon> feature -
	 * @param <L.Map> map -
	 * @param <Number> sensitivity -
	 * @param <Boolean> vertexonly -
	 * @param <Number> minDist -
	 *
	 * @return <Object> minDist and minPoint
	*/
	_snapToMultiPolygon: function (latlng, multipolygon, map, sensitivity, vertexonly, minDist) {
		var i, keys, res, minPoint;

		minPoint = null;

		keys = Object.keys(multipolygon._layers);
		for (i = 0; i < keys.length; i++) {
			res = this._snapToPolygon(latlng, multipolygon._layers[keys[i]], map, sensitivity, vertexonly, minDist);

			if (res.minDist < minDist) {
				minDist = res.minDist;
				minPoint = res.minPoint;
			}
		}

		return {'minDist': minDist, 'minPoint': minPoint};
	},

	/**
	 * Snap to <Array> of <Latlang>
	 *
	 * @param <LatLng> latlng - cursor click
	 * @param <Array> latlngs - array of <L.LatLngs> to snap to
	 * @param <Object> opts - snapping options
	 * @param <Boolean> isPolygon - if feature is a polygon
	 *
	 * @return <Object> minDist and minPoint
	*/
	_snapToLatlngs: function (latlng, latlngs, map, sensitivity, vertexonly, minDist) {
		var i, tmpDist, minPoint, p, p1, p2, d2;

		p = map.latLngToLayerPoint(latlng);
		p1 = minPoint = null;

		for (i = 0; i < latlngs.length; i++) {
			p2 = map.latLngToLayerPoint(latlngs[i]);

			if (!vertexonly && p1 !== null) {
				tmpDist = L.LineUtil.pointToSegmentDistance(p, p1, p2);
				if (tmpDist < minDist && tmpDist <= sensitivity) {
					minDist = tmpDist;
					minPoint = map.layerPointToLatLng(L.LineUtil.closestPointOnSegment(p, p1, p2));
				}
			} else if ((d2 = p.distanceTo(p2)) && d2 <= sensitivity && d2 < minDist) {
				minDist = d2;
				minPoint = latlngs[i];
			}

			p1 = p2;
		}

		return {'minDist': minDist, 'minPoint': minPoint};
	}

});
<commit_msg>Fix #15 - Undefined error for no snapping<commit_after>L.Util.extend(L.LineUtil, {

	/**
	 * Snap to all layers
	 *
	 * @param <Latlng> latlng - original position
	 * @param <Number> id - leaflet unique id
	 * @param <Object> opts - snapping options
	 *
	 * @return <Latlng> closest point
	*/
	snapToLayers: function (latlng, id, opts) {
		var i, j, keys, feature, res, sensitivity, vertexonly, layers, minDist, minPoint, map;

		sensitivity = opts.sensitivity || 10;
		vertexonly = opts.vertexonly || false;
		layers = opts.layers || [];
		minDist = Infinity;
		minPoint = latlng;
		minPoint._feature = null; // containing layer

    if (!opts || !opts.layers || !opts.layers.length) {
      return minPoint;
    }

		map = opts.layers[0]._map; // @todo check for undef

		for (i = 0; i < opts.layers.length; i++) {
			keys = Object.keys(opts.layers[i]._layers);
			for (j = 0; j < keys.length; j++) {
				feature = opts.layers[i]._layers[keys[j]];

				// Don't even try snapping to itself!
				if (id === feature._leaflet_id) { continue; }

				// GeometryCollection
				if (feature._layers) {
					var newLatlng = this.snapToLayers(latlng, id, {
						'sensitivity': sensitivity,
						'vertexonly': vertexonly,
						'layers': [feature]
					});
					// What if this is the same?
					res = {'minDist': latlng.distanceTo(newLatlng), 'minPoint': newLatlng};

				// Marker
        } else if (feature instanceof L.Marker) {
					res = this._snapToLatlngs(latlng, [feature.getLatLng()], map, sensitivity, vertexonly, minDist);

				// Polyline
				} else if (feature instanceof L.Polyline) {
					res = this._snapToLatlngs(latlng, feature.getLatLngs(), map, sensitivity, vertexonly, minDist);

				// MultiPolyline
				} else if (feature instanceof L.MultiPolyline) {
					console.error('Snapping to MultiPolyline is currently unsupported', feature);
					res = {'minDist': minDist, 'minPoint': minPoint};

				// Polygon
				} else if (feature instanceof L.Polygon) {
					res = this._snapToPolygon(latlng, feature, map, sensitivity, vertexonly, minDist);

				// MultiPolygon
				} else if (feature instanceof L.MultiPolygon) {
					res = this._snapToMultiPolygon(latlng, feature, map, sensitivity, vertexonly, minDist);

				// Unknown
				} else {
					console.error('Unsupported snapping feature', feature);
					res = {'minDist': minDist, 'minPoint': minPoint};
				}

				if (res.minDist < minDist) {
					minDist = res.minDist;
					minPoint = res.minPoint;
					minPoint._feature = feature;
				}

			}
		}

		return minPoint;
	},

	/**
	 * Snap to Polygon
	 *
	 * @param <Latlng> latlng - original position
	 * @param <L.Polygon> feature -
	 * @param <L.Map> map -
	 * @param <Number> sensitivity -
	 * @param <Boolean> vertexonly -
	 * @param <Number> minDist -
	 *
	 * @return <Object> minDist and minPoint
	*/
	_snapToPolygon: function (latlng, polygon, map, sensitivity, vertexonly, minDist) {
		var res, keys, latlngs, i, minPoint;

		minPoint = null;

		latlngs = polygon.getLatLngs();
		latlngs.push(latlngs[0]);
		res = this._snapToLatlngs(latlng, polygon.getLatLngs(), map, sensitivity, vertexonly, minDist);
		if (res.minDist < minDist) {
			minDist = res.minDist;
			minPoint = res.minPoint;
		}

		keys = Object.keys(polygon._holes);
		for (i = 0; i < keys.length; i++) {
			latlngs = polygon._holes[keys[i]];
			latlngs.push(latlngs[0]);
			res = this._snapToLatlngs(latlng, polygon._holes[keys[i]], map, sensitivity, vertexonly, minDist);
			if (res.minDist < minDist) {
				minDist = res.minDist;
				minPoint = res.minPoint;
			}
		}

		return {'minDist': minDist, 'minPoint': minPoint};
	},

	/**
	 * Snap to MultiPolygon
	 *
	 * @param <Latlng> latlng - original position
	 * @param <L.Polygon> feature -
	 * @param <L.Map> map -
	 * @param <Number> sensitivity -
	 * @param <Boolean> vertexonly -
	 * @param <Number> minDist -
	 *
	 * @return <Object> minDist and minPoint
	*/
	_snapToMultiPolygon: function (latlng, multipolygon, map, sensitivity, vertexonly, minDist) {
		var i, keys, res, minPoint;

		minPoint = null;

		keys = Object.keys(multipolygon._layers);
		for (i = 0; i < keys.length; i++) {
			res = this._snapToPolygon(latlng, multipolygon._layers[keys[i]], map, sensitivity, vertexonly, minDist);

			if (res.minDist < minDist) {
				minDist = res.minDist;
				minPoint = res.minPoint;
			}
		}

		return {'minDist': minDist, 'minPoint': minPoint};
	},

	/**
	 * Snap to <Array> of <Latlang>
	 *
	 * @param <LatLng> latlng - cursor click
	 * @param <Array> latlngs - array of <L.LatLngs> to snap to
	 * @param <Object> opts - snapping options
	 * @param <Boolean> isPolygon - if feature is a polygon
	 *
	 * @return <Object> minDist and minPoint
	*/
	_snapToLatlngs: function (latlng, latlngs, map, sensitivity, vertexonly, minDist) {
		var i, tmpDist, minPoint, p, p1, p2, d2;

		p = map.latLngToLayerPoint(latlng);
		p1 = minPoint = null;

		for (i = 0; i < latlngs.length; i++) {
			p2 = map.latLngToLayerPoint(latlngs[i]);

			if (!vertexonly && p1 !== null) {
				tmpDist = L.LineUtil.pointToSegmentDistance(p, p1, p2);
				if (tmpDist < minDist && tmpDist <= sensitivity) {
					minDist = tmpDist;
					minPoint = map.layerPointToLatLng(L.LineUtil.closestPointOnSegment(p, p1, p2));
				}
			} else if ((d2 = p.distanceTo(p2)) && d2 <= sensitivity && d2 < minDist) {
				minDist = d2;
				minPoint = latlngs[i];
			}

			p1 = p2;
		}

		return {'minDist': minDist, 'minPoint': minPoint};
	}

});
<|endoftext|>


<commit_before>/// <reference path="../../../../../argos-sdk/libraries/ext/ext-core-debug.js"/>
/// <reference path="../../../../../argos-sdk/libraries/sdata/sdata-client-debug"/>
/// <reference path="../../../../../argos-sdk/libraries/Simplate.js"/>
/// <reference path="../../../../../argos-sdk/src/View.js"/>
/// <reference path="../../../../../argos-sdk/src/Edit.js"/>
/// <reference path="../../Format.js"/>

Ext.namespace("Mobile.SalesLogix.Activity");

(function() {
    Mobile.SalesLogix.Activity.Complete = Ext.extend(Sage.Platform.Mobile.Edit, {
        //Localization
        activityInfoText: 'Activity Info',
        accountText: 'account',
        contactText: 'contact',
        opportunityText: 'opportunity',
        ticketNumberText: 'ticket',
        companyText: 'company',
        leadText: 'lead',
        asScheduledText: 'as scheduled',
        categoryText: 'category',
        categoryTitleText: 'Activity Category',
        completedText: 'completed date',
        completionText: 'Completion',
        durationText: 'duration',
        carryOverNotesText: 'carry over notes',
        followUpText: 'follow-up',
        followUpTitleText: 'Activity Follow Up',
        leaderText: 'leader',
        longNotesText: 'notes',
        longNotesTitleText: 'Notes',
        otherInfoText: 'Other Info',
        priorityText: 'priority',
        priorityTitleText: 'Priority',
        regardingText: 'regarding',
        regardingTitleText: 'Activity Regarding',
        resultText: 'result',
        resultTitleText: 'Result',
        startingText: 'start date',
        timelessText: 'timeless',
        durationValueText: {
            0: 'none',
            15: '15 minutes',
            30: '30 minutes',
            60: '1 hour',
            90: '1.5 hours',
            120: '2 hours'
        },
        followupValueText: {
            'none': 'None',
            'atPhoneCall': 'Phone Call',
            'atAppointment': 'Meeting',
            'atToDo': 'To-Do'
        },

        //View Properties
        id: 'activity_complete',
        followupView: 'activity_edit',
        fieldsForLeads: ['AccountName', 'Lead'],
        fieldsForStandard: ['Account', 'Contact', 'Opportunity', 'Ticket'],
        picklistsByType: {
            'atAppointment': {
                'Category': 'Meeting Category Codes',
                'Description': 'Meeting Regarding',
                'Result': 'Meeting Result Codes'
            },
            'atLiterature': {
                'Description': 'Lit Request Regarding'
            },
            'atPersonal': {
                'Category': 'Meeting Category Codes',
                'Description': 'Personal Activity Regarding',
                'Result': 'Personal Activity Result Codes'
            },
            'atPhoneCall': {
                'Category': 'Phone Call Category Codes',
                'Description': 'Phone Call Regarding',
                'Result': 'Phone Call Result Codes'
            },
            'atToDo': {
                'Category': 'To Do Category Codes',
                'Description': 'To Do Regarding',
                'Result': 'To Do Result Codes'
            },
            'atEMail': {
                'Category': 'E-mail Category Codes',
                'Description': 'E-mail Regarding'
            }
        },

        entityName: 'Activity', // todo: is this correct?
        querySelect: [
            'AccountId',
            'AccountName',
            'Alarm',
            'AlarmTime',
            'Category',
            'ContactId',
            'ContactName',
            'CompletedDate',
            'Description',
            'Duration',
            'LeadId',
            'LeadName',
            'LongNotes',
            'OpportunityId',
            'OpportunityName',
            'Priority',
            'Regarding',
            'Result',
            'Rollover',
            'StartDate',
            'TicketId',
            'TicketNumber',
            'Timeless',
            'Type',
            'UserId'
        ],
        resourceKind: 'activities',

        init: function() {
            Mobile.SalesLogix.Activity.Complete.superclass.init.apply(this, arguments);

            this.fields['Leader'].on('change', this.onLeaderChange, this);
            this.fields['Timeless'].on('change', this.onTimelessChange, this);
            this.fields['AsScheduled'].on('change', this.onAsScheduledChange, this);
            this.fields['Followup'].on('change', this.onFollowupChange, this);
            this.fields['Lead'].on('change', this.onLeadChange, this);
        },
        isActivityForLead: function(entry) {
            return entry && /^[\w]{12}$/.test(entry['LeadId']);
        },
        beforeTransitionTo: function() {
            Mobile.SalesLogix.Activity.Complete.superclass.beforeTransitionTo.apply(this, arguments);

            Ext.each(this.fieldsForStandard.concat(this.fieldsForLeads), function(item) {
                if (this.fields[item])
                    this.fields[item].hide();
            }, this);

            var entry = this.options && this.options.entry;
            if (this.isActivityForLead(entry))
            {
                Ext.each(this.fieldsForLeads, function(item) {
                    if (this.fields[item])
                        this.fields[item].show();
                }, this);
            }
            else
            {
                Ext.each(this.fieldsForStandard, function(item) {
                    if (this.fields[item])
                        this.fields[item].show();
                }, this);
            }
        },
        toggleSelectField: function(field, disable, options) {
            disable === true ? field.disable() : field.enable();
        },
        onTimelessChange: function(value, field) {
            this.toggleSelectField(this.fields['Duration'], value);
        },
        onAsScheduledChange: function(scheduled, field) {
            if (scheduled)
            {
                this.toggleSelectField(this.fields['CompletedDate'], true);
                this.fields['CompletedDate'].setValue(this.fields['StartDate'].getValue());
            }
            else
            {
                this.toggleSelectField(this.fields['CompletedDate'], false);
                this.fields['CompletedDate'].setValue(new Date());
            }
        },
        onFollowupChange: function(value, field) {
            var disable = (value === 'none' || (value && value.key === 'none'));
            this.toggleSelectField(this.fields['CarryOverNotes'], disable);
        },
        onLeadChange: function(value, field) {
            var selection = field.getSelection(),
                getV = Sage.Platform.Mobile.Utility.getValue;

            if (selection && this.insert)
            {
                this.fields['Company'].setValue(getV(selection, 'Company'));
            }
        },
        formatPicklistForType: function(type, which) {
            return this.picklistsByType[type] && this.picklistsByType[type][which];
        },
        setValues: function(values) {
            Mobile.SalesLogix.Activity.Complete.superclass.setValues.apply(this, arguments);
            this.fields['CarryOverNotes'].setValue(true);
            this.fields['CompletedDate'].setValue(new Date());
            this.fields['Followup'].setValue('none');
            this.fields['Result'].setValue('Complete');

            this.toggleSelectField(this.fields['CarryOverNotes'], true);
            this.toggleSelectField(this.fields['CompletedDate'], false);
        },
        onLeaderChange: function(value, field) {
            this.fields['UserId'].setValue(value && value['key']);
        },
        formatDurationText: function(val, key, text) {
            return this.durationValueText[key] || text;
        },
        formatFollowupText: function(val, key, text) {
            return this.followupValueText[key] || text;
        },
        createDurationData: function() {
            var list = [];

            for (var duration in this.durationValueText)
            {
                list.push({
                    '$key': duration,
                    '$descriptor': this.durationValueText[duration]
                });
            }

            return {'$resources': list};
        },
        createFollowupData: function() {
            var list = [];

            for (var followup in this.followupValueText)
            {
                list.push({
                    '$key': followup,
                    '$descriptor': this.followupValueText[followup]
                });
            }

            return {'$resources': list};
        },
        navigateToFollowUpView: function(entry) {
            var view = App.getView(this.followupView),
                followupEntry = {
                    'Type': this.fields['Followup'].getValue(),
                    'Description': entry.Description,
                    'AccountId': entry.AccountId,
                    'AccountName': entry.AccountName,
                    'ContactId': entry.ContactId,
                    'ContactName': entry.ContactName,
                    'LeadId': entry.LeadId,
                    'LeadName': entry.LeadName,
                    'LongNotes': (this.fields['CarryOverNotes'].getValue() && entry['LongNotes']) || '',
                    'OpportunityId': entry.OpportunityId,
                    'OpportunityName': entry.OpportunityName,
                    'TicketId': entry.TicketId,
                    'TicketNumber': entry.TicketNumber
                };

            //Return to activity list view after follow up.
            view.show({
                entry: followupEntry,
                insert: true,
                title: this.followupValueText[this.fields['Followup'].getValue()]
            }, {
                returnTo: -1
            });
        },
        completeActivity: function(entry, callback) {
            var completeActivityentry = {
                "$name": "ActivityComplete",
                "request": {
                    "ActivityId": entry.$key,
                    "userId": entry.UserId,
                    "result": this.fields['Result'].getValue(),
                    "completeDate":  this.fields['CompletedDate'].getValue()
                }
            };

            var successFn = (function(scope, callback, entry) {
                return function() {
                    callback.apply(scope, [entry]);
                };
            })(this, callback, entry);

            var request = new Sage.SData.Client.SDataServiceOperationRequest(this.getService())
                .setResourceKind('activities')
                .setOperationName('Complete');

            request.execute(completeActivityentry, {
                success: successFn,
                failure: function() {},
                scope: this
            });
        },
        updateCompleted: function(entry) {
            var followup = this.fields['Followup'].getValue() !== 'none'
                    ? this.navigateToFollowUpView
                    : Mobile.SalesLogix.Activity.Complete.superclass.updateCompleted;

            this.completeActivity(entry, followup);
        },
        formatDependentQuery: function(dependentValue, format, property) {
            var getV = Sage.Platform.Mobile.Utility.getValue;

            property = property || '$key';

            return String.format(format, getV(dependentValue, property));
        },
        createLayout: function() {
            return this.layout || (this.layout = [{
                options: {
                    title: this.activityInfoText,
                    collapsed: false
                },
                as: [{
                    name: 'Type',
                    type: 'hidden'
                },{
                    dependsOn: 'Type',
                    label: this.regardingText,
                    name: 'Description',
                    picklist: this.formatPicklistForType.createDelegate(
                        this, ['Description'], true
                    ),
                    title: this.regardingTitleText,
                    orderBy: 'text asc',
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                },{
                    label: this.startingText,
                    name: 'StartDate',
                    type: 'date',
                    showTimePicker: true,
                    formatString: 'M/d/yyyy h:mm tt',
                    minValue: (new Date(1900, 0, 1)),
                    validator: [
                        Mobile.SalesLogix.Validator.exists,
                        Mobile.SalesLogix.Validator.isDateInRange
                    ]
                },{
                    label: this.durationText,
                    name: 'Duration',
                    type: 'select',
                    view: 'select_list',
                    textRenderer: this.formatDurationText.createDelegate(this),
                    requireSelection: true,
                    valueKeyProperty: false,
                    valueTextProperty: false,
                    data: this.createDurationData(),
                    validator: {
                        fn: function(val, field) {
                            if (field.isDisabled()) return false;
                            if (!/^\d+$/.test(val)) return true;
                        },
                        message: "The field '{2}' must have a value."
                    }
                },{
                    label: this.timelessText,
                    name: 'Timeless',
                    type: 'boolean'
                }]
            },{
                options: {
                    title: this.completionText,
                    collapsed: false
                },
                as: [{
                    label: this.asScheduledText,
                    include: false,
                    name: 'AsScheduled',
                    type: 'boolean'
                },{
                    label: this.completedText,
                    name: 'CompletedDate',
                    type: 'date',
                    showTimePicker: true,
                    formatString: 'M/d/yyyy h:mm tt',
                    minValue: (new Date(1900, 0, 1)),
                    validator: [
                        Mobile.SalesLogix.Validator.exists,
                        Mobile.SalesLogix.Validator.isDateInRange
                    ]
                },{
                    dependsOn: 'Type',
                    label: this.resultText,
                    name: 'Result',
                    picklist: this.formatPicklistForType.createDelegate(
                        this, ['Result'], true
                    ),
                    title: this.resultTitleText,
                    orderBy: 'text asc',
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                },{
                    label: this.followUpText,
                    name: 'Followup',
                    type: 'select',
                    view: 'select_list',
                    textRenderer: this.formatFollowupText.createDelegate(this),
                    requireSelection: true,
                    valueKeyProperty: false,
                    valueTextProperty: false,
                    data: this.createFollowupData(),
                    include: false
                },{
                    label: this.carryOverNotesText,
                    include: false,
                    name: 'CarryOverNotes',
                    type: 'boolean'
                },{
                    label: this.longNotesText,
                    noteProperty: false,
                    name: 'LongNotes',
                    title: this.longNotesTitleText,
                    type: 'note',
                    view: 'text_edit'
                },{
                    label: this.accountText,
                    name: 'Account',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'AccountId',
                    valueTextProperty: 'AccountName',
                    validator: Mobile.SalesLogix.Validator.exists,
                    view: 'account_related'
                },{
                    dependsOn: 'Account',
                    label: this.contactText,
                    name: 'Contact',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'ContactId',
                    valueTextProperty: 'ContactName',
                    view: 'contact_related',
                    where: this.formatDependentQuery.createDelegate(
                        this, ['Account.Id eq "{0}"', 'AccountId'], true
                    )
                },{
                    dependsOn: 'Account',
                    label: this.opportunityText,
                    name: 'Opportunity',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'OpportunityId',
                    valueTextProperty: 'OpportunityName',
                    view: 'opportunity_related',
                    where: this.formatDependentQuery.createDelegate(
                        this, ['Account.Id eq "{0}"', 'AccountId'], true
                    )
                },{
                    dependsOn: 'Account',
                    label: this.ticketNumberText,
                    name: 'Ticket',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'TicketId',
                    valueTextProperty: 'TicketNumber',
                    view: 'ticket_related',
                    where: this.formatDependentQuery.createDelegate(
                        this, ['Account.Id eq "{0}"', 'AccountId'], true
                    )
                },{
                    label: this.companyText,
                    name: 'AccountName',
                    type: 'text'
                },{
                    label: this.leadText,
                    name: 'Lead',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'LeadId',
                    valueTextProperty: 'LeadName',
                    view: 'lead_related'
                }]
            },{
                options: {
                    title: this.otherInfoText,
                    collapsed: false
                },
                as: [{
                    label: this.priorityText,
                    name: 'Priority',
                    picklist: 'Priorities',
                    title: this.priorityTitleText,
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                },{
                    dependsOn: 'Type',
                    label: this.categoryText,
                    name: 'Category',
                    picklist: this.formatPicklistForType.createDelegate(
                        this, ['Category'], true
                    ),
                    orderBy: 'text asc',
                    title: this.categoryTitleText,
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                },{
                    type: 'hidden',
                    name: 'UserId'
                },{
                    label: this.leaderText,
                    name: 'Leader',
                    include: false,
                    type: 'lookup',
                    textProperty: 'UserInfo',
                    textTemplate: Mobile.SalesLogix.Template.nameLF,
                    requireSelection: true,
                    view: 'user_list'
                }]
            }]);
        }
    });
})();
<commit_msg>[fixed #1-79723] history views will now be refreshed after completing an activity.<commit_after>/// <reference path="../../../../../argos-sdk/libraries/ext/ext-core-debug.js"/>
/// <reference path="../../../../../argos-sdk/libraries/sdata/sdata-client-debug"/>
/// <reference path="../../../../../argos-sdk/libraries/Simplate.js"/>
/// <reference path="../../../../../argos-sdk/src/View.js"/>
/// <reference path="../../../../../argos-sdk/src/Edit.js"/>
/// <reference path="../../Format.js"/>

Ext.namespace("Mobile.SalesLogix.Activity");

(function() {
    Mobile.SalesLogix.Activity.Complete = Ext.extend(Sage.Platform.Mobile.Edit, {
        //Localization
        activityInfoText: 'Activity Info',
        accountText: 'account',
        contactText: 'contact',
        opportunityText: 'opportunity',
        ticketNumberText: 'ticket',
        companyText: 'company',
        leadText: 'lead',
        asScheduledText: 'as scheduled',
        categoryText: 'category',
        categoryTitleText: 'Activity Category',
        completedText: 'completed date',
        completionText: 'Completion',
        durationText: 'duration',
        carryOverNotesText: 'carry over notes',
        followUpText: 'follow-up',
        followUpTitleText: 'Activity Follow Up',
        leaderText: 'leader',
        longNotesText: 'notes',
        longNotesTitleText: 'Notes',
        otherInfoText: 'Other Info',
        priorityText: 'priority',
        priorityTitleText: 'Priority',
        regardingText: 'regarding',
        regardingTitleText: 'Activity Regarding',
        resultText: 'result',
        resultTitleText: 'Result',
        startingText: 'start date',
        timelessText: 'timeless',
        durationValueText: {
            0: 'none',
            15: '15 minutes',
            30: '30 minutes',
            60: '1 hour',
            90: '1.5 hours',
            120: '2 hours'
        },
        followupValueText: {
            'none': 'None',
            'atPhoneCall': 'Phone Call',
            'atAppointment': 'Meeting',
            'atToDo': 'To-Do'
        },

        //View Properties
        id: 'activity_complete',
        followupView: 'activity_edit',
        fieldsForLeads: ['AccountName', 'Lead'],
        fieldsForStandard: ['Account', 'Contact', 'Opportunity', 'Ticket'],
        picklistsByType: {
            'atAppointment': {
                'Category': 'Meeting Category Codes',
                'Description': 'Meeting Regarding',
                'Result': 'Meeting Result Codes'
            },
            'atLiterature': {
                'Description': 'Lit Request Regarding'
            },
            'atPersonal': {
                'Category': 'Meeting Category Codes',
                'Description': 'Personal Activity Regarding',
                'Result': 'Personal Activity Result Codes'
            },
            'atPhoneCall': {
                'Category': 'Phone Call Category Codes',
                'Description': 'Phone Call Regarding',
                'Result': 'Phone Call Result Codes'
            },
            'atToDo': {
                'Category': 'To Do Category Codes',
                'Description': 'To Do Regarding',
                'Result': 'To Do Result Codes'
            },
            'atEMail': {
                'Category': 'E-mail Category Codes',
                'Description': 'E-mail Regarding'
            }
        },

        entityName: 'Activity', // todo: is this correct?
        querySelect: [
            'AccountId',
            'AccountName',
            'Alarm',
            'AlarmTime',
            'Category',
            'ContactId',
            'ContactName',
            'CompletedDate',
            'Description',
            'Duration',
            'LeadId',
            'LeadName',
            'LongNotes',
            'OpportunityId',
            'OpportunityName',
            'Priority',
            'Regarding',
            'Result',
            'Rollover',
            'StartDate',
            'TicketId',
            'TicketNumber',
            'Timeless',
            'Type',
            'UserId'
        ],
        resourceKind: 'activities',

        init: function() {
            Mobile.SalesLogix.Activity.Complete.superclass.init.apply(this, arguments);

            this.fields['Leader'].on('change', this.onLeaderChange, this);
            this.fields['Timeless'].on('change', this.onTimelessChange, this);
            this.fields['AsScheduled'].on('change', this.onAsScheduledChange, this);
            this.fields['Followup'].on('change', this.onFollowupChange, this);
            this.fields['Lead'].on('change', this.onLeadChange, this);
        },
        isActivityForLead: function(entry) {
            return entry && /^[\w]{12}$/.test(entry['LeadId']);
        },
        beforeTransitionTo: function() {
            Mobile.SalesLogix.Activity.Complete.superclass.beforeTransitionTo.apply(this, arguments);

            Ext.each(this.fieldsForStandard.concat(this.fieldsForLeads), function(item) {
                if (this.fields[item])
                    this.fields[item].hide();
            }, this);

            var entry = this.options && this.options.entry;
            if (this.isActivityForLead(entry))
            {
                Ext.each(this.fieldsForLeads, function(item) {
                    if (this.fields[item])
                        this.fields[item].show();
                }, this);
            }
            else
            {
                Ext.each(this.fieldsForStandard, function(item) {
                    if (this.fields[item])
                        this.fields[item].show();
                }, this);
            }
        },
        toggleSelectField: function(field, disable, options) {
            disable === true ? field.disable() : field.enable();
        },
        onTimelessChange: function(value, field) {
            this.toggleSelectField(this.fields['Duration'], value);
        },
        onAsScheduledChange: function(scheduled, field) {
            if (scheduled)
            {
                this.toggleSelectField(this.fields['CompletedDate'], true);
                this.fields['CompletedDate'].setValue(this.fields['StartDate'].getValue());
            }
            else
            {
                this.toggleSelectField(this.fields['CompletedDate'], false);
                this.fields['CompletedDate'].setValue(new Date());
            }
        },
        onFollowupChange: function(value, field) {
            var disable = (value === 'none' || (value && value.key === 'none'));
            this.toggleSelectField(this.fields['CarryOverNotes'], disable);
        },
        onLeadChange: function(value, field) {
            var selection = field.getSelection(),
                getV = Sage.Platform.Mobile.Utility.getValue;

            if (selection && this.insert)
            {
                this.fields['Company'].setValue(getV(selection, 'Company'));
            }
        },
        formatPicklistForType: function(type, which) {
            return this.picklistsByType[type] && this.picklistsByType[type][which];
        },
        setValues: function(values) {
            Mobile.SalesLogix.Activity.Complete.superclass.setValues.apply(this, arguments);
            this.fields['CarryOverNotes'].setValue(true);
            this.fields['CompletedDate'].setValue(new Date());
            this.fields['Followup'].setValue('none');
            this.fields['Result'].setValue('Complete');

            this.toggleSelectField(this.fields['CarryOverNotes'], true);
            this.toggleSelectField(this.fields['CompletedDate'], false);
        },
        onLeaderChange: function(value, field) {
            this.fields['UserId'].setValue(value && value['key']);
        },
        formatDurationText: function(val, key, text) {
            return this.durationValueText[key] || text;
        },
        formatFollowupText: function(val, key, text) {
            return this.followupValueText[key] || text;
        },
        createDurationData: function() {
            var list = [];

            for (var duration in this.durationValueText)
            {
                list.push({
                    '$key': duration,
                    '$descriptor': this.durationValueText[duration]
                });
            }

            return {'$resources': list};
        },
        createFollowupData: function() {
            var list = [];

            for (var followup in this.followupValueText)
            {
                list.push({
                    '$key': followup,
                    '$descriptor': this.followupValueText[followup]
                });
            }

            return {'$resources': list};
        },
        navigateToFollowUpView: function(entry) {
            var view = App.getView(this.followupView),
                followupEntry = {
                    'Type': this.fields['Followup'].getValue(),
                    'Description': entry.Description,
                    'AccountId': entry.AccountId,
                    'AccountName': entry.AccountName,
                    'ContactId': entry.ContactId,
                    'ContactName': entry.ContactName,
                    'LeadId': entry.LeadId,
                    'LeadName': entry.LeadName,
                    'LongNotes': (this.fields['CarryOverNotes'].getValue() && entry['LongNotes']) || '',
                    'OpportunityId': entry.OpportunityId,
                    'OpportunityName': entry.OpportunityName,
                    'TicketId': entry.TicketId,
                    'TicketNumber': entry.TicketNumber
                };

            //Return to activity list view after follow up.
            view.show({
                entry: followupEntry,
                insert: true,
                title: this.followupValueText[this.fields['Followup'].getValue()]
            }, {
                returnTo: -1
            });
        },
        completeActivity: function(entry, callback) {
            var completeActivityentry = {
                "$name": "ActivityComplete",
                "request": {
                    "ActivityId": entry.$key,
                    "userId": entry.UserId,
                    "result": this.fields['Result'].getValue(),
                    "completeDate":  this.fields['CompletedDate'].getValue()
                }
            };

            var success = (function(scope, callback, entry) {
                return function() {
                    App.fireEvent('refresh', {
                        resourceKind: 'history'
                    });

                    callback.apply(scope, [entry]);
                };
            })(this, callback, entry);

            var request = new Sage.SData.Client.SDataServiceOperationRequest(this.getService())
                .setResourceKind('activities')
                .setOperationName('Complete');

            request.execute(completeActivityentry, {
                success: success,
                failure: function() {},
                scope: this
            });
        },
        updateCompleted: function(entry) {
            var followup = this.fields['Followup'].getValue() !== 'none'
                    ? this.navigateToFollowUpView
                    : Mobile.SalesLogix.Activity.Complete.superclass.updateCompleted;

            this.completeActivity(entry, followup);
        },
        formatDependentQuery: function(dependentValue, format, property) {
            var getV = Sage.Platform.Mobile.Utility.getValue;

            property = property || '$key';

            return String.format(format, getV(dependentValue, property));
        },
        createLayout: function() {
            return this.layout || (this.layout = [{
                options: {
                    title: this.activityInfoText,
                    collapsed: false
                },
                as: [{
                    name: 'Type',
                    type: 'hidden'
                },{
                    dependsOn: 'Type',
                    label: this.regardingText,
                    name: 'Description',
                    picklist: this.formatPicklistForType.createDelegate(
                        this, ['Description'], true
                    ),
                    title: this.regardingTitleText,
                    orderBy: 'text asc',
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                },{
                    label: this.startingText,
                    name: 'StartDate',
                    type: 'date',
                    showTimePicker: true,
                    formatString: 'M/d/yyyy h:mm tt',
                    minValue: (new Date(1900, 0, 1)),
                    validator: [
                        Mobile.SalesLogix.Validator.exists,
                        Mobile.SalesLogix.Validator.isDateInRange
                    ]
                },{
                    label: this.durationText,
                    name: 'Duration',
                    type: 'select',
                    view: 'select_list',
                    textRenderer: this.formatDurationText.createDelegate(this),
                    requireSelection: true,
                    valueKeyProperty: false,
                    valueTextProperty: false,
                    data: this.createDurationData(),
                    validator: {
                        fn: function(val, field) {
                            if (field.isDisabled()) return false;
                            if (!/^\d+$/.test(val)) return true;
                        },
                        message: "The field '{2}' must have a value."
                    }
                },{
                    label: this.timelessText,
                    name: 'Timeless',
                    type: 'boolean'
                }]
            },{
                options: {
                    title: this.completionText,
                    collapsed: false
                },
                as: [{
                    label: this.asScheduledText,
                    include: false,
                    name: 'AsScheduled',
                    type: 'boolean'
                },{
                    label: this.completedText,
                    name: 'CompletedDate',
                    type: 'date',
                    showTimePicker: true,
                    formatString: 'M/d/yyyy h:mm tt',
                    minValue: (new Date(1900, 0, 1)),
                    validator: [
                        Mobile.SalesLogix.Validator.exists,
                        Mobile.SalesLogix.Validator.isDateInRange
                    ]
                },{
                    dependsOn: 'Type',
                    label: this.resultText,
                    name: 'Result',
                    picklist: this.formatPicklistForType.createDelegate(
                        this, ['Result'], true
                    ),
                    title: this.resultTitleText,
                    orderBy: 'text asc',
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                },{
                    label: this.followUpText,
                    name: 'Followup',
                    type: 'select',
                    view: 'select_list',
                    textRenderer: this.formatFollowupText.createDelegate(this),
                    requireSelection: true,
                    valueKeyProperty: false,
                    valueTextProperty: false,
                    data: this.createFollowupData(),
                    include: false
                },{
                    label: this.carryOverNotesText,
                    include: false,
                    name: 'CarryOverNotes',
                    type: 'boolean'
                },{
                    label: this.longNotesText,
                    noteProperty: false,
                    name: 'LongNotes',
                    title: this.longNotesTitleText,
                    type: 'note',
                    view: 'text_edit'
                },{
                    label: this.accountText,
                    name: 'Account',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'AccountId',
                    valueTextProperty: 'AccountName',
                    validator: Mobile.SalesLogix.Validator.exists,
                    view: 'account_related'
                },{
                    dependsOn: 'Account',
                    label: this.contactText,
                    name: 'Contact',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'ContactId',
                    valueTextProperty: 'ContactName',
                    view: 'contact_related',
                    where: this.formatDependentQuery.createDelegate(
                        this, ['Account.Id eq "{0}"', 'AccountId'], true
                    )
                },{
                    dependsOn: 'Account',
                    label: this.opportunityText,
                    name: 'Opportunity',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'OpportunityId',
                    valueTextProperty: 'OpportunityName',
                    view: 'opportunity_related',
                    where: this.formatDependentQuery.createDelegate(
                        this, ['Account.Id eq "{0}"', 'AccountId'], true
                    )
                },{
                    dependsOn: 'Account',
                    label: this.ticketNumberText,
                    name: 'Ticket',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'TicketId',
                    valueTextProperty: 'TicketNumber',
                    view: 'ticket_related',
                    where: this.formatDependentQuery.createDelegate(
                        this, ['Account.Id eq "{0}"', 'AccountId'], true
                    )
                },{
                    label: this.companyText,
                    name: 'AccountName',
                    type: 'text'
                },{
                    label: this.leadText,
                    name: 'Lead',
                    type: 'lookup',
                    emptyText: '',
                    applyTo: '.',
                    valueKeyProperty: 'LeadId',
                    valueTextProperty: 'LeadName',
                    view: 'lead_related'
                }]
            },{
                options: {
                    title: this.otherInfoText,
                    collapsed: false
                },
                as: [{
                    label: this.priorityText,
                    name: 'Priority',
                    picklist: 'Priorities',
                    title: this.priorityTitleText,
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                },{
                    dependsOn: 'Type',
                    label: this.categoryText,
                    name: 'Category',
                    picklist: this.formatPicklistForType.createDelegate(
                        this, ['Category'], true
                    ),
                    orderBy: 'text asc',
                    title: this.categoryTitleText,
                    type: 'picklist',
                    maxTextLength: 64,
                    validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                },{
                    type: 'hidden',
                    name: 'UserId'
                },{
                    label: this.leaderText,
                    name: 'Leader',
                    include: false,
                    type: 'lookup',
                    textProperty: 'UserInfo',
                    textTemplate: Mobile.SalesLogix.Template.nameLF,
                    requireSelection: true,
                    view: 'user_list'
                }]
            }]);
        }
    });
})();
<|endoftext|>


<commit_before>/*global L: false */

Ext.ns('vrs.ux.touch');

vrs.ux.touch.IMapComponent = Ext.extend(Ext.Component, {
   /**
    * @cfg {String} baseCls
    * The base CSS class to apply to the Maps's element (defaults to 'x-map').
    */
   baseCls: 'x-map',

   /**
    * @cfg {Boolean} enableLocationTracking
    * If true, automatically registers location tracking support on startup.
    *
    * note: this does *not* start the geo location updating, simply registers
    *       the events to allow us to track the location.
    *       call geo.setAutoUpdate(true/false) to enable/disable tracking.
    *       and call enable/disableTracking on this class.
    */
   enableLocationTracking: false,

   /**
    * @cfg {Boolean} useHighAccuracyLocation
    * If true and enableLocationTracking is true, then request the high accuracy version.
    */
   useHighAccuracyLocation: true,

   monitorResize : true,

   /**
    * @cfg {Object} mapOptions
    * options as specified in Leaflet documentation. Passed as options
    * to the Map constructor.
    */
   mapOptions: {},

   /**
    * @type {Leaflet.Map}
    * The wrapped map.
    */
   map: null,

   /**
    * @type {Ext.util.GeoLocation}
    * The geolocation utility object to use with the map.
    */
   geo: null,

   /**
    * @cfg {Boolean} maskMap
    * Masks the map (Defaults to false)
    */
   maskMap: false,

   /**
    * @cfg {Strng} maskMapCls
    * CSS class to add to the map when maskMap is set to true.
    */
   maskMapCls: 'x-mask-map',

   constructor: function() {
      vrs.ux.touch.IMapComponent.superclass.constructor.call(this, arguments);

      this.addEvents({
         'repPicked': true
      });
   },

   /**
    * Add a helper function to the afterRender chain for rendering the map
    * once the DOM is ready.
    */
   afterRender: function() {
      vrs.ux.touch.IMapComponent.superclass.afterRender.apply(this, arguments);
      this.renderMap();
   },

   // ---- Interface Functions ---- //
   /**
    * Convert a lat lon into the internal represtation for the mapping package
    */
   toLocation: function(lat, lon) {
      console.error('toLocation must be overriden');
   },

   /**
    * Change the current zoom level to be zoomLevel
    */
   zoomTo: function(zoomLevel) {
      console.error('zoomTo must be overriden');
   },

   /**
    * Returns the number of zoomLevels available for the map.
    */
   numZoomLevels: function() {
      console.error('numZoomLevels must be overriden');
   },

   addMarker: function(lat, lon) {
      console.error('addMarker must be overriden');
   },

   /**
    * Moves the map center to the location which is the format returned by toLocation.
    */
   updateCenter : function(location) {
      console.error('updateCenter must be overriden');
   },

   // ---- MAP TRACKING SUPPORT ---- //
   /**
    * Register events for auto tracking in the system.
    * This does not cause the triggering to begin.
    * @param enable: If true, enable location tracking.
    */
   registerLocationTracking: function(enable) {
      enable = (enable === undefined) ? true : enable;

      if(!this.geo) {
         this.geo = new Ext.util.GeoLocation({
            autoUpdate:        false,
            allowHighAccuracy: this.useHighAccuracyLocation
         });
      }

      this.geo.on({
         locationupdate : this.onGeoUpdate,
         locationerror  : this.onGeoError,
         scope : this
      });
      this.trackingEnabled = enable;
   },

   setEnableTracking: function(enable) {
      var max_zoom_level;

      if(this.trackingEnabled === enable) {
         return;
      }

      this.trackingEnabled = enable;

      // If we just got enabled, force an update and zoom to a reasonable level
      if(this.trackingEnabled) {
         // XXX: Need better way to do this.  For now just go to one short of max
         this.zoomTo(this.numZoomLevels() -1);

         // Since we are just starting try to get an update quickly.
         this.geo.forceGeoLocationUpdate();
      }
   },

   /** Called when we get a new geo update. */
   onGeoUpdate : function(geoObj) {
      if(!this.trackingEnabled) {
         return;
      }

      this.lastGeoObj = geoObj;

      if (this.rendered) {
         this.updateCenter(this.toLocation(geoObj.latitude, geoObj.longitude));
      }
   },

   /**
    * Called when the geo we are connected to has an error.
    */
   onGeoError : function(geo) {
      if(!this.trackingEnabled) {
         return;
      }

      console.error("Error getting location: ", arguments);
      // XXX  dispatch event about this error so that owner can handle it.
   }
});

/**
 * @class vrs.ux.touch.LeafletMap
 * @extends Ext.IMapComponent
 *
 * Wraps a Leaflet map. http://leaflet.cloudmade.com/
 * To use this component you must include leaflet.js script.
 *
 *
Example code:

 *

var pnl = new Ext.Panel({
    fullscreen: true,
    items     : [
        {
            xtype                 : 'leaflet_map',
            enableLocationTracking: true
        }
    ]
});

 * @xtype leaflet_map
 *
 * TODO:
 *  * ...
 */
vrs.ux.touch.LeafletMap = Ext.extend(vrs.ux.touch.IMapComponent, {
   /**
    * initialize the component as part
    * of component construction.
    */
   initComponent : function() {
      var me = this;

      if(! window.L) {
         this.html = 'Leaflet API is required.';
      }

      vrs.ux.touch.LeafletMap.superclass.initComponent.call(this);

      Ext.applyIf(this.mapOptions, {
         center: new L.LatLng(0, 0),
         zoom: 1,
         attributionControl: false
      });
   },

   renderMap: function() {
      this.map = new L.Map(this.getEl().dom, this.mapOptions);

      // add the CloudMade layer to the map
      this.map.addLayer(new L.TileLayer(
         'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
         {maxZoom: 19}
      ));
   },

   // ---- Interface Functions ---- //
   /**
    * Convert a lat lon into the internal represtation for the mapping package
    */
   toLocation: function(lat, lon) {
      return new L.LatLng(lat, lon);
   },

   /**
    * Change the current zoom level to be zoomLevel
    */
   zoomTo: function(zoomLevel) {
      this.map.setZoom(zoomLevel);
   },

   /**
    * Returns the number of zoomLevels available for the map.
    */
   numZoomLevels: function() {
      return this.map.getMaxZoom();
   },

   /**
    * Moves the map center to the location which is the format returned by toLocation.
    */
   updateCenter : function(location) {
      this.map.panTo(location);
   },

   addMarker: function(lat, lon) {
      var marker = new L.Marker(this.toLocation(lat, lon));
      marker.dude = "adfsasdf";
      marker.on('click', this.onMarkerPicked, this);

      this.map.addLayer(marker);
   },

   onMarkerPicked: function(event) {
      this.fireEvent('repPicked', event.target);
   }
});

Ext.reg('leaflet_map', vrs.ux.touch.LeafletMap);

/**
* A panel that can be used as popup window on Leaflet
* maps.  Support anchoring to a location so it moves
* accordingly when the map is panned and zoomed
*/
vrs.ux.touch.LeafletPopupPanel = Ext.extend(Ext.Panel, {
   /**
   * The map that we are attached onto.
   */
   map: null,

   /** google.maps.LatLng location of the panel anchor. */
   location: null,

   /** {bool} True if the popup should be anchored to it's location. */
   anchored: true,

   /** {bool} True if we should pan the map so the popup is in view. */
   panIn: true,

   /** A config object to pass to setPanelSize after panel
   * is constructed.
   */
   sizeConfig: null,

   /** Set to allow custom styling. */
   componentCls: 'x-map-popup',

   /**
   * If set, then automatically removes the panel and
   * associated objects when the popup is hidden.
   */
   autoRemoveOnHide : true,

   /** override the panel details. */
   floating      : true,
   hideOnMaskTap : false,  // Don't auto-hide when tap outside the component.

   /**
   * Construct the popup panel.
   *
   * Note: Since this is a panel, normal panel configuration settings can
   *       be passed in.  (ex: items, dockedItems, layout, ...)
   */
   constructor: function(config) {
      var me = this;

      Ext.apply(config, {
         renderTo: config.map._panes.popupPane
      });

      this.addEvents({
         /** Fired after the panel has been added to the map DOM.
         * called as: afterAdd(panel)
         */
         'afterAdd': true,

         /** Fired after the panel has been removed from the map DOM.
         * called as: afterRemove(panel)
         */
         'afterRemove': true
      });

      vrs.ux.touch.LeafletPopupPanel.superclass.constructor.apply(this, arguments);

      this.map.on('viewreset', this._updatePosition, this);

      // Add flag we use to track if have toggled ourselves into fullscreen mode
      // used in setPanelSize to handle fullscreen settings.
      this._isFullscreen = false;

      // If setup to auto remove, register to call remove at the end of hide
      if(this.autoRemoveOnHide) {
         this.on({
            hide: function() { me.remove(); }
         });
      }
   },

   /**
   * Set the size of the panel to use.
   *
   * @param config
   *      fullscreen: If true, then set to a fullscreen panel centered on screen.
   *                 (still respects width and height settings as computed)
   *      width: if set, sets to this width in pixels.
   *      height: if set, sets to this height in pixels.
   *      size: if set to one of 'small', 'medium', 'large', 'fullscreen'
   *           or variation ending in '-tall' or '-wide'. (ex: 'small-wide')
   *           automatically picks window of good size for the current device.
   *      autoPosition: If true, automatically call the position setting. (default: true)
   *      anchor: If true, we set anchored (if not already set) and update.
   *               (true by default except for fullscreen which is false by default)
   *
   * note: fullscreen is a special variation that will override some other settings.
   */
   setPanelSize: function(config) {
      var vp_size = Ext.Viewport.getSize(),
          tall_wide_factor = 1.5,  // Factor used to increase size of tall/wide panels.
          set_sizes = {},          // map from size name to size values to use.
          size_name, var_name,
          width, height,
          extra_margin = 12,  // extra margin to fix layout issues
          auto_position      = config.autoPosition || true, // if we should update position.
          should_anchor      = config.anchor || true,       // if we should be anchored
          setting_fullscreen = config.fullscreen || false,  // if we are setting to fullscreen
          was_fullscreen     = this._isFullscreen;          // if we were previously fullscreen.

      // Lookup table of named size values
      set_sizes = {
         small: {
            width  : vp_size.width  * 0.2,
            height : vp_size.height * 0.2
         },
         medium: {
            width  : vp_size.width  * 0.35,
            height : vp_size.height * 0.35
         },
         large: {
            width  : vp_size.width  * 0.5,
            height : vp_size.height * 0.5
         }
      };

      // compute the final width and height values
      if(config.size) {
         size_name = config.size.split('-')[0];
         var_name  = config.size.split('-')[1];
         width  = set_sizes[size_name].width;
         height = set_sizes[size_name].height;
         if('tall' === var_name) {
            height = height * tall_wide_factor;
         }
         if('wide' === var_name) {
            width = width * tall_wide_factor;
         }
      } else {
         width  = config.width;
         height = config.height;
      }

      // If fullscreen and no explicit width set, then set defaults.
      if(setting_fullscreen) {
         auto_position = false;
         should_anchor = false;

         if(Ext.isEmpty(width)) {
            width  = vp_size.width  * 0.9;
            height = vp_size.height * 0.9;
         }
      }

      // finalize the size and layout if we are visible.
      // - we don't layout if not visible because that would remove the sizing for first show
      this.setSize(width + extra_margin, height + extra_margin);
      if(this.isVisible()) {
         this.doLayout();
      }

      // If we should be anchoring and it doesn't match previous setting,
      // then change and possible update the rendering to remove/add anchor.
      if(should_anchor !== this.anchored) {
         this.anchored = should_anchor;
         if(this.rendered && this.isVisible()) {
            this.show();        // reshow to get anchor laid out correctly.
         }
      }

      // If we are rendered and we should update position, then do so now.
      if(this.rendered && this.isVisible() && auto_position) {
         this._updatePosition();
      }

      if(setting_fullscreen) {
         this.setCentered(true, true);  // set to centered and update it
      } else if(was_fullscreen && this.centered) {
         this.setCentered(false);      // reset to not be centered
      }

      // update fullscreen flag.
      this._isFullscreen = setting_fullscreen;
   },

   // --- INTERNAL HELPERS ---- //
   _updatePosition: function() {
      var pos    = this.map.latLngToLayerPoint(this.location);

      this.el.setTop(pos.y);
      this.el.setLeft(pos.x);
   },

   /**
   * Called after the overlay panel holder has added us to a pane.
   *
   * Should only be called once per map.
   */
   afterAdd: function() {
      // Pass the size configuration
      if(this.sizeConfig) {
         this.setPanelSize(this.sizeConfig);
      }

      // Auto show the popup
      this.show();

      this.fireEvent('afterAdd', this);
   },

   /**
   * Called after the overlay panel holder has removed us from the map.
   */
   afterRemove: function() {
      this.map.off('viewreset', this._updatePosition, this);

      this.fireEvent('afterRemove', this);

      // finish removal by destroying and cleaning up everything here.
      this.map            = null;
      this._overlayHolder = null;
      this.destroy();
   }
});
<commit_msg>Break some code apart into common and unique sections.<commit_after>/*global L: false */

Ext.ns('vrs.ux.touch');

vrs.ux.touch.IMapComponent = Ext.extend(Ext.Component, {
   /**
    * @cfg {String} baseCls
    * The base CSS class to apply to the Maps's element (defaults to 'x-map').
    */
   baseCls: 'x-map',

   /**
    * @cfg {Boolean} enableLocationTracking
    * If true, automatically registers location tracking support on startup.
    *
    * note: this does *not* start the geo location updating, simply registers
    *       the events to allow us to track the location.
    *       call geo.setAutoUpdate(true/false) to enable/disable tracking.
    *       and call enable/disableTracking on this class.
    */
   enableLocationTracking: false,

   /**
    * @cfg {Boolean} useHighAccuracyLocation
    * If true and enableLocationTracking is true, then request the high accuracy version.
    */
   useHighAccuracyLocation: true,

   monitorResize : true,

   /**
    * @cfg {Object} mapOptions
    * options as specified in Leaflet documentation. Passed as options
    * to the Map constructor.
    */
   mapOptions: {},

   /**
    * @type {Leaflet.Map}
    * The wrapped map.
    */
   map: null,

   /**
    * @type {Ext.util.GeoLocation}
    * The geolocation utility object to use with the map.
    */
   geo: null,

   /**
    * @cfg {Boolean} maskMap
    * Masks the map (Defaults to false)
    */
   maskMap: false,

   /**
    * @cfg {Strng} maskMapCls
    * CSS class to add to the map when maskMap is set to true.
    */
   maskMapCls: 'x-mask-map',

   constructor: function() {
      vrs.ux.touch.IMapComponent.superclass.constructor.call(this, arguments);

      this.addEvents({
         'repPicked': true
      });
   },

   /**
    * Add a helper function to the afterRender chain for rendering the map
    * once the DOM is ready.
    */
   afterRender: function() {
      vrs.ux.touch.IMapComponent.superclass.afterRender.apply(this, arguments);
      this.renderMap();
   },

   // ---- Interface Functions ---- //
   /**
    * Convert a lat lon into the internal represtation for the mapping package
    */
   toLocation: function(lat, lon) {
      console.error('toLocation must be overriden');
   },

   /**
    * Change the current zoom level to be zoomLevel
    */
   zoomTo: function(zoomLevel) {
      console.error('zoomTo must be overriden');
   },

   /**
    * Returns the number of zoomLevels available for the map.
    */
   numZoomLevels: function() {
      console.error('numZoomLevels must be overriden');
   },

   addMarker: function(lat, lon) {
      console.error('addMarker must be overriden');
   },

   /**
    * Moves the map center to the location which is the format returned by toLocation.
    */
   updateCenter : function(location) {
      console.error('updateCenter must be overriden');
   },

   // ---- MAP TRACKING SUPPORT ---- //
   /**
    * Register events for auto tracking in the system.
    * This does not cause the triggering to begin.
    * @param enable: If true, enable location tracking.
    */
   registerLocationTracking: function(enable) {
      enable = (enable === undefined) ? true : enable;

      if(!this.geo) {
         this.geo = new Ext.util.GeoLocation({
            autoUpdate:        false,
            allowHighAccuracy: this.useHighAccuracyLocation
         });
      }

      this.geo.on({
         locationupdate : this.onGeoUpdate,
         locationerror  : this.onGeoError,
         scope : this
      });
      this.trackingEnabled = enable;
   },

   setEnableTracking: function(enable) {
      var max_zoom_level;

      if(this.trackingEnabled === enable) {
         return;
      }

      this.trackingEnabled = enable;

      // If we just got enabled, force an update and zoom to a reasonable level
      if(this.trackingEnabled) {
         // XXX: Need better way to do this.  For now just go to one short of max
         this.zoomTo(this.numZoomLevels() -1);

         // Since we are just starting try to get an update quickly.
         this.geo.forceGeoLocationUpdate();
      }
   },

   /** Called when we get a new geo update. */
   onGeoUpdate : function(geoObj) {
      if(!this.trackingEnabled) {
         return;
      }

      this.lastGeoObj = geoObj;

      if (this.rendered) {
         this.updateCenter(this.toLocation(geoObj.latitude, geoObj.longitude));
      }
   },

   /**
    * Called when the geo we are connected to has an error.
    */
   onGeoError : function(geo) {
      if(!this.trackingEnabled) {
         return;
      }

      console.error("Error getting location: ", arguments);
      // XXX  dispatch event about this error so that owner can handle it.
   }
});

/**
 * @class vrs.ux.touch.LeafletMap
 * @extends Ext.IMapComponent
 *
 * Wraps a Leaflet map. http://leaflet.cloudmade.com/
 * To use this component you must include leaflet.js script.
 *
 *
Example code:

 *

var pnl = new Ext.Panel({
    fullscreen: true,
    items     : [
        {
            xtype                 : 'leaflet_map',
            enableLocationTracking: true
        }
    ]
});

 * @xtype leaflet_map
 *
 * TODO:
 *  * ...
 */
vrs.ux.touch.LeafletMap = Ext.extend(vrs.ux.touch.IMapComponent, {
   /**
    * initialize the component as part
    * of component construction.
    */
   initComponent : function() {
      var me = this;

      if(! window.L) {
         this.html = 'Leaflet API is required.';
      }

      vrs.ux.touch.LeafletMap.superclass.initComponent.call(this);

      Ext.applyIf(this.mapOptions, {
         center: new L.LatLng(0, 0),
         zoom: 1,
         attributionControl: false
      });
   },

   renderMap: function() {
      this.map = new L.Map(this.getEl().dom, this.mapOptions);

      // add the CloudMade layer to the map
      this.map.addLayer(new L.TileLayer(
         'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
         {maxZoom: 19}
      ));
   },

   // ---- Interface Functions ---- //
   /**
    * Convert a lat lon into the internal represtation for the mapping package
    */
   toLocation: function(lat, lon) {
      return new L.LatLng(lat, lon);
   },

   /**
    * Change the current zoom level to be zoomLevel
    */
   zoomTo: function(zoomLevel) {
      this.map.setZoom(zoomLevel);
   },

   /**
    * Returns the number of zoomLevels available for the map.
    */
   numZoomLevels: function() {
      return this.map.getMaxZoom();
   },

   /**
    * Moves the map center to the location which is the format returned by toLocation.
    */
   updateCenter : function(location) {
      this.map.panTo(location);
   },

   addMarker: function(lat, lon) {
      var marker = new L.Marker(this.toLocation(lat, lon));
      marker.dude = "adfsasdf";
      marker.on('click', this.onMarkerPicked, this);

      this.map.addLayer(marker);
   },

   onMarkerPicked: function(event) {
      this.fireEvent('repPicked', event.target);
   }
});

Ext.reg('leaflet_map', vrs.ux.touch.LeafletMap);

vrs.ux.touch.IMapPopupPanel = Ext.extend(Ext.Panel, {
   /**
   * The map that we are attached onto.
   */
   map: null,

   /** google.maps.LatLng location of the panel anchor. */
   location: null,

   /** {bool} True if the popup should be anchored to it's location. */
   anchored: true,

   /** {bool} True if we should pan the map so the popup is in view. */
   panIn: true,

   /** A config object to pass to setPanelSize after panel
   * is constructed.
   */
   sizeConfig: null,

   /** Set to allow custom styling. */
   componentCls: 'x-map-popup',

   /**
   * If set, then automatically removes the panel and
   * associated objects when the popup is hidden.
   */
   autoRemoveOnHide : true,

   /** override the panel details. */
   floating      : true,
   hideOnMaskTap : false,  // Don't auto-hide when tap outside the component.

   constructor: function() {
      this.addEvents({
         /** Fired after the panel has been added to the map DOM.
         * called as: afterAdd(panel)
         */
         'afterAdd': true,

         /** Fired after the panel has been removed from the map DOM.
         * called as: afterRemove(panel)
         */
         'afterRemove': true
      });

      vrs.ux.touch.IMapPopupPanel.superclass.constructor.apply(this, arguments);

      // If setup to auto remove, register to call remove at the end of hide
      if(this.autoRemoveOnHide) {
         this.on({
            hide: function() { me.remove(); }
         });
      }
   },

   calculatePanelSize: function(config) {
      var vp_size = Ext.Viewport.getSize(),
         tall_wide_factor = 1.5,  // Factor used to increase size of tall/wide panels.
          set_sizes = {},          // map from size name to size values to use.
          size_name, var_name,
          width, height,
          extra_margin = 12,  // extra margin to fix layout issues
          auto_position      = config.autoPosition || true, // if we should update position.
          should_anchor      = config.anchor || true,       // if we should be anchored
          setting_fullscreen = config.fullscreen || false,  // if we are setting to fullscreen
          was_fullscreen     = this._isFullscreen;          // if we were previously fullscreen.

      // Lookup table of named size values
      set_sizes = {
         small: {
            width  : vp_size.width  * 0.2,
            height : vp_size.height * 0.2
         },
         medium: {
            width  : vp_size.width  * 0.35,
            height : vp_size.height * 0.35
         },
         large: {
            width  : vp_size.width  * 0.5,
            height : vp_size.height * 0.5
         }
      };

      // compute the final width and height values
      if(config.size) {
         size_name = config.size.split('-')[0];
         var_name  = config.size.split('-')[1];
         width  = set_sizes[size_name].width;
         height = set_sizes[size_name].height;
         if('tall' === var_name) {
            height = height * tall_wide_factor;
         }
         if('wide' === var_name) {
            width = width * tall_wide_factor;
         }
      } else {
         width  = config.width;
         height = config.height;
      }

      // If fullscreen and no explicit width set, then set defaults.
      if(setting_fullscreen) {
         auto_position = false;
         should_anchor = false;

         if(Ext.isEmpty(width)) {
            width  = vp_size.width  * 0.9;
            height = vp_size.height * 0.9;
         }
      }

      return {w: width, h: height, margin: extra_margin};
   },

   /**
   * Set the size of the panel to use.
   *
   * @param config
   *      fullscreen: If true, then set to a fullscreen panel centered on screen.
   *                 (still respects width and height settings as computed)
   *      width: if set, sets to this width in pixels.
   *      height: if set, sets to this height in pixels.
   *      size: if set to one of 'small', 'medium', 'large', 'fullscreen'
   *           or variation ending in '-tall' or '-wide'. (ex: 'small-wide')
   *           automatically picks window of good size for the current device.
   *      autoPosition: If true, automatically call the position setting. (default: true)
   *      anchor: If true, we set anchored (if not already set) and update.
   *               (true by default except for fullscreen which is false by default)
   *
   * note: fullscreen is a special variation that will override some other settings.
   */
   setPopupSizeAndPosition: function() {
      var dims = this.calculatePanelSize(this.config),
          auto_position      = config.autoPosition || true, // if we should update position.
          should_anchor      = config.anchor || true,       // if we should be anchored
          setting_fullscreen = config.fullscreen || false;  // if we are setting to fullscreen;

      console.log(dims)

      // finalize the size and layout if we are visible.
      // - we don't layout if not visible because that would remove the sizing for first show
      this.setSize(dims.w + dims.margin, dims.h + dims.margin);
      if(this.isVisible()) {
         this.doLayout();
      }

      // If we should be anchoring and it doesn't match previous setting,
      // then change and possible update the rendering to remove/add anchor.
      if(should_anchor !== this.anchored) {
         this.anchored = should_anchor;
         if(this.rendered && this.isVisible()) {
            this.show();        // reshow to get anchor laid out correctly.
         }
      }

      // If we are rendered and we should update position, then do so now.
      if(this.rendered && this.isVisible() && auto_position) {
         this.updatePosition();
      }

      if(setting_fullscreen) {
         this.setCentered(true, true);  // set to centered and update it
      } else if(was_fullscreen && this.centered) {
         this.setCentered(false);      // reset to not be centered
      }

      // update fullscreen flag.
      this._isFullscreen = setting_fullscreen;
   }

});

/**
* A panel that can be used as popup window on Leaflet
* maps.  Support anchoring to a location so it moves
* accordingly when the map is panned and zoomed
*/
vrs.ux.touch.LeafletPopupPanel = Ext.extend(vrs.ux.touch.IMapPopupPanel, {
   /**
   * Construct the popup panel.
   *
   * Note: Since this is a panel, normal panel configuration settings can
   *       be passed in.  (ex: items, dockedItems, layout, ...)
   */
   constructor: function(config) {
      Ext.apply(config, {
         renderTo: config.map._panes.popupPane
      });

      vrs.ux.touch.LeafletPopupPanel.superclass.constructor.apply(this, arguments);

      this.map.on('viewreset', this.updatePosition, this);

      // Add flag we use to track if have toggled ourselves into fullscreen mode
      // used in setPanelSize to handle fullscreen settings.
      this._isFullscreen = false;
   },

   // --- INTERNAL HELPERS ---- //
   updatePosition: function() {
      var pos    = this.map.latLngToLayerPoint(this.location);

      this.el.setTop(pos.y);
      this.el.setLeft(pos.x);
   },

   /**
   * Called after the overlay panel holder has added us to a pane.
   *
   * Should only be called once per map.
   */
   afterAdd: function() {
      // Pass the size configuration
      if(this.sizeConfig) {
         this.setPanelSize(this.sizeConfig);
      }

      // Auto show the popup
      this.show();

      this.fireEvent('afterAdd', this);
   },

   /**
   * Called after the overlay panel holder has removed us from the map.
   */
   afterRemove: function() {
      this.map.off('viewreset', this.updatePosition, this);

      this.fireEvent('afterRemove', this);

      // finish removal by destroying and cleaning up everything here.
      this.map            = null;
      this._overlayHolder = null;
      this.destroy();
   }
});
<|endoftext|>


<commit_before>var users = require('./users');

module.exports = function(app) {
  var prefix = '/api/v2';

  app.get(prefix + '/users', users.getUsers);
  app.get(prefix + '/users/:id', users.getUser);
  app.post(prefix + '/users', users.newUser);
  app.put(prefix + '/users/:id', users.updateUser);
  app.delete(prefix + '/users/:id', users.deleteUser);

  app.get(prefix + '/user', users.getCurrentUser);

  return app;
};
<commit_msg>Added missing setup routes.<commit_after>var users = require('./users');
var setup = require('./setup');

module.exports = function(app) {
  var prefix = '/api/v2';

  app.get(prefix + '/users', users.getUsers);
  app.get(prefix + '/users/:id', users.getUser);
  app.post(prefix + '/users', users.newUser);
  app.put(prefix + '/users/:id', users.updateUser);
  app.delete(prefix + '/users/:id', users.deleteUser);

  app.get(prefix + '/user', users.getCurrentUser);

  app.post(prefix + '/setup-auth', setup.authenticate);
  app.post(prefix + '/setup', setup.doSetup);

  return app;
};
<|endoftext|>