root / snf-cyclades-app / synnefo / ui / static / snf / js / views_ext.js @ fa5e1f54
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 |
this.handle_create_click();
|
229 |
}, this));
|
230 |
|
231 |
if (this.quota_key && !this.quota) { |
232 |
this.quota = synnefo.storage.quotas.get(this.quota_key); |
233 |
} |
234 |
|
235 |
if (this.quota) { |
236 |
this.quota.bind("change", _.bind(this.update_quota, this)); |
237 |
this.update_quota();
|
238 |
} |
239 |
}, |
240 |
|
241 |
update_quota: function() { |
242 |
var available = this.quota.get_available(); |
243 |
if (available > 0) { |
244 |
this.create_button.removeClass("disabled"); |
245 |
this.create_button.attr("title", this.quota_limit_message || "Quota limit reached") |
246 |
} else {
|
247 |
this.create_button.addClass("disabled"); |
248 |
this.create_button.attr("title", ""); |
249 |
} |
250 |
}, |
251 |
|
252 |
post_create: function() { |
253 |
this.quota && this.quota.increase(); |
254 |
}, |
255 |
|
256 |
post_destroy: function() { |
257 |
this.quota && this.quota.decrease(); |
258 |
}, |
259 |
|
260 |
handle_create_click: function() { |
261 |
if (this.create_button.hasClass("disabled")) { return } |
262 |
|
263 |
if (this._create_view) { |
264 |
this._create_view.show();
|
265 |
} |
266 |
}, |
267 |
|
268 |
pre_show: function() { |
269 |
views.ext.CollectionView.__super__.pre_show.apply(this, arguments); |
270 |
this.update_models();
|
271 |
}, |
272 |
|
273 |
handle_collection_reset: function() { |
274 |
this.update_models();
|
275 |
}, |
276 |
|
277 |
handle_model_change: function(model) { |
278 |
var el, index, model, parent, view, anim;
|
279 |
view = this._model_views[model.id];
|
280 |
if (!view) { return } |
281 |
el = view.el; |
282 |
parent = this.parent_for_model(model);
|
283 |
index = this.collection.indexOf(model);
|
284 |
if (!parent.find(el).length) {
|
285 |
anim = true;
|
286 |
this.place_in_parent(parent, el, model, index, anim);
|
287 |
} |
288 |
if (index != view.el.data('index')) { |
289 |
this.place_in_parent(parent, el, model, index, false); |
290 |
} |
291 |
}, |
292 |
|
293 |
handle_collection_change: function() { |
294 |
this.update_models();
|
295 |
}, |
296 |
|
297 |
handle_model_add: function(model, collection, options) { |
298 |
this.add_model(model);
|
299 |
$(window).trigger("resize"); |
300 |
}, |
301 |
|
302 |
handle_model_remove: function(model, collection, options) { |
303 |
this.remove_model(model);
|
304 |
}, |
305 |
|
306 |
show_empty: function() { |
307 |
this.empty_el.show();
|
308 |
}, |
309 |
|
310 |
hide_empty: function() { |
311 |
this.empty_el.hide();
|
312 |
}, |
313 |
|
314 |
check_empty: function() { |
315 |
if (this.collection.length == 0) { |
316 |
this.show_empty();
|
317 |
this.list_el.hide();
|
318 |
} else {
|
319 |
this.list_el.show();
|
320 |
this.hide_empty();
|
321 |
} |
322 |
}, |
323 |
|
324 |
parent_for_model: function(model) { |
325 |
return this.list_el; |
326 |
}, |
327 |
|
328 |
place_in_parent: function(parent, el, m, index, anim) { |
329 |
var place_func, place_func_context, position_found, exists;
|
330 |
|
331 |
_.each(parent.find(".model-item"), function(el) { |
332 |
var el = $(el); |
333 |
var el_index = el.data('index'); |
334 |
if (!el_index || position_found) { return }; |
335 |
if (parseInt(el_index) < index) {
|
336 |
place_func = el.before; |
337 |
place_func_context = el; |
338 |
position_found = true;
|
339 |
} |
340 |
}); |
341 |
|
342 |
if (!position_found) {
|
343 |
place_func = parent.append; |
344 |
place_func_context = parent; |
345 |
} |
346 |
|
347 |
if (anim) {
|
348 |
var self = this; |
349 |
el.fadeOut(this.animation_speed, function() { |
350 |
place_func.call(place_func_context, el); |
351 |
el.fadeIn(self.animation_speed); |
352 |
}); |
353 |
} else {
|
354 |
place_func.call(place_func_context, el); |
355 |
} |
356 |
el.attr("data-index", index);
|
357 |
}, |
358 |
|
359 |
get_model_view_cls: function(m) { |
360 |
return this.model_view_cls |
361 |
}, |
362 |
|
363 |
add_model: function(m, index) { |
364 |
// if no available class for model exists, skip model add
|
365 |
var view_cls = this.get_model_view_cls(m); |
366 |
if (!view_cls) { return } |
367 |
|
368 |
// avoid duplicate entries
|
369 |
if (this._model_views[m.id]) { return } |
370 |
|
371 |
// handle empty collection
|
372 |
this.check_empty();
|
373 |
|
374 |
// initialize view
|
375 |
var view = this.create_view(this.get_model_view_cls(m), {model: m}); |
376 |
this.add_model_view(view, m, index);
|
377 |
}, |
378 |
|
379 |
add_model_view: function(view, model, index) { |
380 |
// append html element to the parent
|
381 |
var el = view.init_element();
|
382 |
// append to registry object
|
383 |
this._model_views[model.id] = view;
|
384 |
el.addClass("model-item");
|
385 |
// where to place ?
|
386 |
var parent = this.parent_for_model(model); |
387 |
// append
|
388 |
this.place_in_parent(parent, el, model, index);
|
389 |
// make it visible by default
|
390 |
this.add_subview(view);
|
391 |
view.show(true);
|
392 |
this.post_add_model_view(view, model);
|
393 |
}, |
394 |
post_add_model_view: function() {}, |
395 |
|
396 |
each_model_view: function(cb, context) { |
397 |
if (!context) { context = this }; |
398 |
_.each(this._model_views, function(view, model_id){ |
399 |
var model = this.collection.get(model_id); |
400 |
cb.call(this, model, view, model_id);
|
401 |
}, this);
|
402 |
}, |
403 |
|
404 |
remove_model: function(m) { |
405 |
var model_view = this._model_views[m.id]; |
406 |
if (!model_view) {
|
407 |
console.error("no view found");
|
408 |
return;
|
409 |
} |
410 |
model_view.hide(); |
411 |
model_view.el.remove(); |
412 |
this.remove_view(model_view);
|
413 |
this.post_remove_model_view(model_view, m);
|
414 |
$(window).trigger("resize"); |
415 |
delete this._model_views[m.id]; |
416 |
this.check_empty();
|
417 |
}, |
418 |
|
419 |
post_remove_model_view: function() {}, |
420 |
|
421 |
update_models: function(m) { |
422 |
this.check_empty();
|
423 |
this.collection.each(function(model, index) { |
424 |
if (!(model.id in this._model_views)) { |
425 |
this.add_model(model, index);
|
426 |
} else {
|
427 |
if (model != this._model_views[model.id].model) { |
428 |
this._model_views[model.id].model = model;
|
429 |
this._model_views[model.id].rivets_unbind();
|
430 |
this._model_views[model.id].rivets_bind();
|
431 |
} |
432 |
this.handle_model_change(model);
|
433 |
} |
434 |
}, this);
|
435 |
|
436 |
this.each_model_view(function(model, view, model_id){ |
437 |
if (!model) {
|
438 |
model = {'id': model_id};
|
439 |
this.remove_model(model);
|
440 |
} |
441 |
}) |
442 |
} |
443 |
}); |
444 |
|
445 |
views.ext.ModelView = views.ext.View.extend({ |
446 |
rivets_view: true, |
447 |
|
448 |
initialize: function() { |
449 |
views.ext.ModelView.__super__.initialize.apply(this, arguments); |
450 |
var actions = this.model.get('actions'); |
451 |
if (actions) {
|
452 |
this.init_action_methods(this.model.get('actions')); |
453 |
this.bind("hide", function() { |
454 |
actions.reset_pending(); |
455 |
}); |
456 |
} |
457 |
}, |
458 |
|
459 |
action_cls_map: {
|
460 |
'remove': 'destroy' |
461 |
}, |
462 |
|
463 |
_set_confirm: function(action) { |
464 |
this.pending_action = action;
|
465 |
this.set_action_indicator(action);
|
466 |
}, |
467 |
|
468 |
_unset_confirm: function(action) { |
469 |
this.pending_action = undefined; |
470 |
this.reset_action_indicator(action);
|
471 |
}, |
472 |
|
473 |
set_action_indicator: function(action) { |
474 |
action = this.action_cls_map[action] || action;
|
475 |
var indicator = this.el.find(".action-indicator"); |
476 |
indicator = $(indicator[indicator.length - 1]); |
477 |
indicator.attr("class", "").addClass("state action-indicator " + action); |
478 |
}, |
479 |
|
480 |
reset_action_indicator: function() { |
481 |
var indicator = this.el.find(".action-indicator"); |
482 |
indicator = $(indicator[indicator.length - 1]); |
483 |
indicator.attr("class", "").addClass("state action-indicator"); |
484 |
if (this.pending_action) { |
485 |
this.set_action_indicator(this.pending_action); |
486 |
} |
487 |
}, |
488 |
|
489 |
set_confirm: function() {}, |
490 |
unset_confirm: function() {}, |
491 |
|
492 |
init_action_methods: function(actions) { |
493 |
var self = this; |
494 |
if (this.model && this.model.actions) { |
495 |
this.model.actions.bind("reset-pending", function() { |
496 |
this._unset_confirm();
|
497 |
}, this);
|
498 |
this.model.actions.bind("set-pending", function(action) { |
499 |
console.log("ACTION", action);
|
500 |
this._set_confirm(action)
|
501 |
}, this);
|
502 |
} |
503 |
_.each(actions.actions, function(action) {
|
504 |
this.el.find(".action-container." + action).hover(function() { |
505 |
self.set_action_indicator(action); |
506 |
}, function() {
|
507 |
self.reset_action_indicator(); |
508 |
}); |
509 |
var method;
|
510 |
method = 'set_{0}_confirm'.format(action);
|
511 |
if (this[method]) { return } |
512 |
this[method] = _.bind(function(model, ev) { |
513 |
if (ev) { ev.stopPropagation() }
|
514 |
var data = {};
|
515 |
this._set_confirm(action);
|
516 |
this.set_confirm(action);
|
517 |
this.model.actions.set_pending_action(action);
|
518 |
}, this);
|
519 |
method = 'unset_{0}_confirm'.format(action);
|
520 |
if (this[method]) { return } |
521 |
this[method] = _.bind(function(model, ev) { |
522 |
if (ev) { ev.stopPropagation() }
|
523 |
var data = {};
|
524 |
this._unset_confirm(action);
|
525 |
this.unset_confirm(action);
|
526 |
this.model.actions.unset_pending_action(action);
|
527 |
}, this);
|
528 |
}, this);
|
529 |
}, |
530 |
|
531 |
get_rivet_object: function() { |
532 |
var model = {
|
533 |
model: this.model |
534 |
} |
535 |
return model
|
536 |
}, |
537 |
|
538 |
post_init_element: function() {}, |
539 |
|
540 |
init_element: function() { |
541 |
this.el.attr("id", "model-" + this.model.id); |
542 |
this.post_init_element();
|
543 |
this.update_layout();
|
544 |
return this.el; |
545 |
}, |
546 |
|
547 |
update_layout: function() {} |
548 |
|
549 |
}); |
550 |
|
551 |
views.ModelRenameView = views.ext.ModelView.extend({ |
552 |
tpl: '#rename-view-tpl', |
553 |
title_attr: 'name', |
554 |
|
555 |
init: function() { |
556 |
views.ModelRenameView.__super__.init.apply(this, arguments); |
557 |
this.name_cont = this.$(".model-name"); |
558 |
this.edit_cont = this.$(".edit"); |
559 |
|
560 |
this.edit_btn = this.$(".edit-btn"); |
561 |
this.value = this.$(".value"); |
562 |
this.input = this.$("input"); |
563 |
this.confirm = this.edit_cont.find(".confirm"); |
564 |
this.cancel = this.edit_cont.find(".cancel"); |
565 |
|
566 |
if (this.model.get('rename_disabled')) { |
567 |
this.edit_btn.remove();
|
568 |
} |
569 |
|
570 |
this.value.dblclick(_.bind(function(e) { |
571 |
this.set_edit();
|
572 |
}, this));
|
573 |
this.input.bind('keyup', _.bind(function(e) { |
574 |
// enter keypress
|
575 |
if (e.which == 13) { this.rename(); } |
576 |
// esc keypress
|
577 |
if (e.which == 27) { this.unset_edit(); } |
578 |
}, this));
|
579 |
// initial state
|
580 |
this.unset_edit();
|
581 |
}, |
582 |
|
583 |
post_hide: function() { |
584 |
this.unset_edit();
|
585 |
}, |
586 |
|
587 |
set_edit: function() { |
588 |
if (this.model.get('rename_disabled')) { return } |
589 |
var self = this; |
590 |
this.input.val(this.model.get('name')); |
591 |
window.setTimeout(function() {
|
592 |
self.input.focus(); |
593 |
}, 20);
|
594 |
this.name_cont.hide();
|
595 |
this.edit_cont.show();
|
596 |
}, |
597 |
|
598 |
unset_edit: function() { |
599 |
this.name_cont.show();
|
600 |
this.edit_cont.hide();
|
601 |
}, |
602 |
|
603 |
rename: function() { |
604 |
var value = _.trim(this.input.val()); |
605 |
if (value) {
|
606 |
this.model.rename(value);
|
607 |
this.unset_edit();
|
608 |
} |
609 |
} |
610 |
}); |
611 |
|
612 |
views.ext.SelectModelView = views.ext.ModelView.extend({ |
613 |
select: function() { |
614 |
if (!this.delegate_checked) { |
615 |
this.input.attr("checked", true); |
616 |
this.item.addClass("selected"); |
617 |
} |
618 |
this.selected = true; |
619 |
this.trigger("change:select", this, this.selected); |
620 |
}, |
621 |
|
622 |
deselect: function() { |
623 |
if (!this.delegate_checked) { |
624 |
this.input.attr("checked", false); |
625 |
this.item.removeClass("selected"); |
626 |
} |
627 |
this.selected = false; |
628 |
this.trigger("change:select", this, this.selected); |
629 |
}, |
630 |
|
631 |
toggle_select: function() { |
632 |
if (this.selected) { |
633 |
this.deselect();
|
634 |
} else {
|
635 |
this.select();
|
636 |
} |
637 |
}, |
638 |
|
639 |
post_init_element: function() { |
640 |
this.input = $(this.$("input").get(0)); |
641 |
this.item = $(this.$(".select-item").get(0)); |
642 |
this.delegate_checked = this.model.get('noselect'); |
643 |
this.deselect();
|
644 |
|
645 |
var self = this; |
646 |
if (self.model.get('forced')) { |
647 |
this.select();
|
648 |
this.input.attr("disabled", true); |
649 |
$(this.el).attr('title', this.forced_title); |
650 |
$(this.el).tooltip({ |
651 |
'tipClass': 'tooltip', |
652 |
'position': 'top center', |
653 |
'offset': [29, 0] |
654 |
}); |
655 |
} |
656 |
|
657 |
$(this.item).click(function(e) { |
658 |
if (self.model.get('forced')) { return } |
659 |
e.stopPropagation(); |
660 |
self.toggle_select(); |
661 |
}); |
662 |
|
663 |
views.ext.SelectModelView.__super__.post_init_element.apply(this,
|
664 |
arguments);
|
665 |
} |
666 |
}); |
667 |
|
668 |
|
669 |
views.ext.ModelCreateView = views.ext.ModelView.extend({}); |
670 |
views.ext.ModelEditView = views.ext.ModelCreateView.extend({}); |
671 |
|
672 |
})(this);
|