0

I'm generating nested form from a json object say formObject and binding the values in the json object itself. I'm parsing the values recursively and pulling out the actual data say dataObject at submit.

I can retrieve the dataObject in a linear form like this one. http://jsfiddle.net/DrQ77/80/.

<select ng-model="Answers[question.Name]" ng-options="option for option in question.Options">

In contrast to the above, http://jsfiddle.net/DrQ77/92/ has some recursion. I've renamed question to element for it to represent both questions & sections. Every section can have multiple questions & again, multiple sections (this is what I meant by nesting). What I want eventually is an object in a form below with any level of nesting.

Answers=[{
    section:"Personal",
    values:[{GenderQuestion:"Male"},{MaritalStatus:"Married"},{section:"Sub Personal",values:[{LivingWith:"Alone"}]}]
}, {
    section:"Random",
    values:[{ColorQuestion:"Red"}],
},
{SectionLess:"opt1"}]

This is one solution & I can get it on submit, $scope.Answers from the first fiddle does not (I think) allow that kind of nesting. But when I have to update an existing dataObject, I felt a need to map the dataObjects onto the formObject before rendering it, and then parsing again on submit. Now this isn't MVC, doesn't look elegant (due to the recursion) & I think there's an 'angular way' for this.

Has anybody tried this & got it working in a better fashion? How do I get around it?

4
  • 2
    I didn't quite understand problem with your approach. Maybe it's because the example on JSFiddle is not complex enough - there's no mentioned 'recursion' and the need of 'parsing again on submit' - $scope.Answers is constantly up-to-date and ready to be submitted at any time, due to Angular's data-binding. But maybe it's because I'm too sleepy :) Anyways, It'd be cool if you could make the fiddle more elaborated (but not too much - hello-worldish style appreciated) to fully illustrate downsides of your current approach. Commented Feb 7, 2014 at 8:37
  • 2
    As @vucalur said, I too don't quite understand what the problem is in the fiddle. I also do not see any recursion in the example. Can you update your question and explain better what your problems are? Commented Feb 7, 2014 at 11:20
  • Yes, tried to explain a bit! Sorry for not elaborating the problem statement the first time, I've weird ideas about brevity! Commented Feb 8, 2014 at 7:26
  • @vucalur, is the question clearer now? Or do I need to elaborate it any more? Commented Feb 10, 2014 at 4:15

4 Answers 4

1

Not very pretty, but this is an alternative to the solution you've already proposed.

http://jsfiddle.net/DrQ77/84/

function QuestionController($scope) {
    $scope.answers = {};
    $scope.tempOption = "";
    $scope.questions = [
    {
        "text": "Gender?",
        "name": "GenderQuestion",
        "options": ["Male", "Female"]},
    {
        "text": "Favorite color?",
        "name": "ColorQuestion",
        "options": ["Red", "Blue", "Green"]}
    ];

    $scope.showAnswers = function () {
      console.log($scope.answers);
    };

    $scope.pumpOption = function (name, tempOption) {
      $scope.answers[name] = tempOption;
    };
};

<ul ng-repeat="question in questions">
    <li>
        <div>{{question.text}}</div>
        <select ng-model="tempOption" ng-options="opt for opt in question.options" ng-change="pumpOption(question.name, tempOption)">
        </select>
    </li>
</ul>    

We bind the value of the selected option in the select tag to a $scope.tempOption variable.

We then listen for the ng-change event to occur on this select tag, where we run a function that takes the $scope.tempOption variable plus the {{question.name}} associated with the select tag.

This function then sets answers[name] to the current value of $scope.tempOption.

Hope this works out for you, good luck :)

Sign up to request clarification or add additional context in comments.

1 Comment

yes, this works, but things look a lot ugly under recursion when I have the json data with nested objects. I currently have a solution like, but it isn't MVC & there's a bit of work to be done for updating fields via the same form.
1
var model = {
    colors:["Red","Blue","Green","Black","White"],
    genders:["Male", "Female"],
    topic:["Gender", "Color"],
    category:["Favorite", "Least favorite"],
};

function makeQuestion(topic, category){
    return (category+1 ? model.category[category] + ' ' : '') 
    + ' ' + model.topic[topic] + '?'
}

function QuestionController($scope){

    $scope.Answers = {};
    $scope.Questions = [
        {
            "Text": makeQuestion(0),
            "Name": "GenderQuestion",
            "Options": model.genders 
        },{
            "Text": makeQuestion(1,0),
            "Name": "ColorQuestion",
            "Options": model.colors.slice(0,3)
        },{
            "Text": makeQuestion(0,1),
            "Name": "SexistQuestion",
            "Options": model.genders
        },{
            "Text": makeQuestion(1,1),
            "Name": "RacistQuestion",
            "Options":model.colors.slice(3)
        }
    ];

    $scope.ShowAnswers = function()
    {
        console.log($scope.Answers);
    };
}    

Ok, i was kidding around. But have you tried using a flat related object relational table approach instead of nested?

{
  sections:[
    { "Name": 'Personal', "questions":[0], sub:[1] },
    { "Name": 'SubPersonal', "questions":[3], sub:[]},
    { "Name": 'Random',   "questions":[1,2], sub:[] }
  ],
  questions:[
    { "Name":"Gender",     "Text":"Gender?",         "Options":["Male", "Female"] },
    { "Name":"Color",      "Text":"Favorite Color?", "Options":["Red","Blue","Green"] },
    { "Name":"LivingWith", "Text":"Living With?",    "Options":["Alone","Someone"] },
    { "Name":"Random",     "Text":"SectionLess",     "Options":["opt1", "opt2"] }
  ]
}

2 Comments

Yes, but then again, I'll have to write something to generate the original object. I don't want to represent my data models in flat tables!
How else do you expect to write a recursive JSON object relationship? I mean, there's a reason why JSON.stringify doesn't allow circular references. Parent = { child:{ parent:{ child:{ parent:{ child: {see what i mean?} } } } } }
0

simply create $scope.Answer = {}; and link ng-model to it. http://jsfiddle.net/2AwLM/40/

1 Comment

yes, it's one way, but it still doesn't answer the problem of nesting. E.g. I can't use Answers[0].values[0].GenderQuestion without referencing it's element Answer[0].values[0].
0

Ok - this wasn't as trivial as I first thought, so I hope it's what you're after - you'll want to use a directive that dynamically generates its template HTML (better yet, use templateUrl and a function which passes in type)

TLDR: See the fiddle at http://jsfiddle.net/cirrusinno/SzaNW/2

The HTML is quite simple

<div ng-controller="questionsController">

<!-- for each question, render section or question -->
<div ng-repeat="item in Questions">
    <!-- we ignore section here as it's a root section -->
    <question item="item"></question>
</div>

You'll need to create a directive that can render both questions & sections. It will also need to keep track of the section so it can build the answers

.directive('question', ['$compile', '$templateCache', function ($compile, $templateCache) {
    var generateHtmlTemplate = function (item, section) {
        return item.type != 'section' ?
        // render the question    
        'question ' + item.name + '<br/>' +
            '<select ng-model="optionValue" ng-options="opt for opt in item.options" ng-change="pushAnswer(item.name, section, optionValue)"></select>' :
        // or the template for a section
        '<br/><hr/>section ' + item.sectionName + '<br/>' +
            '<div ng-repeat="q in item.questions"><question item="q" section="item"></question></div><hr/>';
    }

    var currentSection = null;

    return {
        scope: {
            item: '=',
            section: '='
        },
        restrict: 'E',
        link: function ($scope, element, attrs) {
            // check current section
            if ($scope.item.type == 'section') {
                // set new current section
                $scope.currentSection = $scope.item;
            } else {
                // use section passed in from parent section
                $scope.currentSection = $scope.section;
            }

            // get template html
            var t = generateHtmlTemplate($scope.item, $scope.currentSection);

            // set the scope function to set the value
            $scope.pushAnswer = function (q, section, value) {
                // build the Answers object here however you want...
                if (section != null) {
                    console.log('pushAnswer q=' + q.name + ' - section=' + section.sectionName + ' - value=' + value);
                } else {
                    console.log('pushAnswer q=' + q.name + ' - section=rootSection - value=' + value);
                }

            };

            // chuck it into element as template
            element.html(t);

            // compile it up and return it
            $compile(element.contents())($scope);
        },
    };
}])

2 Comments

Okay, but the mapping an existing model is a problem. I had thought of making directives for individual elements to interject a parent model object for a nested element in ng-model,. I think I should try that!
I've just used the existing model that was given in other questions - if you have a separate model, please post it and we'll apply that to the directive. In any case, the directive will drill down into the model. Creating a "one size fits all" directive would be a much more complex task. I also thought of using two directives, one for a section, one for questions but I started using one, and it just work gradually. Perhaps two directives would be cleaner, as the scope variables would be a little cleaner (ie: A section doesn't need a currentSection, it just passes it into a child question).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.