AngularJS - dynamic rows and columns with a two way bound custom control

user2728841 picture user2728841 · Feb 6, 2014 · Viewed 9.2k times · Source

I am new to Angular and am stuck on my next step of development which is to have a custom control bound to a dynamic row-column table.

I have a simple fiddle here which shows how to data bind a custom control:

http://jsfiddle.net/paull3876/WPWAc/2/

And another fiddle here which is my starting point, shows how to bind a row-column table with data driven column names:

http://jsfiddle.net/paull3876/3mz5L/1/

<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <title>Angular</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.min.js"></script>

<script>

var app = angular.module("myApp", []);

function datacontroller($scope, $http)
{
    $scope.mydata = [{f1:"r1f1", f2:"r1f2"}, {f1:"r2f1",f2:"r2f2"}, {f1:"r3f1",f2:"r3f2", f3:"Hello"}];
    $scope.mycolumns = [{name:"Column 1", fieldname:"f1"}, {name:"Column 2", fieldname:"f2"}, {name:"Column 3", fieldname:"f3"}];

    $scope.showdata = function()
    {
        alert(JSON.stringify($scope.mydata));
    }

    $scope.getcolumnname = function(cell)
    {
        return cell.fieldname;
    }
}

</script>

</head>
<body>
    <div data-ng-controller="datacontroller">
        <table>
        <tr>
        <td data-ng-repeat="cell in mycolumns">{{cell.name}}</td>
        </tr>
        <tr data-ng-repeat="record in mydata">
        <td data-ng-repeat="cell in mycolumns">
        <input type="text" data-ng-init="mycol=getcolumnname(cell);" data-ng-model="record[mycol]" />
        </td>
        </tr>
        </table>

        <br />
        <input type="button" value="Save Data" ng-click="showdata()" />
        <br />
        <br />

    </div>

</body>
</html>

Now I want to take the second fiddle above, and replace the INPUT element with a user control which has two way data binding. I've spend a day on this already and can't get it working, so I guess I'm also needing some help on the concepts here

An explanation on top of a solution greatly appreciated.

http://jsfiddle.net/paull3876/rc7uC/1/

Answer

user2728841 picture user2728841 · Feb 7, 2014

Here's the full working solution, I hope someone finds it useful.

<!DOCTYPE html>
<html >
<head>
    <meta http-equiv="pragma" content="no-cache">
    <meta http-equiv="expires" content="-1">
    <title>Angular</title>
    <!--scr ipt src="htt ps://ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.min.js"></script-->
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.js"></script>
</head>
<body>
    <div data-ng-app="myApp" data-ng-controller="datacontroller">
        <table>
            <tr>
                <td data-ng-repeat="cell in mycolumns">{{cell.name}}</td>
            </tr>
            <tr data-ng-repeat="record in mydata">
                <td data-ng-repeat="cell in mycolumns">
                    <mycontrol data-my-model="record[cell.fieldname]"></mycontrol>
                </td>
            </tr>
        </table>
        <br />
        <input type="button" value="Save Data" ng-click="showdata()" />
        <input type="button" value="Change Divs" onclick="changediv()" />
        <input type="button" value="Change Scope" onclick="changescope()" />
        <br />
        <br />
    </div>
</body>
</html>
<script>
    var globalscope;
    var app = angular.module("myApp", [])
        .directive("mycontrol", function ($compile) {
        return {
            restrict: "E",
            scope: {
                value: "=myModel"
            },
            template: "<div data-ng-bind='value'/>"
        };
    });

    function datacontroller($scope, $http) {
        globalscope = $scope;
        var mydata = [];
        // generate some data
        for (var i = 0; i < 20; i++)
        {
            var row = {
                f1:"f1x" + i, 
                f2:"f2x" + i, 
                f3:"f3x"+i, 
                f4:"f4x"+i,
                f5:"f5x"+i,
                f6:"f6x"+i,
                f7:"f7x"+i,
                f8:"f8x"+i,
                f9:"f9x"+i,
                f10:"f10x"+i,
                f11:"f11x"+i,
                f12:"f12x"+i,
                f13:"f13x"+i
                };
            mydata.push(row);
        }

        // push it to angulars scope
        $scope.mydata = mydata;

        // generate some metadata for the columns
        $scope.mycolumns = [{
            name: "Column 1",
            fieldname: "f1",
            type: "input"
        }, {
            name: "Column 2",
            fieldname: "f2",
            type: "textarea"
        }, {
            name: "Column 3",
            fieldname: "f3",
            type: "div"
        }, {
            name: "Column 4",
            fieldname: "f4",
            type: "div"
        }, {
            name: "Column 5",
            fieldname: "f5",
            type: "div"
        }, {
            name: "Column 6",
            fieldname: "f6",
            type: "div"
        }, {
            name: "Column 7",
            fieldname: "f7",
            type: "div"
        }, {
            name: "Column 8",
            fieldname: "f8",
            type: "div"
        }, {
            name: "Column 9",
            fieldname: "f9",
            type: "div"
        }, {
            name: "Column 10",
            fieldname: "f10",
            type: "div"
        }, {
            name: "Column 11",
            fieldname: "f11",
            type: "div"
        }, {
            name: "Column 12",
            fieldname: "f12",
            type: "div"
        }, {
            name: "Column 13",
            fieldname: "f13",
            type: "div"
        }];

        $scope.showdata = function () {
            alert(JSON.stringify($scope.mydata));
        };

        $scope.getcolumnname = function (cell) {
            return cell.fieldname;
        };
    }

    function changediv()
    {
        // this will change the data in the divs but it won't reflect back to the scope

        var divs = document.getElementsByClassName("fred");
        for (var i = 0; i < divs.length; i++)
        {
            var div = divs[i];
            div.innerText = "XXXX";
        }

    }

    function changescope()
    {
        // shows how to change data programmatically and have it reflected in the controls and in the scope data

        var scope = globalscope;
        for (r in scope.mydata)
        {
            scope.mydata[r].f3 = "UUUU";
        }
        scope.$apply();
    }
</script>