w
clone your own copy | download snapshot

Snapshots | iceberg

No images in this repository’s iceberg at this time

Inside this repository

views.js
application/javascript

Download raw (91.1 KB)

;
window.W = window.W || {};


(function (undefined) {
    'use strict';

    var rangeType = {
        undetermined: '0',
        exact: '1',
        minimal: '2',
        range: '3'
    };

    var tags = W.tags = [">", "|", "//", "≥", "||"];

    function formatTimestamp(timestring) {
        var d = new Date(timestring);
        return d.getDate()
            + '-' + (d.getMonth() + 1)
            + '-' + d.getFullYear()
            + ' ' + d.getHours()
            + ':' + d.getMinutes();
    }

    /**
     * Transform time in seconds to timecode: (hh:?)mm:ss
     * @param {number} t time
     */
    function toTimecode (t) {
        t = Math.round(t)
        var hours = Math.floor(t / 3600),
            minutes = Math.floor((t % 3600) / 60),
            seconds = t % 60,
            timecode = '';

        if (hours  > 0) {
            timecode += (hours > 9) ? hours.toString(10) : '0' + hours.toString(10)
            timecode += ':';
        }
        
        timecode += (minutes > 9) ? minutes.toString(10) : '0' + minutes.toString(10)
        timecode += ':' + ((seconds > 9) ? seconds.toString(10) : '0' + seconds.toString(10))

        return timecode;
    }

    // https://stackoverflow.com/a/18197511
    function download(filename, text) {
        var a = document.createElement('a');
        a.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
        a.setAttribute('download', filename);

        if (document.createEvent) {
            var event = document.createEvent('MouseEvents');
            event.initEvent('click', true, true);
            a.dispatchEvent(event);
        }
        else {
            a.click();
        }
    }

    function stripIds(data) {
        data.sublines = _.map(data.sublines, stripIds);
        return _.omit(data, ['id']);
    }

    function valid_mount_for_link(link, mount) {
        if (link.get('target') == mount) {
            return false;
        } else if (mount instanceof W.LinkModel) {
            return valid_mount_for_link(link, mount.get('target'));
        } else if (mount.get('parent')) {
            return valid_mount_for_link(link, mount.get('parent'));
        }

        return true;
    }

    function find_links(node) {
        var links = [];

        if (node instanceof W.LinkModel) {
            links.push(node);
            links = links.concat(find_links(node.get('target')));
        } else {
            var sublines = node.get('sublines');

            if (sublines) {
                for (var i = 0; i < sublines.length; i++) {
                    var candidate = sublines.at(i);

                    if (candidate instanceof W.LinkModel) {
                        links.push(candidate);
                        links = links.concat(find_links(candidate.get('target')));
                    } else {
                        links = links.concat(find_links(candidate));
                    }
                }
            }
        }

        return links;
    }

    function valid_move(node, mount) {
        // If link, the target is not allowed to be one of the parents of the tree.
        if (node instanceof W.LinkModel) {
            if (!valid_mount_for_link(node, mount)) {
                return false
            }
        }

        // If there are links in the node referring to any parent of mount
        if (_.some(find_links(node), function (l) { return (!valid_mount_for_link(l, mount)) })) {
            return false;
        }

        return true;
    }
    
    function initState() {
        return {
            'collapsed': {},
            'expandLevel': 1
        }
    };

    var state = initState();


    /** Unknown file types, let browser deal with it or download */
    W.AttachmentDownload = Marionette.View.extend({
        tagName: 'section',
        template: '#attachment-download-template',
        className: 'attachment--view attachment--view--download',

        templateContext: function () {
            return {
                'dataUrl': this.dataUrl
            }
        },

        initialize: function () {
            if (typeof this.model.get('attachment') === 'string') {
                this.dataUrl = this.model.get('attachment');
            } else {
                this.dataUrl = '';
                this.loadDataURL(this.model.get('attachment'));
            }
        },

        loadDataURL: function (file) {
            var reader = new FileReader();
            reader.addEventListener('load', _.bind(function () {
                this.dataUrl = reader.result;
                this.render();
            }, this));
            
            reader.readAsDataURL(file);    
        },
    })

    W.AttachmentToggleable = Marionette.View.extend({
        ui: {
            'close': '.btn--close'
        },

        events: {
            'click @ui.close': 'close',
            'click': 'toggle',
        },

        templateContext: function () {
            return {
                'dataUrl': this.dataUrl
            }
        },

        initialize: function () {
            this.opened = false;
            if (typeof this.model.get('attachment') === 'string') {
                this.dataUrl = this.model.get('attachment');
            } else {
                this.dataUrl = '';
                this.loadDataURL(this.model.get('attachment'));
            }
        },

        loadDataURL: function (file) {
            if (file) {
                var reader = new FileReader();
                reader.addEventListener('load', _.bind(function () {
                    this.dataUrl = reader.result;
                    this.render();
                }, this));
    
                reader.readAsDataURL(file);    
            }
        },
        
        toggle: function(e) {
            if (e) {
                e.preventDefault();
            }
            if (this.opened) {
                this.close();
            } else {
                this.open();
            }
        },
        open: function (e) {
            if (e) {
                e.preventDefault();
            }
            this.opened = true;
            this.el.dataset.opened = true;
        },
        close: function (e) {
            if (e) {
                e.stopImmediatePropagation();
            }
            this.opened = false;
            this.el.dataset.opened = false;
        }
    });

    /** Image files */
    W.AttachmentImage = W.AttachmentToggleable.extend({
        tagName: 'section',
        template: '#attachment-image-template',
        className: 'attachment--view attachment--view--image',
    })

     /** PDF */
     W.AttachmentPDF =  W.AttachmentToggleable.extend({
        tagName: 'section',
        template: '#attachment-pdf-template',
        className: 'attachment--view attachment--view--pdf'
    })

    W.AttachmentVimeoView = W.AttachmentToggleable.extend({
        tagName: 'section',
        template: '#attachment-vimeo-template',
        className: 'attachment--view attachment--view--vimeo',
        templateContext: function () {
            return {
                'dataUrl': this.dataUrl,
                'vimeoId': /https:\/\/(?:player\.)?vimeo.com\/(?:\/video\/)?(\d+)\/?/.exec(this.model.get('url'))[1]
            }
        },
    })

    /** Audio file, show discrete player in view */
    W.AttachmentAudio = Marionette.View.extend({
        template: '#attachment-audio-template',
        className: 'attachment--view attachment--view--audio',
        ui: {
            'player': 'audio',
            'currentTime': '.audio--timecode--current',
            'duration': '.audio--timecode--duration'
        },
        events: {
            'click .button--pause': 'pause',
            'click .button--play': 'play'
        },
        attributes: {
            'data-playing': 'false'
        },

        templateContext: function () {
            return {
                'dataUrl': this.dataUrl
            }
        },

        initialize: function () {
            this.opened = false;
            if (typeof this.model.get('attachment') === 'string') {
                this.dataUrl = this.model.get('attachment');
            } else {
                this.dataUrl = '';
                this.loadDataURL(this.model.get('attachment'));
            }
        },

        loadDataURL: function (file) {
            var reader = new FileReader();
            reader.addEventListener('load', _.bind(function () {
                this.dataUrl = reader.result;
                this.render();
            }, this));

            reader.readAsDataURL(file);    
        },
        

        pause: function (e) {
            if (e) {
                e.preventDefault();
            }

            this.el.dataset.playing = this.playing = false;
            this.ui.player.get(0).pause();
        },

        play: function (e) {
            if (e) {
                e.preventDefault();
            }

            this.el.dataset.playing = this.playing = true;
            this.updateCurrenttime();
            this.ui.player.get(0).play();
        },

        ended: function () {
            this.el.dataset.playing = false;
        },

        updateCurrenttime: function () {
            var last,
                player = this.ui.player,
                currenttimeLabel = this.ui.currentTime,
                updater = function () {
                    if (this.playing) {
                        var current = toTimecode(player.get(0).currentTime);
                        if (current != last) {
                            currenttimeLabel.text(current);
                            last = current;
                        }
                        window.requestAnimationFrame(updater);
                    }
                }.bind(this);
            
            updater();
        },

        updateDuration: function () {
            var player = this.ui.player,
                durationLabel = this.ui.duration,
                duration = player.get(0).duration;

            if (duration) {
                durationLabel.text(toTimecode(duration));
            } else {
                player.one('durationchange', this.updateDuration.bind(this));
            }
        },

        onRender: function () {
            this.ui.player.on('ended', this.ended.bind(this));
            this.updateCurrenttime();
            this.updateDuration();
        }
    })

    // W.Attachment = Marionette.View.extend({
    //     template: '#attachment-template',

    //     className:  'attachment--view',
        
    //     triggers: {
    //         'click .btn-close': 'attachment:close'
    //     }


    // });

    function getAttachmentViewFromFile(attachment) {
        var mime = attachment.get('attachment').type;

        if (/image\//.test(mime)) {
            return new W.AttachmentImage({ model: attachment })
        } else if (/application\/pdf/ .test(mime)) {
            return new W.AttachmentPDF({ model: attachment })
        } else if (/audio\//.test(mime)) {
            return new W.AttachmentAudio({ model: attachment })
        }

        return new W.AttachmentDownload({ model: attachment })
    }

    function getAttachmentViewByExtension(attachment) {
        var patt = new RegExp(/\.(\w+)$/),
            m = patt.exec(attachment.get('attachment')),
            ext = (m && m[1]) ? m[1].toLowerCase() : '';

        switch(ext) {
            case 'jpg':
            case 'jpeg':
            case 'png':
            case 'gif':
                return new W.AttachmentImage({ model: attachment })
            case 'pdf':
                return new W.AttachmentPDF({ model: attachment })

            case 'mp3':
            case 'flac':
            case 'ogg':
            case 'oga':
            case 'wav':
            case 'opus':
            case 'aac':
            case 'm4a':
                return new W.AttachmentAudio({ model: attachment })
            
            
            default:
                return new W.AttachmentDownload({ model: attachment })
        }
    }

    function getAttachmentView (attachment) {
        if (typeof attachment.get('attachment') == 'string' || typeof attachment.get('url') == 'string') {
            if (/https:\/\/(?:player\.)?vimeo.com\/(?:\/video\/)?\d+\/?/.test(attachment.get('url'))) {
                return new W.AttachmentVimeoView({ model: attachment });
            } else {
                return getAttachmentViewByExtension(attachment);
            }
        } else {
            return getAttachmentViewFromFile(attachment);
        }
    }


    /**
     * Instantiate with at least a label.
     */
    W.RangeField = Marionette.View.extend({
        label: '',

        className: 'fieldgroup',

        tagName: 'section',

        ui: {
            'checkbox': '[name="toggle-range-field"]',
            'typeSelect': '[name="type"]',
            'minimal': '[name="minimal"]',
            'exact': '[name="exact"]',
            'rangeMin': '[name="range-min"]',
            'rangeMax': '[name="range-max"]'
        },

        events: {
            'render': 'onRender',
            'change @ui.checkbox': 'onToggle',
            'change @ui.typeSelect': 'onChangeType',
            'change @ui.rangeMin': 'onChangeRangeMin',
            'change @ui.rangeMax': 'onChangeRangeMax',
        },

        template: '#rangefield-template',

        templateContext: function () {
            var val = this.getOption('value');

            return {
                label: this.getOption('label'),
                enabled: (val) ? true : false,
                value: (val) ? val.value : null,
                type: (val) ? val.type : null,
                rangeType: rangeType
            }
        },

        onRender: function () {
            this.onToggle();
            this.onChangeType();
        },

        enabled: function () {
            return this.ui.checkbox.prop('checked');
        },

        onToggle: function (e) {
            if (e) {
                e.stopImmediatePropagation();
            }

            var disabled = !this.enabled();

            this.ui.typeSelect.prop('disabled', disabled);
            this.ui.minimal.prop('disabled', disabled);
            this.ui.exact.prop('disabled', disabled);
            this.ui.rangeMin.prop('disabled', disabled);
            this.ui.rangeMax.prop('disabled', disabled);
        },

        onChangeType: function (e) {
            if (e) {
                e.stopImmediatePropagation();
            }

            var type = this.ui.typeSelect.val();
            this.$('.type-fields').addClass('hidden');
            this.$('[data-range-type="' + type + '"]').removeClass('hidden');
        },

        /**
         * Ensure min is smaller than or equal to max
         */
        onChangeRangeMax: function (e) {
            if (e) {
                e.stopImmediatePropagation();
            }

            var min = parseInt(this.ui.rangeMin.val()), 
                max = parseInt(this.ui.rangeMax.val());

            this.ui.rangeMin.val(Math.min(min, max));
        },

        /**
         * Ensure max bigger than or equal to min
         */
        onChangeRangeMin: function (e) {
            if (e) {
                e.stopImmediatePropagation();
            }

            var min = parseInt(this.ui.rangeMin.val()), 
                max = parseInt(this.ui.rangeMax.val());

            this.ui.rangeMax.val(Math.max(min, max));
        },

        value: function () {
            if (this.enabled()) {
                switch (this.ui.typeSelect.val()) {
                    case rangeType.undetermined:
                        return {
                            type: rangeType.undetermined,
                            value: null
                        };
                    case rangeType.exact:
                        return {
                            type: rangeType.exact,
                            value: parseInt(this.ui.exact.val())
                        };
                    case rangeType.minimal:
                        return {
                            type: rangeType.minimal,
                            value: parseInt(this.ui.minimal.val())
                        };
                    case rangeType.range:
                        var min = parseInt(this.ui.rangeMin.val());
                        var max = parseInt(this.ui.rangeMax.val());

                        return {
                            type: rangeType.range,
                            value: [Math.min(min, max), Math.max(max, min)]
                        };
                }
            }

            return null;
        }
    });


    W.BoucleField = W.RangeField.extend({ template: '#field-boucle-template' });

    W.AlternativeField = W.RangeField.extend({ template: '#field-alternative-template' });

    W.AttachmentUploadField = Marionette.View.extend({
        ui: {
            'title': 'input[name="title"]',
            'file': 'input[name="file"]'
        },

        triggers: {
            'click button': 'toggle'
        },
        
        template: '#field-attachment-upload-template',

        value: function () {
            if (this.ui.file.get(0).files.length > 0) {
                var title = this.ui.title.val().trim();
                
                if (title == '') {
                    // If no title was provided tack filename
                    title = this.ui.file.get(0).files[0].name;
                }
                return new W.AttachmentModel({
                    'title': title,
                    'attachment': this.ui.file.get(0).files[0]
                });
            }

            return null;
        }
    });

    W.AttachmentURLField = Marionette.View.extend({
        ui: {
            'title': 'input[name="title"]',
            'url': 'input[name="url"]'
        },

        triggers: {
            'click button': 'toggle'
        },

        template: '#field-attachment-url-template',
        
        value: function () {
            var title = this.ui.title.val().trim();
            var url = this.ui.url.val().trim();
            if (url !== '') {
                if (title === '') {
                    // If no title was provided tack filename
                    title = url;
                }

                return new W.AttachmentModel({ 'title': title, 'url': url });
            }

            return null;
        }
    });


    // Might have a model? A model means saved to the server?
    // Otherwise a more simple object
    // How to differentiate between synced model and file? Marker?
    W.AttachmentField = Marionette.View.extend({
        tagName: 'section',

        ui: {
            'title': 'input[name="title"]',
        },
        
        events: {
            'click button': 'delete'
        },

        template: '#field-attachment-template',
        
        delete: function (e) {
            e.preventDefault();
            var r = confirm("Vous êtes sur le point de supprimer ce pièces-jointes. Continuer?");

            if (r) {
                this.model = null;
                this.trigger('delete');
            }
        },

        value: function () {
            var title = this.ui.title.val().trim();
            if (title !== this.model.get('title')) {
                this.model.set('title', title);
            }
            return this.model;
        }
    });

    /**
     * Node editing form
     */
    W.NodeForm = Marionette.View.extend({
        tagName: 'form',
        className: 'axis-edit-form',

        template: '#node-form-template',

        ui: {
            'extraFields': '[name="extra-fields"]',
            'close': '[data-name="close"]',
            'submit': '[data-name="submit"]',
            'link': '[data-name="link"]',
        },

        regions: {
            boucle: {
                el: '[data-name="boucle"]',
                replaceElement: true
            },

            alternative: {
                el: '[data-name="alternative"]',
                replaceElement: true
            },

            attachment: {
                el: '[data-name="attachment"]',
                replaceElement: true
            }
        },

        events: {
            'render': 'onRender',
            'keydown': 'keydown',
            'click @ui.extraFields legend': 'toggleExtraFields',
            'click @ui.submit': 'submit'
        },

        triggers: {
            'click @ui.close': 'form:close',
            'click @ui.link': 'form:makeLink'
        },

        keydown: function (e) {
            if (e.keyCode == 27) {
                e.preventDefault();
                this.trigger('form:close');
            }

            if (e.keyCode == 13 && e.target.tagName != 'TEXTAREA') {
                e.preventDefault();
                this.submit();
            }
        },

        templateContext: function () {
            return {
                aspectChoices: ["duratif", "itératif", "sémelfactif"],
                alternativeChoices: ["inclusive", "exclusive"],
                tagChoices: tags,
                rangeType: rangeType
            }
        },

        renderBoucle: function () {
            var boucleField = new W.BoucleField({
                label: 'Boucle&thinsp;:',
                value: this.model.get('boucle')
            });

            this.showChildView('boucle', boucleField);
        },

        renderAlternative: function () {
            var alternativeField = new W.AlternativeField({
                label: 'Alternative&thinsp;:',
                value: this.model.get('alternative')
            });
            
            this.showChildView('alternative', alternativeField);
        },

        renderAttachment: function (attachment) {
            if (attachment) {
                this.renderAttachmentDisplayField(attachment);
            } else {
                this.renderAttachmentUploadField();
            }
        },

        renderAttachmentDisplayField: function (attachment) {
            var field = new W.AttachmentField({ model: attachment });
            this.listenTo(field, 'delete', this.renderAttachmentUploadField);
            this.showChildView('attachment', field);
        },

        renderAttachmentUploadField: function () {
            var field = new W.AttachmentUploadField();
            this.listenTo(field, 'toggle', this.renderAttachmentURLField);
            this.showChildView('attachment', field);
        },

        renderAttachmentURLField: function () {
            var field = new W.AttachmentURLField();
            this.listenTo(field, 'toggle', this.renderAttachmentUploadField);
            this.showChildView('attachment', field);
        },
        
        onRender: function () {
            this.renderAlternative();
            this.renderBoucle();
            this.renderAttachment(this.model.get('attachment'));
            var el = this.el;
            
            // TODO: improve positioning of the form, especially on small screens.

            if (el) {
                window.requestAnimationFrame(function () {
                    var main = document.querySelector('main.main-area'),
                        mainRect = main.getBoundingClientRect(),
                        form = el.getBoundingClientRect();
          
                    if (form.bottom > mainRect.bottom) {
                        var distance = form.bottom - mainRect.bottom
                        if (form.height > main.height) {
                            distance -= form.height - mainRect.height;
                        }
                        main.scrollBy({ top: distance, behavior: 'smooth' })
                        // animatedScroll(document.querySelector('main.main-area'), distance, 500);
                    }
                });
            }
        },

        toggleExtraFields: function () {
            var $extraFields = this.ui.extraFields;
            $extraFields.toggleClass('expanded');

            window.requestAnimationFrame(function () {
                var main = document.querySelector('main.main-area'),
                    mainRect = main.getBoundingClientRect(),
                    extraFieldsRect = $extraFields.get(0).getBoundingClientRect();
                
                    if (extraFieldsRect.bottom > mainRect.bottom) {
                    var distance = extraFieldsRect.bottom - mainRect.bottom;
                    
                    main.scrollBy({ top: distance, behavior: 'smooth' })
                }
            });
        },

        /**
         * Return input field with name
         */
        field: function (name) {
            return this.$el.find('[name="' + name + '"]');
        },

        /**
         * Return value for field with name.
         * Integer fields are converted to numbers
         * All other fields are trimmed.
         */
        fieldVal: function (name) {
            var field = this.field(name);

            if (!field) {
                consoles.warn("Can't find field " + name);
            } else {
                if (!field.prop('disabled')) {
                    var value = field.val();
                    if (field.attr('type') == 'number') {
                        return parseInt(value);
                    } else if (value) {
                        return value.trim();
                    } else {
                        return null;
                    }
                }
            }

            return null;
        },

        isChecked: function (name) {
            return this.field(name).prop('checked');
        },

        submit: function (e) {
            if (e) {
                e.stopImmediatePropagation();
                e.preventDefault();
            }
            
            // Todo: merge boucle and alternative into one value / datatype:
            // null | [number, number]
            var data = {
                'actant': this.fieldVal('actant'),
                'adresse': this.fieldVal('adresse'),
                'condition': this.fieldVal('condition'),
                'imperative': this.isChecked('imperative'),
                'terme': this.fieldVal('terme'),
                'tag': this.fieldVal('tag'),
                'title': this.fieldVal('title'),
                'module_': this.isChecked('module'),
                'contingent': this.isChecked('contingent'),
                'boucle': this.getChildView('boucle').value(),
                'alternative': this.getChildView('alternative').value(),
                'aspect': this.fieldVal('aspect-value'),
                'commandement': this.fieldVal('commandement'),
                'destination': this.fieldVal('destination'),
                'code': this.fieldVal('code'),
                'indications': this.fieldVal('indications'),
                'attachment': this.getChildView('attachment').value()
            };

            this.trigger('form:submit', data);
        }
    });

    W.NodeInlineTitleForm = Marionette.View.extend({
        tagName: 'form',
        className: 'axis-inline-title-form',

        template: '#node-inline-form-template',

        ui: {
            'title': '[name="axis-inline-title"]',
            'extraFields': '[name="axis-inline-title"]',
            'submit': '[name="submit"]',
            'close': '[data-name="close"]',
        },

        events: {
            'submit': 'submit',
            'keyup @ui.title': 'keyup',
            'click @ui.submit': 'submit',
            'click @ui.close': 'close',
        },

        onAttach: function () {
            this.ui.title.select();
        },

        keyup: function (e) {
            if (e.keyCode == 27) {
                this.close();
            }
        },

        close: function (e) {
            this.trigger('inlinetitleform:close');
        },

        submit: function (e) {
            e.stopImmediatePropagation();
            e.preventDefault();

            // Todo: merge boucle and alternative into one value / datatype:
            // null | [number, number]
            var data = this.ui.title.val().trim();

            this.trigger('inlinetitleform:submit', data);
        }
    });

    /**
    * Detail page views
    */
    W.TreeNode = Marionette.View.extend({
        tagName: 'li',
        className: 'axis score__line line',

        attributes: function () {
            return {
                'data-len': this.model.getChildCount()
            }
        },

        template: '#node-template-vue',

        templateContext: function () {
            var hasAttachment = (this.model.get('attachment')) ? true : false,
                hasSublines = (this.model.get('sublines') && this.model.get('sublines').length > 0) ? true : false;

            return {
                aspectChoices: ["duratif", "itératif", "sémelfactif"],
                alternativeChoices: ["inclusive", "exclusive"],
                tagChoices: tags,
                hasIndications: this.hasIndications(),
                showTag: (this.showTag()) ? 'true' : 'false',
                rangeType: rangeType,
                isLink: (this.link) ? true : false,
                hasSublines: hasSublines,
                hasAttachment: hasAttachment,
                sublineAlternative: this.getAltSymbol(),
                sublineTag: (hasSublines) ? this.model.get('sublines').first().get('tag') : ''
            }
        },

        ui: {
            'title': '.axis-row',
            'titleSpan': '.line__title'
        },

        regions: {
            tree: {
                el: '.sublines',
                replaceElement: true
            },

            form: {
                el: 'form',
                replaceElement: true
            },

            inlineTitleForm: {
                el: '.axis-inline-title-form',
                replaceElement: true
            },

            attachment: {
                el: '.attachment--view',
                replaceElement: true
            }
        },

        events: {
            'click @ui.title': 'toggleClick',
            'dblclick @ui.titleSpan': 'titleDblClick',
            'click @ui.titleSpan': 'titleClick',
            'click [name="toggle"]': 'toggle',
            'click [name="add"]': 'addLine',
            'click [name="edit"]': 'toggleForm',
            'click [name="delete"]': 'delete',
            'click [name="unlink"]': 'unlink',
            'change #tag': 'editSiblings',
            'relocate': 'relocate',
            'relocateChild': 'relocateChild'
        },

        childViewEvents: {
            'form:submit': 'update',
            'form:close': 'toggleForm',
            'form:makeLink': 'makeLink',
            'inlinetitleform:submit': 'updateTitle',
            'inlinetitleform:close': 'closeInlineForm'
        },

        childViewTriggers: {
            'change': 'change'
        },

        initialize: function (options) {
            this.model.set('boucle', this.getBoucle());
            this.model.unset('boucle_check');
            this.model.unset('boucle_n');
            this.model.unset('boucle_p');
            this.model.set('alternative', this.getAlternative());
            this.model.unset('alternative_n');
            this.model.unset('alternative_p');
            this.model.unset('alternative_mode');
            this.editMode = false;

            if (!(this.model.cid in state.collapsed)) {
                state.collapsed[this.model.cid] = true;
            }

            if (options && 'link' in options && options.link) {
                this.link = options.link;
            } else {
                this.link = null
            }

            this.listenTo(this.model, 'change', this.render);

            this.listenTo(this.model.get('sublines'), 'change:tag', this.updateTag);

        },


        updateTag: function() {
            var $el = this.$el.find(".sublines__metadata");
            var tag = this.model.get('sublines').first().get('tag');
            $el.get(0).dataset.tag = tag;
            $el.find(".tag").get(0).dataset.tag = tag;
        },

        /**
        * Adapter function between old and new boucle field
        * temporary
        */
        getBoucle: function () {
            var boucle = this.model.get('boucle');
            var boucle_check = this.model.get('boucle_check');
            var boucle_n = this.model.get('boucle_n');
            var boucle_p = this.model.get('boucle_p');

            // console.log('boucle check', this.model.get('boucle_check'), 'n', boucle_n, 'p', boucle_p);

            if (boucle_check) {
                if (boucle_n === boucle_p) {
                    return {
                        type: rangeType.exact,
                        value: parseInt(boucle_n)
                    }
                } else {
                    return {
                        type: rangeType.range,
                        value: [parseInt(boucle_n), parseInt(boucle_p)]
                    }
                }
            } else if (boucle_check !== undefined) {
                return null;
            }

            return boucle;
        },


        getAltSymbol: function () {
            var alternative = this.model.get("alternative");

            if (alternative) {
                if (alternative.type === rangeType.undetermined) {
                    return "indéterminé";
                } else if (alternative.type === rangeType.exact) {
                    return alternative.value;
                } else if (alternative.type === rangeType.minimal) {
                    return alternative.value + "+";
                } else if (alternative.type === rangeType.range) {
                    return alternative.value[0] + "-" + alternative.value[1];
                }
            }

            return "";
        },


        /**
        * Adapter function between old and new alternative field
        * temporary
        */
        getAlternative: function () {
            var alternative = this.model.get('alternative');
            var alternative_n = this.model.get('alternative_n');
            var alternative_p = this.model.get('alternative_p');

            if (typeof alternative === 'object') {
                return alternative;
            } else {
                if (alternative_n !== false && alternative_n !== undefined) {
                    if (alternative_n === alternative_p) {
                        return {
                            type: rangeType.exact,
                            value: parseInt(alternative_n)
                        }
                    } else {
                        return {
                            type: rangeType.range,
                            value: [parseInt(alternative_n), parseInt(alternative_p)]
                        }
                    }
                }
            }

            return null;
        },

        makeLink: function (e) {
            var link = new W.LinkModel({ target: this.model });
            var parent = this.model.get('parent');

            if (parent) {
                var sublines = parent.get('sublines');

                sublines.add(link, { at: sublines.indexOf(this.model) + 1 });
            }

            this.toggleForm();
            this.trigger('change');
        },

        unlink: function (e) {
            e.preventDefault();
            e.stopImmediatePropagation();

            if (this.link) {
                this.link.unlink();
            }
        },

        /**
         * Only show indications marker if the line has
         * indications, commandement, destination of code
         */
        hasIndications: function () {
            return (
                this.model.get('indications')
                || this.model.get('commandement')
                || this.model.get('destination')
                || this.model.get('code')
            );
        },

        /**
         * Hide tag when it's the root axis or when the parent is
         * in exclusive alternative.
         */
        showTag: function () {
            var sublines = this.model.get('sublines');

            if (sublines && sublines.length > 1) {
                var alternative = this.model.get('alternative');
                if (alternative && alternative.type === rangeType.exact) {
                    return false;
                }
                
                return true;
            }

            return false;
        },

        toggleForm: function (e) {
            if (e && e.stopImmediatePropagation) {
                e.stopImmediatePropagation();
            }

            var form = this.getChildView('form');

            if (form) {
                this.getRegion('form').reset();
            } else {
                form = new W.NodeForm({ model: this.model });
                this.showChildView('form', form);
            }
        },

        update: function (data) {
            var model = this.model;
                
            // delete data['attachment  '];
            var attachmentChanged = (data['attachment'] && data['attachment'].hasChanged);
            var changes = model.changedAttributes(data);

            if (changes) {
                if (data.alternative) {
                    var alt = data.alternative;
                    var minimal = 2;

                    if (alt.type == rangeType.range) {
                        minimal = alt.value[1];
                    } else if (alt.type == rangeType.exact || alt.type == rangeType.minimal) {
                        minimal = alt.value + 1;
                    }

                    var sublines = this.model.get('sublines');

                    while (sublines.length < minimal) {
                        this.model.addSubLine();
                    }

                    // The line wasn't alternative. Set all children to contingent
                    if (!this.model.get('alternative')) {
                        var sublines = this.model.get('sublines');
                        for(var i=0;i < sublines.length; i++) {
                            sublines.at(i).set('contingent', true)
                        }
                    }

                    this.expand();
                }

                model.set(data);

                if (model.collection && changes && 'tag' in changes) {
                    // Propagate changed tag.
                    model.collection.each(function (m) {
                        if (m.cid !== model.cid) {
                            m.set('tag', data['tag']);
                        }
                    });
                    model.collection.trigger('change:tag');
                }

                this.trigger('change');
            } else if (attachmentChanged) {
                this.trigger('change');
                this.render();
            } else {
                this.toggleForm();
            }
        },

        showInlineForm: function () {
            var form = new W.NodeInlineTitleForm({ model: this.model });
            this.showChildView('inlineTitleForm', form);
            this.ui.titleSpan.hide();
        },

        closeInlineForm: function () {
            this.detachChildView('inlineTitleForm');
            this.ui.titleSpan.show();
        },

        updateTitle: function (title) {
            var changes = this.model.changedAttributes({ 'title': title });

            if (changes) {
                this.model.set('title', title);
                this.trigger('change');
            } else {
                this.closeInlineForm();
            }
        },

        // Triggered by jquery nested sortable when the item is
        relocate: function (event, ui) {
            if (this.link) {
                this.link.relocate(event, ui);
            } else {
                var parentItem = this.$el.parent().closest("li");
                parentItem.trigger('relocateChild', [this.model, ui.item.index()]);

                return false;
            }
        },

        // Triggered by a subline view beeing relocated
        relocateChild: function (event, model, index) {
            this.expand();

            if (valid_move(model, this.model)) {
                var previous_parent = model.get("parent");
                previous_parent.get("sublines").remove(model);
                this.model.get("sublines").add(model, { at: index });
                previous_parent.trigger('change');
                this.model.trigger('change');
                this.trigger('change');

                var alts;
                var alternative_mode = model.get("parent").get("alternative_mode");

                if (alternative_mode) {
                    var alternative_n = model.get("parent").get('alternative_n');
                    var achildViewTriggerslternative_p = model.get("parent").get('alternative_p');

                    if (alternative_mode == "exclusive") {
                        alts = "V" + alternative_p;
                    } else {
                        alts = "V" + alternative_n + "-" + alternative_p;
                    }

                    model.set('alternative_symbole', alts);
                }
            } else {
                this.trigger('render');
            }

            return false;
        },

        delete: function (e) {
            // Todo: insert check to see whether there is more than one subline ?
            e.stopImmediatePropagation();
            // Todo: check cross-consistency of axis
            if (this.link) {
                this.link.delete();
            } else {
                var r = confirm("Vous êtes sur le point de supprimer l’axe " + this.model.get('title') + " et tous ses sous-axes. Continuer?");
                if (r) {
                    this.trigger('change');
                    var parent = this.model.get('parent');
                    parent.get('sublines').remove([this.model]);
                    parent.trigger('change');
                    // As models have id's Backbone tries to make a servercall to remove the item.
                    // this.model.destroy();
                }
            }
        },

        titleDblClick: function (e) {
            e.stopImmediatePropagation();
            e.preventDefault();

            // alert('dbl click');
                this.toggle();
        },

        titleClick: function (e) {
            e.stopImmediatePropagation();
            e.preventDefault();

            if (e.shiftKey) {
			 	this.model.getEditable() && this.showInlineForm();
            } else if (e.ctrlKey || e.metaKey || e.altKey) {
			 	this.model.getEditable() && this.toggleForm();
            } 
            
        },

        toggleClick: function (e) {
            if (e && e.target == this.ui.title.get(0)
                || e.target == this.ui.titleSpan.get(0)) {
                e.stopImmediatePropagation();
                e.preventDefault();
                return this.toggle(e);
            }
        },

        toggle: function (e) {
            if (e) {
                e.stopImmediatePropagation();
                e.preventDefault();
            }

            // inverts the data-collapsed option
            // Uses CSS to toggle visibility

            state.collapsed[this.model.cid] = !state.collapsed[this.model.cid];
            this.$el.attr("data-collapsed", state.collapsed[this.model.cid]);
        },

        collapse: function () {
            state.collapsed[this.model.cid] = true;
            this.$el.attr("data-collapsed", state.collapsed[this.model.cid]);
        },

        expand: function () {
            state.collapsed[this.model.cid] = false;
            this.$el.attr("data-collapsed", state.collapsed[this.model.cid]);
        },

        addLine: function (minimal) {
            // AJouter une subline
            // makes sure the line is unfolded to see the new line.
            this.expand();

            var sublines = this.model.get("sublines");

            if (arguments.length < 1 || typeof minimal !== "number") {
                minimal = Math.max(sublines.length + 1, 2);
            }

            while (sublines.length < minimal) {
                this.model.addSubLine();
            }

            this.trigger('change');
            this.render();

            return false;
        },


        toggleAspectEnabled: function (e) {
            e.stopPropagation();
            this.$('#aspect').get(0).disabled = !(e.target.checked);
        },

        onExpandUntill: function (level) {
            if (this.model.getLevel() < level) {
                this.expand();
            } else {
                this.collapse();
            }

            var subtree = this.getChildView('tree');
            if (subtree) {
                subtree.triggerMethod('expandUntill', level);
            }
        },

        renderAttachmentView: function () {
            var attachment = this.model.get('attachment');
            if (attachment) {
                this.showChildView('attachment', getAttachmentView(attachment));
            }
        },

        onRender: function () {
            // attribut des attributs html pour styler en css
            this.$el.attr("data-collapsed", state.collapsed[this.model.cid]);
            this.$el.attr("data-module", this.model.get("module_"));
            this.$el.attr("data-contingent", this.model.get("contingent"));
            this.$el.attr("data-id", this.model.get("id"));
            this.$el.attr("id", this.model.get("id"));

            var sublines = this.model.get("sublines");

            //show mainline nodes if they are present
            if (sublines.length) {
                var sublinesView = new W.TreeView({ collection: sublines });
                this.$el.attr("data-sublines", "true");
                this.showChildView('tree', sublinesView);
            } else {
                this.$el.attr("data-sublines", "false");
            }

            this.renderAttachmentView();
        }
    });

    W.TreeNodeLink = Marionette.View.extend({
        tagName: 'li',
        className: 'linked-node axis score__line line',
        template: _.noop, // Trick to have an empty template

        initialize: function () {
            this.nodeView = null;
        },

        onRender: function () {
            if (!this.nodeView) {
                var target = this.model.get('target');
                if (target) {
                    this.nodeView = new W.TreeNode({
                        model: this.model.get('target'),
                        link: this,
                        el: this.el
                    });
                }
            }

            if (target) {
                this.nodeView.render();
            }
        },

        onExpandUntill: function (level) {
            if (this.nodeView) {
                this.nodeView.triggerMethod('expandUntill', level);
            }
        },

        relocate: function (event, ui) {
            var parentItem = this.$el.parent().closest("li");
            parentItem.trigger('relocateChild', [this.model, ui.item.index()]);

            return false;
        },

        unlink: function () {
            this.trigger('change');

            var data = stripIds(this.model.get('target').toJSON());
            var sublines = this.model.get('parent').get('sublines');
            var index = sublines.findIndex(this.model);

            sublines.remove(this.model);
            sublines.add((new W.LineModel(data)), { at: index });
        },

        delete: function () {
            // FIXME: check spelling
            var r = confirm("Vous êtes sur le point de supprimer ce lien lieé a " + this.model.get('target').get('title') + ". Continuer?");
            if (r) {
                this.trigger('change');
                this.model.get('parent').get('sublines').remove([this.model]);
            }
        }
    });

    W.TreeView = Marionette.CollectionView.extend({
        className: 'sublines',
        tagName: 'ol',

        initialize: function () {
            this.listenTo(this.collection, 'change:tag', this.updateTag);
        },

        childView: function (model) {
            if (model instanceof W.LinkModel) {
                return W.TreeNodeLink;
            } else {
                return W.TreeNode;
            }
        },

        childViewTriggers: {
            // Propagate change events to the parent
            'change': 'change'
        },

        attributes: function () {
            if (this.getAltSymbol()) {
                return {
                    'data-tag': this.collection.first().get('tag'),
                    'data-alternative': this.getAltSymbol()
                }
            }
            return {
                'data-tag': this.collection.first().get('tag'),
            }
        },

        updateTag: function () {
            this.el.dataset.tag = this.collection.first().get('tag');
        },

        templateContext: function () {
            return {
                'tag': this.collection.first().get('tag'),
                'alternative': this.getAltSymbol()
            }
        },

        onExpandUntill: function (level) {
            this.children.each(function (child) {
                child.triggerMethod('expandUntill', level);
            });
        },
        
        getAltSymbol: function () {
            var alternative = this.collection.parent.get("alternative");

            if (alternative) {
                if (alternative.type === rangeType.undetermined) {
                    return "indéterminé";
                } else if (alternative.type === rangeType.exact) {
                    return alternative.value;
                } else if (alternative.type === rangeType.minimal) {
                    return alternative.value + "+";
                } else if (alternative.type === rangeType.range) {
                    return alternative.value[0] + "-" + alternative.value[1];
                }
            }

            return "";
        }
    });


    W.EffectifField = W.RangeField.extend({
        template: '#field-effectif-template',
        enabled: function () { return true },
    });


    W.ScoreForm = Marionette.View.extend({
        template: '#score-template-form',

        tagName: 'form',

        className: 'score-form',

        ui: {
            'submit': '[data-name="submit"]',
            'close': '[data-name="close"]',
        },

        regions: {
            effectif: {
                el: '[data-name="effectif"]',
                replaceElement: true
            },
            share_with: {
                el: '#share_with'
            }
        },

        events: {
            'click @ui.submit': 'submit',
            'change [name="score_type"]': 'onChangeScoreType'
        },

        triggers: {
            'click @ui.close': 'form:close'
        },

        templateContext: function () {
            return {
                rangeType: rangeType,
                // getUserLanguage: getUserLanguage
            };
        },

        /**
         * Return input field with name
         */
        field: function (name) {
            return this.$el.find('[name="' + name + '"]');
        },
        /**
         * Return value for field with name.
         * Integer fields are converted to numbers
         * All other fields are trimmed.
         */
        fieldVal: function (name) {
            var field = this.field(name);

            if (!field) {
                consoles.warn("Can't find field " + name);
            } else {
                if (!field.prop('disabled')) {
                    var value = field.val();
                    if (field.attr('type') == 'checkbox') {
                        return field.prop('checked');
                    } else if (field.attr('type') == 'number') {
                        return parseInt(value);
                    } else if (value) {
                        return value.trim();
                    } else {
                        return "";
                    }
                }
            }

            return "";
        },

        onRender: function () {
            var effectifField = new W.EffectifField({
                label: 'effectif',
                value: this.model.get('effectif')
            });

            this.showChildView('effectif', effectifField);

            var shareWithView = new W.ShareWithView({model: this.model});
            this.showChildView('share_with', shareWithView);
        },

        onChangeScoreType: function (e) {
            if (parseInt(this.fieldVal('score_type')) === 2) {
                this.field('performance_author').prop('disabled', true);
            } else {
                this.field('performance_author').prop('disabled', false);
            }
        },

        submit: function (e) {
            if (e) {
                e.stopImmediatePropagation();
                e.preventDefault();
            }

            // Cast tag value into a list
            var tags = this.fieldVal('tags').trim();
            tags = (tags != "") ? tags.split(/\s*,\s*/) : [];
            var tags = _.map(tags, function(s){ return s.trim(); })

            var data = {
                title: this.fieldVal('title'),
                score_type: parseInt(this.fieldVal('score_type')),
                score_author: this.fieldVal('score_author'),
                performance_author: this.fieldVal('performance_author'),
                presentation: this.fieldVal('presentation'),
                effectif: this.getChildView('effectif').value(),
                stage_set: this.fieldVal('stage_set'),
                duration: this.fieldVal('duration'),
                tags: tags,
                language: this.fieldVal('language'),
                is_public: this.fieldVal('is_public')
            };

            this.trigger('form:submit', data);
        }
    });


    W.ForbiddenScoreDetailView = Marionette.View.extend({
        template: _.template("Cette partition n’existe pas ou bien ne vous est pas accessible."),
        className: 'content'
    });


    W.ScoreDetailView = Marionette.View.extend({
        template: '#score-template-vue',

        attributes: {
            id: 'score',
            class: 'score content',
            tabindex: '1'
        },

        regions: {
            permissions: '#permissions',

            mainline: 'ol',

            form: {
                el: 'aside form',
                replaceElement: true
            },

            slider: {
                el: '#expand-level-slider',
                replaceElement: true
            },
        },

        ui: {
            'save': '.js-save',
            'edit': '.js-edit',
            'delete': '.js-delete',
            'duplicate': '.js-duplicate',

            // 'expandLevel': '[name="expand-level"]',
            'scoreMeta': '.score-meta',
            'title': '#title',
            'meta': '.panel--score-meta'
        },

        events: {
            'click @ui.save': 'save',
            'click @ui.edit': 'toggleForm',
            'click @ui.delete': 'delete',
            'click @ui.duplicate': 'duplicate',

            'click .unfold': 'unfold',
            'click .fold': 'fold',
            // 'change @ui.expandLevel': 'expandUntill',
            'click [data-name="export"]': 'downloadAsJSON',
            'click .panel--score-meta .js-toggle': 'toggleMeta'
        },

        childViewEvents: {
            'change': 'markChanged',
            'level:change': 'expandUntill',
            'form:close': 'toggleForm',
            'form:submit': 'update'
        },

        templateContext: function () {
            return {
                hasChanges: this.hasChanges,
                depth: this.model.getDepth(),
                rangeType: rangeType,
                formatTimestamp: formatTimestamp,
                metaExpanded: this.metaExpanded,
            };
        },

        initialize: function () {
            this.hasChanges = false;
            this.metaExpanded = true;
            this.mainlineView = null;
            this.sliderView = null;
            // this.listenTo(this.model, 'change', this.render);
            // this.listenTo(this.model, 'sync', this.render);

            $(window).on('beforeunload', (function (e) {
                if (this.hasChanges) {
                    if (!window.confirm("There are unsaved changes to the score. Are you sure to leave?")) {
                        e.preventDefault();
                        return false;
                    }
                }
            }).bind(this));

            this.listenToOnce(this.model, 'sync', (function () {
                this.listenTo(this.model, 'change', this.render);
                // @TODO check whether line below should be re-enabled
                // this.listenTo(this.model, 'sync', this.render);
            }).bind(this));
        },

        toggleForm: function (e) {
            if (e && e.stopImmediatePropagation) {
                e.stopImmediatePropagation();
            }

            var form = this.getChildView('form');

            if (form) {
                this.getRegion('form').reset();
            } else {
                form = new W.ScoreForm({ model: this.model });
                this.showChildView('form', form);
            }
        },

        update: function (data) {
            if (this.model.changedAttributes(data)) {
                this.markChanged();
                this.model.save(data);

            } else {
                this.toggleForm();
            }
        },

        markChanged: function () {
            this.ui.save.prop('disabled', false);
            this.hasChanges = true;
            /* If a change has been detected in the model, rerender the slider */
            var sliderView = new W.SliderView({ depth: this.model.getDepth() });
            this.showChildView('slider', sliderView);
        },

        unfold: function () {
            $('li').attr('data-collapsed', false);
            this.getChildView('slider').val(this.model.getDepth());
        },

        fold: function () {
            $('li').attr('data-collapsed', true);
            this.getChildView('slider').val(1);
        },

        save: function () {
            var button = this.ui.save;
            this.hasChanges = false;
            button.prop('disabled', true);

            var done = _.bind(function () {
                button.text(button.data('label'));
            }, this);

            var failed = _.bind(function (_, status) {
                button.text(button.data('label'));  
                button.prop('disabled', false);
                this.hasChanges = true;
                alert('Error while saving -- ' + status.status + ' -- ' + status.statusText);
            }, this);

            var saveModel = _.bind(function () {
                button.text(button.data('label-progress'));
                this.model.save(null, {
                    silent: true,
                    success: done,
                    error: failed
                });
            }, this);

            // saveModel();

            this.uploadNewAttachments(this.model.get('mainline'), saveModel, failed);
        },

        uploadNewAttachments: function (line, success, error) {
            var sublines = line.get('sublines'),
                i = -1;

            var uploadOwnAttachment = _.bind(function () {
                var attachment = line.get('attachment');
                
                if (attachment && attachment.isNew()) {
                    var formData = new FormData();
                    formData.append('title', attachment.get('title'));
                    if (attachment.get('attachment')) {
                        formData.append('attachment', attachment.get('attachment'));
                    }
                    else {
                        formData.append('url', attachment.get('url'));
                    }
                    $.ajax({
                        type: "POST",
                        url: '/api/attachments/',
                        enctype: 'multipart/form-data',
                        data: formData,
                        cache: false,
                        contentType: false,
                        processData: false,
                        success: _.bind(function (data) {
                            attachment.set(data);
                            success();
                        }, this),
                        error: error,
                        dataType: 'json'
                    });
                } else {
                    success();
                }
            }, this);

            if (sublines && sublines.length > 0) {
                var uploadSublineAttachments = _.bind(function () {
                    i++;
                    if (i < sublines.length) {
                        this.uploadNewAttachments(sublines.at(i), uploadSublineAttachments, error);
                    } else {
                        uploadOwnAttachment();
                    }
                }, this);
    
                uploadSublineAttachments();            
            } else {
                uploadOwnAttachment();
            }
        },

        delete: function (view, event) {
            var r = confirm("Vous êtes sur le point de supprimer la partition intitulée \"" + this.model.get("title") + "\". Continuer?");
            if (r) {
                this.model.destroy({
                    success: function () {
                        Backbone.history.navigate('/', {
                            trigger: true
                        });
                    }
                });
            }
        },

        duplicate: function (view, event) {
            var data = this.model.attributes;
            delete data.id

            var newModel = new W.ScoreModel(data);

            newModel.save({
                title: newModel.get("title") + " (copy)",
            }, {
                    success: function () {
                        Backbone.history.navigate('/partitions/' + newModel.id, {
                            trigger: true
                        });
                    }
                });
        },

        expandUntill: function (level) {
            this.getChildView('mainline').triggerMethod('expandUntill', level);
        },

        onRender: function () {
            document.title = this.model.get('title');

            this.$el.toggleClass('read-only', !this.model.get('is_editable'));

            var mainlineView = new W.TreeNode({ model: this.model.get('mainline') });
            this.showChildView('mainline', mainlineView);

            var sliderView = new W.SliderView({ depth: this.model.getDepth() });
            this.showChildView('slider', sliderView);

            // var permissions = this.model.get("permissions");
            // var permissionView = new W.PermissionView({ collection: permissions });
            // this.showChildView('permissions', permissionView);

            this.getRegion('mainline').$el.nestedSortable({
                placeholder: 'axis placeholder',
                forcePlaceholderSize: true,
                opacity: 0.25,
                helper: 'clone',
                handle: '.line__title',
                isTree: true,
                items: 'li',
                toleranceElement: '> header',
                // tolerance: 'pointer',
                protectRoot: true,
                rootID: 'foo',
                errorClass: 'move-not-allowed',
                isAllowed: function (placeholder, placeholderParent, currentItem) {
                    var currentId = currentItem.attr('data-id');
                    if (currentId == placeholderParent.attr('data-id')) {
                        return false
                    };

                    var parentIds = _.map(placeholder.parents('li.axis'), function (n) {
                        return n.getAttribute('data-id');
                    });

                    if (_.indexOf(parentIds, currentItem) > 0) {
                        return false
                    }

                    var childrenIds = _.map(currentItem.find('li.axis'), function (n) {
                        return n.getAttribute('data-id');
                    });

                    if (_.intersection(parentIds, childrenIds).length > 0) {
                        return false
                    }

                    return true;
                },
                relocate: function (_, ui) {
                    /*
                     * When the sort action is over:
                     * 1. trigger an event "relocate" on the item beeing sorted, along with its index
                     * 2. listen and catch event "relocate" on the view that is responsible for the item
                     * 3. trigger an event "relocateChild" on the parent element, along with the index, and the item model
                     * 4. listen and catch event "relocateChild" on the view that is responsible for the parent item
                     * 5. console.log that
                     */
                    ui.item.trigger("relocate", ui);
                },
            });

        },

        onBeforeDestroy: function () {
            $(window).off('beforeunload');
        },

        downloadAsJSON: function () {
            download(this.model.get('title') + '-export.json', JSON.stringify(this.model.toJSON()));
        },

        toggleMeta: function () {
            this.metaExpanded = !this.metaExpanded;
            this.ui.meta.toggleClass('is-collapsed', !this.metaExpanded)
        }
    });


    W.SliderView = Marionette.View.extend({
        template: '#slider-template',

        tagName: 'ol',

        className: 'score__slider slider',

        triggers: {
            'click .slider__level': 'click',
        },

        templateContext: function () {
            return {
                'depth': this.getOption('depth')
            };
        },

        onClick: function (view, e) {
            e.preventDefault();
            e.stopImmediatePropagation();
            this.val(parseInt($(e.target).attr('data-value')));
        },

        val: function (level) {
            state.expandLevel = level;
            this.$('[data-selected="true"]').attr('data-selected', false);
            this.$('[data-value="' + level + '"]').attr('data-selected', true);
            this.trigger('level:change', level);
        },

        onRender: function () {
            this.$('[data-selected="true"]').attr('data-selected', false);
            this.$('[data-value="' + state.expandLevel + '"]').attr('data-selected', true);
        }
    });


    // W.UserDataListItemView = Marionette.View.extend({
    //     template: _.template('<option value="<%- username %>">'),
    // });


    // W.UserDataListView = Marionette.CollectionView.extend({
    //     tagName: 'datalist',

    //     attributes: { id: 'users' },

    //     childView: W.UserDataListItemView,
    // });


    // W.PermissionItemView = Marionette.View.extend({
    //     tagName: 'li',
    //     template: '#permission-item-template',

    //     ui: {
    //         'remove': 'span',
    //     },

    //     triggers: {
    //         'click @ui.remove': 'remove:permissions',
    //     },

    //     onRemovePermissions: function() {
    //         var score = this.model.collection.score;
    //         this.model.destroy();
    //         // Backbone.Relational.store.unregister(this.model);
    //         console.log(score);
    //         // // var collection = this.model.collection;
    //         // var that = this;
    //         // this.model.destroy({
    //         //     success: function() {
    //         //         console.log(that.parent.collection);
    //         //     }
    //         // });
    //         score.save(null, {silent: true});
    //     },

    //     initialize: function () {
    //         this.$el.attr("data-username", this.model.get("username"))
    //     },
    // });


    // W.PermissionListView = Marionette.CollectionView.extend({
    //     tagName: 'ol',

    //     className: 'permission-list',

    //     childView: W.PermissionItemView,

    //     // changePermissions: function(event) {
    //         // var perms = [];

    //         // this.$el.find("li").each(function() {
    //             // var data = {};
    //             // data.username = $(this).attr("data-username");
    //             // data.permissions = [];
                
    //             // $(this).find('[type="checkbox"]:checked').each(function(){
    //                 // data.permissions.push($(this).val());
    //             // });

    //             // perms.push(data);
    //         // });
            
    //         // this.collection.score.save({permissions: perms}, {silent: true})
    //     // },
    // });


    // W.PermissionView = Marionette.View.extend({
    //     template: '#permissions-template',

    //     regions: {
    //         permissionList: '#permission-list',
    //         dataList: '#datalist',
    //     },

    //     ui: {
    //         'search': '.search-username',
    //         'add': '.submit-username'
    //     },

    //     triggers: {
    //         'click @ui.add': 'submit',
    //     },

    //     onSubmit: function(view, event) {
    //         // Retrieves the submitted username and add a permission line 
    //         var username = view.$el.find(".search-username").val();

    //         // Prevents adding non-existing or duplicate users 
    //         if (this.collection.where({username: username}).length == 0 
    //                 && this.users.where({username: username}).length != 0) {

    //             this.collection.add({username: username, permissions: ["view_score", "change_score"]})
    //             this.collection.score.save(null, {silent: true});
    //         }
    //     },

    //     onRender: function() {
    //         var permissionListView = new W.PermissionListView({ collection: this.collection });
    //         this.showChildView('permissionList', permissionListView);

    //         var dataListView = new W.UserDataListView({ collection: this.users });
    //         this.showChildView('dataList', dataListView);
    //     },

    //     initialize: function () {
    //         this.users = new W.UserCollection();
    //         this.users.fetch();
    //     },
    // });


    W.UserFooItemView = Marionette.View.extend({
        tagName: 'li',
        
        template: '#permission-item-template',

        ui: {
            'remove': 'span',
        },

        triggers: {
            'click @ui.remove': 'remove:permissions',
        },

        onRemovePermissions: function() {
           this.model.collection.remove(this.model);
        }
    });


    W.UserFooListView = Marionette.CollectionView.extend({
        tagName: 'ol',
        
        childViewEvents: {
            'remove:permissions': 'removePermissions',
        },

        childView: W.UserFooItemView,
    });


    W.ShareWithView = Marionette.View.extend({
        template: '#share-with-template',

        regions: {
            userList: {
                el: '#user-list'
            } 
        },

        onRender: function() {
            var that = this;

            $('#autocomplete', this.$el).devbridgeAutocomplete({
                serviceUrl: '/api/users/',
                dataType: 'json',
                paramName: 'search',
                transformResult: function(response) {
                    return {
                        suggestions: _.map(response, function(item) {
                            return { value: item.username, data: item.id };
                        })
                    };
                },
                onSelect: function (suggestion) {
                    var collection = that.getRegion('userList').currentView.collection;
                    collection.add([{username: suggestion.value, id: suggestion.data}]);
                    $(this).val("");
                    that.model.save(null, {silent: true});

                    return false;
                }
            });
        },

        initialize: function () {
            var collection = this.model.get("shared_with");
            this.showChildView('userList', new W.UserFooListView({collection: collection}));
        },
    });


    /**
     * List page views
     */


    W.SearchView = Marionette.View.extend({
        template: '#search-template',

        className: 'scores__search search',

        triggers: {
            'submit form': 'search'
        },

        onSearch: function (view, event) {
            this.collection.queryParams.search = this.$el.find('.search-input').val();
            this.collection.fetch();

            return false;
        },
    });


    W.ChoiceItemView = Marionette.View.extend({
        // template: '#filters-template',
        template: _.template('<%- label %> (<%- n %>)'),
        tagName: 'li',
        attributes : function () {
            return {
                'data-value' : this.model.get( 'value' ),
                'class': 'filter__item'
            };
        },

        triggers: {
            'click': 'filter'
        },
    });


    W.ChoiceListView = Marionette.CollectionView.extend({
        childViewContainer: 'ol',

        template: _.template('<h1 class="filter__heading js-help" data-href="<%- helpAnchor %>"><%- title %></h1><ol class="filter__list"></ol>'),

        className: 'scores__filter filter',

        childView: W.ChoiceItemView,

        childViewTriggers: {
            // Propagate change events to the parent
            'filter': 'filter'
        },

        templateContext: function () {
            return {
                title: this.title,
                helpAnchor: this.helpAnchor,
            }
        },

        initialize: function (options) {
            this.title = options.title || 'untitled section';
            this.helpAnchor = options.helpAnchor || '#';

            var that = this;

            options.syncWith.listenTo(this, 'filter', function(view, event, options) {
                var action = that.collection.action;

                if (this.queryParams[action] && action == "tags") {
                    this.queryParams[that.collection.action] += "," + $(event.currentTarget).data("value");
                } else {
                    this.queryParams[that.collection.action] = $(event.currentTarget).data("value");
                }
                this.getFirstPage();
            });
        },
    });


    W.CreateView = Marionette.View.extend({
        template: '#create-template',

        className: 'main-header__create create',

        triggers: {
            'click a': 'toggleForm',
            'submit .create__form': 'create'
        },

        onToggleForm: function (event) {
            this.$el.find('.create__form').toggleClass("create__form--inactive"); 
        },

        onCreate: function (event) {
            this.$el.find('.create__form').addClass("create__form--inactive"); 

            var title = this.$el.find('[name="title"]').val();
            var myModel = new W.ScoreModel();
            myModel.save({
                title: title
            }, {
                    success: function () {
                        Backbone.history.navigate('/partitions/' + myModel.id, {
                            trigger: true
                        });
                    }
                });
        },
    });


    W.AppliedFiltersView = Marionette.View.extend({
        template: '#applied-filters-template',

        className: 'appliedfilters',

        templateContext: function() {
            return {
                search: this.collection.queryParams.search,
                tags: this.collection.queryParams.tags,
                score_type: this.collection.queryParams.score_type,
                language: this.collection.queryParams.language
            }
        },

        triggers: {
            'click span': 'remove'
        },

        onRemove: function(view, event) {
            var filter = $(event.currentTarget).data("filter");
            delete this.collection.queryParams[filter];
            this.collection.getFirstPage();
        },

        initialize: function () {
            this.listenTo(this.collection, 'sync', this.render);
        }
    });


    W.SearchPanelView = Marionette.View.extend({
        behaviors: [W.ToggableBehavior],

        template: '#search-panel-template',

        className: 'scores__filters panel panel--right',

        triggers: {
            'click .js-sort-by-title': 'sortByTitle',
            'click .js-sort-by-date': 'sortByDate'
        },

        onSortByTitle: function() {
            this.collection.state.sortKey = "title";
            this.collection.fetch();
            // this.collection.setSorting("title");
            // this.collection.sort();

        },

        onSortByDate: function() {
            this.collection.state.sortKey = "-created_at";
            this.collection.fetch();
            // this.collection.setSorting("-created_at");
            // this.collection.sort();
        },

        regions: {
            search: {
                el: '#search',
                replaceElement: true
            },
            languageFilter: {
                el: '#language-filter',
                replaceElement: true
            },
            scoreTypeFilter: {
                el: '#score-type-filter',
                replaceElement: true
            },
            tagFilter: {
                el: '#tag-filter',
                replaceElement: true
            },
            appliedFilters: {
                el: '#applied-filters',
                replaceElement: true
            }
        },

        onRender: function () {
            var mySearchView = new W.SearchView({
                collection: this.collection
            });

            // var myAppliedFiltersView = new W.AppliedFiltersView({
            //     collection: this.collection
            // });

            var languageCollection = new W.ChoiceCollection([], {action: "language", syncWith: this.collection});
            var languageChoiceListView = new W.ChoiceListView({collection: languageCollection, syncWith: this.collection, title: t('langue'), helpAnchor: "#langue"});

            var scoreTypeCollection = new W.ChoiceCollection([], {action: "score_type", syncWith: this.collection})
            var scoreTypeChoiceListView = new W.ChoiceListView({collection: scoreTypeCollection, syncWith: this.collection, title: t('type'), helpAnchor: "#type-de-partition"})

            var TagCollection = new W.ChoiceCollection([], {action: "tags", syncWith: this.collection})
            var TagChoiceListView = new W.ChoiceListView({collection: TagCollection, syncWith: this.collection, title: t('tags'), helpAnchor: "#tags"})
            TagChoiceListView.$el.addClass("filter--tags");

            this.showChildView('search', mySearchView);
            this.showChildView('languageFilter', languageChoiceListView);
            this.showChildView('scoreTypeFilter', scoreTypeChoiceListView);
            this.showChildView('tagFilter', TagChoiceListView);
            // this.showChildView('appliedFilters', myAppliedFiltersView);
        },

        initialize: function() {
            // this.listenTo(this.collection, 'sync', this.render);
            // this.render()
        }
    });


    W.ScoreItemView = Marionette.View.extend({
        tagName: 'li',

        className: 'scores__item item',

        template: '#score-item-template', 

        templateContext: function () {
            moment.locale('fr');
            var humanDate = moment(this.model.get('updated_at')).format('Do MMMM YYYY');

            return {
                humanDate: humanDate,
            }
        },
    });


    // W.EmptyView = Marionette.View.extend({
    //     template: _.template('Nothing to display.')
    // });


    W.ScoreListFooView = Marionette.CollectionView.extend({
        childView: W.ScoreItemView,

        // emptyView: W.EmptyView,

        childViewContainer: 'ol',

        template: _.template('<% if (title) { %><h1 class="scores__heading"><%- title %></h1><% } %><ol class="scores__list"></ol>'),

//         getTemplate() {
//             if (this.collection.length == 0) {
//                 return _.template('');
//             }

//             return this.template;
//         },

        templateContext: function () {
            return {
                title: this.title,
            }
        },

        initialize: function (options) {
            this.listenTo(this.collection, 'sync', this.render);
            this.title = options.title || null;
        }
    });


    W.ScoreListView = Marionette.View.extend({
        template: '#score-list-template',

        className: 'scores',

        regions: {
            list: {
                el: '#list',
                replaceElement: true
            },
            panel: {
                el: '#search-panel',
                replaceElement: true
            },
            foo: {
                el: '#foo',
                replaceElement: true
            }
        },

        onRender: function () {
            document.title = 'Notation W';


            var scoreCollection = new W.ScoreCollection([], {
                // All the `state` and `queryParams` key value pairs are merged with
                // the defaults too.
                state: {
                    pageSize: null,
                    firstPage: 1,
                    currentPage: 1,
                    sortKey: "title",
                    order: -1
                }
            });

            var myListView = new W.ScoreListFooView({
                collection: scoreCollection,
                // title: "Partitions existantes"
            });

            var mySearchPanelView = new W.SearchPanelView({collection: scoreCollection});

            var myAppliedFiltersView = new W.AppliedFiltersView({
                collection: scoreCollection
            });

            this.showChildView('foo', myAppliedFiltersView);


            this.showChildView('list', myListView);
            this.showChildView('panel', mySearchPanelView);

            scoreCollection.fetch();
        }
    });


    /**
     * User views
     */


    W.UserListView = Marionette.View.extend({
        template: '#user-list-template',
    });


    W.UserDetailView = Marionette.View.extend({
        template: '#user-detail-template',

        className: 'content',

        regions: {
            list: '#list',
        },

        onRender: function () {
            var scoreCollection = new W.ScoreCollection([], {
                // All the `state` and `queryParams` key value pairs are merged with
                // the defaults too.
                state: {
                    pageSize: 25,
                    firstPage: 1,
                    currentPage: 1,
                    sortKey: "title",
                    order: -1
                }
            });

            // scoreCollection.queryParams.shared_with=true;
            scoreCollection.queryParams.can_edit=true;

            var mySearchView = new W.SearchView({
                collection: scoreCollection,
            });
                

            var myListView = new W.ScoreListFooView({
                collection: scoreCollection,
                title: "Partitions partagées avec vous"
            });

            var that = this;

            myListView.collection.fetch({
                success: function () {
                    that.showChildView('list', myListView);
                }
            });
        }
    });


    W.LoginView = Marionette.View.extend({
        template: '#login-template',

        className: 'page__modal modal',

        behaviors: [W.ModalBehavior],

        onSubmit: function (foo, event) {
            event.preventDefault();

            var model = this.model;
            var that = this;

            var form = this.$el.find("form");

            $.ajax({
                type: "POST",
                url: form.attr('action'),
                data: form.serialize(),
                success: function(data, textStatus, jqXHR) {
                    model.fetch();
                    that.trigger('hide:modal');
                },
                error: function(xhr, ajaxOptions, thrownError){
                    alert('login failed - please try again');
                },
            });
        }
    });


    W.LogoutView = Marionette.View.extend({
        template: '#logout-template',

        className: 'page__modal modal',

        behaviors: [W.ModalBehavior],

        onSubmit: function (foo, event) {
            event.preventDefault();

            var that = this;
            var model = this.model;

            var form = this.$el.find("form");

            $.ajax({
                type: "POST",
                url: form.attr('action'),
                success: function(data, textStatus, jqXHR){
                    that.trigger('hide:modal');
                    model.clear();
                    // model.set(model.defaults); // makes sure the username is set
                },
                error: function(xhr, ajaxOptions, thrownError){
                    alert('logout failed - please try again');
                },
            });
        }
    });


    W.RegisterView = Marionette.View.extend({
        template: '#register-template',

        className: 'page__modal modal',

        behaviors: [W.ModalBehavior],

        templateContext: {
            errors: undefined
        },

        onSubmit: function (foo, event) {
            event.preventDefault();

            var that = this;
            var model = this.model;

            var form = this.$el.find("form");

            $.ajax({
                type: "POST",
                url: form.attr('action'),
                data: form.serialize(),
                success: function(data, textStatus, jqXHR){
                    that.trigger('hide:modal');
                    model.set(model.defaults);
                },
                error: function(xhr, ajaxOptions, thrownError){
                    if (xhr.status == 400) {
                        that.templateContext.errors = xhr.responseJSON;
                        that.render();
                        that.templateContext.errors = undefined;

                    };
                },
            });
        }
    });


    /**
     *  Other views
     */


    W.AboutView = Marionette.View.extend({
        template: '#about-template',

        className: 'body-text body-text--about content',
    });
                

    W.CreditsView = Marionette.View.extend({
        template: '#credits-template',

        className: 'body-text body-text--about content',
    });
                

    W.HomeView = Marionette.View.extend({
        template: '#home-template',

        regions: {
            latest_updated: {
                el: '#latest_updated',
                replaceElement: true
            },

            user_scores: {
                el: '#user_scores',
                replaceElement: true
            },
        },

        className: 'content',

        initialize: function() {
            // Backbone.Radio.channel('user').on('change', (this.renderUserScores).bind(this));
        },

        onRender: function () {
            document.title = 'Notation W';

            var collection = new W.ScoreCollection([], {
                state: {
                    pageSize: 5,
                    sortKey: "updated_at",
                    order: 1,
                }
            });

            collection.queryParams.is_featured = true;

            var view = new W.ScoreListFooView({
                collection: collection,
                title: t("Le choix de W"),
                className: "last-updated"
            });

            view.collection.fetch({
                success: (function () {
                    this.showChildView('latest_updated', view);
                }).bind(this)
            });

            var collection2 = new W.ScoreCollection([], {
                state: {
                    pageSize: 5,
                    sortKey: "updated_at",
                    order: 1
                }
            });

            collection2.queryParams.can_edit = true;

            var view2 = new W.ScoreListFooView({
                collection: collection2,
                title: _("Mes partitions"),
                className: "user-scores"
            });

            view2.collection.fetch({
                success: (function (collection, response, options) {
                    if (collection.length) {
                        this.showChildView('user_scores', view2);
                    }
                }).bind(this)
            });
        },
    });


    W.helpView = Marionette.View.extend({
        behaviors: [W.ToggableBehavior],

        template: '#help-template',

        className: 'page__help help is-collapsed panel panel--right'
    });


    W.HeaderView = Marionette.View.extend({
        tagName: 'header',

        template: '#header-template',

        className: 'page__header main-header panel panel--left',

        behaviors: [W.ToggableBehavior],

        ui: {
            'login': '.js-login',
            'logout': '.js-logout',
            'register': '.js-register',
            'lang': '.js-lang',
        },

        triggers: {
            'click @ui.login': 'show:login',
            'click @ui.logout': 'show:logout',
            'click @ui.register': 'show:register',
            'click @ui.lang': 'switch:language',
        },

        initialize: function () {
            this.listenTo(this.model, 'change', this.render);
        },

        regions: {
            create: {
                el: '#create',
                replaceElement: true
            }
        },

        templateContext: function () {
            return {
                'isLoggedIn': this.model.id ? true : false
            }
        },

        onRender: function () {

            if (this.model.id) {
                var myCreateView = new W.CreateView();
                this.showChildView('create', myCreateView);
            }
        }
    });


    /**
     *  Base view, the mother of all views
     */

    W.BaseView = Marionette.View.extend({
        template: '#base-template',

        className: 'page__wrapper',

        regions: {
            header: {
                el: '#main-header',
                replaceElement: true
            },
            main: {
                el: '#main-area',
                // replaceElement: true
            },
            help: {
                el: '#help',
                replaceElement: true
            },
            modal: {
                el: '#modal',
                replaceElement: true
            }
        },

		childViewEvents: {
			'show:login': 'showLogin',
			'show:register': 'showRegister',
			'show:logout': 'showLogout',
			'hide:modal': 'hideModal',
			'switch:language': 'switchLanguage'
		},

        events: {
            'click .js-help': 'showHelp'
        },

        // modelEvents: {
        //     'change': 'foobar'
        // },

        // initialize: function() {
        //     this.listenTo(this.model, 'change', this.foobar);
        // },

        foobar: function() {
            _.each(this.getRegions(), function(element, index, list) {
                var currentView = element.currentView;
                if (currentView) { currentView.render(); }
            });
        },

        showHelp: function(event) {
            var anchor = $(event.currentTarget).data("href");
            var view = this.getRegion('help').currentView;
            view.triggerMethod("show");
            view.$el.find('iframe').get(0).contentWindow.location.hash = anchor;
            view.$el.find('iframe').contents().find(anchor).removeClass('help__section--closed');
        },

        onRender: function () {
            this.showChildView('header', new W.HeaderView({model: this.model}));
            this.showChildView('help', new W.helpView());
        },

        switchLanguage: function (view, event) {
            W.config.lang = $(event.currentTarget).attr("data-lang");
            var lang = W.utils.setCookie("lang", W.config.lang);
            
            _.each(this.getRegions(), function(element, index, list) {
                var currentView = element.currentView;
                if (currentView) { currentView.render(); }
            });
        },

        showLogin: function(event) {
            this.showChildView('modal', new W.LoginView({model: this.model}));
        },

        showLogout: function(event) {
            this.showChildView('modal', new W.LogoutView({model: this.model}));
        },

        showRegister: function(event) {
            this.showChildView('modal', new W.RegisterView());
        },

        hideModal: function(event) {
            this.getRegion('modal').reset();
        },
    });
})();