No images in this repository’s iceberg at this time
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 :', value: this.model.get('boucle') }); this.showChildView('boucle', boucleField); }, renderAlternative: function () { var alternativeField = new W.AlternativeField({ label: 'Alternative :', 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(); }, }); })();