flapjack 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
}
|