flapjack 0.8.4 → 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/bin/flapper +1 -1
- data/lib/flapjack/data/contact.rb +28 -3
- data/lib/flapjack/data/entity.rb +2 -0
- data/lib/flapjack/data/notification_rule.rb +5 -1
- data/lib/flapjack/gateways/jsonapi.rb +19 -2
- data/lib/flapjack/gateways/jsonapi/contact_methods.rb +187 -253
- data/lib/flapjack/gateways/jsonapi/entity_methods.rb +0 -28
- data/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb +14 -6
- data/lib/flapjack/gateways/web.rb +1 -0
- data/lib/flapjack/gateways/web/public/img/select2-spinner.gif +0 -0
- data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +58 -0
- data/lib/flapjack/gateways/web/public/js/contacts.js +339 -62
- data/lib/flapjack/gateways/web/views/contacts.html.erb +5 -0
- data/lib/flapjack/gateways/web/views/edit_contacts.html.erb +94 -38
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/gateways/jsonapi/contact_methods_spec.rb +24 -536
- data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +1 -104
- metadata +3 -2
@@ -131,11 +131,6 @@ module Flapjack
|
|
131
131
|
'{"entities":[' + entities_json + ']}'
|
132
132
|
end
|
133
133
|
|
134
|
-
app.get '/checks/:entity' do
|
135
|
-
content_type :json
|
136
|
-
entity = find_entity(params[:entity])
|
137
|
-
entity.check_list.to_json
|
138
|
-
end
|
139
134
|
|
140
135
|
app.get %r{/status#{ENTITY_CHECK_FRAGMENT}} do
|
141
136
|
content_type :json
|
@@ -316,29 +311,6 @@ module Flapjack
|
|
316
311
|
errors.empty? ? 204 : err(403, *errors)
|
317
312
|
end
|
318
313
|
|
319
|
-
app.post '/entities/:entity/tags' do
|
320
|
-
content_type :json
|
321
|
-
|
322
|
-
tags = find_tags(params[:tag])
|
323
|
-
entity = find_entity(params[:entity])
|
324
|
-
entity.add_tags(*tags)
|
325
|
-
entity.tags.to_json
|
326
|
-
end
|
327
|
-
|
328
|
-
app.delete '/entities/:entity/tags' do
|
329
|
-
tags = find_tags(params[:tag])
|
330
|
-
entity = find_entity(params[:entity])
|
331
|
-
entity.delete_tags(*tags)
|
332
|
-
status 204
|
333
|
-
end
|
334
|
-
|
335
|
-
app.get '/entities/:entity/tags' do
|
336
|
-
content_type :json
|
337
|
-
|
338
|
-
entity = find_entity(params[:entity])
|
339
|
-
entity.tags.to_json
|
340
|
-
end
|
341
|
-
|
342
314
|
end
|
343
315
|
|
344
316
|
end
|
@@ -8,11 +8,15 @@ module Flapjack
|
|
8
8
|
module Rack
|
9
9
|
class JsonParamsParser < Struct.new(:app)
|
10
10
|
def call(env)
|
11
|
-
|
11
|
+
t = type(env)
|
12
|
+
if env['rack.input'] and not input_parsed?(env) and type_match?(t)
|
12
13
|
env['rack.request.form_input'] = env['rack.input']
|
13
|
-
|
14
|
+
json_data = env['rack.input'].read
|
14
15
|
env['rack.input'].rewind
|
15
|
-
|
16
|
+
|
17
|
+
data = Oj.load(json_data)
|
18
|
+
env['rack.request.form_hash'] = data.empty? ? {} :
|
19
|
+
(('application/json-patch+json'.eql?(t)) ? {'ops' => data} : data)
|
16
20
|
end
|
17
21
|
app.call(env)
|
18
22
|
end
|
@@ -21,9 +25,13 @@ module Flapjack
|
|
21
25
|
env['rack.request.form_input'].eql? env['rack.input']
|
22
26
|
end
|
23
27
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
28
|
+
def type(env)
|
29
|
+
return unless env['CONTENT_TYPE']
|
30
|
+
env['CONTENT_TYPE'].split(/\s*[;,]\s*/, 2).first.downcase
|
31
|
+
end
|
32
|
+
|
33
|
+
def type_match?(t)
|
34
|
+
Flapjack::Gateways::JSONAPI::JSON_REQUEST_MIME_TYPES.include?(t)
|
27
35
|
end
|
28
36
|
end
|
29
37
|
end
|
Binary file
|
@@ -73,3 +73,61 @@ Backbone.Model.prototype.parse = function (response) {
|
|
73
73
|
return obj;
|
74
74
|
};
|
75
75
|
|
76
|
+
toolbox.savePatch = function(model, attrs, patch) {
|
77
|
+
var patch_json = JSON.stringify(patch);
|
78
|
+
return model.save(attrs, {
|
79
|
+
data: patch_json,
|
80
|
+
patch: true,
|
81
|
+
contentType: 'application/json-patch+json'
|
82
|
+
});
|
83
|
+
};
|
84
|
+
|
85
|
+
// makes sense to call this with model.patch(model.changedAttributes),
|
86
|
+
// if that value isn't false
|
87
|
+
Backbone.Model.prototype.patch = function(attrs) {
|
88
|
+
if (attrs == null) {
|
89
|
+
attrs = {};
|
90
|
+
}
|
91
|
+
|
92
|
+
var context = this;
|
93
|
+
|
94
|
+
var patch = _.inject(attrs, function(memo, val, key) {
|
95
|
+
// skip if not a simple attribute value
|
96
|
+
if ( (key == 'links') || _.isObject(val) || _.isArray(val) ) {
|
97
|
+
return memo;
|
98
|
+
}
|
99
|
+
|
100
|
+
memo.push({
|
101
|
+
op: 'replace',
|
102
|
+
path: '/' + context.urlType + '/0/' + key,
|
103
|
+
value: val
|
104
|
+
});
|
105
|
+
|
106
|
+
return memo;
|
107
|
+
}, new Array());
|
108
|
+
|
109
|
+
toolbox.savePatch(this, attrs, patch);
|
110
|
+
};
|
111
|
+
|
112
|
+
// singular operation only -- TODO batch up and submit en masse
|
113
|
+
Backbone.Model.prototype.addLinked = function(type, obj) {
|
114
|
+
var patch = [{
|
115
|
+
op: 'add',
|
116
|
+
path: '/' + this.urlType + '/0/links/' + type + '/-',
|
117
|
+
value: obj.get('id')
|
118
|
+
}];
|
119
|
+
|
120
|
+
toolbox.savePatch(this, {}, patch);
|
121
|
+
this.get('links')[type].add(obj);
|
122
|
+
};
|
123
|
+
|
124
|
+
// singular operation only -- TODO batch up and submit en masse
|
125
|
+
Backbone.Model.prototype.removeLinked = function(type, obj) {
|
126
|
+
var patch = [{
|
127
|
+
op: 'remove',
|
128
|
+
path: '/' + this.urlType + '/0/links/' + type + '/' + obj.get('id'),
|
129
|
+
}];
|
130
|
+
|
131
|
+
toolbox.savePatch(this, {}, patch);
|
132
|
+
this.get('links')[type].remove(obj);
|
133
|
+
};
|
@@ -1,5 +1,8 @@
|
|
1
1
|
$(document).ready(function() {
|
2
2
|
|
3
|
+
// fix select2 with modal
|
4
|
+
$.fn.modal.Constructor.prototype.enforceFocus = function() {};
|
5
|
+
|
3
6
|
var app = {
|
4
7
|
api_url: $('div#data-api-url').data('api-url')
|
5
8
|
};
|
@@ -19,13 +22,38 @@ $(document).ready(function() {
|
|
19
22
|
toJSON: function() {
|
20
23
|
return { entities: [ _.clone( this.attributes ) ] }
|
21
24
|
},
|
22
|
-
|
25
|
+
urlType: 'entities',
|
26
|
+
urlRoot: function() { return app.api_url + "/" + this.urlType; }
|
23
27
|
});
|
24
28
|
|
25
29
|
app.EntityCollection = Backbone.Collection.extend({
|
26
30
|
model: app.Entity,
|
27
31
|
comparator: 'name',
|
28
|
-
|
32
|
+
urlType: 'entities',
|
33
|
+
url: function() { return app.api_url + "/" + this.urlType; }
|
34
|
+
});
|
35
|
+
|
36
|
+
app.Medium = Backbone.Model.extend({
|
37
|
+
name: 'media',
|
38
|
+
defaults: {
|
39
|
+
address: '',
|
40
|
+
interval: 60,
|
41
|
+
rollup_threshold: 3,
|
42
|
+
id: null,
|
43
|
+
contact_id: null,
|
44
|
+
},
|
45
|
+
toJSON: function() {
|
46
|
+
return { media: [ _.clone( this.attributes ) ] }
|
47
|
+
},
|
48
|
+
urlType: 'media',
|
49
|
+
urlRoot: function() { return app.api_url + "/" + this.urlType; }
|
50
|
+
});
|
51
|
+
|
52
|
+
app.MediumCollection = Backbone.Collection.extend({
|
53
|
+
model: app.Medium,
|
54
|
+
comparator: 'type',
|
55
|
+
urlType: 'media',
|
56
|
+
url: function() { return app.api_url + "/" + this.urlType; }
|
29
57
|
});
|
30
58
|
|
31
59
|
app.Contact = Backbone.Model.extend({
|
@@ -43,18 +71,24 @@ $(document).ready(function() {
|
|
43
71
|
// TODO how will we handle circular references? make it a string and eval it?
|
44
72
|
linkages: {
|
45
73
|
entity: app.Entity,
|
46
|
-
entities: app.EntityCollection
|
74
|
+
entities: app.EntityCollection,
|
75
|
+
medium: app.Medium,
|
76
|
+
media: app.MediumCollection
|
47
77
|
},
|
48
|
-
|
78
|
+
urlType: 'contacts',
|
79
|
+
urlRoot: function() { return app.api_url + "/" + this.urlType; }
|
49
80
|
});
|
50
81
|
|
51
82
|
app.ContactCollection = Backbone.Collection.extend({
|
52
83
|
model: app.Contact,
|
53
|
-
url: app.api_url + "/contacts",
|
54
84
|
linkages: {
|
55
85
|
entity: app.Entity,
|
56
|
-
entities: app.EntityCollection
|
86
|
+
entities: app.EntityCollection,
|
87
|
+
medium: app.Medium,
|
88
|
+
media: app.MediumCollection
|
57
89
|
},
|
90
|
+
urlType: 'contacts',
|
91
|
+
url: function() { return app.api_url + "/" + this.urlType; }
|
58
92
|
});
|
59
93
|
|
60
94
|
app.ActionsView = Backbone.View.extend({
|
@@ -69,17 +103,53 @@ $(document).ready(function() {
|
|
69
103
|
if ( $('#contactModal').hasClass('in') ) { return; }
|
70
104
|
|
71
105
|
$('#contactModal h4#contactModalLabel').text('New Contact');
|
72
|
-
$('#contactModal button
|
106
|
+
$('#contactModal button#contactAccept').text('Create Contact');
|
107
|
+
|
108
|
+
var context = this;
|
109
|
+
|
110
|
+
// TODO if validating or leaving modal open, re-establish the event
|
111
|
+
$('#contactModal button#contactAccept').one('click', function() { context.save(); });
|
112
|
+
|
113
|
+
this.model = new app.Contact();
|
114
|
+
this.model.set('links', {entities: new app.EntityCollection(), media: new app.MediumCollection()});
|
115
|
+
|
116
|
+
var contactView = new app.ContactView({model: this.model});
|
117
|
+
|
118
|
+
$('#contactModal div.modal-footer').siblings().remove();
|
119
|
+
$('#contactModal div.modal-footer').before(contactView.render().$el);
|
120
|
+
|
121
|
+
var currentEntities = this.model.get('links')['entities'];
|
122
|
+
|
123
|
+
var contactEntityList = new app.ContactEntityList({collection: currentEntities, contact: this.model});
|
124
|
+
$('#contactModal tbody#contactEntityList').replaceWith( contactEntityList.render().$el );
|
125
|
+
|
126
|
+
var entityChooser = new app.EntityChooser({model: this.model, currentEntities: currentEntities});
|
127
|
+
entityChooser.render();
|
128
|
+
|
129
|
+
var context = this;
|
130
|
+
|
131
|
+
// Setup contact media
|
132
|
+
var contactMediaList = new app.ContactMediaList({
|
133
|
+
collection: this.model.get('links')['media'],
|
134
|
+
contact: this.model
|
135
|
+
});
|
136
|
+
|
137
|
+
$('#contactModal tbody#contactMediaList')
|
138
|
+
.replaceWith( contactMediaList.render().$el )
|
73
139
|
|
74
|
-
$('#contactModal input[name=contact_id]').val('');
|
75
|
-
$('#contactModal input[name=contact_first_name]').val('');
|
76
|
-
$('#contactModal input[name=contact_last_name]').val('');
|
77
|
-
$('#contactModal input[name=contact_email]').val('');
|
78
140
|
$('#contactModal').modal('show');
|
79
141
|
},
|
80
142
|
render: function() {
|
81
143
|
this.$el.html(this.template({}));
|
82
144
|
return this;
|
145
|
+
},
|
146
|
+
save: function() {
|
147
|
+
data = {'first_name': $('#contactModal input[name=contact_first_name]').val(),
|
148
|
+
'last_name': $('#contactModal input[name=contact_last_name]').val(),
|
149
|
+
'email': $('#contactModal input[name=contact_email]').val()};
|
150
|
+
this.model.save(data, {type: 'POST', contentType: 'application/vnd.api+json'});
|
151
|
+
contacts.add(this.model);
|
152
|
+
$('#contactModal').modal('hide');
|
83
153
|
}
|
84
154
|
});
|
85
155
|
|
@@ -87,73 +157,111 @@ $(document).ready(function() {
|
|
87
157
|
// this.collection == duplicate of entities with
|
88
158
|
// entities enabled for this contact removed
|
89
159
|
app.EntityChooser = Backbone.View.extend({
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
160
|
+
template: _.template($('#contact-entity-chooser').html()),
|
161
|
+
el: $("#entityAdd"),
|
162
|
+
events: {
|
163
|
+
'click button#add-contact-entity' : 'addEntities',
|
164
|
+
},
|
165
|
+
initialize: function(options) {
|
166
|
+
this.options = options || {};
|
167
|
+
this.listenTo(options.currentEntities, 'add', this.refresh);
|
168
|
+
this.listenTo(options.currentEntities, 'remove', this.refresh);
|
169
|
+
},
|
170
|
+
render: function() {
|
95
171
|
|
96
|
-
|
97
|
-
return _.contains(contact_entity_ids, item.get('id'));
|
98
|
-
});
|
172
|
+
this.calculate();
|
99
173
|
|
100
|
-
|
101
|
-
|
102
|
-
render: function() {
|
103
|
-
var jqel = $(this.el);
|
174
|
+
// clear array
|
175
|
+
this.entityIdsToAdd = new Array();
|
104
176
|
|
105
|
-
|
106
|
-
|
177
|
+
this.$el.html(this.template({}));
|
178
|
+
|
179
|
+
var jqel = $(this.el).find('input#entityChooser');
|
180
|
+
|
181
|
+
var context = this;
|
182
|
+
jqel.on('change', function(e) {
|
183
|
+
if ( !_.isArray(e.removed) && _.isObject(e.removed) ) {
|
184
|
+
context.entityIdsToAdd = _.without(context.entityIdsToAdd, e.removed.id);
|
185
|
+
}
|
186
|
+
|
187
|
+
if ( !_.isArray(e.added) && _.isObject(e.added) && (context.entityIdsToAdd.indexOf(e.added.id) == -1) ) {
|
188
|
+
context.entityIdsToAdd.push(e.added.id);
|
189
|
+
}
|
107
190
|
});
|
108
191
|
|
109
192
|
var format = function(item) { return item.name; }
|
193
|
+
var context = this;
|
110
194
|
|
111
195
|
jqel.select2({
|
112
|
-
placeholder: "Select
|
113
|
-
data: {
|
196
|
+
placeholder: "Select Entities",
|
197
|
+
data: {results: context.results, text: 'name'},
|
114
198
|
formatSelection: format,
|
115
|
-
formatResult: format
|
199
|
+
formatResult: format,
|
200
|
+
multiple: true,
|
201
|
+
width: 'off',
|
116
202
|
});
|
117
203
|
|
118
204
|
return this;
|
119
205
|
},
|
206
|
+
calculate: function() {
|
207
|
+
var contact_entity_ids = this.options.currentEntities.pluck('id');
|
120
208
|
|
121
|
-
|
209
|
+
var someEntities = allEntities.reject(function(item, context) {
|
210
|
+
return _.contains(contact_entity_ids, item.get('id'));
|
211
|
+
});
|
212
|
+
|
213
|
+
this.collection = new app.EntityCollection(someEntities);
|
122
214
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
attributes: {
|
127
|
-
size: "12",
|
128
|
-
multiple: "multiple"
|
215
|
+
this.results = this.collection.map( function(item) {
|
216
|
+
return item.attributes;
|
217
|
+
});
|
129
218
|
},
|
130
|
-
|
131
|
-
this.
|
219
|
+
refresh: function(model, collection, options) {
|
220
|
+
this.calculate();
|
221
|
+
var jqel = $(this.el).find('input#entityChooser');
|
222
|
+
var context = this;
|
223
|
+
var format = function(item) { return item.name; }
|
224
|
+
jqel.select2({
|
225
|
+
placeholder: "Select Entities",
|
226
|
+
data: {results: context.results, text: 'name'},
|
227
|
+
formatSelection: format,
|
228
|
+
formatResult: format,
|
229
|
+
multiple: true,
|
230
|
+
width: 'off',
|
231
|
+
});
|
132
232
|
},
|
133
|
-
|
134
|
-
var jqel = $(this.el);
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
jqel.append(item.render().el);
|
233
|
+
addEntities: function() {
|
234
|
+
var jqel = $(this.el).find('input#entityChooser');
|
235
|
+
jqel.select2("val", null);
|
236
|
+
var context = this;
|
237
|
+
_.each(this.entityIdsToAdd, function(entity_id) {
|
238
|
+
var newEntity = allEntities.find(function(entity) { return entity.id == entity_id; });
|
239
|
+
context.model.addLinked('entities', newEntity);
|
141
240
|
});
|
241
|
+
this.entityIdsToAdd.length = 0;
|
242
|
+
},
|
243
|
+
});
|
142
244
|
|
245
|
+
app.ContactView = Backbone.View.extend({
|
246
|
+
template: _.template($('#contact-template').html()),
|
247
|
+
id: 'contactView',
|
248
|
+
render: function() {
|
249
|
+
var template_values = _.clone(this.model.attributes);
|
250
|
+
this.$el.html(this.template(template_values));
|
143
251
|
return this;
|
144
|
-
}
|
252
|
+
}
|
145
253
|
});
|
146
254
|
|
147
255
|
app.ContactList = Backbone.View.extend({
|
148
|
-
tagName: '
|
149
|
-
|
256
|
+
tagName: 'tbody',
|
257
|
+
el: $('#contactList'),
|
150
258
|
initialize: function() {
|
151
259
|
this.collection.on('add', this.render, this);
|
152
260
|
},
|
153
261
|
render: function() {
|
154
262
|
var jqel = $(this.el);
|
155
263
|
jqel.empty();
|
156
|
-
|
264
|
+
var context = this;
|
157
265
|
this.collection.each(function(contact) {
|
158
266
|
var item = new app.ContactListItem({ model: contact });
|
159
267
|
jqel.append($(item.render().el));
|
@@ -163,15 +271,45 @@ $(document).ready(function() {
|
|
163
271
|
},
|
164
272
|
});
|
165
273
|
|
166
|
-
app.
|
167
|
-
tagName: '
|
274
|
+
app.ContactEntityList = Backbone.View.extend({
|
275
|
+
tagName: 'tbody',
|
276
|
+
id: 'contactEntityList',
|
277
|
+
initialize: function(options) {
|
278
|
+
this.options = options || {};
|
279
|
+
this.collection.on('add', this.render, this);
|
280
|
+
this.collection.on('remove', this.render, this);
|
281
|
+
},
|
168
282
|
render: function() {
|
283
|
+
var jqel = $(this.el);
|
284
|
+
jqel.empty();
|
285
|
+
var contact = this.options.contact;
|
286
|
+
this.collection.each(function(entity) {
|
287
|
+
var item = new app.ContactEntityListItem({ model: entity, contact: contact });
|
288
|
+
jqel.append(item.render().el);
|
289
|
+
});
|
169
290
|
|
170
|
-
|
171
|
-
|
291
|
+
return this;
|
292
|
+
},
|
293
|
+
});
|
172
294
|
|
295
|
+
app.ContactEntityListItem = Backbone.View.extend({
|
296
|
+
tagName: 'tr',
|
297
|
+
template: _.template($('#contact-entities-list-item-template').html()),
|
298
|
+
events: {
|
299
|
+
'click button.delete-entity' : 'removeEntity',
|
300
|
+
},
|
301
|
+
initialize: function(options) {
|
302
|
+
this.options = options || {};
|
303
|
+
},
|
304
|
+
render: function() {
|
305
|
+
var template_values = _.clone(this.model.attributes);
|
306
|
+
this.$el.html(this.template(template_values));
|
173
307
|
return this;
|
174
|
-
}
|
308
|
+
},
|
309
|
+
removeEntity: function() {
|
310
|
+
this.options.contact.removeLinked('entities', this.model);
|
311
|
+
this.$el.remove();
|
312
|
+
},
|
175
313
|
});
|
176
314
|
|
177
315
|
app.ContactListItem = Backbone.View.extend({
|
@@ -179,7 +317,12 @@ $(document).ready(function() {
|
|
179
317
|
className: 'contact_list_item',
|
180
318
|
template: _.template($('#contact-list-item-template').html()),
|
181
319
|
events: {
|
182
|
-
|
320
|
+
'click .button.delete-contact': 'removeContact',
|
321
|
+
'click': 'editContact',
|
322
|
+
},
|
323
|
+
initialize: function() {
|
324
|
+
// causes an unnecessary render on create, but required for update TODO cleanup
|
325
|
+
this.listenTo(this.model, "sync", this.render);
|
183
326
|
},
|
184
327
|
|
185
328
|
render: function() {
|
@@ -193,16 +336,151 @@ $(document).ready(function() {
|
|
193
336
|
if ( $('#contactModal').hasClass('in') ) { return; }
|
194
337
|
|
195
338
|
$('#contactModal h4#contactModalLabel').text('Edit Contact');
|
196
|
-
$('#contactModal button
|
339
|
+
$('#contactModal button#contactAccept').text('Update Contact');
|
340
|
+
|
341
|
+
var context = this;
|
342
|
+
|
343
|
+
// TODO if validating or leaving modal open, re-establish the event
|
344
|
+
$('#contactModal button#contactAccept').one('click', function() { context.save(); });
|
345
|
+
|
346
|
+
var contactView = new app.ContactView({model: this.model});
|
347
|
+
|
348
|
+
$('#contactModal div.modal-footer').siblings().remove();
|
349
|
+
$('#contactModal div.modal-footer').before(contactView.render().$el);
|
350
|
+
|
351
|
+
var currentEntities = this.model.get('links')['entities'];
|
352
|
+
|
353
|
+
var contactEntityList = new app.ContactEntityList({collection: currentEntities, contact: this.model});
|
354
|
+
$('#contactModal tbody#contactEntityList').replaceWith( contactEntityList.render().$el );
|
355
|
+
|
356
|
+
var entityChooser = new app.EntityChooser({model: this.model, currentEntities: currentEntities});
|
357
|
+
entityChooser.render();
|
358
|
+
|
359
|
+
// Setup contact media
|
360
|
+
var contactMediaList = new app.ContactMediaList({
|
361
|
+
collection: this.model.get('links')['media'],
|
362
|
+
contact: this.model
|
363
|
+
});
|
197
364
|
|
198
|
-
$('#contactModal
|
199
|
-
|
200
|
-
$('#contactModal input[name=contact_last_name]').val(this.model.get('last_name'));
|
201
|
-
$('#contactModal input[name=contact_email]').val(this.model.get('email'));
|
365
|
+
$('#contactModal tbody#contactMediaList')
|
366
|
+
.replaceWith( contactMediaList.render().$el )
|
202
367
|
|
203
368
|
$('#contactModal').modal('show');
|
204
369
|
},
|
205
370
|
|
371
|
+
save: function() {
|
372
|
+
data = {'first_name': $('#contactModal input[name=contact_first_name]').val(),
|
373
|
+
'last_name': $('#contactModal input[name=contact_last_name]').val(),
|
374
|
+
'email': $('#contactModal input[name=contact_email]').val()};
|
375
|
+
this.model.save(data, {type: 'PUT', contentType: 'application/vnd.api+json'});
|
376
|
+
$('#contactModal').modal('hide');
|
377
|
+
},
|
378
|
+
|
379
|
+
removeContact: function(e) {
|
380
|
+
e.stopImmediatePropagation();
|
381
|
+
|
382
|
+
var context = this;
|
383
|
+
|
384
|
+
context.model.destroy({
|
385
|
+
success: function() {
|
386
|
+
context.remove()
|
387
|
+
}
|
388
|
+
});
|
389
|
+
},
|
390
|
+
});
|
391
|
+
|
392
|
+
app.ContactMediaList = Backbone.View.extend({
|
393
|
+
tagName: 'tbody',
|
394
|
+
id: 'contactMediaList',
|
395
|
+
initialize: function(options) {
|
396
|
+
var context = this;
|
397
|
+
_.each(['email', 'sms', 'jabber'], function(type) {
|
398
|
+
var medium = context.collection.find(function(cm) {
|
399
|
+
return cm.get('type') == type;
|
400
|
+
});
|
401
|
+
|
402
|
+
if ( _.isUndefined(medium) ) {
|
403
|
+
medium = new app.Medium({
|
404
|
+
type: type,
|
405
|
+
address: '',
|
406
|
+
interval: 15,
|
407
|
+
rollup_threshold: 3,
|
408
|
+
contact_id: options.contact.get('id')
|
409
|
+
});
|
410
|
+
context.collection.add(medium);
|
411
|
+
}
|
412
|
+
});
|
413
|
+
},
|
414
|
+
render: function() {
|
415
|
+
var jqel = $(this.el);
|
416
|
+
jqel.empty();
|
417
|
+
|
418
|
+
this.collection.each(function(medium) {
|
419
|
+
var item = new app.ContactMediaListItem({ model: medium });
|
420
|
+
jqel.append(item.render().el);
|
421
|
+
});
|
422
|
+
|
423
|
+
return this;
|
424
|
+
},
|
425
|
+
});
|
426
|
+
|
427
|
+
app.ContactMediaListItem = Backbone.View.extend({
|
428
|
+
tagName: 'tr',
|
429
|
+
template: _.template($('#contact-media-list-item-template').html()),
|
430
|
+
events: {
|
431
|
+
// scoped to this view's el
|
432
|
+
'change input' : 'updateMedium'
|
433
|
+
},
|
434
|
+
render: function() {
|
435
|
+
var template_values = _.clone(this.model.attributes);
|
436
|
+
template_values['labels'] = {
|
437
|
+
'email' : 'Email',
|
438
|
+
'sms' : 'SMS',
|
439
|
+
'jabber' : 'Jabber'
|
440
|
+
};
|
441
|
+
this.$el.html(this.template(template_values));
|
442
|
+
return this;
|
443
|
+
},
|
444
|
+
updateMedium: function(event) {
|
445
|
+
var address = $(event.target).parent('td')
|
446
|
+
.siblings().addBack().find('input[data-attr=address]');
|
447
|
+
var interval = $(event.target).parent('td')
|
448
|
+
.siblings().addBack().find('input[data-attr=interval]');
|
449
|
+
var rollupThreshold =
|
450
|
+
$(event.target).parent('td')
|
451
|
+
.siblings().addBack().find('input[data-attr=rollup_threshold]');
|
452
|
+
|
453
|
+
var addressVal = address.val();
|
454
|
+
var intervalVal = interval.val();
|
455
|
+
var rollupThresholdVal = rollupThreshold.val();
|
456
|
+
|
457
|
+
var numRE = /^[0-9]+$/;
|
458
|
+
|
459
|
+
if ( !numRE.test(intervalVal) || !numRE.test(rollupThresholdVal) ) {
|
460
|
+
// only save if numeric fields have acceptable values
|
461
|
+
return;
|
462
|
+
}
|
463
|
+
|
464
|
+
if ( _.isUndefined(addressVal) || (addressVal.length == 0) ) {
|
465
|
+
// only save if address not blank
|
466
|
+
return;
|
467
|
+
}
|
468
|
+
|
469
|
+
// TODO visually highlight error
|
470
|
+
|
471
|
+
var attrName = event.target.getAttribute('data-attr');
|
472
|
+
var value = event.target.value;
|
473
|
+
|
474
|
+
var attrs = {};
|
475
|
+
attrs[attrName] = value;
|
476
|
+
|
477
|
+
if ( this.model.isNew() ) {
|
478
|
+
this.model.save(attrs);
|
479
|
+
this.model.set('id', this.model.get('contact_id') + '_' + this.model.get('type'));
|
480
|
+
} else {
|
481
|
+
this.model.patch(attrs);
|
482
|
+
}
|
483
|
+
}
|
206
484
|
});
|
207
485
|
|
208
486
|
var allEntities = new app.EntityCollection();
|
@@ -215,8 +493,7 @@ $(document).ready(function() {
|
|
215
493
|
var actionsView = new app.ActionsView({collection: collection});
|
216
494
|
var contactList = new app.ContactList({collection: collection});
|
217
495
|
$('#container').append(actionsView.render().el);
|
218
|
-
|
219
|
-
$('#contactList').append($(contactList.render().el).find('tr'));
|
496
|
+
contactList.render();
|
220
497
|
}
|
221
498
|
});
|
222
499
|
}
|