Camera Shapes in Spread for WinForms allow the developer to take snapshots of content in a range of cells for use in other areas of the sheet, or different sheets entirely. This can help with organizing and presenting data in one cohesive view without having to worry about constraints on that view. In particular, camera shapes help make creating dashboards and complex reports simple and easy. In this blog post, I will show you how to create a custom CameraShape class for use in SpreadJS. Finished Report with SpreadJS Camera Shapes Finished Report with SpreadJS Camera Shapes To download the sample used in this blog post, see here: SpreadJS Camera Shape

Creating the Camera Shape

SpreadJS does not have a Camera Shape built into it like Spread for Winforms, but one can be created by extending the CustomFloatingObject class. To start off, create a JavaScript file called CameraShape.js. In that file, add some code to initialize a CustomFloatingObject and a new class called CameraShape that calls the base class initializer:


var CustomFloatingObject = GcSpread.Sheets.CustomFloatingObject;  
function CameraShape(name, spread, srcSheet, rangeRowStart, rangeRowEnd, rangeColStart, rangeColEnd) {  
    CustomFloatingObject.call(this, name);  
}  
CameraShape.prototype = Object.create(CustomFloatingObject.prototype);  
CameraShape.prototype.constructor = CustomFloatingObject;  

The bulk of the code for the CameraShape will go into the initializer function, so the rest of this post will be about adding code to that function. Add some code to that function to set up the floating object and create some variables we will be using later:


function CameraShape(name, spread, srcSheet, rangeRowStart, rangeRowEnd, rangeColStart, rangeColEnd) {  
    CustomFloatingObject.call(this, name);  

    var sizeAdjustment = 3;  

    var innerDiv = document.createElement('div');  

    innerDiv.innerHTML = "<div id='" + name + "' style='width: 100%; height: 100%; border: " + sizeAdjustment + "px solid gray;'></div>";  
    this.Content(innerDiv);  

    var innerSpread;  
    var destSheet = spread.getActiveSheet();  
}  

The next step is to create a Spread instance inside the CameraShape and use to/fromJSON to copy the data from the source sheet to the inner Spread instance, essentially creating the CameraShape:


function loadInnerSpread() {  
        // Create a new Spread instance and load its properties  
        innerSpread = new GcSpread.Sheets.Spread(document.getElementById(name));   
        reloadInnerSpread();  
    }  

    function reloadInnerSpread() {  
        // Reload the Spread instance's properties  
        // If the camera shape is on the same sheet as the source sheet, then find the control already on the page.  
        var floatingObjects = spread.getActiveSheet().getFloatingObjects();  
        for (var i = 0; i < floatingObjects.length; i++) {  
            if (floatingObjects[i].name() === name) {  
                innerSpread = GcSpread.Sheets.findControl(document.getElementById(name));  
            }  
        }  

        innerSpread.isPaintSuspended(true);  
        innerSpread.tabStripVisible(false);  
        innerSpread.showHorizontalScrollbar(false);  
        innerSpread.showVerticalScrollbar(false);  

        var innerActiveSheet = innerSpread.getSheet(0);  

        // Copy the sheet from the source Spread instance to destination Spread instance via JSON  
        var sourceSheetJSON = srcSheet.toJSON();  
        innerActiveSheet.fromJSON(sourceSheetJSON);  
        innerActiveSheet.setName("inner_" + innerActiveSheet.getName());  

        // Move the view to the selected data in the sheet  
        innerActiveSheet.showCell(rangeRowStart, rangeColStart, GcSpread.Sheets.VerticalPosition.top, GcSpread.Sheets.HorizontalPosition.left);  

        // Set properties of the active sheet in the inner Spread instance  
        innerActiveSheet.setFrozenTrailingRowCount(innerActiveSheet.getRowCount() - rangeRowStart);  
        innerActiveSheet.setColumnHeaderVisible(false);  
        innerActiveSheet.setRowHeaderVisible(false);  
        innerActiveSheet.setIsProtected(true);  
        innerActiveSheet.protectionOption().allowSelectLockedCells = false;  
        innerActiveSheet.allowCellOverflow(true);  

        spread.focus(true);  
        innerSpread.isPaintSuspended(false);  
    }  

We want the new Spread instance in the CameraShape to load when the CameraShape loads. To do this, bind a function to the current sheet’s CustomFloatingObjectLoaded event that calls these new functions we created, and change the width and height to reflect the data:


destSheet.bind(GcSpread.Sheets.Events.CustomFloatingObjectLoaded, function (sender, args) {  
    if (args.customFloatingObject.name() === name) {  
        loadInnerSpread();  

        innerActiveSheet = innerSpread.getActiveSheet();  

        // Set size of camera shape to size of range  
        var height = 0;  
        var width = 0;  
        for (var r = rangeRowStart; r <= rangeRowEnd; r++) {  
            height += innerActiveSheet.getRowHeight(r);  
        }  
        for (var c = rangeColStart; c <= rangeColEnd; c++) {  
            width += innerActiveSheet.getColumnWidth(c);  
        }  
        args.customFloatingObject.height(height + sizeAdjustment);  
        args.customFloatingObject.width(width + sizeAdjustment);  
        args.customFloatingObject.allowResize(false);  
        $("#" + name).height(height);  
        $("#" + name).width(width);  

        innerSpread.refresh();  
    }  
});  

Binding Events

The code for the CameraShape thus far would create one on a Sheet with the specified row and column bounds. However, code must be added to update the cells, columns, and widths in the CameraShape when the user changes data:


srcSheet.bind(GcSpread.Sheets.Events.CellChanged, function (sender, args) {  
    // Update data and styles of cells in camera shape  
    if ((args.row >= rangeRowStart || args.row <= rangeRowEnd) && (args.col >= rangeColStart || args.col <= rangeColEnd)) {  
        reloadInnerSpread();  
    }  
});  

Code also has to be added to account for the user changing row heights and columns widths of the range:


srcSheet.bind(GcSpread.Sheets.Events.RowHeightChanged, function (e, info) {  
    // Update row heights of cells in camera shape  
    reloadInnerSpread();  
    changeHeight();  
});  

srcSheet.bind(GcSpread.Sheets.Events.ColumnWidthChanged, function (e, info) {  
    // Update column widths of cells in camera shape  
    reloadInnerSpread();  
    changeWidth();  
});  

function changeHeight() {  
    // Set height of camera shape to height of range  
    var height = 0;  
    var innerActiveSheet = innerSpread.getActiveSheet();  
    for (var r = rangeRowStart; r <= rangeRowEnd; r++) {  
        height += srcSheet.getRowHeight(r);  
    }  
    var floatingObjects = destSheet.getFloatingObjects();  
    for (var i = 0; i < floatingObjects.length; i++) {  
        if (floatingObjects[i].name() === name) {  
            floatingObjects[i].height(height + sizeAdjustment);  
        }  
    }  
    $("#" + name).height(height);  
    innerSpread.refresh();  
}  

function changeWidth() {  
    // Set width of camera shape to width of range  
    var width = 0;  
    var innerActiveSheet = innerSpread.getActiveSheet();  
    for (var c = rangeColStart; c <= rangeColEnd; c++) {  
        width += srcSheet.getColumnWidth(c);  
    }  
    var floatingObjects = destSheet.getFloatingObjects();  
    for (var i = 0; i < floatingObjects.length; i++) {  
        if (floatingObjects[i].name() === name) {  
            floatingObjects[i].width(width + sizeAdjustment);  
        }  
    }  
    $("#" + name).width(width);  
    innerSpread.refresh();  
}  

Test Page

With this code, you will have a CameraShape that you can use just as easily as a CustomFloatingObject on a sheet. To test it out, we will create a sample page that utilizes CameraShapes to show data in specific areas of a sheet. In this example, we will use a SSJSON file that already has Sheets with data inside of them:

  • A Report sheet to show all the data in one view.
  • A Sales and Expenses sheet that has data for sales and expenses on it.
  • A Products sheet that has data about the products for the report.

The Sales & Expenses Sheet The Sales & Expenses Sheet The Products sheet The Products Sheet First, create a simple HTML page with an instance of SpreadJS on it:


<!DOCTYPE html>  
<html>  
<head>  
    <title>SpreadJS Camera Shape</title>  
<meta charset="utf-8" />  
    <script src="http://code.jquery.com/jquery-2.1.3.min.js" type="text/javascript"></script>  

    <script type="text/javascript" src="http://cdn.grapecity.com/spreadjs/hosted/scripts/gcspread.sheets.all.9.40.20161.0.min.js"></script>  
    <link href="http://cdn.grapecity.com/spreadjs/hosted/css/gcspread.sheets.9.40.20161.0.css" rel="stylesheet" type="text/css">  

    <script type="text/javascript" src="CameraShape.js"></script>  
</head>  
<body>  
    <div id="ss" style="width: 100%; height: 600px; border: 1px solid gray;"></div>  
</body>  
</html>  

Then add some code to load the SSJSON file into that SpreadJS Instance:


window.onload = function () {  
    var spread = new GcSpread.Sheets.Spread(document.getElementById("ss"), { sheetCount: 2 });  

    $.ajax({  
        url: "CameraShapeSheet.ssjson",  
        datatype: "json",  
        success: function (data) {  
            //Load ssjson file.  
            spread.isPaintSuspended(true);  
            spread.fromJSON(JSON.parse(data));                      
            spread.isPaintSuspended(false);  
        },  
        error: function (ex) {  
            alert('Exception:' + ex);  
        }  
    });  
}  

The Report Sheet without Camera Shapes The Report Sheet without Camera Shapes In that success function, add code to create two CameraShapes for each sheet to show on the Report sheet:


//Load ssjson file.  
spread.isPaintSuspended(true);  
spread.fromJSON(JSON.parse(data));  

spread.setActiveSheet("Report");  
var activeSheet = spread.getActiveSheet();  

var salesExpenseCS = new CameraShape("SalesAndExpenses", spread, spread.getSheet(1), 1, 11, 1, 9);  
salesExpenseCS.startRow(5);  
salesExpenseCS.startRowOffset(7);  
salesExpenseCS.startColumn(1);  
activeSheet.addFloatingObject(salesExpenseCS);  

var productsCS = new CameraShape("Products", spread, spread.getSheet(2), 1, 6, 1, 8);  
productsCS.startRow(19);  
productsCS.startRowOffset(7);  
productsCS.startColumn(1);  
activeSheet.addFloatingObject(productsCS);  

spread.isPaintSuspended(false);  

spread.refresh();  

This code will place the CameraShapes in the Report sheet so that the user does not have to switch sheets to see different data. Finished Report with SpreadJS Camera Shapes Finished Report with SpreadJS Camera Shapes In this blog, we created a CameraShape class that extends the CustomFloatingObject class. By adding code to create an instance of SpreadJS inside the CameraShape and events to bind to, we created functionality similar to the Camera Shape in Spread for WinForms. To download a free trial of SpreadJS, go here: http://spread.grapecity.com/downloads/