tweak checkbox styling
[qcg-portal.git] / filex / static / filex / filex.js
index 3aed569..9f34b5e 100644 (file)
@@ -15,6 +15,10 @@ $(function(){
     });
 
     Filex.File = OfflineModel.extend({
+        defaults: {
+            checked: false
+        },
+
         isDir: function() {
             return false;
         },
@@ -25,6 +29,10 @@ $(function(){
 
         isHidden: function() {
             return this.get('name')[0] == '.';
+        },
+
+        toggle: function() {
+            this.set('checked', !this.get('checked'));
         }
     });
 
@@ -59,7 +67,7 @@ $(function(){
                 case 'file':
                     return new Filex.File(attrs, options);
                 default:
-                    console.log('Unknown model type:', attrs['type']);
+                    console.error('Unknown model type:', attrs['type']);
             }
         },
 
@@ -86,26 +94,28 @@ $(function(){
     Filex.Path = Backbone.Collection.extend({
         model: Filex.PathBit,
 
-        initialize: function() {
-            this.listenTo(this, 'reset', this.setActive);
-            this.listenTo(this, 'add', this.setActive);
-        },
-
-        setActive: function() {
-            _.each(this.initial(), function(bit) {
-                bit.set('active', false);
-            });
-
-            this.last().set('active', true);
+        full: function() {
+            return this.pluck('name').join('/').replace(/^\/+/, '/');
         },
 
-        full: function() {
-            return this.pluck('path').join('/') || '/';
+        append: function(name) {
+            return this.pluck('name').concat(name).join('/').replace(/^\/+/, '/');
         }
     });
 
 
     // ------------------------------------------------------------------------
+    // Routers
+    // ------------------------------------------------------------------------
+
+    Filex.Router = Backbone.Router.extend({
+               routes: {
+                       '*url': 'load'
+               }
+       });
+
+
+    // ------------------------------------------------------------------------
     // Views
     // ------------------------------------------------------------------------
 
@@ -113,12 +123,13 @@ $(function(){
         tagName:  'tr',
 
         events: {
-            'click .link': 'selected'
+            'click': 'click'
         },
 
         initialize: function(options) {
             this.view = options.view;
 
+            this.listenTo(this.model, 'change:checked', this.toggleChecked);
             this.listenTo(this.model, 'remove', this.remove);
             this.listenTo(this.model, 'hidden', this.toggleHidden);
         },
@@ -131,11 +142,17 @@ $(function(){
 
         render: function() {
             var data = this.model.toJSON();
-            data['url_params'] = $.param({
-                host: this.view.host,
-                path: this.view.path.full(),
-                name: this.model.get('name')
-            });
+
+            if (this.model.isDir()) {
+                data['url'] = this.view.host + '/' + this.view.path.append(this.model.get('name'))
+            }
+            else {
+                data['params'] = $.param({
+                    host: this.view.host,
+                    path: this.view.path.append(this.model.get('name'))
+                });
+            }
+            data['cid'] = this.model.cid;
 
             this.$el.html(this.template()(data));
             this.toggleHidden();
@@ -144,24 +161,27 @@ $(function(){
         },
 
         toggleHidden: function() {
-            this.$el.toggleClass('hidden', this.model.isHidden() && !this.view.showHidden());
+            var isHidden = this.model.isHidden() && !this.view.showHidden();
+            this.$el.toggleClass('hidden', isHidden);
+
+            if (isHidden && this.model.get('checked'))
+                this.model.toggle();
         },
 
-        selected: function(e) {
-            if (this.model.isDir()) {
-                e.preventDefault();
-                this.model.trigger('selected:dir', this.model);
-            }
+        toggleChecked: function(obj, value) {
+            this.$el.toggleClass('active', value);
+            this.$el.find('input[type="checkbox"]').prop('checked', value);
+        },
+
+        click: function(e) {
+            if (e.target.className != 'link')
+                this.model.toggle();
         }
     });
 
     Filex.Breadcrumb = Backbone.View.extend({
         tagName: 'li',
 
-        events: {
-            'click a': 'selected'
-        },
-
         initialize: function() {
             this.listenTo(this.model, 'change:active', this.render);
             this.listenTo(this.model, 'remove', this.remove);
@@ -169,22 +189,17 @@ $(function(){
 
         render: function() {
             if (this.model.get('active')) {
-                this.$el.text(this.model.get('text'));
+                this.$el.addClass('active');
+                this.$el.text(this.model.get('name'));
             }
             else {
                 this.$el.html($('<a/>', {
-                    href: '#',
-                    text: this.model.get('text')
+                    href: '#' + this.model.get('url'),
+                    text: this.model.get('name')
                 }));
             }
-            this.$el.toggleClass('active', this.model.get('active'));
 
             return this;
-        },
-
-        selected: function(e) {
-            e.preventDefault();
-            this.model.trigger('selected', this.model);
         }
     });
 
@@ -193,85 +208,157 @@ $(function(){
 
         events: {
             'change #show-hidden': 'toggleHidden',
-            'click #host-controls .view': 'hostEdit'
+            'click #select-all': 'selectAll',
+            'click #btn-refresh': 'reloadFiles',
+            'click #btn-favorites': 'toggleFavorites',
+            'click #btn-host': 'editHost'
         },
 
         initialize: function(options) {
             this.$noItems = $('#no-items');
             this.$error = $('#error');
             this.$showHidden = $('#show-hidden');
-            this.$host = $('#host-controls');
+            this.$selectAll = $('#select-all');
+            this.$favorites = $('#btn-favorites');
+            this.$host = $('#btn-host');
 
-            this.host = options.host;
             this.path = new Filex.Path();
             this.files = new Filex.FileList();
+            this.router = new Filex.Router();
 
-            this.listenTo(this.path, 'reset', this.resetPath);
-            this.listenTo(this.path, 'add', this.reloadFiles);
             this.listenTo(this.path, 'add', this.addPath);
-            this.listenTo(this.path, 'selected', this.selectedPath);
+            this.listenTo(this.path, 'reset', this.resetPath);
             this.listenTo(this.files, 'reset', this.resetFiles);
-            this.listenTo(this.files, 'selected:dir', this.selectedDir);
+            this.listenTo(this.files, 'change:checked', this.updateSelectAll);
+            this.listenTo(this.router, 'route:load', this.load);
 
             // used in selectize callbacks
             var view = this,
-                optionTemplate = _.template('<div><div><%= host %></div><div class="small text-muted"><%= path %></div></div>');
+                optionTemplate = _.template('<div><div><%- host %></div><div class="small text-muted"><%- path %></div></div>');
 
             this.$('#host-selector').selectize({
-                valueField: 'host',
+                optgroupField: 'group',
                 labelField: 'host',
                 searchField: ['host', 'path'],
                 sortField: [
                     {field: 'host', direction: 'asc'},
                     {field: 'path', direction: 'asc'}
                 ],
-                items: [this.host],
-                options: options.hostOptions,
+                options: options.locations,
+                optgroups: [
+                    {value: 'sys', label: 'Podstawowe'},
+                    {value: 'usr', label: 'Użytkownika'}
+                ],
+                lockOptgroupOrder: true,
+                create: function(input, callback) {
+                    var $form = $('#favorite-form'),
+                        parts = input.split('/'),
+                        callback_called = false;
+
+                    $form[0].reset();
+                    $form.find('.alert-danger').remove();
+                    $form.find('#id_host').val(parts.shift());
+
+                    if (parts.length)
+                        $form.find('#id_path').val(parts.join('/'));
+
+                    $form.off();
+                    $form.on('submit', function(e) {
+                        var $this = $(this),
+                            $btn = $this.find('[type="submit"]');
+
+                        e.preventDefault();
+                        $btn.button('loading');
+                        $this.find('.alert-danger').remove();
+
+                        $.post($this.attr('action'), $this.serialize(), function(data) {
+                            callback(data);
+                            callback_called = true;
+
+                            $this.modal('hide');
+                            $btn.button('reset');
+                        }, 'json').fail(function(xhr) {
+                            console.error(arguments);
+                            var error = (xhr.responseJSON || {}).error || undefined;
+
+                            if (error.__all__) {
+                                error = 'Podana lokalizacja jest już zapisana'
+                            }
+                            else if (xhr.status == 400) {
+                                error = 'Niepoprawna nazwa hosta lub ścieżka'
+                            }
+
+                            if (typeof error === 'string') {
+                                $('<div>', {
+                                    'class': 'alert alert-danger',
+                                    html: '<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> ' + error
+                                }).prependTo($this.find('.modal-body'));
+                                $btn.button('reset');
+                            }
+                            else {
+                                $btn.button('error');
+                            }
+                        });
+                    });
+
+                    $form.one('hide.bs.modal', function() {
+                        if (!callback_called)
+                            callback();
+                        $form.off();
+                    });
+
+                    $form.modal();
+                },
                 render: {
                     option: function(item) {
                         return optionTemplate(item);
+                    },
+                    option_create : function(data, escape) {
+                        return '<div class="create">Dodaj <em>' + escape(data.input) + '</em>&hellip;</div>';
                     }
                 },
-                onDropdownClose: function() {
+                onItemAdd: function(value) {
+                    view.router.navigate('#' + value, {trigger: true});
                     this.blur();
                 },
                 onBlur: function() {
-                    view.$host.toggleClass('editing');
-
-                    var value = this.getValue();
-                    if (!value) {
-                        this.addItem(view.host, true);
-                        return;
-                    }
-
-                    if (value != view.host) {
-                        var location = this.options[value];
-
-                        view.host = location.host;
-                        view.navigate(location.path);
-                    }
+                    $('#host').removeClass('edit');
+                    this.clear();
                 }
             });
             this.hostSelectize = this.$('#host-selector')[0].selectize;
 
             this.render();
-            this.navigate(this.hostSelectize.options[this.host].path);
+               Backbone.history.start();
         },
 
         render: function() {
-            this.$host.find('.view').text(this.host);
-            this.$noItems.toggle((this.showHidden() ? !Boolean(this.files.length) : !Boolean(this.files.visible().length)));
+            this.updateSelectAll();
+            this.updateFavorites();
+            this.$noItems.toggle(!Boolean(this.visibleFiles().length));
             this.$error.hide();
         },
 
-        navigate: function(path) {
-            var pathBits = [new Filex.PathBit({'text': '/', 'path': ''})];
+        load: function(location) {
+            if (!location)
+                return;
+
+            var hostRootPath = location.split(/\/(\/|~)(.*)/), self = this;
 
-            pathBits = pathBits.concat(_.map(path.replace(/(^\/+|\/+$)/g, '').split('/'), function(name) {
-                return new Filex.PathBit({'text': name, 'path': name});
-            }));
+            this.host = hostRootPath[0];
+            this.$host.text(this.host);
 
-            this.path.reset(pathBits);
+            this.path.reset([new Filex.PathBit({'name': hostRootPath[1], url: this.host + '/' + hostRootPath[1]})]);
+            _.each(hostRootPath[2].split('/'), function(item) {
+                if (item)
+                    self.path.add(new Filex.PathBit({name: item, url: self.host + '/' + self.path.append(item)}));
+            });
+            this.path.last().set('active', true);
+
+            this.reloadFiles();
+            this.updateFavorites();
+
+            localStorage.last_location = location;
         },
 
         reloadFiles: function() {
@@ -289,7 +376,7 @@ $(function(){
                 error: function(collection, response) {
                     view.files.reset();
 
-                    var msg = (response.responseJSON || {}).msg || 'Błąd serwera';
+                    var msg = (response.responseJSON || {}).error || 'Błąd serwera';
 
                     view.$noItems.hide();
                     view.$error.find('.msg').text(msg);
@@ -305,8 +392,6 @@ $(function(){
         },
 
         resetPath: function(models, options) {
-            this.reloadFiles();
-
             _.each(options.previousModels, function(model) {
                 model.trigger('remove');
             });
@@ -330,31 +415,113 @@ $(function(){
             this.render();
         },
 
-        selectedDir: function(dir) {
-            this.path.add({'text': dir.get('name'), 'path': dir.get('name')});
-        },
-
-        selectedPath: function(bit) {
-            var newPath = this.path.slice(0, this.path.indexOf(bit) + 1);
-            this.path.set(newPath);
-            this.reloadFiles();
-        },
-
         showHidden: function() {
             return this.$showHidden[0].checked;
         },
 
-        hostEdit: function() {
-            this.$host.toggleClass('editing');
-            this.hostSelectize.focus();
-        },
-
         busy: function() {
             this.$el.addClass('busy');
         },
 
         idle: function() {
             this.$el.removeClass('busy');
+        },
+
+        visibleFiles: function() {
+            return this.showHidden() ? this.files.models : this.files.visible();
+        },
+
+        selectedFiles: function() {
+            return _.filter(this.visibleFiles(), function(item) {
+                return item.get('checked');
+            });
+        },
+
+        selectAll: function() {
+            var checked = this.$selectAll[0].checked;
+
+            _.each(this.visibleFiles(), function(item) {
+                item.set('checked', checked);
+            })
+        },
+
+        updateSelectAll: function() {
+            if (this.visibleFiles().length) {
+                this.$selectAll.prop('disabled', false);
+                this.$selectAll.prop('checked', this.selectedFiles().length == this.visibleFiles().length);
+            }
+            else {
+                this.$selectAll.prop('disabled', true);
+                this.$selectAll.prop('checked', false);
+            }
+        },
+
+        clearSelection: function() {
+            _.each(this.visibleFiles(), function(item) {
+                item.set('checked', false);
+            });
+        },
+
+        toggleFavorites: function() {
+            var $btn = this.$favorites,
+                locations = this.hostSelectize,
+                is_active = $btn.hasClass('active'),
+                url = is_active ? '/filex/fav/delete/' : '/filex/fav/add/',
+                data = {
+                    host: this.host,
+                    path: this.path.full()
+                };
+
+            $btn.button('loading');
+
+            $.post(url, data, 'json').done(function (response) {
+                $btn.button('reset');
+
+                if (is_active)
+                    locations.removeOption(data.host + '/' + data.path);
+                else
+                    locations.addOption(response);
+            }).fail(function() {
+                $btn.button('reset');
+                $btn.button('toggle');
+
+                console.error(arguments);
+            });
+        },
+
+        initialLoad: function() {
+            var opts = this.hostSelectize.options,
+                location = localStorage.last_location || opts[Object.keys(opts)[0]].value;
+
+            this.router.navigate('#' + location, {trigger: true, replace: true});
+        },
+
+        updateFavorites: function() {
+            var loc = this.host + '/' + this.path.full(),
+                favorites = this.hostSelectize.options;
+
+            if (favorites.hasOwnProperty(loc)) {
+                if (favorites[loc].group == 'sys') {
+                    this.$favorites.addClass('disabled').prop('disabled', true);
+                    if (this.$favorites.hasClass('active'))
+                        this.$favorites.button('toggle');
+                }
+                else {
+                    this.$favorites.removeClass('disabled').prop('disabled', false);
+                    if (!this.$favorites.hasClass('active'))
+                        this.$favorites.button('toggle');
+                }
+            }
+            else {
+                this.$favorites.removeClass('disabled').prop('disabled', false);
+                if (this.$favorites.hasClass('active'))
+                    this.$favorites.button('toggle');
+            }
+        },
+
+        editHost: function() {
+            $('#host').addClass('edit');
+            this.hostSelectize.focus();
         }
     });
 });