PageSourceSearch

https://www.splittingimage.com.au/static/js/app.js

js splittingimage.com.au collected 2026-09-27 01:13:17 UTC 178,042 bytes, 3,946 lines download raw bytes

1var app = angular.module("fluro", window.dependencies);
2
3var useSiteCache = false;
4
5function getMetaKey(stringKey) {
6    var metas = document.getElementsByTagName("meta");
7    for (i = 0; i < metas.length; i++) {
8        if (metas[i].getAttribute("property") == stringKey) {
9            return metas[i].getAttribute("content");
10        }
11    }
12    return "";
13}
14
15var $globalStates;
16
17app.config(function($controllerProvider, $compileProvider, $filterProvider, $provide, $stateProvider, $httpProvider, FluroProvider, $urlRouterProvider, $analyticsProvider, $locationProvider) {
18    $analyticsProvider.settings.ga.additionalAccountNames = [ "fluro" ];
19    if (!applicationData) {
20        return;
21    } else {}
22    $globalStates = $stateProvider;
23    $compileProvider.debugInfoEnabled(false);
24    app.components = {};
25    app.components.controller = $controllerProvider.register;
26    app.components.directive = $compileProvider.directive;
27    app.components.filter = $filterProvider.register;
28    app.components.factory = $provide.factory;
29    app.components.service = $provide.service;
30    var access_token = getMetaKey("fluro_application_key");
31    var api_url = getMetaKey("fluro_url");
32    var timezoneOffset = getMetaKey("fluro_timezone_offset");
33    var timezone = getMetaKey("fluro_timezone");
34    var configDetails = window.configHandshakeDetails;
35    if (!configDetails) {
36        configDetails = {
37            apiURL: api_url,
38            token: access_token
39        };
40    }
41    if (timezoneOffset && String(timezoneOffset).length) {
42        configDetails.timezoneOffset = Number(timezoneOffset);
43    }
44    if (timezone && String(timezone).length) {
45        configDetails.timezone = timezone;
46    }
47    FluroProvider.set(configDetails);
48    if (!configDetails.token) {
49        $httpProvider.defaults.withCredentials = true;
50    }
51    $httpProvider.interceptors.push("FluroAuthentication");
52    $locationProvider.html5Mode(true);
53    $locationProvider.hashPrefix("!");
54    $urlRouterProvider.otherwise("/");
55    var siteModel = applicationData.model;
56    function getFlattenedRoutes(array) {
57        return _.chain(array).map(function(route) {
58            if (route.type == "folder") {
59                return getFlattenedRoutes(route.routes);
60            } else {
61                return route;
62            }
63        }).flatten().compact().value();
64    }
65    var flattenedRoutes = getFlattenedRoutes(siteModel.routes);
66    _.each(flattenedRoutes, function(route) {
67        var template = "";
68        if (!route.state || !route.state.length) {
69            if (route.title) {} else {}
70            return;
71        }
72        var stateDetails = {
73            name: route.state,
74            url: route.url + "?sv",
75            title: route.title,
76            routeData: route,
77            views: {}
78        };
79        var viewName = "$site@";
80        if (route.vid) {
81            viewName = route.vid;
82        }
83        stateDetails.views[viewName] = {
84            template: '<route class="site-page" ng-page="page" ng-model="route" ng-items="items" ng-slug="slug" ng-results="results" ng-collections="collections"></route>',
85            controller: [ "$scope", "route", "items", "slug", "collections", "results", "pageCollections", "pageResults", function($scope, route, items, slug, collections, results, pageCollections, pageResults) {
86                $scope.route = route;
87                $scope.items = items;
88                $scope.results = results;
89                $scope.collections = collections;
90                $scope.slug = slug;
91                $scope.page = {
92                    results: pageResults,
93                    collections: pageCollections
94                };
95            } ],
96            resolve: {
97                route: function() {
98                    return route;
99                },
100                slug: [ "$rootScope", "$q", "$stateParams", "FluroContentRetrieval", function($rootScope, $q, $stateParams, FluroContentRetrieval) {
101                    var deferred = $q.defer();
102                    if ($stateParams.slug || $stateParams._id) {
103                        var appendPostCount = route.appendPostCount;
104                        var appendForms = route.appendForms;
105                        var appendPosts = route.appendPosts;
106                        var appendContactDetail = route.appendContactDetail;
107                        var appendProcess = route.appendProcess;
108                        var appendTeams = route.appendTeams;
109                        var appendAssignments = route.appendAssignments;
110                        var includePublicSearchResults = route.includePublic;
111                        if (!appendPostCount || !appendPostCount.length) {
112                            appendPostCount = "all";
113                        }
114                        if (!appendProcess || !appendProcess.length) {
115                            appendProcess = "all";
116                        }
117                        if (!appendForms || !appendForms.length) {
118                            appendForms = "all";
119                        }
120                        var queryParams = {
121                            includePublic: includePublicSearchResults,
122                            searchInheritable: true,
123                            appendPostCount: appendPostCount,
124                            appendPosts: appendPosts,
125                            appendProcess: appendProcess,
126                            appendContactDetail: appendContactDetail,
127                            appendForms: appendForms,
128                            appendAssignments: appendAssignments,
129                            appendTeams: appendTeams
130                        };
131                        if (route.excludeInheritable) {
132                            queryParams.searchInheritable = false;
133                        }
134                        if (route.noCacheSlug) {
135                            queryParams.noCache = true;
136                        }
137                        if (useSiteCache) {
138                            queryParams.noCache = true;
139                        }
140                        if ($stateParams.from && $stateParams._id) {
141                            var promise = FluroContentRetrieval.getItemFromProduct($stateParams._id, $stateParams.from);
142                        } else {
143                            var queryRequest = {};
144                            if ($stateParams._id && $stateParams._id.length) {
145                                queryRequest._id = $stateParams._id;
146                            } else {
147                                queryRequest.slug = $stateParams.slug;
148                            }
149                            var promise = FluroContentRetrieval.query(queryRequest, null, null, queryParams);
150                        }
151                        promise.then(function(slug) {
152                            if (_.isArray(slug) && slug.length) {
153                                var actualSlugItem = slug[0];
154                                $rootScope.slug = actualSlugItem;
155                                return deferred.resolve(actualSlugItem);
156                            }
157                            if (slug) {
158                                $rootScope.slug = slug;
159                                return deferred.resolve(slug);
160                            }
161                            $rootScope.slug = null;
162                            deferred.resolve(null);
163                        }, deferred.reject);
164                    } else {
165                        $rootScope.slug = null;
166                        deferred.resolve();
167                    }
168                    return deferred.promise;
169                } ],
170                items: [ "$http", "Fluro", "FluroContentRetrieval", function($http, Fluro, FluroContentRetrieval) {
171                    var contentNids = _.chain(route.sections).map(function(section) {
172                        var nids = [];
173                        nids = nids.concat(section.content);
174                        var keyContent = _.map(section.keys, function(keyGroup) {
175                            return keyGroup.content;
176                        });
177                        nids = nids.concat(keyContent);
178                        return nids;
179                    }).flattenDeep().value();
180                    if (route.content && route.content.length) {
181                        contentNids = contentNids.concat(route.content);
182                    }
183                    if (route.keys && route.keys.length) {
184                        var keyContent = _.chain(route.keys).map(function(keyGroup) {
185                            return keyGroup.content;
186                        }).flattenDeep().value();
187                        contentNids = contentNids.concat(keyContent);
188                    }
189                    if (contentNids && contentNids.length) {
190                        return FluroContentRetrieval.get(contentNids, useSiteCache, {
191                            searchInheritable: true
192                        });
193                    } else {
194                        return [];
195                    }
196                } ],
197                collections: [ "$http", "Fluro", "$q", "FluroContentRetrieval", function($http, Fluro, $q, FluroContentRetrieval) {
198                    var collectionIds = _.chain(route.sections).map(function(section) {
199                        return section.collections;
200                    }).flatten().value();
201                    if (collectionIds && collectionIds.length) {
202                        var deferred = $q.defer();
203                        function collectionsError(err) {
204                            deferred.resolve([]);
205                        }
206                        function collectionsSuccess(res) {
207                            deferred.resolve(res);
208                        }
209                        FluroContentRetrieval.getMultipleCollections(collectionIds, useSiteCache, {
210                            searchInheritable: true
211                        }).then(collectionsSuccess, collectionsError);
212                        return deferred.promise;
213                    } else {
214                        return [];
215                    }
216                } ],
217                results: [ "$http", "$q", "Fluro", "FluroContentRetrieval", "$stateParams", "$parse", "route", "slug", "items", "collections", function($http, $q, Fluro, FluroContentRetrieval, $stateParams, $parse, route, slug, items, collections) {
218                    var queryVariables;
219                    var queryIds = _.chain(route.sections).map(function(section) {
220                        var scopeObject = {};
221                        scopeObject.items = items;
222                        scopeObject.collections = collections;
223                        scopeObject.stateParams = $stateParams;
224                        if (slug) {
225                            scopeObject.slug = slug;
226                        }
227                        scopeObject.route = route;
228                        scopeObject.model = section;
229                        if (section.queryVariables) {
230                            var localQueryVariables = _.mapValues(section.queryVariables, function(value) {
231                                var val = $parse(value)(scopeObject);
232                                return val;
233                            });
234                            localQueryVariables = _.pick(localQueryVariables, _.identity);
235                            if (!queryVariables) {
236                                queryVariables = localQueryVariables;
237                            } else {
238                                _.assign(queryVariables, localQueryVariables);
239                            }
240                        }
241                        return section.queries;
242                    }
242).flatten().value();
243                    if (queryIds && queryIds.length) {
244                        return FluroContentRetrieval.queryMultiple(queryIds, useSiteCache, queryVariables);
245                    } else {
246                        return [];
247                    }
248                } ],
249                pageCollections: [ "$http", "$q", "Fluro", "FluroContentRetrieval", function($http, $q, Fluro, FluroContentRetrieval) {
250                    var collectionIds = route.collections;
251                    if (collectionIds && collectionIds.length) {
252                        var deferred = $q.defer();
253                        function collectionsError(err) {
254                            deferred.resolve([]);
255                        }
256                        function collectionsSuccess(res) {
257                            deferred.resolve(res);
258                        }
259                        FluroContentRetrieval.getMultipleCollections(collectionIds, useSiteCache).then(collectionsSuccess, collectionsError);
260                        return deferred.promise;
261                    } else {
262                        return [];
263                    }
264                } ],
265                pageResults: [ "$http", "$q", "Fluro", "FluroContentRetrieval", "$parse", "route", "slug", "items", "collections", function($http, $q, Fluro, FluroContentRetrieval, $parse, route, slug, items, collections) {
266                    var scopeObject = {};
267                    scopeObject.items = items;
268                    scopeObject.collections = collections;
269                    if (slug) {
270                        scopeObject.slug = slug;
271                    }
272                    scopeObject.route = route;
273                    scopeObject.model = route;
274                    var queryVariables;
275                    var queryIds = route.queries;
276                    if (route.queryVariables) {
277                        var localQueryVariables = _.mapValues(route.queryVariables, function(value) {
278                            var val = $parse(value)(scopeObject);
279                            return val;
280                        });
281                        localQueryVariables = _.pick(localQueryVariables, _.identity);
282                        if (!queryVariables) {
283                            queryVariables = localQueryVariables;
284                        } else {
285                            _.assign(queryVariables, localQueryVariables);
286                        }
287                    }
288                    if (queryIds && queryIds.length) {
289                        return FluroContentRetrieval.queryMultiple(queryIds, useSiteCache, queryVariables);
290                    } else {
291                        return [];
292                    }
293                } ]
294            }
295        };
296        $stateProvider.state(route.state, stateDetails);
297    });
298});
299
300app.run(function(FluroSocket, $window, FluroTokenService, $state, $location, $localStorage, $sessionStorage, $templateCache, $document, $stateParams, $rootScope, FluroAccess, FluroMenuService, DateTools, FluroScrollService, FluroWindowService, FluroSEO, Asset, $window, $timeout) {
301    var siteModel = applicationData.model;
302    if (siteModel && siteModel.templates && siteModel.templates.length) {
303        _.each(siteModel.templates, function(sectionTemplate) {
304            $templateCache.put(sectionTemplate.key, sectionTemplate.html);
305        });
306    }
307    if (!FluroTokenService.backup) {
308        FluroTokenService.backup = {};
309    }
310    var hardcoded_access_token = getMetaKey("fluro_application_key");
311    if (hardcoded_access_token && hardcoded_access_token.length) {
312        FluroTokenService.backup.token = hardcoded_access_token;
313    }
314    if ($initUser) {
315        $rootScope.user = $initUser;
316        FluroTokenService.backup.user = $initUser;
317    } else {
318        if (siteModel.account) {
319            FluroSocket.join(siteModel.account);
320        }
321    }
322    $rootScope.localStorage = $localStorage;
323    $rootScope.sessionStorage = $sessionStorage;
324    $rootScope.access = FluroAccess;
325    $rootScope.date = DateTools;
326    $rootScope.seo = FluroSEO;
327    $rootScope.asset = Asset;
328    $rootScope.menu = FluroMenuService;
329    $rootScope.scroll = FluroScrollService;
330    $rootScope.window = FluroWindowService;
331    $rootScope.site = applicationData.model;
332    $rootScope.appData = applicationData;
333    $rootScope.$state = $state;
334    $rootScope.$stateParams = $stateParams;
335    var clicked;
336    $rootScope.goBack = function() {
337        clicked = false;
338        var previousState = $rootScope.previousState;
339        if (previousState) {
340            $state.go(previousState.name, previousState.params);
341        } else {
342            if ($state.$current.parent && $state.$current.parent.self.name.length) {
343                $state.go("^");
344            } else {
345                $state.go("home");
346            }
347        }
348    };
349    $document.on("click", function(event) {
350        clicked = true;
351        setTimeout(function() {
352            clicked = false;
353        }, 100);
354    });
355    $rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams, error) {
356        var path = $state.href(fromState, fromParams);
357        FluroScrollService.cache[path] = FluroScrollService.get();
358        if (!fromState.abstract) {
359            $rootScope.previousState = {
360                name: fromState.name,
361                params: fromParams
362            };
363        }
364        if (clicked) {
365            var toPath = $state.href(toState, toParams);
366            delete FluroScrollService.cache[toPath];
367        }
368    });
369    $rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
370        throw error;
371    });
372    $rootScope.$on("$stateChangeSuccess", function(event, toState, toParams, fromState, fromParams, error) {
373        $rootScope.currentRoute = toState.routeData;
374        $rootScope.bodyClasses = "route-" + _.kebabCase(toState.name);
375        var hash = FluroScrollService.getAnchor();
376        if (hash && hash.length) {
377            $location.hash(hash);
378            $timeout(function() {
379                FluroScrollService.scrollToId(hash, 0);
380            });
381        } else {
382            var path = $state.href($state.current.name, $stateParams);
383            var cachedValue = FluroScrollService.cache[path];
384            if (cachedValue) {
385                $timeout(function() {
386                    FluroScrollService.scrollTo(cachedValue, 0);
387                    delete FluroScrollService.cache[path];
388                });
389            } else {
390                $timeout(function() {
391                    FluroScrollService.scrollTo(0, 0);
392                });
393            }
394        }
395    });
396});
397
398app.directive("application", function() {
399    return {
400        restrict: "E",
401        replace: true,
402        templateUrl: "application/application.html",
403        controller: "ApplicationController"
404    };
405});
406
407app.controller("ApplicationController", function($scope) {
408    $scope.message = "hey";
409});
410
411(function() {
412    Date.shortMonths = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
413    Date.longMonths = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "
413November", "December" ];
414    Date.shortDays = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ];
415    Date.longDays = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ];
416    var replaceChars = {
417        d: function() {
418            return (this.getDate() < 10 ? "0" : "") + this.getDate();
419        },
420        D: function() {
421            return Date.shortDays[this.getDay()];
422        },
423        j: function() {
424            return this.getDate();
425        },
426        l: function() {
427            return Date.longDays[this.getDay()];
428        },
429        N: function() {
430            return this.getDay() == 0 ? 7 : this.getDay();
431        },
432        S: function() {
433            return this.getDate() % 10 == 1 && this.getDate() != 11 ? "st" : this.getDate() % 10 == 2 && this.getDate() != 12 ? "nd" : this.getDate() % 10 == 3 && this.getDate() != 13 ? "rd" : "th";
434        },
435        w: function() {
436            return this.getDay();
437        },
438        z: function() {
439            var d = new Date(this.getFullYear(), 0, 1);
440            return Math.ceil((this - d) / 864e5);
441        },
442        W: function() {
443            var target = new Date(this.valueOf());
444            var dayNr = (this.getDay() + 6) % 7;
445            target.setDate(target.getDate() - dayNr + 3);
446            var firstThursday = target.valueOf();
447            target.setMonth(0, 1);
448            if (target.getDay() !== 4) {
449                target.setMonth(0, 1 + (4 - target.getDay() + 7) % 7);
450            }
451            return 1 + Math.ceil((firstThursday - target) / 6048e5);
452        },
453        F: function() {
454            return Date.longMonths[this.getMonth()];
455        },
456        m: function() {
457            return (this.getMonth() < 9 ? "0" : "") + (this.getMonth() + 1);
458        },
459        M: function() {
460            return Date.shortMonths[this.getMonth()];
461        },
462        n: function() {
463            return this.getMonth() + 1;
464        },
465        t: function() {
466            var d = new Date();
467            return new Date(d.getFullYear(), d.getMonth(), 0).getDate();
468        },
469        L: function() {
470            var year = this.getFullYear();
471            return year % 400 == 0 || year % 100 != 0 && year % 4 == 0;
472        },
473        o: function() {
474            var d = new Date(this.valueOf());
475            d.setDate(d.getDate() - (this.getDay() + 6) % 7 + 3);
476            return d.getFullYear();
477        },
478        Y: function() {
479            return this.getFullYear();
480        },
481        y: function() {
482            return ("" + this.getFullYear()).substr(2);
483        },
484        a: function() {
485            return this.getHours() < 12 ? "am" : "pm";
486        },
487        A: function() {
488            return this.getHours() < 12 ? "AM" : "PM";
489        },
490        B: function() {
491            return Math.floor(((this.getUTCHours() + 1) % 24 + this.getUTCMinutes() / 60 + this.getUTCSeconds() / 3600) * 1e3 / 24);
492        },
493        g: function() {
494            return this.getHours() % 12 || 12;
495        },
496        G: function() {
497            return this.getHours();
498        },
499        h: function() {
500            return ((this.getHours() % 12 || 12) < 10 ? "0" : "") + (this.getHours() % 12 || 12);
501        },
502        H: function() {
503            return (this.getHours() < 10 ? "0" : "") + this.getHours();
504        },
505        i: function() {
506            return (this.getMinutes() < 10 ? "0" : "") + this.getMinutes();
507        },
508        s: function() {
509            return (this.getSeconds() < 10 ? "0" : "") + this.getSeconds();
510        },
511        u: function() {
512            var m = this.getMilliseconds();
513            return (m < 10 ? "00" : m < 100 ? "0" : "") + m;
514        },
515        e: function() {
516            return "Not Yet Supported";
517        },
518        I: function() {
519            var DST = null;
520            for (var i = 0; i < 12; ++i) {
521                var d = new Date(this.getFullYear(), i, 1);
522                var offset = d.getTimezoneOffset();
523                if (DST === null) DST = offset; else if (offset < DST) {
524                    DST = offset;
525                    break;
526                } else if (offset > DST) break;
527            }
528            return this.getTimezoneOffset() == DST | 0;
529        },
530        O: function() {
531            return (-this.getTimezoneOffset() < 0 ? "-" : "+") + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? "0" : "") + Math.abs(this.getTimezoneOffset() / 60) + "00";
532        },
533        P: function() {
534            return (-this.getTimezoneOffset() < 0 ? "-" : "+") + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? "0" : "") + Math.abs(this.getTimezoneOffset() / 60) + ":00";
535        },
536        T: function() {
537            return this.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, "$1");
538        },
539        Z: function() {
540            return -this.getTimezoneOffset() * 60;
541        },
542        c: function() {
543            return this.format("Y-m-d\\TH:i:sP");
544        },
545        r: function() {
546            return this.toString();
547        },
548        U: function() {
549            return this.getTime() / 1e3;
550        }
551    };
552    Date.prototype.format = function(format) {
553        var date = this;
554        return format.replace(/(\\?)(.)/g, function(_, esc, chr) {
555            return esc === "" && replaceChars[chr] ? replaceChars[chr].call(date) : chr;
556        });
557    };
558}).call(this);
559
560app.factory("dateutils", function() {
561    function validateDate(date) {
562        var d = new Date(Date.UTC(date.year, date.month, date.day));
563        var result = d && (d.getMonth() === date.month && d.getDate() === Number(date.day));
564        return result;
565    }
566    function changeDate(date) {
567        if (date.day > 28) {
568            date.day--;
569            return date;
570        } else if (date.month > 11) {
571            date.day = 31;
572            date.month--;
573            return date;
574        }
575    }
576    var self = this;
577    this.days = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 ];
578    this.months = [ {
579        value: 0,
580        name: "January"
581    }, {
582        value: 1,
583        name: "February"
584    }, {
585        value: 2,
586        name: "March"
587    }, {
588        value: 3,
589        name: "April"
590    }, {
591        value: 4,
592        name: "May"
593    }, {
594        value: 5,
595        name: "June"
596    }, {
597        value: 6,
598        name: "July"
599    }, {
600        value: 7,
601        name: "August"
602    }, {
603        value: 8,
604        name: "September"
605    }, {
606        value: 9,
607        name: "October"
608    }, {
609        value: 10,
610        name: "November"
611    }, {
612        value: 11,
613        name: "December"
614    } ];
615    return {
616        checkDate: function(date) {
617            if (!date.day) {
618                return false;
619            }
620            if (date.month === 0) {} else {
621                if (!date.month) {
622                    return false;
623                }
624            }
625            if (!date.year) {
626                return false;
627            }
628            if (validateDate(date)) {
629                return date;
630            } else {
631                return this.checkDate(changeDate(date));
632            }
633        },
634        get: function(name) {
635            return self[name];
636        }
637    };
638});
639
640app.directive("dobSelect", [ "dateutils", function(dateutils) {
641    return {
642        restrict: "E",
643        replace: true,
644        require: "ngModel",
645        scope: {
646            model: "=ngModel",
647            hideAge: "=?",
648            hideDates: "=?"
649        },
650        controller: [ "$scope", "dateutils", function($scope, dateutils) {
651            $scope.days = dateutils.get("days");
652            $scope.months = dateutils.get("months");
653            if (!$scope.model) {} else {
654                if (!_.isDate($scope.model)) {
655                    $scope.model = new Date($scope.model);
656                }
657            }
658            var dateFields = {};
659            if ($scope.model) {
660                dateFields.day = new Date($scope.model).getDate();
661                dateFields.month = new Date($scope.model).getMonth();
662                dateFields.year = new Date($scope.model).getFullYear();
663                $scope.dateFields = dateFields;
664                $scope.age = moment().diff($scope.model, "years");
665            }
666            $scope.checkDate = function() {
667                var date = dateutils.checkDate($scope.dateFields);
668                if (date) {
669                    $scope.dateFields = date;
670                }
671            };
672            function dateFieldsChanged(newDate, oldDate) {
673                if (newDate == oldDate) {
674                    return;
675                }
676                stopWatchingAge();
677                if (!$scope.dateFields) {
678                    return;
679                }
680                if (!$scope.dateFields.year) {
681                    return;
682                }
683                if ($scope.dateFields.month === 0) {} else {
684                    if (!$scope.dateFields.month) {
685                        return;
686                    }
687                }
688                if (!$scope.dateFields.day) {
689                    return;
690                }
691                var date = new Date($scope.dateFields.year, $scope.dateFields.month, $scope.dateFields.day);
692                date.setHours(0, 0, 0, 0);
693                if (!$scope.model) {
694                    $scope.model = new Date();
695                }
696                $scope.model.setDate(date.getDate());
697                $scope.model.setMonth(date.getMonth());
698                $scope.model.setFullYear(date.getFullYear());
699                var diff = moment().diff($scope.model, "years");
700                $scope.age = parseInt(diff);
701                startWatchingAge();
702            }
703            function ageChanged(newAge, oldAge) {
704                if (newAge == oldAge) {
705                    return;
706                }
707                var today = new Date();
708                today.setHours(0, 0, 0, 0);
709                var currentYear = today.getFullYear();
710                var diff = parseInt(currentYear) - parseInt(newAge);
711                if (diff) {
712                    stopWatchingDate();
713                    if (!$scope.dateFields) {
714                        var dateFields = {};
715                        dateFields.year = diff;
716                        $scope.dateFields = dateFields;
717                    } else {
718                        $scope.dateFields.year = diff;
719                    }
720                    startWatchingDate();
721                } else {}
722            }
723            var ageWatcher;
724            function startWatchingAge() {
725                ageWatcher = $scope.$watch("age", ageChanged);
726            }
727            function stopWatchingAge() {
728                if (ageWatcher) {
729                    ageWatcher();
730                }
731            }
732            var dateWatcher;
733            function startWatchingDate() {
734                dateWatcher = $scope.$watch("dateFields", dateFieldsChanged, true);
735            }
736            function stopWatchingDate() {
737                if (dateWatcher) {
738                    dateWatcher();
739                }
740            }
741            startWatchingAge();
742            startWatchingDate();
743        } ],
744        templateUrl: "fluro-dob-select/fluro-dob-select.html",
745        link: function(scope, element, attrs, ctrl) {
746            if (attrs.yearText) {
747                scope.yearText = true;
748            }
749            if (attrs.dayDivClass) {
750                angular.element(element[0].children[0]).removeClass("form-group col-xs-3");
751                angular.element(element[0].children[0]).addClass(attrs.dayDivClass);
752            }
753            if (attrs.dayClass) {
754                angular.element(element[0].children[0].children[0]).removeClass("form-control");
755                angular.element(element[0].children[0].children[0]).addClass(attrs.dayClass);
756            }
757            if (attrs.monthDivClass) {
758                angular.element(element[0].children[1]).removeClass("form-group col-xs-5");
759                angular.element(element[0].children[1]).addClass(attrs.monthDivClass);
760            }
761            if (attrs.monthClass) {
762                angular.element(element[0].children[1].children[0]).removeClass("form-control");
763                angular.element(element[0].children[1].children[0]).addClass(attrs.monthClass);
764            }
765            if (attrs.yearDivClass) {
766                angular.element(element[0].children[2]).removeClass("form-group col-xs-4");
767                angular.element(element[0].children[2]).addClass(attrs.yearDivClass);
768            }
769            if (attrs.yearClass) {
770                angular.element(element[0].children[2].children[0]).removeClass("form-control");
771                angular.element(element[0].children[2].children[0]).addClass(attrs.yearClass);
772            }
773            var currentYear = parseInt(attrs.startingYear, 10) || new Date().getFullYear();
774            var numYears = parseInt(attrs.numYears, 10) || 100;
775            var oldestYear = currentYear - numYears;
776            scope.years = [];
777            for (var i = currentYear; i >= oldestYear; i--) {
778                scope.years.push(i);
779            }
780            scope.$parent.$watch(attrs.ngDisabled, function(newVal) {
781                scope.disableFields = newVal;
782            });
783        }
784    };
785} ]);
786
787app.controller("FluroInteractionButtonSelectController", function($scope, FluroValidate) {
788    var to = $scope.to;
789    var opts = $scope.options;
790    $scope.selection = {
791        values: [],
792        value: null
793    };
794    var definition = $scope.to.definition;
795    var minimum = definition.minimum;
796    var maximum = definition.maximum;
797    if (!minimum) {
798        minimum = 0;
799    }
800    if (!maximum) {
801        maximim = 0;
802    }
803    $scope.multiple = maximum != 1;
804    $scope.dragControlListeners = {
805        orderChanged: function(event) {
806            $scope.model[opts.key] = angular.copy($scope.selection.values);
807        }
808    };
809    $scope.selectBox = {};
810    $scope.selectUpdate = function() {
811        if (!$scope.selectBox.item) {
812            return;
813        }
814        $scope.selection.values.push($scope.selectBox.item);
815        $scope.model[opts.key] = angular.copy($scope.selection.values);
816    };
817    $scope.canAddMore = function() {
818        if (!maximum) {
819            return true;
820        }
821        if ($scope.multiple) {
822            return $scope.selection.values.length < maximum;
823        } else {
824            if (!$scope.selection.value) {
825                return true;
826            }
827        }
828    };
829    $scope.contains = function(value) {
830        if ($scope.multiple) {
831            return _.includes($scope.selection.values, value);
832        } else {
833            return $scope.selection.value == value;
834        }
835    };
836    $scope.select = function(value) {
837        if ($scope.multiple) {
838            if (!$scope.canAddMore()) {
839                return;
840            }
841            $scope.selection.values.push(value);
842        } else {
843            $scope.selection.value = value;
844        }
845    };
846    $scope.deselect = function(value) {
847        if ($scope.multiple) {
848            _.pull($scope.selection.values, value);
849        } else {
850            $scope.selection.value = null;
851        }
852    };
853    $scope.toggle = function(reference) {
854        if ($scope.contains(reference)) {
855            $scope.deselect(reference);
856        } else {
857            $scope.select(reference);
858        }
859        setModel();
860    };
861    $scope.$watch("model", function(newModelValue, oldModelValue) {
862        if (newModelValue != oldModelValue) {
863            var modelValue;
864            if (_.keys(newModelValue).length) {
865                modelValue = newModelValue[opts.key];
866                if ($scope.multiple) {
867                    if (modelValue && _.isArray(modelValue)) {
868                        $scope.selection.values = angular.copy(modelValue);
869                    } else {
870                        $scope.selection.values = [];
871                    }
872                } else {
873                    $scope.selection.value = angular.copy(modelValue);
874                }
875            }
876        }
877    }, true);
878    function checkValidity() {
879        var validRequired;
880        var validInput = FluroValidate.validate($scope.model[$scope.options.key], definition);
881        if ($scope.multiple) {
882            if ($scope.to.required) {
883                validRequired = _.isArray($scope.model[opts.key]) && $scope.model[opts.key].length > 0;
884            }
885        } else {
886            if ($scope.to.required) {
887                if ($scope.model[opts.key]) {
888                    validRequired = true;
889                }
890            }
891        }
892        if ($scope.fc) {
893            $scope.fc.$setValidity("required", validRequired);
894            $scope.fc.$setValidity("validInput", validInput);
895        }
896    }
897    function setModel() {
898        if ($scope.multiple) {
899            $scope.model[opts.key] = angular.copy($scope.selection.values);
900        } else {
901            $scope.model[opts.key] = angular.copy($scope.selection.value);
902        }
903        if ($scope.fc) {
904            $scope.fc.$setTouched();
905        }
906        checkValidity();
907    }
908    if (opts.expressionProperties && opts.expressionProperties["templateOptions.required"]) {
909        $scope.$watch(function() {
910            return $scope.to.required;
911        }, function(newValue) {
912            checkValidity();
913        });
914    }
915    if ($scope.to.required) {
916        var unwatchFormControl = $scope.$watch("fc", function(newValue) {
917            if (!newValue) {
918                return;
919            }
920            checkValidity();
921            unwatchFormControl();
922        });
923    }
924});
925
926app.directive("fluroDateSelect", [ function() {
927    return {
928        restrict: "E",
929        replace: true,
930        scope: {
931            model: "=ngModel"
932        },
933        templateUrl: "fluro-interaction-form/date-select/date-selector.html",
934        controller: [ "$scope", "dateutils", function($scope, dateutils) {
935            $scope.days = dateutils.get("days");
936            $scope.months = dateutils.get("months");
937            var dateFields = {};
938            $scope.$watch("dateFields", dateFieldsChanged, true);
939            $scope.$watch("model", modelChanged);
940            function modelChanged() {
941                if ($scope.model) {
942                    if (!_.isDate($scope.model)) {
943                        $scope.model = new Date($scope.model);
944                    }
945                    dateFields.day = new Date($scope.model).getDate();
946                    dateFields.month = new Date($scope.model).getMonth();
947                    dateFields.year = new Date($scope.model).getFullYear();
948                    $scope.dateFields = dateFields;
949                }
950            }
951            function dateFieldsChanged(newDate, oldDate) {
952                if (newDate == oldDate) {
953                    return;
954                }
955                if (!$scope.dateFields) {
956                    return;
957                }
958                if (!$scope.dateFields.year) {
959                    return;
960                }
961                if ($scope.dateFields.month === 0) {} else {
962                    if (!$scope.dateFields.month) {
963                        return;
964                    }
965                }
966                if (!$scope.dateFields.day) {
967                    return;
968                }
969                var date = new Date($scope.dateFields.year, $scope.dateFields.month, $scope.dateFields.day);
970                date.setHours(0, 0, 0, 0);
971                if (!$scope.model) {
972                    $scope.model = new Date();
973                }
974                $scope.model.setDate(date.getDate());
975                $scope.model.setMonth(date.getMonth());
976                $scope.model.setFullYear(date.getFullYear());
977            }
978            $scope.checkDate = function() {
979                var date = dateutils.checkDate($scope.dateFields);
980                if (date) {
981                    $scope.dateFields = date;
982                }
983            };
984        } ]
985    };
986} ]);
987
988app.run(function(formlyConfig, $templateCache) {
989    formlyConfig.setType({
990        name: "dob-select",
991        overwriteOk: true,
992        templateUrl: "fluro-interaction-form/dob-select/fluro-dob-select.html",
993        wrapper: [ "bootstrapHasError" ]
994    });
995});
996
997app.run(function(formlyConfig, $templateCache) {
998    formlyConfig.setType({
999        name: "embedded",
1000        overwriteOk: true,
1001        templateUrl: "fluro-interaction-form/embedded/fluro-embedded.html",
1002        controller: "FluroInteractionNestedController",
1003        wrapper: [ "bootstrapHasError" ]
1004    });
1005});
1006
1007app.directive("interactionForm", function($compile) {
1008    return {
1009        restrict: "E",
1010        replace: true,
1011        scope: {
1012            model: "=ngModel",
1013            integration: "=ngPaymentIntegration",
1014            vm: "=?config",
1015            callback: "=?callback"
1016        },
1017        transclude: true,
1018        controller: "InteractionFormController",
1019        template: '<div class="fluro-interaction-form" ng-transclude-here />',
1020        link: function($scope, $element, $attrs, $ctrl, $transclude) {
1021            $transclude($scope, function(clone, $scope) {
1022                $element.find("[ng-transclude-here]").append(clone);
1023            });
1024        }
1025    };
1026});
1027
1028app.directive("webform", function($compile) {
1029    return {
1030        restrict: "E",
1031        replace: true,
1032        scope: {
1033            model: "=ngModel",
1034            integration: "=ngPaymentIntegration",
1035            vm: "=?config",
1036            debugMode: "=?debugMode",
1037            callback: "=?callback",
1038            linkedEvent: "=?linkedEvent"
1039        },
1040        transclude: true,
1041        controller: "InteractionFormController",
1042        templateUrl: "fluro-interaction-form/fluro-web-form.html",
1043        link: function($scope, $element, $attrs, $ctrl, $transclude) {
1044            $transclude($scope, function(clone, $scope) {
1045                $scope.transcludedContent = clone;
1046            });
1047        }
1048    };
1049});
1050
1051app.config(function(formlyConfigProvider) {
1052    formlyConfigProvider.setType({
1053        name: "currency",
1054        extends: "input",
1055        overwriteOk: true,
1056        controller: [ "$scope", function($scope) {} ],
1057        wrapper: [ "bootstrapLabel", "bootstrapHasError" ],
1058        defaultOptions: {
1059            ngModelAttrs: {
1060                currencyDirective: {
1061                    attribute: "ng-currency"
1062                },
1063                fractionValue: {
1064                    attribute: "fraction",
1065                    bound: "fraction"
1066                },
1067                minimum: {
1068                    attribute: "min",
1069                    bound: "min"
1070                },
1071                maximum: {
1072                    attribute: "max",
1073                    bound: "max"
1074                }
1075            },
1076            templateOptions: {
1077                customAttrVal: "",
1078                required: true,
1079                fraction: 2
1080            },
1081            validators: {
1082                validInput: {
1083                    expression: function($viewValue, $modelValue, scope) {
1084                        var numericValue = Number($modelValue);
1085                        if (isNaN(numericValue)) {
1086                            return false;
1087                        }
1088                        var minimumAmount = scope.options.data.minimumAmount;
1089                        var maximumAmount = scope.options.data.maximumAmount;
1090                        if (minimumAmount && numericValue < minimumAmount) {
1091                            return false;
1092                        }
1093                        if (maximumAmount && numericValue > maximumAmount) {
1094                            return false;
1095                        }
1096                        return true;
1097                    }
1098                }
1099            }
1100        }
1101    });
1102});
1103
1104app.run(function(formlyConfig, $templateCache) {
1105    formlyConfig.templateManipulators.postWrapper.push(function(template, options, scope) {
1106        var fluroErrorTemplate = $templateCache.get("fluro-interaction-form/field-errors.html");
1107        return "<div>" + template + fluroErrorTemplate + "</div>";
1108    });
1109    formlyConfig.setType({
1110        name: "multiInput",
1111        overwriteOk: true,
1112        templateUrl: "fluro-interaction-form/multi.html",
1113        defaultOptions: {
1114            noFormControl: true,
1115            wrapper: [ "bootstrapLabel", "bootstrapHasError" ],
1116            templateOptions: {
1117                inputOptions: {
1118                    wrapper: null
1119                }
1120            }
1121        },
1122        controller: [ "$scope", function($scope) {
1123            $scope.copyItemOptions = copyItemOptions;
1124            function copyItemOptions() {
1125                return angular.copy($scope.to.inputOptions);
1126            }
1127        } ]
1128    });
1129    formlyConfig.setType({
1130        name: "payment",
1131        overwriteOk: true,
1132        templateUrl: "fluro-interaction-form/payment/payment.html",
1133        defaultOptions: {
1134            noFormControl: true
1135        }
1136    });
1137    formlyConfig.setType({
1138        name: "custom",
1139        overwriteOk: true,
1140        templateUrl: "fluro-interaction-form/custom.html",
1141        controller: "CustomInteractionFieldController",
1142        wrapper: [ "bootstrapHasError" ]
1143    });
1144    formlyConfig.setType({
1145        name: "button-select",
1146        overwriteOk: true,
1147        templateUrl: "fluro-interaction-form/button-select/fluro-button-select.html",
1148        controller: "FluroInteractionButtonSelectController",
1149        wrapper: [ "bootstrapLabel", "bootstrapHasError" ]
1150    });
1151    formlyConfig.setType({
1152        name: "date-select",
1153        overwriteOk: true,
1154        templateUrl: "fluro-interaction-form/date-select/fluro-date-select.html",
1155        wrapper: [ "bootstrapLabel", "bootstrapHasError" ]
1156    });
1157    formlyConfig.setType({
1158        name: "terms",
1159        overwriteOk: true,
1160        templateUrl: "fluro-interaction-form/fluro-terms.html",
1161        wrapper: [ "bootstrapLabel", "bootstrapHasError" ]
1162    });
1163    formlyConfig.setType({
1164        name: "order-select",
1165        overwriteOk: true,
1166        templateUrl: "fluro-interaction-form/order-select/fluro-order-select.html",
1167        controller: "FluroInteractionButtonSelectController",
1168        wrapper: [ "bootstrapLabel", "bootstrapHasError" ]
1169    });
1170});
1171
1172app.controller("CustomInteractionFieldController", function($scope, FluroValidate) {
1173    $scope.$watch("model[options.key]", function(value) {
1174        if (value) {
1175            if ($scope.fc) {
1176                $scope.fc.$setTouched();
1177            }
1178        }
1179    }, true);
1180});
1181
1182app.controller("InteractionFormController", function($scope, $q, $timeout, $rootScope, FluroAccess, $parse, $filter, formlyValidationMessages, FluroContent, FluroContentRetrieval, FluroValidate, FluroInteraction) {
1183    if (!$scope.vm) {
1184        $scope.vm = {};
1185    }
1186    $formScope = $scope;
1187    function debugLog() {
1188        if ($scope.debugMode) {
1189            console.log(arguments);
1190        }
1191    }
1192    if ($scope.vm.defaultModel) {
1193        $scope.vm.model = angular.copy($scope.vm.defaultModel);
1194    } else {
1195        $scope.vm.model = {};
1196    }
1197    $scope.vm.modelFields = [];
1198    $scope.vm.state = "ready";
1199    $scope.correctPermissions = true;
1200    $scope.readyToSubmit = false;
1201    $scope.$watch("vm.modelForm.$invalid + vm.modelForm.$error", function() {
1202        var interactionForm = $scope.vm.modelForm;
1203        if (!interactionForm) {
1204            return $scope.readyToSubmit = false;
1205        }
1206        if (interactionForm.$invalid) {
1207            return $scope.readyToSubmit = false;
1208        }
1209        if (interactionForm.$error) {
1210            if (interactionForm.$error.required && interactionForm.$error.required.length) {
1211                return $scope.readyToSubmit = false;
1212            }
1213            if (interactionForm.$error.validInput && interactionForm.$error.validInput.length) {
1214                return $scope.readyToSubmit = false;
1215            }
1216        }
1217        $scope.readyToSubmit = true;
1218    }, true);
1219    formlyValidationMessages.addStringMessage("required", "This field is required");
1220    formlyValidationMessages.messages.validInput = function($viewValue, $modelValue, scope) {
1221        return scope.to.label + " is not a valid value";
1222    };
1223    formlyValidationMessages.messages.date = function($viewValue, $modelValue, scope) {
1224        return scope.to.label + " is not a valid date";
1225    };
1226    $scope.reset = function() {
1227        if ($scope.vm.defaultModel) {
1228            $scope.vm.model = angular.copy($scope.vm.defaultModel);
1229        } else {
1230            $scope.vm.model = {};
1231        }
1232        $scope.vm.modelForm.$setPristine();
1233        $scope.vm.options.resetModel();
1234        definitionModelChanged();
1235        $scope.response = null;
1236        $scope.vm.state = "ready";
1237        $scope.$broadcast("form-reset");
1238    };
1239    $scope.$watch("model", definitionModelChanged);
1240    function definitionModelChanged() {
1241        if (!$scope.model || $scope.model.parentType != "interaction") {
1242            return console.log("Model is not an interaction");
1243        }
1244        if ($scope.vm.defaultModel) {
1245            $scope.vm.model = angular.copy($scope.vm.defaultModel);
1246        } else {
1247            $scope.vm.model = {};
1248        }
1249        $scope.vm.modelFields = [];
1250        $scope.vm.state = "ready";
1251        $scope.vm.onSubmit = submitInteraction;
1252        $scope.promises = [];
1253        $scope.submitLabel = "Submit";
1254        if ($scope.model && $scope.model.data && $scope.model.data.submitLabel && $scope.model.data.submitLabel.length) {
1255            $scope.submitLabel = $scope.model.data.submitLabel;
1256        }
1257        var interactionFormSettings = $scope.model.data;
1258        if (!interactionFormSettings) {
1259            interactionFormSettings = {};
1260        }
1261        if (!interactionFormSettings.allowAnonymous && !interactionFormSettings.disableDefaultFields) {
1262            interactionFormSettings.requireFirstName = true;
1263            interactionFormSettings.requireLastName = true;
1264            interactionFormSettings.requireGender = true;
1265            interactionFormSettings.requireEmail = true;
1266            switch (interactionFormSettings.identifier) {
1267              case "both":
1268                interactionFormSettings.requireEmail = interactionFormSettings.requirePhone = true;
1269                break;
1270
1271              case "email":
1272                interactionFormSettings.requireEmail = true;
1273                break;
1274
1275              case "phone":
1276                interactionFormSettings.requirePhone = true;
1277                break;
1278
1279              case "either":
1280                interactionFormSettings.askPhone = true;
1281                interactionFormSettings.askEmail = true;
1282                break;
1283            }
1284        }
1285        var firstNameField;
1286        var lastNameField;
1287        var genderField;
1288        if (interactionFormSettings.askGender || interactionFormSettings.requireGender) {
1289            genderField = {
1290                key: "_gender",
1291                type: "select",
1292                templateOptions: {
1293                    type: "email",
1294                    label: "Title",
1295                    placeholder: "Please select a title",
1296                    options: [ {
1297                        name: "Mr",
1298                        value: "male"
1299                    }, {
1300                        name: "Ms / Mrs",
1301                        value: "female"
1302                    } ],
1303                    required: interactionFormSettings.requireGender,
1304                    onBlur: "to.focused=false",
1305                    onFocus: "to.focused=true"
1306                },
1307                validators: {
1308                    validInput: function($viewValue, $modelValue, scope) {
1309                        var value = $modelValue || $viewValue;
1310                        return value == "male" || value == "female";
1311                    }
1312                }
1313            };
1314        }
1315        if (interactionFormSettings.askFirstName || interactionFormSettings.requireFirstName) {
1316            firstNameField = {
1317                key: "_firstName",
1318                type: "input",
1319                templateOptions: {
1320                    type: "text",
1321                    label: "First Name",
1322                    placeholder: "Please enter your first name",
1323                    required: interactionFormSettings.requireFirstName,
1324                    onBlur: "to.focused=false",
1325                    onFocus: "to.focused=true"
1326                }
1327            };
1328        }
1329        if (interactionFormSettings.askLastName || interactionFormSettings.requireLastName) {
1330            lastNameField = {
1331                key: "_lastName",
1332                type: "input",
1333                templateOptions: {
1334                    type: "text",
1335                    label: "Last Name",
1336                    placeholder: "Please enter your last name",
1337                    required: interactionFormSettings.requireLastName,
1338                    onBlur: "to.focused=false",
1339                    onFocus: "to.focused=true"
1340                }
1341            };
1342        }
1343        if (genderField && firstNameField && lastNameField) {
1344            genderField.className = "col-sm-2";
1345            firstNameField.className = lastNameField.className = "col-sm-5";
1346            $scope.vm.modelFields.push({
1347                fieldGroup: [ genderField, firstNameField, lastNameField ],
1348                className: "row"
1349            });
1350        } else if (firstNameField && lastNameField && !genderField) {
1351            firstNameField.className = lastNameField.className = "col-sm-6";
1352            $scope.vm.modelFields.push({
1353                fieldGroup: [ firstNameField, lastNameField ],
1354                className: "row"
1355            });
1356        } else {
1357            if (genderField) {
1358                $scope.vm.modelFields.push(genderField);
1359            }
1360            if (firstNameField) {
1361                $scope.vm.modelFields.push(firstNameField);
1362            }
1363            if (lastNameField) {
1364                $scope.vm.modelFields.push(lastNameField);
1365            }
1366        }
1367        if (interactionFormSettings.askEmail || interactionFormSettings.requireEmail) {
1368            var newField = {
1369                key: "_email",
1370                type: "input",
1371                templateOptions: {
1372                    type: "email",
1373                    label: "Email Address",
1374                    placeholder: "Please enter a valid email address",
1375                    required: interactionFormSettings.requireEmail,
1376                    onBlur: "to.focused=false",
1377                    onFocus: "to.focused=true"
1378                },
1379                validators: {
1380                    validInput: function($viewValue, $modelValue, scope) {
1381                        var value = $modelValue || $viewValue;
1382                        return validator.isEmail(value);
1383                    }
1384                }
1385            };
1386            if (interactionFormSettings.identifier == "either") {
1387                newField.expressionProperties = {
1388                    "templateOptions.required": function($viewValue, $modelValue, scope) {
1389                        if (!scope.model._phoneNumber || !scope.model._phoneNumber.length) {
1390                            return true;
1391                        } else {
1392                            return false;
1393                        }
1394                    }
1395                };
1396            }
1397            $scope.vm.modelFields.push(newField);
1398        }
1399        if (interactionFormSettings.askPhone || interactionFormSettings.requirePhone) {
1400            var newField = {
1401                key: "_phoneNumber",
1402                type: "input",
1403                templateOptions: {
1404                    type: "tel",
1405                    label: "Contact Phone Number",
1406                    placeholder: "Please enter a contact phone number",
1407                    required: interactionFormSettings.requirePhone,
1408                    onBlur: "to.focused=false",
1409                    onFocus: "to.focused=true"
1410                }
1411            };
1412            if (interactionFormSettings.identifier == "either") {
1413                newField.expressionProperties = {
1414                    "templateOptions.required": function($viewValue, $modelValue, scope) {
1415                        if (!scope.model._email || !scope.model._email.length) {
1416                            return true;
1417                        } else {
1418                            return false;
1419                        }
1420                    }
1421                };
1422            }
1423            $scope.vm.modelFields.push(newField);
1424        }
1425        if (interactionFormSettings.askDOB || interactionFormSettings.requireDOB) {
1426            var newField = {
1427                key: "_dob",
1428                type: "dob-select",
1429                templateOptions: {
1430                    label: "Date of birth",
1431                    placeholder: "Please provide your date of birth",
1432                    required: interactionFormSettings.requireDOB,
1433                    maxDate: new Date(),
1434                    onBlur: "to.focused=false",
1435                    onFocus: "to.focused=true"
1436                }
1437            };
1438            $scope.vm.modelFields.push(newField);
1439        }
1440        function addFieldDefinition(array, fieldDefinition) {
1441            if (fieldDefinition.params && fieldDefinition.params.disableWebform) {
1442                return;
1443            }
1444            var newField = {};
1445            newField.key = fieldDefinition.key;
1446            if (fieldDefinition.className) {
1447                newField.className = fieldDefinition.className;
1448            }
1449            var templateOptions = {};
1450            templateOptions.type = "text";
1451            templateOptions.label = fieldDefinition.title;
1452            templateOptions.description = fieldDefinition.description;
1453            templateOptions.params = fieldDefinition.params;
1454            if (fieldDefinition.errorMessage) {
1455                templateOptions.errorMessage = fieldDefinition.errorMessage;
1456            }
1457            templateOptions.definition = fieldDefinition;
1458            if (fieldDefinition.placeholder && fieldDefinition.placeholder.length) {
1459                templateOptions.placeholder = fieldDefinition.placeholder;
1460            } else if (fieldDefinition.description && fieldDefinition.description.length) {
1461                templateOptions.placeholder = fieldDefinition.description;
1462            } else {
1463                templateOptions.placeholder = fieldDefinition.title;
1464            }
1465            templateOptions.required = fieldDefinition.minimum > 0;
1466            templateOptions.onBlur = "to.focused=false";
1467            templateOptions.onFocus = "to.focused=true";
1468            switch (fieldDefinition.directive) {
1469              case "reference-select":
1470              case "value-select":
1471                newField.type = "button-select";
1472                break;
1473
1474              case "select":
1475                newField.type = "select";
1476                break;
1477
1478              case "wysiwyg":
1479                newField.type = "textarea";
1480                break;
1481
1482              default:
1483                newField.type = fieldDefinition.directive;
1484                break;
1485            }
1486            switch (fieldDefinition.type) {
1487              case "reference":
1488                if (fieldDefinition.allowedReferences && fieldDefinition.allowedReferences.length) {
1489                    templateOptions.options = _.map(fieldDefinition.allowedReferences, function(ref) {
1490                        return {
1491                            name: ref.title,
1492                            value: ref._id
1493                        };
1494                    });
1495                } else {
1496                    templateOptions.options = [];
1497                    if (fieldDefinition.sourceQuery) {
1498                        var queryId = fieldDefinition.sourceQuery;
1499                        if (queryId._id) {
1500                            queryId = queryId._id;
1501                        }
1502                        var options = {};
1503                        if (fieldDefinition.queryTemplate) {
1504                            options.template = fieldDefinition.queryTemplate;
1505                            if (options.template._id) {
1506                                options.template = options.template._id;
1507                            }
1508                        }
1509                        var promise = FluroContentRetrieval.getQuery(queryId, options);
1510                        promise.then(function(res) {
1511                            templateOptions.options = _.map(res, function(ref) {
1512                                return {
1513                                    name: ref.title,
1514                                    value: ref._id
1515                                };
1516                            });
1517                        });
1518                    } else {
1519                        if (fieldDefinition.directive != "embedded") {
1520                            if (fieldDefinition.params.restrictType && fieldDefinition.params.restrictType.length) {
1521                                FluroContent.resource(fieldDefinition.params.restrictType).query().$promise.then(function(res) {
1522                                    templateOptions.options = _.map(res, function(ref) {
1523                                        return {
1524                                            name: ref.title,
1525                                            value: ref._id
1526                                        };
1527                                    });
1528                                });
1529                            }
1530                        }
1531                    }
1532                }
1533                break;
1534
1535              default:
1536                if (fieldDefinition.options && fieldDefinition.options.length) {
1537                    templateOptions.options = fieldDefinition.options;
1538                } else {
1539                    templateOptions.options = _.map(fieldDefinition.allowedValues, function(val) {
1540                        return {
1541                            name: val,
1542                            value: val
1543                        };
1544                    });
1545                }
1546                break;
1547            }
1548            if (fieldDefinition.attributes && _.keys(fieldDefinition.attributes).length) {
1549                newField.ngModelAttrs = _.reduce(fieldDefinition.attributes, function(results, attr, key) {
1550                    var customKey = "customAttr" + key;
1551                    results[customKey] = {
1552                        attribute: key
1553                    };
1554                    templateOptions[customKey] = attr;
1555                    return results;
1556                }, {});
1557            }
1558            if (fieldDefinition.directive != "custom") {
1559                switch (fieldDefinition.type) {
1560                  case "boolean":
1561                    if (fieldDefinition.params && fieldDefinition.params.storeCopy) {
1562                        newField.type = "terms";
1563                    } else {
1564                        newField.type = "checkbox";
1565                    }
1566                    break;
1567
1568                  case "number":
1569                  case "float":
1570                  case "integer":
1571                  case "decimal":
1572                    templateOptions.type = "input";
1573                    if (!newField.ngModelAttrs) {
1574                        newField.ngModelAttrs = {};
1575                    }
1576                    if (fieldDefinition.type == "integer") {
1577                        templateOptions.type = "number";
1578                        templateOptions.baseDefaultValue = 0;
1579                        newField.ngModelAttrs.customAttrpattern = {
1580                            attribute: "pattern"
1581                        };
1582                        newField.ngModelAttrs.customAttrinputmode = {
1583                            attribute: "inputmode"
1584                        };
1585                        templateOptions.customAttrpattern = "[0-9]*";
1586                        templateOptions.customAttrinputmode = "numeric";
1587                        if (fieldDefinition.params) {
1588                            if (parseInt(fieldDefinition.params.maxValue) !== 0) {
1589                                templateOptions.max = fieldDefinition.params.maxValue;
1590                            }
1591                            if (parseInt(fieldDefinition.params.minValue) !== 0) {
1592                                templateOptions.min = fieldDefinition.params.minValue;
1593                            } else {
1594                                templateOptions.min = 0;
1595                            }
1596                        }
1597                    }
1598                    break;
1599                }
1600            }
1601            if (fieldDefinition.maximum == 1) {
1602                if (fieldDefinition.type == "reference" && fieldDefinition.directive != "embedded") {
1603                    if (fieldDefinition.defaultReferences && fieldDefinition.defaultReferences.length) {
1604                        if (fieldDefinition.directive == "search-select") {
1605                            templateOptions.baseDefaultValue = fieldDefinition.defaultReferences[0];
1606                        } else {
1607                            templateOptions.baseDefaultValue = fieldDefinition.defaultReferences[0]._id;
1608                        }
1609                    }
1610                } else {
1611                    if (fieldDefinition.defaultValues && fieldDefinition.defaultValues.length) {
1612                        if (templateOptions.type == "number") {
1613                            templateOptions.baseDefaultValue = Number(fieldDefinition.defaultValues[0]);
1614                        } else {
1615                            templateOptions.baseDefaultValue = fieldDefinition.defaultValues[0];
1616                        }
1617                    }
1618                }
1619            } else {
1620                if (fieldDefinition.type == "reference" && fieldDefinition.directive != "embedded") {
1621                    if (fieldDefinition.defaultReferences && fieldDefinition.defaultReferences.length) {
1622                        if (fieldDefinition.directive == "search-select") {
1623                            templateOptions.baseDefaultValue = fieldDefinition.defaultReferences;
1624                        } else {
1625                            templateOptions.baseDefaultValue = _.map(fieldDefinition.defaultReferences, function(ref) {
1626                                return ref._id;
1627                            });
1628                        }
1629                    } else {
1630                        templateOptions.baseDefaultValue = [];
1631                    }
1632                } else {
1633                    if (fieldDefinition.defaultValues && fieldDefinition.defaultValues.length) {
1634                        if (templateOptions.type == "number") {
1635                            templateOptions.baseDefaultValue = _.map(fieldDefinition.defaultValues, function(val) {
1636                                return Number(val);
1637                            });
1638                        } else {
1639                            templateOptions.baseDefaultValue = fieldDefinition.defaultValues;
1640                        }
1641                    }
1642                }
1643            }
1644            newField.templateOptions = templateOptions;
1645            newField.validators = {
1646                validInput: function($viewValue, $modelValue, scope) {
1647                    var value = $modelValue || $viewValue;
1648                    if (!value) {
1649                        return true;
1650                    }
1651                    var valid = FluroValidate.validate(value, fieldDefinition);
1652                    if (!valid) {}
1653                    return valid;
1654                }
1655            };
1656            if (fieldDefinition.directive == "embedded") {
1657                newField.type = "embedded";
1658                if (fieldDefinition.maximum == 1 && fieldDefinition.minimum == 1) {
1659                    templateOptions.baseDefaultValue = {
1660                        data: {}
1661                    };
1662                } else {
1663                    var askCount = 0;
1664                    if (fieldDefinition.askCount) {
1665                        askCount = fieldDefinition.askCount;
1666                    }
1667                    if (fieldDefinition.minimum && askCount < fieldDefinition.minimum) {
1668                        askCount = fieldDefinition.minimum;
1669                    }
1670                    if (fieldDefinition.maximum && askCount > fieldDefinition.maximum) {
1671                        askCount = fieldDefinition.maximum;
1672                    }
1673                    var initialArray = [];
1674                    if (askCount) {
1675                        _.times(askCount, function() {
1676                            initialArray.push({});
1677                        });
1678                    }
1679                    templateOptions.baseDefaultValue = initialArray;
1680                }
1681                newField.data = {
1682                    fields: [],
1683                    dataFields: [],
1684                    replicatedFields: []
1685                };
1686                var fieldContainer = newField.data.fields;
1687                var dataFieldContainer = newField.data.dataFields;
1688                if (fieldDefinition.fields && fieldDefinition.fields.length) {
1689                    _.each(fieldDefinition.fields, function(sub) {
1690                        addFieldDefinition(fieldContainer, sub);
1691                    });
1692                }
1693                var promise = FluroContent.endpoint("defined/" + fieldDefinition.params.restrictType).get().$promise;
1694                promise.then(function(embeddedDefinition) {
1695                    if (embeddedDefinition && embeddedDefinition.fields && embeddedDefinition.fields.length) {
1696                        var childFields = embeddedDefinition.fields;
1697                        if (fieldDefinition.params.excludeKeys && fieldDefinition.params.excludeKeys.length) {
1698                            childFields = _.reject(childFields, function(f) {
1699                                return _.includes(fieldDefinition.params.excludeKeys, f.key);
1700                            });
1701                        }
1702                        _.each(childFields, function(sub) {
1703                            addFieldDefinition(dataFieldContainer, sub);
1704                        });
1705                    }
1706                });
1707                $scope.promises.push(promise);
1708            }
1709            if (fieldDefinition.type == "group" && fieldDefinition.fields && fieldDefinition.fields.length || fieldDefinition.asObject) {
1710                var fieldContainer;
1711                if (fieldDefinition.asObject) {
1712                    newField.type = "nested";
1713                    if (fieldDefinition.key && fieldDefinition.maximum == 1 && fieldDefinition.minimum == 1) {
1714                        templateOptions.baseDefaultValue = {};
1715                    } else {
1716                        var askCount = 0;
1717                        if (fieldDefinition.askCount) {
1718                            askCount = fieldDefinition.askCount;
1719                        }
1720                        if (fieldDefinition.minimum && askCount < fieldDefinition.minimum) {
1721                            askCount = fieldDefinition.minimum;
1722                        }
1723                        if (fieldDefinition.maximum && askCount > fieldDefinition.maximum) {
1724                            askCount = fieldDefinition.maximum;
1725                        }
1726                        var initialArray = [];
1727                        if (askCount) {
1728                            _.times(askCount, function() {
1729                                initialArray.push({});
1730                            });
1731                        }
1732                        templateOptions.baseDefaultValue = initialArray;
1733                    }
1734                    newField.data = {
1735                        fields: [],
1736                        replicatedFields: []
1737                    };
1738                    fieldContainer = newField.data.fields;
1739                } else {
1740                    newField = {
1741                        fieldGroup: [],
1742                        className: fieldDefinition.className
1743                    };
1744                    fieldContainer = newField.fieldGroup;
1745                }
1746                _.each(fieldDefinition.fields, function(sub) {
1747                    addFieldDefinition(fieldContainer, sub);
1748                });
1749            }
1750            if (fieldDefinition.expressions && _.keys(fieldDefinition.expressions).length) {
1751                if (fieldDefinition.hideExpression && fieldDefinition.hideExpression.length) {
1752                    fieldDefinition.expressions.hide = fieldDefinition.hideExpression;
1753                }
1754                var allExpressions = _.values(fieldDefinition.expressions).join("+");
1755                newField.watcher = {
1756                    expression: function(field, scope) {
1757                        return $parse(allExpressions)(scope);
1758                    },
1759                    listener: function(field, newValue, oldValue, scope, stopWatching) {
1760                        var checkScope = {
1761                            model: scope.model,
1762                            data: $scope.vm.model,
1763                            interaction: $scope.vm.model
1764                        };
1765                        _.each(fieldDefinition.expressions, function(expression, key) {
1766                            var retrievedValue = $parse(expression)(checkScope);
1767                            debugLog("expression:", retrievedValue, checkScope);
1768                            var fieldKey = field.key;
1769                            switch (key) {
1770                              case "defaultValue":
1771                                if (!field.formControl || !field.formControl.$dirty) {
1772                                    return scope.model[fieldKey] = retrievedValue;
1773                                }
1774                                break;
1775
1776                              case "value":
1777                                return scope.model[fieldKey] = retrievedValue;
1778                                break;
1779
1780                              case "required":
1781                                return field.templateOptions.required = retrievedValue;
1782                                break;
1783
1784                              case "hide":
1785                                return field.hide = retrievedValue;
1786                                break;
1787                            }
1788                        });
1789                    }
1790                };
1791            }
1792            if (fieldDefinition.hideExpression && fieldDefinition.hideExpression.length) {
1793                newField.hideExpression = function($viewValue, $modelValue, scope) {
1794                    var checkScope = {
1795                        model: scope.model,
1796                        data: $scope.vm.model,
1797                        interaction: $scope.vm.model
1798                    };
1799                    var parsedValue = $parse(fieldDefinition.hideExpression)(checkScope);
1800                    return parsedValue;
1801                };
1802            }
1803            if (!newField.fieldGroup) {
1804                newField.defaultValue = angular.copy(templateOptions.baseDefaultValue);
1805            }
1806            if (newField.type == "pathlink") {
1807                return;
1808            }
1809            array.push(newField);
1810        }
1811        _.each($scope.model.fields, function(fieldDefinition) {
1812            addFieldDefinition($scope.vm.modelFields, fieldDefinition);
1813        });
1814        if (!$scope.model.paymentDetails) {
1815            $scope.model.paymentDetails = {};
1816        }
1817        var paymentSettings = $scope.model.paymentDetails;
1818        if (paymentSettings.required || paymentSettings.allow) {
1819            var paymentWrapperFields = [];
1820            var paymentCardFields = [];
1821            if (paymentSettings.required) {
1822                debugLog("-- DEBUG -- Payment Settings Required");
1823                paymentWrapperFields.push({
1824                    templateUrl: "fluro-interaction-form/payment/payment-summary.html",
1825                    controller: [ "$scope", "$parse", function($scope, $parse) {
1826                        $scope.paymentDetails = paymentSettings;
1827                        var requiredAmount = paymentSettings.amount;
1828                        $formScope.vm.model.calculatedTotal = requiredAmount;
1829                        $scope.calculatedTotal = requiredAmount;
1830                        var watchString = "";
1831                        var modelVariables = _.chain(paymentSettings.modifiers).map(function(paymentModifier) {
1832                            var string = "(" + paymentModifier.expression + ") + (" + paymentModifier.condition + ")";
1833                            return string;
1834                        }
1834).flatten().compact().uniq().value();
1835                        if (modelVariables.length) {
1836                            watchString = modelVariables.join(' + "a" + ');
1837                        }
1838                        if (watchString.length) {
1839                            debugLog("Watching changes", watchString);
1840                            $scope.$watch(watchString, calculateTotal);
1841                        } else {
1842                            $scope.calculatedTotal = requiredAmount;
1843                            $scope.modifications = [];
1844                        }
1845                        function calculateTotal(watchString) {
1846                            debugLog("Recalculate total", watchString);
1847                            $scope.calculatedTotal = requiredAmount;
1848                            $scope.modifications = [];
1849                            if (!paymentSettings.modifiers || !paymentSettings.modifiers.length) {
1850                                debugLog("No payment modifiers set");
1851                                return;
1852                            }
1853                            _.each(paymentSettings.modifiers, function(modifier) {
1854                                var parsedValue = $parse(modifier.expression)($scope);
1855                                parsedValue = Number(parsedValue);
1856                                if (isNaN(parsedValue)) {
1857                                    debugLog("Payment modifier error", modifier.title, parsedValue);
1858                                    return;
1859                                }
1860                                var parsedCondition = true;
1861                                if (modifier.condition && String(modifier.condition).length) {
1862                                    parsedCondition = $parse(modifier.condition)($scope);
1863                                }
1864                                if (!parsedCondition) {
1865                                    debugLog("inactive", modifier.title, modifier, $scope);
1866                                    return;
1867                                }
1868                                var operator = "";
1869                                var operatingValue = "$" + parseFloat(parsedValue / 100).toFixed(2);
1870                                switch (modifier.operation) {
1871                                  case "add":
1872                                    operator = "+";
1873                                    $scope.calculatedTotal = $scope.calculatedTotal + parsedValue;
1874                                    break;
1875
1876                                  case "subtract":
1877                                    operator = "-";
1878                                    $scope.calculatedTotal = $scope.calculatedTotal - parsedValue;
1879                                    break;
1880
1881                                  case "divide":
1882                                    operator = "÷";
1883                                    operatingValue = parsedValue;
1884                                    $scope.calculatedTotal = $scope.calculatedTotal / parsedValue;
1885                                    break;
1886
1887                                  case "multiply":
1888                                    operator = "×";
1889                                    operatingValue = parsedValue;
1890                                    $scope.calculatedTotal = $scope.calculatedTotal * parsedValue;
1891                                    break;
1892
1893                                  case "set":
1894                                    $scope.calculatedTotal = parsedValue;
1895                                    break;
1896                                }
1897                                $scope.modifications.push({
1898                                    title: modifier.title,
1899                                    total: $scope.calculatedTotal,
1900                                    description: operator + " " + operatingValue,
1901                                    operation: modifier.operation
1902                                });
1903                            });
1904                            if (!$scope.calculatedTotal || isNaN($scope.calculatedTotal) || $scope.calculatedTotal < 0) {
1905                                $scope.calculatedTotal = 0;
1906                            }
1907                            $timeout(function() {
1908                                $formScope.vm.model.calculatedTotal = $scope.calculatedTotal;
1909                            });
1910                        }
1911                    } ]
1912                });
1913            } else {
1914                var amountDescription = "Please enter an amount (" + String(paymentSettings.currency).toUpperCase() + ")";
1915                var minimum = paymentSettings.minAmount;
1916                var maximum = paymentSettings.maxAmount;
1917                var defaultAmount = paymentSettings.amount;
1918                var paymentErrorMessage = "Invalid amount";
1919                if (minimum) {
1920                    minimum = parseInt(minimum) / 100;
1921                    paymentErrorMessage = "Amount must be a number at least " + $filter("currency")(minimum, "$");
1922                    amountDescription += "Enter at least " + $filter("currency")(minimum, "$") + " " + String(paymentSettings.currency).toUpperCase();
1923                }
1924                if (maximum) {
1925                    maximum = parseInt(maximum) / 100;
1926                    paymentErrorMessage = "Amount must be a number less than " + $filter("currency")(maximum, "$");
1927                    amountDescription += "Enter up to " + $filter("currency")(maximum, "$") + " " + String(paymentSettings.currency).toUpperCase();
1928                }
1929                if (minimum && maximum) {
1930                    amountDescription = "Enter a numeric amount between " + $filter("currency")(minimum) + " and  " + $filter("currency")(maximum) + " " + String(paymentSettings.currency).toUpperCase();
1931                    paymentErrorMessage = "Amount must be a number between " + $filter("currency")(minimum) + " and " + $filter("currency")(maximum);
1932                }
1933                var fieldConfig = {
1934                    key: "_paymentAmount",
1935                    type: "currency",
1936                    templateOptions: {
1937                        type: "text",
1938                        label: "Amount",
1939                        description: amountDescription,
1940                        placeholder: "0.00",
1941                        required: true,
1942                        errorMessage: paymentErrorMessage,
1943                        min: minimum,
1944                        max: maximum,
1945                        onBlur: "to.focused=false",
1946                        onFocus: "to.focused=true"
1947                    },
1948                    data: {
1949                        customMaxLength: 8,
1950                        minimumAmount: minimum,
1951                        maximumAmount: maximum
1952                    }
1953                };
1954                if (minimum) {
1955                    fieldConfig.defaultValue = minimum;
1956                }
1957                paymentWrapperFields.push({
1958                    template: "<hr/><h3>Payment Details</h3>"
1959                });
1960                paymentWrapperFields.push(fieldConfig);
1961            }
1962            var defaultCardName;
1963            var defaultCardNumber;
1964            var defaultCardExpMonth;
1965            var defaultCardExpYear;
1966            var defaultCardCVN;
1967            if ($scope.debugMode) {
1968                defaultCardName = "John Citizen";
1969                defaultCardNumber = "4242424242424242";
1970                defaultCardExpMonth = "05";
1971                defaultCardExpYear = "2020";
1972                defaultCardCVN = "123";
1973            }
1974            paymentCardFields.push({
1975                key: "_paymentCardName",
1976                type: "input",
1977                defaultValue: defaultCardName,
1978                templateOptions: {
1979                    type: "text",
1980                    label: "Name on Card",
1981                    placeholder: "eg. (John Smith)",
1982                    required: paymentSettings.required,
1983                    onBlur: "to.focused=false",
1984                    onFocus: "to.focused=true"
1985                }
1986            });
1987            paymentCardFields.push({
1988                key: "_paymentCardNumber",
1989                type: "input",
1990                defaultValue: defaultCardNumber,
1991                templateOptions: {
1992                    type: "text",
1993                    label: "Card Number",
1994                    placeholder: "Card Number (No dashes or spaces)",
1995                    required: paymentSettings.required,
1996                    onBlur: "to.focused=false",
1997                    onFocus: "to.focused=true"
1998                },
1999                validators: {
2000                    validInput: function($viewValue, $modelValue, scope) {
2001                        var luhnChk = function(a) {
2002                            return function(c) {
2003                                if (!c) {
2004                                    return false;
2005                                }
2006                                for (var l = c.length, b = 1, s = 0, v; l; ) v = parseInt(c.charAt(--l), 10), s += (b ^= 1) ? a[v] : v;
2007                                return s && 0 === s % 10;
2008                            };
2009                        }([ 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 ]);
2010                        var value = $modelValue || $viewValue;
2011                        var valid = luhnChk(value);
2012                        return valid;
2013                    }
2014                }
2015            });
2016            paymentCardFields.push({
2017                className: "row clearfix",
2018                fieldGroup: [ {
2019                    key: "_paymentCardExpMonth",
2020                    className: "col-xs-6 col-sm-5",
2021                    type: "input",
2022                    defaultValue: defaultCardExpMonth,
2023                    templateOptions: {
2024                        type: "text",
2025                        label: "Expiry Month",
2026                        placeholder: "MM",
2027                        required: paymentSettings.required,
2028                        onBlur: "to.focused=false",
2029                        onFocus: "to.focused=true"
2030                    }
2031                }, {
2032                    key: "_paymentCardExpYear",
2033                    className: "col-xs-6 col-sm-5",
2034                    type: "input",
2035                    defaultValue: defaultCardExpYear,
2036                    templateOptions: {
2037                        type: "text",
2038                        label: "Expiry Year",
2039                        placeholder: "YYYY",
2040                        required: paymentSettings.required,
2041                        onBlur: "to.focused=false",
2042                        onFocus: "to.focused=true"
2043                    }
2044                }, {
2045                    key: "_paymentCardCVN",
2046                    className: "col-xs-4 col-sm-2",
2047                    type: "input",
2048                    defaultValue: defaultCardCVN,
2049                    templateOptions: {
2050                        type: "text",
2051                        label: "CVN",
2052                        placeholder: "CVN",
2053                        required: paymentSettings.required,
2054                        onBlur: "to.focused=false",
2055                        onFocus: "to.focused=true"
2056                    }
2057                } ]
2058            });
2059            var cardDetailsField = {
2060                className: "payment-details",
2061                fieldGroup: paymentCardFields,
2062                hideExpression: function($viewValue, $modelValue, scope) {
2063                    if ($formScope.vm.model.calculatedTotal === 0) {
2064                        return true;
2065                    }
2066                }
2067            };
2068            if (paymentSettings.allowAlternativePayments && paymentSettings.paymentMethods && paymentSettings.paymentMethods.length) {
2069                var methodSelection = {
2070                    className: "payment-method-select",
2071                    data: {
2072                        fields: [ cardDetailsField ],
2073                        settings: paymentSettings
2074                    },
2075                    controller: [ "$scope", function($scope) {
2076                        $scope.settings = paymentSettings;
2077                        $scope.methodOptions = _.map(paymentSettings.paymentMethods, function(method) {
2078                            return method;
2079                        });
2080                        $scope.methodOptions.unshift({
2081                            title: "Pay with Card",
2082                            key: "card"
2083                        });
2084                        if (!$scope.model._paymentMethod) {
2085                            $scope.model._paymentMethod = "card";
2086                        }
2087                        $scope.selected = {
2088                            method: $scope.methodOptions[0]
2089                        };
2090                        $scope.selectMethod = function(method) {
2091                            $scope.settings.showOptions = false;
2092                            $scope.selected.method = method;
2093                            $scope.model._paymentMethod = method.key;
2094                        };
2095                    } ],
2096                    templateUrl: "fluro-interaction-form/payment/payment-method.html",
2097                    hideExpression: function($viewValue, $modelValue, scope) {
2098                        if ($formScope.vm.model.calculatedTotal === 0) {
2099                            return true;
2100                        }
2101                    }
2102                };
2103                paymentWrapperFields.push(methodSelection);
2104            } else {
2105                paymentWrapperFields.push(cardDetailsField);
2106            }
2107            $scope.vm.modelFields.push({
2108                fieldGroup: paymentWrapperFields
2109            });
2110        }
2111        if (!$scope.promises.length) {
2112            console.log("NO PROMISES");
2113            $scope.promisesResolved = true;
2114        } else {
2115            $scope.promisesResolved = false;
2116            $q.all($scope.promises).then(function() {
2117                console.log("Promises have been resolved");
2118                $scope.promisesResolved = true;
2119            });
2120        }
2121    }
2122    function getAllErrorFields(array) {
2123        return _.chain(array).map(function(field) {
2124            if (field.fieldGroup && field.fieldGroup.length) {
2125                return getAllErrorFields(field.fieldGroup);
2126            } else if (field.data && (field.data.fields && field.data.fields.length || field.data.dataFields && field.data.dataFields || field.data.replicatedFields && field.data.replicatedFields)) {
2127                var combined = [];
2128                combined = combined.concat(field.data.fields, field.data.dataFields, field.data.replicatedFields);
2129                combined = _.compact(combined);
2130                return getAllErrorFields(combined);
2131            } else {
2132                return field;
2133            }
2134        }).flatten().value();
2135    }
2136    $scope.$watch("vm.modelFields", function(fields) {
2137        $scope.errorList = getAllErrorFields(fields);
2138    }, true);
2139    function submitInteraction() {
2140        $scope.vm.state = "sending";
2141        var interactionKey = $scope.model.definitionName;
2142        var interactionDetails = angular.copy($scope.vm.model);
2143        var requiresPayment;
2144        var allowsPayment;
2145        var paymentConfiguration = $scope.model.paymentDetails;
2146        if (paymentConfiguration) {
2147            requiresPayment = paymentConfiguration.required;
2148            allowsPayment = paymentConfiguration.allow;
2149        }
2150        if (requiresPayment || allowsPayment) {
2151            var paymentDetails = {};
2152            if (paymentConfiguration.allowAlternativePayments && paymentConfiguration.paymentMethods) {
2153                var selectedMethod = interactionDetails._paymentMethod;
2154                if (selectedMethod && selectedMethod != "card") {
2155                    paymentDetails.method = selectedMethod;
2156                    return processRequest();
2157                }
2158            }
2159            if (requiresPayment) {
2160                if (!$formScope.vm.model.calculatedTotal) {
2161                    return processRequest();
2162                }
2163            }
2164            var paymentIntegration = $scope.integration;
2165            if (!paymentIntegration || !paymentIntegration.publicDetails) {
2166                alert("This form has not been configured properly. Please notify the administrator of this website immediately.");
2167                $scope.vm.state = "ready";
2168                return;
2169            }
2170            paymentDetails.integration = paymentIntegration._id;
2171            switch (paymentIntegration.module) {
2172              case "eway":
2173                if (!window.eCrypt) {
2174                    return $scope.vm.state = "ready";
2175                }
2176                var key = paymentIntegration.publicDetails.publicKey;
2177                var cardDetails = {};
2178                cardDetails.name = interactionDetails._paymentCardName;
2179                cardDetails.number = eCrypt.encryptValue(interactionDetails._paymentCardNumber, key);
2180                cardDetails.cvc = eCrypt.encryptValue(interactionDetails._paymentCardCVN, key);
2181                var expiryMonth = String(interactionDetails._paymentCardExpMonth);
2182                var expiryYear = String(interactionDetails._paymentCardExpYear);
2183                if (expiryMonth.length < 1) {
2184                    expiryMonth = "0" + expiryMonth;
2185                }
2186                cardDetails.exp_month = expiryMonth;
2187                cardDetails.exp_year = expiryYear.slice(-2);
2188                paymentDetails.details = cardDetails;
2189                return processRequest();
2190                break;
2191
2192              case "stripe":
2193                if (!window.Stripe) {
2194                    return $scope.vm.state = "ready";
2195                }
2196                var liveKey = paymentIntegration.publicDetails.livePublicKey;
2197                var sandboxKey = paymentIntegration.publicDetails.testPublicKey;
2198                var key = liveKey;
2199                if (paymentIntegration.publicDetails.sandbox) {
2200                    key = sandboxKey;
2201                }
2202                Stripe.setPublishableKey(key);
2203                var cardDetails = {};
2204                cardDetails.name = interactionDetails._paymentCardName;
2205                cardDetails.number = interactionDetails._paymentCardNumber;
2206                cardDetails.cvc = interactionDetails._paymentCardCVN;
2207                cardDetails.exp_month = interactionDetails._paymentCardExpMonth;
2208                cardDetails.exp_year = interactionDetails._paymentCardExpYear;
2209                Stripe.card.createToken(cardDetails, function(status, response) {
2210                    if (response.error) {
2211                        $scope.processErrorMessages = [ response.error.message ];
2212                        $scope.vm.state = "error";
2213                    } else {
2214                        paymentDetails.details = response;
2215                        return processRequest();
2216                    }
2217                });
2218                break;
2219            }
2220        } else {
2221            return processRequest();
2222        }
2223        function processRequest() {
2224            delete interactionDetails._paymentCardCVN;
2225            delete interactionDetails._paymentCardExpMonth;
2226            delete interactionDetails._paymentCardExpYear;
2227            delete interactionDetails._paymentCardName;
2228            delete interactionDetails._paymentCardNumber;
2229            if (interactionDetails._paymentAmount) {
2230                paymentDetails.amount = parseFloat(interactionDetails._paymentAmount) * 100;
2231            }
2232            var request = FluroInteraction.interact($scope.model.title, interactionKey, interactionDetails, paymentDetails, $scope.linkedEvent);
2233            request.then(submissionSuccess, submissionFail);
2234            function submissionSuccess(res) {
2235                if ($scope.vm.defaultModel) {
2236                    $scope.vm.model = angular.copy($scope.vm.defaultModel);
2237                } else {
2238                    $scope.vm.model = {};
2239                }
2240                $scope.vm.modelForm.$setPristine();
2241                $scope.vm.options.resetModel();
2242                $formScope = $scope;
2243                $scope.response = res;
2244                $scope.vm.state = "complete";
2245            }
2246            function submissionFail(res) {
2247                $scope.vm.state = "error";
2248                if (!res.data) {
2249                    return $scope.processErrorMessages = [ "Error: " + res ];
2250                }
2251                if (res.data.error) {
2252                    if (res.data.error.message) {
2253                        return $scope.processErrorMessages = [ res.error.message ];
2254                    } else {
2255                        return $scope.processErrorMessages = [ res.error ];
2256                    }
2257                }
2258                if (res.data.errors) {
2259                    return $scope.processErrorMessages = _.map(res.data.errors, function(error) {
2260                        return error.message;
2261                    });
2262                }
2263                if (_.isArray(res.data)) {
2264                    return $scope.processErrorMessages = res.data;
2265                } else {
2266                    $scope.processErrorMessages = [ res.data ];
2267                }
2268            }
2269        }
2270    }
2271});
2272
2273app.directive("postForm", function($compile) {
2274    return {
2275        restrict: "E",
2276        scope: {
2277            model: "=ngModel",
2278            host: "=hostId",
2279            reply: "=?reply",
2280            thread: "=?thread",
2281            userStore: "=?user",
2282            vm: "=?config",
2283            debugMode: "=?debugMode",
2284            callback: "=?callback"
2285        },
2286        transclude: true,
2287        controller: "PostFormController",
2288        templateUrl: "fluro-interaction-form/fluro-web-form.html",
2289        link: function($scope, $element, $attrs, $ctrl, $transclude) {
2290            $transclude($scope, function(clone, $scope) {
2291                $scope.transcludedContent = clone;
2292            });
2293        }
2294    };
2295});
2296
2297app.directive("recaptchaRender", function($window) {
2298    return {
2299        restrict: "A",
2300        link: function($scope, $element, $attrs, $ctrl) {
2301            if ($scope.model.data && $scope.model.data.recaptcha) {
2302                var element = $element[0];
2303                var cancelWatch;
2304                if (window.grecaptcha) {
2305                    activateRecaptcha(window.grecaptcha);
2306                } else {
2307                    cancelWatch = $scope.$watch(function() {
2308                        return window.grecaptcha;
2309                    }, activateRecaptcha);
2310                }
2311                function activateRecaptcha(recaptcha) {
2312                    if (cancelWatch) {
2313                        cancelWatch();
2314                    }
2315                    if (recaptcha) {
2316                        $scope.vm.recaptchaID = recaptcha.render(element, {
2317                            sitekey: "6LelOyUTAAAAADSACojokFPhb9AIzvrbGXyd-33z"
2318                        });
2319                    }
2320                }
2321            }
2322        }
2323    };
2324});
2325
2326app.controller("PostFormController", function($scope, $rootScope, $q, $http, Fluro, FluroAccess, $parse, $filter, formlyValidationMessages, FluroContent, FluroContentRetrieval, FluroValidate, FluroInteraction) {
2327    if (!$scope.thread) {
2328        $scope.thread = [];
2329    }
2330    if (!$scope.vm) {
2331        $scope.vm = {};
2332    }
2333    $scope.promisesResolved = true;
2334    $scope.correctPermissions = true;
2335    if ($scope.vm.defaultModel) {
2336        $scope.vm.model = angular.copy($scope.vm.defaultModel);
2337    } else {
2338        $scope.vm.model = {};
2339    }
2340    $scope.vm.modelFields = [];
2341    $scope.vm.state = "ready";
2342    $scope.readyToSubmit = false;
2343    $scope.$watch("vm.modelForm.$invalid + vm.modelForm.$error", function() {
2344        var interactionForm = $scope.vm.modelForm;
2345        if (!interactionForm) {
2346            return $scope.readyToSubmit = false;
2347        }
2348        if (interactionForm.$invalid) {
2349            return $scope.readyToSubmit = false;
2350        }
2351        if (interactionForm.$error) {
2352            if (interactionForm.$error.required && interactionForm.$error.required.length) {
2353                return $scope.readyToSubmit = false;
2354            }
2355            if (interactionForm.$error.validInput && interactionForm.$error.validInput.length) {
2356                return $scope.readyToSubmit = false;
2357            }
2358        }
2359        $scope.readyToSubmit = true;
2360    }, true);
2361    formlyValidationMessages.addStringMessage("required", "This field is required");
2362    formlyValidationMessages.messages.validInput = function($viewValue, $modelValue, scope) {
2363        return scope.to.label + " is not a valid value";
2364    };
2365    formlyValidationMessages.messages.date = function($viewValue, $modelValue, scope) {
2366        return scope.to.label + " is not a valid date";
2367    };
2368    function resetCaptcha() {
2369        var recaptchaID = $scope.vm.recaptchaID;
2370        if (window.grecaptcha && recaptchaID) {
2371            window.grecaptcha.reset(recaptchaID);
2372        }
2373    }
2374    $scope.reset = function() {
2375        if ($scope.vm.defaultModel) {
2376            $scope.vm.model = angular.copy($scope.vm.defaultModel);
2377        } else {
2378            $scope.vm.model = {};
2379        }
2380        $scope.vm.modelForm.$setPristine();
2381        $scope.vm.options.resetModel();
2382        resetCaptcha();
2383        $scope.response = null;
2384        $scope.vm.state = "ready";
2385        $scope.$broadcast("form-reset");
2386    };
2387    $scope.$watch("model", function(newData, oldData) {
2388        if (!$scope.model || $scope.model.parentType != "post") {
2389            return;
2390        }
2391        if ($scope.vm.defaultModel) {
2392            $scope.vm.model = angular.copy($scope.vm.defaultModel);
2393        } else {
2394            $scope.vm.model = {};
2395        }
2396        $scope.vm.modelFields = [];
2397        $scope.vm.state = "ready";
2398        $scope.vm.onSubmit = submitPost;
2399        $scope.promises = [];
2400        $scope.submitLabel = "Submit";
2401        if ($scope.model && $scope.model.data && $scope.model.data.submitLabel && $scope.model.data.submitLabel.length) {
2402            $scope.submitLabel = $scope.model.data.submitLabel;
2403        }
2404        var interactionFormSettings = $scope.model.data;
2405        if (!interactionFormSettings) {
2406            interactionFormSettings = {};
2407        }
2408        function addFieldDefinition(array, fieldDefinition) {
2409            if (fieldDefinition.params && fieldDefinition.params.disableWebform) {
2410                return;
2411            }
2412            var newField = {};
2413            newField.key = fieldDefinition.key;
2414            if (fieldDefinition.className) {
2415                newField.className = fieldDefinition.className;
2416            }
2417            var templateOptions = {};
2418            templateOptions.type = "text";
2419            templateOptions.label = fieldDefinition.title;
2420            templateOptions.description = fieldDefinition.description;
2421            templateOptions.params = fieldDefinition.params;
2422            if (fieldDefinition.errorMessage) {
2423                templateOptions.errorMessage = fieldDefinition.errorMessage;
2424            }
2425            templateOptions.definition = fieldDefinition;
2426            if (fieldDefinition.placeholder && fieldDefinition.placeholder.length) {
2427                templateOptions.placeholder = fieldDefinition.placeholder;
2428            } else if (fieldDefinition.description && fieldDefinition.description.length) {
2429                templateOptions.placeholder = fieldDefinition.description;
2430            } else {
2431                templateOptions.placeholder = fieldDefinition.title;
2432            }
2433            templateOptions.required = fieldDefinition.minimum > 0;
2434            templateOptions.onBlur = "to.focused=false";
2435            templateOptions.onFocus = "to.focused=true";
2436            switch (fieldDefinition.directive) {
2437              case "reference-select":
2438              case "value-select":
2439                newField.type = "button-select";
2440                break;
2441
2442              case "select":
2443                newField.type = "select";
2444                break;
2445
2446              case "wysiwyg":
2447                newField.type = "textarea";
2448                break;
2449
2450              default:
2451                newField.type = fieldDefinition.directive;
2452                break;
2453            }
2454            switch (fieldDefinition.type) {
2455              case "reference":
2456                if (fieldDefinition.allowedReferences && fieldDefinition.allowedReferences.length) {
2457                    templateOptions.options = _.map(fieldDefinition.allowedReferences, function(ref) {
2458                        return {
2459                            name: ref.title,
2460                            value: ref._id
2461                        };
2462                    });
2463                } else {
2464                    templateOptions.options = [];
2465                    if (fieldDefinition.sourceQuery) {
2466                        var queryId = fieldDefinition.sourceQuery;
2467                        if (queryId._id) {
2468                            queryId = queryId._id;
2469                        }
2470                        var options = {};
2471                        if (fieldDefinition.queryTemplate) {
2472                            options.template = fieldDefinition.queryTemplate;
2473                            if (options.template._id) {
2474                                options.template = options.template._id;
2475                            }
2476                        }
2477                        var promise = FluroContentRetrieval.getQuery(queryId, options);
2478                        promise.then(function(res) {
2479                            templateOptions.options = _.map(res, function(ref) {
2480                                return {
2481                                    name: ref.title,
2482                                    value: ref._id
2483                                };
2484                            });
2485                        });
2486                    } else {
2487                        if (fieldDefinition.directive != "embedded") {
2488                            if (fieldDefinition.params.restrictType && fieldDefinition.params.restrictType.length) {
2489                                FluroContent.resource(fieldDefinition.params.restrictType).query().$promise.then(function(res) {
2490                                    templateOptions.options = _.map(res, function(ref) {
2491                                        return {
2492                                            name: ref.title,
2493                                            value: ref._id
2494                                        };
2495                                    });
2496                                });
2497                            }
2498                        }
2499                    }
2500                }
2501                break;
2502
2503              default:
2504                if (fieldDefinition.options && fieldDefinition.options.length) {
2505                    templateOptions.options = fieldDefinition.options;
2506                } else {
2507                    templateOptions.options = _.map(fieldDefinition.allowedValues, function(val) {
2508                        return {
2509                            name: val,
2510                            value: val
2511                        };
2512                    });
2513                }
2514                break;
2515            }
2516            if (fieldDefinition.attributes && _.keys(fieldDefinition.attributes).length) {
2517                newField.ngModelAttrs = _.reduce(fieldDefinition.attributes, function(results, attr, key) {
2518                    var customKey = "customAttr" + key;
2519                    results[customKey] = {
2520                        attribute: key
2521                    };
2522                    templateOptions[customKey] = attr;
2523                    return results;
2524                }, {});
2525            }
2526            if (fieldDefinition.directive != "custom") {
2527                switch (fieldDefinition.type) {
2528                  case "boolean":
2529                    if (fieldDefinition.params && fieldDefinition.params.storeCopy) {
2530                        newField.type = "terms";
2531                    } else {
2532                        newField.type = "checkbox";
2533                    }
2534                    break;
2535
2536                  case "number":
2537                  case "float":
2538                  case "integer":
2539                  case "decimal":
2540                    templateOptions.type = "input";
2541                    if (!newField.ngModelAttrs) {
2542                        newField.ngModelAttrs = {};
2543                    }
2544                    if (fieldDefinition.type == "integer") {
2545                        templateOptions.type = "number";
2546                        templateOptions.baseDefaultValue = 0;
2547                        newField.ngModelAttrs.customAttrpattern = {
2548                            attribute: "pattern"
2549                        };
2550                        newField.ngModelAttrs.customAttrinputmode = {
2551                            attribute: "inputmode"
2552                        };
2553                        templateOptions.customAttrpattern = "[0-9]*";
2554                        templateOptions.customAttrinputmode = "numeric";
2555                        if (fieldDefinition.params) {
2556                            if (parseInt(fieldDefinition.params.maxValue) !== 0) {
2557                                templateOptions.max = fieldDefinition.params.maxValue;
2558                            }
2559                            if (parseInt(fieldDefinition.params.minValue) !== 0) {
2560                                templateOptions.min = fieldDefinition.params.minValue;
2561                            } else {
2562                                templateOptions.min = 0;
2563                            }
2564                        }
2565                    }
2566                    break;
2567                }
2568            }
2569            if (fieldDefinition.maximum == 1) {
2570                if (fieldDefinition.type == "reference" && fieldDefinition.directive != "embedded") {
2571                    if (fieldDefinition.defaultReferences && fieldDefinition.defaultReferences.length) {
2572                        if (fieldDefinition.directive == "search-select") {
2573                            templateOptions.baseDefaultValue = fieldDefinition.defaultReferences[0];
2574                        } else {
2575                            templateOptions.baseDefaultValue = fieldDefinition.defaultReferences[0]._id;
2576                        }
2577                    }
2578                } else {
2579                    if (fieldDefinition.defaultValues && fieldDefinition.defaultValues.length) {
2580                        if (templateOptions.type == "number") {
2581                            templateOptions.baseDefaultValue = Number(fieldDefinition.defaultValues[0]);
2582                        } else {
2583                            templateOptions.baseDefaultValue = fieldDefinition.defaultValues[0];
2584                        }
2585                    }
2586                }
2587            } else {
2588                if (fieldDefinition.type == "reference" && fieldDefinition.directive != "embedded") {
2589                    if (fieldDefinition.defaultReferences && fieldDefinition.defaultReferences.length) {
2590                        if (fieldDefinition.directive == "search-select") {
2591                            templateOptions.baseDefaultValue = fieldDefinition.defaultReferences;
2592                        } else {
2593                            templateOptions.baseDefaultValue = _.map(fieldDefinition.defaultReferences, function(ref) {
2594                                return ref._id;
2595                            });
2596                        }
2597                    } else {
2598                        templateOptions.baseDefaultValue = [];
2599                    }
2600                } else {
2601                    if (fieldDefinition.defaultValues && fieldDefinition.defaultValues.length) {
2602                        if (templateOptions.type == "number") {
2603                            templateOptions.baseDefaultValue = _.map(fieldDefinition.defaultValues, function(val) {
2604                                return Number(val);
2605                            });
2606                        } else {
2607                            templateOptions.baseDefaultValue = fieldDefinition.defaultValues;
2608                        }
2609                    }
2610                }
2611            }
2612            newField.templateOptions = templateOptions;
2613            newField.validators = {
2614                validInput: function($viewValue, $modelValue, scope) {
2615                    var value = $modelValue || $viewValue;
2616                    if (!value) {
2617                        return true;
2618                    }
2619                    var valid = FluroValidate.validate(value, fieldDefinition);
2620                    if (!valid) {}
2621                    return valid;
2622                }
2623            };
2624            if (fieldDefinition.directive == "embedded") {
2625                newField.type = "embedded";
2626                if (fieldDefinition.maximum == 1 && fieldDefinition.minimum == 1) {
2627                    templateOptions.baseDefaultValue = {
2628                        data: {}
2629                    };
2630                } else {
2631                    var askCount = 0;
2632                    if (fieldDefinition.askCount) {
2633                        askCount = fieldDefinition.askCount;
2634                    }
2635                    if (fieldDefinition.minimum && askCount < fieldDefinition.minimum) {
2636                        askCount = fieldDefinition.minimum;
2637                    }
2638                    if (fieldDefinition.maximum && askCount > fieldDefinition.maximum) {
2639                        askCount = fieldDefinition.maximum;
2640                    }
2641                    var initialArray = [];
2642                    if (askCount) {
2643                        _.times(askCount, function() {
2644                            initialArray.push({});
2645                        });
2646                    }
2647                    templateOptions.baseDefaultValue = initialArray;
2648                }
2649                newField.data = {
2650                    fields: [],
2651                    dataFields: [],
2652                    replicatedFields: []
2653                };
2654                var fieldContainer = newField.data.fields;
2655                var dataFieldContainer = newField.data.dataFields;
2656                if (fieldDefinition.fields && fieldDefinition.fields.length) {
2657                    _.each(fieldDefinition.fields, function(sub) {
2658                        addFieldDefinition(fieldContainer, sub);
2659                    });
2660                }
2661                var promise = FluroContent.endpoint("defined/" + fieldDefinition.params.restrictType).get().$promise;
2662                promise.then(function(embeddedDefinition) {
2663                    if (embeddedDefinition && embeddedDefinition.fields && embeddedDefinition.fields.length) {
2664                        var childFields = embeddedDefinition.fields;
2665                        if (fieldDefinition.params.excludeKeys && fieldDefinition.params.excludeKeys.length) {
2666                            childFields = _.reject(childFields, function(f) {
2667                                return _.includes(fieldDefinition.params.excludeKeys, f.key);
2668                            });
2669                        }
2670                        _.each(childFields, function(sub) {
2671                            addFieldDefinition(dataFieldContainer, sub);
2672                        });
2673                    }
2674                });
2675                $scope.promises.push(promise);
2676            }
2677            if (fieldDefinition.type == "group" && fieldDefinition.fields && fieldDefinition.fields.length || fieldDefinition.asObject) {
2678                var fieldContainer;
2679                if (fieldDefinition.asObject) {
2680                    newField.type = "nested";
2681                    if (fieldDefinition.key && fieldDefinition.maximum == 1 && fieldDefinition.minimum == 1) {
2682                        templateOptions.baseDefaultValue = {};
2683                    } else {
2684                        var askCount = 0;
2685                        if (fieldDefinition.askCount) {
2686                            askCount = fieldDefinition.askCount;
2687                        }
2688                        if (fieldDefinition.minimum && askCount < fieldDefinition.minimum) {
2689                            askCount = fieldDefinition.minimum;
2690                        }
2691                        if (fieldDefinition.maximum && askCount > fieldDefinition.maximum) {
2692                            askCount = fieldDefinition.maximum;
2693                        }
2694                        var initialArray = [];
2695                        if (askCount) {
2696                            _.times(askCount, function() {
2697                                initialArray.push({});
2698                            });
2699                        }
2700                        templateOptions.baseDefaultValue = initialArray;
2701                    }
2702                    newField.data = {
2703                        fields: [],
2704                        replicatedFields: []
2705                    };
2706                    fieldContainer = newField.data.fields;
2707                } else {
2708                    newField = {
2709                        fieldGroup: [],
2710                        className: fieldDefinition.className
2711                    };
2712                    fieldContainer = newField.fieldGroup;
2713                }
2714                _.each(fieldDefinition.fields, function(sub) {
2715                    addFieldDefinition(fieldContainer, sub);
2716                });
2717            }
2718            if (fieldDefinition.expressions && _.keys(fieldDefinition.expressions).length) {
2719                if (fieldDefinition.hideExpression && fieldDefinition.hideExpression.length) {
2720                    fieldDefinition.expressions.hide = fieldDefinition.hideExpression;
2721                }
2722                var allExpressions = _.values(fieldDefinition.expressions).join("+");
2723                newField.watcher = {
2724                    expression: function(field, scope) {
2725                        return $parse(allExpressions)(scope);
2726                    },
2727                    listener: function(field, newValue, oldValue, scope, stopWatching) {
2728                        if (!scope.interaction) {
2729                            scope.interaction = $scope.vm.model;
2730                        }
2731                        _.each(fieldDefinition.expressions, function(expression, key) {
2732                            var retrievedValue = $parse(expression)(scope);
2733                            var fieldKey = field.key;
2734                            switch (key) {
2735                              case "defaultValue":
2736                                if (!field.formControl || !field.formControl.$dirty) {
2737                                    return scope.model[fieldKey] = retrievedValue;
2738                                }
2739                                break;
2740
2741                              case "value":
2742                                return scope.model[fieldKey] = retrievedValue;
2743                                break;
2744
2745                              case "required":
2746                                return field.templateOptions.required = retrievedValue;
2747                                break;
2748
2749                              case "hide":
2750                                return field.hide = retrievedValue;
2751                                break;
2752                            }
2753                        });
2754                    }
2755                };
2756            }
2757            if (fieldDefinition.hideExpression) {
2758                newField.hideExpression = fieldDefinition.hideExpression;
2759            }
2760            if (!newField.fieldGroup) {
2761                newField.defaultValue = angular.copy(templateOptions.baseDefaultValue);
2762            }
2763            if (newField.type == "pathlink") {
2764                return;
2765            }
2766            array.push(newField);
2767        }
2768        _.each($scope.model.fields, function(fieldDefinition) {
2769            addFieldDefinition($scope.vm.modelFields, fieldDefinition);
2770        });
2771    });
2772    function getAllErrorFields(array) {
2773        return _.chain(array).map(function(field) {
2774            if (field.fieldGroup && field.fieldGroup.length) {
2775                return getAllErrorFields(field.fieldGroup);
2776            } else if (field.data && (field.data.fields && field.data.fields.length || field.data.dataFields && field.data.dataFields || field.data.replicatedFields && field.data.replicatedFields)) {
2777                var combined = [];
2778                combined = combined.concat(field.data.fields, field.data.dataFields, field.data.replicatedFields);
2779                combined = _.compact(combined);
2780                return getAllErrorFields(combined);
2781            } else {
2782                return field;
2783            }
2784        }
2784).flatten().value();
2785    }
2786    $scope.$watch("vm.modelFields", function(fields) {
2787        $scope.errorList = getAllErrorFields(fields);
2788    }, true);
2789    function submitPost() {
2790        $scope.vm.state = "sending";
2791        var submissionKey = $scope.model.definitionName;
2792        var submissionModel = {
2793            data: angular.copy($scope.vm.model)
2794        };
2795        var hostID = $scope.host;
2796        if ($scope.reply) {
2797            submissionModel.reply = $scope.reply;
2798        }
2799        if (typeof $scope.vm.recaptchaID !== "undefined") {
2800            var response = window.grecaptcha.getResponse($scope.vm.recaptchaID);
2801            submissionModel["g-recaptcha-response"] = response;
2802        }
2803        var request;
2804        if ($scope.userStore) {
2805            $scope.userStore.config().then(function(config) {
2806                var postURL = Fluro.apiURL + "/post/" + hostID + "/" + submissionKey;
2807                request = $http.post(postURL, submissionModel, config);
2808                request.then(function(res) {
2809                    return submissionSuccess(res.data);
2810                }, function(res) {
2811                    return submissionFail(res.data);
2812                });
2813            });
2814        } else {
2815            request = FluroContent.endpoint("post/" + hostID + "/" + submissionKey).save(submissionModel).$promise;
2816            request.then(submissionSuccess, submissionFail);
2817        }
2818        function submissionSuccess(res) {
2819            if ($scope.vm.defaultModel) {
2820                $scope.vm.model = angular.copy($scope.vm.defaultModel);
2821            } else {
2822                $scope.vm.model = {
2823                    data: {}
2824                };
2825            }
2826            $scope.vm.modelForm.$setPristine();
2827            $scope.vm.options.resetModel();
2828            resetCaptcha();
2829            $scope.response = res;
2830            if ($scope.thread) {
2831                $scope.thread.push(res);
2832            }
2833            $scope.vm.state = "complete";
2834        }
2835        function submissionFail(res) {
2836            $scope.vm.state = "error";
2837            if (!res.data) {
2838                return $scope.processErrorMessages = [ "Error: " + res ];
2839            }
2840            if (res.data.error) {
2841                if (res.data.error.message) {
2842                    return $scope.processErrorMessages = [ res.error.message ];
2843                } else {
2844                    return $scope.processErrorMessages = [ res.error ];
2845                }
2846            }
2847            if (res.data.errors) {
2848                return $scope.processErrorMessages = _.map(res.data.errors, function(error) {
2849                    return error.message;
2850                });
2851            }
2852            if (_.isArray(res.data)) {
2853                return $scope.processErrorMessages = res.data;
2854            } else {
2855                $scope.processErrorMessages = [ res.data ];
2856            }
2857        }
2858    }
2859});
2860
2861app.directive("postThread", function(FluroContent) {
2862    return {
2863        restrict: "E",
2864        transclude: true,
2865        scope: {
2866            definitionName: "=?type",
2867            host: "=?hostId",
2868            thread: "=?thread"
2869        },
2870        link: function($scope, $element, $attrs, $ctrl, $transclude) {
2871            $transclude($scope, function(clone, $scope) {
2872                $element.replaceWith(clone);
2873            });
2874        },
2875        controller: function($scope, $filter) {
2876            $scope.outer = $scope.$parent;
2877            if (!$scope.thread) {
2878                $scope.thread = [];
2879            }
2880            $scope.$watch("host + definitionName", function() {
2881                var hostID = $scope.host;
2882                var definitionName = $scope.definitionName;
2883                if (!hostID || !definitionName) {
2884                    return;
2885                }
2886                var request = FluroContent.endpoint("post/" + hostID + "/" + definitionName).query().$promise;
2887                function postsLoaded(res) {
2888                    var allPosts = res;
2889                    $scope.thread = _.chain(res).map(function(post) {
2890                        post.thread = _.filter(allPosts, function(sub) {
2891                            return sub.reply == post._id;
2892                        });
2893                        if (!post.reply) {
2894                            return post;
2895                        }
2896                    }
2896).compact().value();
2897                }
2898                function postsError(res) {
2899                    $scope.thread = [];
2900                }
2901                request.then(postsLoaded, postsError);
2902            });
2903        }
2904    };
2905});
2906
2907app.run(function(formlyConfig, $templateCache) {
2908    formlyConfig.setType({
2909        name: "nested",
2910        overwriteOk: true,
2911        templateUrl: "fluro-interaction-form/nested/fluro-nested.html",
2912        controller: "FluroInteractionNestedController"
2913    });
2914});
2915
2916app.controller("FluroInteractionNestedController", function($scope) {
2917    var def = $scope.to.definition;
2918    var minimum = def.minimum;
2919    var maximum = def.maximum;
2920    $scope.$watch("model[options.key]", function(model) {
2921        if (!model) {
2922            resetDefaultValue();
2923        }
2924    });
2925    function resetDefaultValue() {
2926        var defaultValue = angular.copy($scope.to.baseDefaultValue);
2927        if (!$scope.model) {}
2928        $scope.model[$scope.options.key] = defaultValue;
2929    }
2930    $scope.$on("form-reset", resetDefaultValue);
2931    $scope.addAnother = function() {
2932        $scope.model[$scope.options.key].push({});
2933    };
2934    $scope.canRemove = function() {
2935        if (minimum) {
2936            if ($scope.model[$scope.options.key].length > minimum) {
2937                return true;
2938            }
2939        } else {
2940            return true;
2941        }
2942    };
2943    $scope.canAdd = function() {
2944        if (maximum) {
2945            if ($scope.model[$scope.options.key].length < maximum) {
2946                return true;
2947            }
2948        } else {
2949            return true;
2950        }
2951    };
2952    $scope.copyFields = function() {
2953        var copiedFields = angular.copy($scope.options.data.fields);
2954        $scope.options.data.replicatedFields.push(copiedFields);
2955        return copiedFields;
2956    };
2957    $scope.copyDataFields = function() {
2958        var copiedFields = angular.copy($scope.options.data.dataFields);
2959        $scope.options.data.replicatedFields.push(copiedFields);
2960        return copiedFields;
2961    };
2962});
2963
2964app.run(function(formlyConfig, $templateCache) {
2965    formlyConfig.setType({
2966        name: "search-select",
2967        overwriteOk: true,
2968        templateUrl: "fluro-interaction-form/search-select/fluro-search-select.html",
2969        controller: "FluroSearchSelectController",
2970        wrapper: [ "bootstrapLabel", "bootstrapHasError" ]
2971    });
2972});
2973
2974app.controller("FluroSearchSelectController", function($scope, $http, Fluro, $filter, FluroValidate) {
2975    $scope.search = {};
2976    $scope.proposed = {};
2977    var to = $scope.to;
2978    var opts = $scope.options;
2979    $scope.selection = {};
2980    var definition = $scope.to.definition;
2981    if (!definition.params) {
2982        definition.params = {};
2983    }
2984    var restrictType = definition.params.restrictType;
2985    var searchLimit = definition.params.searchLimit;
2986    if (!searchLimit) {
2987        searchLimit = 10;
2988    }
2989    var minimum = definition.minimum;
2990    var maximum = definition.maximum;
2991    if (!minimum) {
2992        minimum = 0;
2993    }
2994    if (!maximum) {
2995        maximim = 0;
2996    }
2997    $scope.multiple = maximum != 1;
2998    if ($scope.multiple) {
2999        if ($scope.model[opts.key] && _.isArray($scope.model[opts.key])) {
3000            $scope.selection.values = angular.copy($scope.model[opts.key]);
3001        }
3002    } else {
3003        if ($scope.model[opts.key]) {
3004            $scope.selection.value = $scope.model[opts.key];
3005        }
3006    }
3007    $scope.canAddMore = function() {
3008        if (!maximum) {
3009            return true;
3010        }
3011        if ($scope.multiple) {
3012            return $scope.selection.values.length < maximum;
3013        } else {
3014            if (!$scope.selection.value) {
3015                return true;
3016            }
3017        }
3018    };
3019    $scope.contains = function(value) {
3020        if ($scope.multiple) {
3021            return _.includes($scope.selection.values, value);
3022        } else {
3023            return $scope.selection.value == value;
3024        }
3025    };
3026    $scope.$watch("model", function(newModelValue, oldModelValue) {
3027        if (newModelValue != oldModelValue) {
3028            var modelValue;
3029            if (_.keys(newModelValue).length) {
3030                modelValue = newModelValue[opts.key];
3031                if ($scope.multiple) {
3032                    if (modelValue && _.isArray(modelValue)) {
3033                        $scope.selection.values = angular.copy(modelValue);
3034                    } else {
3035                        $scope.selection.values = [];
3036                    }
3037                } else {
3038                    $scope.selection.value = angular.copy(modelValue);
3039                }
3040            }
3041        }
3042    }, true);
3043    function setModel() {
3044        if ($scope.multiple) {
3045            $scope.model[opts.key] = angular.copy($scope.selection.values);
3046        } else {
3047            $scope.model[opts.key] = angular.copy($scope.selection.value);
3048        }
3049        if ($scope.fc) {
3050            $scope.fc.$setTouched();
3051        }
3052        checkValidity();
3053    }
3054    if (opts.expressionProperties && opts.expressionProperties["templateOptions.required"]) {
3055        $scope.$watch(function() {
3056            return $scope.to.required;
3057        }, function(newValue) {
3058            checkValidity();
3059        });
3060    }
3061    if ($scope.to.required) {
3062        var unwatchFormControl = $scope.$watch("fc", function(newValue) {
3063            if (!newValue) {
3064                return;
3065            }
3066            checkValidity();
3067            unwatchFormControl();
3068        });
3069    }
3070    function checkValidity() {
3071        var validRequired;
3072        var validInput = FluroValidate.validate($scope.model[$scope.options.key], definition);
3073        if ($scope.multiple) {
3074            if ($scope.to.required) {
3075                validRequired = _.isArray($scope.model[opts.key]) && $scope.model[opts.key].length > 0;
3076            }
3077        } else {
3078            if ($scope.to.required) {
3079                if ($scope.model[opts.key]) {
3080                    validRequired = true;
3081                }
3082            }
3083        }
3084        if ($scope.fc) {
3085            $scope.fc.$setValidity("required", validRequired);
3086            $scope.fc.$setValidity("validInput", validInput);
3087        }
3088    }
3089    $scope.select = function(value) {
3090        if ($scope.multiple) {
3091            if (!$scope.canAddMore()) {
3092                return;
3093            }
3094            $scope.selection.values.push(value);
3095        } else {
3096            $scope.selection.value = value;
3097        }
3098        $scope.proposed = {};
3099        setModel();
3100    };
3101    $scope.retrieveReferenceOptions = function(val) {
3102        var searchUrl = Fluro.apiURL + "/content";
3103        if (restrictType) {
3104            searchUrl += "/" + restrictType;
3105        }
3106        searchUrl += "/search";
3107        return $http.get(searchUrl + "/" + val, {
3108            ignoreLoadingBar: true,
3109            params: {
3110                limit: searchLimit
3111            }
3112        }).then(function(response) {
3113            var results = response.data;
3114            return _.reduce(results, function(filtered, item) {
3115                var exists = _.some($scope.selection.values, {
3116                    _id: item._id
3117                });
3118                if (!exists) {
3119                    filtered.push(item);
3120                }
3121                return filtered;
3122            }, []);
3123        });
3124    };
3125    $scope.getValueLabel = function(value) {
3126        if (definition.options && definition.options.length) {
3127            var match = _.find(definition.options, {
3128                value: value
3129            });
3130            if (match && match.name) {
3131                return match.name;
3132            }
3133        }
3134        return value;
3135    };
3136    $scope.retrieveValueOptions = function(val) {
3137        if (definition.options && definition.options.length) {
3138            var options = _.reduce(definition.options, function(results, item) {
3139                var exists;
3140                if ($scope.multiple) {
3141                    exists = _.includes($scope.selection.values, item.value);
3142                } else {
3143                    exists = $scope.selection.value == item.value;
3144                }
3145                if (!exists) {
3146                    results.push({
3147                        name: item.name,
3148                        value: item.value
3149                    });
3150                }
3151                return results;
3152            }, []);
3153            return $filter("filter")(options, val);
3154        } else if (definition.allowedValues && definition.allowedValues.length) {
3155            var options = _.reduce(definition.allowedValues, function(results, allowedValue) {
3156                var exists;
3157                if ($scope.multiple) {
3158                    exists = _.includes($scope.selection.values, allowedValue);
3159                } else {
3160                    exists = $scope.selection.value == allowedValue;
3161                }
3162                if (!exists) {
3163                    results.push({
3164                        name: allowedValue,
3165                        value: allowedValue
3166                    });
3167                }
3168                return results;
3169            }, []);
3170            return $filter("filter")(options, val);
3171        }
3172    };
3173    $scope.deselect = function(value) {
3174        if ($scope.multiple) {
3175            _.pull($scope.selection.values, value);
3176        } else {
3177            delete $scope.selection.value;
3178        }
3179        setModel();
3180    };
3181    $scope.toggle = function(reference) {
3182        if ($scope.contains(reference)) {
3183            $scope.deselect(reference);
3184        } else {
3185            $scope.select(reference);
3186        }
3187    };
3188});
3189
3190app.run(function(formlyConfig, $templateCache) {
3191    formlyConfig.setType({
3192        name: "value",
3193        overwriteOk: true,
3194        templateUrl: "fluro-interaction-form/value/value.html",
3195        wrapper: [ "bootstrapHasError" ]
3196    });
3197});
3198
3199app.service("FluroMenuService", function($rootScope) {
3200    var controller = {};
3201    controller.get = function(string, id) {
3202        var menu;
3203        var result;
3204        if ($rootScope.site) {
3205            menu = _.find($rootScope.site.menus, {
3206                key: string
3207            });
3208        }
3209        if (menu && id) {
3210            result = _.find(menu.items, {
3211                id: id
3212            });
3213        } else {
3214            result = menu;
3215        }
3216        return result;
3217    };
3218    return controller;
3219});
3220
3221app.directive("scrollActive", function($compile, $timeout, $window, FluroScrollService) {
3222    return {
3223        restrict: "A",
3224        link: function($scope, $element, $attrs) {
3225            var onActive;
3226            var onBefore;
3227            var onAfter;
3228            var onAnchor;
3229            var currentContext = "";
3230            var anchored;
3231            if ($attrs.onActive) {
3232                onActive = function() {
3233                    $scope.$eval($attrs.onActive);
3234                };
3235            }
3236            if ($attrs.onAnchor) {
3237                onAnchor = function() {
3238                    $scope.$eval($attrs.onAnchor);
3239                };
3240            }
3241            if ($attrs.onAfter) {
3242                onAfter = function() {
3243                    $scope.$eval($attrs.onAfter);
3244                };
3245            }
3246            if ($attrs.onBefore) {
3247                onBefore = function() {
3248                    $scope.$eval($attrs.onBefore);
3249                };
3250            }
3251            var parent = $element.closest("[scroll-active-parent]");
3252            var body = angular.element("body");
3253            if (parent.length) {
3254                parent.bind("scroll", updateParentScroll);
3255                $timeout(updateParentScroll, 10);
3256            } else {
3257                $scope.$watch(function() {
3258                    return FluroScrollService.getScroll();
3259                }, updateFromMainScroll);
3260                $timeout(updateFromMainScroll, 10);
3261            }
3262            function setScrollContext(context) {
3263                if (currentContext != context) {
3264                    currentContext = context;
3265                    $timeout(function() {
3266                        switch (context) {
3267                          case "active":
3268                            $element.removeClass("scroll-after");
3269                            $element.removeClass("scroll-before");
3270                            $element.addClass("scroll-active");
3271                            $scope.scrollActive = true;
3272                            $scope.scrollBefore = false;
3273                            $scope.scrollAfter = false;
3274                            if (onActive) {
3275                                onActive();
3276                            }
3277                            break;
3278
3279                          case "before":
3280                            $element.removeClass("scroll-after");
3281                            $element.addClass("scroll-before");
3282                            $element.removeClass("scroll-active");
3283                            $scope.scrollActive = false;
3284                            $scope.scrollBefore = true;
3285                            $scope.scrollAfter = false;
3286                            if (onBefore) {
3287                                onBefore();
3288                            }
3289                            break;
3290
3291                          case "after":
3292                            $element.addClass("scroll-after");
3293                            $element.removeClass("scroll-before");
3294                            $element.removeClass("scroll-active");
3295                            $scope.scrollActive = false;
3296                            $scope.scrollBefore = false;
3297                            $scope.scrollAfter = true;
3298                            if (onAfter) {
3299                                onAfter();
3300                            }
3301                            break;
3302                        }
3303                    });
3304                }
3305            }
3306            function updateParentScroll() {
3307                var scrollValue = parent.scrollTop();
3308                var viewportHeight = parent.height();
3309                var contentHeight = parent.get(0).scrollHeight;
3310                var viewportHalf = viewportHeight / 2;
3311                var maxScroll = contentHeight - viewportHeight;
3312                var startView = 0;
3313                var endView = startView + viewportHeight;
3314                var halfView = endView - viewportHeight / 2;
3315                var elementHeight = $element.outerHeight();
3316                var elementStart = $element.position().top;
3317                var elementEnd = elementStart + elementHeight;
3318                var elementHalf = elementStart + elementHeight / 4;
3319                if (onAnchor) {
3320                    var start = parseInt(startView);
3321                    var rangeStart = parseInt(elementStart);
3322                    var rangeEnd = parseInt(elementHalf);
3323                    if (start >= rangeStart && start < rangeEnd) {
3324                        if (!anchored) {
3325                            anchored = true;
3326                            if (anchored) {
3327                                onAnchor();
3328                            }
3329                        }
3330                    } else {
3331                        anchored = false;
3332                    }
3333                }
3334                var entirelyViewable = elementStart >= startView && elementEnd <= endView;
3335                if (entirelyViewable) {
3336                    return setScrollContext("active");
3337                }
3338                if (halfView >= elementEnd) {
3339                    return setScrollContext("after");
3340                }
3341                if (halfView >= elementStart) {
3342                    return setScrollContext("active");
3343                }
3344                if (startView >= maxScroll - 200) {
3345                    return setScrollContext("active");
3346                }
3347                return setScrollContext("before");
3348            }
3349            function updateFromMainScroll(scrollValue) {
3350                var windowHeight = $window.innerHeight;
3351                var documentHeight = body.height();
3352                var windowHalf = windowHeight / 2;
3353                var maxScroll = documentHeight - windowHeight;
3354                var startView = scrollValue;
3355                if (!startView) {
3356                    startView = 0;
3357                }
3358                var endView = startView + windowHeight;
3359                var halfView = endView - windowHeight / 2;
3360                var elementHeight = $element.outerHeight();
3361                var elementStart = $element.offset().top;
3362                var elementEnd = elementStart + elementHeight;
3363                var elementHalf = elementStart + elementHeight / 4;
3364                if (onAnchor) {
3365                    var start = parseInt(startView);
3366                    var rangeStart = parseInt(elementStart);
3367                    var rangeEnd = parseInt(elementHalf);
3368                    if (start >= rangeStart && start < rangeEnd) {
3369                        if (!anchored) {
3370                            anchored = true;
3371                            if (anchored) {
3372                                onAnchor();
3373                            }
3374                        }
3375                    } else {
3376                        anchored = false;
3377                    }
3378                }
3379                var entirelyViewable = elementStart >= startView && elementEnd <= endView;
3380                if (entirelyViewable) {
3381                    return setScrollContext("active");
3382                }
3383                if (halfView >= elementEnd) {
3384                    return setScrollContext("after");
3385                }
3386                if (halfView >= elementStart) {
3387                    return setScrollContext("active");
3388                }
3389                if (startView >= maxScroll - 200) {
3390                    return setScrollContext("active");
3391                }
3392                return setScrollContext("before");
3393            }
3394        }
3395    };
3396});
3397
3398app.service("FluroScrollService", function($window, $location, $timeout) {
3399    var controller = {};
3400    controller.cache = {};
3401    controller.direction = "down";
3402    var _value = 0;
3403    var body = angular.element("html,body");
3404    controller.setAnchor = function(id) {
3405        $location.hash("jump-to-" + id);
3406    };
3407    controller.getAnchor = function() {
3408        var hash = $location.hash();
3409        if (_.startsWith(hash, "jump-to-")) {
3410            return hash.substring(8);
3411        } else {
3412            return hash;
3413        }
3414    };
3415    function updateScroll() {
3416        var v = this.pageYOffset;
3417        if (_value != this.pageYOffset) {
3418            if (v < _value) {
3419                controller.direction = "up";
3420            } else {
3421                controller.direction = "down";
3422            }
3423            $timeout(function() {
3424                _value = this.pageYOffset;
3425            });
3426        }
3427    }
3428    controller.scrollToID = controller.scrollToId = function(id, speed, selector, offset) {
3429        if (speed != 0 && !speed) {
3430            speed = "fast";
3431        }
3432        var $target = angular.element("#" + id);
3433        if ($target && $target.offset && $target.offset()) {
3434            if (!selector) {
3435                selector = "body,html";
3436            }
3437            var pos = $target.offset().top;
3438            if (offset) {
3439                pos += Number(offset);
3440            }
3441            angular.element(selector).animate({
3442                scrollTop: pos
3443            }, speed);
3444        }
3445    };
3446    controller.scrollToPosition = controller.scrollTo = function(pos, speed, selector, offset) {
3447        if (speed != 0 && !speed) {
3448            speed = "fast";
3449        }
3450        if (!selector) {
3451            selector = "body,html";
3452        }
3453        if (offset) {
3454            pos += Number(offset);
3455        }
3456        angular.element(selector).animate({
3457            scrollTop: pos
3458        }, speed);
3459    };
3460    controller.get = controller.getScroll = function() {
3461        return _value;
3462    };
3463    controller.getMax = function(selector) {
3464        if (!selector) {
3465            selector = "body,html";
3466        }
3467        var bodyHeight = angular.element(selector).height();
3468        var windowHeight = $window.innerHeight;
3469        return bodyHeight - windowHeight;
3470    };
3471    controller.getHalfPoint = function() {
3472        return $window.innerHeight / 2;
3473    };
3474    controller.getWindowHeight = function() {
3475        return $window.innerHeight;
3476    };
3477    angular.element($window).bind("scroll", updateScroll);
3478    updateScroll();
3479    return controller;
3480});
3481
3482angular.module("fluro").service("FluroSEO", function($state, $rootScope, $location, Asset) {
3483    var controller = {};
3484    controller.headTitle = function() {
3485        var siteTitle = controller.siteTitle();
3486        var pageTitle = controller.pageTitle();
3487        if (pageTitle && pageTitle.length) {
3488            return pageTitle + " | " + siteTitle;
3489        }
3490        return siteTitle;
3491    };
3492    controller.siteTitle = function() {
3493        var site = $rootScope.site;
3494        var _siteTitle = site.title;
3495        if (site.seo) {
3496            if (site.seo.title && site.seo.title.length) {
3497                _siteTitle = site.seo.title;
3498            }
3499        }
3500        return _siteTitle;
3501    };
3502    controller.getImage = function() {
3503        var site = $rootScope.site;
3504        var page = $rootScope.currentRoute;
3505        var slug = $rootScope.slug;
3506        var imageId;
3507        if (site.seo) {
3508            if (site.seo.image) {
3509                imageId = site.seo.image;
3510            }
3511        }
3512        if (page && page.seo) {
3513            if (page.seo.image) {
3514                imageId = page.seo.image;
3515            }
3516            if (slug) {
3517                if (page.seo.slugImage && page.seo.slugImage.length) {
3518                    var key = page.seo.slugImage;
3519                    var imageObject = _.get(slug, key);
3520                    if (_.isArray(imageObject)) {
3521                        imageObject = imageObject[0];
3522                    }
3523                    if (imageObject) {
3524                        if (imageObject._id) {
3525                            imageId = imageObject._id;
3526                        } else {
3527                            imageId = imageObject;
3528                        }
3529                    }
3530                }
3531            }
3532        }
3533        if (imageId) {
3534            var imageURL = Asset.imageUrl(imageId, 940, 492, {
3535                extension: "jpg"
3536            });
3537            return imageURL;
3538        }
3539    };
3540    controller.getUrl = function() {
3541        return $location.$$absUrl;
3542    };
3543    controller.pageTitle = function() {
3544        var pageTitle;
3545        var page = $rootScope.currentRoute;
3546        var slug = $rootScope.slug;
3547        if (page) {
3548            pageTitle = page.title;
3549            if (page.seo) {
3550                if (page.seo.title && page.seo.title.length) {
3551                    pageTitle = page.seo.title;
3552                }
3553                if (slug) {
3554                    if (page.seo.slugTitle && page.seo.slugTitle.length) {
3555                        var key = page.seo.slugTitle;
3556                        pageTitle = _.get(slug, key);
3557                    }
3558                }
3559            }
3560        }
3561        return pageTitle;
3562    };
3563    controller.metaDescription = function() {
3564        var site = $rootScope.site;
3565        var page = $rootScope.currentRoute;
3566        var slug = $rootScope.slug;
3567        var description = "";
3568        if (site.seo) {
3569            if (site.seo.description && site.seo.description.length) {
3570                description = site.seo.description;
3571            }
3572        }
3573        if (page && page.seo) {
3574            if (page.seo.description && page.seo.description.length) {
3575                description = page.seo.description;
3576            }
3577            if (slug) {
3578                if (page.seo.slugDescription && page.seo.slugDescription.length) {
3579                    var key = page.seo.slugDescription;
3580                    description = _.get(slug, key);
3581                }
3582            }
3583        }
3584        return description;
3585    };
3586    return controller;
3587});
3588
3589app.service("FluroWindowService", function($window, $timeout) {
3590    var controller = {};
3591    controller.breakpoint = "xs";
3592    function updateSize() {
3593        controller.width = $window.innerWidth;
3594        controller.height = $window.innerHeight;
3595        if (controller.width < 768) {
3596            return controller.breakpoint = "xs";
3597        }
3598        if (controller.width < 992) {
3599            return controller.breakpoint = "sm";
3600        }
3601        if (controller.width < 1200) {
3602            return controller.breakpoint = "md";
3603        }
3604        return controller.breakpoint = "lg";
3605    }
3606    angular.element($window).bind("resize", updateSize);
3607    updateSize();
3608    return controller;
3609});
3610
3611app.directive("compileHtml", function($compile) {
3612    return {
3613        restrict: "A",
3614        link: function(scope, element, attrs) {
3615            scope.$watch(function() {
3616                return scope.$eval(attrs.compileHtml);
3617            }, function(value) {
3618                element.html(value);
3619                $compile(element.contents())(scope);
3620            });
3621        }
3622    };
3623});
3624
3625app.filter("age", function() {
3626    return function(date) {
3627        return Number(moment().diff(date, "years"));
3628    };
3629});
3630
3631app.filter("kebabcase", function() {
3632    return function(text) {
3633        return _.kebabCase(text);
3634    };
3635});
3636
3637app.filter("startFrom", function() {
3638    return function(input, start) {
3639        if (input) {
3640            start = +start;
3641            return input.slice(start);
3642        }
3643        return [];
3644    };
3645});
3646
3647app.filter("timeago", function() {
3648    return function(date) {
3649        return moment(date).fromNow();
3650    };
3651});
3652
3653app.directive("route", function($compile) {
3654    return {
3655        restrict: "E",
3656        replace: true,
3657        scope: {
3658            route: "=ngModel",
3659            slug: "=ngSlug",
3660            _injectedItems: "=ngItems",
3661            _injectedResults: "=ngResults",
3662            _injectedCollections: "=ngCollections",
3663            _injectedPageData: "=ngPage"
3664        },
3665        template: "<div></div>",
3666        controller: function($scope, $rootScope, $stateParams, $controller) {
3667            this.items = $scope._injectedItems;
3668            this.results = $scope._injectedResults;
3669            this.collections = $scope._injectedCollections;
3670            this.slug = $scope.slug || $rootScope.slug;
3671            this.route = $scope.route;
3672            if (!$scope._injectedPageData) {
3673                return;
3674            }
3675            var routeModel = $scope.route;
3676            var populatedItems = $scope._injectedItems;
3677            var extraPageInjectedData = $scope._injectedPageData;
3678            if (routeModel.params) {
3679                $scope.params = routeModel.params;
3680            } else {
3681                $scope.params = {};
3682            }
3683            $scope.queryResults = [];
3684            $scope.results = _.reduce(routeModel.queries, function(result, queryId) {
3685                var array = extraPageInjectedData.results[queryId];
3686                $scope.queryResults.push(array);
3687                if (array && array.length) {
3688                    _.each(array, function(item) {
3689                        result.push(item);
3690                    });
3691                }
3692                return result;
3693            }, []);
3694            if (routeModel.content) {
3695                $scope.items = _.chain(routeModel.content).map(function(id) {
3696                    var populated = _.find(populatedItems, {
3697                        _id: id
3698                    });
3699                    if (!populated) {}
3700                    return populated;
3701                }).compact().value();
3702            } else {
3703                $scope.items = [];
3704            }
3705            if (routeModel.keys) {
3706                $scope.content = _.reduce(routeModel.keys, function(result, keyGroup) {
3707                    result[keyGroup.key] = _.chain(keyGroup.content).map(function(nid) {
3708                        return _.find(populatedItems, {
3709                            _id: nid
3710                        });
3711                    }).compact().value();
3712                    return result;
3713                }, {});
3714            } else {
3715                $scope.content = {};
3716            }
3717            if (routeModel.collections) {
3718                $scope.collections = _.map(routeModel.collections, function(collectionId) {
3719                    return _.find(extraPageInjectedData.collections, {
3720                        _id: collectionId
3721                    });
3722                });
3723                $scope.collectionResults = _.chain(extraPageInjectedData.collections).map(function(collection) {
3724                    return collection.items;
3725                }).flatten().uniq().value();
3726            } else {
3727                $scope.collections = [];
3728                $scope.collectionResults = [];
3729            }
3730            this.page = $scope;
3731            if (routeModel.controllerName && routeModel.controllerName.length) {
3732                $controller(routeModel.controllerName, {
3733                    $scope: $scope
3734                });
3735            }
3736        },
3737        link: function($scope, $element, $attr) {
3738            var routeModel = $scope.route;
3739            var template = '<route-section ng-model="section" ng-repeat="section in route.sections"></route-section>';
3740            if (routeModel) {
3741                if (routeModel.html && routeModel.html.length) {
3742                    template = routeModel.html;
3743                }
3744            }
3745            var compiled = $compile(template)($scope);
3746            $element.append(compiled);
3747        }
3748    };
3749});
3750
3751app.directive("routeSection", function($compile, $templateCache, $rootScope, $timeout, FluroContentRetrieval) {
3752    return {
3753        require: "^route",
3754        restrict: "E",
3755        replace: true,
3756        scope: {
3757            model: "=ngModel"
3758        },
3759        template: "<section></section>",
3760        link: function($scope, $element, $attr, $routeController) {
3761            var populatedItems = $routeController.items;
3762            $scope.route = $routeController.route;
3763            $scope.page = $routeController.page;
3764            if ($routeController.slug) {
3765                $scope.slug = $routeController.slug;
3766            }
3767            if ($routeController.results) {
3768                $scope.queryResults = [];
3769                $scope.results = _.reduce($scope.model.queries, function(result, queryId) {
3770                    var array = $routeController.results[queryId];
3771                    $scope.queryResults.push(array);
3772                    if (array && array.length) {
3773                        _.each(array, function(item) {
3774                            result.push(item);
3775                        });
3776                    }
3777                    return result;
3778                }, []);
3779            }
3780            $scope.items = _.chain($scope.model.content).map(function(id) {
3781                var populated = _.find(populatedItems, {
3782                    _id: id
3783                });
3784                if (!populated) {}
3785                return populated;
3786            }
3786).compact().value();
3787            $scope.content = _.reduce($scope.model.keys, function(result, keyGroup) {
3788                result[keyGroup.key] = _.chain(keyGroup.content).map(function(nid) {
3789                    return _.find(populatedItems, {
3790                        _id: nid
3791                    });
3792                }).compact().value();
3793                return result;
3794            }, {});
3795            if ($routeController.collections) {
3796                $scope.collections = _.map($scope.model.collections, function(collectionId) {
3797                    return _.find($routeController.collections, {
3798                        _id: collectionId
3799                    });
3800                });
3801                $scope.collectionResults = _.chain($scope.collections).compact().map(function(collection) {
3802                    return collection.items;
3803                }).flatten().uniq().value();
3804            }
3805            if ($scope.model) {
3806                $scope.params = $scope.model.params;
3807                $scope.model.temp = {};
3808                var html;
3809                if ($scope.model.template && $scope.model.template.length) {
3810                    html = $templateCache.get($scope.model.template);
3811                } else {
3812                    html = $scope.model.html;
3813                }
3814                if (html && html.length) {
3815                    $element.append(html);
3816                    $compile($element.contents())($scope);
3817                }
3818            }
3819        }
3820    };
3821});
3822
3823app.directive("site", function($compile, FluroContentRetrieval) {
3824    return {
3825        restrict: "E",
3826        replace: true,
3827        template: '<div class="site"/>',
3828        link: function($scope, $element) {
3829            var outerSections = [];
3830            var site = $scope.site;
3831            var siteTemplate = "";
3832            if (site.outerHTML && site.outerHTML.length) {
3833                siteTemplate = site.outerHTML;
3834                siteTemplate = siteTemplate.replace(/<fluro-header\/>/g, '<route class="site-header" ng-if="!$root.currentRoute.disableHeader && outerContentReady()" ng-model="site.header" ng-items="items" ng-results="results" ng-slug="slug" ng-collections="collections"/>');
3835                siteTemplate = siteTemplate.replace(/<fluro-content\/>/g, '<div class="site-page-container"><div ui-view="$site"/></div>');
3836                siteTemplate = siteTemplate.replace(/<fluro-footer\/>/g, '<route class="site-footer" ng-if="!$root.currentRoute.disableFooter && outerContentReady()" ng-model="site.footer" ng-items="items" ng-results="results" ng-slug="slug" ng-collections="collections"/>');
3837            } else {
3838                siteTemplate = '<route class="site-header" ng-if="!$root.currentRoute.disableHeader && outerContentReady()" ng-model="site.header" ng-items="items" ng-results="results" ng-slug="slug" ng-collections="collections"/>';
3839                siteTemplate += '<div class="site-page-container"><div ui-view="$site"/></div>';
3840                siteTemplate += '<route class="site-footer" ng-if="!$root.currentRoute.disableFooter && outerContentReady()" ng-model="site.footer" ng-items="items" ng-results="results" ng-slug="slug" ng-collections="collections"/>';
3841            }
3842            var compiled = $compile(siteTemplate)($scope);
3843            $element.append(compiled);
3844            if (site.header && site.header.sections && site.header.sections.length) {
3845                outerSections = outerSections.concat(site.header.sections);
3846            }
3847            if (site.footer && site.footer.sections && site.footer.sections.length) {
3848                outerSections = outerSections.concat(site.footer.sections);
3849            }
3850            var contentIds = [];
3851            var collectionIds = [];
3852            var queryIds = [];
3853            var keyIds = [];
3854            _.each(outerSections, function(section) {
3855                contentIds = contentIds.concat(section.content);
3856                queryIds = queryIds.concat(section.queries);
3857                collectionIds = collectionIds.concat(section.collections);
3858                var newKeyIds = _.map(section.keys, function(keyGroup) {
3859                    return keyGroup.content;
3860                });
3861                keyIds = keyIds.concat(newKeyIds);
3862            });
3863            contentIds = _.chain(contentIds).flatten().compact().value(
3863);
3864            queryIds = _.chain(queryIds).flatten().compact().value();
3865            collectionIds = _.chain(collectionIds).flatten().compact().value();
3866            keyIds = _.chain(keyIds).flatten().compact().value();
3867            if (keyIds.length) {
3868                contentIds = contentIds.concat(keyIds);
3869            }
3870            if (contentIds.length) {
3871                FluroContentRetrieval.get(contentIds, useSiteCache, {
3872                    searchInheritable: true
3873                }).then(function(items) {
3874                    $scope.items = items;
3875                    $scope.contentIsReady = true;
3876                });
3877            } else {
3878                $scope.contentIsReady = true;
3879            }
3880            if (queryIds.length) {
3881                var queryVariables;
3882                FluroContentRetrieval.queryMultiple(queryIds, useSiteCache, queryVariables).then(function(results) {
3883                    $scope.results = results;
3884                    $scope.resultsAreReady = true;
3885                });
3886            } else {
3887                $scope.resultsAreReady = true;
3888            }
3889            if (collectionIds.length) {
3890                FluroContentRetrieval.getMultipleCollections(collectionIds, useSiteCache).then(function(res) {
3891                    $scope.collections = res;
3892                    $scope.collectionResults = _.chain($scope.collections).map(function(collection) {
3893                        return collection.items;
3894                    }).flatten().uniq().value();
3895                    $scope.collectionsReady = true;
3896                });
3897            } else {
3898                $scope.collectionsReady = true;
3899            }
3900            $scope.outerContentReady = function() {
3901                return $scope.collectionsReady && $scope.resultsAreReady && $scope.contentIsReady;
3902            };
3903        }
3904    };
3905});
3906
3907app.directive("compileTemplate", function($compile, $rootScope, $templateCache) {
3908    return {
3909        restrict: "A",
3910        link: function($scope, $element, $attr) {
3911            $attr.$observe("compileTemplate", function(templateName) {
3912                $element.empty();
3913                var html = $templateCache.get(templateName);
3914                if (html && html.length) {
3915                    var template = $compile(html)($scope);
3916                    $element.append(template);
3917                }
3918            });
3919        }
3920    };
3921});
3922
3923angular.module("fluro").run([ "$templateCache", function($templateCache) {
3924    "use strict";
3925    $templateCache.put("application/application.html", "<div class=application><notifications></notifications><div ui-view></div></div>");
3926    $templateCache.put("fluro-dob-select/fluro-dob-select.html", "<div><div class=form-group ng-hide=hideAge><label>Age</label><input type=number select-on-focus ng-model-options=\"{debounce: { 'default': 400, 'blur': 0 } }\" class=form-control pattern=[0-9]* placeholder=Age ng-model=age inputmode=numeric></div><div class=form-group ng-hide=hideDates><label>Date of Birth {{$parent.to.required ? '*' : ''}}</label><div class=row><div class=col-xs-3><select name=dateFields.day data-ng-model=dateFields.day placeholder=Day class=form-control ng-options=\"day for day in days\" ng-change=checkDate() ng-disabled=disableFields></select></div><div class=col-xs-5><select name=dateFields.month data-ng-model=dateFields.month placeholder=Month class=form-control ng-options=\"month.value as month.name for month in months\" value={{dateField.month}} ng-change=checkDate() ng-disabled=disableFields></select></div><div class=col-xs-4><select ng-if=!yearText name=dateFields.year data-ng-model=dateFields.year placeholder=Year class=form-control ng-options=\"year for year in years\" ng-change=checkDate() ng-disabled=disableFields></select><input ng-if=yearText name=dateFields.year data-ng-model=dateFields.year placeholder=Year class=form-control ng-disabled=disableFields></div></div></div></div>");
3927    $templateCache.put("fluro-interaction-form/button-select/fluro-button-select.html", '<div id={{options.id}} class="button-select {{to.definition.directive}}-buttons" ng-model=model[options.key]><a ng-repeat="(key, option) in to.options" ng-class={active:contains(option.value)} class="btn btn-default" id="{{id + \'_\'+ $index}}" ng-click=toggle(option.value)><span>{{option.name}}</span><i class="fa fa-check"></i></a></div>');
3928    $templateCache.put("fluro-interaction-form/custom.html", "<div ng-model=model[options.key] compile-html=to.definition.template></div>");
3929    $templateCache.put("fluro-interaction-form/date-select/date-selector.html", '<div class=row><div class=col-xs-3><select name=dateFields.day data-ng-model=dateFields.day placeholder=Day class=form-control ng-options="day for day in days" ng-change=checkDate()><option value="" disabled selected>Day</option></select></div><div class=col-xs-5><select name=dateFields.month data-ng-model=dateFields.month placeholder=Month class=form-control ng-options="month.value as month.name for month in months" value={{dateField.month}} ng-change=checkDate()><option value="" disabled selected>Month</option></select></div><div class=col-xs-4><input type=number name=dateFields.year min=0 max=9999 data-ng-model=dateFields.year placeholder=YYYY class=form-control></div></div>');
3930    $templateCache.put("fluro-interaction-form/date-select/fluro-date-select.html", "<div><div class=visible-xs-block><input type=date class=form-control ng-model=model[options.key] ng-required=to.required></div><div class=hidden-xs><fluro-date-select ng-model=model[options.key]></fluro-date-select></div></div>");
3931    $templateCache.put("fluro-interaction-form/dob-select/fluro-dob-select.html", "<div class=fluro-interaction-dob-select><dob-select ng-model=model[options.key] hide-age=to.params.hideAge hide-
3931dates=to.params.hideDates></dob-select></div>");
3932    $templateCache.put("fluro-interaction-form/embedded/fluro-embedded.html", '<div class=fluro-embedded-form><div class=form-multi-group ng-if="to.definition.maximum != 1"><div class="panel panel-default" ng-init="fields = copyFields(); dataFields = copyDataFields(); " ng-repeat="entry in model[options.key] track by $index"><div class="panel-heading clearfix"><a ng-if=canRemove() class="btn btn-danger btn-sm pull-right" ng-click="model[options.key].splice($index, 1)"><span>Remove {{to.label}}</span><i class="fa fa-times"></i></a><h5>{{to.label}} {{$index + 1}}</h5></div><div class=panel-body><formly-form model=entry fields=fields></formly-form><formly-form model=entry.data fields=dataFields></formly-form></div></div><a class="btn btn-primary btn-sm" ng-if=canAdd() ng-click=addAnother()><span>Add <span ng-if=model[options.key].length>another</span> {{to.label}}</span><i class="fa fa-plus"></i></a></div><div ng-if="to.definition.maximum == 1 && options.key"><formly-form model=model[options.key] fields=options.data.fields></formly-form><formly-form model=model[options.key].data fields=options.data.dataFields></formly-form></div></div>');
3933    $templateCache.put("fluro-interaction-form/field-errors.html", '<div class=field-errors ng-if="fc.$touched && fc.$invalid"><div ng-show=fc.$error.required class="alert alert-danger" role=alert><span class="fa fa-exclamation" aria-hidden=true></span> <span class=sr-only>Error:</span> {{to.label}} is required.</div><div ng-show=fc.$error.validInput class="alert alert-danger" role=alert><span class="fa fa-exclamation" aria-hidden=true></span> <span class=sr-only>Error:</span> <span ng-if=!to.errorMessage.length>\'{{fc.$viewValue}}\' is not a valid value</span> <span ng-if=to.errorMessage.length>{{to.errorMessage}}</span></div><div ng-show=fc.$error.email class="alert alert-danger" role=alert><span class="fa fa-exclamation" aria-hidden=true></span> <span class=sr-only>Error:</span> <span>\'{{fc.$viewValue}}\' is not a valid email address</span></div></div>');
3934    $templateCache.put("fluro-interaction-form/fluro-interaction-input.html", '<div class="fluro-input form-group" scroll-active ng-class="{\'fluro-valid\':isValid(), \'fluro-dirty\':isDirty, \'fluro-invalid\':!isValid()}"><label><i class="fa fa-check" ng-if=isValid()></i><i class="fa fa-exclamation" ng-if=!isValid()></i><span>{{field.title}}</span></label><div class="error-message help-block"><span ng-if=field.errorMessage>{{field.errorMessage}}</span> <span ng-if=!field.errorMessage>Please provide valid input for this field</span></div><span class=help-block ng-if="field.description && field.type != \'boolean\'">{{field.description}}</span><div class=fluro-input-wrapper></div></div>');
3935    $templateCache.put("fluro-interaction-form/fluro-terms.html", '<div class=terms-checkbox><div class=checkbox><label><input type=checkbox ng-model="model[options.key]"> {{to.definition.params.storeData}}</label></div></div>');
3936    $templateCache.put("fluro-interaction-form/fluro-web-form.html", '<div class=fluro-interaction-form><div ng-if=!correctPermissions class=form-permission-warning><div class="alert alert-warning small"><i class="fa fa-warning"></i> <span>You do not have permission to post {{model.plural}}</span></div></div><div ng-if=promisesResolved><div ng-if=debugMode><div class="btn-group btn-group-justified"><a ng-click="vm.state = \'ready\'" class="btn btn-default">State to ready</a> <a ng-click="vm.state = \'complete\'" class="btn btn-default">State to complete</a> <a ng-click=reset() class="btn btn-default">Reset</a></div><hr></div><div ng-show="vm.state != \'complete\'"><form novalidate ng-submit=vm.onSubmit()><formly-form model=vm.model fields=vm.modelFields form=vm.modelForm options=vm.options><div ng-if=model.data.recaptcha><div recaptcha-render></div></div><div class="form-error-summary form-client-error alert alert-warning" ng-if="vm.modelForm.$invalid && !vm.modelForm.$pristine"><div class=form-error-summary-item ng-repeat="field in errorList" ng-if=field.formControl.$invalid><i class="fa fa-exclamation"></i> <span ng-if=field.templateOptions.definition.errorMessage.length>{{field.templateOptions.definition.errorMessage}}</span> <span ng-if=!field.templateOptions.definition.errorMessage.length>{{field.templateOptions.label}}
3936 has not been provided.</span></div></div><div ng-switch=vm.state><div ng-switch-when=sending><a class="btn btn-primary" ng-disabled=true><span>Processing</span> <i class="fa fa-spinner fa-spin"></i></a></div><div ng-switch-when=error><div class="form-error-summary form-server-error alert alert-danger" ng-if=processErrorMessages.length><div class=form-error-summary-item ng-repeat="error in processErrorMessages track by $index"><i class="fa fa-exclamation"></i> <span>Error processing your submission: {{error}}</span></div></div><button type=submit class="btn btn-primary" ng-disabled=!readyToSubmit><span>Try Again</span> <i class="fa fa-angle-right"></i></button></div><div ng-switch-default><button type=submit class="btn btn-primary" ng-disabled=!readyToSubmit><span>{{submitLabel}}</span> <i class="fa fa-angle-right"></i></button></div></div></formly-form></form></div><div ng-show="vm.state == \'complete\'"><div compile-html=transcludedContent></div></div></div></div>');
3937    $templateCache.put("fluro-interaction-form/nested/fluro-nested.html", '<div><div class=form-multi-group ng-if="to.definition.maximum != 1"><div class="panel panel-default" ng-init="fields = copyFields()" ng-repeat="entry in model[options.key] track by $index"><div class="panel-heading clearfix"><a ng-if=canRemove() class="btn btn-danger btn-sm pull-right" ng-click="model[options.key].splice($index, 1)"><span>Remove {{to.label}}</span><i class="fa fa-times"></i></a><h5>{{to.label}} {{$index + 1}}</h5></div><div class=panel-body><formly-form model=entry fields=fields></formly-form></div></div><a class="btn btn-primary btn-sm" ng-if=canAdd() ng-click=addAnother()><span>Add <span ng-if=model[options.key].length>another</span> {{to.label}}</span><i class="fa fa-plus"></i></a></div><div ng-if="to.definition.maximum == 1 && options.key"><formly-form model=model[options.key] fields=options.data.fields></formly-form></div></div>');
3938    $templateCache.put("fluro-interaction-form/order-select/fluro-order-select.html", '<div id={{options.id}} class=fluro-order-select><div ng-if=selection.values.length><p class=help-block>Drag to reorder your choices</p></div><div class=list-group as-sortable=dragControlListeners formly-skip-ng-model-attrs-manipulator ng-model=selection.values><div class="list-group-item clearfix" as-sortable-item ng-repeat="item in selection.values"><div class=pull-left as-sortable-item-handle><i class="fa fa-arrows order-select-handle"></i> <span class="order-number text-muted">{{$index+1}}</span> <span>{{item}}</span></div><div class="pull-right order-select-remove" ng-click=deselect(item)><i class="fa fa-times"></i></div></div></div><div ng-if=canAddMore()><p class=help-block>Choose by selecting options below</p><select class=form-control ng-model=selectBox.item ng-change=selectUpdate()><option ng-repeat="(key, option) in to.options | orderBy:\'value\'" ng-if=!contains(option.value) value={{option.value}}>{{option.value}}</option></select></div></div>');
3939    $templateCache.put("fluro-interaction-form/payment/payment-method.html", '<hr><div class=payment-method-select><div ng-if=!settings.showOptions><h3 class=clearfix>{{selected.method.title}} <em class="pull-right small" ng-click="settings.showOptions = !settings.showOptions">Other payment options <i class="fa fa-angle-right"></i></em></h3></div><div ng-if=settings.showOptions><h3 class=clearfix>Select payment method <em ng-click="settings.showOptions = false" class="pull-right small">Back <i class="fa fa-angle-up"></i></em></h3><div class="payment-method-list list-group"><div class="payment-method-list-item list-group-item" ng-class="{active:method == selected.method}" ng-click=selectMethod(method) ng-repeat="method in methodOptions"><h5 class=title>{{method.title}}</h5></div></div></div><div ng-if=!settings.showOptions><div ng-if="selected.method.key == \'card\'"><formly-form model=model fields=options.data.fields></formly-form></div><div ng-if="selected.method == method && selected.method.description.length" ng-repeat="method in methodOptions"><div compile-html=method.description></div></div></div></div><hr>');
3940    $templateCache.put("fluro-interaction-form/payment/payment-summary.html", '<hr><div class=payment-summary><h3>Payment details</h3><div class=form-group><div ng-if=modifications.length class=payment-running-total><div class="row payment-base-row"><div class=col-xs-6><strong>Base Price</strong></div><div class="col-xs-3 col-xs-offset-3">{{paymentDetails.amount / 100 | currency}}</div></div><div class="row text-muted" ng-repeat="mod in modifications"><div class=col-xs-6><em>{{mod.title}}</em></div><div class="col-xs-3 text-right"><em>{{mod.description}}</em></div><div class=col-xs-3><em class=text-muted>{{mod.total / 100 | currency}}</em></div></div><div class="row payment-total-row"><div class=col-xs-6><h4>Total</h4></div><div class="col-xs-3 col-xs-offset-3"><h4>{{calculatedTotal /100 |currency}} <span class="text-uppercase text-muted">{{paymentDetails.currency}}</span></h4></div></div></div><div class=payment-amount ng-if=!modifications.length>{{calculatedTotal /100 |currency}} <span class=text-uppercase>({{paymentDetails.currency}})</span></div></div></div>');
3941    $templateCache.put("fluro-interaction-form/search-select/fluro-search-select-item.html", '<a class=clearfix><i class="fa fa-{{match.model._type}}"></i> <span ng-bind-html="match.label | trusted | typeaheadHighlight:query"></span> <span ng-if="match.model._type == \'event\' || match.model._type == \'plan\'" class="small text-muted">// {{match.model.startDate | formatDate:\'jS F Y - g:ia\'}}</span></a>');
3942    $templateCache.put("fluro-interaction-form/search-select/fluro-search-select-value.html", '<a class=clearfix><span ng-bind-html="match.label | trusted | typeaheadHighlight:query"></span></a>');
3943    $templateCache.put("fluro-interaction-form/search-select/fluro-search-select.html", '<div class=fluro-search-select><div ng-if="to.definition.type == \'reference\'"><div class=list-group ng-if="multiple && selection.values.length"><div class=list-group-item ng-repeat="item in selection.values"><i class="fa fa-times pull-right" ng-click=deselect(item)></i> {{item.title}}</div></div><div class=list-group ng-if="!multiple && selection.value"><div class="list-group-item clearfix"><i class="fa fa-times pull-right" ng-click=deselect(selection.value)></i> {{selection.value.title}}</div></div><div ng-if=canAddMore()><div class=input-group><input class=form-control formly-skip-ng-model-attrs-manipulator ng-model=proposed.value typeahead-template-url=fluro-interaction-form/search-select/fluro-search-select-item.html typeahead-on-select=select($item) placeholder=Search typeahead="item.title for item in retrieveReferenceOptions($viewValue)" typeahead-loading="search.loading"><div class=input-group-addon ng-if=!search.loading ng-click="search.terms = \'\'"><i class=fa ng-class="{\'fa-search\':!search.terms.length, \'fa-times\':search.terms.length}"></i></div><div class=input-group-addon ng-if=search.loading><i class="fa fa-spin fa-spinner"></i></div></div></div></div><div ng-if="to.definition.type != \'reference\'"><div class=list-group ng-if="multiple && selection.values.length"><div class=list-group-item ng-repeat="value in selection.values"><i class="fa fa-times pull-right" ng-click=deselect(value)></i> {{getValueLabel(value)}}</div></div><div class=list-group ng-if="!multiple && selection.value"><div class="list-group-item clearfix"><i class="fa fa-times pull-right" ng-click=deselect(selection.value)></i> {{getValueLabel(selection.value)}}</div></div><div ng-if=canAddMore()><div class=input-group><input class=form-control formly-skip-ng-model-attrs-manipulator ng-model=proposed.value typeahead-template-url=fluro-interaction-form/search-select/fluro-search-select-value.html typeahead-on-select=select($item.value) placeholder=Search typeahead="item.name for item in retrieveValueOptions($viewValue)" typeahead-loading="search.loading"><div class=input-group-addon ng-if=!search.loading ng-click="search.terms = \'\'"><i class=fa ng-class="{\'fa-search\':!search.terms.length, \'fa-times\':search.terms.length}"></i></div><div class=input-group-addon ng-if=search.loading><i class="fa fa-spin fa-spinner"></i></div></div></div></div></div>');
3944    $templateCache.put("fluro-interaction-form/value/value.html", "<div class=fluro-interaction-value style=display:none><pre>{{model[options.key] | json}}</pre></div>");
3945    $templateCache.put("site/site.html", "<div class=site></div>");
3946} ]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.