root / snf-cyclades-app / synnefo / ui / static / snf / js / views_ext.js @ d3e3cba2
History | View | Annotate | Download (19.6 kB)
1 |
;(function(root){
|
---|---|
2 |
|
3 |
// root
|
4 |
var root = root;
|
5 |
|
6 |
// setup namepsaces
|
7 |
var snf = root.synnefo = root.synnefo || {};
|
8 |
var models = snf.models = snf.models || {}
|
9 |
var storage = snf.storage = snf.storage || {};
|
10 |
var ui = snf.ui = snf.ui || {};
|
11 |
var util = snf.util || {};
|
12 |
var views = snf.views = snf.views || {}
|
13 |
|
14 |
// shortcuts
|
15 |
var bb = root.Backbone;
|
16 |
|
17 |
// logging
|
18 |
var logger = new snf.logging.logger("SNF-VIEWS"); |
19 |
var debug = _.bind(logger.debug, logger);
|
20 |
|
21 |
// Extended views module
|
22 |
// View objects to provide more sophisticated base objects for views
|
23 |
// that are bind to existing storage model/collection objects.
|
24 |
views.ext = {}; |
25 |
|
26 |
views.ext.View = views.View.extend({ |
27 |
rivets_view: false, |
28 |
rivets: undefined, |
29 |
container: undefined, |
30 |
classes:'', |
31 |
|
32 |
storage_handlers: {},
|
33 |
|
34 |
init: function() {}, |
35 |
post_init: function() {}, |
36 |
|
37 |
initialize: function(options) { |
38 |
views.ext.View.__super__.initialize.apply(this, arguments); |
39 |
this.container = options && options.container;
|
40 |
this._subviews = [];
|
41 |
if (this.tpl) { |
42 |
this.el = $(this.tpl).clone().removeClass("hidden").removeAttr('id'); |
43 |
} |
44 |
this.init.apply(this, arguments); |
45 |
this.post_init.apply(this, arguments); |
46 |
this.append_to_container();
|
47 |
$(this.el).addClass(this.classes); |
48 |
_.bindAll(this);
|
49 |
}, |
50 |
|
51 |
append_to_container: function() { |
52 |
if (!this.container) { return } |
53 |
var cont = $(this.container); |
54 |
cont.append(this.el);
|
55 |
}, |
56 |
|
57 |
create_view: function(view_cls, options) { |
58 |
var options = _.extend({}, options);
|
59 |
options.parent_view = this;
|
60 |
var view = new view_cls(options); |
61 |
if (view.css_classes) {
|
62 |
view.el.addClass(view.css_classes) |
63 |
} |
64 |
return view;
|
65 |
}, |
66 |
|
67 |
add_subview: function(view) { |
68 |
view.parent_view = this;
|
69 |
this._subviews.push(view);
|
70 |
}, |
71 |
|
72 |
remove_view: function(view) { |
73 |
this._subviews = _.without(this._subviews, view); |
74 |
}, |
75 |
|
76 |
hide_subviews: function() { |
77 |
_.each(this._subviews, function(view) { |
78 |
view.hide(true);
|
79 |
}); |
80 |
}, |
81 |
|
82 |
show_subviews: function() { |
83 |
_.each(this._subviews, function(view) { |
84 |
view.show(true);
|
85 |
}); |
86 |
}, |
87 |
|
88 |
pre_hide: function() { |
89 |
this.rivets_unbind();
|
90 |
this.remove_handlers();
|
91 |
}, |
92 |
|
93 |
get_extra_rivet_models: function() {}, |
94 |
|
95 |
get_rivet_object: function() { |
96 |
return this.rivet_object; |
97 |
}, |
98 |
|
99 |
post_hide: function() { |
100 |
this.hide_subviews();
|
101 |
this.trigger("hide"); |
102 |
}, |
103 |
|
104 |
rivets_init: function() { |
105 |
if (!this.rivets_view) { return } |
106 |
var rivet_object = this.get_rivet_object(); |
107 |
rivet_object['view'] = this; |
108 |
if (this.el != $("body").get(0)) { |
109 |
this.rivets = rivets.bind(this.el, rivet_object); |
110 |
} else {
|
111 |
} |
112 |
}, |
113 |
|
114 |
rivets_update: function() { |
115 |
if (!this.rivets_view) { return } |
116 |
this.rivets.update();
|
117 |
}, |
118 |
|
119 |
rivets_bind: function() { |
120 |
if (!this.rivets_view) { return } |
121 |
if (!this.rivets) { this.rivets_init(); return } |
122 |
var rivet_object = this.get_rivet_object(); |
123 |
rivet_object['view'] = this; |
124 |
this.rivets.models = rivet_object;
|
125 |
//this.rivets.build();
|
126 |
this.rivets.bind();
|
127 |
}, |
128 |
|
129 |
rivets_unbind: function() { |
130 |
if (!this.rivets_view) { return } |
131 |
if (!this.rivets) { return } |
132 |
this.rivets.unbind();
|
133 |
}, |
134 |
|
135 |
pre_show: function() { |
136 |
this.set_handlers();
|
137 |
this.rivets_bind();
|
138 |
this.show_subviews();
|
139 |
}, |
140 |
|
141 |
resolve_storage_object: function(id) { |
142 |
var result;
|
143 |
if (this['resolve_' + id + '_storage_object']) { |
144 |
return this['resolve_' + id + '_storage_object'](); |
145 |
} |
146 |
result = synnefo.storage[id]; |
147 |
return result ? result : this.collection |
148 |
}, |
149 |
|
150 |
each_storage_handler: function(cb, context) { |
151 |
if (!context) { context = this } |
152 |
_.each(this.storage_handlers, function(handlers, object_name) { |
153 |
_.each(handlers, function(events, handler_name) {
|
154 |
_.each(events, function(event) {
|
155 |
object = this.resolve_storage_object(object_name);
|
156 |
handler = this['handle_' + handler_name]; |
157 |
if (!handler) {
|
158 |
throw "Handler " + handler_name + " does not exist"; |
159 |
} |
160 |
if (!object) {
|
161 |
throw "Storage object " + object_name + " does not exist"; |
162 |
} |
163 |
cb.call(context, object, event, handler); |
164 |
}, this);
|
165 |
}, this);
|
166 |
}, this);
|
167 |
}, |
168 |
|
169 |
get_handler: function(id) { |
170 |
}, |
171 |
|
172 |
set_handlers: function() { |
173 |
this.each_storage_handler(this.set_handler, this); |
174 |
}, |
175 |
|
176 |
remove_handlers: function() { |
177 |
this.each_storage_handler(this.remove_handler, this); |
178 |
}, |
179 |
|
180 |
set_handler: function(object, event, handler) { |
181 |
object.bind(event, handler); |
182 |
}, |
183 |
|
184 |
remove_handler: function(object, event, handler) { |
185 |
object.unbind(event, handler); |
186 |
} |
187 |
}); |
188 |
|
189 |
views.ext.PaneView = views.ext.View.extend({ |
190 |
collection_view_cls: null, |
191 |
collection_view_selector: '.collection', |
192 |
init: function() { |
193 |
var options = {};
|
194 |
options['el'] = $(this.$(this.collection_view_selector).get(0)); |
195 |
this.collection_view = this.create_view(this.collection_view_cls, options); |
196 |
this.add_subview(this.collection_view); |
197 |
}, |
198 |
}); |
199 |
|
200 |
views.ext.CollectionView = views.ext.View.extend({ |
201 |
collection: undefined, |
202 |
model_view_cls: undefined, |
203 |
animation_speed: 200, |
204 |
quota_key: undefined, |
205 |
quota_limit_message: undefined, |
206 |
|
207 |
init: function() { |
208 |
var handlers = {};
|
209 |
handlers[this.collection_name] = {
|
210 |
'collection_change': ['update', 'sort'], |
211 |
'collection_reset': ['reset'], |
212 |
'model_change': ['change'], |
213 |
'model_add': ['add'], |
214 |
'model_remove': ['remove'] |
215 |
} |
216 |
this.storage_handlers = _.extend(handlers, this.storage_handlers) |
217 |
this._model_views = {};
|
218 |
this.list_el = $(this.$(".items-list").get(0)); |
219 |
this.empty_el = $(this.$(".empty-list").get(0)); |
220 |
if (this.create_view_cls) { |
221 |
this._create_view = new this.create_view_cls(); |
222 |
this._create_view.parent_view = this; |
223 |
} |
224 |
|
225 |
this.create_button = this.$(".create-button a"); |
226 |
this.create_button.click(_.bind(function(e) { |
227 |
e.preventDefault(); |
228 |
if (this.$(".create-button a").hasClass("disabled")) { |
229 |
return;
|
230 |
} |
231 |
this.handle_create_click();
|
232 |
}, this));
|
233 |
|
234 |
if (this.quota_key && !this.quota) { |
235 |
this.quota = synnefo.storage.quotas.get(this.quota_key); |
236 |
} |
237 |
|
238 |
if (this.quota) { |
239 |
this.quota.bind("change", _.bind(this.update_quota, this)); |
240 |
this.update_quota();
|
241 |
} |
242 |
}, |
243 |
|
244 |
update_quota: function() { |
245 |
var available = this.quota.get_available(); |
246 |
if (available > 0) { |
247 |
this.create_button.removeClass("disabled"); |
248 |
this.create_button.attr("title", ""); |
249 |
} else {
|
250 |
this.create_button.addClass("disabled"); |
251 |
this.create_button.attr("title", this.quota_limit_message || "Quota limit reached") |
252 |
} |
253 |
}, |
254 |
|
255 |
post_create: function() { |
256 |
this.quota && this.quota.increase(); |
257 |
}, |
258 |
|
259 |
post_destroy: function() { |
260 |
this.quota && this.quota.decrease(); |
261 |
}, |
262 |
|
263 |
handle_create_click: function() { |
264 |
if (this.create_button.hasClass("disabled")) { return } |
265 |
|
266 |
if (this._create_view) { |
267 |
this._create_view.show();
|
268 |
} |
269 |
}, |
270 |
|
271 |
pre_show: function() { |
272 |
views.ext.CollectionView.__super__.pre_show.apply(this, arguments); |
273 |
this.update_models();
|
274 |
}, |
275 |
|
276 |
handle_collection_reset: function() { |
277 |
this.update_models();
|
278 |
}, |
279 |
|
280 |
handle_model_change: function(model) { |
281 |
var el, index, model, parent, view, anim;
|
282 |
view = this._model_views[model.id];
|
283 |
if (!view) { return } |
284 |
el = view.el; |
285 |
parent = this.parent_for_model(model);
|
286 |
index = this.collection.indexOf(model);
|
287 |
if (!parent.find(el).length) {
|
288 |
anim = true;
|
289 |
this.place_in_parent(parent, el, model, index, anim);
|
290 |
} |
291 |
if (index != view.el.data('index')) { |
292 |
this.place_in_parent(parent, el, model, index, false); |
293 |
} |
294 |
}, |
295 |
|
296 |
handle_collection_change: function() { |
297 |
this.update_models();
|
298 |
}, |
299 |
|
300 |
handle_model_add: function(model, collection, options) { |
301 |
this.add_model(model);
|
302 |
$(window).trigger("resize"); |
303 |
}, |
304 |
|
305 |
handle_model_remove: function(model, collection, options) { |
306 |
this.remove_model(model);
|
307 |
}, |
308 |
|
309 |
show_empty: function() { |
310 |
this.empty_el.show();
|
311 |
}, |
312 |
|
313 |
hide_empty: function() { |
314 |
this.empty_el.hide();
|
315 |
}, |
316 |
|
317 |
check_empty: function() { |
318 |
if (this.collection.length == 0) { |
319 |
this.show_empty();
|
320 |
this.list_el.hide();
|
321 |
} else {
|
322 |
this.list_el.show();
|
323 |
this.hide_empty();
|
324 |
} |
325 |
}, |
326 |
|
327 |
parent_for_model: function(model) { |
328 |
return this.list_el; |
329 |
}, |
330 |
|
331 |
place_in_parent: function(parent, el, m, index, anim) { |
332 |
var place_func, place_func_context, position_found, exists;
|
333 |
|
334 |
_.each(parent.find(">.model-item"), function(el) { |
335 |
var el = $(el); |
336 |
var el_index = el.data('index'); |
337 |
if (!el_index || position_found) { return }; |
338 |
if (parseInt(el_index) < index) {
|
339 |
place_func = el.before; |
340 |
place_func_context = el; |
341 |
position_found = true;
|
342 |
} |
343 |
}); |
344 |
|
345 |
if (!position_found) {
|
346 |
place_func = parent.append; |
347 |
place_func_context = parent; |
348 |
} |
349 |
|
350 |
if (anim) {
|
351 |
var self = this; |
352 |
el.fadeOut(this.animation_speed, function() { |
353 |
place_func.call(place_func_context, el); |
354 |
el.fadeIn(self.animation_speed); |
355 |
}); |
356 |
} else {
|
357 |
place_func.call(place_func_context, el); |
358 |
} |
359 |
el.attr("data-index", index);
|
360 |
}, |
361 |
|
362 |
get_model_view_cls: function(m) { |
363 |
return this.model_view_cls |
364 |
}, |
365 |
|
366 |
add_model: function(m, index) { |
367 |
// if no available class for model exists, skip model add
|
368 |
var view_cls = this.get_model_view_cls(m); |
369 |
if (!view_cls) { return } |
370 |
|
371 |
// avoid duplicate entries
|
372 |
if (this._model_views[m.id]) { return } |
373 |
|
374 |
// handle empty collection
|
375 |
this.check_empty();
|
376 |
|
377 |
// initialize view
|
378 |
var view = this.create_view(this.get_model_view_cls(m), {model: m}); |
379 |
this.add_model_view(view, m, index);
|
380 |
}, |
381 |
|
382 |
add_model_view: function(view, model, index) { |
383 |
// append html element to the parent
|
384 |
var el = view.init_element();
|
385 |
// append to registry object
|
386 |
this._model_views[model.id] = view;
|
387 |
el.addClass("model-item");
|
388 |
// where to place ?
|
389 |
var parent = this.parent_for_model(model); |
390 |
// append
|
391 |
this.place_in_parent(parent, el, model, index);
|
392 |
// make it visible by default
|
393 |
this.add_subview(view);
|
394 |
view.show(true);
|
395 |
this.post_add_model_view(view, model);
|
396 |
}, |
397 |
post_add_model_view: function() {}, |
398 |
|
399 |
each_model_view: function(cb, context) { |
400 |
if (!context) { context = this }; |
401 |
_.each(this._model_views, function(view, model_id){ |
402 |
var model = this.collection.get(model_id); |
403 |
cb.call(this, model, view, model_id);
|
404 |
}, this);
|
405 |
}, |
406 |
|
407 |
remove_model: function(m) { |
408 |
var model_view = this._model_views[m.id]; |
409 |
if (!model_view) {
|
410 |
console.error("no view found");
|
411 |
return;
|
412 |
} |
413 |
model_view.hide(); |
414 |
model_view.el.remove(); |
415 |
this.remove_view(model_view);
|
416 |
this.post_remove_model_view(model_view, m);
|
417 |
$(window).trigger("resize"); |
418 |
delete this._model_views[m.id]; |
419 |
this.check_empty();
|
420 |
}, |
421 |
|
422 |
post_remove_model_view: function() {}, |
423 |
|
424 |
update_models: function(m) { |
425 |
this.check_empty();
|
426 |
this.collection.each(function(model, index) { |
427 |
if (!(model.id in this._model_views)) { |
428 |
this.add_model(model, index);
|
429 |
} else {
|
430 |
if (model != this._model_views[model.id].model) { |
431 |
this._model_views[model.id].model = model;
|
432 |
this._model_views[model.id].rivets_unbind();
|
433 |
this._model_views[model.id].rivets_bind();
|
434 |
} |
435 |
this.handle_model_change(model);
|
436 |
} |
437 |
}, this);
|
438 |
|
439 |
this.each_model_view(function(model, view, model_id){ |
440 |
if (!model) {
|
441 |
model = {'id': model_id};
|
442 |
this.remove_model(model);
|
443 |
} |
444 |
}) |
445 |
} |
446 |
}); |
447 |
|
448 |
views.ext.ModelView = views.ext.View.extend({ |
449 |
rivets_view: true, |
450 |
|
451 |
initialize: function() { |
452 |
views.ext.ModelView.__super__.initialize.apply(this, arguments); |
453 |
var actions = this.model.get('actions'); |
454 |
if (actions) {
|
455 |
this.init_action_methods(this.model.get('actions')); |
456 |
this.bind("hide", function() { |
457 |
actions.reset_pending(); |
458 |
}); |
459 |
} |
460 |
}, |
461 |
|
462 |
action_cls_map: {
|
463 |
'remove': 'destroy' |
464 |
}, |
465 |
|
466 |
_set_confirm: function(action) { |
467 |
this.pending_action = action;
|
468 |
this.set_action_indicator(action);
|
469 |
}, |
470 |
|
471 |
_unset_confirm: function(action) { |
472 |
this.pending_action = undefined; |
473 |
this.reset_action_indicator(action);
|
474 |
}, |
475 |
|
476 |
set_action_indicator: function(action) { |
477 |
action = this.action_cls_map[action] || action;
|
478 |
var indicator = this.el.find(".action-indicator"); |
479 |
indicator = $(indicator[indicator.length - 1]); |
480 |
indicator.attr("class", "").addClass("state action-indicator " + action); |
481 |
}, |
482 |
|
483 |
reset_action_indicator: function() { |
484 |
var indicator = this.el.find(".action-indicator"); |
485 |
indicator = $(indicator[indicator.length - 1]); |
486 |
indicator.attr("class", "").addClass("state action-indicator"); |
487 |
if (this.pending_action) { |
488 |
this.set_action_indicator(this.pending_action); |
489 |
} |
490 |
}, |
491 |
|
492 |
set_confirm: function() {}, |
493 |
unset_confirm: function() {}, |
494 |
|
495 |
init_action_methods: function(actions) { |
496 |
var self = this; |
497 |
if (this.model && this.model.actions) { |
498 |
this.model.actions.bind("reset-pending", function() { |
499 |
this._unset_confirm();
|
500 |
}, this);
|
501 |
this.model.actions.bind("set-pending", function(action) { |
502 |
this._set_confirm(action)
|
503 |
}, this);
|
504 |
} |
505 |
_.each(actions.actions, function(action) {
|
506 |
this.el.find(".action-container." + action).hover(function() { |
507 |
self.set_action_indicator(action); |
508 |
}, function() {
|
509 |
self.reset_action_indicator(); |
510 |
}); |
511 |
var method;
|
512 |
method = 'set_{0}_confirm'.format(action);
|
513 |
if (this[method]) { return } |
514 |
this[method] = _.bind(function(model, ev) { |
515 |
if (ev) { ev.stopPropagation() }
|
516 |
var data = {};
|
517 |
this._set_confirm(action);
|
518 |
this.set_confirm(action);
|
519 |
this.model.actions.set_pending_action(action);
|
520 |
}, this);
|
521 |
method = 'unset_{0}_confirm'.format(action);
|
522 |
if (this[method]) { return } |
523 |
this[method] = _.bind(function(model, ev) { |
524 |
if (ev) { ev.stopPropagation() }
|
525 |
var data = {};
|
526 |
this._unset_confirm(action);
|
527 |
this.unset_confirm(action);
|
528 |
this.model.actions.unset_pending_action(action);
|
529 |
}, this);
|
530 |
}, this);
|
531 |
}, |
532 |
|
533 |
get_rivet_object: function() { |
534 |
var model = {
|
535 |
model: this.model |
536 |
} |
537 |
return model
|
538 |
}, |
539 |
|
540 |
post_init_element: function() {}, |
541 |
|
542 |
init_element: function() { |
543 |
this.el.attr("id", "model-" + this.model.id); |
544 |
this.post_init_element();
|
545 |
this.update_layout();
|
546 |
return this.el; |
547 |
}, |
548 |
|
549 |
update_layout: function() {} |
550 |
|
551 |
}); |
552 |
|
553 |
views.ModelRenameView = views.ext.ModelView.extend({ |
554 |
tpl: '#rename-view-tpl', |
555 |
title_attr: 'name', |
556 |
|
557 |
init: function() { |
558 |
views.ModelRenameView.__super__.init.apply(this, arguments); |
559 |
this.name_cont = this.$(".model-name"); |
560 |
this.edit_cont = this.$(".edit"); |
561 |
|
562 |
this.edit_btn = this.$(".edit-btn"); |
563 |
this.value = this.$(".value"); |
564 |
this.input = this.$("input"); |
565 |
this.confirm = this.edit_cont.find(".confirm"); |
566 |
this.cancel = this.edit_cont.find(".cancel"); |
567 |
|
568 |
if (this.model.get('rename_disabled')) { |
569 |
this.edit_btn.remove();
|
570 |
} |
571 |
|
572 |
this.value.dblclick(_.bind(function(e) { |
573 |
this.set_edit();
|
574 |
}, this));
|
575 |
this.input.bind('keyup', _.bind(function(e) { |
576 |
// enter keypress
|
577 |
if (e.which == 13) { this.rename(); } |
578 |
// esc keypress
|
579 |
if (e.which == 27) { this.unset_edit(); } |
580 |
}, this));
|
581 |
// initial state
|
582 |
this.unset_edit();
|
583 |
}, |
584 |
|
585 |
post_hide: function() { |
586 |
this.unset_edit();
|
587 |
}, |
588 |
|
589 |
set_edit: function() { |
590 |
if (this.model.get('rename_disabled')) { return } |
591 |
var self = this; |
592 |
this.input.val(this.model.get('name')); |
593 |
window.setTimeout(function() {
|
594 |
self.input.focus(); |
595 |
}, 20);
|
596 |
this.name_cont.hide();
|
597 |
this.edit_cont.show();
|
598 |
}, |
599 |
|
600 |
unset_edit: function() { |
601 |
this.name_cont.show();
|
602 |
this.edit_cont.hide();
|
603 |
}, |
604 |
|
605 |
rename: function() { |
606 |
var value = _.trim(this.input.val()); |
607 |
if (value) {
|
608 |
this.model.rename(value);
|
609 |
this.unset_edit();
|
610 |
} |
611 |
} |
612 |
}); |
613 |
|
614 |
views.ext.SelectModelView = views.ext.ModelView.extend({ |
615 |
select: function() { |
616 |
if (!this.delegate_checked) { |
617 |
this.input.attr("checked", true); |
618 |
this.item.addClass("selected"); |
619 |
} |
620 |
this.selected = true; |
621 |
this.trigger("change:select", this, this.selected); |
622 |
}, |
623 |
|
624 |
deselect: function() { |
625 |
if (!this.delegate_checked) { |
626 |
this.input.attr("checked", false); |
627 |
this.item.removeClass("selected"); |
628 |
} |
629 |
this.selected = false; |
630 |
this.trigger("change:select", this, this.selected); |
631 |
}, |
632 |
|
633 |
toggle_select: function() { |
634 |
if (this.selected) { |
635 |
this.deselect();
|
636 |
} else {
|
637 |
this.select();
|
638 |
} |
639 |
}, |
640 |
|
641 |
post_init_element: function() { |
642 |
this.input = $(this.$("input").get(0)); |
643 |
this.item = $(this.$(".select-item").get(0)); |
644 |
this.delegate_checked = this.model.get('noselect'); |
645 |
this.deselect();
|
646 |
|
647 |
var self = this; |
648 |
if (self.model.get('forced')) { |
649 |
this.select();
|
650 |
this.input.attr("disabled", true); |
651 |
$(this.el).attr('title', this.forced_title); |
652 |
$(this.el).tooltip({ |
653 |
'tipClass': 'tooltip', |
654 |
'position': 'top center', |
655 |
'offset': [29, 0] |
656 |
}); |
657 |
} |
658 |
|
659 |
$(this.item).click(function(e) { |
660 |
if (self.model.get('forced')) { return } |
661 |
e.stopPropagation(); |
662 |
self.toggle_select(); |
663 |
}); |
664 |
|
665 |
views.ext.SelectModelView.__super__.post_init_element.apply(this,
|
666 |
arguments);
|
667 |
} |
668 |
}); |
669 |
|
670 |
|
671 |
views.ext.ModelCreateView = views.ext.ModelView.extend({}); |
672 |
views.ext.ModelEditView = views.ext.ModelCreateView.extend({}); |
673 |
|
674 |
})(this);
|