tmcgee/cmv-widgets

View on GitHub
widgets/PrintPlus.js

Summary

Maintainability
B
4 hrs
Test Coverage
/*eslint max-statements: 0, no-console: 0 */
//http://resources.arcgis.com/en/help/main/10.2/index.html#//0154000004w8000000
//https://developers.arcgis.com/javascript/jssamples/map_showloading.html
define([
    'dojo/_base/declare',
    'dijit/_WidgetBase',
    'dijit/_TemplatedMixin',
    'dijit/_WidgetsInTemplateMixin',
    'esri/tasks/PrintTask',
    'dojo/store/Memory',
    'dojo/_base/lang',
    'dojo/_base/array',
    'dojo/dom-style',
    'dojo/dom-construct',
    'dojo/dom-class',

    'dojo/text!./PrintPlus/templates/Print.html',
    'dojo/text!./PrintPlus/templates/PrintResult.html',

    'dojo/aspect',
    'esri/tasks/PrintTemplate',
    'esri/tasks/PrintParameters',
    'esri/request',
    'esri/config',
    'esri/layers/GraphicsLayer',

    //Print Plus Enhancements BEGIN
    'dojo/dom',
    'dojo/number',
    'dojo/_base/Color',
    'esri/symbols/SimpleLineSymbol',
    'esri/symbols/SimpleFillSymbol',
    'esri/geometry/Polyline',
    'esri/geometry/Polygon',
    'esri/graphic',
    'esri/geometry/Extent',
    'esri/units',
    'esri/SpatialReference',
    'esri/tasks/LengthsParameters',
    'dijit/form/HorizontalRule',
    'dijit/form/HorizontalRuleLabels',

    'dijit/form/HorizontalSlider',
    //Print Plus Enhancements END

    'dijit/form/Form',
    'dijit/form/FilteringSelect',
    'dijit/form/ValidationTextBox',
    'dijit/form/NumberTextBox',
    'dijit/form/Button',
    'dijit/form/CheckBox',
    'dijit/ProgressBar',
    'dijit/form/DropDownButton',
    'dijit/TooltipDialog',
    'dijit/form/RadioButton',

    'xstyle/css!./PrintPlus/css/Print.css'

], function (
    declare,
    _WidgetBase,
    _TemplatedMixin,
    _WidgetsInTemplateMixin,
    PrintTask,
    Memory,
    lang,
    array,
    Style,
    domConstruct,
    domClass,

    printTemplate,
    printResultTemplate,

    aspect,
    PrintTemplate,
    PrintParameters,
    esriRequest,
    esriConfig,
    GraphicsLayer,

    //Print Plus Enhancements BEGIN
    dom,
    number,
    Color,
    SimpleLineSymbol,
    SimpleFillSymbol,
    Polyline,
    Polygon,
    Graphic,
    Extent,
    Units,
    SpatialReference,
    LengthsParameters,
    HorizontalRule,
    HorizontalRuleLabels
    //Print Plus Enhancements END

) {

    // Print result dijit
    var PrintResultDijit = declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], {
        widgetsInTemplate: true,
        templateString: printResultTemplate,
        url: null,
        postCreate: function () {
            this.inherited(arguments);
            this.fileHandle.then(lang.hitch(this, '_onPrintComplete'), lang.hitch(this, '_onPrintError'));
        },
        _onPrintComplete: function (data) {
            if (data.url) {
                this.url = data.url;
                this.nameNode.innerHTML = '<span class="bold">' + this.docName + '</span>';
                domClass.add(this.resultNode, 'printResultHover');
            } else {
                this._onPrintError('Error, try again');
            }
        },
        _onPrintError: function (err) {
            console.log(err.toString()); //lcs - BUG - added toString()
            this.nameNode.innerHTML = '<span class="bold">Error, try again</span>';
            domClass.add(this.resultNode, 'printResultError');
        },
        _openPrint: function () {
            if (this.url !== null) {
                window.open(this.url);
            }
        }
    });

    // Main print dijit
    var PrintDijit = declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], {
        widgetsInTemplate: true,
        templateString: printTemplate,
        map: null,
        count: 1,
        results: [],
        authorText: null,
        copyrightText: null,
        defaultTitle: null,
        defaultFormat: null,
        defaultLayout: null,
        growler: null,
        //Print Enhancements BEGIN
        noTitleBlockPrefix: null,
        layoutParams: null,
        relativeScale: null,
        relativeScaleFactor: null,
        scalePrecision: null,
        mapScales: null,
        outWkid: null,
        showLayout: null,
        suspendExtentHandler: false,
        // scaleLabelMaps defines the location of scale labels for an array of scales with length equal to the index in this array + 1
        // (e.g. an array of scales with length 7 would have labels for the first, third, fifth, and seventh scales ([0, 2, 4, 6]))
        scaleLabelMaps: [[0], [0, 1], [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4], [0, 5], [0, 2, 4, 6], [0, 7], [0, 2, 4, 6, 8], [0, 3, 6, 9], [0, 5, 10], [0, 11], [0, 6, 12],
            [0, 13], [0, 7, 14], [0, 5, 10, 15], [0, 4, 8, 12, 16], [0, 17], [0, 9, 18], [0, 19], [0, 5, 10, 15, 20], [0, 7, 14, 21], [0, 11, 22], [0, 23],
            [0, 6, 12, 18, 24], [0, 5, 10, 15, 20, 25], [0, 13, 26], [0, 9, 18, 27], [0, 7, 14, 21, 28], [0, 29], [0, 10, 19, 30], [0, 8, 16, 23, 31],
            [0, 8, 16, 24, 32], [0, 11, 22, 33], [0, 17, 34], [0, 7, 14, 24, 28, 35], [0, 9, 18, 28, 36], [0, 37], [0, 19, 38], [0, 13, 25, 39]],
        //Print Enhancements END
        //
        baseClass: 'gis_PrintPlusDijit',
        printTaskURL: null,
        printTask: null,

        postCreate: function () {
            this.inherited(arguments);
            this.printTask = new PrintTask(this.printTaskURL);
            this.printparams = new PrintParameters();
            this.printparams.map = this.map;
            this.printparams.outSpatialReference = (this.outWkid && typeof this.outWkid !== 'undefined') ? new SpatialReference(this.outWkid) : this.map.spatialReference;
            if (this.showLayout) {
                this.mapUnitsToMeters = this.getUnitToMetersFactor(this.map._params.units); //Print Plus Enhancements (this may break in future API releases)
                if (isNaN(this.mapUnitsToMeters.x) || isNaN(this.mapUnitsToMeters.y)) {
                    this.showLayout = false;
                } else if (this.map._params.units === Units.DECIMAL_DEGREES || this.map.spatialReference.isWebMercator) {
                    this.printGL = new GraphicsLayer({
                        id: 'printGraphics',
                        opacity: 1.0
                    });
                    this.map.addLayer(this.printGL);
                    this.printGL.enableMouseEvents(); //Print Plus Enhancements
                    var e = this.map.extent; // change this to get the initial extent???
                    var lineN = new Polyline(this.map.spatialReference);
                    lineN.addPath([[e.xmin, e.ymax], [e.xmax, e.ymax]]);
                    var lineS = new Polyline(this.map.spatialReference);
                    lineS.addPath([[e.xmin, e.ymin], [e.xmax, e.ymin]]);
                    var lineE = new Polyline(this.map.spatialReference);
                    lineE.addPath([[e.xmax, e.ymax], [e.xmax, e.ymin]]);
                    var lineW = new Polyline(this.map.spatialReference);
                    lineW.addPath([[e.xmin, e.ymax], [e.xmin, e.ymin]]);
                    var eDims = {
                        x: e.getWidth(),
                        y: e.getHeight()
                    };
                    var lp = new LengthsParameters();
                    lp.polylines = [lineN, lineS, lineE, lineW];
                    lp.lengthUnit = esriConfig.defaults.geometryService.UNIT_METER;
                    lp.geodesic = true;
                    esriConfig.defaults.geometryService.lengths(lp,
                        lang.hitch(this, function (result) {
                            if (result.lengths.length === 4) {
                                var southRatio = (result.lengths[0] / eDims.x);
                                var northRatio = (result.lengths[1] / eDims.x);
                                var westRatio = (result.lengths[2] / eDims.y);
                                var eastRatio = (result.lengths[3] / eDims.y);
                                // put in a check to fail if the ratios are too different?
                                //var variation = [Math.abs(1 - (southRatio / northRatio)), Math.abs(1 - (westRatio / eastRatio))];

                                this.mapUnitsToMeters.x *= (southRatio + northRatio) / 2;
                                this.mapUnitsToMeters.y *= (westRatio + eastRatio) / 2;
                            } else {
                                this.showLayout = false;
                                if (this.growler) {
                                    this.growler.growl({
                                        title: 'Get Map Units to Layout Units Conversion Factors',
                                        message: 'Calculating conversion factors failed.  Print layouts will not be shown on the map.'
                                    });
                                }
                            }
                        }),
                        lang.hitch(this, function () {
                            this.showLayout = false;
                            if (this.growler) {
                                this.growler.growl({
                                    title: 'Get Map Units to Layout Units Conversion Factors',
                                    message: 'Calculating conversion factors failed.  Print layouts will not be shown on the map.'
                                });
                            }
                        })
                    );
                } else {
                    this.printGL = new GraphicsLayer({
                        id: 'printGraphics',
                        opacity: 1.0
                    });
                    this.map.addLayer(this.printGL);
                    this.printGL.enableMouseEvents();
                }
            }
            if (this.showLayout) {
                this.printRequested = false;
                this.initializeFeatureLayers();
            }

            if (this.parentWidget && this.parentWidget.toggleable) {
                this.own(aspect.after(this.parentWidget, 'toggle', lang.hitch(this, function () {
                    this._onLayoutChange(this.parentWidget.open);
                })));
            }

            esriRequest({
                url: this.printTaskURL,
                content: {
                    f: 'json'
                },
                handleAs: 'json',
                callbackParamName: 'callback',
                load: lang.hitch(this, '_handlePrintInfo'),
                error: lang.hitch(this, '_handleError')
            });
            //aspect.after(this.printTask, '_createOperationalLayers', this.operationalLayersInspector, false);
        },

        operationalLayersInspector: function (opLayers) {
            array.forEach(opLayers, function (layer) {
                if (layer.id === 'Measurement_graphicslayer') {
                    array.forEach(layer.featureCollection.layers, function (fcLayer) {
                        array.forEach(fcLayer.featureSet.features, function (feature) {
                            delete feature.attributes;
                            feature.symbol.font.family = 'Courier';
                        //feature.symbol.font.variant = esri.symbol.Font.VARIANT_NORMAL;
                        //feature.symbol.font.size = '32pt';
                        });
                    });
                }
            });
            return opLayers;
        },

        initializeFeatureLayers: function () {
            this.featureLayerStatus = [];
            array.forEach(this.map.graphicsLayerIds, lang.hitch(this, function (item) {
                var l = this.map.getLayer(item);
                if (l.hasOwnProperty('type') && l.type === 'Feature Layer') {
                    this.featureLayerStatus.push({
                        id: l.id,
                        value: true
                    });
                    l.on('update-start', lang.hitch(this, function () {
                        array.forEach(this.featureLayerStatus, function (startItem) {
                            if (startItem.id === l.id) {
                                startItem.value = false;
                            }
                        });
                    }));
                    l.on('update-end', lang.hitch(this, function () {
                        array.forEach(this.featureLayerStatus, function (endItem) {
                            if (endItem.id === l.id) {
                                endItem.value = true;
                            }
                        });
                        this.printAndRestoreSettings();
                    }));
                }
            }));
        },

        _handleError: function (err) {
            console.log(1, err.toString()); //lcs - BUG - added toString()
        },

        _handlePrintInfo: function (data) {
            //Print Plus Enhancements BEGIN (somehow, this is required to set the value of this.noTitleBlockPrefix)
            /*
            var dummy = new Memory({
                data: this.noTitleBlockPrefix
            });
            */
            var prefix = this.noTitleBlockPrefix;
            //Print Plus Enhancements END

            var layoutTemplate = array.filter(data.parameters, function (param) {
                return param.name === 'Layout_Template';
            });
            if (layoutTemplate.length === 0) {
                console.log('print service parameters name for templates must be "Layout_Template"');
                return;
            }
            //var allLayoutItems = Layout_Template[0].choiceList;  //Print Plus Enhancements
            var layoutItems = array.map(layoutTemplate[0].choiceList, function (item) {
                return {
                    name: item,
                    id: item
                };
            });

            //Print Plus Enhancements BEGIN
            // Filter out the No Title Block templates
            layoutItems = array.filter(layoutItems, function (item) {
                return item.name.indexOf(prefix) !== 0;
            });

            // Replace the names with the aliases
            var keys = null;
            if (this.layoutParams) {
                keys = Object.keys(this.layoutParams);
                array.forEach(layoutItems, lang.hitch(this, function (item) {
                    var index = array.indexOf(keys, item.id);
                    if (index) {
                        var key = keys[index];
                        if (key) {
                            var layout = this.layoutParams[key];
                            if (layout) {
                                item.name = layout.alias;
                            }
                        }
                    }
                }));

            }

            // Sort the layouts in the order they are listed in layoutParams (config.js).  If a layout is not included in layoutParams
            // (and has not been eliminated by the noTitleBlockPrefix filter above), put it at the end of the list.
            layoutItems.sort(function (a, b) {
                var bIndex = array.indexOf(keys, b.id);
                return (bIndex !== -1) ? array.indexOf(keys, a.id) - bIndex : -1;
            });

            // Replace this sort with the above sort
            // layoutItems.sort(function (a, b) {
            // return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0);
            // });
            //Print Plus Enhancements END

            var layout = new Memory({
                data: layoutItems
            });
            this.layoutDijit.set('store', layout);
            if (this.defaultLayout) {
                this.layoutDijit.set('value', this.defaultLayout);
            } else {
                this.layoutDijit.set('value', layoutTemplate[0].defaultValue);
            }

            var Format = array.filter(data.parameters, function (param) {
                return param.name === 'Format';
            });
            if (Format.length === 0) {
                console.log('print service parameters name for format must be "Format"');
                return;
            }
            var formatItems = array.map(Format[0].choiceList, function (item) {
                return {
                    name: item,
                    id: item
                };
            });
            formatItems.sort(function (a, b) {
                return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0);
            });
            var format = new Memory({
                data: formatItems
            });
            this.formatDijit.set('store', format);
            if (this.defaultFormat) {
                this.formatDijit.set('value', this.defaultFormat);
            } else {
                this.formatDijit.set('value', Format[0].defaultValue);
            }
        },

        //Print Plus Enhancements - added this function
        print: function () {
            if (!this.printGL) {
                // If not showing the layout footprint, just call the original print function (renamed to submitPrintJob).
                this.printRequested = false;
                this.submitPrintJob();
            } else if (this.printSettingsFormDijit.get('value').layout === 'MAP_ONLY') {
                // For MAP_ONLY, just call the original print function (renamed to submitPrintJob).
                this.printRequested = false;
                this.submitPrintJob();
                return;
            } else if (!this.printRequested) {
                // Don't do anything if a print has already been requested.
                // If a print has not been requested, get the current map extent,
                // graphics layer opacity, and LODs (if the map has LODs).
                this.printRequested = true;
                var printScale = this.scaleSliderDijit.get('value');
                this.oldExtent = this.map.extent;
                this.oldOpacity = this.printGL.opacity;
                this.printGL.opacity = 0;
                this.printGL.redraw();
                this.oldLODs = null;
                this.suspendExtentHandler = true; //Suspend the Extent change handler
                var deferred = null;
                if (this.map._params.tileInfo && this.map._params.tileInfo.lods) {
                    this.oldLODs = this.map._params.tileInfo.lods;
                    // Set one LOD for the printing scale and zoom to it.
                    var resolution = printScale / (this.oldLODs[0].scale / this.oldLODs[0].resolution);
                    this.map._params.tileInfo.lods = [{
                        'level': 0,
                        'resolution': resolution,
                        'scale': printScale
                    }];
                    deferred = this.map.centerAndZoom(this.mapAreaCenter, 0);
                } else {
                    // Zoom to the print scale
                    deferred = this.map.centerAndZoom(this.mapAreaCenter, printScale / this.map.getScale());
                }
                deferred.then(lang.hitch(this, 'printAndRestoreSettings'));
            }
        },

        //Print Plus Enhancements - added this function
        printAndRestoreSettings: function () {
            // This function is called by the print function and every feature layer's 'update-end' event.
            // Don't do anything unless the print button was clicked and all feature layers have been updated.
            var layersSet = array.every(this.featureLayerStatus, function (item) {
                return item.value;
            });
            if (this.printRequested && layersSet) {
                this.printRequested = false;

                this.submitPrintJob();

                // Restore the map extent, the graphics layer opacity, and the LODs (if the map has LODs).
                if (this.oldLODs) {
                    this.map._params.tileInfo.lods = this.oldLODs;
                }
                var deferred = this.map.setExtent(this.oldExtent);
                deferred.then(lang.hitch(this, function () {
                    this.suspendExtentHandler = false;
                    this.printGL.opacity = this.oldOpacity;
                    this.printGL.redraw();
                }));
            }
        },

        //Print Plus Enhancements - changed the name of this function from "print" to "submitPrintJob"
        submitPrintJob: function () {
            if (this.printSettingsFormDijit.isValid()) {
                var form = this.printSettingsFormDijit.get('value');
                var preserve = this.preserveFormDijit.get('value');
                if (form.layout !== 'MAP_ONLY') {
                    // Set this here so it doesn't change the user's settings for MAP_ONLY
                    preserve.preserveScale = true;
                }
                lang.mixin(form, preserve);
                var layoutForm = this.layoutFormDijit.get('value');
                var mapQualityForm = this.mapQualityFormDijit.get('value');
                var dpi = mapQualityForm.dpi;
                var mapOnlyForm = this.mapOnlyFormDijit.get('value');
                mapOnlyForm.width *= dpi;
                mapOnlyForm.height *= dpi;
                if (mapOnlyForm.printUnits === Units.CENTIMETERS) {
                    mapOnlyForm.width /= 2.54;
                    mapOnlyForm.height /= 2.54;
                }
                lang.mixin(mapOnlyForm, mapQualityForm);

                var template = new PrintTemplate();
                template.format = form.format;
                //template.layout = form.layout;
                template.layout = form.titleBlock[0] ? form.layout : this.noTitleBlockPrefix + form.layout; //Print Plus Enhancements
                template.preserveScale = form.preserveScale;
                template.label = form.title;
                template.exportOptions = mapOnlyForm;
                template.layoutOptions = {
                    authorText: form.author,
                    copyrightText: this.copyrightText,
                    legendLayers: (layoutForm.legend.length > 0 && layoutForm.legend[0]) ? null : [],
                    titleText: form.title,
                    scalebarUnit: layoutForm.scalebarUnit
                };
                this.printparams.template = template;
                var fileHandel = this.printTask.execute(this.printparams);

                var result = new PrintResultDijit({
                    count: this.count.toString(),
                    iconClass: (form.format === 'PDF') ? 'pdfIcon' : 'imageIcon',
                    docName: form.title,
                    title: form.format + ', ' + form.layout,
                    fileHandle: fileHandel
                }).placeAt(this.printResultsNode, 'last');
                result.startup();
                Style.set(this.clearActionBarNode, 'display', 'block');
                this.count++;
            } else {
                this.printSettingsFormDijit.validate();
            }
        },

        clearResults: function () {
            domConstruct.empty(this.printResultsNode);
            Style.set(this.clearActionBarNode, 'display', 'none');
            this.count = 1;
        },

        //Print Plus Enhancements BEGIN
        _onTitleBlockChange: function () {
            // Don't draw a map sheet if the conversion from map units to meters is not known (e.g. Geographic Coordinate Systems)
            if (!this.showLayout) {
                return;
            }

            this.setPrintOptionVisibilities();
            this.printGL.clear();
            this.setScaleRanges();
            this.drawMapSheet(this.mapAreaCenter);
        },

        _onLayoutChange: function (layout) {
            // Called with a boolean argument whenever the widget is opened or closed (true means it is open).
            // The layout dropdown calls the function with the name of the layout on the change event.

            // Set the cursor position to the left end of the value
            // (in case the value is longer than the available space).
            var layoutNode = this.layoutDijit;
            layoutNode.focusNode.selectionStart = 0;
            layoutNode.focusNode.selectionEnd = 0;

            // Set the visibilities of the controls based on the layout selected.
            this.setPrintOptionVisibilities();

            // Don't draw a map sheet if the conversion from map units to meters is not known (e.g. Geographic Coordinate Systems)
            if (!this.showLayout) {
                return;
            }

            if (this.domNode.scrollHeight === 0) {
                return;
            }

            this.printGL.clear();

            if (typeof layout === 'boolean') {
                if (layout) {
                    var form = this.printSettingsFormDijit.get('value');
                    layout = form.layout;
                } else {
                    if (this._zoomEndHandler) {
                        this._zoomEndHandler.remove();
                        this._zoomEndHandler = null;
                    }
                    if (this._panEndHandler) {
                        this._panEndHandler.remove();
                        this._panEndHandler = null;
                    }
                    return;
                }
            }

            this.mapSheetParams = null;

            if (layout === 'MAP_ONLY') {
                // Delete the map sheet graphic and remove its mouse handlers
                this.printGL.clear();
                if (this._zoomEndHandler) {
                    this._zoomEndHandler.remove();
                    this._zoomEndHandler = null;
                }
                if (this._panEndHandler) {
                    this._panEndHandler.remove();
                    this._panEndHandler = null;
                }

                // Set the map sketch to reflect the settings
                this._adjustMapSketch();
            } else {
                if (!this._zoomEndHandler) {
                    this._zoomEndHandler = this.map.on('zoom-end', lang.hitch(this, 'adjustLayoutToMap'));
                }
                if (!this._panEndHandler) {
                    this._panEndHandler = this.map.on('pan-end', lang.hitch(this, 'adjustLayoutToMap'));
                }
                for (var key in this.layoutParams) {
                    if (key === layout) {
                        var layoutParam = this.layoutParams[key];
                        var layoutUnitsToMeters = this.getUnitToMetersFactor(layoutParam.units);
                        this.mapSheetParams = {
                            layout: layout,
                            pageSize: layoutParam.pageSize,
                            pageMargins: layoutParam.pageMargins,
                            mapSize: layoutParam.mapSize,
                            titleBlockOffsets: layoutParam.titleBlockOffsets,
                            unitRatio: {
                                x: layoutUnitsToMeters.x / this.mapUnitsToMeters.x,
                                y: layoutUnitsToMeters.y / this.mapUnitsToMeters.y
                            }
                        };
                        break;
                    }
                }

                // unknown layout so we can't draw the graphics
                //if (!this.mapSheetParams) {
                //  return;
                //}

                var centerPt = this.map.extent.getCenter();
                //var printScale = this.scaleSliderDijit.get('value');
                this.setScaleRanges();
                this.drawMapSheet(centerPt);
                //var moveStartPt;
                //var mapSheetMoving = false;
                this.printGL.on('mouse-down', lang.hitch(this, function (evt) {
                    if (evt.graphic.id === this.mapSheetGraphicId) {
                        this.map.disablePan();
                        this.mapSheetMoving = true;
                        this.moveStartPt = evt.mapPoint;
                    }
                }));
                this.printGL.on('mouse-drag', lang.hitch(this, function (evt) {
                    if (evt.graphic.id === this.mapSheetGraphicId && this.mapSheetMoving) {
                        var moveEndPt = evt.mapPoint;
                        var xOffset = moveEndPt.x - this.moveStartPt.x;
                        var yOffset = moveEndPt.y - this.moveStartPt.y;
                        this.moveMapSheet(evt.graphic, xOffset, yOffset);
                        this.moveStartPt = moveEndPt;
                        if (!this.map.extent.contains(this.mapAreaExtent)) {
                            this.adjustMapToLayout();
                        }
                    }
                }));
                this.printGL.on('mouse-over', lang.hitch(this, function (evt) {
                    if (evt.graphic.id === this.mapSheetGraphicId) {
                        this.map.setMapCursor('move');
                    }
                }));
                this.printGL.on('mouse-out', lang.hitch(this, function (evt) {
                    if (evt.graphic.id === this.mapSheetGraphicId) {
                        this.map.setMapCursor('default');
                    }
                    this.map.enablePan();
                    this.mapSheetMoving = false;
                }));
                this.printGL.on('mouse-up', lang.hitch(this, function (evt) {
                    if (evt.graphic.id === this.mapSheetGraphicId && this.mapSheetMoving) {
                        this.map.enablePan();
                        this.mapSheetMoving = false;
                    }
                }));
            }
        },

        _onScaleBoxChange: function (value) {
            this.scaleSliderDijit.set('value', value);
        },

        _onScaleSliderChange: function (value) {
            // Don't draw a map sheet if the conversion from map units to meters is not known (e.g. Geographic Coordinate Systems)
            if (!this.showLayout) {
                return;
            }

            this.scaleBoxDijit.set('value', value);

            //var form = this.printSettingsFormDijit.get('value');
            //var layout = form.layout;
            this.relativeScaleDijit.innerHTML = this.relativeScale.replace('[value]', number.format(value * this.relativeScaleFactor, {
                places: this.scalePrecision
            }));
            this.printGL.clear();
            if (!this.mapAreaCenter) {
                this.mapAreaCenter = this.map.extent.getCenter();
            }
            this.drawMapSheet(this.mapAreaCenter);
        },

        setPrintOptionVisibilities: function () {
            var form = this.printSettingsFormDijit.get('value');
            var layout = form.layout;
            var titleBlock = form.titleBlock[0];
            var state = null;
            if (!this.showLayout && layout === 'MAP_ONLY') {
                //Don't show the page layout graphic options, but show the 'MAP_ONLY' options
                state = 'A';
            } else if (!this.showLayout) {
                //Don't show the page layout graphic options or the 'MAP_ONLY' options
                state = 'B';
            } else if (layout === 'MAP_ONLY') {
                //Don't show the page layout graphic options, but show the 'MAP_ONLY' options
                state = 'C';
            } else if (titleBlock) {
                //Show the map layout graphic options for the layout template with a title block, but don't show the 'MAP_ONLY' options
                state = 'D';
            } else {
                //Show the page layout graphic options for the layout template without a title block, don't show the 'MAP_ONLY' options
                state = 'E';
            }

            var printOptions = {
                'relativeScaleDijit': {
                    A: 'none',
                    B: 'none',
                    C: 'none',
                    D: 'inline',
                    E: 'inline'
                },
                'scaleBoxRowDijit': {
                    A: 'none',
                    B: 'none',
                    C: 'none',
                    D: 'table-row',
                    E: 'table-row'
                },
                'scaleSliderRowDijit': {
                    A: 'none',
                    B: 'none',
                    C: 'none',
                    D: 'table-row',
                    E: 'table-row'
                },
                'titleDomDijit': {
                    A: 'none',
                    B: 'none',
                    C: 'none',
                    D: 'inline',
                    E: 'inline'
                },
                'mapScaleHeaderDijit': {
                    A: 'inline',
                    B: 'inline',
                    C: 'inline',
                    D: 'none',
                    E: 'none'
                },
                'layoutHeaderDijit': {
                    A: 'none',
                    B: 'inline',
                    C: 'none',
                    D: 'inline',
                    E: 'none'
                },
                'mapOnlyHeaderDijit': {
                    A: 'inline',
                    B: 'none',
                    C: 'inline',
                    D: 'none',
                    E: 'none'
                },
                'mapSketchDijit': {
                    A: 'none',
                    B: 'none',
                    C: 'block',
                    D: 'none',
                    E: 'none'
                }
            };
            // Add the map sketch settings here

            for (var key in printOptions) {
                if (printOptions.hasOwnProperty(key)) {
                    var dijit = this[key];
                    if (!dijit) {
                        dijit = dom.byId(key);
                    }
                    if (dijit) {
                        var style = dijit.style;
                        if (style) {
                            style.display = printOptions[key][state];
                        }
                    }
                }
            }
        },

        drawMapSheet: function (centerPt) {
            if (!this.mapSheetParams) {
                return;
            }

            var scale = this.scaleSliderDijit.get('value');
            var titleBlock = this.titleBlockDijit.get('value');
            var pageSize = this.mapSheetParams.pageSize;
            var unitRatio = this.mapSheetParams.unitRatio;
            var mapOffsets = null;
            var mapDims = null;

            if (titleBlock) {
                mapOffsets = this.mapSheetParams.pageMargins;
                mapDims = this.mapSheetParams.mapSize;
            } else {
                mapOffsets = this.mapSheetParams.titleBlockOffsets;
                mapDims = {
                    x: pageSize.x - mapOffsets.x * 2,
                    y: pageSize.y - mapOffsets.y * 2
                };
            }

            // Calculate the boundaries for the print area
            var minX = centerPt.x - mapDims.x / 2 * scale * unitRatio.x;
            var minY = centerPt.y - mapDims.y / 2 * scale * unitRatio.y;
            var maxX = centerPt.x + mapDims.x / 2 * scale * unitRatio.x;
            var maxY = centerPt.y + mapDims.y / 2 * scale * unitRatio.y;

            // List the points in counter-clockwise order (this is the hole for the map)
            var printAreaRing = [[minX, minY], [maxX, minY], [maxX, maxY], [minX, maxY], [minX, minY]];

            // Update the map area center and extent so other functions can use them
            this.mapAreaCenter = centerPt;
            this.mapAreaExtent = new Extent(minX, minY, maxX, maxY, this.map.spatialReference);

            // Calculate the boundaries for the sheet boundary
            minX -= (mapOffsets.x * scale * unitRatio.x);
            minY -= (mapOffsets.y * scale * unitRatio.y);
            maxX = minX + pageSize.x * scale * unitRatio.x;
            maxY = minY + pageSize.y * scale * unitRatio.y;

            // list the points in clockwise order (this is the paper)
            var printPageRing = [[minX, minY], [minX, maxY], [maxX, maxY], [maxX, minY], [minX, minY]];

            var printPageGeom = new Polygon(this.map.spatialReference);
            printPageGeom.addRing(printAreaRing);
            printPageGeom.addRing(printPageRing);

            var polygonSymbol = new SimpleFillSymbol(SimpleFillSymbol.STYLE_SOLID,
                new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([33, 33, 33, 0.8]), 1),
                new Color([255, 0, 0, 0.5]));

            var gra = new Graphic(printPageGeom, polygonSymbol);
            gra.id = this.mapSheetGraphicId;
            this.printGL.add(gra);
        },

        moveMapSheet: function (gra, xOffset, yOffset) {
            this.mapAreaCenter = this.mapAreaCenter.offset(xOffset, yOffset);
            this.mapAreaExtent = this.mapAreaExtent.centerAt(this.mapAreaCenter);

            var polygon = new Polygon(gra.geometry);
            for (var i = 0; i < polygon.rings.length; i++) {
                for (var j = 0; j < polygon.rings[i].length; j++) {
                    var pt = polygon.getPoint(i, j).offset(xOffset, yOffset);
                    polygon.setPoint(i, j, pt);
                }
            }
            gra.setGeometry(polygon);
            gra.getLayer().redraw();
        },

        _adjustMapSketch: function () {
            // Get the preserve, width, and height settings and draw the map sketch if there is enough data
            var preserveScale = this.preserveFormDijit.get('value').preserveScale === 'true';
            var mapOnlyForm = this.mapOnlyFormDijit.get('value');
            var printWidth = mapOnlyForm.width;
            var printHeight = mapOnlyForm.height;
            var printUnits = mapOnlyForm.printUnits;

            //if (printWidth <= 0 || printHeight <= 0) {
            // Can't draw the sketch
            //}

            var max = 150; // This should be coordinated with the width of the layoutDropDownDijit
            var offset = 80; // This should be coordinated with the width of the layoutDropDownDijit

            // The Dims object is like a Point, only much leaner
            function Dims (x, y) {
                this.x = x;
                this.y = y;
            }

            // Get the dimensions of the paper map and the map in the browser window projected onto paper.
            var paperAspectRatio = printWidth / printHeight;
            var browserAspectRatio = this.map.width / this.map.height;
            var paperMapDims = new Dims(printWidth, printHeight);
            var browserMapDims = null;
            if (preserveScale) {
                var layoutUnitsToMeters = this.getUnitToMetersFactor(printUnits);
                var unitRatio = {
                    x: layoutUnitsToMeters.x / this.mapUnitsToMeters.x,
                    y: layoutUnitsToMeters.y / this.mapUnitsToMeters.y
                };
                var scale = this.map.getScale();
                browserMapDims = new Dims(this.map.extent.getWidth() / scale / unitRatio.x, this.map.extent.getHeight() / scale / unitRatio.y);
            } else if (paperAspectRatio > browserAspectRatio) {
                browserMapDims = new Dims(paperMapDims.x * browserAspectRatio / paperAspectRatio, paperMapDims.y);
            } else {
                browserMapDims = new Dims(paperMapDims.x, paperMapDims.y * paperAspectRatio / browserAspectRatio);
            }

            // Normalize the map sizes to the space available
            var normalize = Math.max(paperMapDims.x, paperMapDims.y, browserMapDims.x, browserMapDims.y) / max;
            paperMapDims = new Dims(paperMapDims.x / normalize, paperMapDims.y / normalize);
            browserMapDims = new Dims(browserMapDims.x / normalize, browserMapDims.y / normalize);

            // Calculate the offsets required to center both rectangles
            var paperMapOffset = new Dims((offset + max - paperMapDims.x) / 2, Math.max(0, (browserMapDims.y - paperMapDims.y) / 2));
            var browserMapOffset = new Dims((offset + max - browserMapDims.x) / 2, Math.max(0, (paperMapDims.y - browserMapDims.y) / 2));

            // Draw the paper map extent
            var paperMap = this.paperMapDijit;
            paperMap.style.position = (paperMapDims.y <= browserMapDims.y) ? 'absolute' : 'relative';
            paperMap.style.width = paperMapDims.x.toFixed(0) + 'px';
            paperMap.style.height = paperMapDims.y.toFixed(0) + 'px';
            paperMap.style.left = paperMapOffset.x.toFixed(0) + 'px';
            paperMap.style.top = paperMapOffset.y.toFixed(0) + 'px';

            // Draw the extent of the map in the  browser window
            var browserMap = this.browserMapDijit;
            browserMap.style.position = (browserMapDims.y < paperMapDims.y) ? 'absolute' : 'relative';
            browserMap.style.width = browserMapDims.x.toFixed(0) + 'px';
            browserMap.style.height = browserMapDims.y.toFixed(0) + 'px';
            browserMap.style.left = browserMapOffset.x.toFixed(0) + 'px';
            browserMap.style.top = browserMapOffset.y.toFixed(0) + 'px';

        },

        adjustLayoutToMap: function (evt) {
            if (!this.suspendExtentHandler) {
                var centerPt = null;
                if (evt.hasOwnProperty('level')) {
                    // This was a zoom, so reset the scales and redraw the map sheet
                    this.setScaleRanges();
                    centerPt = evt.extent.getCenter();
                    this.printGL.clear();
                    this.drawMapSheet(centerPt);
                }
                if (this.mapAreaExtent && !evt.extent.contains(this.mapAreaExtent)) {
                    // Map extent does not contain the layout's map area, so get correction
                    var correction = this.getCorrection();
                    centerPt = this.mapAreaCenter.offset(correction.x, correction.y);
                    this.printGL.clear();
                    this.drawMapSheet(centerPt);
                }
            }
        },

        adjustMapToLayout: function () {
            var correction = this.getCorrection();
            this.suspendExtentHandler = true;
            this.map.setExtent(this.map.extent.offset(-correction.x, -correction.y));
            this.suspendExtentHandler = false;
        },

        getCorrection: function () {
            var correction = {
                x: 0,
                y: 0
            };
            var mapExtent = this.map.extent;
            if (mapExtent.xmin > this.mapAreaExtent.xmin) {
                correction.x = mapExtent.xmin - this.mapAreaExtent.xmin;
            }
            if (mapExtent.ymin > this.mapAreaExtent.ymin) {
                correction.y = mapExtent.ymin - this.mapAreaExtent.ymin;
            }
            if (mapExtent.xmax < this.mapAreaExtent.xmax) {
                correction.x = mapExtent.xmax - this.mapAreaExtent.xmax;
            }
            if (mapExtent.ymax < this.mapAreaExtent.ymax) {
                correction.y = mapExtent.ymax - this.mapAreaExtent.ymax;
            }
            return correction;
        },

        setScaleRanges: function () {
            if (!this.mapSheetParams) {
                return;
            }

            var form = this.printSettingsFormDijit.get('value');
            var titleBlock = form.titleBlock[0];
            var titleBlockOffsets = this.mapSheetParams.titleBlockOffsets;
            var layout = this.mapSheetParams.layout;
            var pageSize = this.mapSheetParams.pageSize;
            var mapSize = this.mapSheetParams.mapSize;
            var unitRatio = this.mapSheetParams.unitRatio;
            var maxScale = null;
            var mapExtent = this.map.extent;

            //get the maximum scale of the map
            if (layout === 'MAP_ONLY') {
                return;
            }

            if (!isNaN(unitRatio.x) && !isNaN(unitRatio.y)) {
                if (titleBlock) {
                    maxScale = Math.ceil(Math.min(mapExtent.getHeight() / mapSize.y / unitRatio.y,
                        mapExtent.getWidth() / mapSize.x / unitRatio.x));
                } else {
                    maxScale = Math.ceil(Math.min(mapExtent.getHeight() / (pageSize.y - titleBlockOffsets.y * 2) / unitRatio.y,
                        mapExtent.getWidth() / (pageSize.x - titleBlockOffsets.x * 2) / unitRatio.x));
                }
            } else {
                maxScale = this.map.scale;
            }

            function getPrimeFactors (value) {
                // This function returns an array of the factors of value.
                // The numbers 1 and value are never in the array, so if value is a prime number, a zero length array is returned.

                var primeFactors = [];
                var newValue = value;
                for (var i = 2; i < value; i++) {
                    while (newValue % i === 0) {
                        newValue /= i;
                        primeFactors.push(i);
                    }

                    if (newValue === 1) {
                        break;
                    }
                }
                return primeFactors;
            }

            function getSnapInterval (scales) {
                // This function returns the largest common factor in the scales array.
                var largestFactor = 1;
                var minScale = scales[scales.length - 1];
                var factors = getPrimeFactors(minScale);
                var failedFactors = [];
                var tryFactor = null;
                var i = null,
                    j = null;

                for (i = 0; i < factors.length; i++) {
                    // if a tryFactor has failed once, don't try it again
                    if (array.indexOf(failedFactors, largestFactor * factors[i]) === -1) {
                        tryFactor = largestFactor * factors[i];
                        // ignore the largest scale - it was calculated, not taken from the list
                        for (j = 1; j < scales.length - 1; j++) {
                            if (scales[j] % tryFactor !== 0) {
                                break;
                            }
                        }

                        if (j === scales.length - 1) {
                            largestFactor = tryFactor;
                        } else {
                            failedFactors.push(tryFactor);
                        }
                    }
                }

                return largestFactor;
            }

            function getValidScales (mapScales) {
                var validScales = [];
                var minScale = Math.ceil(maxScale / 7);

                for (var i = 0; i < mapScales.length; i++) {
                    var scale = mapScales[i];
                    if (scale < maxScale) {
                        validScales.push(scale);
                    }
                    if (scale < minScale) {
                        break;
                    }
                }
                return validScales;
            }

            function getSnapScale (scale, scales) {
                var snapScale = null;
                if (scale === 0) {
                    // Scale is 0; the widget is just being opened.  Return the scale that will set
                    // the sheet graphic to the largest size that does not include the entire map extent.
                    snapScale = scales[1];
                } else if (array.indexOf(scales, scale) !== -1) {
                    // Scale is one of the values in scales; return scale
                    snapScale = scale;
                } else if (scale > scales[0]) {
                    // Scale is larger than the largest value in scales; return the second largest value in scales
                    snapScale = scales[1];
                } else if (scale < scales[scales.length - 1]) {
                    // Scale is smaller than the smallest value in scales; return the smallest value in scales
                    snapScale = scales[scales.length - 1];
                } else {
                    // Return the value in scales that is closest to scale
                    for (var i = 1; i < scales.length; i++) {
                        if (scale <= scales[i - 1] && scale >= scales[i]) {
                            if (scales[i - 1] - scale > scale - scales[i]) {
                                snapScale = scales[i];
                            } else {
                                snapScale = scales[i - 1];
                            }
                            break;
                        }
                    }
                }
                return snapScale;
            }

            function getLabels (minScale, snapInterval, scaleIndices) {
                var labelArray = [],
                    scale = null;

                for (var i = 0; i < scaleIndices.length; i++) {
                    scale = minScale + (snapInterval * scaleIndices[i]);
                    labelArray.push(number.format(scale, {
                        pattern: '###,###'
                    }));
                }
                return labelArray;
            }

            // set the ranges on the scale slider
            var scaleArray = getValidScales(this.mapScales);
            maxScale = scaleArray[0];
            var snapInterval = getSnapInterval(scaleArray);
            var minScale = scaleArray[scaleArray.length - 1];
            var oldScale = this.scaleSliderDijit.get('value');
            var startingScale = getSnapScale(oldScale, scaleArray);
            var discreteScales = ((maxScale - minScale) / snapInterval) + 1;
            this.scaleBoxDijit.set('value', startingScale);
            this.scaleBoxDijit.set('data-dojo-props', 'constraints:{min:' + minScale.toString() + ',max:' + maxScale.toString() + ',places:0,pattern:"000,000"}');
            this.scaleBoxDijit.set('invalidMessage', 'Invalid scale');
            this.scaleSliderDijit.set('minimum', minScale);
            this.scaleSliderDijit.set('maximum', maxScale);
            this.scaleSliderDijit.set('value', startingScale);
            this.scaleSliderDijit.set('discreteValues', discreteScales);

            // The scale labels (number of labels based on number of scales in scaleArray)
            var labels = getLabels(minScale, snapInterval, this.scaleLabelMaps[discreteScales - 1]);

            // The ticks for each scale increment
            if (this.sliderRule) {
                this.sliderRule.destroy();
            }
            var scaleSliderDom = this.scaleSliderDomDijit;
            var ruleNode = domConstruct.create('div', {}, scaleSliderDom, 'last');
            this.sliderRule = new HorizontalRule({
                container: 'bottomDecoration',
                count: discreteScales,
                ruleStyle: 'border-width: thin;',
                style: 'width: 94%; left: 3%; height: 8px;'
            }, ruleNode);
            this.sliderRule.startup();

            // The longer ticks for each scale label
            if (this.sliderRule1) {
                this.sliderRule1.destroy();
            }
            var ruleNode1 = domConstruct.create('div', {}, scaleSliderDom, 'last');
            this.sliderRule1 = new HorizontalRule({
                container: 'bottomDecoration',
                count: labels.length,
                ruleStyle: 'border-width: thin;',
                style: 'width: 94%; left: 3%; height: 4px;'
            }, ruleNode1);
            this.sliderRule1.startup();

            // The scale labels
            if (this.sliderLabels) {
                this.sliderLabels.destroy();
            }
            var labelsNode = domConstruct.create('div', {}, scaleSliderDom, 'last');
            this.sliderLabels = new HorizontalRuleLabels({
                container: 'bottomDecoration',
                labels: labels,
                style: 'width: 94%; left: 3%; height: 1em; font-size: 10px;'
            }, labelsNode);
            this.sliderLabels.startup();

        },
        getUnitToMetersFactor: function (unit) {
            switch (unit) {
            case Units.CENTIMETERS:
                return {
                    x: 0.01,
                    y: 0.01
                };
            case Units.DECIMETERS:
                return {
                    x: 0.1,
                    y: 0.1
                };
            case Units.FEET:
                return {
                    x: 0.3048,
                    y: 0.3048
                };
            case Units.INCHES:
                return {
                    x: 0.0254,
                    y: 0.0254
                };
            case Units.KILOMETERS:
                return {
                    x: 100.0,
                    y: 100.0
                };
            case Units.METERS:
                return {
                    x: 1.0,
                    y: 1.0
                };
            case Units.MILES:
                return {
                    x: 1609.344,
                    y: 1609.344
                };
            case Units.MILLIMETERS:
                return {
                    x: 0.001,
                    y: 0.001
                };
            case Units.NAUTICAL_MILES:
                return {
                    x: 1852.0,
                    y: 1852.0
                };
            case Units.YARDS:
                return {
                    x: 0.9144,
                    y: 0.9144
                };
            case Units.UNKNOWN_UNITS:
                return {
                    x: NaN,
                    y: NaN
                };
            case Units.DECIMAL_DEGREES:
                return {
                    x: 1.0,
                    y: 1.0
                };
            default:
                return {
                    x: NaN,
                    y: NaN
                };
            }
        }
    //Print Plus Enhancements END
    });

    return PrintDijit;
});