Merge branch 'fixUXBugs' into updateSetupScript

This commit is contained in:
David Baldwynn 2017-11-21 15:59:18 -08:00
commit bc2f49b090
16 changed files with 203 additions and 162 deletions

View file

@ -364,8 +364,8 @@ exports.update = function(req, res) {
var form = req.form; var form = req.form;
var updatedForm = req.body.form; var updatedForm = req.body.form;
if(!form.analytics){ if(!form.analytics && req.body.form.analytics){
form.analytics = { form.analytics = {
visitors: [], visitors: [],
gaCode: '' gaCode: ''
@ -379,9 +379,18 @@ exports.update = function(req, res) {
diff.applyChange(form._doc, true, change); diff.applyChange(form._doc, true, change);
}); });
} else { } else {
if(!updatedForm){
res.status(400).send({
message: 'Updated Form is empty'
});
}
delete updatedForm.__v; delete updatedForm.lastModified;
delete updatedForm.created; delete updatedForm.created;
delete updatedForm.id;
delete updatedForm._id;
delete updatedForm.__v;
//Unless we have 'admin' privileges, updating the form's admin is disabled //Unless we have 'admin' privileges, updating the form's admin is disabled
if(updatedForm && req.user.roles.indexOf('admin') === -1) { if(updatedForm && req.user.roles.indexOf('admin') === -1) {
delete updatedForm.admin; delete updatedForm.admin;
@ -556,7 +565,8 @@ exports.formByIDFast = function(req, res, next, id) {
*/ */
exports.hasAuthorization = function(req, res, next) { exports.hasAuthorization = function(req, res, next) {
var form = req.form; var form = req.form;
if (req.form.admin.id !== req.user.id && req.user.roles.indexOf('admin') === -1) { debugger
if (req.form.admin.id !== req.user.id || req.user.roles.indexOf('admin') > -1) {
res.status(403).send({ res.status(403).send({
message: 'User '+req.user.username+' is not authorized to edit Form: '+form.title message: 'User '+req.user.username+' is not authorized to edit Form: '+form.title
}); });

View file

@ -22,24 +22,33 @@ var constants = module.exports = {
'date', 'date',
'email', 'email',
'legal', 'legal',
'url',
'textarea', 'textarea',
'link',
'statement', 'statement',
'welcome',
'thankyou',
'file',
'dropdown', 'dropdown',
'scale',
'rating', 'rating',
'radio', 'radio',
'checkbox',
'hidden', 'hidden',
'yes_no', 'yes_no',
'natural',
'stripe',
'number' 'number'
], ],
ratingShapeTypes: [
'Heart',
'Star',
'thumbs-up',
'thumbs-down',
'Circle',
'Square',
'Check Circle',
'Smile Outlined',
'Hourglass',
'bell',
'Paper Plane',
'Comment',
'Trash'
],
ratingShapeTypes: [ ratingShapeTypes: [
'Heart', 'Heart',
'Star', 'Star',

View file

@ -39,8 +39,8 @@ module.exports = {
createFieldDict: function(form_fields){ createFieldDict: function(form_fields){
var formFieldDict = {}; var formFieldDict = {};
form_fields.forEach(function(field){ form_fields.forEach(function(field){
if(field.hasOwnProperty('globalId') && field.hasOwnProperty('fieldValue')){ if(field.hasOwnProperty('fieldValue') && field.hasOwnProperty('_id')){
formFieldDict[field.globalId+''] = field.fieldValue+''; formFieldDict[field._id] = String(field.fieldValue);
} }
}); });
return formFieldDict; return formFieldDict;

View file

@ -228,30 +228,6 @@ FormSchema.plugin(timeStampPlugin, {
useVirtual: false useVirtual: false
}); });
function getDeletedIndexes(needle, haystack){
var deletedIndexes = [];
if(haystack.length > 0){
for(var i = 0; i < needle.length; i++){
if(haystack.indexOf(needle[i]) === -1){
deletedIndexes.push(i);
}
}
}
return deletedIndexes;
}
function formFieldsAllHaveIds(form_fields){
if(form_fields){
for(var i=0; i<form_fields.length; i++){
if(form_fields[i] && !form_fields[i].hasOwnProperty('_id') && !form_fields[i].hasOwnProperty('globalId')){
return false;
}
}
}
return true;
}
FormSchema.pre('save', function (next) { FormSchema.pre('save', function (next) {
if(this.form_fields && this.form_fields.length){ if(this.form_fields && this.form_fields.length){
this.form_fields = this.form_fields.filter(function(field){ this.form_fields = this.form_fields.filter(function(field){

View file

@ -49,13 +49,6 @@ function BaseFieldSchema(){
Schema.apply(this, arguments); Schema.apply(this, arguments);
this.add({ this.add({
newOptionSchema: {
type: Boolean,
default: false
},
globalId: {
type: String,
},
isSubmission: { isSubmission: {
type: Boolean, type: Boolean,
default: false default: false
@ -151,21 +144,13 @@ FormFieldSchema.pre('validate', function(next) {
if(this.fieldOptions && this.fieldOptions.length > 0){ if(this.fieldOptions && this.fieldOptions.length > 0){
error.errors.ratingOptions = new mongoose.Error.ValidatorError({path:'fieldOptions', message: 'fieldOptions are only allowed for type dropdown, checkbox or radio fields.', type: 'notvalid', value: this.ratingOptions}); error.errors.ratingOptions = new mongoose.Error.ValidatorError({path:'fieldOptions', message: 'fieldOptions are only allowed for type dropdown, checkbox or radio fields.', type: 'notvalid', value: this.ratingOptions});
console.error(error); console.error(error);
return(next(error)); return next(error);
} }
} }
return next(); return next();
}); });
//LogicJump Save
FormFieldSchema.pre('save', function(next) {
if(!this.globalId){
this.globalId = tokgen();
}
next();
});
//Submission fieldValue correction //Submission fieldValue correction
FormFieldSchema.pre('save', function(next) { FormFieldSchema.pre('save', function(next) {
if(this.fieldType === 'dropdown' && this.isSubmission){ if(this.fieldType === 'dropdown' && this.isSubmission){

View file

@ -40,8 +40,8 @@ describe('Form Model Unit Tests:', function() {
language: 'en', language: 'en',
form_fields: [ form_fields: [
{'fieldType':'textfield', title:'First Name', 'fieldValue': ''}, {'fieldType':'textfield', title:'First Name', 'fieldValue': ''},
{'fieldType':'checkbox', title:'nascar', 'fieldValue': ''}, {'fieldType':'legal', title:'nascar', 'fieldValue': ''},
{'fieldType':'checkbox', title:'hockey', 'fieldValue': ''} {'fieldType':'legal', title:'hockey', 'fieldValue': ''}
] ]
}); });
done(); done();

View file

@ -10,7 +10,22 @@ var should = require('should'),
Form = require('../models/form.server.model.js'), Form = require('../models/form.server.model.js'),
FormSubmission = require('../models/form_submission.server.model.js'), FormSubmission = require('../models/form_submission.server.model.js'),
Field = mongoose.model('Field'), Field = mongoose.model('Field'),
async = require('async'); async = require('async'),
_ = require('lodash');
function omitDeep(collection, excludeKeys) {
function omitFn(value) {
if (value && typeof value === 'object') {
excludeKeys.forEach((key) => {
delete value[key];
});
}
}
return _.cloneDeepWith(collection, omitFn);
}
/** /**
* Globals * Globals
@ -24,6 +39,18 @@ var credentials = {
password: 'password' password: 'password'
}; };
var sampleVisitorData = [{
socketId: 'ntneooe8989eotnoeeo',
referrer: 'http://google.com',
timeElapsed: 89898989,
isSubmitted: true,
language: 'en',
ipAddr: '192.168.1.1',
deviceType: 'desktop',
userAgent: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
filledOutFields: []
}];
/** /**
* Form routes tests * Form routes tests
*/ */
@ -50,8 +77,8 @@ describe('Form Routes Unit tests', function() {
admin: user.id, admin: user.id,
form_fields: [ form_fields: [
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}), new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}), new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''}) new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''})
], ],
isLive: true isLive: true
}; };
@ -91,7 +118,7 @@ describe('Form Routes Unit tests', function() {
FormObj.save(function(err, form) { FormObj.save(function(err, form) {
if(err) return done(err); if(err) return done(err);
userSession.get('/subdomain/' + credentials.username + '/forms/' + form._id + '/render') userSession.get('/forms/' + form._id + '/render')
.expect(200) .expect(200)
.end(function(err, res) { .end(function(err, res) {
if(err) return done(err) if(err) return done(err)
@ -114,7 +141,7 @@ describe('Form Routes Unit tests', function() {
FormObj.save(function(err, form) { FormObj.save(function(err, form) {
if(err) return done(err); if(err) return done(err);
userSession.get('/subdomain/' + credentials.username + '/forms/' + form._id + '/render') userSession.get('/forms/' + form._id + '/render')
.expect(401, {message: 'Form is Not Public'}) .expect(401, {message: 'Form is Not Public'})
.end(function(err, res) { .end(function(err, res) {
done(err); done(err);
@ -315,8 +342,8 @@ describe('Form Routes Unit tests', function() {
admin: user.id, admin: user.id,
form_fields: [ form_fields: [
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}), new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}), new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''}) new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''})
], ],
isLive: true isLive: true
}; };
@ -327,8 +354,8 @@ describe('Form Routes Unit tests', function() {
admin: user.id, admin: user.id,
form_fields: [ form_fields: [
new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}), new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'formula one', 'fieldValue': ''}), new Field({'fieldType':'legal', 'title':'formula one', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'football', 'fieldValue': ''}) new Field({'fieldType':'legal', 'title':'football', 'fieldValue': ''})
], ],
isLive: true isLive: true
}; };
@ -364,6 +391,123 @@ describe('Form Routes Unit tests', function() {
}); });
}); });
it(' > should preserve visitor data when updating a Form', function(done) {
// Create new Form model instance
var formObject = {
title: 'First Form',
language: 'en',
admin: user.id,
form_fields: [
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}),
new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''})
],
isLive: true,
analytics: {
gaCode: '',
visitors: sampleVisitorData
}
};
var formUpdateObject = {
title: 'Second Form',
language: 'en',
admin: user.id,
form_fields: [
new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}),
new Field({'fieldType':'legal', 'title':'formula one', 'fieldValue': ''}),
new Field({'fieldType':'legal', 'title':'football', 'fieldValue': ''})
],
isLive: true
};
var CurrentForm = new Form(formObject);
// Save the Form
CurrentForm.save(function(err, form) {
if(err) return done(err);
loginSession.put('/forms/' + form.id)
.send({ form: formUpdateObject })
.expect(200)
.end(function(err, res) {
should.not.exist(err);
Form.findById(form.id, function (FormFindErr, UpdatedForm){
should.not.exist(FormFindErr);
should.exist(UpdatedForm);
var updatedFormObj = UpdatedForm.toJSON();
var oldFormObj = CurrentForm.toJSON();
updatedFormObj.analytics.should.deepEqual(oldFormObj.analytics);
done(FormFindErr);
});
});
});
});
it(' > shouldn\'t allow a user to change the id when updating a form', function(done) {
// Create new Form model instance
var formObject = {
title: 'First Form',
language: 'en',
admin: user.id,
form_fields: [
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}),
new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''})
],
isLive: true
};
var formUpdateObject = {
id: mongoose.Types.ObjectId(),
title: 'First Form',
language: 'en',
admin: user.id,
form_fields: [
new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}),
new Field({'fieldType':'legal', 'title':'formula one', 'fieldValue': ''}),
new Field({'fieldType':'legal', 'title':'football', 'fieldValue': ''})
],
isLive: true
};
var CurrentForm = new Form(formObject);
// Save the Form
CurrentForm.save(function(err, InitialForm) {
if(err) return done(err);
loginSession.put('/forms/' + InitialForm.id)
.send({ form: formUpdateObject })
.expect(200)
.end(function(err, OldForm) {
should.not.exist(err);
Form.findById(InitialForm.id, function (FormFindErr, UpdatedForm){
should.not.exist(FormFindErr);
should.exist(UpdatedForm);
var updatedFormObj = UpdatedForm.toJSON();
var oldFormObj = InitialForm.toJSON();
updatedFormObj = omitDeep('lastModified');
oldFormObj = omitDeep('lastModified');
updatedFormObj.should.deepEqual(oldFormObj);
done(FormFindErr);
});
});
});
});
afterEach('should be able to signout user', function(done){ afterEach('should be able to signout user', function(done){
authenticatedSession.get('/auth/signout') authenticatedSession.get('/auth/signout')
.expect(200) .expect(200)

View file

@ -166,7 +166,7 @@ describe('FormSubmission Model Unit Tests:', function() {
}); });
it('should be able to find FormSubmission by $elemMatch on form_fields id', function(done){ it('should be able to find FormSubmission by $elemMatch on form_fields id', function(done){
FormSubmission.findOne({ form: myForm._id, form_fields: {$elemMatch: {globalId: myForm.form_fields[0].globalId} } }) FormSubmission.findOne({ form: myForm.id, form_fields: {$elemMatch: {_id: myForm.form_fields[0]._id} } })
.exec(function(err, submission){ .exec(function(err, submission){
should.not.exist(err); should.not.exist(err);
should.exist(submission); should.exist(submission);
@ -176,78 +176,6 @@ describe('FormSubmission Model Unit Tests:', function() {
}); });
}); });
/*
describe('Test FormField and Submission Logic', function() {
beforeEach(function(done){
//Create Submission
mySubmission = new FormSubmission({
form_fields: _.merge(sampleSubmission, myForm.form_fields),
admin: user,
form: myForm,
timeElapsed: 17.55
});
mySubmission.save(function(err){
should.not.exist(err);
done();
});
});
it('should preserve deleted form_fields that have submissions without any problems', function(done) {
var fieldPropertiesToOmit = ['deletePreserved', 'globalId', 'lastModified', 'created', '_id', 'submissionId', 'isSubmission', 'validFieldTypes', 'title'];
var old_fields = myForm.toObject().form_fields;
var new_form_fields = _.clone(myForm.toObject().form_fields);
new_form_fields.splice(0, 1);
myForm.form_fields = new_form_fields;
myForm.save(function(err, _form) {
should.not.exist(err);
should.exist(_form.form_fields);
var actual_fields = _.deepOmit(_form.toObject().form_fields, fieldPropertiesToOmit);
old_fields = _.deepOmit(old_fields, fieldPropertiesToOmit);
should.deepEqual(actual_fields, old_fields, 'old form_fields not equal to newly saved form_fields');
done();
});
});
it('should delete \'preserved\' form_fields whose submissions have been removed without any problems', function(done) {
var old_fields = myForm.toObject().form_fields;
old_fields.splice(0,1);
var new_form_fields = _.clone(myForm.toObject().form_fields);
new_form_fields.splice(0, 1);
myForm.form_fields = new_form_fields;
myForm.save(function(err, _form){
should.not.exist(err);
should.exist(_form.form_fields);
should.exist(old_fields);
var actual_fields = _.deepOmit(_form.toObject().form_fields, ['lastModified', 'created', '_id']);
old_fields = _.deepOmit(old_fields, ['lastModified', 'created', '_id']);
should.deepEqual(JSON.stringify(actual_fields), JSON.stringify(old_fields)); //'old form_fields not equal to newly saved form_fields');
done();
});
});
afterEach(function(done){
mySubmission.remove(function(){
done();
});
});
});
*/
afterEach(function(done) { afterEach(function(done) {
Form.remove().exec(function() { Form.remove().exec(function() {
User.remove().exec(function() { User.remove().exec(function() {

View file

@ -54,8 +54,8 @@ describe('Form Submission Routes Unit tests', function() {
admin: user._id, admin: user._id,
form_fields: [ form_fields: [
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}), new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}), new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}),
new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''}) new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''})
], ],
selfNotifications: { selfNotifications: {
fromField: mongoose.Types.ObjectId(), fromField: mongoose.Types.ObjectId(),
@ -81,8 +81,8 @@ describe('Form Submission Routes Unit tests', function() {
form: form._id, form: form._id,
form_fields: [ form_fields: [
{'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David', _id: '', isSubmission: false, deletePreserved: false}, {'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David', _id: '', isSubmission: false, deletePreserved: false},
{'fieldType':'checkbox', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true}, {'fieldType':'legal', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true},
{'fieldType':'checkbox', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false} {'fieldType':'legal', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false}
], ],
percentageComplete: 100, percentageComplete: 100,
timeElapsed: 11.55, timeElapsed: 11.55,
@ -101,8 +101,8 @@ describe('Form Submission Routes Unit tests', function() {
_id: form._id, _id: form._id,
form_fields: [ form_fields: [
{'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David', _id: '', isSubmission: false, deletePreserved: false}, {'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David', _id: '', isSubmission: false, deletePreserved: false},
{'fieldType':'checkbox', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true}, {'fieldType':'legal', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true},
{'fieldType':'checkbox', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false} {'fieldType':'legal', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false}
], ],
percentageComplete: 100, percentageComplete: 100,
timeElapsed: 11.55, timeElapsed: 11.55,

View file

@ -14,9 +14,9 @@ const should = require('should'),
* Globals * Globals
*/ */
const validFormFields = [ const validFormFields = [
{fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false, globalId:'56340745f59a6fc9e22028e9'}, {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'},
{fieldType:'link', title:'Your Website', fieldValue: 'https://johnsmith.me', deletePreserved: false, globalId:'5c9e22028e907634f45f59a6'}, {fieldType:'link', title:'Your Website', fieldValue: 'https://johnsmith.me', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'},
{fieldType:'number', title:'Your Age', fieldValue: 45, deletePreserved: false, globalId:'56e90745f5934fc9e22028a6'} {fieldType:'number', title:'Your Age', fieldValue: 45, deletePreserved: false, _id:'56e90745f5934fc9e22028a6'}
]; ];
const validFieldDict = { const validFieldDict = {

1
config/env/test.js vendored
View file

@ -18,6 +18,7 @@ module.exports = {
//stream: 'access.log' //stream: 'access.log'
} }
}, },
subdomainsDisabled: true,
app: { app: {
title: 'TellForm Test' title: 'TellForm Test'
}, },

View file

@ -207,22 +207,11 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
if(dataToSend.analytics && dataToSend.analytics.visitors){ if(dataToSend.analytics && dataToSend.analytics.visitors){
delete dataToSend.analytics.visitors; delete dataToSend.analytics.visitors;
} }
if(dataToSend.submissions){
delete dataToSend.submissions;
}
if(dataToSend.visible_form_fields){ if(dataToSend.visible_form_fields){
delete dataToSend.visible_form_fields; delete dataToSend.visible_form_fields;
} }
if(dataToSend.analytics){
delete dataToSend.analytics.visitors;
delete dataToSend.analytics.fields;
delete dataToSend.analytics.submissions;
delete dataToSend.analytics.views;
delete dataToSend.analytics.conversionRate;
}
delete dataToSend.created; delete dataToSend.created;
delete dataToSend.lastModified; delete dataToSend.lastModified;
delete dataToSend.__v; delete dataToSend.__v;

View file

@ -18,7 +18,7 @@ angular.module('forms').directive('configureFormDirective', ['$rootScope', '$sta
placeholder: { placeholder: {
placeholders: $scope.myform.visible_form_fields.map(function(field){ placeholders: $scope.myform.visible_form_fields.map(function(field){
return { return {
id: field.globalId, id: field.id,
label: field.title label: field.title
}; };
}), }),

View file

@ -26,7 +26,6 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
forceHelperSize: true, forceHelperSize: true,
forcePlaceholderSize: true, forcePlaceholderSize: true,
stop: function(e, ui) { stop: function(e, ui) {
debugger;
$scope.update(false, $scope.myform, true, false, function(err){ $scope.update(false, $scope.myform, true, false, function(err){
if(err){ if(err){
console.error(err); console.error(err);

View file

@ -36,7 +36,7 @@
<ui-select-match placeholder="{{ 'OPTION_PLACEHOLDER' | translate }}"> <ui-select-match placeholder="{{ 'OPTION_PLACEHOLDER' | translate }}">
{{$select.selected.title}} {{$select.selected.title}}
</ui-select-match> </ui-select-match>
<ui-select-choices repeat="field.globalId as field in emailFields | filter: { title: $select.search }"> <ui-select-choices repeat="field.id as field in emailFields | filter: { title: $select.search }">
<span ng-bind-html="field.title | highlight: $select.search"> <span ng-bind-html="field.title | highlight: $select.search">
</span> </span>
</ui-select-choices> </ui-select-choices>

View file

@ -42,7 +42,7 @@
<ui-select-match placeholder="{{ 'OPTION_PLACEHOLDER' | translate }}"> <ui-select-match placeholder="{{ 'OPTION_PLACEHOLDER' | translate }}">
{{$select.selected.title}} {{$select.selected.title}}
</ui-select-match> </ui-select-match>
<ui-select-choices repeat="field.globalId as field in emailFields | filter: { title: $select.search }"> <ui-select-choices repeat="field.id as field in emailFields | filter: { title: $select.search }">
<span ng-bind-html="field.title | highlight: $select.search"> <span ng-bind-html="field.title | highlight: $select.search">
</span> </span>
</ui-select-choices> </ui-select-choices>