@@ -242,6 +242,60 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s
242
242
}
243
243
}
244
244
}
245
+ } ,
246
+ condition : function ( args ) {
247
+ // Do we have a condition? Then we slap on an ng-if on all children,
248
+ // but be nice to existing ng-if.
249
+ if ( args . form . condition ) {
250
+ var evalExpr = 'evalExpr(' + args . path +
251
+ '.contidion, { model: model, "arrayIndex": $index})' ;
252
+ if ( args . form . key ) {
253
+ var strKey = sfPathProvider . stringify ( args . form . key ) ;
254
+ evalExpr = 'evalExpr(' + args . path + '.condition,{ model: model, "arrayIndex": $index, ' +
255
+ '"modelValue": model' + ( strKey [ 0 ] === '[' ? '' : '.' ) + strKey + '})' ;
256
+ }
257
+
258
+ var children = args . fieldFrag . children ;
259
+ for ( var i = 0 ; i < children . length ; i ++ ) {
260
+ var child = children [ i ] ;
261
+ var ngIf = child . getAttribute ( 'ng-if' ) ;
262
+ child . setAttribute (
263
+ 'ng-if' ,
264
+ ngIf ?
265
+ '(' + ngIf +
266
+ ') || (' + evalExpr + ')'
267
+ : evalExpr
268
+ ) ;
269
+ }
270
+ }
271
+ } ,
272
+ array : function ( args ) {
273
+ var items = args . fieldFrag . querySelector ( '[schema-form-array-items]' ) ;
274
+ if ( items ) {
275
+ state = angular . copy ( args . state ) ;
276
+ state . keyRedaction = state . keyRedaction || 0 ;
277
+ state . keyRedaction += args . form . key . length + 1 ;
278
+
279
+ // Special case, an array with just one item in it that is not an object.
280
+ // So then we just override the modelValue
281
+ if ( args . form . schema && args . form . schema . items &&
282
+ args . form . schema . items . type &&
283
+ args . form . schema . items . type . indexOf ( 'object' ) === - 1 &&
284
+ args . form . schema . items . type . indexOf ( 'array' ) === - 1 ) {
285
+ var strKey = sfPathProvider . stringify ( args . form . key ) . replace ( / " / g, '"' ) + '[$index]' ;
286
+ state . modelValue = 'modelArray[$index]' ;
287
+ } else {
288
+ state . modelName = 'item' ;
289
+ }
290
+
291
+ // Flag to the builder that where in an array.
292
+ // This is needed for compatabiliy if a "old" add-on is used that
293
+ // hasn't been transitioned to the new builder.
294
+ state . arrayCompatFlag = true ;
295
+
296
+ var childFrag = args . build ( args . form . items , args . path + '.items' , state ) ;
297
+ items . appendChild ( childFrag ) ;
298
+ }
245
299
}
246
300
} ;
247
301
this . builders = builders ;
@@ -279,12 +333,20 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s
279
333
if ( ! field . replace ) {
280
334
// Backwards compatability build
281
335
var n = document . createElement ( snakeCase ( decorator . __name , '-' ) ) ;
282
- n . setAttribute ( 'form' , path + '[' + index + ']' ) ;
336
+ if ( state . arrayCompatFlag ) {
337
+ n . setAttribute ( 'form' , 'copyWithIndex($index)' ) ;
338
+ } else {
339
+ n . setAttribute ( 'form' , path + '[' + index + ']' ) ;
340
+ }
341
+
283
342
( checkForSlot ( f , slots ) || frag ) . appendChild ( n ) ;
284
343
285
344
} else {
286
345
var tmpl ;
287
346
347
+ // Reset arrayCompatFlag, it's only valid for direct children of the array.
348
+ state . arrayCompatFlag = false ;
349
+
288
350
// TODO: Create a couple fo testcases, small and large and
289
351
// measure optmization. A good start is probably a cache of DOM nodes for a particular
290
352
// template that can be cloned instead of using innerHTML
@@ -330,17 +392,17 @@ angular.module('schemaForm').provider('sfBuilder', ['sfPathProvider', function(s
330
392
} ;
331
393
332
394
return {
333
- /**
334
- * Builds a form from a canonical form definition
335
- */
336
- build : function ( form , decorator , slots , lookup ) {
337
- return build ( form , decorator , function ( url ) {
338
- return $templateCache . get ( url ) ;
339
- } , slots , undefined , undefined , lookup ) ;
340
-
341
- } ,
342
- builder : builders ,
343
- internalBuild : build
395
+ /**
396
+ * Builds a form from a canonical form definition
397
+ */
398
+ build : function ( form , decorator , slots , lookup ) {
399
+ return build ( form , decorator , function ( url ) {
400
+ return $templateCache . get ( url ) ;
401
+ } , slots , undefined , undefined , lookup ) ;
402
+
403
+ } ,
404
+ builder : builders ,
405
+ internalBuild : build
344
406
} ;
345
407
} ] ;
346
408
@@ -1494,6 +1556,7 @@ angular.module('schemaForm').factory('sfValidator', [function() {
1494
1556
1495
1557
/**
1496
1558
* Directive that handles the model arrays
1559
+ * DEPRECATED with the new builder use the sfNewArray instead.
1497
1560
*/
1498
1561
angular . module ( 'schemaForm' ) . directive ( 'sfArray' , [ 'sfSelect' , 'schemaForm' , 'sfValidator' , 'sfPath' ,
1499
1562
function ( sfSelect , schemaForm , sfValidator , sfPath ) {
@@ -2087,6 +2150,218 @@ angular.module('schemaForm').directive('sfMessage',
2087
2150
} ;
2088
2151
} ] ) ;
2089
2152
2153
+ /**
2154
+ * Directive that handles the model arrays
2155
+ */
2156
+ angular . module ( 'schemaForm' ) . directive ( 'sfNewArray' , [ 'sfSelect' , 'sfPath' , 'schemaForm' ,
2157
+ function ( sel , sfPath , schemaForm ) {
2158
+ return {
2159
+ scope : false ,
2160
+ link : function ( scope , element , attrs ) {
2161
+ scope . min = 0 ;
2162
+
2163
+ scope . modelArray = scope . $eval ( attrs . sfNewArray ) ;
2164
+
2165
+ // We need to have a ngModel to hook into validation. It doesn't really play well with
2166
+ // arrays though so we both need to trigger validation and onChange.
2167
+ // So we watch the value as well. But watching an array can be tricky. We wan't to know
2168
+ // when it changes so we can validate,
2169
+ var watchFn = function ( ) {
2170
+ //scope.modelArray = modelArray;
2171
+ scope . modelArray = scope . $eval ( attrs . sfNewArray ) ;
2172
+ // validateField method is exported by schema-validate
2173
+ if ( scope . validateField ) {
2174
+ scope . validateField ( ) ;
2175
+ }
2176
+ } ;
2177
+
2178
+ var onChangeFn = function ( ) {
2179
+ if ( scope . form && scope . form . onChange ) {
2180
+ if ( angular . isFunction ( scope . form . onChange ) ) {
2181
+ scope . form . onChange ( scope . modelArray , scope . form ) ;
2182
+ } else {
2183
+ scope . evalExpr ( scope . form . onChange , { 'modelValue' : scope . modelArray , form : scope . form } ) ;
2184
+ }
2185
+ }
2186
+ } ;
2187
+
2188
+ // We need the form definition to make a decision on how we should listen.
2189
+ var once = scope . $watch ( 'form' , function ( form ) {
2190
+ if ( ! form ) {
2191
+ return ;
2192
+ }
2193
+
2194
+ // Always start with one empty form unless configured otherwise.
2195
+ // Special case: don't do it if form has a titleMap
2196
+ if ( ! form . titleMap && form . startEmpty !== true && ( ! scope . modelArray || scope . modelArray . length === 0 ) ) {
2197
+ scope . appendToArray ( ) ;
2198
+ }
2199
+
2200
+ // If we have "uniqueItems" set to true, we must deep watch for changes.
2201
+ if ( scope . form && scope . form . schema && scope . form . schema . uniqueItems === true ) {
2202
+ scope . $watch ( attrs . sfNewArray , watchFn , true ) ;
2203
+
2204
+ // We still need to trigger onChange though.
2205
+ scope . $watch ( [ attrs . sfNewArray , attrs . sfNewArray + '.length' ] , onChangeFn ) ;
2206
+
2207
+ } else {
2208
+ // Otherwise we like to check if the instance of the array has changed, or if something
2209
+ // has been added/removed.
2210
+ if ( scope . $watchGroup ) {
2211
+ scope . $watchGroup ( [ attrs . sfNewArray , attrs . sfNewArray + '.length' ] , function ( ) {
2212
+ watchFn ( ) ;
2213
+ onChangeFn ( ) ;
2214
+ } ) ;
2215
+ } else {
2216
+ // Angular 1.2 support
2217
+ scope . $watch ( attrs . sfNewArray , function ( ) {
2218
+ watchFn ( ) ;
2219
+ onChangeFn ( ) ;
2220
+ } ) ;
2221
+ scope . $watch ( attrs . sfNewArray + '.length' , function ( ) {
2222
+ watchFn ( ) ;
2223
+ onChangeFn ( ) ;
2224
+ } ) ;
2225
+ }
2226
+ }
2227
+
2228
+ // Title Map handling
2229
+ // If form has a titleMap configured we'd like to enable looping over
2230
+ // titleMap instead of modelArray, this is used for intance in
2231
+ // checkboxes. So instead of variable number of things we like to create
2232
+ // a array value from a subset of values in the titleMap.
2233
+ // The problem here is that ng-model on a checkbox doesn't really map to
2234
+ // a list of values. This is here to fix that.
2235
+ if ( form . titleMap && form . titleMap . length > 0 ) {
2236
+ scope . titleMapValues = [ ] ;
2237
+
2238
+ // We watch the model for changes and the titleMapValues to reflect
2239
+ // the modelArray
2240
+ var updateTitleMapValues = function ( arr ) {
2241
+ scope . titleMapValues = [ ] ;
2242
+ arr = arr || [ ] ;
2243
+
2244
+ form . titleMap . forEach ( function ( item ) {
2245
+ scope . titleMapValues . push ( arr . indexOf ( item . value ) !== - 1 ) ;
2246
+ } ) ;
2247
+ } ;
2248
+ //Catch default values
2249
+ updateTitleMapValues ( scope . modelArray ) ;
2250
+
2251
+ // TODO: Refactor and see if we can get rid of this watch by piggy backing on the
2252
+ // validation watch.
2253
+ scope . $watchCollection ( 'modelArray' , updateTitleMapValues ) ;
2254
+
2255
+ //To get two way binding we also watch our titleMapValues
2256
+ scope . $watchCollection ( 'titleMapValues' , function ( vals , old ) {
2257
+ if ( vals && vals !== old ) {
2258
+ var arr = scope . modelArray ;
2259
+
2260
+ // Apparently the fastest way to clear an array, readable too.
2261
+ // http://jsperf.com/array-destroy/32
2262
+ while ( arr . length > 0 ) {
2263
+ arr . pop ( ) ;
2264
+ }
2265
+ form . titleMap . forEach ( function ( item , index ) {
2266
+ if ( vals [ index ] ) {
2267
+ arr . push ( item . value ) ;
2268
+ }
2269
+ } ) ;
2270
+
2271
+ // Time to validate the rebuilt array.
2272
+ // validateField method is exported by schema-validate
2273
+ if ( scope . validateField ) {
2274
+ scope . validateField ( ) ;
2275
+ }
2276
+ }
2277
+ } ) ;
2278
+ }
2279
+
2280
+ once ( ) ;
2281
+ } ) ;
2282
+
2283
+ scope . appendToArray = function ( ) {
2284
+
2285
+ var empty ;
2286
+
2287
+ // Same old add empty things to the array hack :(
2288
+ if ( scope . form && scope . form . schema ) {
2289
+ if ( scope . form . schema . items ) {
2290
+ if ( scope . form . schema . items . type === 'object' ) {
2291
+ empty = { } ;
2292
+ } else if ( scope . form . schema . items . type === 'array' ) {
2293
+ empty = [ ] ;
2294
+ }
2295
+ }
2296
+ }
2297
+
2298
+ var model = scope . modelArray ;
2299
+ if ( ! model ) {
2300
+ // Create and set an array if needed.
2301
+ var selection = sfPath . parse ( attrs . sfNewArray ) ;
2302
+ model = [ ] ;
2303
+ sel ( selection , scope , model ) ;
2304
+ scope . modelArray = model ;
2305
+ }
2306
+ model . push ( empty ) ;
2307
+
2308
+ return model ;
2309
+ } ;
2310
+
2311
+ scope . deleteFromArray = function ( index ) {
2312
+ var model = scope . modelArray ;
2313
+ if ( model ) {
2314
+ model . splice ( index , 1 ) ;
2315
+ }
2316
+ return model ;
2317
+ } ;
2318
+
2319
+ // For backwards compatability, i.e. when a bootstrap-decorator tag is used
2320
+ // as child to the array.
2321
+ var setIndex = function ( index ) {
2322
+ return function ( form ) {
2323
+ if ( form . key ) {
2324
+ form . key [ form . key . indexOf ( '' ) ] = index ;
2325
+ }
2326
+ } ;
2327
+ } ;
2328
+ var formDefCache = { } ;
2329
+ scope . copyWithIndex = function ( index ) {
2330
+ var form = scope . form ;
2331
+ if ( ! formDefCache [ index ] ) {
2332
+
2333
+ // To be more compatible with JSON Form we support an array of items
2334
+ // in the form definition of "array" (the schema just a value).
2335
+ // for the subforms code to work this means we wrap everything in a
2336
+ // section. Unless there is just one.
2337
+ var subForm = form . items [ 0 ] ;
2338
+ if ( form . items . length > 1 ) {
2339
+ subForm = {
2340
+ type : 'section' ,
2341
+ items : form . items . map ( function ( item ) {
2342
+ item . ngModelOptions = form . ngModelOptions ;
2343
+ if ( angular . isUndefined ( item . readonly ) ) {
2344
+ item . readonly = form . readonly ;
2345
+ }
2346
+ return item ;
2347
+ } )
2348
+ } ;
2349
+ }
2350
+
2351
+ if ( subForm ) {
2352
+ var copy = angular . copy ( subForm ) ;
2353
+ copy . arrayIndex = index ;
2354
+ schemaForm . traverseForm ( copy , setIndex ( index ) ) ;
2355
+ formDefCache [ index ] = copy ;
2356
+ }
2357
+ }
2358
+ return formDefCache [ index ] ;
2359
+ } ;
2360
+
2361
+ }
2362
+ } ;
2363
+ } ] ) ;
2364
+
2090
2365
/*
2091
2366
FIXME: real documentation
2092
2367
<form sf-form="form" sf-schema="schema" sf-decorator="foobar"></form>
0 commit comments