123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- /*
- * Copyright (c) 2015
- *
- * This file is licensed under the Affero General Public License version 3
- * or later.
- *
- * See the COPYING-README file.
- *
- */
-
- /* global Handlebars */
-
- (function(OC) {
-
- /**
- * @class OC.SystemTags.SystemTagsInputField
- * @classdesc
- *
- * Displays a file's system tags
- *
- */
- var SystemTagsInputField = OC.Backbone.View.extend(
- /** @lends OC.SystemTags.SystemTagsInputField.prototype */ {
-
- _rendered: false,
-
- _newTag: null,
-
- _lastUsedTags: [],
-
- className: 'systemTagsInputFieldContainer',
-
- template: function(data) {
- return '<input class="systemTagsInputField" type="hidden" name="tags" value=""/>';
- },
-
- /**
- * Creates a new SystemTagsInputField
- *
- * @param {Object} [options]
- * @param {string} [options.objectType=files] object type for which tags are assigned to
- * @param {bool} [options.multiple=false] whether to allow selecting multiple tags
- * @param {bool} [options.allowActions=true] whether tags can be renamed/delete within the dropdown
- * @param {bool} [options.allowCreate=true] whether new tags can be created
- * @param {bool} [options.isAdmin=true] whether the user is an administrator
- * @param {Function} options.initSelection function to convert selection to data
- */
- initialize: function(options) {
- options = options || {};
-
- this._multiple = !!options.multiple;
- this._allowActions = _.isUndefined(options.allowActions) || !!options.allowActions;
- this._allowCreate = _.isUndefined(options.allowCreate) || !!options.allowCreate;
- this._isAdmin = !!options.isAdmin;
-
- if (_.isFunction(options.initSelection)) {
- this._initSelection = options.initSelection;
- }
-
- this.collection = options.collection || OC.SystemTags.collection;
-
- var self = this;
- this.collection.on('change:name remove', function() {
- // refresh selection
- _.defer(self._refreshSelection);
- });
-
- _.defer(_.bind(this._getLastUsedTags, this));
-
- _.bindAll(
- this,
- '_refreshSelection',
- '_onClickRenameTag',
- '_onClickDeleteTag',
- '_onSelectTag',
- '_onDeselectTag',
- '_onSubmitRenameTag'
- );
- },
-
- _getLastUsedTags: function() {
- var self = this;
- $.ajax({
- type: 'GET',
- url: OC.generateUrl('/apps/systemtags/lastused'),
- success: function (response) {
- self._lastUsedTags = response;
- }
- });
- },
-
- /**
- * Refreshes the selection, triggering a call to
- * select2's initSelection
- */
- _refreshSelection: function() {
- this.$tagsField.select2('val', this.$tagsField.val());
- },
-
- /**
- * Event handler whenever the user clicked the "rename" action.
- * This will display the rename field.
- */
- _onClickRenameTag: function(ev) {
- var $item = $(ev.target).closest('.systemtags-item');
- var tagId = $item.attr('data-id');
- var tagModel = this.collection.get(tagId);
-
- var oldName = tagModel.get('name');
- var $renameForm = $(OC.SystemTags.Templates['result_form']({
- cid: this.cid,
- name: oldName,
- deleteTooltip: t('core', 'Delete'),
- renameLabel: t('core', 'Rename'),
- isAdmin: this._isAdmin
- }));
- $item.find('.label').after($renameForm);
- $item.find('.label, .systemtags-actions').addClass('hidden');
- $item.closest('.select2-result').addClass('has-form');
-
- $renameForm.find('[title]').tooltip({
- placement: 'bottom',
- container: 'body'
- });
- $renameForm.find('input').focus().selectRange(0, oldName.length);
- return false;
- },
-
- /**
- * Event handler whenever the rename form has been submitted after
- * the user entered a new tag name.
- * This will submit the change to the server.
- *
- * @param {Object} ev event
- */
- _onSubmitRenameTag: function(ev) {
- ev.preventDefault();
- var $form = $(ev.target);
- var $item = $form.closest('.systemtags-item');
- var tagId = $item.attr('data-id');
- var tagModel = this.collection.get(tagId);
- var newName = $(ev.target).find('input').val().trim();
- if (newName && newName !== tagModel.get('name')) {
- tagModel.save({'name': newName});
- // TODO: spinner, and only change text after finished saving
- $item.find('.label').text(newName);
- }
- $item.find('.label, .systemtags-actions').removeClass('hidden');
- $form.remove();
- $item.closest('.select2-result').removeClass('has-form');
- },
-
- /**
- * Event handler whenever a tag must be deleted
- *
- * @param {Object} ev event
- */
- _onClickDeleteTag: function(ev) {
- var $item = $(ev.target).closest('.systemtags-item');
- var tagId = $item.attr('data-id');
- this.collection.get(tagId).destroy();
- $(ev.target).tooltip('hide');
- $item.closest('.select2-result').remove();
- // TODO: spinner
- return false;
- },
-
- _addToSelect2Selection: function(selection) {
- var data = this.$tagsField.select2('data');
- data.push(selection);
- this.$tagsField.select2('data', data);
- },
-
- /**
- * Event handler whenever a tag is selected.
- * Also called whenever tag creation is requested through the dummy tag object.
- *
- * @param {Object} e event
- */
- _onSelectTag: function(e) {
- var self = this;
- var tag;
- if (e.object && e.object.isNew) {
- // newly created tag, check if existing
- // create a new tag
- tag = this.collection.create({
- name: e.object.name.trim(),
- userVisible: true,
- userAssignable: true,
- canAssign: true
- }, {
- success: function(model) {
- self._addToSelect2Selection(model.toJSON());
- self._lastUsedTags.unshift(model.id);
- self.trigger('select', model);
- },
- error: function(model, xhr) {
- if (xhr.status === 409) {
- // re-fetch collection to get the missing tag
- self.collection.reset();
- self.collection.fetch({
- success: function(collection) {
- // find the tag in the collection
- var model = collection.where({
- name: e.object.name.trim(),
- userVisible: true,
- userAssignable: true
- });
- if (model.length) {
- model = model[0];
- // the tag already exists or was already assigned,
- // add it to the list anyway
- self._addToSelect2Selection(model.toJSON());
- self.trigger('select', model);
- }
- }
- });
- }
- }
- });
- this.$tagsField.select2('close');
- e.preventDefault();
- return false;
- } else {
- tag = this.collection.get(e.object.id);
- this._lastUsedTags.unshift(tag.id);
- }
- this._newTag = null;
- this.trigger('select', tag);
- },
-
- /**
- * Event handler whenever a tag gets deselected.
- *
- * @param {Object} e event
- */
- _onDeselectTag: function(e) {
- this.trigger('deselect', e.choice.id);
- },
-
- /**
- * Autocomplete function for dropdown results
- *
- * @param {Object} query select2 query object
- */
- _queryTagsAutocomplete: function(query) {
- var self = this;
- this.collection.fetch({
- success: function(collection) {
- var tagModels = collection.filterByName(query.term.trim());
- if (!self._isAdmin) {
- tagModels = _.filter(tagModels, function(tagModel) {
- return tagModel.get('canAssign');
- });
- }
- query.callback({
- results: _.invoke(tagModels, 'toJSON')
- });
- }
- });
- },
-
- _preventDefault: function(e) {
- e.stopPropagation();
- },
-
- /**
- * Formats a single dropdown result
- *
- * @param {Object} data data to format
- * @return {string} HTML markup
- */
- _formatDropDownResult: function(data) {
- return OC.SystemTags.Templates['result'](_.extend({
- renameTooltip: t('core', 'Rename'),
- allowActions: this._allowActions,
- tagMarkup: this._isAdmin ? OC.SystemTags.getDescriptiveTag(data)[0].innerHTML : null,
- isAdmin: this._isAdmin
- }, data));
- },
-
- /**
- * Formats a single selection item
- *
- * @param {Object} data data to format
- * @return {string} HTML markup
- */
- _formatSelection: function(data) {
- return OC.SystemTags.Templates['selection'](_.extend({
- tagMarkup: this._isAdmin ? OC.SystemTags.getDescriptiveTag(data)[0].innerHTML : null,
- isAdmin: this._isAdmin
- }, data));
- },
-
- /**
- * Create new dummy choice for select2 when the user
- * types an arbitrary string
- *
- * @param {string} term entered term
- * @return {Object} dummy tag
- */
- _createSearchChoice: function(term) {
- term = term.trim();
- if (this.collection.filter(function(entry) {
- return entry.get('name') === term;
- }).length) {
- return;
- }
- if (!this._newTag) {
- this._newTag = {
- id: -1,
- name: term,
- userAssignable: true,
- userVisible: true,
- canAssign: true,
- isNew: true
- };
- } else {
- this._newTag.name = term;
- }
-
- return this._newTag;
- },
-
- _initSelection: function(element, callback) {
- var self = this;
- var ids = $(element).val().split(',');
-
- function modelToSelection(model) {
- var data = model.toJSON();
- if (!self._isAdmin && !data.canAssign) {
- // lock static tags for non-admins
- data.locked = true;
- }
- return data;
- }
-
- function findSelectedObjects(ids) {
- var selectedModels = self.collection.filter(function(model) {
- return ids.indexOf(model.id) >= 0 && (self._isAdmin || model.get('userVisible'));
- });
- return _.map(selectedModels, modelToSelection);
- }
-
- this.collection.fetch({
- success: function() {
- callback(findSelectedObjects(ids));
- }
- });
- },
-
- /**
- * Renders this details view
- */
- render: function() {
- var self = this;
- this.$el.html(this.template());
-
- this.$el.find('[title]').tooltip({placement: 'bottom'});
- this.$tagsField = this.$el.find('[name=tags]');
- this.$tagsField.select2({
- placeholder: t('core', 'Collaborative tags'),
- containerCssClass: 'systemtags-select2-container',
- dropdownCssClass: 'systemtags-select2-dropdown',
- closeOnSelect: false,
- allowClear: false,
- multiple: this._multiple,
- toggleSelect: this._multiple,
- query: _.bind(this._queryTagsAutocomplete, this),
- id: function(tag) {
- return tag.id;
- },
- initSelection: _.bind(this._initSelection, this),
- formatResult: _.bind(this._formatDropDownResult, this),
- formatSelection: _.bind(this._formatSelection, this),
- createSearchChoice: this._allowCreate ? _.bind(this._createSearchChoice, this) : undefined,
- sortResults: function(results) {
- var selectedItems = _.pluck(self.$tagsField.select2('data'), 'id');
- results.sort(function(a, b) {
- var aSelected = selectedItems.indexOf(a.id) >= 0;
- var bSelected = selectedItems.indexOf(b.id) >= 0;
- if (aSelected === bSelected) {
- var aLastUsed = self._lastUsedTags.indexOf(a.id);
- var bLastUsed = self._lastUsedTags.indexOf(b.id);
-
- if (aLastUsed !== bLastUsed) {
- if (bLastUsed === -1) {
- return -1;
- }
- if (aLastUsed === -1) {
- return 1;
- }
- return aLastUsed < bLastUsed ? -1 : 1;
- }
-
- // Both not found
- return OC.Util.naturalSortCompare(a.name, b.name);
- }
- if (aSelected && !bSelected) {
- return -1;
- }
- return 1;
- });
- return results;
- },
- formatNoMatches: function() {
- return t('core', 'No tags found');
- }
- })
- .on('select2-selecting', this._onSelectTag)
- .on('select2-removing', this._onDeselectTag);
-
- var $dropDown = this.$tagsField.select2('dropdown');
- // register events for inside the dropdown
- $dropDown.on('mouseup', '.rename', this._onClickRenameTag);
- $dropDown.on('mouseup', '.delete', this._onClickDeleteTag);
- $dropDown.on('mouseup', '.select2-result-selectable.has-form', this._preventDefault);
- $dropDown.on('submit', '.systemtags-rename-form', this._onSubmitRenameTag);
-
- this.delegateEvents();
- },
-
- remove: function() {
- if (this.$tagsField) {
- this.$tagsField.select2('destroy');
- }
- },
-
- getValues: function() {
- this.$tagsField.select2('val');
- },
-
- setValues: function(values) {
- this.$tagsField.select2('val', values);
- },
-
- setData: function(data) {
- this.$tagsField.select2('data', data);
- }
- });
-
- OC.SystemTags = OC.SystemTags || {};
- OC.SystemTags.SystemTagsInputField = SystemTagsInputField;
-
- })(OC);
|