How can I validate a group within another group and maintain separate error messages? - jquery-validate

I'm trying to use the 'require_from_group_update' method with jquery validator and have run into a snag I need help with. I working with multiple forms (24 in total) and each has many, many fields. In some instances, I need to validate groups of questions. As long as the groups are exclusive, this works great, but in some instances, for example, I need to have 5 total answers but at least 3 from the last 4 questions.
So that could mean 1 question in the first group is required and 4 from the second or 2 from the first and 3 from the second, etc. In my real form, the forms have between 10 and 20 questions long with different required counts for each.
I created the example with the fields in separate groups but really wanted to see if it was possible to check if 3 of the last 4 questions are answered and that 5 out of the total 8 are also answered. I tried to have all questions in one group and the last 4 in another - but that didn't work. Here I just picked 8 as example - in reality, there can be 10 to 20 total questions with varying requirements for each.
Many thanks in advance - I hope this is clear and concise enough.
Sample code is below - or there's a fiddle as well - jsfiddle.net/43rdworld/qZ6rn/4/
<!DOCTYPE html>
<html lang="en">
<head>
<title>Online Nomination Form</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script>
<script src="//ajax.aspnetcdn.com/ajax/jquery.validate/1.11.0/jquery.validate.js"></script>
<style type="text/css" media="all">
textarea {margin-left:20px;}
.wrapper {width: 200px;clear:both;float:left;margin-bottom:10px;}
div.errorContainer {width:200px;float:left;font-size:9pt;}
</style>
<script>
$(document).ready(function () {
$("#theForm").validate({
groups: {
group1: "sm_Appreciation,sm_Impact,sm_Safety,sm_People,sm_Parents,sm_Goals,sm_Money,sm_Decision",
group2: "sm_Parents,sm_Goals,sm_Money,sm_Decision"
},
rules: {
sm_Appreciation: {require_from_group_updated: [2, ".smg1"]},
sm_Impact: {require_from_group_updated: [2, ".smg1"]},
sm_Safety: {require_from_group_updated: [2, ".smg1"]},
sm_People: {require_from_group_updated: [2, ".smg1"]},
sm_Parents: {require_from_group_updated: [3, ".smg2"]},
sm_Goals: {require_from_group_updated: [3, ".smg2"]},
sm_Money: {require_from_group_updated: [3, ".smg2"]},
sm_Decision: {require_from_group_updated: [3, ".smg2"]},
},
errorPlacement: function(error, element) {
if ((element.attr("name") === "sm_Appreciation") || (element.attr("name") === "sm_Impact") || (element.attr("name") === "sm_Safety") || (element.attr("name") === "sm_People")) {
error.appendTo("#smError1");
} else if ((element.attr("name") === "sm_Parents") || (element.attr("name") === "sm_Goals") || (element.attr("name") === "sm_Money") || (element.attr("name") === "sm_Decision")) {
error.appendTo("#smError2");
}
},
});
jQuery.validator.addMethod('require_from_group_updated', function (value, element, options) {
var numberRequired = options[0];
var selector = options[1];
var fields = $(selector, element.form);
var filled_fields = fields.filter(function () {
// it's more clear to compare with empty string
return $(this).val() != "";
});
var empty_fields = fields.not(filled_fields);
// we will mark only first empty field as invalid
if (filled_fields.length < numberRequired && empty_fields[0] == element) {
return false;
}
return true;
// {0} below is the 0th item in the options field
}, jQuery.format("* Please fill out at least {0} of these fields."));
});
</script>
</head>
<body>
<form name="theForm" id="theForm" method="post" action="" autocomplete="off">
<h4>Answer 5 of the following 8 questions, with at least 3 from questions 4-8</h4>
<div class="wrapper">
<label for="sm_Appreciation">1. Question 1</label><br>
<textarea name="sm_Appreciation" id="sm_Appreciation" class="smg1" rows="3" cols="20" maxlength="200" tabindex="1"></textarea>
</div>
<div id="smError1" class="errorContainer"></div>
<div class="wrapper">
<label for="sm_Impact">2. Question 2</label><br>
<textarea name="sm_Impact" id="sm_Impact" class="smg1" rows="3" cols="20" maxlength="200" tabindex="2"></textarea>
</div>
<div class="wrapper">
<label for="sm_Safety">3. Question 3</label><br>
<textarea name="sm_Safety" id="sm_Safety" class="smg1" rows="3" cols="20" maxlength="200" tabindex="3"></textarea>
</div>
<div class="wrapper">
<label for="sm_People">4. Question 4</label><br>
<textarea name="sm_People" id="sm_People" class="smg1" rows="3" cols="20" maxlength="200" tabindex="4"></textarea>
</div>
<h4 style="clear:both;">Answer at least 3 questions from questions 4 through 8</h4>
<div class="wrapper">
<label for="sm_Parents">5. Question 5</label><br>
<textarea name="sm_Parents" id="sm_Parents" class="smg2" rows="3" cols="20" maxlength="200" tabindex="5"></textarea>
</div>
<div id="smError2" class="errorContainer"></div>
<div class="wrapper">
<label for="sm_Goals">6. Question 6</label><br>
<textarea name="sm_Goals" id="sm_Goals" class="smg2" rows="3" cols="20" maxlength="200" tabindex="6"></textarea>
</div>
<div class="wrapper">
<label for="sm_Money">7. Question 7</label><br>
<textarea name="sm_Money" id="sm_Money" class="smg2" rows="3" cols="20" maxlength="200" tabindex="7"></textarea>
</div>
<div class="wrapper">
<label for="sm_Decision">8. Question 8</label><br>
<textarea name="sm_Decision" id="sm_Decision" class="smg2" rows="3" cols="20" maxlength="200" tabindex="8"></textarea>
</div>
<div style="clear:both;"><input type="submit" value="submit"></div>
</form>
</body>
</html>

First I updated the plugin to version 1.11.1 and I included the corresponding version of the additional-methods.js file. Version 1.11.1 is where all the bugs were finally fixed in the require_from_group method.
Then I simply created a group within a group. You already have the right idea with your groups option, but that's only used for consolidating the error messages. Since your whole form is one group, you have to apply the require_from_group rule to all fields. Then since you have another unique group within it, you have to again apply the require_from_group rule to only those fields.
rules: {
sm_Appreciation: {
require_from_group: [5, ".smg1"]
},
sm_Impact: {
require_from_group: [5, ".smg1"]
},
sm_Safety: {
require_from_group: [5, ".smg1"]
},
sm_People: {
require_from_group: [5, ".smg1"]
},
sm_Parents: {
require_from_group: [5, ".smg1"],
require_from_group: [3, ".smg2"]
},
sm_Goals: {
require_from_group: [5, ".smg1"],
require_from_group: [3, ".smg2"]
},
sm_Money: {
require_from_group: [5, ".smg1"],
require_from_group: [3, ".smg2"]
},
sm_Decision: {
require_from_group: [5, ".smg1"],
require_from_group: [3, ".smg2"]
}
},
And finally, you can clean up your errorPlacement by using the class name rather than manually checking against each field name. I also removed the .appendTo() method because it just keeps repeating the error message creating a whole string of the same message over and over.
errorPlacement: function (error, element) {
// if it's only part of 'smg1' group and not 'smg2' sub-group
if (element.hasClass("smg1") && !element.hasClass("smg2")) {
$("#smError1").html(error);
// else if it's part of 'smg2' sub-group
} else if (element.hasClass("smg2")) {
$("#smError2").html(error);
}
},
groups: {
group1: "sm_Appreciation,sm_Impact,sm_Safety,sm_People",
group2: "sm_Parents,sm_Goals,sm_Money,sm_Decision"
}
Working DEMO: http://jsfiddle.net/2xW76/
You'll probably still need to tweak your message placement and your groups option for the exact desired effect, however, my demo shows the working logic for validation of groups within groups.

Related

element UI table get filtered data or indices of original data

I would like to access the filtered data of an Element UI Table.
https://element.eleme.io/#/en-US/component/table
Suppose the full data of the table looks like this:
$index: {value}
[
0: {A},
1: {B},
2: {C},
3: {D}
]
Now suppose I set a filter via filter-method on a column and the filtered dataset only leaves behind values B and D.
The table now looks like this:
[
0: {B},
1: {D}
]
whereas I would like to have the original indecies, or access the leftover data that the table shows.
[
1: {B},
3: {D}
]
How can I do this, does anyone have an idea?
I basically want to color my cells via cell-class-name but because of this behahiour, the cells are not colored correctly while the data is filtered. If I could access the remaining data OR the original indecies that would solve my problem.
Thank you!
<html>
<head>
<script src="https://unpkg.com/vue#2/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<div id="app">
<template>
<div>
<el-table
:data="data"
style="width: 100%;"
height="600"
>
<el-table-column
label="Index"
width="60">
<template v-slot="scope">
<div class="data">
{{ scope.$index }}
</div>
</template>
</el-table-column>
<el-table-column
label="Data"
:filters="[{text: 'A', value: 'A'}, {text: 'B', value:'B'}, {text: 'C', value: 'C'}, {text: 'D', value:'D'}]"
:filter-method="filterHandler"
width="100">
<template v-slot="scope">
<div class="data">
{{ scope.row.value }}
</div>
</template>
</el-table-column>
</el-table-column>
</el-table>
</div>
</template>
</div>
<script>
new Vue({
el: '#app', //Tells Vue to render in HTML element with id "app"
data() {
return {
data: [{value: "A"},{value: "B"},{value: "C"},{value: "D"}]
}
},
methods: {
filterHandler(value, row, column) {
return row.value === value;
}
}
});
</script>
</html>

Printing multiple ng-repeat elements in a single row

I have a list of item and ng-repeat display them in the page. Below is the simplified structure -
[
{
"id": "elem-1",
"position": {
"row": 1,
"column": 1
}
},
{
"id": "elem-2",
"position": {
"row": 2,
"column": 1
}
},
{
"id": "elem-3",
"position": {
"row": 2,
"column": 2
}
}
]
Now elem-1 should come in first row, while the second row should have elem-2 and elem-3, side-by-side. I have checked ng-repeat-start and also gone through the existing similar topics in Stack overflow but not sure how to do this. This is my HTML template
<div layout="row" ng-repeat="item in inputElemAll">
<div ng-if="if the current row has 1 column">
<md-input-container class="md-block">
<!-- Print the element-->
</md-input-container>
</div>
<div ng-if="if current row has more than 1 column">
<md-input-container class="md-block" flex="33">
<!-- Print the element-->
</md-input-container>
</div>
</div>
All I'm trying here is to add a flex attribute in case I have to show more columns in a single row. But the problem is, with every ng-repeat a new <div> with "row" layout is started and I'm stuck here. Not sure how to access multiple ng-repeat elements and also be on the same <div> simultaneously.
P.S. I use material layout, in case you are interested.
I am assuming that you wanna show your array like a table and maximum rows/columns are not greater than length of your array. You have to use two loops with ng-repeat, first one is for all rows and seconds one is for columns. also use 'orderBy' to sort. I mixed your json array and the result is as expected:
<div layout="row" ng-repeat="row in inputElemAll">
<div ng-repeat="item in inputElemAll | orderBy:'position.column'">
<div ng-if="$parent.$index==item.position.row-1">
<md-input-container class="md-block">
{{item.id}}
</md-input-container>
</div>
</div>
</div>
Check this jsFiddle working example:
http://jsfiddle.net/hd1by95r/47/

How to disable uib-timepicker arrow keys?

even after I use this it still showing up arrows
<div uib-timepicker ng-model="mytime" arrowkeys="false" show-meridian="false"></div>
Here's the plunker : https://plnkr.co/edit/j5JlXWsoldsj0iEdSUMY?p=preview
How to disable them ? Anyone knows? Their documentation states that arrows can be hidden. Is this a bug?
Angular Bootstrap timepicker plugin: link
Thank you
If you're talking about the little up and down arrows above and below the hours, minutes, and (optionally) seconds input fields, you actually want to set show-spinners="false" on the directive.
<div uib-timepicker ng-model="myDate" show-spinners="false"></div>
The arrowkeys setting is just for whether you can press up and down arrows on the keyboard while focused within the text field to increase or decrease the values.
Actually there is a small misunderstanding happened from our side regarding the arrowkeys attribute of uib-timepicker. Actually while setting arrowkeys="false" will not hide the arrow keys instead it will block the up and down arrow key events inside the text box. On setting arrowkeys="true", you can increment or decrement the time values by up and down arrow keys, on setting it to false it wont happen.
arrowkeys (Defaults: true) : Whether user can use up/down arrowkeys
inside the hours & minutes input to increase or decrease it's values.
To achieve your requirement you will need to go for a hack.
I don't know whether this is the best way or not, but what about hiding the up and down arrows. If this could solve your problem, I have attached a sample code.
<!doctype html>
<html ng-app="ui.bootstrap.demo">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.0/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.0/angular-animate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-sanitize/1.5.9/angular-sanitize.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/2.3.0/ui-bootstrap-tpls.min.js"></script>
<script>
angular.module('ui.bootstrap.demo', ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);
angular.module('ui.bootstrap.demo').controller('TimepickerDemoCtrl', function ($scope, $log) {
$scope.mytime = new Date();
$scope.hstep = 1;
$scope.mstep = 15;
$scope.options = {
hstep: [1, 2, 3],
mstep: [1, 5, 10, 15, 25, 30]
};
$scope.ismeridian = true;
$scope.toggleMode = function () {
$scope.ismeridian = !$scope.ismeridian;
};
$scope.update = function () {
var d = new Date();
d.setHours(14);
d.setMinutes(0);
$scope.mytime = d;
};
$scope.changed = function () {
$log.log('Time changed to: ' + $scope.mytime);
};
$scope.clear = function () {
$scope.mytime = null;
};
});
</script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<style>
.timepickercontainer .uib-timepicker .btn-link {
display: none;
}
</style>
</head>
<body>
<div ng-controller="TimepickerDemoCtrl">
<div class="timepickercontainer">
<div uib-timepicker ng-model="mytime" ng-change="changed()" arrowkeys="false" hour-step="hstep" minute-step="mstep" show-meridian="false"></div>
</div>
<pre class="alert alert-info">Time is: {{mytime | date:'shortTime' }}</pre>
<div class="row">
<div class="col-xs-6">
Hours step is:
<select class="form-control" ng-model="hstep" ng-options="opt for opt in options.hstep"></select>
</div>
<div class="col-xs-6">
Minutes step is:
<select class="form-control" ng-model="mstep" ng-options="opt for opt in options.mstep"></select>
</div>
</div>
<hr>
<button type="button" class="btn btn-info" ng-click="toggleMode()">12H / 24H</button>
<button type="button" class="btn btn-default" ng-click="update()">Set to 14:00</button>
<button type="button" class="btn btn-danger" ng-click="clear()">Clear</button>
</div>
</body>
</html>
Just add a div with a class as the container of time picker say timepickercontainer
then set
.timepickercontainer .uib-timepicker .btn-link {
display: none;
}

Conditional display with Dojo MVC

I have a dojox/mvc/Repeat area which is bound to an array of records.
Within the row of the Repeat there is a field (the id of the record) which should be a simple display Output if the record has already been saved to the database, but it should be a TextBox if the record is new (the user must enter the value).
How do I solve this elegantly? I am fairly new to Dojo and its MVC part is very under-documented.
The most MVC-ish solution I have found so far is as follows:
1)
I put a "hasBeenSaved" property into the model which will mark the server-side saved state of the record. This attribute will be bound to the view with a transformation since the "display" style attribute of the DIV will be bound to the hasBeenSaved model attribute (one is a boolean the other is a string: "block"/"none").
2)
Within the Row, I put a conditionally visible div around the id input field. This will be visible only when the record is new, so its display style attribute is bound with an appropriate transformer attached to the Dojo MVC binding.
The same is done for the id output field but the transformer is different on the binding since this will be displayed only when the record has already been saved.
The JSFiddle which I have used to prototype this solution: http://jsfiddle.net/asoltesz/6t4dj1w7/15/
require([
"dojo/_base/declare", "dojo/dom-style", "dojo/parser", "dojo/ready",
"dijit/_WidgetBase", "dijit/_TemplatedMixin",
'dojox/mvc/getStateful'
], function(
declare, domStyle, parser, ready,
_WidgetBase, _TemplatedMixin,
getStateful
){
// setting up the data model for MVC
model = {
items: [
{ id: 'id1',
hasBeenSaved: true
},
{ id: null,
hasBeenSaved: false
},
{ id: null,
hasBeenSaved: false
},
{ id: 'id3',
hasBeenSaved: true
}
]
};
model = getStateful(model);
/**
* This mixin makes it possible to set the "display" style property of
* the DOM node (of any widget) as a Widget property and thus bind it to an MVC model
* when needed.
*/
declare("_DisplayAttributeMixin", [], {
// parameters
display: "block",
_setDisplayAttr: function(/*String*/ display){
this._set("display", display);
domStyle.set(this.domNode, "display", display);
}
});
/** Transformer methods for converting hasBeenSaved to visible/hidden values */
transfSavedToHidden = {format: function(hasBeenSaved){
console.log("transfSavedToHidden: " + (hasBeenSaved ? "none" : "block"));
return hasBeenSaved ? "none" : "block";
}};
transfSavedToVisible = {format: function(hasBeenSaved){
console.log("transfSavedToHidden: " + (hasBeenSaved ? "block" : "none"));
return hasBeenSaved ? "block" : "none";
}};
ready(function(){
// Call the parser manually so it runs after our mixin is defined, and page has finished loading
parser.parse();
});
});
The HTML markup:
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: model"
>
<div id="repeatId"
data-dojo-type="dojox/mvc/Repeat"
data-dojo-props="children: at('rel:', 'items')"
>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: at('rel:', ${this.index})"
>
<span>Record: ${this.index}</span>
<!-- This is displayed only when the record is new (not saved yet) -->
<div
data-dojo-type="dijit/_WidgetBase"
data-dojo-mixins="_DisplayAttributeMixin"
data-mvc-bindings="
display: at('rel:', 'hasBeenSaved')
.direction(at.from)
.transform(transfSavedToHidden)"
>
<label for="idInput${this.index}">id:</label>
<input
data-dojo-type="dijit/form/TextBox"
id="idInput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></input>
</div> <!-- end conditionally hidden div -->
<!-- This is displayed only when the record has already been saved -->
<div
data-dojo-type="dijit/_WidgetBase"
data-dojo-mixins="_DisplayAttributeMixin"
data-mvc-bindings="
display: at('rel:', 'hasBeenSaved')
.direction(at.from)
.transform(transfSavedToVisible)"
>
<label for="idInput${this.index}">id:</label>
<span
data-dojo-type="dojox/mvc/Output"
id="idOutput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></span>
</div> <!-- end conditionally hidden div -->
<hr/>
</div> <!-- end of row -->
</div> <!-- end of Repeat -->
</div> <!-- end of Group -->
A secondary, less complex solution:
Bind the "hasBeenSaved" property to a hidden text within the repeating div.
Put an onChange event on the hidden field which gets the index of the repeat as well.
The onChange event simply hides the field which is not appropriate in light of the hasBeenChanged property value for the record.
The fiddle is here: http://jsfiddle.net/asoltesz/8u9js6sz/5/
Code:
hasBeenSavedChanged = function(field, index) {
var divToHide
if (field.value == true) {
divToHide = "idInputDiv"
}
else {
divToHide = "idOutputDiv"
}
var div = document.getElementById(divToHide + index);
div.style.display = "none";
}
require([
"dojo/_base/declare", "dojo/dom-style", "dojo/parser", "dojo/ready",
"dijit/_WidgetBase", "dijit/_TemplatedMixin",
'dojox/mvc/getStateful'
], function(
declare, domStyle, parser, ready,
_WidgetBase, _TemplatedMixin,
getStateful
){
// setting up the data model for MVC
model = {
items: [
{ id: 'id1',
hasBeenSaved: true
},
{ id: null,
hasBeenSaved: false
},
{ id: null,
hasBeenSaved: false
},
{ id: 'id3',
hasBeenSaved: true
}
]
};
model = getStateful(model);
ready(function(){
// Call the parser manually so it runs after our mixin is defined, and page has finished loading
parser.parse();
});
});
HTML:
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: model"
>
<span id="itemsCtl"
data-dojo-type="dojox/mvc/ListController"
data-dojo-props="model: model.items">
</span>
<div id="itemsRepeat"
data-dojo-type="dojox/mvc/Repeat"
data-dojo-props="children: at('rel:', 'items')"
>
<div
data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: at('rel:', ${this.index})"
>
<span>Record: ${this.index}</span>
<input
id="hasBeenChanged${this.index}"
data-dojo-type="dijit/form/TextBox"
data-dojo-props="value: at('rel:', 'hasBeenSaved')"
onChange="hasBeenSavedChanged(this, '${this.index}');"
type="hidden"
></input>
<!-- This is displayed only when the record is new (not saved yet) -->
<div id="idInputDiv${this.index}"
>
<label for="idInput${this.index}">id:</label>
<input
data-dojo-type="dijit/form/TextBox"
id="idInput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></input>
</div> <!-- end conditionally hidden div -->
<!-- This is displayed only when the record has already been saved -->
<div id="idOutputDiv${this.index}" >
<label for="idInput${this.index}">id:</label>
<span
data-dojo-type="dojox/mvc/Output"
id="idOutput${this.index}"
data-dojo-props="value: at('rel:', 'id')"
></span>
</div> <!-- end conditionally hidden div -->
<hr/>
</div> <!-- end of row -->
</div> <!-- end of Repeat -->
</div> <!-- end of Group -->
Mixin/transformer approach is something I had in mind, too. Two things I’d add there are out-of-the-box Dijit features, one is dijitDisplayNone class, another is attribute mapping feature.
Though it’s strange that the former is undocumented, the intent may have been for private usage within Dijit codebase.
Though it’s a bit hackish (and may be broken in future 1.x Dijit releases), overriding the Dijit code that’s responsible for attribute mapping will allow you to map a widget attribute to a CSS class that’s toggled.
Here’s a code sample that uses the above two:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dojo/resources/dojo.css">
<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dijit/themes/dijit.css">
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dojo/dojo.js" data-dojo-config="async: 1, parseOnLoad: 0"></script>
<script type="text/javascript">
require([
"dojo/_base/array",
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/dom-class",
"dojo/parser",
"dojox/mvc/getStateful",
"dijit/form/TextBox"
], function(array, declare, lang, domClass, parser, getStateful){
declare("CssToggleMixin", null, {
// summary:
// Mixin class to support widget attributes with toggleClass type.
// toggleClass type allows boolean value of an attribute to reflect existence of a CSS class in a DOM node in the widget.
_attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){
// summary:
// Handle widget attribute with toggleClass type.
// See dijit/_WidgetBase._attrToDom() for more details.
var callee = arguments.callee;
array.forEach((function(){ return lang.isArray(commands) ? commands.slice(0) : [commands]; })(arguments.length >= 3 ? commands : this.attributeMap[attr]), function(command){
command.type != "toggleClass" ?
this.inherited("_attrToDom", lang.mixin([attr, value, command], {callee: callee})) :
domClass.toggle(this[command.node || "domNode"], command.className || attr, value);
}, this);
}
});
flipConverter = {
format: function (value) {
return !value;
},
parse: function (value) {
return !value;
}
};
model = getStateful({
items: [
{
value: "Foo",
hasBeenSaved: true
},
{
hasBeenSaved: false
},
{
hasBeenSaved: false
},
{
value: "Bar",
hasBeenSaved: true
}
]
});
parser.parse();
})
</script>
</head>
<body>
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div data-dojo-type="dojox/mvc/WidgetList"
data-dojo-mixins="dojox/mvc/_InlineTemplateMixin"
data-dojo-props="children: at(model, 'items')">
<script type="dojox/mvc/InlineTemplate">
<div>
<span data-dojo-type="dijit/_WidgetBase"
data-dojo-mixins="CssToggleMixin"
data-dojo-props="value: at('rel:', 'value'),
noDisplay: at('rel:', 'hasBeenSaved').transform(flipConverter),
_setValueAttr: {node: 'domNode', type: 'innerText'},
_setNoDisplayAttr: {type: 'toggleClass', className: 'dijitDisplayNone'}"></span>
<span data-dojo-type="dijit/form/TextBox"
data-dojo-mixins="CssToggleMixin"
data-dojo-props="value: at('rel:', 'value'),
noDisplay: at('rel:', 'hasBeenSaved'),
_setNoDisplayAttr: {type: 'toggleClass', className: 'dijitDisplayNone'}"></span>
</div>
</script>
</div>
</body>
</html>
Hope it’ll shed some light.
Best, Akira

data binding in nested angularjs repeater

I have controller as follows:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.questionTypes = [
{display: 'Text', 'name': 'text'},
{display: 'Paragraph', 'name': 'textarea'},
{display: 'Multiple Choice', 'name': 'radio'},
];
$scope.top = {
heading: '',
questions: [
{
tite: 'title 1',
choices: ['']
}
]
};
});
And an HTML body as follows:
<body ng-controller="MainCtrl">
<input ng-model="top.heading" placeholder="heading"/>
<br/>
<div ng-repeat="question in top.questions track by $index">
<select ng-model="question.type" ng-options="c.name as c.display for c in questionTypes"></select>
<div ng-if="question.type == 'radio'">
<div ng-repeat="option in question.choices track by $index">
<input type="text" ng-model="option"/>
<button ng-click="question.choices.push('')" ng-disabled="$index < question.choices.length - 1">Add</button>
<button ng-click="question.choices.splice($index, 1)" ng-disabled="question.choices.length == 1">Del</button>
</div>
</div>
</div>
<pre>{{top | json}}</pre>
</body>
When the user makes the Multiple Choice selection, I want to show a fragment that provides the ability to add various choices. The choices are displayed in repeater.
That all works, but data binding on nested repeater is not working. I assuming this has something to do with scoping, but I can't figure it out.
Any help would be appreciated.
I have created a plunkr at http://plnkr.co/edit/6FxY44HgddRjrLOHlQGF
After fumbling around with this for a while, this is what I did to fix the problem.
I changed:
<input type="text" ng-model="option"/> //after changing model to ng-model
To
<input type="text" ng-model="question.choices[$index]"/>
This allowed the input to reference the parent question object and the choices array on the object instead of referencing the option reference within ng-repeat.

Resources